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

Reply via email to