cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aadamc...@apache.org
Subject [3/8] cayenne git commit: CAY-2007 Refactoring SelectTranslator for better extensibility
Date Mon, 11 May 2015 04:42:38 GMT
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;
 	}


Mime
View raw message