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;
     }
 

Reply via email to