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:39 UTC

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

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;
+	}
 }