Skip to content

Commit

Permalink
Added configurable SDO operator
Browse files Browse the repository at this point in the history
  • Loading branch information
Andreas Kosubek committed Nov 29, 2023
1 parent e2a8818 commit fd21e9e
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 37 deletions.
59 changes: 53 additions & 6 deletions docs/source/data-publishing/ogcapi-features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ Oracle
.. note::
Requires Python package oracledb

Connection
""""""""""
.. code-block:: yaml
providers:
Expand All @@ -296,19 +298,64 @@ Oracle
table: lakes
geom_field: geometry
title_field: name
# sql_manipulator: tests.test_oracle_provider.SqlManipulator
# sql_manipulator_options:
# foo: bar
# mandatory_properties:
# - bbox
The provider supports connection over host and port with SID or SERVICE_NAME. For TNS naming, the system
The provider supports connection over host and port with SID, SERVICE_NAME or TNS_NAME. For TNS naming, the system
environment variable TNS_ADMIN or the configuration parameter tns_admin must be set.

The providers supports external authentication. At the moment only wallet authentication is implemented.

Sometimes it is necessary to use the Oracle client for the connection. In this case init_oracle_client must be set to True.

SDO options
"""""""""""
.. code-block:: yaml
providers:
- type: feature
name: OracleDB
data:
host: 127.0.0.1
port: 1521
service_name: XEPDB1
user: geo_test
password: geo_test
id_field: id
table: lakes
geom_field: geometry
title_field: name
sdo_operator: sdo_relate # defaults to sdo_filter
sdo_param: mask=touch+coveredby # defaults to mask=anyinteract
The provider supports two different SDO operators, sdo_filter and sdo_relate. When not set, the default is sdo_relate!
Further more it is possible to set the sdo_param option. When sdo_relate is used the default is anyinteraction!
`See Oracle Documentation for details <https://docs.oracle.com/en/database/oracle/oracle-database/23/spatl/spatial-operators-reference.html>`_.

Mandatory properties
""""""""""""""""""""
.. code-block:: yaml
providers:
- type: feature
name: OracleDB
data:
host: 127.0.0.1
port: 1521
service_name: XEPDB1
user: geo_test
password: geo_test
id_field: id
table: lakes
geom_field: geometry
title_field: name
manadory_properties:
- example_group_id
On large tables it could be useful to disallow a query on the complete dataset. For this reason it is possible to
configure mandatory properties. When this is activated, the provoder throws an exception when the parameter
is not in the query uri.

Custom SQL Manipulator Plugin
"""""""""""""""""""""""""""""
The provider supports a SQL-Manipulator-Plugin class. With this, the SQL statement could be manipulated. This is
useful e.g. for authorization at row level or manipulation of the explain plan with hints.

Expand Down
110 changes: 79 additions & 31 deletions pygeoapi/provider/oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,13 +343,15 @@ def __init__(self, provider_def):
# self.default_crs = get_crs_from_uri(default_crs_uri)

# SDO properties
self.sdo_mask = provider_def.get("sdo_mask", "anyinteraction")
self.sdo_param = provider_def.get("sdo_param")
self.sdo_operator = provider_def.get("sdo_operator", "sdo_filter")

LOGGER.debug("Setting Oracle properties:")
LOGGER.debug(f"Name:{self.name}")
LOGGER.debug(f"ID_field:{self.id_field}")
LOGGER.debug(f"Table:{self.table}")
LOGGER.debug(f"sdo_mask: {self.sdo_mask}")
LOGGER.debug(f"sdo_param: {self.sdo_param}")
LOGGER.debug(f"sdo_operator: {self.sdo_operator}")
LOGGER.debug(f"storage_crs {self.storage_crs}")

# TODO See Issue #1393
Expand All @@ -373,7 +375,12 @@ def get_fields(self):
return self.fields

def _get_where_clauses(
self, properties, bbox, bbox_crs, sdo_mask="anyinteraction"
self,
properties,
bbox,
bbox_crs,
sdo_param=None,
sdo_operator="sdo_filter",
):
"""
Generarates WHERE conditions to be implemented in query.
Expand All @@ -397,33 +404,72 @@ def _get_where_clauses(
if bbox:
bbox_dict = {"clause": "", "properties": {}}

sdo_mask = f"mask={sdo_mask}"

bbox_dict["properties"] = {
"srid": self._get_srid_from_crs(bbox_crs),
"minx": bbox[0],
"miny": bbox[1],
"maxx": bbox[2],
"maxy": bbox[3],
"sdo_mask": sdo_mask,
}
if sdo_operator == "sdo_relate":
if not sdo_param:
sdo_param = "mask=anyinteract"

bbox_dict["properties"] = {
"srid": self._get_srid_from_crs(bbox_crs),
"minx": bbox[0],
"miny": bbox[1],
"maxx": bbox[2],
"maxy": bbox[3],
"sdo_param": sdo_param,
}

bbox_query = f"""
sdo_relate({self.geom},
mdsys.sdo_geometry(2003,
:srid,
NULL,
mdsys.sdo_elem_info_array(
1,
1003,
3
),
mdsys.sdo_ordinate_array(
:minx,
:miny,
:maxx,
:maxy
)
),
:sdo_param
) = 'TRUE'
"""

bbox_dict[
"clause"
] = f"sdo_relate({self.geom}, \
mdsys.sdo_geometry(2003, \
:srid, \
NULL, \
mdsys.sdo_elem_info_array(\
1, \
1003, \
3\
), \
mdsys.sdo_ordinate_array(:minx, \
:miny, \
:maxx, \
:maxy)), \
:sdo_mask) = 'TRUE'"
else:
bbox_dict["properties"] = {
"srid": self._get_srid_from_crs(bbox_crs),
"minx": bbox[0],
"miny": bbox[1],
"maxx": bbox[2],
"maxy": bbox[3],
"sdo_param": sdo_param,
}

bbox_query = f"""
sdo_filter({self.geom},
mdsys.sdo_geometry(2003,
:srid,
NULL,
mdsys.sdo_elem_info_array(
1,
1003,
3
),
mdsys.sdo_ordinate_array(
:minx,
:miny,
:maxx,
:maxy
)
),
:sdo_param
) = 'TRUE'
"""

bbox_dict["clause"] = bbox_query

where_conditions.append(bbox_dict["clause"])
where_dict["properties"].update(bbox_dict["properties"])
Expand Down Expand Up @@ -541,7 +587,8 @@ def query(
properties=properties,
bbox=bbox,
bbox_crs=self.storage_crs,
sdo_mask=self.sdo_mask,
sdo_param=self.sdo_param,
sdo_operator=self.sdo_operator,
)

# Not dangerous to use self.table as substitution,
Expand Down Expand Up @@ -582,7 +629,8 @@ def query(
properties=properties,
bbox=bbox,
bbox_crs=self.storage_crs,
sdo_mask=self.sdo_mask,
sdo_param=self.sdo_param,
sdo_operator=self.sdo_operator,
)

# Get correct SRID
Expand Down

0 comments on commit fd21e9e

Please sign in to comment.