You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2015/05/11 06:42:36 UTC

[1/8] cayenne git commit: cleanup

Repository: cayenne
Updated Branches:
  refs/heads/master 09b93d0ce -> 200ac46e7


cleanup


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/51ce4ce8
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/51ce4ce8
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/51ce4ce8

Branch: refs/heads/master
Commit: 51ce4ce850f4cc4b6ac703366c191f9f8f59ad97
Parents: 09b93d0
Author: aadamchik <aa...@apache.org>
Authored: Sun May 10 14:43:30 2015 -0400
Committer: aadamchik <aa...@apache.org>
Committed: Sun May 10 14:43:30 2015 -0400

----------------------------------------------------------------------
 .../access/translator/select/DataObjectMatchTranslator.java        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/51ce4ce8/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java
index f52d990..b9bc561 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DataObjectMatchTranslator.java
@@ -92,7 +92,7 @@ public class DataObjectMatchTranslator {
 
     public void setDataObject(Persistent obj) {
         if (obj == null) {
-            values = Collections.EMPTY_MAP;
+            values = Collections.emptyMap();
             return;
         }
 


[4/8] cayenne git commit: CAY-2007 Refactoring SelectTranslator for better extensibility

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
index c79ff42..152dfb3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
@@ -19,13 +19,24 @@
 
 package org.apache.cayenne.dba;
 
+import java.net.URL;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory;
+import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
@@ -37,624 +48,615 @@ import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.Resource;
 import org.apache.cayenne.resource.ResourceLocator;
 import org.apache.cayenne.util.Util;
 
-import java.net.URL;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.sql.Types;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
 /**
  * A generic DbAdapter implementation. Can be used as a default adapter or as a
  * superclass of a concrete adapter implementation.
  */
 public class JdbcAdapter implements DbAdapter {
 
-    private PkGenerator pkGenerator;
-    protected QuotingStrategy quotingStrategy;
-
-    protected TypesHandler typesHandler;
-    protected ExtendedTypeMap extendedTypes;
-    protected boolean supportsBatchUpdates;
-    protected boolean supportsUniqueConstraints;
-    protected boolean supportsGeneratedKeys;
-    protected EJBQLTranslatorFactory ejbqlTranslatorFactory;
-
-    protected ResourceLocator resourceLocator;
-    protected boolean caseInsensitiveCollations;
-
-    /**
-     * @since 3.1
-     * @deprecated since 4.0 BatchQueryBuilderfactory is attached to the
-     *             DataNode.
-     */
-    @Inject
-    protected BatchTranslatorFactory batchQueryBuilderFactory;
-
-    @Inject
-    protected JdbcEventLogger logger;
-
-    /**
-     * Creates new JdbcAdapter with a set of default parameters.
-     */
-    public JdbcAdapter(@Inject RuntimeProperties runtimeProperties,
-            @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-            @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-            @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject ResourceLocator resourceLocator) {
-
-        // init defaults
-        this.setSupportsBatchUpdates(false);
-        this.setSupportsUniqueConstraints(true);
-        this.caseInsensitiveCollations = runtimeProperties.getBoolean(Constants.CI_PROPERTY, false);
-        this.resourceLocator = resourceLocator;
-
-        this.pkGenerator = createPkGenerator();
-        this.quotingStrategy = createQuotingStrategy();
-
-        this.ejbqlTranslatorFactory = createEJBQLTranslatorFactory();
-        this.typesHandler = TypesHandler.getHandler(findResource("/types.xml"));
-        this.extendedTypes = new ExtendedTypeMap();
-        initExtendedTypes(defaultExtendedTypes, userExtendedTypes, extendedTypeFactories);
-    }
-
-    /**
-     * Returns default separator - a semicolon.
-     * 
-     * @since 1.0.4
-     */
-    @Override
-    public String getBatchTerminator() {
-        return ";";
-    }
-
-    /**
-     * @since 3.1
-     */
-    public JdbcEventLogger getJdbcEventLogger() {
-        return this.logger;
-    }
-
-    /**
-     * Locates and returns a named adapter resource. A resource can be an XML
-     * file, etc.
-     * <p>
-     * This implementation is based on the premise that each adapter is located
-     * in its own Java package and all resources are in the same package as
-     * well. Resource lookup is recursive, so that if DbAdapter is a subclass of
-     * another adapter, parent adapter package is searched as a failover.
-     * </p>
-     * 
-     * @since 3.0
-     */
-    protected URL findResource(String name) {
-        Class<?> adapterClass = getClass();
-
-        while (adapterClass != null && JdbcAdapter.class.isAssignableFrom(adapterClass)) {
-
-            String path = Util.getPackagePath(adapterClass.getName()) + name;
-            Collection<Resource> resources = resourceLocator.findResources(path);
-
-            if (!resources.isEmpty()) {
-                return resources.iterator().next().getURL();
-            }
-
-            adapterClass = adapterClass.getSuperclass();
-        }
-
-        return null;
-    }
-
-    /**
-     * Called from {@link #initExtendedTypes(List, List, List)} to load
-     * adapter-specific types into the ExtendedTypeMap right after the default
-     * types are loaded, but before the DI overrides are. This method has
-     * specific implementations in JdbcAdapter subclasses.
-     */
-    protected void configureExtendedTypes(ExtendedTypeMap map) {
-        // noop... subclasses may override to install custom types
-    }
-
-    /**
-     * @since 3.1
-     */
-    protected void initExtendedTypes(List<ExtendedType> defaultExtendedTypes, List<ExtendedType> userExtendedTypes,
-            List<ExtendedTypeFactory> extendedTypeFactories) {
-        for (ExtendedType type : defaultExtendedTypes) {
-            extendedTypes.registerType(type);
-        }
-
-        // loading adapter specific extended types
-        configureExtendedTypes(extendedTypes);
-
-        for (ExtendedType type : userExtendedTypes) {
-            extendedTypes.registerType(type);
-        }
-        for (ExtendedTypeFactory typeFactory : extendedTypeFactories) {
-            extendedTypes.addFactory(typeFactory);
-        }
-    }
-
-    /**
-     * Creates and returns a primary key generator. This factory method should
-     * be overriden by JdbcAdapter subclasses to provide custom implementations
-     * of PKGenerator.
-     */
-    protected PkGenerator createPkGenerator() {
-        return new JdbcPkGenerator(this);
-    }
-
-    /**
-     * Creates and returns an {@link EJBQLTranslatorFactory} used to generate
-     * visitors for EJBQL to SQL translations. This method should be overriden
-     * by subclasses that need to customize EJBQL generation.
-     * 
-     * @since 3.0
-     */
-    protected EJBQLTranslatorFactory createEJBQLTranslatorFactory() {
-        JdbcEJBQLTranslatorFactory translatorFactory = new JdbcEJBQLTranslatorFactory();
-        translatorFactory.setCaseInsensitive(caseInsensitiveCollations);
-        return translatorFactory;
-    }
-
-    /**
-     * Returns primary key generator associated with this DbAdapter.
-     */
-    @Override
-    public PkGenerator getPkGenerator() {
-        return pkGenerator;
-    }
-
-    /**
-     * Sets new primary key generator.
-     * 
-     * @since 1.1
-     */
-    public void setPkGenerator(PkGenerator pkGenerator) {
-        this.pkGenerator = pkGenerator;
-    }
-
-    /**
-     * Returns true.
-     * 
-     * @since 1.1
-     */
-    @Override
-    public boolean supportsUniqueConstraints() {
-        return supportsUniqueConstraints;
-    }
-
-    /**
-     * Returns true.
-     *
-     * @since 4.0
-     */
-    @Override
-    public boolean supportsCatalogsOnReverseEngineering() {
-        return true;
-    }
-
-    /**
-     * @since 1.1
-     */
-    public void setSupportsUniqueConstraints(boolean flag) {
-        this.supportsUniqueConstraints = flag;
-    }
-
-    /**
-     * Returns true if supplied type can have a length attribute as a part of column
-     * definition
-     * 
-     * @since 4.0
-     */
+	private PkGenerator pkGenerator;
+	protected QuotingStrategy quotingStrategy;
+
+	protected TypesHandler typesHandler;
+	protected ExtendedTypeMap extendedTypes;
+	protected boolean supportsBatchUpdates;
+	protected boolean supportsUniqueConstraints;
+	protected boolean supportsGeneratedKeys;
+	protected EJBQLTranslatorFactory ejbqlTranslatorFactory;
+
+	protected ResourceLocator resourceLocator;
+	protected boolean caseInsensitiveCollations;
+
+	/**
+	 * @since 3.1
+	 * @deprecated since 4.0 BatchQueryBuilderfactory is attached to the
+	 *             DataNode.
+	 */
+	@Inject
+	protected BatchTranslatorFactory batchQueryBuilderFactory;
+
+	@Inject
+	protected JdbcEventLogger logger;
+
+	/**
+	 * Creates new JdbcAdapter with a set of default parameters.
+	 */
+	public JdbcAdapter(@Inject RuntimeProperties runtimeProperties,
+			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+			@Inject ResourceLocator resourceLocator) {
+
+		// init defaults
+		this.setSupportsBatchUpdates(false);
+		this.setSupportsUniqueConstraints(true);
+		this.caseInsensitiveCollations = runtimeProperties.getBoolean(Constants.CI_PROPERTY, false);
+		this.resourceLocator = resourceLocator;
+
+		this.pkGenerator = createPkGenerator();
+		this.quotingStrategy = createQuotingStrategy();
+
+		this.ejbqlTranslatorFactory = createEJBQLTranslatorFactory();
+		this.typesHandler = TypesHandler.getHandler(findResource("/types.xml"));
+		this.extendedTypes = new ExtendedTypeMap();
+		initExtendedTypes(defaultExtendedTypes, userExtendedTypes, extendedTypeFactories);
+	}
+
+	/**
+	 * Returns default separator - a semicolon.
+	 * 
+	 * @since 1.0.4
+	 */
+	@Override
+	public String getBatchTerminator() {
+		return ";";
+	}
+
+	/**
+	 * @since 3.1
+	 */
+	public JdbcEventLogger getJdbcEventLogger() {
+		return this.logger;
+	}
+
+	/**
+	 * Locates and returns a named adapter resource. A resource can be an XML
+	 * file, etc.
+	 * <p>
+	 * This implementation is based on the premise that each adapter is located
+	 * in its own Java package and all resources are in the same package as
+	 * well. Resource lookup is recursive, so that if DbAdapter is a subclass of
+	 * another adapter, parent adapter package is searched as a failover.
+	 * </p>
+	 * 
+	 * @since 3.0
+	 */
+	protected URL findResource(String name) {
+		Class<?> adapterClass = getClass();
+
+		while (adapterClass != null && JdbcAdapter.class.isAssignableFrom(adapterClass)) {
+
+			String path = Util.getPackagePath(adapterClass.getName()) + name;
+			Collection<Resource> resources = resourceLocator.findResources(path);
+
+			if (!resources.isEmpty()) {
+				return resources.iterator().next().getURL();
+			}
+
+			adapterClass = adapterClass.getSuperclass();
+		}
+
+		return null;
+	}
+
+	/**
+	 * Called from {@link #initExtendedTypes(List, List, List)} to load
+	 * adapter-specific types into the ExtendedTypeMap right after the default
+	 * types are loaded, but before the DI overrides are. This method has
+	 * specific implementations in JdbcAdapter subclasses.
+	 */
+	protected void configureExtendedTypes(ExtendedTypeMap map) {
+		// noop... subclasses may override to install custom types
+	}
+
+	/**
+	 * @since 3.1
+	 */
+	protected void initExtendedTypes(List<ExtendedType> defaultExtendedTypes, List<ExtendedType> userExtendedTypes,
+			List<ExtendedTypeFactory> extendedTypeFactories) {
+		for (ExtendedType type : defaultExtendedTypes) {
+			extendedTypes.registerType(type);
+		}
+
+		// loading adapter specific extended types
+		configureExtendedTypes(extendedTypes);
+
+		for (ExtendedType type : userExtendedTypes) {
+			extendedTypes.registerType(type);
+		}
+		for (ExtendedTypeFactory typeFactory : extendedTypeFactories) {
+			extendedTypes.addFactory(typeFactory);
+		}
+	}
+
+	/**
+	 * Creates and returns a primary key generator. This factory method should
+	 * be overriden by JdbcAdapter subclasses to provide custom implementations
+	 * of PKGenerator.
+	 */
+	protected PkGenerator createPkGenerator() {
+		return new JdbcPkGenerator(this);
+	}
+
+	/**
+	 * Creates and returns an {@link EJBQLTranslatorFactory} used to generate
+	 * visitors for EJBQL to SQL translations. This method should be overriden
+	 * by subclasses that need to customize EJBQL generation.
+	 * 
+	 * @since 3.0
+	 */
+	protected EJBQLTranslatorFactory createEJBQLTranslatorFactory() {
+		JdbcEJBQLTranslatorFactory translatorFactory = new JdbcEJBQLTranslatorFactory();
+		translatorFactory.setCaseInsensitive(caseInsensitiveCollations);
+		return translatorFactory;
+	}
+
+	/**
+	 * Returns primary key generator associated with this DbAdapter.
+	 */
+	@Override
+	public PkGenerator getPkGenerator() {
+		return pkGenerator;
+	}
+
+	/**
+	 * Sets new primary key generator.
+	 * 
+	 * @since 1.1
+	 */
+	public void setPkGenerator(PkGenerator pkGenerator) {
+		this.pkGenerator = pkGenerator;
+	}
+
+	/**
+	 * Returns true.
+	 * 
+	 * @since 1.1
+	 */
+	@Override
+	public boolean supportsUniqueConstraints() {
+		return supportsUniqueConstraints;
+	}
+
+	/**
+	 * Returns true.
+	 *
+	 * @since 4.0
+	 */
+	@Override
+	public boolean supportsCatalogsOnReverseEngineering() {
+		return true;
+	}
+
+	/**
+	 * @since 1.1
+	 */
+	public void setSupportsUniqueConstraints(boolean flag) {
+		this.supportsUniqueConstraints = flag;
+	}
+
+	/**
+	 * Returns true if supplied type can have a length attribute as a part of
+	 * column definition
+	 * 
+	 * @since 4.0
+	 */
 	public boolean typeSupportsLength(int type) {
-	    return JdbcAdapter.supportsLength(type);
-    }
-    
-    /**
-     * Returns true if supplied type can have a length attribute as a part of column
-     * definition
-     * 
-     * TODO: this is a static method only to support the deprecated method {@link TypesMapping#supportsLength(int)}
-     * When the deprecated method is removed this body should be moved in to {@link #typeSupportsLength(int)}
-     * 
-     * @deprecated
-     */
-    static boolean supportsLength(int type) {
-        return type == Types.BINARY
-                || type == Types.CHAR
-                || type == Types.NCHAR
-                || type == Types.NVARCHAR
-                || type == Types.LONGNVARCHAR
-                || type == Types.DECIMAL
-                || type == Types.DOUBLE
-                || type == Types.FLOAT
-                || type == Types.NUMERIC
-                || type == Types.REAL
-                || type == Types.VARBINARY
-                || type == Types.VARCHAR;
-    }
-    
-    /**
-     * @since 3.0
-     */
-    @Override
-    public Collection<String> dropTableStatements(DbEntity table) {
-
-        StringBuilder buf = new StringBuilder("DROP TABLE ");
-        buf.append(quotingStrategy.quotedFullyQualifiedName(table));
-
-        return Collections.singleton(buf.toString());
-    }
-
-    /**
-     * Returns a SQL string that can be used to create database table
-     * corresponding to <code>ent</code> parameter.
-     */
-    @Override
-    public String createTable(DbEntity entity) {
-
-        StringBuffer sqlBuffer = new StringBuffer();
-        sqlBuffer.append("CREATE TABLE ");
-        sqlBuffer.append(quotingStrategy.quotedFullyQualifiedName(entity));
-
-        sqlBuffer.append(" (");
-        // columns
-        Iterator<DbAttribute> it = entity.getAttributes().iterator();
-        if (it.hasNext()) {
-            boolean first = true;
-            while (it.hasNext()) {
-                if (first) {
-                    first = false;
-                } else {
-                    sqlBuffer.append(", ");
-                }
-
-                DbAttribute column = it.next();
-
-                // attribute may not be fully valid, do a simple check
-                if (column.getType() == TypesMapping.NOT_DEFINED) {
-                    throw new CayenneRuntimeException("Undefined type for attribute '" + entity.getFullyQualifiedName()
-                            + "." + column.getName() + "'.");
-                }
-
-                createTableAppendColumn(sqlBuffer, column);
-            }
-
-            createTableAppendPKClause(sqlBuffer, entity);
-        }
-
-        sqlBuffer.append(')');
-        return sqlBuffer.toString();
-    }
-
-    /**
-     * @since 1.2
-     */
-    protected void createTableAppendPKClause(StringBuffer sqlBuffer, DbEntity entity) {
-
-        Iterator<DbAttribute> pkit = entity.getPrimaryKeys().iterator();
-        if (pkit.hasNext()) {
-            sqlBuffer.append(", PRIMARY KEY (");
-            boolean firstPk = true;
-
-            while (pkit.hasNext()) {
-                if (firstPk) {
-                    firstPk = false;
-                } else {
-                    sqlBuffer.append(", ");
-                }
-
-                DbAttribute at = pkit.next();
-
-                sqlBuffer.append(quotingStrategy.quotedName(at));
-            }
-            sqlBuffer.append(')');
-        }
-    }
-
-    /**
-     * Appends SQL for column creation to CREATE TABLE buffer.
-     * 
-     * @since 1.2
-     */
-    @Override
-    public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
-        sqlBuffer.append(quotingStrategy.quotedName(column));
-        sqlBuffer.append(' ').append(getType(this, column));
-
-        sqlBuffer.append(sizeAndPrecision(this, column));
-        sqlBuffer.append(column.isMandatory() ? " NOT NULL" : " NULL");
-    }
-
-    public static String sizeAndPrecision(DbAdapter adapter, DbAttribute column) {
-        if (!adapter.typeSupportsLength(column.getType())) {
-            return "";
-        }
-
-        int len = column.getMaxLength();
-        int scale = TypesMapping.isDecimal(column.getType()) && column.getType() != Types.FLOAT ? column.getScale() : -1;
-
-        // sanity check
-        if (scale > len) {
-            scale = -1;
-        }
-
-        if (len > 0) {
-            return "(" + len + (scale >= 0 ? ", " + scale : "") + ")";
-        }
-
-        return "";
-    }
-
-    public static String getType(DbAdapter adapter, DbAttribute column) {
-        String[] types = adapter.externalTypesForJdbcType(column.getType());
-        if (types == null || types.length == 0) {
-            String entityName = column.getEntity() != null ? column.getEntity().getFullyQualifiedName() : "<null>";
-            throw new CayenneRuntimeException("Undefined type for attribute '"
-                    + entityName + "." + column.getName() + "': " + column.getType());
-        }
-        return types[0];
-    }
-
-    /**
-     * Returns a DDL string to create a unique constraint over a set of columns.
-     * 
-     * @since 1.1
-     */
-    @Override
-    public String createUniqueConstraint(DbEntity source, Collection<DbAttribute> columns) {
-
-        if (columns == null || columns.isEmpty()) {
-            throw new CayenneRuntimeException("Can't create UNIQUE constraint - no columns specified.");
-        }
-
-        StringBuilder buf = new StringBuilder();
-
-        buf.append("ALTER TABLE ");
-        buf.append(quotingStrategy.quotedFullyQualifiedName(source));
-        buf.append(" ADD UNIQUE (");
-
-        Iterator<DbAttribute> it = columns.iterator();
-        DbAttribute first = it.next();
-        buf.append(quotingStrategy.quotedName(first));
-
-        while (it.hasNext()) {
-            DbAttribute next = it.next();
-            buf.append(", ");
-            buf.append(quotingStrategy.quotedName(next));
-        }
-
-        buf.append(")");
-
-        return buf.toString();
-    }
-
-    /**
-     * Returns a SQL string that can be used to create a foreign key constraint
-     * for the relationship.
-     */
-    @Override
-    public String createFkConstraint(DbRelationship rel) {
-
-        DbEntity source = (DbEntity) rel.getSourceEntity();
-        StringBuilder buf = new StringBuilder();
-        StringBuilder refBuf = new StringBuilder();
-
-        buf.append("ALTER TABLE ");
-
-        buf.append(quotingStrategy.quotedFullyQualifiedName(source));
-        buf.append(" ADD FOREIGN KEY (");
-
-        boolean first = true;
-
-        for (DbJoin join : rel.getJoins()) {
-            if (first) {
-                first = false;
-            } else {
-                buf.append(", ");
-                refBuf.append(", ");
-            }
-
-            buf.append(quotingStrategy.quotedSourceName(join));
-            refBuf.append(quotingStrategy.quotedTargetName(join));
-        }
-
-        buf.append(") REFERENCES ");
-
-        buf.append(quotingStrategy.quotedFullyQualifiedName((DbEntity) rel.getTargetEntity()));
-
-        buf.append(" (").append(refBuf.toString()).append(')');
-        return buf.toString();
-    }
-
-    @Override
-    public String[] externalTypesForJdbcType(int type) {
-        return typesHandler.externalTypesForJdbcType(type);
-    }
-
-    @Override
-    public ExtendedTypeMap getExtendedTypes() {
-        return extendedTypes;
-    }
-
-    @Override
-    public DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls) {
-
-        DbAttribute attr = new DbAttribute();
-        attr.setName(name);
-        attr.setType(type);
-        attr.setMandatory(!allowNulls);
-
-        if (size >= 0) {
-            attr.setMaxLength(size);
-        }
-
-        if (scale >= 0) {
-            attr.setScale(scale);
-        }
-
-        return attr;
-    }
-
-    @Override
-    public String tableTypeForTable() {
-        return "TABLE";
-    }
-
-    @Override
-    public String tableTypeForView() {
-        return "VIEW";
-    }
-
-    /**
-     * Creates and returns a default implementation of a qualifier translator.
-     */
-    @Override
-    public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-        QualifierTranslator translator = new QualifierTranslator(queryAssembler);
-        translator.setCaseInsensitive(caseInsensitiveCollations);
-        return translator;
-    }
-
-    /**
-     * Uses JdbcActionBuilder to create the right action.
-     * 
-     * @since 1.2
-     */
-    @Override
-    public SQLAction getAction(Query query, DataNode node) {
-        return query.createSQLAction(new JdbcActionBuilder(node));
-    }
-
-    @Override
-    public void bindParameter(PreparedStatement statement, Object object, int pos, int sqlType, int scale)
-            throws SQLException, Exception {
-
-        if (object == null) {
-            statement.setNull(pos, sqlType);
-        } else {
-            ExtendedType typeProcessor = getExtendedTypes().getRegisteredType(object.getClass());
-            typeProcessor.setJdbcObject(statement, object, pos, sqlType, scale);
-        }
-    }
-
-    @Override
-    public boolean supportsBatchUpdates() {
-        return this.supportsBatchUpdates;
-    }
-
-    public void setSupportsBatchUpdates(boolean flag) {
-        this.supportsBatchUpdates = flag;
-    }
-
-    /**
-     * @since 1.2
-     */
-    @Override
-    public boolean supportsGeneratedKeys() {
-        return supportsGeneratedKeys;
-    }
-
-    /**
-     * @since 1.2
-     */
-    public void setSupportsGeneratedKeys(boolean flag) {
-        this.supportsGeneratedKeys = flag;
-    }
-
-    /**
-     * Returns a translator factory for EJBQL to SQL translation. The factory is
-     * normally initialized in constructor by calling
-     * {@link #createEJBQLTranslatorFactory()}, and can be changed later by
-     * calling {@link #setEjbqlTranslatorFactory(EJBQLTranslatorFactory)}.
-     * 
-     * @since 3.0
-     */
-    public EJBQLTranslatorFactory getEjbqlTranslatorFactory() {
-        return ejbqlTranslatorFactory;
-    }
-
-    /**
-     * Sets a translator factory for EJBQL to SQL translation. This property is
-     * normally initialized in constructor by calling
-     * {@link #createEJBQLTranslatorFactory()}, so users would only override it
-     * if they need to customize EJBQL translation.
-     * 
-     * @since 3.0
-     */
-    public void setEjbqlTranslatorFactory(EJBQLTranslatorFactory ejbqlTranslatorFactory) {
-        this.ejbqlTranslatorFactory = ejbqlTranslatorFactory;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public MergerFactory mergerFactory() {
-        return new MergerFactory();
-    }
-
-    /**
-     * @since 4.0
-     * @return
-     */
-    protected QuotingStrategy createQuotingStrategy() {
-        return new DefaultQuotingStrategy("\"", "\"");
-    }
-
-    /**
-     * @since 3.0
-     * @deprecated since 4.0 use {@link #getQuotingStrategy()}.
-     */
-    @Deprecated
-    public QuotingStrategy getQuotingStrategy(boolean needQuotes) {
-        return getQuotingStrategy();
-    }
-
-    /**
-     * @since 4.0
-     */
-    public QuotingStrategy getQuotingStrategy() {
-        return quotingStrategy;
-    }
-
-    /**
-     * @since 3.1
-     * @deprecated since 4.0 BatchQueryBuilderfactory is attached to the
-     *             DataNode.
-     */
-    @Deprecated
-    public BatchTranslatorFactory getBatchQueryBuilderFactory() {
-        return batchQueryBuilderFactory;
-    }
-
-    /**
-     * @since 3.1
-     * @deprecated since 4.0 BatchQueryBuilderfactory is attached to the
-     *             DataNode.
-     */
-    @Deprecated
-    public void setBatchQueryBuilderFactory(BatchTranslatorFactory batchQueryBuilderFactory) {
-        this.batchQueryBuilderFactory = batchQueryBuilderFactory;
-    }
-
-    /**
-     * Simply returns this, as JdbcAdapter is not a wrapper.
-     * 
-     * @since 4.0
-     */
-    @Override
-    public DbAdapter unwrap() {
-        return this;
-    }
+		return JdbcAdapter.supportsLength(type);
+	}
+
+	/**
+	 * Returns true if supplied type can have a length attribute as a part of
+	 * column definition
+	 * 
+	 * TODO: this is a static method only to support the deprecated method
+	 * {@link TypesMapping#supportsLength(int)} When the deprecated method is
+	 * removed this body should be moved in to {@link #typeSupportsLength(int)}
+	 * 
+	 * @deprecated
+	 */
+	static boolean supportsLength(int type) {
+		return type == Types.BINARY || type == Types.CHAR || type == Types.NCHAR || type == Types.NVARCHAR
+				|| type == Types.LONGNVARCHAR || type == Types.DECIMAL || type == Types.DOUBLE || type == Types.FLOAT
+				|| type == Types.NUMERIC || type == Types.REAL || type == Types.VARBINARY || type == Types.VARCHAR;
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	@Override
+	public Collection<String> dropTableStatements(DbEntity table) {
+
+		StringBuilder buf = new StringBuilder("DROP TABLE ");
+		buf.append(quotingStrategy.quotedFullyQualifiedName(table));
+
+		return Collections.singleton(buf.toString());
+	}
+
+	/**
+	 * Returns a SQL string that can be used to create database table
+	 * corresponding to <code>ent</code> parameter.
+	 */
+	@Override
+	public String createTable(DbEntity entity) {
+
+		StringBuffer sqlBuffer = new StringBuffer();
+		sqlBuffer.append("CREATE TABLE ");
+		sqlBuffer.append(quotingStrategy.quotedFullyQualifiedName(entity));
+
+		sqlBuffer.append(" (");
+		// columns
+		Iterator<DbAttribute> it = entity.getAttributes().iterator();
+		if (it.hasNext()) {
+			boolean first = true;
+			while (it.hasNext()) {
+				if (first) {
+					first = false;
+				} else {
+					sqlBuffer.append(", ");
+				}
+
+				DbAttribute column = it.next();
+
+				// attribute may not be fully valid, do a simple check
+				if (column.getType() == TypesMapping.NOT_DEFINED) {
+					throw new CayenneRuntimeException("Undefined type for attribute '" + entity.getFullyQualifiedName()
+							+ "." + column.getName() + "'.");
+				}
+
+				createTableAppendColumn(sqlBuffer, column);
+			}
+
+			createTableAppendPKClause(sqlBuffer, entity);
+		}
+
+		sqlBuffer.append(')');
+		return sqlBuffer.toString();
+	}
+
+	/**
+	 * @since 1.2
+	 */
+	protected void createTableAppendPKClause(StringBuffer sqlBuffer, DbEntity entity) {
+
+		Iterator<DbAttribute> pkit = entity.getPrimaryKeys().iterator();
+		if (pkit.hasNext()) {
+			sqlBuffer.append(", PRIMARY KEY (");
+			boolean firstPk = true;
+
+			while (pkit.hasNext()) {
+				if (firstPk) {
+					firstPk = false;
+				} else {
+					sqlBuffer.append(", ");
+				}
+
+				DbAttribute at = pkit.next();
+
+				sqlBuffer.append(quotingStrategy.quotedName(at));
+			}
+			sqlBuffer.append(')');
+		}
+	}
+
+	/**
+	 * Appends SQL for column creation to CREATE TABLE buffer.
+	 * 
+	 * @since 1.2
+	 */
+	@Override
+	public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
+		sqlBuffer.append(quotingStrategy.quotedName(column));
+		sqlBuffer.append(' ').append(getType(this, column));
+
+		sqlBuffer.append(sizeAndPrecision(this, column));
+		sqlBuffer.append(column.isMandatory() ? " NOT NULL" : " NULL");
+	}
+
+	public static String sizeAndPrecision(DbAdapter adapter, DbAttribute column) {
+		if (!adapter.typeSupportsLength(column.getType())) {
+			return "";
+		}
+
+		int len = column.getMaxLength();
+		int scale = TypesMapping.isDecimal(column.getType()) && column.getType() != Types.FLOAT ? column.getScale()
+				: -1;
+
+		// sanity check
+		if (scale > len) {
+			scale = -1;
+		}
+
+		if (len > 0) {
+			return "(" + len + (scale >= 0 ? ", " + scale : "") + ")";
+		}
+
+		return "";
+	}
+
+	public static String getType(DbAdapter adapter, DbAttribute column) {
+		String[] types = adapter.externalTypesForJdbcType(column.getType());
+		if (types == null || types.length == 0) {
+			String entityName = column.getEntity() != null ? column.getEntity().getFullyQualifiedName() : "<null>";
+			throw new CayenneRuntimeException("Undefined type for attribute '" + entityName + "." + column.getName()
+					+ "': " + column.getType());
+		}
+		return types[0];
+	}
+
+	/**
+	 * Returns a DDL string to create a unique constraint over a set of columns.
+	 * 
+	 * @since 1.1
+	 */
+	@Override
+	public String createUniqueConstraint(DbEntity source, Collection<DbAttribute> columns) {
+
+		if (columns == null || columns.isEmpty()) {
+			throw new CayenneRuntimeException("Can't create UNIQUE constraint - no columns specified.");
+		}
+
+		StringBuilder buf = new StringBuilder();
+
+		buf.append("ALTER TABLE ");
+		buf.append(quotingStrategy.quotedFullyQualifiedName(source));
+		buf.append(" ADD UNIQUE (");
+
+		Iterator<DbAttribute> it = columns.iterator();
+		DbAttribute first = it.next();
+		buf.append(quotingStrategy.quotedName(first));
+
+		while (it.hasNext()) {
+			DbAttribute next = it.next();
+			buf.append(", ");
+			buf.append(quotingStrategy.quotedName(next));
+		}
+
+		buf.append(")");
+
+		return buf.toString();
+	}
+
+	/**
+	 * Returns a SQL string that can be used to create a foreign key constraint
+	 * for the relationship.
+	 */
+	@Override
+	public String createFkConstraint(DbRelationship rel) {
+
+		DbEntity source = (DbEntity) rel.getSourceEntity();
+		StringBuilder buf = new StringBuilder();
+		StringBuilder refBuf = new StringBuilder();
+
+		buf.append("ALTER TABLE ");
+
+		buf.append(quotingStrategy.quotedFullyQualifiedName(source));
+		buf.append(" ADD FOREIGN KEY (");
+
+		boolean first = true;
+
+		for (DbJoin join : rel.getJoins()) {
+			if (first) {
+				first = false;
+			} else {
+				buf.append(", ");
+				refBuf.append(", ");
+			}
+
+			buf.append(quotingStrategy.quotedSourceName(join));
+			refBuf.append(quotingStrategy.quotedTargetName(join));
+		}
+
+		buf.append(") REFERENCES ");
+
+		buf.append(quotingStrategy.quotedFullyQualifiedName((DbEntity) rel.getTargetEntity()));
+
+		buf.append(" (").append(refBuf.toString()).append(')');
+		return buf.toString();
+	}
+
+	@Override
+	public String[] externalTypesForJdbcType(int type) {
+		return typesHandler.externalTypesForJdbcType(type);
+	}
+
+	@Override
+	public ExtendedTypeMap getExtendedTypes() {
+		return extendedTypes;
+	}
+
+	@Override
+	public DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls) {
+
+		DbAttribute attr = new DbAttribute();
+		attr.setName(name);
+		attr.setType(type);
+		attr.setMandatory(!allowNulls);
+
+		if (size >= 0) {
+			attr.setMaxLength(size);
+		}
+
+		if (scale >= 0) {
+			attr.setScale(scale);
+		}
+
+		return attr;
+	}
+
+	@Override
+	public String tableTypeForTable() {
+		return "TABLE";
+	}
+
+	@Override
+	public String tableTypeForView() {
+		return "VIEW";
+	}
+
+	/**
+	 * Creates and returns a default implementation of a qualifier translator.
+	 */
+	@Override
+	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
+		QualifierTranslator translator = new QualifierTranslator(queryAssembler);
+		translator.setCaseInsensitive(caseInsensitiveCollations);
+		return translator;
+	}
+
+	/**
+	 * Uses JdbcActionBuilder to create the right action.
+	 * 
+	 * @since 1.2
+	 */
+	@Override
+	public SQLAction getAction(Query query, DataNode node) {
+		return query.createSQLAction(new JdbcActionBuilder(node));
+	}
+
+	@Override
+	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
+		return new DefaultSelectTranslator(query, this, entityResolver);
+	}
+
+	@Override
+	public void bindParameter(PreparedStatement statement, Object object, int pos, int sqlType, int scale)
+			throws SQLException, Exception {
+
+		if (object == null) {
+			statement.setNull(pos, sqlType);
+		} else {
+			ExtendedType typeProcessor = getExtendedTypes().getRegisteredType(object.getClass());
+			typeProcessor.setJdbcObject(statement, object, pos, sqlType, scale);
+		}
+	}
+
+	@Override
+	public boolean supportsBatchUpdates() {
+		return this.supportsBatchUpdates;
+	}
+
+	public void setSupportsBatchUpdates(boolean flag) {
+		this.supportsBatchUpdates = flag;
+	}
+
+	/**
+	 * @since 1.2
+	 */
+	@Override
+	public boolean supportsGeneratedKeys() {
+		return supportsGeneratedKeys;
+	}
+
+	/**
+	 * @since 1.2
+	 */
+	public void setSupportsGeneratedKeys(boolean flag) {
+		this.supportsGeneratedKeys = flag;
+	}
+
+	/**
+	 * Returns a translator factory for EJBQL to SQL translation. The factory is
+	 * normally initialized in constructor by calling
+	 * {@link #createEJBQLTranslatorFactory()}, and can be changed later by
+	 * calling {@link #setEjbqlTranslatorFactory(EJBQLTranslatorFactory)}.
+	 * 
+	 * @since 3.0
+	 */
+	public EJBQLTranslatorFactory getEjbqlTranslatorFactory() {
+		return ejbqlTranslatorFactory;
+	}
+
+	/**
+	 * Sets a translator factory for EJBQL to SQL translation. This property is
+	 * normally initialized in constructor by calling
+	 * {@link #createEJBQLTranslatorFactory()}, so users would only override it
+	 * if they need to customize EJBQL translation.
+	 * 
+	 * @since 3.0
+	 */
+	public void setEjbqlTranslatorFactory(EJBQLTranslatorFactory ejbqlTranslatorFactory) {
+		this.ejbqlTranslatorFactory = ejbqlTranslatorFactory;
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	public MergerFactory mergerFactory() {
+		return new MergerFactory();
+	}
+
+	/**
+	 * @since 4.0
+	 * @return
+	 */
+	protected QuotingStrategy createQuotingStrategy() {
+		return new DefaultQuotingStrategy("\"", "\"");
+	}
+
+	/**
+	 * @since 3.0
+	 * @deprecated since 4.0 use {@link #getQuotingStrategy()}.
+	 */
+	@Deprecated
+	public QuotingStrategy getQuotingStrategy(boolean needQuotes) {
+		return getQuotingStrategy();
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	public QuotingStrategy getQuotingStrategy() {
+		return quotingStrategy;
+	}
+
+	/**
+	 * @since 3.1
+	 * @deprecated since 4.0 BatchQueryBuilderfactory is attached to the
+	 *             DataNode.
+	 */
+	@Deprecated
+	public BatchTranslatorFactory getBatchQueryBuilderFactory() {
+		return batchQueryBuilderFactory;
+	}
+
+	/**
+	 * @since 3.1
+	 * @deprecated since 4.0 BatchQueryBuilderfactory is attached to the
+	 *             DataNode.
+	 */
+	@Deprecated
+	public void setBatchQueryBuilderFactory(BatchTranslatorFactory batchQueryBuilderFactory) {
+		this.batchQueryBuilderFactory = batchQueryBuilderFactory;
+	}
+
+	/**
+	 * Simply returns this, as JdbcAdapter is not a wrapper.
+	 * 
+	 * @since 4.0
+	 */
+	@Override
+	public DbAdapter unwrap() {
+		return this;
+	}
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseActionBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseActionBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseActionBuilder.java
deleted file mode 100644
index 5ed5cf2..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseActionBuilder.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.frontbase;
-
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.jdbc.SelectAction;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
-import org.apache.cayenne.dba.JdbcActionBuilder;
-import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
-
-class FrontBaseActionBuilder extends JdbcActionBuilder {
-
-    FrontBaseActionBuilder(DataNode dataNode) {
-        super(dataNode);
-    }
-
-    @Override
-    public <T> SQLAction objectSelectAction(SelectQuery<T> query) {
-        return new SelectAction(query, dataNode) {
-
-            @Override
-            protected SelectTranslator createTranslator() {
-                return new FrontBaseSelectTranslator(query, dataNode.getAdapter(), dataNode.getEntityResolver());
-            }
-        };
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
index 4cb0dfa..217aff1 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/frontbase/FrontBaseAdapter.java
@@ -26,7 +26,7 @@ import java.util.Iterator;
 import java.util.List;
 
 import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
@@ -39,13 +39,13 @@ import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 /**
- * DbAdapter implementation for <a href="http://www.frontbase.com/">FrontBase RDBMS</a>.
- * Sample connection settings to use with FrontBase are shown below:
+ * DbAdapter implementation for <a href="http://www.frontbase.com/">FrontBase
+ * RDBMS</a>. Sample connection settings to use with FrontBase are shown below:
  * 
  * <pre>
  *          fb.jdbc.username = _system
@@ -59,171 +59,160 @@ import org.apache.cayenne.resource.ResourceLocator;
 // TODO, Andrus 11/8/2005:
 // Limitations (also see FrontBaseStackAdapter in unit tests):
 //
-// 1. Case insensitive ordering (i.e. UPPER in the ORDER BY clause) is supported by
-// FrontBase, however aliases don't work ( ORDER BY UPPER(t0.ARTIST_NAME)) ... not sure
+// 1. Case insensitive ordering (i.e. UPPER in the ORDER BY clause) is supported
+// by
+// FrontBase, however aliases don't work ( ORDER BY UPPER(t0.ARTIST_NAME)) ...
+// not sure
 // what to do about it.
 public class FrontBaseAdapter extends JdbcAdapter {
 
-    public FrontBaseAdapter(
-            @Inject RuntimeProperties runtimeProperties,
-            @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-            @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-            @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject ResourceLocator resourceLocator) {
-        super(
-                runtimeProperties,
-                defaultExtendedTypes,
-                userExtendedTypes,
-                extendedTypeFactories, 
-                resourceLocator);
-        setSupportsBatchUpdates(true);
-    }
-
-    /**
-     * Uses special action builder to create the right action.
-     */
-    @Override
-    public SQLAction getAction(Query query, DataNode node) {
-        return query.createSQLAction(new FrontBaseActionBuilder(node));
-    }
-
-    @Override
-    public String tableTypeForTable() {
-        return "BASE TABLE";
-    }
-
-    @Override
-    protected void configureExtendedTypes(ExtendedTypeMap map) {
-        super.configureExtendedTypes(map);
-
-        map.registerType(new FrontBaseByteArrayType());
-        map.registerType(new FrontBaseBooleanType());
-        map.registerType(new FrontBaseCharType());
-    }
-
-    /**
-     * Customizes table creating procedure for FrontBase.
-     */
-    @Override
-    public String createTable(DbEntity ent) {
-        QuotingStrategy context = getQuotingStrategy();
-        StringBuilder buf = new StringBuilder();
-        buf.append("CREATE TABLE ");
-        buf.append(context.quotedFullyQualifiedName(ent));
-        buf.append(" (");
-
-        // columns
-        Iterator<DbAttribute> it = ent.getAttributes().iterator();
-        boolean first = true;
-        while (it.hasNext()) {
-            if (first) {
-                first = false;
-            } else {
-                buf.append(", ");
-            }
-
-            DbAttribute at = it.next();
-
-            // attribute may not be fully valid, do a simple check
-            if (at.getType() == TypesMapping.NOT_DEFINED) {
-                throw new CayenneRuntimeException("Undefined type for attribute '"
-                        + ent.getFullyQualifiedName()
-                        + "."
-                        + at.getName()
-                        + "'.");
-            }
-
-            String[] types = externalTypesForJdbcType(at.getType());
-            if (types == null || types.length == 0) {
-                throw new CayenneRuntimeException("Undefined type for attribute '"
-                        + ent.getFullyQualifiedName()
-                        + "."
-                        + at.getName()
-                        + "': "
-                        + at.getType());
-            }
-
-            String type = types[0];
-            buf.append(context.quotedName(at)).append(' ').append(type);
-
-            // Mapping LONGVARCHAR without length creates a column with length "1" which
-            // is definitely not what we want...so just use something very large (1Gb seems
-            // to be the limit for FB)
-            if (at.getType() == Types.LONGVARCHAR) {
-
-                int len = at.getMaxLength() > 0 ? at.getMaxLength() : 1073741824;
-                buf.append("(").append(len).append(")");
-            }
-            else if (at.getType() == Types.VARBINARY || at.getType() == Types.BINARY) {
-
-                // use a BIT column with size * 8
-                int len = at.getMaxLength() > 0 ? at.getMaxLength() : 1073741824;
-                len *= 8;
-                buf.append("(").append(len).append(")");
-            }
-            else if (typeSupportsLength(at.getType())) {
-                int len = at.getMaxLength();
-                int scale = TypesMapping.isDecimal(at.getType()) ? at.getScale() : -1;
-
-                // sanity check
-                if (scale > len) {
-                    scale = -1;
-                }
-
-                if (len > 0) {
-                    buf.append('(').append(len);
-
-                    if (scale >= 0) {
-                        buf.append(", ").append(scale);
-                    }
-
-                    buf.append(')');
-                }
-            }
-
-            if (at.isMandatory()) {
-                buf.append(" NOT NULL");
-            }
-            // else: don't appen NULL for FrontBase:
-        }
-
-        // primary key clause
-        Iterator<DbAttribute> pkit = ent.getPrimaryKeys().iterator();
-        if (pkit.hasNext()) {
-            if (first) {
-                first = false;
-            } else {
-                buf.append(", ");
-            }
-
-            buf.append("PRIMARY KEY (");
-            boolean firstPk = true;
-            while (pkit.hasNext()) {
-                if (firstPk) {
-                    firstPk = false;
-                } else {
-                    buf.append(", ");
-                }
-
-                DbAttribute at = pkit.next();
-                buf.append(quotingStrategy.quotedName(at));
-            }
-            buf.append(')');
-        }
-        buf.append(')');
-        return buf.toString();
-    }
-
-    /**
-     * Adds the CASCADE option to the DROP TABLE clause.
-     */
-    @Override
-    public Collection<String> dropTableStatements(DbEntity table) {
-        return Collections.singleton("DROP TABLE " + getQuotingStrategy().quotedFullyQualifiedName(table) + " CASCADE");
-    }
-
-    @Override
-    protected PkGenerator createPkGenerator() {
-        return new FrontBasePkGenerator(this);
-    }
+	public FrontBaseAdapter(@Inject RuntimeProperties runtimeProperties,
+			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+			@Inject ResourceLocator resourceLocator) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+		setSupportsBatchUpdates(true);
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
+		return new FrontBaseSelectTranslator(query, this, entityResolver);
+	}
+
+	@Override
+	public String tableTypeForTable() {
+		return "BASE TABLE";
+	}
+
+	@Override
+	protected void configureExtendedTypes(ExtendedTypeMap map) {
+		super.configureExtendedTypes(map);
+
+		map.registerType(new FrontBaseByteArrayType());
+		map.registerType(new FrontBaseBooleanType());
+		map.registerType(new FrontBaseCharType());
+	}
+
+	/**
+	 * Customizes table creating procedure for FrontBase.
+	 */
+	@Override
+	public String createTable(DbEntity ent) {
+		QuotingStrategy context = getQuotingStrategy();
+		StringBuilder buf = new StringBuilder();
+		buf.append("CREATE TABLE ");
+		buf.append(context.quotedFullyQualifiedName(ent));
+		buf.append(" (");
+
+		// columns
+		Iterator<DbAttribute> it = ent.getAttributes().iterator();
+		boolean first = true;
+		while (it.hasNext()) {
+			if (first) {
+				first = false;
+			} else {
+				buf.append(", ");
+			}
+
+			DbAttribute at = it.next();
+
+			// attribute may not be fully valid, do a simple check
+			if (at.getType() == TypesMapping.NOT_DEFINED) {
+				throw new CayenneRuntimeException("Undefined type for attribute '" + ent.getFullyQualifiedName() + "."
+						+ at.getName() + "'.");
+			}
+
+			String[] types = externalTypesForJdbcType(at.getType());
+			if (types == null || types.length == 0) {
+				throw new CayenneRuntimeException("Undefined type for attribute '" + ent.getFullyQualifiedName() + "."
+						+ at.getName() + "': " + at.getType());
+			}
+
+			String type = types[0];
+			buf.append(context.quotedName(at)).append(' ').append(type);
+
+			// Mapping LONGVARCHAR without length creates a column with length
+			// "1" which
+			// is definitely not what we want...so just use something very large
+			// (1Gb seems
+			// to be the limit for FB)
+			if (at.getType() == Types.LONGVARCHAR) {
+
+				int len = at.getMaxLength() > 0 ? at.getMaxLength() : 1073741824;
+				buf.append("(").append(len).append(")");
+			} else if (at.getType() == Types.VARBINARY || at.getType() == Types.BINARY) {
+
+				// use a BIT column with size * 8
+				int len = at.getMaxLength() > 0 ? at.getMaxLength() : 1073741824;
+				len *= 8;
+				buf.append("(").append(len).append(")");
+			} else if (typeSupportsLength(at.getType())) {
+				int len = at.getMaxLength();
+				int scale = TypesMapping.isDecimal(at.getType()) ? at.getScale() : -1;
+
+				// sanity check
+				if (scale > len) {
+					scale = -1;
+				}
+
+				if (len > 0) {
+					buf.append('(').append(len);
+
+					if (scale >= 0) {
+						buf.append(", ").append(scale);
+					}
+
+					buf.append(')');
+				}
+			}
+
+			if (at.isMandatory()) {
+				buf.append(" NOT NULL");
+			}
+			// else: don't appen NULL for FrontBase:
+		}
+
+		// primary key clause
+		Iterator<DbAttribute> pkit = ent.getPrimaryKeys().iterator();
+		if (pkit.hasNext()) {
+			if (first) {
+				first = false;
+			} else {
+				buf.append(", ");
+			}
+
+			buf.append("PRIMARY KEY (");
+			boolean firstPk = true;
+			while (pkit.hasNext()) {
+				if (firstPk) {
+					firstPk = false;
+				} else {
+					buf.append(", ");
+				}
+
+				DbAttribute at = pkit.next();
+				buf.append(quotingStrategy.quotedName(at));
+			}
+			buf.append(')');
+		}
+		buf.append(')');
+		return buf.toString();
+	}
+
+	/**
+	 * Adds the CASCADE option to the DROP TABLE clause.
+	 */
+	@Override
+	public Collection<String> dropTableStatements(DbEntity table) {
+		return Collections.singleton("DROP TABLE " + getQuotingStrategy().quotedFullyQualifiedName(table) + " CASCADE");
+	}
+
+	@Override
+	protected PkGenerator createPkGenerator() {
+		return new FrontBasePkGenerator(this);
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
index 1f5028c..3195dcd 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
@@ -26,6 +26,7 @@ import java.util.List;
 
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.configuration.Constants;
@@ -36,9 +37,11 @@ import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 /**
@@ -55,169 +58,177 @@ import org.apache.cayenne.resource.ResourceLocator;
  */
 public class HSQLDBAdapter extends JdbcAdapter {
 
-    public HSQLDBAdapter(@Inject RuntimeProperties runtimeProperties,
-            @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-            @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-            @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject ResourceLocator resourceLocator) {
-        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
-    }
-
-    /**
-     * Generate fully-qualified name for 1.8 and on. Subclass generates
-     * unqualified name.
-     * 
-     * @since 1.2
-     */
-    protected String getTableName(DbEntity entity) {
-        return quotingStrategy.quotedFullyQualifiedName(entity);
-    }
-
-    /**
-     * Returns DbEntity schema name for 1.8 and on. Subclass generates
-     * unqualified name.
-     * 
-     * @since 1.2
-     */
-    protected String getSchemaName(DbEntity entity) {
-        return entity.getSchema();
-    }
-
-    /**
-     * Uses special action builder to create the right action.
-     * 
-     * @since 1.2
-     */
-    @Override
-    public SQLAction getAction(Query query, DataNode node) {
-        return query.createSQLAction(new HSQLActionBuilder(node));
-    }
-
-    /**
-     * Returns a DDL string to create a unique constraint over a set of columns.
-     * 
-     * @since 1.1
-     */
-    @Override
-    public String createUniqueConstraint(DbEntity source, Collection<DbAttribute> columns) {
-
-        if (columns == null || columns.isEmpty()) {
-            throw new CayenneRuntimeException("Can't create UNIQUE constraint - no columns specified.");
-        }
-
-        String srcName = getTableName(source);
-
-        StringBuilder buf = new StringBuilder();
-
-        buf.append("ALTER TABLE ").append(srcName);
-        buf.append(" ADD CONSTRAINT ");
-
-        String name = "U_" + source.getName() + "_" + (long) (System.currentTimeMillis() / (Math.random() * 100000));
-        buf.append(quotingStrategy.quotedIdentifier(source, source.getSchema(), name));
-        buf.append(" UNIQUE (");
-
-        Iterator<DbAttribute> it = columns.iterator();
-        DbAttribute first = it.next();
-        buf.append(quotingStrategy.quotedName(first));
-
-        while (it.hasNext()) {
-            DbAttribute next = it.next();
-            buf.append(", ");
-            buf.append(quotingStrategy.quotedName(next));
-        }
-
-        buf.append(")");
-
-        return buf.toString();
-    }
-
-    /**
-     * Adds an ADD CONSTRAINT clause to a relationship constraint.
-     * 
-     * @see JdbcAdapter#createFkConstraint(DbRelationship)
-     */
-    @Override
-    public String createFkConstraint(DbRelationship rel) {
-     
-        StringBuilder buf = new StringBuilder();
-        StringBuilder refBuf = new StringBuilder();
-
-        String srcName = getTableName(rel.getSourceEntity());
-        String dstName = getTableName(rel.getTargetEntity());
-
-        buf.append("ALTER TABLE ");
-        buf.append(srcName);
-
-        // hsqldb requires the ADD CONSTRAINT statement
-        buf.append(" ADD CONSTRAINT ");
-
-        String name = "U_" + rel.getSourceEntity().getName() + "_"
-                + (long) (System.currentTimeMillis() / (Math.random() * 100000));
-
-        DbEntity sourceEntity = rel.getSourceEntity();
-
-        buf.append(quotingStrategy.quotedIdentifier(sourceEntity, sourceEntity.getSchema(), name));
-        buf.append(" FOREIGN KEY (");
-
-        boolean first = true;
-        for (DbJoin join : rel.getJoins()) {
-            if (!first) {
-                buf.append(", ");
-                refBuf.append(", ");
-            } else {
-                first = false;
-            }
-
-            buf.append(quotingStrategy.quotedSourceName(join));
-            refBuf.append(quotingStrategy.quotedTargetName(join));
-        }
-
-        buf.append(") REFERENCES ");
-        buf.append(dstName);
-        buf.append(" (");
-        buf.append(refBuf.toString());
-        buf.append(')');
-
-        // also make sure we delete dependent FKs
-        buf.append(" ON DELETE CASCADE");
-
-        return buf.toString();
-    }
-
-    /**
-     * Uses "CREATE CACHED TABLE" instead of "CREATE TABLE".
-     * 
-     * @since 1.2
-     */
-    @Override
-    public String createTable(DbEntity ent) {
-        // SET SCHEMA <schemaname>
-        String sql = super.createTable(ent);
-        if (sql != null && sql.toUpperCase().startsWith("CREATE TABLE ")) {
-            sql = "CREATE CACHED TABLE " + sql.substring("CREATE TABLE ".length());
-        }
-
-        return sql;
-    }
-
-    @Override
-    public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
-        // CAY-1095: if the column is type double, temporarily set the max
-        // length to 0 to
-        // avoid adding precision information.
-        if (column.getType() == Types.DOUBLE && column.getMaxLength() > 0) {
-            int len = column.getMaxLength();
-            column.setMaxLength(0);
-            super.createTableAppendColumn(sqlBuffer, column);
-            column.setMaxLength(len);
-        } else {
-            super.createTableAppendColumn(sqlBuffer, column);
-        }
-    }
-
-    @Override
-    public MergerFactory mergerFactory() {
-        return new HSQLMergerFactory();
-    }
+	public HSQLDBAdapter(@Inject RuntimeProperties runtimeProperties,
+			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+			@Inject ResourceLocator resourceLocator) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
+		return new HSQLSelectTranslator(query, this, entityResolver);
+	}
+
+	/**
+	 * Generate fully-qualified name for 1.8 and on. Subclass generates
+	 * unqualified name.
+	 * 
+	 * @since 1.2
+	 */
+	protected String getTableName(DbEntity entity) {
+		return quotingStrategy.quotedFullyQualifiedName(entity);
+	}
+
+	/**
+	 * Returns DbEntity schema name for 1.8 and on. Subclass generates
+	 * unqualified name.
+	 * 
+	 * @since 1.2
+	 */
+	protected String getSchemaName(DbEntity entity) {
+		return entity.getSchema();
+	}
+
+	/**
+	 * Uses special action builder to create the right action.
+	 * 
+	 * @since 1.2
+	 */
+	@Override
+	public SQLAction getAction(Query query, DataNode node) {
+		return query.createSQLAction(new HSQLActionBuilder(node));
+	}
+
+	/**
+	 * Returns a DDL string to create a unique constraint over a set of columns.
+	 * 
+	 * @since 1.1
+	 */
+	@Override
+	public String createUniqueConstraint(DbEntity source, Collection<DbAttribute> columns) {
+
+		if (columns == null || columns.isEmpty()) {
+			throw new CayenneRuntimeException("Can't create UNIQUE constraint - no columns specified.");
+		}
+
+		String srcName = getTableName(source);
+
+		StringBuilder buf = new StringBuilder();
+
+		buf.append("ALTER TABLE ").append(srcName);
+		buf.append(" ADD CONSTRAINT ");
+
+		String name = "U_" + source.getName() + "_" + (long) (System.currentTimeMillis() / (Math.random() * 100000));
+		buf.append(quotingStrategy.quotedIdentifier(source, source.getSchema(), name));
+		buf.append(" UNIQUE (");
+
+		Iterator<DbAttribute> it = columns.iterator();
+		DbAttribute first = it.next();
+		buf.append(quotingStrategy.quotedName(first));
+
+		while (it.hasNext()) {
+			DbAttribute next = it.next();
+			buf.append(", ");
+			buf.append(quotingStrategy.quotedName(next));
+		}
+
+		buf.append(")");
+
+		return buf.toString();
+	}
+
+	/**
+	 * Adds an ADD CONSTRAINT clause to a relationship constraint.
+	 * 
+	 * @see JdbcAdapter#createFkConstraint(DbRelationship)
+	 */
+	@Override
+	public String createFkConstraint(DbRelationship rel) {
+
+		StringBuilder buf = new StringBuilder();
+		StringBuilder refBuf = new StringBuilder();
+
+		String srcName = getTableName(rel.getSourceEntity());
+		String dstName = getTableName(rel.getTargetEntity());
+
+		buf.append("ALTER TABLE ");
+		buf.append(srcName);
+
+		// hsqldb requires the ADD CONSTRAINT statement
+		buf.append(" ADD CONSTRAINT ");
+
+		String name = "U_" + rel.getSourceEntity().getName() + "_"
+				+ (long) (System.currentTimeMillis() / (Math.random() * 100000));
+
+		DbEntity sourceEntity = rel.getSourceEntity();
+
+		buf.append(quotingStrategy.quotedIdentifier(sourceEntity, sourceEntity.getSchema(), name));
+		buf.append(" FOREIGN KEY (");
+
+		boolean first = true;
+		for (DbJoin join : rel.getJoins()) {
+			if (!first) {
+				buf.append(", ");
+				refBuf.append(", ");
+			} else {
+				first = false;
+			}
+
+			buf.append(quotingStrategy.quotedSourceName(join));
+			refBuf.append(quotingStrategy.quotedTargetName(join));
+		}
+
+		buf.append(") REFERENCES ");
+		buf.append(dstName);
+		buf.append(" (");
+		buf.append(refBuf.toString());
+		buf.append(')');
+
+		// also make sure we delete dependent FKs
+		buf.append(" ON DELETE CASCADE");
+
+		return buf.toString();
+	}
+
+	/**
+	 * Uses "CREATE CACHED TABLE" instead of "CREATE TABLE".
+	 * 
+	 * @since 1.2
+	 */
+	@Override
+	public String createTable(DbEntity ent) {
+		// SET SCHEMA <schemaname>
+		String sql = super.createTable(ent);
+		if (sql != null && sql.toUpperCase().startsWith("CREATE TABLE ")) {
+			sql = "CREATE CACHED TABLE " + sql.substring("CREATE TABLE ".length());
+		}
+
+		return sql;
+	}
+
+	@Override
+	public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
+		// CAY-1095: if the column is type double, temporarily set the max
+		// length to 0 to
+		// avoid adding precision information.
+		if (column.getType() == Types.DOUBLE && column.getMaxLength() > 0) {
+			int len = column.getMaxLength();
+			column.setMaxLength(0);
+			super.createTableAppendColumn(sqlBuffer, column);
+			column.setMaxLength(len);
+		} else {
+			super.createTableAppendColumn(sqlBuffer, column);
+		}
+	}
+
+	@Override
+	public MergerFactory mergerFactory() {
+		return new HSQLMergerFactory();
+	}
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLSelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLSelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLSelectAction.java
index af721ac..21f4e11 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLSelectAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLSelectAction.java
@@ -20,7 +20,6 @@ package org.apache.cayenne.dba.hsqldb;
 
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.jdbc.SelectAction;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.query.SelectQuery;
 
 /**
@@ -36,9 +35,4 @@ class HSQLSelectAction extends SelectAction {
 	protected int getInMemoryOffset(int queryOffset) {
 		return 0;
 	}
-
-	@Override
-	protected SelectTranslator createTranslator() {
-		return new HSQLSelectTranslator(query, dataNode.getAdapter(), dataNode.getEntityResolver());
-	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
index a427d8b..30efe30 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
@@ -28,6 +28,7 @@ import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.translator.select.TrimmingQualifierTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
@@ -39,10 +40,11 @@ import org.apache.cayenne.dba.PkGenerator;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 /**
@@ -59,101 +61,109 @@ import org.apache.cayenne.resource.ResourceLocator;
  */
 public class IngresAdapter extends JdbcAdapter {
 
-    public static final String TRIM_FUNCTION = "TRIM";
-
-    public IngresAdapter(@Inject RuntimeProperties runtimeProperties,
-            @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-            @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-            @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject ResourceLocator resourceLocator) {
-        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
-        setSupportsUniqueConstraints(true);
-        setSupportsGeneratedKeys(true);
-    }
-
-    @Override
-    public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-        return new TrimmingQualifierTranslator(queryAssembler, IngresAdapter.TRIM_FUNCTION);
-    }
-
-    @Override
-    public SQLAction getAction(Query query, DataNode node) {
-        return query.createSQLAction(new IngresActionBuilder(node));
-    }
-
-    @Override
-    protected void configureExtendedTypes(ExtendedTypeMap map) {
-        super.configureExtendedTypes(map);
-        map.registerType(new IngresCharType());
-
-        // configure boolean type to work with numeric columns
-        map.registerType(new IngresBooleanType());
-    }
-
-    /**
-     * @see JdbcAdapter#createPkGenerator()
-     */
-    @Override
-    protected PkGenerator createPkGenerator() {
-        return new IngresPkGenerator(this);
-    }
-
-    @Override
-    public void bindParameter(PreparedStatement statement, Object object, int pos, int sqlType, int scale)
-            throws SQLException, Exception {
-
-        if (object == null && (sqlType == Types.BOOLEAN || sqlType == Types.BIT)) {
-            statement.setNull(pos, Types.VARCHAR);
-        } else {
-            super.bindParameter(statement, object, pos, sqlType, scale);
-        }
-    }
-
-    @Override
-    public MergerFactory mergerFactory() {
-        return new IngresMergerFactory();
-    }
-
-    @Override
-    public void createTableAppendColumn(StringBuffer buf, DbAttribute at) {
-
-        String[] types = externalTypesForJdbcType(at.getType());
-        if (types == null || types.length == 0) {
-            throw new CayenneRuntimeException("Undefined type for attribute '"
-                    + at.getEntity().getFullyQualifiedName() + "." + at.getName() + "': " + at.getType());
-        }
-
-        String type = types[0];
-        buf.append(quotingStrategy.quotedName(at)).append(' ').append(type);
-
-        // append size and precision (if applicable)
-        if (typeSupportsLength(at.getType())) {
-            int len = at.getMaxLength();
-            int scale = TypesMapping.isDecimal(at.getType()) ? at.getScale() : -1;
-
-            // sanity check
-            if (scale > len) {
-                scale = -1;
-            }
-
-            if (len > 0) {
-                buf.append('(').append(len);
-
-                if (scale >= 0) {
-                    buf.append(", ").append(scale);
-                }
-
-                buf.append(')');
-            }
-        }
-
-        if (at.isGenerated()) {
-            buf.append(" GENERATED BY DEFAULT AS IDENTITY ");
-        }
-
-        // Ingres does not like "null" for non mandatory fields
-        if (at.isMandatory()) {
-            buf.append(" NOT NULL");
-        }
-    }
+	public static final String TRIM_FUNCTION = "TRIM";
+
+	public IngresAdapter(@Inject RuntimeProperties runtimeProperties,
+			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+			@Inject ResourceLocator resourceLocator) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+		setSupportsUniqueConstraints(true);
+		setSupportsGeneratedKeys(true);
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
+		return new IngresSelectTranslator(query, this, entityResolver);
+	}
+
+	@Override
+	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
+		return new TrimmingQualifierTranslator(queryAssembler, IngresAdapter.TRIM_FUNCTION);
+	}
+
+	@Override
+	public SQLAction getAction(Query query, DataNode node) {
+		return query.createSQLAction(new IngresActionBuilder(node));
+	}
+
+	@Override
+	protected void configureExtendedTypes(ExtendedTypeMap map) {
+		super.configureExtendedTypes(map);
+		map.registerType(new IngresCharType());
+
+		// configure boolean type to work with numeric columns
+		map.registerType(new IngresBooleanType());
+	}
+
+	/**
+	 * @see JdbcAdapter#createPkGenerator()
+	 */
+	@Override
+	protected PkGenerator createPkGenerator() {
+		return new IngresPkGenerator(this);
+	}
+
+	@Override
+	public void bindParameter(PreparedStatement statement, Object object, int pos, int sqlType, int scale)
+			throws SQLException, Exception {
+
+		if (object == null && (sqlType == Types.BOOLEAN || sqlType == Types.BIT)) {
+			statement.setNull(pos, Types.VARCHAR);
+		} else {
+			super.bindParameter(statement, object, pos, sqlType, scale);
+		}
+	}
+
+	@Override
+	public MergerFactory mergerFactory() {
+		return new IngresMergerFactory();
+	}
+
+	@Override
+	public void createTableAppendColumn(StringBuffer buf, DbAttribute at) {
+
+		String[] types = externalTypesForJdbcType(at.getType());
+		if (types == null || types.length == 0) {
+			throw new CayenneRuntimeException("Undefined type for attribute '" + at.getEntity().getFullyQualifiedName()
+					+ "." + at.getName() + "': " + at.getType());
+		}
+
+		String type = types[0];
+		buf.append(quotingStrategy.quotedName(at)).append(' ').append(type);
+
+		// append size and precision (if applicable)
+		if (typeSupportsLength(at.getType())) {
+			int len = at.getMaxLength();
+			int scale = TypesMapping.isDecimal(at.getType()) ? at.getScale() : -1;
+
+			// sanity check
+			if (scale > len) {
+				scale = -1;
+			}
+
+			if (len > 0) {
+				buf.append('(').append(len);
+
+				if (scale >= 0) {
+					buf.append(", ").append(scale);
+				}
+
+				buf.append(')');
+			}
+		}
+
+		if (at.isGenerated()) {
+			buf.append(" GENERATED BY DEFAULT AS IDENTITY ");
+		}
+
+		// Ingres does not like "null" for non mandatory fields
+		if (at.isMandatory()) {
+			buf.append(" NOT NULL");
+		}
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSelectAction.java
index 07f05b1..3c6e1cb 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSelectAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresSelectAction.java
@@ -20,22 +20,16 @@ package org.apache.cayenne.dba.ingres;
 
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.jdbc.SelectAction;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.query.SelectQuery;
 
 public class IngresSelectAction extends SelectAction {
 
-    public <T> IngresSelectAction(SelectQuery<T> query, DataNode dataNode) {
-        super(query, dataNode);
-    }
+	public <T> IngresSelectAction(SelectQuery<T> query, DataNode dataNode) {
+		super(query, dataNode);
+	}
 
-    @Override
-    protected int getInMemoryOffset(int queryOffset) {
-        return 0;
-    }
-
-    @Override
-    protected SelectTranslator createTranslator() {
-        return new IngresSelectTranslator(query, dataNode.getAdapter(), dataNode.getEntityResolver());
-    }
+	@Override
+	protected int getInMemoryOffset(int queryOffset) {
+		return 0;
+	}
 }


[5/8] cayenne git commit: CAY-2007 Refactoring SelectTranslator for better extensibility

Posted by aa...@apache.org.
CAY-2007 Refactoring SelectTranslator for better extensibility

SelectTranslator factory


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/741ad3be
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/741ad3be
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/741ad3be

Branch: refs/heads/master
Commit: 741ad3be2b8c5fb75698fabedfe95330ea3e7e78
Parents: 51ce4ce
Author: aadamchik <aa...@apache.org>
Authored: Sun May 10 15:09:10 2015 -0400
Committer: aadamchik <aa...@apache.org>
Committed: Sun May 10 15:49:55 2015 -0400

----------------------------------------------------------------------
 .../org/apache/cayenne/access/DataNode.java     |  921 ++++++-------
 .../cayenne/access/jdbc/SelectAction.java       |    7 +-
 .../select/DefaultSelectTranslatorFactory.java  |   37 +
 .../select/SelectTranslatorFactory.java         |   37 +
 .../server/DefaultDataNodeFactory.java          |    5 +
 .../configuration/server/ServerModule.java      |  305 +++--
 .../org/apache/cayenne/dba/AutoAdapter.java     |  432 ++++---
 .../java/org/apache/cayenne/dba/DbAdapter.java  |  360 +++---
 .../org/apache/cayenne/dba/JdbcAdapter.java     | 1212 +++++++++---------
 .../dba/frontbase/FrontBaseActionBuilder.java   |   45 -
 .../cayenne/dba/frontbase/FrontBaseAdapter.java |  327 +++--
 .../cayenne/dba/hsqldb/HSQLDBAdapter.java       |  339 ++---
 .../cayenne/dba/hsqldb/HSQLSelectAction.java    |    6 -
 .../cayenne/dba/ingres/IngresAdapter.java       |  206 +--
 .../cayenne/dba/ingres/IngresSelectAction.java  |   20 +-
 .../apache/cayenne/dba/mysql/MySQLAdapter.java  |  671 +++++-----
 .../cayenne/dba/mysql/MySQLSelectAction.java    |    6 -
 .../dba/openbase/OpenBaseActionBuilder.java     |   48 -
 .../cayenne/dba/openbase/OpenBaseAdapter.java   |  523 ++++----
 .../dba/oracle/Oracle8ActionBuilder.java        |   54 +-
 .../cayenne/dba/oracle/Oracle8Adapter.java      |  135 +-
 .../cayenne/dba/oracle/Oracle8SelectAction.java |   38 -
 .../cayenne/dba/oracle/OracleAdapter.java       |  568 ++++----
 .../cayenne/dba/oracle/OracleSelectAction.java  |    6 -
 .../cayenne/dba/postgres/PostgresAdapter.java   |  421 +++---
 .../dba/postgres/PostgresSelectAction.java      |    6 -
 .../dba/sqlserver/SQLServerActionBuilder.java   |   52 +-
 .../cayenne/dba/sqlserver/SQLServerAdapter.java |  130 +-
 .../dba/sqlserver/SQLServerSelectAction.java    |   37 -
 .../server/DataDomainProviderTest.java          |    3 +
 .../cayenne/unit/di/server/SchemaBuilder.java   |    2 +
 .../di/server/ServerCaseDataNodeFactory.java    |   57 +-
 32 files changed, 3454 insertions(+), 3562 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java
index 4d1b5f0..84c72f5 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/DataNode.java
@@ -41,6 +41,8 @@ import org.apache.cayenne.access.jdbc.reader.RowReader;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
 import org.apache.cayenne.access.translator.batch.BatchTranslator;
 import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
+import org.apache.cayenne.access.translator.select.SelectTranslatorFactory;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.log.JdbcEventLogger;
@@ -51,6 +53,7 @@ import org.apache.cayenne.map.ObjAttribute;
 import org.apache.cayenne.query.BatchQuery;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.tx.BaseTransaction;
 import org.apache.cayenne.tx.Transaction;
 import org.apache.cayenne.util.ToStringBuilder;
@@ -62,454 +65,462 @@ import org.apache.cayenne.util.ToStringBuilder;
  */
 public class DataNode implements QueryEngine {
 
-    protected String name;
-    protected DataSource dataSource;
-    protected DbAdapter adapter;
-    protected String dataSourceLocation;
-    protected String dataSourceFactory;
-    protected String schemaUpdateStrategyName;
-    protected EntityResolver entityResolver;
-    protected SchemaUpdateStrategy schemaUpdateStrategy;
-    protected Map<String, DataMap> dataMaps;
-
-    private JdbcEventLogger jdbcEventLogger;
-    private RowReaderFactory rowReaderFactory;
-    private BatchTranslatorFactory batchTranslatorFactory;
-    private SQLTemplateProcessor sqlTemplateProcessor;
-
-    TransactionDataSource readThroughDataSource;
-
-    /**
-     * Creates a new unnamed DataNode.
-     */
-    public DataNode() {
-        this(null);
-    }
-
-    /**
-     * Creates a new DataNode, assigning it a name.
-     */
-    public DataNode(String name) {
-
-        this.name = name;
-        this.dataMaps = new HashMap<String, DataMap>();
-        this.readThroughDataSource = new TransactionDataSource();
-
-        // make sure logger is not null
-        this.jdbcEventLogger = NoopJdbcEventLogger.getInstance();
-    }
-
-    /**
-     * @since 3.0
-     */
-    public String getSchemaUpdateStrategyName() {
-        if (schemaUpdateStrategyName == null) {
-            schemaUpdateStrategyName = SkipSchemaUpdateStrategy.class.getName();
-        }
-        return schemaUpdateStrategyName;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public void setSchemaUpdateStrategyName(String schemaUpdateStrategyName) {
-        this.schemaUpdateStrategyName = schemaUpdateStrategyName;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public SchemaUpdateStrategy getSchemaUpdateStrategy() {
-        return schemaUpdateStrategy;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public void setSchemaUpdateStrategy(SchemaUpdateStrategy schemaUpdateStrategy) {
-        this.schemaUpdateStrategy = schemaUpdateStrategy;
-    }
-
-    /**
-     * @since 3.1
-     */
-    public JdbcEventLogger getJdbcEventLogger() {
-        if (jdbcEventLogger == null && adapter instanceof JdbcAdapter) {
-            jdbcEventLogger = ((JdbcAdapter) adapter).getJdbcEventLogger();
-        }
-
-        return jdbcEventLogger;
-    }
-
-    /**
-     * @since 3.1
-     */
-    public void setJdbcEventLogger(JdbcEventLogger logger) {
-        this.jdbcEventLogger = logger;
-    }
-
-    /**
-     * Returns node name. Name is used to uniquely identify DataNode within a
-     * DataDomain.
-     */
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    /**
-     * Returns a location of DataSource of this node. Depending on how this node
-     * was created, location is either a JNDI name, or a location of node XML
-     * file, etc.
-     */
-    public String getDataSourceLocation() {
-        return dataSourceLocation;
-    }
-
-    public void setDataSourceLocation(String dataSourceLocation) {
-        this.dataSourceLocation = dataSourceLocation;
-    }
-
-    /**
-     * Returns a name of DataSourceFactory class for this node.
-     */
-    public String getDataSourceFactory() {
-        return dataSourceFactory;
-    }
-
-    public void setDataSourceFactory(String dataSourceFactory) {
-        this.dataSourceFactory = dataSourceFactory;
-    }
-
-    /**
-     * Returns an unmodifiable collection of DataMaps handled by this DataNode.
-     */
-    public Collection<DataMap> getDataMaps() {
-        return Collections.unmodifiableCollection(dataMaps.values());
-    }
-
-    /**
-     * Returns datamap with specified name, null if none present
-     */
-    public DataMap getDataMap(String name) {
-        return dataMaps.get(name);
-    }
-
-    public void setDataMaps(Collection<DataMap> dataMaps) {
-        for (DataMap map : dataMaps) {
-            this.dataMaps.put(map.getName(), map);
-        }
-    }
-
-    /**
-     * Adds a DataMap to be handled by this node.
-     */
-    public void addDataMap(DataMap map) {
-        this.dataMaps.put(map.getName(), map);
-    }
-
-    public void removeDataMap(DataMap map) {
-        removeDataMap(map.getName());
-    }
-
-    public void removeDataMap(String mapName) {
-        dataMaps.remove(mapName);
-    }
-
-    /**
-     * Returns DataSource used by this DataNode to obtain connections.
-     */
-    public DataSource getDataSource() {
-        return dataSource != null ? readThroughDataSource : null;
-    }
-
-    public void setDataSource(DataSource dataSource) {
-        this.dataSource = dataSource;
-    }
-
-    /**
-     * Returns DbAdapter object. This is a plugin that handles RDBMS
-     * vendor-specific features.
-     */
-    public DbAdapter getAdapter() {
-        return adapter;
-    }
-
-    public void setAdapter(DbAdapter adapter) {
-        this.adapter = adapter;
-    }
-
-    /**
-     * Returns a DataNode that should handle queries for all DataMap components.
-     * 
-     * @since 1.1
-     */
-    public DataNode lookupDataNode(DataMap dataMap) {
-        // we don't know any better than to return ourselves...
-        return this;
-    }
-
-    /**
-     * Runs queries using Connection obtained from internal DataSource.
-     * 
-     * @since 1.1
-     */
-    @Override
-    public void performQueries(Collection<? extends Query> queries, OperationObserver callback) {
-
-        int listSize = queries.size();
-        if (listSize == 0) {
-            return;
-        }
-
-        if (callback.isIteratedResult() && listSize > 1) {
-            throw new CayenneRuntimeException("Iterated queries are not allowed in a batch. Batch size: " + listSize);
-        }
-
-        // do this meaningless inexpensive operation to trigger AutoAdapter lazy
-        // initialization before opening a connection. Otherwise we may end up
-        // with two
-        // connections open simultaneously, possibly hitting connection pool
-        // upper limit.
-        getAdapter().getExtendedTypes();
-
-        Connection connection = null;
-
-        try {
-            connection = this.getDataSource().getConnection();
-        } catch (Exception globalEx) {
-            getJdbcEventLogger().logQueryError(globalEx);
-
-            Transaction transaction = BaseTransaction.getThreadTransaction();
-            if (transaction != null) {
-                transaction.setRollbackOnly();
-            }
-
-            callback.nextGlobalException(globalEx);
-            return;
-        }
-
-        try {
-            DataNodeQueryAction queryRunner = new DataNodeQueryAction(this, callback);
-
-            for (Query nextQuery : queries) {
-
-                // catch exceptions for each individual query
-                try {
-                    queryRunner.runQuery(connection, nextQuery);
-                } catch (Exception queryEx) {
-                    getJdbcEventLogger().logQueryError(queryEx);
-
-                    // notify consumer of the exception,
-                    // stop running further queries
-                    callback.nextQueryException(nextQuery, queryEx);
-
-                    Transaction transaction = BaseTransaction.getThreadTransaction();
-                    if (transaction != null) {
-                        transaction.setRollbackOnly();
-                    }
-                    break;
-                }
-            }
-        } finally {
-            try {
-                connection.close();
-            } catch (SQLException e) {
-                // ignore closing exceptions...
-            }
-        }
-    }
-
-    /**
-     * Returns EntityResolver that handles DataMaps of this node.
-     */
-    @Override
-    public EntityResolver getEntityResolver() {
-        return entityResolver;
-    }
-
-    /**
-     * Sets EntityResolver. DataNode relies on externally set EntityResolver, so
-     * if the node is created outside of DataDomain stack, a valid
-     * EntityResolver must be provided explicitly.
-     * 
-     * @since 1.1
-     */
-    public void setEntityResolver(EntityResolver entityResolver) {
-        this.entityResolver = entityResolver;
-    }
-
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this).append("name", getName()).toString();
-    }
-
-    // a read-through DataSource that ensures returning the same connection
-    // within
-    // transaction.
-    final class TransactionDataSource implements DataSource {
-
-        final String CONNECTION_RESOURCE_PREFIX = "DataNode.Connection.";
-
-        public Connection getConnection() throws SQLException {
-            if (schemaUpdateStrategy != null) {
-                schemaUpdateStrategy.updateSchema(DataNode.this);
-            }
-            Transaction t = BaseTransaction.getThreadTransaction();
-
-            if (t != null) {
-                String key = CONNECTION_RESOURCE_PREFIX + name;
-                Connection c = t.getConnection(key);
-
-                if (c == null || c.isClosed()) {
-                    c = dataSource.getConnection();
-                    t.addConnection(key, c);
-                }
-
-                // wrap transaction-attached connections in a decorator that
-                // prevents them
-                // from being closed by callers, as transaction should take care
-                // of them
-                // on commit or rollback.
-                return new TransactionConnectionDecorator(c);
-            }
-
-            return dataSource.getConnection();
-        }
-
-        public Connection getConnection(String username, String password) throws SQLException {
-            if (schemaUpdateStrategy != null) {
-                schemaUpdateStrategy.updateSchema(DataNode.this);
-            }
-            Transaction t = BaseTransaction.getThreadTransaction();
-            if (t != null) {
-                String key = CONNECTION_RESOURCE_PREFIX + name;
-                Connection c = t.getConnection(key);
-
-                if (c == null || c.isClosed()) {
-                    c = dataSource.getConnection();
-                    t.addConnection(key, c);
-                }
-
-                // wrap transaction-attached connections in a decorator that
-                // prevents them
-                // from being closed by callers, as transaction should take care
-                // of them
-                // on commit or rollback.
-                return new TransactionConnectionDecorator(c);
-            }
-
-            return dataSource.getConnection(username, password);
-        }
-
-        public int getLoginTimeout() throws SQLException {
-            return dataSource.getLoginTimeout();
-        }
-
-        public PrintWriter getLogWriter() throws SQLException {
-            return dataSource.getLogWriter();
-        }
-
-        public void setLoginTimeout(int seconds) throws SQLException {
-            dataSource.setLoginTimeout(seconds);
-        }
-
-        public void setLogWriter(PrintWriter out) throws SQLException {
-            dataSource.setLogWriter(out);
-        }
-
-        /**
-         * @since 3.0
-         */
-        // JDBC 4 compatibility under Java 1.5
-        public boolean isWrapperFor(Class<?> iface) throws SQLException {
-            return iface.isAssignableFrom(dataSource.getClass());
-        }
-
-        /**
-         * @since 3.0
-         */
-        // JDBC 4 compatibility under Java 1.5
-        public <T> T unwrap(Class<T> iface) throws SQLException {
-            try {
-                return iface.cast(dataSource);
-            } catch (ClassCastException e) {
-                throw new SQLException("Not a DataSource: " + e.getMessage());
-            }
-        }
-
-        /**
-         * @since 3.1
-         * 
-         *        JDBC 4.1 compatibility under Java 1.5
-         */
-        public Logger getParentLogger() throws SQLFeatureNotSupportedException {
-            // don't throw SQLFeatureNotSupported - this will break JDK 1.5
-            // runtime
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    /**
-     * Creates a {@link RowReader} using internal {@link RowReaderFactory}.
-     * 
-     * @since 4.0
-     */
-    public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata) {
-        return rowReader(descriptor, queryMetadata, Collections.<ObjAttribute, ColumnDescriptor> emptyMap());
-    }
-
-    /**
-     * Creates a {@link RowReader} using internal {@link RowReaderFactory}.
-     * 
-     * @since 4.0
-     */
-    public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata,
-            Map<ObjAttribute, ColumnDescriptor> attributeOverrides) {
-        return rowReaderFactory.rowReader(descriptor, queryMetadata, getAdapter(), attributeOverrides);
-    }
-    
-    /**
-     * @since 4.0
-     */
-    public BatchTranslator batchTranslator(BatchQuery query, String trimFunction) {
-        return batchTranslatorFactory.translator(query, getAdapter(), trimFunction);
-    }
-
-    /**
-     * @since 4.0
-     */
-    public RowReaderFactory getRowReaderFactory() {
-        return rowReaderFactory;
-    }
-
-    /**
-     * @since 4.0
-     */
-    public void setRowReaderFactory(RowReaderFactory rowReaderFactory) {
-        this.rowReaderFactory = rowReaderFactory;
-    }
-
-    /**
-     * @since 4.0
-     */
-    public BatchTranslatorFactory getBatchTranslatorFactory() {
-        return batchTranslatorFactory;
-    }
-
-    /**
-     * @since 4.0
-     */
-    public void setBatchTranslatorFactory(BatchTranslatorFactory batchTranslatorFactory) {
-        this.batchTranslatorFactory = batchTranslatorFactory;
-    }
-
-    /**
-     * @since 4.0
-     */
+	protected String name;
+	protected DataSource dataSource;
+	protected DbAdapter adapter;
+	protected String dataSourceLocation;
+	protected String dataSourceFactory;
+	protected String schemaUpdateStrategyName;
+	protected EntityResolver entityResolver;
+	protected SchemaUpdateStrategy schemaUpdateStrategy;
+	protected Map<String, DataMap> dataMaps;
+
+	private JdbcEventLogger jdbcEventLogger;
+	private RowReaderFactory rowReaderFactory;
+	private BatchTranslatorFactory batchTranslatorFactory;
+	private SelectTranslatorFactory selectTranslatorFactory;
+	private SQLTemplateProcessor sqlTemplateProcessor;
+
+	TransactionDataSource readThroughDataSource;
+
+	/**
+	 * Creates a new unnamed DataNode.
+	 */
+	public DataNode() {
+		this(null);
+	}
+
+	/**
+	 * Creates a new DataNode, assigning it a name.
+	 */
+	public DataNode(String name) {
+
+		this.name = name;
+		this.dataMaps = new HashMap<String, DataMap>();
+		this.readThroughDataSource = new TransactionDataSource();
+
+		// make sure logger is not null
+		this.jdbcEventLogger = NoopJdbcEventLogger.getInstance();
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	public String getSchemaUpdateStrategyName() {
+		if (schemaUpdateStrategyName == null) {
+			schemaUpdateStrategyName = SkipSchemaUpdateStrategy.class.getName();
+		}
+		return schemaUpdateStrategyName;
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	public void setSchemaUpdateStrategyName(String schemaUpdateStrategyName) {
+		this.schemaUpdateStrategyName = schemaUpdateStrategyName;
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	public SchemaUpdateStrategy getSchemaUpdateStrategy() {
+		return schemaUpdateStrategy;
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	public void setSchemaUpdateStrategy(SchemaUpdateStrategy schemaUpdateStrategy) {
+		this.schemaUpdateStrategy = schemaUpdateStrategy;
+	}
+
+	/**
+	 * @since 3.1
+	 */
+	public JdbcEventLogger getJdbcEventLogger() {
+		if (jdbcEventLogger == null && adapter instanceof JdbcAdapter) {
+			jdbcEventLogger = ((JdbcAdapter) adapter).getJdbcEventLogger();
+		}
+
+		return jdbcEventLogger;
+	}
+
+	/**
+	 * @since 3.1
+	 */
+	public void setJdbcEventLogger(JdbcEventLogger logger) {
+		this.jdbcEventLogger = logger;
+	}
+
+	/**
+	 * Returns node name. Name is used to uniquely identify DataNode within a
+	 * DataDomain.
+	 */
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * Returns a location of DataSource of this node. Depending on how this node
+	 * was created, location is either a JNDI name, or a location of node XML
+	 * file, etc.
+	 */
+	public String getDataSourceLocation() {
+		return dataSourceLocation;
+	}
+
+	public void setDataSourceLocation(String dataSourceLocation) {
+		this.dataSourceLocation = dataSourceLocation;
+	}
+
+	/**
+	 * Returns a name of DataSourceFactory class for this node.
+	 */
+	public String getDataSourceFactory() {
+		return dataSourceFactory;
+	}
+
+	public void setDataSourceFactory(String dataSourceFactory) {
+		this.dataSourceFactory = dataSourceFactory;
+	}
+
+	/**
+	 * Returns an unmodifiable collection of DataMaps handled by this DataNode.
+	 */
+	public Collection<DataMap> getDataMaps() {
+		return Collections.unmodifiableCollection(dataMaps.values());
+	}
+
+	/**
+	 * Returns datamap with specified name, null if none present
+	 */
+	public DataMap getDataMap(String name) {
+		return dataMaps.get(name);
+	}
+
+	public void setDataMaps(Collection<DataMap> dataMaps) {
+		for (DataMap map : dataMaps) {
+			this.dataMaps.put(map.getName(), map);
+		}
+	}
+
+	/**
+	 * Adds a DataMap to be handled by this node.
+	 */
+	public void addDataMap(DataMap map) {
+		this.dataMaps.put(map.getName(), map);
+	}
+
+	public void removeDataMap(DataMap map) {
+		removeDataMap(map.getName());
+	}
+
+	public void removeDataMap(String mapName) {
+		dataMaps.remove(mapName);
+	}
+
+	/**
+	 * Returns DataSource used by this DataNode to obtain connections.
+	 */
+	public DataSource getDataSource() {
+		return dataSource != null ? readThroughDataSource : null;
+	}
+
+	public void setDataSource(DataSource dataSource) {
+		this.dataSource = dataSource;
+	}
+
+	/**
+	 * Returns DbAdapter object. This is a plugin that handles RDBMS
+	 * vendor-specific features.
+	 */
+	public DbAdapter getAdapter() {
+		return adapter;
+	}
+
+	public void setAdapter(DbAdapter adapter) {
+		this.adapter = adapter;
+	}
+
+	/**
+	 * Returns a DataNode that should handle queries for all DataMap components.
+	 * 
+	 * @since 1.1
+	 */
+	public DataNode lookupDataNode(DataMap dataMap) {
+		// we don't know any better than to return ourselves...
+		return this;
+	}
+
+	/**
+	 * Runs queries using Connection obtained from internal DataSource.
+	 * 
+	 * @since 1.1
+	 */
+	@Override
+	public void performQueries(Collection<? extends Query> queries, OperationObserver callback) {
+
+		int listSize = queries.size();
+		if (listSize == 0) {
+			return;
+		}
+
+		if (callback.isIteratedResult() && listSize > 1) {
+			throw new CayenneRuntimeException("Iterated queries are not allowed in a batch. Batch size: " + listSize);
+		}
+
+		// do this meaningless inexpensive operation to trigger AutoAdapter lazy
+		// initialization before opening a connection. Otherwise we may end up
+		// with two
+		// connections open simultaneously, possibly hitting connection pool
+		// upper limit.
+		getAdapter().getExtendedTypes();
+
+		Connection connection = null;
+
+		try {
+			connection = this.getDataSource().getConnection();
+		} catch (Exception globalEx) {
+			getJdbcEventLogger().logQueryError(globalEx);
+
+			Transaction transaction = BaseTransaction.getThreadTransaction();
+			if (transaction != null) {
+				transaction.setRollbackOnly();
+			}
+
+			callback.nextGlobalException(globalEx);
+			return;
+		}
+
+		try {
+			DataNodeQueryAction queryRunner = new DataNodeQueryAction(this, callback);
+
+			for (Query nextQuery : queries) {
+
+				// catch exceptions for each individual query
+				try {
+					queryRunner.runQuery(connection, nextQuery);
+				} catch (Exception queryEx) {
+					getJdbcEventLogger().logQueryError(queryEx);
+
+					// notify consumer of the exception,
+					// stop running further queries
+					callback.nextQueryException(nextQuery, queryEx);
+
+					Transaction transaction = BaseTransaction.getThreadTransaction();
+					if (transaction != null) {
+						transaction.setRollbackOnly();
+					}
+					break;
+				}
+			}
+		} finally {
+			try {
+				connection.close();
+			} catch (SQLException e) {
+				// ignore closing exceptions...
+			}
+		}
+	}
+
+	/**
+	 * Returns EntityResolver that handles DataMaps of this node.
+	 */
+	@Override
+	public EntityResolver getEntityResolver() {
+		return entityResolver;
+	}
+
+	/**
+	 * Sets EntityResolver. DataNode relies on externally set EntityResolver, so
+	 * if the node is created outside of DataDomain stack, a valid
+	 * EntityResolver must be provided explicitly.
+	 * 
+	 * @since 1.1
+	 */
+	public void setEntityResolver(EntityResolver entityResolver) {
+		this.entityResolver = entityResolver;
+	}
+
+	@Override
+	public String toString() {
+		return new ToStringBuilder(this).append("name", getName()).toString();
+	}
+
+	// a read-through DataSource that ensures returning the same connection
+	// within
+	// transaction.
+	final class TransactionDataSource implements DataSource {
+
+		final String CONNECTION_RESOURCE_PREFIX = "DataNode.Connection.";
+
+		public Connection getConnection() throws SQLException {
+			if (schemaUpdateStrategy != null) {
+				schemaUpdateStrategy.updateSchema(DataNode.this);
+			}
+			Transaction t = BaseTransaction.getThreadTransaction();
+
+			if (t != null) {
+				String key = CONNECTION_RESOURCE_PREFIX + name;
+				Connection c = t.getConnection(key);
+
+				if (c == null || c.isClosed()) {
+					c = dataSource.getConnection();
+					t.addConnection(key, c);
+				}
+
+				// wrap transaction-attached connections in a decorator that
+				// prevents them
+				// from being closed by callers, as transaction should take care
+				// of them
+				// on commit or rollback.
+				return new TransactionConnectionDecorator(c);
+			}
+
+			return dataSource.getConnection();
+		}
+
+		public Connection getConnection(String username, String password) throws SQLException {
+			if (schemaUpdateStrategy != null) {
+				schemaUpdateStrategy.updateSchema(DataNode.this);
+			}
+			Transaction t = BaseTransaction.getThreadTransaction();
+			if (t != null) {
+				String key = CONNECTION_RESOURCE_PREFIX + name;
+				Connection c = t.getConnection(key);
+
+				if (c == null || c.isClosed()) {
+					c = dataSource.getConnection();
+					t.addConnection(key, c);
+				}
+
+				// wrap transaction-attached connections in a decorator that
+				// prevents them
+				// from being closed by callers, as transaction should take care
+				// of them
+				// on commit or rollback.
+				return new TransactionConnectionDecorator(c);
+			}
+
+			return dataSource.getConnection(username, password);
+		}
+
+		public int getLoginTimeout() throws SQLException {
+			return dataSource.getLoginTimeout();
+		}
+
+		public PrintWriter getLogWriter() throws SQLException {
+			return dataSource.getLogWriter();
+		}
+
+		public void setLoginTimeout(int seconds) throws SQLException {
+			dataSource.setLoginTimeout(seconds);
+		}
+
+		public void setLogWriter(PrintWriter out) throws SQLException {
+			dataSource.setLogWriter(out);
+		}
+
+		/**
+		 * @since 3.0
+		 */
+		// JDBC 4 compatibility under Java 1.5
+		public boolean isWrapperFor(Class<?> iface) throws SQLException {
+			return iface.isAssignableFrom(dataSource.getClass());
+		}
+
+		/**
+		 * @since 3.0
+		 */
+		// JDBC 4 compatibility under Java 1.5
+		public <T> T unwrap(Class<T> iface) throws SQLException {
+			try {
+				return iface.cast(dataSource);
+			} catch (ClassCastException e) {
+				throw new SQLException("Not a DataSource: " + e.getMessage());
+			}
+		}
+
+		/**
+		 * @since 3.1
+		 * 
+		 *        JDBC 4.1 compatibility under Java 1.5
+		 */
+		public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+			// don't throw SQLFeatureNotSupported - this will break JDK 1.5
+			// runtime
+			throw new UnsupportedOperationException();
+		}
+	}
+
+	/**
+	 * Creates a {@link RowReader} using internal {@link RowReaderFactory}.
+	 * 
+	 * @since 4.0
+	 */
+	public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata) {
+		return rowReader(descriptor, queryMetadata, Collections.<ObjAttribute, ColumnDescriptor> emptyMap());
+	}
+
+	/**
+	 * Creates a {@link RowReader} using internal {@link RowReaderFactory}.
+	 * 
+	 * @since 4.0
+	 */
+	public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata,
+			Map<ObjAttribute, ColumnDescriptor> attributeOverrides) {
+		return rowReaderFactory.rowReader(descriptor, queryMetadata, getAdapter(), attributeOverrides);
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	public BatchTranslator batchTranslator(BatchQuery query, String trimFunction) {
+		return batchTranslatorFactory.translator(query, getAdapter(), trimFunction);
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	public SelectTranslator selectTranslator(SelectQuery<?> query) {
+		return selectTranslatorFactory.translator(query, getAdapter(), getEntityResolver());
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	public RowReaderFactory getRowReaderFactory() {
+		return rowReaderFactory;
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	public void setRowReaderFactory(RowReaderFactory rowReaderFactory) {
+		this.rowReaderFactory = rowReaderFactory;
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	public BatchTranslatorFactory getBatchTranslatorFactory() {
+		return batchTranslatorFactory;
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	public void setBatchTranslatorFactory(BatchTranslatorFactory batchTranslatorFactory) {
+		this.batchTranslatorFactory = batchTranslatorFactory;
+	}
+
+	/**
+	 * @since 4.0
+	 */
 	public SQLTemplateProcessor getSqlTemplateProcessor() {
 		return sqlTemplateProcessor;
 	}
@@ -520,4 +531,18 @@ public class DataNode implements QueryEngine {
 	public void setSqlTemplateProcessor(SQLTemplateProcessor sqlTemplateProcessor) {
 		this.sqlTemplateProcessor = sqlTemplateProcessor;
 	}
+
+	/**
+	 * @since 4.0
+	 */
+	public SelectTranslatorFactory getSelectTranslatorFactory() {
+		return selectTranslatorFactory;
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	public void setSelectTranslatorFactory(SelectTranslatorFactory selectTranslatorFactory) {
+		this.selectTranslatorFactory = selectTranslatorFactory;
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java
index 00785c9..f5ff8d1 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java
@@ -31,7 +31,6 @@ import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.OperationObserver;
 import org.apache.cayenne.access.jdbc.reader.RowReader;
 import org.apache.cayenne.access.translator.ParameterBinding;
-import org.apache.cayenne.access.translator.select.DefaultSelectTranslator;
 import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.log.JdbcEventLogger;
@@ -82,10 +81,6 @@ public class SelectAction extends BaseSQLAction {
 		this.queryMetadata = query.getMetaData(dataNode.getEntityResolver());
 	}
 
-	protected SelectTranslator createTranslator() {
-		return new DefaultSelectTranslator(query, dataNode.getAdapter(), dataNode.getEntityResolver());
-	}
-
 	@SuppressWarnings({ "unchecked", "rawtypes", "resource" })
 	@Override
 	public void performAction(Connection connection, OperationObserver observer) throws SQLException, Exception {
@@ -93,7 +88,7 @@ public class SelectAction extends BaseSQLAction {
 		final long t1 = System.currentTimeMillis();
 
 		JdbcEventLogger logger = dataNode.getJdbcEventLogger();
-		SelectTranslator translator = createTranslator();
+		SelectTranslator translator = dataNode.selectTranslator(query);
 		final String sql = translator.getSql();
 
 		ParameterBinding[] bindings = translator.getBindings();

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorFactory.java
new file mode 100644
index 0000000..eec4e1c
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslatorFactory.java
@@ -0,0 +1,37 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * A {@link SelectTranslator} factory that delegates translator creation to
+ * DbAdapter.
+ * 
+ * @since 4.0
+ */
+public class DefaultSelectTranslatorFactory implements SelectTranslatorFactory {
+
+	@Override
+	public SelectTranslator translator(SelectQuery<?> query, DbAdapter adapter, EntityResolver entityResolver) {
+		return adapter.getSelectTranslator(query, entityResolver);
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SelectTranslatorFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SelectTranslatorFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SelectTranslatorFactory.java
new file mode 100644
index 0000000..4c306b5
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SelectTranslatorFactory.java
@@ -0,0 +1,37 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.access.translator.select;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.SelectQuery;
+
+/**
+ * A factory for {@link SelectTranslator} objects.
+ * 
+ * @since 4.0
+ */
+public interface SelectTranslatorFactory {
+
+	/**
+	 * Creates a proper translator for a BatchQuery
+	 */
+	SelectTranslator translator(SelectQuery<?> query, DbAdapter adapter, EntityResolver entityResolver);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDataNodeFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDataNodeFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDataNodeFactory.java
index 4acaa9e..07c7fdf 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDataNodeFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDataNodeFactory.java
@@ -25,6 +25,7 @@ import org.apache.cayenne.access.dbsync.SchemaUpdateStrategy;
 import org.apache.cayenne.access.jdbc.SQLTemplateProcessor;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
 import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
+import org.apache.cayenne.access.translator.select.SelectTranslatorFactory;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
@@ -46,6 +47,9 @@ public class DefaultDataNodeFactory implements DataNodeFactory {
 
     @Inject
     protected BatchTranslatorFactory batchTranslatorFactory;
+    
+    @Inject
+    protected SelectTranslatorFactory selectTranslatorFactory;
 
     @Inject
     protected DbAdapterFactory adapterFactory;
@@ -67,6 +71,7 @@ public class DefaultDataNodeFactory implements DataNodeFactory {
         dataNode.setJdbcEventLogger(jdbcEventLogger);
         dataNode.setRowReaderFactory(rowReaderFactory);
         dataNode.setBatchTranslatorFactory(batchTranslatorFactory);
+        dataNode.setSelectTranslatorFactory(selectTranslatorFactory);
         dataNode.setSqlTemplateProcessor(sqlTemplateProcessor);
 
         dataNode.setDataSourceLocation(nodeDescriptor.getParameters());

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
index 8119982..7b0a520 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
@@ -32,6 +32,8 @@ import org.apache.cayenne.access.jdbc.reader.DefaultRowReaderFactory;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
 import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
 import org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory;
+import org.apache.cayenne.access.translator.select.DefaultSelectTranslatorFactory;
+import org.apache.cayenne.access.translator.select.SelectTranslatorFactory;
 import org.apache.cayenne.access.types.BigDecimalType;
 import org.apache.cayenne.access.types.BigIntegerType;
 import org.apache.cayenne.access.types.BooleanType;
@@ -108,166 +110,145 @@ import org.apache.cayenne.velocity.VelocitySQLTemplateProcessor;
  */
 public class ServerModule implements Module {
 
-    private static final int DEFAULT_MAX_ID_QUALIFIER_SIZE = 10000;
-
-    protected String[] configurationLocations;
-
-    /**
-     * Creates a ServerModule with at least one configuration location. For multi-module
-     * projects additional locations can be specified as well.
-     */
-    public ServerModule(String... configurationLocations) {
-
-        if (configurationLocations == null) {
-            configurationLocations = new String[0];
-        }
-
-        this.configurationLocations = configurationLocations;
-    }
-
-    public void configure(Binder binder) {
-
-        // configure global stack properties
-        binder.bindMap(Constants.PROPERTIES_MAP)
-            .put(Constants.SERVER_MAX_ID_QUALIFIER_SIZE_PROPERTY, String.valueOf(DEFAULT_MAX_ID_QUALIFIER_SIZE));
-
-        binder.bind(JdbcEventLogger.class).to(CommonsJdbcEventLogger.class);  
-        binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class);
-        binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
-
-        // configure known DbAdapter detectors in reverse order of popularity. Users can
-        // add their own to install custom adapters automatically
-        
-        // a bit ugly - need to bind all sniffers explicitly first before placing then in a list
-        binder.bind(FirebirdSniffer.class).to(FirebirdSniffer.class);
-        binder.bind(OpenBaseSniffer.class).to(OpenBaseSniffer.class);
-        binder.bind(FrontBaseSniffer.class).to(FrontBaseSniffer.class);
-        binder.bind(IngresSniffer.class).to(IngresSniffer.class);
-        binder.bind(SQLiteSniffer.class).to(SQLiteSniffer.class);
-        binder.bind(DB2Sniffer.class).to(DB2Sniffer.class);
-        binder.bind(H2Sniffer.class).to(H2Sniffer.class);
-        binder.bind(HSQLDBSniffer.class).to(HSQLDBSniffer.class);
-        binder.bind(SybaseSniffer.class).to(SybaseSniffer.class);
-        binder.bind(DerbySniffer.class).to(DerbySniffer.class);
-        binder.bind(SQLServerSniffer.class).to(SQLServerSniffer.class);
-        binder.bind(OracleSniffer.class).to(OracleSniffer.class);
-        binder.bind(PostgresSniffer.class).to(PostgresSniffer.class);
-        binder.bind(MySQLSniffer.class).to(MySQLSniffer.class);
-        
-        binder.bindList(Constants.SERVER_ADAPTER_DETECTORS_LIST)
-                .add(FirebirdSniffer.class)
-                .add(OpenBaseSniffer.class)
-                .add(FrontBaseSniffer.class)
-                .add(IngresSniffer.class)
-                .add(SQLiteSniffer.class)
-                .add(DB2Sniffer.class)
-                .add(H2Sniffer.class)
-                .add(HSQLDBSniffer.class)
-                .add(SybaseSniffer.class)
-                .add(DerbySniffer.class)
-                .add(SQLServerSniffer.class)
-                .add(OracleSniffer.class)
-                .add(PostgresSniffer.class)
-                .add(MySQLSniffer.class);
-
-        // configure an empty filter chain
-        binder.bindList(Constants.SERVER_DOMAIN_FILTERS_LIST);
-        
-        // configure extended types
-        binder
-                .bindList(Constants.SERVER_DEFAULT_TYPES_LIST)
-                .add(new VoidType())
-                .add(new BigDecimalType())
-                .add(new BigIntegerType())
-                .add(new BooleanType())
-                .add(new ByteArrayType(false, true))
-                .add(new ByteType(false))
-                .add(new CharType(false, true))
-                .add(new DateType())
-                .add(new DoubleType())
-                .add(new FloatType())
-                .add(new IntegerType())
-                .add(new LongType())
-                .add(new ShortType(false))
-                .add(new TimeType())
-                .add(new TimestampType())
-                .add(new UtilDateType())
-                .add(new CalendarType<GregorianCalendar>(GregorianCalendar.class))
-                .add(new CalendarType<Calendar>(Calendar.class))
-                .add(new UUIDType()); 
-        binder.bindList(Constants.SERVER_USER_TYPES_LIST);
-        binder.bindList(Constants.SERVER_TYPE_FACTORIES_LIST);
-
-        // configure explicit configurations
-        ListBuilder<Object> locationsListBuilder = binder
-                .bindList(Constants.SERVER_PROJECT_LOCATIONS_LIST);
-        for (String location : configurationLocations) {
-            locationsListBuilder.add(location);
-        }
-
-        binder.bind(ConfigurationNameMapper.class).to(
-                DefaultConfigurationNameMapper.class);
-
-        binder.bind(EventManager.class).to(DefaultEventManager.class);
-
-        binder.bind(QueryCache.class).toProvider(MapQueryCacheProvider.class);
-
-        // a service to provide the main stack DataDomain
-        binder.bind(DataDomain.class).toProvider(DataDomainProvider.class);
-        
-        binder.bind(DataNodeFactory.class).to(DefaultDataNodeFactory.class);
-
-        // will return DataDomain for request for a DataChannel
-        binder.bind(DataChannel.class).toProvider(DomainDataChannelProvider.class);
-
-        binder.bind(ObjectContextFactory.class).to(DataContextFactory.class);
-        
-        binder.bind(TransactionFactory.class).to(DefaultTransactionFactory.class);
-        
-        // a service to load project XML descriptors
-        binder.bind(DataChannelDescriptorLoader.class).to(
-                XMLDataChannelDescriptorLoader.class);
-        binder.bind(DataChannelDescriptorMerger.class).to(
-                DefaultDataChannelDescriptorMerger.class);
-
-        // a service to load DataMap XML descriptors
-        binder.bind(DataMapLoader.class).to(XMLDataMapLoader.class);
-
-        // a locator of resources, such as XML descriptors
-        binder.bind(ResourceLocator.class).to(ClassLoaderResourceLocator.class);
-
-        // a global properties object
-        binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
-
-        // a service to load DataSourceFactories. DelegatingDataSourceFactory will attempt
-        // to find the actual worker factory dynamically on each call depending on
-        // DataNodeDescriptor data and the environment
-        binder.bind(DataSourceFactory.class).to(DelegatingDataSourceFactory.class);
-
-        // a default SchemaUpdateStrategy (used when no explicit strategy is specified in
-        // XML)
-        binder.bind(SchemaUpdateStrategy.class).to(SkipSchemaUpdateStrategy.class);
-
-        // a default DBAdapterFactory used to load custom and automatic DbAdapters
-        binder.bind(DbAdapterFactory.class).to(DefaultDbAdapterFactory.class);
-
-        // binding AshwoodEntitySorter without scope, as this is a stateful object and is
-        // configured by the owning domain
-        binder.bind(EntitySorter.class).to(AshwoodEntitySorter.class).withoutScope();
-
-        binder.bind(BatchTranslatorFactory.class).to(
-                DefaultBatchTranslatorFactory.class);
-
-        // a default ObjectMapRetainStrategy used to create objects map for ObjectStore
-        binder.bind(ObjectMapRetainStrategy.class).to(
-                DefaultObjectMapRetainStrategy.class);
-
-        // a default ObjectStoreFactory used to create ObjectStores for contexts
-        binder.bind(ObjectStoreFactory.class).to(DefaultObjectStoreFactory.class);
-        
-        binder.bind(TransactionManager.class).to(DefaultTransactionManager.class);
-        binder.bind(RowReaderFactory.class).to(DefaultRowReaderFactory.class);
-        
-        binder.bind(SQLTemplateProcessor.class).to(VelocitySQLTemplateProcessor.class);
-    }
+	private static final int DEFAULT_MAX_ID_QUALIFIER_SIZE = 10000;
+
+	protected String[] configurationLocations;
+
+	/**
+	 * Creates a ServerModule with at least one configuration location. For
+	 * multi-module projects additional locations can be specified as well.
+	 */
+	public ServerModule(String... configurationLocations) {
+
+		if (configurationLocations == null) {
+			configurationLocations = new String[0];
+		}
+
+		this.configurationLocations = configurationLocations;
+	}
+
+	public void configure(Binder binder) {
+
+		// configure global stack properties
+		binder.bindMap(Constants.PROPERTIES_MAP).put(Constants.SERVER_MAX_ID_QUALIFIER_SIZE_PROPERTY,
+				String.valueOf(DEFAULT_MAX_ID_QUALIFIER_SIZE));
+
+		binder.bind(JdbcEventLogger.class).to(CommonsJdbcEventLogger.class);
+		binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class);
+		binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
+
+		// configure known DbAdapter detectors in reverse order of popularity.
+		// Users can
+		// add their own to install custom adapters automatically
+
+		// a bit ugly - need to bind all sniffers explicitly first before
+		// placing then in a list
+		binder.bind(FirebirdSniffer.class).to(FirebirdSniffer.class);
+		binder.bind(OpenBaseSniffer.class).to(OpenBaseSniffer.class);
+		binder.bind(FrontBaseSniffer.class).to(FrontBaseSniffer.class);
+		binder.bind(IngresSniffer.class).to(IngresSniffer.class);
+		binder.bind(SQLiteSniffer.class).to(SQLiteSniffer.class);
+		binder.bind(DB2Sniffer.class).to(DB2Sniffer.class);
+		binder.bind(H2Sniffer.class).to(H2Sniffer.class);
+		binder.bind(HSQLDBSniffer.class).to(HSQLDBSniffer.class);
+		binder.bind(SybaseSniffer.class).to(SybaseSniffer.class);
+		binder.bind(DerbySniffer.class).to(DerbySniffer.class);
+		binder.bind(SQLServerSniffer.class).to(SQLServerSniffer.class);
+		binder.bind(OracleSniffer.class).to(OracleSniffer.class);
+		binder.bind(PostgresSniffer.class).to(PostgresSniffer.class);
+		binder.bind(MySQLSniffer.class).to(MySQLSniffer.class);
+
+		binder.bindList(Constants.SERVER_ADAPTER_DETECTORS_LIST).add(FirebirdSniffer.class).add(OpenBaseSniffer.class)
+				.add(FrontBaseSniffer.class).add(IngresSniffer.class).add(SQLiteSniffer.class).add(DB2Sniffer.class)
+				.add(H2Sniffer.class).add(HSQLDBSniffer.class).add(SybaseSniffer.class).add(DerbySniffer.class)
+				.add(SQLServerSniffer.class).add(OracleSniffer.class).add(PostgresSniffer.class)
+				.add(MySQLSniffer.class);
+
+		// configure an empty filter chain
+		binder.bindList(Constants.SERVER_DOMAIN_FILTERS_LIST);
+
+		// configure extended types
+		binder.bindList(Constants.SERVER_DEFAULT_TYPES_LIST).add(new VoidType()).add(new BigDecimalType())
+				.add(new BigIntegerType()).add(new BooleanType()).add(new ByteArrayType(false, true))
+				.add(new ByteType(false)).add(new CharType(false, true)).add(new DateType()).add(new DoubleType())
+				.add(new FloatType()).add(new IntegerType()).add(new LongType()).add(new ShortType(false))
+				.add(new TimeType()).add(new TimestampType()).add(new UtilDateType())
+				.add(new CalendarType<GregorianCalendar>(GregorianCalendar.class))
+				.add(new CalendarType<Calendar>(Calendar.class)).add(new UUIDType());
+		binder.bindList(Constants.SERVER_USER_TYPES_LIST);
+		binder.bindList(Constants.SERVER_TYPE_FACTORIES_LIST);
+
+		// configure explicit configurations
+		ListBuilder<Object> locationsListBuilder = binder.bindList(Constants.SERVER_PROJECT_LOCATIONS_LIST);
+		for (String location : configurationLocations) {
+			locationsListBuilder.add(location);
+		}
+
+		binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
+
+		binder.bind(EventManager.class).to(DefaultEventManager.class);
+
+		binder.bind(QueryCache.class).toProvider(MapQueryCacheProvider.class);
+
+		// a service to provide the main stack DataDomain
+		binder.bind(DataDomain.class).toProvider(DataDomainProvider.class);
+
+		binder.bind(DataNodeFactory.class).to(DefaultDataNodeFactory.class);
+
+		// will return DataDomain for request for a DataChannel
+		binder.bind(DataChannel.class).toProvider(DomainDataChannelProvider.class);
+
+		binder.bind(ObjectContextFactory.class).to(DataContextFactory.class);
+
+		binder.bind(TransactionFactory.class).to(DefaultTransactionFactory.class);
+
+		// a service to load project XML descriptors
+		binder.bind(DataChannelDescriptorLoader.class).to(XMLDataChannelDescriptorLoader.class);
+		binder.bind(DataChannelDescriptorMerger.class).to(DefaultDataChannelDescriptorMerger.class);
+
+		// a service to load DataMap XML descriptors
+		binder.bind(DataMapLoader.class).to(XMLDataMapLoader.class);
+
+		// a locator of resources, such as XML descriptors
+		binder.bind(ResourceLocator.class).to(ClassLoaderResourceLocator.class);
+
+		// a global properties object
+		binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
+
+		// a service to load DataSourceFactories. DelegatingDataSourceFactory
+		// will attempt
+		// to find the actual worker factory dynamically on each call depending
+		// on
+		// DataNodeDescriptor data and the environment
+		binder.bind(DataSourceFactory.class).to(DelegatingDataSourceFactory.class);
+
+		// a default SchemaUpdateStrategy (used when no explicit strategy is
+		// specified in
+		// XML)
+		binder.bind(SchemaUpdateStrategy.class).to(SkipSchemaUpdateStrategy.class);
+
+		// a default DBAdapterFactory used to load custom and automatic
+		// DbAdapters
+		binder.bind(DbAdapterFactory.class).to(DefaultDbAdapterFactory.class);
+
+		// binding AshwoodEntitySorter without scope, as this is a stateful
+		// object and is
+		// configured by the owning domain
+		binder.bind(EntitySorter.class).to(AshwoodEntitySorter.class).withoutScope();
+
+		binder.bind(BatchTranslatorFactory.class).to(DefaultBatchTranslatorFactory.class);
+		binder.bind(SelectTranslatorFactory.class).to(DefaultSelectTranslatorFactory.class);
+
+		// a default ObjectMapRetainStrategy used to create objects map for
+		// ObjectStore
+		binder.bind(ObjectMapRetainStrategy.class).to(DefaultObjectMapRetainStrategy.class);
+
+		// a default ObjectStoreFactory used to create ObjectStores for contexts
+		binder.bind(ObjectStoreFactory.class).to(DefaultObjectStoreFactory.class);
+
+		binder.bind(TransactionManager.class).to(DefaultTransactionManager.class);
+		binder.bind(RowReaderFactory.class).to(DefaultRowReaderFactory.class);
+
+		binder.bind(SQLTemplateProcessor.class).to(VelocitySQLTemplateProcessor.class);
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
index 4e16194..0976763 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
@@ -19,245 +19,241 @@
 
 package org.apache.cayenne.dba;
 
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Collection;
+
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
 import org.apache.cayenne.di.Provider;
 import org.apache.cayenne.log.JdbcEventLogger;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
-
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.util.Collection;
+import org.apache.cayenne.query.SelectQuery;
 
 /**
- * A DbAdapter that automatically detects the kind of database it is running on and
- * instantiates an appropriate DB-specific adapter, delegating all subsequent method calls
- * to this adapter.
+ * A DbAdapter that automatically detects the kind of database it is running on
+ * and instantiates an appropriate DB-specific adapter, delegating all
+ * subsequent method calls to this adapter.
  * 
  * @since 1.2
  */
 public class AutoAdapter implements DbAdapter {
 
-    protected Provider<DbAdapter> adapterProvider;
-    protected PkGenerator pkGenerator;
-    protected JdbcEventLogger logger;
-
-    /**
-     * The actual adapter that is delegated methods execution.
-     */
-    volatile DbAdapter adapter;
-
-    /**
-     * Creates an {@link AutoAdapter} based on a delegate adapter obtained via
-     * "adapterProvider".
-     * 
-     * @since 3.1
-     */
-    public AutoAdapter(Provider<DbAdapter> adapterProvider, JdbcEventLogger logger) {
-
-        if (adapterProvider == null) {
-            throw new CayenneRuntimeException("Null adapterProvider");
-        }
-
-        this.adapterProvider = adapterProvider;
-        this.logger = logger;
-    }
-
-    /**
-     * Returns a proxied DbAdapter, lazily creating it on first invocation.
-     */
-    protected DbAdapter getAdapter() {
-        if (adapter == null) {
-            synchronized (this) {
-                if (adapter == null) {
-                    this.adapter = loadAdapter();
-                }
-            }
-        }
-
-        return adapter;
-    }
-
-    /**
-     * Loads underlying DbAdapter delegate.
-     */
-    protected DbAdapter loadAdapter() {
-        return adapterProvider.get();
-    }
-
-    @Override
-    public String getBatchTerminator() {
-        return getAdapter().getBatchTerminator();
-    }
-
-    @Override
-    public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-        return getAdapter().getQualifierTranslator(queryAssembler);
-    }
-
-    @Override
-    public SQLAction getAction(Query query, DataNode node) {
-        return getAdapter().getAction(query, node);
-    }
-
-    @Override
-    public boolean supportsUniqueConstraints() {
-        return getAdapter().supportsUniqueConstraints();
-    }
-
-    @Override
-    public boolean supportsCatalogsOnReverseEngineering() {
-        return getAdapter().supportsCatalogsOnReverseEngineering();
-    }
-
-    @Override
-    public boolean supportsGeneratedKeys() {
-        return getAdapter().supportsGeneratedKeys();
-    }
-
-    @Override
-    public boolean supportsBatchUpdates() {
-        return getAdapter().supportsBatchUpdates();
-    }
+	protected Provider<DbAdapter> adapterProvider;
+	protected PkGenerator pkGenerator;
+	protected JdbcEventLogger logger;
+
+	/**
+	 * The actual adapter that is delegated methods execution.
+	 */
+	volatile DbAdapter adapter;
+
+	/**
+	 * Creates an {@link AutoAdapter} based on a delegate adapter obtained via
+	 * "adapterProvider".
+	 * 
+	 * @since 3.1
+	 */
+	public AutoAdapter(Provider<DbAdapter> adapterProvider, JdbcEventLogger logger) {
+
+		if (adapterProvider == null) {
+			throw new CayenneRuntimeException("Null adapterProvider");
+		}
+
+		this.adapterProvider = adapterProvider;
+		this.logger = logger;
+	}
+
+	/**
+	 * Returns a proxied DbAdapter, lazily creating it on first invocation.
+	 */
+	protected DbAdapter getAdapter() {
+		if (adapter == null) {
+			synchronized (this) {
+				if (adapter == null) {
+					this.adapter = loadAdapter();
+				}
+			}
+		}
+
+		return adapter;
+	}
+
+	/**
+	 * Loads underlying DbAdapter delegate.
+	 */
+	protected DbAdapter loadAdapter() {
+		return adapterProvider.get();
+	}
+	
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
+		return getAdapter().getSelectTranslator(query, entityResolver);
+	}
+
+	@Override
+	public String getBatchTerminator() {
+		return getAdapter().getBatchTerminator();
+	}
+
+	@Override
+	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
+		return getAdapter().getQualifierTranslator(queryAssembler);
+	}
+
+	@Override
+	public SQLAction getAction(Query query, DataNode node) {
+		return getAdapter().getAction(query, node);
+	}
+
+	@Override
+	public boolean supportsUniqueConstraints() {
+		return getAdapter().supportsUniqueConstraints();
+	}
+
+	@Override
+	public boolean supportsCatalogsOnReverseEngineering() {
+		return getAdapter().supportsCatalogsOnReverseEngineering();
+	}
+
+	@Override
+	public boolean supportsGeneratedKeys() {
+		return getAdapter().supportsGeneratedKeys();
+	}
+
+	@Override
+	public boolean supportsBatchUpdates() {
+		return getAdapter().supportsBatchUpdates();
+	}
+
+	@Override
+	public boolean typeSupportsLength(int type) {
+		return getAdapter().typeSupportsLength(type);
+	}
+
+	@Override
+	public Collection<String> dropTableStatements(DbEntity table) {
+		return getAdapter().dropTableStatements(table);
+	}
+
+	@Override
+	public String createTable(DbEntity entity) {
+		return getAdapter().createTable(entity);
+	}
+
+	@Override
+	public String createUniqueConstraint(DbEntity source, Collection<DbAttribute> columns) {
+		return getAdapter().createUniqueConstraint(source, columns);
+	}
+
+	@Override
+	public String createFkConstraint(DbRelationship rel) {
+		return getAdapter().createFkConstraint(rel);
+	}
+
+	@Override
+	public String[] externalTypesForJdbcType(int type) {
+		return getAdapter().externalTypesForJdbcType(type);
+	}
+
+	@Override
+	public ExtendedTypeMap getExtendedTypes() {
+		return getAdapter().getExtendedTypes();
+	}
+
+	/**
+	 * Returns a primary key generator.
+	 */
+	@Override
+	public PkGenerator getPkGenerator() {
+		return (pkGenerator != null) ? pkGenerator : getAdapter().getPkGenerator();
+	}
+
+	/**
+	 * Sets a PK generator override. If set to non-null value, such PK generator
+	 * will be used instead of the one provided by wrapped adapter.
+	 */
+	public void setPkGenerator(PkGenerator pkGenerator) {
+		this.pkGenerator = pkGenerator;
+	}
+
+	@Override
+	public DbAttribute buildAttribute(String name, String typeName, int type, int size, int precision,
+			boolean allowNulls) {
+
+		return getAdapter().buildAttribute(name, typeName, type, size, precision, allowNulls);
+	}
+
+	@Override
+	public void bindParameter(PreparedStatement statement, Object object, int pos, int sqlType, int precision)
+			throws SQLException, Exception {
+		getAdapter().bindParameter(statement, object, pos, sqlType, precision);
+	}
+
+	@Override
+	public String tableTypeForTable() {
+		return getAdapter().tableTypeForTable();
+	}
+
+	@Override
+	public String tableTypeForView() {
+		return getAdapter().tableTypeForView();
+	}
+
+	@Override
+	public MergerFactory mergerFactory() {
+		return getAdapter().mergerFactory();
+	}
+
+	@Override
+	public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
+		getAdapter().createTableAppendColumn(sqlBuffer, column);
+	}
+
+	/**
+	 * @deprecated since 4.0
+	 */
+	@Deprecated
+	@Override
+	public QuotingStrategy getQuotingStrategy(boolean isQuoteStrategy) {
+		return getAdapter().getQuotingStrategy(isQuoteStrategy);
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public QuotingStrategy getQuotingStrategy() {
+		return getAdapter().getQuotingStrategy();
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public DbAdapter unwrap() {
+		return getAdapter();
+	}
 
+	/**
+	 * @since 4.0
+	 */
 	@Override
-    public boolean typeSupportsLength(int type) {
-    	return getAdapter().typeSupportsLength(type);
-    }
-    
-    @Override
-    public Collection<String> dropTableStatements(DbEntity table) {
-        return getAdapter().dropTableStatements(table);
-    }
-
-    @Override
-    public String createTable(DbEntity entity) {
-        return getAdapter().createTable(entity);
-    }
-
-    @Override
-    public String createUniqueConstraint(DbEntity source, Collection<DbAttribute> columns) {
-        return getAdapter().createUniqueConstraint(source, columns);
-    }
-
-    @Override
-    public String createFkConstraint(DbRelationship rel) {
-        return getAdapter().createFkConstraint(rel);
-    }
-
-    @Override
-    public String[] externalTypesForJdbcType(int type) {
-        return getAdapter().externalTypesForJdbcType(type);
-    }
-
-    @Override
-    public ExtendedTypeMap getExtendedTypes() {
-        return getAdapter().getExtendedTypes();
-    }
-
-    /**
-     * Returns a primary key generator.
-     */
-    @Override
-    public PkGenerator getPkGenerator() {
-        return (pkGenerator != null) ? pkGenerator : getAdapter().getPkGenerator();
-    }
-
-    /**
-     * Sets a PK generator override. If set to non-null value, such PK generator will be
-     * used instead of the one provided by wrapped adapter.
-     */
-    public void setPkGenerator(PkGenerator pkGenerator) {
-        this.pkGenerator = pkGenerator;
-    }
-
-    @Override
-    public DbAttribute buildAttribute(
-            String name,
-            String typeName,
-            int type,
-            int size,
-            int precision,
-            boolean allowNulls) {
-
-        return getAdapter().buildAttribute(
-                name,
-                typeName,
-                type,
-                size,
-                precision,
-                allowNulls);
-    }
-
-    @Override
-    public void bindParameter(
-            PreparedStatement statement,
-            Object object,
-            int pos,
-            int sqlType,
-            int precision) throws SQLException, Exception {
-        getAdapter().bindParameter(statement, object, pos, sqlType, precision);
-    }
-
-    @Override
-    public String tableTypeForTable() {
-        return getAdapter().tableTypeForTable();
-    }
-
-    @Override
-    public String tableTypeForView() {
-        return getAdapter().tableTypeForView();
-    }
-
-    @Override
-    public MergerFactory mergerFactory() {
-        return getAdapter().mergerFactory();
-    }
-
-    @Override
-    public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
-        getAdapter().createTableAppendColumn(sqlBuffer, column);
-    }
-
-    /**
-     * @deprecated since 4.0
-     */
-    @Deprecated
-    @Override
-    public QuotingStrategy getQuotingStrategy(boolean isQuoteStrategy) {
-        return getAdapter().getQuotingStrategy(isQuoteStrategy);
-    }
-    
-    /**
-     * @since 4.0
-     */
-    @Override
-    public QuotingStrategy getQuotingStrategy() {
-        return getAdapter().getQuotingStrategy();
-    }
-
-    /**
-     * @since 4.0
-     */
-    @Override
-    public DbAdapter unwrap() {
-        return getAdapter();
-    }
-
-    /**
-     * @since 4.0
-     */
-    @Override
-    public EJBQLTranslatorFactory getEjbqlTranslatorFactory() {
-        return getAdapter().getEjbqlTranslatorFactory();
-    }
+	public EJBQLTranslatorFactory getEjbqlTranslatorFactory() {
+		return getAdapter().getEjbqlTranslatorFactory();
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
index 5d6c4d9..b71a741 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
@@ -22,13 +22,16 @@ import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
 
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
@@ -42,179 +45,186 @@ import java.util.Collection;
  */
 public interface DbAdapter {
 
-    /**
-     * Returns a String used to terminate a batch in command-line tools. E.g.
-     * ";" on Oracle or "go" on Sybase.
-     * 
-     * @since 1.0.4
-     */
-    String getBatchTerminator();
-
-    // TODO: deprecate and move into SQLAction implementation
-    QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler);
-
-    /**
-     * Returns an instance of SQLAction that should handle the query.
-     * 
-     * @since 1.2
-     */
-    SQLAction getAction(Query query, DataNode node);
-
-    /**
-     * Returns true if a target database supports UNIQUE constraints.
-     *
-     * @since 1.1
-     */
-    boolean supportsUniqueConstraints();
-
-    /**
-     * Returns true if a target database supports catalogs on reverse engineering.
-     *
-     * @since 4.0
-     */
-    boolean supportsCatalogsOnReverseEngineering();
-
-    /**
-     * Returns true if a target database supports key autogeneration. This
-     * feature also requires JDBC3-compliant driver.
-     * 
-     * @since 1.2
-     */
-    boolean supportsGeneratedKeys();
-
-    /**
-     * Returns <code>true</code> if the target database supports batch updates.
-     */
-    boolean supportsBatchUpdates();
-
-    boolean typeSupportsLength(int type);
-    
-    /**
-     * Returns a collection of SQL statements needed to drop a database table.
-     * 
-     * @since 3.0
-     */
-    Collection<String> dropTableStatements(DbEntity table);
-
-    /**
-     * Returns a SQL string that can be used to create database table
-     * corresponding to <code>entity</code> parameter.
-     */
-    String createTable(DbEntity entity);
-
-    /**
-     * Returns a DDL string to create a unique constraint over a set of columns,
-     * or null if the unique constraints are not supported.
-     * 
-     * @since 1.1
-     */
-    String createUniqueConstraint(DbEntity source, Collection<DbAttribute> columns);
-
-    /**
-     * Returns a SQL string that can be used to create a foreign key constraint
-     * for the relationship, or null if foreign keys are not supported.
-     */
-    String createFkConstraint(DbRelationship rel);
-
-    /**
-     * Returns an array of RDBMS types that can be used with JDBC
-     * <code>type</code>. Valid JDBC types are defined in java.sql.Types.
-     */
-    String[] externalTypesForJdbcType(int type);
-
-    /**
-     * Returns a map of ExtendedTypes that is used to translate values between
-     * Java and JDBC layer.
-     */
-    ExtendedTypeMap getExtendedTypes();
-
-    /**
-     * Returns primary key generator associated with this DbAdapter.
-     */
-    PkGenerator getPkGenerator();
-
-    /**
-     * Creates and returns a DbAttribute based on supplied parameters (usually
-     * obtained from database meta data).
-     * 
-     * @param name
-     *            database column name
-     * @param typeName
-     *            database specific type name, may be used as a hint to
-     *            determine the right JDBC type.
-     * @param type
-     *            JDBC column type
-     * @param size
-     *            database column size (ignored if less than zero)
-     * @param scale
-     *            database column scale, i.e. the number of decimal digits
-     *            (ignored if less than zero)
-     * @param allowNulls
-     *            database column nullable parameter
-     */
-    DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls);
-
-    /**
-     * Binds an object value to PreparedStatement's numbered parameter.
-     */
-    void bindParameter(PreparedStatement statement, Object object, int pos, int sqlType, int scale)
-            throws SQLException, Exception;
-
-    /**
-     * Returns the name of the table type (as returned by
-     * <code>DatabaseMetaData.getTableTypes</code>) for a simple user table.
-     */
-    String tableTypeForTable();
-
-    /**
-     * Returns the name of the table type (as returned by
-     * <code>DatabaseMetaData.getTableTypes</code>) for a view table.
-     */
-    String tableTypeForView();
-
-    /**
-     * @since 3.0
-     */
-    MergerFactory mergerFactory();
-
-    /**
-     * Append the column type part of a "create table" to the given
-     * {@link StringBuffer}
-     * 
-     * @param sqlBuffer
-     *            the {@link StringBuffer} to append the column type to
-     * @param column
-     *            the {@link DbAttribute} defining the column to append type for
-     * @since 3.0
-     */
-    void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column);
-
-    /**
-     * @since 3.0
-     * @deprecated since 4.0 use {@link #getQuotingStrategy()}.
-     */
-    @Deprecated
-    QuotingStrategy getQuotingStrategy(boolean needQuotes);
-
-    /**
-     * Returns SQL identifier quoting strategy object
-     * 
-     * @since 4.0
-     */
-    QuotingStrategy getQuotingStrategy();
-
-    /**
-     * Allows the users to get access to the adapter decorated by a given
-     * adapter.
-     * 
-     * @since 4.0
-     */
-    DbAdapter unwrap();
-
-    /**
-     * Returns a translator factory for EJBQL to SQL translation.
-     * 
-     * @since 4.0
-     */
-    EJBQLTranslatorFactory getEjbqlTranslatorFactory();
+	/**
+	 * Returns a String used to terminate a batch in command-line tools. E.g.
+	 * ";" on Oracle or "go" on Sybase.
+	 * 
+	 * @since 1.0.4
+	 */
+	String getBatchTerminator();
+
+	/**
+	 * Returns a SelectTranslator that works with the adapter target database.
+	 * 
+	 * @since 4.0
+	 */
+	SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver);
+
+	QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler);
+
+	/**
+	 * Returns an instance of SQLAction that should handle the query.
+	 * 
+	 * @since 1.2
+	 */
+	SQLAction getAction(Query query, DataNode node);
+
+	/**
+	 * Returns true if a target database supports UNIQUE constraints.
+	 *
+	 * @since 1.1
+	 */
+	boolean supportsUniqueConstraints();
+
+	/**
+	 * Returns true if a target database supports catalogs on reverse
+	 * engineering.
+	 *
+	 * @since 4.0
+	 */
+	boolean supportsCatalogsOnReverseEngineering();
+
+	/**
+	 * Returns true if a target database supports key autogeneration. This
+	 * feature also requires JDBC3-compliant driver.
+	 * 
+	 * @since 1.2
+	 */
+	boolean supportsGeneratedKeys();
+
+	/**
+	 * Returns <code>true</code> if the target database supports batch updates.
+	 */
+	boolean supportsBatchUpdates();
+
+	boolean typeSupportsLength(int type);
+
+	/**
+	 * Returns a collection of SQL statements needed to drop a database table.
+	 * 
+	 * @since 3.0
+	 */
+	Collection<String> dropTableStatements(DbEntity table);
+
+	/**
+	 * Returns a SQL string that can be used to create database table
+	 * corresponding to <code>entity</code> parameter.
+	 */
+	String createTable(DbEntity entity);
+
+	/**
+	 * Returns a DDL string to create a unique constraint over a set of columns,
+	 * or null if the unique constraints are not supported.
+	 * 
+	 * @since 1.1
+	 */
+	String createUniqueConstraint(DbEntity source, Collection<DbAttribute> columns);
+
+	/**
+	 * Returns a SQL string that can be used to create a foreign key constraint
+	 * for the relationship, or null if foreign keys are not supported.
+	 */
+	String createFkConstraint(DbRelationship rel);
+
+	/**
+	 * Returns an array of RDBMS types that can be used with JDBC
+	 * <code>type</code>. Valid JDBC types are defined in java.sql.Types.
+	 */
+	String[] externalTypesForJdbcType(int type);
+
+	/**
+	 * Returns a map of ExtendedTypes that is used to translate values between
+	 * Java and JDBC layer.
+	 */
+	ExtendedTypeMap getExtendedTypes();
+
+	/**
+	 * Returns primary key generator associated with this DbAdapter.
+	 */
+	PkGenerator getPkGenerator();
+
+	/**
+	 * Creates and returns a DbAttribute based on supplied parameters (usually
+	 * obtained from database meta data).
+	 * 
+	 * @param name
+	 *            database column name
+	 * @param typeName
+	 *            database specific type name, may be used as a hint to
+	 *            determine the right JDBC type.
+	 * @param type
+	 *            JDBC column type
+	 * @param size
+	 *            database column size (ignored if less than zero)
+	 * @param scale
+	 *            database column scale, i.e. the number of decimal digits
+	 *            (ignored if less than zero)
+	 * @param allowNulls
+	 *            database column nullable parameter
+	 */
+	DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls);
+
+	/**
+	 * Binds an object value to PreparedStatement's numbered parameter.
+	 */
+	void bindParameter(PreparedStatement statement, Object object, int pos, int sqlType, int scale)
+			throws SQLException, Exception;
+
+	/**
+	 * Returns the name of the table type (as returned by
+	 * <code>DatabaseMetaData.getTableTypes</code>) for a simple user table.
+	 */
+	String tableTypeForTable();
+
+	/**
+	 * Returns the name of the table type (as returned by
+	 * <code>DatabaseMetaData.getTableTypes</code>) for a view table.
+	 */
+	String tableTypeForView();
+
+	/**
+	 * @since 3.0
+	 */
+	MergerFactory mergerFactory();
+
+	/**
+	 * Append the column type part of a "create table" to the given
+	 * {@link StringBuffer}
+	 * 
+	 * @param sqlBuffer
+	 *            the {@link StringBuffer} to append the column type to
+	 * @param column
+	 *            the {@link DbAttribute} defining the column to append type for
+	 * @since 3.0
+	 */
+	void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column);
+
+	/**
+	 * @since 3.0
+	 * @deprecated since 4.0 use {@link #getQuotingStrategy()}.
+	 */
+	@Deprecated
+	QuotingStrategy getQuotingStrategy(boolean needQuotes);
+
+	/**
+	 * Returns SQL identifier quoting strategy object
+	 * 
+	 * @since 4.0
+	 */
+	QuotingStrategy getQuotingStrategy();
+
+	/**
+	 * Allows the users to get access to the adapter decorated by a given
+	 * adapter.
+	 * 
+	 * @since 4.0
+	 */
+	DbAdapter unwrap();
+
+	/**
+	 * Returns a translator factory for EJBQL to SQL translation.
+	 * 
+	 * @since 4.0
+	 */
+	EJBQLTranslatorFactory getEjbqlTranslatorFactory();
 }


[8/8] cayenne git commit: CAY-1995 | Improve Select queries API

Posted by aa...@apache.org.
CAY-1995 | Improve Select queries API

* removing incorrect method-level parameterization... Select<T> already
  defines T at the class level


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/200ac46e
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/200ac46e
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/200ac46e

Branch: refs/heads/master
Commit: 200ac46e7076a6425a752204991ecb778c2680ae
Parents: 73bff10
Author: aadamchik <aa...@apache.org>
Authored: Mon May 11 01:53:44 2015 +0200
Committer: aadamchik <aa...@apache.org>
Committed: Mon May 11 01:53:44 2015 +0200

----------------------------------------------------------------------
 .../org/apache/cayenne/query/ObjectSelect.java  |   2 +-
 .../org/apache/cayenne/query/SQLSelect.java     |  58 +-
 .../java/org/apache/cayenne/query/Select.java   | 147 +++--
 .../org/apache/cayenne/query/SelectById.java    | 203 +++---
 .../org/apache/cayenne/query/SelectQuery.java   |  64 +-
 .../cayenne/remote/IncrementalSelectQuery.java  | 654 ++++++++++---------
 6 files changed, 566 insertions(+), 562 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/200ac46e/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
index 515a4c5..bf1564a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ObjectSelect.java
@@ -671,7 +671,7 @@ public class ObjectSelect<T> extends IndirectQuery implements Select<T> {
     }
 
     @Override
-    public <T> void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
+    public void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
         context.iterate((Select<T>) this, callback);
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/200ac46e/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java
index 1451bdb..4aaab66 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLSelect.java
@@ -18,6 +18,13 @@
  ****************************************************************/
 package org.apache.cayenne.query;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.DataRow;
 import org.apache.cayenne.ObjectContext;
@@ -28,13 +35,6 @@ import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.SQLResult;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
 /**
  * A selecting query based on raw SQL and featuring fluent API.
  * 
@@ -115,37 +115,37 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 		this.pageSize = QueryMetadata.PAGE_SIZE_DEFAULT;
 	}
 
-    @Override
+	@Override
 	public List<T> select(ObjectContext context) {
 		return context.select(this);
 	}
 
-    @Override
+	@Override
 	public T selectOne(ObjectContext context) {
 		return context.selectOne(this);
 	}
 
-    @Override
-    public T selectFirst(ObjectContext context) {
-        return context.selectFirst(limit(1));
-    }
+	@Override
+	public T selectFirst(ObjectContext context) {
+		return context.selectFirst(limit(1));
+	}
 
-    @Override
-    public <T> void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
-        context.iterate((Select<T>) this, callback);
-    }
+	@Override
+	public void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
+		context.iterate((Select<T>) this, callback);
+	}
 
-    @Override
-    public ResultIterator<T> iterator(ObjectContext context) {
-        return context.iterator(this);
-    }
+	@Override
+	public ResultIterator<T> iterator(ObjectContext context) {
+		return context.iterator(this);
+	}
 
-    @Override
-    public ResultBatchIterator<T> batchIterator(ObjectContext context, int size) {
-        return context.batchIterator(this, size);
-    }
+	@Override
+	public ResultBatchIterator<T> batchIterator(ObjectContext context, int size) {
+		return context.batchIterator(this, size);
+	}
 
-    public boolean isFetchingDataRows() {
+	public boolean isFetchingDataRows() {
 		return persistentType == null;
 	}
 
@@ -297,7 +297,7 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 	 * </pre>
 	 */
 	public SQLSelect<T> localCache(String... cacheGroups) {
-        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroups);
+		return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroups);
 	}
 
 	/**
@@ -309,8 +309,8 @@ public class SQLSelect<T> extends IndirectQuery implements Select<T> {
 	 * </pre>
 	 */
 	public SQLSelect<T> sharedCache(String... cacheGroups) {
-        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroups);
-    }
+		return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroups);
+	}
 
 	public QueryCacheStrategy getCacheStrategy() {
 		return cacheStrategy;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/200ac46e/cayenne-server/src/main/java/org/apache/cayenne/query/Select.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/Select.java b/cayenne-server/src/main/java/org/apache/cayenne/query/Select.java
index b4380ba..9b58085 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/Select.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/Select.java
@@ -27,84 +27,87 @@ import org.apache.cayenne.ResultIteratorCallback;
 import java.util.List;
 
 /**
- * A common interface for grouping together different kinds of queries
- * that return results. 
+ * A common interface for grouping together different kinds of queries that
+ * return results.
  */
 public interface Select<T> extends Query {
 
-    /**
-     * Selects objects using provided context.
-     * <p>
-     * Essentially the inversion of "ObjectContext.select(Select)".
-     *
-     * @since 4.0
-     */
-    <T> List<T> select(ObjectContext context);
+	/**
+	 * Selects objects using provided context.
+	 * <p>
+	 * Essentially the inversion of "ObjectContext.select(Select)".
+	 *
+	 * @since 4.0
+	 */
+	List<T> select(ObjectContext context);
 
-    /**
-     * Selects a single object using provided context. The query is expected to
-     * match zero or one object. It returns null if no objects were matched. If
-     * query matched more than one object, {@link org.apache.cayenne.CayenneRuntimeException} is
-     * thrown.
-     * <p>
-     * Essentially the inversion of "ObjectContext.selectOne(Select)".
-     *
-     * @since 4.0
-     */
-    <T> T selectOne(ObjectContext context);
+	/**
+	 * Selects a single object using provided context. The query is expected to
+	 * match zero or one object. It returns null if no objects were matched. If
+	 * query matched more than one object,
+	 * {@link org.apache.cayenne.CayenneRuntimeException} is thrown.
+	 * <p>
+	 * Essentially the inversion of "ObjectContext.selectOne(Select)".
+	 *
+	 * @since 4.0
+	 */
+	T selectOne(ObjectContext context);
 
-    /**
-     * Selects a single object using provided context. The query itself can
-     * match any number of objects, but will return only the first one. It
-     * returns null if no objects were matched.
-     * <p>
-     * If it matched more than one object, the first object from the list is
-     * returned. This makes 'selectFirst' different from
-     * {@link #selectOne(ObjectContext)}, which would throw in this situation.
-     * 'selectFirst' is useful e.g. when the query is ordered and we only want
-     * to see the first object (e.g. "most recent news article"), etc.
-     * <p>
-     * Selecting the first object via "Select.selectFirst(ObjectContext)"
-     * is more comprehensible than selecting via "ObjectContext.selectFirst(Select)",
-     * because implementations of "Select" set fetch size limit to one.
-     *
-     * @since 4.0
-     */
-    <T> T selectFirst(ObjectContext context);
+	/**
+	 * Selects a single object using provided context. The query itself can
+	 * match any number of objects, but will return only the first one. It
+	 * returns null if no objects were matched.
+	 * <p>
+	 * If it matched more than one object, the first object from the list is
+	 * returned. This makes 'selectFirst' different from
+	 * {@link #selectOne(ObjectContext)}, which would throw in this situation.
+	 * 'selectFirst' is useful e.g. when the query is ordered and we only want
+	 * to see the first object (e.g. "most recent news article"), etc.
+	 * <p>
+	 * Selecting the first object via "Select.selectFirst(ObjectContext)" is
+	 * more comprehensible than selecting via
+	 * "ObjectContext.selectFirst(Select)", because implementations of "Select"
+	 * set fetch size limit to one.
+	 *
+	 * @since 4.0
+	 */
+	T selectFirst(ObjectContext context);
 
-    /**
-     * Creates a ResultIterator based on the provided context and passes it to a
-     * callback for processing. The caller does not need to worry about closing
-     * the iterator. This method takes care of it.
-     * <p>
-     * Essentially the inversion of "ObjectContext.iterate(Select, ResultIteratorCallback)".
-     *
-     * @since 4.0
-     */
-    <T> void iterate(ObjectContext context, ResultIteratorCallback<T> callback);
+	/**
+	 * Creates a ResultIterator based on the provided context and passes it to a
+	 * callback for processing. The caller does not need to worry about closing
+	 * the iterator. This method takes care of it.
+	 * <p>
+	 * Essentially the inversion of
+	 * "ObjectContext.iterate(Select, ResultIteratorCallback)".
+	 *
+	 * @since 4.0
+	 */
+	void iterate(ObjectContext context, ResultIteratorCallback<T> callback);
 
-    /**
-     * Creates a ResultIterator based on the provided context. It is usually
-     * backed by an open result set and is useful for processing of large data
-     * sets, preserving a constant memory footprint. The caller must wrap
-     * iteration in try/finally (or try-with-resources for Java 1.7 and higher) and
-     * close the ResultIterator explicitly.
-     * Or use {@link #iterate(ObjectContext, ResultIteratorCallback)} as an alternative.
-     * <p>
-     * Essentially the inversion of "ObjectContext.iterator(Select)".
-     *
-     * @since 4.0
-     */
-    <T> ResultIterator<T> iterator(ObjectContext context);
+	/**
+	 * Creates a ResultIterator based on the provided context. It is usually
+	 * backed by an open result set and is useful for processing of large data
+	 * sets, preserving a constant memory footprint. The caller must wrap
+	 * iteration in try/finally (or try-with-resources for Java 1.7 and higher)
+	 * and close the ResultIterator explicitly. Or use
+	 * {@link #iterate(ObjectContext, ResultIteratorCallback)} as an
+	 * alternative.
+	 * <p>
+	 * Essentially the inversion of "ObjectContext.iterator(Select)".
+	 *
+	 * @since 4.0
+	 */
+	ResultIterator<T> iterator(ObjectContext context);
 
-    /**
-     * Creates a ResultBatchIterator based on the provided context and batch size. It is usually
-     * backed by an open result set and is useful for processing of large data
-     * sets, preserving a constant memory footprint. The caller must wrap
-     * iteration in try/finally (or try-with-resources for Java 1.7 and higher) and
-     * close the ResultBatchIterator explicitly.
-     *
-     * @since 4.0
-     */
-    <T> ResultBatchIterator<T> batchIterator(ObjectContext context, int size);
+	/**
+	 * Creates a ResultBatchIterator based on the provided context and batch
+	 * size. It is usually backed by an open result set and is useful for
+	 * processing of large data sets, preserving a constant memory footprint.
+	 * The caller must wrap iteration in try/finally (or try-with-resources for
+	 * Java 1.7 and higher) and close the ResultBatchIterator explicitly.
+	 *
+	 * @since 4.0
+	 */
+	ResultBatchIterator<T> batchIterator(ObjectContext context, int size);
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/200ac46e/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java
index 8b998e0..489537c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectById.java
@@ -18,6 +18,13 @@
  ****************************************************************/
 package org.apache.cayenne.query;
 
+import static java.util.Collections.singletonMap;
+import static org.apache.cayenne.exp.ExpressionFactory.matchAllDbExp;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.DataRow;
 import org.apache.cayenne.ObjectContext;
@@ -29,13 +36,6 @@ import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.ObjEntity;
 
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import static java.util.Collections.singletonMap;
-import static org.apache.cayenne.exp.ExpressionFactory.matchAllDbExp;
-
 /**
  * A query to select single objects by id.
  * 
@@ -129,127 +129,126 @@ public class SelectById<T> extends IndirectQuery implements Select<T> {
 		}
 	}
 
+	@Override
+	public List<T> select(ObjectContext context) {
+		return context.select(this);
+	}
 
-    @Override
-    public List<T> select(ObjectContext context) {
-        return context.select(this);
-    }
-
-    /**
-     * Since we are selecting by ID, multiple matched objects likely
-     * indicate a database referential integrity problem.
-     */
-    @Override
+	/**
+	 * Since we are selecting by ID, multiple matched objects likely indicate a
+	 * database referential integrity problem.
+	 */
+	@Override
 	public T selectOne(ObjectContext context) {
 		return context.selectOne(this);
 	}
 
-    /**
-     * Since we are selecting by ID, we don't need to limit fetch size.
-     * Multiple matched objects likely indicate a database referential integrity problem.
-     */
-    @Override
-    public T selectFirst(ObjectContext context) {
-        return selectFirst(context);
-    }
-
-    @Override
-    public <T> void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
-        context.iterate((Select<T>) this, callback);
-    }
-
-    @Override
-    public ResultIterator<T> iterator(ObjectContext context) {
-        return context.iterator(this);
-    }
-
-    @Override
-    public ResultBatchIterator<T> batchIterator(ObjectContext context, int size) {
-        return context.batchIterator(this, size);
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "local" cache when
-     * running the query. This is a short-hand notation for:
-     *
-     * <pre>
-     * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroups);
-     * </pre>
-     *
-     * @since 4.0.M3
-     */
-    public SelectById<T> localCache(String... cacheGroups) {
-        return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroups);
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "shared" cache when
-     * running the query. This is a short-hand notation for:
-     *
-     * <pre>
-     * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroups);
-     * </pre>
-     *
-     * @since 4.0.M3
-     */
-    public SelectById<T> sharedCache(String... cacheGroups) {
-        return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroups);
-    }
-
-    /**
-     * Instructs Cayenne to look for query results in the "local" cache when
-     * running the query. This is a short-hand notation for:
-     *
-     * @deprecated since 4.0.M3 use {@link #localCache(String...)}
-     */
-    @Deprecated
+	/**
+	 * Since we are selecting by ID, we don't need to limit fetch size. Multiple
+	 * matched objects likely indicate a database referential integrity problem.
+	 */
+	@Override
+	public T selectFirst(ObjectContext context) {
+		return selectFirst(context);
+	}
+
+	@Override
+	public void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
+		context.iterate((Select<T>) this, callback);
+	}
+
+	@Override
+	public ResultIterator<T> iterator(ObjectContext context) {
+		return context.iterator(this);
+	}
+
+	@Override
+	public ResultBatchIterator<T> batchIterator(ObjectContext context, int size) {
+		return context.batchIterator(this, size);
+	}
+
+	/**
+	 * Instructs Cayenne to look for query results in the "local" cache when
+	 * running the query. This is a short-hand notation for:
+	 *
+	 * <pre>
+	 * query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroups);
+	 * </pre>
+	 *
+	 * @since 4.0.M3
+	 */
+	public SelectById<T> localCache(String... cacheGroups) {
+		return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroups);
+	}
+
+	/**
+	 * Instructs Cayenne to look for query results in the "shared" cache when
+	 * running the query. This is a short-hand notation for:
+	 *
+	 * <pre>
+	 * query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroups);
+	 * </pre>
+	 *
+	 * @since 4.0.M3
+	 */
+	public SelectById<T> sharedCache(String... cacheGroups) {
+		return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroups);
+	}
+
+	/**
+	 * Instructs Cayenne to look for query results in the "local" cache when
+	 * running the query. This is a short-hand notation for:
+	 *
+	 * @deprecated since 4.0.M3 use {@link #localCache(String...)}
+	 */
+	@Deprecated
 	public SelectById<T> useLocalCache(String... cacheGroups) {
-        return localCache(cacheGroups);
+		return localCache(cacheGroups);
 	}
 
-    /**
-     * Instructs Cayenne to look for query results in the "shared" cache when
-     * running the query. This is a short-hand notation for:
-     *
-     * @deprecated since 4.0.M3 use {@link #sharedCache(String...)}
-     */
-    @Deprecated
+	/**
+	 * Instructs Cayenne to look for query results in the "shared" cache when
+	 * running the query. This is a short-hand notation for:
+	 *
+	 * @deprecated since 4.0.M3 use {@link #sharedCache(String...)}
+	 */
+	@Deprecated
 	public SelectById<T> useSharedCache(String... cacheGroups) {
-        return sharedCache(cacheGroups);
+		return sharedCache(cacheGroups);
 	}
 
 	public QueryCacheStrategy getCacheStrategy() {
 		return cacheStrategy;
 	}
 
-    public SelectById<T> cacheStrategy(QueryCacheStrategy strategy, String... cacheGroups) {
-        if (this.cacheStrategy != strategy) {
-            this.cacheStrategy = strategy;
-            this.replacementQuery = null;
-        }
+	public SelectById<T> cacheStrategy(QueryCacheStrategy strategy, String... cacheGroups) {
+		if (this.cacheStrategy != strategy) {
+			this.cacheStrategy = strategy;
+			this.replacementQuery = null;
+		}
 
-        return cacheGroups(cacheGroups);
-    }
+		return cacheGroups(cacheGroups);
+	}
 
 	public String[] getCacheGroups() {
 		return cacheGroups;
 	}
 
 	public SelectById<T> cacheGroups(String... cacheGroups) {
-        this.cacheGroups = cacheGroups != null && cacheGroups.length > 0 ? cacheGroups : null;
-        this.replacementQuery = null;
+		this.cacheGroups = cacheGroups != null && cacheGroups.length > 0 ? cacheGroups : null;
+		this.replacementQuery = null;
 		return this;
 	}
 
-    public SelectById<T> cacheGroups(Collection<String> cacheGroups) {
+	public SelectById<T> cacheGroups(Collection<String> cacheGroups) {
 
-        if (cacheGroups == null) {
-            return cacheGroups((String) null);
-        }
+		if (cacheGroups == null) {
+			return cacheGroups((String) null);
+		}
 
-        String[] array = new String[cacheGroups.size()];
-        return cacheGroups(cacheGroups.toArray(array));
-    }
+		String[] array = new String[cacheGroups.size()];
+		return cacheGroups(cacheGroups.toArray(array));
+	}
 
 	public boolean isFetchingDataRows() {
 		return fetchingDataRows;
@@ -314,7 +313,7 @@ public class SelectById<T> extends IndirectQuery implements Select<T> {
 		prefetches.addPath(path).setSemantics(semantics);
 		return this;
 	}
-	
+
 	public PrefetchTreeNode getPrefetches() {
 		return prefetches;
 	}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/200ac46e/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
index db012b3..793d3d8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
@@ -19,6 +19,12 @@
 
 package org.apache.cayenne.query;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.cayenne.DataRow;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.ResultBatchIterator;
@@ -34,12 +40,6 @@ import org.apache.cayenne.map.Procedure;
 import org.apache.cayenne.util.XMLEncoder;
 import org.apache.cayenne.util.XMLSerializable;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
 /**
  * A query that selects persistent objects of a certain type or "raw data" (aka
  * DataRows). Supports expression qualifier, multiple orderings and a number of
@@ -308,38 +308,38 @@ public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery,
 		this.setQualifier(qualifier);
 	}
 
-    @Override
-    public <T> List<T> select(ObjectContext context) {
-        return context.performQuery(this);
-    }
+	@Override
+	public List<T> select(ObjectContext context) {
+		return context.select(this);
+	}
 
-    @Override
-    public T selectOne(ObjectContext context) {
-        return context.selectOne(this);
-    }
+	@Override
+	public T selectOne(ObjectContext context) {
+		return context.selectOne(this);
+	}
 
-    @Override
-    public T selectFirst(ObjectContext context) {
-        setFetchLimit(1);
-        return context.selectFirst(this);
-    }
+	@Override
+	public T selectFirst(ObjectContext context) {
+		setFetchLimit(1);
+		return context.selectFirst(this);
+	}
 
-    @Override
-    public <T> void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
-        context.iterate((Select<T>) this, callback);
-    }
+	@Override
+	public void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
+		context.iterate((Select<T>) this, callback);
+	}
 
-    @Override
-    public ResultIterator<T> iterator(ObjectContext context) {
-        return context.iterator(this);
-    }
+	@Override
+	public ResultIterator<T> iterator(ObjectContext context) {
+		return context.iterator(this);
+	}
 
-    @Override
-    public ResultBatchIterator<T> batchIterator(ObjectContext context, int size) {
-        return context.batchIterator(this, size);
-    }
+	@Override
+	public ResultBatchIterator<T> batchIterator(ObjectContext context, int size) {
+		return context.batchIterator(this, size);
+	}
 
-    /**
+	/**
 	 * @since 1.2
 	 */
 	@Override

http://git-wip-us.apache.org/repos/asf/cayenne/blob/200ac46e/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
index 7da3793..846b5d1 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
@@ -18,6 +18,10 @@
  ****************************************************************/
 package org.apache.cayenne.remote;
 
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.ResultBatchIterator;
 import org.apache.cayenne.ResultIterator;
@@ -42,336 +46,334 @@ import org.apache.cayenne.query.SortOrder;
 import org.apache.cayenne.reflect.ClassDescriptor;
 import org.apache.cayenne.util.XMLEncoder;
 
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
 /**
- * A SelectQuery decorator that overrides the metadata to ensure that query result is
- * cached on the server, so that subranges could be retrieved at a later time. Note that a
- * special decorator that is a subclass of SelectQuery is needed so that
- * {@link IncrementalFaultList} on the server-side could apply SelectQuery-specific
- * optimizations.
+ * A SelectQuery decorator that overrides the metadata to ensure that query
+ * result is cached on the server, so that subranges could be retrieved at a
+ * later time. Note that a special decorator that is a subclass of SelectQuery
+ * is needed so that {@link IncrementalFaultList} on the server-side could apply
+ * SelectQuery-specific optimizations.
  * 
  * @since 3.0
  */
 class IncrementalSelectQuery<T> extends SelectQuery<T> {
 
-    private SelectQuery<T> query;
-    private String cacheKey;
-
-    IncrementalSelectQuery(SelectQuery<T> delegate, String cacheKey) {
-        this.query = delegate;
-        this.cacheKey = cacheKey;
-    }
-
-    @Override
-    public QueryMetadata getMetaData(EntityResolver resolver) {
-        final QueryMetadata metadata = query.getMetaData(resolver);
-
-        // the way paginated queries work on the server is that they are never cached
-        // (IncrementalFaultList interception happens before cache interception). So
-        // overriding caching settings in the metadata will only affect
-        // ClientServerChannel behavior
-
-        return new QueryMetadata() {
-
-            public Query getOrginatingQuery() {
-                return null;
-            }
-
-            public String getCacheKey() {
-                return cacheKey;
-            }
-
-            public List<Object> getResultSetMapping() {
-                return metadata.getResultSetMapping();
-            }
-
-            public String[] getCacheGroups() {
-                return metadata.getCacheGroups();
-            }
-
-            public QueryCacheStrategy getCacheStrategy() {
-                return metadata.getCacheStrategy();
-            }
-
-            public DataMap getDataMap() {
-                return metadata.getDataMap();
-            }
-
-            public DbEntity getDbEntity() {
-                return metadata.getDbEntity();
-            }
-
-            public int getFetchLimit() {
-                return metadata.getFetchLimit();
-            }
-
-            public int getFetchOffset() {
-                return metadata.getFetchOffset();
-            }
-
-            public ObjEntity getObjEntity() {
-                return metadata.getObjEntity();
-            }
-
-            public ClassDescriptor getClassDescriptor() {
-                return metadata.getClassDescriptor();
-            }
-
-            public int getPageSize() {
-                return metadata.getPageSize();
-            }
-
-            public PrefetchTreeNode getPrefetchTree() {
-                return metadata.getPrefetchTree();
-            }
-
-            public Procedure getProcedure() {
-                return metadata.getProcedure();
-            }
-
-            public Map<String, String> getPathSplitAliases() {
-                return metadata.getPathSplitAliases();
-            }
-
-            public boolean isFetchingDataRows() {
-                return metadata.isFetchingDataRows();
-            }
-
-            public boolean isRefreshingObjects() {
-                return metadata.isRefreshingObjects();
-            }
-
-            public int getStatementFetchSize() {
-                return metadata.getStatementFetchSize();
-            }
-        };
-    }
-
-    @Override
-    public void addOrdering(Ordering ordering) {
-        query.addOrdering(ordering);
-    }
-
-    @Override
-    public void addOrdering(String sortPathSpec, SortOrder order) {
-        query.addOrdering(sortPathSpec, order);
-    }
-
-    @Override
-    public void addOrderings(Collection<? extends Ordering> orderings) {
-    	query.addOrderings(orderings);
-    }
-
-    @Override
-    public PrefetchTreeNode addPrefetch(String prefetchPath) {
-        return query.addPrefetch(prefetchPath);
-    }
-
-    @Override
-    public void andQualifier(Expression e) {
-        query.andQualifier(e);
-    }
-
-    @Override
-    public void clearOrderings() {
-        query.clearOrderings();
-    }
-
-    @Override
-    public void clearPrefetches() {
-        query.clearPrefetches();
-    }
-
-    @Override
-    public SelectQuery<T> createQuery(Map parameters) {
-        return query.createQuery(parameters);
-    }
-
-    @Override
-    public SQLAction createSQLAction(SQLActionVisitor visitor) {
-        return query.createSQLAction(visitor);
-    }
-
-    @Override
-    public void encodeAsXML(XMLEncoder encoder) {
-        query.encodeAsXML(encoder);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        return query.equals(obj);
-    }
-
-    @Override
-    public String[] getCacheGroups() {
-        return query.getCacheGroups();
-    }
-
-    @Override
-    public int getFetchLimit() {
-        return query.getFetchLimit();
-    }
-
-    @Override
-    public String getName() {
-        return query.getName();
-    }
-
-    @Override
-    public List<Ordering> getOrderings() {
-        return query.getOrderings();
-    }
-
-    @Override
-    public int getPageSize() {
-        return query.getPageSize();
-    }
-
-    @Override
-    public PrefetchTreeNode getPrefetchTree() {
-        return query.getPrefetchTree();
-    }
-
-    @Override
-    public Expression getQualifier() {
-        return query.getQualifier();
-    }
-
-    @Override
-    public Object getRoot() {
-        return query.getRoot();
-    }
-
-    @Override
-    public int hashCode() {
-        return query.hashCode();
-    }
-
-    @Override
-    public void initWithProperties(Map properties) {
-        query.initWithProperties(properties);
-    }
-
-    @Override
-    public boolean isDistinct() {
-        return query.isDistinct();
-    }
-
-    @Override
-    public boolean isFetchingDataRows() {
-        return query.isFetchingDataRows();
-    }
-
-    @Override
-    public void orQualifier(Expression e) {
-        query.orQualifier(e);
-    }
-
-    @Override
-    public SelectQuery<T> queryWithParameters(Map parameters, boolean pruneMissing) {
-        return query.queryWithParameters(parameters, pruneMissing);
-    }
-
-    @Override
-    public SelectQuery<T> queryWithParameters(Map parameters) {
-        return query.queryWithParameters(parameters);
-    }
-
-    @Override
-    public void removeOrdering(Ordering ordering) {
-        query.removeOrdering(ordering);
-    }
-
-    @Override
-    public void removePrefetch(String prefetchPath) {
-        query.removePrefetch(prefetchPath);
-    }
-
-    @Override
-    public void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery) {
-        query.route(router, resolver, substitutedQuery);
-    }
-
-    @Override
-    public void setCacheGroups(String... cachGroups) {
-        query.setCacheGroups(cachGroups);
-    }
-
-    @Override
-    public void setDistinct(boolean distinct) {
-        query.setDistinct(distinct);
-    }
-
-    @Override
-    public void setFetchingDataRows(boolean flag) {
-        query.setFetchingDataRows(flag);
-    }
-
-    @Override
-    public void setFetchLimit(int fetchLimit) {
-        query.setFetchLimit(fetchLimit);
-    }
-
-    @Override
-    public void setName(String name) {
-        query.setName(name);
-    }
-
-    @Override
-    public void setPageSize(int pageSize) {
-        query.setPageSize(pageSize);
-    }
-
-    @Override
-    public void setPrefetchTree(PrefetchTreeNode prefetchTree) {
-        query.setPrefetchTree(prefetchTree);
-    }
-
-    @Override
-    public void setQualifier(Expression qualifier) {
-        query.setQualifier(qualifier);
-    }
-
-    @Override
-    public void setRoot(Object value) {
-        query.setRoot(value);
-    }
-
-    @Override
-    public String toString() {
-        return query.toString();
-    }
-
-    @Override
-    public <T> List<T> select(ObjectContext context) {
-        return query.select(context);
-    }
-
-    @Override
-    public T selectOne(ObjectContext context) {
-        return query.selectOne(context);
-    }
-
-    @Override
-    public T selectFirst(ObjectContext context) {
-        return query.selectFirst(context);
-    }
-
-    @Override
-    public <T> void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
-        query.iterate(context, callback);
-    }
-
-    @Override
-    public ResultIterator<T> iterator(ObjectContext context) {
-        return query.iterator(context);
-    }
-
-    @Override
-    public ResultBatchIterator<T> batchIterator(ObjectContext context, int size) {
-        return query.batchIterator(context, size);
-    }
+	private SelectQuery<T> query;
+	private String cacheKey;
+
+	IncrementalSelectQuery(SelectQuery<T> delegate, String cacheKey) {
+		this.query = delegate;
+		this.cacheKey = cacheKey;
+	}
+
+	@Override
+	public QueryMetadata getMetaData(EntityResolver resolver) {
+		final QueryMetadata metadata = query.getMetaData(resolver);
+
+		// the way paginated queries work on the server is that they are never
+		// cached
+		// (IncrementalFaultList interception happens before cache
+		// interception). So
+		// overriding caching settings in the metadata will only affect
+		// ClientServerChannel behavior
+
+		return new QueryMetadata() {
+
+			public Query getOrginatingQuery() {
+				return null;
+			}
+
+			public String getCacheKey() {
+				return cacheKey;
+			}
+
+			public List<Object> getResultSetMapping() {
+				return metadata.getResultSetMapping();
+			}
+
+			public String[] getCacheGroups() {
+				return metadata.getCacheGroups();
+			}
+
+			public QueryCacheStrategy getCacheStrategy() {
+				return metadata.getCacheStrategy();
+			}
+
+			public DataMap getDataMap() {
+				return metadata.getDataMap();
+			}
+
+			public DbEntity getDbEntity() {
+				return metadata.getDbEntity();
+			}
+
+			public int getFetchLimit() {
+				return metadata.getFetchLimit();
+			}
+
+			public int getFetchOffset() {
+				return metadata.getFetchOffset();
+			}
+
+			public ObjEntity getObjEntity() {
+				return metadata.getObjEntity();
+			}
+
+			public ClassDescriptor getClassDescriptor() {
+				return metadata.getClassDescriptor();
+			}
+
+			public int getPageSize() {
+				return metadata.getPageSize();
+			}
+
+			public PrefetchTreeNode getPrefetchTree() {
+				return metadata.getPrefetchTree();
+			}
+
+			public Procedure getProcedure() {
+				return metadata.getProcedure();
+			}
+
+			public Map<String, String> getPathSplitAliases() {
+				return metadata.getPathSplitAliases();
+			}
+
+			public boolean isFetchingDataRows() {
+				return metadata.isFetchingDataRows();
+			}
+
+			public boolean isRefreshingObjects() {
+				return metadata.isRefreshingObjects();
+			}
+
+			public int getStatementFetchSize() {
+				return metadata.getStatementFetchSize();
+			}
+		};
+	}
+
+	@Override
+	public void addOrdering(Ordering ordering) {
+		query.addOrdering(ordering);
+	}
+
+	@Override
+	public void addOrdering(String sortPathSpec, SortOrder order) {
+		query.addOrdering(sortPathSpec, order);
+	}
+
+	@Override
+	public void addOrderings(Collection<? extends Ordering> orderings) {
+		query.addOrderings(orderings);
+	}
+
+	@Override
+	public PrefetchTreeNode addPrefetch(String prefetchPath) {
+		return query.addPrefetch(prefetchPath);
+	}
+
+	@Override
+	public void andQualifier(Expression e) {
+		query.andQualifier(e);
+	}
+
+	@Override
+	public void clearOrderings() {
+		query.clearOrderings();
+	}
+
+	@Override
+	public void clearPrefetches() {
+		query.clearPrefetches();
+	}
+
+	@Override
+	public SelectQuery<T> createQuery(Map parameters) {
+		return query.createQuery(parameters);
+	}
+
+	@Override
+	public SQLAction createSQLAction(SQLActionVisitor visitor) {
+		return query.createSQLAction(visitor);
+	}
+
+	@Override
+	public void encodeAsXML(XMLEncoder encoder) {
+		query.encodeAsXML(encoder);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		return query.equals(obj);
+	}
+
+	@Override
+	public String[] getCacheGroups() {
+		return query.getCacheGroups();
+	}
+
+	@Override
+	public int getFetchLimit() {
+		return query.getFetchLimit();
+	}
+
+	@Override
+	public String getName() {
+		return query.getName();
+	}
+
+	@Override
+	public List<Ordering> getOrderings() {
+		return query.getOrderings();
+	}
+
+	@Override
+	public int getPageSize() {
+		return query.getPageSize();
+	}
+
+	@Override
+	public PrefetchTreeNode getPrefetchTree() {
+		return query.getPrefetchTree();
+	}
+
+	@Override
+	public Expression getQualifier() {
+		return query.getQualifier();
+	}
+
+	@Override
+	public Object getRoot() {
+		return query.getRoot();
+	}
+
+	@Override
+	public int hashCode() {
+		return query.hashCode();
+	}
+
+	@Override
+	public void initWithProperties(Map properties) {
+		query.initWithProperties(properties);
+	}
+
+	@Override
+	public boolean isDistinct() {
+		return query.isDistinct();
+	}
+
+	@Override
+	public boolean isFetchingDataRows() {
+		return query.isFetchingDataRows();
+	}
+
+	@Override
+	public void orQualifier(Expression e) {
+		query.orQualifier(e);
+	}
+
+	@Override
+	public SelectQuery<T> queryWithParameters(Map parameters, boolean pruneMissing) {
+		return query.queryWithParameters(parameters, pruneMissing);
+	}
+
+	@Override
+	public SelectQuery<T> queryWithParameters(Map parameters) {
+		return query.queryWithParameters(parameters);
+	}
+
+	@Override
+	public void removeOrdering(Ordering ordering) {
+		query.removeOrdering(ordering);
+	}
+
+	@Override
+	public void removePrefetch(String prefetchPath) {
+		query.removePrefetch(prefetchPath);
+	}
+
+	@Override
+	public void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery) {
+		query.route(router, resolver, substitutedQuery);
+	}
+
+	@Override
+	public void setCacheGroups(String... cachGroups) {
+		query.setCacheGroups(cachGroups);
+	}
+
+	@Override
+	public void setDistinct(boolean distinct) {
+		query.setDistinct(distinct);
+	}
+
+	@Override
+	public void setFetchingDataRows(boolean flag) {
+		query.setFetchingDataRows(flag);
+	}
+
+	@Override
+	public void setFetchLimit(int fetchLimit) {
+		query.setFetchLimit(fetchLimit);
+	}
+
+	@Override
+	public void setName(String name) {
+		query.setName(name);
+	}
+
+	@Override
+	public void setPageSize(int pageSize) {
+		query.setPageSize(pageSize);
+	}
+
+	@Override
+	public void setPrefetchTree(PrefetchTreeNode prefetchTree) {
+		query.setPrefetchTree(prefetchTree);
+	}
+
+	@Override
+	public void setQualifier(Expression qualifier) {
+		query.setQualifier(qualifier);
+	}
+
+	@Override
+	public void setRoot(Object value) {
+		query.setRoot(value);
+	}
+
+	@Override
+	public String toString() {
+		return query.toString();
+	}
+
+	@Override
+	public List<T> select(ObjectContext context) {
+		return query.select(context);
+	}
+
+	@Override
+	public T selectOne(ObjectContext context) {
+		return query.selectOne(context);
+	}
+
+	@Override
+	public T selectFirst(ObjectContext context) {
+		return query.selectFirst(context);
+	}
+
+	@Override
+	public void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
+		query.iterate(context, callback);
+	}
+
+	@Override
+	public ResultIterator<T> iterator(ObjectContext context) {
+		return query.iterator(context);
+	}
+
+	@Override
+	public ResultBatchIterator<T> batchIterator(ObjectContext context, int size) {
+		return query.batchIterator(context, size);
+	}
 }


[6/8] cayenne git commit: CAY-2009 Non-blocking connection pool

Posted by aa...@apache.org.
CAY-2009 Non-blocking connection pool

fixing a problem with manager working on exhausted pool


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/4f8c1142
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/4f8c1142
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/4f8c1142

Branch: refs/heads/master
Commit: 4f8c1142a9134681d589333b714aab206ca2e05f
Parents: 741ad3b
Author: aadamchik <aa...@apache.org>
Authored: Sun May 10 19:20:51 2015 -0400
Committer: aadamchik <aa...@apache.org>
Committed: Sun May 10 19:20:51 2015 -0400

----------------------------------------------------------------------
 .../datasource/ManagedPoolingDataSource.java    |  8 ++++---
 .../cayenne/datasource/PoolingDataSource.java   |  4 +++-
 .../datasource/PoolingDataSourceTest.java       | 22 ++++++++++++++++++++
 3 files changed, 30 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/4f8c1142/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java
index 182ad75..80c3db8 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/ManagedPoolingDataSource.java
@@ -40,11 +40,13 @@ public class ManagedPoolingDataSource implements DataSource, ScopeEventListener
 	private DataSource dataSource;
 
 	public ManagedPoolingDataSource(PoolingDataSource dataSource) {
+		// wake every 2 minutes...
+		this(dataSource, 120000);
+	}
 
+	public ManagedPoolingDataSource(PoolingDataSource dataSource, long managerWakeTime) {
 		this.dataSource = dataSource;
-
-		// wake every 2 minutes...
-		this.dataSourceManager = new PoolingDataSourceManager(dataSource, 120000);
+		this.dataSourceManager = new PoolingDataSourceManager(dataSource, managerWakeTime);
 
 		dataSourceManager.start();
 	}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/4f8c1142/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSource.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSource.java b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSource.java
index f760ec1..9d3b5cf 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSource.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/datasource/PoolingDataSource.java
@@ -169,7 +169,9 @@ public class PoolingDataSource implements DataSource {
 
 			try {
 				PoolAwareConnection c = createUnchecked();
-				reclaim(c);
+				if (c != null) {
+					reclaim(c);
+				}
 			} catch (SQLException e) {
 				LOGGER.info("Error creating new connection when managing connection pool, ignoring", e);
 			}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/4f8c1142/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceTest.java b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceTest.java
index 25b7e7e..5cbfa4c 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/datasource/PoolingDataSourceTest.java
@@ -126,6 +126,28 @@ public class PoolingDataSourceTest {
 	}
 
 	@Test
+	public void testManagePool_Empty() throws SQLException {
+
+		int max = 5;
+
+		params.setMinConnections(1);
+		params.setMaxConnections(max);
+		PoolingDataSource ds = new PoolingDataSource(nonPooling, params);
+
+		// opening and closing 'max' connections should fill the pool to the
+		// top...
+		Connection[] open = new Connection[max];
+		for (int i = 0; i < max; i++) {
+			open[i] = ds.getConnection();
+		}
+
+		// all connections are in use, so managePool should do nothing
+		assertEquals(max, ds.poolSize());
+		ds.managePool();
+		assertEquals(max, ds.poolSize());
+	}
+
+	@Test
 	public void testValidateUnchecked() {
 
 		final PoolAwareConnection[] connections = validConnections(4);


[2/8] cayenne git commit: CAY-2007 Refactoring SelectTranslator for better extensibility

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
index 05904d8..22e4f6f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
@@ -19,10 +19,19 @@
 
 package org.apache.cayenne.dba.postgres;
 
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
@@ -36,19 +45,13 @@ import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.sql.Types;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
 /**
  * DbAdapter implementation for <a href="http://www.postgresql.org">PostgreSQL
  * RDBMS </a>. Sample connection settings to use with PostgreSQL are shown
@@ -63,200 +66,212 @@ import java.util.List;
  */
 public class PostgresAdapter extends JdbcAdapter {
 
-    public static final String BYTEA = "bytea";
-
-    public PostgresAdapter(@Inject RuntimeProperties runtimeProperties,
-            @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-            @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-            @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject ResourceLocator resourceLocator) {
-        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
-        setSupportsBatchUpdates(true);
-    }
-
-    /**
-     * Uses PostgresActionBuilder to create the right action.
-     * 
-     * @since 1.2
-     */
-    @Override
-    public SQLAction getAction(Query query, DataNode node) {
-        return query.createSQLAction(new PostgresActionBuilder(node));
-    }
-
-    /**
-     * Installs appropriate ExtendedTypes as converters for passing values
-     * between JDBC and Java layers.
-     */
-    @Override
-    protected void configureExtendedTypes(ExtendedTypeMap map) {
-
-        super.configureExtendedTypes(map);
-
-        map.registerType(new CharType(true, false));
-        map.registerType(new PostgresByteArrayType(true, true));
-    }
-
-    @Override
-    public DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls) {
-
-        // "bytea" maps to pretty much any binary type, so
-        // it is up to us to select the most sensible default.
-        // And the winner is LONGVARBINARY
-        if (BYTEA.equalsIgnoreCase(typeName)) {
-            type = Types.LONGVARBINARY;
-        }
-        // oid is returned as INTEGER, need to make it BLOB
-        else if ("oid".equals(typeName)) {
-            type = Types.BLOB;
-        }
-        // somehow the driver reverse-engineers "text" as VARCHAR, must be CLOB
-        else if ("text".equalsIgnoreCase(typeName)) {
-            type = Types.CLOB;
-        }
-
-        return super.buildAttribute(name, typeName, type, size, scale, allowNulls);
-    }
-
-    @Override
-    public void bindParameter(PreparedStatement statement, Object object, int pos, int sqlType, int scale) throws SQLException, Exception {
-        super.bindParameter(statement, object, pos, mapNTypes(sqlType), scale);
-    }
-
-    private int mapNTypes(int sqlType) {
-        switch (sqlType) {
-            case Types.NCHAR : return Types.CHAR;
-            case Types.NCLOB : return Types.CLOB;
-            case Types.NVARCHAR : return Types.VARCHAR;
-            case Types.LONGNVARCHAR : return Types.LONGVARCHAR;
-
-            default:
-                return sqlType;
-        }
-    }
-
-    /**
-     * Customizes table creating procedure for PostgreSQL. One difference with
-     * generic implementation is that "bytea" type has no explicit length unlike
-     * similar binary types in other databases.
-     * 
-     * @since 1.0.2
-     */
-    @Override
-    public String createTable(DbEntity ent) {
-
-        QuotingStrategy context = getQuotingStrategy();
-        StringBuilder buf = new StringBuilder();
-        buf.append("CREATE TABLE ").append(context.quotedFullyQualifiedName(ent)).append(" (");
-
-        // columns
-        Iterator<DbAttribute> it = ent.getAttributes().iterator();
-        boolean first = true;
-        while (it.hasNext()) {
-            if (first) {
-                first = false;
-            } else {
-                buf.append(", ");
-            }
-
-            createAttribute(ent, context, buf, it.next());
-        }
-
-        // primary key clause
-        Iterator<DbAttribute> pkit = ent.getPrimaryKeys().iterator();
-        if (pkit.hasNext()) {
-            if (first) {
-                first = false;
-            } else {
-                buf.append(", ");
-            }
-
-            buf.append("PRIMARY KEY (");
-            boolean firstPk = true;
-            while (pkit.hasNext()) {
-                if (firstPk) {
-                    firstPk = false;
-                } else {
-                    buf.append(", ");
-                }
-
-                DbAttribute at = pkit.next();
-                buf.append(context.quotedName(at));
-            }
-            buf.append(')');
-        }
-        buf.append(')');
-        return buf.toString();
-    }
-
-    private void createAttribute(DbEntity ent, QuotingStrategy context, StringBuilder buf, DbAttribute at) {
-        // attribute may not be fully valid, do a simple check
-        if (at.getType() == TypesMapping.NOT_DEFINED) {
-            throw new CayenneRuntimeException("Undefined type for attribute '" + ent.getFullyQualifiedName() + "."
-                    + at.getName() + "'.");
-        }
-
-        String[] types = externalTypesForJdbcType(at.getType());
-        if (types == null || types.length == 0) {
-            throw new CayenneRuntimeException("Undefined type for attribute '" + ent.getFullyQualifiedName() + "."
-                    + at.getName() + "': " + at.getType());
-        }
-
-        buf.append(context.quotedName(at))
-           .append(' ').append(types[0]).append(sizeAndPrecision(this, at))
-           .append(at.isMandatory() ? " NOT" : "").append(" NULL");
-    }
-
-    @Override
-    public boolean typeSupportsLength(int type) {
-        // "bytea" type does not support length
-        String[] externalTypes = externalTypesForJdbcType(type);
-        if (externalTypes != null && externalTypes.length > 0) {
-            for (String externalType : externalTypes) {
-                if (BYTEA.equalsIgnoreCase(externalType)) {
-                    return false;
-                }
-            }
-        }
-
-        return super.typeSupportsLength(type);
-    }
-
-    /**
-     * Adds the CASCADE option to the DROP TABLE clause.
-     */
-    @Override
-    public Collection<String> dropTableStatements(DbEntity table) {
-        QuotingStrategy context = getQuotingStrategy();
-        return Collections.singleton("DROP TABLE " + context.quotedFullyQualifiedName(table) + " CASCADE");
-    }
-
-    /**
-     * Returns a trimming translator.
-     */
-    @Override
-    public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-        QualifierTranslator translator = new PostgresQualifierTranslator(queryAssembler);
-        translator.setCaseInsensitive(caseInsensitiveCollations);
-        return translator;
-    }
-
-    /**
-     * @see JdbcAdapter#createPkGenerator()
-     */
-    @Override
-    protected PkGenerator createPkGenerator() {
-        return new PostgresPkGenerator(this);
-    }
-
-    @Override
-    public MergerFactory mergerFactory() {
-        return new PostgresMergerFactory();
-    }
-
-    @Override
-    public boolean supportsCatalogsOnReverseEngineering() {
-        return false;
-    }
+	public static final String BYTEA = "bytea";
+
+	public PostgresAdapter(@Inject RuntimeProperties runtimeProperties,
+			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+			@Inject ResourceLocator resourceLocator) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+		setSupportsBatchUpdates(true);
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
+		return new PostgresSelectTranslator(query, this, entityResolver);
+	}
+
+	/**
+	 * Uses PostgresActionBuilder to create the right action.
+	 * 
+	 * @since 1.2
+	 */
+	@Override
+	public SQLAction getAction(Query query, DataNode node) {
+		return query.createSQLAction(new PostgresActionBuilder(node));
+	}
+
+	/**
+	 * Installs appropriate ExtendedTypes as converters for passing values
+	 * between JDBC and Java layers.
+	 */
+	@Override
+	protected void configureExtendedTypes(ExtendedTypeMap map) {
+
+		super.configureExtendedTypes(map);
+
+		map.registerType(new CharType(true, false));
+		map.registerType(new PostgresByteArrayType(true, true));
+	}
+
+	@Override
+	public DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls) {
+
+		// "bytea" maps to pretty much any binary type, so
+		// it is up to us to select the most sensible default.
+		// And the winner is LONGVARBINARY
+		if (BYTEA.equalsIgnoreCase(typeName)) {
+			type = Types.LONGVARBINARY;
+		}
+		// oid is returned as INTEGER, need to make it BLOB
+		else if ("oid".equals(typeName)) {
+			type = Types.BLOB;
+		}
+		// somehow the driver reverse-engineers "text" as VARCHAR, must be CLOB
+		else if ("text".equalsIgnoreCase(typeName)) {
+			type = Types.CLOB;
+		}
+
+		return super.buildAttribute(name, typeName, type, size, scale, allowNulls);
+	}
+
+	@Override
+	public void bindParameter(PreparedStatement statement, Object object, int pos, int sqlType, int scale)
+			throws SQLException, Exception {
+		super.bindParameter(statement, object, pos, mapNTypes(sqlType), scale);
+	}
+
+	private int mapNTypes(int sqlType) {
+		switch (sqlType) {
+		case Types.NCHAR:
+			return Types.CHAR;
+		case Types.NCLOB:
+			return Types.CLOB;
+		case Types.NVARCHAR:
+			return Types.VARCHAR;
+		case Types.LONGNVARCHAR:
+			return Types.LONGVARCHAR;
+
+		default:
+			return sqlType;
+		}
+	}
+
+	/**
+	 * Customizes table creating procedure for PostgreSQL. One difference with
+	 * generic implementation is that "bytea" type has no explicit length unlike
+	 * similar binary types in other databases.
+	 * 
+	 * @since 1.0.2
+	 */
+	@Override
+	public String createTable(DbEntity ent) {
+
+		QuotingStrategy context = getQuotingStrategy();
+		StringBuilder buf = new StringBuilder();
+		buf.append("CREATE TABLE ").append(context.quotedFullyQualifiedName(ent)).append(" (");
+
+		// columns
+		Iterator<DbAttribute> it = ent.getAttributes().iterator();
+		boolean first = true;
+		while (it.hasNext()) {
+			if (first) {
+				first = false;
+			} else {
+				buf.append(", ");
+			}
+
+			createAttribute(ent, context, buf, it.next());
+		}
+
+		// primary key clause
+		Iterator<DbAttribute> pkit = ent.getPrimaryKeys().iterator();
+		if (pkit.hasNext()) {
+			if (first) {
+				first = false;
+			} else {
+				buf.append(", ");
+			}
+
+			buf.append("PRIMARY KEY (");
+			boolean firstPk = true;
+			while (pkit.hasNext()) {
+				if (firstPk) {
+					firstPk = false;
+				} else {
+					buf.append(", ");
+				}
+
+				DbAttribute at = pkit.next();
+				buf.append(context.quotedName(at));
+			}
+			buf.append(')');
+		}
+		buf.append(')');
+		return buf.toString();
+	}
+
+	private void createAttribute(DbEntity ent, QuotingStrategy context, StringBuilder buf, DbAttribute at) {
+		// attribute may not be fully valid, do a simple check
+		if (at.getType() == TypesMapping.NOT_DEFINED) {
+			throw new CayenneRuntimeException("Undefined type for attribute '" + ent.getFullyQualifiedName() + "."
+					+ at.getName() + "'.");
+		}
+
+		String[] types = externalTypesForJdbcType(at.getType());
+		if (types == null || types.length == 0) {
+			throw new CayenneRuntimeException("Undefined type for attribute '" + ent.getFullyQualifiedName() + "."
+					+ at.getName() + "': " + at.getType());
+		}
+
+		buf.append(context.quotedName(at)).append(' ').append(types[0]).append(sizeAndPrecision(this, at))
+				.append(at.isMandatory() ? " NOT" : "").append(" NULL");
+	}
+
+	@Override
+	public boolean typeSupportsLength(int type) {
+		// "bytea" type does not support length
+		String[] externalTypes = externalTypesForJdbcType(type);
+		if (externalTypes != null && externalTypes.length > 0) {
+			for (String externalType : externalTypes) {
+				if (BYTEA.equalsIgnoreCase(externalType)) {
+					return false;
+				}
+			}
+		}
+
+		return super.typeSupportsLength(type);
+	}
+
+	/**
+	 * Adds the CASCADE option to the DROP TABLE clause.
+	 */
+	@Override
+	public Collection<String> dropTableStatements(DbEntity table) {
+		QuotingStrategy context = getQuotingStrategy();
+		return Collections.singleton("DROP TABLE " + context.quotedFullyQualifiedName(table) + " CASCADE");
+	}
+
+	/**
+	 * Returns a trimming translator.
+	 */
+	@Override
+	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
+		QualifierTranslator translator = new PostgresQualifierTranslator(queryAssembler);
+		translator.setCaseInsensitive(caseInsensitiveCollations);
+		return translator;
+	}
+
+	/**
+	 * @see JdbcAdapter#createPkGenerator()
+	 */
+	@Override
+	protected PkGenerator createPkGenerator() {
+		return new PostgresPkGenerator(this);
+	}
+
+	@Override
+	public MergerFactory mergerFactory() {
+		return new PostgresMergerFactory();
+	}
+
+	@Override
+	public boolean supportsCatalogsOnReverseEngineering() {
+		return false;
+	}
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectAction.java
index b9e8fe5..c20e746 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectAction.java
@@ -20,7 +20,6 @@ package org.apache.cayenne.dba.postgres;
 
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.jdbc.SelectAction;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.query.SelectQuery;
 
 /**
@@ -36,9 +35,4 @@ class PostgresSelectAction extends SelectAction {
 	protected int getInMemoryOffset(int queryOffset) {
 		return 0;
 	}
-
-	@Override
-	protected SelectTranslator createTranslator() {
-		return new PostgresSelectTranslator(query, dataNode.getAdapter(), dataNode.getEntityResolver());
-	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
index 9b3a10c..6630a8b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
@@ -24,39 +24,33 @@ import org.apache.cayenne.dba.JdbcActionBuilder;
 import org.apache.cayenne.query.BatchQuery;
 import org.apache.cayenne.query.ProcedureQuery;
 import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
-
 
 /**
  * @since 1.2
  */
 public class SQLServerActionBuilder extends JdbcActionBuilder {
 
-    /**
-     * @since 4.0
-     */
-    public SQLServerActionBuilder(DataNode dataNode) {
-        super(dataNode);
-    }
-    
-    @Override
-    public SQLAction batchAction(BatchQuery query) {
-        // check run strategy...
-
-        // optimistic locking is not supported in batches due to JDBC driver limitations
-        boolean useOptimisticLock = query.isUsingOptimisticLocking();
-
-        boolean runningAsBatch = !useOptimisticLock && dataNode.getAdapter().supportsBatchUpdates();
-        return new SQLServerBatchAction(query, dataNode, runningAsBatch);
-    }
-
-    @Override
-    public <T> SQLAction objectSelectAction(SelectQuery<T> query) {
-        return new SQLServerSelectAction(query, dataNode);
-    }    
-    
-    @Override
-    public SQLAction procedureAction(ProcedureQuery query) {
-        return new SQLServerProcedureAction(query, dataNode);
-    }
+	/**
+	 * @since 4.0
+	 */
+	public SQLServerActionBuilder(DataNode dataNode) {
+		super(dataNode);
+	}
+
+	@Override
+	public SQLAction batchAction(BatchQuery query) {
+		// check run strategy...
+
+		// optimistic locking is not supported in batches due to JDBC driver
+		// limitations
+		boolean useOptimisticLock = query.isUsingOptimisticLocking();
+
+		boolean runningAsBatch = !useOptimisticLock && dataNode.getAdapter().supportsBatchUpdates();
+		return new SQLServerBatchAction(query, dataNode, runningAsBatch);
+	}
+
+	@Override
+	public SQLAction procedureAction(ProcedureQuery query) {
+		return new SQLServerProcedureAction(query, dataNode);
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
index e553a72..faf2ad2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
@@ -24,6 +24,7 @@ import java.util.List;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.configuration.Constants;
@@ -31,14 +32,19 @@ import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.sybase.SybaseAdapter;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 /**
- * <p>Cayenne DbAdapter implementation for <a href="http://www.microsoft.com/sql/">Microsoft
- * SQL Server </a> engine. </p> <h3>Microsoft Driver Settings</h3>
+ * <p>
+ * Cayenne DbAdapter implementation for <a
+ * href="http://www.microsoft.com/sql/">Microsoft SQL Server </a> engine.
+ * </p>
+ * <h3>Microsoft Driver Settings</h3>
  * <p>
  * Sample connection settings to use with MS SQL Server are shown below:
  * 
@@ -49,16 +55,16 @@ import org.apache.cayenne.resource.ResourceLocator;
  *       sqlserver.jdbc.driver = com.microsoft.sqlserver.jdbc.SQLServerDriver
  * </pre>
  * <p>
- * <i>Note on case-sensitive LIKE: if your application requires case-sensitive LIKE
- * support, ask your DBA to configure the database to use a case-senstitive collation (one
- * with "CS" in symbolic collation name instead of "CI", e.g.
+ * <i>Note on case-sensitive LIKE: if your application requires case-sensitive
+ * LIKE support, ask your DBA to configure the database to use a case-senstitive
+ * collation (one with "CS" in symbolic collation name instead of "CI", e.g.
  * "SQL_Latin1_general_CP1_CS_AS"). </i>
  * </p>
  * <h3>jTDS Driver Settings</h3>
  * <p>
  * jTDS is an open source driver that can be downloaded from <a href=
- * "http://jtds.sourceforge.net">http://jtds.sourceforge.net </a>. It supports both
- * SQLServer and Sybase. Sample SQLServer settings are the following:
+ * "http://jtds.sourceforge.net">http://jtds.sourceforge.net </a>. It supports
+ * both SQLServer and Sybase. Sample SQLServer settings are the following:
  * </p>
  * 
  * <pre>
@@ -72,67 +78,69 @@ import org.apache.cayenne.resource.ResourceLocator;
  */
 public class SQLServerAdapter extends SybaseAdapter {
 
-    public static final String TRIM_FUNCTION = "RTRIM";
+	public static final String TRIM_FUNCTION = "RTRIM";
+
+	public SQLServerAdapter(@Inject RuntimeProperties runtimeProperties,
+			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+			@Inject ResourceLocator resourceLocator) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
 
-    public SQLServerAdapter(
-            @Inject RuntimeProperties runtimeProperties,
-            @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-            @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-            @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject ResourceLocator resourceLocator) {
-        super(
-                runtimeProperties,
-                defaultExtendedTypes,
-                userExtendedTypes,
-                extendedTypeFactories, resourceLocator);
+		// TODO: i wonder if Sybase supports generated keys...
+		// in this case we need to move this to the super.
+		this.setSupportsGeneratedKeys(true);
+		this.setSupportsBatchUpdates(true);
+	}
 
-        // TODO: i wonder if Sybase supports generated keys...
-        // in this case we need to move this to the super.
-        this.setSupportsGeneratedKeys(true);
-        this.setSupportsBatchUpdates(true);
-    }
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
+		return new SQLServerSelectTranslator(query, this, entityResolver);
+	}
 
-    /**
-     * Uses SQLServerActionBuilder to create the right action.
-     * 
-     * @since 1.2
-     */
-    @Override
-    public SQLAction getAction(Query query, DataNode node) {
-        return query.createSQLAction(new SQLServerActionBuilder(node));
-    }
+	/**
+	 * Uses SQLServerActionBuilder to create the right action.
+	 * 
+	 * @since 1.2
+	 */
+	@Override
+	public SQLAction getAction(Query query, DataNode node) {
+		return query.createSQLAction(new SQLServerActionBuilder(node));
+	}
 
-    /**
-     * Returns a trimming translator.
-     */
-    @Override
-    public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-        QualifierTranslator translator = new SQLServerTrimmingQualifierTranslator(
-                queryAssembler,
-                SQLServerAdapter.TRIM_FUNCTION);
-        translator.setCaseInsensitive(caseInsensitiveCollations);
-        return translator;
-    }
+	/**
+	 * Returns a trimming translator.
+	 */
+	@Override
+	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
+		QualifierTranslator translator = new SQLServerTrimmingQualifierTranslator(queryAssembler,
+				SQLServerAdapter.TRIM_FUNCTION);
+		translator.setCaseInsensitive(caseInsensitiveCollations);
+		return translator;
+	}
 
-    /**
-     * Overrides super implementation to correctly set up identity columns.
-     * 
-     * @since 1.2
-     */
-    @Override
-    public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
+	/**
+	 * Overrides super implementation to correctly set up identity columns.
+	 * 
+	 * @since 1.2
+	 */
+	@Override
+	public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
 
-        super.createTableAppendColumn(sqlBuffer, column);
+		super.createTableAppendColumn(sqlBuffer, column);
 
-        if (column.isGenerated()) {
-            // current limitation - we don't allow to set identity parameters...
-            sqlBuffer.append(" IDENTITY (1, 1)");
-        }
-    }
+		if (column.isGenerated()) {
+			// current limitation - we don't allow to set identity parameters...
+			sqlBuffer.append(" IDENTITY (1, 1)");
+		}
+	}
 
-    @Override
-    public MergerFactory mergerFactory() {
-        return new SQLServerMergerFactory();
-    }
+	@Override
+	public MergerFactory mergerFactory() {
+		return new SQLServerMergerFactory();
+	}
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectAction.java
deleted file mode 100644
index 413a6a6..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSelectAction.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.sqlserver;
-
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.jdbc.SelectAction;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
-import org.apache.cayenne.query.SelectQuery;
-
-public class SQLServerSelectAction extends SelectAction {
-
-    public <T> SQLServerSelectAction(SelectQuery<T> query, DataNode dataNode) {
-        super(query, dataNode);
-    }
-
-    @Override
-    protected SelectTranslator createTranslator() {
-        return new SQLServerSelectTranslator(query, dataNode.getAdapter(), dataNode.getEntityResolver());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
index 69b5164..935a621 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
@@ -39,6 +39,8 @@ import org.apache.cayenne.access.jdbc.SQLTemplateProcessor;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
 import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
 import org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory;
+import org.apache.cayenne.access.translator.select.DefaultSelectTranslatorFactory;
+import org.apache.cayenne.access.translator.select.SelectTranslatorFactory;
 import org.apache.cayenne.ashwood.AshwoodEntitySorter;
 import org.apache.cayenne.cache.QueryCache;
 import org.apache.cayenne.configuration.ConfigurationNameMapper;
@@ -194,6 +196,7 @@ public class DataDomainProviderTest {
 				binder.bind(DbAdapterFactory.class).to(DefaultDbAdapterFactory.class);
 				binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
 				binder.bind(BatchTranslatorFactory.class).to(DefaultBatchTranslatorFactory.class);
+				binder.bind(SelectTranslatorFactory.class).to(DefaultSelectTranslatorFactory.class);
 
 				binder.bind(DataSourceFactory.class).toInstance(new MockDataSourceFactory());
 				binder.bind(JdbcEventLogger.class).to(CommonsJdbcEventLogger.class);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java
index cd55c39..0bb751b 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java
@@ -25,6 +25,7 @@ import org.apache.cayenne.access.DbGenerator;
 import org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy;
 import org.apache.cayenne.access.jdbc.reader.DefaultRowReaderFactory;
 import org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory;
+import org.apache.cayenne.access.translator.select.DefaultSelectTranslatorFactory;
 import org.apache.cayenne.ashwood.AshwoodEntitySorter;
 import org.apache.cayenne.cache.MapQueryCache;
 import org.apache.cayenne.dba.DbAdapter;
@@ -160,6 +161,7 @@ public class SchemaBuilder {
         node.setSchemaUpdateStrategy(new SkipSchemaUpdateStrategy());
         node.setRowReaderFactory(new DefaultRowReaderFactory());
         node.setBatchTranslatorFactory(new DefaultBatchTranslatorFactory());
+        node.setSelectTranslatorFactory(new DefaultSelectTranslatorFactory());
         domain.addNode(node);
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseDataNodeFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseDataNodeFactory.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseDataNodeFactory.java
index 85ab18f..6dfb322 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseDataNodeFactory.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseDataNodeFactory.java
@@ -23,6 +23,7 @@ import org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy;
 import org.apache.cayenne.access.jdbc.SQLTemplateProcessor;
 import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
 import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory;
+import org.apache.cayenne.access.translator.select.SelectTranslatorFactory;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.configuration.server.DataNodeFactory;
 import org.apache.cayenne.dba.DbAdapter;
@@ -31,39 +32,43 @@ import org.apache.cayenne.log.JdbcEventLogger;
 
 public class ServerCaseDataNodeFactory implements DataNodeFactory {
 
-    @Inject
-    private JdbcEventLogger jdbcEventLogger;
+	@Inject
+	private JdbcEventLogger jdbcEventLogger;
 
-    @Inject
-    protected RowReaderFactory rowReaderFactory;
+	@Inject
+	protected RowReaderFactory rowReaderFactory;
 
-    @Inject
-    protected BatchTranslatorFactory batchTranslatorFactory;
+	@Inject
+	protected BatchTranslatorFactory batchTranslatorFactory;
 
-    @Inject
-    private ServerCaseDataSourceFactory dataSourceFactory;
+	@Inject
+	protected SelectTranslatorFactory selectTranslatorFactory;
 
-    @Inject
-    private DbAdapter adapter;
-    
-    @Inject
-    protected SQLTemplateProcessor sqlTemplateProcessor;
+	@Inject
+	private ServerCaseDataSourceFactory dataSourceFactory;
 
-    @Override
-    public DataNode createDataNode(DataNodeDescriptor nodeDescriptor) throws Exception {
-        DataNode dataNode = new DataNode(nodeDescriptor.getName());
+	@Inject
+	private DbAdapter adapter;
 
-        dataNode.setJdbcEventLogger(jdbcEventLogger);
-        dataNode.setRowReaderFactory(rowReaderFactory);
-        dataNode.setBatchTranslatorFactory(batchTranslatorFactory);
+	@Inject
+	protected SQLTemplateProcessor sqlTemplateProcessor;
 
-        // shared or dedicated DataSources can be mapped per DataMap
-        dataNode.setDataSource(dataSourceFactory.getDataSource(nodeDescriptor.getName()));
-        dataNode.setAdapter(adapter);
-        dataNode.setSchemaUpdateStrategy(new SkipSchemaUpdateStrategy());
-        dataNode.setSqlTemplateProcessor(sqlTemplateProcessor);
+	@Override
+	public DataNode createDataNode(DataNodeDescriptor nodeDescriptor) throws Exception {
+		DataNode dataNode = new DataNode(nodeDescriptor.getName());
 
-        return dataNode;
-    }
+		dataNode.setJdbcEventLogger(jdbcEventLogger);
+		dataNode.setRowReaderFactory(rowReaderFactory);
+		dataNode.setBatchTranslatorFactory(batchTranslatorFactory);
+		dataNode.setSelectTranslatorFactory(selectTranslatorFactory);
+
+		// shared or dedicated DataSources can be mapped per DataMap
+		dataNode.setDataSource(dataSourceFactory.getDataSource(nodeDescriptor.getName()));
+		dataNode.setAdapter(adapter);
+		dataNode.setSchemaUpdateStrategy(new SkipSchemaUpdateStrategy());
+		dataNode.setSqlTemplateProcessor(sqlTemplateProcessor);
+
+		return dataNode;
+	}
 
 }


[3/8] cayenne git commit: CAY-2007 Refactoring SelectTranslator for better extensibility

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
index 1a37f36..57456f5 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
@@ -36,6 +36,7 @@ import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ByteArrayType;
 import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
@@ -52,9 +53,11 @@ import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 /**
@@ -64,9 +67,8 @@ import org.apache.cayenne.resource.ResourceLocator;
  * <p>
  * Foreign key constraints are supported by InnoDB engine and NOT supported by
  * MyISAM engine. This adapter by default assumes MyISAM, so
- * <code>supportsFkConstraints</code> will
- * be false. Users can manually change this by calling
- * <em>setSupportsFkConstraints(true)</em> or better by using an
+ * <code>supportsFkConstraints</code> will be false. Users can manually change
+ * this by calling <em>setSupportsFkConstraints(true)</em> or better by using an
  * {@link org.apache.cayenne.dba.AutoAdapter}, i.e. not entering the adapter
  * name at all for the DataNode, letting Cayenne guess it in runtime. In the
  * later case Cayenne will check the <em>table_type</em> MySQL variable to
@@ -80,330 +82,341 @@ import org.apache.cayenne.resource.ResourceLocator;
  */
 public class MySQLAdapter extends JdbcAdapter {
 
-    static final String DEFAULT_STORAGE_ENGINE = "InnoDB";
-    static final String MYSQL_QUOTE_SQL_IDENTIFIERS_CHAR_START = "`";
-    static final String MYSQL_QUOTE_SQL_IDENTIFIERS_CHAR_END = "`";
-
-    protected String storageEngine;
-    protected boolean supportsFkConstraints;
-
-    public MySQLAdapter(@Inject RuntimeProperties runtimeProperties,
-            @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-            @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-            @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject ResourceLocator resourceLocator) {
-        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
-
-        // init defaults
-        this.storageEngine = DEFAULT_STORAGE_ENGINE;
-
-        setSupportsBatchUpdates(true);
-        setSupportsFkConstraints(true);
-        setSupportsUniqueConstraints(true);
-        setSupportsGeneratedKeys(true);
-    }
-
-    void setSupportsFkConstraints(boolean flag) {
-        this.supportsFkConstraints = flag;
-    }
-
-    @Override
-    protected QuotingStrategy createQuotingStrategy() {
-        return new DefaultQuotingStrategy("`", "`");
-    }
-
-    @Override
-    public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-        QualifierTranslator translator = new MySQLQualifierTranslator(queryAssembler);
-        translator.setCaseInsensitive(caseInsensitiveCollations);
-        return translator;
-    }
-
-    /**
-     * Uses special action builder to create the right action.
-     * 
-     * @since 1.2
-     */
-    @Override
-    public SQLAction getAction(Query query, DataNode node) {
-        return query.createSQLAction(new MySQLActionBuilder(node));
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public Collection<String> dropTableStatements(DbEntity table) {
-        // note that CASCADE is a noop as of MySQL 5.0, so we have to use FK
-        // checks
-        // statement
-        StringBuilder buf = new StringBuilder();
-        QuotingStrategy context = getQuotingStrategy();
-        buf.append(context.quotedFullyQualifiedName(table));
-
-        return Arrays.asList("SET FOREIGN_KEY_CHECKS=0", "DROP TABLE IF EXISTS " + buf.toString() + " CASCADE",
-                "SET FOREIGN_KEY_CHECKS=1");
-    }
-
-    /**
-     * Installs appropriate ExtendedTypes used as converters for passing values
-     * between JDBC and Java layers.
-     */
-    @Override
-    protected void configureExtendedTypes(ExtendedTypeMap map) {
-        super.configureExtendedTypes(map);
-
-        // must handle CLOBs as strings, otherwise there
-        // are problems with NULL clobs that are treated
-        // as empty strings... somehow this doesn't happen
-        // for BLOBs (ConnectorJ v. 3.0.9)
-        map.registerType(new CharType(false, false));
-        map.registerType(new ByteArrayType(false, false));
-    }
-
-    @Override
-    public DbAttribute buildAttribute(String name, String typeName, int type, int size, int precision,
-            boolean allowNulls) {
-
-        if (typeName != null) {
-            typeName = typeName.toLowerCase();
-        }
-
-        // all LOB types are returned by the driver as OTHER... must remap them
-        // manually
-        // (at least on MySQL 3.23)
-        if (type == Types.OTHER) {
-            if ("longblob".equals(typeName)) {
-                type = Types.BLOB;
-            } else if ("mediumblob".equals(typeName)) {
-                type = Types.BLOB;
-            } else if ("blob".equals(typeName)) {
-                type = Types.BLOB;
-            } else if ("tinyblob".equals(typeName)) {
-                type = Types.VARBINARY;
-            } else if ("longtext".equals(typeName)) {
-                type = Types.CLOB;
-            } else if ("mediumtext".equals(typeName)) {
-                type = Types.CLOB;
-            } else if ("text".equals(typeName)) {
-                type = Types.CLOB;
-            } else if ("tinytext".equals(typeName)) {
-                type = Types.VARCHAR;
-            }
-        }
-        // types like "int unsigned" map to Long
-        else if (typeName != null && typeName.endsWith(" unsigned")) {
-            // per
-            // http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-type-conversions.html
-            if (typeName.equals("int unsigned") || typeName.equals("integer unsigned")
-                    || typeName.equals("mediumint unsigned")) {
-                type = Types.BIGINT;
-            }
-            // BIGINT UNSIGNED maps to BigInteger according to MySQL docs, but
-            // there is no
-            // JDBC mapping for BigInteger
-        }
-
-        return super.buildAttribute(name, typeName, type, size, precision, allowNulls);
-    }
-
-    @Override
-    public void bindParameter(PreparedStatement statement, Object object, int pos, int sqlType, int scale) throws SQLException, Exception {
-        super.bindParameter(statement, object, pos, mapNTypes(sqlType), scale);
-    }
-
-    private int mapNTypes(int sqlType) {
-        switch (sqlType) {
-            case Types.NCHAR : return Types.CHAR;
-            case Types.NCLOB : return Types.CLOB;
-            case Types.NVARCHAR : return Types.VARCHAR;
-            case Types.LONGNVARCHAR : return Types.LONGVARCHAR;
-
-            default:
-                return sqlType;
-        }
-    }
-
-    /**
-     * Creates and returns a primary key generator. Overrides superclass
-     * implementation to return an instance of MySQLPkGenerator that does the
-     * correct table locking.
-     */
-    @Override
-    protected PkGenerator createPkGenerator() {
-        return new MySQLPkGenerator(this);
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    protected EJBQLTranslatorFactory createEJBQLTranslatorFactory() {
-        JdbcEJBQLTranslatorFactory translatorFactory = new MySQLEJBQLTranslatorFactory();
-        translatorFactory.setCaseInsensitive(caseInsensitiveCollations);
-        return translatorFactory;
-    }
-
-    /**
-     * Overrides super implementation to explicitly set table engine to InnoDB
-     * if FK constraints are supported by this adapter.
-     */
-    @Override
-    public String createTable(DbEntity entity) {
-        String ddlSQL = super.createTable(entity);
-
-        if (storageEngine != null) {
-            ddlSQL += " ENGINE=" + storageEngine;
-        }
-
-        return ddlSQL;
-    }
-
-    /**
-     * Customizes PK clause semantics to ensure that generated columns are in
-     * the beginning of the PK definition, as this seems to be a requirement for
-     * InnoDB tables.
-     * 
-     * @since 1.2
-     */
-    // See CAY-358 for details of the InnoDB problem
-    @Override
-    protected void createTableAppendPKClause(StringBuffer sqlBuffer, DbEntity entity) {
-
-        // must move generated to the front...
-        List<DbAttribute> pkList = new ArrayList<DbAttribute>(entity.getPrimaryKeys());
-        Collections.sort(pkList, new PKComparator());
-
-        Iterator<DbAttribute> pkit = pkList.iterator();
-        if (pkit.hasNext()) {
-
-            sqlBuffer.append(", PRIMARY KEY (");
-            boolean firstPk = true;
-            while (pkit.hasNext()) {
-                if (firstPk)
-                    firstPk = false;
-                else
-                    sqlBuffer.append(", ");
-
-                DbAttribute at = pkit.next();
-                sqlBuffer.append(quotingStrategy.quotedName(at));
-            }
-            sqlBuffer.append(')');
-        }
-
-        // if FK constraints are supported, we must add indices to all FKs
-        // Note that according to MySQL docs, FK indexes are created
-        // automatically when
-        // constraint is defined, starting at MySQL 4.1.2
-        if (supportsFkConstraints) {
-            for (DbRelationship r : entity.getRelationships()) {
-                if (r.getJoins().size() > 0 && r.isToPK() && !r.isToDependentPK()) {
-
-                    sqlBuffer.append(", KEY (");
-
-                    Iterator<DbAttribute> columns = r.getSourceAttributes().iterator();
-                    DbAttribute column = columns.next();
-                    sqlBuffer.append(quotingStrategy.quotedName(column));
-
-                    while (columns.hasNext()) {
-                        column = columns.next();
-                        sqlBuffer.append(", ").append(quotingStrategy.quotedName(column));
-                    }
-
-                    sqlBuffer.append(")");
-                }
-            }
-        }
-    }
-
-    /**
-     * Appends AUTO_INCREMENT clause to the column definition for generated
-     * columns.
-     */
-    @Override
-    public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
-
-        String[] types = externalTypesForJdbcType(column.getType());
-        if (types == null || types.length == 0) {
-            String entityName = column.getEntity() != null ? ((DbEntity) column.getEntity()).getFullyQualifiedName()
-                    : "<null>";
-            throw new CayenneRuntimeException("Undefined type for attribute '" + entityName + "." + column.getName()
-                    + "': " + column.getType());
-        }
-
-        String type = types[0];
-        sqlBuffer.append(quotingStrategy.quotedName(column));
-        sqlBuffer.append(' ').append(type);
-
-        // append size and precision (if applicable)s
-        if (typeSupportsLength(column.getType())) {
-            int len = column.getMaxLength();
-
-            int scale = TypesMapping.isDecimal(column.getType()) ? column.getScale() : -1;
-
-            // sanity check
-            if (scale > len) {
-                scale = -1;
-            }
-
-            if (len > 0) {
-                sqlBuffer.append('(').append(len);
-
-                if (scale >= 0) {
-                    sqlBuffer.append(", ").append(scale);
-                }
-
-                sqlBuffer.append(')');
-            }
-        }
-
-        sqlBuffer.append(column.isMandatory() ? " NOT NULL" : " NULL");
-
-        if (column.isGenerated()) {
-            sqlBuffer.append(" AUTO_INCREMENT");
-        }
-    }
-
-    @Override
-    public boolean typeSupportsLength(int type) {
-    	// As of MySQL 5.6.4 the "TIMESTAMP" and "TIME" types support length, which is the number of decimal places for fractional seconds
-    	// http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html
-    	switch (type) {
-	    	case Types.TIMESTAMP:
-	    	case Types.TIME:
-	    		return true;
-	    	default:
-	    		return super.typeSupportsLength(type);
-    	}
-    }
-    
-    @Override
-    public MergerFactory mergerFactory() {
-        return new MySQLMergerFactory();
-    }
-
-    final class PKComparator implements Comparator<DbAttribute> {
-
-        public int compare(DbAttribute a1, DbAttribute a2) {
-            if (a1.isGenerated() != a2.isGenerated()) {
-                return a1.isGenerated() ? -1 : 1;
-            } else {
-                return a1.getName().compareTo(a2.getName());
-            }
-        }
-    }
-
-    /**
-     * @since 3.0
-     */
-    public String getStorageEngine() {
-        return storageEngine;
-    }
-
-    /**
-     * @since 3.0
-     */
-    public void setStorageEngine(String engine) {
-        this.storageEngine = engine;
-    }
+	static final String DEFAULT_STORAGE_ENGINE = "InnoDB";
+	static final String MYSQL_QUOTE_SQL_IDENTIFIERS_CHAR_START = "`";
+	static final String MYSQL_QUOTE_SQL_IDENTIFIERS_CHAR_END = "`";
+
+	protected String storageEngine;
+	protected boolean supportsFkConstraints;
+
+	public MySQLAdapter(@Inject RuntimeProperties runtimeProperties,
+			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+			@Inject ResourceLocator resourceLocator) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+
+		// init defaults
+		this.storageEngine = DEFAULT_STORAGE_ENGINE;
+
+		setSupportsBatchUpdates(true);
+		setSupportsFkConstraints(true);
+		setSupportsUniqueConstraints(true);
+		setSupportsGeneratedKeys(true);
+	}
+
+	void setSupportsFkConstraints(boolean flag) {
+		this.supportsFkConstraints = flag;
+	}
+
+	@Override
+	protected QuotingStrategy createQuotingStrategy() {
+		return new DefaultQuotingStrategy("`", "`");
+	}
+
+	@Override
+	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
+		return new MySQLSelectTranslator(query, this, entityResolver);
+	}
+
+	@Override
+	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
+		QualifierTranslator translator = new MySQLQualifierTranslator(queryAssembler);
+		translator.setCaseInsensitive(caseInsensitiveCollations);
+		return translator;
+	}
+
+	/**
+	 * Uses special action builder to create the right action.
+	 * 
+	 * @since 1.2
+	 */
+	@Override
+	public SQLAction getAction(Query query, DataNode node) {
+		return query.createSQLAction(new MySQLActionBuilder(node));
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	@Override
+	public Collection<String> dropTableStatements(DbEntity table) {
+		// note that CASCADE is a noop as of MySQL 5.0, so we have to use FK
+		// checks
+		// statement
+		StringBuilder buf = new StringBuilder();
+		QuotingStrategy context = getQuotingStrategy();
+		buf.append(context.quotedFullyQualifiedName(table));
+
+		return Arrays.asList("SET FOREIGN_KEY_CHECKS=0", "DROP TABLE IF EXISTS " + buf.toString() + " CASCADE",
+				"SET FOREIGN_KEY_CHECKS=1");
+	}
+
+	/**
+	 * Installs appropriate ExtendedTypes used as converters for passing values
+	 * between JDBC and Java layers.
+	 */
+	@Override
+	protected void configureExtendedTypes(ExtendedTypeMap map) {
+		super.configureExtendedTypes(map);
+
+		// must handle CLOBs as strings, otherwise there
+		// are problems with NULL clobs that are treated
+		// as empty strings... somehow this doesn't happen
+		// for BLOBs (ConnectorJ v. 3.0.9)
+		map.registerType(new CharType(false, false));
+		map.registerType(new ByteArrayType(false, false));
+	}
+
+	@Override
+	public DbAttribute buildAttribute(String name, String typeName, int type, int size, int precision,
+			boolean allowNulls) {
+
+		if (typeName != null) {
+			typeName = typeName.toLowerCase();
+		}
+
+		// all LOB types are returned by the driver as OTHER... must remap them
+		// manually
+		// (at least on MySQL 3.23)
+		if (type == Types.OTHER) {
+			if ("longblob".equals(typeName)) {
+				type = Types.BLOB;
+			} else if ("mediumblob".equals(typeName)) {
+				type = Types.BLOB;
+			} else if ("blob".equals(typeName)) {
+				type = Types.BLOB;
+			} else if ("tinyblob".equals(typeName)) {
+				type = Types.VARBINARY;
+			} else if ("longtext".equals(typeName)) {
+				type = Types.CLOB;
+			} else if ("mediumtext".equals(typeName)) {
+				type = Types.CLOB;
+			} else if ("text".equals(typeName)) {
+				type = Types.CLOB;
+			} else if ("tinytext".equals(typeName)) {
+				type = Types.VARCHAR;
+			}
+		}
+		// types like "int unsigned" map to Long
+		else if (typeName != null && typeName.endsWith(" unsigned")) {
+			// per
+			// http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-type-conversions.html
+			if (typeName.equals("int unsigned") || typeName.equals("integer unsigned")
+					|| typeName.equals("mediumint unsigned")) {
+				type = Types.BIGINT;
+			}
+			// BIGINT UNSIGNED maps to BigInteger according to MySQL docs, but
+			// there is no
+			// JDBC mapping for BigInteger
+		}
+
+		return super.buildAttribute(name, typeName, type, size, precision, allowNulls);
+	}
+
+	@Override
+	public void bindParameter(PreparedStatement statement, Object object, int pos, int sqlType, int scale)
+			throws SQLException, Exception {
+		super.bindParameter(statement, object, pos, mapNTypes(sqlType), scale);
+	}
+
+	private int mapNTypes(int sqlType) {
+		switch (sqlType) {
+		case Types.NCHAR:
+			return Types.CHAR;
+		case Types.NCLOB:
+			return Types.CLOB;
+		case Types.NVARCHAR:
+			return Types.VARCHAR;
+		case Types.LONGNVARCHAR:
+			return Types.LONGVARCHAR;
+
+		default:
+			return sqlType;
+		}
+	}
+
+	/**
+	 * Creates and returns a primary key generator. Overrides superclass
+	 * implementation to return an instance of MySQLPkGenerator that does the
+	 * correct table locking.
+	 */
+	@Override
+	protected PkGenerator createPkGenerator() {
+		return new MySQLPkGenerator(this);
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	@Override
+	protected EJBQLTranslatorFactory createEJBQLTranslatorFactory() {
+		JdbcEJBQLTranslatorFactory translatorFactory = new MySQLEJBQLTranslatorFactory();
+		translatorFactory.setCaseInsensitive(caseInsensitiveCollations);
+		return translatorFactory;
+	}
+
+	/**
+	 * Overrides super implementation to explicitly set table engine to InnoDB
+	 * if FK constraints are supported by this adapter.
+	 */
+	@Override
+	public String createTable(DbEntity entity) {
+		String ddlSQL = super.createTable(entity);
+
+		if (storageEngine != null) {
+			ddlSQL += " ENGINE=" + storageEngine;
+		}
+
+		return ddlSQL;
+	}
+
+	/**
+	 * Customizes PK clause semantics to ensure that generated columns are in
+	 * the beginning of the PK definition, as this seems to be a requirement for
+	 * InnoDB tables.
+	 * 
+	 * @since 1.2
+	 */
+	// See CAY-358 for details of the InnoDB problem
+	@Override
+	protected void createTableAppendPKClause(StringBuffer sqlBuffer, DbEntity entity) {
+
+		// must move generated to the front...
+		List<DbAttribute> pkList = new ArrayList<DbAttribute>(entity.getPrimaryKeys());
+		Collections.sort(pkList, new PKComparator());
+
+		Iterator<DbAttribute> pkit = pkList.iterator();
+		if (pkit.hasNext()) {
+
+			sqlBuffer.append(", PRIMARY KEY (");
+			boolean firstPk = true;
+			while (pkit.hasNext()) {
+				if (firstPk)
+					firstPk = false;
+				else
+					sqlBuffer.append(", ");
+
+				DbAttribute at = pkit.next();
+				sqlBuffer.append(quotingStrategy.quotedName(at));
+			}
+			sqlBuffer.append(')');
+		}
+
+		// if FK constraints are supported, we must add indices to all FKs
+		// Note that according to MySQL docs, FK indexes are created
+		// automatically when
+		// constraint is defined, starting at MySQL 4.1.2
+		if (supportsFkConstraints) {
+			for (DbRelationship r : entity.getRelationships()) {
+				if (r.getJoins().size() > 0 && r.isToPK() && !r.isToDependentPK()) {
+
+					sqlBuffer.append(", KEY (");
+
+					Iterator<DbAttribute> columns = r.getSourceAttributes().iterator();
+					DbAttribute column = columns.next();
+					sqlBuffer.append(quotingStrategy.quotedName(column));
+
+					while (columns.hasNext()) {
+						column = columns.next();
+						sqlBuffer.append(", ").append(quotingStrategy.quotedName(column));
+					}
+
+					sqlBuffer.append(")");
+				}
+			}
+		}
+	}
+
+	/**
+	 * Appends AUTO_INCREMENT clause to the column definition for generated
+	 * columns.
+	 */
+	@Override
+	public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
+
+		String[] types = externalTypesForJdbcType(column.getType());
+		if (types == null || types.length == 0) {
+			String entityName = column.getEntity() != null ? ((DbEntity) column.getEntity()).getFullyQualifiedName()
+					: "<null>";
+			throw new CayenneRuntimeException("Undefined type for attribute '" + entityName + "." + column.getName()
+					+ "': " + column.getType());
+		}
+
+		String type = types[0];
+		sqlBuffer.append(quotingStrategy.quotedName(column));
+		sqlBuffer.append(' ').append(type);
+
+		// append size and precision (if applicable)s
+		if (typeSupportsLength(column.getType())) {
+			int len = column.getMaxLength();
+
+			int scale = TypesMapping.isDecimal(column.getType()) ? column.getScale() : -1;
+
+			// sanity check
+			if (scale > len) {
+				scale = -1;
+			}
+
+			if (len > 0) {
+				sqlBuffer.append('(').append(len);
+
+				if (scale >= 0) {
+					sqlBuffer.append(", ").append(scale);
+				}
+
+				sqlBuffer.append(')');
+			}
+		}
+
+		sqlBuffer.append(column.isMandatory() ? " NOT NULL" : " NULL");
+
+		if (column.isGenerated()) {
+			sqlBuffer.append(" AUTO_INCREMENT");
+		}
+	}
+
+	@Override
+	public boolean typeSupportsLength(int type) {
+		// As of MySQL 5.6.4 the "TIMESTAMP" and "TIME" types support length,
+		// which is the number of decimal places for fractional seconds
+		// http://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html
+		switch (type) {
+		case Types.TIMESTAMP:
+		case Types.TIME:
+			return true;
+		default:
+			return super.typeSupportsLength(type);
+		}
+	}
+
+	@Override
+	public MergerFactory mergerFactory() {
+		return new MySQLMergerFactory();
+	}
+
+	final class PKComparator implements Comparator<DbAttribute> {
+
+		public int compare(DbAttribute a1, DbAttribute a2) {
+			if (a1.isGenerated() != a2.isGenerated()) {
+				return a1.isGenerated() ? -1 : 1;
+			} else {
+				return a1.getName().compareTo(a2.getName());
+			}
+		}
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	public String getStorageEngine() {
+		return storageEngine;
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	public void setStorageEngine(String engine) {
+		this.storageEngine = engine;
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSelectAction.java
index a39bd16..0b522d3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSelectAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLSelectAction.java
@@ -20,7 +20,6 @@ package org.apache.cayenne.dba.mysql;
 
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.jdbc.SelectAction;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.query.SelectQuery;
 
 /**
@@ -36,9 +35,4 @@ class MySQLSelectAction extends SelectAction {
 	protected int getInMemoryOffset(int queryOffset) {
 		return 0;
 	}
-
-	@Override
-	protected SelectTranslator createTranslator() {
-		return new MySQLSelectTranslator(query, dataNode.getAdapter(), dataNode.getEntityResolver());
-	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseActionBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseActionBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseActionBuilder.java
deleted file mode 100644
index fb03bde..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseActionBuilder.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-
-package org.apache.cayenne.dba.openbase;
-
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.jdbc.SelectAction;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
-import org.apache.cayenne.dba.JdbcActionBuilder;
-import org.apache.cayenne.query.SQLAction;
-import org.apache.cayenne.query.SelectQuery;
-
-/**
- * @since 1.2
- */
-class OpenBaseActionBuilder extends JdbcActionBuilder {
-
-	OpenBaseActionBuilder(DataNode dataNode) {
-		super(dataNode);
-	}
-
-	@Override
-	public <T> SQLAction objectSelectAction(SelectQuery<T> query) {
-		return new SelectAction(query, dataNode) {
-
-			@Override
-			protected SelectTranslator createTranslator() {
-				return new OpenBaseSelectTranslator(query, dataNode.getAdapter(), dataNode.getEntityResolver());
-			}
-		};
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
index f7aeb82..a018aa4 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
@@ -27,9 +27,9 @@ import java.util.Iterator;
 import java.util.List;
 
 import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ByteType;
 import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
@@ -45,14 +45,14 @@ import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.query.Query;
-import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 /**
- * DbAdapter implementation for <a href="http://www.openbase.com">OpenBase</a>. Sample
- * connection settings to use with OpenBase are shown below:
+ * DbAdapter implementation for <a href="http://www.openbase.com">OpenBase</a>.
+ * Sample connection settings to use with OpenBase are shown below:
  * 
  * <pre>
  * openbase.jdbc.username = test
@@ -65,284 +65,239 @@ import org.apache.cayenne.resource.ResourceLocator;
  */
 public class OpenBaseAdapter extends JdbcAdapter {
 
-    public OpenBaseAdapter(
-            @Inject RuntimeProperties runtimeProperties,
-            @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-            @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-            @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject ResourceLocator resourceLocator) {
-        super(
-                runtimeProperties,
-                defaultExtendedTypes,
-                userExtendedTypes,
-                extendedTypeFactories,
-                resourceLocator);
-
-        // init defaults
-        this.setSupportsUniqueConstraints(false);
-    }
-
-    /**
-     * Uses special action builder to create the right action.
-     * 
-     * @since 1.2
-     */
-    @Override
-    public SQLAction getAction(Query query, DataNode node) {
-        return query.createSQLAction(new OpenBaseActionBuilder(node));
-    }
-
-    @Override
-    protected void configureExtendedTypes(ExtendedTypeMap map) {
-        super.configureExtendedTypes(map);
-
-        // Byte handling doesn't work on read...
-        // need special converter
-        map.registerType(new OpenBaseByteType());
-
-        map.registerType(new OpenBaseCharType());
-    }
-
-    @Override
-    public DbAttribute buildAttribute(
-            String name,
-            String typeName,
-            int type,
-            int size,
-            int scale,
-            boolean allowNulls) {
-
-        // OpenBase makes no distinction between CHAR and VARCHAR
-        // so lets use VARCHAR, since it seems more generic
-        if (type == Types.CHAR) {
-            type = Types.VARCHAR;
-        }
-
-        return super.buildAttribute(name, typeName, type, size, scale, allowNulls);
-    }
-
-    /**
-     * Returns word "go".
-     */
-    @Override
-    public String getBatchTerminator() {
-        return "go";
-    }
-
-    /**
-     * Returns null, since views are not yet supported in openbase.
-     */
-    @Override
-    public String tableTypeForView() {
-        // TODO: according to OpenBase docs views *ARE* supported.
-        return null;
-    }
-
-    /**
-     * Returns OpenBase-specific translator for queries.
-     */
-    @Override
-    public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-        return new OpenBaseQualifierTranslator(queryAssembler);
-    }
-
-    /**
-     * Creates and returns a primary key generator. Overrides superclass implementation to
-     * return an instance of OpenBasePkGenerator that uses built-in multi-server primary
-     * key generation.
-     */
-    @Override
-    protected PkGenerator createPkGenerator() {
-        return new OpenBasePkGenerator(this);
-    }
-
-    /**
-     * Returns a SQL string that can be used to create database table corresponding to
-     * <code>ent</code> parameter.
-     */
-    @Override
-    public String createTable(DbEntity ent) {
-
-        StringBuilder buf = new StringBuilder();
-
-        buf.append("CREATE TABLE ");
-        buf.append(quotingStrategy.quotedFullyQualifiedName(ent));
-        buf.append(" (");
-
-        // columns
-        Iterator<DbAttribute> it = ent.getAttributes().iterator();
-        boolean first = true;
-        while (it.hasNext()) {
-            if (first) {
-                first = false;
-            }
-            else {
-                buf.append(", ");
-            }
-
-            DbAttribute at = it.next();
-
-            // attribute may not be fully valid, do a simple check
-            if (at.getType() == TypesMapping.NOT_DEFINED) {
-                throw new CayenneRuntimeException("Undefined type for attribute '"
-                        + ent.getFullyQualifiedName()
-                        + "."
-                        + at.getName()
-                        + "'.");
-            }
-
-            String[] types = externalTypesForJdbcType(at.getType());
-            if (types == null || types.length == 0) {
-                throw new CayenneRuntimeException("Undefined type for attribute '"
-                        + ent.getFullyQualifiedName()
-                        + "."
-                        + at.getName()
-                        + "': "
-                        + at.getType());
-            }
-
-            String type = types[0];
-            buf.append(quotingStrategy.quotedName(at)).append(' ').append(type);
-
-            // append size and precision (if applicable)
-            if (typeSupportsLength(at.getType())) {
-                int len = at.getMaxLength();
-                int scale = TypesMapping.isDecimal(at.getType()) ? at.getScale() : -1;
-
-                // sanity check
-                if (scale > len) {
-                    scale = -1;
-                }
-
-                if (len > 0) {
-                    buf.append('(').append(len);
-
-                    if (scale >= 0) {
-                        buf.append(", ").append(scale);
-                    }
-
-                    buf.append(')');
-                }
-            }
-
-            if (at.isMandatory()) {
-                buf.append(" NOT NULL");
-            }
-            else {
-                buf.append(" NULL");
-            }
-        }
-
-        buf.append(')');
-        return buf.toString();
-    }
-
-    /**
-     * Returns a SQL string that can be used to create a foreign key constraint for the
-     * relationship.
-     */
-    @Override
-    public String createFkConstraint(DbRelationship rel) {
-        StringBuilder buf = new StringBuilder();
-
-        // OpendBase Specifics is that we need to create a constraint going
-        // from destination to source for this to work...
-
-        DbEntity sourceEntity = (DbEntity) rel.getSourceEntity();
-        DbEntity targetEntity = (DbEntity) rel.getTargetEntity();
-        String toMany = (!rel.isToMany()) ? "'1'" : "'0'";
-
-        // TODO: doesn't seem like OpenBase supports compound joins...
-        // need to doublecheck that
-
-        int joinsLen = rel.getJoins().size();
-        if (joinsLen == 0) {
-            throw new CayenneRuntimeException("Relationship has no joins: "
-                    + rel.getName());
-        }
-        else if (joinsLen > 1) {
-            // ignore extra joins
-        }
-
-        DbJoin join = rel.getJoins().get(0);
-
-        buf
-                .append("INSERT INTO _SYS_RELATIONSHIP (")
-                .append("dest_table, dest_column, source_table, source_column, ")
-                .append(
-                        "block_delete, cascade_delete, one_to_many, operator, relationshipName")
-                .append(") VALUES ('")
-                .append(sourceEntity.getFullyQualifiedName())
-                .append("', '")
-                .append(join.getSourceName())
-                .append("', '")
-                .append(targetEntity.getFullyQualifiedName())
-                .append("', '")
-                .append(join.getTargetName())
-                .append("', 0, 0, ")
-                .append(toMany)
-                .append(", '=', '")
-                .append(rel.getName())
-                .append("')");
-
-        return buf.toString();
-    }
-
-    // OpenBase JDBC driver has trouble reading "integer" as byte
-    // this converter addresses such problem
-    static class OpenBaseByteType extends ByteType {
-
-        OpenBaseByteType() {
-            super(true);
-        }
-
-        @Override
-        public Object materializeObject(ResultSet rs, int index, int type)
-                throws Exception {
-
-            // read value as int, and then narrow it down
-            int val = rs.getInt(index);
-            return (rs.wasNull()) ? null : Byte.valueOf((byte) val);
-        }
-
-        @Override
-        public Object materializeObject(CallableStatement rs, int index, int type)
-                throws Exception {
-
-            // read value as int, and then narrow it down
-            int val = rs.getInt(index);
-            return (rs.wasNull()) ? null : Byte.valueOf((byte) val);
-        }
-    }
-
-    static class OpenBaseCharType extends CharType {
-
-        OpenBaseCharType() {
-            super(false, true);
-        }
-
-        @Override
-        public void setJdbcObject(
-                PreparedStatement st,
-                Object val,
-                int pos,
-                int type,
-                int precision) throws Exception {
-
-            // These to types map to "text"; and when setting "text" as object
-            // OB assumes that the object is the actual CLOB... weird
-            if (type == Types.CLOB || type == Types.LONGVARCHAR) {
-                st.setString(pos, (String) val);
-            }
-            else {
-                super.setJdbcObject(st, val, pos, type, precision);
-            }
-        }
-    }
-
-    @Override
-    public MergerFactory mergerFactory() {
-        return new OpenBaseMergerFactory();
-    }
+	public OpenBaseAdapter(@Inject RuntimeProperties runtimeProperties,
+			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+			@Inject ResourceLocator resourceLocator) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+
+		// init defaults
+		this.setSupportsUniqueConstraints(false);
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
+		return new OpenBaseSelectTranslator(query, this, entityResolver);
+	}
+
+	@Override
+	protected void configureExtendedTypes(ExtendedTypeMap map) {
+		super.configureExtendedTypes(map);
+
+		// Byte handling doesn't work on read...
+		// need special converter
+		map.registerType(new OpenBaseByteType());
+
+		map.registerType(new OpenBaseCharType());
+	}
+
+	@Override
+	public DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls) {
+
+		// OpenBase makes no distinction between CHAR and VARCHAR
+		// so lets use VARCHAR, since it seems more generic
+		if (type == Types.CHAR) {
+			type = Types.VARCHAR;
+		}
+
+		return super.buildAttribute(name, typeName, type, size, scale, allowNulls);
+	}
+
+	/**
+	 * Returns word "go".
+	 */
+	@Override
+	public String getBatchTerminator() {
+		return "go";
+	}
+
+	/**
+	 * Returns null, since views are not yet supported in openbase.
+	 */
+	@Override
+	public String tableTypeForView() {
+		// TODO: according to OpenBase docs views *ARE* supported.
+		return null;
+	}
+
+	/**
+	 * Returns OpenBase-specific translator for queries.
+	 */
+	@Override
+	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
+		return new OpenBaseQualifierTranslator(queryAssembler);
+	}
+
+	/**
+	 * Creates and returns a primary key generator. Overrides superclass
+	 * implementation to return an instance of OpenBasePkGenerator that uses
+	 * built-in multi-server primary key generation.
+	 */
+	@Override
+	protected PkGenerator createPkGenerator() {
+		return new OpenBasePkGenerator(this);
+	}
+
+	/**
+	 * Returns a SQL string that can be used to create database table
+	 * corresponding to <code>ent</code> parameter.
+	 */
+	@Override
+	public String createTable(DbEntity ent) {
+
+		StringBuilder buf = new StringBuilder();
+
+		buf.append("CREATE TABLE ");
+		buf.append(quotingStrategy.quotedFullyQualifiedName(ent));
+		buf.append(" (");
+
+		// columns
+		Iterator<DbAttribute> it = ent.getAttributes().iterator();
+		boolean first = true;
+		while (it.hasNext()) {
+			if (first) {
+				first = false;
+			} else {
+				buf.append(", ");
+			}
+
+			DbAttribute at = it.next();
+
+			// attribute may not be fully valid, do a simple check
+			if (at.getType() == TypesMapping.NOT_DEFINED) {
+				throw new CayenneRuntimeException("Undefined type for attribute '" + ent.getFullyQualifiedName() + "."
+						+ at.getName() + "'.");
+			}
+
+			String[] types = externalTypesForJdbcType(at.getType());
+			if (types == null || types.length == 0) {
+				throw new CayenneRuntimeException("Undefined type for attribute '" + ent.getFullyQualifiedName() + "."
+						+ at.getName() + "': " + at.getType());
+			}
+
+			String type = types[0];
+			buf.append(quotingStrategy.quotedName(at)).append(' ').append(type);
+
+			// append size and precision (if applicable)
+			if (typeSupportsLength(at.getType())) {
+				int len = at.getMaxLength();
+				int scale = TypesMapping.isDecimal(at.getType()) ? at.getScale() : -1;
+
+				// sanity check
+				if (scale > len) {
+					scale = -1;
+				}
+
+				if (len > 0) {
+					buf.append('(').append(len);
+
+					if (scale >= 0) {
+						buf.append(", ").append(scale);
+					}
+
+					buf.append(')');
+				}
+			}
+
+			if (at.isMandatory()) {
+				buf.append(" NOT NULL");
+			} else {
+				buf.append(" NULL");
+			}
+		}
+
+		buf.append(')');
+		return buf.toString();
+	}
+
+	/**
+	 * Returns a SQL string that can be used to create a foreign key constraint
+	 * for the relationship.
+	 */
+	@Override
+	public String createFkConstraint(DbRelationship rel) {
+		StringBuilder buf = new StringBuilder();
+
+		// OpendBase Specifics is that we need to create a constraint going
+		// from destination to source for this to work...
+
+		DbEntity sourceEntity = (DbEntity) rel.getSourceEntity();
+		DbEntity targetEntity = (DbEntity) rel.getTargetEntity();
+		String toMany = (!rel.isToMany()) ? "'1'" : "'0'";
+
+		// TODO: doesn't seem like OpenBase supports compound joins...
+		// need to doublecheck that
+
+		int joinsLen = rel.getJoins().size();
+		if (joinsLen == 0) {
+			throw new CayenneRuntimeException("Relationship has no joins: " + rel.getName());
+		} else if (joinsLen > 1) {
+			// ignore extra joins
+		}
+
+		DbJoin join = rel.getJoins().get(0);
+
+		buf.append("INSERT INTO _SYS_RELATIONSHIP (").append("dest_table, dest_column, source_table, source_column, ")
+				.append("block_delete, cascade_delete, one_to_many, operator, relationshipName").append(") VALUES ('")
+				.append(sourceEntity.getFullyQualifiedName()).append("', '").append(join.getSourceName())
+				.append("', '").append(targetEntity.getFullyQualifiedName()).append("', '")
+				.append(join.getTargetName()).append("', 0, 0, ").append(toMany).append(", '=', '")
+				.append(rel.getName()).append("')");
+
+		return buf.toString();
+	}
+
+	// OpenBase JDBC driver has trouble reading "integer" as byte
+	// this converter addresses such problem
+	static class OpenBaseByteType extends ByteType {
+
+		OpenBaseByteType() {
+			super(true);
+		}
+
+		@Override
+		public Object materializeObject(ResultSet rs, int index, int type) throws Exception {
+
+			// read value as int, and then narrow it down
+			int val = rs.getInt(index);
+			return (rs.wasNull()) ? null : Byte.valueOf((byte) val);
+		}
+
+		@Override
+		public Object materializeObject(CallableStatement rs, int index, int type) throws Exception {
+
+			// read value as int, and then narrow it down
+			int val = rs.getInt(index);
+			return (rs.wasNull()) ? null : Byte.valueOf((byte) val);
+		}
+	}
+
+	static class OpenBaseCharType extends CharType {
+
+		OpenBaseCharType() {
+			super(false, true);
+		}
+
+		@Override
+		public void setJdbcObject(PreparedStatement st, Object val, int pos, int type, int precision) throws Exception {
+
+			// These to types map to "text"; and when setting "text" as object
+			// OB assumes that the object is the actual CLOB... weird
+			if (type == Types.CLOB || type == Types.LONGVARCHAR) {
+				st.setString(pos, (String) val);
+			} else {
+				super.setJdbcObject(st, val, pos, type, precision);
+			}
+		}
+	}
+
+	@Override
+	public MergerFactory mergerFactory() {
+		return new OpenBaseMergerFactory();
+	}
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8ActionBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8ActionBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8ActionBuilder.java
index 3cd8f31..4dec81c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8ActionBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8ActionBuilder.java
@@ -23,7 +23,6 @@ import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.query.BatchQuery;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SQLTemplate;
-import org.apache.cayenne.query.SelectQuery;
 
 /**
  * An action builder for Oracle8Adapter.
@@ -32,33 +31,28 @@ import org.apache.cayenne.query.SelectQuery;
  */
 class Oracle8ActionBuilder extends OracleActionBuilder {
 
-    Oracle8ActionBuilder(DataNode dataNode) {
-        super(dataNode);
-    }
-
-    @Override
-    public SQLAction sqlAction(SQLTemplate query) {
-        return new Oracle8SQLTemplateAction(query, dataNode);
-    }
-
-    @Override
-    public <T> SQLAction objectSelectAction(SelectQuery<T> query) {
-        return new Oracle8SelectAction(query, dataNode);
-    }
-
-    @Override
-    public SQLAction batchAction(BatchQuery query) {
-        // special handling for LOB updates
-        if (OracleAdapter.isSupportsOracleLOB() && OracleAdapter.updatesLOBColumns(query)) {
-            // Special action for Oracle8. See CAY-1307.
-            return new Oracle8LOBBatchAction(query, dataNode.getAdapter(), dataNode.getJdbcEventLogger());
-        } else {
-            // optimistic locking is not supported in batches due to JDBC driver
-            // limitations
-            boolean useOptimisticLock = query.isUsingOptimisticLocking();
-            boolean runningAsBatch = !useOptimisticLock && dataNode.getAdapter().supportsBatchUpdates();
-
-            return new OracleBatchAction(query, dataNode, runningAsBatch);
-        }
-    }
+	Oracle8ActionBuilder(DataNode dataNode) {
+		super(dataNode);
+	}
+
+	@Override
+	public SQLAction sqlAction(SQLTemplate query) {
+		return new Oracle8SQLTemplateAction(query, dataNode);
+	}
+
+	@Override
+	public SQLAction batchAction(BatchQuery query) {
+		// special handling for LOB updates
+		if (OracleAdapter.isSupportsOracleLOB() && OracleAdapter.updatesLOBColumns(query)) {
+			// Special action for Oracle8. See CAY-1307.
+			return new Oracle8LOBBatchAction(query, dataNode.getAdapter(), dataNode.getJdbcEventLogger());
+		} else {
+			// optimistic locking is not supported in batches due to JDBC driver
+			// limitations
+			boolean useOptimisticLock = query.isUsingOptimisticLocking();
+			boolean runningAsBatch = !useOptimisticLock && dataNode.getAdapter().supportsBatchUpdates();
+
+			return new OracleBatchAction(query, dataNode, runningAsBatch);
+		}
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
index 2a85312..c0acdb9 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8Adapter.java
@@ -26,82 +26,91 @@ import java.util.List;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 /**
- * A flavor of OracleAdapter that implements workarounds for some old driver limitations.
+ * A flavor of OracleAdapter that implements workarounds for some old driver
+ * limitations.
  * 
  * @since 1.2
  */
 public class Oracle8Adapter extends OracleAdapter {
 
-    private static Method outputStreamFromBlobMethod;
-    private static Method writerFromClobMethod;
-
-    static {
-        initOracle8DriverInformation();
-    }
-    
-    public Oracle8Adapter(@Inject RuntimeProperties runtimeProperties,
-            @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-            @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-            @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject ResourceLocator resourceLocator) {
-        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
-    }
-
-    private static void initOracle8DriverInformation() {
-        initDone = true;
-
-        // configure static information
-        try {
-            outputStreamFromBlobMethod = Class.forName("oracle.sql.BLOB").getMethod(
-                    "getBinaryOutputStream");
-            writerFromClobMethod = Class.forName("oracle.sql.CLOB").getMethod(
-                    "getCharacterOutputStream");
-        }
-        catch (Throwable th) {
-            // ignoring...
-        }
-    }
-
-    static Method getWriterFromClobMethod() {
-        return writerFromClobMethod;
-    }
-
-    static Method getOutputStreamFromBlobMethod() {
-        return outputStreamFromBlobMethod;
-    }
-
-    /**
-     * Uses OracleActionBuilder to create the right action.
-     */
-    @Override
-    public SQLAction getAction(Query query, DataNode node) {
-        return query.createSQLAction(new Oracle8ActionBuilder(node));
-    }
-
-    @Override
-    protected URL findResource(String name) {
-
-        if ("/types.xml".equals(name)) {
-            name = "/types-oracle8.xml";
-        }
-
-        return super.findResource(name);
-    }
-
-    @Override
-    public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-        QualifierTranslator translator = new Oracle8QualifierTranslator(queryAssembler);
-        translator.setCaseInsensitive(caseInsensitiveCollations);
-        return translator;
-    }
+	private static Method outputStreamFromBlobMethod;
+	private static Method writerFromClobMethod;
+
+	static {
+		initOracle8DriverInformation();
+	}
+
+	public Oracle8Adapter(@Inject RuntimeProperties runtimeProperties,
+			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+			@Inject ResourceLocator resourceLocator) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+	}
+
+	private static void initOracle8DriverInformation() {
+		initDone = true;
+
+		// configure static information
+		try {
+			outputStreamFromBlobMethod = Class.forName("oracle.sql.BLOB").getMethod("getBinaryOutputStream");
+			writerFromClobMethod = Class.forName("oracle.sql.CLOB").getMethod("getCharacterOutputStream");
+		} catch (Throwable th) {
+			// ignoring...
+		}
+	}
+
+	static Method getWriterFromClobMethod() {
+		return writerFromClobMethod;
+	}
+
+	static Method getOutputStreamFromBlobMethod() {
+		return outputStreamFromBlobMethod;
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
+		return new Oracle8SelectTranslator(query, this, entityResolver);
+	}
+
+	/**
+	 * Uses OracleActionBuilder to create the right action.
+	 */
+	@Override
+	public SQLAction getAction(Query query, DataNode node) {
+		return query.createSQLAction(new Oracle8ActionBuilder(node));
+	}
+
+	@Override
+	protected URL findResource(String name) {
+
+		if ("/types.xml".equals(name)) {
+			name = "/types-oracle8.xml";
+		}
+
+		return super.findResource(name);
+	}
+
+	@Override
+	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
+		QualifierTranslator translator = new Oracle8QualifierTranslator(queryAssembler);
+		translator.setCaseInsensitive(caseInsensitiveCollations);
+		return translator;
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8SelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8SelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8SelectAction.java
deleted file mode 100644
index abef7a7..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/Oracle8SelectAction.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*****************************************************************
- *   Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- ****************************************************************/
-package org.apache.cayenne.dba.oracle;
-
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
-import org.apache.cayenne.query.SelectQuery;
-
-/**
- * @since 3.0
- */
-class Oracle8SelectAction extends OracleSelectAction {
-
-	<T> Oracle8SelectAction(SelectQuery<T> query, DataNode dataNode) {
-		super(query, dataNode);
-	}
-
-	@Override
-	protected SelectTranslator createTranslator() {
-		return new Oracle8SelectTranslator(query, dataNode.getAdapter(), dataNode.getEntityResolver());
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
index 50d621c..f16b69d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
@@ -34,6 +34,7 @@ import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
+import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.access.types.ByteType;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
@@ -43,21 +44,22 @@ import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.dba.PkGenerator;
-import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.BatchQuery;
 import org.apache.cayenne.query.InsertBatchQuery;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.query.UpdateBatchQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 /**
- * DbAdapter implementation for <a href="http://www.oracle.com">Oracle RDBMS </a>. Sample
- * connection settings to use with Oracle are shown below:
+ * DbAdapter implementation for <a href="http://www.oracle.com">Oracle RDBMS
+ * </a>. Sample connection settings to use with Oracle are shown below:
  * 
  * <pre>
  *          test-oracle.jdbc.username = test
@@ -68,288 +70,280 @@ import org.apache.cayenne.resource.ResourceLocator;
  */
 public class OracleAdapter extends JdbcAdapter {
 
-    public static final String ORACLE_FLOAT = "FLOAT";
-    public static final String ORACLE_BLOB = "BLOB";
-    public static final String ORACLE_CLOB = "CLOB";
-    public static final String ORACLE_NCLOB = "NCLOB";
-
-    public static final String TRIM_FUNCTION = "RTRIM";
-    public static final String NEW_CLOB_FUNCTION = "EMPTY_CLOB()";
-    public static final String NEW_BLOB_FUNCTION = "EMPTY_BLOB()";
-
-    protected static boolean initDone;
-    protected static int oracleCursorType = Integer.MAX_VALUE;
-
-    protected static boolean supportsOracleLOB;
-
-    static {
-        // TODO: as CAY-234 shows, having such initialization done in a static fashion
-        // makes it untestable and any potential problems hard to reproduce. Make this
-        // an instance method (with all the affected vars) and write unit tests.
-        initDriverInformation();
-    }
-
-    protected static void initDriverInformation() {
-        initDone = true;
-
-        // configure static information
-        try {
-            Class<?> oraTypes = Class.forName("oracle.jdbc.driver.OracleTypes");
-            Field cursorField = oraTypes.getField("CURSOR");
-            oracleCursorType = cursorField.getInt(null);
-
-            supportsOracleLOB = true;
-        }
-        catch (Throwable th) {
-            // ignoring...
-        }
-    }
-
-    // TODO: rename to something that looks like English ...
-    public static boolean isSupportsOracleLOB() {
-        return supportsOracleLOB;
-    }
-
-    /**
-     * Utility method that returns <code>true</code> if the query will update at least one
-     * BLOB or CLOB DbAttribute.
-     * 
-     * @since 1.2
-     */
-    static boolean updatesLOBColumns(BatchQuery query) {
-        boolean isInsert = query instanceof InsertBatchQuery;
-        boolean isUpdate = query instanceof UpdateBatchQuery;
-
-        if (!isInsert && !isUpdate) {
-            return false;
-        }
-
-        List<DbAttribute> updatedAttributes = (isInsert)
-                ? query.getDbAttributes()
-                : ((UpdateBatchQuery) query).getUpdatedAttributes();
-
-        for (DbAttribute attr : updatedAttributes) {
-            int type = attr.getType();
-            if (type == Types.CLOB || type == Types.BLOB) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Returns an Oracle JDBC extension type defined in
-     * oracle.jdbc.driver.OracleTypes.CURSOR. This value is determined from Oracle driver
-     * classes via reflection in runtime, so that Cayenne code has no compile dependency
-     * on the driver. This means that calling this method when the driver is not available
-     * will result in an exception.
-     */
-    public static int getOracleCursorType() {
-
-        if (oracleCursorType == Integer.MAX_VALUE) {
-            throw new CayenneRuntimeException(
-                    "No information exists about oracle types. "
-                            + "Check that Oracle JDBC driver is available to the application.");
-        }
-
-        return oracleCursorType;
-    }
-
-    public OracleAdapter(
-            @Inject RuntimeProperties runtimeProperties,
-            @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-            @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-            @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-            @Inject ResourceLocator resourceLocator) {
-        super(
-                runtimeProperties,
-                defaultExtendedTypes,
-                userExtendedTypes,
-                extendedTypeFactories,
-                resourceLocator);
-
-        // enable batch updates by default
-        setSupportsBatchUpdates(true);
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    protected EJBQLTranslatorFactory createEJBQLTranslatorFactory() {
-        return new OracleEJBQLTranslatorFactory();
-    }
-
-    /**
-     * Installs appropriate ExtendedTypes as converters for passing values between JDBC
-     * and Java layers.
-     */
-    @Override
-    protected void configureExtendedTypes(ExtendedTypeMap map) {
-        super.configureExtendedTypes(map);
-
-        // create specially configured CharType handler
-        map.registerType(new OracleCharType());
-
-        // create specially configured ByteArrayType handler
-        map.registerType(new OracleByteArrayType());
-
-        // override date handler with Oracle handler
-        map.registerType(new OracleUtilDateType());
-
-        // At least on MacOS X, driver does not handle Short and Byte properly
-        map.registerType(new ShortType(true));
-        map.registerType(new ByteType(true));
-        map.registerType(new OracleBooleanType());
-    }
-
-    /**
-     * Creates and returns a primary key generator. Overrides superclass implementation to
-     * return an instance of OraclePkGenerator.
-     */
-    @Override
-    protected PkGenerator createPkGenerator() {
-        return new OraclePkGenerator(this);
-    }
-
-    /**
-     * Returns a query string to drop a table corresponding to <code>ent</code> DbEntity.
-     * Changes superclass behavior to drop all related foreign key constraints.
-     * 
-     * @since 3.0
-     */
-    @Override
-    public Collection<String> dropTableStatements(DbEntity table) {
-        return Collections.singleton("DROP TABLE " + getQuotingStrategy().quotedFullyQualifiedName(table)
-                + " CASCADE CONSTRAINTS");
-    }
-
-    @Override
-    public void bindParameter(
-            PreparedStatement statement,
-            Object object,
-            int pos,
-            int sqlType,
-            int scale) throws SQLException, Exception {
-
-        // Oracle doesn't support BOOLEAN even when binding NULL, so have to intercept
-        // NULL Boolean here, as super doesn't pass it through ExtendedType...
-        if (object == null && sqlType == Types.BOOLEAN) {
-            ExtendedType typeProcessor = getExtendedTypes().getRegisteredType(
-                    Boolean.class);
-            typeProcessor.setJdbcObject(statement, object, pos, sqlType, scale);
-        }
-        else {
-            super.bindParameter(statement, object, pos, sqlType, scale);
-        }
-    }
-
-    /**
-     * Fixes some reverse engineering problems. Namely if a columns is created as DECIMAL
-     * and has non-positive precision it is converted to INTEGER.
-     */
-    @Override
-    public DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls) {
-        DbAttribute attr = super.buildAttribute(name, typeName, type, size, scale, allowNulls);
-
-        if (type == Types.DECIMAL && scale <= 0) {
-            attr.setType(Types.INTEGER);
-            attr.setScale(-1);
-        } else if (type == Types.OTHER) {
-            // in this case we need to guess the attribute type
-            // based on its string value
-            if (ORACLE_FLOAT.equals(typeName)) {
-                attr.setType(Types.FLOAT);
-            } else if (ORACLE_BLOB.equals(typeName)) {
-                attr.setType(Types.BLOB);
-            } else if (ORACLE_CLOB.equals(typeName)) {
-                attr.setType(Types.CLOB);
-            } else if (ORACLE_NCLOB.equals(typeName)) {
-                attr.setType(Types.NCLOB);
-            }
-        } else if (type == Types.DATE) {
-            // Oracle DATE can store JDBC TIMESTAMP
-            if ("DATE".equals(typeName)) {
-                attr.setType(Types.TIMESTAMP);
-            }
-        }
-
-        return attr;
-    }
-
-    /**
-     * Returns a trimming translator.
-     */
-    @Override
-    public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-        QualifierTranslator translator = new Oracle8QualifierTranslator(queryAssembler);
-        translator.setCaseInsensitive(caseInsensitiveCollations);
-        return translator;
-    }
-
-    /**
-     * Uses OracleActionBuilder to create the right action.
-     * 
-     * @since 1.2
-     */
-    @Override
-    public SQLAction getAction(Query query, DataNode node) {
-        return query.createSQLAction(new OracleActionBuilder(node));
-    }
-
-    /**
-     * @since 3.0
-     */
-    final class OracleBooleanType implements ExtendedType {
-
-        @Override
-        public String getClassName() {
-            return Boolean.class.getName();
-        }
-
-        @Override
-        public void setJdbcObject(
-                PreparedStatement st,
-                Object val,
-                int pos,
-                int type,
-                int precision) throws Exception {
-
-            // Oracle does not support Types.BOOLEAN, so we have to override user mapping
-            // unconditionally
-            if (val == null) {
-                st.setNull(pos, Types.INTEGER);
-            }
-            else {
-                boolean flag = Boolean.TRUE.equals(val);
-                st.setInt(pos, flag ? 1 : 0);
-            }
-        }
-
-        @Override
-        public Object materializeObject(ResultSet rs, int index, int type)
-                throws Exception {
-
-            // Oracle does not support Types.BOOLEAN, so we have to override user mapping
-            // unconditionally
-            int i = rs.getInt(index);
-            return rs.wasNull() ? null : i == 0 ? Boolean.FALSE : Boolean.TRUE;
-        }
-
-        @Override
-        public Object materializeObject(CallableStatement st, int index, int type)
-                throws Exception {
-
-            // Oracle does not support Types.BOOLEAN, so we have to override user mapping
-            // unconditionally
-            int i = st.getInt(index);
-            return st.wasNull() ? null : i == 0 ? Boolean.FALSE : Boolean.TRUE;
-        }
-    }
-
-    @Override
-    public MergerFactory mergerFactory() {
-        return new OracleMergerFactory();
-    }
+	public static final String ORACLE_FLOAT = "FLOAT";
+	public static final String ORACLE_BLOB = "BLOB";
+	public static final String ORACLE_CLOB = "CLOB";
+	public static final String ORACLE_NCLOB = "NCLOB";
+
+	public static final String TRIM_FUNCTION = "RTRIM";
+	public static final String NEW_CLOB_FUNCTION = "EMPTY_CLOB()";
+	public static final String NEW_BLOB_FUNCTION = "EMPTY_BLOB()";
+
+	protected static boolean initDone;
+	protected static int oracleCursorType = Integer.MAX_VALUE;
+
+	protected static boolean supportsOracleLOB;
+
+	static {
+		// TODO: as CAY-234 shows, having such initialization done in a static
+		// fashion
+		// makes it untestable and any potential problems hard to reproduce.
+		// Make this
+		// an instance method (with all the affected vars) and write unit tests.
+		initDriverInformation();
+	}
+
+	protected static void initDriverInformation() {
+		initDone = true;
+
+		// configure static information
+		try {
+			Class<?> oraTypes = Class.forName("oracle.jdbc.driver.OracleTypes");
+			Field cursorField = oraTypes.getField("CURSOR");
+			oracleCursorType = cursorField.getInt(null);
+
+			supportsOracleLOB = true;
+		} catch (Throwable th) {
+			// ignoring...
+		}
+	}
+
+	// TODO: rename to something that looks like English ...
+	public static boolean isSupportsOracleLOB() {
+		return supportsOracleLOB;
+	}
+
+	/**
+	 * Utility method that returns <code>true</code> if the query will update at
+	 * least one BLOB or CLOB DbAttribute.
+	 * 
+	 * @since 1.2
+	 */
+	static boolean updatesLOBColumns(BatchQuery query) {
+		boolean isInsert = query instanceof InsertBatchQuery;
+		boolean isUpdate = query instanceof UpdateBatchQuery;
+
+		if (!isInsert && !isUpdate) {
+			return false;
+		}
+
+		List<DbAttribute> updatedAttributes = (isInsert) ? query.getDbAttributes() : ((UpdateBatchQuery) query)
+				.getUpdatedAttributes();
+
+		for (DbAttribute attr : updatedAttributes) {
+			int type = attr.getType();
+			if (type == Types.CLOB || type == Types.BLOB) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Returns an Oracle JDBC extension type defined in
+	 * oracle.jdbc.driver.OracleTypes.CURSOR. This value is determined from
+	 * Oracle driver classes via reflection in runtime, so that Cayenne code has
+	 * no compile dependency on the driver. This means that calling this method
+	 * when the driver is not available will result in an exception.
+	 */
+	public static int getOracleCursorType() {
+
+		if (oracleCursorType == Integer.MAX_VALUE) {
+			throw new CayenneRuntimeException("No information exists about oracle types. "
+					+ "Check that Oracle JDBC driver is available to the application.");
+		}
+
+		return oracleCursorType;
+	}
+
+	public OracleAdapter(@Inject RuntimeProperties runtimeProperties,
+			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+			@Inject ResourceLocator resourceLocator) {
+		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+
+		// enable batch updates by default
+		setSupportsBatchUpdates(true);
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	@Override
+	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
+		return new OracleSelectTranslator(query, this, entityResolver);
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	@Override
+	protected EJBQLTranslatorFactory createEJBQLTranslatorFactory() {
+		return new OracleEJBQLTranslatorFactory();
+	}
+
+	/**
+	 * Installs appropriate ExtendedTypes as converters for passing values
+	 * between JDBC and Java layers.
+	 */
+	@Override
+	protected void configureExtendedTypes(ExtendedTypeMap map) {
+		super.configureExtendedTypes(map);
+
+		// create specially configured CharType handler
+		map.registerType(new OracleCharType());
+
+		// create specially configured ByteArrayType handler
+		map.registerType(new OracleByteArrayType());
+
+		// override date handler with Oracle handler
+		map.registerType(new OracleUtilDateType());
+
+		// At least on MacOS X, driver does not handle Short and Byte properly
+		map.registerType(new ShortType(true));
+		map.registerType(new ByteType(true));
+		map.registerType(new OracleBooleanType());
+	}
+
+	/**
+	 * Creates and returns a primary key generator. Overrides superclass
+	 * implementation to return an instance of OraclePkGenerator.
+	 */
+	@Override
+	protected PkGenerator createPkGenerator() {
+		return new OraclePkGenerator(this);
+	}
+
+	/**
+	 * Returns a query string to drop a table corresponding to <code>ent</code>
+	 * DbEntity. Changes superclass behavior to drop all related foreign key
+	 * constraints.
+	 * 
+	 * @since 3.0
+	 */
+	@Override
+	public Collection<String> dropTableStatements(DbEntity table) {
+		return Collections.singleton("DROP TABLE " + getQuotingStrategy().quotedFullyQualifiedName(table)
+				+ " CASCADE CONSTRAINTS");
+	}
+
+	@Override
+	public void bindParameter(PreparedStatement statement, Object object, int pos, int sqlType, int scale)
+			throws SQLException, Exception {
+
+		// Oracle doesn't support BOOLEAN even when binding NULL, so have to
+		// intercept
+		// NULL Boolean here, as super doesn't pass it through ExtendedType...
+		if (object == null && sqlType == Types.BOOLEAN) {
+			ExtendedType typeProcessor = getExtendedTypes().getRegisteredType(Boolean.class);
+			typeProcessor.setJdbcObject(statement, object, pos, sqlType, scale);
+		} else {
+			super.bindParameter(statement, object, pos, sqlType, scale);
+		}
+	}
+
+	/**
+	 * Fixes some reverse engineering problems. Namely if a columns is created
+	 * as DECIMAL and has non-positive precision it is converted to INTEGER.
+	 */
+	@Override
+	public DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls) {
+		DbAttribute attr = super.buildAttribute(name, typeName, type, size, scale, allowNulls);
+
+		if (type == Types.DECIMAL && scale <= 0) {
+			attr.setType(Types.INTEGER);
+			attr.setScale(-1);
+		} else if (type == Types.OTHER) {
+			// in this case we need to guess the attribute type
+			// based on its string value
+			if (ORACLE_FLOAT.equals(typeName)) {
+				attr.setType(Types.FLOAT);
+			} else if (ORACLE_BLOB.equals(typeName)) {
+				attr.setType(Types.BLOB);
+			} else if (ORACLE_CLOB.equals(typeName)) {
+				attr.setType(Types.CLOB);
+			} else if (ORACLE_NCLOB.equals(typeName)) {
+				attr.setType(Types.NCLOB);
+			}
+		} else if (type == Types.DATE) {
+			// Oracle DATE can store JDBC TIMESTAMP
+			if ("DATE".equals(typeName)) {
+				attr.setType(Types.TIMESTAMP);
+			}
+		}
+
+		return attr;
+	}
+
+	/**
+	 * Returns a trimming translator.
+	 */
+	@Override
+	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
+		QualifierTranslator translator = new Oracle8QualifierTranslator(queryAssembler);
+		translator.setCaseInsensitive(caseInsensitiveCollations);
+		return translator;
+	}
+
+	/**
+	 * Uses OracleActionBuilder to create the right action.
+	 * 
+	 * @since 1.2
+	 */
+	@Override
+	public SQLAction getAction(Query query, DataNode node) {
+		return query.createSQLAction(new OracleActionBuilder(node));
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	final class OracleBooleanType implements ExtendedType {
+
+		@Override
+		public String getClassName() {
+			return Boolean.class.getName();
+		}
+
+		@Override
+		public void setJdbcObject(PreparedStatement st, Object val, int pos, int type, int precision) throws Exception {
+
+			// Oracle does not support Types.BOOLEAN, so we have to override
+			// user mapping
+			// unconditionally
+			if (val == null) {
+				st.setNull(pos, Types.INTEGER);
+			} else {
+				boolean flag = Boolean.TRUE.equals(val);
+				st.setInt(pos, flag ? 1 : 0);
+			}
+		}
+
+		@Override
+		public Object materializeObject(ResultSet rs, int index, int type) throws Exception {
+
+			// Oracle does not support Types.BOOLEAN, so we have to override
+			// user mapping
+			// unconditionally
+			int i = rs.getInt(index);
+			return rs.wasNull() ? null : i == 0 ? Boolean.FALSE : Boolean.TRUE;
+		}
+
+		@Override
+		public Object materializeObject(CallableStatement st, int index, int type) throws Exception {
+
+			// Oracle does not support Types.BOOLEAN, so we have to override
+			// user mapping
+			// unconditionally
+			int i = st.getInt(index);
+			return st.wasNull() ? null : i == 0 ? Boolean.FALSE : Boolean.TRUE;
+		}
+	}
+
+	@Override
+	public MergerFactory mergerFactory() {
+		return new OracleMergerFactory();
+	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/741ad3be/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectAction.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectAction.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectAction.java
index f361aa4..60e8e5a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectAction.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleSelectAction.java
@@ -21,7 +21,6 @@ package org.apache.cayenne.dba.oracle;
 
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.jdbc.SelectAction;
-import org.apache.cayenne.access.translator.select.SelectTranslator;
 import org.apache.cayenne.query.SelectQuery;
 
 /**
@@ -34,11 +33,6 @@ class OracleSelectAction extends SelectAction {
 	}
 
 	@Override
-	protected SelectTranslator createTranslator() {
-		return new OracleSelectTranslator(query, dataNode.getAdapter(), dataNode.getEntityResolver());
-	}
-
-	@Override
 	protected int getInMemoryOffset(int queryOffset) {
 		return 0;
 	}


[7/8] cayenne git commit: 4.0 docs - ObjectSelect

Posted by aa...@apache.org.
4.0 docs - ObjectSelect


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/73bff100
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/73bff100
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/73bff100

Branch: refs/heads/master
Commit: 73bff1001a56259b27aeb7aaa38cea80e8888dbb
Parents: 4f8c114
Author: aadamchik <aa...@apache.org>
Authored: Sun May 10 19:43:05 2015 -0400
Committer: aadamchik <aa...@apache.org>
Committed: Sun May 10 19:43:05 2015 -0400

----------------------------------------------------------------------
 .../cayenne-guide/src/docbkx/queries.xml        | 87 ++++++++++----------
 1 file changed, 45 insertions(+), 42 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/73bff100/docs/docbook/cayenne-guide/src/docbkx/queries.xml
----------------------------------------------------------------------
diff --git a/docs/docbook/cayenne-guide/src/docbkx/queries.xml b/docs/docbook/cayenne-guide/src/docbkx/queries.xml
index eb1f38b..2b3b580 100644
--- a/docs/docbook/cayenne-guide/src/docbkx/queries.xml
+++ b/docs/docbook/cayenne-guide/src/docbkx/queries.xml
@@ -22,61 +22,64 @@
         engine. Most often queries are used to find objects matching certain criteria, but there are
         other types of queries too. E.g. those allowing to run native SQL, call DB stored
         procedures, etc. When committing objects, Cayenne itself creates special queries to
-        insert/update/delete rows in the dabase.</para>
-    <para>There is a number of built-in queries in Cayenne, described later in this chapter. Users can
-        also define their own query types to abstract certain DB interactions that for whatever
-        reason can not be adequately described by the built-in set. </para>
+        insert/update/delete rows in the database. </para>
+    <para>There is a number of built-in queries in Cayenne, described later in this chapter. Most of
+        the newer queries use fluent API and can be created and executed as easy-to-read one-liners.
+        Users can define their own query types to abstract certain DB interactions that for whatever
+        reason can not be adequately described by the built-in set.</para>
     <para>Queries can be roughly categorized as "object" and "native". Object queries (most notably
-        SelectQuery and EJBQLQuery) are built with abstractions originating in the object model (the
-        "object" side in the "object-relational" divide). E.g. SelectQuery is assembled from a Java
-        class of the objects to fetch, a qualifier expression, orderings, etc. - all of this
-        expressed in terms of the object model.</para>
-    <para>Native queries describe a desired DB operation as SQL code (SQLTemplate query) or a
-        reference to a stored procedure (ProcedureQuery), etc. The results of native queries are
-        usually presented as Lists of Maps, with each map representing a row in the DB (a term "data
-        row" is often used to describe such a map). They can potentially be converted to objects,
-        however it may take a considerable effort to do so. Native queries are also less (if at all)
-        portable across databases than object queries. </para>
+        ObjectSelect, SelectById, and EJBQLQuery) are built with abstractions originating in the
+        object model (the "object" side in the "object-relational" divide). E.g. ObjectSelect is
+        assembled from a Java class of the objects to fetch, a qualifier expression, orderings, etc.
+        - all of this expressed in terms of the object model.</para>
+    <para>Native queries describe a desired DB operation as SQL code (SQLSelect, SQLTemplate query)
+        or a reference to a stored procedure (ProcedureQuery), etc. The results of native queries
+        are usually presented as Lists of Maps, with each map representing a row in the DB (a term
+        "data row" is often used to describe such a map). They can potentially be converted to
+        objects, however it may take a considerable effort to do so. Native queries are also less
+        (if at all) portable across databases than object queries. </para>
     <section xml:id="selectquery">
-        <title>SelectQuery</title>
-        <para>SelectQuery is the most commonly used query in user applications. This may be the only
-            query you will need in most appplications. It returns a list of persistent objects of a
-            certain type specified in the
-            query:<programlisting language="java">SelectQuery query = new SelectQuery(Artist.class);
-List&lt;Artist> objects = context.performQuery(query);</programlisting>This
+        <title>ObjectSelect</title>
+        <para>
+            <emphasis role="italic">ObjectSelect supersedes older SelectQuery. SelectQuery is still
+                available and supported. </emphasis>
+        </para>
+        <para>ObjectSelect is the most commonly used query in Cayenne applications. This may be the
+            only query you will ever need. It returns a list of persistent objects (or data rows) of
+            a certain type specified in the
+            query:<programlisting language="java">List&lt;Artist> objects = ObjectSelect.query(Artist.class).select(context);</programlisting>This
             returned all rows in the "ARTIST" table. If the logs were turned on, you might see the
             following SQL
             printed:<programlisting language="java">INFO: SELECT t0.DATE_OF_BIRTH, t0.NAME, t0.ID FROM ARTIST t0
 INFO: === returned 5 row. - took 5 ms.</programlisting></para>
-        <para>This SQL was generated by Cayenne from the SelectQuery above. SelectQuery can have a
-            qualifier to select only the data that you care about. Qualifier is simply an Expression
-            (Expressions where discussed in the previous chapter). If you only want artists whose
-            name begins with 'Pablo', you might use the following qualifier expression:
-            <programlisting language="java">SelectQuery query = new SelectQuery(Artist.class, 
-        ExpressionFactory.likeExp(Artist.NAME_PROPERTY, "Pablo%"));
-List&lt;Artist> objects = context.performQuery(query);</programlisting>The
+        <para>This SQL was generated by Cayenne from the ObjectSelect above. ObjectSelect can have a
+            qualifier to select only the data matching specific criteria. Qualifier is simply an
+            Expression (Expressions where discussed in the previous chapter), appended to the query
+            using "where" method. If you only want artists whose name begins with 'Pablo', you might
+            use the following qualifier expression:
+            <programlisting language="java">List&lt;Artist> objects = ObjectSelect.query(Artist.class)
+        .where(Artist.NAME.like("Pablo%"))
+        .select(context);</programlisting>The
             SQL will look different this
             time:<programlisting language="java">INFO: SELECT t0.DATE_OF_BIRTH, t0.NAME, t0.ID FROM ARTIST t0 WHERE t0.NAME LIKE ?
 [bind: 1->NAME:'Pablo%']
 INFO: === returned 1 row. - took 6 ms.</programlisting></para>
-        <para>SelectQuery allows to append parts of qualifier to
-            self:<programlisting language="java">SelectQuery query = new SelectQuery(Artist.class);
-query.setQualifier(ExpressionFactory.likeExp(Artist.NAME_PROPERTY, "A%"));
-query.andQualifier(ExpressionFactory.greaterExp(Artist.DATE_OF_BIRTH_PROPERTY, someDate));</programlisting></para>
-        <para>To order the results of SelectQuery, one or more Orderings can be applied. Ordering
-            were already discussed earlier.
-            E.g.:<programlisting language="java">SelectQuery query = new SelectQuery(Artist.class);
-
-// create Ordering object explicitly
-query.addOrdering(new Ordering(Artist.DATE_OF_BIRTH_PROPERTY, SortOrder.DESCENDING));
-
-// or let SelectQuery create it behind the scenes
-query.addOrdering(Artist.NAME_PROPERTY, SortOrder.ASCENDING);</programlisting></para>
-        <para>There's a number of other useful properties in SelectQuery that define what to select
+        <para>ObjectSelect allows to assemble qualifier from parts, using "and" and "or" method to
+            chain then
+            together:<programlisting language="java">List&lt;Artist> objects = ObjectSelect.query(Artist.class)
+        .where(Artist.NAME.like("A%"))
+        .and(Artist.DATE_OF_BIRTH.gt(someDate)
+        .select(context);</programlisting></para>
+        <para>To order the results of ObjectSelect, one or more orderings can be
+            applied:<programlisting language="java">List&lt;Artist> objects = ObjectSelect.query(Artist.class)
+        .addOrderBy(Artist.DATE_OF_BIRTH.desc())
+        .addOrderBy(Artist.NAME.asc())
+        .select(context);</programlisting></para>
+        <para>There's a number of other useful methods in ObjectSelect that define what to select
             and how to optimize database interaction (prefetching, caching, fetch offset and limit,
             pagination, etc.). Some of them are discussed in separate chapters on caching and
             performance optimization. Others are fairly self-explanatory. Please check the API docs
-            for the full extent of the SelectQuery features.</para>
+            for the full extent of the ObjectSelect features.</para>
     </section>
     <section xml:id="ejbqlquery">
         <title>EJBQLQuery</title>