fix(automap): fixes naming conflicts for PostgreSQLProvider (#1184)
* fix(automap): fix naming conflicts for PostgreSQLProvider Automaping classes and relationships from database schema throws an error if a column name is the same as a relationship name (see https://docs-sqlalchemy.readthedocs.io/ko/latest/orm/extensions/automap.html#handling-simple-naming-conflicts). * renaming function name_for_scalar_relationship -> _name_for_scalar_relationship * test: test that PostgreSQL provider can handle naming conflicts Re-use data file from https://github.com/geopython/pygeoapi/pull/1185, and make to new and empty tables that create naming conflicts when queried. * Change test function post_collection_items -> get_collection_items * fix path to table
This commit is contained in:
@@ -1,17 +1,17 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.md'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- '!**.md'
|
||||
paths-ignore:
|
||||
- '!**.md'
|
||||
release:
|
||||
types:
|
||||
- released
|
||||
- released
|
||||
|
||||
jobs:
|
||||
flake8_py3:
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
- name: Install sqlite and gpkg dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: libsqlite3-mod-spatialite
|
||||
packages: libsqlite3-mod-spatialite
|
||||
version: 4.3.0a-6build1
|
||||
- name: Install requirements 📦
|
||||
run: |
|
||||
@@ -96,6 +96,7 @@ jobs:
|
||||
python3 tests/load_es_data.py tests/cite/ogcapi-features/canada-hydat-daily-mean-02HC003.geojson IDENTIFIER
|
||||
python3 tests/load_mongo_data.py tests/data/ne_110m_populated_places_simple.geojson
|
||||
gunzip < tests/data/hotosm_bdi_waterways.sql.gz | psql postgresql://postgres:${{ secrets.DatabasePassword || 'postgres' }}@localhost:5432/test
|
||||
psql postgresql://postgres:${{ secrets.DatabasePassword || 'postgres' }}@localhost:5432/test -f tests/data/dummy_data.sql
|
||||
- name: run unit tests ⚙️
|
||||
env:
|
||||
POSTGRESQL_PASSWORD: ${{ secrets.DatabasePassword || 'postgres' }}
|
||||
|
||||
@@ -319,11 +319,31 @@ class PostgreSQLProvider(BaseProvider):
|
||||
raise ProviderQueryError(msg)
|
||||
|
||||
Base = automap_base(metadata=metadata)
|
||||
Base.prepare()
|
||||
Base.prepare(
|
||||
name_for_scalar_relationship=self._name_for_scalar_relationship,
|
||||
)
|
||||
TableModel = getattr(Base.classes, self.table)
|
||||
|
||||
return TableModel
|
||||
|
||||
@staticmethod
|
||||
def _name_for_scalar_relationship(
|
||||
base, local_cls, referred_cls, constraint,
|
||||
):
|
||||
"""Function used when automapping classes and relationships from
|
||||
database schema and fixes potential naming conflicts.
|
||||
"""
|
||||
name = referred_cls.__name__.lower()
|
||||
local_table = local_cls.__table__
|
||||
if name in local_table.columns:
|
||||
newname = name + '_'
|
||||
LOGGER.debug(
|
||||
f'Already detected column name {name!r} in table '
|
||||
f'{local_table!r}. Using {newname!r} for relationship name.'
|
||||
)
|
||||
return newname
|
||||
return name
|
||||
|
||||
def _sqlalchemy_to_feature(self, item):
|
||||
feature = {
|
||||
'type': 'Feature'
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
CREATE SCHEMA IF NOT EXISTS dummy AUTHORIZATION postgres;
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS postgis WITH SCHEMA dummy;
|
||||
|
||||
-- table with multiple geometry columns
|
||||
CREATE TABLE IF NOT EXISTS dummy.buildings(
|
||||
gid serial PRIMARY KEY,
|
||||
centroid geometry(POINT, 25833),
|
||||
contours geometry(POLYGON, 25833)
|
||||
);
|
||||
|
||||
INSERT INTO dummy.buildings(centroid, contours)
|
||||
VALUES (ST_GeomFromText('POINT (473449 7463146)', 25833),
|
||||
ST_GeomFromText('POLYGON ((473447.9967755177 7463140.685534775, 473453.51980463834 7463143.029921546, 473450.0032244818 7463151.314465227, 473444.4801953612 7463148.970078456, 473447.9967755177 7463140.685534775))', 25833)),
|
||||
(ST_GeomFromText('POINT (473458 7463104)', 25833),
|
||||
ST_GeomFromText('POLYGON ((473460.9359104787 7463106.762323238, 473457.1106914547 7463107.931810057, 473455.06408952177 7463101.237676765, 473458.88930854574 7463100.068189946, 473460.9359104787 7463106.762323238))', 25833)),
|
||||
(ST_GeomFromText('POINT (473446 7463144)', 25833),
|
||||
ST_GeomFromText('POLYGON ((473446.09474694915 7463138.853056925, 473450.31999101397 7463146.79958526, 473445.9052530499 7463149.146943075, 473441.6800089851 7463141.20041474, 473446.09474694915 7463138.853056925))', 25833)),
|
||||
(ST_GeomFromText('POINT (473449 7463142)', 25833),
|
||||
ST_GeomFromText('POLYGON ((473452.3381955018 7463138.820935548, 473452.65221123956 7463144.812712757, 473445.6618044963 7463145.179064451, 473445.3477887586 7463139.187287242, 473452.3381955018 7463138.820935548))', 25833)),
|
||||
(ST_GeomFromText('POINT (473443 7463137)', 25833),
|
||||
ST_GeomFromText('POLYGON ((473447.7083111685 7463135.5571535295, 473440.9159249468 7463141.46168479, 473438.2916888306 7463138.44284647, 473445.0840750523 7463132.538315209, 473447.7083111685 7463135.5571535295))', 25833)),
|
||||
(ST_GeomFromText('POINT (473433 7463125)', 25833),
|
||||
ST_GeomFromText('POLYGON ((473432.73905580025 7463120.082489641, 473436.8249702975 7463128.10154836, 473433.2609442007 7463129.917510359, 473429.1750297034 7463121.898451641, 473432.73905580025 7463120.082489641))', 25833)),
|
||||
(ST_GeomFromText('POINT (473451 7463140)', 25833),
|
||||
ST_GeomFromText('POLYGON ((473454.99435667787 7463139.456755368, 473453.4959303038 7463143.165490787, 473447.00564332213 7463140.543244633, 473448.5040696962 7463136.834509214, 473454.99435667787 7463139.456755368))', 25833)),
|
||||
(ST_GeomFromText('POINT (473438 7463144)', 25833),
|
||||
ST_GeomFromText('POLYGON ((473438.99554283824 7463137.7143898895, 473444.28561010957 7463144.995542839, 473437.00445716083 7463150.28561011, 473431.7143898895 7463143.00445716, 473438.99554283824 7463137.7143898895))', 25833)),
|
||||
(ST_GeomFromText('POINT (473474 7463101)', 25833),
|
||||
ST_GeomFromText('POLYGON ((473474.83006438427 7463097.491297516, 473477.55805782415 7463100.416712323, 473473.1699356148 7463104.508702483, 473470.441942174 7463101.583287676, 473474.83006438427 7463097.491297516))', 25833)),
|
||||
-- gid 10
|
||||
(NULL,
|
||||
ST_GeomFromText('POLYGON ((473464.1495667333 7463116.574655892, 473461.1307284124 7463119.1988920085, 473457.85043326765 7463115.425344108, 473460.8692715885 7463112.8011079915, 473464.1495667333 7463116.574655892))', 25833)),
|
||||
-- gid 11
|
||||
(ST_GeomFromText('POINT (473461 7463116)', 25833),
|
||||
NULL),
|
||||
-- gid 12
|
||||
(NULL,
|
||||
NULL);
|
||||
|
||||
/* Two tables which create a naming conflict
|
||||
|
||||
The name of relationship or referred table is the same as the name of an
|
||||
existing column. Example adapted from
|
||||
https://docs-sqlalchemy.readthedocs.io/ko/latest/orm/extensions/automap.html#handling-simple-naming-conflicts
|
||||
*/
|
||||
CREATE TABLE IF NOT EXISTS dummy.referred_table(
|
||||
id INTEGER PRIMARY KEY
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dummy.naming_conflicts_table(
|
||||
id INTEGER PRIMARY KEY,
|
||||
point_geom geometry(POINT, 4326),
|
||||
referred_table INTEGER,
|
||||
FOREIGN KEY(referred_table) REFERENCES dummy.referred_table(id)
|
||||
);
|
||||
@@ -134,4 +134,35 @@ resources:
|
||||
search_path: [osm, public]
|
||||
id_field: osm_id
|
||||
table: hotosm_bdi_waterways
|
||||
geom_field: foo_geom
|
||||
geom_field: foo_geom
|
||||
dummy_naming_conflicts:
|
||||
type: collection
|
||||
title: Dummy data
|
||||
description: Dummy data creating naming conflicts
|
||||
keywords:
|
||||
- dummy
|
||||
links:
|
||||
- type: text/html
|
||||
rel: canonical
|
||||
title: Source instructions for loading data
|
||||
href: https://github.com/geopython/pygeoapi/blob/master/pygeoapi/provider/postgresql.py
|
||||
hreflang: en-UK
|
||||
extents:
|
||||
spatial:
|
||||
bbox: [-180, -90, 180, 90]
|
||||
crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84
|
||||
temporal:
|
||||
begin: null
|
||||
end: null # or empty (either means open ended)
|
||||
providers:
|
||||
- type: feature
|
||||
name: PostgreSQL
|
||||
data:
|
||||
host: localhost
|
||||
dbname: test
|
||||
user: postgres
|
||||
password: postgres
|
||||
search_path: [dummy]
|
||||
id_field: id
|
||||
table: naming_conflicts_table
|
||||
geom_field: point_geom
|
||||
|
||||
@@ -570,3 +570,17 @@ def test_post_collection_items_postgresql_cql_bad_cql(pg_api_, bad_cql):
|
||||
error_response = json.loads(response)
|
||||
assert error_response['code'] == 'InvalidParameterValue'
|
||||
assert error_response['description'].startswith('Bad CQL string')
|
||||
|
||||
|
||||
def test_get_collection_items_postgresql_automap_naming_conflicts(pg_api_):
|
||||
"""
|
||||
Test that PostgreSQLProvider can handle naming conflicts when automapping
|
||||
classes and relationships from database schema.
|
||||
"""
|
||||
req = mock_request()
|
||||
rsp_headers, code, response = pg_api_.get_collection_items(
|
||||
req, 'dummy_naming_conflicts')
|
||||
|
||||
assert code == HTTPStatus.OK
|
||||
features = json.loads(response).get('features')
|
||||
assert len(features) == 0
|
||||
|
||||
Reference in New Issue
Block a user