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 dafc1fe309 Make `SQLStore` more extensible, in preparation for Geopackage support. It requires making `SQLStore` abstract and moving the concrete class in a new subclass named `SimpleFeatureStore`. dafc1fe309 is described below commit dafc1fe3091258bf06c6d613d10cd076bb4fdb98 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Fri Aug 23 17:30:36 2024 +0200 Make `SQLStore` more extensible, in preparation for Geopackage support. It requires making `SQLStore` abstract and moving the concrete class in a new subclass named `SimpleFeatureStore`. https://issues.apache.org/jira/browse/SIS-603 --- .../org/apache/sis/storage/sql/DataAccess.java | 69 +++++-- .../apache/sis/storage/sql/ResourceDefinition.java | 47 +++-- .../main/org/apache/sis/storage/sql/SQLStore.java | 210 +++++++++++++++------ .../apache/sis/storage/sql/SQLStoreProvider.java | 8 +- .../apache/sis/storage/sql/SimpleFeatureStore.java | 166 ++++++++++++++++ .../apache/sis/storage/sql/feature/Analyzer.java | 31 ++- .../apache/sis/storage/sql/feature/Database.java | 40 ++-- .../sis/storage/sql/feature/InfoStatements.java | 4 - .../org/apache/sis/storage/sql/package-info.java | 5 +- .../apache/sis/storage/sql/postgis/Postgres.java | 8 +- .../org/apache/sis/storage/sql/DataAccessTest.java | 2 +- .../org/apache/sis/storage/sql/SQLStoreTest.java | 20 +- .../sis/storage/sql/postgis/PostgresTest.java | 3 +- .../geopackage/featureset/GpkgFeatureSet.java | 6 +- 14 files changed, 457 insertions(+), 162 deletions(-) diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/DataAccess.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/DataAccess.java index 727317a4fa..4da9b5c468 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/DataAccess.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/DataAccess.java @@ -34,10 +34,19 @@ import org.apache.sis.util.resources.Errors; /** - * Provides a <abbr>SQL</abbr> connection to the database, together with helper methods. - * Each {@code DataAccess} object can hold a {@link Connection} (created when first needed), - * and sometime a read or write lock. The locks are used only for some databases such as SQLite, - * when concurrency issues are observed during database transactions. + * Low-level accesses to the database content. + * This class provides a <abbr>SQL</abbr> {@link Connection} to the database, + * sometime protected by read or write lock (it may depend on which database driver is used). + * The connection can be used for custom <abbr>SQL</abbr> queries or updates. + * This class also provides helper method for performing queries or updates in the {@code "SPATIAL_REF_SYS"} table + * (the table name may vary depending on the spatial schema used by the database). + * + * <h2>Usage</h2> + * {@code DataAccess} instances are created by calls to {@link SQLStore#newDataAccess(boolean)}. + * The Boolean argument tells whether the caller may perform write operations. That flag determines + * not only the {@linkplain Connection#setReadOnly(boolean) read-only state} of the connection, + * but also whether to acquire a {@linkplain ReadWriteLock#readLock() read lock} + * or a {@linkplain ReadWriteLock#writeLock() write lock} if locking is needed. * * <p>This object shall be used in a {@code try ... finally} block for ensuring that the connection * is closed and the lock (if any) released. Note that the <abbr>SQL</abbr> connection does not need @@ -54,6 +63,7 @@ import org.apache.sis.util.resources.Errors; * } * } * + * <h2>Multi-threading</h2> * This class is not thread safe. Each instance should be used by a single thread. * * @author Martin Desruisseaux (Geomatys) @@ -64,19 +74,21 @@ public class DataAccess implements AutoCloseable { /** * The data store for which this object is providing data access. * The value of this field is specified at construction time. + * + * @see #getDataStore() */ protected final SQLStore store; /** * The SQL connection, created when first needed. */ - private Connection connection; + Connection connection; /** * Helper methods for fetching information such as coordinate reference systems. * Created when first needed. */ - private InfoStatements statements; + InfoStatements spatialInformation; /** * A read or write lock to unlock when the data access will be closed, or {@code null} if none. @@ -111,6 +123,15 @@ public class DataAccess implements AutoCloseable { this.write = write; } + /** + * Returns the SQL store for which this object is providing low-level access. + * + * @return the SQL store that provided this data access object. + */ + public SQLStore getDataStore() { + return store; + } + /** * Returns the error message for the exception to throw when the connection is closed. */ @@ -139,6 +160,19 @@ public class DataAccess implements AutoCloseable { lock = c; // Store only if the lock succeed. } connection = store.getDataSource().getConnection(); + /* + * Setting the connection in read-only mode is needed for allowing `findSRID(CRS)` + * to detect that it should not try to add new row in the "SPATIAL_REF_SYS" table, + * and that is should throw an exception with a "CRS not found" message instead. + * + * TODO: should be unconditional if we could remove the need for `supportsReadOnlyUpdate()`. + * It can be done if we provide our own JDBC driver for SQLite using Panama instead of the + * driver from Xerial. It would avoid embedding C/C++ code for ~20 platforms. + */ + final Database<?> model = store.modelOrNull(); + if (model != null && model.dialect.supportsReadOnlyUpdate()) { + connection.setReadOnly(!write); + } } return connection; } @@ -147,19 +181,19 @@ public class DataAccess implements AutoCloseable { * Returns the helper object for fetching information from {@code SPATIAL_REF_SYS} table. * The helper object is created the first time that this method is invoked. */ - private InfoStatements statements() throws Exception { - if (statements == null) { + private InfoStatements spatialInformation() throws Exception { + if (spatialInformation == null) { final Connection c = getConnection(); synchronized (store) { final Database<?> model = store.model(c); if (model.dialect.supportsReadOnlyUpdate()) { - // TODO: should be in `getConnection() if we could remove the need for `supportsReadOnlyUpdate()`. + // Workaround for the "TODO" in `getConnection()`. Should be removed after "TODO" is resolved. c.setReadOnly(!write); } - statements = model.createInfoStatements(c); + spatialInformation = model.createInfoStatements(c); } } - return statements; + return spatialInformation; } /** @@ -197,7 +231,7 @@ public class DataAccess implements AutoCloseable { Database<?> database = store.modelOrNull(); CoordinateReferenceSystem crs; if (database == null || (crs = database.getCachedCRS(srid)) == null) try { - crs = statements().fetchCRS(srid); + crs = spatialInformation().fetchCRS(srid); } catch (DataStoreContentException e) { throw new NoSuchDataException(e.getMessage(), e.getCause()); } catch (DataStoreException e) { @@ -212,8 +246,9 @@ public class DataAccess implements AutoCloseable { * Returns the <abbr>SRID</abbr> associated to the given spatial reference system. * This method is the converse of {@link #findCRS(int)}. * - * <p>If the {@code write} argument given at construction time was {@code true}, then this method is allowed - * to add a new row in the {@code "SPATIAL_REF_SYS"} table if the given <abbr>CRS</abbr> is not found.</p> + * <h4>Potential write operation</h4> + * If the {@code write} argument given at construction time was {@code true}, then this method is allowed + * to add a new row in the {@code "SPATIAL_REF_SYS"} table if the given <abbr>CRS</abbr> is not found. * * @param crs the CRS for which to find a SRID, or {@code null}. * @return SRID for the given <abbr>CRS</abbr>, or 0 if the given <abbr>CRS</abbr> was null. @@ -233,7 +268,7 @@ public class DataAccess implements AutoCloseable { } final int srid; try { - srid = statements().findSRID(crs); + srid = spatialInformation().findSRID(crs); } catch (DataStoreException e) { throw e; } catch (Exception e) { @@ -252,9 +287,9 @@ public class DataAccess implements AutoCloseable { isClosed = true; // Set first in case an exception is thrown. try { try { - final InfoStatements c = statements; + final InfoStatements c = spatialInformation; if (c != null) { - statements = null; + spatialInformation = null; c.close(); } } finally { diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/ResourceDefinition.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/ResourceDefinition.java index 7a95a43dcf..dc7f198952 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/ResourceDefinition.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/ResourceDefinition.java @@ -22,19 +22,28 @@ import java.util.Optional; import org.opengis.util.NameSpace; import org.opengis.util.NameFactory; import org.opengis.util.GenericName; +import org.apache.sis.storage.FeatureSet; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.iso.DefaultNameFactory; import static org.apache.sis.storage.sql.feature.Database.WILDCARD; /** - * Definition of a resource (table, view or query) to include in a {@link SQLStore}. + * Definition of a resource (table, view or query) to include in a {@code SQLStore}. + * Each {@code ResourceDefinition} instance can specify a table or a group of tables + * (based on name pattern) to view as {@link FeatureSet} instances. + * A {@code ResourceDefinition} instance can also specify a query instead of a table. + * + * <p>{@code ResourceDefinition}s are given to the {@link SimpleFeatureStore} constructor, + * which implies that the tables to use are known in advance (e.g., hard-coded). + * If this is not the case, then the {@code ResourceDefinition}s can be provided + * later by overriding {@link SQLStore#readResourceDefinitions(DataAccess)} instead.</p> * * @author Martin Desruisseaux (Geomatys) - * @version 1.4 + * @version 1.5 * @since 1.1 */ -public final class ResourceDefinition { +public class ResourceDefinition { /** * The namespace for table names, created when first needed. * Used for specifying the name separator, which is {@code '.'}. @@ -42,7 +51,7 @@ public final class ResourceDefinition { private static volatile NameSpace tableNS; /** - * The table name or the query name. + * The name pattern of a table or a view, or an arbitrary query name. * This field has two meanings, depending on whether {@link #query} is null or not: * * <ul> @@ -65,10 +74,21 @@ public final class ResourceDefinition { final String query; /** - * Creates a new definition. + * Creates a new description of a resource in a SQL database. + * If the {@code query} argument is null, then the {@code name} argument + * shall be the name pattern of a table or a view. + * + * @param name table, view or query name pattern. May contain {@code LIKE} wildcard characters. + * @return SQL query to execute for the resource, or {@code null} if the resource is a table or view. + * + * @see #table(String) + * @see #table(String, String, String) + * @see #query(String, String) + * + * @since 1.5 */ - private ResourceDefinition(final GenericName name, final String query) { - this.name = name; + protected ResourceDefinition(final GenericName name, final String query) { + this.name = Objects.requireNonNull(name); this.query = query; } @@ -187,9 +207,8 @@ public final class ResourceDefinition { } /** - * Returns the name of the table, view or query to access as a resource. - * There is small differences in the way it is used depending on whether - * the resource is a table or a query: + * Returns the name pattern of the table, view or query to access as a resource. + * There is small differences in the way it is used depending on whether the resource is a table or a query: * * <ul> * <li>If the resource is a table or a view, then this is the fully qualified name (including catalog and schema) @@ -199,7 +218,7 @@ public final class ResourceDefinition { * the query result.</li> * </ul> * - * @return the name of the table, view or query. + * @return the table or view name pattern, or an arbitrary query name. */ public GenericName getName() { return name; @@ -225,8 +244,8 @@ public final class ResourceDefinition { if (this == obj) { return true; } - if (obj instanceof ResourceDefinition) { - final ResourceDefinition other = (ResourceDefinition) obj; + if (obj != null && obj.getClass() == getClass()) { + final var other = (ResourceDefinition) obj; return name.equals(other.name) && Objects.equals(query, other.query); } return false; @@ -249,7 +268,7 @@ public final class ResourceDefinition { */ @Override public String toString() { - final StringBuilder b = new StringBuilder("Resource[\"").append(name).append('"'); + final var b = new StringBuilder(getClass().getSimpleName()).append("[\"").append(name).append('"'); if (query != null) { b.append(" = ").append(query); } diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java index a6619fc28e..c9454b512d 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStore.java @@ -16,13 +16,12 @@ */ package org.apache.sis.storage.sql; -import java.util.Map; -import java.util.LinkedHashMap; import java.util.Optional; import java.util.Collection; import java.util.concurrent.locks.ReadWriteLock; import javax.sql.DataSource; import java.sql.Connection; +import java.sql.DatabaseMetaData; import java.lang.reflect.Method; import org.opengis.util.GenericName; import org.opengis.metadata.Metadata; @@ -30,7 +29,6 @@ import org.opengis.parameter.ParameterValueGroup; import org.opengis.metadata.spatial.SpatialRepresentationType; import org.apache.sis.storage.Resource; import org.apache.sis.storage.Aggregate; -import org.apache.sis.storage.FeatureSet; import org.apache.sis.storage.DataStore; import org.apache.sis.storage.DataStoreProvider; import org.apache.sis.storage.DataStoreException; @@ -42,6 +40,7 @@ import org.apache.sis.storage.event.WarningEvent; import org.apache.sis.storage.sql.feature.Database; import org.apache.sis.storage.sql.feature.Resources; import org.apache.sis.storage.sql.feature.SchemaModifier; +import org.apache.sis.storage.sql.feature.InfoStatements; import org.apache.sis.storage.base.MetadataBuilder; import org.apache.sis.io.stream.InternalOptionKey; import org.apache.sis.util.ArgumentChecks; @@ -53,17 +52,23 @@ import org.apache.sis.setup.OptionKey; /** - * A data store capable to read and create features from a spatial database. + * An abstract data store for reading or writing resources from/to a spatial database. * {@code SQLStore} requires a {@link DataSource} to be specified (indirectly) at construction time. - * The {@code DataSource} should provide pooled connections, because {@code SQLStore} will frequently - * opens and closes them. + * While not mandatory, a pooled data source is recommended because {@code SQLStore} will frequently + * opens and closes connections. + * + * <p>This class provides basic support for ISO 19125-2, also known as + * <a href="https://www.ogc.org/standards/sfs">OGC Simple feature access - Part 2: SQL option</a>: + * selected tables, views and queries can be viewed as {@link org.apache.sis.storage.FeatureSet} resources. + * This selection is specified by implementing the {@link #readResourceDefinitions(DataAccess)} method. + * The mapping from table structures to feature types is described in the package Javadoc.</p> * * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) * @version 1.5 * @since 1.0 */ -public class SQLStore extends DataStore implements Aggregate { +public abstract class SQLStore extends DataStore implements Aggregate { /** * Names of possible public getter methods for data source title, in preference order. */ @@ -77,10 +82,13 @@ public class SQLStore extends DataStore implements Aggregate { /** * The data source to use for obtaining connections to the database. + * This is the storage given (indirectly, through the {@link StorageConnector} argument) at construction time. * * @see #getDataSource() + * + * @since 1.5 */ - private final DataSource source; + protected final DataSource source; /** * The library to use for creating geometric objects, or {@code null} for system default. @@ -93,6 +101,7 @@ public class SQLStore extends DataStore implements Aggregate { * * @see #model() * @see #model(Connection) + * @see #refresh() */ private volatile Database<?> model; @@ -109,14 +118,22 @@ public class SQLStore extends DataStore implements Aggregate { * The pattern can use {@code '_'} and {@code '%'} wildcards characters.</li> * </ul> * - * Only the main tables need to be specified; dependencies will be followed automatically. + * Only the main tables need to be specified, dependencies will be followed automatically. + * If {@code null}, then the array will be created when first needed. + * + * @see #setModelSources(ResourceDefinition[]) + * @see #tableNames() */ - private final GenericName[] tableNames; + private GenericName[] tableNames; /** * Queries to expose as resources, or an empty array if none. + * If {@code null}, then the array will be created when first needed. + * + * @see #setModelSources(ResourceDefinition[]) + * @see #queries() */ - private final ResourceDefinition[] queries; + private ResourceDefinition[] queries; /** * The metadata, created when first requested. @@ -146,33 +163,37 @@ public class SQLStore extends DataStore implements Aggregate { final ReadWriteLock transactionLocks; /** - * Creates a new {@code SQLStore} for the given data source and tables, views or queries. - * The given {@code connector} shall contain a {@link DataSource} instance. - * Tables or views to include in the store are specified by the {@code resources} argument. - * Only the main tables need to be specified; dependencies will be followed automatically. + * Creates a new {@code SQLStore} for the given data source. The given {@code connector} shall contain + * a {@link DataSource} instance. Tables or views to include in the store will be specified by the + * {@link #readResourceDefinitions(DataAccess)} method, which will be invoked when first needed. * * @param provider the factory that created this {@code DataStore} instance, or {@code null} if unspecified. * @param connector information about the storage (JDBC data source, <i>etc</i>). - * @param resources tables, views or queries to include in this store. * @throws DataStoreException if an error occurred while creating the data store for the given storage. * * @since 1.5 */ - public SQLStore(final DataStoreProvider provider, final StorageConnector connector, final ResourceDefinition... resources) - throws DataStoreException - { + protected SQLStore(final DataStoreProvider provider, final StorageConnector connector) throws DataStoreException { super(provider, connector); - ArgumentChecks.ensureNonEmpty("resources", resources); - source = connector.getStorageAs(DataSource.class); - geomLibrary = connector.getOption(OptionKey.GEOMETRY_LIBRARY); - customizer = connector.getOption(SchemaModifier.OPTION); + source = connector.getStorageAs(DataSource.class); + geomLibrary = connector.getOption(OptionKey.GEOMETRY_LIBRARY); + customizer = connector.getOption(SchemaModifier.OPTION); + transactionLocks = connector.getOption(InternalOptionKey.LOCKS); + } + /** + * Declares the tables or queries to use as the sources of feature resources. + * + * @param resources tables, views or queries to include in this store. + * @throws DataStoreException if an error occurred while processing the given resources. + */ + final void setModelSources(final ResourceDefinition[] resources) throws DataStoreException { @SuppressWarnings("LocalVariableHidesMemberVariable") - final GenericName[] tableNames = new GenericName[resources.length]; + final var tableNames = new GenericName[resources.length]; int tableCount = 0; @SuppressWarnings("LocalVariableHidesMemberVariable") - final ResourceDefinition[] queries = new ResourceDefinition[resources.length]; + final var queries = new ResourceDefinition[resources.length]; int queryCount = 0; for (int i=0; i<resources.length; i++) { @@ -189,24 +210,22 @@ public class SQLStore extends DataStore implements Aggregate { queries[queryCount++] = resource; } } - this.tableNames = ArraysExt.resize(tableNames, tableCount); - this.queries = ArraysExt.resize(queries, queryCount); - transactionLocks = connector.getOption(InternalOptionKey.LOCKS); + this.tableNames = ArraysExt.resize(tableNames, tableCount); + this.queries = ArraysExt.resize(queries, queryCount); } /** - * The data source to use for obtaining connections to the database. - * This is the data source specified at construction time. + * Returns the data source to use for obtaining connections to the database. * * @return the data source to use for obtaining connections to the database. * @since 1.5 */ - public final DataSource getDataSource() { + public DataSource getDataSource() { return source; } /** - * Returns the parameters used to open this netCDF data store. + * Returns parameters that can be used for opening this SQL data store. * The parameters are described by {@link SQLStoreProvider#getOpenParameters()} and contains * at least a parameter named {@value SQLStoreProvider#LOCATION} with a {@link DataSource} value. * @@ -219,29 +238,52 @@ public class SQLStore extends DataStore implements Aggregate { } final ParameterValueGroup pg = provider.getOpenParameters().createValue(); pg.parameter(SQLStoreProvider.LOCATION).setValue(source); - if (tableNames != null) { - pg.parameter(SQLStoreProvider.TABLES).setValue(tableNames); - } - if (queries != null) { - final Map<GenericName,String> m = new LinkedHashMap<>(); - for (final ResourceDefinition query : queries) { - m.put(query.getName(), query.query); - } - pg.parameter(SQLStoreProvider.QUERIES).setValue(m); - } + /* + * Do not include `tableNames` and `queries` because they are initially null + * and determined dynamically when first needed. Because potentially dynamic, + * their values cannot be in parameters. + */ return Optional.of(pg); } /** - * SQL data store root resource has no identifier. + * Returns an identifier for the root resource of this SQL store, or an empty value if none. + * The default implementation returns an empty name because the root resource of a SQL store has no identifier. * - * @return empty. + * @return an identifier for the root resource of this SQL store. */ @Override public Optional<GenericName> getIdentifier() throws DataStoreException { return Optional.empty(); } + /** + * Returns the fully qualified names of the tables to include in this store. + * The returned array shall be considered read-only. + */ + @SuppressWarnings("ReturnOfCollectionOrArrayField") + final GenericName[] tableNames() { + return tableNames; + } + + /** + * Returns the queries to expose as resources. + * The returned array shall be considered read-only. + */ + @SuppressWarnings("ReturnOfCollectionOrArrayField") + final ResourceDefinition[] queries() { + return queries; + } + + /** + * Clears the cached model. The model will be reloaded when first needed. + * This method shall be invoked in a block synchronized on {@code this}. + */ + final void clearModel() { + model = null; + metadata = null; + } + /** * Returns the database model if it already exists, or {@code null} otherwise. * This method is thread-safe. @@ -254,7 +296,7 @@ public class SQLStore extends DataStore implements Aggregate { * Returns the database model, analyzing the database schema when first needed. * This method is thread-safe. */ - private Database<?> model() throws DataStoreException { + final Database<?> model() throws DataStoreException { Database<?> current = model; if (current == null) { synchronized (this) { @@ -281,7 +323,18 @@ public class SQLStore extends DataStore implements Aggregate { assert Thread.holdsLock(this); Database<?> current = model; if (current == null) { - current = Database.create(this, source, c, geomLibrary, tableNames, queries, customizer, listeners, transactionLocks); + final DatabaseMetaData md = c.getMetaData(); + current = Database.create(source, md, geomLibrary, listeners, transactionLocks); + try (final InfoStatements spatialInformation = current.createInfoStatements(c)) { + if (tableNames == null) { + final DataAccess dao = newDataAccess(false); + dao.connection = c; + dao.spatialInformation = spatialInformation; + setModelSources(readResourceDefinitions(dao)); + // Do not close the DAO, because we still use the connection and the info statements. + } + current.analyze(this, md, tableNames, queries, customizer, spatialInformation); + } model = current; } return current; @@ -333,22 +386,36 @@ public class SQLStore extends DataStore implements Aggregate { } /** - * Returns the tables (feature sets) in this SQL store. - * The list contains only the tables explicitly named at construction time. + * Returns the resources (feature set or coverages) in this SQL store. + * The collection of resources should be constructed only when first needed and cached + * for future invocations of this method. The cache can be cleared by {@link #refresh()}. + * This method should not load immediately the whole data of the {@code Resource} elements, + * as {@link Resource} sub-interfaces provide the <abbr>API</abbr> for deferred loading. + * + * <h4>Default implementation</h4> + * By default, the collection contains one {@link org.apache.sis.storage.FeatureSet} per table, view or + * query matching a {@link ResourceDefinition} returned by {@link #readResourceDefinitions(DataAccess)}. * * @return children resources that are components of this SQL store. * @throws DataStoreException if an error occurred while fetching the components. */ @Override - public Collection<Resource> components() throws DataStoreException { + public Collection<? extends Resource> components() throws DataStoreException { return model().tables(); } /** * Searches for a resource identified by the given identifier. - * The given identifier should match one of the table names. - * It may be one of the tables named at construction time, or one of the dependencies. - * The given name may be qualified with the schema name, or may be only the table name if there is no ambiguity. + * This method shall recognize at least the {@linkplain Resource#getIdentifier() identifiers} of the + * resources returned by {@link #components()}, but may also (optionally) recognize the identifiers + * of auxiliary resources such as component dependencies (e.g., tables referenced by foreigner keys). + * + * <h4>Default implementation</h4> + * By default, this method searches for a table, view or query with a name matching the given identifier. + * The scope of the search includes the tables, views or queries matching a {@link ResourceDefinition}, + * together with other tables referenced by foreigner keys (the dependencies). + * The given identifier may be qualified with the schema name, + * or may be only the table name if there is no ambiguity. * * @param identifier identifier of the resource to fetch. Must be non-null. * @return resource associated to the given identifier (never {@code null}). @@ -356,14 +423,35 @@ public class SQLStore extends DataStore implements Aggregate { * @throws DataStoreException if another kind of error occurred while searching resources. */ @Override - public FeatureSet findResource(final String identifier) throws DataStoreException { + public Resource findResource(final String identifier) throws DataStoreException { return model().findTable(this, identifier); } /** - * Creates a new data access object. The returned object can give a <abbr>SQL</abbr> {@link Connection} - * to the database and provider methods for fetching or adding <abbr>CRS</abbr> definitions from/into - * the {@code SPATIAL_REF_SYS} table. + * A callback for providing the resource definitions of a database, typically from a content table. + * {@code SQLStore} will invoke this method when first needed after construction or after calls to + * {@link #refresh()}. Implementations can use the <abbr>SQL</abbr> connection and methods provided + * by the {@code dao} argument. This method does not need to cache the result. + * + * <div class="note"><b>Example:</b> in a database conform to the Geopackage standard, + * the resource definitions are provided by the {@code "gpkg_contents"} table. + * Therefore, the {@link org.apache.sis.storage.geopackage.GpkgStore} subclass + * will read the content of that table every times this method is invoked.</div> + * + * @param dao low-level access (such as <abbr>SQL</abbr> connection) to the database. + * @return tables or views to include in the store. Only the main tables need to be specified. + * Dependencies (inferred from the foreigner keys) will be followed automatically. + * @throws DataStoreException if an error occurred while fetching the resource definitions. + * + * @since 1.5 + */ + protected abstract ResourceDefinition[] readResourceDefinitions(DataAccess dao) throws DataStoreException; + + /** + * Creates a new low-level data access object. Each {@code DataAccess} instance can provide a single + * <abbr>SQL</abbr> {@link Connection} to the database (sometime protected by a read or write lock), + * together with methods for fetching or adding <abbr>CRS</abbr> definitions from/into the + * {@code SPATIAL_REF_SYS} table. * * <p>The returned object shall be used in a {@code try ... finally} block. * This is needed not only for closing the connection, but also for releasing read or write lock. @@ -377,7 +465,7 @@ public class SQLStore extends DataStore implements Aggregate { * } * } * - * @param write whether write operations may be requested. + * @param write whether write operations may be performed. * @return an object provider low-level access (e.g. through <abbr>SQL</abbr> queries) to the data. * * @since 1.5 @@ -407,8 +495,9 @@ public class SQLStore extends DataStore implements Aggregate { * @since 1.5 */ public synchronized void refresh() { - model = null; - metadata = null; + clearModel(); + queries = null; + tableNames = null; } /** @@ -420,7 +509,6 @@ public class SQLStore extends DataStore implements Aggregate { public synchronized void close() throws DataStoreException { listeners.close(); // Should never fail. // There is no JDBC connection to close here. - model = null; - metadata = null; + clearModel(); } } diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStoreProvider.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStoreProvider.java index 762618ee2c..556acfe88f 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStoreProvider.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SQLStoreProvider.java @@ -92,14 +92,14 @@ public class SQLStoreProvider extends DataStoreProvider { /** * Description of the parameter providing the list of tables or views to include as resources in the - * {@link SQLStore}. At least one of {@code TABLES_PARAM} or {@link #QUERIES_PARAM} must be provided. + * {@link SimpleFeatureStore}. At least one of {@code TABLES_PARAM} or {@link #QUERIES_PARAM} must be provided. * * @since 1.1 */ public static final ParameterDescriptor<GenericName[]> TABLES_PARAM; /** - * Description of the parameter providing the queries to include as resources in the {@link SQLStore}. + * Description of the parameter providing the queries to include as resources in the {@link SimpleFeatureStore}. * Map keys are the resource names as {@link GenericName} or {@link String} instances. * Values are SQL statements (as {@link String} instances) to execute when the associated resource is requested. * At least one of {@link #TABLES_PARAM} or {@code QUERIES_PARAM} must be provided. @@ -212,7 +212,7 @@ public class SQLStoreProvider extends DataStoreProvider { */ @Override public DataStore open(final StorageConnector connector) throws DataStoreException { - return new SQLStore(this, connector, ResourceDefinition.table(WILDCARD)); + return new SimpleFeatureStore(this, connector, ResourceDefinition.table(WILDCARD)); } /** @@ -229,7 +229,7 @@ public class SQLStoreProvider extends DataStoreProvider { final StorageConnector connector = new StorageConnector(p.getValue(SOURCE_PARAM)); final GenericName[] tableNames = p.getValue(TABLES_PARAM); final Map<?,?> queries = p.getValue(QUERIES_PARAM); - return new SQLStore(this, connector, ResourceDefinition.wrap(tableNames, queries)); + return new SimpleFeatureStore(this, connector, ResourceDefinition.wrap(tableNames, queries)); } catch (ParameterNotFoundException | UnconvertibleObjectException e) { throw new IllegalOpenParameterException(e.getMessage(), e); } diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SimpleFeatureStore.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SimpleFeatureStore.java new file mode 100644 index 0000000000..512d85f495 --- /dev/null +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/SimpleFeatureStore.java @@ -0,0 +1,166 @@ +/* + * 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; + +import java.util.Optional; +import java.util.Collection; +import java.util.LinkedHashMap; +import javax.sql.DataSource; +import org.opengis.util.GenericName; +import org.opengis.parameter.ParameterValueGroup; +import org.apache.sis.storage.StorageConnector; +import org.apache.sis.storage.DataStoreProvider; +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.storage.FeatureSet; +import org.apache.sis.util.ArgumentChecks; + + +/** + * A concrete data store capable to read and write features from/to a spatial <abbr>SQL</abbr> database. + * All resources created by this class are {@link FeatureSet}s. + * The data are specified by two main arguments given at construction time: + * + * <ul> + * <li>A {@link DataSource} (specified indirectly) providing connections to the database. While not mandatory, + * a pooled data source is recommended because {@code SimpleFeatureStore} will frequently opens and closes + * connections.</li> + * <li>A list of tables, views or queries to view as {@link FeatureSet}s. This list is provided by + * {@link ResourceDefinition} objects. Only the main tables need to be specified. Dependencies + * inferred by foreigner keys will be followed automatically.</li> + * </ul> + * + * The mapping from table structures to feature types is described in the package Javadoc. + * + * @author Johann Sorel (Geomatys) + * @author Martin Desruisseaux (Geomatys) + * @version 1.5 + * + * @see <a href="https://www.ogc.org/standards/sfs">OGC Simple feature access - Part 2: SQL option</a> + * + * @since 1.5 + */ +public class SimpleFeatureStore extends SQLStore { + /** + * Creates a new {@code SimpleFeatureStore} for the given data source and tables, views or queries. + * The given {@code connector} shall contain a {@link DataSource} instance. + * Tables or views to include in the store are specified by the {@code resources} argument. + * Only the main tables need to be specified, as dependencies will be followed automatically. + * + * @param provider the factory that created this {@code DataStore} instance, or {@code null} if unspecified. + * @param connector information about the storage (JDBC data source, <i>etc</i>). + * @param resources tables, views or queries to include in this store. + * @throws DataStoreException if an error occurred while creating the data store for the given storage. + */ + @SuppressWarnings("this-escape") // `setModelSources(…)` is final and part of initialization. + public SimpleFeatureStore(final DataStoreProvider provider, final StorageConnector connector, final ResourceDefinition... resources) + throws DataStoreException + { + super(provider, connector); + ArgumentChecks.ensureNonEmpty("resources", resources); + setModelSources(resources); + } + + /** + * Returns parameters that can be used for opening this Simple Features data store. + * The parameters are described by {@link SQLStoreProvider#getOpenParameters()} and + * can contain some or all of the following: + * + * <ul> + * <li>A parameter named {@value SQLStoreProvider#LOCATION} with a {@link DataSource} value.</li> + * <li>{@link SQLStoreProvider#TABLES_PARAM} with {@link ResourceDefinition}s specified at construction time for tables.</li> + * <li>{@link SQLStoreProvider#QUERIES_PARAM} with {@link ResourceDefinition}s specified at construction time for queries.</li> + * </ul> + * + * @return parameters used for opening this data store. + */ + @Override + public Optional<ParameterValueGroup> getOpenParameters() { + final Optional<ParameterValueGroup> opg = super.getOpenParameters(); + opg.ifPresent((pg) -> { + final GenericName[] tableNames = tableNames(); + if (tableNames.length != 0) { + pg.parameter(SQLStoreProvider.TABLES).setValue(tableNames); + } + final ResourceDefinition[] queries = queries(); + if (queries.length != 0) { + final var m = new LinkedHashMap<GenericName,String>(); + for (final ResourceDefinition query : queries) { + m.put(query.getName(), query.query); + } + pg.parameter(SQLStoreProvider.QUERIES).setValue(m); + } + }); + return opg; + } + + /** + * Returns the tables (feature sets) in this SQL store. + * The collection contains only the tables matching a {@link ResourceDefinition} given at construction time. + * + * @return children resources that are components of this SQL store. + * @throws DataStoreException if an error occurred while fetching the components. + */ + @Override + public Collection<FeatureSet> components() throws DataStoreException { + return model().tables(); + } + + /** + * Searches for a resource identified by the given identifier. + * The given identifier shall match the name of one of the tables, views or queries inferred at construction time. + * It may be a table named explicitly at construction time, or a dependency inferred by following foreigner keys. + * The given identifier may be qualified with the schema name, or may be only the table name if there is no ambiguity. + * + * @param identifier identifier of the resource to fetch. Must be non-null. + * @return resource associated to the given identifier (never {@code null}). + * @throws IllegalNameException if no resource is found for the given identifier, or if more than one resource is found. + * @throws DataStoreException if another kind of error occurred while searching resources. + */ + @Override + public FeatureSet findResource(final String identifier) throws DataStoreException { + return model().findTable(this, identifier); + } + + /** + * Returns the resource definitions equivalent to the ones specified at construction time. + * This method is defined for completness, but is not used by {@code SimpleFeatureStore}. + * + * @param dao ignored. + */ + @Override + protected ResourceDefinition[] readResourceDefinitions(final DataAccess dao) { + final GenericName[] tableNames = tableNames(); + final ResourceDefinition[] queries = queries(); + final var definitions = new ResourceDefinition[tableNames.length + queries.length]; + for (int i=0; i<tableNames.length; i++) { + definitions[i] = new ResourceDefinition(tableNames[i], null); + } + System.arraycopy(queries, 0, definitions, tableNames.length, queries.length); + return definitions; + } + + /** + * Clears the cache so that next operations will recreate the list + * of tables from the patterns specified at construction time. + * + * @hidden + */ + @Override + public synchronized void refresh() { + clearModel(); + } +} 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 a6c61b1c59..6cbb945461 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 @@ -24,7 +24,6 @@ import java.util.Collection; import java.util.Objects; import java.util.logging.Level; import java.sql.SQLException; -import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import org.opengis.util.NameFactory; @@ -118,23 +117,23 @@ final class Analyzer { /** * Creates a new analyzer for the database described by given metadata. * - * @param database information about the spatial database. - * @param connection an existing connection to the database, used only for the lifetime of this {@code Analyzer}. - * @param metadata value of {@code connection.getMetaData()} (provided because already known by caller). - * @param customizer user-specified modification to the features, or {@code null} if none. + * @param database information about the spatial database. + * @param metadata value of {@code connection.getMetaData()} (provided because already known by caller). + * @param customizer user-specified modification to the features, or {@code null} if none. + * @param spatialInformation statements for fetching SRID, geometry types, <i>etc.</i> */ - Analyzer(final Database<?> database, final Connection connection, final DatabaseMetaData metadata, - final SchemaModifier customizer) throws SQLException + Analyzer(final Database<?> database, final DatabaseMetaData metadata, final SchemaModifier customizer, + final InfoStatements spatialInformation) throws SQLException { - this.database = database; - this.tables = new HashMap<>(); - this.strings = new HashMap<>(); - this.warnings = new LinkedHashSet<>(); - this.customizer = customizer; - this.metadata = metadata; - this.escape = metadata.getSearchStringEscape(); - this.nameFactory = DefaultNameFactory.provider(); - spatialInformation = database.getSpatialSchema().isPresent() ? database.createInfoStatements(connection) : null; + this.database = database; + this.tables = new HashMap<>(); + this.strings = new HashMap<>(); + this.warnings = new LinkedHashSet<>(); + this.customizer = customizer; + this.metadata = metadata; + this.escape = metadata.getSearchStringEscape(); + this.nameFactory = DefaultNameFactory.provider(); + this.spatialInformation = database.getSpatialSchema().isPresent() ? spatialInformation : 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 6e62677f7f..d25a292c62 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 @@ -47,7 +47,6 @@ import org.apache.sis.metadata.sql.privy.Dialect; import org.apache.sis.metadata.sql.privy.Reflection; import org.apache.sis.metadata.sql.privy.SQLBuilder; import org.apache.sis.metadata.sql.privy.SQLUtilities; -import org.apache.sis.storage.Resource; import org.apache.sis.storage.FeatureSet; import org.apache.sis.storage.FeatureNaming; import org.apache.sis.storage.DataStoreException; @@ -295,34 +294,28 @@ public class Database<G> extends Syntax { /** * Creates a new handler for a spatial database. + * Callers shall invoke {@link #analyze analyze(…)} after this method. * - * @param store the data store for which we are creating a model. Used only in case of error. * @param source provider of (pooled) connections to the database. - * @param connection connection to the database. Sometimes the caller already has a connection at hand. + * @param metadata value of {@code connection.getMetaData()} (provided because already known by caller). * @param geomLibrary the factory to use for creating geometric objects. - * @param tableNames qualified name of the tables. Specified by users at construction time. - * @param queries additional resources associated to SQL queries. Specified by users at construction time. - * @param customizer user-specified modification to the features, or {@code null} if none. * @param listeners where to send warnings. * @param locks the read/write locks, or {@code null} if none. * @return handler for the spatial database. * @throws SQLException if a database error occurred while reading metadata. * @throws DataStoreException if a logical error occurred while analyzing the database structure. */ - public static Database<?> create(final SQLStore store, final DataSource source, final Connection connection, - final GeometryLibrary geomLibrary, final GenericName[] tableNames, final ResourceDefinition[] queries, - final SchemaModifier customizer, final StoreListeners listeners, final ReadWriteLock locks) + public static Database<?> create(final DataSource source, final DatabaseMetaData metadata, + final GeometryLibrary geomLibrary, final StoreListeners listeners, final ReadWriteLock locks) throws Exception { - final DatabaseMetaData metadata = connection.getMetaData(); final Geometries<?> g = Geometries.factory(geomLibrary); final Dialect dialect = Dialect.guess(metadata); final Database<?> db; switch (dialect) { - case POSTGRESQL: db = new Postgres<>(source, connection, metadata, dialect, g, listeners, locks); break; - default: db = new Database<>(source, metadata, dialect, g, listeners, locks); break; + case POSTGRESQL: db = new Postgres<>(source, metadata, dialect, g, listeners, locks); break; + default: db = new Database<>(source, metadata, dialect, g, listeners, locks); break; } - db.analyze(store, connection, tableNames, queries, customizer); return db; } @@ -343,18 +336,19 @@ public class Database<G> extends Syntax { * The pattern can use {@code '_'} and {@code '%'} wildcards characters.</li> * </ul> * - * @param store the data store for which we are creating a model. Used only in case of error. - * @param connection connection to the database. Sometimes the caller already has a connection at hand. - * @param tableNames qualified name of the tables. Specified by users at construction time. - * @param queries additional resources associated to SQL queries. Specified by users at construction time. - * @param customizer user-specified modification to the features, or {@code null} if none. + * @param store the data store for which we are creating a model. Used only in case of error. + * @param metadata value of {@code connection.getMetaData()} (provided because already known by caller). + * @param tableNames qualified name of the tables. Specified by users at construction time. + * @param queries additional resources associated to SQL queries. Specified by users at construction time. + * @param customizer user-specified modification to the features, or {@code null} if none. + * @param spatialInformation statements for fetching SRID, geometry types, <i>etc.</i> * @throws SQLException if a database error occurred while reading metadata. * @throws DataStoreException if a logical error occurred while analyzing the database structure. */ - private void analyze(final SQLStore store, final Connection connection, final GenericName[] tableNames, - final ResourceDefinition[] queries, final SchemaModifier customizer) throws Exception + public final void analyze(final SQLStore store, final DatabaseMetaData metadata, final GenericName[] tableNames, + final ResourceDefinition[] queries, final SchemaModifier customizer, + final InfoStatements spatialInformation) throws Exception { - final DatabaseMetaData metadata = connection.getMetaData(); final String[] tableTypes = getTableTypes(metadata); /* * The following tables are defined by ISO 19125 / OGC Simple feature access part 2. @@ -414,7 +408,7 @@ public class Database<G> extends Syntax { * Some schemas have additional columns for optional encodings, for example a separated column for WKT2. * The preference order will be defined by the `CRSEncoding` enumeration order. */ - final Analyzer analyzer = new Analyzer(this, connection, metadata, customizer); + final Analyzer analyzer = new Analyzer(this, metadata, customizer, spatialInformation); if (spatialSchema != null) { final String schema = SQLUtilities.escape(schemaOfSpatialTables, analyzer.escape); final String table = SQLUtilities.escape(crsTable, analyzer.escape); @@ -535,7 +529,7 @@ public class Database<G> extends Syntax { * * @return all tables in an unmodifiable list. */ - public final List<Resource> tables() { + public final List<FeatureSet> tables() { return UnmodifiableArrayList.wrap(tables); } 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 8df07b0a93..72c72efd3e 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 @@ -284,8 +284,6 @@ public class InfoStatements implements Localized, AutoCloseable { * 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. - * - * @see org.apache.sis.storage.sql.SQLStore#findCRS(int) */ public final CoordinateReferenceSystem fetchCRS(final int srid) throws Exception { /* @@ -472,8 +470,6 @@ public class InfoStatements implements Localized, AutoCloseable { * @param crs the CRS for which to find a SRID, or {@code null}. * @return SRID for the given CRS, or 0 if the given CRS was null. * @throws Exception if an SQL error, parsing error or other error occurred. - * - * @see org.apache.sis.storage.sql.SQLStore#findSRID(CoordinateReferenceSystem) */ public final int findSRID(final CoordinateReferenceSystem crs) throws Exception { if (crs == null) { diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java index 49ced53cff..1b38c39225 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/package-info.java @@ -17,8 +17,8 @@ /** - * Data store capable to read and create features from a JDBC connection to a database. - * {@link org.apache.sis.storage.sql.SQLStore} takes a one or more tables at construction time. + * Data store capable to read and write features using a JDBC connection to a database. + * {@link org.apache.sis.storage.sql.SimpleFeatureStore} takes one or more tables at construction time. * Each enumerated table is represented by a {@link org.opengis.feature.FeatureType}. * Each row in those table represents a {@link org.opengis.feature.Feature} instance. * Each relation defined by a foreigner key is represented by an {@link org.opengis.feature.FeatureAssociationRole} @@ -48,7 +48,6 @@ * <ul> * <li>Current implementation does not scan the {@code "GEOMETRY_COLUMNS"} (from Simple Feature Access) * or {@code "gpkg_content"} (from GeoPackage) tables for a default list of feature tables.</li> - * <li>Current implementation does not yet map geometric objects (e.g. PostGIS types).</li> * <li>If a parent feature contains association to other features, those other features are created * at the same time as the parent feature (no lazy instantiation yet).</li> * </ul> 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 d8ad170df1..586c47c274 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 @@ -68,7 +68,6 @@ public final class Postgres<G> extends Database<G> { * Creates a new session for a PostGIS database. * * @param source provider of (pooled) connections to the database. - * @param connection the connection to the database. Should be considered as read-only. * @param metadata metadata about the database for which a session is created. * @param dialect additional information not provided by {@code metadata}. * @param geomLibrary the factory to use for creating geometric objects. @@ -76,14 +75,13 @@ public final class Postgres<G> extends Database<G> { * @param locks the read/write locks, or {@code null} if none. * @throws SQLException if an error occurred while reading database metadata. */ - public Postgres(final DataSource source, final Connection connection, final DatabaseMetaData metadata, - final Dialect dialect, final Geometries<G> geomLibrary, final StoreListeners listeners, - final ReadWriteLock locks) + public Postgres(final DataSource source, final DatabaseMetaData metadata, final Dialect dialect, + final Geometries<G> geomLibrary, final StoreListeners listeners, final ReadWriteLock locks) throws SQLException { super(source, metadata, dialect, geomLibrary, listeners, locks); Version version = null; - try (Statement st = connection.createStatement(); + try (Statement st = metadata.getConnection().createStatement(); ResultSet result = st.executeQuery("SELECT public.PostGIS_version();")) { while (result.next()) { diff --git a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/DataAccessTest.java b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/DataAccessTest.java index 991fd55514..c7613f18b5 100644 --- a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/DataAccessTest.java +++ b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/DataAccessTest.java @@ -60,7 +60,7 @@ public final class DataAccessTest extends TestCase { */ private void test(final TestDatabase database) throws Exception { database.executeSQL(List.of(InfoStatementsTest.createSpatialRefSys())); - try (SQLStore store = new SQLStore(null, new StorageConnector(database.source), ResourceDefinition.table("%")); + try (SQLStore store = new SimpleFeatureStore(null, new StorageConnector(database.source), ResourceDefinition.table("%")); DataAccess dao = store.newDataAccess(true)) { assertEquals(4326, dao.findSRID(HardCodedCRS.WGS84)); 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 fcf690c9b6..6b38bac882 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 @@ -165,7 +165,7 @@ public final class SQLStoreTest extends TestOnAllDatabases { * Creates a {@link SQLStore} instance with the specified table as a resource, then tests some queries. */ private void testTableQuery(final StorageConnector connector, final ResourceDefinition table) throws Exception { - try (SQLStore store = new SQLStore(new SQLStoreProvider(), connector, table)) { + try (SimpleFeatureStore store = new SimpleFeatureStore(new SQLStoreProvider(), connector, table)) { verifyFeatureTypes(store); final Map<String,Integer> countryCount = new HashMap<>(); try (Stream<Feature> features = store.findResource("Cities").features(false)) { @@ -195,7 +195,7 @@ public final class SQLStoreTest extends TestOnAllDatabases { * @param isCyclicAssociationAllowed whether dependencies are allowed to have an association * to their dependent feature, which create a cyclic dependency. */ - private void verifyFeatureTypes(final SQLStore store) throws DataStoreException { + private void verifyFeatureTypes(final SimpleFeatureStore store) throws DataStoreException { verifyFeatureType(store.findResource("Cities").getType(), new String[] {"sis:identifier", "pk:country", "country", "native_name", "english_name", "population", "parks"}, new Object[] {null, String.class, "Countries", String.class, String.class, Integer.class, "Parks"}); @@ -328,7 +328,7 @@ public final class SQLStoreTest extends TestOnAllDatabases { * @param dataset the store on which to query the features. * @throws DataStoreException if an error occurred during query execution. */ - private void verifySimpleQuerySorting(final SQLStore dataset) throws DataStoreException { + private void verifySimpleQuerySorting(final SimpleFeatureStore dataset) throws DataStoreException { /* * Property that we are going to request and expected result. * Note that "english_name" below is a property of the "Park" feature, @@ -367,7 +367,7 @@ public final class SQLStoreTest extends TestOnAllDatabases { * @param dataset the store on which to query the features. * @throws DataStoreException if an error occurred during query execution. */ - private void verifySimpleQueryWithLimit(final SQLStore dataset) throws DataStoreException { + private void verifySimpleQueryWithLimit(final SimpleFeatureStore dataset) throws DataStoreException { final FeatureSet parks = dataset.findResource("Parks"); final FeatureQuery query = new FeatureQuery(); query.setLimit(2); @@ -382,7 +382,7 @@ public final class SQLStoreTest extends TestOnAllDatabases { * @param dataset the store on which to query the features. * @throws DataStoreException if an error occurred during query execution. */ - private void verifySimpleWhere(SQLStore dataset) throws Exception { + private void verifySimpleWhere(SimpleFeatureStore dataset) throws Exception { /* * Property that we are going to request and expected result. */ @@ -412,7 +412,7 @@ public final class SQLStoreTest extends TestOnAllDatabases { * @param dataset the store on which to query the features. * @throws DataStoreException if an error occurred during query execution. */ - private void verifyWhereOnLink(SQLStore dataset) throws Exception { + private void verifyWhereOnLink(SimpleFeatureStore dataset) throws Exception { final String desiredProperty = "native_name"; final String[] expectedValues = {"Canada"}; final FeatureSet countries = dataset.findResource("Countries"); @@ -474,7 +474,7 @@ public final class SQLStoreTest extends TestOnAllDatabases { * Tests fetching the content of the Cities table, but using a user supplied SQL query. */ private void verifyFetchCityTableAsQuery(final StorageConnector connector) throws Exception { - try (SQLStore store = new SQLStore(null, connector, ResourceDefinition.query("LargeCities", + try (SimpleFeatureStore store = new SimpleFeatureStore(null, connector, ResourceDefinition.query("LargeCities", "SELECT * FROM " + SCHEMA + ".\"Cities\" WHERE \"population\" >= 1000000"))) { final FeatureSet cities = store.findResource("LargeCities"); @@ -494,7 +494,7 @@ public final class SQLStoreTest extends TestOnAllDatabases { * Tests a user supplied query followed by another query built from filters. */ private void verifyNestedSQLQuery(final StorageConnector connector) throws Exception { - try (SQLStore store = new SQLStore(null, connector, ResourceDefinition.query("MyParks", + try (SimpleFeatureStore store = new SimpleFeatureStore(null, connector, ResourceDefinition.query("MyParks", "SELECT * FROM " + SCHEMA + ".\"Parks\""))) { final FeatureSet parks = store.findResource("MyParks"); @@ -529,7 +529,7 @@ public final class SQLStoreTest extends TestOnAllDatabases { * but stack on it (i.e. the feature set provide user defined result, and the stream navigate through it). */ private void verifyLimitOffsetAndColumnSelectionFromQuery(final StorageConnector connector) throws Exception { - try (SQLStore store = new SQLStore(null, connector, ResourceDefinition.query("MyQuery", + try (SimpleFeatureStore store = new SimpleFeatureStore(null, connector, ResourceDefinition.query("MyQuery", "SELECT \"english_name\" AS \"title\" " + "FROM " + SCHEMA + ".\"Parks\"\n" + // Test that multiline text is accepted. "ORDER BY \"english_name\" ASC " + @@ -572,7 +572,7 @@ public final class SQLStoreTest extends TestOnAllDatabases { */ private void verifyDistinctQuery(final StorageConnector connector) throws Exception { final Object[] expected; - try (SQLStore store = new SQLStore(null, connector, ResourceDefinition.query("Countries", + try (SimpleFeatureStore store = new SimpleFeatureStore(null, connector, ResourceDefinition.query("Countries", "SELECT \"country\" FROM " + SCHEMA + ".\"Parks\" ORDER BY \"country\""))) { final FeatureSet countries = store.findResource("Countries"); diff --git a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java index c9f3d458a2..05418638e6 100644 --- a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java +++ b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/PostgresTest.java @@ -39,6 +39,7 @@ import org.apache.sis.storage.FeatureSet; import org.apache.sis.storage.StorageConnector; import org.apache.sis.storage.sql.SQLStore; import org.apache.sis.storage.sql.SQLStoreProvider; +import org.apache.sis.storage.sql.SimpleFeatureStore; import org.apache.sis.storage.sql.ResourceDefinition; import org.apache.sis.coverage.grid.GridCoverage; import org.apache.sis.io.stream.ChannelDataInput; @@ -111,7 +112,7 @@ public final class PostgresTest extends TestCase { final StorageConnector connector = new StorageConnector(database.source); connector.setOption(OptionKey.GEOMETRY_LIBRARY, GeometryLibrary.JTS); final ResourceDefinition table = ResourceDefinition.table(null, SQLStoreTest.SCHEMA, "SpatialData"); - try (SQLStore store = new SQLStore(new SQLStoreProvider(), connector, table)) { + try (SimpleFeatureStore store = new SimpleFeatureStore(new SQLStoreProvider(), connector, table)) { /* * Invoke the private `model()` method. We have to use reflection because the class * is not in the same package and we do not want to expose the method in public API. diff --git a/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/featureset/GpkgFeatureSet.java b/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/featureset/GpkgFeatureSet.java index 0fb1a94a49..77d8ed261d 100644 --- a/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/featureset/GpkgFeatureSet.java +++ b/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/featureset/GpkgFeatureSet.java @@ -29,7 +29,7 @@ import org.apache.sis.storage.geopackage.GpkgContentResource; import org.apache.sis.storage.geopackage.GpkgStore; import org.apache.sis.storage.geopackage.privy.Record; import org.apache.sis.storage.sql.ResourceDefinition; -import org.apache.sis.storage.sql.SQLStore; +import org.apache.sis.storage.sql.SimpleFeatureStore; import org.apache.sis.storage.sql.SQLStoreProvider; import org.apache.sis.util.iso.Names; import org.opengis.feature.Feature; @@ -88,8 +88,8 @@ final class GpkgFeatureSet extends AbstractResource implements FeatureSet, GpkgC final StorageConnector connector = new StorageConnector(store.getDataSource()); final ResourceDefinition table = ResourceDefinition.table(null, null, row.tableName); - final SQLStore sqlStore = new SQLStore(new SQLStoreProvider(), connector, table); - sqlSet = (FeatureSet) sqlStore.components().iterator().next(); + final var sqlStore = new SimpleFeatureStore(new SQLStoreProvider(), connector, table); + sqlSet = sqlStore.components().iterator().next(); return sqlSet; }