diff --git a/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AppSchemaDataAccess.java b/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AppSchemaDataAccess.java index 29603ed..66700d0 100644 --- a/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AppSchemaDataAccess.java +++ b/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AppSchemaDataAccess.java @@ -337,7 +337,7 @@ public class AppSchemaDataAccess implements DataAccess { newQuery.setSortBy( sort.toArray(new SortBy[sort.size()]) ); JoiningQuery jQuery = new JoiningQuery(newQuery); - jQuery.setJoins(((JoiningQuery)query).getJoins()); + jQuery.setQueryJoins(((JoiningQuery)query).getQueryJoins()); unrolledQuery = jQuery; } else { diff --git a/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/MappingFeatureSource.java b/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/MappingFeatureSource.java index 2ecaaed..a3fd806 100644 --- a/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/MappingFeatureSource.java +++ b/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/MappingFeatureSource.java @@ -110,7 +110,7 @@ class MappingFeatureSource implements FeatureSource { namedQuery.setSortBy(query.getSortBy()); namedQuery.setHints(query.getHints()); if (query instanceof JoiningQuery) { - ((JoiningQuery) namedQuery).setJoins(((JoiningQuery) query).getJoins()); + ((JoiningQuery) namedQuery).setQueryJoins(((JoiningQuery) query).getQueryJoins()); } return namedQuery; } diff --git a/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningNestedAttributeMapping.java b/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningNestedAttributeMapping.java index 751bd9e..19c7f39 100644 --- a/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningNestedAttributeMapping.java +++ b/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningNestedAttributeMapping.java @@ -138,20 +138,20 @@ public class JoiningNestedAttributeMapping extends NestedAttributeMapping { } Expression nestedSourceExpression = mappings.get(0).getSourceExpression(); - List joins = new ArrayList(); + List joins = new ArrayList(); if (instance.baseTableQuery instanceof JoiningQuery) { - if (((JoiningQuery) instance.baseTableQuery).getJoins() != null) { - joins.addAll(((JoiningQuery) instance.baseTableQuery).getJoins()); + if (((JoiningQuery) instance.baseTableQuery).getQueryJoins() != null) { + joins.addAll(((JoiningQuery) instance.baseTableQuery).getQueryJoins()); } } - JoiningQuery.Join join = new JoiningQuery.Join(); + JoiningQuery.QueryJoin join = new JoiningQuery.QueryJoin(); join.setForeignKeyName(sourceExpression); join.setJoiningKeyName(nestedSourceExpression); join.setJoiningTypeName(instance.baseTableQuery.getTypeName()); join.setSortBy(instance.baseTableQuery.getSortBy()); // incorporate order joins.add(0, join); - query.setJoins(joins); + query.setQueryJoins(joins); if (selectedProperties != null) { selectedProperties = new ArrayList(selectedProperties); @@ -192,8 +192,8 @@ public class JoiningNestedAttributeMapping extends NestedAttributeMapping { DataAccessMappingFeatureIterator daFeatureIterator = (DataAccessMappingFeatureIterator) featureIterator; List foreignIds = new ArrayList(); - for (int i = 0; i < query.getJoins().size(); i++) { - for (int j = 0; j < query.getJoins().get(i).getSortBy().length; j++) { + for (int i = 0; i < query.getQueryJoins().size(); i++) { + for (int j = 0; j < query.getQueryJoins().get(i).getSortBy().length; j++) { foreignIds.add(filterFac.property(JoiningJDBCFeatureSource.FOREIGN_ID + "_" + i + "_" + j)); } diff --git a/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningQuery.java b/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningQuery.java index 7d3487b..3451933 100644 --- a/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningQuery.java +++ b/modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningQuery.java @@ -33,7 +33,7 @@ import org.opengis.filter.sort.SortBy; */ public class JoiningQuery extends Query { - public static class Join { + public static class QueryJoin { protected String joiningTypeName; protected Expression foreignKeyName; protected Expression joiningKeyName; @@ -72,12 +72,12 @@ public class JoiningQuery extends Query { } } - protected List joins; + protected List queryJoins; public JoiningQuery(JoiningQuery query) { super(query); - setJoins(query.getJoins()); + setQueryJoins(query.getQueryJoins()); } public JoiningQuery(Query query){ @@ -87,12 +87,12 @@ public class JoiningQuery extends Query { public JoiningQuery() { } - public void setJoins(List joins){ - this.joins = joins; + public void setQueryJoins(List queryJoins){ + this.queryJoins = queryJoins; } - public List getJoins(){ - return joins; + public List getQueryJoins(){ + return queryJoins; } } diff --git a/modules/extension/app-schema/app-schema/src/main/java/org/geotools/jdbc/JoiningJDBCFeatureSource.java b/modules/extension/app-schema/app-schema/src/main/java/org/geotools/jdbc/JoiningJDBCFeatureSource.java index 05b9c2b..ee1c71b 100644 --- a/modules/extension/app-schema/app-schema/src/main/java/org/geotools/jdbc/JoiningJDBCFeatureSource.java +++ b/modules/extension/app-schema/app-schema/src/main/java/org/geotools/jdbc/JoiningJDBCFeatureSource.java @@ -158,7 +158,7 @@ public class JoiningJDBCFeatureSource extends JDBCFeatureSource { } } - protected void addMultiValuedSort(String tableName, SortBy[] sort , StringBuffer sql, JoiningQuery.Join join ) throws IOException, FilterToSQLException { + protected void addMultiValuedSort(String tableName, SortBy[] sort , StringBuffer sql, JoiningQuery.QueryJoin join ) throws IOException, FilterToSQLException { sql.append(" CASE WHEN "); FilterToSQL toSQL1 = createFilterToSQL(getDataStore().getSchema(tableName)); toSQL1.setFieldEncoder(new JoiningFieldEncoder(tableName)); @@ -182,8 +182,8 @@ public class JoiningJDBCFeatureSource extends JDBCFeatureSource { protected void sort(JoiningQuery query, StringBuffer sql, String[] aliases) throws IOException, SQLException, FilterToSQLException { boolean orderby = false; - for (int j = query.getJoins() == null? -1 : query.getJoins().size() -1; j >= -1 ; j-- ) { - JoiningQuery.Join join = j<0 ? null : query.getJoins().get(j); + for (int j = query.getQueryJoins() == null? -1 : query.getQueryJoins().size() -1; j >= -1 ; j-- ) { + JoiningQuery.QueryJoin join = j<0 ? null : query.getQueryJoins().get(j); SortBy[] sort = j<0? query.getSortBy() : join.getSortBy(); if ((sort != null) && (sort.length > 0)) { @@ -193,8 +193,8 @@ public class JoiningJDBCFeatureSource extends JDBCFeatureSource { } if (j < 0) { sort(query.getTypeName(), sort, sql, false); - if (query.getJoins()!= null && query.getJoins().size()>0) { - addMultiValuedSort(query.getTypeName(), sort, sql, query.getJoins().get(0)); + if (query.getQueryJoins()!= null && query.getQueryJoins().size()>0) { + addMultiValuedSort(query.getTypeName(), sort, sql, query.getQueryJoins().get(0)); } } else { if (aliases!=null && aliases[j] != null) { @@ -202,8 +202,8 @@ public class JoiningJDBCFeatureSource extends JDBCFeatureSource { } else { sort(join.getJoiningTypeName() , sort, sql, false); } - if (query.getJoins().size()>j+1) { - addMultiValuedSort(join.getJoiningTypeName(), sort, sql, query.getJoins().get(j+1)); + if (query.getQueryJoins().size()>j+1) { + addMultiValuedSort(join.getJoiningTypeName(), sort, sql, query.getQueryJoins().get(j+1)); } } } @@ -308,13 +308,13 @@ public class JoiningJDBCFeatureSource extends JDBCFeatureSource { String[] aliases = null; - if (query.getJoins() != null) { + if (query.getQueryJoins() != null) { SortBy[] lastSortBy = query.getSortBy(); - aliases = new String[query.getJoins().size()]; + aliases = new String[query.getQueryJoins().size()]; - for (int i=0; i< query.getJoins().size(); i++) { - JoiningQuery.Join join = query.getJoins().get(i); + for (int i=0; i< query.getQueryJoins().size(); i++) { + JoiningQuery.QueryJoin join = query.getQueryJoins().get(i); if (lastSortBy!= null && lastSortBy.length > 0) { tableNames.add(curTypeName); @@ -416,11 +416,11 @@ public class JoiningJDBCFeatureSource extends JDBCFeatureSource { sql.append(","); } - if (query.getJoins() != null && query.getJoins().size() > 0) { - for (int i=0; i 0) { + for (int i=0; i + * The Join class is similar to Query in that it allows one to specify a FeatureType name, a set of + * properties, and a filter. A Join must specify: + *
    + *
  1. A type name that references the feature type to join to, see {@link #getTypeName()} + *
  2. A join filter that describes how to join, see {@link #getJoinFilter()} + *
+ * Optionally a Join may also specify: + *
    + *
  • A set of property names constraining the attributes of joined features, see {@link #getProperties()} + *
  • A secondary filter used to constrained features from the joined feature type, see {@link #getFilter()} + *
  • An alias for the joined feature type, which can be used in the join filter to disambiguate + * attributes of the feature types being joined, see {@link #getAlias()} + *
  • A join type specifying what type of join (inner, outer, etc...) should be performed, see {@link #getType()} + *
+ *

+ * + * @author Justin Deoliveira, OpenGeo + * + * @since 8.0 + */ +public class Join { + + /** + * filter factory + */ + static final FilterFactory ff = CommonFactoryFinder.getFilterFactory(null); + + /** + * type of join + */ + public static enum Type { + INNER, OUTER; + } + + /** join type */ + Type type; + + /** + * the feature type name being joined to + */ + String typeName; + + /** + * attributes to fetch for this feature type + */ + List properties = Query.ALL_PROPERTIES; + + /** + * the join predicate + */ + Filter join; + + /** + * additional predicate against the target of the join + */ + Filter filter; + + /** + * The alias to be used for the typeName in this join + */ + String alias; + + /** + * Constructs a join. + * + * @param typeName The name of the feature type to join to. + * @param join The filter specifying the join condition between the two feature types being + * joined. + */ + public Join(String typeName, Filter join) { + this.typeName = typeName; + this.join = join; + this.type = Type.INNER; + this.properties = Query.ALL_PROPERTIES; + this.filter = Filter.INCLUDE; + this.alias = null; + } + + /** + * Constructs a join from another. + */ + public Join(Join other) { + this.typeName = other.getTypeName(); + this.join = other.getJoinFilter(); + this.filter = other.getFilter(); + this.type = other.getType(); + this.properties = other.getProperties(); + this.filter = other.getFilter(); + this.alias = other.getAlias(); + } + + /** + * The name of the feature type being joined to. + *

+ * This name may be the same as the name of the primary feature type, this is how a self join is + * specified. + *

+ */ + public String getTypeName() { + return typeName; + } + + /** + * The filter defining the join condition between the primary feature type and the feature + * type being joined to. + *

+ * This filter should be a comparison operator whose contents are two {@link PropertyName} + * instances. For example: + *

+     * new Join("theOtherType", propertyIsEqualTo(propertyName("foo"), propertyName("bar")));
+     * 
+ * In instances where the two property names involved in the join are the same a prefix or + * alias must be used to differentiate: + *
+     * Join j = new Join("theOtherType", propertyIsEqualTo(propertyName("foo"), propertyName("other.bar")));
+     * j.alias("other");
+     * 
+ *

+ */ + public Filter getJoinFilter() { + return join; + } + + /** + * Sets the join type. + * @see #getType() + */ + public void setType(Type type) { + this.type = type; + } + + /** + * The type of the join. + *

+ * {@link Type#INNER} is the default join type. + *

+ * + */ + public Type getType() { + return type; + } + + /** + * List of properties specifying which attributes of joined features to obtain. + *

+ * This method has the same purpose as {@link Query#getProperties()}. + *

+ */ + public List getProperties() { + if (properties == Query.ALL_PROPERTIES) { + return properties; + } + return Collections.unmodifiableList(properties); + } + + /** + * Sets list of properties specifying which attributes of joined features to obtain. + *

+ * This method has the same purpose as {@link Query#setProperties(List)}. + *

+ */ + public void setProperties(List properties) { + this.properties = properties; + } + + /** + * List of property names specifying which attributes of joined features to obtain. + *

+ * This method has the same purpose as {@link Query#getPropertyNames()}. + *

+ */ + public String[] getPropertyNames() { + if (properties == Query.ALL_PROPERTIES) { + return Query.ALL_NAMES; + } + + String[] names = new String[properties.size()]; + for (int i = 0; i < names.length; i++) { + names[i] = properties.get(i).getPropertyName(); + } + return names; + } + + /** + * Sets the filter used to constrain which features from the joined feature type to return. + * + * @see #getFilter() + */ + public void setFilter(Filter filter) { + this.filter = filter; + } + + /** + * Filter used to constrain which features from the joined feature type to return. + *

+ * This filter must only reference attributes from the joined feature type, and not of any other + * feature types involved in the join. + *

+ */ + public Filter getFilter() { + return filter; + } + + /** + * Sets an alias for the feature type being joined to. + * @see #getAlias() + */ + public void setAlias(String alias) { + this.alias = alias; + } + + /** + * An alias for the feature type being joined to. + *

+ * This method is useful in cases where the two feature types being joined contain attributes + * identically named, or in cases where a self join is being performed: + *

+     * Join j = new Join("theOtherType", PropertyIsEqualTo(PropertyName("foo"), PropertyName("other.foo")));
+     * j.setAlias("other");
+     * 
+ *

+ * + * @see #getJoinFilter() + */ + public String getAlias() { + return alias; + } + + /** + * Convenience method that returns the attribute name to be used for this join. + *

+ * Convenience for: + * + *

+     *  return getAlias() != null ? getAlias() : getTypeName();
+     * 
+ * + *

+ */ + public String attributeName() { + return getAlias() != null ? getAlias() : getTypeName(); + } + + /** + * Chaining method for {@link #getProperties()} + */ + public Join properties(String... properties) { + this.properties = new ArrayList(); + for (String p : properties) { + this.properties.add(ff.property(p)); + } + return this; + } + + /** + * Chaining method for {@link #setFilter(Filter)} + */ + public Join filter(Filter filter) { + setFilter(filter); + return this; + } + + /** + * Chaining method for {@link #setAlias(String)} + */ + public Join alias(String alias) { + setAlias(alias); + return this; + } + + /** + * Chaining method for {@link #setType(Type)} + */ + public Join type(Type type) { + setType(type); + return this; + } +} diff --git a/modules/library/api/src/main/java/org/geotools/data/Query.java b/modules/library/api/src/main/java/org/geotools/data/Query.java index b658aa8..9a33cee 100644 --- a/modules/library/api/src/main/java/org/geotools/data/Query.java +++ b/modules/library/api/src/main/java/org/geotools/data/Query.java @@ -72,6 +72,21 @@ import org.geotools.factory.Hints; * provided by a feature source implementation. * * + * + * Joins: + *

+ * The Query class supports the concepts of joins in that a query can result in a join of the + * feature type to other feature types in the same datastore. For example, the following would be + * a spatial join that selected the country that contain a particular city. + *


+ * Query query = new Query("countries");
+ * Join join = new Join("cities", CQL.toFilter("CONTAINS(geometry, b.geometry)"));
+ * join.setAlias("b");
+ * join.setFilter(CQL.toFilter("CITY_NAME = 'Canmore'"))
+ * query.getJoins().add(join);
+ * 
+ *

+ * * Example:

  * Filter filter = CQL.toFilter("NAME like '%land'");
  * Query query = new Query( "countries", filter );
@@ -173,6 +188,9 @@ public class Query {
 
     /** The typeName to get */
     protected String typeName;
+    
+    /** The optional alias for type name */
+    protected String alias;
 
     /** The namespace to get */
     protected URI namespace =Query.NO_NAMESPACE;
@@ -194,7 +212,10 @@ public class Query {
     
     /** The hints to be used during query execution */
     protected Hints hints;
-    
+
+    /** join clauses for this query */
+    protected List joins = new ArrayList();
+
     /**
      * Default constructor. Use setter methods to configure the Query
      * before use (the default Query will retrieve all features).
@@ -326,6 +347,11 @@ public class Query {
       this.version = query.getVersion();
       this.hints = query.getHints();
       this.startIndex = query.getStartIndex();
+      this.alias = query.getAlias();
+      this.joins = new ArrayList();
+      for (Join j : query.getJoins()) {
+          this.joins.add(new Join(j));
+      }
     }
 
     /**
@@ -585,7 +611,29 @@ public class Query {
     public void setTypeName(String typeName) {
         this.typeName = typeName;
     }
-    
+
+    /**
+     * An alias substitutable for {@link #getTypeName()}.
+     * 

+ * This value is typically used in a join query in which the join filter requires disambiguation + * due to property name overlaps between joined types. + *

+ * @since 8.0 + */ + public String getAlias() { + return alias; + } + + /** + * Sets the type name alias. + * + * @since 8.0 + * @see #getAlias() + */ + public void setAlias(String alias) { + this.alias = alias; + } + /** * Get the namespace of the feature type to be queried. * @@ -904,4 +952,16 @@ public class Query { return returnString.toString(); } + + /** + * The list of joins for this query. + *

+ * Each {@link Join} object specifies a feature type to join to. The join may only reference + * a feature type from within the same datastore. + *

+ * @see Join + */ + public List getJoins() { + return joins; + } } diff --git a/modules/library/api/src/main/java/org/geotools/data/QueryCapabilities.java b/modules/library/api/src/main/java/org/geotools/data/QueryCapabilities.java index 71a91c7..8ccba9b 100644 --- a/modules/library/api/src/main/java/org/geotools/data/QueryCapabilities.java +++ b/modules/library/api/src/main/java/org/geotools/data/QueryCapabilities.java @@ -108,4 +108,11 @@ public class QueryCapabilities { public boolean isUseProvidedFIDSupported() { return false; } + + /** + * If true the datastore supports joins between feature types within the datastores. + */ + public boolean isJoiningSupported() { + return false; + } } diff --git a/modules/library/data/src/main/java/org/geotools/data/store/ContentFeatureCollection.java b/modules/library/data/src/main/java/org/geotools/data/store/ContentFeatureCollection.java index 4bac526..3935b76 100644 --- a/modules/library/data/src/main/java/org/geotools/data/store/ContentFeatureCollection.java +++ b/modules/library/data/src/main/java/org/geotools/data/store/ContentFeatureCollection.java @@ -31,6 +31,7 @@ import org.geotools.data.DefaultQuery; import org.geotools.data.FeatureEvent; import org.geotools.data.FeatureListener; import org.geotools.data.FeatureReader; +import org.geotools.data.Join; import org.geotools.data.Query; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; @@ -113,13 +114,23 @@ public class ContentFeatureCollection implements SimpleFeatureCollection { this.featureSource = featureSource; this.query = query; + this.featureType = featureSource.getSchema(); + //retype feature type if necessary if ( query.getPropertyNames() != Query.ALL_NAMES ) { this.featureType = - SimpleFeatureTypeBuilder.retype(featureSource.getSchema(), query.getPropertyNames() ); + SimpleFeatureTypeBuilder.retype(this.featureType, query.getPropertyNames() ); } - else { - this.featureType = featureSource.getSchema(); + + //check for join and expand attributes as necessary + if (!query.getJoins().isEmpty()) { + SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder(); + tb.init(this.featureType); + + for (Join join : query.getJoins()) { + tb.add(join.attributeName(), SimpleFeature.class); + } + this.featureType = tb.buildFeatureType(); } } diff --git a/modules/library/data/src/main/java/org/geotools/data/store/ContentFeatureSource.java b/modules/library/data/src/main/java/org/geotools/data/store/ContentFeatureSource.java index 48684ef..4651a9b 100644 --- a/modules/library/data/src/main/java/org/geotools/data/store/ContentFeatureSource.java +++ b/modules/library/data/src/main/java/org/geotools/data/store/ContentFeatureSource.java @@ -477,7 +477,12 @@ public abstract class ContentFeatureSource implements SimpleFeatureSource { dq.setSortBy(new SortBy[] {SortBy.NATURAL_ORDER}); query = dq; } - + + //check for a join + if (!query.getJoins().isEmpty() && getQueryCapabilities().isJoiningSupported()) { + throw new IOException("Feature source does not support joins"); + } + FeatureReader reader = getReaderInternal( query ); // @@ -868,7 +873,7 @@ public abstract class ContentFeatureSource implements SimpleFeatureSource { // join the queries return DataUtilities.mixQueries(this.query, query, null); } - + /** * This method changes the query object so that all propertyName references are resolved * to simple attribute names against the schema of the feature source. diff --git a/modules/library/jdbc/src/main/java/org/geotools/data/jdbc/FilterToSQL.java b/modules/library/jdbc/src/main/java/org/geotools/data/jdbc/FilterToSQL.java index 315d31d..140938e 100644 --- a/modules/library/jdbc/src/main/java/org/geotools/data/jdbc/FilterToSQL.java +++ b/modules/library/jdbc/src/main/java/org/geotools/data/jdbc/FilterToSQL.java @@ -33,9 +33,8 @@ import org.geotools.factory.Hints; import org.geotools.filter.FilterCapabilities; import org.geotools.filter.FunctionImpl; import org.geotools.filter.LikeFilterImpl; -import org.geotools.filter.function.FilterFunction_strConcat; -import org.geotools.filter.function.FilterFunction_strEndsWith; import org.geotools.jdbc.JDBCDataStore; +import org.geotools.jdbc.JoinPropertyName; import org.geotools.jdbc.PrimaryKey; import org.geotools.util.ConverterFactory; import org.geotools.util.Converters; @@ -47,6 +46,7 @@ import org.opengis.filter.BinaryComparisonOperator; import org.opengis.filter.BinaryLogicOperator; import org.opengis.filter.ExcludeFilter; import org.opengis.filter.Filter; +import org.opengis.filter.FilterFactory; import org.opengis.filter.FilterVisitor; import org.opengis.filter.Id; import org.opengis.filter.IncludeFilter; @@ -90,6 +90,7 @@ import org.opengis.filter.temporal.AnyInteracts; import org.opengis.filter.temporal.Before; import org.opengis.filter.temporal.Begins; import org.opengis.filter.temporal.BegunBy; +import org.opengis.filter.temporal.BinaryTemporalOperator; import org.opengis.filter.temporal.During; import org.opengis.filter.temporal.EndedBy; import org.opengis.filter.temporal.Ends; @@ -99,6 +100,7 @@ import org.opengis.filter.temporal.OverlappedBy; import org.opengis.filter.temporal.TContains; import org.opengis.filter.temporal.TEquals; import org.opengis.filter.temporal.TOverlaps; +import org.opengis.temporal.Period; import com.vividsolutions.jts.geom.Geometry; @@ -150,6 +152,9 @@ public class FilterToSQL implements FilterVisitor, ExpressionVisitor { /** error message for exceptions */ protected static final String IO_ERROR = "io problem writing filter"; + /** filter factory */ + protected static FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory(null); + /** The filter types that this class can encode */ protected FilterCapabilities capabilities = null; @@ -188,7 +193,10 @@ public class FilterToSQL implements FilterVisitor, ExpressionVisitor { /** the srid corresponding to the current binary spatial filter being encoded */ protected Integer currentSRID; - + + /** inline flag, controlling whether "WHERE" will prefix the SQL encoded filter */ + protected boolean inline = false; + /** * Default constructor */ @@ -206,7 +214,11 @@ public class FilterToSQL implements FilterVisitor, ExpressionVisitor { public void setWriter(Writer out) { this.out = out; } - + + public void setInline(boolean inline) { + this.inline = inline; + } + /** * Performs the encoding, sends the encoded sql to the writer passed in. * @@ -220,7 +232,10 @@ public class FilterToSQL implements FilterVisitor, ExpressionVisitor { if (getCapabilities().fullySupports(filter)) { try { - out.write("WHERE "); + if (!inline) { + out.write("WHERE "); + } + filter.accept(this, null); //out.write(";"); @@ -349,6 +364,17 @@ public class FilterToSQL implements FilterVisitor, ExpressionVisitor { capabilities.addType(Id.class); capabilities.addType(IncludeFilter.class); capabilities.addType(ExcludeFilter.class); + + //temporal filters + capabilities.addType(After.class); + capabilities.addType(Before.class); + capabilities.addType(Begins.class); + capabilities.addType(BegunBy.class); + capabilities.addType(During.class); + capabilities.addType(Ends.class); + capabilities.addType(EndedBy.class); + capabilities.addType(TContains.class); + capabilities.addType(TEquals.class); return capabilities; } @@ -854,51 +880,244 @@ public class FilterToSQL implements FilterVisitor, ExpressionVisitor { + "can't do SDO relate against it: " + filter.getClass()); + // extract the property name and the geometry literal - PropertyName property; - Literal geometry; BinaryComparisonOperator op = (BinaryComparisonOperator) filter; - if (op.getExpression1() instanceof PropertyName - && op.getExpression2() instanceof Literal) { - property = (PropertyName) op.getExpression1(); - geometry = (Literal) op.getExpression2(); - } else if (op.getExpression2() instanceof PropertyName - && op.getExpression1() instanceof Literal) { - property = (PropertyName) op.getExpression2(); - geometry = (Literal) op.getExpression1(); - } else { - throw new IllegalArgumentException( - "Can only encode spatial filters that do " - + "compare a property name and a geometry"); + Expression e1 = op.getExpression1(); + Expression e2 = op.getExpression2(); + + if (e1 instanceof Literal && e2 instanceof PropertyName) { + e1 = (PropertyName) op.getExpression2(); + e2 = (Literal) op.getExpression1(); } - // handle native srid - currentGeometry = null; - currentSRID = null; - if (featureType != null) { - // going thru evaluate ensures we get the proper result even if the - // name has - // not been specified (convention -> the default geometry) - AttributeDescriptor descriptor = (AttributeDescriptor) property - .evaluate(featureType); - if (descriptor instanceof GeometryDescriptor) { - currentGeometry = (GeometryDescriptor) descriptor; - currentSRID = (Integer) descriptor.getUserData().get( - JDBCDataStore.JDBC_NATIVE_SRID); + if (e1 instanceof PropertyName) { + // handle native srid + currentGeometry = null; + currentSRID = null; + if (featureType != null) { + // going thru evaluate ensures we get the proper result even if the + // name has + // not been specified (convention -> the default geometry) + AttributeDescriptor descriptor = (AttributeDescriptor) e1.evaluate(featureType); + if (descriptor instanceof GeometryDescriptor) { + currentGeometry = (GeometryDescriptor) descriptor; + currentSRID = (Integer) descriptor.getUserData().get( + JDBCDataStore.JDBC_NATIVE_SRID); + } } } - return visitBinarySpatialOperator(filter, property, geometry, filter - .getExpression1() instanceof Literal, extraData); + if (e1 instanceof PropertyName && e2 instanceof Literal) { + //call the "regular" method + return visitBinarySpatialOperator(filter, (PropertyName)e1, (Literal)e2, filter + .getExpression1() instanceof Literal, extraData); + } + else { + //call the join version + return visitBinarySpatialOperator(filter, e1, e2, extraData); + } + } + /** + * Handles the common case of a PropertyName,Literal geometry binary spatial operator. + */ protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, PropertyName property, Literal geometry, boolean swapped, Object extraData) { throw new RuntimeException( "Subclasses must implement this method in order to handle geometries"); } - + + /** + * Handles the more general case of two generic expressions. + *

+ * The most common case is two PropertyName expressions, which happens during a spatial join. + *

+ */ + protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, + Expression e2, Object extraData) { + throw new RuntimeException( + "Subclasses must implement this method in order to handle geometries"); + } + + protected Object visitBinaryTemporalOperator(BinaryTemporalOperator filter, + Object extraData) { + if (filter == null) { + throw new NullPointerException("Null filter"); + } + + Expression e1 = filter.getExpression1(); + Expression e2 = filter.getExpression2(); + + if (e1 instanceof Literal && e2 instanceof PropertyName) { + e1 = (PropertyName) filter.getExpression2(); + e2 = (Literal) filter.getExpression1(); + } + + if (e1 instanceof PropertyName && e2 instanceof Literal) { + //call the "regular" method + return visitBinaryTemporalOperator(filter, (PropertyName)e1, (Literal)e2, + filter.getExpression1() instanceof Literal, extraData); + } + else { + //call the join version + return visitBinaryTemporalOperator(filter, e1, e2, extraData); + } + } + + /** + * Handles the common case of a PropertyName,Literal geometry binary temporal operator. + *

+ * Subclasses should override if they support more temporal operators than what is handled in + * this base class. + *

+ */ + protected Object visitBinaryTemporalOperator(BinaryTemporalOperator filter, + PropertyName property, Literal temporal, boolean swapped, Object extraData) { + + Class typeContext = null; + AttributeDescriptor attType = (AttributeDescriptor)property.evaluate(featureType); + if (attType != null) { + typeContext = attType.getType().getBinding(); + } + + //check for time period + Period period = null; + if (temporal.evaluate(null) instanceof Period) { + period = (Period) temporal.evaluate(null); + } + + //verify that those filters that require a time period have one + if ((filter instanceof Begins || filter instanceof BegunBy || filter instanceof Ends || + filter instanceof EndedBy || filter instanceof During || filter instanceof TContains) && + period == null) { + if (period == null) { + throw new IllegalArgumentException("Filter requires a time period"); + } + } + if (filter instanceof TEquals && period != null) { + throw new IllegalArgumentException("TEquals filter does not accept time period"); + } + + //ensure the time period is the correct argument + if ((filter instanceof Begins || filter instanceof Ends || filter instanceof During) && + swapped) { + throw new IllegalArgumentException("Time period must be second argument of Filter"); + } + if ((filter instanceof BegunBy || filter instanceof EndedBy || filter instanceof TContains) && + !swapped) { + throw new IllegalArgumentException("Time period must be first argument of Filter"); + } + + try { + if (filter instanceof After || filter instanceof Before) { + String op = filter instanceof After ? " > " : " < "; + String inv = filter instanceof After ? " < " : " > "; + + if (period != null) { + out.write("("); + + property.accept(this, extraData); + out.write(swapped ? inv : op); + visitBegin(period, extraData); + + out.write(" AND "); + + property.accept(this, extraData); + out.write(swapped ? inv : op); + visitEnd(period, extraData); + + out.write(")"); + } + else { + if (swapped) { + temporal.accept(this, typeContext); + } + else { + property.accept(this, extraData); + } + + out.write(op); + + if (swapped) { + property.accept(this, extraData); + } + else { + temporal.accept(this, typeContext); + } + } + } + else if (filter instanceof Begins || filter instanceof Ends || + filter instanceof BegunBy || filter instanceof EndedBy ) { + property.accept(this, extraData); + out.write( " = "); + + if (filter instanceof Begins || filter instanceof BegunBy) { + visitBegin(period, extraData); + } + else { + visitEnd(period, extraData); + } + } + else if (filter instanceof During || filter instanceof TContains){ + property.accept(this, extraData); + out.write( " BETWEEN "); + + visitBegin(period, extraData); + out.write( " AND "); + visitEnd(period, extraData); + } + else if (filter instanceof TEquals) { + property.accept(this, extraData); + out.write(" = "); + temporal.accept(this, typeContext); + } + } + catch(IOException e) { + throw new RuntimeException("Error encoding temporal filter", e); + } + + return extraData; + } + + void visitBegin(Period p, Object extraData) { + filterFactory.literal(p.getBeginning().getPosition().getDate()).accept(this, extraData); + } + + void visitEnd(Period p, Object extraData) { + filterFactory.literal(p.getEnding().getPosition().getDate()).accept(this, extraData); + } + + /** + * Handles the general case of two expressions in a binary temporal filter. + *

+ * Subclasses should override if they support more temporal operators than what is handled in + * this base class. + *

+ */ + protected Object visitBinaryTemporalOperator(BinaryTemporalOperator filter, Expression e1, + Expression e2, Object extraData) { + + if (!(filter instanceof After || filter instanceof Before || filter instanceof TEquals)) { + throw new IllegalArgumentException("Unsupported filter: " + filter + + ". Only After,Before,TEquals supported"); + } + + String op = filter instanceof After ? ">" : filter instanceof Before ? "<" : "="; + + try { + e1.accept(this, extraData); + out.write(" " + op + " "); + e2.accept(this, extraData); + } + catch(IOException e) { + return new RuntimeException("Error encoding temporal filter", e); + } + return extraData; + } + /** * Encodes a null filter value. The current implementation * does exactly nothing. @@ -928,7 +1147,17 @@ public class FilterToSQL implements FilterVisitor, ExpressionVisitor { if(extraData instanceof Class) { target = (Class) extraData; } + try { + SimpleFeatureType featureType = this.featureType; + + //check for join + if (expression instanceof JoinPropertyName) { + //encode the prefix + out.write(escapeName(((JoinPropertyName)expression).getAlias())); + out.write("."); + } + //first evaluate expression against feautre type get the attribute, // this handles xpath AttributeDescriptor attribute = null; @@ -1003,8 +1232,9 @@ public class FilterToSQL implements FilterVisitor, ExpressionVisitor { // handle geometry case if (literal instanceof Geometry) { // call this method for backwards compatibility with subclasses - visitLiteralGeometry(CommonFactoryFinder.getFilterFactory(null).literal(literal)); - } else { + visitLiteralGeometry(filterFactory.literal(literal)); + } + else { // write out the literal allowing subclasses to override this // behaviour (for writing out dates and the like using the BDMS custom functions) writeLiteral(literal); @@ -1088,7 +1318,12 @@ public class FilterToSQL implements FilterVisitor, ExpressionVisitor { throw new RuntimeException( "Subclasses must implement this method in order to handle geometries"); } - + + protected void visitLiteralTimePeriod(Period expression) { + throw new RuntimeException("Time periods not supported, subclasses must implement this " + + "method to support encoding timeperiods"); + } + public Object visit(Add expression, Object extraData) { return visit((BinaryExpression)expression, "+", extraData); } @@ -1211,46 +1446,46 @@ public class FilterToSQL implements FilterVisitor, ExpressionVisitor { //temporal filters, not supported public Object visit(After after, Object extraData) { - throw new UnsupportedOperationException("Temporal filter After not implemented"); + return visitBinaryTemporalOperator(after, extraData); } public Object visit(AnyInteracts anyInteracts, Object extraData) { - throw new UnsupportedOperationException("Temporal filter AnyInteracts not implemented"); + return visitBinaryTemporalOperator(anyInteracts, extraData); } public Object visit(Before before, Object extraData) { - throw new UnsupportedOperationException("Temporal filter Before not implemented"); + return visitBinaryTemporalOperator(before, extraData); } public Object visit(Begins begins, Object extraData) { - throw new UnsupportedOperationException("Temporal filter Begins not implemented"); + return visitBinaryTemporalOperator(begins, extraData); } public Object visit(BegunBy begunBy, Object extraData) { - throw new UnsupportedOperationException("Temporal filter BegunBy not implemented"); + return visitBinaryTemporalOperator(begunBy, extraData); } public Object visit(During during, Object extraData) { - throw new UnsupportedOperationException("Temporal filter During not implemented"); + return visitBinaryTemporalOperator(during, extraData); } public Object visit(EndedBy endedBy, Object extraData) { - throw new UnsupportedOperationException("Temporal filter EndedBy not implemented"); + return visitBinaryTemporalOperator(endedBy, extraData); } public Object visit(Ends ends, Object extraData) { - throw new UnsupportedOperationException("Temporal filter Ends not implemented"); + return visitBinaryTemporalOperator(ends, extraData); } public Object visit(Meets meets, Object extraData) { - throw new UnsupportedOperationException("Temporal filter Meets not implemented"); + return visitBinaryTemporalOperator(meets, extraData); } public Object visit(MetBy metBy, Object extraData) { - throw new UnsupportedOperationException("Temporal filter MetBy not implemented"); + return visitBinaryTemporalOperator(metBy, extraData); } public Object visit(OverlappedBy overlappedBy, Object extraData) { - throw new UnsupportedOperationException("Temporal filter OverlappedBy not implemented"); + return visitBinaryTemporalOperator(overlappedBy, extraData); } public Object visit(TContains contains, Object extraData) { - throw new UnsupportedOperationException("Temporal filter TContains not implemented"); + return visitBinaryTemporalOperator(contains, extraData); } public Object visit(TEquals equals, Object extraData) { - throw new UnsupportedOperationException("Temporal filter TEquals not implemented"); + return visitBinaryTemporalOperator(equals, extraData); } public Object visit(TOverlaps contains, Object extraData) { - throw new UnsupportedOperationException("Temporal filter TOverlaps not implemented"); + return visitBinaryTemporalOperator(contains, extraData); } /** @@ -1321,7 +1556,7 @@ public class FilterToSQL implements FilterVisitor, ExpressionVisitor { return s; } } - + /** * Current field encoder */ diff --git a/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStore.java b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStore.java index 1db084a..1e6b92c 100644 --- a/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStore.java +++ b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCDataStore.java @@ -45,7 +45,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import javax.sql.DataSource; -import javax.xml.transform.TransformerException; import org.geotools.data.DataStore; import org.geotools.data.DefaultQuery; @@ -72,6 +71,7 @@ import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.feature.visitor.CountVisitor; 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.opengis.feature.FeatureVisitor; @@ -155,7 +155,12 @@ public final class JDBCDataStore extends ContentDataStore * type. */ public static final String JDBC_NATIVE_TYPENAME = "org.geotools.jdbc.nativeTypeName"; - + + /** + * 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"; + /** * name of table to use to store geometries when {@link #associations} * is set. @@ -265,6 +270,14 @@ public final class JDBCDataStore extends ContentDataStore */ protected Map virtualTables = new ConcurrentHashMap(); + 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 this * method will replace it with the new one. @@ -1636,9 +1649,23 @@ public final class JDBCDataStore extends ContentDataStore } /** - * Encodes a feature id from a primary key and result set values. + * Calls through to: + *
+     *   encodeFID(pkey, rs, 0);
+     * 
*/ - protected String encodeFID( PrimaryKey pkey, ResultSet rs ) throws SQLException, IOException { + protected 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. + *

+ * offset 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 if(pkey.getColumns().isEmpty()) { return SimpleFeatureBuilder.createDefaultFeatureId(); @@ -1646,17 +1673,17 @@ public final class JDBCDataStore extends ContentDataStore // just one, no need to build support structures if(pkey.getColumns().size() == 1) - return rs.getString(1); + return rs.getString(offset+1); // more than one List keyValues = new ArrayList(); for(int i = 0; i < pkey.getColumns().size(); i++) { - String o = rs.getString(i+1); + String o = rs.getString(offset+i+1); keyValues.add( o ); } return encodeFID( keyValues ); } - + protected String encodeFID( List keyValues ) { StringBuffer fid = new StringBuffer(); for ( Object o : keyValues ) { @@ -2810,7 +2837,70 @@ public final class JDBCDataStore extends ContentDataStore 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, query.getHints()); + + //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); + + // finally encode limit/offset, if necessary + applyLimitOffset(sql, query); + + return sql.toString(); + } + 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); + + return sql.toString(); + } + + void selectColumns(SimpleFeatureType featureType, String prefix, Query query, StringBuffer sql) + throws IOException { + //primary key PrimaryKey key = null; try { @@ -2822,7 +2912,12 @@ public final class JDBCDataStore extends ContentDataStore // we need to add the primary key columns only if they are not already exposed for ( PrimaryKeyColumn col : key.getColumns() ) { - dialect.encodeColumnName(col.getName(), sql); + 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(","); } @@ -2832,47 +2927,48 @@ public final class JDBCDataStore extends ContentDataStore // 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) { + int i = sql.length(); + //encode as geometry - encodeGeometryColumn((GeometryDescriptor) att, sql, query.getHints()); - - //alias it to be the name of the original geometry - dialect.encodeColumnAlias(columnName, sql); + 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(columnName, sql); + dialect.encodeColumnName(prefix, columnName, sql); + } + + if (alias != null) { + dialect.encodeColumnAlias(alias, sql); } sql.append(","); } + } - sql.setLength(sql.length() - 1); - dialect.encodePostSelect(featureType, sql); + FilterToSQL filter(SimpleFeatureType featureType, Filter filter, StringBuffer sql) throws IOException { - sql.append(" FROM "); - encodeTableName(featureType.getTypeName(), sql, query.getHints()); - - //filtering - Filter filter = query.getFilter(); - if (filter != null && !Filter.INCLUDE.equals(filter)) { - //encode filter - try { - // grab the full feature type, as we might be encoding a filter - // that uses attributes that aren't returned in the results - SimpleFeatureType fullSchema = getSchema(featureType.getTypeName()); - FilterToSQL toSQL = createFilterToSQL(fullSchema); - sql.append(" ").append(toSQL.encodeToString(filter)); - } catch (FilterToSQLException e) { - throw new RuntimeException(e); - } + try { + // grab the full feature type, as we might be encoding a filter + // that uses attributes that aren't returned in the results + SimpleFeatureType fullSchema = getSchema(featureType.getTypeName()); + FilterToSQL toSQL = dialect instanceof PreparedStatementSQLDialect ? + createPreparedFilterToSQL(fullSchema) : createFilterToSQL(fullSchema); + toSQL.setInline(true); + sql.append(" ").append(toSQL.encodeToString(filter)); + return toSQL; + } catch (FilterToSQLException e) { + throw new RuntimeException(e); } - - //sorting - sort(featureType, query.getSortBy(), key, sql); - - // finally encode limit/offset, if necessary - applyLimitOffset(sql, query); - - return sql.toString(); } /** @@ -2883,9 +2979,9 @@ public final class JDBCDataStore extends ContentDataStore * @param sql * @throws IOException */ - void sort(SimpleFeatureType featureType, SortBy[] sort, - PrimaryKey key, StringBuffer sql) throws IOException { + 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 (int i = 0; i < sort.length; i++) { @@ -2901,12 +2997,12 @@ public final class JDBCDataStore extends ContentDataStore throw new IOException("Cannot do natural order without a primary key"); for ( PrimaryKeyColumn col : key.getColumns() ) { - dialect.encodeColumnName(col.getName(), sql); + dialect.encodeColumnName(prefix, col.getName(), sql); sql.append(order); sql.append(","); } } else { - dialect.encodeColumnName(getPropertyName(featureType, sort[i].getPropertyName()), + dialect.encodeColumnName(prefix, getPropertyName(featureType, sort[i].getPropertyName()), sql); sql.append(order); sql.append(","); @@ -2939,44 +3035,11 @@ public final class JDBCDataStore extends ContentDataStore StringBuffer sql = new StringBuffer(); sql.append("SELECT "); - // primary key - PrimaryKey key = null; - - try { - key = getPrimaryKey(featureType); - } catch (IOException e) { - throw new RuntimeException(e); - } - Set pkColumnNames = getColumnNames(key); - - for ( PrimaryKeyColumn col : key.getColumns() ) { - dialect.encodeColumnName(col.getName(), sql); - sql.append(","); - } - - //other columns - for (AttributeDescriptor att : featureType.getAttributeDescriptors()) { - // skip the eventually exposed pk column values - String columnName = att.getLocalName(); - if(pkColumnNames.contains(columnName)) - continue; - - if (att instanceof GeometryDescriptor) { - //encode as geometry - encodeGeometryColumn((GeometryDescriptor) att, sql, query.getHints()); - - //alias it to be the name of the original geometry - dialect.encodeColumnAlias(columnName, sql); - } else { - dialect.encodeColumnName(columnName, sql); - } - - sql.append(","); - } - + //column names + selectColumns(featureType, null, query, sql); sql.setLength(sql.length() - 1); dialect.encodePostSelect(featureType, sql); - + sql.append(" FROM "); encodeTableName(featureType.getTypeName(), sql, query.getHints()); @@ -2984,20 +3047,14 @@ public final class JDBCDataStore extends ContentDataStore PreparedFilterToSQL toSQL = null; Filter filter = query.getFilter(); if (filter != null && !Filter.INCLUDE.equals(filter)) { + sql.append(" WHERE "); + //encode filter - try { - // grab the full feature type, as we might be encoding a filter - // that uses attributes that aren't returned in the results - SimpleFeatureType fullSchema = getSchema(featureType.getTypeName()); - toSQL = createPreparedFilterToSQL(fullSchema); - sql.append(" ").append(toSQL.encodeToString(filter)); - } catch (FilterToSQLException e) { - throw new RuntimeException(e); - } + toSQL = (PreparedFilterToSQL) filter(featureType, filter, sql); } //sorting - sort(featureType, query.getSortBy(), key, sql); + sort(featureType, query.getSortBy(), null, sql); // finally encode limit/offset, if necessary applyLimitOffset(sql, query); @@ -3013,6 +3070,59 @@ public final class JDBCDataStore extends ContentDataStore 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 toSQLs = encodeWhereJoin(featureType, join, sql); + + //sorting + sort(featureType, query.getSortBy(), join.getPrimaryAlias(), sql); + + // finally encode limit/offset, if necessary + applyLimitOffset(sql, query); + + 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 toSQLs, Connection cx) + throws SQLException { + int offset = 0; + for (PreparedFilterToSQL toSQL : (List) toSQLs) { + setPreparedFilterValues(ps, toSQL, offset, cx); + offset += toSQL.getLiteralValues().size(); + } + } + /** * Helper method for setting the values of the WHERE class of a prepared statement. * @@ -3195,7 +3305,7 @@ public final class JDBCDataStore extends ContentDataStore * as limit/offset usually alters the number of returned rows * (and a count returns just one), and then count on the result of that first select */ - protected String selectCountSQL(SimpleFeatureType featureType, Query query) throws SQLException { + protected String selectCountSQL(SimpleFeatureType featureType, Query query) throws SQLException, IOException { //JD: this method should not be called anymore return selectAggregateSQL("count",null,featureType,query); } @@ -3204,7 +3314,7 @@ public final class JDBCDataStore extends ContentDataStore * Generates a 'SELECT count(*) FROM' prepared statement. */ protected PreparedStatement selectCountSQLPS(SimpleFeatureType featureType, Query query, Connection cx ) - throws SQLException { + throws SQLException, IOException { //JD: this method shold not be called anymore return selectAggregateSQLPS("count",null,featureType,query,cx); } @@ -3213,41 +3323,9 @@ public final class JDBCDataStore extends ContentDataStore * Generates a 'SELECT () FROM' statement. */ protected String selectAggregateSQL(String function, AttributeDescriptor att, - SimpleFeatureType featureType, Query query) throws SQLException { + SimpleFeatureType featureType, Query query) throws SQLException, IOException { StringBuffer sql = new StringBuffer(); - - boolean limitOffset = checkLimitOffset(query); - if(limitOffset) { - sql.append("SELECT * FROM "); - } else { - sql.append("SELECT "); - encodeFunction(function,att,query,sql); - sql.append( " FROM "); - } - encodeTableName(featureType.getTypeName(), sql, query.getHints()); - - 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); - } - } - - if(limitOffset) { - applyLimitOffset(sql, query); - - StringBuffer sql2 = new StringBuffer("SELECT "); - encodeFunction(function,att,query,sql2); - sql2.append(" AS gt_result_"); - sql2.append(" FROM ("); - sql.insert(0,sql2.toString()); - sql.append(") gt_limited_"); - } - + doSelectAggregateSQL(function, att, featureType, query, sql); return sql.toString(); } @@ -3255,29 +3333,63 @@ public final class JDBCDataStore extends ContentDataStore * Generates a 'SELECT () FROM' prepared statement. */ protected PreparedStatement selectAggregateSQLPS(String function, AttributeDescriptor att, SimpleFeatureType featureType, Query query, Connection cx) - throws SQLException { + throws SQLException, IOException { StringBuffer sql = new StringBuffer(); + List toSQL = doSelectAggregateSQL(function, att, featureType, query, 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 doSelectAggregateSQL(String function, AttributeDescriptor att, + SimpleFeatureType featureType, Query query, StringBuffer sql) throws SQLException, IOException { + + JoinInfo join = !query.getJoins().isEmpty() + ? JoinInfo.create(query, featureType, this) : null; boolean limitOffset = checkLimitOffset(query); if(limitOffset) { - sql.append("SELECT * FROM "); + 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 "); encodeFunction(function,att,query,sql); sql.append( " FROM "); } - encodeTableName(featureType.getTypeName(), sql, query.getHints()); + + if (join != null) { + encodeTableJoin(featureType, join, query, sql); + } + else { + encodeTableName(featureType.getTypeName(), sql, query.getHints()); + } - Filter filter = query.getFilter(); - 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); + List toSQL = new ArrayList(); + 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)); } } @@ -3292,17 +3404,9 @@ public final class JDBCDataStore extends ContentDataStore sql.append(") gt_limited_"); } - 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; + return toSQL; } - + protected void encodeFunction( String function, AttributeDescriptor att, Query query, StringBuffer sql ) { sql.append(function).append("("); if ( att == null ) { @@ -3963,6 +4067,70 @@ public final class JDBCDataStore extends ContentDataStore } /** + * Helper method to encode the join clause(s) of a query. + */ + protected void encodeTableJoin(SimpleFeatureType featureType, JoinInfo join, Query query, StringBuffer sql) + throws SQLException { + encodeTableName(featureType.getTypeName(), sql, query.getHints()); + dialect.encodeTableAlias(join.getPrimaryAlias(), sql); + + for (JoinPart part : join.getParts()) { + sql.append(" "); + dialect.encodeJoin(part.getJoin().getType(), sql); + sql.append(" "); + encodeTableName(part.getQueryFeatureType().getTypeName(), sql, null); + dialect.encodeTableAlias(part.getAlias(), sql); + sql.append(" ON "); + + Filter j = part.getJoinFilter(); + + FilterToSQL toSQL = dialect instanceof PreparedStatementSQLDialect ? + createPreparedFilterToSQL(null) : createFilterToSQL(null); + toSQL.setInline(true); + try { + sql.append(" ").append(toSQL.encodeToString(j)); + } + catch (FilterToSQLException e) { + throw new RuntimeException(e); + } + } + } + + protected List encodeWhereJoin(SimpleFeatureType featureType, + JoinInfo join, StringBuffer sql) throws IOException { + + List 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) { @@ -4171,21 +4339,24 @@ public final class JDBCDataStore extends ContentDataStore * @param hints , may be null */ protected 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, srid,sql,distance); + dialect.encodeGeometryColumnGeneralized(gatt, prefix, srid,sql,distance); return; } if (isSimplificationRequired(hints, gatt)==true) { Double distance = (Double) hints.get(Hints.GEOMETRY_SIMPLIFICATION); - dialect.encodeGeometryColumnSimplified(gatt,srid, sql,distance); + dialect.encodeGeometryColumnSimplified(gatt, prefix, srid, sql,distance); return; } - - dialect.encodeGeometryColumn(gatt,srid, sql); + + dialect.encodeGeometryColumn(gatt,prefix,srid, sql); } /** diff --git a/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCFeatureReader.java b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCFeatureReader.java index ef54b91..d346141 100644 --- a/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCFeatureReader.java +++ b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCFeatureReader.java @@ -25,7 +25,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -34,44 +33,29 @@ import java.util.NoSuchElementException; import java.util.logging.Level; import java.util.logging.Logger; -import org.geotools.data.DefaultQuery; import org.geotools.data.FeatureReader; import org.geotools.data.Transaction; -import org.geotools.data.store.ContentFeatureStore; import org.geotools.factory.Hints; import org.geotools.feature.IllegalAttributeException; import org.geotools.feature.simple.SimpleFeatureBuilder; -import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.filter.identity.FeatureIdImpl; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.util.Converters; import org.geotools.util.logging.Logging; -import org.opengis.feature.Association; import org.opengis.feature.FeatureFactory; import org.opengis.feature.GeometryAttribute; import org.opengis.feature.Property; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; -import org.opengis.feature.type.AssociationDescriptor; -import org.opengis.feature.type.AssociationType; import org.opengis.feature.type.AttributeDescriptor; -import org.opengis.feature.type.FeatureTypeFactory; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.feature.type.Name; -import org.opengis.filter.FilterFactory; -import org.opengis.filter.Id; -import org.opengis.filter.expression.PropertyName; import org.opengis.filter.identity.FeatureId; import org.opengis.geometry.BoundingBox; -import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateSequenceFactory; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; -import com.vividsolutions.jts.geom.LineString; -import com.vividsolutions.jts.geom.Point; -import com.vividsolutions.jts.geom.Polygon; -import com.vividsolutions.jts.geom.impl.CoordinateArraySequence; /** * Reader for jdbc datastore @@ -138,6 +122,11 @@ public class JDBCFeatureReader implements FeatureReader 0 dont - // resolve the associated feature or geometry - Integer depth = (Integer) hints.get(Hints.ASSOCIATION_TRAVERSAL_DEPTH); - - if (depth == null) { - depth = new Integer(0); - } - - PropertyName associationPropertyName = - (PropertyName) hints.get(Hints.ASSOCIATION_PROPERTY); - // round up attributes final int attributeCount = featureType.getAttributeCount(); int[] attributeRsIndex = buildAttributeRsIndex(); for(int i = 0; i < attributeCount; i++) { AttributeDescriptor type = featureType.getDescriptor(i); - //figure out if any referenced attributes should be resolved - boolean resolve = depth.intValue() > 0; - - if (resolve && (associationPropertyName != null)) { - AttributeDescriptor associationProperty = (AttributeDescriptor) associationPropertyName - .evaluate(featureType); - resolve = (associationProperty != null) - && associationProperty.getLocalName().equals(type.getLocalName()); - } - try { Object value = null; @@ -320,7 +308,7 @@ public class JDBCFeatureReader implements FeatureReader r = dataStore.getFeatureReader(query, tx); - try { - r.hasNext(); - - SimpleFeature associated = r.next(); - association.setValue(associated); - } finally { - r.close(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - // set the actual value to be the association - value = association; - } - } finally { - dataStore.closeSafe(associations); - dataStore.closeSafe(select); - } + value = rs.getObject(offset+attributeRsIndex[i]); } // they value may need conversion. We let converters chew the initial @@ -860,7 +551,7 @@ public class JDBCFeatureReader implements FeatureReader allAttributes = new ArrayList(Arrays.asList(query.getPropertyNames())); - for (String extraAttribute : extraAttributes) { - if(!allAttributes.contains(extraAttribute)) - allAttributes.add(extraAttribute); - } - String[] allAttributeArray = (String[]) allAttributes.toArray(new String[allAttributes.size()]); - querySchema = SimpleFeatureTypeBuilder.retype(getSchema(), allAttributeArray); - } - } - + SimpleFeatureType[] types = + buildQueryAndReturnFeatureTypes(getSchema(), query.getPropertyNames(), postFilter); + SimpleFeatureType querySchema = types[0]; + SimpleFeatureType returnedSchema = types[1]; + //grab connection Connection cx = getDataStore().getConnection(getState()); @@ -560,16 +561,39 @@ public class JDBCFeatureSource extends ContentFeatureSource { if(getState().getTransaction() == Transaction.AUTO_COMMIT) { cx.setAutoCommit(dialect.isAutoCommitQuery()); } - - if ( dialect instanceof PreparedStatementSQLDialect ) { - PreparedStatement ps = getDataStore().selectSQLPS(querySchema, preQuery, cx); - reader = new JDBCFeatureReader( ps, cx, this, querySchema, query.getHints() ); - } else { - //build up a statement for the content - String sql = getDataStore().selectSQL(querySchema, preQuery); - getDataStore().getLogger().fine(sql); - - reader = new JDBCFeatureReader( sql, cx, this, querySchema, query.getHints() ); + + if (query.getJoins().isEmpty()) { + //regular query + if ( dialect instanceof PreparedStatementSQLDialect ) { + PreparedStatement ps = getDataStore().selectSQLPS(querySchema, preQuery, cx); + reader = new JDBCFeatureReader( ps, cx, this, querySchema, query.getHints() ); + } else { + //build up a statement for the content + String sql = getDataStore().selectSQL(querySchema, preQuery); + getDataStore().getLogger().fine(sql); + + reader = new JDBCFeatureReader( sql, cx, this, querySchema, query.getHints() ); + } + } + else { + JoinInfo join = JoinInfo.create(preQuery, this); + + if ( dialect instanceof PreparedStatementSQLDialect ) { + PreparedStatement ps =getDataStore().selectJoinSQLPS(querySchema, join, preQuery, cx); + reader = new JDBCJoiningFeatureReader(ps, cx, this, querySchema, join, query.getHints()); + } else { + //build up a statement for the content + String sql = getDataStore().selectJoinSQL(querySchema, join, preQuery); + getDataStore().getLogger().fine(sql); + + reader = new JDBCJoiningFeatureReader(sql, cx, this, querySchema, join, query.getHints()); + } + + //check for post filters + if (join.hasPostFilters()) { + reader = new JDBCJoiningFilteringFeatureReader(reader, join); + //TODO: retyping + } } } catch (Throwable e) { // NOSONAR // close the connection @@ -593,6 +617,37 @@ public class JDBCFeatureSource extends ContentFeatureSource { return reader; } + SimpleFeatureType[] buildQueryAndReturnFeatureTypes(SimpleFeatureType featureType, + String[] propertyNames, Filter filter) { + + SimpleFeatureType[] types = null; + if(propertyNames == Query.ALL_NAMES) { + return new SimpleFeatureType[]{featureType, featureType}; + } else { + SimpleFeatureType returnedSchema = SimpleFeatureTypeBuilder.retype(featureType, propertyNames); + SimpleFeatureType querySchema = returnedSchema; + + if (filter != null && !filter.equals(Filter.INCLUDE)) { + FilterAttributeExtractor extractor = new FilterAttributeExtractor(featureType); + filter.accept(extractor, null); + + String[] extraAttributes = extractor.getAttributeNames(); + if(extraAttributes != null && extraAttributes.length > 0) { + List allAttributes = new ArrayList(Arrays.asList(propertyNames)); + for (String extraAttribute : extraAttributes) { + if(!allAttributes.contains(extraAttribute)) + allAttributes.add(extraAttribute); + } + String[] allAttributeArray = + (String[]) allAttributes.toArray(new String[allAttributes.size()]); + querySchema = SimpleFeatureTypeBuilder.retype(getSchema(), allAttributeArray); + } + } + types = new SimpleFeatureType[]{querySchema, returnedSchema}; + } + return types; + } + @Override protected boolean handleVisitor(Query query, FeatureVisitor visitor) throws IOException { //grab connection diff --git a/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCFeatureStore.java b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCFeatureStore.java index 4cb2822..125b6b4 100644 --- a/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCFeatureStore.java +++ b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCFeatureStore.java @@ -99,6 +99,10 @@ public final class JDBCFeatureStore extends ContentFeatureStore { return delegate.getDataStore(); } + public JDBCFeatureSource getFeatureSource() { + return delegate; + } + @Override public ContentEntry getEntry() { return delegate.getEntry(); diff --git a/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCJoiningFeatureReader.java b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCJoiningFeatureReader.java new file mode 100644 index 0000000..1b43218 --- /dev/null +++ b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCJoiningFeatureReader.java @@ -0,0 +1,125 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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 java.io.IOException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; + +import org.geotools.factory.Hints; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.feature.simple.SimpleFeatureTypeBuilder; +import org.geotools.jdbc.JoinInfo.JoinPart; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.simple.SimpleFeatureType; + +/** + * Feature reader that wraps multiple feature readers in a join query. + * + * @author Justin Deoliveira, OpenGeo + * + */ +public class JDBCJoiningFeatureReader extends JDBCFeatureReader { + + List joinReaders; + SimpleFeatureBuilder joinFeatureBuilder; + + public JDBCJoiningFeatureReader(String sql, Connection cx, JDBCFeatureSource featureSource, + SimpleFeatureType featureType, JoinInfo join, Hints hints) + throws SQLException, IOException { + + //super(sql, cx, featureSource, retype(featureType, join), hints); + super(sql, cx, featureSource, featureType, hints); + + init(cx, featureSource, featureType, join, hints); + } + + public JDBCJoiningFeatureReader(PreparedStatement st, Connection cx, JDBCFeatureSource featureSource, + SimpleFeatureType featureType, JoinInfo join, Hints hints) + throws SQLException, IOException { + + super(st, cx, featureSource, featureType, hints); + + init(cx, featureSource, featureType, join, hints); + } + + void init(Connection cx, JDBCFeatureSource featureSource, SimpleFeatureType featureType, + JoinInfo join, Hints hints) throws SQLException, IOException { + joinReaders = new ArrayList(); + int offset = featureType.getAttributeCount() + getPrimaryKey().getColumns().size(); + + for (JoinPart part : join.getParts()) { + SimpleFeatureType ft = part.getQueryFeatureType(); + joinReaders.add(new JDBCFeatureReader(rs, cx, offset, + featureSource.getDataStore().getAbsoluteFeatureSource(ft.getTypeName()), ft, hints)); + } + + //builder for the final joined feature + joinFeatureBuilder = new SimpleFeatureBuilder(retype(featureType, join)); + } + + @Override + public boolean hasNext() throws IOException { + boolean next = super.hasNext(); + for (JDBCFeatureReader r : joinReaders) { + r.setNext(next); + } + return next; + } + + @Override + public SimpleFeature next() throws IOException, IllegalArgumentException, + NoSuchElementException { + //read the regular feature + SimpleFeature f = super.next(); + + //rebuild it with the join feature type + joinFeatureBuilder.init(f); + f = joinFeatureBuilder.buildFeature(f.getID()); + + //add additional attributes for joined features + for (int i = 0; i < joinReaders.size(); i++) { + JDBCFeatureReader r = joinReaders.get(i); + f.setAttribute(f.getAttributeCount() - joinReaders.size() + i, r.next()); + } + + return f; + } + + @Override + public void close() throws IOException { + super.close(); + + //we don't need to close the delegate readers because they share the same result set + // and connection as this reader + } + + static SimpleFeatureType retype(SimpleFeatureType featureType, JoinInfo join) { + SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); + b.init(featureType); + + for (JoinPart part : join.getParts()) { + b.add(part.getAttributeName(), SimpleFeature.class); + } + return b.buildFeatureType(); + } +} diff --git a/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCJoiningFilteringFeatureReader.java b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCJoiningFilteringFeatureReader.java new file mode 100644 index 0000000..9fd07fc --- /dev/null +++ b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JDBCJoiningFilteringFeatureReader.java @@ -0,0 +1,97 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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 java.io.IOException; +import java.util.NoSuchElementException; + +import org.geotools.data.DelegatingFeatureReader; +import org.geotools.data.FeatureReader; +import org.geotools.jdbc.JoinInfo.JoinPart; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.simple.SimpleFeatureType; + +/** + * Feature reader that wraps multiple feature readers in a joining / post filtered query. + * + * @author Justin Deoliveira, OpenGeo + * + */ +public class JDBCJoiningFilteringFeatureReader implements DelegatingFeatureReader { + + FeatureReader delegate; + JoinInfo join; + SimpleFeature next; + + public JDBCJoiningFilteringFeatureReader(FeatureReader delegate, JoinInfo join) { + this.delegate = delegate; + this.join = join; + } + + public FeatureReader getDelegate() { + return delegate; + } + + public SimpleFeatureType getFeatureType() { + return delegate.getFeatureType(); + } + + public boolean hasNext() throws IOException { + if (next != null) { + return true; + } + + //scroll through the delegate reader until we get a feature whose joined features match + // all the post features + while(delegate.hasNext()) { + SimpleFeature peek = delegate.next(); + + for (JoinPart part : join.getParts()) { + if (part.getPostFilter() != null) { + SimpleFeature f = (SimpleFeature) peek.getAttribute(part.getAttributeName()); + if (!part.getPostFilter().evaluate(f)) { + peek = null; + break; + } + } + } + + if (peek != null) { + next = peek; + break; + } + } + + return next != null; + } + + public SimpleFeature next() throws IOException, IllegalArgumentException, + NoSuchElementException { + if (!hasNext()) { + throw new NoSuchElementException("No more features"); + } + + SimpleFeature f = next; + next = null; + return f; + } + + public void close() throws IOException { + delegate.close(); + } +} diff --git a/modules/library/jdbc/src/main/java/org/geotools/jdbc/JoinInfo.java b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JoinInfo.java new file mode 100644 index 0000000..181a9ea --- /dev/null +++ b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JoinInfo.java @@ -0,0 +1,356 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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 java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.geotools.data.Join; +import org.geotools.data.Query; +import org.geotools.feature.simple.SimpleFeatureTypeBuilder; +import org.geotools.filter.visitor.DuplicatingFilterVisitor; +import org.opengis.feature.simple.SimpleFeatureType; +import org.opengis.feature.type.AttributeDescriptor; +import org.opengis.filter.Filter; +import org.opengis.filter.expression.PropertyName; + +/** + * Holds information about a join query. + * + * @author Justin Deoliveira, OpenGeo + * + */ +public class JoinInfo { + + public static JoinInfo create(Query query, JDBCFeatureSource featureSource) throws IOException { + return create(query, featureSource.getSchema(), featureSource.getDataStore()); + } + + public static JoinInfo create(Query query, SimpleFeatureType featureType, JDBCDataStore dataStore) + throws IOException { + + JoinInfo info = new JoinInfo(); + info.setPrimaryAlias("a"); + + for (int i = 0; i < query.getJoins().size(); i++) { + Join j = query.getJoins().get(i); + + JoinPart part = new JoinPart(j); + info.getParts().add(part); + + //load the feature type being joined to + JDBCFeatureSource joinFeatureSource = dataStore.getAbsoluteFeatureSource(j.getTypeName()); + part.setFeatureSource(joinFeatureSource); + + //ensure every join as a unique alias + String alias = String.valueOf((char)('b' + i)); + part.setAlias(alias); + + //hack on the join filter as necessary + Filter joinFilter = j.getJoinFilter(); + + if (query.getAlias() != null) { + //rewrite any user specified alias with the one we specified + joinFilter = + (Filter) joinFilter.accept(new JoinPrefixRewriter(query.getAlias(), "a"), null); + } + if (j.getAlias() != null) { + //rewrite any user specified alias with the one we specified + joinFilter = + (Filter) joinFilter.accept(new JoinPrefixRewriter(j.getAlias(), alias), null); + } + + //qualify all property names in the join filter so that they known about their + // feature type and alias + joinFilter = (Filter) joinFilter.accept(new JoinQualifier(featureType, "a", + joinFeatureSource.getSchema(), alias), null); + part.setJoinFilter(joinFilter); + + //split the other filter + Filter[] prePostFilters = joinFeatureSource.splitFilter(j.getFilter()); + + //build the query and return feature types based on the post filter + SimpleFeatureType[] types = joinFeatureSource.buildQueryAndReturnFeatureTypes( + joinFeatureSource.getSchema(), j.getPropertyNames(), prePostFilters[1]); + + //alias any attributes in this feature type that clash with attributes in the primary + // feature type + types[0] = SimpleFeatureTypeBuilder.copy(types[0]); + for (AttributeDescriptor att : types[0].getAttributeDescriptors()) { + if (featureType.getDescriptor(att.getName()) != null) { + att.getUserData().put( + JDBCDataStore.JDBC_COLUMN_ALIAS, alias + "_" + att.getLocalName()); + } + } + + part.setQueryFeatureType(types[0]); + part.setReturnFeatureType(types[1]); + + //qualify the pre filter + if (prePostFilters[0] != null && prePostFilters[0] != Filter.INCLUDE) { + prePostFilters[0] = (Filter) prePostFilters[0].accept( + new JoinQualifier(joinFeatureSource.getSchema(), alias), null); + } + part.setPreFilter(prePostFilters[0]); + part.setPostFilter(prePostFilters[1]); + + //assign an attribute name in the resulting feature type + //TODO: we should check to ensure that the joined feature type attribute name are + // actually unique + part.setAttributeName(part.getJoin().getAlias() != null ? + part.getJoin().getAlias() : part.getQueryFeatureType().getTypeName()); + } + + //qualify the main query filter + Filter filter = query.getFilter(); + if (filter != null && !Filter.INCLUDE.equals(filter)) { + filter = (Filter) filter.accept(new JoinQualifier(featureType, "a"), null); + } + info.setFilter(filter); + return info; + } + + /** primary table alias */ + String primaryAlias; + + /** parts of the join */ + List parts = new ArrayList(); + + /** the "joinified" filter of the main query */ + Filter filter; + + private JoinInfo() { + } + + public String getPrimaryAlias() { + return primaryAlias; + } + + public void setPrimaryAlias(String primaryAlias) { + this.primaryAlias = primaryAlias; + } + + public Filter getFilter() { + return filter; + } + + public void setFilter(Filter filter) { + this.filter = filter; + } + + public boolean hasPostFilters() { + for (JoinPart p : parts) { + if (p.getPostFilter() != null && !Filter.INCLUDE.equals(p.getPostFilter())) { + return true; + } + } + return false; + } + + public List getParts() { + return parts; + } + + public static class JoinPart { + + /** original join object */ + Join join; + + /** assigned alias */ + String alias; + + /** join filter */ + Filter joinFilter; + + /** feature source being joined to */ + JDBCFeatureSource featureSource; + + /** query feature type */ + SimpleFeatureType queryFeatureType; + + /** join feature type */ + SimpleFeatureType returnFeatureType; + + /** pre filter */ + Filter preFilter; + + /** post filter */ + Filter postFilter; + + /** the attribute in the final feature type this part is assigned */ + String attributeName; + + public JoinPart(Join join) { + this.join = join; + } + + public Join getJoin() { + return join; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public Filter getJoinFilter() { + return joinFilter; + } + + public void setJoinFilter(Filter joinFilter) { + this.joinFilter = joinFilter; + } + + public JDBCFeatureSource getFeatureSource() { + return featureSource; + } + + public void setFeatureSource(JDBCFeatureSource featureSource) { + this.featureSource = featureSource; + } + + public SimpleFeatureType getQueryFeatureType() { + return queryFeatureType; + } + + public void setQueryFeatureType(SimpleFeatureType queryFeatureType) { + this.queryFeatureType = queryFeatureType; + } + + public SimpleFeatureType getReturnFeatureType() { + return returnFeatureType; + } + + public void setReturnFeatureType(SimpleFeatureType returnFeatureType) { + this.returnFeatureType = returnFeatureType; + } + + public Filter getPreFilter() { + return preFilter; + } + + public void setPreFilter(Filter preFilter) { + this.preFilter = preFilter; + } + + public Filter getPostFilter() { + return postFilter; + } + + public void setPostFilter(Filter postFilter) { + this.postFilter = postFilter; + } + + public String getAttributeName() { + return attributeName; + } + + public void setAttributeName(String attributeName) { + this.attributeName = attributeName; + } + } + + static class JoinPrefixRewriter extends DuplicatingFilterVisitor { + String from, to; + + public JoinPrefixRewriter(String from, String to) { + this.from = from; + this.to = to; + } + + @Override + public Object visit(PropertyName expression, Object extraData) { + String name = expression.getPropertyName(); + if (name.startsWith(from+".")) { + name = to + "." + name.substring((from+".").length()); + } + return getFactory(extraData).property(name, expression.getNamespaceContext()); + } + } + + static class JoinQualifier extends DuplicatingFilterVisitor { + + SimpleFeatureType ft1, ft2; + String alias1, alias2; + + public JoinQualifier(SimpleFeatureType ft, String alias) { + this(ft, alias, null, null); + } + + public JoinQualifier(SimpleFeatureType ft1, String alias1, SimpleFeatureType ft2, String alias2) { + this.ft1 = ft1; + this.ft2 = ft2; + this.alias1 = alias1; + this.alias2 = alias2; + } + + @Override + public Object visit(PropertyName expression, Object extraData) { + String name = expression.getPropertyName(); + String[] split = name.split("\\."); + + //if split.length > 2 then join up remaining parts, means the column name itself had a + // period in it + if (split.length > 2) { + String prefix = split[0]; + StringBuffer sb = new StringBuffer(); + for (int i = 1; i < split.length; i++) { + sb.append(split[i]); + } + split = new String[]{prefix, sb.toString()}; + } + + JoinPropertyName propertyName = null; + + //if we only have one feature type its easy, use the first feature type + if (ft2 == null) { + propertyName = new JoinPropertyName(ft1, alias1, split.length > 1 ? split[1] : split[0]); + } + else { + if (split.length == 1) { + //name was unprefixed, figure out what feature type the meant + SimpleFeatureType ft = ft1.getDescriptor(split[0]) != null ? ft1 : + ft2.getDescriptor(split[0]) != null ? ft2 : null; + if (ft == null) { + throw new IllegalArgumentException(String.format("Attribute '%s' not present in" + + " either type '%s' or '%s'", split[0], ft1.getTypeName(), ft2.getTypeName())); + } + + propertyName = new JoinPropertyName(ft, ft == ft1 ? alias1 : alias2, split[0]); + } + else { + //name was prefixed, look up the type based on prefix + SimpleFeatureType ft = split[0].equals(alias1) ? ft1 : + split[0].equals(alias2) ? ft2 : null; + if (ft == null) { + throw new IllegalArgumentException(String.format("Prefix '%s' does not match " + + "either alias '%s' or '%s'", split[0], alias1, alias2)); + } + + propertyName = new JoinPropertyName(ft, split[0], split[1]); + } + } + + return propertyName; + } + } + +} diff --git a/modules/library/jdbc/src/main/java/org/geotools/jdbc/JoinPropertyName.java b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JoinPropertyName.java new file mode 100644 index 0000000..3946320 --- /dev/null +++ b/modules/library/jdbc/src/main/java/org/geotools/jdbc/JoinPropertyName.java @@ -0,0 +1,49 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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 org.geotools.filter.AttributeExpressionImpl; +import org.opengis.feature.simple.SimpleFeatureType; + +/** + * Property name that knows what feature type it comes from. + *

+ * Used by the sql encoder to determine how to property encode the join query. + *

+ * + * @author Justin Deoliveira, OpenGeo + */ +public class JoinPropertyName extends AttributeExpressionImpl { + + SimpleFeatureType featureType; + String alias; + + public JoinPropertyName(SimpleFeatureType featureType, String alias, String name) { + super(name); + this.featureType = featureType; + this.alias = alias; + } + + public SimpleFeatureType getFeatureType() { + return featureType; + } + + public String getAlias() { + return alias; + } +} diff --git a/modules/library/jdbc/src/main/java/org/geotools/jdbc/SQLDialect.java b/modules/library/jdbc/src/main/java/org/geotools/jdbc/SQLDialect.java index 0818777..0c0c68a 100644 --- a/modules/library/jdbc/src/main/java/org/geotools/jdbc/SQLDialect.java +++ b/modules/library/jdbc/src/main/java/org/geotools/jdbc/SQLDialect.java @@ -32,6 +32,7 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import org.geotools.data.Join.Type; import org.geotools.data.Query; import org.geotools.factory.Hints; import org.geotools.feature.visitor.CountVisitor; @@ -443,8 +444,29 @@ public abstract class SQLDialect { * {@link #getNameEscape()}. Subclasses usually dont override this method * and instead override {@link #getNameEscape()}. *

+ * + * @deprecated use {@link #encodeColumnName(String, String, StringBuffer)}. */ - public void encodeColumnName(String raw, StringBuffer sql) { + public final void encodeColumnName(String raw, StringBuffer sql) { + sql.append(ne()).append(raw).append(ne()); + } + + /** + * Encodes the name of a column in an SQL statement. + *

+ * This method wraps raw in the character provided by + * {@link #getNameEscape()}. Subclasses usually don't override this method + * and instead override {@link #getNameEscape()}. + *

+ *

+ * The prefix parameter may be null so subclasses that do override must + * handle that case. + *

+ */ + public void encodeColumnName(String prefix, String raw, StringBuffer sql) { + if (prefix != null) { + sql.append(ne()).append(prefix).append(ne()).append("."); + } sql.append(ne()).append(raw).append(ne()); } @@ -475,9 +497,9 @@ public abstract class SQLDialect { */ public void encodeColumnAlias(String raw, StringBuffer sql) { sql.append(" as "); - encodeColumnName(raw, sql); + encodeColumnName(null, raw, sql); } - + /** * Encodes the alias of a table in an sql query. *

@@ -487,7 +509,7 @@ public abstract class SQLDialect { */ public void encodeTableAlias(String raw, StringBuffer sql) { sql.append(" as "); - encodeColumnName(raw, sql); + encodeColumnName(null, raw, sql); } /** @@ -650,11 +672,44 @@ public abstract class SQLDialect { * This default implementation simply uses the column name without any * wrapping function, subclasses must override. *

+ * + * @deprecated use {@link #encodeGeometryColumn(GeometryDescriptor, String, int, StringBuffer)} */ - public void encodeGeometryColumn(GeometryDescriptor gatt, int srid, StringBuffer sql) { + public final void encodeGeometryColumn(GeometryDescriptor gatt, int srid, StringBuffer sql) { encodeColumnName(gatt.getLocalName(), sql); } - + + /** + * Encodes the name of a geometry column in a SELECT statement. + *

+ * This method should wrap the column name in any functions that are used to + * retrieve its value. For instance, often it is necessary to use the function + * asText, or asWKB when fetching a geometry. + *

+ *

+ * This method must also be sure to properly encode the name of the column with the + * {@link #encodeColumnName(String, String, StringBuffer)} function. + *

+ *

+ * Example: + *

+ *
+    *   
+    *   sql.append( "asText(" );
+    *   column( gatt.getLocalName(), sql );
+    *   sql.append( ")" );
+    *   
+    * 
+ *

+ *

+ * This default implementation simply uses the column name without any + * wrapping function, subclasses must override. + *

+ */ + public void encodeGeometryColumn(GeometryDescriptor gatt, String prefix, int srid, StringBuffer sql) { + encodeColumnName(prefix, gatt.getLocalName(), sql); + } + /** * Encodes a generalized geometry using a DB provided SQL function if available * If not supported, subclasses should not implement @@ -673,10 +728,15 @@ public abstract class SQLDialect { * *

*

- * + * @deprecated use {@link #encodeGeometryColumnGeneralized(GeometryDescriptor, String, int, StringBuffer, Double)} */ - public void encodeGeometryColumnGeneralized(GeometryDescriptor gatt, int srid,StringBuffer sql, Double distance) { + public final void encodeGeometryColumnGeneralized(GeometryDescriptor gatt, int srid,StringBuffer sql, Double distance) { + throw new UnsupportedOperationException("Geometry generalization not supported"); + } + + public void encodeGeometryColumnGeneralized(GeometryDescriptor gatt, String prefix, int srid, + StringBuffer sql, Double distance) { throw new UnsupportedOperationException("Geometry generalization not supported"); } @@ -687,12 +747,16 @@ public abstract class SQLDialect { * If not supported, subclasses should not implement * Only called if {@link Hints#GEOMETRY_SIMPLIFICATION is supported} * @see SQLDialect#encodeGeometryColumnGeneralized(GeometryDescriptor, StringBuffer, Double) - * + * @deprecated use {@link #encodeGeometryColumnSimplified(GeometryDescriptor, String, int, StringBuffer, Double)} */ public void encodeGeometryColumnSimplified(GeometryDescriptor gatt, int srid,StringBuffer sql, Double distance) { throw new UnsupportedOperationException("Geometry simplification not supported"); } + public void encodeGeometryColumnSimplified(GeometryDescriptor gatt, String prefix, int srid, + StringBuffer sql, Double distance) { + throw new UnsupportedOperationException("Geometry simplification not supported"); + } /** * Decodes a geometry value from the result of a query. @@ -755,11 +819,26 @@ public abstract class SQLDialect { * */ public void encodePrimaryKey(String column, StringBuffer sql) { - encodeColumnName( column, sql ); + encodeColumnName( null, column, sql ); sql.append( " INTEGER PRIMARY KEY" ); } /** + * Encodes the syntax for a join between two tables. + */ + public void encodeJoin(Type joinType, StringBuffer sql) { + switch(joinType) { + case INNER: + sql.append("INNER"); break; + case OUTER: + sql.append("LEFT OUTER"); break; + default: + throw new IllegalArgumentException("Join type " + joinType + " not supported"); + } + sql.append(" JOIN"); + } + + /** * Encodes anything post a column in a CREATE TABLE statement. *

* This is appended after the column name and type. Subclasses may choose to override @@ -1049,5 +1128,4 @@ public abstract class SQLDialect { public boolean isAutoCommitQuery() { return false; } - } diff --git a/modules/library/jdbc/src/test/java/org/geotools/data/jdbc/FilterToSQLTest.java b/modules/library/jdbc/src/test/java/org/geotools/data/jdbc/FilterToSQLTest.java index c4e2802..6b5d816 100644 --- a/modules/library/jdbc/src/test/java/org/geotools/data/jdbc/FilterToSQLTest.java +++ b/modules/library/jdbc/src/test/java/org/geotools/data/jdbc/FilterToSQLTest.java @@ -159,4 +159,14 @@ public class FilterToSQLTest extends TestCase { encoder.encode(equal); assertEquals("WHERE testAttr = testAttr + 5", output.toString()); } + + public void testInline() throws Exception { + PropertyIsEqualTo equal = filterFac.equal(filterFac.property("testAttr"), filterFac.literal(5), false); + StringWriter output = new StringWriter(); + FilterToSQL encoder = new FilterToSQL(output); + encoder.setInline(true); + + encoder.encode(equal); + assertEquals("testAttr = 5", output.toString()); + } } diff --git a/modules/library/jdbc/src/test/java/org/geotools/jdbc/EqualsFunction.java b/modules/library/jdbc/src/test/java/org/geotools/jdbc/EqualsFunction.java new file mode 100644 index 0000000..40bf629 --- /dev/null +++ b/modules/library/jdbc/src/test/java/org/geotools/jdbc/EqualsFunction.java @@ -0,0 +1,45 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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 org.geotools.filter.FunctionImpl; +import org.geotools.util.Converters; +import org.opengis.filter.expression.PropertyName; + +/** + * A test function used for jdbc tests. + * + * @author Justin Deoliveira, OpenGeo + * + */ +public class EqualsFunction extends FunctionImpl { + + public EqualsFunction() { + setName("__equals"); + } + + @Override + public Object evaluate(Object object) { + PropertyName name = (PropertyName) getParameters().get(0); + Object literal = getParameters().get(1).evaluate(null); + + Object o = name.evaluate(object); + return o.equals(Converters.convert(literal, o.getClass())); + } + +} diff --git a/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCForeignKeyTest.java b/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCForeignKeyTest.java deleted file mode 100644 index 825f86f..0000000 --- a/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCForeignKeyTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * GeoTools - The Open Source Java GIS Toolkit - * http://geotools.org - * - * (C) 2002-2008, 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 org.geotools.data.DefaultQuery; -import org.geotools.data.FeatureReader; -import org.geotools.data.Transaction; -import org.geotools.factory.Hints; -import org.opengis.feature.Association; -import org.opengis.feature.Property; -import org.opengis.feature.simple.SimpleFeature; -import org.opengis.feature.simple.SimpleFeatureType; -import org.opengis.feature.type.AttributeDescriptor; - - -/** - * - * - * @source $URL$ - */ -public abstract class JDBCForeignKeyTest extends JDBCTestSupport { - protected void connect() throws Exception { - super.connect(); - - dataStore.setAssociations(true); - } - - public void testGetSchema() throws Exception { - SimpleFeatureType featureType = dataStore.getSchema("fk"); - - assertNotNull(featureType); - - AttributeDescriptor att = featureType.getDescriptor("ft1"); - assertNotNull(att); - - assertEquals(Association.class, att.getType().getBinding()); - } - - public void testGetFeatures() throws Exception { - Hints hints = new Hints(Hints.ASSOCIATION_TRAVERSAL_DEPTH, new Integer(1)); - - DefaultQuery query = new DefaultQuery(); - query.setTypeName("fk"); - query.setHints(hints); - - FeatureReader reader = dataStore.getFeatureReader(query, Transaction.AUTO_COMMIT); - assertTrue(reader.hasNext()); - - SimpleFeature feature = reader.next(); - Association association = (Association) feature.getAttribute("ft1"); - assertEquals("ft1.0", association.getUserData().get("gml:id")); - - SimpleFeature associated = (SimpleFeature) association.getValue(); - assertNotNull(associated); - - assertEquals("zero", associated.getAttribute("stringProperty")); - - Property attribute = feature.getProperty("ft1"); - assertEquals("ft1.0", attribute.getUserData().get("gml:id")); - - reader.close(); - } - - public void testGetFeaturesWithZeroDepth() throws Exception { - Hints hints = new Hints(Hints.ASSOCIATION_TRAVERSAL_DEPTH, new Integer(0)); - - DefaultQuery query = new DefaultQuery(); - query.setTypeName("fk"); - query.setHints(hints); - - FeatureReader reader = dataStore.getFeatureReader(query, Transaction.AUTO_COMMIT); - assertTrue(reader.hasNext()); - - SimpleFeature feature = reader.next(); - Association association = (Association) feature.getAttribute("ft1"); - assertNull(association.getValue()); - - Property attribute = feature.getProperty("ft1"); - assertEquals("ft1.0", attribute.getUserData().get("gml:id")); - - reader.close(); - } -} diff --git a/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCGeometryAssociationTestSupport.java b/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCGeometryAssociationTestSupport.java deleted file mode 100644 index 1b8d6e6..0000000 --- a/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCGeometryAssociationTestSupport.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * GeoTools - The Open Source Java GIS Toolkit - * http://geotools.org - * - * (C) 2002-2008, 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 java.util.Map; - -import org.geotools.data.DefaultQuery; -import org.geotools.data.FeatureReader; -import org.geotools.data.Transaction; -import org.geotools.factory.Hints; -import org.opengis.feature.simple.SimpleFeature; -import org.opengis.feature.simple.SimpleFeatureType; -import org.opengis.filter.identity.GmlObjectId; - -import com.vividsolutions.jts.geom.Coordinate; -import com.vividsolutions.jts.geom.Geometry; -import com.vividsolutions.jts.geom.MultiPoint; -import com.vividsolutions.jts.geom.Point; - - -/** - * - * - * @source $URL$ - */ -public abstract class JDBCGeometryAssociationTestSupport extends JDBCTestSupport { - protected void connect() throws Exception { - super.connect(); - - dataStore.setAssociations(true); - } - - public void testGetFeatureNoAssociation() throws Exception { - Hints hints = new Hints(Hints.ASSOCIATION_TRAVERSAL_DEPTH, new Integer(1)); - - DefaultQuery query = new DefaultQuery(); - query.setTypeName("ga"); - query.setHints(hints); - - FeatureReader reader = dataStore.getFeatureReader(query, Transaction.AUTO_COMMIT); - assertTrue(reader.hasNext()); - - SimpleFeature feature = (SimpleFeature) reader.next(); - assertNotNull(feature); - - assertEquals("ga.0", feature.getID()); - - Geometry g = (Geometry) feature.getDefaultGeometry(); - assertNotNull(g); - assertEquals(new Coordinate(0, 0), g.getCoordinate()); - - Map ud = (Map) g.getUserData(); - assertEquals("0", ud.get("gml:id")); - reader.close(); - } - - public void testGetFeatureWithAssociation() throws Exception { - Hints hints = new Hints(Hints.ASSOCIATION_TRAVERSAL_DEPTH, new Integer(1)); - - DefaultQuery query = new DefaultQuery(); - query.setTypeName("ga"); - query.setHints(hints); - - FeatureReader reader = dataStore.getFeatureReader(query, Transaction.AUTO_COMMIT); - assertTrue(reader.hasNext()); - reader.next(); - - assertTrue(reader.hasNext()); - - SimpleFeature feature = (SimpleFeature) reader.next(); - assertNotNull(feature); - - assertEquals("ga.1", feature.getID()); - - Geometry g = (Geometry) feature.getDefaultGeometry(); - assertNotNull(g); - assertEquals(new Coordinate(1, 1), g.getCoordinate()); - assertTrue(g.getUserData() instanceof Map); - assertEquals("1", ((Map) g.getUserData()).get("gml:id")); - - reader.close(); - - //test with zero dpeth - query.setHints(new Hints(Hints.ASSOCIATION_TRAVERSAL_DEPTH, new Integer(0))); - reader = dataStore.getFeatureReader(query, Transaction.AUTO_COMMIT); - - assertTrue(reader.hasNext()); - reader.next(); - - assertTrue(reader.hasNext()); - - feature = (SimpleFeature) reader.next(); - assertNotNull(feature); - - assertEquals("ga.1", feature.getID()); - - g = (Geometry) feature.getDefaultGeometry(); - assertNotNull(g); - assertTrue(g.isEmpty()); - //assertTrue( g instanceof NullGeometry ); - assertTrue(g.getUserData() instanceof Map); - assertEquals("1", ((Map) g.getUserData()).get("gml:id")); - - reader.close(); - } - - public void testMultiGeometryAssociation() throws Exception { - Hints hints = new Hints(Hints.ASSOCIATION_TRAVERSAL_DEPTH, new Integer(1)); - - DefaultQuery query = new DefaultQuery(); - query.setTypeName("ga"); - query.setHints(hints); - - FeatureReader reader = dataStore.getFeatureReader(query, Transaction.AUTO_COMMIT); - assertTrue(reader.hasNext()); - reader.next(); - assertTrue(reader.hasNext()); - reader.next(); - - assertTrue(reader.hasNext()); - - SimpleFeature feature = reader.next(); - - Geometry g = (Geometry) feature.getDefaultGeometry(); - assertNotNull(g); - assertTrue(g instanceof MultiPoint); - - MultiPoint mp = (MultiPoint) g; - assertEquals("2", ((Map) mp.getUserData()).get("gml:id")); - - assertEquals(2, mp.getNumGeometries()); - - Point p = (Point) mp.getGeometryN(0); - assertEquals(new Coordinate(0, 0), p.getCoordinate()); - assertEquals("0", ((Map) p.getUserData()).get("gml:id")); - - p = (Point) mp.getGeometryN(1); - assertEquals(new Coordinate(1, 1), p.getCoordinate()); - assertEquals("1", ((Map) p.getUserData()).get("gml:id")); - - reader.close(); - } - - public void testGetGmlObjectGeometry() throws Exception { - GmlObjectId id = dataStore.getFilterFactory().gmlObjectId("0"); - Object o = dataStore.getGmlObject( id, null); - - assertNotNull( o ); - assertTrue( o instanceof Point ); - assertEquals( new Coordinate( 0 , 0 ), ((Point)o).getCoordinate() ); - - id = dataStore.getFilterFactory().gmlObjectId( "ft1.0" ); - o = dataStore.getGmlObject( id, null); - assertNotNull( o ); - assertTrue( o instanceof SimpleFeature ); - } -} diff --git a/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCJoinTest.java b/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCJoinTest.java new file mode 100644 index 0000000..dcf7649 --- /dev/null +++ b/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCJoinTest.java @@ -0,0 +1,435 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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 java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.geotools.data.Join; +import org.geotools.data.Query; +import org.geotools.data.Join.Type; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.filter.Filter; +import org.opengis.filter.FilterFactory; +import org.opengis.filter.FilterFactory2; +import org.opengis.filter.sort.SortBy; +import org.opengis.filter.sort.SortOrder; + +public abstract class JDBCJoinTest extends JDBCTestSupport { + + @Override + protected abstract JDBCJoinTestSetup createTestSetup(); + + public void testSimpleJoin() throws Exception { + doTestSimpleJoin(false); + doTestSimpleJoin(true); + } + + void doTestSimpleJoin(boolean exposePrimaryKeys) throws Exception { + dataStore.setExposePrimaryKeyColumns(exposePrimaryKeys); + SimpleFeatureIterator ita = + dataStore.getFeatureSource(tname("ft1")).getFeatures().features(); + SimpleFeatureIterator itb = + dataStore.getFeatureSource(tname("ftjoin")).getFeatures().features(); + + FilterFactory ff = dataStore.getFilterFactory(); + Query q = new Query(tname("ft1")); + q.getJoins().add(new Join(tname("ftjoin"), + ff.equal(ff.property(aname("stringProperty")), ff.property(aname("name")), true))); + + SimpleFeatureCollection features = dataStore.getFeatureSource(tname("ft1")).getFeatures(q); + assertEquals(dataStore.getFeatureSource(tname("ft1")).getFeatures(q).size(), features.size()); + + SimpleFeatureIterator it = features.features(); + try { + assertTrue(it.hasNext() && ita.hasNext() && itb.hasNext()); + + while(it.hasNext()) { + SimpleFeature f = it.next(); + assertEquals(5, f.getAttributeCount()); + + SimpleFeature g = (SimpleFeature) f.getAttribute(tname("ftjoin")); + + SimpleFeature a = ita.next(); + SimpleFeature b = itb.next(); + + for (int i = 0; i < a.getAttributeCount(); i++) { + assertAttributeValuesEqual(a.getAttribute(i), f.getAttribute(i)); + } + for (int i = 0; i < b.getAttributeCount(); i++) { + assertAttributeValuesEqual(b.getAttribute(i), g.getAttribute(i)); + } + } + } + finally { + it.close(); + ita.close(); + itb.close(); + } + } + + public void testSimpleJoinWithFilter() throws Exception { + doTestSimpleJoinWithFilter(false); + doTestSimpleJoinWithFilter(true); + } + + void doTestSimpleJoinWithFilter(boolean exposePrimaryKeys) throws Exception { + dataStore.setExposePrimaryKeyColumns(exposePrimaryKeys); + FilterFactory ff = dataStore.getFilterFactory(); + Query q = new Query(tname("ft1")); + q.getJoins().add(new Join(tname("ftjoin"), + ff.equal(ff.property(aname("stringProperty")), ff.property(aname("name")), true))); + q.setFilter(ff.equal(ff.property(aname("stringProperty")), ff.literal("two"), true)); + + SimpleFeatureCollection features = dataStore.getFeatureSource(tname("ft1")).getFeatures(q); + assertEquals(1, features.size()); + + SimpleFeatureIterator it = features.features(); + try { + SimpleFeature f = it.next(); + assertEquals(5, f.getAttributeCount()); + assertEquals(2, ((Number)f.getAttribute(aname("intProperty"))).intValue()); + assertEquals("two", f.getAttribute(aname("stringProperty"))); + + SimpleFeature g = (SimpleFeature) f.getAttribute(aname("ftjoin")); + assertEquals(3, g.getAttributeCount()); + assertEquals(2, ((Number)g.getAttribute(aname("id"))).intValue()); + assertEquals("two", g.getAttribute(aname("name"))); + } + finally { + it.close(); + } + } + + public void testSimpleJoinWithFilterNoProperties() throws Exception { + doTestSimpleJoinWithFilterNoProperties(false); + doTestSimpleJoinWithFilterNoProperties(true); + } + + void doTestSimpleJoinWithFilterNoProperties(boolean exposePrimaryKeys) throws Exception { + dataStore.setExposePrimaryKeyColumns(exposePrimaryKeys); + FilterFactory ff = dataStore.getFilterFactory(); + Query q = new Query(tname("ft1")); + Join j = new Join(tname("ftjoin"), + ff.equal(ff.property(aname("stringProperty")), ff.property(aname("name")), true)); + j.setProperties(Query.NO_PROPERTIES); + q.getJoins().add(j); + q.setFilter(ff.equal(ff.property(aname("stringProperty")), ff.literal("two"), true)); + + SimpleFeatureCollection features = dataStore.getFeatureSource(tname("ft1")).getFeatures(q); + assertEquals(1, features.size()); + + SimpleFeatureIterator it = features.features(); + try { + SimpleFeature f = it.next(); + assertEquals(5, f.getAttributeCount()); + assertEquals(2, ((Number)f.getAttribute(aname("intProperty"))).intValue()); + assertEquals("two", f.getAttribute(aname("stringProperty"))); + + SimpleFeature g = (SimpleFeature) f.getAttribute(aname("ftjoin")); + assertEquals(0, g.getAttributeCount()); + } + finally { + it.close(); + } + } + + public void testSimpleJoinWithFilterCount() throws Exception { + doTestSimpleJoinWithFilterCount(false); + doTestSimpleJoinWithFilterCount(true); + } + + void doTestSimpleJoinWithFilterCount(boolean exposePrimaryKeys) throws Exception { + dataStore.setExposePrimaryKeyColumns(exposePrimaryKeys); + FilterFactory ff = dataStore.getFilterFactory(); + Query q = new Query(tname("ft1")); + Join j = new Join(tname("ftjoin"), + ff.equal(ff.property(aname("stringProperty")), ff.property(aname("name")), true)); + j.filter(ff.greater(ff.property(aname("id")), ff.literal(1))); + q.getJoins().add(j); + q.setFilter(ff.less(ff.property(aname("intProperty")), ff.literal(3))); + + assertEquals(1, dataStore.getFeatureSource(tname("ft1")).getCount(q)); + } + + public void testSimpleJoinWithPostFilter() throws Exception { + doTestSimpleJoinWithPostFilter(false); + doTestSimpleJoinWithPostFilter(true); + } + + void doTestSimpleJoinWithPostFilter(boolean exposePrimaryKeys) throws Exception { + dataStore.setExposePrimaryKeyColumns(exposePrimaryKeys); + FilterFactory ff = dataStore.getFilterFactory(); + + Filter j = ff.equal(ff.property(aname("stringProperty")), ff.property(aname("name")), true); + Query q = new Query(tname("ft1")); + q.getJoins().add(new Join(tname("ftjoin"), j)); + q.setFilter(ff.equal( + ff.function("__equals", ff.property(aname("stringProperty")), ff.literal("one")), + ff.literal(true), true)); + + SimpleFeatureCollection features = dataStore.getFeatureSource(tname("ft1")).getFeatures(q); + assertEquals(1, features.size()); + + //test with post filter on table being joined + q = new Query(tname("ft1")); + Join join = new Join(tname("ftjoin"), j); + join.filter(ff.equal( + ff.function("__equals", ff.property(aname("name")), ff.literal("one")), + ff.literal(true), true)); + q.getJoins().add(join); + + features = dataStore.getFeatureSource(tname("ft1")).getFeatures(q); + assertEquals(1, features.size()); + } + + public void testSimpleJoinWithPostFilterNoProperties() throws Exception { + doTestSimpleJoinWithPostFilterNoProperties(false); + doTestSimpleJoinWithPostFilterNoProperties(true); + } + + void doTestSimpleJoinWithPostFilterNoProperties(boolean exposePrimaryKeys) throws Exception { + dataStore.setExposePrimaryKeyColumns(exposePrimaryKeys); + FilterFactory ff = dataStore.getFilterFactory(); + + Filter j = ff.equal(ff.property(aname("stringProperty")), ff.property(aname("name")), true); + Query q = new Query(tname("ft1")); + Join join = new Join(tname("ftjoin"), j); + join.setProperties(Query.NO_PROPERTIES); + q.getJoins().add(join); + q.setFilter(ff.equal( + ff.function("__equals", ff.property(aname("stringProperty")), ff.literal("one")), + ff.literal(true), true)); + + SimpleFeatureCollection features = dataStore.getFeatureSource(tname("ft1")).getFeatures(q); + assertEquals(1, features.size()); + + //test with post filter on table being joined + q = new Query(tname("ft1")); + join = new Join(tname("ftjoin"), j); + join.setProperties(Query.NO_PROPERTIES); + join.filter(ff.equal( + ff.function("__equals", ff.property(aname("name")), ff.literal("one")), + ff.literal(true), true)); + q.getJoins().add(join); + + features = dataStore.getFeatureSource(tname("ft1")).getFeatures(q); + assertEquals(1, features.size()); + } + + public void testSimpleJoinWithSort() throws Exception { + doTestSimpleJoinWithSort(false); + doTestSimpleJoinWithSort(true); + } + + void doTestSimpleJoinWithSort(boolean exposePrimaryKeys) throws Exception { + dataStore.setExposePrimaryKeyColumns(exposePrimaryKeys); + FilterFactory ff = dataStore.getFilterFactory(); + + Filter j = ff.equal(ff.property(aname("stringProperty")), ff.property(aname("name")), true); + Query q = new Query(tname("ft1")); + q.getJoins().add(new Join(tname("ftjoin"), j)); + q.setSortBy(new SortBy[]{ff.sort(aname("intProperty"), SortOrder.DESCENDING)}); + + SimpleFeatureCollection features = dataStore.getFeatureSource(tname("ft1")).getFeatures(q); + SimpleFeatureIterator it = features.features(); + try { + assertTrue(it.hasNext()); + assertEquals("two", it.next().getAttribute(aname("stringProperty"))); + assertTrue(it.hasNext()); + assertEquals("one", it.next().getAttribute(aname("stringProperty"))); + assertTrue(it.hasNext()); + assertEquals("zero", it.next().getAttribute(aname("stringProperty"))); + } + finally { + it.close(); + } + } + + public void testSimpleJoinWithLimitOffset() throws Exception { + doTestSimpleJoinWithLimitOffset(false); + doTestSimpleJoinWithLimitOffset(true); + } + + void doTestSimpleJoinWithLimitOffset(boolean exposePrimaryKeys) throws Exception { + dataStore.setExposePrimaryKeyColumns(exposePrimaryKeys); + FilterFactory ff = dataStore.getFilterFactory(); + + Filter j = ff.equal(ff.property(aname("stringProperty")), ff.property(aname("name")), true); + Query q = new Query(tname("ft1")); + q.getJoins().add(new Join(tname("ftjoin"), j)); + q.setFilter(ff.greater(ff.property(aname("intProperty")), ff.literal(0))); + q.setStartIndex(1); + q.setSortBy(new SortBy[]{ff.sort(aname("intProperty"), SortOrder.ASCENDING)}); + + SimpleFeatureCollection features = dataStore.getFeatureSource(tname("ft1")).getFeatures(q); + assertEquals(1, features.size()); + + SimpleFeatureIterator it = features.features(); + try { + assertTrue(it.hasNext()); + + SimpleFeature f = it.next(); + assertEquals("two", f.getAttribute(aname("stringProperty"))); + + SimpleFeature g = (SimpleFeature) f.getAttribute(aname("ftjoin")); + assertEquals("two", g.getAttribute(aname("name"))); + } + finally { + it.close(); + } + } + + public void testSelfJoin() throws Exception { + doTestSelfJoin(false); + doTestSelfJoin(true); + } + + public void doTestSelfJoin(boolean exposePrimaryKeys) throws Exception { + dataStore.setExposePrimaryKeyColumns(exposePrimaryKeys); + FilterFactory ff = dataStore.getFilterFactory(); + Query q = new Query(tname("ft1")); + q.getJoins().add(new Join(tname("ft1"), + ff.equal(ff.property(aname("intProperty")), ff.property(aname("foo.intProperty")), true)).alias(aname("foo"))); + q.setFilter(ff.equal(ff.property(aname("stringProperty")), ff.literal("two"), true)); + + SimpleFeatureCollection features = dataStore.getFeatureSource(tname("ft1")).getFeatures(q); + assertEquals(1, features.size()); + + SimpleFeatureIterator it = features.features(); + try { + assertTrue(it.hasNext()); + + SimpleFeature f = it.next(); + assertEquals(5, f.getAttributeCount()); + assertEquals(2, ((Number)f.getAttribute(aname("intProperty"))).intValue()); + assertEquals("two", f.getAttribute(aname("stringProperty"))); + + SimpleFeature g = (SimpleFeature) f.getAttribute(aname("foo")); + assertEquals(4, g.getAttributeCount()); + assertEquals(2, ((Number)g.getAttribute(aname("intProperty"))).intValue()); + assertEquals("two", g.getAttribute(aname("stringProperty"))); + } + finally { + it.close(); + } + } + + public void testSpatialJoin() throws Exception { + doTestSpatialJoin(false); + doTestSpatialJoin(true); + } + + void doTestSpatialJoin(boolean exposePrimaryKeys) throws Exception { + dataStore.setExposePrimaryKeyColumns(exposePrimaryKeys); + FilterFactory2 ff = (FilterFactory2) dataStore.getFilterFactory(); + Query q = new Query(tname("ft1")); + q.setPropertyNames(Arrays.asList(aname("geometry"), aname("intProperty"))); + q.setSortBy(new SortBy[]{ff.sort(aname("intProperty"), SortOrder.ASCENDING)}); + q.getJoins().add(new Join(tname("ftjoin"), + ff.contains(ff.property(aname("geom")), ff.property(aname("geometry"))))); + + SimpleFeatureCollection features = dataStore.getFeatureSource(tname("ft1")).getFeatures(q); + assertEquals(6, features.size()); + + SimpleFeatureIterator it = features.features(); + SimpleFeature f; + try { + Set s = new HashSet(Arrays.asList("zero", "one", "two")); + + assertTrue(it.hasNext()); + f = it.next(); + assertEquals(0, ((Number)f.getAttribute(aname("intProperty"))).intValue()); + s.remove(((SimpleFeature)f.getAttribute(tname("ftjoin"))).getAttribute(aname("name"))); + + assertTrue(it.hasNext()); + f = it.next(); + assertEquals(0, ((Number)f.getAttribute(aname("intProperty"))).intValue()); + s.remove(((SimpleFeature)f.getAttribute(tname("ftjoin"))).getAttribute(aname("name"))); + + assertTrue(it.hasNext()); + f = it.next(); + assertEquals(0, ((Number)f.getAttribute(aname("intProperty"))).intValue()); + s.remove(((SimpleFeature)f.getAttribute(tname("ftjoin"))).getAttribute(aname("name"))); + + assertEquals(0, s.size()); + + s = new HashSet(Arrays.asList("one", "two")); + + assertTrue(it.hasNext()); + f = it.next(); + assertEquals(1, ((Number)f.getAttribute(aname("intProperty"))).intValue()); + s.remove(((SimpleFeature)f.getAttribute(tname("ftjoin"))).getAttribute(aname("name"))); + + assertTrue(it.hasNext()); + f = it.next(); + assertEquals(1, ((Number)f.getAttribute(aname("intProperty"))).intValue()); + s.remove(((SimpleFeature)f.getAttribute(tname("ftjoin"))).getAttribute(aname("name"))); + + assertEquals(0, s.size()); + + assertTrue(it.hasNext()); + f = it.next(); + assertEquals(2, ((Number)f.getAttribute(aname("intProperty"))).intValue()); + assertEquals("two", ((SimpleFeature)f.getAttribute(tname("ftjoin"))).getAttribute(aname("name"))); + + assertFalse(it.hasNext()); + } + finally { + it.close(); + } + } + + public void testOuterJoin() throws Exception { + doTestOuterJoin(false); + doTestOuterJoin(true); + } + + void doTestOuterJoin(boolean exposePrimaryKeys) throws Exception { + dataStore.setExposePrimaryKeyColumns(exposePrimaryKeys); + FilterFactory ff = dataStore.getFilterFactory(); + Query q = new Query(tname("ftjoin")); + q.getJoins().add(new Join(tname("ft1"), + ff.equal(ff.property(aname("name")), ff.property(aname("stringProperty")), true)).type(Type.OUTER)); + + SimpleFeatureCollection features = dataStore.getFeatureSource(tname("ftjoin")).getFeatures(q); + assertEquals(dataStore.getFeatureSource(tname("ftjoin")).getFeatures(q).size(), features.size()); + + SimpleFeatureIterator it = features.features(); + try { + + while(it.hasNext()) { + SimpleFeature f = it.next(); + assertEquals(4, f.getAttributeCount()); + + SimpleFeature g = (SimpleFeature) f.getAttribute(tname("ft1")); + if ("three".equals(f.getAttribute(aname("name")))) { + assertNull(g); + } + else { + assertNotNull(g); + } + } + } + finally { + it.close(); + } + } +} diff --git a/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCJoinTestSetup.java b/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCJoinTestSetup.java new file mode 100644 index 0000000..5032025 --- /dev/null +++ b/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCJoinTestSetup.java @@ -0,0 +1,57 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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 java.sql.SQLException; + +public abstract class JDBCJoinTestSetup extends JDBCDelegatingTestSetup { + + protected JDBCJoinTestSetup(JDBCTestSetup delegate) { + super(delegate); + } + + protected final void setUpData() throws Exception { + delegate.setUpData(); + + //kill all the data + try { + dropJoinTable(); + } catch (SQLException e) { + } + + //create all the data + createJoinTable(); + } + + /** + * Creates a table with the following schema: + *

+ * ftjoin( id:Integer; name:String; geom:POLYGON ) + *

+ *

+ * The table should be populated with the following data: + * 0 | 'zero' | POLYGON ((-0.1 -0.1, -0.1 0.1, 0.1 0.1, 0.1 -0.1, -0.1 -0.1)) + * 1 | 'one' | POLYGON ((-1.1 -1.1, -1.1 1.1, 1.1 1.1, 1.1 -1.1, -1.1 -1.1)) + * 2 | 'two' | POLYGON ((-10 -10, -10 10, 10 10, 10 -10, -10 -10)) + * 3 | 'three' | NULL + *

+ */ + protected abstract void createJoinTable() throws Exception; + + protected abstract void dropJoinTable() throws Exception; + +} diff --git a/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCTemporalFilterTest.java b/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCTemporalFilterTest.java new file mode 100644 index 0000000..4a177d6 --- /dev/null +++ b/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCTemporalFilterTest.java @@ -0,0 +1,212 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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 java.sql.Timestamp; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.geotools.data.Join; +import org.geotools.data.Query; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.geotools.data.simple.SimpleFeatureSource; +import org.geotools.temporal.object.DefaultInstant; +import org.geotools.temporal.object.DefaultPeriod; +import org.geotools.temporal.object.DefaultPosition; +import org.geotools.util.Converters; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.filter.Filter; +import org.opengis.filter.FilterFactory; +import org.opengis.filter.sort.SortBy; +import org.opengis.filter.sort.SortOrder; +import org.opengis.filter.temporal.After; +import org.opengis.filter.temporal.Before; +import org.opengis.filter.temporal.Begins; +import org.opengis.filter.temporal.BegunBy; +import org.opengis.filter.temporal.During; +import org.opengis.filter.temporal.Ends; +import org.opengis.filter.temporal.TContains; +import org.opengis.filter.temporal.TEquals; +import org.opengis.temporal.Instant; +import org.opengis.temporal.Period; + +public abstract class JDBCTemporalFilterTest extends JDBCTestSupport { + + /* dates(d:Date,dt:Datetime,t:Time) + *

+ *

+ * The table has the following data: + * + * 2009-06-28 | 2009-06-28 15:12:41 | 15:12:41 + * 2009-01-15 | 2009-01-15 13:10:12 | 13:10:12 + * 2009-09-29 | 2009-09-29 17:54:23 | 17:54:23 + */ + + @Override + protected abstract JDBCDateTestSetup createTestSetup(); + + void assertDatesMatch(Filter filter, String...dates) throws Exception { + FilterFactory ff = dataStore.getFilterFactory(); + + Query query = new Query(aname("dates"), filter); + query.setSortBy(new SortBy[]{ff.sort(aname("dt"), SortOrder.ASCENDING)}); + assertDatesMatch(query, dates); + } + + void assertDatesMatch(Query query, String...dates) throws Exception { + SimpleFeatureSource source = dataStore.getFeatureSource(tname("dates")); + + assertEquals(dates.length, source.getCount(query)); + + SimpleFeatureCollection features = source.getFeatures(query); + SimpleFeatureIterator it = features.features(); + int i = 0; + try { + while(it.hasNext()) { + SimpleFeature f = it.next(); + Date expected = FORMAT.parse(dates[i++]); + + assertEquals(Converters.convert(expected, Timestamp.class), f.getAttribute(aname("dt"))); + } + } + finally { + it.close(); + } + } + + public void testAfter() throws Exception { + FilterFactory ff = dataStore.getFilterFactory(); + + After after = ff.after(ff.property(aname("dt")), ff.literal("2009-02-01 00:00:00")); + assertDatesMatch(after, "2009-06-28 15:12:41", "2009-09-29 17:54:23"); + } + + public void testAfterInterval() throws Exception { + Period period = period("2009-02-01 00:00:00", "2009-07-01 00:00:00"); + + FilterFactory ff = dataStore.getFilterFactory(); + + After after = ff.after(ff.property(aname("dt")), ff.literal(period)); + assertDatesMatch(after, "2009-09-29 17:54:23"); + + after = ff.after(ff.literal(period), ff.property(aname("dt"))); + assertDatesMatch(after, "2009-01-15 13:10:12"); + } + + public void testBefore() throws Exception { + FilterFactory ff = dataStore.getFilterFactory(); + + Before before = ff.before(ff.property(aname("dt")), ff.literal("2009-02-01 00:00:00")); + assertDatesMatch(before, "2009-01-15 13:10:12"); + } + + public void testBeforeInterval() throws Exception { + Period period = period("2009-07-01 00:00:00", "2009-12-01 00:00:00"); + + FilterFactory ff = dataStore.getFilterFactory(); + + Before before = ff.before(ff.property(aname("dt")), ff.literal(period)); + assertDatesMatch(before, "2009-01-15 13:10:12", "2009-06-28 15:12:41"); + + period = period("2009-07-01 00:00:00", "2009-08-01 00:00:00"); + before = ff.before(ff.literal(period), ff.property(aname("dt"))); + assertDatesMatch(before, "2009-09-29 17:54:23"); + } + + public void testBegins() throws Exception { + Period period = period("2009-01-15 13:10:12", "2009-06-28 15:12:41"); + FilterFactory ff = dataStore.getFilterFactory(); + + Begins before = ff.begins(ff.property(aname("dt")), ff.literal(period)); + assertDatesMatch(before, "2009-01-15 13:10:12"); + } + + public void testBegunBy() throws Exception { + Period period = period("2009-01-15 13:10:12", "2009-06-28 15:12:41"); + FilterFactory ff = dataStore.getFilterFactory(); + + BegunBy before = ff.begunBy(ff.literal(period), ff.property(aname("dt"))); + assertDatesMatch(before, "2009-01-15 13:10:12"); + } + + public void testEnds() throws Exception { + Period period = period("2009-01-15 13:10:12", "2009-06-28 15:12:41"); + FilterFactory ff = dataStore.getFilterFactory(); + + Ends before = ff.ends(ff.property(aname("dt")), ff.literal(period)); + assertDatesMatch(before, "2009-06-28 15:12:41"); + } + + public void testEndedBy() throws Exception { + Period period = period("2009-01-15 13:10:12", "2009-06-28 15:12:41"); + FilterFactory ff = dataStore.getFilterFactory(); + + Ends before = ff.ends(ff.property(aname("dt")), ff.literal(period)); + assertDatesMatch(before, "2009-06-28 15:12:41"); + } + + public void testDuring() throws Exception { + Period period = period("2009-01-01 00:00:00", "2009-07-28 15:12:41"); + FilterFactory ff = dataStore.getFilterFactory(); + + During during = ff.during(ff.property(aname("dt")), ff.literal(period)); + assertDatesMatch(during, "2009-01-15 13:10:12", "2009-06-28 15:12:41"); + } + + public void testTContains() throws Exception { + Period period = period("2009-01-01 00:00:00", "2009-07-28 15:12:41"); + FilterFactory ff = dataStore.getFilterFactory(); + + TContains during = ff.tcontains(ff.literal(period), ff.property(aname("dt"))); + assertDatesMatch(during, "2009-01-15 13:10:12", "2009-06-28 15:12:41"); + } + + public void testTEquals() throws Exception { + FilterFactory ff = dataStore.getFilterFactory(); + + TEquals equals = ff.tequals(ff.literal("2009-01-15 13:10:12"), ff.property(aname("dt"))); + assertDatesMatch(equals, "2009-01-15 13:10:12"); + } + + public void testTemporalJoin() throws Exception { + FilterFactory ff = dataStore.getFilterFactory(); + + After after = ff.after(ff.property(aname("dt")), ff.property("other." + aname("dt"))); + Query q = new Query(tname("dates")); + q.getJoins().add(new Join(tname("dates"), after).alias("other")); + q.setSortBy(new SortBy[]{ff.sort(aname("dt"), SortOrder.ASCENDING)}); + + assertDatesMatch(q, "2009-06-28 15:12:41", "2009-09-29 17:54:23", "2009-09-29 17:54:23"); + } + + static DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + protected Date date(String date) throws ParseException { + return FORMAT.parse(date); + } + + protected Instant instant(String d) throws ParseException { + return new DefaultInstant(new DefaultPosition(date(d))); + } + + protected Period period(String d1, String d2) throws ParseException { + return new DefaultPeriod(instant(d1), instant(d2)); + } +} diff --git a/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCTestSupport.java b/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCTestSupport.java index 4d92ff2..2cbbf54 100644 --- a/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCTestSupport.java +++ b/modules/library/jdbc/src/test/java/org/geotools/jdbc/JDBCTestSupport.java @@ -218,7 +218,20 @@ public abstract class JDBCTestSupport extends OnlineTestCase { else { assertTrue(texpected.getBinding().isAssignableFrom(tactual.getBinding())); } - + } + + protected void assertAttributeValuesEqual(Object expected, Object actual) { + if (expected == null) { + assertNull(actual); + return; + } + + if (expected instanceof Geometry) { + assertTrue(((Geometry)expected).equals((Geometry)actual)); + return; + } + + assertEquals(expected, actual); } protected boolean areCRSEqual(CoordinateReferenceSystem crs1, CoordinateReferenceSystem crs2) { diff --git a/modules/library/jdbc/src/test/resources/META-INF/services/org.opengis.filter.expression.Function b/modules/library/jdbc/src/test/resources/META-INF/services/org.opengis.filter.expression.Function new file mode 100644 index 0000000..2d827d0 --- /dev/null +++ b/modules/library/jdbc/src/test/resources/META-INF/services/org.opengis.filter.expression.Function @@ -0,0 +1 @@ +org.geotools.jdbc.EqualsFunction \ No newline at end of file diff --git a/modules/library/main/src/main/java/org/geotools/data/DefaultQuery.java b/modules/library/main/src/main/java/org/geotools/data/DefaultQuery.java index f88fd78..17d91fd 100644 --- a/modules/library/main/src/main/java/org/geotools/data/DefaultQuery.java +++ b/modules/library/main/src/main/java/org/geotools/data/DefaultQuery.java @@ -141,6 +141,8 @@ public class DefaultQuery extends Query { this.version = query.getVersion(); this.hints = query.getHints(); this.startIndex = query.getStartIndex(); + this.alias = query.getAlias(); + this.joins = query.getJoins(); } } diff --git a/modules/library/main/src/main/java/org/geotools/feature/simple/SimpleFeatureTypeBuilder.java b/modules/library/main/src/main/java/org/geotools/feature/simple/SimpleFeatureTypeBuilder.java index d75feb8..01bd295 100644 --- a/modules/library/main/src/main/java/org/geotools/feature/simple/SimpleFeatureTypeBuilder.java +++ b/modules/library/main/src/main/java/org/geotools/feature/simple/SimpleFeatureTypeBuilder.java @@ -42,6 +42,7 @@ import org.opengis.feature.type.GeometryType; import org.opengis.feature.type.Name; import org.opengis.feature.type.Schema; import org.opengis.filter.Filter; +import org.opengis.filter.expression.PropertyName; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.util.InternationalString; @@ -1014,22 +1015,25 @@ public class SimpleFeatureTypeBuilder { * @return SimpleFeatureType containing just the types indicated by name */ public static SimpleFeatureType retype( SimpleFeatureType original, String[] types ) { - SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); - - //initialize the builder - b.init( original ); - - //clear the attributes - b.attributes().clear(); - - //add attributes in order - for ( int i = 0; i < types.length; i++ ) { - b.add( original.getDescriptor( types[i] ) ); - } - - return b.buildFeatureType(); + return retype(original, Arrays.asList(types)); } + public static SimpleFeatureType retype( SimpleFeatureType original, List types ) { + SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); + + //initialize the builder + b.init( original ); + + //clear the attributes + b.attributes().clear(); + + //add attributes in order + for ( int i = 0; i < types.size(); i++ ) { + b.add( original.getDescriptor( types.get(i) ) ); + } + + return b.buildFeatureType(); + } /** * Create a SimpleFeatureType with the same content; just updating the geometry * attribute to match the provided coordinate reference system. @@ -1060,4 +1064,28 @@ public class SimpleFeatureTypeBuilder { } return b.buildFeatureType(); } + + /** + * Copys a feature type. + *

+ * This method does a deep copy in that all individual attributes are copied as well. + *

+ */ + public static SimpleFeatureType copy( SimpleFeatureType original ) { + SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); + + //initialize the builder + b.init( original ); + + //clear attributes + b.attributes().clear(); + + //add attributes in order + for( AttributeDescriptor descriptor : original.getAttributeDescriptors() ){ + AttributeTypeBuilder ab = new AttributeTypeBuilder( b.factory ); + ab.init( descriptor ); + b.add( ab.buildDescriptor( descriptor.getLocalName() )); + } + return b.buildFeatureType(); + } } diff --git a/modules/library/main/src/main/java/org/geotools/filter/visitor/DuplicatingFilterVisitor.java b/modules/library/main/src/main/java/org/geotools/filter/visitor/DuplicatingFilterVisitor.java index e43d360..cb948b0 100644 --- a/modules/library/main/src/main/java/org/geotools/filter/visitor/DuplicatingFilterVisitor.java +++ b/modules/library/main/src/main/java/org/geotools/filter/visitor/DuplicatingFilterVisitor.java @@ -407,7 +407,7 @@ public class DuplicatingFilterVisitor implements FilterVisitor, ExpressionVisito } public Object visit(TContains contains, Object extraData) { - return getFactory(extraData).contains(visit(contains.getExpression1(), extraData), + return getFactory(extraData).tcontains(visit(contains.getExpression1(), extraData), visit(contains.getExpression2(), extraData)); } diff --git a/modules/library/main/src/main/java/org/geotools/util/CommonsConverterFactory.java b/modules/library/main/src/main/java/org/geotools/util/CommonsConverterFactory.java index 1848a34..e53304f 100644 --- a/modules/library/main/src/main/java/org/geotools/util/CommonsConverterFactory.java +++ b/modules/library/main/src/main/java/org/geotools/util/CommonsConverterFactory.java @@ -221,23 +221,22 @@ public class CommonsConverterFactory implements ConverterFactory { static class DateConverter implements Converter { private static SimpleDateFormat format1 = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss.S a" ); private static SimpleDateFormat format2 = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ssa" ); + private static SimpleDateFormat format3 = new SimpleDateFormat( "yyyy-MM-dd" ); + private static SimpleDateFormat[] formats = new SimpleDateFormat[]{format1,format2,format3}; public T convert(Object source, Class target) throws Exception { if( source == null ) return null; String string = (String) source; - try { - Date parsed = format1.parse(string); - return target.cast( parsed ); - } - catch( Exception ignore){ - } - try { - Date parsed = format2.parse(string); - return target.cast( parsed ); - } - catch( Exception ignore){ + for (SimpleDateFormat format : formats) { + try { + Date parsed = format.parse(string); + return target.cast( parsed ); + } + catch( Exception ignore){ + } } + return null; } } diff --git a/modules/plugin/jdbc/jdbc-db2/src/main/java/org/geotools/data/db2/DB2SQLDialect.java b/modules/plugin/jdbc/jdbc-db2/src/main/java/org/geotools/data/db2/DB2SQLDialect.java index f9cbd4c..7a71089 100644 --- a/modules/plugin/jdbc/jdbc-db2/src/main/java/org/geotools/data/db2/DB2SQLDialect.java +++ b/modules/plugin/jdbc/jdbc-db2/src/main/java/org/geotools/data/db2/DB2SQLDialect.java @@ -231,15 +231,15 @@ public class DB2SQLDialect extends SQLDialect { return srid; } - - public void encodeGeometryColumn(GeometryDescriptor gatt, int srid, StringBuffer sql) { - encodeGeometryColumn(gatt, sql); + @Override + public void encodeGeometryColumn(GeometryDescriptor gatt, String prefix, int srid, + StringBuffer sql) { + encodeGeometryColumn(gatt, prefix, sql); } - - - public void encodeGeometryColumn(GeometryDescriptor gatt, StringBuffer sql) { + + public void encodeGeometryColumn(GeometryDescriptor gatt, String prefix, StringBuffer sql) { sql.append("db2gse.ST_AsBinary("); - encodeColumnName(gatt.getLocalName(), sql); + encodeColumnName(prefix, gatt.getLocalName(), sql); sql.append(")"); } @@ -247,7 +247,7 @@ public class DB2SQLDialect extends SQLDialect { public void encodeGeometryEnvelope(String tableName,String geometryColumn, StringBuffer sql) { sql.append("db2gse.ST_AsBinary(db2gse.ST_GetAggrResult(MAX(db2gse.ST_BuildMBRAggr("); - encodeColumnName(geometryColumn, sql); + encodeColumnName(null, geometryColumn, sql); sql.append("))))"); // sql.append("db2gse.ST_AsBinary("); @@ -536,11 +536,11 @@ public class DB2SQLDialect extends SQLDialect { } } - public void encodeGeometryColumnGeneralized(GeometryDescriptor gatt, int srid, StringBuffer sql,Double distance) { + public void encodeGeometryColumnGeneralized(GeometryDescriptor gatt, String prefix, int srid, StringBuffer sql,Double distance) { sql.append("db2gse.ST_AsBinary(db2gse.st_Generalize("); - encodeColumnName(gatt.getLocalName(), sql); + encodeColumnName(null, gatt.getLocalName(), sql); sql.append(",").append(distance); sql.append("))"); } diff --git a/modules/plugin/jdbc/jdbc-db2/src/main/java/org/geotools/data/db2/DB2SQLDialectBasic.java b/modules/plugin/jdbc/jdbc-db2/src/main/java/org/geotools/data/db2/DB2SQLDialectBasic.java index 498ef7c..d849771 100644 --- a/modules/plugin/jdbc/jdbc-db2/src/main/java/org/geotools/data/db2/DB2SQLDialectBasic.java +++ b/modules/plugin/jdbc/jdbc-db2/src/main/java/org/geotools/data/db2/DB2SQLDialectBasic.java @@ -88,15 +88,15 @@ public class DB2SQLDialectBasic extends BasicSQLDialect { } - public void encodeGeometryColumn(GeometryDescriptor gatt,StringBuffer sql) { - delegate.encodeGeometryColumn(gatt, sql); - } - - public void encodeGeometryColumn(GeometryDescriptor gatt, int srid, StringBuffer sql) { - delegate.encodeGeometryColumn(gatt, srid, sql); + delegate.encodeGeometryColumn(gatt, null, sql); } + @Override + public void encodeGeometryColumn(GeometryDescriptor gatt, String prefix, int srid, + StringBuffer sql) { + delegate.encodeGeometryColumn(gatt, prefix, srid, sql); + } @Override public void encodeGeometryEnvelope(String tableName,String geometryColumn, StringBuffer sql) { @@ -184,9 +184,12 @@ public class DB2SQLDialectBasic extends BasicSQLDialect { delegate.applyLimitOffset(sql, limit, offset); } - public void encodeGeometryColumnGeneralized(GeometryDescriptor gatt, int srid, StringBuffer sql,Double distance) { - delegate.encodeGeometryColumnGeneralized(gatt, srid, sql, distance); + @Override + public void encodeGeometryColumnGeneralized(GeometryDescriptor gatt, String prefix, int srid, + StringBuffer sql, Double distance) { + delegate.encodeGeometryColumnGeneralized(gatt, prefix, srid, sql, distance); } + @Override protected void addSupportedHints(Set hints) { delegate.addSupportedHints(hints); diff --git a/modules/plugin/jdbc/jdbc-db2/src/main/java/org/geotools/data/db2/DB2SQLDialectPrepared.java b/modules/plugin/jdbc/jdbc-db2/src/main/java/org/geotools/data/db2/DB2SQLDialectPrepared.java index 4e21b0c..b53a0e3 100644 --- a/modules/plugin/jdbc/jdbc-db2/src/main/java/org/geotools/data/db2/DB2SQLDialectPrepared.java +++ b/modules/plugin/jdbc/jdbc-db2/src/main/java/org/geotools/data/db2/DB2SQLDialectPrepared.java @@ -87,14 +87,15 @@ public class DB2SQLDialectPrepared extends PreparedStatementSQLDialect { } public void encodeGeometryColumn(GeometryDescriptor gatt, StringBuffer sql) { - delegate.encodeGeometryColumn(gatt, sql); + delegate.encodeGeometryColumn(gatt, null, sql); } - public void encodeGeometryColumn(GeometryDescriptor gatt, int srid, StringBuffer sql) { - delegate.encodeGeometryColumn(gatt, srid, sql); + @Override + public void encodeGeometryColumn(GeometryDescriptor gatt, String prefix, int srid, + StringBuffer sql) { + delegate.encodeGeometryColumn(gatt, prefix, srid, sql); } - @Override public void encodeGeometryEnvelope(String tableName,String geometryColumn, StringBuffer sql) { delegate.encodeGeometryEnvelope(tableName, geometryColumn, sql); @@ -208,9 +209,13 @@ public class DB2SQLDialectPrepared extends PreparedStatementSQLDialect { delegate.applyLimitOffset(sql, limit, offset); } - public void encodeGeometryColumnGeneralized(GeometryDescriptor gatt, int srid,StringBuffer sql,Double distance) { - delegate.encodeGeometryColumnGeneralized(gatt,srid,sql,distance); + @Override + public void encodeGeometryColumnGeneralized(GeometryDescriptor gatt, String prefix, int srid, + StringBuffer sql, Double distance) { + + delegate.encodeGeometryColumnGeneralized(gatt, prefix, srid, sql, distance); } + @Override protected void addSupportedHints(Set hints) { delegate.addSupportedHints(hints); diff --git a/modules/plugin/jdbc/jdbc-h2/src/main/java/org/geotools/data/h2/H2Dialect.java b/modules/plugin/jdbc/jdbc-h2/src/main/java/org/geotools/data/h2/H2Dialect.java index 4de92f8..c5d043d 100644 --- a/modules/plugin/jdbc/jdbc-h2/src/main/java/org/geotools/data/h2/H2Dialect.java +++ b/modules/plugin/jdbc/jdbc-h2/src/main/java/org/geotools/data/h2/H2Dialect.java @@ -294,7 +294,7 @@ public class H2Dialect extends SQLDialect { //try grabbing directly from a geometry StringBuffer sql = new StringBuffer(); sql.append("SELECT ST_SRID("); - encodeColumnName(columnName, sql); + encodeColumnName(null, columnName, sql); sql.append(") "); sql.append("FROM "); @@ -305,7 +305,7 @@ public class H2Dialect extends SQLDialect { encodeSchemaName(tableName, sql); sql.append(" WHERE "); - encodeColumnName(columnName, sql); + encodeColumnName(null, columnName, sql); sql.append(" is not null LIMIT 1"); dataStore.getLogger().fine(sql.toString()); @@ -332,7 +332,7 @@ public class H2Dialect extends SQLDialect { public void encodeGeometryEnvelope(String tableName, String geometryColumn, StringBuffer sql) { //TODO: change spatialdbbox to use envelope sql.append("ST_Envelope("); - encodeColumnName(geometryColumn, sql); + encodeColumnName(null, geometryColumn, sql); sql.append(")"); } @@ -373,7 +373,7 @@ public class H2Dialect extends SQLDialect { } public void encodePrimaryKey(String column, StringBuffer sql) { - encodeColumnName(column, sql); + encodeColumnName(null, column, sql); sql.append(" int AUTO_INCREMENT(1) PRIMARY KEY"); } diff --git a/modules/plugin/jdbc/jdbc-h2/src/main/java/org/geotools/data/h2/H2FilterToSQL.java b/modules/plugin/jdbc/jdbc-h2/src/main/java/org/geotools/data/h2/H2FilterToSQL.java index a14f1e9..178176f 100644 --- a/modules/plugin/jdbc/jdbc-h2/src/main/java/org/geotools/data/h2/H2FilterToSQL.java +++ b/modules/plugin/jdbc/jdbc-h2/src/main/java/org/geotools/data/h2/H2FilterToSQL.java @@ -17,9 +17,12 @@ package org.geotools.data.h2; import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; import org.geotools.data.jdbc.FilterToSQL; import org.geotools.filter.FilterCapabilities; +import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Literal; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.spatial.BBOX; @@ -75,16 +78,29 @@ public class H2FilterToSQL extends FilterToSQL { out.write( "ST_GeomFromText('"+g.toText()+"', "+currentSRID+")"); } + @Override protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, PropertyName property, Literal geometry, boolean swapped, Object extraData) { - + return visitBinarySpatialOperator(filter, (Expression) property, (Expression) geometry, + swapped, extraData); + } + + @Override + protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, + Expression e2, Object extraData) { + return visitBinarySpatialOperator(filter, e1, e2, false, extraData); + } + + protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, + Expression e2, boolean swapped, Object extraData) { + try { if (filter instanceof DistanceBufferOperator) { out.write("ST_Distance("); - property.accept(this, extraData); + e1.accept(this, extraData); out.write(", "); - geometry.accept(this, extraData); + e2.accept(this, extraData); out.write(")"); if (filter instanceof DWithin) { @@ -101,9 +117,9 @@ public class H2FilterToSQL extends FilterToSQL { else if (filter instanceof BBOX) { //TODO: make a loose bounding box parameter out.write("ST_Intersects("); - property.accept(this, extraData); + e1.accept(this, extraData); out.write(","); - geometry.accept(this, extraData); + e2.accept(this, extraData); out.write(")"); } else { @@ -137,20 +153,21 @@ public class H2FilterToSQL extends FilterToSQL { } if (swapped) { - geometry.accept(this, extraData); + e2.accept(this, extraData); out.write(", "); - property.accept(this, extraData); + e1.accept(this, extraData); } else { - property.accept(this, extraData); + e1.accept(this, extraData); out.write(", "); - geometry.accept(this, extraData); + e2.accept(this, extraData); } out.write(")"); } - if (!(filter instanceof Disjoint)) { + Expression geometry = e1 instanceof Literal ? e1 : e2 instanceof Literal ? e2 : null; + if (geometry != null && !(filter instanceof Disjoint)) { String spatialIndex = (String) currentGeometry.getUserData().get(H2Dialect.H2_SPATIAL_INDEX); if (spatialIndex != null) { @@ -185,4 +202,24 @@ public class H2FilterToSQL extends FilterToSQL { return extraData; } + + static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); + static SimpleDateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ"); + + @Override + protected void writeLiteral(Object literal) throws IOException { + if (literal instanceof Date) { + out.write("PARSEDATETIME("); + if (literal instanceof java.sql.Date) { + out.write("'" + DATE_FORMAT.format(literal) + "', 'yyyy-MM-dd'"); + } + else { + out.write("'" + DATETIME_FORMAT.format(literal) + "', 'yyyy-MM-dd HH:mm:ss.SSSZ'"); + } + out.write(")"); + } + else { + super.writeLiteral(literal); + } + } } diff --git a/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2DateTestSetup.java b/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2DateTestSetup.java index 234d5b3..451034f 100644 --- a/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2DateTestSetup.java +++ b/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2DateTestSetup.java @@ -15,6 +15,9 @@ public class H2DateTestSetup extends JDBCDateTestSetup { @Override protected void createDateTable() throws Exception { + String sql = "CREATE SCHEMA \"geotools\";"; + runSafe(sql); + run( "CREATE TABLE \"geotools\".\"dates\" (\"d\" DATE, \"dt\" TIMESTAMP, \"t\" TIME)"); run( "INSERT INTO \"geotools\".\"dates\" VALUES (" + diff --git a/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2ForeignKeyTest.java b/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2ForeignKeyTest.java deleted file mode 100644 index 2a17686..0000000 --- a/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2ForeignKeyTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * GeoTools - The Open Source Java GIS Toolkit - * http://geotools.org - * - * (C) 2002-2008, 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.data.h2; - -import org.geotools.jdbc.JDBCForeignKeyTest; -import org.geotools.jdbc.JDBCTestSetup; - - -/** - * - * - * @source $URL$ - */ -public class H2ForeignKeyTest extends JDBCForeignKeyTest { - protected JDBCTestSetup createTestSetup() { - return new H2TestSetup() { - protected void setUpData() throws Exception { - super.setUpData(); - - try { - run("DROP TABLE \"geotools\".\"feature_relationships\"; COMMIT;"); - } catch (Exception e) { - } - - ; - - String sql = "CREATE TABLE \"geotools\".\"feature_relationships\" (" - + "\"table\" varchar, \"col\" varchar, \"rtable\" varchar, \"rcol\" varchar" - + ")"; - run(sql); - - try { - run("DROP TABLE \"geotools\".\"feature_associations\"; COMMIT;"); - } catch (Exception e) { - } - - sql = "CREATE TABLE \"geotools\".\"feature_associations\" (" - + "\"fid\" varchar, \"rtable\" varchar, \"rcol\" varchar, \"rfid\" varchar" - + ");"; - run(sql); - - try { - run("DROP TABLE \"geotools\".\"fk\"; COMMIT;"); - } catch (Exception e) { - } - - sql = "CREATE TABLE \"geotools\".\"fk\" (" - + "\"id\" int AUTO_INCREMENT(1) PRIMARY KEY, " - + "\"geometry\" BLOB, \"intProperty\" int, " - + "\"ft1\" int REFERENCES \"ft1\"" + ")"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"fk\" VALUES (" - + "0,ST_GeomFromText('POINT(0 0)',4326), 0, 0);"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"feature_relationships\" VALUES (" - + "'fk', 'ft1', 'ft1', 'id'" + ");"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"feature_associations\" VALUES (" - + "'fk.0', 'ft1', 'id', '0' " + ");"; - run(sql); - } - - public void tearDown() throws Exception { - super.tearDown(); - - try { - run("DROP TABLE \"geotools\".\"fk\"; COMMIT;"); - } catch (Exception e) { - } - } - }; - } -} diff --git a/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2GeometryAssociationTest.java b/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2GeometryAssociationTest.java deleted file mode 100644 index 7dbab4c..0000000 --- a/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2GeometryAssociationTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * GeoTools - The Open Source Java GIS Toolkit - * http://geotools.org - * - * (C) 2002-2008, 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.data.h2; - -import org.geotools.jdbc.JDBCGeometryAssociationTestSupport; -import org.geotools.jdbc.JDBCTestSetup; - - -/** - * - * - * @source $URL$ - */ -public class H2GeometryAssociationTest extends JDBCGeometryAssociationTestSupport { - protected JDBCTestSetup createTestSetup() { - return new H2TestSetup() { - protected void setUpData() throws Exception { - super.setUpData(); - - try { - run("DROP TABLE \"geotools\".\"geometry\"; COMMIT;"); - } catch (Exception e) { - } - - String sql = "CREATE TABLE \"geotools\".\"geometry\" (" - + "\"id\" VARCHAR, \"name\" VARCHAR, \"description\" VARCHAR, " - + "\"type\" VARCHAR, \"geometry\" POINT" + ")"; - run(sql); - - sql = "CALL AddGeometryColumn('geotools', 'geometry', 'geometry', 4326, 'POINT', 2)"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"geometry\" VALUES (" - + "'0','name-0','description-0', 'POINT', ST_GeomFromText('POINT(0 0)',4326) " - + ");"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"geometry\" VALUES (" - + "'1','name-1','description-1', 'POINT', ST_GeomFromText('POINT(1 1)',4326) " - + ");"; - run(sql); - - try { - run("DROP TABLE \"geotools\".\"multi_geometry\"; COMMIT;"); - } catch (Exception e) { - } - - sql = "CREATE TABLE \"geotools\".\"multi_geometry\" (" + - "\"id\" VARCHAR, \"mgid\" VARCHAR, \"ref\" boolean" + - ")"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"geometry\" VALUES (" - + "'2','name-2','description-2','MULTIPOINT', NULL" + ");"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"multi_geometry\" VALUES ('2', '0', false);"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"multi_geometry\" VALUES ('2', '1', false);"; - run(sql); - - try { - run("DROP TABLE \"geotools\".\"geometry_associations\"; COMMIT;"); - } catch (Exception e) { - } - - sql = "CREATE TABLE \"geotools\".\"geometry_associations\" (" - + "\"fid\" VARCHAR, \"gid\" VARCHAR, \"gname\" VARCHAR, " - + "\"ref\" BOOLEAN" + ")"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"geometry_associations\" VALUES (" - + "'ga.0', '0', 'geometry', false" + ");"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"geometry_associations\" VALUES (" - + "'ga.1', '1', 'geometry', true" + ");"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"geometry_associations\" VALUES (" - + "'ga.2', '2', 'geometry', false" + ");"; - run(sql); - - try { - run("DROP TABLE \"geotools\".\"ga\"; COMMIT;"); - } catch (Exception e) { - } - - sql = "CREATE TABLE \"geotools\".\"ga\" (" - + "\"id\" int AUTO_INCREMENT(1) PRIMARY KEY, " + "\"geometry\" GEOMETRY " + ")"; - run(sql); - - sql = "CALL AddGeometryColumn('geotools', 'ga', 'geometry', 4326, 'GEOMETRY', 2)"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"ga\" VALUES (0, NULL);"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"ga\" VALUES (1, NULL);"; - run(sql); - - sql = "INSERT INTO \"geotools\".\"ga\" VALUES (2, NULL);"; - run(sql); - } - - public void tearDown() throws Exception { - super.tearDown(); - - try { - run("DROP TABLE \"geotools\".\"fk\"; COMMIT;"); - } catch (Exception e) { - } - } - }; - } -} diff --git a/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2JoinTest.java b/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2JoinTest.java new file mode 100644 index 0000000..8eb286b --- /dev/null +++ b/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2JoinTest.java @@ -0,0 +1,29 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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.data.h2; + +import org.geotools.jdbc.JDBCJoinTest; +import org.geotools.jdbc.JDBCJoinTestSetup; + +public class H2JoinTest extends JDBCJoinTest { + + @Override + protected JDBCJoinTestSetup createTestSetup() { + return new H2JoinTestSetup(); + } + +} diff --git a/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2JoinTestSetup.java b/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2JoinTestSetup.java new file mode 100644 index 0000000..3e0794a --- /dev/null +++ b/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2JoinTestSetup.java @@ -0,0 +1,43 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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.data.h2; + +import org.geotools.jdbc.JDBCJoinTestSetup; + +public class H2JoinTestSetup extends JDBCJoinTestSetup { + + protected H2JoinTestSetup() { + super(new H2TestSetup()); + } + + @Override + protected void createJoinTable() throws Exception { + run( "CREATE TABLE \"geotools\".\"ftjoin\" ( \"id\" int, " + "\"name\" VARCHAR, \"geom\" GEOMETRY)" ); + run("CALL AddGeometryColumn('geotools', 'ftjoin', 'geom', 4326, 'GEOMETRY', 2)"); + + run( "INSERT INTO \"geotools\".\"ftjoin\" VALUES (0, 'zero', ST_GeomFromText('POLYGON ((-0.1 -0.1, -0.1 0.1, 0.1 0.1, 0.1 -0.1, -0.1 -0.1))', 4326))"); + run( "INSERT INTO \"geotools\".\"ftjoin\" VALUES (1, 'one', ST_GeomFromText('POLYGON ((-1.1 -1.1, -1.1 1.1, 1.1 1.1, 1.1 -1.1, -1.1 -1.1))', 4326))"); + run( "INSERT INTO \"geotools\".\"ftjoin\" VALUES (2, 'two', ST_GeomFromText('POLYGON ((-10 -10, -10 10, 10 10, 10 -10, -10 -10))', 4326))"); + run( "INSERT INTO \"geotools\".\"ftjoin\" VALUES (3, 'three', NULL)"); + } + + @Override + protected void dropJoinTable() throws Exception { + run( "DROP TABLE \"geotools\".\"ftjoin\"" ); + } + +} diff --git a/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2TemporalFilterTest.java b/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2TemporalFilterTest.java new file mode 100644 index 0000000..5ad90db --- /dev/null +++ b/modules/plugin/jdbc/jdbc-h2/src/test/java/org/geotools/data/h2/H2TemporalFilterTest.java @@ -0,0 +1,13 @@ +package org.geotools.data.h2; + +import org.geotools.jdbc.JDBCDateTestSetup; +import org.geotools.jdbc.JDBCTemporalFilterTest; + +public class H2TemporalFilterTest extends JDBCTemporalFilterTest { + + @Override + protected JDBCDateTestSetup createTestSetup() { + return new H2DateTestSetup(); + } + +} diff --git a/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLDialect.java b/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLDialect.java index 9034333..6629104 100644 --- a/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLDialect.java +++ b/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLDialect.java @@ -141,12 +141,12 @@ public class MySQLDialect extends SQLDialect { //first check the geometry_columns table StringBuffer sql = new StringBuffer(); sql.append("SELECT "); - encodeColumnName("srid", sql); + encodeColumnName(null, "srid", sql); sql.append(" FROM "); encodeTableName("geometry_columns", sql); sql.append(" WHERE "); - encodeColumnName("f_table_schema", sql); + encodeColumnName(null, "f_table_schema", sql); if (schemaName != null) { sql.append( " = '").append(schemaName).append("'"); @@ -156,10 +156,10 @@ public class MySQLDialect extends SQLDialect { } sql.append(" AND "); - encodeColumnName("f_table_name", sql); + encodeColumnName(null, "f_table_name", sql); sql.append(" = '").append(tableName).append("' AND "); - encodeColumnName("f_geometry_column", sql); + encodeColumnName(null, "f_geometry_column", sql); sql.append(" = '").append(columnName).append("'"); dataStore.getLogger().fine(sql.toString()); @@ -186,7 +186,7 @@ public class MySQLDialect extends SQLDialect { //execute SELECT srid() FROM LIMIT 1; sql = new StringBuffer(); sql.append("SELECT srid("); - encodeColumnName(columnName, sql); + encodeColumnName(null, columnName, sql); sql.append(") "); sql.append("FROM "); @@ -197,7 +197,7 @@ public class MySQLDialect extends SQLDialect { encodeSchemaName(tableName, sql); sql.append(" WHERE "); - encodeColumnName(columnName, sql); + encodeColumnName(null, columnName, sql); sql.append(" is not null LIMIT 1"); dataStore.getLogger().fine(sql.toString()); @@ -221,16 +221,18 @@ public class MySQLDialect extends SQLDialect { } } - public void encodeGeometryColumn(GeometryDescriptor gatt, int srid, StringBuffer sql) { + @Override + public void encodeGeometryColumn(GeometryDescriptor gatt, String prefix, int srid, + StringBuffer sql) { sql.append("asWKB("); - encodeColumnName(gatt.getLocalName(), sql); + encodeColumnName(prefix, gatt.getLocalName(), sql); sql.append(")"); } - + public void encodeGeometryEnvelope(String tableName, String geometryColumn, StringBuffer sql) { sql.append("asWKB("); sql.append("envelope("); - encodeColumnName(geometryColumn, sql); + encodeColumnName(null, geometryColumn, sql); sql.append("))"); } @@ -335,12 +337,12 @@ public class MySQLDialect extends SQLDialect { StringBuffer sql = new StringBuffer("CREATE TABLE "); encodeTableName("geometry_columns", sql); sql.append("("); - encodeColumnName("f_table_schema", sql); sql.append(" varchar(255), "); - encodeColumnName("f_table_name", sql); sql.append(" varchar(255), "); - encodeColumnName("f_geometry_column", sql); sql.append(" varchar(255), "); - encodeColumnName("coord_dimension", sql); sql.append(" int, "); - encodeColumnName("srid", sql); sql.append(" int, "); - encodeColumnName("type", sql); sql.append(" varchar(32)"); + encodeColumnName(null, "f_table_schema", sql); sql.append(" varchar(255), "); + encodeColumnName(null, "f_table_name", sql); sql.append(" varchar(255), "); + encodeColumnName(null, "f_geometry_column", sql); sql.append(" varchar(255), "); + encodeColumnName(null, "coord_dimension", sql); sql.append(" int, "); + encodeColumnName(null, "srid", sql); sql.append(" int, "); + encodeColumnName(null, "type", sql); sql.append(" varchar(32)"); sql.append(")"); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine(sql.toString()); } @@ -367,7 +369,7 @@ public class MySQLDialect extends SQLDialect { StringBuffer sql = new StringBuffer("ALTER TABLE "); encodeTableName(featureType.getTypeName(), sql); sql.append(" ADD SPATIAL INDEX ("); - encodeColumnName(gd.getLocalName(), sql); + encodeColumnName(null, gd.getLocalName(), sql); sql.append(")"); LOGGER.fine( sql.toString() ); @@ -418,7 +420,7 @@ public class MySQLDialect extends SQLDialect { } public void encodePrimaryKey(String column, StringBuffer sql) { - encodeColumnName(column, sql); + encodeColumnName(null, column, sql); sql.append(" int AUTO_INCREMENT PRIMARY KEY"); } diff --git a/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLDialectBasic.java b/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLDialectBasic.java index 7f833d3..cad3d72 100644 --- a/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLDialectBasic.java +++ b/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLDialectBasic.java @@ -83,13 +83,14 @@ public class MySQLDialectBasic extends BasicSQLDialect { } @Override - public void encodeColumnName(String raw, StringBuffer sql) { - delegate.encodeColumnName(raw, sql); + public void encodeColumnName(String prefix, String raw, StringBuffer sql) { + delegate.encodeColumnName(prefix, raw, sql); } @Override - public void encodeGeometryColumn(GeometryDescriptor gatt, int srid, StringBuffer sql) { - delegate.encodeGeometryColumn(gatt, srid, sql); + public void encodeGeometryColumn(GeometryDescriptor gatt, String prefix, int srid, + StringBuffer sql) { + delegate.encodeGeometryColumn(gatt, prefix, srid, sql); } @Override diff --git a/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLDialectPrepared.java b/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLDialectPrepared.java index c50161d..95c306d 100644 --- a/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLDialectPrepared.java +++ b/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLDialectPrepared.java @@ -80,9 +80,9 @@ public class MySQLDialectPrepared extends PreparedStatementSQLDialect { return delegate.getGeometrySRID(schemaName, tableName, columnName, cx); } - @Override - public void encodeColumnName(String raw, StringBuffer sql) { - delegate.encodeColumnName(raw, sql); + @Override + public void encodeColumnName(String prefix, String raw, StringBuffer sql) { + delegate.encodeColumnName(prefix, raw, sql); } @Override @@ -91,9 +91,9 @@ public class MySQLDialectPrepared extends PreparedStatementSQLDialect { } @Override - public void encodeGeometryColumn(GeometryDescriptor gatt, int srid, + public void encodeGeometryColumn(GeometryDescriptor gatt, String prefix, int srid, StringBuffer sql) { - delegate.encodeGeometryColumn(gatt, srid, sql); + delegate.encodeGeometryColumn(gatt, prefix, srid, sql); } @Override diff --git a/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLFilterToSQL.java b/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLFilterToSQL.java index abb8d5d..37c822a 100644 --- a/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLFilterToSQL.java +++ b/modules/plugin/jdbc/jdbc-mysql/src/main/java/org/geotools/data/mysql/MySQLFilterToSQL.java @@ -20,6 +20,7 @@ import java.io.IOException; import org.geotools.data.jdbc.FilterToSQL; import org.geotools.filter.FilterCapabilities; +import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Literal; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.spatial.BBOX; @@ -72,18 +73,30 @@ public class MySQLFilterToSQL extends FilterToSQL { } out.write( "GeomFromText('"+g.toText()+"', "+currentSRID+")"); } - + @Override protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, PropertyName property, Literal geometry, boolean swapped, Object extraData) { - + return visitBinarySpatialOperator(filter, (Expression)property, (Expression)geometry, + swapped, extraData); + } + + @Override + protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, + Expression e2, Object extraData) { + return visitBinarySpatialOperator(filter, e1, e2, false, extraData); + } + + protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, + Expression e2, boolean swapped, Object extraData) { + try { if (!(filter instanceof Disjoint)) { out.write("MbrIntersects("); - property.accept(this, extraData); + e1.accept(this, extraData); out.write(","); - geometry.accept(this, extraData); + e2.accept(this, extraData); out.write(")"); if (!(filter instanceof BBOX)) { @@ -98,9 +111,9 @@ public class MySQLFilterToSQL extends FilterToSQL { if (filter instanceof DistanceBufferOperator) { out.write("Distance("); - property.accept(this, extraData); + e1.accept(this, extraData); out.write(", "); - geometry.accept(this, extraData); + e2.accept(this, extraData); out.write(")"); if (filter instanceof DWithin) { @@ -148,14 +161,14 @@ public class MySQLFilterToSQL extends FilterToSQL { } if (swapped) { - geometry.accept(this, extraData); + e2.accept(this, extraData); out.write(", "); - property.accept(this, extraData); + e1.accept(this, extraData); } else { - property.accept(this, extraData); + e1.accept(this, extraData); out.write(", "); - geometry.accept(this, extraData); + e2.accept(this, extraData); } out.write(")"); diff --git a/modules/plugin/jdbc/jdbc-mysql/src/test/java/org/geotools/data/mysql/MySQLJoinTest.java b/modules/plugin/jdbc/jdbc-mysql/src/test/java/org/geotools/data/mysql/MySQLJoinTest.java new file mode 100644 index 0000000..0ad0939 --- /dev/null +++ b/modules/plugin/jdbc/jdbc-mysql/src/test/java/org/geotools/data/mysql/MySQLJoinTest.java @@ -0,0 +1,29 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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.data.mysql; + +import org.geotools.jdbc.JDBCJoinTest; +import org.geotools.jdbc.JDBCJoinTestSetup; + +public class MySQLJoinTest extends JDBCJoinTest { + + @Override + protected JDBCJoinTestSetup createTestSetup() { + return new MySQLJoinTestSetup(); + } + +} diff --git a/modules/plugin/jdbc/jdbc-mysql/src/test/java/org/geotools/data/mysql/MySQLJoinTestSetup.java b/modules/plugin/jdbc/jdbc-mysql/src/test/java/org/geotools/data/mysql/MySQLJoinTestSetup.java new file mode 100644 index 0000000..b0d356e --- /dev/null +++ b/modules/plugin/jdbc/jdbc-mysql/src/test/java/org/geotools/data/mysql/MySQLJoinTestSetup.java @@ -0,0 +1,66 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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.data.mysql; + +import org.geotools.jdbc.JDBCDataStore; +import org.geotools.jdbc.JDBCJoinTestSetup; + +public class MySQLJoinTestSetup extends JDBCJoinTestSetup { + + public MySQLJoinTestSetup() { + super(new MySQLTestSetup()); + } + + @Override + protected void createJoinTable() throws Exception { + //create some data + StringBuffer sb = new StringBuffer(); + sb.append("CREATE TABLE ftjoin ").append("(id int, ") + .append("name VARCHAR(255) COLLATE latin1_general_cs, geom POLYGON) ENGINE=InnoDB;"); + run(sb.toString()); + + sb = new StringBuffer(); + sb.append("INSERT INTO ftjoin VALUES (") + .append("0, 'zero', GeometryFromText('POLYGON ((-0.1 -0.1, -0.1 0.1, 0.1 0.1, 0.1 -0.1, -0.1 -0.1))',4326));"); + run(sb.toString()); + + sb = new StringBuffer(); + sb.append("INSERT INTO ftjoin VALUES (") + .append("1, 'one', GeometryFromText('POLYGON ((-1.1 -1.1, -1.1 1.1, 1.1 1.1, 1.1 -1.1, -1.1 -1.1))',4326));"); + run(sb.toString()); + + sb = new StringBuffer(); + sb.append("INSERT INTO ftjoin VALUES (") + .append("2, 'two', GeometryFromText('POLYGON ((-10 -10, -10 10, 10 10, 10 -10, -10 -10))',4326));"); + run(sb.toString()); + + sb = new StringBuffer(); + sb.append("INSERT INTO ftjoin VALUES (") + .append("3, 'three', NULL);"); + run(sb.toString()); + } + + @Override + protected void dropJoinTable() throws Exception { + runSafe("DROP TABLE ftjoin"); + } + + @Override + protected void setUpDataStore(JDBCDataStore dataStore) { + dataStore.setDatabaseSchema(null); + } +} diff --git a/modules/plugin/jdbc/jdbc-oracle/src/main/java/org/geotools/data/oracle/OracleDialect.java b/modules/plugin/jdbc/jdbc-oracle/src/main/java/org/geotools/data/oracle/OracleDialect.java index 9b4f847..f5c31ce 100644 --- a/modules/plugin/jdbc/jdbc-oracle/src/main/java/org/geotools/data/oracle/OracleDialect.java +++ b/modules/plugin/jdbc/jdbc-oracle/src/main/java/org/geotools/data/oracle/OracleDialect.java @@ -345,7 +345,15 @@ public class OracleDialect extends PreparedStatementSQLDialect { } @Override - public void encodeColumnName(String raw, StringBuffer sql) { + public void encodeColumnName(String prefix, String raw, StringBuffer sql) { + if (prefix != null) { + prefix = prefix.toUpperCase(); + if (prefix.length() > 30) { + prefix = prefix.substring(0,30); + } + sql.append(prefix).append("."); + } + raw = raw.toUpperCase(); if(raw.length() > 30) raw = raw.substring(0, 30); @@ -596,7 +604,7 @@ public class OracleDialect extends PreparedStatementSQLDialect { @Override public void encodeGeometryEnvelope(String tableName, String geometryColumn, StringBuffer sql) { sql.append( "SDO_AGGR_MBR("); - encodeColumnName(geometryColumn, sql); + encodeColumnName(null, geometryColumn, sql); sql.append( ")"); } diff --git a/modules/plugin/jdbc/jdbc-oracle/src/main/java/org/geotools/data/oracle/OracleFilterToSQL.java b/modules/plugin/jdbc/jdbc-oracle/src/main/java/org/geotools/data/oracle/OracleFilterToSQL.java index 4df3482..e434138 100644 --- a/modules/plugin/jdbc/jdbc-oracle/src/main/java/org/geotools/data/oracle/OracleFilterToSQL.java +++ b/modules/plugin/jdbc/jdbc-oracle/src/main/java/org/geotools/data/oracle/OracleFilterToSQL.java @@ -30,6 +30,7 @@ import org.geotools.jdbc.PreparedFilterToSQL; import org.geotools.jdbc.PreparedStatementSQLDialect; import org.geotools.jdbc.SQLDialect; import org.opengis.filter.Filter; +import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Literal; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.spatial.BBOX; @@ -45,6 +46,15 @@ import org.opengis.filter.spatial.Intersects; import org.opengis.filter.spatial.Overlaps; import org.opengis.filter.spatial.Touches; import org.opengis.filter.spatial.Within; +import org.opengis.filter.temporal.After; +import org.opengis.filter.temporal.Before; +import org.opengis.filter.temporal.Begins; +import org.opengis.filter.temporal.BegunBy; +import org.opengis.filter.temporal.During; +import org.opengis.filter.temporal.EndedBy; +import org.opengis.filter.temporal.Ends; +import org.opengis.filter.temporal.TEquals; +import org.opengis.filter.temporal.TOverlaps; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; @@ -147,14 +157,55 @@ public class OracleFilterToSQL extends PreparedFilterToSQL { caps.addType(DWithin.class); caps.addType(Beyond.class); + //temporal filters + caps.addType(After.class); + caps.addType(Before.class); + caps.addType(Begins.class); + caps.addType(BegunBy.class); + caps.addType(During.class); + caps.addType(TOverlaps.class); + caps.addType(Ends.class); + caps.addType(EndedBy.class); + caps.addType(TEquals.class); + return caps; } @Override protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, PropertyName property, Literal geometry, boolean swapped, Object extraData) { + return visitBinarySpatialOperator(filter, (Expression)property, (Expression) geometry, + swapped, extraData); + } + + @Override + protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, + Expression e2, Object extraData) { + return visitBinarySpatialOperator(filter, e1, e2, false, extraData); + } + + protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, + Expression e2, boolean swapped, Object extraData) { + try { - Geometry eval = geometry.evaluate(filter, Geometry.class); + e1 = clipToWorld(filter, e1); + e2 = clipToWorld(filter, e2); + + if(filter instanceof Beyond || filter instanceof DWithin) + doSDODistance(filter, e1, e2, extraData); + else if(filter instanceof BBOX && looseBBOXEnabled) { + doSDOFilter(filter, e1, e2, extraData); + } else + doSDORelate(filter, e1, e2, swapped, extraData); + } catch (IOException ioe) { + throw new RuntimeException(IO_ERROR, ioe); + } + return extraData; + } + + Expression clipToWorld(BinarySpatialOperator filter, Expression e) { + if (e instanceof Literal) { + Geometry eval = e.evaluate(filter, Geometry.class); // Oracle cannot deal with filters using geometries that span beyond the whole world // in case the if (dialect != null && isCurrentGeometryGeodetic() && @@ -165,22 +216,13 @@ public class OracleFilterToSQL extends PreparedFilterToSQL { if(result instanceof GeometryCollection) { result = distillSameTypeGeometries((GeometryCollection) result, eval); } - geometry = new FilterFactoryImpl().createLiteralExpression(result); + e = new FilterFactoryImpl().createLiteralExpression(result); } } - - if(filter instanceof Beyond || filter instanceof DWithin) - doSDODistance(filter, property, geometry, extraData); - else if(filter instanceof BBOX && looseBBOXEnabled) { - doSDOFilter(filter, property, geometry, extraData); - } else - doSDORelate(filter, property, geometry, swapped, extraData); - } catch (IOException ioe) { - throw new RuntimeException(IO_ERROR, ioe); } - return extraData; + return e; } - + /** * Returns true if the current geometry has the geodetic marker raised * @return @@ -222,11 +264,11 @@ public class OracleFilterToSQL extends PreparedFilterToSQL { } } - protected void doSDOFilter(Filter filter, PropertyName property, Literal geometry, Object extraData) throws IOException { + protected void doSDOFilter(Filter filter, Expression e1, Expression e2, Object extraData) throws IOException { out.write("SDO_FILTER("); - property.accept(this, extraData); + e1.accept(this, extraData); out.write(", "); - geometry.accept(this, extraData); + e2.accept(this, extraData); // for backwards compatibility with Oracle 9 we add the mask and querytypes params out.write(", 'mask=anyinteract querytype=WINDOW') = 'TRUE' "); } @@ -238,7 +280,7 @@ public class OracleFilterToSQL extends PreparedFilterToSQL { * @param geometry * @param extraData */ - protected void doSDORelate(Filter filter, PropertyName property, Literal geometry, boolean swapped, Object extraData) throws IOException { + protected void doSDORelate(Filter filter, Expression e1, Expression e2, boolean swapped, Object extraData) throws IOException { // grab the operating mask String mask = null; for (Class filterClass : SDO_RELATE_MASK_MAP.keySet()) { @@ -252,9 +294,9 @@ public class OracleFilterToSQL extends PreparedFilterToSQL { // ok, ready to write out the SDO_RELATE out.write("SDO_RELATE("); - property.accept(this, extraData); + e1.accept(this, extraData); out.write(", "); - geometry.accept(this, extraData); + e2.accept(this, extraData); // for disjoint we ask for no interaction, anyinteract == false if(filter instanceof Disjoint) { out.write(", 'mask=ANYINTERACT querytype=WINDOW') <> 'TRUE' "); @@ -264,15 +306,15 @@ public class OracleFilterToSQL extends PreparedFilterToSQL { } protected void doSDODistance(BinarySpatialOperator filter, - PropertyName property, Literal geometry, Object extraData) throws IOException { + Expression e1, Expression e2, Object extraData) throws IOException { double distance = ((DistanceBufferOperator) filter).getDistance(); String unit = ((DistanceBufferOperator) filter).getDistanceUnits(); String within = filter instanceof DWithin ? "TRUE" : "FALSE"; out.write("SDO_WITHIN_DISTANCE("); - property.accept(this, extraData); + e1.accept(this, extraData); out.write(","); - geometry.accept(this, extraData); + e2.accept(this, extraData); // encode the unit verbatim when available if(unit != null && !"".equals(unit.trim())) diff --git a/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleJoinTest.java b/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleJoinTest.java new file mode 100644 index 0000000..a954ebf --- /dev/null +++ b/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleJoinTest.java @@ -0,0 +1,29 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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.data.oracle; + +import org.geotools.jdbc.JDBCJoinTest; +import org.geotools.jdbc.JDBCJoinTestSetup; + +public class OracleJoinTest extends JDBCJoinTest { + + @Override + protected JDBCJoinTestSetup createTestSetup() { + return new OracleJoinTestSetup(); + } + +} diff --git a/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleJoinTestSetup.java b/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleJoinTestSetup.java new file mode 100644 index 0000000..d82a65e --- /dev/null +++ b/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleJoinTestSetup.java @@ -0,0 +1,67 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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.data.oracle; + +import org.geotools.jdbc.JDBCJoinTestSetup; + +public class OracleJoinTestSetup extends JDBCJoinTestSetup { + + public OracleJoinTestSetup() { + super(new OracleTestSetup()); + } + + @Override + protected void createJoinTable() throws Exception { + String sql = "CREATE TABLE ftjoin (" + + "id INT, name VARCHAR(255), geom MDSYS.SDO_GEOMETRY)"; + run(sql); + + sql = "INSERT INTO USER_SDO_GEOM_METADATA (TABLE_NAME, COLUMN_NAME, DIMINFO, SRID ) " + + "VALUES ('ftjoin','geom',MDSYS.SDO_DIM_ARRAY(MDSYS.SDO_DIM_ELEMENT('X',-180,180,0.5), " + + "MDSYS.SDO_DIM_ELEMENT('Y',-90,90,0.5)), 4326)"; + run(sql); + + sql = "CREATE INDEX ftjoin_GEOMETRY_IDX ON FTJOIN(GEOM) INDEXTYPE IS MDSYS.SPATIAL_INDEX" // + + " PARAMETERS ('SDO_INDX_DIMS=2 LAYER_GTYPE=\"POLYGON\"')"; + run(sql); + + sql = "INSERT INTO ftjoin VALUES (0, 'zero', MDSYS.SDO_GEOMETRY(2003, 4326, NULL," + + " MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1), " + + " MDSYS.SDO_ORDINATE_ARRAY(-0.1,-0.1, -0.1,0.1, 0.1,0.1, 0.1,-0.1, -0.1,-0.1)))"; + run(sql); + + sql = "INSERT INTO ftjoin VALUES (1, 'one', MDSYS.SDO_GEOMETRY(2003, 4326, NULL," + + " MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1), " + + " MDSYS.SDO_ORDINATE_ARRAY(-1.1,-1.1, -1.1,1.1, 1.1,1.1, 1.1,-1.1, -1.1,-1.1)))"; + run(sql); + + sql = "INSERT INTO ftjoin VALUES (2, 'two', MDSYS.SDO_GEOMETRY(2003, 4326, NULL," + + " MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1), " + + " MDSYS.SDO_ORDINATE_ARRAY(-10,-10, -10,10, 10,10, 10,-10, -10,-10)))"; + run(sql); + + sql = "INSERT INTO ftjoin VALUES (3, 'three', NULL)"; + run(sql); + } + + @Override + protected void dropJoinTable() throws Exception { + runSafe("DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME = 'FTJOIN'"); + runSafe("DROP TABLE ftjoin purge"); + } + +} diff --git a/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleTemporalFilterTest.java b/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleTemporalFilterTest.java new file mode 100644 index 0000000..9ee447b --- /dev/null +++ b/modules/plugin/jdbc/jdbc-oracle/src/test/java/org/geotools/data/oracle/OracleTemporalFilterTest.java @@ -0,0 +1,13 @@ +package org.geotools.data.oracle; + +import org.geotools.jdbc.JDBCDateTestSetup; +import org.geotools.jdbc.JDBCTemporalFilterTest; + +public class OracleTemporalFilterTest extends JDBCTemporalFilterTest { + + @Override + protected JDBCDateTestSetup createTestSetup() { + return new OracleDateTestSetup(new OracleTestSetup()); + } + +} diff --git a/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/FilterToSqlHelper.java b/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/FilterToSqlHelper.java index 27750f9..c9a232f 100644 --- a/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/FilterToSqlHelper.java +++ b/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/FilterToSqlHelper.java @@ -69,6 +69,15 @@ import org.opengis.filter.spatial.Intersects; import org.opengis.filter.spatial.Overlaps; import org.opengis.filter.spatial.Touches; import org.opengis.filter.spatial.Within; +import org.opengis.filter.temporal.After; +import org.opengis.filter.temporal.Before; +import org.opengis.filter.temporal.Begins; +import org.opengis.filter.temporal.BegunBy; +import org.opengis.filter.temporal.During; +import org.opengis.filter.temporal.EndedBy; +import org.opengis.filter.temporal.Ends; +import org.opengis.filter.temporal.TEquals; +import org.opengis.filter.temporal.TOverlaps; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; @@ -108,6 +117,17 @@ class FilterToSqlHelper { caps.addType(DWithin.class); caps.addType(Beyond.class); + //temporal filters + caps.addType(After.class); + caps.addType(Before.class); + caps.addType(Begins.class); + caps.addType(BegunBy.class); + caps.addType(During.class); + caps.addType(TOverlaps.class); + caps.addType(Ends.class); + caps.addType(EndedBy.class); + caps.addType(TEquals.class); + if(encodeFunctions) { // add support for string functions caps.addType(FilterFunction_strConcat.class); @@ -153,6 +173,16 @@ class FilterToSqlHelper { return extraData; } + protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, + Expression e2, Object extraData) { + + try { + visitBinarySpatialOperator(filter, e1, e2, false, extraData); + } catch (IOException e) { + throw new RuntimeException(IO_ERROR, e); + } + return extraData; + } void visitDistanceSpatialOperator(DistanceBufferOperator filter, PropertyName property, Literal geometry, boolean swapped, Object extraData) throws IOException { @@ -212,6 +242,12 @@ class FilterToSqlHelper { out.write(" AND "); } + visitBinarySpatialOperator(filter, (Expression)property, (Expression)geometry, swapped, extraData); + } + + void visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, Expression e2, + boolean swapped, Object extraData) throws IOException { + String closingParenthesis = ")"; if (filter instanceof Equals) { out.write("ST_Equals"); @@ -241,13 +277,13 @@ class FilterToSqlHelper { } out.write("("); - property.accept(delegate, extraData); + e1.accept(delegate, extraData); out.write(", "); - geometry.accept(delegate, extraData); + e2.accept(delegate, extraData); out.write(closingParenthesis); } - + boolean isCurrentGeography() { AttributeDescriptor geom = null; if(delegate instanceof PostgisPSFilterToSql) { diff --git a/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostGISDialect.java b/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostGISDialect.java index 180c6b8..03bb2fb 100644 --- a/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostGISDialect.java +++ b/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostGISDialect.java @@ -187,7 +187,7 @@ public class PostGISDialect extends BasicSQLDialect { } @Override - public void encodeGeometryColumn(GeometryDescriptor gatt, int srid, + public void encodeGeometryColumn(GeometryDescriptor gatt, String prefix, int srid, StringBuffer sql) { boolean geography = "geography".equals(gatt.getUserData().get(JDBCDataStore.JDBC_NATIVE_TYPENAME)); @@ -198,7 +198,7 @@ public class PostGISDialect extends BasicSQLDialect { sql.append("ST_Force_2D("); } - encodeColumnName(gatt.getLocalName(), sql); + encodeColumnName(prefix, gatt.getLocalName(), sql); if (!geography) { sql.append(")"); } diff --git a/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostGISPSDialect.java b/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostGISPSDialect.java index 6eddd48..4902d36 100644 --- a/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostGISPSDialect.java +++ b/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostGISPSDialect.java @@ -78,13 +78,12 @@ public class PostGISPSDialect extends PreparedStatementSQLDialect { .decodeGeometryValue(descriptor, rs, column, factory, cx); } - - public void encodeGeometryColumn(GeometryDescriptor gatt, int srid, + @Override + public void encodeGeometryColumn(GeometryDescriptor gatt, String prefix, int srid, StringBuffer sql) { - delegate.encodeGeometryColumn(gatt, srid, sql); + delegate.encodeGeometryColumn(gatt, prefix, srid, sql); } - public void encodeGeometryEnvelope(String tableName, String geometryColumn, StringBuffer sql) { delegate.encodeGeometryEnvelope(tableName, geometryColumn, sql); diff --git a/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostgisFilterToSQL.java b/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostgisFilterToSQL.java index c1b9427..d90884e 100644 --- a/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostgisFilterToSQL.java +++ b/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostgisFilterToSQL.java @@ -23,6 +23,7 @@ import org.geotools.filter.FilterCapabilities; import org.geotools.jdbc.JDBCDataStore; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.filter.expression.Add; +import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Function; import org.opengis.filter.expression.Literal; import org.opengis.filter.expression.PropertyName; @@ -95,6 +96,12 @@ public class PostgisFilterToSQL extends FilterToSQL { swapped, extraData); } + protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, + Expression e2, Object extraData) { + helper.out = out; + return helper.visitBinarySpatialOperator(filter, e1, e2, extraData); + } + GeometryDescriptor getCurrentGeometry() { return currentGeometry; } diff --git a/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostgisPSFilterToSql.java b/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostgisPSFilterToSql.java index a8205e9..42c8077 100644 --- a/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostgisPSFilterToSql.java +++ b/modules/plugin/jdbc/jdbc-postgis/src/main/java/org/geotools/data/postgis/PostgisPSFilterToSql.java @@ -19,6 +19,7 @@ package org.geotools.data.postgis; import org.geotools.filter.FilterCapabilities; import org.geotools.jdbc.PreparedFilterToSQL; import org.opengis.feature.type.GeometryDescriptor; +import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Literal; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.spatial.BinarySpatialOperator; @@ -58,7 +59,13 @@ public class PostgisPSFilterToSql extends PreparedFilterToSQL { helper.out = out; return helper.visitBinarySpatialOperator(filter, property, geometry, swapped, extraData); } - + + protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, + Expression e2, Object extraData) { + helper.out = out; + return helper.visitBinarySpatialOperator(filter, e1, e2, extraData); + } + GeometryDescriptor getCurrentGeometry() { return currentGeometry; } diff --git a/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisJoinTest.java b/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisJoinTest.java new file mode 100644 index 0000000..61b413a --- /dev/null +++ b/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisJoinTest.java @@ -0,0 +1,29 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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.data.postgis; + +import org.geotools.jdbc.JDBCJoinTest; +import org.geotools.jdbc.JDBCJoinTestSetup; + +public class PostgisJoinTest extends JDBCJoinTest { + + @Override + protected JDBCJoinTestSetup createTestSetup() { + return new PostgisJoinTestSetup(); + } + +} diff --git a/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisJoinTestSetup.java b/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisJoinTestSetup.java new file mode 100644 index 0000000..d2d1fc1 --- /dev/null +++ b/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisJoinTestSetup.java @@ -0,0 +1,49 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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.data.postgis; + +import org.geotools.jdbc.JDBCJoinTestSetup; +import org.geotools.jdbc.JDBCTestSetup; + +public class PostgisJoinTestSetup extends JDBCJoinTestSetup { + + public PostgisJoinTestSetup() { + this(new PostGISTestSetup()); + } + + public PostgisJoinTestSetup(JDBCTestSetup setup) { + super(setup); + } + + @Override + protected void createJoinTable() throws Exception { + run("CREATE TABLE \"ftjoin\" ( \"id\" int, " + "\"name\" VARCHAR, \"geom\" GEOMETRY)" ); + run("INSERT INTO geometry_columns VALUES ('', 'public', 'ftjoin', 'geom', 2, 4326, 'GEOMETRY')"); + + run( "INSERT INTO \"ftjoin\" VALUES (0, 'zero', ST_GeomFromText('POLYGON ((-0.1 -0.1, -0.1 0.1, 0.1 0.1, 0.1 -0.1, -0.1 -0.1))', 4326))"); + run( "INSERT INTO \"ftjoin\" VALUES (1, 'one', ST_GeomFromText('POLYGON ((-1.1 -1.1, -1.1 1.1, 1.1 1.1, 1.1 -1.1, -1.1 -1.1))', 4326))"); + run( "INSERT INTO \"ftjoin\" VALUES (2, 'two', ST_GeomFromText('POLYGON ((-10 -10, -10 10, 10 10, 10 -10, -10 -10))', 4326))"); + run( "INSERT INTO \"ftjoin\" VALUES (3, 'three', NULL)"); + } + + @Override + protected void dropJoinTable() throws Exception { + run( "DROP TABLE \"ftjoin\"" ); + run( "DELETE FROM geometry_columns WHERE f_table_name = 'ftjoin'"); + } + +} diff --git a/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisTemporalFilterTest.java b/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisTemporalFilterTest.java new file mode 100644 index 0000000..2a9465a --- /dev/null +++ b/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/PostgisTemporalFilterTest.java @@ -0,0 +1,13 @@ +package org.geotools.data.postgis; + +import org.geotools.jdbc.JDBCDateTestSetup; +import org.geotools.jdbc.JDBCTemporalFilterTest; + +public class PostgisTemporalFilterTest extends JDBCTemporalFilterTest { + + @Override + protected JDBCDateTestSetup createTestSetup() { + return new PostgisDateTestSetup(new PostGISTestSetup()); + } + +} diff --git a/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/ps/PostgisJoinTest.java b/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/ps/PostgisJoinTest.java new file mode 100644 index 0000000..e178c11 --- /dev/null +++ b/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/ps/PostgisJoinTest.java @@ -0,0 +1,30 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 2002-2011, 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.data.postgis.ps; + +import org.geotools.data.postgis.PostgisJoinTestSetup; +import org.geotools.jdbc.JDBCJoinTest; +import org.geotools.jdbc.JDBCJoinTestSetup; + +public class PostgisJoinTest extends JDBCJoinTest { + + @Override + protected JDBCJoinTestSetup createTestSetup() { + return new PostgisJoinTestSetup(new PostGISPSTestSetup()); + } + +} diff --git a/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/ps/PostgisTemporalFilterTest.java b/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/ps/PostgisTemporalFilterTest.java new file mode 100644 index 0000000..73249a7 --- /dev/null +++ b/modules/plugin/jdbc/jdbc-postgis/src/test/java/org/geotools/data/postgis/ps/PostgisTemporalFilterTest.java @@ -0,0 +1,14 @@ +package org.geotools.data.postgis.ps; + +import org.geotools.data.postgis.PostgisDateTestSetup; +import org.geotools.jdbc.JDBCDateTestSetup; +import org.geotools.jdbc.JDBCTemporalFilterTest; + +public class PostgisTemporalFilterTest extends JDBCTemporalFilterTest { + + @Override + protected JDBCDateTestSetup createTestSetup() { + return new PostgisDateTestSetup(new PostGISPSTestSetup()); + } + +} diff --git a/modules/plugin/jdbc/jdbc-spatialite/src/main/java/org/geotools/data/spatialite/SpatiaLiteDialect.java b/modules/plugin/jdbc/jdbc-spatialite/src/main/java/org/geotools/data/spatialite/SpatiaLiteDialect.java index 3d1d976..4fefba9 100644 --- a/modules/plugin/jdbc/jdbc-spatialite/src/main/java/org/geotools/data/spatialite/SpatiaLiteDialect.java +++ b/modules/plugin/jdbc/jdbc-spatialite/src/main/java/org/geotools/data/spatialite/SpatiaLiteDialect.java @@ -199,9 +199,9 @@ public class SpatiaLiteDialect extends BasicSQLDialect { } @Override - public void encodeGeometryColumn(GeometryDescriptor gatt, int srid, StringBuffer sql) { + public void encodeGeometryColumn(GeometryDescriptor gatt, String prefix, int srid, StringBuffer sql) { sql.append( "AsText("); - encodeColumnName( gatt.getLocalName(), sql); + encodeColumnName( prefix, gatt.getLocalName(), sql); sql.append( ")||';").append(srid).append("'"); } @@ -239,7 +239,7 @@ public class SpatiaLiteDialect extends BasicSQLDialect { @Override public void encodeGeometryEnvelope(String tableName, String geometryColumn, StringBuffer sql) { sql.append("asText(envelope("); - encodeColumnName(geometryColumn, sql); + encodeColumnName(null, geometryColumn, sql); sql.append( "))"); } diff --git a/modules/plugin/jdbc/jdbc-sqlserver/src/main/java/org/geotools/data/sqlserver/SQLServerDialect.java b/modules/plugin/jdbc/jdbc-sqlserver/src/main/java/org/geotools/data/sqlserver/SQLServerDialect.java index ea2c86e..6de1520 100644 --- a/modules/plugin/jdbc/jdbc-sqlserver/src/main/java/org/geotools/data/sqlserver/SQLServerDialect.java +++ b/modules/plugin/jdbc/jdbc-sqlserver/src/main/java/org/geotools/data/sqlserver/SQLServerDialect.java @@ -132,7 +132,7 @@ public class SQLServerDialect extends BasicSQLDialect { sql.append( " ON "); encodeTableName(featureType.getTypeName(), sql); sql.append("("); - encodeColumnName(gd.getLocalName(), sql); + encodeColumnName(null, gd.getLocalName(), sql); sql.append(")"); sql.append( " WITH ( BOUNDING_BOX = ").append(bbox).append(")"); @@ -151,14 +151,14 @@ public class SQLServerDialect extends BasicSQLDialect { String columnName, Connection cx) throws SQLException { StringBuffer sql = new StringBuffer("SELECT "); - encodeColumnName(columnName, sql); + encodeColumnName(null, columnName, sql); sql.append( ".STSrid"); sql.append( " FROM "); encodeTableName(schemaName, tableName, sql, true); sql.append( " WHERE "); - encodeColumnName(columnName, sql ); + encodeColumnName(null, columnName, sql ); sql.append( " IS NOT NULL"); dataStore.getLogger().fine( sql.toString() ); @@ -183,14 +183,14 @@ public class SQLServerDialect extends BasicSQLDialect { } @Override - public void encodeGeometryColumn(GeometryDescriptor gatt, int srid, StringBuffer sql) { + public void encodeGeometryColumn(GeometryDescriptor gatt, String prefix, int srid, StringBuffer sql) { sql.append( "CAST("); - encodeColumnName( gatt.getLocalName(), sql ); + encodeColumnName( prefix, gatt.getLocalName(), sql ); sql.append( ".STSrid as VARCHAR)"); sql.append( " + ':' + " ); - encodeColumnName( gatt.getLocalName(), sql ); + encodeColumnName( prefix, gatt.getLocalName(), sql ); sql.append( ".STAsText()"); //encodeColumnName(gatt.getLocalName(), sql); @@ -253,12 +253,12 @@ public class SQLServerDialect extends BasicSQLDialect { public void encodeGeometryEnvelope(String tableName, String geometryColumn, StringBuffer sql) { sql.append( "CAST("); - encodeColumnName( geometryColumn, sql ); + encodeColumnName( null, geometryColumn, sql ); sql.append( ".STSrid as VARCHAR)"); sql.append( " + ':' + " ); - encodeColumnName( geometryColumn, sql ); + encodeColumnName( null, geometryColumn, sql ); sql.append( ".STEnvelope().ToString()"); }