.. _app-schema.polymorphism:
Polymorphism
============
Polymorphism in this context refers to the ability of an attribute to have different forms.
Depending on the source value, it could be encoded with a specific structure, type, as an xlink:href reference, or not encoded at all.
To achieve this, we reuse feature chaining syntax and allow OCQL functions in the linkElement tag.
Read more about :ref:`app-schema.feature-chaining`, if you're not familiar with the syntax.
Data-type polymorphism
----------------------
You can use normal feature chaining to get an attribute to be encoded as a certain type.
For example::
ex:someAttribute
VALUE_ID
NumericType
FEATURE_LINK
ex:someAttribute
VALUE_ID
gsml:CGI_TermValue
FEATURE_LINK
Note: NumericType here is a mappingName, whereas gsml:CGI_TermValue is a targetElement.
In the above example, ex:someAttribute would be encoded with the configuration in NumericType if the foreign key matches the linkField.
Both instances would be encoded if the foreign key matches the candidate keys in both linked configurations.
Therefore this would only work for 0 to many relationships.
Functions can be used for single attribute instances. See `useful functions`_ for a list of commonly used functions. Specify the function in the linkElement, and it would map it to the first matching FeatureTypeMapping.
For example::
ex:someAttribute
VALUE_ID
Recode(CLASS_TEXT, 'numeric', 'NumericType', 'literal', 'gsml:CGI_TermValue')
FEATURE_LINK
true
The above example means, if the CLASS_TEXT value is 'numeric', it would link to 'NumericType' FeatureTypeMapping, with VALUE_ID as foreign key to the linked type.
It would require all the potential matching types to have a common attribute that is specified in linkField. In this example, the linkField is FEATURE_LINK, which is a fake attribute used only for feature chaining.
You can omit the linkField and OCQL if the FeatureTypeMapping being linked to has the same sourceType with the container type.
This would save us from unnecessary extra queries, which would affect performance.
For example:
FeatureTypeMapping of the container type::
PropertyFiles
PolymorphicFeature
FeatureTypeMapping of NumericType points to the same table::
NumericType
PropertyFiles
PolymorphicFeature
FeatureTypeMapping of gsml:CGI_TermValue also points to the same table::
PropertyFiles
PolymorphicFeature
gsml:CGI_TermValue
In this case, we can omit linkField in the polymorphic attribute mapping::
ex:someAttribute
Recode(CLASS_TEXT, 'numeric', 'NumericType', 'literal', 'gsml:CGI_TermValue')
true
Referential polymorphism
------------------------
This is when an attribute is set to be encoded as an xlink:href reference on the top level.
When the scenario only has reference cases in it, setting a function in Client Property will do the job. E.g.::
ex:someAttribute
xlink:href
if_then_else(isNull(NUMERIC_VALUE), 'urn:ogc:def:nil:OGC:1.0:missing', strConcat('#', NUMERIC_VALUE))
The above example means, if NUMERIC_VALUE is null, the attribute should be encoded as::
Otherwise, it would be encoded as::
where NUMERIC_VALUE = '123'
However, this is not possible when we have cases where a fully structured attribute is also a possibility.
The `toxlinkhref`_ function can be used for this scenario. E.g.::
ex:someAttribute
if_then_else(isNull(NUMERIC_VALUE), toXlinkHref('urn:ogc:def:nil:OGC:1.0:missing'),
if_then_else(lessEqualThan(NUMERIC_VALUE, 1000), 'numeric_value', toXlinkHref('urn:ogc:def:nil:OGC:1.0:missing')))
The above example means, if NUMERIC_VALUE is null, the output would be encoded as::
Otherwise, if NUMERIC_VALUE is less or equal than 1000, it would be encoded with attributes from FeatureTypeMapping with 'numeric_value' mappingName.
If NUMERIC_VALUE is greater than 1000, it would be encoded as the first scenario.
Useful functions
----------------
if_then_else function
`````````````````````
**Syntax**::
if_then_else(BOOLEAN_EXPRESSION, value, default value)
* **BOOLEAN_EXPRESSION**: could be a Boolean column value, or a Boolean function
* **value**: the value to map to, if BOOLEAN_EXPRESSION is true
* **default value**: the value to map to, if BOOLEAN_EXPRESSION is false
Recode function
```````````````
**Syntax**::
Recode(EXPRESSION, key1, value1, key2, value2,...)
* **EXPRESSION**: column name to get values from, or another function
* **key-n**:
* key expression to map to value-n
* if the evaluated value of EXPRESSION doesn't match any key, nothing would be encoded for the attribute.
* **value-n**: value expression which translates to a mappingName or targetElement
lessEqualThan
`````````````
Returns true if ATTRIBUTE_EXPRESSION evaluates to less or equal than LIMIT_EXPRESSION.
**Syntax**::
lessEqualThan(ATTRIBUTE_EXPRESSION, LIMIT_EXPRESSION)
* **ATTRIBUTE_EXPRESSION**: expression of the attribute being evaluated.
* **LIMIT_EXPRESSION**: expression of the numeric value to be compared against.
lessThan
````````
Returns true if ATTRIBUTE_EXPRESSION evaluates to less than LIMIT_EXPRESSION.
**Syntax**::
lessThan(ATTRIBUTE_EXPRESSION, LIMIT_EXPRESSION)
* **ATTRIBUTE_EXPRESSION**: expression of the attribute being evaluated.
* **LIMIT_EXPRESSION**: expression of the numeric value to be compared against.
equalTo
```````
Compares two expressions and returns true if they're equal.
**Syntax**::
equalTo(LHS_EXPRESSION, RHS_EXPRESSION)
isNull
``````
Returns a Boolean that is true if the expression evaluates to null.
**Syntax**::
isNull(EXPRESSION)
* **EXPRESSION**: expression to be evaluated.
toXlinkHref
```````````
Special function written for referential polymorphism and feature chaining, not to be used outside of linkElement.
It infers that the attribute should be encoded as xlink:href.
**Syntax**::
toXlinkHref(XLINK_HREF_EXPRESSION)
* **XLINK_HREF_EXPRESSION**:
* could be a function or a literal
* has to be wrapped in single quotes if it's a literal
.. note::
* To get toXlinkHref function working, you need to declare xlink URI in the namespaces.
Other functions
```````````````
Please refer to :ref:`filter_function_reference`.
Combinations
````````````
You can combine functions, but it might affect performance.
E.g.::
if_then_else(isNull(NUMERIC_VALUE), toXlinkHref('urn:ogc:def:nil:OGC:1.0:missing'),
if_then_else(lessEqualThan(NUMERIC_VALUE, 1000), 'numeric_value', toXlinkHref('urn:ogc:def:nil:OGC:1.0:missing')))
.. note::
* When specifying a mappingName or targetElement as a value in functions, make sure they're enclosed in single quotes.
* Some functions have no null checking, and will fail when they encounter null.
* The workaround for this is to wrap the expression with isNull() function if null is known to exist in the data set.
Null or missing value
---------------------
To skip the attribute for a specific case, you can use Expression.NIL as a value in if_then_else or not include the key in `Recode function`_ .
E.g.::
if_then_else(isNull(VALUE), Expression.NIL, 'gsml:CGI_TermValue')
means the attribute would not be encoded if VALUE is null.
Recode(VALUE, 'term_value', 'gsml:CGI_TermValue')
means the attribute would not be encoded if VALUE is anything but 'term_value'.
To encode an attribute as xlink:href that represents missing value on the top level, see `Referential Polymorphism`_.
Any type
--------
Having xs:anyType as the attribute type itself infers that it is polymorphic, since they can be encoded as any type.
If the type is pre-determined and would always be the same, we might need to specify :ref:`app-schema.mapping-file.targetAttributeNode`.
E.g.::
om:result
gml:MeasureType
TOPAGE
xsi:type
'gml:MeasureType'
uom
'http://www.opengis.net/def/uom/UCUM/0/Ma'
If the casting type is complex, this is not a requirement as app-schema is able to automatically determine the type from the XPath in targetAttribute.
E.g., in this example ``om:result`` is automatically specialised as a MappedFeatureType::
om:result/gsml:MappedFeature/gml:name
NAME
Alternatively, we can use feature chaining. For the same example above, the mapping would be::
om:result
LEX_D
gsml:MappedFeature
gml:name
If the type is conditional, the mapping style for such attributes is the same as any other polymorphic attributes. E.g.::
om:result
Recode(NAME, Expression.Nil, toXlinkHref('urn:ogc:def:nil:OGC::missing'),'numeric',
toXlinkHref(strConcat('urn:numeric-value::', NUMERIC_VALUE)), 'literal', 'TermValue2')
Filters
-------
Filters should work as usual, as long as the users know what they want to filter.
For example, when an attribute could be encoded as gsml:CGI_TermValue or gsml:CGI_NumericValue, users can run filters with property names of:
* ex:someAttribute/gsml:CGI_TermValue/gsml:value to return matching attributes that are encoded as gsml:CGI_TermValue and satisfy the filter.
* likewise, ex:someAttribute/gsml:CGI_NumericValue/gsml:principalValue should return matching gsml:CGI_NumericValue attributes.
Another limitation is filtering attributes of an xlink:href attribute pointing to an instance outside of the document.