/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2003-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.jdbc; import org.geotools.data.Query; import org.geotools.data.jdbc.fidmapper.FIDMapper; import org.geotools.factory.Hints; import org.geotools.filter.FilterCapabilities; import org.geotools.filter.SQLEncoder; import org.geotools.filter.SQLEncoderException; import org.geotools.filter.visitor.ClientTransactionAccessor; import org.geotools.filter.visitor.PostPreProcessFilterSplittingVisitor; import org.geotools.filter.visitor.SimplifyingFilterVisitor; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.filter.Filter; import org.opengis.filter.expression.Expression; import org.opengis.filter.sort.SortBy; import org.opengis.filter.sort.SortOrder; /** * Builds a complete SQL query to select the specified attributes for the * specified feature type, using a specified filter to generate a WHERE * clause. * * <p> * The actual WHERE clause is generated by the FilterToSQL class or appropriate * subclass for a particular database. If a specific encoder is to be used, * it must be specified to the constructor for this class. * </p> * * <p> * In order to implement the functionality of the application-specified Filter, * this is split into a 'preQueryFilter' which can be incorporated into the * SQL query itself and a 'postQueryFilter. The encoder capabilities are used * to determine how much of the function can be performed by the database * directly and how much has to be performed on the result set. * </p> * * @author Sean Geoghegan, Defence Science and Technology Organisation. * * @source $URL$ * * @deprecated scheduled for removal in 2.7, use classes in org.geotools.jdbc */ public class GeoAPISQLBuilder implements SQLBuilder { // The instance of the encoder to be used to generate the WHERE clause protected FilterToSQL encoder; protected SimpleFeatureType ft; protected ClientTransactionAccessor accessor; private Filter lastFilter = null; private Filter lastPreFilter = null; private Filter lastPostFilter = null; private Hints hints; /** * Constructs an instance of this class with a default FilterToSQL */ public GeoAPISQLBuilder() { this(new FilterToSQL(), null, null); } /** * Constructs an instance of this class using the encoder class specified. * This will typically be from the getSqlBuilder method of a JDBCDataStore * subclass. * * @param encoder the specific encoder to be used. * @param featureType * @param accessor client-side transaction handler; may be null. */ public GeoAPISQLBuilder(FilterToSQL encoder, SimpleFeatureType featureType, ClientTransactionAccessor accessor) { this.encoder = encoder; this.ft = featureType; this.accessor = accessor; //set the feature type on teh encoders encoder.setFeatureType( featureType ); } public void setHints(Hints hints) { this.hints = hints; } /** Check the hints to see if we are forced into 2D */ public boolean isForce2D(){ if( hints == null ){ return false; } Boolean force2d = (Boolean) hints.get( Hints.FEATURE_2D ); if(force2d == null ){ return false; } return force2d.booleanValue(); } /** * Return the postQueryFilter that must be applied to the database query * result set. * * @param filter the application filter which must be applied * * @return the filter representing the functionality that must be performed * on the result set. */ public Filter getPostQueryFilter(Filter filter) { if (filter != null && ( lastFilter == null || !filter.equals(lastFilter) ) ) { splitFilter(filter); } return lastPostFilter; } /** * Return the preQueryFilter that can be used to generate the WHERE clause. * * @param filter the application filter which must be applied * * @return the filter representing the functionality that can be performed * by the database. */ public Filter getPreQueryFilter(Filter filter) { if (filter != null && ( lastFilter == null || !filter.equals(lastFilter) ) ) { splitFilter(filter); } return lastPreFilter; } protected void splitFilter(Filter filter) { lastFilter = filter; FilterCapabilities cap = encoder.getCapabilities(); PostPreProcessFilterSplittingVisitor pfv = new PostPreProcessFilterSplittingVisitor(cap, ft, accessor); filter.accept(pfv, null); lastPreFilter = (Filter) pfv.getFilterPre(); lastPostFilter = (Filter) pfv.getFilterPost(); SimplifyingFilterVisitor.FIDValidator validator = getFidValidator(); SimplifyingFilterVisitor visitor = new SimplifyingFilterVisitor(); visitor.setFIDValidator(validator); lastPreFilter = (Filter) lastPreFilter.accept(visitor, null); lastPostFilter = (Filter) lastPostFilter.accept(visitor, null); } /** * Returns a fid validator to filter out the invalid fids from a Filter * * @return a fid validator wrapping the {@link SQLEncoder encoder}'s * {@link FIDMapper#isValid(String)} * @see #splitFilter(Filter) */ protected SimplifyingFilterVisitor.FIDValidator getFidValidator() { final FIDMapper mapper = this.encoder.getFIDMapper(); SimplifyingFilterVisitor.FIDValidator validator; if (mapper == null) { validator = SimplifyingFilterVisitor.ANY_FID_VALID; } else { validator = new SimplifyingFilterVisitor.FIDValidator() { public boolean isValid(String fid) { return mapper.isValid(fid); } }; } return validator; } /** * Constructs the FROM clause for a featureType * * <p> * sql: <code>FROM typeName</code> * </p> * * @param sql the StringBuffer that the WHERE clause should be appended to * @param typeName the name of the table (feature type) to be queried */ public void sqlFrom(StringBuffer sql, String typeName) { sql.append(" FROM "); sql.append(encoder.escapeName(typeName)); } /** * Constructs WHERE clause, if needed, for FILTER. * * <p> * sql: <code>WHERE filter encoding</code> * </p> * * @param sql The StringBuffer that the WHERE clause should be appended to * @param preFilter The filter to be used by the encoder class to generate * the WHERE clause * * @throws SQLEncoderException Not thrown here but may be thrown by the * encoder * FIXME: Throw FilterToSQLException when the parent interface is fixed. */ public void sqlWhere(StringBuffer sql, Filter preFilter) throws SQLEncoderException { if ((preFilter != null) && (preFilter != Filter.INCLUDE)) { try { String where = encoder.encodeToString(preFilter); sql.append(" "); sql.append(where); } catch (FilterToSQLException fse) { throw new SQLEncoderException("", fse); } } } /** * Constructs the full SQL SELECT statement for the supplied Filter. * * <p> * The statement is constructed by concatenating the SELECT column list, * FROM table specification and WHERE clause appropriate to the supplied * Filter. * </p> * * <p> * Subclasses that support {@link Query#getStartIndex() startIndex} should override as * appropriate. * </p> * * @param typeName The name of the table (feature type) to be queried * @param mapper FIDMapper to identify the FID columns in the table * @param attrTypes The specific attribute columns to be selected * @param filter The Filter that will be used by the encoder to construct * the WHERE clause * * @return The fully formed SQL SELECT statement * * @throws SQLEncoderException Not thrown by this method but may be thrown * by the encoder class * FIXME: Throw FilterToSQLException when the parent interface is fixed. */ public String buildSQLQuery(String typeName, FIDMapper mapper, AttributeDescriptor[] attrTypes, Filter filter, SortBy[] sortBy, Integer offset, Integer limit) throws SQLEncoderException { if (offset != null && offset.intValue() != 0) { throw new UnsupportedOperationException("Requested an startIndex of " + offset + " where its not supported"); } StringBuffer sqlBuffer = new StringBuffer(); sqlBuffer.append("SELECT "); sqlColumns(sqlBuffer, mapper, attrTypes); sqlFrom(sqlBuffer, typeName); encoder.setFIDMapper(mapper); sqlWhere(sqlBuffer, filter); //order by clause if ( sortBy != null ) { //encode the sortBy clause sqlOrderBy( sqlBuffer, null, sortBy); } String sqlStmt = sqlBuffer.toString(); return sqlStmt; } /** * @deprecated use {@link #buildSQLQuery(String, FIDMapper, AttributeDescriptor[], Filter, SortBy[], Integer, Integer)} */ public String buildSQLQuery(String typeName, FIDMapper mapper, AttributeDescriptor[] attrTypes, Filter filter) throws SQLEncoderException { return buildSQLQuery(typeName, mapper, attrTypes, filter, null, null, null); } /** * Appends the names of the columns to be selected. * * <p> * sqlGeometryColumn is invoked for any special handling for geometry * columns. * </p> * * @param sql StringBuffer to be appended to * @param mapper FIDMapper to provide the name(s) of the FID columns * @param attributes Array of columns to be selected * */ public void sqlColumns(StringBuffer sql, FIDMapper mapper, AttributeDescriptor[] attributes) { for (int i = 0; i < mapper.getColumnCount(); i++) { sql.append(encoder.escapeName(mapper.getColumnName(i)) + ", "); } for (int i = 0; i < attributes.length; i++) { if (attributes[i] instanceof GeometryDescriptor) { sqlGeometryColumn(sql, attributes[i]); } else { sql.append(encoder.escapeName(attributes[i].getLocalName())); } if (i < (attributes.length - 1)) { sql.append(", "); } } } /** * Generates the select column specification for a geometry column. * * <p> * This should typically be overridden in the subclass to return a * meaningful value that the attribute i/o handler can process. * </p> * * @param sql A StringBuffer that the column specification can be appended * to * @param geomAttribute An AttributeDescriptor for a geometry attribute */ public void sqlGeometryColumn(StringBuffer sql, AttributeDescriptor geomAttribute) { sql.append(encoder.escapeName(geomAttribute.getLocalName())); } /** * @deprecated */ public void sqlOrderBy(StringBuffer sql, SortBy[] sortBy) throws SQLEncoderException { sqlOrderBy(sql, null, sortBy); } /** * Generates the order by clause. * <p> * This uses the standard ASC,DESC sql keywords to denote ascending,descending * sort respectivley. * </p> * * FIXME: Throw FilterToSQLException when the parent interface is fixed. */ public void sqlOrderBy(StringBuffer sql, FIDMapper mapper, SortBy[] sortBy) throws SQLEncoderException { if ( sortBy == null || sortBy.length == 0 ) return; //nothing to sort on sql.append( " ORDER BY "); for ( int i = 0; i < sortBy.length; i++ ) { AttributeDescriptor type = (AttributeDescriptor) sortBy[i].getPropertyName().evaluate( ft ); if ( type != null ) { sql.append( encoder.escapeName( type.getLocalName() ) ); } else { sql.append( encoder.escapeName( sortBy[i].getPropertyName().getPropertyName() ) ); } if ( SortOrder.DESCENDING.equals( sortBy[i].getSortOrder() ) ) { sql.append( " DESC"); } else { sql.append( " ASC"); } if ( i < sortBy.length-1 ) { sql.append( ", "); } } } public void encode(StringBuffer sql, Expression expression) throws SQLEncoderException { try { sql.append(encoder.encodeToString(expression)); } catch (FilterToSQLException fse) { throw new SQLEncoderException("", fse); } } public void encode(StringBuffer sql, Filter filter) throws SQLEncoderException { try { sql.append(encoder.encodeToString(filter).replaceAll("^\\s*WHERE\\s*", "")); } catch (FilterToSQLException fse) { throw new SQLEncoderException("", fse); } } }