/* * 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 SQLEncoder 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 Use GeoAPISQLBuilder instead * * @deprecated scheduled for removal in 2.7, use classes in org.geotools.jdbc */ public class DefaultSQLBuilder implements SQLBuilder { // The instance of the encoder to be used to generate the WHERE clause protected SQLEncoder 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 SQLEncoder */ public DefaultSQLBuilder() { this(new SQLEncoder()); } /** * Constructs an instance of this class using the encoder class specified. * This will typically be from the getSqlBuilder method of a JDBCDataStore * subclass. * <p> * This constructor should not be used to obtain Pre/Post filters, as these * methods require a SimpleFeatureType to function properly. * * @deprecated * @param encoder the specific encoder to be used. */ public DefaultSQLBuilder(SQLEncoder encoder) { this(encoder, 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 DefaultSQLBuilder(SQLEncoder 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 synchronized 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 != lastFilter) ) { splitFilter(filter); } return lastPostFilter; // SQLUnpacker unpacker = new SQLUnpacker(cap); // // //figure out which of the filter we can use. // unpacker.unPackAND(filter); // // return unpacker.getUnSupported(); } /** * 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; // SQLUnpacker unpacker = new SQLUnpacker(encoder.getCapabilities()); // // //figure out which of the filter we can use. // unpacker.unPackAND(filter); // // return unpacker.getSupported(); } 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 */ public void sqlWhere(StringBuffer sql, Filter preFilter) throws SQLEncoderException { if ((preFilter != null) && (preFilter != Filter.INCLUDE)) { String where = encoder.encode(preFilter); sql.append(" "); sql.append(where); } } /** * @deprecated */ public String buildSQLQuery(String typeName, FIDMapper mapper, AttributeDescriptor[] attrTypes, org.opengis.filter.Filter filter) throws SQLEncoderException { return buildSQLQuery(typeName, mapper, attrTypes, filter, (SortBy[])null, null, null); } /** * 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 * Filte. * </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 */ public String buildSQLQuery(String typeName, FIDMapper mapper, AttributeDescriptor[] attrTypes, Filter filter, SortBy[] sortBy, Integer offset, Integer limit) throws SQLEncoderException { 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, mapper, sortBy); } String sqlStmt = sqlBuffer.toString(); return sqlStmt; } /** * 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> */ 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++) { final SortBy sortAttribute = sortBy[i]; if (SortBy.NATURAL_ORDER.equals(sortAttribute) || SortBy.REVERSE_ORDER.equals(sortAttribute)) { addOrderByPK(sql, mapper, sortAttribute.getSortOrder()); } else { AttributeDescriptor type = (AttributeDescriptor) sortAttribute.getPropertyName() .evaluate(ft); if (type != null) { sql.append(encoder.escapeName(type.getLocalName())); } else { sql.append(encoder .escapeName(sortAttribute.getPropertyName().getPropertyName())); } if (SortOrder.DESCENDING.equals(sortAttribute.getSortOrder())) { sql.append(" DESC"); } else { sql.append(" ASC"); } } if (i < sortBy.length - 1) { sql.append(", "); } } } public void encode(StringBuffer sql, Expression expression) throws SQLEncoderException { sql.append(encoder.encode(expression)); } public void encode(StringBuffer sql, Filter filter) throws SQLEncoderException { // we reuse the encoder method already available and get rid of the first where // statement encountered sql.append(encoder.encode(filter).replaceAll("^\\s*WHERE\\s*", "")); } /** * @param sql the buffer where the select statement is being built, already contains the "ORDER * BY " clause, only needs to be appended with the PK fields. * @param mapper the fid mapper where to get the PK fields from, may be null, in which case * implementations should either throw an exception or not, depending on whether they * actually need it. * @param sortOrder the order in which to encode the PK fields (eg, * <code>"pkAtt1 DESC, pkAtt2 DESC"</code>) * @throws SQLEncoderException by default, subclasses shall override if pk ordering is supported */ protected void addOrderByPK(StringBuffer sql, FIDMapper mapper, SortOrder sortOrder) throws SQLEncoderException { throw new SQLEncoderException("NATURAL_ORDER or REVERSE_ORDER " + "ordering is not supported for this FeatureType"); } }