This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 0ddd4d225c806b7b513f8eedce26fe7a4f187aa1 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Wed Mar 12 16:19:55 2025 +0100 Make the search for geometry columns robust to the case where there is no information about which catalog and schema contain the table. This case happens with materialized views. --- .../apache/sis/storage/sql/feature/Database.java | 20 ++++++-- .../sis/storage/sql/feature/InfoStatements.java | 33 ++++++++++-- .../sis/storage/sql/feature/SpatialSchema.java | 59 +++++++++++++++++----- .../sis/storage/sql/postgis/ExtendedInfo.java | 1 + 4 files changed, 92 insertions(+), 21 deletions(-) diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java index b09cfed294..daa92f3695 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java @@ -295,6 +295,18 @@ public class Database<G> extends Syntax { transactionLocks = dialect.supportsConcurrency() ? null : locks; } + /** + * Returns the value of a {@code LIKE} statement with wildcard characters escaped. + * The wildcard characters are {@code '_'} and {@code '%'}. This method is invoked + * when an exact match for the given value is desired. + * + * @param value the {@code LIKE} pattern to search. + * @return the given pattern with wildcard characters escaped. + */ + final String escapeWildcards(final String value) { + return SQLUtilities.escape(value, escape); + } + /** * Detects automatically which spatial schema is in use. Detects also the catalog name and schema name. * This method is invoked exactly once after construction and before the analysis of feature tables. @@ -336,7 +348,7 @@ public class Database<G> extends Syntax { String catalog = null, schema = null; for (final Map.Entry<String,Boolean> entry : ignoredTables.entrySet()) { if (entry.getValue()) { - String table = SQLUtilities.escape(entry.getKey(), escape); + String table = escapeWildcards(entry.getKey()); try (ResultSet reflect = metadata.getTables(null, null, table, tableTypes)) { while (reflect.next()) { consistent &= consistent(catalog, catalog = reflect.getString(Reflection.TABLE_CAT)); @@ -363,14 +375,14 @@ public class Database<G> extends Syntax { * The preference order will be defined by the `CRSEncoding` enumeration order. */ if (spatialSchema != null) { - final String schema = SQLUtilities.escape(schemaOfSpatialTables, escape); - final String table = SQLUtilities.escape(crsTable, escape); + final String schema = escapeWildcards(schemaOfSpatialTables); + final String table = escapeWildcards(crsTable); for (Map.Entry<CRSEncoding, String> entry : spatialSchema.crsDefinitionColumn.entrySet()) { String column = entry.getValue(); if (metadata.storesLowerCaseIdentifiers()) { column = column.toLowerCase(Locale.US); } - column = SQLUtilities.escape(column, escape); + column = escapeWildcards(column); try (ResultSet reflect = metadata.getColumns(catalogOfSpatialTables, schema, table, column)) { if (reflect.next()) { crsEncodings.add(entry.getKey()); diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java index 111d0ae99f..440e195d90 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java @@ -203,12 +203,37 @@ public class InfoStatements implements Localized, AutoCloseable { sql.append(", ").append(geomTypeColumn); } appendFrom(sql, table); - if (database.supportsCatalogs) appendColumn(sql, raster, schema.geomCatalogColumn).append("=? AND "); - if (database.supportsSchemas) appendColumn(sql, raster, schema.geomSchemaColumn) .append("=? AND "); + /* + * In principle, all tables should be unambiguously specified with their catalog and schema name. + * However, some JDBC drivers do not provide this information in some circumstances such as materialized views. + * Therefore, we use the `LIKE` operator instead of `=` for making possible to disable the filtering by schema. + */ + if (database.supportsCatalogs) appendColumn(sql, raster, schema.geomCatalogColumn).append(" LIKE ? AND "); + if (database.supportsSchemas) appendColumn(sql, raster, schema.geomSchemaColumn) .append(" LIKE ? AND "); appendColumn(sql, raster, schema.geomTableColumn).append("=?"); return connection.prepareStatement(sql.toString()); } + /** + * Sets the parameter value for a table catalog or schema. Those parameters use the {@code LIKE} statement + * in order to ignore the catalog or schema when it is not specified. A catalog or schema is not specified + * if the string is null or empty. The latter case may happens with some drivers with, for example, + * materialized views. + * + * @param columnQuery the query where to set the parameter. + * @param p index of the parameter to set. + * @param source catalog or schema name to set, or null or empty if unknown. + * @throws SQLException if an error occurred while setting the parameter value. + */ + private void setCatalogOrSchema(final PreparedStatement columnQuery, final int p, String source) throws SQLException { + if (source == null || source.isEmpty()) { + source = "%"; + } else { + source = database.escapeWildcards(source); + } + columnQuery.setString(p, source); + } + /** * Gets all geometry and raster columns for the given table and sets information on the corresponding columns. * Column instances in the {@code columns} map are modified in-place (the map itself is not modified). @@ -251,8 +276,8 @@ public class InfoStatements implements Localized, AutoCloseable { final Map<String,Column> columns, final GeometryTypeEncoding typeValueKind) throws Exception { int p = 0; - if (database.supportsCatalogs) columnQuery.setString(++p, source.catalog); - if (database.supportsSchemas) columnQuery.setString(++p, source.schema); + if (database.supportsCatalogs) setCatalogOrSchema(columnQuery, ++p, source.catalog); + if (database.supportsSchemas) setCatalogOrSchema(columnQuery, ++p, source.schema); columnQuery.setString(++p, source.table); try (ResultSet result = columnQuery.executeQuery()) { while (result.next()) { diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java index 42b62ef88f..712eeb0e36 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java @@ -40,12 +40,22 @@ public enum SpatialSchema { * except for table names, for the case (Geopackage uses lower case) and for the addition of a * {@code geometry_type_name} column. */ - GEOPACKAGE("gpkg_spatial_ref_sys", "srs_name", "srs_id", "organization", "organization_coordsys_id", - Map.of(CRSEncoding.WKT1, "definition", - CRSEncoding.WKT2, "definition_12_063"), - "description", - "gpkg_geometry_columns", "table_catalog", "table_schema", "table_name", - "column_name", "geometry_type_name", GeometryTypeEncoding.TEXTUAL), + GEOPACKAGE( + "gpkg_spatial_ref_sys", // Table for Spatial Reference System definitions. + "srs_name", // Column for CRS names. + "srs_id", // Column for CRS identifiers. + "organization", // Column for CRS authority names. + "organization_coordsys_id", // Column for CRS authority codes. + Map.of(CRSEncoding.WKT1, "definition", // Columns for CRS definitions in WKT format. + CRSEncoding.WKT2, "definition_12_063"), + "description", // Column for the CRS description. + "gpkg_geometry_columns", // Table enumerating the geometry columns. + "table_catalog", // Column where the catalog of each geometry column is stored. + "table_schema", // Column where the schema of each geometry column is stored. + "table_name", // Column where the table of each geometry column is stored. + "column_name", // Column where the column of each geometry column is stored. + "geometry_type_name", // Column where the type of each geometry column is stored. + GeometryTypeEncoding.TEXTUAL), // How geometry types are encoded in the above-cited type column. /** * Table and column names as specified by ISO-13249 SQL/MM. This is the same thing as {@link #SIMPLE_FEATURE} @@ -64,9 +74,20 @@ public enum SpatialSchema { * In Geopackage, this table is named {@code "gpkg_spatial_ref_sys"} but otherwise has identical content * except for the case (Geopackage uses lower case). */ - SQL_MM("ST_SPATIAL_REFERENCE_SYSTEMS", "SRS_NAME", "SRS_ID", "ORGANIZATION", "ORGANIZATION_COORDSYS_ID", - Map.of(CRSEncoding.WKT1, "DEFINITION"), "DESCRIPTION", - "ST_GEOMETRY_COLUMNS", "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "COLUMN_NAME", null, null), + SQL_MM("ST_SPATIAL_REFERENCE_SYSTEMS", // Table for Spatial Reference System definitions. + "SRS_NAME", // Column for CRS names. + "SRS_ID", // Column for CRS identifiers. + "ORGANIZATION", // Column for CRS authority names. + "ORGANIZATION_COORDSYS_ID", // Column for CRS authority codes. + Map.of(CRSEncoding.WKT1, "DEFINITION"), // Columns for CRS definitions in WKT format. + "DESCRIPTION", // Column for the CRS description. + "ST_GEOMETRY_COLUMNS", // Table enumerating the geometry columns. + "TABLE_CATALOG", // Column where the catalog of each geometry column is stored. + "TABLE_SCHEMA", // Column where the schema of each geometry column is stored. + "TABLE_NAME", // Column where the table of each geometry column is stored. + "COLUMN_NAME", // Column where the column of each geometry column is stored. + null, // Column where the type of each geometry column is stored, or null if none. + null), // How geometry types are encoded in the above-cited type column. /** * Table and column names as specified by ISO 19125 / OGC Simple feature access part 2. @@ -82,9 +103,21 @@ public enum SpatialSchema { * SRTEXT CHARACTER VARYING(2048)) * } */ - SIMPLE_FEATURE("SPATIAL_REF_SYS", null, "SRID", "AUTH_NAME", "AUTH_SRID", Map.of(CRSEncoding.WKT1, "SRTEXT"), null, - "GEOMETRY_COLUMNS", "F_TABLE_CATALOG", "F_TABLE_SCHEMA", "F_TABLE_NAME", "F_GEOMETRY_COLUMN", - "GEOMETRY_TYPE", GeometryTypeEncoding.NUMERIC); + SIMPLE_FEATURE( + "SPATIAL_REF_SYS", // Table for Spatial Reference System definitions. + null, // Column for CRS names, or `null` if none. + "SRID", // Column for CRS identifiers. + "AUTH_NAME", // Column for CRS authority names. + "AUTH_SRID", // Column for CRS authority codes. + Map.of(CRSEncoding.WKT1, "SRTEXT"), // Columns for CRS definitions in WKT format. + null, // Column for the CRS description, or `null` if none. + "GEOMETRY_COLUMNS", // Table enumerating the geometry columns. + "F_TABLE_CATALOG", // Column where the catalog of each geometry column is stored. + "F_TABLE_SCHEMA", // Column where the schema of each geometry column is stored. + "F_TABLE_NAME", // Column where the table of each geometry column is stored. + "F_GEOMETRY_COLUMN", // Column where the column of each geometry column is stored. + "GEOMETRY_TYPE", // Column where the type of each geometry column is stored. + GeometryTypeEncoding.NUMERIC); // How geometry types are encoded in the above-cited type column. /** * Name of the table for Spatial Reference System definitions. @@ -177,7 +210,7 @@ public enum SpatialSchema { * @param crsIdentifierColumn name of the column for CRS identifiers. * @param crsAuthorityNameColumn name of the column for CRS authority names. * @param crsAuthorityCodeColumn name of the column for CRS authority codes. - * @param crsDefinitionColumn name of the column for CRS definitions in <abbr>WKT</abbr> format. + * @param crsDefinitionColumn name of the columns for CRS definitions in <abbr>WKT</abbr> format. * @param crsDescriptionColumn name of the column for the CRS description, or {@code null} if none. * @param geometryColumns name of the table enumerating the geometry columns. * @param geomCatalogColumn name of the column where the catalog of each geometry column is stored. diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtendedInfo.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtendedInfo.java index e43555c88e..702a81ab09 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtendedInfo.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/ExtendedInfo.java @@ -94,6 +94,7 @@ final class ExtendedInfo extends InfoStatements { * Closes all prepared statements. This method does <strong>not</strong> close the connection. */ @Override + @SuppressWarnings("ConvertToTryWithResources") public void close() throws SQLException { if (geographyColumns != null) { geographyColumns.close();