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 <[email protected]>
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;
}