sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Rename some sis-sql internal classes. Leverage functionalities available in other SIS modules. Reduce the number of fields in Database class.
Date Thu, 05 Jul 2018 14:55:58 GMT
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 748e998  Rename some sis-sql internal classes. Leverage functionalities available in other SIS modules. Reduce the number of fields in Database class.
748e998 is described below

commit 748e998b9f42e75aa929dfbea8b21f6d43e546f9
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu Jul 5 16:55:14 2018 +0200

    Rename some sis-sql internal classes. Leverage functionalities available in other SIS modules. Reduce the number of fields in Database class.
---
 .../apache/sis/internal/metadata/sql/Dialect.java  |   3 +-
 .../sis/internal/metadata/sql/SQLUtilities.java    |   5 +-
 .../referencing/factory/sql/EPSGDataAccess.java    |   2 +-
 .../feature/{ColumnMetaModel.java => Column.java}  |  10 +-
 .../feature/{DataBaseModel.java => Database.java}  | 234 ++++++++-------------
 .../apache/sis/internal/sql/feature/MetaModel.java |  97 ++++++---
 .../sis/internal/sql/feature/PrimaryKey.java       |   6 +-
 .../sis/internal/sql/feature/QueryFeatureSet.java  |   4 +-
 .../{RelationMetaModel.java => Relation.java}      |  15 +-
 .../feature/{SchemaMetaModel.java => Schema.java}  |  29 +--
 .../{Dialect.java => SpatialFunctions.java}        |  55 ++++-
 .../feature/{TableMetaModel.java => Table.java}    |  34 ++-
 .../sis/internal/sql/feature/package-info.java     |   3 +-
 .../sis/internal/sql/postgres/PostgresDialect.java |  86 --------
 .../sis/internal/sql/postgres/PostgresStore.java   |  88 --------
 .../sql/postgres/PostgresStoreProvider.java        |  74 -------
 .../java/org/apache/sis/storage/sql/SQLStore.java  |  18 +-
 17 files changed, 269 insertions(+), 494 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Dialect.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Dialect.java
index fb834c9..76e1236 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Dialect.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Dialect.java
@@ -88,9 +88,10 @@ public enum Dialect {
 
     /**
      * Returns the presumed SQL dialect.
+     * If this method can not guess the dialect, than {@link #ANSI} is presumed.
      *
      * @param  metadata  the database metadata.
-     * @return the presumed SQL dialect.
+     * @return the presumed SQL dialect (never {@code null}).
      * @throws SQLException if an error occurred while querying the metadata.
      */
     public static Dialect guess(final DatabaseMetaData metadata) throws SQLException {
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLUtilities.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLUtilities.java
index 40bef8b..107a76d 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLUtilities.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLUtilities.java
@@ -63,14 +63,17 @@ public final class SQLUtilities extends Static {
     /**
      * Converts the given string to a boolean value, or returns {@code null} if the value is unrecognized.
      * This method recognizes "true", "false", "yes", "no", "t", "f", 0 and 1 (case insensitive).
+     * An empty string is interpreted as {@code null}.
      *
      * @param  text  the characters to convert to a boolean value, or {@code null}.
      * @return the given characters as a boolean value, or {@code null} if the given text was null or empty.
      * @throws SQLDataException if the given text is non-null and non-empty but not recognized.
      *
+     * @see Boolean#parseBoolean(String)
+     *
      * @since 0.8
      */
-    public static Boolean toBoolean(final String text) throws SQLException {
+    public static Boolean parseBoolean(final String text) throws SQLException {
         if (text == null) {
             return null;
         }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index f909d98..d3d465c 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -2549,7 +2549,7 @@ next:               while (r.next()) {
                             b = r.getBoolean(1);
                             if (r.wasNull()) b = null;
                         } else {
-                            b = SQLUtilities.toBoolean(r.getString(1));     // May throw SQLException - see above comment.
+                            b = SQLUtilities.parseBoolean(r.getString(1));  // May throw SQLException - see above comment.
                         }
                         if (b != null) {
                             isReversible = b ? SignReversalComment.OPPOSITE : SignReversalComment.SAME;
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/ColumnMetaModel.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Column.java
similarity index 94%
rename from storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/ColumnMetaModel.java
rename to storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Column.java
index 0d11928..85afb3a 100644
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/ColumnMetaModel.java
+++ b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Column.java
@@ -36,7 +36,7 @@ import org.apache.sis.internal.metadata.sql.SQLBuilder;
  * @since   1.0
  * @module
  */
-public final class ColumnMetaModel {
+final class Column {
     /**
      * Description of the attribute telling whether a field is unique in the database.
      */
@@ -47,9 +47,9 @@ public final class ColumnMetaModel {
     /**
      * Property information, if the field is a relation.
      */
-    static final AttributeType<RelationMetaModel> JDBC_PROPERTY_RELATION = new DefaultAttributeType<>(
+    static final AttributeType<Relation> JDBC_PROPERTY_RELATION = new DefaultAttributeType<>(
             Collections.singletonMap(DefaultAttributeType.NAME_KEY, "relation"),
-            RelationMetaModel.class, 1, 1, null);
+            Relation.class, 1, 1, null);
 
     /**
      * Whether values in a column are generated by the database, computed from a sequence of supplied.
@@ -123,7 +123,7 @@ public final class ColumnMetaModel {
      * @param type          if the column is a primary key, specify how the value is generated.
      * @param sequenceName  if the column is a primary key, optional sequence name.
      */
-    ColumnMetaModel(final String schema, final String table, final String name,
+    Column(final String schema, final String table, final String name,
             final int sqlType, final String sqlTypeName, final Class<?> clazz,
             final Type type, final String sequenceName)
     {
@@ -146,7 +146,7 @@ public final class ColumnMetaModel {
      * @throws SQLException if a JDBC error occurred while executing a statement.
      * @throws DataStoreException if another error occurred while fetching the next value.
      */
-    public Object nextValue(final Dialect dialect, final Connection cx) throws SQLException, DataStoreException {
+    public Object nextValue(final SpatialFunctions dialect, final Connection cx) throws SQLException, DataStoreException {
         Object next = null;
         if (type == Type.AUTO || type == Type.SEQUENCED) {
             // Delegate to the database for next value.
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/DataBaseModel.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Database.java
similarity index 70%
rename from storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/DataBaseModel.java
rename to storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Database.java
index d20f49d..bfdd113 100644
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/DataBaseModel.java
+++ b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Database.java
@@ -41,18 +41,18 @@ import org.apache.sis.feature.builder.AttributeRole;
 import org.apache.sis.feature.builder.AttributeTypeBuilder;
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
 import org.apache.sis.feature.builder.PropertyTypeBuilder;
+import org.apache.sis.internal.metadata.sql.SQLUtilities;
 import org.apache.sis.internal.metadata.sql.Reflection;
 import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.storage.sql.SQLStore;
-import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureNaming;
+import org.apache.sis.storage.IllegalNameException;
 import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.logging.WarningListeners;
 
 
 /**
- * Represent the structure of the database.
+ * Represent the structure of features in the database.
  * The work done here is similar to reverse engineering.
  *
  * @author  Johann Sorel (Geomatys)
@@ -60,12 +60,13 @@ import org.apache.sis.util.logging.WarningListeners;
  * @since   1.0
  * @module
  */
-public final class DataBaseModel {
-
-    private static final String TYPE_TABLE = "TABLE";
-    private static final String TYPE_VIEW  = "VIEW";
-    private static final String VALUE_YES = "YES";
-    private static final String VALUE_NO = "NO";
+public final class Database {
+    /**
+     * Possible value for the {@value Reflection#TABLE_TYPE} column in the {@link ResultSet}
+     * returned by {@link DatabaseMetaData#getTables(String, String, String, String[])}.
+     * Also a possible value for the last argument of above-cited method.
+     */
+    private static final String TABLE = "TABLE", VIEW = "VIEW";
 
     /**
      * Feature type used to mark types which are sub-types of others.
@@ -78,113 +79,57 @@ public final class DataBaseModel {
         SUBTYPE = ftb.build();
     }
 
-    private final SQLStore store;
-    private final Dialect dialect;
-    private final String databaseSchema;
-    private final String databaseTable;
-
-    private FeatureNaming<PrimaryKey> pkIndex = new FeatureNaming<>();
-    private Set<GenericName> typeNames = new HashSet<>();
-    private FeatureNaming<FeatureType> typeIndex = new FeatureNaming<>();
-    private Map<String,SchemaMetaModel> schemas;
-    private Set<GenericName> nameCache;
-
-    //various cache while analyzing model
-    private DatabaseMetaData metadata;
-    //this set contains schema names which are needed to rebuild relations
-    private Set<String> visitedSchemas;
-    private Set<String> requieredSchemas;
-
-    private final WarningListeners<DataStore> listeners;
+    private final SpatialFunctions functions;
+    private final FeatureNaming<PrimaryKey> pkIndex;
+    private final FeatureNaming<FeatureType> typeIndex;
+    private final Map<String,Schema> schemas;
 
-    public DataBaseModel(final SQLStore store, final Dialect dialect, final String schema, final String table, final WarningListeners<DataStore> listeners) {
+    public Database(final SQLStore store, final SpatialFunctions functions, final String schema, final String table,
+            final List<String> addWarningsTo) throws SQLException, IllegalNameException
+    {
         if (table != null) {
             ArgumentChecks.ensureNonEmpty("table", table);
         }
-        this.store          = store;
-        this.dialect        = dialect;
-        this.databaseSchema = schema;
-        this.databaseTable  = table;
-        this.listeners      = listeners;
+        this.functions = functions;
+        pkIndex = new FeatureNaming<>();
+        typeIndex = new FeatureNaming<>();
+        schemas = new HashMap<>();
+        analyze(store, schema, table, addWarningsTo);
     }
 
-    private Collection<SchemaMetaModel> getSchemaMetaModels() throws SQLException, DataStoreException {
-        if (schemas == null) {
-            analyze();
-        }
+    private Collection<Schema> getSchemaMetaModels() {
         return schemas.values();
     }
 
-    private SchemaMetaModel getSchemaMetaModel(String name) throws SQLException, DataStoreException {
-        if (schemas == null) {
-            analyze();
-        }
+    private Schema getSchemaMetaModel(String name) {
         return schemas.get(name);
     }
 
-    /**
-     * Clear the model cache. A new database analyze will be made the next time it is needed.
-     */
-    private synchronized void clearCache() {
-        pkIndex   = new FeatureNaming<>();
-        typeIndex = new FeatureNaming<>();
-        typeNames = new HashSet<>();
-        nameCache = null;
-        schemas   = null;
-    }
-
-    private PrimaryKey getPrimaryKey(final String featureTypeName) throws SQLException, DataStoreException {
-        if (schemas == null) {
-            analyze();
-        }
+    private PrimaryKey getPrimaryKey(final SQLStore store, final String featureTypeName) throws IllegalNameException {
         return pkIndex.get(store, featureTypeName);
     }
 
-    private synchronized Set<GenericName> getNames() throws SQLException, DataStoreException {
-        Set<GenericName> ref = nameCache;
-        if (ref == null) {
-            analyze();
-            final Set<GenericName> names = new HashSet<>();
-            for (GenericName name : typeNames) {
-                final FeatureType type = typeIndex.get(store, name.toString());
-                if (SUBTYPE.isAssignableFrom(type)) continue;
-                if (dialect.isTableIgnored(name.tip().toString())) continue;
-                names.add(name);
-            }
-            ref = Collections.unmodifiableSet(names);
-            nameCache = ref;
-        }
-        return ref;
-    }
-
-    public FeatureType getFeatureType(final String typeName) throws SQLException, DataStoreException {
-        if (schemas == null) {
-            analyze();
-        }
+    public FeatureType getFeatureType(final SQLStore store, final String typeName) throws IllegalNameException {
         return typeIndex.get(store, typeName);
     }
 
     /**
      * Explores all tables and views then recreate a complex feature model from relations.
      */
-    private synchronized void analyze() throws SQLException, DataStoreException {
-        if (schemas != null) {
-            return;                         // Already analyzed
-        }
-        clearCache();
-        schemas = new HashMap<>();
-        visitedSchemas = new HashSet<>();
-        requieredSchemas = new HashSet<>();
-
+    private synchronized void analyze(final SQLStore store, final String schemaName, final String tableName, final List<String> addWarningsTo)
+            throws SQLException, IllegalNameException
+    {
         try (Connection cx = store.getDataSource().getConnection()) {
-            metadata = cx.getMetaData();
+            final DatabaseMetaData metadata = cx.getMetaData();
+            final Set<String> requieredSchemas = new HashSet<>();
+            final Set<String> visitedSchemas = new HashSet<>();
             /*
              * Schema names available in the database:
              * 1. TABLE_SCHEM   : String  =>  schema name
              * 2. TABLE_CATALOG : String  =>  catalog name (may be null)
              */
-            if (databaseSchema != null) {
-                requieredSchemas.add(databaseSchema);
+            if (schemaName != null) {
+                requieredSchemas.add(schemaName);
             } else try (ResultSet reflect = metadata.getSchemas()) {
                 while (reflect.next()) {
                     requieredSchemas.add(reflect.getString(Reflection.TABLE_SCHEM));        // TODO: use schemas in getTables instead.
@@ -198,41 +143,35 @@ public final class DataBaseModel {
                 visitedSchemas.add(sn);
                 requieredSchemas.remove(sn);
                 // TODO: escape with metadata.getSearchStringEscape().
-                final SchemaMetaModel schema = analyzeSchema(sn, databaseTable);
+                final Schema schema = analyzeSchema(metadata, sn, tableName, requieredSchemas, visitedSchemas, addWarningsTo);
                 schemas.put(schema.name, schema);
             }
-            reverseSimpleFeatureTypes();
-        } finally {
-            metadata = null;
-            visitedSchemas = null;
-            requieredSchemas = null;
+            reverseSimpleFeatureTypes(metadata);
         }
         /*
          * Build indexes.
          */
-        final String baseSchemaName = databaseSchema;
-        final Collection<SchemaMetaModel> candidates;
-        if (baseSchemaName == null) {
+        final Collection<Schema> candidates;
+        if (schemaName == null) {
             candidates = getSchemaMetaModels();             // Take all schemas.
         } else {
-            candidates = Collections.singleton(getSchemaMetaModel(baseSchemaName));
+            candidates = Collections.singleton(getSchemaMetaModel(schemaName));
         }
-        for (SchemaMetaModel schema : candidates) {
+        for (Schema schema : candidates) {
            if (schema != null) {
-                for (TableMetaModel table : schema.getTables()) {
+                for (Table table : schema.getTables()) {
 
-                    final FeatureTypeBuilder ft = table.getType(TableMetaModel.View.SIMPLE_FEATURE_TYPE);
+                    final FeatureTypeBuilder ft = table.getType(Table.View.SIMPLE_FEATURE_TYPE);
                     final GenericName name = ft.getName();
                     pkIndex.add(store, name, table.key);
                     if (table.isSubType()) {
                         // We don't show subtype, they are part of other feature types, add a flag to identify then
                         ft.setSuperTypes(SUBTYPE);
                     }
-                    typeNames.add(name);
                     typeIndex.add(store, name, ft.build());
                  }
             } else {
-                throw new DataStoreException("Specifed schema " + baseSchemaName + " does not exist.");
+                throw new SQLException("Specifed schema " + schemaName + " does not exist.");
             }
         }
     }
@@ -240,41 +179,47 @@ public final class DataBaseModel {
     /**
      * @param  schemaPattern  schema name with "%" and "_" interpreted as wildcards, or {@code null} for all schemas.
      */
-    private SchemaMetaModel analyzeSchema(final String schemaPattern, final String tableNamePattern) throws SQLException, DataStoreException {
-        final SchemaMetaModel schema = new SchemaMetaModel(schemaPattern);
+    private Schema analyzeSchema(final DatabaseMetaData metadata, final String schemaPattern, final String tableNamePattern,
+            final Set<String> requieredSchemas, final Set<String> visitedSchemas, final List<String> addWarningsTo)
+            throws SQLException, IllegalNameException
+    {
+        final Schema schema = new Schema(schemaPattern);
         /*
          * Description of the tables available:
          * 1. TABLE_SCHEM : String  =>  table schema (may be null)
          * 2. TABLE_NAME  : String  =>  table name
          * 3. TABLE_TYPE  : String  =>  table type (typically "TABLE" or "VIEW").
          */
-        try (ResultSet reflect = metadata.getTables(null, schemaPattern, tableNamePattern, new String[] {TYPE_TABLE, TYPE_VIEW})) {   // TODO: use metadata.getTableTypes()
+        try (ResultSet reflect = metadata.getTables(null, schemaPattern, tableNamePattern, new String[] {TABLE, VIEW})) {   // TODO: use metadata.getTableTypes()
             while (reflect.next()) {
-                final TableMetaModel table = analyzeTable(reflect);
+                final Table table = analyzeTable(metadata, reflect, requieredSchemas, visitedSchemas, addWarningsTo);
                 schema.tables.put(table.name, table);
             }
         }
         return schema;
     }
 
-    private TableMetaModel analyzeTable(final ResultSet tableSet) throws SQLException, DataStoreException {
+    private Table analyzeTable(final DatabaseMetaData metadata, final ResultSet tableSet,
+            final Set<String> requieredSchemas, final Set<String> visitedSchemas, final List<String> addWarningsTo)
+            throws SQLException, IllegalNameException
+    {
         final String schemaName = tableSet.getString(Reflection.TABLE_SCHEM);
         final String tableName  = tableSet.getString(Reflection.TABLE_NAME);
         final String tableType  = tableSet.getString(Reflection.TABLE_TYPE);
-        final TableMetaModel table = new TableMetaModel(tableName, tableType);
+        final Table table = new Table(tableName, tableType);
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
         /*
          * Explore all columns.
          */
         try (ResultSet reflect = metadata.getColumns(null, schemaName, tableName, null)) {
             while (reflect.next()) {
-                analyzeColumn(reflect, ftb.addAttribute(Object.class));
+                analyzeColumn(metadata, reflect, ftb.addAttribute(Object.class));
             }
         }
         /*
          * Find primary keys.
          */
-        final List<ColumnMetaModel> cols = new ArrayList<>();
+        final List<Column> cols = new ArrayList<>();
         try (ResultSet rp = metadata.getPrimaryKeys(null, schemaName, tableName)) {
             while (rp.next()) {
                 final String columnNamePattern = rp.getString(Reflection.COLUMN_NAME);
@@ -283,24 +228,24 @@ public final class DataBaseModel {
                     while (reflect.next()) {                                        // Should loop exactly once.
                         final int sqlType = reflect.getInt(Reflection.DATA_TYPE);
                         final String sqlTypeName = reflect.getString(Reflection.TYPE_NAME);
-                        Class<?> columnType = dialect.getJavaType(sqlType, sqlTypeName);
+                        Class<?> columnType = functions.getJavaType(sqlType, sqlTypeName);
                         if (columnType == null) {
-                            listeners.warning("No class for SQL type " + sqlType, null);
+                            addWarningsTo.add("No class for SQL type " + sqlType);
                             columnType = Object.class;
                         }
-                        ColumnMetaModel col;
-                        final String str = reflect.getString(Reflection.IS_AUTOINCREMENT);
-                        if (VALUE_YES.equalsIgnoreCase(str)) {
-                            col = new ColumnMetaModel(schemaName, tableName, columnNamePattern, sqlType, sqlTypeName, columnType, ColumnMetaModel.Type.AUTO, null);
+                        Column col;
+                        final Boolean b = SQLUtilities.parseBoolean(reflect.getString(Reflection.IS_AUTOINCREMENT));
+                        if (b != null && b) {
+                            col = new Column(schemaName, tableName, columnNamePattern, sqlType, sqlTypeName, columnType, Column.Type.AUTO, null);
                         } else {
                             // TODO: need to distinguish "NO" and empty string.
-                            final String sequenceName = dialect.getColumnSequence(metadata.getConnection(), schemaName, tableName, columnNamePattern);
+                            final String sequenceName = functions.getColumnSequence(metadata.getConnection(), schemaName, tableName, columnNamePattern);
                             if (sequenceName != null) {
-                                col = new ColumnMetaModel(schemaName, tableName, columnNamePattern, sqlType,
-                                        sqlTypeName, columnType, ColumnMetaModel.Type.SEQUENCED,sequenceName);
+                                col = new Column(schemaName, tableName, columnNamePattern, sqlType,
+                                        sqlTypeName, columnType, Column.Type.SEQUENCED,sequenceName);
                             } else {
-                                col = new ColumnMetaModel(schemaName, tableName, columnNamePattern, sqlType,
-                                        sqlTypeName, columnType, ColumnMetaModel.Type.PROVIDED, null);
+                                col = new Column(schemaName, tableName, columnNamePattern, sqlType,
+                                        sqlTypeName, columnType, Column.Type.PROVIDED, null);
                             }
                         }
                         cols.add(col);
@@ -352,7 +297,7 @@ public final class DataBaseModel {
                 for (PropertyTypeBuilder desc : ftb.properties()) {
                     if (desc.getName().tip().toString().equals(columnName)) {
                         final AttributeTypeBuilder<?> atb = (AttributeTypeBuilder) desc;
-                        atb.addCharacteristic(ColumnMetaModel.JDBC_PROPERTY_UNIQUE).setDefaultValue(Boolean.TRUE);
+                        atb.addCharacteristic(Column.JDBC_PROPERTY_UNIQUE).setDefaultValue(Boolean.TRUE);
                     }
                 }
             }
@@ -367,9 +312,9 @@ public final class DataBaseModel {
                     if (names.contains(columnName)) {
                         final int sqlType = reflect.getInt(Reflection.DATA_TYPE);
                         final String sqlTypeName = reflect.getString(Reflection.TYPE_NAME);
-                        final Class<?> columnType = dialect.getJavaType(sqlType, sqlTypeName);
-                        final ColumnMetaModel col = new ColumnMetaModel(schemaName, tableName, columnName,
-                                sqlType, sqlTypeName, columnType, ColumnMetaModel.Type.PROVIDED, null);
+                        final Class<?> columnType = functions.getJavaType(sqlType, sqlTypeName);
+                        final Column col = new Column(schemaName, tableName, columnName,
+                                sqlType, sqlTypeName, columnType, Column.Type.PROVIDED, null);
                         cols.add(col);
                         /*
                          * Set as identifier
@@ -386,8 +331,8 @@ public final class DataBaseModel {
             }
         }
         if (cols.isEmpty()) {
-            if (TYPE_TABLE.equals(tableType)) {
-                listeners.warning("No primary key found for " + tableName, null);
+            if (TABLE.equals(tableType)) {
+                addWarningsTo.add("No primary key found for " + tableName);
             }
         }
         table.key = new PrimaryKey(tableName, cols);
@@ -395,7 +340,7 @@ public final class DataBaseModel {
          * Mark primary key columns.
          */
         for (PropertyTypeBuilder desc : ftb.properties()) {
-            for (ColumnMetaModel col : cols) {
+            for (Column col : cols) {
                 if (desc.getName().tip().toString().equals(col.name)) {
                     final AttributeTypeBuilder<?> atb = (AttributeTypeBuilder) desc;
                     atb.addRole(AttributeRole.IDENTIFIER_COMPONENT);
@@ -416,14 +361,14 @@ public final class DataBaseModel {
                 final String refColumnName = reflect.getString(Reflection.PKCOLUMN_NAME);
                 final int deleteRule = reflect.getInt(Reflection.DELETE_RULE);
                 final boolean deleteCascade = DatabaseMetaData.importedKeyCascade == deleteRule;
-                final RelationMetaModel relation = new RelationMetaModel(relationName,localColumn,
+                final Relation relation = new Relation(relationName,localColumn,
                         refSchemaName, refTableName, refColumnName, true, deleteCascade);
                 table.importedKeys.add(relation);
                 if (refSchemaName!=null && !visitedSchemas.contains(refSchemaName)) requieredSchemas.add(refSchemaName);
                 for (PropertyTypeBuilder desc : ftb.properties()) {
                     if (desc.getName().tip().toString().equals(localColumn)) {
                         final AttributeTypeBuilder<?> atb = (AttributeTypeBuilder) desc;
-                        atb.addCharacteristic(ColumnMetaModel.JDBC_PROPERTY_RELATION).setDefaultValue(relation);
+                        atb.addCharacteristic(Column.JDBC_PROPERTY_RELATION).setDefaultValue(relation);
                         break;
                     }
                 }
@@ -442,7 +387,7 @@ public final class DataBaseModel {
                 final String refColumnName = reflect.getString(Reflection.FKCOLUMN_NAME);
                 final int deleteRule = reflect.getInt(Reflection.DELETE_RULE);
                 final boolean deleteCascade = DatabaseMetaData.importedKeyCascade == deleteRule;
-                table.exportedKeys.add(new RelationMetaModel(relationName, localColumn,
+                table.exportedKeys.add(new Relation(relationName, localColumn,
                         refSchemaName, refTableName, refColumnName, false, deleteCascade));
 
                 if (refSchemaName != null && !visitedSchemas.contains(refSchemaName)) {
@@ -455,7 +400,7 @@ public final class DataBaseModel {
         return table;
     }
 
-    private AttributeType<?> analyzeColumn(final ResultSet columnSet, final AttributeTypeBuilder<?> atb) throws SQLException {
+    private AttributeType<?> analyzeColumn(final DatabaseMetaData metadata, final ResultSet columnSet, final AttributeTypeBuilder<?> atb) throws SQLException {
         final String schemaName     = columnSet.getString(Reflection.TABLE_SCHEM);
         final String tableName      = columnSet.getString(Reflection.TABLE_NAME);
         final String columnName     = columnSet.getString(Reflection.COLUMN_NAME);
@@ -465,9 +410,10 @@ public final class DataBaseModel {
         final String columnNullable = columnSet.getString(Reflection.IS_NULLABLE);
         atb.setName(columnName);
         atb.setMaximalLength(columnSize);
-        dialect.decodeColumnType(atb, metadata.getConnection(), columnTypeName, columnDataType, schemaName, tableName, columnName);
+        functions.decodeColumnType(atb, metadata.getConnection(), columnTypeName, columnDataType, schemaName, tableName, columnName);
         // TODO: need to distinguish "YES" and empty string?
-        atb.setMinimumOccurs(VALUE_NO.equalsIgnoreCase(columnNullable) ? 1 : 0);
+        final Boolean b = SQLUtilities.parseBoolean(columnNullable);
+        atb.setMinimumOccurs(b != null && !b ? 1 : 0);
         atb.setMaximumOccurs(1);
         return atb.build();
     }
@@ -490,12 +436,12 @@ public final class DataBaseModel {
 
             // Search if we already have this property
             PropertyType desc = null;
-            final SchemaMetaModel schema = getSchemaMetaModel(schemaName);
+            final Schema schema = getSchemaMetaModel(schemaName);
             if (schema != null) {
-                TableMetaModel table = schema.getTable(tableName);
+                Table table = schema.getTable(tableName);
                 if (table != null) {
                     try {
-                        desc = table.getType(TableMetaModel.View.SIMPLE_FEATURE_TYPE).build().getProperty(columnName);
+                        desc = table.getType(Table.View.SIMPLE_FEATURE_TYPE).build().getProperty(columnName);
                     } catch (PropertyNotFoundException ex) {
                         // ok
                     }
@@ -512,7 +458,7 @@ public final class DataBaseModel {
                 atb.setMinimumOccurs(nullable == ResultSetMetaData.columnNullable ? 0 : 1);
                 atb.setMaximumOccurs(1);
                 atb.setName(columnLabel);
-                atb.setValueClass(dialect.getJavaType(sqlType, sqlTypeName));
+                atb.setValueClass(functions.getJavaType(sqlType, sqlTypeName));
             }
         }
         return ftb.build();
@@ -521,9 +467,9 @@ public final class DataBaseModel {
     /**
      * Rebuild simple feature types for each table.
      */
-    private void reverseSimpleFeatureTypes() throws SQLException {
-        for (final SchemaMetaModel schema : schemas.values()) {
-            for (final TableMetaModel table : schema.getTables()) {
+    private void reverseSimpleFeatureTypes(final DatabaseMetaData metadata) throws SQLException {
+        for (final Schema schema : schemas.values()) {
+            for (final Table table : schema.getTables()) {
                 final FeatureTypeBuilder ftb = new FeatureTypeBuilder(table.tableType.build());
                 final String featureName = ftb.getName().tip().toString();
                 ftb.setName(featureName);
@@ -542,7 +488,7 @@ public final class DataBaseModel {
                         // TODO: escape columnNamePattern with metadata.getSearchStringEscape().
                         try (ResultSet reflect = metadata.getColumns(null, schema.name, table.name, name)) {
                             while (reflect.next()) {        // Should loop exactly once.
-                                CoordinateReferenceSystem crs = dialect.createGeometryCRS(reflect);
+                                CoordinateReferenceSystem crs = functions.createGeometryCRS(reflect);
                                 atb.setCRS(crs);
                                 if (isGeometry & !defaultGeomSet) {
                                     atb.addRole(AttributeRole.DEFAULT_GEOMETRY);
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/MetaModel.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/MetaModel.java
index 880437e..fd7975d 100644
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/MetaModel.java
+++ b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/MetaModel.java
@@ -16,12 +16,18 @@
  */
 package org.apache.sis.internal.sql.feature;
 
-import java.util.Iterator;
-import org.apache.sis.util.CharSequences;
+import java.util.Collection;
+import java.sql.DatabaseMetaData;
+import org.apache.sis.util.Debug;
+import org.apache.sis.util.collection.TreeTable;
+import org.apache.sis.util.collection.TableColumn;
+import org.apache.sis.util.collection.DefaultTreeTable;
 
 
 /**
- * Base class of descriptions about a database entity (table or schema).
+ * Description about a database entity (schema, table, relation, <i>etc</i>).
+ * The information provided by subclasses are inferred from {@link DatabaseMetaData}
+ * and stored as structures from the {@link org.apache.sis.feature} package.
  *
  * @author  Johann Sorel (Geomatys)
  * @version 1.0
@@ -30,50 +36,75 @@ import org.apache.sis.util.CharSequences;
  */
 abstract class MetaModel {
     /**
-     * The schema or table name.
+     * The entity (schema, table, <i>etc</i>) name.
      */
     final String name;
 
     /**
-     * Creates a new object describing a database table or a schema.
+     * Creates a new object describing a database entity (schema, table, <i>etc</i>).
      *
-     * @param  name  the schema or table name.
+     * @param  name  the database entity name.
      */
     MetaModel(final String name) {
         this.name = name;
     }
 
     /**
-     * Formats a graphical representation of the specified objects. This representation can be
-     * printed to the {@linkplain System#out standard output stream} (for example) if it uses
-     * a monospaced font and supports Unicode.
+     * Creates a tree representation of this object for debugging purpose.
+     * The default implementation adds a single node with the {@link #name} of this entity
+     * and returns that node. Subclasses can override for appending additional information.
      *
-     * @param  root     the root name of the tree to format, or {@code null} if none.
-     * @param  objects  the objects to format as root children, or {@code null} if none.
-     * @param  sb       where to format the tree.
+     * @param  parent  the parent node where to add the tree representation.
+     * @return the node added by this method.
      */
-    static void appendTree(final String root, final Iterable<?> objects, final StringBuilder sb, final String lineSeparator) {
-        if (root != null) {
-            sb.append(root);
-        }
-        if (objects != null) {
-            final Iterator<?> it = objects.iterator();
-            boolean hasNext;
-            if (it.hasNext()) do {
-                sb.append(lineSeparator);
-                final Object next = it.next();
-                hasNext = it.hasNext();
-                sb.append(hasNext ? "├─ " : "└─ ");
+    @Debug
+    TreeTable.Node appendTo(final TreeTable.Node parent) {
+        return newChild(parent, name);
+    }
+
+    /**
+     * Add a child of the given name to the given node.
+     *
+     * @param  parent  the node where to add a child.
+     * @param  name    the name to assign to the child.
+     * @return the child node.
+     */
+    @Debug
+    private static TreeTable.Node newChild(final TreeTable.Node parent, final String name) {
+        final TreeTable.Node child = parent.newChild();
+        child.setValue(TableColumn.NAME, name);
+        return child;
+    }
 
-                final CharSequence[] parts = CharSequences.splitOnEOL(String.valueOf(next));
-                sb.append(parts[0]);
-                for (int k=1; k < parts.length; k++) {
-                    sb.append(lineSeparator)
-                      .append(hasNext ? '│' : ' ')
-                      .append("  ")
-                      .append(parts[k]);
-                }
-            } while (hasNext);
+    /**
+     * Appends all children to the given parent. The children are added under a node of the given name.
+     * If the collection of children is empty, then no node of the given {@code name} is inserted.
+     *
+     * @param  parent    the node where to add children.
+     * @param  name      the name of a node to insert between the parent and the children, or {@code null} if none.
+     * @param  children  the children to add, or an empty collection if none.
+     */
+    @Debug
+    static void appendAll(TreeTable.Node parent, final String name, final Collection<? extends MetaModel> children) {
+        if (!children.isEmpty()) {
+            if (name != null) {
+                parent = newChild(parent, name);
+            }
+            for (final MetaModel child : children) {
+                child.appendTo(parent);
+            }
         }
     }
+
+    /**
+     * Formats a graphical representation of this object for debugging purpose. This representation can
+     * be printed to the {@linkplain System#out standard output stream} (for example) if the output device
+     * uses a monospaced font and supports Unicode.
+     */
+    @Override
+    public String toString() {
+        final DefaultTreeTable table = new DefaultTreeTable(TableColumn.NAME);
+        appendTo(table.getRoot());
+        return table.toString();
+    }
 }
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/PrimaryKey.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/PrimaryKey.java
index ff2667a..a4e9139 100644
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/PrimaryKey.java
+++ b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/PrimaryKey.java
@@ -36,9 +36,9 @@ import org.apache.sis.storage.DataStoreException;
 final class PrimaryKey {
 
     final String table;
-    final List<ColumnMetaModel> columns;
+    final List<Column> columns;
 
-    PrimaryKey(final String table, List<ColumnMetaModel> columns) {
+    PrimaryKey(final String table, List<Column> columns) {
         this.table = table;
         if (columns == null) {
             columns = Collections.emptyList();
@@ -93,7 +93,7 @@ final class PrimaryKey {
      * @throws SQLException if a JDBC error occurred while executing a statement.
      * @throws DataStoreException if another error occurred while fetching the next value.
      */
-    Object[] nextValues(final Dialect dialect, final Connection cx) throws SQLException, DataStoreException {
+    Object[] nextValues(final SpatialFunctions dialect, final Connection cx) throws SQLException, DataStoreException {
         final Object[] parts = new Object[columns.size()];
         for (int i=0; i<parts.length; i++) {
             parts[i] = columns.get(i).nextValue(dialect, cx);
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/QueryFeatureSet.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/QueryFeatureSet.java
index d2ce7d0..15dd6c9 100644
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/QueryFeatureSet.java
+++ b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/QueryFeatureSet.java
@@ -43,12 +43,12 @@ import org.opengis.metadata.Metadata;
  */
 public final class QueryFeatureSet implements FeatureSet {
 
-    private final DataBaseModel model;
+    private final Database model;
     private final SQLStore store;
     private final SQLQuery query;
     private FeatureType type;
 
-    public QueryFeatureSet(final SQLStore store, final DataBaseModel model, final SQLQuery query) {
+    public QueryFeatureSet(final SQLStore store, final Database model, final SQLQuery query) {
         this.store = store;
         this.model = model;
         this.query = query;
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/RelationMetaModel.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Relation.java
similarity index 75%
rename from storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/RelationMetaModel.java
rename to storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Relation.java
index bc83e29..a395f63 100644
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/RelationMetaModel.java
+++ b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Relation.java
@@ -16,8 +16,6 @@
  */
 package org.apache.sis.internal.sql.feature;
 
-import org.apache.sis.util.ArgumentChecks;
-
 
 /**
  * Description of a relation between two tables.
@@ -27,9 +25,8 @@ import org.apache.sis.util.ArgumentChecks;
  * @since   1.0
  * @module
  */
-final class RelationMetaModel {
+final class Relation extends MetaModel {
 
-    final String  relationName;
     final String  currentColumn;
     final String  foreignSchema;
     final String  foreignTable;
@@ -37,15 +34,11 @@ final class RelationMetaModel {
     final boolean isImported;
     final boolean cascadeOnDelete;
 
-    RelationMetaModel(final String relationName, final String currentColumn, final String foreignSchema,
+    Relation(final String name, final String currentColumn, final String foreignSchema,
             final String foreignTable, final String foreignColumn,
             final boolean isImported, final boolean cascadeOnDelete)
     {
-        ArgumentChecks.ensureNonNull("relationName",  relationName);
-        ArgumentChecks.ensureNonNull("currentColumn", currentColumn);
-        ArgumentChecks.ensureNonNull("foreignTable",  foreignTable);
-        ArgumentChecks.ensureNonNull("foreignColumn", foreignColumn);
-        this.relationName    = relationName;
+        super(name);
         this.currentColumn   = currentColumn;
         this.foreignSchema   = foreignSchema;
         this.foreignTable    = foreignTable;
@@ -57,7 +50,7 @@ final class RelationMetaModel {
     @Override
     public String toString() {
         return new StringBuilder(currentColumn)
-                .append((isImported) ? " → " : " ← ")
+                .append(isImported ? " → " : " ← ")
                 .append(foreignSchema).append('.')
                 .append(foreignTable).append('.').append(foreignColumn)
                 .toString();
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/SchemaMetaModel.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Schema.java
similarity index 71%
rename from storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/SchemaMetaModel.java
rename to storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Schema.java
index 21b4a60..0d0d7a2 100644
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/SchemaMetaModel.java
+++ b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Schema.java
@@ -16,9 +16,11 @@
  */
 package org.apache.sis.internal.sql.feature;
 
-import java.util.Collection;
-import java.util.HashMap;
 import java.util.Map;
+import java.util.HashMap;
+import java.util.Collection;
+import org.apache.sis.util.Debug;
+import org.apache.sis.util.collection.TreeTable;
 
 
 /**
@@ -29,17 +31,17 @@ import java.util.Map;
  * @since   1.0
  * @module
  */
-final class SchemaMetaModel extends MetaModel {
+final class Schema extends MetaModel {
     /**
      * The tables in the schema.
      */
-    final Map<String,TableMetaModel> tables;
+    final Map<String,Table> tables;
 
     /**
      * Creates a new schema of the given name.
      * It is caller responsibility to populate the {@link #tables} map.
      */
-    SchemaMetaModel(final String name) {
+    Schema(final String name) {
         super(name);
         tables = new HashMap<>();
     }
@@ -47,24 +49,27 @@ final class SchemaMetaModel extends MetaModel {
     /**
      * Returns all tables in this schema.
      */
-    Collection<TableMetaModel> getTables() {
+    Collection<Table> getTables() {
         return tables.values();
     }
 
     /**
      * Returns the table of the given name, or {@code null} if none.
      */
-    TableMetaModel getTable(final String name){
+    Table getTable(final String name){
         return tables.get(name);
     }
 
     /**
-     * Returns a string representation of this schema for debugging purposes.
+     * Creates a tree representation of this object for debugging purpose.
+     *
+     * @param  parent  the parent node where to add the tree representation.
      */
+    @Debug
     @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder(100);
-        appendTree(name, getTables(), sb, System.lineSeparator());
-        return sb.toString();
+    TreeTable.Node appendTo(final TreeTable.Node parent) {
+        final TreeTable.Node node = super.appendTo(parent);
+        appendAll(parent, null, getTables());
+        return node;
     }
 }
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Dialect.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/SpatialFunctions.java
similarity index 68%
rename from storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Dialect.java
rename to storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/SpatialFunctions.java
index 102886a..e53966b 100644
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Dialect.java
+++ b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/SpatialFunctions.java
@@ -16,39 +16,74 @@
  */
 package org.apache.sis.internal.sql.feature;
 
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Locale;
 import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.DatabaseMetaData;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.apache.sis.feature.builder.AttributeTypeBuilder;
+import org.apache.sis.internal.metadata.sql.Dialect;
 import org.apache.sis.storage.DataStoreException;
 
 
 /**
- * Description or handling of syntax elements specific to a database.
- * The dialect provides descriptions and methods implementing the different
- * functionalities required by the data store to generate SQL statements.
+ * Access to functions provided by geospatial databases.
+ * Those functions may depend on the actual database product (PostGIS, etc).
  *
  * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
  * @version 1.0
  * @since   1.0
  * @module
  */
-public abstract class Dialect {
+abstract class SpatialFunctions {
     /**
-     * For subclass constructors.
+     * The tables to be ignored when inspecting the tables in a database schema.
+     * Those tables are used for database (e.g. PostGIS) internal working.
      */
-    protected Dialect() {
+    private final Set<String> ignoredTables;
+
+    /**
+     * Creates a new accessor to geospatial functions for the database described by given metadata.
+     */
+    SpatialFunctions(final DatabaseMetaData metadata) throws SQLException {
+        ignoredTables = new HashSet<>(4);
+        /*
+         * The following tables are defined by ISO 19125 / OGC Simple feature access part 2.
+         * Note that the standard specified those names in upper-case letters, which is also
+         * the default case specified by the SQL standard.  However some databases use lower
+         * cases instead.
+         */
+        String crs  = "SPATIAL_REF_SYS";
+        String geom = "GEOMETRY_COLUMNS";
+        if (metadata.storesLowerCaseIdentifiers()) {
+            crs  = crs .toLowerCase(Locale.US).intern();
+            geom = geom.toLowerCase(Locale.US).intern();
+        }
+        ignoredTables.add(crs);
+        ignoredTables.add(geom);
+        final Dialect dialect = Dialect.guess(metadata);
+        if (dialect == Dialect.POSTGRESQL) {
+            ignoredTables.add("geography_columns");     // Postgis 1+
+            ignoredTables.add("raster_columns");        // Postgis 2
+            ignoredTables.add("raster_overviews");
+        }
     }
 
     /**
-     * Indicates whether a table will be used as a {@code FeatureType}.
+     * Indicates whether a table is reserved for the database internal working.
+     * If this method returns {@code false}, then the given table is a candidate
+     * for use as a {@code FeatureType}.
      *
-     * @param  name  database table name.
+     * @param  name  database table name to test.
      * @return {@code true} if the named table should be ignored when looking for feature types.
      */
-    public abstract boolean isTableIgnored(String name);
+    final boolean isIgnoredTable(final String name) {
+        return ignoredTables.contains(name);
+    }
 
     /**
      * Gets the Java class mapped to a given SQL type.
@@ -70,7 +105,7 @@ public abstract class Dialect {
      * @throws SQLException if a JDBC error occurred while executing a statement.
      * @throws DataStoreException if another error occurred while fetching the next value.
      */
-    public abstract Object nextValue(ColumnMetaModel column, Connection cx) throws SQLException, DataStoreException;
+    public abstract Object nextValue(Column column, Connection cx) throws SQLException, DataStoreException;
 
     /**
      * Gets the value sequence name used by a column.
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/TableMetaModel.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Table.java
similarity index 72%
rename from storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/TableMetaModel.java
rename to storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Table.java
index efb4239..ecdf264 100644
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/TableMetaModel.java
+++ b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/Table.java
@@ -18,6 +18,8 @@ package org.apache.sis.internal.sql.feature;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import org.apache.sis.util.Debug;
+import org.apache.sis.util.collection.TreeTable;
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
 
 
@@ -29,7 +31,7 @@ import org.apache.sis.feature.builder.FeatureTypeBuilder;
  * @since   1.0
  * @module
  */
-final class TableMetaModel extends MetaModel {
+final class Table extends MetaModel {
     enum View {
         TABLE,
         SIMPLE_FEATURE_TYPE,
@@ -49,19 +51,19 @@ final class TableMetaModel extends MetaModel {
     /**
      * those are 0:1 relations
      */
-    final Collection<RelationMetaModel> importedKeys = new ArrayList<>();
+    final Collection<Relation> importedKeys = new ArrayList<>();
 
     /**
      * those are 0:N relations
      */
-    final Collection<RelationMetaModel> exportedKeys = new ArrayList<>();
+    final Collection<Relation> exportedKeys = new ArrayList<>();
 
     /**
      * inherited tables
      */
     final Collection<String> parents = new ArrayList<>();
 
-    TableMetaModel(final String name, final String type) {
+    Table(final String name, final String type) {
         super(name);
         this.type = type;
     }
@@ -78,7 +80,7 @@ final class TableMetaModel extends MetaModel {
      * @todo a subtype of what?
      */
     boolean isSubType() {
-        for (RelationMetaModel relation : importedKeys) {
+        for (Relation relation : importedKeys) {
             if (relation.cascadeOnDelete) {
                 return true;
             }
@@ -97,20 +99,16 @@ final class TableMetaModel extends MetaModel {
     }
 
     /**
-     * Returns a string representation of this schema for debugging purposes.
+     * Creates a tree representation of this object for debugging purpose.
+     *
+     * @param  parent  the parent node where to add the tree representation.
      */
+    @Debug
     @Override
-    public String toString() {
-        final String lineSeparator = System.lineSeparator();
-        final StringBuilder sb = new StringBuilder(100).append(name);
-        if (!importedKeys.isEmpty()) {
-            appendTree(" Imported Keys", importedKeys, sb.append(lineSeparator), lineSeparator);
-            sb.append(lineSeparator);
-        }
-        if (!exportedKeys.isEmpty()) {
-            appendTree(" Exported Keys", exportedKeys, sb.append(lineSeparator), lineSeparator);
-            sb.append(lineSeparator);
-        }
-        return sb.toString();
+    TreeTable.Node appendTo(final TreeTable.Node parent) {
+        final TreeTable.Node node = super.appendTo(parent);
+        appendAll(parent, "Imported Keys", importedKeys);
+        appendAll(parent, "Exported Keys", exportedKeys);
+        return node;
     }
 }
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/package-info.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/package-info.java
index 4b8369b..80a2211 100644
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/package-info.java
+++ b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/package-info.java
@@ -17,7 +17,8 @@
 
 
 /**
- * A set of helper classes for the SIS implementation of SQL data stores.
+ * Build {@link org.opengis.feature.FeatureType}s by inspection of a database schema.
+ * The work done here is similar to reverse engineering.
  *
  * <STRONG>Do not use!</STRONG>
  *
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/postgres/PostgresDialect.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/postgres/PostgresDialect.java
deleted file mode 100644
index f18fee2..0000000
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/postgres/PostgresDialect.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.internal.sql.postgres;
-
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.HashSet;
-import java.util.Set;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.internal.sql.feature.ColumnMetaModel;
-import org.apache.sis.internal.sql.feature.Dialect;
-import org.apache.sis.feature.builder.AttributeTypeBuilder;
-import org.apache.sis.storage.DataStoreException;
-
-
-/**
- * Implements PostgreSQL-specific functionalities.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-final class PostgresDialect extends Dialect {
-
-    private static final Set<String> IGNORE_TABLES = new HashSet<>(8);
-    static {
-        // Postgis 1+ geometry and referencing
-        IGNORE_TABLES.add("spatial_ref_sys");
-        IGNORE_TABLES.add("geometry_columns");
-        IGNORE_TABLES.add("geography_columns");
-        // Postgis 2 raster
-        IGNORE_TABLES.add("raster_columns");
-        IGNORE_TABLES.add("raster_overviews");
-    }
-
-    @Override
-    public boolean isTableIgnored(String name) {
-        return IGNORE_TABLES.contains(name.toLowerCase());
-    }
-
-    @Override
-    public Class<?> getJavaType(int sqlType, String sqlTypeName) {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    @Override
-    public Object nextValue(ColumnMetaModel column, Connection cx) throws SQLException, DataStoreException {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    @Override
-    public String getColumnSequence(Connection cx, String schemaName, String tableName, String columnName) throws SQLException {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    @Override
-    public void decodeColumnType(AttributeTypeBuilder<?> atb, Connection cx, String typeName, int datatype, String schemaName, String tableName, String columnName) throws SQLException {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    @Override
-    public void decodeGeometryColumnType(AttributeTypeBuilder<?> atb, Connection cx, ResultSet rs, int columnIndex, boolean customquery) throws SQLException {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    @Override
-    public CoordinateReferenceSystem createGeometryCRS(ResultSet reflect) throws SQLException {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-}
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/postgres/PostgresStore.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/postgres/PostgresStore.java
deleted file mode 100644
index 5174621..0000000
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/postgres/PostgresStore.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.internal.sql.postgres;
-
-import org.opengis.metadata.Metadata;
-import org.opengis.parameter.ParameterValueGroup;
-import org.apache.sis.storage.sql.SQLStore;
-import org.apache.sis.storage.sql.SQLQuery;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.event.ChangeEvent;
-import org.apache.sis.storage.event.ChangeListener;
-import org.apache.sis.internal.sql.feature.DataBaseModel;
-import org.apache.sis.internal.sql.feature.QueryFeatureSet;
-import org.apache.sis.storage.FeatureSet;
-import org.apache.sis.storage.StorageConnector;
-
-
-/**
- * A data store backed by a PostgreSQL database.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-final class PostgresStore extends SQLStore {
-    private final PostgresDialect dialect = new PostgresDialect();
-
-    private final String schema;
-    private final String table;
-
-    private final DataBaseModel model;
-
-    public PostgresStore(final PostgresStoreProvider provider, final StorageConnector connector,
-            final String schema, final String table) throws DataStoreException
-    {
-        super(provider, connector);
-        this.schema = schema;
-        this.table  = table;
-        this.model  = new DataBaseModel(this, dialect, schema, table, listeners);
-    }
-
-    @Override
-    public ParameterValueGroup getOpenParameters() {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    @Override
-    public Metadata getMetadata() throws DataStoreException {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    /**
-     * Executes a query directly on the database.
-     *
-     * @param  query the query to execute (can not be null).
-     * @return the features obtained by the given given query.
-     */
-    public FeatureSet query(SQLQuery query) {
-        return new QueryFeatureSet(this, model, query);
-    }
-
-    @Override
-    public void close() throws DataStoreException {
-    }
-
-    @Override
-    public <T extends ChangeEvent> void addListener(ChangeListener<? super T> listener, Class<T> eventType) {
-    }
-
-    @Override
-    public <T extends ChangeEvent> void removeListener(ChangeListener<? super T> listener, Class<T> eventType) {
-    }
-}
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/postgres/PostgresStoreProvider.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/postgres/PostgresStoreProvider.java
deleted file mode 100644
index d88e644..0000000
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/postgres/PostgresStoreProvider.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.internal.sql.postgres;
-
-import org.apache.sis.storage.sql.SQLStoreProvider;
-import org.apache.sis.storage.DataStore;
-import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.ProbeResult;
-import org.apache.sis.storage.StorageConnector;
-import org.opengis.parameter.ParameterDescriptorGroup;
-
-
-/**
- * The provider of {@link PostgresStore}.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- *
- * @todo We should not have specialized data store provider for PostgreSQL.
- *       Instead we should detect from the data source or the connection.
- */
-final class PostgresStoreProvider extends SQLStoreProvider {
-    /**
-     * Name of the data store.
-     */
-    private static final String NAME = "PostgreSQL";
-
-    /**
-     * Creates a new provider.
-     */
-    public PostgresStoreProvider() {
-    }
-
-    /**
-     * Returns a short name for the data store, which is {@value #NAME}.
-     *
-     * @return {@value #NAME}.
-     */
-    @Override
-    public String getShortName() {
-        return NAME;
-    }
-
-    @Override
-    public ParameterDescriptorGroup getOpenParameters() {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    @Override
-    public ProbeResult probeContent(StorageConnector connector) throws DataStoreException {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    @Override
-    public DataStore open(StorageConnector connector) throws DataStoreException {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-}
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/storage/sql/SQLStore.java b/storage/sis-sql/src/main/java/org/apache/sis/storage/sql/SQLStore.java
index 991c4b1..9eaaae7 100644
--- a/storage/sis-sql/src/main/java/org/apache/sis/storage/sql/SQLStore.java
+++ b/storage/sis-sql/src/main/java/org/apache/sis/storage/sql/SQLStore.java
@@ -17,6 +17,8 @@
 package org.apache.sis.storage.sql;
 
 import javax.sql.DataSource;
+import org.apache.sis.internal.sql.feature.Database;
+import org.apache.sis.internal.sql.feature.QueryFeatureSet;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureSet;
@@ -24,7 +26,8 @@ import org.apache.sis.storage.StorageConnector;
 
 
 /**
- * A data store capable to read and create features from a database.
+ * A data store capable to read and create features from a spatial database.
+ * An example of spatial database is PostGIS.
  *
  * <div class="warning">This is an experimental class,
  * not yet target for any Apache SIS release at this time.</div>
@@ -41,9 +44,14 @@ public abstract class SQLStore extends DataStore {
     private final DataSource source;
 
     /**
+     * The result of inspecting database schema for deriving {@link org.opengis.feature.FeatureType}s.
+     * Created when first needed. May be discarded and recreated if the store needs a refresh.
+     */
+    private Database model;
+
+    /**
      * Creates a new instance for the given storage.
-     * The {@code provider} argument is an optional information.
-     * The {@code connector} argument is mandatory.
+     * The given {@code connector} shall contain a {@link DataSource}.
      *
      * @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>).
@@ -69,5 +77,7 @@ public abstract class SQLStore extends DataStore {
      * @param  query the query to execute (can not be null).
      * @return the features obtained by the given given query.
      */
-    public abstract FeatureSet query(SQLQuery query);
+    public FeatureSet query(final SQLQuery query) {
+        return new QueryFeatureSet(this, model, query);
+    }
 }


Mime
View raw message