sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/03: Begins a review of storage/sis-sql module. The constants by database reflection API moved to sis-metadata internal package, for sharing by other classes doing similar operations.
Date Wed, 04 Jul 2018 16:08:15 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

commit e8631df70bae6656aaa89c5b7e5ff832dfd6cde7
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Jul 4 16:53:40 2018 +0200

    Begins a review of storage/sis-sql module. The constants by database reflection API moved to sis-metadata internal package, for sharing by other classes doing similar operations.
---
 .../sis/internal/metadata/sql/Reflection.java      | 185 ++++++
 .../sis/internal/metadata/sql/SQLBuilder.java      |  16 +-
 .../sis/internal/sql/feature/ColumnMetaModel.java  |  14 +-
 .../sis/internal/sql/feature/DataBaseModel.java    | 620 ++++++++++-----------
 .../apache/sis/internal/sql/feature/Dialect.java   |  32 +-
 .../internal/sql/feature/MetaDataConstants.java    | 515 -----------------
 .../sql/feature/SingleAttributeTypeBuilder.java    | 260 ---------
 .../sis/internal/sql/postgres/PostgresDialect.java |  25 +-
 8 files changed, 494 insertions(+), 1173 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Reflection.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Reflection.java
new file mode 100644
index 0000000..45f21b2
--- /dev/null
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/Reflection.java
@@ -0,0 +1,185 @@
+/*
+ * 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.metadata.sql;
+
+import java.sql.DatabaseMetaData;
+
+
+/**
+ * Column names used in database reflection API. Reflection provides information about schemas, tables, columns,
+ * constraints, <i>etc.</i> in the form of database tables. The main JDBC methods for those reflections are:
+ *
+ * <ul>
+ *   <li>{@link DatabaseMetaData#getSchemas()}</li>
+ *   <li>{@link DatabaseMetaData#getTables(String, String, String, String[])}</li>
+ *   <li>{@link DatabaseMetaData#getColumns(String, String, String, String)}</li>
+ * </ul>
+ *
+ * This class enumerates all the constants used by Apache SIS, and only those constants (this give a way to have
+ * an overview of which database metadata are needed by SIS). Unless specified otherwise, the columns with those
+ * names contain only {@code String} values. The main exceptions are {@link #DATA_TYPE}, {@link #COLUMN_SIZE} and
+ * {@link #DELETE_RULE}, which contain integers.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public final class Reflection {
+    /**
+     * The {@value} key for getting a schema name. This column appears in all reflection
+     * operations (listing schemas, tables, columns, constraints, <i>etc.</i>) used by SIS.
+     * The value in that column may be null.
+     */
+    public static final String TABLE_SCHEM = "TABLE_SCHEM";
+
+    /**
+     * The {@value} key for getting a table name. This column appears in most reflection
+     * operations (listing tables, columns, constraints, <i>etc.</i>) used by SIS.
+     */
+    public static final String TABLE_NAME = "TABLE_NAME";
+
+    /**
+     * The {@value} key for getting a table type. The values that may appear in this column
+     * are listed by {@link DatabaseMetaData#getTableTypes()}. Typical values are "TABLE",
+     * "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM".
+     */
+    public static final String TABLE_TYPE = "TABLE_TYPE";
+
+    /**
+     * The {@value} key for getting a column name.
+     */
+    public static final String COLUMN_NAME = "COLUMN_NAME";
+
+    /**
+     * The {@value} key for getting the data type as one of {@link java.sql.Types} constants.
+     *
+     * <p>Values in this column are integers ({@code int}) rather than {@code String}.</p>
+     */
+    public static final String DATA_TYPE = "DATA_TYPE";
+
+    /**
+     * Data source dependent type name. For a UDT the type name is fully qualified.
+     */
+    public static final String TYPE_NAME = "TYPE_NAME";
+
+    /**
+     * The {@value} key for the size for of a column. For numeric data, this is the maximum precision.
+     * For character data, this is the length in characters.
+     *
+     * <p>Values in this column are integers ({@code int}) rather than {@code String}.</p>
+     */
+    public static final String COLUMN_SIZE = "COLUMN_SIZE";
+
+    /**
+     * The {@value} key for the number of fractional digits.
+     * Null is returned for data types where {@code DECIMAL_DIGITS} is not applicable.
+     *
+     * <p>Values in this column are integers ({@code int}) rather than {@code String}.</p>
+     */
+    public static final String DECIMAL_DIGITS = "DECIMAL_DIGITS";
+
+    /**
+     * The {@value} key for the nullability of a column. Possible values are {@code "YES"} if
+     * the parameter can include NULLs, {@code "NO"} if the parameter cannot include NULLs,
+     * and empty string if the nullability for the parameter is unknown.
+     */
+    public static final String IS_NULLABLE = "IS_NULLABLE";
+
+    /**
+     * The {@value} key for indicating whether this column is auto incremented. Possible values
+     * are {@code "YES"} if the column is auto incremented, {@code "NO"} if the column is not
+     * auto incremented, or empty string if whether the column is auto incremented is unknown.
+     */
+    public static final String IS_AUTOINCREMENT = "IS_AUTOINCREMENT";
+
+    /**
+     * The {@value} key for comment describing columns.
+     * Values in this column may be null.
+     */
+    public static final String REMARKS = "REMARKS";
+
+    /**
+     * The {@value} key for primary key name.
+     * Values in this column may be null.
+     */
+    public static final String PK_NAME = "PK_NAME";
+
+    /**
+     * The {@value} key for the primary key table schema being imported.
+     * Values in this column may be null.
+     */
+    public static final String PKTABLE_SCHEM = "PKTABLE_SCHEM";
+
+    /**
+     * The {@value} key for the primary key table name being imported.
+     */
+    public static final String PKTABLE_NAME = "PKTABLE_NAME";
+
+    /**
+     * The {@value} key for the primary key column name being imported.
+     */
+    public static final String PKCOLUMN_NAME = "PKCOLUMN_NAME";
+
+    /**
+     * The {@value} key for foreign key name.
+     * Values in this column may be null.
+     */
+    public static final String FK_NAME = "FK_NAME";
+
+    /**
+     * The {@value} key for the foreign key table schema.
+     * Values in this column may be null.
+     */
+    public static final String FKTABLE_SCHEM = "FKTABLE_SCHEM";
+
+    /**
+     * The {@value} key for the foreign key table name.
+     */
+    public static final String FKTABLE_NAME = "FKTABLE_NAME";
+
+    /**
+     * The {@value} key for the foreign key column name.
+     */
+    public static final String FKCOLUMN_NAME = "FKCOLUMN_NAME";
+
+    /**
+     * The {@value} key for what happens to the foreign key when primary is deleted.
+     * Possible values are:
+     * <ul>
+     *   <li>{@code importedKeyNoAction}   — do not allow delete of primary key if it has been imported.</li>
+     *   <li>{@code importedKeyCascade}    — delete rows that import a deleted key.</li>
+     *   <li>{@code importedKeySetNull}    — change imported key to NULL if its primary key has been deleted.</li>
+     *   <li>{@code importedKeySetDefault} — change imported key to default if its primary key has been deleted.</li>
+     * </ul>
+     *
+     * <p>Values in this column are short integers ({@code short}) rather than {@code String}.</p>
+     */
+    public static final String DELETE_RULE = "DELETE_RULE";
+
+    /**
+     * The {@value} key for the name of the index.
+     * Values in this column may be null.
+     */
+    public static final String INDEX_NAME = "INDEX_NAME";
+
+    /**
+     * Do not allow instantiation of this class.
+     */
+    private Reflection() {
+    }
+}
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java
index 56661c5..adc0352 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/sql/SQLBuilder.java
@@ -46,6 +46,14 @@ public class SQLBuilder {
     private final String quote;
 
     /**
+     * Whether the schema name should be written between quotes. If {@code false},
+     * we will let the database engine uses its default lower case / upper case policy.
+     *
+     * @see #appendIdentifier(String, String)
+     */
+    private final boolean quoteSchema;
+
+    /**
      * The string that can be used to escape wildcard characters.
      * This is the value returned by {@link DatabaseMetaData#getSearchStringEscape()}.
      */
@@ -57,14 +65,6 @@ public class SQLBuilder {
     private final StringBuilder buffer = new StringBuilder();
 
     /**
-     * Whether the schema name should be written between quotes. If {@code false},
-     * we will let the database engine uses its default lower case / upper case policy.
-     *
-     * @see #appendIdentifier(String, String)
-     */
-    private final boolean quoteSchema;
-
-    /**
      * Creates a new {@code SQLBuilder} initialized from the given database metadata.
      *
      * @param  metadata     the database metadata.
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/ColumnMetaModel.java
index 6ff5453..9b86ac3 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/ColumnMetaModel.java
@@ -25,6 +25,7 @@ import java.util.UUID;
 import org.opengis.feature.AttributeType;
 import org.apache.sis.feature.DefaultAttributeType;
 import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.internal.metadata.sql.SQLBuilder;
 
 
 /**
@@ -161,13 +162,14 @@ public final class ColumnMetaModel {
             // Generate value if possible.
             if (Number.class.isAssignableFrom(clazz)) {
                 // Get the maximum value in the database and increment it
-                final StringBuilder sql = new StringBuilder();
-                sql.append("SELECT 1 + MAX(");
-                dialect.encodeColumnName(sql, name);
-                sql.append(") FROM ");
-                dialect.encodeSchemaAndTableName(sql, schema, table);
+                final String sql = new SQLBuilder(cx.getMetaData(), true)
+                        .append("SELECT 1 + MAX(")
+                        .appendIdentifier(name)
+                        .append(") FROM ")
+                        .appendIdentifier(schema, table)
+                        .toString();
                 try (Statement st = cx.createStatement();
-                    ResultSet rs = st.executeQuery(sql.toString())) {
+                    ResultSet rs = st.executeQuery(sql)) {
                     rs.next();
                     next = rs.getObject(1);
                 }
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/DataBaseModel.java
index 5f73ec4..4e3d86d 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/DataBaseModel.java
@@ -44,8 +44,8 @@ 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.Reflection;
 import org.apache.sis.internal.feature.Geometries;
-import org.apache.sis.internal.sql.feature.MetaDataConstants.*;
 import org.apache.sis.storage.sql.SQLStore;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
@@ -63,17 +63,11 @@ import org.apache.sis.util.logging.WarningListeners;
  * @module
  */
 public final class DataBaseModel {
-    /**
-     * @todo Does not seem to be used yet.
-     */
-    private static final String ASSOCIATION_SEPARATOR = "→";
 
-    /**
-     * The native SRID associated to a certain descriptor
-     *
-     * @todo Does not seem to be used yet.
-     */
-    private static final String JDBC_NATIVE_SRID = "nativeSRID";
+    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";
 
     /**
      * Feature type used to mark types which are sub-types of others.
@@ -99,7 +93,6 @@ public final class DataBaseModel {
 
     //various cache while analyzing model
     private DatabaseMetaData metadata;
-    private CachedResultSet cacheSchemas;
     private CachedResultSet cacheTables;
     private CachedResultSet cacheColumns;
     private CachedResultSet cachePrimaryKeys;
@@ -135,8 +128,7 @@ public final class DataBaseModel {
     }
 
     /**
-     * Clear the model cache. A new database analyze will be made the next time
-     * it is needed.
+     * Clear the model cache. A new database analyze will be made the next time it is needed.
      */
     private synchronized void clearCache() {
         pkIndex   = new FeatureNaming<>();
@@ -191,87 +183,59 @@ public final class DataBaseModel {
         requieredSchemas = new HashSet<>();
 
         try (Connection cx = store.getDataSource().getConnection()) {
-
             metadata = cx.getMetaData();
-
-            // Cache all metadata informations, we will loop on them plenty of times ////////
-            cacheSchemas = new CachedResultSet(metadata.getSchemas(),
-                    Schema.TABLE_SCHEM);
+            /*
+             * Schema names available in the database:
+             * 1. TABLE_SCHEM   : String  =>  schema name
+             * 2. TABLE_CATALOG : String  =>  catalog name (may be null)
+             */
+            final CachedResultSet cacheSchemas = new CachedResultSet(metadata.getSchemas(),
+                    Reflection.TABLE_SCHEM);
+            /*
+             * 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").
+             */
             cacheTables = new CachedResultSet(
-                    metadata.getTables(null,null,null,new String[]{Table.VALUE_TYPE_TABLE, Table.VALUE_TYPE_VIEW}),
-                    Table.TABLE_SCHEM,
-                    Table.TABLE_NAME,
-                    Table.TABLE_TYPE);
-            cacheColumns = new CachedResultSet(metadata.getColumns(null, null, null, "%"),
-                    Column.TABLE_SCHEM,
-                    Column.TABLE_NAME,
-                    Column.COLUMN_NAME,
-                    Column.COLUMN_SIZE,
-                    Column.DATA_TYPE,
-                    Column.TYPE_NAME,
-                    Column.IS_NULLABLE,
-                    Column.IS_AUTOINCREMENT,
-                    Column.REMARKS);
-            if (dialect.supportGlobalMetadata()) {
-                cachePrimaryKeys = new CachedResultSet(metadata.getPrimaryKeys(null, null, null),
-                        Column.TABLE_SCHEM,
-                        Column.TABLE_NAME,
-                        Column.COLUMN_NAME);
-                cacheImportedKeys = new CachedResultSet(metadata.getImportedKeys(null, null, null),
-                        ImportedKey.PK_NAME,
-                        ImportedKey.FK_NAME,
-                        ImportedKey.FKTABLE_SCHEM,
-                        ImportedKey.FKTABLE_NAME,
-                        ImportedKey.FKCOLUMN_NAME,
-                        ImportedKey.PKTABLE_SCHEM,
-                        ImportedKey.PKTABLE_NAME,
-                        ImportedKey.PKCOLUMN_NAME,
-                        ImportedKey.DELETE_RULE);
-                cacheExportedKeys = new CachedResultSet(metadata.getExportedKeys(null, null, null),
-                        ExportedKey.PK_NAME,
-                        ExportedKey.FK_NAME,
-                        ExportedKey.PKTABLE_SCHEM,
-                        ExportedKey.PKTABLE_NAME,
-                        ExportedKey.PKCOLUMN_NAME,
-                        ExportedKey.FKTABLE_SCHEM,
-                        ExportedKey.FKTABLE_NAME,
-                        ExportedKey.FKCOLUMN_NAME,
-                        ExportedKey.DELETE_RULE);
-            } else {
-                //we have to loop ourself on all schema and tables to collect informations
-                cachePrimaryKeys = new CachedResultSet();
-                cacheImportedKeys = new CachedResultSet();
-                cacheExportedKeys = new CachedResultSet();
-
-                final Iterator<Map<String,Object>> ite = cacheSchemas.records.iterator();
-                while (ite.hasNext()) {
-                    final String schemaName = (String) ite.next().get(Schema.TABLE_SCHEM);
-                    for (Map<String,Object> info : cacheTables.records) {
-                        if (!Objects.equals(info.get(Table.TABLE_SCHEM),schemaName)) continue;
-                        cachePrimaryKeys.append(metadata.getPrimaryKeys(null, schemaName, (String) info.get(Table.TABLE_NAME)),
-                            Column.TABLE_SCHEM,
-                            Column.TABLE_NAME,
-                            Column.COLUMN_NAME);
-                        cacheImportedKeys.append(metadata.getImportedKeys(null, schemaName, (String) info.get(Table.TABLE_NAME)),
-                            ImportedKey.FKTABLE_SCHEM,
-                            ImportedKey.FKTABLE_NAME,
-                            ImportedKey.FKCOLUMN_NAME,
-                            ImportedKey.PKTABLE_SCHEM,
-                            ImportedKey.PKTABLE_NAME,
-                            ImportedKey.PKCOLUMN_NAME,
-                            ImportedKey.DELETE_RULE);
-                        cacheExportedKeys.append(metadata.getExportedKeys(null, schemaName, (String) info.get(Table.TABLE_NAME)),
-                            ImportedKey.PKTABLE_SCHEM,
-                            ImportedKey.PKTABLE_NAME,
-                            ExportedKey.PKCOLUMN_NAME,
-                            ExportedKey.FKTABLE_SCHEM,
-                            ExportedKey.FKTABLE_NAME,
-                            ExportedKey.FKCOLUMN_NAME,
-                            ImportedKey.DELETE_RULE);
-                    }
-                }
-            }
-
+                    metadata.getTables(null, null, null, new String[] {TYPE_TABLE, TYPE_VIEW}),   // TODO: use metadata.getTableTypes()
+                    Reflection.TABLE_SCHEM,
+                    Reflection.TABLE_NAME,
+                    Reflection.TABLE_TYPE);
+            cacheColumns = new CachedResultSet(metadata.getColumns(null, null, null, null),
+                    Reflection.TABLE_SCHEM,
+                    Reflection.TABLE_NAME,
+                    Reflection.COLUMN_NAME,
+                    Reflection.COLUMN_SIZE,
+                    Reflection.DATA_TYPE,
+                    Reflection.TYPE_NAME,
+                    Reflection.IS_NULLABLE,
+                    Reflection.IS_AUTOINCREMENT,
+                    Reflection.REMARKS);
+            cachePrimaryKeys = new CachedResultSet(metadata.getPrimaryKeys(null, null, null),
+                    Reflection.TABLE_SCHEM,
+                    Reflection.TABLE_NAME,
+                    Reflection.COLUMN_NAME);
+            cacheImportedKeys = new CachedResultSet(metadata.getImportedKeys(null, null, null),
+                    Reflection.PK_NAME,
+                    Reflection.FK_NAME,
+                    Reflection.FKTABLE_SCHEM,
+                    Reflection.FKTABLE_NAME,
+                    Reflection.FKCOLUMN_NAME,
+                    Reflection.PKTABLE_SCHEM,
+                    Reflection.PKTABLE_NAME,
+                    Reflection.PKCOLUMN_NAME,
+                    Reflection.DELETE_RULE);
+            cacheExportedKeys = new CachedResultSet(metadata.getExportedKeys(null, null, null),
+                    Reflection.PK_NAME,
+                    Reflection.FK_NAME,
+                    Reflection.PKTABLE_SCHEM,
+                    Reflection.PKTABLE_NAME,
+                    Reflection.PKCOLUMN_NAME,
+                    Reflection.FKTABLE_SCHEM,
+                    Reflection.FKTABLE_NAME,
+                    Reflection.FKCOLUMN_NAME,
+                    Reflection.DELETE_RULE);
 
             ////////////////////////////////////////////////////////////////////////////////
 
@@ -280,7 +244,7 @@ public final class DataBaseModel {
             } else {
                 final Iterator<Map<String,Object>> ite = cacheSchemas.records.iterator();
                 while (ite.hasNext()) {
-                    requieredSchemas.add((String) ite.next().get(Schema.TABLE_SCHEM));
+                    requieredSchemas.add((String) ite.next().get(Reflection.TABLE_SCHEM));
                 }
             }
 
@@ -298,7 +262,6 @@ public final class DataBaseModel {
         } catch (SQLException e) {
             throw new DataStoreException("Error occurred analyzing database model.\n" + e.getMessage(), e);
         } finally {
-            cacheSchemas = null;
             cacheTables = null;
             cacheColumns = null;
             cachePrimaryKeys = null;
@@ -343,279 +306,267 @@ public final class DataBaseModel {
 
     }
 
-    private SchemaMetaModel analyzeSchema(final String schemaName, final Connection cx) throws DataStoreException {
+    private SchemaMetaModel analyzeSchema(final String schemaName, final Connection cx) throws SQLException {
         final SchemaMetaModel schema = new SchemaMetaModel(schemaName);
-        try {
-            for (Map<String,Object> info : cacheTables.records) {
-                if (!Objects.equals(info.get(Table.TABLE_SCHEM), schemaName)) continue;
-                if (databaseTable != null && !databaseTable.isEmpty() && !Objects.equals(info.get(Table.TABLE_NAME),databaseTable)) continue;
-                final TableMetaModel table = analyzeTable(info,cx);
-                schema.tables.put(table.name, table);
-            }
-        } catch (SQLException e) {
-            throw new DataStoreException("Error occurred analyzing database model.", e);
+        for (Map<String,Object> info : cacheTables.records) {
+            if (!Objects.equals(info.get(Reflection.TABLE_SCHEM), schemaName)) continue;
+            if (databaseTable != null && !databaseTable.isEmpty() && !Objects.equals(info.get(Reflection.TABLE_NAME), databaseTable)) continue;
+            final TableMetaModel table = analyzeTable(info, cx);
+            schema.tables.put(table.name, table);
         }
         return schema;
     }
 
-    private TableMetaModel analyzeTable(final Map tableSet, final Connection cx) throws DataStoreException, SQLException {
-        final String schemaName = (String) tableSet.get(Table.TABLE_SCHEM);
-        final String tableName  = (String) tableSet.get(Table.TABLE_NAME);
-        final String tableType  = (String) tableSet.get(Table.TABLE_TYPE);
-
+    private TableMetaModel analyzeTable(final Map<String,Object> tableSet, final Connection cx) throws SQLException {
+        final String schemaName = (String) tableSet.get(Reflection.TABLE_SCHEM);
+        final String tableName  = (String) tableSet.get(Reflection.TABLE_NAME);
+        final String tableType  = (String) tableSet.get(Reflection.TABLE_TYPE);
         final TableMetaModel table = new TableMetaModel(tableName,tableType);
-
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
-        try {
+        // Explore all columns ----------------------------------------------
+        final Predicate<Map<String,Object>> tableFilter = (Map<String,Object> info) -> {
+            return Objects.equals(info.get(Reflection.TABLE_SCHEM), schemaName)
+                && Objects.equals(info.get(Reflection.TABLE_NAME), tableName);
+        };
+
+        final Iterator<Map<String,Object>> ite1 = cacheColumns.records.stream().filter(tableFilter).iterator();
+        while (ite1.hasNext()) {
+            analyzeColumn(ite1.next(), cx, ftb.addAttribute(Object.class));
+        }
+
+        // Find primary key -------------------------------------------------
+        final List<ColumnMetaModel> cols = new ArrayList<>();
+        final Iterator<Map<String,Object>> pkIte = cachePrimaryKeys.records.stream().filter(tableFilter).iterator();
+        while (pkIte.hasNext()) {
+            final Map<String,Object> result = pkIte.next();
+            final String columnName = (String) result.get(Reflection.COLUMN_NAME);
 
-            // Explore all columns ----------------------------------------------
-            final Predicate<Map<String,Object>> tableFilter = (Map<String,Object> info) -> {
-                return Objects.equals(info.get(Table.TABLE_SCHEM), schemaName)
-                    && Objects.equals(info.get(Table.TABLE_NAME), tableName);
+            final Predicate<Map<String,Object>> colFilter = (Map<String,Object> info) -> {
+                return Objects.equals(info.get(Reflection.COLUMN_NAME), columnName);
             };
+            final Iterator<Map<String,Object>> cite = cacheColumns.records.stream().filter(tableFilter.and(colFilter)).iterator();
+            final Map<String,Object> column = cite.next();
 
-            final Iterator<Map<String,Object>> ite1 = cacheColumns.records.stream().filter(tableFilter).iterator();
-            while (ite1.hasNext()) {
-                ftb.addAttribute(analyzeColumn(ite1.next(),cx));
-            }
+            final int sqlType = ((Number) column.get(Reflection.DATA_TYPE)).intValue();
+            final String sqlTypeName = (String) column.get(Reflection.TYPE_NAME);
+            Class<?> columnType = dialect.getJavaType(sqlType, sqlTypeName);
 
-            // Find primary key -------------------------------------------------
-            final List<ColumnMetaModel> cols = new ArrayList<>();
-            final Iterator<Map<String,Object>> pkIte = cachePrimaryKeys.records.stream().filter(tableFilter).iterator();
-            while (pkIte.hasNext()) {
-                final Map<String,Object> result = pkIte.next();
-                final String columnName = (String) result.get(Column.COLUMN_NAME);
-
-                final Predicate<Map<String,Object>> colFilter = (Map<String,Object> info) -> {
-                    return Objects.equals(info.get(Column.COLUMN_NAME), columnName);
-                };
-                final Iterator<Map<String,Object>> cite = cacheColumns.records.stream().filter(tableFilter.and(colFilter)).iterator();
-                final Map<String,Object> column = cite.next();
-
-                final int sqlType = ((Number) column.get(Column.DATA_TYPE)).intValue();
-                final String sqlTypeName = (String) column.get(Column.TYPE_NAME);
-                Class<?> columnType = dialect.getJavaType(sqlType, sqlTypeName);
-
-                if (columnType == null) {
-                    listeners.warning("No class for SQL type " + sqlType, null);
-                    columnType = Object.class;
-                }
+            if (columnType == null) {
+                listeners.warning("No class for SQL type " + sqlType, null);
+                columnType = Object.class;
+            }
 
-                ColumnMetaModel col = null;
+            ColumnMetaModel col = null;
 
-                final String str = (String) column.get(Column.IS_AUTOINCREMENT);
-                if (Column.VALUE_YES.equalsIgnoreCase(str)) {
-                    col = new ColumnMetaModel(schemaName, tableName, columnName, sqlType, sqlTypeName, columnType, ColumnMetaModel.Type.AUTO, null);
+            final String str = (String) column.get(Reflection.IS_AUTOINCREMENT);
+            if (VALUE_YES.equalsIgnoreCase(str)) {
+                col = new ColumnMetaModel(schemaName, tableName, columnName, sqlType, sqlTypeName, columnType, ColumnMetaModel.Type.AUTO, null);
+            } else {
+                // TODO: need to distinguish "NO" and empty string.
+                final String sequenceName = dialect.getColumnSequence(cx,schemaName, tableName, columnName);
+                if (sequenceName != null) {
+                    col = new ColumnMetaModel(schemaName, tableName, columnName, sqlType,
+                            sqlTypeName, columnType, ColumnMetaModel.Type.SEQUENCED,sequenceName);
                 } else {
-                    final String sequenceName = dialect.getColumnSequence(cx,schemaName, tableName, columnName);
-                    if (sequenceName != null) {
-                        col = new ColumnMetaModel(schemaName, tableName, columnName, sqlType,
-                                sqlTypeName, columnType, ColumnMetaModel.Type.SEQUENCED,sequenceName);
-                    } else {
-                        col = new ColumnMetaModel(schemaName, tableName, columnName, sqlType,
-                                sqlTypeName, columnType, ColumnMetaModel.Type.PROVIDED, null);
-                    }
+                    col = new ColumnMetaModel(schemaName, tableName, columnName, sqlType,
+                            sqlTypeName, columnType, ColumnMetaModel.Type.PROVIDED, null);
                 }
-                cols.add(col);
             }
-            /*
-             * Search indexes, they provide informations such as:
-             * - Unique indexes may indicate 1:1 relations in complexe features
-             * - Unique indexes can be used as primary key if no primary key are defined
-             */
-            final boolean pkEmpty = cols.isEmpty();
-            final List<String> names = new ArrayList<>();
-            final Map<String,List<String>> uniqueIndexes = new HashMap<>();
-            String indexname = null;
-            // We can't cache this one, seems to be a bug in the driver, it won't find anything for table name like '%'
-            cacheIndexInfos = new CachedResultSet(metadata.getIndexInfo(null, schemaName, tableName, true, false),
-                    Index.TABLE_SCHEM,
-                    Index.TABLE_NAME,
-                    Index.COLUMN_NAME,
-                    Index.INDEX_NAME);
-            final Iterator<Map<String,Object>> indexIte = cacheIndexInfos.records.stream().filter(tableFilter).iterator();
-            while (indexIte.hasNext()) {
-                final Map<String,Object> result = indexIte.next();
-                final String columnName = (String) result.get(Index.COLUMN_NAME);
-                final String idxName = (String) result.get(Index.INDEX_NAME);
-
-                List<String> lst = uniqueIndexes.get(idxName);
-                if (lst == null) {
-                    lst = new ArrayList<>();
-                    uniqueIndexes.put(idxName, lst);
-                }
-                lst.add(columnName);
-
-                if (pkEmpty) {
-                    // We use a single index columns set as primary key
-                    // We must not mix with other potential indexes.
-                    if (indexname == null) {
-                        indexname = idxName;
-                    } else if (!indexname.equals(idxName)) {
-                        continue;
-                    }
-                    names.add(columnName);
-                }
+            cols.add(col);
+        }
+        /*
+         * Search indexes, they provide informations such as:
+         * - Unique indexes may indicate 1:1 relations in complexe features
+         * - Unique indexes can be used as primary key if no primary key are defined
+         */
+        final boolean pkEmpty = cols.isEmpty();
+        final List<String> names = new ArrayList<>();
+        final Map<String,List<String>> uniqueIndexes = new HashMap<>();
+        String indexname = null;
+        // We can't cache this one, seems to be a bug in the driver, it won't find anything for table name like '%'
+        cacheIndexInfos = new CachedResultSet(metadata.getIndexInfo(null, schemaName, tableName, true, false),
+                Reflection.TABLE_SCHEM,
+                Reflection.TABLE_NAME,
+                Reflection.COLUMN_NAME,
+                Reflection.INDEX_NAME);
+        final Iterator<Map<String,Object>> indexIte = cacheIndexInfos.records.stream().filter(tableFilter).iterator();
+        while (indexIte.hasNext()) {
+            final Map<String,Object> result = indexIte.next();
+            final String columnName = (String) result.get(Reflection.COLUMN_NAME);
+            final String idxName = (String) result.get(Reflection.INDEX_NAME);
+
+            List<String> lst = uniqueIndexes.get(idxName);
+            if (lst == null) {
+                lst = new ArrayList<>();
+                uniqueIndexes.put(idxName, lst);
             }
-
-            // For each unique index composed of one column add a flag on the property descriptor
-            for (Entry<String,List<String>> entry : uniqueIndexes.entrySet()) {
-                final List<String> columns = entry.getValue();
-                if (columns.size() == 1) {
-                    String columnName = columns.get(0);
-                    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);
-                        }
-                    }
+            lst.add(columnName);
+
+            if (pkEmpty) {
+                // We use a single index columns set as primary key
+                // We must not mix with other potential indexes.
+                if (indexname == null) {
+                    indexname = idxName;
+                } else if (!indexname.equals(idxName)) {
+                    continue;
                 }
+                names.add(columnName);
             }
+        }
 
-            if (pkEmpty && !names.isEmpty()) {
-                // Build a primary key from unique index
-                final Iterator<Map<String,Object>> ite = cacheColumns.records.stream().filter(tableFilter).iterator();
-                while (ite.hasNext()) {
-                    final Map<String,Object> result = ite.next();
-                    final String columnName = (String) result.get(Column.COLUMN_NAME);
-                    if (!names.contains(columnName)) {
-                        continue;
-                    }
-
-                    final int sqlType = ((Number) result.get(Column.DATA_TYPE)).intValue();
-                    final String sqlTypeName = (String) result.get(Column.TYPE_NAME);
-                    final Class<?> columnType = dialect.getJavaType(sqlType, sqlTypeName);
-                    final ColumnMetaModel col = new ColumnMetaModel(schemaName, tableName, columnName,
-                            sqlType, sqlTypeName, columnType, ColumnMetaModel.Type.PROVIDED, null);
-                    cols.add(col);
-
-                    // Set as identifier
-                    for (PropertyTypeBuilder desc : ftb.properties()) {
-                        if (desc.getName().tip().toString().equals(columnName)) {
-                            final AttributeTypeBuilder<?> atb = (AttributeTypeBuilder) desc;
-                            atb.addRole(AttributeRole.IDENTIFIER_COMPONENT);
-                            break;
-                        }
+        // For each unique index composed of one column add a flag on the property descriptor
+        for (Entry<String,List<String>> entry : uniqueIndexes.entrySet()) {
+            final List<String> columns = entry.getValue();
+            if (columns.size() == 1) {
+                String columnName = columns.get(0);
+                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);
                     }
                 }
             }
+        }
 
-
-            if (cols.isEmpty()) {
-                if (Table.VALUE_TYPE_TABLE.equals(tableType)) {
-                    listeners.warning("No primary key found for " + tableName, null);
+        if (pkEmpty && !names.isEmpty()) {
+            // Build a primary key from unique index
+            final Iterator<Map<String,Object>> ite = cacheColumns.records.stream().filter(tableFilter).iterator();
+            while (ite.hasNext()) {
+                final Map<String,Object> result = ite.next();
+                final String columnName = (String) result.get(Reflection.COLUMN_NAME);
+                if (!names.contains(columnName)) {
+                    continue;
                 }
-            }
-            table.key = new PrimaryKey(tableName, cols);
 
-            // Mark primary key columns
-            for (PropertyTypeBuilder desc : ftb.properties()) {
-                for (ColumnMetaModel col : cols) {
-                    if (desc.getName().tip().toString().equals(col.name)) {
+                final int sqlType = ((Number) result.get(Reflection.DATA_TYPE)).intValue();
+                final String sqlTypeName = (String) result.get(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);
+                cols.add(col);
+
+                // Set as identifier
+                for (PropertyTypeBuilder desc : ftb.properties()) {
+                    if (desc.getName().tip().toString().equals(columnName)) {
                         final AttributeTypeBuilder<?> atb = (AttributeTypeBuilder) desc;
                         atb.addRole(AttributeRole.IDENTIFIER_COMPONENT);
                         break;
                     }
                 }
             }
+        }
 
 
-            // Find imported keys -----------------------------------------------
-            final Predicate<Map<String,Object>> fkFilter = (Map<String,Object> info) -> {
-                return Objects.equals(info.get(ImportedKey.FKTABLE_SCHEM), schemaName)
-                    && Objects.equals(info.get(ImportedKey.FKTABLE_NAME), tableName);
-            };
-            Iterator<Map<String,Object>> ite = cacheImportedKeys.records.stream().filter(fkFilter).iterator();
-            while (ite.hasNext()) {
-                final Map<String,Object> result = ite.next();
-                String relationName = (String) result.get(ImportedKey.PK_NAME);
-                if (relationName == null) relationName = (String) result.get(ImportedKey.FK_NAME);
-                final String localColumn = (String) result.get(ImportedKey.FKCOLUMN_NAME);
-                final String refSchemaName = (String) result.get(ImportedKey.PKTABLE_SCHEM);
-                final String refTableName = (String) result.get(ImportedKey.PKTABLE_NAME);
-                final String refColumnName = (String) result.get(ImportedKey.PKCOLUMN_NAME);
-                final int deleteRule = ((Number) result.get(ImportedKey.DELETE_RULE)).intValue();
-                final boolean deleteCascade = DatabaseMetaData.importedKeyCascade == deleteRule;
-                final RelationMetaModel relation = new RelationMetaModel(relationName,localColumn,
-                        refSchemaName, refTableName, refColumnName, true, deleteCascade);
-                table.importedKeys.add(relation);
-
-                if (refSchemaName!=null && !visitedSchemas.contains(refSchemaName)) requieredSchemas.add(refSchemaName);
-
-                // Set the information
-                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);
-                        break;
-                    }
+        if (cols.isEmpty()) {
+            if (TYPE_TABLE.equals(tableType)) {
+                listeners.warning("No primary key found for " + tableName, null);
+            }
+        }
+        table.key = new PrimaryKey(tableName, cols);
+
+        // Mark primary key columns
+        for (PropertyTypeBuilder desc : ftb.properties()) {
+            for (ColumnMetaModel col : cols) {
+                if (desc.getName().tip().toString().equals(col.name)) {
+                    final AttributeTypeBuilder<?> atb = (AttributeTypeBuilder) desc;
+                    atb.addRole(AttributeRole.IDENTIFIER_COMPONENT);
+                    break;
                 }
             }
+        }
 
-            // Find exported keys -----------------------------------------------
-            final Predicate<Map<String,Object>> ekFilter = (Map<String,Object> info) -> {
-                return Objects.equals(info.get(ImportedKey.PKTABLE_SCHEM), schemaName)
-                    && Objects.equals(info.get(ImportedKey.PKTABLE_NAME), tableName);
-            };
-            ite = cacheExportedKeys.records.stream().filter(ekFilter).iterator();
-            while (ite.hasNext()) {
-                final Map<String,Object> result = ite.next();
-                String relationName = (String) result.get(ExportedKey.FKCOLUMN_NAME);
-                if (relationName == null) relationName = (String) result.get(ExportedKey.FK_NAME);
-                final String localColumn = (String) result.get(ExportedKey.PKCOLUMN_NAME);
-                final String refSchemaName = (String) result.get(ExportedKey.FKTABLE_SCHEM);
-                final String refTableName = (String) result.get(ExportedKey.FKTABLE_NAME);
-                final String refColumnName = (String) result.get(ExportedKey.FKCOLUMN_NAME);
-                final int deleteRule = ((Number) result.get(ImportedKey.DELETE_RULE)).intValue();
-                final boolean deleteCascade = DatabaseMetaData.importedKeyCascade == deleteRule;
-                table.exportedKeys.add(new RelationMetaModel(relationName, localColumn,
-                        refSchemaName, refTableName, refColumnName, false, deleteCascade));
-
-                if (refSchemaName != null && !visitedSchemas.contains(refSchemaName)) requieredSchemas.add(refSchemaName);
+
+        // Find imported keys -----------------------------------------------
+        final Predicate<Map<String,Object>> fkFilter = (Map<String,Object> info) -> {
+            return Objects.equals(info.get(Reflection.FKTABLE_SCHEM), schemaName)
+                && Objects.equals(info.get(Reflection.FKTABLE_NAME), tableName);
+        };
+        Iterator<Map<String,Object>> ite = cacheImportedKeys.records.stream().filter(fkFilter).iterator();
+        while (ite.hasNext()) {
+            final Map<String,Object> result = ite.next();
+            String relationName = (String) result.get(Reflection.PK_NAME);
+            if (relationName == null) relationName = (String) result.get(Reflection.FK_NAME);
+            final String localColumn = (String) result.get(Reflection.FKCOLUMN_NAME);
+            final String refSchemaName = (String) result.get(Reflection.PKTABLE_SCHEM);
+            final String refTableName = (String) result.get(Reflection.PKTABLE_NAME);
+            final String refColumnName = (String) result.get(Reflection.PKCOLUMN_NAME);
+            final int deleteRule = ((Number) result.get(Reflection.DELETE_RULE)).intValue();
+            final boolean deleteCascade = DatabaseMetaData.importedKeyCascade == deleteRule;
+            final RelationMetaModel relation = new RelationMetaModel(relationName,localColumn,
+                    refSchemaName, refTableName, refColumnName, true, deleteCascade);
+            table.importedKeys.add(relation);
+
+            if (refSchemaName!=null && !visitedSchemas.contains(refSchemaName)) requieredSchemas.add(refSchemaName);
+
+            // Set the information
+            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);
+                    break;
+                }
             }
+        }
 
-            // Find parent table if any -----------------------------------------
-//            if(handleSuperTableMetadata == null || handleSuperTableMetadata){
-//                try{
-//                    result = metadata.getSuperTables(null, schemaName, tableName);
-//                    while (result.next()) {
-//                        final String parentTable = result.getString(SuperTable.SUPERTABLE_NAME);
-//                        table.parents.add(parentTable);
-//                    }
-//                }catch(final SQLException ex){
-//                    //not implemented by database
-//                    handleSuperTableMetadata = Boolean.FALSE;
-//                    store.getLogger().log(Level.INFO, "Database does not handle getSuperTable, feature type hierarchy will be ignored.");
-//                }finally{
-//                    closeSafe(store.getLogger(),result);
+        // Find exported keys -----------------------------------------------
+        final Predicate<Map<String,Object>> ekFilter = (Map<String,Object> info) -> {
+            return Objects.equals(info.get(Reflection.PKTABLE_SCHEM), schemaName)
+                && Objects.equals(info.get(Reflection.PKTABLE_NAME), tableName);
+        };
+        ite = cacheExportedKeys.records.stream().filter(ekFilter).iterator();
+        while (ite.hasNext()) {
+            final Map<String,Object> result = ite.next();
+            String relationName = (String) result.get(Reflection.FKCOLUMN_NAME);
+            if (relationName == null) relationName = (String) result.get(Reflection.FK_NAME);
+            final String localColumn = (String) result.get(Reflection.PKCOLUMN_NAME);
+            final String refSchemaName = (String) result.get(Reflection.FKTABLE_SCHEM);
+            final String refTableName = (String) result.get(Reflection.FKTABLE_NAME);
+            final String refColumnName = (String) result.get(Reflection.FKCOLUMN_NAME);
+            final int deleteRule = ((Number) result.get(Reflection.DELETE_RULE)).intValue();
+            final boolean deleteCascade = DatabaseMetaData.importedKeyCascade == deleteRule;
+            table.exportedKeys.add(new RelationMetaModel(relationName, localColumn,
+                    refSchemaName, refTableName, refColumnName, false, deleteCascade));
+
+            if (refSchemaName != null && !visitedSchemas.contains(refSchemaName)) requieredSchemas.add(refSchemaName);
+        }
+
+        // Find parent table if any -----------------------------------------
+//        if (handleSuperTableMetadata == null || handleSuperTableMetadata) {
+//            try {
+//                result = metadata.getSuperTables(null, schemaName, tableName);
+//                while (result.next()) {
+//                    final String parentTable = result.getString(SuperTable.SUPERTABLE_NAME);
+//                    table.parents.add(parentTable);
 //                }
+//            } catch (SQLException ex) {
+//                //not implemented by database
+//                handleSuperTableMetadata = Boolean.FALSE;
+//                store.getLogger().log(Level.INFO, "Database does not handle getSuperTable, feature type hierarchy will be ignored.");
+//            } finally {
+//                closeSafe(store.getLogger(),result);
 //            }
+//        }
 
-        } catch (SQLException e) {
-            throw new DataStoreException("Error occurred analyzing table: " + tableName, e);
-        }
         ftb.setName(tableName);
         table.tableType = ftb;
         return table;
     }
 
-    private AttributeType<?> analyzeColumn(final Map<String,Object> columnSet, final Connection cx) throws SQLException, DataStoreException{
-        final String schemaName     = (String)  columnSet.get(Column.TABLE_SCHEM);
-        final String tableName      = (String)  columnSet.get(Column.TABLE_NAME);
-        final String columnName     = (String)  columnSet.get(Column.COLUMN_NAME);
-        final int columnSize        = ((Number) columnSet.get(Column.COLUMN_SIZE)).intValue();
-        final int columnDataType    = ((Number) columnSet.get(Column.DATA_TYPE)).intValue();
-        final String columnTypeName = (String)  columnSet.get(Column.TYPE_NAME);
-        final String columnNullable = (String)  columnSet.get(Column.IS_NULLABLE);
-        final SingleAttributeTypeBuilder atb = new SingleAttributeTypeBuilder();
+    private AttributeType<?> analyzeColumn(final Map<String,Object> columnSet, final Connection cx, final AttributeTypeBuilder<?> atb)
+            throws SQLException
+    {
+        final String schemaName     = (String)  columnSet.get(Reflection.TABLE_SCHEM);
+        final String tableName      = (String)  columnSet.get(Reflection.TABLE_NAME);
+        final String columnName     = (String)  columnSet.get(Reflection.COLUMN_NAME);
+        final int columnSize        = ((Number) columnSet.get(Reflection.COLUMN_SIZE)).intValue();
+        final int columnDataType    = ((Number) columnSet.get(Reflection.DATA_TYPE)).intValue();
+        final String columnTypeName = (String)  columnSet.get(Reflection.TYPE_NAME);
+        final String columnNullable = (String)  columnSet.get(Reflection.IS_NULLABLE);
         atb.setName(columnName);
-        atb.setLength(columnSize);
-        try {
-            dialect.decodeColumnType(atb, cx, columnTypeName, columnDataType, schemaName, tableName, columnName);
-        } catch (SQLException e) {
-            throw new DataStoreException("Error occurred analyzing column: " + columnName, e);
-        }
-        atb.setMinimumOccurs(Column.VALUE_NO.equalsIgnoreCase(columnNullable) ? 1 : 0);
+        atb.setMaximalLength(columnSize);
+        dialect.decodeColumnType(atb, cx, columnTypeName, columnDataType, schemaName, tableName, columnName);
+        // TODO: need to distinguish "YES" and empty string?
+        atb.setMinimumOccurs(VALUE_NO.equalsIgnoreCase(columnNullable) ? 1 : 0);
         atb.setMaximumOccurs(1);
         return atb.build();
     }
@@ -623,7 +574,7 @@ public final class DataBaseModel {
     /**
      * Analyze the metadata of the ResultSet to rebuild a feature type.
      */
-    public FeatureType analyzeResult(final ResultSet result, final String name) throws SQLException, DataStoreException{
+    final FeatureType analyzeResult(final ResultSet result, final String name) throws SQLException, DataStoreException {
         final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
         ftb.setName(name);
 
@@ -652,15 +603,16 @@ public final class DataBaseModel {
                     }
                 }
             }
-
-            if (desc == null) {
-                //could not find the original type
-                //this column must be calculated
-                final SingleAttributeTypeBuilder atb = new SingleAttributeTypeBuilder();
+            if (desc != null) {
+                ftb.addProperty(desc);
+            } else {
+                // could not find the original type
+                // this column must be calculated
+                final AttributeTypeBuilder<?> atb = ftb.addAttribute(Object.class);
 
                 final int nullable = metadata.isNullable(i);
                 atb.setName(columnLabel);
-                atb.setMinimumOccurs(nullable == metadata.columnNullable ? 0 : 1);
+                atb.setMinimumOccurs(nullable == ResultSetMetaData.columnNullable ? 0 : 1);
                 atb.setMaximumOccurs(1);
                 atb.setName(columnLabel);
 
@@ -676,9 +628,7 @@ public final class DataBaseModel {
                 } catch (SQLException e) {
                     throw new DataStoreException("Error occurred analyzing column : " + columnName, e);
                 }
-                desc = atb.build();
             }
-            ftb.addProperty(desc);
         }
         return ftb.build();
     }
@@ -709,9 +659,9 @@ public final class DataBaseModel {
                     if (Geometries.isKnownType(binding)) {
 
                         final Predicate<Map<?,?>> colFilter = (Map<?,?> info) -> {
-                            return Objects.equals(info.get(Table.TABLE_SCHEM), schema.name)
-                                && Objects.equals(info.get(Table.TABLE_NAME), tableName)
-                                && Objects.equals(info.get(Column.COLUMN_NAME), name);
+                            return Objects.equals(info.get(Reflection.TABLE_SCHEM), schema.name)
+                                && Objects.equals(info.get(Reflection.TABLE_NAME), tableName)
+                                && Objects.equals(info.get(Reflection.COLUMN_NAME), name);
                         };
                         final Map<String,Object> metas = cacheColumns.records.stream().filter(colFilter).findFirst().get();
 
@@ -730,9 +680,9 @@ public final class DataBaseModel {
                         }
                     } else if (Coverage.class.isAssignableFrom(binding)) {
                         final Predicate<Map<String,Object>> colFilter = (Map<String,Object> info) -> {
-                            return Objects.equals(info.get(Table.TABLE_SCHEM), schema.name)
-                                && Objects.equals(info.get(Table.TABLE_NAME), tableName)
-                                && Objects.equals(info.get(Column.COLUMN_NAME), name);
+                            return Objects.equals(info.get(Reflection.TABLE_SCHEM), schema.name)
+                                && Objects.equals(info.get(Reflection.TABLE_NAME), tableName)
+                                && Objects.equals(info.get(Reflection.COLUMN_NAME), name);
                         };
                         final Map<String,Object> metas = cacheColumns.records.stream().filter(colFilter).findFirst().get();
 
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/Dialect.java
index 17f9547..ad9930f 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/Dialect.java
@@ -21,6 +21,7 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.feature.builder.AttributeTypeBuilder;
 import org.apache.sis.storage.DataStoreException;
 
 
@@ -42,16 +43,6 @@ public abstract class Dialect {
     }
 
     /**
-     * Indicates if the JDBC driver support global metadata.
-     * Some drivers force specifying a schema or table to return results.
-     * This prevent us from loading all metadata in one request and
-     * makes us loop on all tables.
-     *
-     * @return whether global JDBC metadata are available.
-     */
-    public abstract boolean supportGlobalMetadata();
-
-    /**
      * Indicates whether a table will be used as a {@code FeatureType}.
      *
      * @param  name  database table name.
@@ -71,23 +62,6 @@ public abstract class Dialect {
     public abstract Class<?> getJavaType(int sqlType, String sqlTypeName);
 
     /**
-     * Encodes the column name part of a SQL query.
-     *
-     * @param sql   where to write the SQL statement.
-     * @param name  column name to write, not null.
-     */
-    public abstract void encodeColumnName(StringBuilder sql, String name);
-
-    /**
-     * Encodes the schema and table name parts of a SQL query.
-     *
-     * @param sql     where to write the SQL statement.
-     * @param schema  database schema to write, or null if none.
-     * @param table   database table to write, not null.
-     */
-    public abstract void encodeSchemaAndTableName(StringBuilder sql, String schema, String table);
-
-    /**
      * If a column is an auto-increment or has a sequence, tries to extract next value.
      *
      * @param  column  description of the database column for which to get the next value.
@@ -122,7 +96,7 @@ public abstract class Dialect {
      * @param  column    name of the database column.
      * @throws SQLException if a JDBC error occurred while executing a statement.
      */
-    public abstract void decodeColumnType(final SingleAttributeTypeBuilder atb, final Connection cx,
+    public abstract void decodeColumnType(final AttributeTypeBuilder<?> atb, final Connection cx,
             final String typeName, final int datatype, final String schema,
             final String table, final String column) throws SQLException;
 
@@ -136,7 +110,7 @@ public abstract class Dialect {
      * @param  customquery  {@code true} if the request is a custom query.
      * @throws SQLException if a JDBC error occurred while executing a statement.
      */
-    public abstract void decodeGeometryColumnType(final SingleAttributeTypeBuilder atb, final Connection cx,
+    public abstract void decodeGeometryColumnType(final AttributeTypeBuilder<?> atb, final Connection cx,
             final ResultSet rs, final int columnIndex, boolean customquery) throws SQLException;
 
     /**
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/MetaDataConstants.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/MetaDataConstants.java
deleted file mode 100644
index 393785b..0000000
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/MetaDataConstants.java
+++ /dev/null
@@ -1,515 +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.feature;
-
-
-/**
- * Constants defined by JDBC to retrieve a database meta-model.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- */
-final class MetaDataConstants {
-
-    static final class Schema {
-        /** schema name. */
-        public static final String TABLE_SCHEM = "TABLE_SCHEM";
-
-        /** catalog name (values in this column may be null). */
-        public static final String TABLE_CATALOG = "TABLE_CATALOG";
-
-        private Schema() {
-        }
-    }
-
-    static final class Table {
-        /** Table catalog (values in this column may be null). */
-        public static final String TABLE_CAT = "TABLE_CAT";
-
-        /** Table schema (values in this column may be null). */
-
-        public static final String TABLE_SCHEM = "TABLE_SCHEM";
-
-        /** Table name. */
-        public static final String TABLE_NAME = "TABLE_NAME";
-
-        /**
-         * Table type. Typical types are:
-         * "TABLE", "VIEW", "SYSTEM TABLE",
-         * "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM".
-         */
-        public static final String TABLE_TYPE = "TABLE_TYPE";
-
-        /** Explanatory comment on the table. */
-        public static final String REMARKS = "REMARKS";
-
-        /** The types catalog (values in this column may be null). */
-        public static final String TYPE_CAT = "TYPE_CAT";
-
-        /** The types schema (values in this column may be null). */
-        public static final String TYPE_SCHEM = "TYPE_SCHEM";
-
-        /** Type name (values in this column may be null). */
-        public static final String TYPE_NAME = "TYPE_NAME";
-
-        /** Name of the designated "identifier" column of a typed table (values in this column may be null). */
-        public static final String SELF_REFERENCING_COL_NAME = "SELF_REFERENCING_COL_NAME";
-
-        /**
-         * Specifies how values in SELF_REFERENCING_COL_NAME are created.
-         * Values are "SYSTEM", "USER", "DERIVED". values in this column may be null.
-         */
-        public static final String REF_GENERATION =  "REF_GENERATION";
-
-        public static final String VALUE_TYPE_TABLE             = "TABLE";
-        public static final String VALUE_TYPE_VIEW              = "VIEW";
-        public static final String VALUE_TYPE_SYSTEMTABLE       = "SYSTEM TABLE";
-        public static final String VALUE_TYPE_GLOBALTEMPORARY   = "GLOBAL TEMPORARY";
-        public static final String VALUE_TYPE_LOCALTEMPORARY    = "LOCAL TEMPORARY";
-        public static final String VALUE_TYPE_ALIAS             = "ALIAS";
-        public static final String VALUE_TYPE_SYNONYM           = "SYNONYM";
-
-        public static final String VALUE_REFGEN_SYSTEM          = "SYSTEM";
-        public static final String VALUE_REFGEN_USER            = "USER";
-        public static final String VALUE_REFGEN_DERIVED         = "DERIVED";
-
-        private Table() {
-        }
-    }
-
-    public static final class SuperTable {
-        /** The type's catalog (values in this column may be null). */
-        public static final String TABLE_CAT = "TABLE_CAT";
-
-        /** Type's schema (values in this column may be null). */
-        public static final String TABLE_SCHEM = "TABLE_SCHEM";
-
-        /** Type name. */
-        public static final String TABLE_NAME = "TABLE_NAME";
-
-        /** The direct super type's name. */
-        public static final String SUPERTABLE_NAME =  "SUPERTABLE_NAME";
-
-        private SuperTable() {
-        }
-    }
-
-    public static final class Column {
-        /** Table catalog (values in this column may be null). */
-        public static final String TABLE_CAT = "TABLE_CAT";
-
-        /** Table schema (values in this column may be null). */
-        public static final String TABLE_SCHEM = "TABLE_SCHEM";
-
-        /** Table name. */
-        public static final String TABLE_NAME = "TABLE_NAME";
-
-        /** Column name. */
-        public static final String COLUMN_NAME = "COLUMN_NAME";
-
-        /** SQL type from {@link java.sql.Types}. */
-        public static final String DATA_TYPE = "DATA_TYPE";
-
-        /** Data source dependent type name, for a UDT the type name is fully qualified. */
-        public static final String TYPE_NAME = "TYPE_NAME";
-
-        /** Column size ({@code int} values). */
-        public static final String COLUMN_SIZE = "COLUMN_SIZE";
-
-        /** Not used. */
-        public static final String BUFFER_LENGTH = "BUFFER_LENGTH";
-
-        /**
-         * The number of fractional digits as {@code int} values.
-         * Null is returned for data types where DECIMAL_DIGITS is not applicable.
-         */
-        public static final String DECIMAL_DIGITS = "DECIMAL_DIGITS";
-
-        /** Radix (typically either 10 or 2) as {@code int} values. */
-        public static final String NUM_PREC_RADIX = "NUM_PREC_RADIX";
-
-        /**
-         * Whether NULL allowed as a {@code int} code.
-         * <ul>
-         *   <li>{@code columnNoNulls} - might not allow NULL values</li>
-         *   <li>{@code columnNullable} - definitely allows NULL values</li>
-         *   <li>{@code columnNullableUnknown} - nullability unknown</li>
-         * </ul>
-         */
-        public static final String NULLABLE = "NULLABLE";
-
-        /** Comment describing column (values in this column may be null). */
-        public static final String REMARKS = "REMARKS";
-
-        /**
-         * Default value for the column, which should be interpreted as a
-         * string when the value is enclosed in single quotes (values in this column may be null)
-         */
-        public static final String COLUMN_DEF = "COLUMN_DEF";
-
-        /** Unused {@code int} values. */
-        public static final String SQL_DATA_TYPE = "SQL_DATA_TYPE";
-
-        /** Unused {@code int} values. */
-        public static final String SQL_DATETIME_SUB = "SQL_DATETIME_SUB";
-
-        /** Maximum number (as a {@code int}) of bytes in the column of type {@code char}. */
-        public static final String CHAR_OCTET_LENGTH = "CHAR_OCTET_LENGTH";
-
-        /** Index of column in table (starting at 1). */
-        public static final String ORDINAL_POSITION = "ORDINAL_POSITION";
-
-        /**
-         * ISO rules are used to determine the nullability for a column.
-         * YES if the parameter can include NULLs,
-         * NO if the parameter cannot include NULLs,
-         * empty string if the nullability for the parameter is unknown.
-         */
-        public static final String IS_NULLABLE = "IS_NULLABLE";
-
-        /**
-         * Catalog of table that is the scope of a reference attribute
-         * (null if DATA_TYPE isn't REF).
-         */
-        public static final String SCOPE_CATLOG = "SCOPE_CATLOG";
-
-        /**
-         * Schema of table that is the scope of a reference attribute
-         * (null if the DATA_TYPE isn't REF).
-         */
-        public static final String SCOPE_SCHEMA = "SCOPE_SCHEMA";
-
-        /**
-         * Table name that this the scope of a reference attribute
-         * (null if the DATA_TYPE isn't REF).
-         */
-        public static final String SCOPE_TABLE = "SCOPE_TABLE";
-
-        /**
-         * Source type of a distinct type or user-generated Ref type.
-         * This is a SQL type from {@link java.sql.Types}
-         * (null if DATA_TYPE isn't DISTINCT or user-generated REF).
-         */
-        public static final String SOURCE_DATA_TYPE = "SOURCE_DATA_TYPE";
-
-        /**
-         * Indicates whether this column is auto incremented.
-         * YES if the column is auto incremented,
-         * NO if the column is not auto incremented,
-         * empty string if whether the column is auto incremented is unknown.
-         */
-        public static final String IS_AUTOINCREMENT = "IS_AUTOINCREMENT";
-
-        public static final String VALUE_YES = "YES";
-        public static final String VALUE_NO = "NO";
-
-        private Column() {
-        }
-    }
-
-    public static final class PrimaryKey{
-        /** Table catalog (values in this column may be null). */
-        public static final String TABLE_CAT = "TABLE_CAT";
-
-        /** Table schema (values in this column may be null). */
-        public static final String TABLE_SCHEM = "TABLE_SCHEM";
-
-        /** Table name. */
-        public static final String TABLE_NAME = "TABLE_NAME";
-
-        /** Column name. */
-        public static final String COLUMN_NAME = "COLUMN_NAME";
-
-        /**
-         * Sequence number within primary key as a {@code short} code.
-         * A value of 1 represents the first column of the primary key;
-         * a value of 2 would represent the second column within the primary key.
-         */
-        public static final String KEY_SEQ = "KEY_SEQ";
-
-        /** Primary key name (values in this column may be null). */
-        public static final String PK_NAME = "PK_NAME";
-
-        private PrimaryKey() {
-        }
-    }
-
-    public static final class ImportedKey{
-        /** Primary key table catalog being imported (values in this column may be null). */
-        public static final String PKTABLE_CAT = "PKTABLE_CAT";
-
-        /** Primary key table schema being imported (values in this column may be null). */
-        public static final String PKTABLE_SCHEM = "PKTABLE_SCHEM";
-
-        /** Primary key table name being imported. */
-        public static final String PKTABLE_NAME = "PKTABLE_NAME";
-
-        /** Primary key column name being imported. */
-        public static final String PKCOLUMN_NAME = "PKCOLUMN_NAME";
-
-        /** Foreign key table catalog (may be null). */
-        public static final String FKTABLE_CAT = "FKTABLE_CAT";
-
-        /** Foreign key table schema (may be null). */
-        public static final String FKTABLE_SCHEM = "FKTABLE_SCHEM";
-
-        /** Foreign key table name. */
-        public static final String FKTABLE_NAME = "FKTABLE_NAME";
-
-        /** Foreign key column name. */
-        public static final String FKCOLUMN_NAME = "FKCOLUMN_NAME";
-
-        /**
-         * Sequence number within a foreign key as a {@code short} code.
-         * A value of 1 represents the first column of the foreign key;
-         * a value of 2 would represent the second column within the foreign key.
-         */
-        public static final String KEY_SEQ = "KEY_SEQ";
-
-        /**
-         * What happens to a foreign key when the primary key is updated, as a {@code short} code.
-         * <ul>
-         *   <li>{@code importedNoAction} - do not allow update of primary key if it has been imported</li>
-         *   <li>{@code importedKeyCascade} - change imported key to agree with primary key update</li>
-         *   <li>{@code importedKeySetNull} - change imported key to NULL if its primary key has been updated</li>
-         *   <li>{@code importedKeySetDefault} - change imported key to default values if its primary key has been updated</li>
-         *   <li>{@code importedKeyRestrict} - same as importedKeyNoAction (for ODBC 2.x compatibility).</li>
-         * </ul>
-         */
-        public static final String UPDATE_RULE = "UPDATE_RULE";
-
-        /**
-         * What happens to the foreign key when primary is deleted, as a {@code short} code.
-         * <ul>
-         *   <li>{@code importedKeyNoAction} - do not allow delete of primary key if it has been imported</li>
-         *   <li>{@code importedKeyCascade} - delete rows that import a deleted key</li>
-         *   <li>{@code importedKeySetNull} - change imported key to NULL if its primary key has been deleted</li>
-         *   <li>{@code importedKeyRestrict} - same as importedKeyNoAction (for ODBC 2.x compatibility)</li>
-         *   <li>{@code importedKeySetDefault} - change imported key to default if its primary key has been deleted.</li>
-         * </ul>
-         */
-        public static final String DELETE_RULE = "DELETE_RULE";
-
-        /** Foreign key name (values in this column may be null). */
-        public static final String FK_NAME = "FK_NAME";
-
-        /** Primary key name (values in this column may be null). */
-        public static final String PK_NAME = "PK_NAME";
-
-        /**
-         * Whether the evaluation of foreign key constraints can be deferred until commit (as a {@code short} code).
-         * <ul>
-         *   <li>{@code importedKeyInitiallyDeferred} - see SQL92 for definition</li>
-         *   <li>{@code importedKeyInitiallyImmediate} - see SQL92 for definition</li>
-         *   <li>{@code importedKeyNotDeferrable} - see SQL92 for definition</li>
-         * </ul>
-         */
-        public static final String DEFERRABILITY = "DEFERRABILITY";
-
-        private ImportedKey() {
-        }
-    }
-
-    public static final class ExportedKey{
-        /** Primary key table catalog (values in this column may be null). */
-        public static final String PKTABLE_CAT = "PKTABLE_CAT";
-
-        /** Primary key table schema (values in this column may be null). */
-        public static final String PKTABLE_SCHEM = "PKTABLE_SCHEM";
-
-        /** Primary key table name. */
-        public static final String PKTABLE_NAME = "PKTABLE_NAME";
-
-        /** Primary key column name. */
-        public static final String PKCOLUMN_NAME = "PKCOLUMN_NAME";
-
-        /** Foreign key table catalog (may be null) being exported (values in this column may be null). */
-        public static final String FKTABLE_CAT = "FKTABLE_CAT";
-
-        /** Foreign key table schema (may be null) being exported (values in this column may be null). */
-        public static final String FKTABLE_SCHEM = "FKTABLE_SCHEM";
-
-        /** Foreign key table name being exported. */
-        public static final String FKTABLE_NAME = "FKTABLE_NAME";
-
-        /** Foreign key column name being exported. */
-        public static final String FKCOLUMN_NAME = "FKCOLUMN_NAME";
-
-        /**
-         * Sequence number within foreign key as a {@code short} code.
-         * a value of 1 represents the first column of the foreign key;
-         * a value of 2 would represent the second column within the foreign key.
-         */
-        public static final String KEY_SEQ = "KEY_SEQ";
-
-        /**
-         * What happens to foreign key when primary is updated, as a {@code short} code.
-         * <ul>
-         *   <li>{@code importedNoAction} - do not allow update of primary key if it has been imported</li>
-         *   <li>{@code importedKeyCascade} - change imported key to agree with primary key update</li>
-         *   <li>{@code importedKeySetNull} - change imported key to NULL if its primary key has been updated</li>
-         *   <li>{@code importedKeySetDefault} - change imported key to default values if its primary key has been updated</li>
-         *   <li>{@code importedKeyRestrict} - same as importedKeyNoAction (for ODBC 2.x compatibility)</li>
-         * </ul>
-         */
-        public static final String UPDATE_RULE = "UPDATE_RULE";
-
-        /**
-         * What happens to the foreign key when primary is deleted, as a {@code short} code.
-         * <ul>
-         *   <li>{@code importedKeyNoAction} - do not allow delete of primary key if it has been imported</li>
-         *   <li>{@code importedKeyCascade} - delete rows that import a deleted key</li>
-         *   <li>{@code importedKeySetNull} - change imported key to NULL if its primary key has been deleted</li>
-         *   <li>{@code importedKeyRestrict} - same as importedKeyNoAction (for ODBC 2.x compatibility)</li>
-         *   <li>{@code importedKeySetDefault} - change imported key to default if its primary key has been deleted</li>
-         * </ul>
-         */
-        public static final String DELETE_RULE = "DELETE_RULE";
-
-        /** Foreign key name (values in this column may be null). */
-        public static final String FK_NAME = "FK_NAME";
-
-        /** Primary key name (values in this column may be null). */
-        public static final String PK_NAME = "PK_NAME";
-
-        /**
-         * Whether the evaluation of foreign key constraints can be deferred until commit, as a {@code short} code.
-         * <ul>
-         *   <li>{@code importedKeyInitiallyDeferred} - see SQL92 for definition</li>
-         *   <li>{@code importedKeyInitiallyImmediate} - see SQL92 for definition</li>
-         *   <li>{@code importedKeyNotDeferrable} - see SQL92 for definition</li>
-         * </ul>
-         */
-        public static final String DEFERRABILITY = "DEFERRABILITY";
-
-        private ExportedKey() {
-        }
-    }
-
-    public static final class BestRow{
-        /**
-         * Actual scope of result as a {@code short} code.
-         *  bestRowTemporary - very temporary, while using row
-         *  bestRowTransaction - valid for remainder of current transaction
-         *  bestRowSession - valid for remainder of current session.
-         */
-        public static final String SCOPE = "SCOPE";
-
-        /** Column name. */
-        public static final String COLUMN_NAME = "COLUMN_NAME";
-
-        /** SQL data type from {@link java.sql.Types}. */
-        public static final String DATA_TYPE = "DATA_TYPE";
-
-        /** Data source dependent type name. For a UDT the type name is fully qualified. */
-        public static final String TYPE_NAME = "TYPE_NAME";
-
-        /** Precision as an {@code int}. */
-        public static final String COLUMN_SIZE = "COLUMN_SIZE";
-
-        /** Not used. */
-        public static final String BUFFER_LENGTH = "BUFFER_LENGTH";
-
-        /** Scale - Null is returned for data types where DECIMAL_DIGITS is not applicable. */
-        public static final String DECIMAL_DIGITS = "DECIMAL_DIGITS";
-
-        /**
-         * Whether this a pseudo column like an Oracle ROWID, as a {@code short} code.
-         * bestRowUnknown - may or may not be pseudo column
-         * bestRowNotPseudo - is NOT a pseudo column
-         * bestRowPseudo - is a pseudo column.
-         */
-        public static final String PSEUDO_COLUMN = "PSEUDO_COLUMN";
-
-        private BestRow() {
-        }
-    }
-
-    public static final class Index {
-        /** Table catalog (values in this column may be null). */
-        public static final String TABLE_CAT = "TABLE_CAT";
-
-        /** Table schema (values in this column may be null). */
-        public static final String TABLE_SCHEM = "TABLE_SCHEM";
-
-        /** Table name. */
-        public static final String TABLE_NAME = "TABLE_NAME";
-
-        /** Whether index values can be non-unique.
-         * false when TYPE is tableIndexStatistic  */
-        public static final String NON_UNIQUE = "NON_UNIQUE";
-
-        /**
-         * Index catalog (values in this column may be null).
-         * Values are null when TYPE is tableIndexStatistic.
-         */
-        public static final String INDEX_QUALIFIER = "INDEX_QUALIFIER";
-
-        /** Index name; null when TYPE is tableIndexStatistic. */
-        public static final String INDEX_NAME = "INDEX_NAME";
-
-        /**
-         * Index type as a {@code short} code.
-         *   tableIndexStatistic - this identifies table statistics that are
-         *       returned in conjuction with a table's index descriptions
-         *   tableIndexClustered - this is a clustered index
-         *   tableIndexHashed - this is a hashed index
-         *   tableIndexOther - this is some other style of index.
-         */
-        public static final String TYPE = "TYPE";
-
-        /**
-         * Column sequence number within index as a {@code short}.
-         * Zero when TYPE is tableIndexStatistic.
-         */
-        public static final String ORDINAL_POSITION = "ORDINAL_POSITION";
-
-        /** Column name. Values are null when TYPE is tableIndexStatistic. */
-        public static final String COLUMN_NAME = "COLUMN_NAME";
-
-        /**
-         * Column sort sequence.
-         * "A" for ascending,
-         * "D" for descending, may be null if sort sequence is not supported;
-         * null when TYPE is tableIndexStatistic.
-         */
-        public static final String ASC_OR_DESC = "ASC_OR_DESC";
-
-        /**
-         * When TYPE is tableIndexStatistic, then this is the number of rows in the table as an {@code int}.
-         * Otherwise, it is the number of unique values in the index.
-         */
-        public static final String CARDINALITY = "CARDINALITY";
-
-        /**
-         * When TYPE is tableIndexStatisic then this is the number of pages used for the table as an {@code int}.
-         * Otherwise it is the number of pages used for the current index.
-         */
-        public static final String PAGES = "PAGES";
-
-        /** Filter condition, if any. Values in this column may be null. */
-        public static final String FILTER_CONDITION = "FILTER_CONDITION";
-
-        private Index() {
-        }
-    }
-
-    private MetaDataConstants() {
-    }
-}
diff --git a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/SingleAttributeTypeBuilder.java b/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/SingleAttributeTypeBuilder.java
deleted file mode 100644
index ad22d65..0000000
--- a/storage/sis-sql/src/main/java/org/apache/sis/internal/sql/feature/SingleAttributeTypeBuilder.java
+++ /dev/null
@@ -1,260 +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.feature;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.apache.sis.feature.DefaultAttributeType;
-import org.opengis.feature.AttributeType;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.util.GenericName;
-import org.apache.sis.internal.feature.AttributeConvention;
-import org.apache.sis.internal.system.DefaultFactories;
-import org.opengis.util.NameFactory;
-
-import static org.apache.sis.feature.AbstractIdentifiedType.*;
-
-
-/**
- * Builder for a single {@link AttributeType}.
- * This is an alternative to {@link org.apache.sis.feature.builder.PropertyTypeBuilder}.
- *
- * @author  Johann Sorel (Geomatys)
- * @version 1.0
- * @since   1.0
- * @module
- *
- * @todo Is this class really needed?
- */
-public final class SingleAttributeTypeBuilder {
-    /**
-     * Properties (name, description, …) to give to the attribute type constructor.
-     */
-    private final Map<String,Object> properties;
-
-    /**
-     * Optional characteristics of the attribute.
-     */
-    private final List<AttributeType<?>> characteristics;
-
-    /**
-     * The class of the attribute.
-     */
-    private Class<?> valueClass;
-
-    /**
-     * Minimum number of occurrences.
-     */
-    private int minimumOccurs;
-
-    /**
-     * Maximum number of occurrences.
-     */
-    private int maximumOccurs;
-
-    /**
-     * The default value, or {@code null} if none.
-     */
-    private Object defaultValue;
-
-    /**
-     * Creates a new attribute type builder.
-     */
-    public SingleAttributeTypeBuilder() {
-        properties      = new HashMap<>();
-        characteristics = new ArrayList<>();
-        minimumOccurs   = 1;
-        maximumOccurs   = 1;
-    }
-
-    /**
-     * Restores this builder to its initial state.
-     * This method can be invoked before to build another attribute.
-     *
-     * @return {@code this} for method call chaining.
-     */
-    public SingleAttributeTypeBuilder reset(){
-        properties.clear();
-        characteristics.clear();
-        valueClass    = null;
-        minimumOccurs = 1;
-        maximumOccurs = 1;
-        defaultValue  = null;
-        return this;
-    }
-
-    /**
-     * Sets this builder to the same properties then the given attribute type.
-     *
-     * @param  template  the attribute type to use as a template.
-     * @return {@code this} for method call chaining.
-     */
-    public SingleAttributeTypeBuilder copy(AttributeType<?> template) {
-        reset();
-        setName       (template.getName());
-        setDefinition (template.getDefinition());
-        setDescription(template.getDescription());
-        setDesignation(template.getDesignation());
-        characteristics.addAll(template.characteristics().values());
-        valueClass    = template.getValueClass();
-        minimumOccurs = template.getMinimumOccurs();
-        maximumOccurs = template.getMaximumOccurs();
-        defaultValue  = template.getDefaultValue();
-        return this;
-    }
-
-    public SingleAttributeTypeBuilder setName(String localPart) {
-        return setName(null, localPart);
-    }
-
-    public SingleAttributeTypeBuilder setName(String namespace, String localPart) {
-        final GenericName name;
-        if (namespace == null || namespace.isEmpty()) {
-            name = DefaultFactories.forBuildin(NameFactory.class).createGenericName(null, localPart);
-        } else {
-            name = DefaultFactories.forBuildin(NameFactory.class).createGenericName(null, namespace, localPart);
-        }
-        return setName(name);
-    }
-
-    public SingleAttributeTypeBuilder setName(GenericName name) {
-        properties.put(DefaultAttributeType.NAME_KEY, name);
-        return this;
-    }
-
-    public GenericName getName() {
-        return (GenericName) properties.get(DefaultAttributeType.NAME_KEY);
-    }
-
-    public SingleAttributeTypeBuilder setDescription(CharSequence description) {
-        properties.put(DESCRIPTION_KEY, description);
-        return this;
-    }
-
-    public CharSequence getDescription() {
-        return (CharSequence) properties.get(DESCRIPTION_KEY);
-    }
-
-    public SingleAttributeTypeBuilder setDesignation(CharSequence designation){
-        properties.put(DESIGNATION_KEY, designation);
-        return this;
-    }
-
-    public CharSequence getDesignation(){
-        return (CharSequence) properties.get(DESIGNATION_KEY);
-    }
-
-    public SingleAttributeTypeBuilder setDefinition(CharSequence definition){
-        properties.put(DEFINITION_KEY, definition);
-        return this;
-    }
-
-    public CharSequence getDefinition(){
-        return (CharSequence) properties.get(DEFINITION_KEY);
-    }
-
-    public SingleAttributeTypeBuilder setValueClass(Class<?> valueClass) {
-        this.valueClass = valueClass;
-        return this;
-    }
-
-    public Class<?> getValueClass(){
-        return valueClass;
-    }
-
-    public SingleAttributeTypeBuilder setDefaultValue(Object defaultValue) {
-        this.defaultValue = defaultValue;
-        return this;
-    }
-
-    public Object getDefaultValue() {
-        return defaultValue;
-    }
-
-    public SingleAttributeTypeBuilder setMinimumOccurs(int minimumOccurs) {
-        this.minimumOccurs = minimumOccurs;
-        return this;
-    }
-
-    public int getMinimumOccurs() {
-        return minimumOccurs;
-    }
-
-    public SingleAttributeTypeBuilder setMaximumOccurs(int maximumOccurs) {
-        this.maximumOccurs = maximumOccurs;
-        return this;
-    }
-
-    public int getMaximumOccurs() {
-        return maximumOccurs;
-    }
-
-    /**
-     * Set maximum string length.
-     *
-     * @param  length maximal number of characters.
-     * @return {@code this} for method call chaining.
-     */
-    public SingleAttributeTypeBuilder setLength(int length) {
-        return addCharacteristic(AttributeConvention.MAXIMAL_LENGTH_CHARACTERISTIC, Integer.class, 0, 1, length);
-    }
-
-    public SingleAttributeTypeBuilder setCRS(CoordinateReferenceSystem crs) {
-        return addCharacteristic(AttributeConvention.CRS_CHARACTERISTIC, CoordinateReferenceSystem.class, 0, 1, crs);
-    }
-
-    public SingleAttributeTypeBuilder setPossibleValues(Collection<?> values) {
-        return addCharacteristic(AttributeConvention.VALID_VALUES_CHARACTERISTIC, Object.class, 0, 1, values);
-    }
-
-    public SingleAttributeTypeBuilder addCharacteristic(String localPart, Class<?> valueClass, int minimumOccurs, int maximumOccurs, Object defaultValue) {
-        final GenericName name = DefaultFactories.forBuildin(NameFactory.class).createGenericName(null, localPart);
-        return addCharacteristic(name,valueClass,minimumOccurs,maximumOccurs,defaultValue);
-    }
-
-    public SingleAttributeTypeBuilder addCharacteristic(GenericName name, Class<?> valueClass, int minimumOccurs, int maximumOccurs, Object defaultValue) {
-        return addCharacteristic(new DefaultAttributeType(
-                    Collections.singletonMap(NAME_KEY, name),
-                    valueClass,minimumOccurs,maximumOccurs,defaultValue));
-    }
-
-    public SingleAttributeTypeBuilder addCharacteristic(AttributeType<?> characteristic) {
-        //search and remove previous characteristic with the same id if it exist
-        for (AttributeType<?> at : characteristics) {
-            if (at.getName().equals(characteristic.getName())) {
-                characteristics.remove(at);
-                break;
-            }
-        }
-        characteristics.add(characteristic);
-        return this;
-    }
-
-    public AttributeType<?> build(){
-        return new DefaultAttributeType(properties, valueClass,
-                minimumOccurs, maximumOccurs,
-                defaultValue, characteristics.toArray(new AttributeType[characteristics.size()]));
-    }
-
-    public static AttributeType<?> create(GenericName name, Class<?> valueClass) {
-        return new DefaultAttributeType(Collections.singletonMap("name", name), valueClass, 1, 1, null);
-    }
-}
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
index 6bd82cd..2ec1041 100644
--- 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
@@ -23,16 +23,16 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.internal.sql.feature.SingleAttributeTypeBuilder;
 import org.apache.sis.internal.sql.feature.ColumnMetaModel;
-import org.apache.sis.storage.DataStoreException;
 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)
+ * @author  Johann Sorel (Geomatys)
  * @version 1.0
  * @since   1.0
  * @module
@@ -51,11 +51,6 @@ final class PostgresDialect extends Dialect {
     }
 
     @Override
-    public boolean supportGlobalMetadata() {
-        return true;
-    }
-
-    @Override
     public boolean isTableIgnored(String name) {
         return IGNORE_TABLES.contains(name.toLowerCase());
     }
@@ -66,16 +61,6 @@ final class PostgresDialect extends Dialect {
     }
 
     @Override
-    public void encodeColumnName(StringBuilder sql, String name) {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    @Override
-    public void encodeSchemaAndTableName(StringBuilder sql, String databaseSchema, String tableName) {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    @Override
     public Object nextValue(ColumnMetaModel column, Connection cx) throws SQLException, DataStoreException {
         throw new UnsupportedOperationException("Not supported yet.");
     }
@@ -86,12 +71,12 @@ final class PostgresDialect extends Dialect {
     }
 
     @Override
-    public void decodeColumnType(SingleAttributeTypeBuilder atb, Connection cx, String typeName, int datatype, String schemaName, String tableName, String columnName) throws SQLException {
+    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(SingleAttributeTypeBuilder atb, Connection cx, ResultSet rs, int columnIndex, boolean customquery) throws SQLException {
+    public void decodeGeometryColumnType(AttributeTypeBuilder<?> atb, Connection cx, ResultSet rs, int columnIndex, boolean customquery) throws SQLException {
         throw new UnsupportedOperationException("Not supported yet.");
     }
 


Mime
View raw message