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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new a20b329fb8 Allow FeatureSQL to understand different standards about
the table of CRS definitions. Supported standards are:
a20b329fb8 is described below
commit a20b329fb80d438e449113acba9200dc8114224f
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Aug 7 14:10:57 2024 +0200
Allow FeatureSQL to understand different standards about the table of CRS
definitions.
Supported standards are:
- Geopackage
- ISO-13249 SQL/MM
- ISO 19125 / OGC Simple feature access part 2
They have basically the same content, but with different table and column
names.
---
.../apache/sis/metadata/sql/privy/SQLBuilder.java | 13 ++
.../org/apache/sis/metadata/sql/TestDatabase.java | 2 +-
.../apache/sis/storage/sql/feature/Analyzer.java | 2 +-
.../apache/sis/storage/sql/feature/Database.java | 93 +++++++----
.../sis/storage/sql/feature/FeatureIterator.java | 5 +-
.../sis/storage/sql/feature/InfoStatements.java | 98 ++++++-----
.../sis/storage/sql/feature/SpatialSchema.java | 180 +++++++++++++++++++++
.../sis/storage/sql/postgis/ExtendedInfo.java | 6 +-
.../apache/sis/storage/sql/postgis/Postgres.java | 15 +-
.../org/apache/sis/storage/sql/SQLStoreTest.java | 2 +-
10 files changed, 327 insertions(+), 89 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLBuilder.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLBuilder.java
index 11fa406bac..50bc4fa242 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLBuilder.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLBuilder.java
@@ -113,6 +113,19 @@ public class SQLBuilder extends Syntax {
return this;
}
+ /**
+ * Appends verbatim a sub-sequence of the given text.
+ *
+ * @param text the text to append verbatim.
+ * @param start starting index of the text append, inclusive.
+ * @param end end index of the text to append, exclusive.
+ * @return this builder, for method call chaining.
+ */
+ public final SQLBuilder append(final String text, final int start, final
int end) {
+ buffer.append(text, start, end);
+ return this;
+ }
+
/**
* Appends the given text verbatim.
* The text should be SQL keywords like {@code "SELECT * FROM"}.
diff --git
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/TestDatabase.java
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/TestDatabase.java
index 31f69a43f8..6568d54fee 100644
---
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/TestDatabase.java
+++
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/TestDatabase.java
@@ -285,7 +285,7 @@ public class TestDatabase implements AutoCloseable {
}
return new TestDatabase(ds, Dialect.POSTGRESQL) {
@Override public void close() throws SQLException {
- final PGSimpleDataSource ds = (PGSimpleDataSource) source;
+ final var ds = (PGSimpleDataSource) source;
try (Connection c = ds.getConnection()) {
try (Statement s = c.createStatement()) {
/*
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java
index 44a534e9b5..a6c61b1c59 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java
@@ -134,7 +134,7 @@ final class Analyzer {
this.metadata = metadata;
this.escape = metadata.getSearchStringEscape();
this.nameFactory = DefaultNameFactory.provider();
- spatialInformation = database.isSpatial() ?
database.createInfoStatements(connection) : null;
+ spatialInformation = database.getSpatialSchema().isPresent() ?
database.createInfoStatements(connection) : null;
}
/**
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 be81eba736..71503d3fd3 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
@@ -25,6 +25,7 @@ import java.util.LinkedHashSet;
import java.util.WeakHashMap;
import java.util.ArrayList;
import java.util.Locale;
+import java.util.Optional;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.logging.LogRecord;
import java.sql.Array;
@@ -79,9 +80,9 @@ import org.apache.sis.util.collection.Cache;
* <h2>Specializations</h2>
* Subclasses may be defined for some database engines. Methods that can be
overridden are:
* <ul>
+ * <li>{@link #getPossibleSpatialSchemas(Map)} for enumerating the
spatial schema conventions that may be used.</li>
* <li>{@link #getMapping(Column)} for adding column types to
recognize.</li>
* <li>{@link #createInfoStatements(Connection)} for more info about
spatial information.</li>
- * <li>{@link #addIgnoredTables(Map)} for specifying more tables
to ignore.</li>
* </ul>
*
* <h2>Multi-threading</h2>
@@ -138,14 +139,17 @@ public class Database<G> extends Syntax {
private Table[] tables;
/**
- * Whether the database contains "GEOMETRY_COLUMNS" and/or
"SPATIAL_REF_SYS" tables.
- * May also be set to {@code true} if some database-specific tables are
found such as
- * {@code "geography_columns"} and {@code "raster_columns"} in PostGIS.
+ * Information about table names and column names used for the spatial
schema, or {@code null}.
+ * This is non-null if the database contains "GEOMETRY_COLUMNS" and/or
"SPATIAL_REF_SYS" tables,
+ * possibly with different name depending on the conventions of the
spatial schema. May also be
+ * non-null if some database-specific tables are found such as {@code
"geography_columns"} and
+ * {@code "raster_columns"} in PostGIS.
+ *
* This field is initialized by {@link #analyze analyze(…)} and shall not
be modified after that point.
*
- * @see #isSpatial()
+ * @see #getSpatialSchema()
*/
- private boolean isSpatial;
+ private SpatialSchema spatialSchema;
/**
* {@code true} if this database contains at least one geometry column.
@@ -164,8 +168,8 @@ public class Database<G> extends Syntax {
private boolean hasRaster;
/**
- * Catalog and schema of the {@value InfoStatements#GEOMETRY_COLUMNS} and
- * {@value InfoStatements#SPATIAL_REF_SYS} tables, or null or empty string
if none.
+ * Catalog and schema of the {@code "GEOMETRY_COLUMNS"} and {@code
"SPATIAL_REF_SYS"} tables,
+ * or null or empty string if none. The actual table names depend on
{@link #spatialSchema}.
*/
String catalogOfSpatialTables, schemaOfSpatialTables;
@@ -201,10 +205,10 @@ public class Database<G> extends Syntax {
/**
* Cache of Coordinate Reference Systems created for a given SRID.
- * SRID are primary keys in the {@value InfoStatements#SPATIAL_REF_SYS}
table.
+ * SRID are primary keys in the {@code "SPATIAL_REF_SYS"} (or equivalent)
table.
* They are not EPSG codes, even if the numerical values are often the
same.
*
- * <p>This mapping depend on the content of {@value
InfoStatements#SPATIAL_REF_SYS} table.
+ * <p>This mapping depends on the content of {@code "SPATIAL_REF_SYS"} (or
equivalent) table.
* For that reason, a distinct cache exists for each database.</p>
*/
final Cache<Integer, CoordinateReferenceSystem> cacheOfCRS;
@@ -342,17 +346,26 @@ public class Database<G> extends Syntax {
* the default case specified by the SQL standard. However, some
databases use lower
* cases instead.
*/
- String tableCRS = InfoStatements.SPATIAL_REF_SYS;
- String tableGeom = InfoStatements.GEOMETRY_COLUMNS;
- if (metadata.storesLowerCaseIdentifiers()) {
- tableCRS = tableCRS .toLowerCase(Locale.US).intern();
- tableGeom = tableGeom.toLowerCase(Locale.US).intern();
+ final var ignoredTables = new HashMap<String,Boolean>(8);
+ for (SpatialSchema schema : getPossibleSpatialSchemas(ignoredTables)) {
+ String tableCRS = schema.crsTable;;
+ String tableGeom = schema.geometryColumns;
+ if (metadata.storesLowerCaseIdentifiers()) {
+ tableCRS = tableCRS .toLowerCase(Locale.US).intern();
+ tableGeom = tableGeom.toLowerCase(Locale.US).intern();
+ } else if (metadata.storesUpperCaseIdentifiers()) {
+ tableCRS = tableCRS .toUpperCase(Locale.US).intern();
+ tableGeom = tableGeom.toUpperCase(Locale.US).intern();
+ }
+ ignoredTables.put(tableCRS, Boolean.TRUE);
+ ignoredTables.put(tableGeom, Boolean.TRUE);
+ if (hasTable(metadata, tableTypes, ignoredTables)) {
+ spatialSchema = schema;
+ break;
+ }
+ ignoredTables.remove(tableCRS);
+ ignoredTables.remove(tableGeom);
}
- final Map<String,Boolean> ignoredTables = new HashMap<>(8);
- ignoredTables.put(tableCRS, Boolean.TRUE);
- ignoredTables.put(tableGeom, Boolean.TRUE);
- addIgnoredTables(ignoredTables);
- isSpatial = hasTable(metadata, tableTypes, ignoredTables);
/*
* Collect the names of all tables specified by user, ignoring the
tables
* used for database internal working (for example by PostGIS).
@@ -434,14 +447,14 @@ public class Database<G> extends Syntax {
*
* @param metadata value of {@code connection.getMetaData()}.
* @param tableTypes value of {@link #getTableTypes(DatabaseMetaData)}.
- * @param tables name of the table to search.
+ * @param tables name of the table to search. Will not be modified.
* @return whether the given table has been found.
*/
private boolean hasTable(final DatabaseMetaData metadata, final String[]
tableTypes, final Map<String,Boolean> tables)
throws SQLException
{
// `SimpleImmutableEntry` used as a way to store a (catalog,schema)
pair of strings.
- final FrequencySortedSet<SimpleImmutableEntry<String,String>> schemas
= new FrequencySortedSet<>(true);
+ final var schemas = new
FrequencySortedSet<SimpleImmutableEntry<String,String>>(true);
int count = 0;
for (final Map.Entry<String,Boolean> entry : tables.entrySet()) {
if (entry.getValue()) {
@@ -527,13 +540,13 @@ public class Database<G> extends Syntax {
}
/**
- * Returns {@code true} if this database is a spatial database.
- * Tables such as "SPATIAL_REF_SYS" are used as sentinel values.
+ * Returns an identification of the table and column naming conventions.
+ * This is absent if the database is not spatial.
*
- * @return whether this database is a spatial database.
+ * @return an identification of the table and column naming conventions.
*/
- public final boolean isSpatial() {
- return isSpatial;
+ public final Optional<SpatialSchema> getSpatialSchema() {
+ return Optional.ofNullable(spatialSchema);
}
/**
@@ -706,16 +719,28 @@ public class Database<G> extends Syntax {
}
/**
- * Adds to the given map a list of tables to ignore when searching for
feature tables.
- * The given map already contains the {@code "SPATIAL_REF_SYS"} and {@code
"GEOMETRY_COLUMNS"}
- * entries when this method is invoked. The default implementation adds
nothing.
+ * Returns the spatial schema conventions that may possibly be supported
by this database.
+ * The default implementation returns all {@link SpatialSchema}
enumeration values.
+ * Subclasses may restrict to a smaller set of possibilities.
+ *
+ * <p>In addition, this method can declare in the supplied map which
tables are used for describing
+ * the spatial schema. The default implementation does nothing because the
entries to add depend on
+ * the {@link SpatialSchema}. For example, if Simple Features conventions
are used, then the tables
+ * are {@code "SPATIAL_REF_SYS"} and {@code "GEOMETRY_COLUMNS"}.
Subclasses can add other entries
+ * if they know in advance that they support only one convention, or that
all the conventions that
+ * they support use the same table names. The table added to the map will
be ignored when searching
+ * for feature tables.</p>
+ *
+ * <p>The values in the map tells whether the table can be used as a
sentinel value for determining
+ * that the {@link SpatialSchema} enumeration value can be accepted.</p>
*
- * <p>Values tells whether the table can be used as a sentinel value for
determining
- * that this database {@linkplain #isSpatial is a spatial database}.</p>
+ * @param tables where to add names of tables that describe the spatial
schema.
+ * @return the spatial schema conventions that may be supported by this
database.
*
- * @param ignoredTables where to add names of tables to ignore.
+ * @see #getSpatialSchema()
*/
- protected void addIgnoredTables(final Map<String,Boolean> ignoredTables) {
+ protected SpatialSchema[] getPossibleSpatialSchemas(Map<String,Boolean>
tables) {
+ return SpatialSchema.values();
}
/**
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
index eb60040003..01b605c8da 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
@@ -85,7 +85,7 @@ final class FeatureIterator implements Spliterator<Feature>,
AutoCloseable {
/**
* A cache of statements for fetching spatial information such as geometry
columns or SRID.
- * This is non-null only if the {@linkplain Database#isSpatial() database
is spatial}.
+ * This is non-null only if the {@linkplain Database#getSpatialSchema()
database is spatial}.
* The same instance is shared by all dependencies of this {@code
FeatureIterator}.
*/
private final InfoStatements spatialInformation;
@@ -118,7 +118,8 @@ final class FeatureIterator implements
Spliterator<Feature>, AutoCloseable {
throws SQLException, InternalDataStoreException
{
adapter = table.adapter(connection);
- spatialInformation = table.database.isSpatial() ?
table.database.createInfoStatements(connection) : null;
+ spatialInformation = table.database.getSpatialSchema().isPresent()
+ ? table.database.createInfoStatements(connection) : null;
String sql = adapter.sql;
if (distinct || filter != null || sort != null || offset > 0 || count
> 0) {
final SQLBuilder builder = new
SQLBuilder(table.database).append(sql);
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 0010191315..45b087f698 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
@@ -74,19 +74,6 @@ import org.opengis.metadata.Identifier;
* @see <a href="https://www.ogc.org/standards/sfs">OGC Simple feature access
— Part 2: SQL option</a>
*/
public class InfoStatements implements Localized, AutoCloseable {
- /**
- * The table containing CRS definitions, as specified by ISO 19125 / OGC
Simple feature access part 2.
- * Note that the standard specifies table names in upper-case letters,
which is also the default case
- * specified by the SQL standard. However, some databases use lower cases
instead. This table name can
- * be used unquoted for letting the database engine converts the case.
- */
- static final String SPATIAL_REF_SYS = "SPATIAL_REF_SYS";
-
- /**
- * The table containing the list of geometry columns, as specified by ISO
19125 / OGC Simple feature access part 2.
- */
- static final String GEOMETRY_COLUMNS = "GEOMETRY_COLUMNS";
-
/**
* Specifies how the geometry type is encoded in the {@code
"GEOMETRY_TYPE"} column.
* The OGC standard defines numeric values, but PostGIS uses textual
values.
@@ -202,38 +189,51 @@ public class InfoStatements implements Localized,
AutoCloseable {
}
/**
- * Appends a statement after {@code "WHERE"} such as {@code ""F_TABLE_NAME
= ?"}.
+ * Appends the name of a geometry column or raster column.
*
- * @param sql the builder where to add the SQL statement.
- * @param prefix the column name prefix: {@code 'F'} for features or
{@code 'R'} for rasters.
- * @param column the column name (e.g. {@code "TABLE_NAME"}.
+ * @param sql the builder where to add the column name.
+ * @param raster whether the statement is for raster table instead of
geometry table.
+ * @param column the column name (i.e., {@code "F_TABLE_NAME"}.
* @return the given SQL builder.
*/
- private static SQLBuilder appendCondition(final SQLBuilder sql, final char
prefix, final String column) {
- return sql.append(prefix).append('_').append(column).append(" = ?");
+ private static SQLBuilder appendColumn(final SQLBuilder sql, final boolean
raster, final String column) {
+ if (raster && column.startsWith("F_")) {
+ return sql.append('R').append(column, 1, column.length());
+ } else {
+ return sql.append(column);
+ }
}
/**
* Prepares the statement for fetching information about all geometry or
raster columns in a specified table.
* This method is for {@link #completeIntrospection(TableReference, Map)}
implementations.
*
- * @param table name of the geometry table. Standard value is
{@code "GEOMETRY_COLUMNS"}.
- * @param prefix column name prefix: {@code 'F'} for features or
{@code 'R'} for rasters.
- * @param column name of the geometry column without prefix.
Standard value is {@code "GEOMETRY_COLUMN"}.
- * @param otherColumn additional columns or {@code null} if none.
Standard value is {@code "GEOMETRY_TYPE"}.
+ * @param table name of the geometry table. Standard value
is {@code "GEOMETRY_COLUMNS"}.
+ * @param raster whether the statement is for raster table
instead of geometry table.
+ * @param geomColNameColumn column of geometry column name, or {@code
null} for the standard value.
+ * @param geomTypeColumn column of geometry type, or {@code null}
for the standard value, or "" for none.
* @return the prepared statement for querying the geometry table.
* @throws SQLException if the statement cannot be created.
*/
- protected final PreparedStatement prepareIntrospectionStatement(final
String table,
- final char prefix, final String column, final String otherColumn)
throws SQLException
+ protected final PreparedStatement prepareIntrospectionStatement(final
String table, final boolean raster,
+ String geomColNameColumn, String geomTypeColumn) throws
SQLException
{
- final SQLBuilder sql = new
SQLBuilder(database).append(SQLBuilder.SELECT)
- .append(prefix).append('_').append(column).append(", SRID ");
- if (otherColumn != null) sql.append(", ").append(otherColumn);
+ final SpatialSchema schema = database.getSpatialSchema().orElseThrow();
+ final var sql = new SQLBuilder(database).append(SQLBuilder.SELECT);
+ if (geomColNameColumn == null) {
+ geomColNameColumn = schema.geomColNameColumn;
+ }
+ appendColumn(sql, raster, geomColNameColumn).append(",
").append(schema.crsIdentifierColumn).append(' ');
+ if (geomTypeColumn == null) {
+ geomTypeColumn = schema.geomTypeColumn;
+ }
+ if (geomTypeColumn != null && !geomTypeColumn.isEmpty()) {
+ sql.append(", ").append(geomTypeColumn);
+ }
appendFrom(sql, table);
- if (database.supportsCatalogs) appendCondition(sql, prefix,
"TABLE_CATALOG").append(" AND ");
- if (database.supportsSchemas) appendCondition(sql, prefix,
"TABLE_SCHEMA" ).append(" AND ");
- appendCondition(sql, prefix, "TABLE_NAME");
+ if (database.supportsCatalogs) appendColumn(sql, raster,
schema.geomCatalogColumn).append("=? AND ");
+ if (database.supportsSchemas) appendColumn(sql, raster,
schema.geomSchemaColumn) .append("=? AND ");
+ appendColumn(sql, raster, schema.geomTableColumn).append("=?");
return connection.prepareStatement(sql.toString());
}
@@ -250,7 +250,8 @@ public class InfoStatements implements Localized,
AutoCloseable {
*/
public void completeIntrospection(final TableReference source, final
Map<String,Column> columns) throws Exception {
if (geometryColumns == null) {
- geometryColumns = prepareIntrospectionStatement(GEOMETRY_COLUMNS,
'F', "GEOMETRY_COLUMN", "GEOMETRY_TYPE");
+ final SpatialSchema schema =
database.getSpatialSchema().orElseThrow();
+ geometryColumns =
prepareIntrospectionStatement(schema.geometryColumns, false, null, null);
}
configureSpatialColumns(geometryColumns, source, columns,
GeometryTypeEncoding.NUMERIC);
}
@@ -261,7 +262,7 @@ public class InfoStatements implements Localized,
AutoCloseable {
* May also be used for non-geometric columns such as rasters, in which
case the
* {@code typeValueKind} argument shall be {@code null}.
*
- * @param columnQuery a statement prepared by {@link
#prepareIntrospectionStatement(String, char, String, String)}.
+ * @param columnQuery a statement prepared by {@link
#prepareIntrospectionStatement(String, boolean, String, String)}.
* @param source the table for which to get all geometry columns.
* @param columns all columns for the specified table. Keys are
column names.
* @param typeValueKind {@code NUMERIC}, {@code TEXTUAL} or {@code null}
if none.
@@ -302,12 +303,13 @@ public class InfoStatements implements Localized,
AutoCloseable {
/**
* Gets a Coordinate Reference System for to given SRID.
* If the given SRID is zero or negative, then this method returns {@code
null}.
- * Otherwise the CRS is decoded from the database {@value
#SPATIAL_REF_SYS} table.
+ * Otherwise the CRS is decoded from the database {@code
"SPATIAL_REF_SYS"} table
+ * or equivalent (depending on the {@link SpatialSchema}).
*
* @param srid the Spatial Reference Identifier (SRID) to resolve as a
CRS object.
* @return the CRS associated to the given SRID, or {@code null} if the
SRID is zero.
* @throws DataStoreContentException if the CRS cannot be fetched.
Possible reasons are:
- * no entry found in the {@value #SPATIAL_REF_SYS} table, or more
than one entry is found,
+ * no entry found in the {@code "SPATIAL_REF_SYS"} table, or more
than one entry is found,
* or a single entry exists but has no WKT definition and its
authority code is unsupported by SIS.
* @throws ParseException if the WKT cannot be parsed.
* @throws SQLException if a SQL error occurred.
@@ -323,7 +325,7 @@ public class InfoStatements implements Localized,
AutoCloseable {
/**
* Invoked when the requested CRS is not in the cache. This method gets
the entry from the
- * {@value #SPATIAL_REF_SYS} table then gets the CRS from its authority
code if possible,
+ * {@link SpatialSchema#refSysTable} then gets the CRS from its authority
code if possible,
* or fallback on the WKT otherwise.
*
* @param srid the Spatial Reference Identifier (SRID) of the CRS to
create from the database content.
@@ -332,10 +334,13 @@ public class InfoStatements implements Localized,
AutoCloseable {
*/
private CoordinateReferenceSystem parseCRS(final int srid) throws
Exception {
if (wktFromSrid == null) {
+ final SpatialSchema schema =
database.getSpatialSchema().orElseThrow();
final SQLBuilder sql = new SQLBuilder(database);
- sql.append("SELECT auth_name, auth_srid, srtext");
- appendFrom(sql, SPATIAL_REF_SYS);
- sql.append("srid=?");
+ sql.append(SQLBuilder.SELECT).append(schema.crsAuthorityNameColumn)
+ .append(", ").append(schema.crsAuthorityCodeColumn)
+ .append(", ").append(schema.crsDefinitionColumn);
+ appendFrom(sql, schema.crsTable);
+ sql.append(schema.crsIdentifierColumn).append("=?");
wktFromSrid = connection.prepareStatement(sql.toString());
}
wktFromSrid.setInt(1, srid);
@@ -393,7 +398,8 @@ public class InfoStatements implements Localized,
AutoCloseable {
if (crs == null) {
crs = v.recommendation;
} else if (!crs.equals(v.recommendation)) {
- throw invalidSRID(Resources.Keys.DuplicatedSRID_2,
SPATIAL_REF_SYS, srid, authorityError);
+ final SpatialSchema schema =
database.getSpatialSchema().orElseThrow();
+ throw invalidSRID(Resources.Keys.DuplicatedSRID_2,
schema.crsTable, srid, authorityError);
}
warning = v.warning(false);
if (warning == null && fromWKT != null) {
@@ -417,7 +423,8 @@ public class InfoStatements implements Localized,
AutoCloseable {
if (authorityError != null) {
throw authorityError;
}
- throw invalidSRID(Resources.Keys.UnknownSRID_2, SPATIAL_REF_SYS,
srid, null);
+ final SpatialSchema schema =
database.getSpatialSchema().orElseThrow();
+ throw invalidSRID(Resources.Keys.UnknownSRID_2, schema.crsTable,
srid, null);
}
if (warning != null) {
warning.setLoggerName(Modules.SQL);
@@ -497,10 +504,13 @@ public class InfoStatements implements Localized,
AutoCloseable {
* Get the WKT and verifies if the CRS is approximately equal.
*/
if (sridFromCRS == null) {
+ final SpatialSchema schema =
database.getSpatialSchema().orElseThrow();
final SQLBuilder sql = new SQLBuilder(database);
- sql.append("SELECT srtext, srid");
- appendFrom(sql, SPATIAL_REF_SYS);
- sql.append("auth_name=? AND auth_srid=?");
+
sql.append(SQLBuilder.SELECT).append(schema.crsDefinitionColumn)
+ .append(",
").append(schema.crsIdentifierColumn);
+ appendFrom(sql, schema.crsTable);
+ sql.append(schema.crsAuthorityNameColumn).append("=? AND ")
+ .append(schema.crsAuthorityCodeColumn).append("=?");
sridFromCRS = connection.prepareStatement(sql.toString());
}
sridFromCRS.setString(1, authority);
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
new file mode 100644
index 0000000000..857530ca9b
--- /dev/null
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SpatialSchema.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.sql.feature;
+
+
+/**
+ * Information about table names and column names used for the spatial schema.
+ * There is many standards, with nearly identical content but different names.
+ *
+ * <ul>
+ * <li>Geopackage</li>
+ * <li>ISO-13249 SQL/MM</li>
+ * <li>ISO 19125 / OGC Simple feature access part 2</li>
+ * </ul>
+ *
+ * The presence of tables for each standard will be tested in enumeration
order.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ */
+public enum SpatialSchema {
+ /**
+ * Table and column names as specified by Geopackage. This is the same
thing as {@link #SQL_MM}
+ * except for table names and for the case (Geopackage uses lower case).
+ */
+ GEOPACKAGE("gpkg_spatial_ref_sys", "srs_id", "organization",
"organization_coordsys_id", "definition",
+ "gpkg_geometry_columns", "table_catalog", "table_schema",
"table_name", "column_name", null),
+
+ /**
+ * Table and column names as specified by ISO-13249 SQL/MM. This is the
same thing as {@link #SIMPLE_FEATURE}
+ * with only different names. The table definition for CRS is:
+ *
+ * {@snippet lang="sql" :
+ * CREATE TABLE ST_SPATIAL_REFERENCE_SYSTEMS(
+ * SRS_NAME CHARACTER VARYING(ST_MaxSRSNameLength) NOT NULL,
+ * SRS_ID INTEGER NOT NULL,
+ * ORGANIZATION CHARACTER VARYING(ST_MaxOrganizationNameLength),
+ * ORGANIZATION_COORDSYS_ID INTEGER,
+ * DEFINITION CHARACTER VARYING(ST_MaxSRSDefinitionLength) NOT NULL,
+ * DESCRIPTION CHARACTER VARYING(ST_MaxDescriptionLength))
+ * }
+ *
+ * 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_ID", "ORGANIZATION",
"ORGANIZATION_COORDSYS_ID", "DEFINITION",
+ "ST_GEOMETRY_COLUMNS", "TABLE_CATALOG", "TABLE_SCHEMA",
"TABLE_NAME", "COLUMN_NAME", null),
+
+ /**
+ * Table and column names as specified by ISO 19125 / OGC Simple feature
access part 2.
+ * Note that the standard specifies table names in upper-case letters,
which is also the default case
+ * specified by the SQL standard. However, some databases use lower cases
instead. This table name can
+ * be used unquoted for letting the database engine converts the case. The
table definition for CRS is:
+ *
+ * {@snippet lang="sql" :
+ * CREATE TABLE SPATIAL_REF_SYS (
+ * SRID INTEGER NOT NULL PRIMARY KEY,
+ * AUTH_NAME CHARACTER VARYING,
+ * AUTH_SRID INTEGER,
+ * SRTEXT CHARACTER VARYING(2048))
+ * }
+ */
+ SIMPLE_FEATURE("SPATIAL_REF_SYS", "SRID", "AUTH_NAME", "AUTH_SRID",
"SRTEXT",
+ "GEOMETRY_COLUMNS", "F_TABLE_CATALOG", "F_TABLE_SCHEMA",
"F_TABLE_NAME", "F_GEOMETRY_COLUMN", "GEOMETRY_TYPE");
+
+ /**
+ * Name of the table for Spatial Reference System definitions.
+ * Example: {@code "SPATIAL_REF_SYS"}, {@code
"ST_SPATIAL_REFERENCE_SYSTEMS"}.
+ */
+ final String crsTable;
+
+ /**
+ * Name of the column for CRS identifiers.
+ * Example: {@code "SRID"}, {@code "SRS_ID"}.
+ * Also used in the geometry columns table.
+ */
+ final String crsIdentifierColumn;
+
+ /**
+ * Name of the column for CRS authority names.
+ * Example: {@code "AUTH_NAME"}, {@code "ORGANIZATION"}.
+ */
+ final String crsAuthorityNameColumn;
+
+ /**
+ * Name of the column for CRS authority codes.
+ * Example: {@code "AUTH_SRID"}, {@code "ORGANIZATION_COORDSYS_ID"}.
+ */
+ final String crsAuthorityCodeColumn;
+
+ /**
+ * Name of the column for CRS definitions in Well-Known Text
(<abbr>WKT</abbr>) format.
+ * Example: {@code "SRTEXT"}, {@code "DEFINITION"}.
+ */
+ final String crsDefinitionColumn;
+
+ /**
+ * Name of the table enumerating the geometry columns.
+ */
+ final String geometryColumns;
+
+ /**
+ * Name of the column where the catalog of each geometry column is stored.
+ * Example: {@code "F_TABLE_CATALOG"}, {@code "TABLE_CATALOG"}.
+ */
+ final String geomCatalogColumn;
+
+ /**
+ * Name of the column where the schema of each geometry column is stored.
+ * Example: {@code "F_TABLE_SCHEMA"}, {@code "TABLE_SCHEMA"}.
+ */
+ final String geomSchemaColumn;
+
+ /**
+ * Name of the column where the table of each geometry column is stored.
+ * Example: {@code "F_TABLE_NAME"}, {@code "TABLE_NAME"}.
+ */
+ final String geomTableColumn;
+
+ /**
+ * Name of the column where the column of each geometry column is stored.
+ * Example: {@code "F_GEOMETRY_COLUMN"}, {@code "COLUMN_NAME"}.
+ */
+ final String geomColNameColumn;
+
+ /**
+ * Name of the column where the type of each geometry column is stored, or
{@code null} if none.
+ * Example: {@code "GEOMETRY_TYPE"}.
+ *
+ * @see InfoStatements.GeometryTypeEncoding
+ */
+ final String geomTypeColumn;
+
+ /**
+ * Creates a new enumeration value.
+ *
+ * @param crsTable name of the table for Spatial Reference
System definitions.
+ * @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 geometryColumns name of the table enumerating the
geometry columns.
+ * @param geomCatalogColumn name of the column where the catalog of
each geometry column is stored.
+ * @param geomSchemaColumn name of the column where the schema of
each geometry column is stored.
+ * @param geomTableColumn name of the column where the table of
each geometry column is stored.
+ * @param geomColNameColumn name of the column where the column of
each geometry column is stored.
+ * @param geomTypeColumn name of the column where the type of
each geometry column is stored, or null if none.
+ */
+ private SpatialSchema(String crsTable, String crsIdentifierColumn, String
crsAuthorityNameColumn,
+ String crsAuthorityCodeColumn, String
crsDefinitionColumn, String geometryColumns,
+ String geomCatalogColumn, String geomSchemaColumn,
String geomTableColumn,
+ String geomColNameColumn, String geomTypeColumn)
+ {
+ this.crsTable = crsTable;
+ this.crsIdentifierColumn = crsIdentifierColumn;
+ this.crsAuthorityNameColumn = crsAuthorityNameColumn;
+ this.crsAuthorityCodeColumn = crsAuthorityCodeColumn;
+ this.crsDefinitionColumn = crsDefinitionColumn;
+ this.geometryColumns = geometryColumns;
+ this.geomCatalogColumn = geomCatalogColumn;
+ this.geomSchemaColumn = geomSchemaColumn;
+ this.geomTableColumn = geomTableColumn;
+ this.geomColNameColumn = geomColNameColumn;
+ this.geomTypeColumn = geomTypeColumn;
+ }
+}
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 2dc0f863e8..843804a7ed 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
@@ -66,13 +66,13 @@ final class ExtendedInfo extends InfoStatements {
@Override
public void completeIntrospection(final TableReference source, final
Map<String,Column> columns) throws Exception {
if (geometryColumns == null) {
- geometryColumns =
prepareIntrospectionStatement("geometry_columns", 'f', "geometry_column",
"type");
+ geometryColumns =
prepareIntrospectionStatement("geometry_columns", false, "f_geometry_column",
"type");
}
if (geographyColumns == null) {
- geographyColumns =
prepareIntrospectionStatement("geography_columns", 'f', "geography_column",
"type");
+ geographyColumns =
prepareIntrospectionStatement("geography_columns", false, "f_geography_column",
"type");
}
if (rasterColumns == null) {
- rasterColumns = prepareIntrospectionStatement("raster_columns",
'r', "raster_column", null);
+ rasterColumns = prepareIntrospectionStatement("raster_columns",
true, "r_raster_column", "");
}
configureSpatialColumns(geometryColumns, source, columns,
GeometryTypeEncoding.TEXTUAL);
configureSpatialColumns(geographyColumns, source, columns,
GeometryTypeEncoding.TEXTUAL);
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/Postgres.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/Postgres.java
index 054f36b253..20a4a43d1a 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/Postgres.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/postgis/Postgres.java
@@ -37,6 +37,7 @@ import org.apache.sis.storage.sql.feature.Database;
import org.apache.sis.storage.sql.feature.ValueGetter;
import org.apache.sis.storage.sql.feature.Resources;
import org.apache.sis.storage.sql.feature.SelectionClauseWriter;
+import org.apache.sis.storage.sql.feature.SpatialSchema;
import org.apache.sis.metadata.sql.privy.Dialect;
import org.apache.sis.storage.event.StoreListeners;
import org.apache.sis.util.Version;
@@ -185,15 +186,23 @@ public final class Postgres<G> extends Database<G> {
}
/**
- * Adds to the given set a list of tables to ignore when searching for
feature tables.
+ * Returns the spatial schema conventions that may possibly be supported
by this database.
+ * The only value expected by PostGIS databases is {@link
SpatialSchema#SIMPLE_FEATURE}.
+ * This method also completes the given map with additional tables
describing the schema.
+ * Those tables shall be ignored when searching for feature tables.
*
- * @param ignoredTables where to add names of tables to ignore.
+ * <p>The values in the map tells whether the table can be used as a
sentinel value for
+ * determining that the {@link SpatialSchema} enumeration value can be
accepted.</p>
+ *
+ * @param tables where to add names of tables that describe the spatial
schema.
+ * @return the spatial schema convention supported by this database.
*/
@Override
- protected void addIgnoredTables(final Map<String,Boolean> ignoredTables) {
+ protected SpatialSchema[] getPossibleSpatialSchemas(final
Map<String,Boolean> ignoredTables) {
ignoredTables.put("geography_columns", Boolean.TRUE); // Postgis 1+
ignoredTables.put("raster_columns", Boolean.TRUE); // Postgis 2
ignoredTables.put("raster_overviews", Boolean.FALSE);
+ return new SpatialSchema[] {SpatialSchema.SIMPLE_FEATURE};
}
/**
diff --git
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
index a797cfe2ce..fcf690c9b6 100644
---
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
+++
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/SQLStoreTest.java
@@ -132,7 +132,7 @@ public final class SQLStoreTest extends TestOnAllDatabases {
*/
@Override
protected void test(final TestDatabase database, final boolean noschema)
throws Exception {
- final var scripts = new ArrayList<>(2);
+ final var scripts = new ArrayList<Object>(2);
if (noschema) {
scripts.add("CREATE SCHEMA " + SCHEMA + ';');
// Omit the "CREATE SCHEMA" statement if the schema already exists.