shuhao 1 day ago
parent
commit
8ad71f510f

+ 135 - 0
GeoLib.iml

@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
+    <output url="file://$MODULE_DIR$/target/classes" />
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <excludeFolder url="file://$MODULE_DIR$/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: org.geotools.jdbc:gt-jdbc-postgis:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.postgresql:postgresql:42.2.5" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-jdbc:21.1" level="project" />
+    <orderEntry type="library" name="Maven: commons-dbcp:commons-dbcp:1.4" level="project" />
+    <orderEntry type="library" name="Maven: commons-collections:commons-collections:3.2.2" level="project" />
+    <orderEntry type="library" name="Maven: javax.media:jai_core:1.1.3" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-shapefile:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-main:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.locationtech.jts:jts-core:1.16.0" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.commons:commons-text:1.6" level="project" />
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.9.7" level="project" />
+    <orderEntry type="library" name="Maven: org.jdom:jdom2:2.0.6" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-swing:21.1" level="project" />
+    <orderEntry type="library" name="Maven: com.miglayout:miglayout:swing:3.7" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-geojson:21.1" level="project" />
+    <orderEntry type="library" name="Maven: com.googlecode.json-simple:json-simple:1.1" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.8.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-render:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-coverage:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.jaitools:jt-zonalstats:1.5.0" level="project" />
+    <orderEntry type="library" name="Maven: org.jaitools:jt-utils:1.5.0" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.affine:jt-affine:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.algebra:jt-algebra:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.bandmerge:jt-bandmerge:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.bandselect:jt-bandselect:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.bandcombine:jt-bandcombine:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.border:jt-border:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.buffer:jt-buffer:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.crop:jt-crop:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.iterators:jt-iterators:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.lookup:jt-lookup:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.mosaic:jt-mosaic:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.nullop:jt-nullop:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.rescale:jt-rescale:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.scale:jt-scale:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.scale2:jt-scale2:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: org.huldra.math:bigint:0.7.1" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.stats:jt-stats:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: com.google.guava:guava:27.0-jre" level="project" />
+    <orderEntry type="library" name="Maven: com.google.guava:failureaccess:1.0" level="project" />
+    <orderEntry type="library" name="Maven: com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava" level="project" />
+    <orderEntry type="library" name="Maven: com.google.code.findbugs:jsr305:3.0.2" level="project" />
+    <orderEntry type="library" name="Maven: org.checkerframework:checker-qual:2.5.2" level="project" />
+    <orderEntry type="library" name="Maven: com.google.errorprone:error_prone_annotations:2.2.0" level="project" />
+    <orderEntry type="library" name="Maven: com.google.j2objc:j2objc-annotations:1.1" level="project" />
+    <orderEntry type="library" name="Maven: org.codehaus.mojo:animal-sniffer-annotations:1.17" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.translate:jt-translate:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.utilities:jt-utilities:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.warp:jt-warp:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.zonal:jt-zonal:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.binarize:jt-binarize:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.format:jt-format:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.colorconvert:jt-colorconvert:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.errordiffusion:jt-errordiffusion:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.orderdither:jt-orderdither:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.colorindexer:jt-colorindexer:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.imagefunction:jt-imagefunction:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.piecewise:jt-piecewise:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.classifier:jt-classifier:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.rlookup:jt-rlookup:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.vectorbin:jt-vectorbin:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.jaiext.shadedrelief:jt-shadedrelief:1.1.9" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-cql:21.1" level="project" />
+    <orderEntry type="library" name="Maven: com.conversantmedia:disruptor:1.2.13" level="project" />
+    <orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.13" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-wfs-ng:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-complex:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-app-schema-resolver:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools.xsd:gt-xsd-gml3:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools.xsd:gt-xsd-core:21.1" level="project" />
+    <orderEntry type="library" name="Maven: picocontainer:picocontainer:1.2" level="project" />
+    <orderEntry type="library" name="Maven: commons-jxpath:commons-jxpath:1.3" level="project" />
+    <orderEntry type="library" name="Maven: org.eclipse.xsd:org.eclipse.xsd:2.12.0" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools.xsd:gt-xsd-gml2:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-xml:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.xml:xml-commons-resolver:1.2" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools.ogc:net.opengis.wfs:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools.ogc:org.w3.xlink:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools.ogc:net.opengis.fes:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.eclipse.emf:org.eclipse.emf.common:2.15.0" level="project" />
+    <orderEntry type="library" name="Maven: org.eclipse.emf:org.eclipse.emf.ecore:2.15.0" level="project" />
+    <orderEntry type="library" name="Maven: org.eclipse.emf:org.eclipse.emf.ecore.xmi:2.15.0" level="project" />
+    <orderEntry type="library" name="Maven: xpp3:xpp3_min:1.1.4c" level="project" />
+    <orderEntry type="library" name="Maven: commons-httpclient:commons-httpclient:3.1" level="project" />
+    <orderEntry type="library" name="Maven: commons-logging:commons-logging:1.0.4" level="project" />
+    <orderEntry type="library" name="Maven: commons-codec:commons-codec:1.2" level="project" />
+    <orderEntry type="library" name="Maven: commons-io:commons-io:2.6" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools.xsd:gt-xsd-wfs:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools.xsd:gt-xsd-filter:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools.xsd:gt-xsd-fes:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools.xsd:gt-xsd-ows:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools.ogc:net.opengis.ows:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-epsg-hsql:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.hsqldb:hsqldb:2.4.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-referencing:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.ejml:ejml-ddense:0.34" level="project" />
+    <orderEntry type="library" name="Maven: org.ejml:ejml-core:0.34" level="project" />
+    <orderEntry type="library" name="Maven: commons-pool:commons-pool:1.5.4" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-metadata:21.1" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-opengis:21.1" level="project" />
+    <orderEntry type="library" name="Maven: systems.uom:systems-common-java8:0.7.2" level="project" />
+    <orderEntry type="library" name="Maven: tec.uom:uom-se:1.0.8" level="project" />
+    <orderEntry type="library" name="Maven: javax.measure:unit-api:1.0" level="project" />
+    <orderEntry type="library" name="Maven: tec.uom.lib:uom-lib-common:1.0.2" level="project" />
+    <orderEntry type="library" name="Maven: si.uom:si-quantity:0.7.1" level="project" />
+    <orderEntry type="library" name="Maven: si.uom:si-units-java8:0.7.1" level="project" />
+    <orderEntry type="library" name="Maven: jgridshift:jgridshift:1.0" level="project" />
+    <orderEntry type="library" name="Maven: net.sf.geographiclib:GeographicLib-Java:1.49" level="project" />
+    <orderEntry type="library" name="Maven: org.geotools:gt-geotiff:21.1" level="project" />
+    <orderEntry type="library" name="Maven: javax.media:jai_imageio:1.1" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.imageio-ext:imageio-ext-tiff:1.2.1" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.imageio-ext:imageio-ext-utilities:1.2.1" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.imageio-ext:imageio-ext-geocore:1.2.1" level="project" />
+    <orderEntry type="library" name="Maven: javax.xml.bind:jaxb-api:2.4.0-b180830.0359" level="project" />
+    <orderEntry type="library" name="Maven: it.geosolutions.imageio-ext:imageio-ext-streams:1.2.1" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: org.glassfish.jaxb:jaxb-runtime:2.4.0-b180830.0438" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: org.glassfish.jaxb:txw2:2.4.0-b180830.0438" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: com.sun.istack:istack-commons-runtime:3.0.7" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: org.jvnet.staxex:stax-ex:1.8" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: com.sun.xml.fastinfoset:FastInfoset:1.2.15" level="project" />
+    <orderEntry type="library" name="Maven: javax.activation:javax.activation-api:1.2.0" level="project" />
+    <orderEntry type="library" name="Maven: javax.media:jai_codec:1.1.3" level="project" />
+  </component>
+</module>

+ 7 - 6
pom.xml

@@ -17,7 +17,7 @@
         <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
         <!-- JAVA版本 -->
         <java.version>1.8</java.version>
-        <geotools.version>28.1</geotools.version>
+        <geotools.version>21.1</geotools.version>
     </properties>
 
     <dependencies>
@@ -53,11 +53,6 @@
             <version>${geotools.version}</version>
         </dependency>
         <dependency>
-            <groupId>org.geotools.ogc</groupId>
-            <artifactId>net.opengis.wfs</artifactId>
-            <version>${geotools.version}</version>
-        </dependency>
-        <dependency>
             <groupId>org.geotools</groupId>
             <artifactId>gt-wfs-ng</artifactId>
             <version>${geotools.version}</version>
@@ -77,6 +72,12 @@
             <artifactId>gt-referencing</artifactId>
             <version>${geotools.version}</version>
         </dependency>
+        <!-- https://mvnrepository.com/artifact/org.geotools/gt-geotiff -->
+        <dependency>
+            <groupId>org.geotools</groupId>
+            <artifactId>gt-geotiff</artifactId>
+            <version>${geotools.version}</version>
+        </dependency>
         <!--geotools使用  end-->
         <!--geoserver使用  end-->
 

+ 0 - 4973
src/main/java/org/geotools/jdbc/JDBCDataStore.java

@@ -1,4973 +0,0 @@
-/*
- *    GeoTools - The Open Source Java GIS Toolkit
- *    http://geotools.org
- *
- *    (C) 2002-2016, Open Source Geospatial Foundation (OSGeo)
- *
- *    This library is free software; you can redistribute it and/or
- *    modify it under the terms of the GNU Lesser General Public
- *    License as published by the Free Software Foundation;
- *    version 2.1 of the License.
- *
- *    This library is distributed in the hope that it will be useful,
- *    but WITHOUT ANY WARRANTY; without even the implied warranty of
- *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- *    Lesser General Public License for more details.
- */
-package org.geotools.jdbc;
-
-import static org.geotools.jdbc.VirtualTable.WHERE_CLAUSE_PLACE_HOLDER;
-import static org.geotools.jdbc.VirtualTable.WHERE_CLAUSE_PLACE_HOLDER_LENGTH;
-import static org.geotools.jdbc.VirtualTable.setKeepWhereClausePlaceHolderHint;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Method;
-import java.net.URLDecoder;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.sql.Types;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.logging.Level;
-import javax.sql.DataSource;
-import org.apache.commons.lang3.ArrayUtils;
-import org.geotools.data.DataStore;
-import org.geotools.data.DefaultTransaction;
-import org.geotools.data.FeatureStore;
-import org.geotools.data.GmlObjectStore;
-import org.geotools.data.InProcessLockingManager;
-import org.geotools.data.Query;
-import org.geotools.data.Transaction;
-import org.geotools.data.Transaction.State;
-import org.geotools.data.jdbc.FilterToSQL;
-import org.geotools.data.jdbc.FilterToSQLException;
-import org.geotools.data.jdbc.datasource.ManageableDataSource;
-import org.geotools.data.simple.SimpleFeatureCollection;
-import org.geotools.data.simple.SimpleFeatureIterator;
-import org.geotools.data.store.ContentDataStore;
-import org.geotools.data.store.ContentEntry;
-import org.geotools.data.store.ContentFeatureSource;
-import org.geotools.data.store.ContentState;
-import org.geotools.feature.AttributeTypeBuilder;
-import org.geotools.feature.NameImpl;
-import org.geotools.feature.simple.SimpleFeatureBuilder;
-import org.geotools.feature.visitor.CountVisitor;
-import org.geotools.feature.visitor.GroupByVisitor;
-import org.geotools.feature.visitor.LimitingVisitor;
-import org.geotools.feature.visitor.UniqueCountVisitor;
-import org.geotools.filter.FilterCapabilities;
-import org.geotools.geometry.jts.ReferencedEnvelope;
-import org.geotools.jdbc.JoinInfo.JoinPart;
-import org.geotools.referencing.CRS;
-import org.geotools.util.Converters;
-import org.geotools.util.SoftValueHashMap;
-import org.geotools.util.factory.Hints;
-import org.locationtech.jts.geom.Envelope;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.Point;
-import org.opengis.feature.FeatureVisitor;
-import org.opengis.feature.simple.SimpleFeature;
-import org.opengis.feature.simple.SimpleFeatureType;
-import org.opengis.feature.type.AttributeDescriptor;
-import org.opengis.feature.type.GeometryDescriptor;
-import org.opengis.feature.type.Name;
-import org.opengis.filter.Filter;
-import org.opengis.filter.Id;
-import org.opengis.filter.PropertyIsLessThanOrEqualTo;
-import org.opengis.filter.expression.BinaryExpression;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.Function;
-import org.opengis.filter.expression.Literal;
-import org.opengis.filter.expression.PropertyName;
-import org.opengis.filter.identity.FeatureId;
-import org.opengis.filter.identity.GmlObjectId;
-import org.opengis.filter.sort.SortBy;
-import org.opengis.filter.sort.SortOrder;
-import org.opengis.referencing.FactoryException;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.TransformException;
-
-/**
- * Datastore implementation for jdbc based relational databases.
- *
- * <p>This class is not intended to be subclassed on a per database basis. Instead the notion of a
- * "dialect" is used.
- *
- * <p>
- *
- * <h3>Dialects</h3>
- *
- * A dialect ({@link SQLDialect}) encapsulates all the operations that are database specific.
- * Therefore to implement a jdbc based datastore one must extend SQLDialect. The specific dialect to
- * use is set using {@link #setSQLDialect(SQLDialect)}.
- *
- * <p>
- *
- * <h3>Database Connections</h3>
- *
- * Connections to the underlying database are obtained through a {@link DataSource}. A datastore
- * must be specified using {@link #setDataSource(DataSource)}.
- *
- * <p>
- *
- * <h3>Schemas</h3>
- *
- * This datastore supports the notion of database schemas, which is more or less just a grouping of
- * tables. When a schema is specified, only those tables which are part of the schema are provided
- * by the datastore. The schema is specified using {@link #setDatabaseSchema(String)}.
- *
- * <p>
- *
- * <h3>Spatial Functions</h3>
- *
- * The set of spatial operations or functions that are supported by the specific database are
- * reported with a {@link FilterCapabilities} instance. This is specified using {@link
- * #setFilterCapabilities(FilterCapabilities)}.
- *
- * @author Justin Deoliveira, The Open Planning Project
- */
-public final class JDBCDataStore extends ContentDataStore implements GmlObjectStore {
-
-    /** Caches the "setValue" method in various aggregate visitors */
-    private static SoftValueHashMap<Class, Method> AGGREGATE_SETVALUE_CACHE =
-            new SoftValueHashMap<>(1000);
-
-    /**
-     * When true, record a stack trace documenting who disposed the JDBCDataStore. If dispose() is
-     * called a second time we can identify the offending parties.
-     */
-    protected static final Boolean TRACE_ENABLED =
-            "true".equalsIgnoreCase(System.getProperty("gt2.jdbc.trace"));
-
-    /**
-     * The native SRID associated to a certain descriptor TODO: qualify this key with
-     * 'org.geotools.jdbc'
-     */
-    public static final String JDBC_NATIVE_SRID = "nativeSRID";
-
-    /** Boolean marker stating whether the feature type is to be considered read only */
-    public static final String JDBC_READ_ONLY = "org.geotools.jdbc.readOnly";
-
-    /** Boolean marker stating whether an attribute is part of the primary key */
-    public static final String JDBC_PRIMARY_KEY_COLUMN = "org.geotools.jdbc.pk.column";
-
-    /**
-     * The key for attribute descriptor user data which specifies the original database column data
-     * type.
-     */
-    public static final String JDBC_NATIVE_TYPENAME = "org.geotools.jdbc.nativeTypeName";
-
-    /**
-     * The key for attribute descriptor user data which specifies the original database column data
-     * type, as a {@link Types} value.
-     */
-    public static final String JDBC_NATIVE_TYPE = "org.geotools.jdbc.nativeType";
-
-    /** Used to specify the column alias to use when encoding a column in a select */
-    public static final String JDBC_COLUMN_ALIAS = "org.geotools.jdbc.columnAlias";
-
-    /** Contains a {@link EnumMapper} to support enums mapped from integer values */
-    public static final String JDBC_ENUM_MAP = "org.geotools.jdbc.enumMap";
-
-    /** name of table to use to store geometries when {@link #associations} is set. */
-    protected static final String GEOMETRY_TABLE = "geometry";
-
-    /**
-     * name of table to use to store multi geometries made up of non-multi geometries when {@link
-     * #associations} is set.
-     */
-    protected static final String MULTI_GEOMETRY_TABLE = "multi_geometry";
-
-    /** name of table to use to store geometry associations when {@link #associations} is set. */
-    protected static final String GEOMETRY_ASSOCIATION_TABLE = "geometry_associations";
-
-    /**
-     * name of table to use to store feature relationships (information about associations) when
-     * {@link #associations} is set.
-     */
-    protected static final String FEATURE_RELATIONSHIP_TABLE = "feature_relationships";
-
-    /** name of table to use to store feature associations when {@link #associations} is set. */
-    protected static final String FEATURE_ASSOCIATION_TABLE = "feature_associations";
-
-    /** The envelope returned when bounds is called against a geometryless feature type */
-    protected static final ReferencedEnvelope EMPTY_ENVELOPE = new ReferencedEnvelope();
-
-    /** Max number of ids to use for the optimized locks checking filter. */
-    public static final int MAX_IDS_IN_FILTER = 100;
-
-    /** data source */
-    protected DataSource dataSource;
-
-    /**
-     * Used with TRACE_ENABLED to record the thread responsible for disposing the JDBCDataStore. In
-     * the event dispose() is called a second time this throwable is used to identify the offending
-     * party.
-     */
-    private Throwable disposedBy = null;
-
-    /** the dialect of sql */
-    public SQLDialect dialect;
-
-    /** The database schema. */
-    protected String databaseSchema;
-
-    /** sql type to java class mappings */
-    protected HashMap<Integer, Class<?>> sqlTypeToClassMappings;
-
-    /** sql type name to java class mappings */
-    protected HashMap<String, Class<?>> sqlTypeNameToClassMappings;
-
-    /** java class to sql type mappings; */
-    protected HashMap<Class<?>, Integer> classToSqlTypeMappings;
-
-    /** sql type to sql type name overrides */
-    protected HashMap<Integer, String> sqlTypeToSqlTypeNameOverrides;
-
-    /** cache of sqltypes found in database */
-    protected ConcurrentHashMap<Integer, String> dBsqlTypesCache;
-
-    /** Feature visitor to aggregate function name */
-    protected HashMap<Class<? extends FeatureVisitor>, String> aggregateFunctions;
-
-    /** java supported filter function mappings to dialect name; */
-    protected HashMap<String, String> supportedFunctions;
-
-    /**
-     * flag controlling if the datastore is supporting feature and geometry relationships with
-     * associations
-     */
-    protected boolean associations = false;
-
-    /**
-     * The fetch size for this datastore, defaulting to 1000. Set to a value less or equal to 0 to
-     * disable fetch size limit and grab all the records in one shot.
-     */
-    public int fetchSize;
-
-    /**
-     * The number of features to bufferize while inserting in order to do batch inserts.
-     *
-     * <p>By default 1 to avoid backward compatibility with badly written code that forgets to close
-     * the JDBCInsertFeatureWriter or does it after closing the DB connection.
-     */
-    protected int batchInsertSize = 1;
-
-    /** flag controlling whether primary key columns of a table are exposed via the feature type. */
-    protected boolean exposePrimaryKeyColumns = false;
-
-    /**
-     * Finds the primary key definitions (instantiated here because the finders might keep state)
-     */
-    protected PrimaryKeyFinder primaryKeyFinder =
-            new CompositePrimaryKeyFinder(
-                    new MetadataTablePrimaryKeyFinder(), new HeuristicPrimaryKeyFinder());
-
-    /** Contains the SQL definition of the various virtual tables */
-    protected Map<String, VirtualTable> virtualTables = new ConcurrentHashMap<>();
-
-    /** The listeners that are allowed to handle the connection lifecycle */
-    protected List<ConnectionLifecycleListener> connectionLifecycleListeners =
-            new CopyOnWriteArrayList<>();
-
-    protected JDBCCallbackFactory callbackFactory = JDBCCallbackFactory.NULL;
-
-    private volatile NamePatternEscaping namePatternEscaping;
-
-    public JDBCDataStore() {
-        super();
-    }
-
-    public void setCallbackFactory(JDBCCallbackFactory factory) {
-        this.callbackFactory = factory;
-    }
-
-    public JDBCCallbackFactory getCallbackFactory() {
-        return callbackFactory;
-    }
-
-    public JDBCFeatureSource getAbsoluteFeatureSource(String typeName) throws IOException {
-        ContentFeatureSource featureSource = getFeatureSource(typeName);
-        if (featureSource instanceof JDBCFeatureSource) {
-            return (JDBCFeatureSource) featureSource;
-        }
-        return ((JDBCFeatureStore) featureSource).getFeatureSource();
-    }
-
-    /**
-     * Adds a virtual table to the data store. If a virtual table with the same name was registered
-     *
-     * @throws IOException If the view definition is not valid
-     */
-    public void createVirtualTable(VirtualTable vtable) throws IOException {
-        try {
-            virtualTables.put(vtable.getName(), new VirtualTable(vtable));
-            // the new vtable might be overriding a previous definition
-            entries.remove(new NameImpl(namespaceURI, vtable.getName()));
-            getSchema(vtable.getName());
-        } catch (IOException e) {
-            virtualTables.remove(vtable.getName());
-            throw e;
-        }
-    }
-
-    /** Returns a modifiable list of connection lifecycle listeners */
-    public List<ConnectionLifecycleListener> getConnectionLifecycleListeners() {
-        return connectionLifecycleListeners;
-    }
-
-    /** Removes and returns the specified virtual table */
-    public VirtualTable dropVirtualTable(String name) {
-        // the new vtable might be overriding a previous definition
-        VirtualTable vt = virtualTables.remove(name);
-        if (vt != null) {
-            entries.remove(new NameImpl(namespaceURI, name));
-        }
-        return vt;
-    }
-
-    /** Returns a live, immutable view of the virtual tables map (from name to definition) */
-    public Map<String, VirtualTable> getVirtualTables() {
-        Map<String, VirtualTable> result = new HashMap<>();
-        for (String key : virtualTables.keySet()) {
-            result.put(key, new VirtualTable(virtualTables.get(key)));
-        }
-        return Collections.unmodifiableMap(result);
-    }
-
-    /** Returns the finder used to build {@link PrimaryKey} representations */
-    public PrimaryKeyFinder getPrimaryKeyFinder() {
-        return primaryKeyFinder;
-    }
-
-    /** Sets the finder used to build {@link PrimaryKey} representations */
-    public void setPrimaryKeyFinder(PrimaryKeyFinder primaryKeyFinder) {
-        this.primaryKeyFinder = primaryKeyFinder;
-    }
-
-    /**
-     * The current fetch size. The fetch size influences how many records are read from the dbms at
-     * a time. If set to a value less or equal than zero, all the records will be read in one shot,
-     * severily increasing the memory requirements to read a big number of features.
-     */
-    public int getFetchSize() {
-        return fetchSize;
-    }
-
-    /** Changes the fetch size. */
-    public void setFetchSize(int fetchSize) {
-        this.fetchSize = fetchSize;
-    }
-
-    /** @return the number of features to bufferize while inserting in order to do batch inserts. */
-    public int getBatchInsertSize() {
-        return batchInsertSize;
-    }
-
-    /**
-     * Set the number of features to bufferize while inserting in order to do batch inserts.
-     *
-     * <p>Warning: when changing this value from its default of 1, the behavior of the {@link
-     * JDBCInsertFeatureWriter} is changed in non backward compatible ways. If your code closes the
-     * writer before closing the connection, you are fine. Plus, the feature added events will be
-     * delayed until a batch is actually inserted.
-     */
-    public void setBatchInsertSize(int batchInsertSize) {
-        this.batchInsertSize = batchInsertSize;
-    }
-
-    /**
-     * Determines if the datastore creates feature types which include those columns / attributes
-     * which compose the primary key.
-     */
-    public boolean isExposePrimaryKeyColumns() {
-        return exposePrimaryKeyColumns;
-    }
-
-    /**
-     * Sets the flag controlling if the datastore creates feature types which include those columns
-     * / attributes which compose the primary key.
-     */
-    public void setExposePrimaryKeyColumns(boolean exposePrimaryKeyColumns) {
-        if (this.exposePrimaryKeyColumns != exposePrimaryKeyColumns) {
-            entries.clear();
-        }
-        this.exposePrimaryKeyColumns = exposePrimaryKeyColumns;
-    }
-
-    /**
-     * The dialect the datastore uses to generate sql statements in order to communicate with the
-     * underlying database.
-     *
-     * @return The dialect, never <code>null</code>.
-     */
-    public SQLDialect getSQLDialect() {
-        return dialect;
-    }
-
-    /**
-     * Sets the dialect the datastore uses to generate sql statements in order to communicate with
-     * the underlying database.
-     *
-     * @param dialect The dialect, never <code>null</code>.
-     */
-    public void setSQLDialect(SQLDialect dialect) {
-        if (dialect == null) {
-            throw new NullPointerException();
-        }
-
-        this.dialect = dialect;
-    }
-
-    /**
-     * The data source the datastore uses to obtain connections to the underlying database.
-     *
-     * @return The data source, never <code>null</code>.
-     */
-    public DataSource getDataSource() {
-        if (dataSource == null) {
-            // Should never return null so throw an exception
-            if (TRACE_ENABLED) {
-                // If TRACE_ENABLED disposedBy may have stored an exception
-                if (disposedBy == null) {
-                    LOGGER.log(Level.WARNING, "JDBCDataStore was never given a DataSource.");
-                    throw new IllegalStateException(
-                            "DataSource not available as it was never set.");
-                } else {
-                    LOGGER.log(
-                            Level.WARNING, "JDBCDataStore was disposed:" + disposedBy, disposedBy);
-                    throw new IllegalStateException(
-                            "DataSource not available after calling dispose().");
-                }
-            } else {
-                throw new IllegalStateException(
-                        "DataSource not available after calling dispose() or before being set.");
-            }
-        }
-        return dataSource;
-    }
-
-    /**
-     * Sets the data source the datastore uses to obtain connections to the underlying database.
-     *
-     * @param dataSource The data source, never <code>null</code>.
-     */
-    public void setDataSource(DataSource dataSource) {
-        if (this.dataSource != null) {
-            LOGGER.log(
-                    Level.FINE,
-                    "Setting DataSource on JDBCDataStore that already has DataSource set");
-        }
-        if (dataSource == null) {
-            throw new IllegalArgumentException(
-                    "JDBCDataStore's DataSource should not be set to null");
-        }
-        this.dataSource = dataSource;
-    }
-
-    /**
-     * The schema from which this datastore is serving tables from.
-     *
-     * @return the schema, or <code>null</code> if non specified.
-     */
-    public String getDatabaseSchema() {
-        return databaseSchema;
-    }
-
-    /**
-     * Set the database schema for the datastore.
-     *
-     * <p>When this value is set only those tables which are part of the schema are served through
-     * the datastore. This value can be set to <code>null</code> to specify no particular schema.
-     *
-     * @param databaseSchema The schema, may be <code>null</code>.
-     */
-    public void setDatabaseSchema(String databaseSchema) {
-        this.databaseSchema = databaseSchema;
-    }
-
-    /**
-     * The filter capabilities which reports which spatial operations the underlying database can
-     * handle natively.
-     *
-     * @return The filter capabilities, never <code>null</code>.
-     */
-    public FilterCapabilities getFilterCapabilities() {
-        if (dialect instanceof PreparedStatementSQLDialect)
-            return ((PreparedStatementSQLDialect) dialect)
-                    .createPreparedFilterToSQL()
-                    .getCapabilities();
-        else return ((BasicSQLDialect) dialect).createFilterToSQL().getCapabilities();
-    }
-
-    /**
-     * Flag controlling if the datastore is supporting feature and geometry relationships with
-     * associations
-     */
-    public boolean isAssociations() {
-        return associations;
-    }
-
-    /**
-     * Sets the flag controlling if the datastore is supporting feature and geometry relationships
-     * with associations
-     */
-    public void setAssociations(boolean foreignKeyGeometries) {
-        this.associations = foreignKeyGeometries;
-    }
-
-    /**
-     * The sql type to java type mappings that the datastore uses when reading and writing objects
-     * to and from the database.
-     *
-     * <p>These mappings are derived from {@link
-     * SQLDialect#registerSqlTypeToClassMappings(Map)}
-     *
-     * @return The mappings, never <code>null</code>.
-     */
-    public Map<Integer, Class<?>> getSqlTypeToClassMappings() {
-        if (sqlTypeToClassMappings == null) {
-            sqlTypeToClassMappings = new HashMap<>();
-            dialect.registerSqlTypeToClassMappings(sqlTypeToClassMappings);
-        }
-
-        return sqlTypeToClassMappings;
-    }
-
-    /**
-     * The sql type name to java type mappings that the dialect uses when
-     * reading and writing objects to and from the database.
-     * <p>
-     * These mappings are derived from {@link SQLDialect#registerSqlTypeNameToClassMappings(Map)}
-     * </p>
-     *
-     * @return The mappings, never <code>null<code>.
-     */
-    public Map<String, Class<?>> getSqlTypeNameToClassMappings() {
-        if (sqlTypeNameToClassMappings == null) {
-            sqlTypeNameToClassMappings = new HashMap<>();
-            dialect.registerSqlTypeNameToClassMappings(sqlTypeNameToClassMappings);
-        }
-
-        return sqlTypeNameToClassMappings;
-    }
-
-    /**
-     * The java type to sql type mappings that the datastore uses when reading and writing objects
-     * to and from the database.
-     *
-     * <p>These mappings are derived from {@link SQLDialect#registerClassToSqlMappings(Map)}
-     *
-     * @return The mappings, never <code>null</code>.
-     */
-    public Map<Class<?>, Integer> getClassToSqlTypeMappings() {
-        if (classToSqlTypeMappings == null) {
-            HashMap<Class<?>, Integer> classToSqlTypeMappings = new HashMap<>();
-            dialect.registerClassToSqlMappings(classToSqlTypeMappings);
-            this.classToSqlTypeMappings = classToSqlTypeMappings;
-        }
-        return classToSqlTypeMappings;
-    }
-
-    /**
-     * Returns any ovverides which map integer constants for database types (from {@link Types}) to
-     * database type names.
-     *
-     * <p>This method will return an empty map when there are no overrides.
-     */
-    public Map<Integer, String> getSqlTypeToSqlTypeNameOverrides() {
-        if (sqlTypeToSqlTypeNameOverrides == null) {
-            sqlTypeToSqlTypeNameOverrides = new HashMap<>();
-            dialect.registerSqlTypeToSqlTypeNameOverrides(sqlTypeToSqlTypeNameOverrides);
-        }
-
-        return sqlTypeToSqlTypeNameOverrides;
-    }
-
-    /**
-     * Returns a map integer constants for database types (from {@link Types}) to database type
-     * names.
-     *
-     * <p>This method will return an empty map when there are no overrides.
-     */
-    public ConcurrentHashMap<Integer, String> getDBsqlTypesCache() {
-        if (dBsqlTypesCache == null) {
-            dBsqlTypesCache = new ConcurrentHashMap<>();
-        }
-
-        return dBsqlTypesCache;
-    }
-
-    /** Returns the supported aggregate functions and the visitors they map to. */
-    public Map<Class<? extends FeatureVisitor>, String> getAggregateFunctions() {
-        if (aggregateFunctions == null) {
-            aggregateFunctions = new HashMap<>();
-            dialect.registerAggregateFunctions(aggregateFunctions);
-        }
-        return aggregateFunctions;
-    }
-
-    /**
-     * Returns the java type mapped to the specified sql type.
-     *
-     * <p>If there is no such type mapped to <tt>sqlType</tt>, <code>null</code> is returned.
-     *
-     * @param sqlType The integer constant for the sql type from {@link Types}.
-     * @return The mapped java class, or <code>null</code>. if no such mapping exists.
-     */
-    public Class<?> getMapping(int sqlType) {
-        return getSqlTypeToClassMappings().get(Integer.valueOf(sqlType));
-    }
-
-    /**
-     * Returns the java type mapped to the specified sql type name.
-     *
-     * <p>If there is no such type mapped to <tt>sqlTypeName</tt>, <code>null</code> is returned.
-     *
-     * @param sqlTypeName The name of the sql type.
-     * @return The mapped java class, or <code>null</code>. if no such mapping exists.
-     */
-    public Class<?> getMapping(String sqlTypeName) {
-        return getSqlTypeNameToClassMappings().get(sqlTypeName);
-    }
-
-    /**
-     * Returns the sql type mapped to the specified java type.
-     *
-     * <p>If there is no such type mapped to <tt>clazz</tt>, <code>Types.OTHER</code> is returned.
-     *
-     * @param clazz The java class.
-     * @return The mapped sql type from {@link Types}, Types.OTHER if no such mapping exists.
-     */
-    public Integer getMapping(Class<?> clazz) {
-        Integer mapping = getClassToSqlTypeMappings().get(clazz);
-
-        // check for arrays, but don't get fooled by BLOB/CLOB java counterparts
-        if (mapping == null && clazz.isArray()) {
-            mapping = Types.ARRAY;
-        }
-
-        if (mapping == null) {
-            // no match, try a "fuzzy" match in which we find the super class which matches best
-            List<Map.Entry<Class<?>, Integer>> matches = new ArrayList<>();
-            for (Map.Entry<Class<?>, Integer> e : getClassToSqlTypeMappings().entrySet()) {
-                if (e.getKey().isAssignableFrom(clazz)) {
-                    matches.add(e);
-                }
-            }
-            if (!matches.isEmpty()) {
-                if (matches.size() == 1) {
-                    // single match, great, use it
-                    mapping = matches.get(0).getValue();
-                } else {
-                    // sort to match lowest class in type hierarchy, if we end up with a list like:
-                    // A, B where B is a super class of A, then chose A since it is the closest
-                    // subclass to match
-
-                    Collections.sort(
-                            matches,
-                            (o1, o2) -> {
-                                if (o1.getKey().isAssignableFrom(o2.getKey())) {
-                                    return 1;
-                                }
-                                if (o2.getKey().isAssignableFrom(o1.getKey())) {
-                                    return -1;
-                                }
-                                return 0;
-                            });
-                    if (matches.get(1).getKey().isAssignableFrom(matches.get(0).getKey())) {
-                        mapping = matches.get(0).getValue();
-                    }
-                }
-            }
-        }
-        if (mapping == null) {
-            mapping = Types.OTHER;
-            LOGGER.warning("No mapping for " + clazz.getName());
-        }
-
-        return mapping;
-    }
-
-    /**
-     * Creates a table in the underlying database from the specified table.
-     *
-     * <p>This method will map the classes of the attributes of <tt>featureType</tt> to sql types
-     * and generate a 'CREATE TABLE' statement against the underlying database.
-     *
-     * @see DataStore#createSchema(SimpleFeatureType)
-     * @throws IllegalArgumentException If the table already exists.
-     * @throws IOException If the table cannot be created due to an error.
-     */
-    @Override
-    public void createSchema(final SimpleFeatureType featureType) throws IOException {
-        if (entry(featureType.getName()) != null) {
-            String msg = "Schema '" + featureType.getName() + "' already exists";
-            throw new IllegalArgumentException(msg);
-        }
-
-        // execute the create table statement
-        // TODO: create a primary key and a spatial index
-        Connection cx = createConnection();
-
-        try {
-            String sql = createTableSQL(featureType, cx);
-            LOGGER.log(Level.FINE, "Create schema: {0}", sql);
-
-            Statement st = cx.createStatement();
-
-            try {
-                st.execute(sql);
-            } finally {
-                closeSafe(st);
-            }
-
-            dialect.postCreateTable(databaseSchema, featureType, cx);
-        } catch (Exception e) {
-            String msg = "Error occurred creating table";
-            throw (IOException) new IOException(msg).initCause(e);
-        } finally {
-            closeSafe(cx);
-        }
-    }
-
-    @Override
-    public void removeSchema(String typeName) throws IOException {
-        removeSchema(name(typeName));
-    }
-
-    @Override
-    public void removeSchema(Name typeName) throws IOException {
-        if (entry(typeName) == null) {
-            String msg = "Schema '" + typeName + "' does not exist";
-            throw new IllegalArgumentException(msg);
-        }
-
-        // check for virtual table
-        if (virtualTables.containsKey(typeName.getLocalPart())) {
-            dropVirtualTable(typeName.getLocalPart());
-            return;
-        }
-
-        SimpleFeatureType featureType = getSchema(typeName);
-
-        // execute the drop table statement
-        Connection cx = createConnection();
-        try {
-            // give the dialect a chance to cleanup pre
-            dialect.preDropTable(databaseSchema, featureType, cx);
-
-            String sql = dropTableSQL(featureType, cx);
-            LOGGER.log(Level.FINE, "Drop schema: {0}", sql);
-
-            Statement st = cx.createStatement();
-
-            try {
-                st.execute(sql);
-            } finally {
-                closeSafe(st);
-            }
-
-            dialect.postDropTable(databaseSchema, featureType, cx);
-            removeEntry(typeName);
-        } catch (Exception e) {
-            String msg = "Error occurred dropping table";
-            throw (IOException) new IOException(msg).initCause(e);
-        } finally {
-            closeSafe(cx);
-        }
-    }
-
-    /** */
-    @Override
-    public Object getGmlObject(GmlObjectId id, Hints hints) throws IOException {
-        // geometry?
-        if (isAssociations()) {
-
-            Connection cx = createConnection();
-            try {
-                try {
-                    Statement st = null;
-                    ResultSet rs = null;
-
-                    if (getSQLDialect() instanceof PreparedStatementSQLDialect) {
-                        st = selectGeometrySQLPS(id.getID(), cx);
-                        rs = ((PreparedStatement) st).executeQuery();
-                    } else {
-                        String sql = selectGeometrySQL(id.getID());
-                        LOGGER.log(Level.FINE, "Get GML object: {0}", sql);
-
-                        st = cx.createStatement();
-                        rs = st.executeQuery(sql);
-                    }
-
-                    try {
-                        if (rs.next()) {
-                            // read the geometry
-                            Geometry g =
-                                    getSQLDialect()
-                                            .decodeGeometryValue(
-                                                    null,
-                                                    rs,
-                                                    "geometry",
-                                                    getGeometryFactory(),
-                                                    cx,
-                                                    hints);
-
-                            // read the metadata
-                            String name = rs.getString("name");
-                            String desc = rs.getString("description");
-                            setGmlProperties(g, id.getID(), name, desc);
-
-                            return g;
-                        }
-                    } finally {
-                        closeSafe(rs);
-                        closeSafe(st);
-                    }
-
-                } catch (SQLException e) {
-                    throw (IOException) new IOException().initCause(e);
-                }
-            } finally {
-                closeSafe(cx);
-            }
-        }
-
-        // regular feature, first feature out the feature type
-        int i = id.getID().indexOf('.');
-        if (i == -1) {
-            LOGGER.info("Unable to determine feature type for GmlObjectId:" + id);
-            return null;
-        }
-
-        // figure out the type name from the id
-        String featureTypeName = id.getID().substring(0, i);
-        SimpleFeatureType featureType = getSchema(featureTypeName);
-        if (featureType == null) {
-            throw new IllegalArgumentException("No such feature type: " + featureTypeName);
-        }
-
-        // load the feature
-        Id filter = getFilterFactory().id(Collections.singleton(id));
-        Query query = new Query(featureTypeName);
-        query.setFilter(filter);
-        query.setHints(hints);
-
-        SimpleFeatureCollection features = getFeatureSource(featureTypeName).getFeatures(query);
-        if (!features.isEmpty()) {
-            try (SimpleFeatureIterator fi = features.features()) {
-                if (fi.hasNext()) {
-                    return fi.next();
-                }
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Creates a new instance of {@link JDBCFeatureStore}.
-     *
-     * @see ContentDataStore#createFeatureSource(ContentEntry)
-     */
-    @Override
-    protected ContentFeatureSource createFeatureSource(ContentEntry entry) throws IOException {
-        // grab the schema, it carries a flag telling us if the feature type is read only
-        SimpleFeatureType schema = entry.getState(Transaction.AUTO_COMMIT).getFeatureType();
-        if (schema == null) {
-            // if the schema still haven't been computed, force its computation so
-            // that we can decide if the feature type is read only
-            schema = new JDBCFeatureSource(entry, null).buildFeatureType();
-            entry.getState(Transaction.AUTO_COMMIT).setFeatureType(schema);
-        }
-
-        Object readOnlyMarker = schema.getUserData().get(JDBC_READ_ONLY);
-        if (Boolean.TRUE.equals(readOnlyMarker)) {
-            return new JDBCFeatureSource(entry, null);
-        }
-        return new JDBCFeatureStore(entry, null);
-    }
-
-    //    /**
-    //     * Creates a new instance of {@link JDBCTransactionState}.
-    //     */
-    //    protected State createTransactionState(ContentSimpleFeatureSource featureSource)
-    //        throws IOException {
-    //        return new JDBCTransactionState((JDBCFeatureStore) featureSource);
-    //    }
-
-    /**
-     * Creates an instanceof {@link JDBCState}.
-     *
-     * @see ContentDataStore#createContentState(ContentEntry)
-     */
-    @Override
-    protected ContentState createContentState(ContentEntry entry) {
-        JDBCState state = new JDBCState(entry);
-        state.setExposePrimaryKeyColumns(exposePrimaryKeyColumns);
-        return state;
-    }
-
-    /**
-     * Generates the list of type names provided by the database.
-     *
-     * <p>The list is generated from the underlying database metadata.
-     */
-    @Override
-    protected List<Name> createTypeNames() throws IOException {
-        Connection cx = createConnection();
-
-        /*
-         *        <LI><B>TABLE_CAT</B> String => table catalog (may be <code>null</code>)
-         *        <LI><B>TABLE_SCHEM</B> String => table schema (may be <code>null</code>)
-         *        <LI><B>TABLE_NAME</B> String => table name
-         *        <LI><B>TABLE_TYPE</B> String => table type.  Typical types are "TABLE",
-         *                        "VIEW",        "SYSTEM TABLE", "GLOBAL TEMPORARY",
-         *                        "LOCAL TEMPORARY", "ALIAS", "SYNONYM".
-         *        <LI><B>REMARKS</B> String => explanatory comment on the table
-         *  <LI><B>TYPE_CAT</B> String => the types catalog (may be <code>null</code>)
-         *  <LI><B>TYPE_SCHEM</B> String => the types schema (may be <code>null</code>)
-         *  <LI><B>TYPE_NAME</B> String => type name (may be <code>null</code>)
-         *  <LI><B>SELF_REFERENCING_COL_NAME</B> String => name of the designated
-         *                  "identifier" column of a typed table (may be <code>null</code>)
-         *        <LI><B>REF_GENERATION</B> String => specifies how values in
-         *                  SELF_REFERENCING_COL_NAME are created. Values are
-         *                  "SYSTEM", "USER", "DERIVED". (may be <code>null</code>)
-         */
-        List<Name> typeNames = new ArrayList<>();
-
-        try {
-            DatabaseMetaData metaData = cx.getMetaData();
-            Set<String> availableTableTypes = new HashSet<>();
-
-            ResultSet tableTypes = null;
-            try {
-                tableTypes = metaData.getTableTypes();
-                while (tableTypes.next()) {
-                    availableTableTypes.add(tableTypes.getString("TABLE_TYPE"));
-                }
-            } finally {
-                closeSafe(tableTypes);
-            }
-            Set<String> queryTypes = new HashSet<>();
-            for (String desiredTableType : dialect.getDesiredTablesType()) {
-                if (availableTableTypes.contains(desiredTableType)) {
-                    queryTypes.add(desiredTableType);
-                }
-            }
-            ResultSet tables =
-                    metaData.getTables(
-                            null,
-                            escapeNamePattern(metaData, databaseSchema),
-                            "%",
-                            queryTypes.toArray(new String[0]));
-            try {
-                if (fetchSize > 1) {
-                    tables.setFetchSize(fetchSize);
-                }
-                while (tables.next()) {
-                    String schemaName = tables.getString("TABLE_SCHEM");
-                    String tableName = tables.getString("TABLE_NAME");
-
-                    // use the dialect to filter
-                    if (!dialect.includeTable(schemaName, tableName, cx)) {
-                        continue;
-                    }
-
-                    typeNames.add(new NameImpl(namespaceURI, tableName));
-                }
-            } finally {
-                closeSafe(tables);
-            }
-        } catch (SQLException e) {
-            throw (IOException)
-                    new IOException("Error occurred getting table name list.").initCause(e);
-        } finally {
-            closeSafe(cx);
-        }
-
-        for (String virtualTable : virtualTables.keySet()) {
-            typeNames.add(new NameImpl(namespaceURI, virtualTable));
-        }
-        return typeNames;
-    }
-
-    /**
-     * Returns the primary key object for a particular entry, deriving it from the underlying
-     * database metadata.
-     */
-    protected PrimaryKey getPrimaryKey(ContentEntry entry) throws IOException {
-        JDBCState state = (JDBCState) entry.getState(Transaction.AUTO_COMMIT);
-
-        if (state.getPrimaryKey() == null) {
-            synchronized (this) {
-                if (state.getPrimaryKey() == null) {
-                    // get metadata from database
-                    Connection cx = createConnection();
-
-                    try {
-                        PrimaryKey pkey = null;
-                        String tableName = entry.getName().getLocalPart();
-                        if (virtualTables.containsKey(tableName)) {
-                            VirtualTable vt = virtualTables.get(tableName);
-                            if (vt.getPrimaryKeyColumns().size() == 0) {
-                                pkey = new NullPrimaryKey(tableName);
-                            } else {
-                                List<ColumnMetadata> metas =
-                                        JDBCFeatureSource.getColumnMetadata(cx, vt, dialect, this);
-
-                                List<PrimaryKeyColumn> kcols = new ArrayList<>();
-                                for (String pkName : vt.getPrimaryKeyColumns()) {
-                                    // look for the pk type
-                                    Class binding = null;
-                                    for (ColumnMetadata meta : metas) {
-                                        if (meta.name.equals(pkName)) {
-                                            binding = meta.binding;
-                                        }
-                                    }
-
-                                    // we build a pk without type, the JDBCFeatureStore will do this
-                                    // for us while building the primary key
-                                    kcols.add(new NonIncrementingPrimaryKeyColumn(pkName, binding));
-                                }
-                                pkey = new PrimaryKey(tableName, kcols);
-                            }
-                        } else {
-                            try {
-                                pkey =
-                                        primaryKeyFinder.getPrimaryKey(
-                                                this, databaseSchema, tableName, cx);
-                            } catch (SQLException e) {
-                                LOGGER.log(
-                                        Level.WARNING,
-                                        "Failure occurred while looking up the primary key with "
-                                                + "finder: "
-                                                + primaryKeyFinder,
-                                        e);
-                            }
-
-                            if (pkey == null) {
-                                String msg =
-                                        "No primary key or unique index found for "
-                                                + tableName
-                                                + ".";
-                                LOGGER.info(msg);
-
-                                pkey = new NullPrimaryKey(tableName);
-                            }
-                        }
-
-                        state.setPrimaryKey(pkey);
-                    } catch (SQLException e) {
-                        String msg = "Error looking up primary key";
-                        throw (IOException) new IOException(msg).initCause(e);
-                    } finally {
-                        closeSafe(cx);
-                    }
-                }
-            }
-        }
-
-        return state.getPrimaryKey();
-    }
-
-    /** Checks whether the tableName corresponds to a view */
-    boolean isView(DatabaseMetaData metaData, String databaseSchema, String tableName)
-            throws SQLException {
-
-        ResultSet tables = null;
-        try {
-            tables =
-                    metaData.getTables(
-                            null,
-                            escapeNamePattern(metaData, databaseSchema),
-                            escapeNamePattern(metaData, tableName),
-                            new String[] {"VIEW"});
-            return tables.next();
-        } finally {
-            closeSafe(tables);
-        }
-    }
-
-    /*
-     * Creates a key from a primary key or unique index.
-     */
-    PrimaryKey createPrimaryKey(
-            ResultSet index, DatabaseMetaData metaData, String tableName, Connection cx)
-            throws SQLException {
-        ArrayList<PrimaryKeyColumn> cols = new ArrayList<>();
-
-        while (index.next()) {
-            String columnName = index.getString("COLUMN_NAME");
-            // work around. For some reason the first record returned is always 'empty'
-            // this was tested on Oracle and Postgres databases
-            if (columnName == null) {
-                continue;
-            }
-
-            // look up the type ( should only be one row )
-            Class columnType = getColumnType(metaData, databaseSchema, tableName, columnName);
-
-            // determine which type of primary key we have
-            PrimaryKeyColumn col = null;
-
-            // 1. Auto Incrementing?
-            Statement st = cx.createStatement();
-
-            try {
-                // not actually going to get data
-                st.setFetchSize(1);
-
-                StringBuffer sql = new StringBuffer();
-                sql.append("SELECT ");
-                dialect.encodeColumnName(null, columnName, sql);
-                sql.append(" FROM ");
-                encodeTableName(tableName, sql, null);
-
-                sql.append(" WHERE 0=1");
-
-                LOGGER.log(Level.FINE, "Grabbing table pk metadata: {0}", sql);
-
-                ResultSet rs = st.executeQuery(sql.toString());
-
-                try {
-                    if (rs.getMetaData().isAutoIncrement(1)) {
-                        col = new AutoGeneratedPrimaryKeyColumn(columnName, columnType);
-                    }
-                } finally {
-                    closeSafe(rs);
-                }
-            } finally {
-                closeSafe(st);
-            }
-
-            // 2. Has a sequence?
-            if (col == null) {
-                try {
-                    String sequenceName =
-                            dialect.getSequenceForColumn(databaseSchema, tableName, columnName, cx);
-                    if (sequenceName != null) {
-                        col = new SequencedPrimaryKeyColumn(columnName, columnType, sequenceName);
-                    }
-                } catch (Exception e) {
-                    // log the exception , and continue on
-                    LOGGER.log(
-                            Level.WARNING,
-                            "Error occured determining sequence for "
-                                    + columnName
-                                    + ", "
-                                    + tableName,
-                            e);
-                }
-            }
-
-            if (col == null) {
-                col = new NonIncrementingPrimaryKeyColumn(columnName, columnType);
-            }
-
-            cols.add(col);
-        }
-
-        if (!cols.isEmpty()) {
-            return new PrimaryKey(tableName, cols);
-        }
-        return null;
-    }
-
-    /**
-     * Returns the type of the column by inspecting the metadata, with the collaboration of the
-     * dialect
-     */
-    protected Class getColumnType(
-            DatabaseMetaData metaData, String databaseSchema2, String tableName, String columnName)
-            throws SQLException {
-        ResultSet columns = null;
-        try {
-            columns =
-                    metaData.getColumns(
-                            null,
-                            escapeNamePattern(metaData, databaseSchema),
-                            escapeNamePattern(metaData, tableName),
-                            escapeNamePattern(metaData, columnName));
-            if (!columns.next()) {
-                throw new SQLException("Could not find metadata for column");
-            }
-
-            int binding = columns.getInt("DATA_TYPE");
-            Class columnType = getMapping(binding);
-
-            if (columnType == null) {
-                LOGGER.warning("No class for sql type " + binding);
-                columnType = Object.class;
-            }
-
-            return columnType;
-        } finally {
-            closeSafe(columns);
-        }
-    }
-
-    /**
-     * Returns the primary key object for a particular feature type / table, deriving it from the
-     * underlying database metadata.
-     */
-    public PrimaryKey getPrimaryKey(SimpleFeatureType featureType) throws IOException {
-        return getPrimaryKey(ensureEntry(featureType.getName()));
-    }
-
-    /** Returns the expose primary key columns flag for the specified feature type */
-    protected boolean isExposePrimaryKeyColumns(SimpleFeatureType featureType) throws IOException {
-        ContentEntry entry = ensureEntry(featureType.getName());
-        JDBCState state = (JDBCState) entry.getState(Transaction.AUTO_COMMIT);
-        return state.isExposePrimaryKeyColumns();
-    }
-
-    /**
-     * Returns the bounds of the features for a particular feature type / table.
-     *
-     * @param featureType The feature type / table.
-     * @param query Specifies rows to include in bounds calculation, as well as how many features
-     *     and the offset if needed
-     */
-    protected ReferencedEnvelope getBounds(
-            SimpleFeatureType featureType, Query query, Connection cx) throws IOException {
-
-        // handle geometryless case by returning an emtpy envelope
-        if (featureType.getGeometryDescriptor() == null) return EMPTY_ENVELOPE;
-
-        Statement st = null;
-        ResultSet rs = null;
-        ReferencedEnvelope bounds =
-                ReferencedEnvelope.create(featureType.getCoordinateReferenceSystem());
-        try {
-            // try optimized bounds computation only if we're targeting the entire table
-            if (isFullBoundsQuery(query, featureType)) {
-                List<ReferencedEnvelope> result =
-                        dialect.getOptimizedBounds(databaseSchema, featureType, cx);
-                if (result != null && !result.isEmpty()) {
-                    // merge the envelopes into one
-                    for (ReferencedEnvelope envelope : result) {
-                        bounds = mergeEnvelope(bounds, envelope);
-                    }
-                    return bounds;
-                }
-            }
-
-            // build an aggregate query
-            if (dialect instanceof PreparedStatementSQLDialect) {
-                st = selectBoundsSQLPS(featureType, query, cx);
-                rs = ((PreparedStatement) st).executeQuery();
-            } else {
-                String sql = selectBoundsSQL(featureType, query);
-                LOGGER.log(Level.FINE, "Retrieving bounding box: {0}", sql);
-
-                st = cx.createStatement();
-                rs = st.executeQuery(sql);
-            }
-
-            // scan through all the rows (just in case a non aggregated function was used)
-            // and through all the columns (in case we have multiple geometry columns)
-            CoordinateReferenceSystem flatCRS =
-                    CRS.getHorizontalCRS(featureType.getCoordinateReferenceSystem());
-            final int columns = rs.getMetaData().getColumnCount();
-            while (rs.next()) {
-                for (int i = 1; i <= columns; i++) {
-                    final Envelope envelope =
-                            dialect.decodeGeometryEnvelope(rs, i, st.getConnection());
-                    if (envelope != null) {
-                        if (envelope instanceof ReferencedEnvelope) {
-                            bounds = mergeEnvelope(bounds, (ReferencedEnvelope) envelope);
-                        } else {
-                            bounds =
-                                    mergeEnvelope(
-                                            bounds, new ReferencedEnvelope(envelope, flatCRS));
-                        }
-                    }
-                }
-            }
-        } catch (Exception e) {
-            String msg = "Error occured calculating bounds for " + featureType.getTypeName();
-            throw (IOException) new IOException(msg).initCause(e);
-        } finally {
-            closeSafe(rs);
-            closeSafe(st);
-        }
-
-        return bounds;
-    }
-
-    /**
-     * Returns true if the query will hit all the geometry columns with no row filtering (a
-     * condition that allows to use spatial index statistics to compute the table bounds)
-     */
-    private boolean isFullBoundsQuery(Query query, SimpleFeatureType schema) {
-        if (query == null) {
-            return true;
-        }
-        if (!query.isMaxFeaturesUnlimited()) {
-            return false; // there is a limit
-        }
-        if (query.getStartIndex() != null && query.getStartIndex() > 0) {
-            return false; // there is an offset
-        }
-        if (!Filter.INCLUDE.equals(query.getFilter())) {
-            return false;
-        }
-        if (query.getProperties() == Query.ALL_PROPERTIES) {
-            return true;
-        }
-
-        List<String> names = Arrays.asList(query.getPropertyNames());
-        for (AttributeDescriptor ad : schema.getAttributeDescriptors()) {
-            if (ad instanceof GeometryDescriptor) {
-                if (!names.contains(ad.getLocalName())) {
-                    return false;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    /** Merges two envelopes handling possibly different CRS */
-    ReferencedEnvelope mergeEnvelope(ReferencedEnvelope base, ReferencedEnvelope merge)
-            throws TransformException, FactoryException {
-        if (base == null || base.isNull()) {
-            return merge;
-        } else if (merge == null || merge.isNull()) {
-            return base;
-        } else {
-            // reproject and merge
-            final CoordinateReferenceSystem crsBase = base.getCoordinateReferenceSystem();
-            final CoordinateReferenceSystem crsMerge = merge.getCoordinateReferenceSystem();
-            if (crsBase == null) {
-                merge.expandToInclude(base);
-                return merge;
-            } else if (crsMerge == null) {
-                base.expandToInclude(base);
-                return base;
-            } else {
-                // both not null, are they equal?
-                if (!CRS.equalsIgnoreMetadata(crsBase, crsMerge)) {
-                    merge = merge.transform(crsBase, true);
-                }
-                base.expandToInclude(merge);
-                return base;
-            }
-        }
-    }
-
-    /** Returns the count of the features for a particular feature type / table. */
-    protected int getCount(SimpleFeatureType featureType, Query query, Connection cx)
-            throws IOException {
-
-        CountVisitor v = new CountVisitor();
-        getAggregateValue(v, featureType, query, cx);
-        return v.getCount();
-    }
-
-    /**
-     * Results the value of an aggregate function over a query.
-     *
-     * @return generated result, or null if unsupported
-     */
-    protected Object getAggregateValue(
-            FeatureVisitor visitor, SimpleFeatureType featureType, Query query, Connection cx)
-            throws IOException {
-        // check if group by is supported by the underlying store
-        if (isGroupByVisitor(visitor)
-                && (!dialect.isGroupBySupported()
-                        || !isSupportedGroupBy((GroupByVisitor) visitor))) {
-            return null;
-        }
-        // try to match the visitor with an aggregate function
-        String function = matchAggregateFunction(visitor);
-        if (function == null) {
-            // this visitor is not supported
-            return null;
-        }
-        // try to extract an aggregate attribute from the visitor
-        Expression aggregateExpression = null;
-        if (!isCountVisitor(visitor)) {
-            aggregateExpression = getAggregateExpression(visitor);
-            if (aggregateExpression != null && !fullySupports(aggregateExpression)) {
-                return null;
-            }
-        }
-
-        // if the visitor is limiting the result to a given start - max, we will
-        // try to apply limits to the aggregate query
-        LimitingVisitor limitingVisitor = null;
-        if (visitor instanceof LimitingVisitor) {
-            limitingVisitor = (LimitingVisitor) visitor;
-        }
-        // if the visitor is a group by visitor we extract the group by attributes
-        List<Expression> groupByExpressions = extractGroupByExpressions(visitor);
-        // result of the function
-        try {
-            Object result = null;
-            List<Object> results = new ArrayList<>();
-            Statement st = null;
-            ResultSet rs = null;
-
-            try {
-                if (dialect instanceof PreparedStatementSQLDialect) {
-                    st =
-                            selectAggregateSQLPS(
-                                    function,
-                                    aggregateExpression,
-                                    groupByExpressions,
-                                    featureType,
-                                    query,
-                                    limitingVisitor,
-                                    cx);
-                    rs = ((PreparedStatement) st).executeQuery();
-                } else {
-                    String sql =
-                            selectAggregateSQL(
-                                    function,
-                                    aggregateExpression,
-                                    groupByExpressions,
-                                    featureType,
-                                    query,
-                                    limitingVisitor);
-                    LOGGER.fine(sql);
-
-                    st = cx.createStatement();
-                    st.setFetchSize(fetchSize);
-                    rs = st.executeQuery(sql);
-                }
-
-                // give the dialect an opportunity to convert outputs, if needed, for databases
-                // with a weak/problematic type system (e.g., sqlite)
-                java.util.function.Function<Object, Object> converter =
-                        dialect.getAggregateConverter(visitor, featureType);
-
-                while (rs.next()) {
-                    if (groupByExpressions == null || groupByExpressions.isEmpty()) {
-                        Object value = rs.getObject(1);
-                        result = converter.apply(value);
-                        results.add(result);
-                    } else {
-                        results.add(
-                                extractValuesFromResultSet(
-                                        rs, groupByExpressions.size(), converter));
-                    }
-                }
-            } finally {
-                closeSafe(rs);
-                closeSafe(st);
-            }
-
-            if (groupByExpressions != null && !groupByExpressions.isEmpty()) {
-                setResult(visitor, results);
-                return results;
-            } else if (setResult(visitor, results.size() > 1 ? results : result)) {
-                return result == null ? results : result;
-            }
-
-            return null;
-        } catch (SQLException e) {
-            throw (IOException) new IOException().initCause(e);
-        }
-    }
-
-    /**
-     * Checks if the groupBy is a supported one, that is, if it's possible to turn to SQL the
-     * various {@link Expression} it's using
-     */
-    private boolean isSupportedGroupBy(GroupByVisitor visitor) {
-        return visitor.getGroupByAttributes().stream().allMatch(xp -> fullySupports(xp));
-    }
-
-    /**
-     * Determines if the expression and all its sub expressions are supported.
-     *
-     * @param expression the expression to be tested.
-     * @return true if all sub filters are supported, false otherwise.
-     * @throws IllegalArgumentException If a null filter is passed in. As this function is recursive
-     *     a null in a logic filter will also cause an error.
-     */
-    private boolean fullySupports(Expression expression) {
-        if (expression == null) {
-            throw new IllegalArgumentException("Null expression can not be unpacked");
-        }
-
-        FilterCapabilities filterCapabilities = getFilterCapabilities();
-
-        if (!filterCapabilities.supports(expression.getClass())) {
-            return false;
-        }
-
-        // check the known composite expressions
-        if (expression instanceof BinaryExpression) {
-            BinaryExpression be = (BinaryExpression) expression;
-            return fullySupports(be.getExpression1()) && fullySupports(be.getExpression2());
-        } else if (expression instanceof Function) {
-            Function function = (Function) expression;
-            for (Expression fe : function.getParameters()) {
-                if (!fullySupports(fe)) {
-                    return false;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    // Helper method that checks if the visitor is of type count visitor.
-    protected boolean isCountVisitor(FeatureVisitor visitor) {
-        if (visitor instanceof CountVisitor) {
-            // is count visitor nothing else to test
-            return true;
-        }
-        // the visitor maybe wrapper by a group by visitor
-        return isGroupByVisitor(visitor)
-                && ((GroupByVisitor) visitor).getAggregateVisitor() instanceof CountVisitor;
-    }
-
-    /**
-     * Helper method the checks if a feature visitor is a group by visitor,
-     *
-     * @param visitor the feature visitor
-     * @return TRUE if the visitor is a group by visitor otherwise FALSE
-     */
-    protected boolean isGroupByVisitor(FeatureVisitor visitor) {
-        return visitor instanceof GroupByVisitor;
-    }
-
-    /**
-     * Helper method that will try to match a feature visitor with an aggregate function. If no
-     * aggregate function machs the visitor NULL will be returned.
-     *
-     * @param visitor the feature visitor
-     * @return the match aggregate function name, or NULL if no match
-     */
-    protected String matchAggregateFunction(FeatureVisitor visitor) {
-        // if is a group by visitor we use use the internal aggregate visitor class otherwise we use
-        // the visitor class
-        Class visitorClass =
-                isGroupByVisitor(visitor)
-                        ? ((GroupByVisitor) visitor).getAggregateVisitor().getClass()
-                        : visitor.getClass();
-        String function = null;
-        // try to find a matching aggregate function walking up the hierarchy if necessary
-        while (function == null && visitorClass != null) {
-            function = getAggregateFunctions().get(visitorClass);
-            visitorClass = visitorClass.getSuperclass();
-        }
-        if (function == null) {
-            // this visitor don't match any aggregate function NULL will be returned
-            LOGGER.info(
-                    "Unable to find aggregate function matching visitor: " + visitor.getClass());
-        }
-        return function;
-    }
-
-    private Expression getAggregateExpression(FeatureVisitor visitor) {
-        // if is a group by visitor we need to use the internal aggregate visitor
-        FeatureVisitor aggregateVisitor =
-                isGroupByVisitor(visitor)
-                        ? ((GroupByVisitor) visitor).getAggregateVisitor()
-                        : visitor;
-        Expression expression = getExpression(aggregateVisitor);
-        if (expression == null) {
-            // no aggregate attribute available, NULL will be returned
-            LOGGER.info("Visitor " + visitor.getClass() + " has no aggregate attribute.");
-            return null;
-        }
-        return expression;
-    }
-
-    /**
-     * Helper method that extracts a list of group by attributes from a group by visitor. If the
-     * visitor is not a group by visitor an empty list will be returned.
-     *
-     * @param visitor the feature visitor
-     * @return the list of the group by attributes or an empty list
-     */
-    protected List<Expression> extractGroupByExpressions(FeatureVisitor visitor) {
-        // if is a group by visitor we get the list of attributes expressions otherwise we get an
-        // empty list
-        List<Expression> expressions =
-                isGroupByVisitor(visitor)
-                        ? ((GroupByVisitor) visitor).getGroupByAttributes()
-                        : new ArrayList<>();
-        return expressions;
-    }
-
-    /**
-     * Helper method that translate the result set to the appropriate group by visitor result format
-     */
-    protected GroupByVisitor.GroupByRawResult extractValuesFromResultSet(
-            ResultSet resultSet,
-            int numberOfGroupByAttributes,
-            java.util.function.Function<Object, Object> converter)
-            throws SQLException {
-        List<Object> groupByValues = new ArrayList<>();
-        for (int i = 0; i < numberOfGroupByAttributes; i++) {
-            Object result = resultSet.getObject(i + 1);
-            groupByValues.add(result);
-        }
-        Object aggregated = resultSet.getObject(numberOfGroupByAttributes + 1);
-        Object converted = converter.apply(aggregated);
-        return new GroupByVisitor.GroupByRawResult(groupByValues, converted);
-    }
-
-    /**
-     * Helper method for getting the expression from a visitor TODO: Remove this method when there
-     * is an interface for aggregate visitors. See GEOT-2325 for details.
-     */
-    Expression getExpression(FeatureVisitor visitor) {
-        if (visitor instanceof CountVisitor) {
-            return null;
-        }
-        try {
-            Method g = visitor.getClass().getMethod("getExpression", null);
-            if (g != null) {
-                Object result = g.invoke(visitor, null);
-                if (result instanceof Expression) {
-                    return (Expression) result;
-                }
-            }
-        } catch (Exception e) {
-            // ignore for now
-        }
-
-        return null;
-    }
-
-    /**
-     * Helper method for setting the result of a aggregate functino on a visitor. TODO: Remove this
-     * method when there is an interface for aggregate visitors. See GEOT-2325 for details.
-     */
-    boolean setResult(FeatureVisitor visitor, Object result) {
-        try {
-            Method s = null;
-            if (AGGREGATE_SETVALUE_CACHE.containsKey(visitor.getClass())) {
-                s = AGGREGATE_SETVALUE_CACHE.get(visitor.getClass());
-            } else {
-                try {
-                    s = visitor.getClass().getMethod("setValue", result.getClass());
-                } catch (Exception e) {
-                }
-
-                if (s == null) {
-                    for (Method m : visitor.getClass().getMethods()) {
-                        if ("setValue".equals(m.getName()) && m.getParameterCount() == 1) {
-                            s = m;
-                            break;
-                        }
-                    }
-                }
-                AGGREGATE_SETVALUE_CACHE.put(visitor.getClass(), s);
-            }
-            if (s != null) {
-                Class<?> type = s.getParameterTypes()[0];
-                if (!type.isInstance(result)) {
-                    // convert
-                    Object converted = Converters.convert(result, type);
-                    if (converted != null) {
-                        result = converted;
-                    } else {
-                        // could not set value
-                        return false;
-                    }
-                }
-
-                s.invoke(visitor, result);
-                return true;
-            }
-        } catch (Exception e) {
-            LOGGER.log(
-                    Level.INFO,
-                    "Failed to set optimized result, will fall back on full collection visit",
-                    e);
-        }
-        return false;
-    }
-
-    /** Inserts a new feature into the database for a particular feature type / table. */
-    protected void insert(SimpleFeature feature, SimpleFeatureType featureType, Connection cx)
-            throws IOException {
-        insert(Collections.singletonList(feature), featureType, cx);
-    }
-
-    /**
-     * Inserts a collection of new features into the database for a particular feature type / table.
-     */
-    protected void insert(
-            Collection<? extends SimpleFeature> features,
-            SimpleFeatureType featureType,
-            Connection cx)
-            throws IOException {
-        PrimaryKey key = getPrimaryKey(featureType);
-
-        // we do this in a synchronized block because we need to do two queries,
-        // first to figure out what the id will be, then the insert statement
-        synchronized (this) {
-            try {
-                if (dialect instanceof PreparedStatementSQLDialect) {
-                    Map<InsertionClassifier, Collection<SimpleFeature>> kinds =
-                            InsertionClassifier.classify(featureType, features);
-                    for (InsertionClassifier kind : kinds.keySet()) {
-                        insertPS(kinds.get(kind), kind, featureType, cx, key);
-                    }
-                } else {
-                    Collection<SimpleFeature> useExistings = new ArrayList<>();
-                    Collection<SimpleFeature> notUseExistings = new ArrayList<>();
-                    for (SimpleFeature cur : features) {
-                        (InsertionClassifier.useExisting(cur) ? useExistings : notUseExistings)
-                                .add(cur);
-                    }
-                    insertNonPS(useExistings, featureType, cx, key, true);
-                    insertNonPS(notUseExistings, featureType, cx, key, false);
-                }
-            } catch (SQLException e) {
-                String msg = "Error inserting features";
-                throw (IOException) new IOException(msg).initCause(e);
-            }
-        }
-    }
-
-    /** Specialized insertion for dialects that are using prepared statements. */
-    private void insertPS(
-            Collection<SimpleFeature> features,
-            InsertionClassifier kind,
-            SimpleFeatureType featureType,
-            Connection cx,
-            PrimaryKey key)
-            throws IOException, SQLException {
-        final PreparedStatementSQLDialect dialect = (PreparedStatementSQLDialect) getSQLDialect();
-
-        final KeysFetcher keysFetcher = KeysFetcher.create(this, cx, kind.useExisting, key);
-
-        final String sql = buildInsertPS(kind, featureType, keysFetcher, dialect);
-        LOGGER.log(Level.FINE, "Inserting new features with ps: {0}", sql);
-
-        // create the prepared statement
-        final PreparedStatement ps;
-        if (keysFetcher.isPostInsert()) {
-            // ask the DB to return the values of all the keys after the insertion
-            ps = cx.prepareStatement(sql, keysFetcher.getColumnNames());
-        } else {
-            ps = cx.prepareStatement(sql);
-        }
-        try {
-            for (SimpleFeature feature : features) {
-                // set the attribute values
-                int i = 1;
-                for (AttributeDescriptor att : featureType.getAttributeDescriptors()) {
-                    String colName = att.getLocalName();
-                    // skip the pk columns in case we have exposed them, we grab the
-                    // value from the pk itself
-                    if (keysFetcher.isKey(colName)) {
-                        continue;
-                    }
-
-                    Class binding = att.getType().getBinding();
-                    EnumMapper mapper =
-                            (EnumMapper) att.getUserData().get(JDBCDataStore.JDBC_ENUM_MAP);
-
-                    Object value = feature.getAttribute(colName);
-                    if (value == null && !att.isNillable()) {
-                        throw new IOException(
-                                "Cannot set a NULL value on the not null column " + colName);
-                    }
-
-                    if (Geometry.class.isAssignableFrom(binding)) {
-                        Geometry g = (Geometry) value;
-                        int srid = getGeometrySRID(g, att);
-                        int dimension = getGeometryDimension(g, att);
-                        dialect.setGeometryValue(g, dimension, srid, binding, ps, i);
-                    } else if (this.dialect.isArray(att)) {
-                        dialect.setArrayValue(value, att, ps, i, cx);
-                    } else {
-                        if (mapper != null) {
-                            value = mapper.fromString((String) value);
-                            binding = Integer.class;
-                        }
-                        dialect.setValue(value, binding, ps, i, cx);
-                    }
-                    if (LOGGER.isLoggable(Level.FINE)) {
-                        LOGGER.fine((i) + " = " + value);
-                    }
-                    i++;
-                }
-
-                keysFetcher.setKeyValues(dialect, ps, cx, featureType, feature, i);
-
-                dialect.onInsert(ps, cx, featureType);
-                ps.addBatch();
-            }
-            int[] inserts = ps.executeBatch();
-            checkAllInserted(inserts, features.size());
-            keysFetcher.postInsert(featureType, features, ps);
-        } finally {
-            closeSafe(ps);
-        }
-    }
-
-    static void checkAllInserted(int[] inserts, int size) throws IOException {
-        int sum = 0;
-        for (int cur : inserts) {
-            if (cur == PreparedStatement.SUCCESS_NO_INFO) {
-                return; // cannot check
-            } else if (cur == PreparedStatement.EXECUTE_FAILED) {
-                throw new IOException("Failed to insert some features");
-            }
-            sum += cur;
-        }
-        if (sum != size) {
-            throw new IOException("Failed to insert some features");
-        }
-    }
-
-    /** Build the insert statement that will be used in a PreparedStatement. */
-    private String buildInsertPS(
-            InsertionClassifier kind,
-            SimpleFeatureType featureType,
-            KeysFetcher keysFetcher,
-            PreparedStatementSQLDialect dialect)
-            throws SQLException {
-        StringBuffer sql = new StringBuffer();
-        sql.append("INSERT INTO ");
-        encodeTableName(featureType.getTypeName(), sql, null);
-
-        // column names
-        sql.append(" ( ");
-
-        for (int i = 0; i < featureType.getAttributeCount(); i++) {
-            String colName = featureType.getDescriptor(i).getLocalName();
-            // skip the pk columns in case we have exposed them
-            if (keysFetcher.isKey(colName)) {
-                continue;
-            }
-
-            dialect.encodeColumnName(null, colName, sql);
-            sql.append(",");
-        }
-
-        // primary key values
-        keysFetcher.addKeyColumns(sql);
-        sql.setLength(sql.length() - 1); // remove the last coma
-
-        // values
-        sql.append(" ) VALUES ( ");
-        for (AttributeDescriptor att : featureType.getAttributeDescriptors()) {
-            String colName = att.getLocalName();
-            // skip the pk columns in case we have exposed them, we grab the
-            // value from the pk itself
-            if (keysFetcher.isKey(colName)) {
-                continue;
-            }
-
-            // geometries might need special treatment, delegate to the dialect
-            if (att instanceof GeometryDescriptor) {
-                Class<? extends Geometry> geometryClass =
-                        kind.geometryTypes.get(att.getName().getLocalPart());
-                dialect.prepareGeometryValue(
-                        geometryClass,
-                        getDescriptorDimension(att),
-                        getDescriptorSRID(att),
-                        att.getType().getBinding(),
-                        sql);
-            } else {
-                sql.append("?");
-            }
-            sql.append(",");
-        }
-        keysFetcher.addKeyBindings(sql);
-
-        sql.setLength(sql.length() - 1);
-        sql.append(")");
-        return sql.toString();
-    }
-
-    /** Specialized insertion for dialects that are not using prepared statements. */
-    private void insertNonPS(
-            Collection<? extends SimpleFeature> features,
-            SimpleFeatureType featureType,
-            Connection cx,
-            PrimaryKey key,
-            boolean useExisting)
-            throws IOException, SQLException {
-        if (features.isEmpty()) {
-            return;
-        }
-        final Statement st = cx.createStatement();
-        final KeysFetcher keysFetcher = KeysFetcher.create(this, cx, useExisting, key);
-        try {
-            for (SimpleFeature feature : features) {
-                String sql = insertSQL(featureType, feature, keysFetcher, cx);
-
-                ((BasicSQLDialect) dialect).onInsert(st, cx, featureType);
-
-                LOGGER.log(Level.FINE, "Inserting new feature: {0}", sql);
-                if (keysFetcher.hasAutoGeneratedKeys()) {
-                    st.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
-                } else {
-                    st.executeUpdate(sql);
-                }
-
-                keysFetcher.postInsert(featureType, feature, cx, st);
-            }
-        } finally {
-            closeSafe(st);
-        }
-    }
-
-    /** Updates an existing feature(s) in the database for a particular feature type / table. */
-    protected void update(
-            SimpleFeatureType featureType,
-            List<AttributeDescriptor> attributes,
-            List<Object> values,
-            Filter filter,
-            Connection cx)
-            throws IOException, SQLException {
-        update(
-                featureType,
-                attributes.toArray(new AttributeDescriptor[attributes.size()]),
-                values.toArray(new Object[values.size()]),
-                filter,
-                cx);
-    }
-
-    /** Updates an existing feature(s) in the database for a particular feature type / table. */
-    protected void update(
-            SimpleFeatureType featureType,
-            AttributeDescriptor[] attributes,
-            Object[] values,
-            Filter filter,
-            Connection cx)
-            throws IOException, SQLException {
-        if ((attributes == null) || (attributes.length == 0)) {
-            LOGGER.warning("Update called with no attributes, doing nothing.");
-
-            return;
-        }
-
-        // grab primary key
-        PrimaryKey key = null;
-        try {
-            key = getPrimaryKey(featureType);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        Set<String> pkColumnNames = getColumnNames(key);
-
-        // do a check to ensure that the update includes at least one non primary key column
-        boolean nonPkeyColumn = false;
-        for (AttributeDescriptor att : attributes) {
-            if (!pkColumnNames.contains(att.getLocalName())) {
-                nonPkeyColumn = true;
-            }
-        }
-        if (!nonPkeyColumn) {
-            throw new IllegalArgumentException(
-                    "Illegal update, must include at least one non primary key column, "
-                            + "all primary key columns are ignored.");
-        }
-        if (dialect instanceof PreparedStatementSQLDialect) {
-            try {
-                PreparedStatement ps =
-                        updateSQLPS(featureType, attributes, values, filter, pkColumnNames, cx);
-                try {
-                    ((PreparedStatementSQLDialect) dialect).onUpdate(ps, cx, featureType);
-                    ps.execute();
-                } finally {
-                    closeSafe(ps);
-                }
-            } catch (SQLException e) {
-                throw new RuntimeException(e);
-            }
-        } else {
-            String sql = updateSQL(featureType, attributes, values, filter, pkColumnNames);
-
-            try {
-                Statement st = cx.createStatement();
-
-                try {
-                    ((BasicSQLDialect) dialect).onUpdate(st, cx, featureType);
-
-                    LOGGER.log(Level.FINE, "Updating feature: {0}", sql);
-                    st.execute(sql);
-                } finally {
-                    closeSafe(st);
-                }
-            } catch (SQLException e) {
-                String msg = "Error occured updating features";
-                throw (IOException) new IOException(msg).initCause(e);
-            }
-        }
-    }
-
-    /** Deletes an existing feature in the database for a particular feature type / fid. */
-    protected void delete(SimpleFeatureType featureType, String fid, Connection cx)
-            throws IOException {
-        Filter filter = filterFactory.id(Collections.singleton(filterFactory.featureId(fid)));
-        delete(featureType, filter, cx);
-    }
-
-    /** Deletes an existing feature(s) in the database for a particular feature type / table. */
-    protected void delete(SimpleFeatureType featureType, Filter filter, Connection cx)
-            throws IOException {
-
-        Statement st = null;
-        try {
-            try {
-                if (dialect instanceof PreparedStatementSQLDialect) {
-                    st = deleteSQLPS(featureType, filter, cx);
-                    @SuppressWarnings("PMD.CloseResource") // actually being closed later
-                    PreparedStatement ps = (PreparedStatement) st;
-
-                    ((PreparedStatementSQLDialect) dialect).onDelete(ps, cx, featureType);
-                    ps.execute();
-                } else {
-                    String sql = deleteSQL(featureType, filter);
-
-                    st = cx.createStatement();
-                    ((BasicSQLDialect) dialect).onDelete(st, cx, featureType);
-
-                    LOGGER.log(Level.FINE, "Removing feature(s): {0}", sql);
-                    st.execute(sql);
-                }
-            } finally {
-                closeSafe(st);
-            }
-        } catch (SQLException e) {
-            String msg = "Error occured during delete";
-            throw (IOException) new IOException(msg).initCause(e);
-        }
-    }
-
-    /**
-     * Returns a JDBC Connection to the underlying database for the specified GeoTools {@link
-     * Transaction}. This has two main use cases:
-     *
-     * <ul>
-     *   <li>Independently accessing the underlying database directly reusing the connection pool
-     *       contained in the {@link JDBCDataStore}
-     *   <li>Performing some direct access to the database in the same JDBC transaction as the
-     *       Geotools code
-     * </ul>
-     *
-     * The connection shall be used in a different way depending on the use case:
-     *
-     * <ul>
-     *   <li>If the transaction is {@link Transaction#AUTO_COMMIT} or if the transaction is not
-     *       shared with this data store and originating {@link FeatureStore} objects it is the duty
-     *       of the caller to properly close the connection after usage, failure to do so will
-     *       result in the connection pool loose one available connection permanently
-     *   <li>If the transaction is on the other side a valid transaction is shared with Geotools the
-     *       client code should refrain from closing the connection, committing or rolling back, and
-     *       use the {@link Transaction} facilities to do so instead
-     * </ul>
-     *
-     * @param t The GeoTools transaction. Can be {@code null}, in that case a new connection will be
-     *     returned (as if {@link Transaction#AUTO_COMMIT} was provided)
-     */
-    public Connection getConnection(Transaction t) throws IOException {
-        // short circuit this state, all auto commit transactions are using the same
-        if (t == Transaction.AUTO_COMMIT) {
-            Connection cx = createConnection();
-            try {
-                if (!cx.getAutoCommit()) {
-                    cx.setAutoCommit(true);
-                }
-            } catch (SQLException e) {
-                throw (IOException) new IOException().initCause(e);
-            }
-            return cx;
-        }
-
-        JDBCTransactionState tstate = (JDBCTransactionState) t.getState(this);
-        if (tstate != null) {
-            return tstate.cx;
-        } else {
-            Connection cx = createConnection();
-            try {
-                cx.setAutoCommit(false);
-            } catch (SQLException e) {
-                throw (IOException) new IOException().initCause(e);
-            }
-
-            tstate = new JDBCTransactionState(cx, this);
-            t.putState(this, tstate);
-            return cx;
-        }
-    }
-
-    /** Gets a database connection for the specified feature store. */
-    protected final Connection getConnection(JDBCState state) throws IOException {
-        return getConnection(state.getTransaction());
-    }
-
-    /**
-     * Creates a new connection.
-     *
-     * <p>Callers of this method should close the connection when done with it. .
-     */
-    protected final Connection createConnection() {
-        try {
-            LOGGER.fine("CREATE CONNECTION");
-
-            Connection cx = getDataSource().getConnection();
-
-            // isolation level is not set in the datastore, see
-            // http://jira.codehaus.org/browse/GEOT-2021
-
-            // call dialect callback to initialize the connection
-            dialect.initializeConnection(cx);
-
-            // if there is any lifecycle listener use it
-            if (!connectionLifecycleListeners.isEmpty()) {
-                List<ConnectionLifecycleListener> locals =
-                        new ArrayList<>(connectionLifecycleListeners);
-                return new LifecycleConnection(this, cx, locals);
-            }
-            return cx;
-        } catch (SQLException e) {
-            throw new RuntimeException("Unable to obtain connection: " + e.getMessage(), e);
-        }
-    }
-
-    /**
-     * Releases an existing connection (paying special attention to {@link Transaction#AUTO_COMMIT}.
-     *
-     * <p>If the state is based off the AUTO_COMMIT transaction - close using {@link
-     * #closeSafe(Connection)}. Otherwise wait until the transaction itself is closed to close the
-     * connection.
-     */
-    protected final void releaseConnection(Connection cx, JDBCState state) {
-        if (state.getTransaction() == Transaction.AUTO_COMMIT) {
-            closeSafe(cx);
-        }
-    }
-
-    /**
-     * Calls through to: <code><pre>
-     *   encodeFID(pkey, rs, 0);
-     * </pre></code>
-     */
-    public String encodeFID(PrimaryKey pkey, ResultSet rs) throws SQLException, IOException {
-        return encodeFID(pkey, rs, 0);
-    }
-
-    /**
-     * Encodes a feature id from a primary key and result set values.
-     *
-     * <p><tt>offset</tt> specifies where in the result set to start from when reading values for
-     * the primary key.
-     */
-    protected String encodeFID(PrimaryKey pkey, ResultSet rs, int offset)
-            throws SQLException, IOException {
-        // no pk columns
-        List<PrimaryKeyColumn> columns = pkey.getColumns();
-        if (columns.isEmpty()) {
-            return SimpleFeatureBuilder.createDefaultFeatureId();
-        }
-
-        // just one, no need to build support structures
-        if (columns.size() == 1) {
-            return dialect.getPkColumnValue(rs, columns.get(0), offset + 1);
-        }
-
-        // more than one
-        List<Object> keyValues = new ArrayList<>();
-        for (int i = 0; i < columns.size(); i++) {
-            String o = dialect.getPkColumnValue(rs, columns.get(0), offset + i + 1);
-            keyValues.add(o);
-        }
-        return encodeFID(keyValues);
-    }
-
-    protected static String encodeFID(List<Object> keyValues) {
-        StringBuffer fid = new StringBuffer();
-        for (Object o : keyValues) {
-            fid.append(o).append(".");
-        }
-        fid.setLength(fid.length() - 1);
-        return fid.toString();
-    }
-
-    /**
-     * Decodes a fid into its components based on a primary key.
-     *
-     * @param strict If set to true the value of the fid will be validated against the type of the
-     *     key columns. If a conversion can not be made, an exception will be thrown.
-     */
-    public static List<Object> decodeFID(PrimaryKey key, String FID, boolean strict) {
-        // strip off the feature type name
-        if (FID.startsWith(key.getTableName() + ".")) {
-            FID = FID.substring(key.getTableName().length() + 1);
-        }
-
-        try {
-            FID = URLDecoder.decode(FID, "UTF-8");
-        } catch (UnsupportedEncodingException e) {
-            throw new RuntimeException(e);
-        }
-
-        // check for case of multi column primary key and try to backwards map using
-        // "." as a seperator of values
-        List<Object> values = null;
-        if (key.getColumns().size() > 1) {
-            String[] split = FID.split("\\.");
-
-            // copy over to avoid array store exception
-            values = new ArrayList<>(split.length);
-            for (String s : split) {
-                values.add(s);
-            }
-        } else {
-            // single value case
-            values = new ArrayList<>();
-            values.add(FID);
-        }
-        if (values.size() != key.getColumns().size()) {
-            throw new IllegalArgumentException(
-                    "Illegal fid: "
-                            + FID
-                            + ". Expected "
-                            + key.getColumns().size()
-                            + " values but got "
-                            + values.size());
-        }
-
-        // convert to the type of the key
-        for (int i = 0; i < values.size(); i++) {
-            Object value = values.get(i);
-            if (value != null) {
-                Class<?> type = key.getColumns().get(i).getType();
-                Object converted = Converters.convert(value, type);
-                if (converted != null) {
-                    values.set(i, converted);
-                }
-                if (strict && !type.isInstance(values.get(i))) {
-                    throw new IllegalArgumentException(
-                            "Value " + values.get(i) + " illegal for type " + type.getName());
-                }
-            }
-        }
-
-        return values;
-    }
-
-    /**
-     * Determines if a primary key is made up entirely of column which are generated via an
-     * auto-generating column or a sequence.
-     */
-    protected boolean isGenerated(PrimaryKey pkey) {
-        for (PrimaryKeyColumn col : pkey.getColumns()) {
-            if (!(col instanceof AutoGeneratedPrimaryKeyColumn)) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    //
-    // SQL generation
-    //
-    /** Generates a 'CREATE TABLE' sql statement. */
-    protected String createTableSQL(SimpleFeatureType featureType, Connection cx) throws Exception {
-        // figure out the names and types of the columns
-        String[] columnNames = new String[featureType.getAttributeCount()];
-        Class[] classes = new Class[featureType.getAttributeCount()];
-
-        // figure out which columns can not be null
-        boolean[] nillable = new boolean[featureType.getAttributeCount()];
-
-        for (int i = 0; i < featureType.getAttributeCount(); i++) {
-            AttributeDescriptor attributeType = featureType.getDescriptor(i);
-
-            // column name
-            columnNames[i] = attributeType.getLocalName();
-
-            // column type
-            classes[i] = attributeType.getType().getBinding();
-
-            // can be null?
-            nillable[i] = attributeType.getMinOccurs() <= 0 || attributeType.isNillable();
-
-            // eventual options
-        }
-
-        String[] sqlTypeNames = getSQLTypeNames(featureType.getAttributeDescriptors(), cx);
-        for (int i = 0; i < sqlTypeNames.length; i++) {
-            if (sqlTypeNames[i] == null) {
-                String msg = "Unable to map " + columnNames[i] + "( " + classes[i].getName() + ")";
-                throw new RuntimeException(msg);
-            }
-        }
-
-        return createTableSQL(
-                featureType.getTypeName(),
-                columnNames,
-                sqlTypeNames,
-                nillable,
-                findPrimaryKeyColumnName(featureType),
-                featureType);
-    }
-
-    /*
-     * search feature type looking for suitable unique column for primary key.
-     */
-    protected String findPrimaryKeyColumnName(SimpleFeatureType featureType) {
-        String[] suffix = {"", "_1", "_2"};
-        String[] base = {"fid", "id", "gt_id", "ogc_fid"};
-
-        for (String b : base) {
-            O:
-            for (String s : suffix) {
-                String name = b + s;
-                for (AttributeDescriptor ad : featureType.getAttributeDescriptors()) {
-                    if (ad.getLocalName().equalsIgnoreCase(name)) {
-                        continue O;
-                    }
-                }
-                return name;
-            }
-        }
-
-        // practically should never get here, but just fall back and fail later
-        return "fid";
-    }
-
-    /** Generates a 'DROP TABLE' sql statement. */
-    protected String dropTableSQL(SimpleFeatureType featureType, Connection cx) throws Exception {
-        StringBuffer sql = new StringBuffer();
-        sql.append("DROP TABLE ");
-
-        encodeTableName(featureType.getTypeName(), sql, null);
-
-        return sql.toString();
-    }
-
-    /**
-     * Ensures that that the specified transaction has access to features specified by a filter.
-     *
-     * <p>If any features matching the filter are locked, and the transaction does not have
-     * authorization with respect to the lock, an exception is thrown.
-     *
-     * @param featureType The feature type / table.
-     * @param filter The filters.
-     * @param tx The transaction.
-     * @param cx The database connection.
-     */
-    protected void ensureAuthorization(
-            SimpleFeatureType featureType, Filter filter, Transaction tx, Connection cx)
-            throws IOException, SQLException {
-
-        InProcessLockingManager lm = (InProcessLockingManager) getLockingManager();
-        // verify if we have any lock to check
-        Map locks = lm.locks(featureType.getTypeName());
-        if (!locks.isEmpty()) {
-            // limiting query to only extract locked features
-            if (locks.size() <= MAX_IDS_IN_FILTER) {
-                Set<FeatureId> ids = getLockedIds(locks);
-                Id lockFilter = getFilterFactory().id(ids);
-                // intersect given filter with ids filter
-                filter = getFilterFactory().and(filter, lockFilter);
-            }
-            Query query = new Query(featureType.getTypeName(), filter, Query.NO_NAMES);
-
-            Statement st = null;
-            try {
-                ResultSet rs = null;
-                if (getSQLDialect() instanceof PreparedStatementSQLDialect) {
-                    st = selectSQLPS(featureType, query, cx);
-
-                    @SuppressWarnings("PMD.CloseResource") // actually being closed later
-                    PreparedStatement ps = (PreparedStatement) st;
-                    ((PreparedStatementSQLDialect) getSQLDialect()).onSelect(ps, cx, featureType);
-                    rs = ps.executeQuery();
-                } else {
-                    String sql = selectSQL(featureType, query);
-
-                    st = cx.createStatement();
-                    st.setFetchSize(fetchSize);
-                    ((BasicSQLDialect) getSQLDialect()).onSelect(st, cx, featureType);
-
-                    LOGGER.fine(sql);
-                    rs = st.executeQuery(sql);
-                }
-
-                try {
-                    PrimaryKey key = getPrimaryKey(featureType);
-
-                    while (rs.next()) {
-                        String fid = featureType.getTypeName() + "." + encodeFID(key, rs);
-                        lm.assertAccess(featureType.getTypeName(), fid, tx);
-                    }
-                } finally {
-                    closeSafe(rs);
-                }
-            } finally {
-                closeSafe(st);
-            }
-        }
-    }
-
-    /** Extracts a set of FeatureId objects from the locks Map. */
-    private Set<FeatureId> getLockedIds(Map locks) {
-        Set<FeatureId> ids = new HashSet<>();
-        for (Object lock : locks.keySet()) {
-            ids.add(getFilterFactory().featureId(lock.toString()));
-        }
-        return ids;
-    }
-
-    /** Helper method for creating geometry association table if it does not exist. */
-    protected void ensureAssociationTablesExist(Connection cx) throws IOException, SQLException {
-        // look for feature relationship table
-        DatabaseMetaData metadata = cx.getMetaData();
-        ResultSet tables =
-                metadata.getTables(
-                        null,
-                        escapeNamePattern(metadata, databaseSchema),
-                        escapeNamePattern(metadata, FEATURE_RELATIONSHIP_TABLE),
-                        null);
-
-        try {
-            if (!tables.next()) {
-                // does not exist, create it
-                String sql = createRelationshipTableSQL(cx);
-                LOGGER.log(Level.FINE, "Creating relationship table: {0}", sql);
-
-                Statement st = cx.createStatement();
-
-                try {
-                    st.execute(sql);
-                } finally {
-                    closeSafe(st);
-                }
-            }
-        } finally {
-            closeSafe(tables);
-        }
-
-        // look for feature association table
-        tables =
-                metadata.getTables(
-                        null,
-                        escapeNamePattern(metadata, databaseSchema),
-                        escapeNamePattern(metadata, FEATURE_ASSOCIATION_TABLE),
-                        null);
-
-        try {
-            if (!tables.next()) {
-                // does not exist, create it
-                String sql = createAssociationTableSQL(cx);
-                LOGGER.log(Level.FINE, "Creating association table: {0}", sql);
-
-                Statement st = cx.createStatement();
-
-                try {
-                    st.execute(sql);
-                } finally {
-                    closeSafe(st);
-                }
-            }
-        } finally {
-            closeSafe(tables);
-        }
-
-        // look up for geometry table
-        tables =
-                metadata.getTables(
-                        null,
-                        escapeNamePattern(metadata, databaseSchema),
-                        escapeNamePattern(metadata, GEOMETRY_TABLE),
-                        null);
-
-        try {
-            if (!tables.next()) {
-                // does not exist, create it
-                String sql = createGeometryTableSQL(cx);
-                LOGGER.log(Level.FINE, "Creating geometry table: {0}", sql);
-
-                Statement st = cx.createStatement();
-
-                try {
-                    st.execute(sql);
-                } finally {
-                    closeSafe(st);
-                }
-            }
-        } finally {
-            closeSafe(tables);
-        }
-
-        // look up for multi geometry table
-        tables =
-                metadata.getTables(
-                        null,
-                        escapeNamePattern(metadata, databaseSchema),
-                        escapeNamePattern(metadata, MULTI_GEOMETRY_TABLE),
-                        null);
-
-        try {
-            if (!tables.next()) {
-                // does not exist, create it
-                String sql = createMultiGeometryTableSQL(cx);
-                LOGGER.log(Level.FINE, "Creating multi-geometry table: {0}", sql);
-
-                Statement st = cx.createStatement();
-
-                try {
-                    st.execute(sql);
-                } finally {
-                    closeSafe(st);
-                }
-            }
-        } finally {
-            closeSafe(tables);
-        }
-
-        // look up for metadata for geometry association table
-        tables =
-                metadata.getTables(
-                        null,
-                        escapeNamePattern(metadata, databaseSchema),
-                        escapeNamePattern(metadata, GEOMETRY_ASSOCIATION_TABLE),
-                        null);
-
-        try {
-            if (!tables.next()) {
-                // does not exist, create it
-                String sql = createGeometryAssociationTableSQL(cx);
-                LOGGER.log(Level.FINE, "Creating geometry association table: {0}", sql);
-
-                Statement st = cx.createStatement();
-
-                try {
-                    st.execute(sql);
-                } finally {
-                    closeSafe(st);
-                }
-            }
-        } finally {
-            closeSafe(tables);
-        }
-    }
-
-    /**
-     * Creates the sql for the relationship table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     */
-    protected String createRelationshipTableSQL(Connection cx) throws SQLException {
-        String[] sqlTypeNames = getSQLTypeNames(descriptors(String.class, String.class), cx);
-        String[] columnNames = {"table", "col"};
-
-        return createTableSQL(
-                FEATURE_RELATIONSHIP_TABLE, columnNames, sqlTypeNames, null, null, null);
-    }
-
-    /**
-     * Creates the sql for the association table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     */
-    protected String createAssociationTableSQL(Connection cx) throws SQLException {
-        String[] sqlTypeNames =
-                getSQLTypeNames(
-                        descriptors(String.class, String.class, String.class, String.class), cx);
-        String[] columnNames = {"fid", "rtable", "rcol", "rfid"};
-
-        return createTableSQL(
-                FEATURE_ASSOCIATION_TABLE, columnNames, sqlTypeNames, null, null, null);
-    }
-
-    /**
-     * Creates the sql for the geometry table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     */
-    protected String createGeometryTableSQL(Connection cx) throws SQLException {
-        String[] sqlTypeNames =
-                getSQLTypeNames(
-                        descriptors(
-                                String.class,
-                                String.class,
-                                String.class,
-                                String.class,
-                                Geometry.class),
-                        cx);
-        String[] columnNames = {"id", "name", "description", "type", "geometry"};
-
-        return createTableSQL(GEOMETRY_TABLE, columnNames, sqlTypeNames, null, null, null);
-    }
-
-    /**
-     * Creates the sql for the multi_geometry table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     */
-    protected String createMultiGeometryTableSQL(Connection cx) throws SQLException {
-        String[] sqlTypeNames =
-                getSQLTypeNames(descriptors(String.class, String.class, Boolean.class), cx);
-        String[] columnNames = {"id", "mgid", "ref"};
-
-        return createTableSQL(MULTI_GEOMETRY_TABLE, columnNames, sqlTypeNames, null, null, null);
-    }
-
-    private List<AttributeDescriptor> descriptors(Class<?>... classes) {
-        AttributeTypeBuilder tb = new AttributeTypeBuilder();
-        List<AttributeDescriptor> result = new ArrayList<>();
-        for (int i = 0; i < classes.length; i++) {
-            Class<?> aClass = classes[i];
-            AttributeDescriptor ad = tb.name("a" + i).binding(aClass).buildDescriptor("a" + i);
-            result.add(ad);
-        }
-
-        return result;
-    }
-
-    /**
-     * Creates the sql for the relationship table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     *
-     * @param table The table of the association
-     * @param column The column of the association
-     */
-    protected String selectRelationshipSQL(String table, String column) throws SQLException {
-        BasicSQLDialect dialect = (BasicSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-        dialect.encodeColumnName(null, "table", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "col", sql);
-
-        sql.append(" FROM ");
-        encodeTableName(FEATURE_RELATIONSHIP_TABLE, sql, null);
-
-        if (table != null) {
-            sql.append(" WHERE ");
-
-            dialect.encodeColumnName(null, "table", sql);
-            sql.append(" = ");
-            dialect.encodeValue(table, String.class, sql);
-        }
-
-        if (column != null) {
-            if (table == null) {
-                sql.append(" WHERE ");
-            } else {
-                sql.append(" AND ");
-            }
-
-            dialect.encodeColumnName(null, "col", sql);
-            sql.append(" = ");
-            dialect.encodeValue(column, String.class, sql);
-        }
-
-        return sql.toString();
-    }
-
-    /**
-     * Creates the prepared statement for a query against the relationship table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     *
-     * @param table The table of the association
-     * @param column The column of the association
-     */
-    protected PreparedStatement selectRelationshipSQLPS(String table, String column, Connection cx)
-            throws SQLException {
-        PreparedStatementSQLDialect dialect = (PreparedStatementSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-        dialect.encodeColumnName(null, "table", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "col", sql);
-
-        sql.append(" FROM ");
-        encodeTableName(FEATURE_RELATIONSHIP_TABLE, sql, null);
-
-        if (table != null) {
-            sql.append(" WHERE ");
-
-            dialect.encodeColumnName(null, "table", sql);
-            sql.append(" = ? ");
-        }
-
-        if (column != null) {
-            if (table == null) {
-                sql.append(" WHERE ");
-            } else {
-                sql.append(" AND ");
-            }
-
-            dialect.encodeColumnName(null, "col", sql);
-            sql.append(" = ? ");
-        }
-
-        LOGGER.fine(sql.toString());
-        PreparedStatement ps = cx.prepareStatement(sql.toString());
-        if (table != null) {
-            ps.setString(1, table);
-        }
-        if (column != null) {
-            ps.setString(table != null ? 2 : 1, column);
-        }
-        return ps;
-    }
-
-    /**
-     * Creates the sql for the association table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     *
-     * @param fid The feature id of the association
-     */
-    protected String selectAssociationSQL(String fid) throws SQLException {
-        BasicSQLDialect dialect = (BasicSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-        dialect.encodeColumnName(null, "fid", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "rtable", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "rcol", sql);
-        sql.append(", ");
-        dialect.encodeColumnName(null, "rfid", sql);
-
-        sql.append(" FROM ");
-        encodeTableName(FEATURE_ASSOCIATION_TABLE, sql, null);
-
-        if (fid != null) {
-            sql.append(" WHERE ");
-
-            dialect.encodeColumnName(null, "fid", sql);
-            sql.append(" = ");
-            dialect.encodeValue(fid, String.class, sql);
-        }
-
-        return sql.toString();
-    }
-
-    /**
-     * Creates the prepared statement for the association table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     *
-     * @param fid The feature id of the association
-     */
-    protected PreparedStatement selectAssociationSQLPS(String fid, Connection cx)
-            throws SQLException {
-        PreparedStatementSQLDialect dialect = (PreparedStatementSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-        dialect.encodeColumnName(null, "fid", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "rtable", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "rcol", sql);
-        sql.append(", ");
-        dialect.encodeColumnName(null, "rfid", sql);
-
-        sql.append(" FROM ");
-        encodeTableName(FEATURE_ASSOCIATION_TABLE, sql, null);
-
-        if (fid != null) {
-            sql.append(" WHERE ");
-
-            dialect.encodeColumnName(null, "fid", sql);
-            sql.append(" = ?");
-        }
-
-        LOGGER.fine(sql.toString());
-        PreparedStatement ps = cx.prepareStatement(sql.toString());
-        if (fid != null) {
-            ps.setString(1, fid);
-        }
-
-        return ps;
-    }
-
-    /**
-     * Creates the sql for a select from the geometry table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     *
-     * @param gid The geometry id to select for, may be <code>null</code>
-     */
-    protected String selectGeometrySQL(String gid) throws SQLException {
-
-        BasicSQLDialect dialect = (BasicSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-        dialect.encodeColumnName(null, "id", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "name", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "description", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "type", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "geometry", sql);
-        sql.append(" FROM ");
-        encodeTableName(GEOMETRY_TABLE, sql, null);
-
-        if (gid != null) {
-            sql.append(" WHERE ");
-
-            dialect.encodeColumnName(null, "id", sql);
-            sql.append(" = ");
-            dialect.encodeValue(gid, String.class, sql);
-        }
-
-        return sql.toString();
-    }
-
-    /**
-     * Creates the prepared for a select from the geometry table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     *
-     * @param gid The geometry id to select for, may be <code>null</code>
-     */
-    protected PreparedStatement selectGeometrySQLPS(String gid, Connection cx) throws SQLException {
-        PreparedStatementSQLDialect dialect = (PreparedStatementSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-        dialect.encodeColumnName(null, "id", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "name", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "description", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "type", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "geometry", sql);
-        sql.append(" FROM ");
-        encodeTableName(GEOMETRY_TABLE, sql, null);
-
-        if (gid != null) {
-            sql.append(" WHERE ");
-
-            dialect.encodeColumnName(null, "id", sql);
-            sql.append(" = ?");
-        }
-
-        LOGGER.fine(sql.toString());
-        PreparedStatement ps = cx.prepareStatement(sql.toString());
-        if (gid != null) {
-            ps.setString(1, gid);
-        }
-
-        return ps;
-    }
-
-    /**
-     * Creates the sql for a select from the multi geometry table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     *
-     * @param gid The geometry id to select for, may be <code>null</code>.
-     */
-    protected String selectMultiGeometrySQL(String gid) throws SQLException {
-        BasicSQLDialect dialect = (BasicSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-        dialect.encodeColumnName(null, "id", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "mgid", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "ref", sql);
-
-        sql.append(" FROM ");
-        encodeTableName(MULTI_GEOMETRY_TABLE, sql, null);
-
-        if (gid != null) {
-            sql.append(" WHERE ");
-
-            dialect.encodeColumnName(null, "id", sql);
-            sql.append(" = ");
-            dialect.encodeValue(gid, String.class, sql);
-        }
-
-        return sql.toString();
-    }
-
-    /**
-     * Creates the prepared statement for a select from the multi geometry table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     *
-     * @param gid The geometry id to select for, may be <code>null</code>.
-     */
-    protected PreparedStatement selectMultiGeometrySQLPS(String gid, Connection cx)
-            throws SQLException {
-        PreparedStatementSQLDialect dialect = (PreparedStatementSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-        dialect.encodeColumnName(null, "id", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "mgid", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "ref", sql);
-
-        sql.append(" FROM ");
-        encodeTableName(MULTI_GEOMETRY_TABLE, sql, null);
-
-        if (gid != null) {
-            sql.append(" WHERE ");
-
-            dialect.encodeColumnName(null, "id", sql);
-            sql.append(" = ?");
-        }
-
-        LOGGER.fine(sql.toString());
-        PreparedStatement ps = cx.prepareStatement(sql.toString());
-        if (gid != null) {
-            ps.setString(1, gid);
-        }
-
-        return ps;
-    }
-
-    /**
-     * Creates the sql for the geometry association table.
-     *
-     * <p>This method is only called when {@link JDBCDataStore#isAssociations()} is true.
-     */
-    protected String createGeometryAssociationTableSQL(Connection cx) throws SQLException {
-        String[] sqlTypeNames =
-                getSQLTypeNames(
-                        descriptors(String.class, String.class, String.class, Boolean.class), cx);
-        String[] columnNames = {"fid", "gname", "gid", "ref"};
-
-        return createTableSQL(
-                GEOMETRY_ASSOCIATION_TABLE, columnNames, sqlTypeNames, null, null, null);
-    }
-
-    /**
-     * Creates the sql for a select from the geometry association table.
-     *
-     * <p>
-     *
-     * @param fid The fid to select for, may be <code>null</code>
-     * @param gid The geometry id to select for, may be <code>null</code>
-     * @param gname The geometry name to select for, may be <code>null</code>
-     */
-    protected String selectGeometryAssociationSQL(String fid, String gid, String gname)
-            throws SQLException {
-        BasicSQLDialect dialect = (BasicSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-        dialect.encodeColumnName(null, "fid", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "gid", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "gname", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "ref", sql);
-
-        sql.append(" FROM ");
-        encodeTableName(GEOMETRY_ASSOCIATION_TABLE, sql, null);
-
-        if (fid != null) {
-            sql.append(" WHERE ");
-            dialect.encodeColumnName(null, "fid", sql);
-            sql.append(" = ");
-            dialect.encodeValue(fid, String.class, sql);
-        }
-
-        if (gid != null) {
-            if (fid == null) {
-                sql.append(" WHERE ");
-            } else {
-                sql.append(" AND ");
-            }
-
-            dialect.encodeColumnName(null, "gid", sql);
-            sql.append(" = ");
-            dialect.encodeValue(gid, String.class, sql);
-        }
-
-        if (gname != null) {
-            if ((fid == null) && (gid == null)) {
-                sql.append(" WHERE ");
-            } else {
-                sql.append(" AND ");
-            }
-
-            dialect.encodeColumnName(null, "gname", sql);
-            sql.append(" = ");
-            dialect.encodeValue(gname, String.class, sql);
-        }
-
-        return sql.toString();
-    }
-    /**
-     * Creates the prepared statement for a select from the geometry association table.
-     *
-     * <p>
-     *
-     * @param fid The fid to select for, may be <code>null</code>
-     * @param gid The geometry id to select for, may be <code>null</code>
-     * @param gname The geometry name to select for, may be <code>null</code>
-     */
-    protected PreparedStatement selectGeometryAssociationSQLPS(
-            String fid, String gid, String gname, Connection cx) throws SQLException {
-        PreparedStatementSQLDialect dialect = (PreparedStatementSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-        dialect.encodeColumnName(null, "fid", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "gid", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "gname", sql);
-        sql.append(",");
-        dialect.encodeColumnName(null, "ref", sql);
-
-        sql.append(" FROM ");
-        encodeTableName(GEOMETRY_ASSOCIATION_TABLE, sql, null);
-
-        if (fid != null) {
-            sql.append(" WHERE ");
-            dialect.encodeColumnName(null, "fid", sql);
-            sql.append(" = ? ");
-        }
-
-        if (gid != null) {
-            if (fid == null) {
-                sql.append(" WHERE ");
-            } else {
-                sql.append(" AND ");
-            }
-
-            dialect.encodeColumnName(null, "gid", sql);
-            sql.append(" = ? ");
-        }
-
-        if (gname != null) {
-            if ((fid == null) && (gid == null)) {
-                sql.append(" WHERE ");
-            } else {
-                sql.append(" AND ");
-            }
-
-            dialect.encodeColumnName(null, "gname", sql);
-            sql.append(" = ?");
-        }
-
-        LOGGER.fine(sql.toString());
-        PreparedStatement ps = cx.prepareStatement(sql.toString());
-        if (fid != null) {
-            ps.setString(1, fid);
-        }
-
-        if (gid != null) {
-            ps.setString(fid != null ? 2 : 1, gid);
-        }
-
-        if (gname != null) {
-            ps.setString(fid != null ? (gid != null ? 3 : 2) : (gid != null ? 2 : 1), gname);
-        }
-
-        return ps;
-    }
-
-    /** Helper method for building a 'CREATE TABLE' sql statement. */
-    private String createTableSQL(
-            String tableName,
-            String[] columnNames,
-            String[] sqlTypeNames,
-            boolean[] nillable,
-            String pkeyColumn,
-            SimpleFeatureType featureType)
-            throws SQLException {
-        // build the create table sql
-        StringBuffer sql = new StringBuffer();
-        dialect.encodeCreateTable(sql);
-
-        encodeTableName(tableName, sql, null);
-        sql.append(" ( ");
-
-        // primary key column
-        if (pkeyColumn != null) {
-            dialect.encodePrimaryKey(pkeyColumn, sql);
-            sql.append(", ");
-        }
-
-        // normal attributes
-        for (int i = 0; i < columnNames.length; i++) {
-            // the column name
-            dialect.encodeColumnName(null, columnNames[i], sql);
-            sql.append(" ");
-
-            // some sql dialects require varchars to have an
-            // associated size with them
-            int length = -1;
-            if (sqlTypeNames[i].toUpperCase().startsWith("VARCHAR")) {
-                if (featureType != null) {
-                    AttributeDescriptor att = featureType.getDescriptor(columnNames[i]);
-                    length = findVarcharColumnLength(att);
-                }
-            }
-
-            // only apply a length if one exists (i.e. to applicable varchars)
-            if (length == -1) {
-                dialect.encodeColumnType(sqlTypeNames[i], sql);
-            } else {
-                dialect.encodeColumnType(sqlTypeNames[i] + "(" + length + ")", sql);
-            }
-
-            // nullable
-            if (nillable != null && !nillable[i]) {
-                sql.append(" NOT NULL ");
-            }
-
-            // delegate to dialect to encode column postamble
-            if (featureType != null) {
-                AttributeDescriptor att = featureType.getDescriptor(columnNames[i]);
-                dialect.encodePostColumnCreateTable(att, sql);
-            }
-
-            // sql.append(sqlTypeNames[i]);
-            if (i < (sqlTypeNames.length - 1)) {
-                sql.append(", ");
-            }
-        }
-
-        sql.append(" ) ");
-
-        // encode anything post create table
-        dialect.encodePostCreateTable(tableName, sql);
-
-        return sql.toString();
-    }
-
-    /**
-     * Searches the attribute descriptor restrictions in an attempt to determine the length of the
-     * specified varchar column.
-     */
-    private Integer findVarcharColumnLength(AttributeDescriptor att) {
-        for (Filter r : att.getType().getRestrictions()) {
-            if (r instanceof PropertyIsLessThanOrEqualTo) {
-                PropertyIsLessThanOrEqualTo c = (PropertyIsLessThanOrEqualTo) r;
-                if (c.getExpression1() instanceof Function
-                        && ((Function) c.getExpression1())
-                                .getName()
-                                .toLowerCase()
-                                .endsWith("length")) {
-                    if (c.getExpression2() instanceof Literal) {
-                        Integer length = c.getExpression2().evaluate(null, Integer.class);
-                        if (length != null) {
-                            return length;
-                        }
-                    }
-                }
-            }
-        }
-
-        return dialect.getDefaultVarcharSize();
-    }
-
-    /**
-     * Helper method for determining what the sql type names are for a set of classes.
-     *
-     * <p>This method uses a combination of dialect mappings and database metadata to determine
-     * which sql types map to the specified classes.
-     */
-    private String[] getSQLTypeNames(List<AttributeDescriptor> descriptors, Connection cx)
-            throws SQLException {
-        // figure out what the sql types are corresponding to the feature type
-        // attributes
-        int[] sqlTypes = new int[descriptors.size()];
-        String[] sqlTypeNames = new String[sqlTypes.length];
-
-        for (int i = 0; i < descriptors.size(); i++) {
-            AttributeDescriptor ad = descriptors.get(i);
-            Class clazz = ad.getType().getBinding();
-            Integer sqlType = dialect.getSQLType(ad);
-
-            if (sqlType == null) {
-                sqlType = getMapping(clazz);
-            }
-
-            if (sqlType == null) {
-                LOGGER.warning(
-                        "No sql type mapping for: " + ad.getLocalName() + " of type " + clazz);
-                sqlType = Types.OTHER;
-            }
-
-            sqlTypes[i] = sqlType;
-
-            // if this a geometric type, get the name from the dialect
-            // if ( attributeType instanceof GeometryDescriptor ) {
-            if (Geometry.class.isAssignableFrom(clazz)) {
-                String sqlTypeName = dialect.getGeometryTypeName(sqlType);
-
-                if (sqlTypeName != null) {
-                    sqlTypeNames[i] = sqlTypeName;
-                }
-            }
-
-            // check types previously read from DB
-            String sqlTypeDBName = getDBsqlTypesCache().get(sqlType);
-            if (sqlTypeDBName != null) {
-                sqlTypeNames[i] = sqlTypeDBName;
-            }
-        }
-        // GEOT-6347 if all sql type names have been found in dialect dont
-        // go to database
-        boolean allTypesFound = !ArrayUtils.contains(sqlTypeNames, null);
-        if (!allTypesFound) {
-            LOGGER.log(Level.WARNING, "Fetching fields from Database");
-            // figure out the type names that correspond to the sql types from
-            // the database metadata
-            DatabaseMetaData metaData = cx.getMetaData();
-
-            /*
-            *      <LI><B>TYPE_NAME</B> String => Type name
-            *        <LI><B>DATA_TYPE</B> int => SQL data type from java.sql.Types
-            *        <LI><B>PRECISION</B> int => maximum precision
-            *        <LI><B>LITERAL_PREFIX</B> String => prefix used to quote a literal
-            *      (may be <code>null</code>)
-            *        <LI><B>LITERAL_SUFFIX</B> String => suffix used to quote a literal
-                (may be <code>null</code>)
-            *        <LI><B>CREATE_PARAMS</B> String => parameters used in creating
-            *      the type (may be <code>null</code>)
-            *        <LI><B>NULLABLE</B> short => can you use NULL for this type.
-            *      <UL>
-            *      <LI> typeNoNulls - does not allow NULL values
-            *      <LI> typeNullable - allows NULL values
-            *      <LI> typeNullableUnknown - nullability unknown
-            *      </UL>
-            *        <LI><B>CASE_SENSITIVE</B> boolean=> is it case sensitive.
-            *        <LI><B>SEARCHABLE</B> short => can you use "WHERE" based on this type:
-            *      <UL>
-            *      <LI> typePredNone - No support
-            *      <LI> typePredChar - Only supported with WHERE .. LIKE
-            *      <LI> typePredBasic - Supported except for WHERE .. LIKE
-            *      <LI> typeSearchable - Supported for all WHERE ..
-            *      </UL>
-            *        <LI><B>UNSIGNED_ATTRIBUTE</B> boolean => is it unsigned.
-            *        <LI><B>FIXED_PREC_SCALE</B> boolean => can it be a money value.
-            *        <LI><B>AUTO_INCREMENT</B> boolean => can it be used for an
-            *      auto-increment value.
-            *        <LI><B>LOCAL_TYPE_NAME</B> String => localized version of type name
-            *      (may be <code>null</code>)
-            *        <LI><B>MINIMUM_SCALE</B> short => minimum scale supported
-            *        <LI><B>MAXIMUM_SCALE</B> short => maximum scale supported
-            *        <LI><B>SQL_DATA_TYPE</B> int => unused
-            *        <LI><B>SQL_DATETIME_SUB</B> int => unused
-            *        <LI><B>NUM_PREC_RADIX</B> int => usually 2 or 10
-            */
-            ResultSet types = metaData.getTypeInfo();
-
-            try {
-                while (types.next()) {
-                    int sqlType = types.getInt("DATA_TYPE");
-                    String sqlTypeName = types.getString("TYPE_NAME");
-
-                    for (int i = 0; i < sqlTypes.length; i++) {
-                        // check if we already have the type name from the dialect
-                        if (sqlTypeNames[i] != null) {
-                            continue;
-                        }
-
-                        if (sqlType == sqlTypes[i]) {
-                            sqlTypeNames[i] = sqlTypeName;
-                            // GEOT-6347
-                            // cache the sqlType which required going to database for sql types
-                            getDBsqlTypesCache().putIfAbsent(sqlType, sqlTypeName);
-                        }
-                    }
-                }
-            } finally {
-                closeSafe(types);
-            }
-        }
-
-        // apply the overrides specified by the dialect
-        Map<Integer, String> overrides = getSqlTypeToSqlTypeNameOverrides();
-        for (int i = 0; i < sqlTypes.length; i++) {
-            String override = overrides.get(sqlTypes[i]);
-            if (override != null) sqlTypeNames[i] = override;
-        }
-
-        return sqlTypeNames;
-    }
-
-    /**
-     * Generates a 'SELECT p1, p2, ... FROM ... WHERE ...' statement.
-     *
-     * @param featureType the feature type that the query must return (may contain less attributes
-     *     than the native one)
-     * @param query the query to be run. The type name and property will be ignored, as they are
-     *     supposed to have been already embedded into the provided feature type
-     */
-    public String selectSQL(SimpleFeatureType featureType, Query query)
-            throws IOException, SQLException {
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-
-        // column names
-        selectColumns(featureType, null, query, sql);
-        sql.setLength(sql.length() - 1);
-        dialect.encodePostSelect(featureType, sql);
-
-        // from
-        sql.append(" FROM ");
-        encodeTableName(featureType.getTypeName(), sql, setKeepWhereClausePlaceHolderHint(query));
-
-        // filtering
-        Filter filter = query.getFilter();
-        if (filter != null && !Filter.INCLUDE.equals(filter)) {
-            sql.append(" WHERE ");
-            // encode filter
-            filter(featureType, filter, sql);
-        }
-
-        // sorting
-        sort(featureType, query.getSortBy(), null, sql);
-
-        // encode limit/offset, if necessary
-        applyLimitOffset(sql, query.getStartIndex(), query.getMaxFeatures());
-
-        // add search hints if the dialect supports them
-        applySearchHints(featureType, query, sql);
-
-        return sql.toString();
-    }
-
-    private void applySearchHints(SimpleFeatureType featureType, Query query, StringBuffer sql) {
-        // we can apply search hints only on real tables
-        if (virtualTables.containsKey(featureType.getTypeName())) {
-            return;
-        }
-
-        dialect.handleSelectHints(sql, featureType, query);
-    }
-
-    protected String selectJoinSQL(SimpleFeatureType featureType, JoinInfo join, Query query)
-            throws IOException, SQLException {
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-
-        // column names
-        selectColumns(featureType, join.getPrimaryAlias(), query, sql);
-
-        // joined columns
-        for (JoinPart part : join.getParts()) {
-            selectColumns(part.getQueryFeatureType(), part.getAlias(), query, sql);
-        }
-
-        sql.setLength(sql.length() - 1);
-        dialect.encodePostSelect(featureType, sql);
-
-        // from
-        sql.append(" FROM ");
-
-        // join clauses
-        encodeTableJoin(featureType, join, query, sql);
-
-        // filtering
-        encodeWhereJoin(featureType, join, sql);
-
-        // TODO: sorting
-        sort(featureType, query.getSortBy(), join.getPrimaryAlias(), sql);
-
-        // finally encode limit/offset, if necessary
-        applyLimitOffset(sql, query.getStartIndex(), query.getMaxFeatures());
-
-        return sql.toString();
-    }
-
-    public void selectColumns(SimpleFeatureType featureType, String prefix, Query query, StringBuffer sql)
-            throws IOException {
-
-        // primary key
-        PrimaryKey key = null;
-        try {
-            key = getPrimaryKey(featureType);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        Set<String> pkColumnNames = getColumnNames(key);
-
-        // we need to add the primary key columns only if they are not already exposed
-        for (PrimaryKeyColumn col : key.getColumns()) {
-            dialect.encodeColumnName(prefix, col.getName(), sql);
-            if (prefix != null) {
-                // if a prefix is specified means we are joining so use a prefix to avoid clashing
-                // with primary key columsn with the same name from other tables in the join
-                dialect.encodeColumnAlias(prefix + "_" + col.getName(), sql);
-            }
-            sql.append(",");
-        }
-
-        // other columns
-        for (AttributeDescriptor att : featureType.getAttributeDescriptors()) {
-            String columnName = att.getLocalName();
-            // skip the eventually exposed pk column values
-            if (pkColumnNames.contains(columnName)) continue;
-
-            String alias = null;
-            if (att.getUserData().containsKey(JDBC_COLUMN_ALIAS)) {
-                alias = (String) att.getUserData().get(JDBC_COLUMN_ALIAS);
-            }
-
-            if (att instanceof GeometryDescriptor) {
-                // encode as geometry
-                encodeGeometryColumn((GeometryDescriptor) att, prefix, sql, query.getHints());
-
-                if (alias == null) {
-                    // alias it to be the name of the original geometry
-                    alias = columnName;
-                }
-            } else {
-                dialect.encodeColumnName(prefix, columnName, sql);
-            }
-
-            if (alias != null) {
-                dialect.encodeColumnAlias(alias, sql);
-            }
-
-            sql.append(",");
-        }
-    }
-
-    FilterToSQL filter(SimpleFeatureType featureType, Filter filter, StringBuffer sql)
-            throws IOException {
-        SimpleFeatureType fullSchema = getSchema(featureType.getTypeName());
-        FilterToSQL toSQL = getFilterToSQL(fullSchema);
-        return filter(featureType, filter, sql, toSQL);
-    }
-
-    FilterToSQL filter(
-            SimpleFeatureType featureType, Filter filter, StringBuffer sql, FilterToSQL toSQL)
-            throws IOException {
-
-        try {
-            // grab the full feature type, as we might be encoding a filter
-            // that uses attributes that aren't returned in the results
-            toSQL.setInline(true);
-            String filterSql = toSQL.encodeToString(filter);
-            int whereClauseIndex = sql.indexOf(WHERE_CLAUSE_PLACE_HOLDER);
-            if (whereClauseIndex != -1) {
-                sql.replace(
-                        whereClauseIndex,
-                        whereClauseIndex + WHERE_CLAUSE_PLACE_HOLDER_LENGTH,
-                        "AND " + filterSql);
-                sql.append("1 = 1");
-            } else {
-                sql.append(filterSql);
-            }
-            return toSQL;
-        } catch (FilterToSQLException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private FilterToSQL getFilterToSQL(SimpleFeatureType fullSchema) {
-        return dialect instanceof PreparedStatementSQLDialect
-                ? createPreparedFilterToSQL(fullSchema)
-                : createFilterToSQL(fullSchema);
-    }
-
-    /** Encodes the sort-by portion of an sql query */
-    void sort(SimpleFeatureType featureType, SortBy[] sort, String prefix, StringBuffer sql)
-            throws IOException {
-        if ((sort != null) && (sort.length > 0)) {
-            PrimaryKey key = getPrimaryKey(featureType);
-            sql.append(" ORDER BY ");
-
-            for (SortBy sortBy : sort) {
-                String order;
-                if (sortBy.getSortOrder() == SortOrder.DESCENDING) {
-                    order = " DESC";
-                } else {
-                    order = " ASC";
-                }
-
-                if (SortBy.NATURAL_ORDER.equals(sortBy) || SortBy.REVERSE_ORDER.equals(sortBy)) {
-                    if (key instanceof NullPrimaryKey)
-                        throw new IOException(
-                                "Cannot do natural order without a primary key, please add it or "
-                                        + "specify a manual sort over existing attributes");
-
-                    for (PrimaryKeyColumn col : key.getColumns()) {
-                        dialect.encodeColumnName(prefix, col.getName(), sql);
-                        sql.append(order);
-                        sql.append(",");
-                    }
-                } else {
-                    dialect.encodeColumnName(
-                            prefix, getPropertyName(featureType, sortBy.getPropertyName()), sql);
-                    sql.append(order);
-                    sql.append(",");
-                }
-            }
-
-            sql.setLength(sql.length() - 1);
-        }
-    }
-
-    /**
-     * Generates a 'SELECT p1, p2, ... FROM ... WHERE ...' prepared statement.
-     *
-     * @param featureType the feature type that the query must return (may contain less attributes
-     *     than the native one)
-     * @param query the query to be run. The type name and property will be ignored, as they are
-     *     supposed to have been already embedded into the provided feature type
-     * @param cx The database connection to be used to create the prepared statement
-     */
-    protected PreparedStatement selectSQLPS(
-            SimpleFeatureType featureType, Query query, Connection cx)
-            throws SQLException, IOException {
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-
-        // column names
-        selectColumns(featureType, null, query, sql);
-        sql.setLength(sql.length() - 1);
-        dialect.encodePostSelect(featureType, sql);
-
-        sql.append(" FROM ");
-        encodeTableName(featureType.getTypeName(), sql, setKeepWhereClausePlaceHolderHint(query));
-
-        // filtering
-        PreparedFilterToSQL toSQL = null;
-        Filter filter = query.getFilter();
-        if (filter != null && !Filter.INCLUDE.equals(filter)) {
-            sql.append(" WHERE ");
-
-            // encode filter
-            toSQL = (PreparedFilterToSQL) filter(featureType, filter, sql);
-        }
-
-        // sorting
-        sort(featureType, query.getSortBy(), null, sql);
-
-        // finally encode limit/offset, if necessary
-        applyLimitOffset(sql, query.getStartIndex(), query.getMaxFeatures());
-
-        // add search hints if the dialect supports them
-        applySearchHints(featureType, query, sql);
-
-        LOGGER.fine(sql.toString());
-        PreparedStatement ps =
-                cx.prepareStatement(
-                        sql.toString(), ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
-        ps.setFetchSize(fetchSize);
-
-        if (toSQL != null) {
-            setPreparedFilterValues(ps, toSQL, 0, cx);
-        }
-
-        return ps;
-    }
-
-    protected PreparedStatement selectJoinSQLPS(
-            SimpleFeatureType featureType, JoinInfo join, Query query, Connection cx)
-            throws SQLException, IOException {
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("SELECT ");
-
-        selectColumns(featureType, join.getPrimaryAlias(), query, sql);
-
-        // joined columns
-        for (JoinPart part : join.getParts()) {
-            selectColumns(part.getQueryFeatureType(), part.getAlias(), query, sql);
-        }
-
-        sql.setLength(sql.length() - 1);
-        dialect.encodePostSelect(featureType, sql);
-
-        sql.append(" FROM ");
-
-        // join clauses
-        encodeTableJoin(featureType, join, query, sql);
-
-        // filtering
-        List<FilterToSQL> toSQLs = encodeWhereJoin(featureType, join, sql);
-
-        // sorting
-        sort(featureType, query.getSortBy(), join.getPrimaryAlias(), sql);
-
-        // finally encode limit/offset, if necessary
-        applyLimitOffset(sql, query.getStartIndex(), query.getMaxFeatures());
-
-        LOGGER.fine(sql.toString());
-        PreparedStatement ps =
-                cx.prepareStatement(
-                        sql.toString(), ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
-        ps.setFetchSize(fetchSize);
-
-        setPreparedFilterValues(ps, toSQLs, cx);
-
-        return ps;
-    }
-
-    /**
-     * Helper method for setting the values of the WHERE class of a prepared statement from a list
-     * of PreparedFilterToSQL.
-     */
-    protected void setPreparedFilterValues(
-            PreparedStatement ps, List<FilterToSQL> toSQLs, Connection cx) throws SQLException {
-        int offset = 0;
-        for (FilterToSQL fts : toSQLs) {
-            PreparedFilterToSQL toSQL = (PreparedFilterToSQL) fts;
-            setPreparedFilterValues(ps, toSQL, offset, cx);
-            offset += toSQL.getLiteralValues().size();
-        }
-    }
-
-    /** Helper method for setting the values of the WHERE class of a prepared statement. */
-    public void setPreparedFilterValues(
-            PreparedStatement ps, PreparedFilterToSQL toSQL, int offset, Connection cx)
-            throws SQLException {
-        PreparedStatementSQLDialect dialect = (PreparedStatementSQLDialect) getSQLDialect();
-
-        for (int i = 0; i < toSQL.getLiteralValues().size(); i++) {
-            Object value = toSQL.getLiteralValues().get(i);
-            Class binding = toSQL.getLiteralTypes().get(i);
-            Integer srid = toSQL.getSRIDs().get(i);
-            Integer dimension = toSQL.getDimensions().get(i);
-            AttributeDescriptor ad = toSQL.getDescriptors().get(i);
-            if (srid == null) {
-                srid = -1;
-            }
-            if (dimension == null) {
-                dimension = 2;
-            }
-
-            if (binding != null && Geometry.class.isAssignableFrom(binding)) {
-                dialect.setGeometryValue(
-                        (Geometry) value, dimension, srid, binding, ps, offset + i + 1);
-            } else if (ad != null && this.dialect.isArray(ad)) {
-                dialect.setArrayValue(value, ad, ps, offset + i + 1, cx);
-            } else {
-                dialect.setValue(value, binding, ps, offset + i + 1, cx);
-            }
-            if (LOGGER.isLoggable(Level.FINE)) {
-                LOGGER.fine((i + 1) + " = " + value);
-            }
-        }
-    }
-
-    /**
-     * Helper method for executing a property name against a feature type.
-     *
-     * <p>This method will fall back on {@link PropertyName#getPropertyName()} if it does not
-     * evaulate against the feature type.
-     */
-    protected String getPropertyName(SimpleFeatureType featureType, PropertyName propertyName) {
-        AttributeDescriptor att = (AttributeDescriptor) propertyName.evaluate(featureType);
-
-        if (att != null) {
-            return att.getLocalName();
-        }
-
-        return propertyName.getPropertyName();
-    }
-
-    /**
-     * Generates a 'SELECT' sql statement which selects bounds.
-     *
-     * @param featureType The feature type / table.
-     * @param query Specifies which features are to be used for the bounds computation (and in
-     *     particular uses filter, start index and max features)
-     */
-    protected String selectBoundsSQL(SimpleFeatureType featureType, Query query)
-            throws SQLException {
-        StringBuffer sql = new StringBuffer();
-
-        boolean offsetLimit = checkLimitOffset(query.getStartIndex(), query.getMaxFeatures());
-        if (offsetLimit) {
-            // envelopes are aggregates, just like count, so we must first isolate
-            // the rows against which the aggregate will work in a subquery
-            sql.append(" SELECT *");
-        } else {
-            sql.append("SELECT ");
-            buildEnvelopeAggregates(featureType, sql);
-        }
-
-        sql.append(" FROM ");
-        encodeTableName(featureType.getTypeName(), sql, setKeepWhereClausePlaceHolderHint(query));
-
-        Filter filter = query.getFilter();
-        if (filter != null && !Filter.INCLUDE.equals(filter)) {
-            // encode filter
-            try {
-                FilterToSQL toSQL = createFilterToSQL(featureType);
-                sql.append(" ").append(toSQL.encodeToString(filter));
-            } catch (FilterToSQLException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        // finally encode limit/offset, if necessary
-        if (offsetLimit) {
-            applyLimitOffset(sql, query.getStartIndex(), query.getMaxFeatures());
-            // build the prologue
-            StringBuffer sb = new StringBuffer();
-            sb.append("SELECT ");
-            buildEnvelopeAggregates(featureType, sb);
-            sb.append("FROM (");
-            // wrap the existing query
-            sql.insert(0, sb.toString());
-            sql.append(")");
-            dialect.encodeTableAlias("GT2_BOUNDS_", sql);
-        }
-
-        // add search hints if the dialect supports them
-        applySearchHints(featureType, query, sql);
-
-        return sql.toString();
-    }
-
-    /**
-     * Generates a 'SELECT' prepared statement which selects bounds.
-     *
-     * @param featureType The feature type / table.
-     * @param query Specifies which features are to be used for the bounds computation (and in
-     *     particular uses filter, start index and max features)
-     * @param cx A database connection.
-     */
-    protected PreparedStatement selectBoundsSQLPS(
-            SimpleFeatureType featureType, Query query, Connection cx) throws SQLException {
-
-        StringBuffer sql = new StringBuffer();
-
-        boolean offsetLimit = checkLimitOffset(query.getStartIndex(), query.getMaxFeatures());
-        if (offsetLimit) {
-            // envelopes are aggregates, just like count, so we must first isolate
-            // the rows against which the aggregate will work in a subquery
-            sql.append(" SELECT *");
-        } else {
-            sql.append("SELECT ");
-            buildEnvelopeAggregates(featureType, sql);
-        }
-
-        sql.append(" FROM ");
-        encodeTableName(featureType.getTypeName(), sql, setKeepWhereClausePlaceHolderHint(query));
-
-        // encode the filter
-        PreparedFilterToSQL toSQL = null;
-        Filter filter = query.getFilter();
-        if (filter != null && !Filter.INCLUDE.equals(filter)) {
-            // encode filter
-            try {
-                toSQL = createPreparedFilterToSQL(featureType);
-                int whereClauseIndex = sql.indexOf(WHERE_CLAUSE_PLACE_HOLDER);
-                if (whereClauseIndex != -1) {
-                    toSQL.setInline(true);
-                    sql.replace(
-                            whereClauseIndex,
-                            whereClauseIndex + WHERE_CLAUSE_PLACE_HOLDER_LENGTH,
-                            "AND " + toSQL.encodeToString(filter));
-                    toSQL.setInline(false);
-                } else {
-                    sql.append(" ").append(toSQL.encodeToString(filter));
-                }
-            } catch (FilterToSQLException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        // finally encode limit/offset, if necessary
-        if (offsetLimit) {
-            applyLimitOffset(sql, query.getStartIndex(), query.getMaxFeatures());
-            // build the prologue
-            StringBuffer sb = new StringBuffer();
-            sb.append("SELECT ");
-            buildEnvelopeAggregates(featureType, sb);
-            sb.append("FROM (");
-            // wrap the existing query
-            sql.insert(0, sb.toString());
-            sql.append(")");
-            dialect.encodeTableAlias("GT2_BOUNDS_", sql);
-        }
-
-        // add search hints if the dialect supports them
-        applySearchHints(featureType, query, sql);
-
-        LOGGER.fine(sql.toString());
-        PreparedStatement ps = cx.prepareStatement(sql.toString());
-
-        if (toSQL != null) {
-            setPreparedFilterValues(ps, toSQL, 0, cx);
-        }
-
-        return ps;
-    }
-
-    /**
-     * Builds a list of the aggregate function calls necesary to compute each geometry column bounds
-     */
-    void buildEnvelopeAggregates(SimpleFeatureType featureType, StringBuffer sql) {
-        // walk through all geometry attributes and build the query
-        for (AttributeDescriptor attribute : featureType.getAttributeDescriptors()) {
-            if (attribute instanceof GeometryDescriptor) {
-                String geometryColumn = attribute.getLocalName();
-                dialect.encodeGeometryEnvelope(featureType.getTypeName(), geometryColumn, sql);
-                sql.append(",");
-            }
-        }
-        sql.setLength(sql.length() - 1);
-    }
-
-    protected String selectAggregateSQL(
-            String function,
-            Expression att,
-            List<Expression> groupByExpressions,
-            SimpleFeatureType featureType,
-            Query query,
-            LimitingVisitor visitor)
-            throws SQLException, IOException {
-        StringBuffer sql = new StringBuffer();
-        doSelectAggregateSQL(function, att, groupByExpressions, featureType, query, visitor, sql);
-        return sql.toString();
-    }
-
-    protected PreparedStatement selectAggregateSQLPS(
-            String function,
-            Expression att,
-            List<Expression> groupByExpressions,
-            SimpleFeatureType featureType,
-            Query query,
-            LimitingVisitor visitor,
-            Connection cx)
-            throws SQLException, IOException {
-
-        StringBuffer sql = new StringBuffer();
-        List<FilterToSQL> toSQL =
-                doSelectAggregateSQL(
-                        function, att, groupByExpressions, featureType, query, visitor, sql);
-
-        LOGGER.fine(sql.toString());
-
-        PreparedStatement ps =
-                cx.prepareStatement(
-                        sql.toString(), ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
-        ps.setFetchSize(fetchSize);
-
-        setPreparedFilterValues(ps, toSQL, cx);
-
-        return ps;
-    }
-
-    /**
-     * Helper method to factor out some commonalities between selectAggregateSQL, and
-     * selectAggregateSQLPS
-     */
-    List<FilterToSQL> doSelectAggregateSQL(
-            String function,
-            Expression expr,
-            List<Expression> groupByExpressions,
-            SimpleFeatureType featureType,
-            Query query,
-            LimitingVisitor visitor,
-            StringBuffer sql)
-            throws SQLException, IOException {
-        JoinInfo join =
-                !query.getJoins().isEmpty() ? JoinInfo.create(query, featureType, this) : null;
-
-        List<FilterToSQL> toSQL = new ArrayList<>();
-        boolean queryLimitOffset = checkLimitOffset(query.getStartIndex(), query.getMaxFeatures());
-        boolean visitorLimitOffset =
-                visitor == null ? false : visitor.hasLimits() && dialect.isLimitOffsetSupported();
-        // grouping over expressions is complex, as we need
-        boolean groupByComplexExpressions = hasComplexExpressions(groupByExpressions);
-        if (queryLimitOffset && !visitorLimitOffset && !groupByComplexExpressions) {
-            if (join != null) {
-                // don't select * to avoid ambigous result set
-                sql.append("SELECT ");
-                dialect.encodeColumnName(null, join.getPrimaryAlias(), sql);
-                sql.append(".* FROM ");
-            } else {
-                sql.append("SELECT * FROM ");
-            }
-        } else {
-            sql.append("SELECT ");
-            FilterToSQL filterToSQL = getFilterToSQL(featureType);
-            if (groupByExpressions != null && !groupByExpressions.isEmpty()) {
-                try {
-                    // we encode all the group by attributes as columns names
-                    int i = 1;
-                    for (Expression expression : groupByExpressions) {
-                        sql.append(filterToSQL.encodeToString(expression));
-                        // if we are using complex group by, we have to given them an alias
-                        if (groupByComplexExpressions) {
-                            sql.append(" as ").append(getAggregateExpressionAlias(i++));
-                        }
-                        sql.append(", ");
-                    }
-                } catch (FilterToSQLException e) {
-                    throw new RuntimeException("Failed to encode group by expressions", e);
-                }
-            }
-
-            if (groupByComplexExpressions) {
-                // if encoding a sub-query, the source of the aggregation function must
-                // also be given an alias (we could use * too, but there is a risk of conflicts)
-                if (expr != null) {
-                    try {
-                        sql.append(filterToSQL.encodeToString(expr));
-                        sql.append(" as gt_agg_src");
-                    } catch (FilterToSQLException e) {
-                        throw new RuntimeException("Failed to encode group by expressions", e);
-                    }
-                } else {
-                    // remove the last comma and space
-                    sql.setLength(sql.length() - 2);
-                }
-            } else {
-                encodeFunction(function, expr, sql, filterToSQL);
-            }
-            toSQL.add(filterToSQL);
-            sql.append(" FROM ");
-        }
-
-        if (join != null) {
-            encodeTableJoin(featureType, join, query, sql);
-        } else {
-            encodeTableName(
-                    featureType.getTypeName(), sql, setKeepWhereClausePlaceHolderHint(query));
-        }
-
-        if (join != null) {
-            toSQL.addAll(encodeWhereJoin(featureType, join, sql));
-        } else {
-            Filter filter = query.getFilter();
-            if (filter != null && !Filter.INCLUDE.equals(filter)) {
-                sql.append(" WHERE ");
-                toSQL.add(filter(featureType, filter, sql));
-            }
-        }
-        if (dialect.isAggregatedSortSupported(function)) {
-            sort(featureType, query.getSortBy(), null, sql);
-        }
-
-        // apply limits
-        if (visitorLimitOffset) {
-            applyLimitOffset(sql, visitor.getStartIndex(), visitor.getMaxFeatures());
-        } else if (queryLimitOffset) {
-            applyLimitOffset(sql, query.getStartIndex(), query.getMaxFeatures());
-        }
-        boolean isUniqueCount = visitor instanceof UniqueCountVisitor;
-        // if the limits were in query or there is a group by with complex expressions
-        // or there is a UniqueCountVisitor
-        // we need to roll what was built so far in a sub-query
-        if (queryLimitOffset || groupByComplexExpressions || isUniqueCount) {
-            StringBuffer sql2 = new StringBuffer("SELECT ");
-            try {
-                if (groupByExpressions != null && !groupByExpressions.isEmpty()) {
-                    FilterToSQL filterToSQL = getFilterToSQL(featureType);
-                    int i = 1;
-                    for (Expression expression : groupByExpressions) {
-                        if (groupByComplexExpressions) {
-                            sql2.append(getAggregateExpressionAlias(i++));
-                        } else {
-                            sql2.append(filterToSQL.encodeToString(expression));
-                        }
-                        sql2.append(",");
-                    }
-                    toSQL.add(filterToSQL);
-                }
-            } catch (FilterToSQLException e) {
-                throw new RuntimeException("Failed to encode group by expressions", e);
-            }
-            FilterToSQL filterToSQL = getFilterToSQL(featureType);
-            boolean countQuery =
-                    isUniqueCount || (groupByComplexExpressions && "count".equals(function));
-            if (countQuery) sql2.append("count(*)");
-            else if (groupByComplexExpressions)
-                sql2.append(function).append("(").append("gt_agg_src").append(")");
-            else encodeFunction(function, expr, sql2, filterToSQL);
-            toSQL.add(filterToSQL);
-            sql2.append(" AS gt_result_");
-            sql2.append(" FROM (");
-            sql.insert(0, sql2.toString());
-            sql.append(") gt_limited_");
-        }
-
-        FilterToSQL filterToSQL = getFilterToSQL(featureType);
-        encodeGroupByStatement(groupByExpressions, sql, filterToSQL, groupByComplexExpressions);
-        toSQL.add(filterToSQL);
-
-        // add search hints if the dialect supports them
-        applySearchHints(featureType, query, sql);
-
-        return toSQL;
-    }
-
-    private String getAggregateExpressionAlias(int idx) {
-        return "gt_agg_" + idx;
-    }
-
-    /** Returns true if the expressions have anything but property names */
-    private boolean hasComplexExpressions(List<Expression> expressions) {
-        if (expressions == null || expressions.isEmpty()) {
-            return false;
-        }
-
-        return expressions.stream().anyMatch(x -> !(x instanceof PropertyName));
-    }
-
-    /**
-     * Helper method that adds a group by statement to the SQL query. If the list of group by
-     * attributes is empty or NULL no group by statement is add.
-     *
-     * @param groupByExpressions the group by attributes to be encoded
-     * @param sql the sql query buffer
-     */
-    protected void encodeGroupByStatement(
-            List<Expression> groupByExpressions,
-            StringBuffer sql,
-            FilterToSQL filterToSQL,
-            boolean aggregateOnExpression) {
-        if (groupByExpressions == null || groupByExpressions.isEmpty()) {
-            // there is not group by attributes to encode so nothing to do
-            return;
-        }
-        sql.append(" GROUP BY ");
-        int i = 1;
-        for (Expression groupByExpression : groupByExpressions) {
-            if (aggregateOnExpression) {
-                sql.append("gt_agg_" + i++);
-            } else {
-                try {
-                    sql.append(filterToSQL.encodeToString(groupByExpression));
-                } catch (FilterToSQLException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-            sql.append(", ");
-        }
-        sql.setLength(sql.length() - 2);
-    }
-
-    protected void encodeFunction(
-            String function, Expression expression, StringBuffer sql, FilterToSQL filterToSQL) {
-        sql.append(function).append("(");
-        if (expression == null) {
-            sql.append("*");
-        } else {
-            try {
-                sql.append(filterToSQL.encodeToString(expression));
-            } catch (FilterToSQLException e) {
-                throw new RuntimeException(e);
-            }
-        }
-        sql.append(")");
-    }
-
-    /** Generates a 'DELETE FROM' sql statement. */
-    protected String deleteSQL(SimpleFeatureType featureType, Filter filter) throws SQLException {
-        StringBuffer sql = new StringBuffer();
-
-        sql.append("DELETE FROM ");
-        encodeTableName(featureType.getTypeName(), sql, null);
-
-        if (filter != null && !Filter.INCLUDE.equals(filter)) {
-            // encode filter
-            try {
-                FilterToSQL toSQL = createFilterToSQL(featureType);
-                sql.append(" ").append(toSQL.encodeToString(filter));
-            } catch (FilterToSQLException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        return sql.toString();
-    }
-
-    /** Generates a 'DELETE FROM' prepared statement. */
-    protected PreparedStatement deleteSQLPS(
-            SimpleFeatureType featureType, Filter filter, Connection cx) throws SQLException {
-        StringBuffer sql = new StringBuffer();
-
-        sql.append("DELETE FROM ");
-        encodeTableName(featureType.getTypeName(), sql, null);
-
-        PreparedFilterToSQL toSQL = null;
-        if (filter != null && !Filter.INCLUDE.equals(filter)) {
-            // encode filter
-            try {
-                toSQL = createPreparedFilterToSQL(featureType);
-                sql.append(" ").append(toSQL.encodeToString(filter));
-            } catch (FilterToSQLException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        LOGGER.fine(sql.toString());
-        PreparedStatement ps = cx.prepareStatement(sql.toString());
-
-        if (toSQL != null) {
-            setPreparedFilterValues(ps, toSQL, 0, cx);
-        }
-
-        return ps;
-    }
-
-    /** Generates a 'INSERT INFO' sql statement. */
-    protected String insertSQL(
-            SimpleFeatureType featureType,
-            SimpleFeature feature,
-            KeysFetcher keysFetcher,
-            Connection cx)
-            throws SQLException, IOException {
-        BasicSQLDialect dialect = (BasicSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("INSERT INTO ");
-        encodeTableName(featureType.getTypeName(), sql, null);
-
-        // column names
-        sql.append(" ( ");
-
-        for (int i = 0; i < featureType.getAttributeCount(); i++) {
-            String colName = featureType.getDescriptor(i).getLocalName();
-            // skip the pk columns in case we have exposed them
-            if (keysFetcher.isKey(colName)) {
-                continue;
-            }
-            dialect.encodeColumnName(null, colName, sql);
-            sql.append(",");
-        }
-
-        // primary key values
-        keysFetcher.addKeyColumns(sql);
-        sql.setLength(sql.length() - 1);
-
-        // values
-        sql.append(" ) VALUES ( ");
-
-        for (int i = 0; i < featureType.getAttributeCount(); i++) {
-            AttributeDescriptor att = featureType.getDescriptor(i);
-            String colName = att.getLocalName();
-            // skip the pk columns in case we have exposed them, we grab the
-            // value from the pk itself
-            if (keysFetcher.isKey(colName)) {
-                continue;
-            }
-
-            Class binding = att.getType().getBinding();
-            EnumMapper mapper = (EnumMapper) att.getUserData().get(JDBCDataStore.JDBC_ENUM_MAP);
-
-            Object value = feature.getAttribute(colName);
-
-            if (value == null) {
-                if (!att.isNillable()) {
-                    throw new IOException(
-                            "Cannot set a NULL value on the not null column " + colName);
-                }
-
-                sql.append("null");
-            } else {
-                if (Geometry.class.isAssignableFrom(binding)) {
-                    try {
-                        Geometry g = (Geometry) value;
-                        int srid = getGeometrySRID(g, att);
-                        int dimension = getGeometryDimension(g, att);
-                        dialect.encodeGeometryValue(g, dimension, srid, sql);
-                    } catch (IOException e) {
-                        throw new RuntimeException(e);
-                    }
-                } else {
-                    if (mapper != null) {
-                        value = mapper.fromString((String) value);
-                        binding = Integer.class;
-                    }
-                    dialect.encodeValue(value, binding, sql);
-                }
-            }
-
-            sql.append(",");
-        }
-        // handle the primary key
-        keysFetcher.setKeyValues(this, cx, featureType, feature, sql);
-        sql.setLength(sql.length() - 1); // remove last comma
-
-        sql.append(")");
-
-        return sql.toString();
-    }
-
-    /**
-     * Returns the set of the primary key column names. The set is guaranteed to have the same
-     * iteration order as the primary key.
-     */
-    protected static LinkedHashSet<String> getColumnNames(PrimaryKey key) {
-        LinkedHashSet<String> pkColumnNames = new LinkedHashSet<>();
-        for (PrimaryKeyColumn pkcol : key.getColumns()) {
-            pkColumnNames.add(pkcol.getName());
-        }
-        return pkColumnNames;
-    }
-
-    /**
-     * Looks up the geometry srs by trying a number of heuristics. Returns -1 if all attempts at
-     * guessing the srid failed.
-     */
-    protected int getGeometrySRID(Geometry g, AttributeDescriptor descriptor) throws IOException {
-        int srid = getDescriptorSRID(descriptor);
-
-        if (g == null) {
-            return srid;
-        }
-
-        // check for srid in the jts geometry then
-        if (srid <= 0 && g.getSRID() > 0) {
-            srid = g.getSRID();
-        }
-
-        // check if the geometry has anything
-        if (srid <= 0 && g.getUserData() instanceof CoordinateReferenceSystem) {
-            // check for crs object
-            CoordinateReferenceSystem crs = (CoordinateReferenceSystem) g.getUserData();
-
-            try {
-                Integer candidate = CRS.lookupEpsgCode(crs, false);
-                if (candidate != null) srid = candidate;
-            } catch (Exception e) {
-                // ok, we tried...
-            }
-        }
-
-        return srid;
-    }
-
-    /**
-     * Looks up the geometry dimension by trying a number of heuristics. Returns 2 if all attempts
-     * at guessing the dimension failed.
-     */
-    protected int getGeometryDimension(Geometry g, AttributeDescriptor descriptor)
-            throws IOException {
-        int dimension = getDescriptorDimension(descriptor);
-
-        if (g == null || dimension > 0) {
-            return dimension;
-        }
-
-        // check for dimension in the geometry coordinate sequences
-        CoordinateSequenceDimensionExtractor dex = new CoordinateSequenceDimensionExtractor();
-        g.apply(dex);
-        return dex.getDimension();
-    }
-
-    /**
-     * Extracts the eventual native SRID user property from the descriptor, returns -1 if not found
-     */
-    protected int getDescriptorSRID(AttributeDescriptor descriptor) {
-        int srid = -1;
-
-        // check if we have stored the native srid in the descriptor (we should)
-        if (descriptor.getUserData().get(JDBCDataStore.JDBC_NATIVE_SRID) != null)
-            srid = (Integer) descriptor.getUserData().get(JDBCDataStore.JDBC_NATIVE_SRID);
-
-        return srid;
-    }
-
-    /**
-     * Extracts the eventual native dimension user property from the descriptor, returns -1 if not
-     * found
-     */
-    protected int getDescriptorDimension(AttributeDescriptor descriptor) {
-        int dimension = -1;
-
-        // check if we have stored the native srid in the descriptor (we should)
-        if (descriptor.getUserData().get(JDBCDataStore.JDBC_NATIVE_SRID) != null) {
-            dimension = (Integer) descriptor.getUserData().get(Hints.COORDINATE_DIMENSION);
-        }
-
-        return dimension;
-    }
-
-    /** Generates an 'UPDATE' sql statement. */
-    protected String updateSQL(
-            SimpleFeatureType featureType,
-            AttributeDescriptor[] attributes,
-            Object[] values,
-            Filter filter,
-            Set<String> pkColumnNames)
-            throws IOException, SQLException {
-        BasicSQLDialect dialect = (BasicSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("UPDATE ");
-        encodeTableName(featureType.getTypeName(), sql, null);
-
-        sql.append(" SET ");
-
-        for (int i = 0; i < attributes.length; i++) {
-            // skip exposed pk columns, they are read only
-            AttributeDescriptor att = attributes[i];
-            String attName = att.getLocalName();
-            if (pkColumnNames.contains(attName)) {
-                continue;
-            }
-
-            // build "colName = value"
-            dialect.encodeColumnName(null, attName, sql);
-            sql.append(" = ");
-
-            Class<?> binding = att.getType().getBinding();
-            Object value = values[i];
-            if (Geometry.class.isAssignableFrom(binding)) {
-                try {
-                    Geometry g = (Geometry) value;
-                    int srid = getGeometrySRID(g, att);
-                    int dimension = getGeometryDimension(g, att);
-                    dialect.encodeGeometryValue(g, dimension, srid, sql);
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
-            } else {
-                EnumMapper mapper = (EnumMapper) att.getUserData().get(JDBCDataStore.JDBC_ENUM_MAP);
-                if (mapper != null) {
-                    value = mapper.fromString(Converters.convert(value, String.class));
-                    binding = Integer.class;
-                }
-                dialect.encodeValue(value, binding, sql);
-            }
-
-            sql.append(",");
-        }
-
-        sql.setLength(sql.length() - 1);
-        sql.append(" ");
-
-        if (filter != null && !Filter.INCLUDE.equals(filter)) {
-            // encode filter
-            try {
-                FilterToSQL toSQL = createFilterToSQL(featureType);
-                sql.append(" ").append(toSQL.encodeToString(filter));
-            } catch (FilterToSQLException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        return sql.toString();
-    }
-
-    /** Generates an 'UPDATE' prepared statement. */
-    protected PreparedStatement updateSQLPS(
-            SimpleFeatureType featureType,
-            AttributeDescriptor[] attributes,
-            Object[] values,
-            Filter filter,
-            Set<String> pkColumnNames,
-            Connection cx)
-            throws IOException, SQLException {
-        PreparedStatementSQLDialect dialect = (PreparedStatementSQLDialect) getSQLDialect();
-
-        StringBuffer sql = new StringBuffer();
-        sql.append("UPDATE ");
-        encodeTableName(featureType.getTypeName(), sql, null);
-
-        sql.append(" SET ");
-
-        for (int i = 0; i < attributes.length; i++) {
-            // skip exposed primary key values, they are read only
-            AttributeDescriptor att = attributes[i];
-            String attName = att.getLocalName();
-            if (pkColumnNames.contains(attName)) {
-                continue;
-            }
-
-            dialect.encodeColumnName(null, attName, sql);
-            sql.append(" = ");
-
-            // geometries might need special treatment, delegate to the dialect
-            if (attributes[i] instanceof GeometryDescriptor) {
-                Geometry geometry = (Geometry) values[i];
-                final Class<?> binding = att.getType().getBinding();
-                dialect.prepareGeometryValue(
-                        geometry,
-                        getDescriptorDimension(att),
-                        getDescriptorSRID(att),
-                        binding,
-                        sql);
-            } else {
-                sql.append("?");
-            }
-            sql.append(",");
-        }
-        sql.setLength(sql.length() - 1);
-        sql.append(" ");
-
-        PreparedFilterToSQL toSQL = null;
-        if (filter != null && !Filter.INCLUDE.equals(filter)) {
-            // encode filter
-            try {
-                toSQL = createPreparedFilterToSQL(featureType);
-                sql.append(" ").append(toSQL.encodeToString(filter));
-            } catch (FilterToSQLException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        PreparedStatement ps = cx.prepareStatement(sql.toString());
-        LOGGER.log(Level.FINE, "Updating features with prepared statement: {0}", sql);
-
-        int i = 0;
-        int j = 0;
-        for (; i < attributes.length; i++) {
-            // skip exposed primary key columns
-            AttributeDescriptor att = attributes[i];
-            String attName = att.getLocalName();
-            if (pkColumnNames.contains(attName)) {
-                continue;
-            }
-
-            Class binding = att.getType().getBinding();
-            Object value = values[i];
-            if (Geometry.class.isAssignableFrom(binding)) {
-                Geometry g = (Geometry) value;
-                dialect.setGeometryValue(
-                        g, getDescriptorDimension(att), getDescriptorSRID(att), binding, ps, j + 1);
-            } else {
-                EnumMapper mapper = (EnumMapper) att.getUserData().get(JDBCDataStore.JDBC_ENUM_MAP);
-                if (mapper != null) {
-                    value = mapper.fromString(Converters.convert(value, String.class));
-                    binding = Integer.class;
-                }
-                dialect.setValue(value, binding, ps, j + 1, cx);
-            }
-            if (LOGGER.isLoggable(Level.FINE)) {
-                LOGGER.fine((j + 1) + " = " + value);
-            }
-            // we do this only if we did not skip the exposed pk
-            j++;
-        }
-
-        if (toSQL != null) {
-            setPreparedFilterValues(ps, toSQL, j, cx);
-        }
-
-        return ps;
-    }
-
-    /**
-     * Creates a new instance of a filter to sql encoder.
-     *
-     * <p>The <tt>featureType</tt> may be null but it is not recommended. Such a case where this may
-     * neccessary is when a literal needs to be encoded in isolation.
-     */
-    public FilterToSQL createFilterToSQL(SimpleFeatureType featureType) {
-        return initializeFilterToSQL(((BasicSQLDialect) dialect).createFilterToSQL(), featureType);
-    }
-
-    /** Creates a new instance of a filter to sql encoder to be used in a prepared statement. */
-    public PreparedFilterToSQL createPreparedFilterToSQL(SimpleFeatureType featureType) {
-        return initializeFilterToSQL(
-                ((PreparedStatementSQLDialect) dialect).createPreparedFilterToSQL(), featureType);
-    }
-
-    /** Helper method to initialize a filter encoder instance. */
-    protected <F extends FilterToSQL> F initializeFilterToSQL(
-            F toSQL, final SimpleFeatureType featureType) {
-        toSQL.setSqlNameEscape(dialect.getNameEscape());
-
-        if (featureType != null) {
-            // set up a fid mapper
-            // TODO: remove this
-            final PrimaryKey key;
-
-            try {
-                key = getPrimaryKey(featureType);
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-
-            toSQL.setFeatureType(featureType);
-            toSQL.setPrimaryKey(key);
-            toSQL.setDatabaseSchema(databaseSchema);
-        }
-
-        return toSQL;
-    }
-
-    /**
-     * Helper method to encode table name which checks if a schema is set and prefixes the table
-     * name with it.
-     */
-    public void encodeTableName(String tableName, StringBuffer sql, Hints hints)
-            throws SQLException {
-        encodeAliasedTableName(tableName, sql, hints, null);
-    }
-
-    /**
-     * Helper method to encode table name which checks if a schema is set and prefixes the table
-     * name with it, with the addition of an alias to the name
-     */
-    public void encodeAliasedTableName(
-            String tableName, StringBuffer sql, Hints hints, String alias) throws SQLException {
-        VirtualTable vtDefinition = virtualTables.get(tableName);
-        if (vtDefinition != null) {
-            sql.append("(").append(vtDefinition.expandParameters(hints)).append(")");
-            if (alias == null) {
-                alias = "vtable";
-            }
-            dialect.encodeTableAlias(alias, sql);
-        } else {
-            if (databaseSchema != null) {
-                dialect.encodeSchemaName(databaseSchema, sql);
-                sql.append(".");
-            }
-
-            dialect.encodeTableName(tableName, sql);
-            if (alias != null) {
-                dialect.encodeTableAlias(alias, sql);
-            }
-        }
-    }
-
-    /** Helper method to encode the join clause(s) of a query. */
-    protected void encodeTableJoin(
-            SimpleFeatureType featureType, JoinInfo join, Query query, StringBuffer sql)
-            throws SQLException {
-        encodeAliasedTableName(
-                featureType.getTypeName(), sql, query.getHints(), join.getPrimaryAlias());
-
-        for (JoinPart part : join.getParts()) {
-            sql.append(" ");
-            dialect.encodeJoin(part.getJoin().getType(), sql);
-            sql.append(" ");
-            encodeAliasedTableName(
-                    part.getQueryFeatureType().getTypeName(),
-                    sql,
-                    setKeepWhereClausePlaceHolderHint(null, true),
-                    part.getAlias());
-            sql.append(" ON ");
-
-            Filter j = part.getJoinFilter();
-
-            FilterToSQL toSQL = getFilterToSQL(null);
-            toSQL.setInline(true);
-            try {
-                sql.append(" ").append(toSQL.encodeToString(j));
-            } catch (FilterToSQLException e) {
-                throw new RuntimeException(e);
-            }
-        }
-        if (sql.indexOf(WHERE_CLAUSE_PLACE_HOLDER) >= 0) {
-            // this means that one of the joined table provided a placeholder
-            throw new RuntimeException(
-                    "Joins between virtual tables that provide a :where_placeholder: are not supported: "
-                            + sql);
-        }
-    }
-
-    protected List<FilterToSQL> encodeWhereJoin(
-            SimpleFeatureType featureType, JoinInfo join, StringBuffer sql) throws IOException {
-
-        List<FilterToSQL> toSQL = new ArrayList<>();
-
-        boolean whereEncoded = false;
-        Filter filter = join.getFilter();
-        if (filter != null && !Filter.INCLUDE.equals(filter)) {
-            sql.append(" WHERE ");
-            whereEncoded = true;
-
-            // encode filter
-            toSQL.add(filter(featureType, filter, sql));
-        }
-
-        // filters for joined feature types
-        for (JoinPart part : join.getParts()) {
-            filter = part.getPreFilter();
-            if (filter == null || Filter.INCLUDE.equals(filter)) continue;
-
-            if (!whereEncoded) {
-                sql.append(" WHERE ");
-                whereEncoded = true;
-            } else {
-                sql.append(" AND ");
-            }
-
-            toSQL.add(filter(part.getQueryFeatureType(), filter, sql));
-        }
-
-        return toSQL;
-    }
-
-    /** Helper method for setting the gml:id of a geometry as user data. */
-    protected void setGmlProperties(Geometry g, String gid, String name, String description) {
-        // set up the user data
-        Map<Object, Object> userData = null;
-
-        if (g.getUserData() != null) {
-            if (g.getUserData() instanceof Map) {
-                @SuppressWarnings("unchecked")
-                Map<Object, Object> cast = (Map) g.getUserData();
-                userData = cast;
-            } else {
-                userData = new HashMap<>();
-                userData.put(g.getUserData().getClass(), g.getUserData());
-            }
-        } else {
-            userData = new HashMap<>();
-        }
-
-        if (gid != null) {
-            userData.put("gml:id", gid);
-        }
-
-        if (name != null) {
-            userData.put("gml:name", name);
-        }
-
-        if (description != null) {
-            userData.put("gml:description", description);
-        }
-
-        g.setUserData(userData);
-    }
-
-    /**
-     * Applies the givenb limit/offset elements if the dialect supports them
-     *
-     * @param sql The sql to be modified
-     * @param offset starting index
-     * @param limit max number of features
-     */
-    void applyLimitOffset(StringBuffer sql, final Integer offset, final int limit) {
-        if (checkLimitOffset(offset, limit)) {
-            dialect.applyLimitOffset(sql, limit, offset != null ? offset : 0);
-        }
-    }
-
-    /**
-     * Applies the limit/offset elements to the query if they are specified and if the dialect
-     * supports them
-     *
-     * @param sql The sql to be modified
-     * @param query the query that holds the limit and offset parameters
-     */
-    public void applyLimitOffset(StringBuffer sql, Query query) {
-        applyLimitOffset(sql, query.getStartIndex(), query.getMaxFeatures());
-    }
-
-    /**
-     * Checks if the query needs limit/offset treatment
-     *
-     * @return true if the query needs limit/offset treatment and if the sql dialect can do that
-     *     natively
-     */
-    boolean checkLimitOffset(final Integer offset, final int limit) {
-        // if we cannot, don't bother checking the query
-        if (!dialect.isLimitOffsetSupported()) return false;
-
-        return limit != Integer.MAX_VALUE || (offset != null && offset > 0);
-    }
-
-    /**
-     * Utility method for closing a result set.
-     *
-     * <p>This method closed the result set "safely" in that it never throws an exception. Any
-     * exceptions that do occur are logged at {@link Level#FINER}.
-     *
-     * @param rs The result set to close.
-     */
-    public void closeSafe(ResultSet rs) {
-        if (rs == null) {
-            return;
-        }
-
-        try {
-            rs.close();
-        } catch (SQLException e) {
-            String msg = "Error occurred closing result set";
-            LOGGER.warning(msg);
-
-            if (LOGGER.isLoggable(Level.FINER)) {
-                LOGGER.log(Level.FINER, msg, e);
-            }
-        }
-    }
-
-    /**
-     * Utility method for closing a statement.
-     *
-     * <p>This method closed the statement"safely" in that it never throws an exception. Any
-     * exceptions that do occur are logged at {@link Level#FINER}.
-     *
-     * @param st The statement to close.
-     */
-    public void closeSafe(Statement st) {
-        if (st == null) {
-            return;
-        }
-
-        try {
-            st.close();
-        } catch (SQLException e) {
-            String msg = "Error occurred closing statement";
-            LOGGER.warning(msg);
-
-            if (LOGGER.isLoggable(Level.FINER)) {
-                LOGGER.log(Level.FINER, msg, e);
-            }
-        }
-    }
-
-    /**
-     * Utility method for closing a connection.
-     *
-     * <p>This method closed the connection "safely" in that it never throws an exception. Any
-     * exceptions that do occur are logged at {@link Level#FINER}.
-     *
-     * @param cx The connection to close.
-     */
-    public void closeSafe(Connection cx) {
-        if (cx == null) {
-            return;
-        }
-
-        try {
-            //            System.out.println("Closing connection " + System.identityHashCode(cx));
-            cx.close();
-            LOGGER.fine("CLOSE CONNECTION");
-        } catch (SQLException e) {
-            String msg = "Error occurred closing connection";
-            LOGGER.warning(msg);
-
-            if (LOGGER.isLoggable(Level.FINER)) {
-                LOGGER.log(Level.FINER, msg, e);
-            }
-        }
-    }
-
-    @Override
-    @SuppressWarnings("deprecation") // finalize is deprecated in Java 9
-    protected void finalize() throws Throwable {
-        if (dataSource != null) {
-            LOGGER.severe(
-                    "There's code using JDBC based datastore and "
-                            + "not disposing them. This may lead to temporary loss of database connections. "
-                            + "Please make sure all data access code calls DataStore.dispose() "
-                            + "before freeing all references to it");
-            dispose();
-        }
-    }
-
-    @Override
-    public void dispose() {
-        super.dispose();
-        if (dataSource != null && dataSource instanceof ManageableDataSource) {
-            try {
-                @SuppressWarnings("PMD.CloseResource") // actually closing it here
-                ManageableDataSource mds = (ManageableDataSource) dataSource;
-                mds.close();
-            } catch (SQLException e) {
-                // it's ok, we did our best..
-                LOGGER.log(Level.FINE, "Could not close dataSource", e);
-            }
-        }
-        // Store the exception for logging later if the object is used after disposal
-        if (TRACE_ENABLED) {
-            disposedBy =
-                    new RuntimeException(
-                            "DataSource disposed by thread " + Thread.currentThread().getName());
-        }
-        dataSource = null;
-    }
-    /**
-     * Checks if geometry generalization required and makes sense
-     *
-     * @param hints hints hints passed in
-     * @param gatt Geometry attribute descriptor
-     * @return true to indicate generalization
-     */
-    protected boolean isGeneralizationRequired(Hints hints, GeometryDescriptor gatt) {
-        return isGeometryReduceRequired(hints, gatt, Hints.GEOMETRY_GENERALIZATION);
-    }
-
-    /**
-     * Checks if geometry simplification required and makes sense
-     *
-     * @param hints hints hints passed in
-     * @param gatt Geometry attribute descriptor
-     * @return true to indicate simplification
-     */
-    protected boolean isSimplificationRequired(Hints hints, GeometryDescriptor gatt) {
-        return isGeometryReduceRequired(hints, gatt, Hints.GEOMETRY_SIMPLIFICATION);
-    }
-    /**
-     * Checks if reduction required and makes sense
-     *
-     * @param hints hints passed in
-     * @param gatt Geometry attribute descriptor
-     * @param param {@link Hints#GEOMETRY_GENERALIZATION} or {@link Hints#GEOMETRY_SIMPLIFICATION}
-     * @return true to indicate reducing the geometry, false otherwise
-     */
-    protected boolean isGeometryReduceRequired(
-            Hints hints, GeometryDescriptor gatt, Hints.Key param) {
-        if (hints == null) return false;
-        if (hints.containsKey(param) == false) return false;
-        if (gatt.getType().getBinding() == Point.class && !dialect.canSimplifyPoints())
-            return false;
-        return true;
-    }
-
-    /**
-     * Encoding a geometry column with respect to hints Supported Hints are provided by {@link
-     * SQLDialect#addSupportedHints(Set)}
-     *
-     * @param hints , may be null
-     */
-    public void encodeGeometryColumn(GeometryDescriptor gatt, StringBuffer sql, Hints hints) {
-        encodeGeometryColumn(gatt, null, sql, hints);
-    }
-
-    protected void encodeGeometryColumn(
-            GeometryDescriptor gatt, String prefix, StringBuffer sql, Hints hints) {
-
-        int srid = getDescriptorSRID(gatt);
-        if (isGeneralizationRequired(hints, gatt) == true) {
-            Double distance = (Double) hints.get(Hints.GEOMETRY_GENERALIZATION);
-            dialect.encodeGeometryColumnGeneralized(gatt, prefix, srid, sql, distance);
-            return;
-        }
-
-        if (isSimplificationRequired(hints, gatt) == true) {
-            Double distance = (Double) hints.get(Hints.GEOMETRY_SIMPLIFICATION);
-            dialect.encodeGeometryColumnSimplified(gatt, prefix, srid, sql, distance);
-            return;
-        }
-
-        dialect.encodeGeometryColumn(gatt, prefix, srid, hints, sql);
-    }
-
-    /**
-     * Builds a transaction object around a user provided connection. The returned transaction
-     * allows the store to work against an externally managed transaction, such as in J2EE
-     * enviroments. It is the duty of the caller to ensure the connection is to the same database
-     * managed by this {@link JDBCDataStore}.
-     *
-     * <p>Calls to {@link Transaction#commit()}, {@link Transaction#rollback()} and {@link
-     * Transaction#close()} will not result in corresponding calls to the provided {@link
-     * Connection} object.
-     *
-     * @param cx The externally managed connection
-     */
-    public Transaction buildTransaction(Connection cx) {
-        DefaultTransaction tx = new DefaultTransaction();
-
-        State state = new JDBCTransactionState(cx, this, true);
-        tx.putState(this, state);
-
-        return tx;
-    }
-
-    /** Creates a new database index */
-    public void createIndex(Index index) throws IOException {
-        SimpleFeatureType schema = getSchema(index.typeName);
-        Connection cx = null;
-        try {
-            cx = getConnection(Transaction.AUTO_COMMIT);
-            dialect.createIndex(cx, schema, databaseSchema, index);
-        } catch (SQLException e) {
-            throw new IOException("Failed to create index", e);
-        } finally {
-            closeSafe(cx);
-        }
-    }
-
-    /** Creates a new database index */
-    public void dropIndex(String typeName, String indexName) throws IOException {
-        SimpleFeatureType schema = getSchema(typeName);
-
-        Connection cx = null;
-        try {
-            cx = getConnection(Transaction.AUTO_COMMIT);
-            dialect.dropIndex(cx, schema, databaseSchema, indexName);
-        } catch (SQLException e) {
-            throw new IOException("Failed to create index", e);
-        } finally {
-            closeSafe(cx);
-        }
-    }
-
-    /**
-     * Lists all indexes associated to the given feature type
-     *
-     * @param typeName Name of the type for which indexes are searched. It's mandatory
-     */
-    public List<Index> getIndexes(String typeName) throws IOException {
-        // just to ensure we have the type name specified
-        getSchema(typeName);
-
-        Connection cx = null;
-        try {
-            cx = getConnection(Transaction.AUTO_COMMIT);
-            return dialect.getIndexes(cx, databaseSchema, typeName);
-        } catch (SQLException e) {
-            throw new IOException("Failed to create index", e);
-        } finally {
-            closeSafe(cx);
-        }
-    }
-
-    /**
-     * Escapes a name pattern used in e.g. {@link DatabaseMetaData#getColumns(String,
-     * String, String, String)} when passed in argument is an exact name and not a pattern.
-     *
-     * <p>When a table name or column name contains underscore (or percen, but this is rare) the
-     * underscore is treated as a placeholder and not an actual character. So if our intention is to
-     * match an exact name, we must escape such characters.
-     */
-    public String escapeNamePattern(DatabaseMetaData metaData, String name) throws SQLException {
-        if (namePatternEscaping == null) {
-            namePatternEscaping = new NamePatternEscaping(metaData.getSearchStringEscape());
-        }
-        return namePatternEscaping.escape(name);
-    }
-
-    public boolean hasSchema(String typeName) throws IOException {
-        Statement statement = null;
-        try {
-            statement = getConnection(Transaction.AUTO_COMMIT).createStatement();
-            ResultSet resultSet = statement.executeQuery("select count(1) as cot  from pg_stat_user_tables where schemaname = 'public' and relname = '"+typeName+"'");
-            resultSet.next();
-            return resultSet.getInt("cot") > 0;
-        } catch (SQLException throwables) {
-            throwables.printStackTrace();
-        }
-        return false;
-    }
-}

+ 0 - 162
src/main/java/org/locationtech/jts/operation/overlay/snap/SnapOverlayOp.java

@@ -1,162 +0,0 @@
-/*
- * Copyright (c) 2016 Vivid Solutions.
- *
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
- * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
- * and the Eclipse Distribution License is available at
- *
- * http://www.eclipse.org/org/documents/edl-v10.php.
- */
-
-package org.locationtech.jts.operation.overlay.snap;
-
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.operation.overlay.OverlayOp;
-import org.locationtech.jts.precision.CommonBitsRemover;
-
-/**
- * Performs an overlay operation using snapping and enhanced precision
- * to improve the robustness of the result.
- * This class <i>always</i> uses snapping.  
- * This is less performant than the standard JTS overlay code, 
- * and may even introduce errors which were not present in the original data.
- * For this reason, this class should only be used 
- * if the standard overlay code fails to produce a correct result. 
- *  
- * @author Martin Davis
- * @version 1.7
- */
-public class SnapOverlayOp
-{
-  public static Geometry overlayOp(Geometry g0, Geometry g1, int opCode)
-  {
-  	SnapOverlayOp op = new SnapOverlayOp(g0, g1);
-  	return op.getResultGeometry(opCode);
-  }
-
-  public static Geometry intersection(Geometry g0, Geometry g1)
-  {
-    if(g0 == null){
-      return g1;
-    }
-    if(g1 == null){
-      return g0;
-    }
-     return overlayOp(g0, g1, OverlayOp.INTERSECTION);
-  }
-
-  public static Geometry union(Geometry g0, Geometry g1)
-  {
-    if(g0 == null){
-      return g1;
-    }
-    if(g1 == null){
-      return g0;
-    }
-     return overlayOp(g0, g1, OverlayOp.UNION);
-  }
-
-  public static Geometry difference(Geometry g0, Geometry g1)
-  {
-    if(g0 == null){
-      return g1;
-    }
-    if(g1 == null){
-      return g0;
-    }
-     return overlayOp(g0, g1, OverlayOp.DIFFERENCE);
-  }
-
-  public static Geometry symDifference(Geometry g0, Geometry g1)
-  {
-    if(g0 == null){
-      return g1;
-    }
-    if(g1 == null){
-      return g0;
-    }
-     return overlayOp(g0, g1, OverlayOp.SYMDIFFERENCE);
-  }
-  
-
-  private Geometry[] geom = new Geometry[2];
-  private double snapTolerance;
-
-  public SnapOverlayOp(Geometry g1, Geometry g2)
-  {
-    geom[0] = g1;
-    geom[1] = g2;
-    computeSnapTolerance();
-  }
-  private void computeSnapTolerance() 
-  {
-		snapTolerance = GeometrySnapper.computeOverlaySnapTolerance(geom[0], geom[1]);
-
-		// System.out.println("Snap tol = " + snapTolerance);
-	}
-
-  public Geometry getResultGeometry(int opCode)
-  {
-//  	Geometry[] selfSnapGeom = new Geometry[] { selfSnap(geom[0]), selfSnap(geom[1])};
-    Geometry[] prepGeom = snap(geom);
-    Geometry result = OverlayOp.overlayOp(prepGeom[0], prepGeom[1], opCode);
-    return prepareResult(result);	
-  }
-  
-  private Geometry selfSnap(Geometry geom)
-  {
-    GeometrySnapper snapper0 = new GeometrySnapper(geom);
-    Geometry snapGeom = snapper0.snapTo(geom, snapTolerance);
-    //System.out.println("Self-snapped: " + snapGeom);
-    //System.out.println();
-    return snapGeom;
-  }
-  
-  private Geometry[] snap(Geometry[] geom)
-  {
-    Geometry[] remGeom = removeCommonBits(geom);
-  	
-  	// MD - testing only
-//  	Geometry[] remGeom = geom;
-    
-    Geometry[] snapGeom = GeometrySnapper.snap(remGeom[0], remGeom[1], snapTolerance);
-    // MD - may want to do this at some point, but it adds cycles
-//    checkValid(snapGeom[0]);
-//    checkValid(snapGeom[1]);
-
-    /*
-    System.out.println("Snapped geoms: ");
-    System.out.println(snapGeom[0]);
-    System.out.println(snapGeom[1]);
-    */
-    return snapGeom;
-  }
-
-  private Geometry prepareResult(Geometry geom)
-  {
-    cbr.addCommonBits(geom);
-    return geom;
-  }
-
-  private CommonBitsRemover cbr;
-
-  private Geometry[] removeCommonBits(Geometry[] geom)
-  {
-    cbr = new CommonBitsRemover();
-    cbr.add(geom[0]);
-    cbr.add(geom[1]);
-    Geometry remGeom[] = new Geometry[2];
-    remGeom[0] = cbr.removeCommonBits(geom[0].copy());
-    remGeom[1] = cbr.removeCommonBits(geom[1].copy());
-    return remGeom;
-  }
-  
-  private void checkValid(Geometry g)
-  {
-  	if (! g.isValid()) {
-  		System.out.println("Snapped geometry is invalid");
-  	}
-  }
-}