This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch past-M2
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 2b2cebe5585199cb5ea6f8232967d66827a71063
Author: Andrus Adamchik <[email protected]>
AuthorDate: Sun May 31 10:11:12 2026 -0400

    cleanup
---
 .../cayenne/access/jdbc/SQLTemplateAction.java     | 731 ++++++++++-----------
 .../apache/cayenne/dba/hsqldb/HSQLDBSniffer.java   |   1 +
 .../org/apache/cayenne/dba/mysql/MySQLAdapter.java |  37 +-
 .../apache/cayenne/dba/oracle/OracleAdapter.java   |  24 +-
 .../cayenne/dba/postgres/PostgresAdapter.java      | 409 ++++++------
 5 files changed, 589 insertions(+), 613 deletions(-)

diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java 
b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
index 444c14e13..c9c13eb05 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SQLTemplateAction.java
@@ -60,370 +60,369 @@ import java.util.Map;
  */
 public class SQLTemplateAction implements SQLAction {
 
-       protected SQLTemplate query;
-       protected QueryMetadata queryMetadata;
-
-       protected DbEntity dbEntity;
-       protected DataNode dataNode;
-       protected DbAdapter dbAdapter;
-
-       /**
-        * @since 4.0
-        */
-       public SQLTemplateAction(SQLTemplate query, DataNode dataNode) {
-               this.query = query;
-               this.dataNode = dataNode;
-               this.queryMetadata = 
query.getMetaData(dataNode.getEntityResolver());
-               this.dbEntity = queryMetadata.getDbEntity();
-
-               // using unwrapped adapter to check for the right SQL flavor...
-               this.dbAdapter = dataNode.getAdapter().unwrap();
-       }
-
-       /**
-        * Returns unwrapped DbAdapter used to find correct SQL for a given DB.
-        */
-       public DbAdapter getAdapter() {
-               return dbAdapter;
-       }
-
-       /**
-        * Runs a SQLTemplate query, collecting all results. If a callback 
expects
-        * an iterated result, result processing is stopped after the first
-        * ResultSet is encountered.
-        */
-       @Override
-       public void performAction(Connection connection, OperationObserver 
callback) throws SQLException, Exception {
-
-               String template = extractTemplateString();
-
-               // sanity check - misconfigured templates
-               if (template == null) {
-                       throw new CayenneRuntimeException("No template string 
configured for adapter " + dbAdapter.getClass().getName());
-               }
-
-               boolean loggable = dataNode.getJdbcEventLogger().isLoggable();
-               List<Number> counts = new ArrayList<>();
-
-               // bind either positional or named parameters;
-               // for legacy reasons named parameters are processed as a 
batch.. this
-               // should go away after 4.0; newer positional parameter only 
support a
-               // single set of values.
-               if (query.getPositionalParams().isEmpty()) {
-                       runWithNamedParametersBatch(connection, callback, 
template, counts, loggable);
-               } else {
-                       runWithPositionalParameters(connection, callback, 
template, counts, loggable);
-               }
-
-               // notify of combined counts of all queries inside SQLTemplate
-               // multiplied by the number of parameter sets...
-               int[] ints = new int[counts.size()];
-               for (int i = 0; i < ints.length; i++) {
-                       ints[i] = counts.get(i).intValue();
-               }
-
-               callback.nextBatchCount(query, ints);
-       }
-
-       private void bindExtendedTypes(ParameterBinding[] bindings) {
-               int i = 1;
-               for (ParameterBinding binding : bindings) {
-                       Object value = binding.getValue();
-                       ExtendedType extendedType = value != null
-                                       ? 
getAdapter().getExtendedTypes().getRegisteredType(value.getClass())
-                                       : 
getAdapter().getExtendedTypes().getDefaultType();
-                       binding.setExtendedType(extendedType);
-                       binding.setStatementPosition(i++);
-               }
-       }
-
-       private void runWithPositionalParameters(Connection connection, 
OperationObserver callback, String template,
-                                                                               
         Collection<Number> counts, boolean loggable) throws Exception {
-
-               TranslatedSQL compiled = 
dataNode.getSqlTemplateTranslator().translate(template,
-                               query.getPositionalParams());
-
-               bindExtendedTypes(compiled.bindings());
-               if (loggable) {
-                       dataNode.getJdbcEventLogger().logQuery(compiled.sql(), 
compiled.bindings());
-               }
-
-               execute(connection, callback, compiled, counts);
-       }
-
-       @SuppressWarnings("unchecked")
-       private void runWithNamedParametersBatch(Connection connection, 
OperationObserver callback, String template,
-                                                                               
         Collection<Number> counts, boolean loggable) throws Exception {
-
-               int size = query.parametersSize();
-
-               // zero size indicates a one-shot query with no parameters
-               // so fake a single entry batch...
-               int batchSize = (size > 0) ? size : 1;
-
-               // for now supporting deprecated batch parameters...
-               Iterator<Map<String, ?>> it;
-               if(size == 0) {
-                       Iterator empty = 
Collections.singleton(Collections.emptyMap()).iterator();
-                       it = empty;
-               } else {
-                       it = query.parametersIterator();
-               }
-
-               for (int i = 0; i < batchSize; i++) {
-                       Map<String, ?> nextParameters = it.next();
-                       TranslatedSQL compiled = 
dataNode.getSqlTemplateTranslator().translate(template, nextParameters);
-                       bindExtendedTypes(compiled.bindings());
-                       if (loggable) {
-                               
dataNode.getJdbcEventLogger().logQuery(compiled.sql(), compiled.bindings());
-                       }
-
-                       execute(connection, callback, compiled, counts);
-               }
-
-       }
-
-       protected void execute(Connection connection, OperationObserver 
callback, TranslatedSQL compiled,
-                                                  Collection<Number> 
updateCounts) throws Exception {
-
-               long t1 = System.currentTimeMillis();
-               boolean iteratedResult = callback.isIteratedResult();
-               int generatedKeys = query.isReturnGeneratedKeys() ? 
Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS;
-               PreparedStatement statement = 
connection.prepareStatement(compiled.sql(), generatedKeys);
-
-               try {
-                       bind(statement, compiled.bindings());
-
-                       // process a mix of results
-                       boolean isResultSet = statement.execute();
-
-                       if(query.isReturnGeneratedKeys()) {
-                               ResultSet generatedKeysResultSet = 
statement.getGeneratedKeys();
-                               if (generatedKeysResultSet != null) {
-                                       processSelectResult(compiled, 
connection, statement, generatedKeysResultSet, callback, t1);
-                               }
-                       }
-
-                       boolean firstIteration = true;
-                       while (true) {
-                               if (firstIteration) {
-                                       firstIteration = false;
-                               } else {
-                                       isResultSet = 
statement.getMoreResults();
-                               }
-
-                               if (isResultSet) {
-
-                                       ResultSet resultSet = 
statement.getResultSet();
-                                       if (resultSet != null) {
-
-                                               try {
-                                                       
processSelectResult(compiled, connection, statement, resultSet, callback, t1);
-                                               } finally {
-                                                       if (!iteratedResult) {
-                                                               
resultSet.close();
-                                                       }
-                                               }
-
-                                               // ignore possible following 
update counts and bail early on iterated results
-                                               if (iteratedResult) {
-                                                       break;
-                                               }
-                                       }
-                               } else {
-                                       int updateCount = 
statement.getUpdateCount();
-                                       if (updateCount == -1) {
-                                               break;
-                                       }
-
-                                       updateCounts.add(updateCount);
-                                       
dataNode.getJdbcEventLogger().logUpdateCount(updateCount);
-                               }
-                       }
-               } finally {
-                       if (!iteratedResult) {
-                               statement.close();
-                       }
-               }
-       }
-
-       @SuppressWarnings({ "unchecked", "rawtypes" })
-       protected void processSelectResult(TranslatedSQL compiled, Connection 
connection, Statement statement,
-                                                                          
ResultSet resultSet, OperationObserver callback, final long startTime) throws 
Exception {
-
-               boolean iteratedResult = callback.isIteratedResult();
-               ExtendedTypeMap types = 
dataNode.getAdapter().getExtendedTypes();
-               RowDescriptorBuilder builder = 
configureRowDescriptorBuilder(compiled, resultSet);
-               recreateQueryMetadata(resultSet);
-               RowReader<?> rowReader = 
dataNode.rowReader(builder.getDescriptor(types), queryMetadata);
-               ResultIterator<?> it = new JDBCResultIterator<>(statement, 
resultSet, rowReader);
-
-               if (iteratedResult) {
-
-                       it = new ConnectionAwareResultIterator(it, connection) {
-                               @Override
-                               protected void doClose() {
-                                       
dataNode.getJdbcEventLogger().logSelectCount(rowCounter, 
System.currentTimeMillis() - startTime);
-                                       super.doClose();
-                               }
-                       };
-               }
-
-               it = new LimitResultIterator<>(it, getFetchOffset(), 
query.getFetchLimit());
-
-               if (iteratedResult) {
-                       try {
-                               callback.nextRows(query, it);
-                       } catch (Exception ex) {
-                               it.close();
-                               throw ex;
-                       }
-               } else {
-                       // note that we are not closing the iterator here, 
relying on caller
-                       // to close the underlying ResultSet on its own... this 
is a hack,
-                       // maybe a cleaner flow is due here.
-                       List<?> resultRows = it.allRows();
-
-                       
dataNode.getJdbcEventLogger().logSelectCount(resultRows.size(), 
System.currentTimeMillis() - startTime);
-
-                       callback.nextRows(query, resultRows);
-               }
-       }
-
-       private void recreateQueryMetadata(ResultSet resultSet) throws 
SQLException {
-               if(query.isUseScalar() && queryMetadata.getResultSetMapping() 
!= null && queryMetadata.getResultSetMapping().isEmpty()){
-                       for(int i = 0; i < 
resultSet.getMetaData().getColumnCount(); i++) {
-                               queryMetadata.getResultSetMapping().add(new 
DefaultScalarResultSegment(String.valueOf(i), i));
-                       }
-               }
-       }
-
-       /**
-        * Creates column descriptors based on compiled statement and query 
metadata
-        */
-       private ColumnDescriptor[] createColumnDescriptors(TranslatedSQL 
compiled) {
-               // SQLTemplate #result columns take precedence over other ways 
to determine the type
-               if (compiled.resultColumns().length > 0) {
-                       if(query.getResultColumnsTypes() != null) {
-                               throw new CayenneRuntimeException("Caused by 
setting return types by directives and by parameters in query.");
-                       } else {
-                               return compiled.resultColumns();
-                       }
-               }
-
-               // check explicitly set column types
-               if(query.getResultColumnsTypes() == null) {
-                       return null;
-               }
-
-               int size = query.getResultColumnsTypes().size();
-               ColumnDescriptor[] columnDescriptors = new 
ColumnDescriptor[size];
-               for(int i = 0; i < size; i++) {
-                       ColumnDescriptor columnDescriptor = new 
ColumnDescriptor();
-                       
columnDescriptor.setJavaClass(query.getResultColumnsTypes().get(i).getCanonicalName());
-                       columnDescriptors[i] = columnDescriptor;
-               }
-               return columnDescriptors;
-       }
-
-       /**
-        * @since 3.0
-        */
-       protected RowDescriptorBuilder 
configureRowDescriptorBuilder(TranslatedSQL compiled, ResultSet resultSet)
-                       throws SQLException {
-               RowDescriptorBuilder builder = new RowDescriptorBuilder()
-                               .setResultSet(resultSet)
-                               .setColumns(createColumnDescriptors(compiled))
-                               .validateDuplicateColumnNames();
-
-               if(query.getResultColumnsTypes() != null) {
-                       builder.mergeColumnsWithRsMetadata();
-               }
-
-               ObjEntity entity = queryMetadata.getObjEntity();
-               if (entity != null && isResultColumnTypesEmpty()) {
-                       // TODO: andrus 2008/03/28 support flattened attributes 
with aliases...
-                       for (ObjAttribute attribute : entity.getAttributes()) {
-                               CayennePath column = 
attribute.getDbAttributePath();
-                               if (column == null || column.length() > 1) {
-                                       continue;
-                               }
-                               builder.overrideColumnType(column.value(), 
attribute.getType());
-                       }
-               }
-
-               // override numeric Java types based on JDBC defaults for 
DbAttributes, as Oracle
-               // ResultSetMetadata is not very precise about NUMERIC 
distinctions...
-               // (BigDecimal vs Long vs. Integer)
-               if (dbEntity != null && isResultColumnTypesEmpty()) {
-                       for (DbAttribute attribute : dbEntity.getAttributes()) {
-                               if (!builder.isOverriden(attribute.getName()) 
&& TypesMapping.isNumeric(attribute.getType())) {
-                                       
builder.overrideColumnType(attribute.getName(), 
TypesMapping.getJavaBySqlType(attribute));
-                               }
-                       }
-               }
-
-               switch (query.getColumnNamesCapitalization()) {
-                       case LOWER:
-                               builder.useLowercaseColumnNames();
-                               break;
-                       case UPPER:
-                               builder.useUppercaseColumnNames();
-                               break;
-               }
-
-               return builder;
-       }
-
-       private boolean isResultColumnTypesEmpty(){
-               return query.getResultColumnsTypes() == null || 
query.getResultColumnsTypes().isEmpty();
-       }
-
-       /**
-        * Extracts a template string from a SQLTemplate query. Exists mainly 
for
-        * the benefit of subclasses that can customize returned template.
-        *
-        * @since 1.2
-        */
-       protected String extractTemplateString() {
-               String sql = query.getTemplate(dbAdapter.getClass().getName());
-
-               // note that we MUST convert line breaks to spaces. On some 
databases (DB2)
-               // queries with breaks simply won't run; the rest are affected 
by CAY-726.
-               return Util.stripLineBreaks(sql, ' ');
-       }
-
-       /**
-        * Binds parameters to the PreparedStatement.
-        */
-       protected void bind(PreparedStatement preparedStatement, 
ParameterBinding[] bindings)
-                       throws SQLException, Exception {
-               // bind parameters
-               for (ParameterBinding binding : bindings) {
-                       dataNode.getAdapter().bindParameter(preparedStatement, 
binding);
-               }
-
-               if (queryMetadata.getStatementFetchSize() != 0) {
-                       
preparedStatement.setFetchSize(queryMetadata.getStatementFetchSize());
-               }
-
-               int queryTimeout = queryMetadata.getQueryTimeout();
-               if(queryTimeout != QueryMetadata.QUERY_TIMEOUT_DEFAULT) {
-                       preparedStatement.setQueryTimeout(queryTimeout);
-               }
-       }
-
-       /**
-        * Returns a SQLTemplate for this action.
-        */
-       public SQLTemplate getQuery() {
-               return query;
-       }
-
-       /**
-        * @since 3.0
-        */
-       protected int getFetchOffset() {
-               return query.getFetchOffset();
-       }
+    protected SQLTemplate query;
+    protected QueryMetadata queryMetadata;
+
+    protected DbEntity dbEntity;
+    protected DataNode dataNode;
+    protected DbAdapter dbAdapter;
+
+    /**
+     * @since 4.0
+     */
+    public SQLTemplateAction(SQLTemplate query, DataNode dataNode) {
+        this.query = query;
+        this.dataNode = dataNode;
+        this.queryMetadata = query.getMetaData(dataNode.getEntityResolver());
+        this.dbEntity = queryMetadata.getDbEntity();
+
+        // using unwrapped adapter to check for the right SQL flavor...
+        this.dbAdapter = dataNode.getAdapter().unwrap();
+    }
+
+    /**
+     * Returns unwrapped DbAdapter used to find correct SQL for a given DB.
+     */
+    public DbAdapter getAdapter() {
+        return dbAdapter;
+    }
+
+    /**
+     * Runs a SQLTemplate query, collecting all results. If a callback expects
+     * an iterated result, result processing is stopped after the first
+     * ResultSet is encountered.
+     */
+    @Override
+    public void performAction(Connection connection, OperationObserver 
callback) throws SQLException, Exception {
+
+        String template = extractTemplateString();
+
+        // sanity check - misconfigured templates
+        if (template == null) {
+            throw new CayenneRuntimeException("No template string configured 
for adapter " + dbAdapter.getClass().getName());
+        }
+
+        boolean loggable = dataNode.getJdbcEventLogger().isLoggable();
+        List<Number> counts = new ArrayList<>();
+
+        // bind either positional or named parameters;
+        // for legacy reasons named parameters are processed as a batch.. this
+        // should go away after 4.0; newer positional parameter only support a
+        // single set of values.
+        if (query.getPositionalParams().isEmpty()) {
+            runWithNamedParametersBatch(connection, callback, template, 
counts, loggable);
+        } else {
+            runWithPositionalParameters(connection, callback, template, 
counts, loggable);
+        }
+
+        // notify of combined counts of all queries inside SQLTemplate
+        // multiplied by the number of parameter sets...
+        int[] ints = new int[counts.size()];
+        for (int i = 0; i < ints.length; i++) {
+            ints[i] = counts.get(i).intValue();
+        }
+
+        callback.nextBatchCount(query, ints);
+    }
+
+    private void bindExtendedTypes(ParameterBinding[] bindings) {
+        int i = 1;
+        for (ParameterBinding binding : bindings) {
+            Object value = binding.getValue();
+            ExtendedType extendedType = value != null
+                    ? 
getAdapter().getExtendedTypes().getRegisteredType(value.getClass())
+                    : getAdapter().getExtendedTypes().getDefaultType();
+            binding.setExtendedType(extendedType);
+            binding.setStatementPosition(i++);
+        }
+    }
+
+    private void runWithPositionalParameters(Connection connection, 
OperationObserver callback, String template,
+                                             Collection<Number> counts, 
boolean loggable) throws Exception {
+
+        TranslatedSQL compiled = 
dataNode.getSqlTemplateTranslator().translate(template,
+                query.getPositionalParams());
+
+        bindExtendedTypes(compiled.bindings());
+        if (loggable) {
+            dataNode.getJdbcEventLogger().logQuery(compiled.sql(), 
compiled.bindings());
+        }
+
+        execute(connection, callback, compiled, counts);
+    }
+
+    @SuppressWarnings("unchecked")
+    private void runWithNamedParametersBatch(Connection connection, 
OperationObserver callback, String template,
+                                             Collection<Number> counts, 
boolean loggable) throws Exception {
+
+        int size = query.parametersSize();
+
+        // zero size indicates a one-shot query with no parameters
+        // so fake a single entry batch...
+        int batchSize = (size > 0) ? size : 1;
+
+        // for now supporting deprecated batch parameters...
+        Iterator<Map<String, ?>> it;
+        if (size == 0) {
+            Iterator empty = 
Collections.singleton(Collections.emptyMap()).iterator();
+            it = empty;
+        } else {
+            it = query.parametersIterator();
+        }
+
+        for (int i = 0; i < batchSize; i++) {
+            Map<String, ?> nextParameters = it.next();
+            TranslatedSQL compiled = 
dataNode.getSqlTemplateTranslator().translate(template, nextParameters);
+            bindExtendedTypes(compiled.bindings());
+            if (loggable) {
+                dataNode.getJdbcEventLogger().logQuery(compiled.sql(), 
compiled.bindings());
+            }
+
+            execute(connection, callback, compiled, counts);
+        }
+
+    }
+
+    protected void execute(Connection connection, OperationObserver callback, 
TranslatedSQL compiled,
+                           Collection<Number> updateCounts) throws Exception {
+
+        long t1 = System.currentTimeMillis();
+        boolean iteratedResult = callback.isIteratedResult();
+        int generatedKeys = query.isReturnGeneratedKeys() ? 
Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS;
+        PreparedStatement statement = 
connection.prepareStatement(compiled.sql(), generatedKeys);
+
+        try {
+            bind(statement, compiled.bindings());
+
+            // process a mix of results
+            boolean isResultSet = statement.execute();
+
+            if (query.isReturnGeneratedKeys()) {
+                ResultSet generatedKeysResultSet = 
statement.getGeneratedKeys();
+                if (generatedKeysResultSet != null) {
+                    processSelectResult(compiled, connection, statement, 
generatedKeysResultSet, callback, t1);
+                }
+            }
+
+            boolean firstIteration = true;
+            while (true) {
+                if (firstIteration) {
+                    firstIteration = false;
+                } else {
+                    isResultSet = statement.getMoreResults();
+                }
+
+                if (isResultSet) {
+
+                    ResultSet resultSet = statement.getResultSet();
+                    if (resultSet != null) {
+
+                        try {
+                            processSelectResult(compiled, connection, 
statement, resultSet, callback, t1);
+                        } finally {
+                            if (!iteratedResult) {
+                                resultSet.close();
+                            }
+                        }
+
+                        // ignore possible following update counts and bail 
early on iterated results
+                        if (iteratedResult) {
+                            break;
+                        }
+                    }
+                } else {
+                    int updateCount = statement.getUpdateCount();
+                    if (updateCount == -1) {
+                        break;
+                    }
+
+                    updateCounts.add(updateCount);
+                    dataNode.getJdbcEventLogger().logUpdateCount(updateCount);
+                }
+            }
+        } finally {
+            if (!iteratedResult) {
+                statement.close();
+            }
+        }
+    }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    protected void processSelectResult(TranslatedSQL compiled, Connection 
connection, Statement statement,
+                                       ResultSet resultSet, OperationObserver 
callback, final long startTime) throws Exception {
+
+        boolean iteratedResult = callback.isIteratedResult();
+        ExtendedTypeMap types = dataNode.getAdapter().getExtendedTypes();
+        RowDescriptorBuilder builder = configureRowDescriptorBuilder(compiled, 
resultSet);
+        recreateQueryMetadata(resultSet);
+        RowReader<?> rowReader = 
dataNode.rowReader(builder.getDescriptor(types), queryMetadata);
+        ResultIterator<?> it = new JDBCResultIterator<>(statement, resultSet, 
rowReader);
+
+        if (iteratedResult) {
+
+            it = new ConnectionAwareResultIterator(it, connection) {
+                @Override
+                protected void doClose() {
+                    dataNode.getJdbcEventLogger().logSelectCount(rowCounter, 
System.currentTimeMillis() - startTime);
+                    super.doClose();
+                }
+            };
+        }
+
+        it = new LimitResultIterator<>(it, getFetchOffset(), 
query.getFetchLimit());
+
+        if (iteratedResult) {
+            try {
+                callback.nextRows(query, it);
+            } catch (Exception ex) {
+                it.close();
+                throw ex;
+            }
+        } else {
+            // note that we are not closing the iterator here, relying on 
caller
+            // to close the underlying ResultSet on its own... this is a hack,
+            // maybe a cleaner flow is due here.
+            List<?> resultRows = it.allRows();
+
+            dataNode.getJdbcEventLogger().logSelectCount(resultRows.size(), 
System.currentTimeMillis() - startTime);
+
+            callback.nextRows(query, resultRows);
+        }
+    }
+
+    private void recreateQueryMetadata(ResultSet resultSet) throws 
SQLException {
+        if (query.isUseScalar() && queryMetadata.getResultSetMapping() != null 
&& queryMetadata.getResultSetMapping().isEmpty()) {
+            for (int i = 0; i < resultSet.getMetaData().getColumnCount(); i++) 
{
+                queryMetadata.getResultSetMapping().add(new 
DefaultScalarResultSegment(String.valueOf(i), i));
+            }
+        }
+    }
+
+    /**
+     * Creates column descriptors based on compiled statement and query 
metadata
+     */
+    private ColumnDescriptor[] createColumnDescriptors(TranslatedSQL compiled) 
{
+        // SQLTemplate #result columns take precedence over other ways to 
determine the type
+        if (compiled.resultColumns().length > 0) {
+            if (query.getResultColumnsTypes() != null) {
+                throw new CayenneRuntimeException("Caused by setting return 
types by directives and by parameters in query.");
+            } else {
+                return compiled.resultColumns();
+            }
+        }
+
+        // check explicitly set column types
+        if (query.getResultColumnsTypes() == null) {
+            return null;
+        }
+
+        int size = query.getResultColumnsTypes().size();
+        ColumnDescriptor[] columnDescriptors = new ColumnDescriptor[size];
+        for (int i = 0; i < size; i++) {
+            ColumnDescriptor columnDescriptor = new ColumnDescriptor();
+            
columnDescriptor.setJavaClass(query.getResultColumnsTypes().get(i).getCanonicalName());
+            columnDescriptors[i] = columnDescriptor;
+        }
+        return columnDescriptors;
+    }
+
+    /**
+     * @since 3.0
+     */
+    protected RowDescriptorBuilder configureRowDescriptorBuilder(TranslatedSQL 
compiled, ResultSet resultSet)
+            throws SQLException {
+        RowDescriptorBuilder builder = new RowDescriptorBuilder()
+                .setResultSet(resultSet)
+                .setColumns(createColumnDescriptors(compiled))
+                .validateDuplicateColumnNames();
+
+        if (query.getResultColumnsTypes() != null) {
+            builder.mergeColumnsWithRsMetadata();
+        }
+
+        ObjEntity entity = queryMetadata.getObjEntity();
+        if (entity != null && isResultColumnTypesEmpty()) {
+            // TODO: andrus 2008/03/28 support flattened attributes with 
aliases...
+            for (ObjAttribute attribute : entity.getAttributes()) {
+                CayennePath column = attribute.getDbAttributePath();
+                if (column == null || column.length() > 1) {
+                    continue;
+                }
+                builder.overrideColumnType(column.value(), 
attribute.getType());
+            }
+        }
+
+        // override numeric Java types based on JDBC defaults for 
DbAttributes, as Oracle
+        // ResultSetMetadata is not very precise about NUMERIC distinctions...
+        // (BigDecimal vs Long vs. Integer)
+        if (dbEntity != null && isResultColumnTypesEmpty()) {
+            for (DbAttribute attribute : dbEntity.getAttributes()) {
+                if (!builder.isOverriden(attribute.getName()) && 
TypesMapping.isNumeric(attribute.getType())) {
+                    builder.overrideColumnType(attribute.getName(), 
TypesMapping.getJavaBySqlType(attribute));
+                }
+            }
+        }
+
+        switch (query.getColumnNamesCapitalization()) {
+            case LOWER:
+                builder.useLowercaseColumnNames();
+                break;
+            case UPPER:
+                builder.useUppercaseColumnNames();
+                break;
+        }
+
+        return builder;
+    }
+
+    private boolean isResultColumnTypesEmpty() {
+        return query.getResultColumnsTypes() == null || 
query.getResultColumnsTypes().isEmpty();
+    }
+
+    /**
+     * Extracts a template string from a SQLTemplate query. Exists mainly for
+     * the benefit of subclasses that can customize returned template.
+     *
+     * @since 1.2
+     */
+    protected String extractTemplateString() {
+        String sql = query.getTemplate(dbAdapter.getClass().getName());
+
+        // note that we MUST convert line breaks to spaces. On some databases 
(DB2)
+        // queries with breaks simply won't run; the rest are affected by 
CAY-726.
+        return Util.stripLineBreaks(sql, ' ');
+    }
+
+    /**
+     * Binds parameters to the PreparedStatement.
+     */
+    protected void bind(PreparedStatement preparedStatement, 
ParameterBinding[] bindings) throws Exception {
+        // bind parameters
+        for (ParameterBinding binding : bindings) {
+            dataNode.getAdapter().bindParameter(preparedStatement, binding);
+        }
+
+        if (queryMetadata.getStatementFetchSize() != 0) {
+            
preparedStatement.setFetchSize(queryMetadata.getStatementFetchSize());
+        }
+
+        int queryTimeout = queryMetadata.getQueryTimeout();
+        if (queryTimeout != QueryMetadata.QUERY_TIMEOUT_DEFAULT) {
+            preparedStatement.setQueryTimeout(queryTimeout);
+        }
+    }
+
+    /**
+     * Returns a SQLTemplate for this action.
+     */
+    public SQLTemplate getQuery() {
+        return query;
+    }
+
+    /**
+     * @since 3.0
+     */
+    protected int getFetchOffset() {
+        return query.getFetchOffset();
+    }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBSniffer.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBSniffer.java
index f83b8c32c..e4bfa0a90 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBSniffer.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBSniffer.java
@@ -50,6 +50,7 @@ public class HSQLDBSniffer implements DbAdapterDetector {
         boolean supportsSchema = md.getDriverMajorVersion() < 1
                 || md.getDriverMajorVersion() == 1 && 
md.getDriverMinorVersion() <= 8;
 
+        // TODO: is this correct? Looks like newer drivers result in 
HSQLDBNoSchemaAdapter (which is presumably legacy?)
         return supportsSchema
                 ? objectFactory.newInstance(DbAdapter.class, 
HSQLDBAdapter.class.getName())
                 : objectFactory.newInstance(DbAdapter.class, 
HSQLDBNoSchemaAdapter.class.getName());
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
index 391589391..71f796caa 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
@@ -49,7 +49,6 @@ import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.resource.ResourceLocator;
 
 import java.sql.PreparedStatement;
-import java.sql.SQLException;
 import java.sql.Types;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -135,7 +134,7 @@ public class MySQLAdapter extends JdbcAdapter {
                QuotingStrategy context = getQuotingStrategy();
                buf.append(context.quotedFullyQualifiedName(table));
 
-               return List.of("SET FOREIGN_KEY_CHECKS=0", "DROP TABLE IF 
EXISTS " + buf.toString() + " CASCADE",
+               return List.of("SET FOREIGN_KEY_CHECKS=0", "DROP TABLE IF 
EXISTS " + buf + " CASCADE",
                                "SET FOREIGN_KEY_CHECKS=1");
        }
 
@@ -228,26 +227,19 @@ public class MySQLAdapter extends JdbcAdapter {
        }
 
        @Override
-       public void bindParameter(PreparedStatement statement, ParameterBinding 
binding)
-                       throws SQLException, Exception {
+       public void bindParameter(PreparedStatement statement, ParameterBinding 
binding) throws Exception {
                binding.setJdbcType(mapNTypes(binding.getJdbcType()));
                super.bindParameter(statement, binding);
        }
 
        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;
-               }
+        return switch (sqlType) {
+            case Types.NCHAR -> Types.CHAR;
+            case Types.NCLOB -> Types.CLOB;
+            case Types.NVARCHAR -> Types.VARCHAR;
+            case Types.LONGNVARCHAR -> Types.LONGVARCHAR;
+            default -> sqlType;
+        };
        }
 
        /**
@@ -365,13 +357,10 @@ public class MySQLAdapter extends JdbcAdapter {
                // 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);
-               }
+        return switch (type) {
+            case Types.TIMESTAMP, Types.TIME -> true;
+            default -> super.typeSupportsLength(type);
+        };
        }
 
        @Override
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
index adb684e60..aa7673555 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
@@ -19,23 +19,12 @@
 
 package org.apache.cayenne.dba.oracle;
 
-import java.lang.reflect.Field;
-import java.sql.CallableStatement;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Types;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
 import org.apache.cayenne.access.types.ByteType;
-import org.apache.cayenne.access.types.CharType;
 import org.apache.cayenne.access.types.ExtendedType;
 import org.apache.cayenne.access.types.ExtendedTypeFactory;
 import org.apache.cayenne.access.types.ExtendedTypeMap;
@@ -45,7 +34,6 @@ import 
org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 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.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
@@ -56,6 +44,15 @@ import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.UpdateBatchQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
+import java.lang.reflect.Field;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Types;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
 /**
  * DbAdapter implementation for <a href="http://www.oracle.com";>Oracle RDBMS
  * </a>. Sample connection settings to use with Oracle are shown below:
@@ -232,8 +229,7 @@ public class OracleAdapter extends JdbcAdapter {
        }
 
        @Override
-       public void bindParameter(PreparedStatement statement, ParameterBinding 
binding)
-                       throws SQLException, Exception {
+       public void bindParameter(PreparedStatement statement, ParameterBinding 
binding) throws Exception {
 
                // Oracle doesn't support BOOLEAN even when binding NULL, so 
have to
                // intercept
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java 
b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
index eac2d8f4a..9a43ce915 100644
--- a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
+++ b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
@@ -19,14 +19,6 @@
 
 package org.apache.cayenne.dba.postgres;
 
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.sql.Types;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor;
@@ -40,7 +32,6 @@ import 
org.apache.cayenne.access.types.ValueObjectTypeRegistry;
 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.dba.TypesMapping;
 import org.apache.cayenne.di.Inject;
@@ -50,6 +41,13 @@ import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.resource.ResourceLocator;
 
+import java.sql.PreparedStatement;
+import java.sql.Types;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
 /**
  * DbAdapter implementation for <a href="http://www.postgresql.org";>PostgreSQL
  * RDBMS </a>. Sample connection settings to use with PostgreSQL are shown
@@ -64,211 +62,204 @@ import org.apache.cayenne.resource.ResourceLocator;
  */
 public class PostgresAdapter extends JdbcAdapter {
 
-       public static final String BYTEA = "bytea";
+    public static final String BYTEA = "bytea";
 
-       private List<String> SYSTEM_SCHEMAS = List.of("information_schema", 
"pg_catalog");
+    private static final List<String> SYSTEM_SCHEMAS = 
List.of("information_schema", "pg_catalog");
 
-       public PostgresAdapter(@Inject RuntimeProperties runtimeProperties,
-                                                  
@Inject(Constants.DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-                                                  
@Inject(Constants.USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-                                                  
@Inject(Constants.TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> 
extendedTypeFactories,
-                                                  
@Inject(Constants.RESOURCE_LOCATOR) ResourceLocator resourceLocator,
-                                                  @Inject 
ValueObjectTypeRegistry valueObjectTypeRegistry) {
-               super(runtimeProperties, defaultExtendedTypes, 
userExtendedTypes, extendedTypeFactories, resourceLocator, 
valueObjectTypeRegistry);
-               setSupportsBatchUpdates(true);
-               setSupportsGeneratedKeys(true);
-       }
+    public PostgresAdapter(@Inject RuntimeProperties runtimeProperties,
+                           @Inject(Constants.DEFAULT_TYPES_LIST) 
List<ExtendedType> defaultExtendedTypes,
+                           @Inject(Constants.USER_TYPES_LIST) 
List<ExtendedType> userExtendedTypes,
+                           @Inject(Constants.TYPE_FACTORIES_LIST) 
List<ExtendedTypeFactory> extendedTypeFactories,
+                           @Inject(Constants.RESOURCE_LOCATOR) ResourceLocator 
resourceLocator,
+                           @Inject ValueObjectTypeRegistry 
valueObjectTypeRegistry) {
+        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, 
extendedTypeFactories, resourceLocator, valueObjectTypeRegistry);
+        setSupportsBatchUpdates(true);
+        setSupportsGeneratedKeys(true);
+    }
 
     /**
      * @since 4.2
      */
-       @Override
-       public SQLTreeProcessor getSqlTreeProcessor() {
-               return new PostgreSQLTreeProcessor();
-       }
-
-       /**
-        * Uses PostgresActionBuilder to create the right action.
-        *
-        * @since 1.2
-        */
-       @Override
-       public SQLAction getAction(Query query, DataNode node) {
-               return query.createSQLAction(new PostgresActionBuilder(node));
-       }
-
-       /**
-        * Installs appropriate ExtendedTypes as converters for passing values
-        * between JDBC and Java layers.
-        */
-       @Override
-       protected void configureExtendedTypes(ExtendedTypeMap map) {
-
-               super.configureExtendedTypes(map);
-
-               CharType charType = new CharType(true, false);
-               map.registerType(charType);
-               map.registerType(new PostgresByteArrayType(true, true));
-               map.registerType(new JsonType(charType, false));
-       }
-
-       @Override
-       public DbAttribute buildAttribute(String name, String typeName, int 
type, int size, int scale, boolean allowNulls) {
-
-               if ("json".equalsIgnoreCase(typeName)) {
-                       type = Types.OTHER;
-               }
-               // "bytea" maps to pretty much any binary type, so
-               // it is up to us to select the most sensible default.
-               // And the winner is LONGVARBINARY
-               else if (BYTEA.equalsIgnoreCase(typeName)) {
-                       type = Types.LONGVARBINARY;
-               }
-               // oid is returned as INTEGER, need to make it BLOB
-               else if ("oid".equals(typeName)) {
-                       type = Types.BLOB;
-               }
-               // somehow the driver reverse-engineers "text" as VARCHAR, must 
be CLOB
-               else if ("text".equalsIgnoreCase(typeName)) {
-                       type = Types.CLOB;
-               }
-
-               return super.buildAttribute(name, typeName, type, size, scale, 
allowNulls);
-       }
-
-       @Override
-       public void bindParameter(PreparedStatement statement, ParameterBinding 
binding)
-                       throws SQLException, Exception {
-               binding.setJdbcType(mapNTypes(binding.getJdbcType()));
-               super.bindParameter(statement, binding);
-       }
-
-       private int mapNTypes(int sqlType) {
-               switch (sqlType) {
-               case Types.NCHAR:
-                       return Types.CHAR;
-               case Types.NCLOB:
-                       return Types.CLOB;
-               case Types.NVARCHAR:
-                       return Types.VARCHAR;
-               case Types.LONGNVARCHAR:
-                       return Types.LONGVARCHAR;
-
-               default:
-                       return sqlType;
-               }
-       }
-
-       /**
-        * Customizes table creating procedure for PostgreSQL. One difference 
with
-        * generic implementation is that "bytea" type has no explicit length 
unlike
-        * similar binary types in other databases.
-        *
-        * @since 1.0.2
-        */
-       @Override
-       public String createTable(DbEntity ent) {
-
-               QuotingStrategy context = getQuotingStrategy();
-               StringBuilder buf = new StringBuilder();
-               buf.append("CREATE TABLE 
").append(context.quotedFullyQualifiedName(ent)).append(" (");
-
-               // columns
-               Iterator<DbAttribute> it = ent.getAttributes().iterator();
-               boolean first = true;
-               while (it.hasNext()) {
-                       if (first) {
-                               first = false;
-                       } else {
-                               buf.append(", ");
-                       }
-
-                       createAttribute(ent, context, buf, it.next());
-               }
-
-               // primary key clause
-               Iterator<DbAttribute> pkit = ent.getPrimaryKeys().iterator();
-               if (pkit.hasNext()) {
-                       if (first) {
-                               first = false;
-                       } else {
-                               buf.append(", ");
-                       }
-
-                       buf.append("PRIMARY KEY (");
-                       boolean firstPk = true;
-                       while (pkit.hasNext()) {
-                               if (firstPk) {
-                                       firstPk = false;
-                               } else {
-                                       buf.append(", ");
-                               }
-
-                               DbAttribute at = pkit.next();
-                               buf.append(context.quotedName(at));
-                       }
-                       buf.append(')');
-               }
-               buf.append(')');
-               return buf.toString();
-       }
-
-       private void createAttribute(DbEntity ent, QuotingStrategy context, 
StringBuilder buf, DbAttribute at) {
-               // attribute may not be fully valid, do a simple check
-               if (at.getType() == TypesMapping.NOT_DEFINED) {
-                       throw new CayenneRuntimeException("Undefined type for 
attribute '%s.%s'"
-                                       , ent.getFullyQualifiedName(), 
at.getName());
-               }
-
-               String[] types = externalTypesForJdbcType(at.getType());
-               if (types == null || types.length == 0) {
-                       throw new CayenneRuntimeException("Undefined type for 
attribute '%s.%s': %s"
-                                       , ent.getFullyQualifiedName(), 
at.getName(), at.getType());
-               }
-
-               // Checking that attribute is generated and we have alternative 
types in types.xml.
-               // If so, use those autoincremented types. For example serial, 
bigserial, smallserial.
-               String type = (at.isGenerated() && types.length > 1) ? types[1] 
: types[0];
-
-               buf.append(context.quotedName(at)).append(' 
').append(type).append(sizeAndPrecision(this, at))
-                               .append(at.isMandatory() ? " NOT" : 
"").append(" NULL");
-       }
-
-       @Override
-       public boolean typeSupportsLength(int type) {
-               // "bytea" type does not support length
-               if(Types.DOUBLE == type || Types.REAL == type){
-                       return false;
-               }
-               String[] externalTypes = externalTypesForJdbcType(type);
-               if (externalTypes != null && externalTypes.length > 0) {
-                       for (String externalType : externalTypes) {
-                               if (BYTEA.equalsIgnoreCase(externalType)) {
-                                       return false;
-                               }
-                       }
-               }
-
-               return super.typeSupportsLength(type);
-       }
-
-       /**
-        * Adds the CASCADE option to the DROP TABLE clause.
-        */
-       @Override
-       public Collection<String> dropTableStatements(DbEntity table) {
-               QuotingStrategy context = getQuotingStrategy();
-               return Collections.singleton("DROP TABLE " + 
context.quotedFullyQualifiedName(table) + " CASCADE");
-       }
-
-       @Override
-       public boolean supportsCatalogsOnReverseEngineering() {
-               return false;
-       }
-
-       @Override
-       public List<String> getSystemSchemas() {
-               return SYSTEM_SCHEMAS;
-       }
+    @Override
+    public SQLTreeProcessor getSqlTreeProcessor() {
+        return new PostgreSQLTreeProcessor();
+    }
+
+    /**
+     * Uses PostgresActionBuilder to create the right action.
+     *
+     * @since 1.2
+     */
+    @Override
+    public SQLAction getAction(Query query, DataNode node) {
+        return query.createSQLAction(new PostgresActionBuilder(node));
+    }
+
+    /**
+     * Installs appropriate ExtendedTypes as converters for passing values
+     * between JDBC and Java layers.
+     */
+    @Override
+    protected void configureExtendedTypes(ExtendedTypeMap map) {
+
+        super.configureExtendedTypes(map);
+
+        CharType charType = new CharType(true, false);
+        map.registerType(charType);
+        map.registerType(new PostgresByteArrayType(true, true));
+        map.registerType(new JsonType(charType, false));
+    }
+
+    @Override
+    public DbAttribute buildAttribute(String name, String typeName, int type, 
int size, int scale, boolean allowNulls) {
+
+        if ("json".equalsIgnoreCase(typeName)) {
+            type = Types.OTHER;
+        }
+        // "bytea" maps to pretty much any binary type, so
+        // it is up to us to select the most sensible default.
+        // And the winner is LONGVARBINARY
+        else if (BYTEA.equalsIgnoreCase(typeName)) {
+            type = Types.LONGVARBINARY;
+        }
+        // oid is returned as INTEGER, need to make it BLOB
+        else if ("oid".equals(typeName)) {
+            type = Types.BLOB;
+        }
+        // somehow the driver reverse-engineers "text" as VARCHAR, must be CLOB
+        else if ("text".equalsIgnoreCase(typeName)) {
+            type = Types.CLOB;
+        }
+
+        return super.buildAttribute(name, typeName, type, size, scale, 
allowNulls);
+    }
+
+    @Override
+    public void bindParameter(PreparedStatement statement, ParameterBinding 
binding) throws Exception {
+        binding.setJdbcType(mapNTypes(binding.getJdbcType()));
+        super.bindParameter(statement, binding);
+    }
+
+    private int mapNTypes(int sqlType) {
+        return switch (sqlType) {
+            case Types.NCHAR -> Types.CHAR;
+            case Types.NCLOB -> Types.CLOB;
+            case Types.NVARCHAR -> Types.VARCHAR;
+            case Types.LONGNVARCHAR -> Types.LONGVARCHAR;
+            default -> sqlType;
+        };
+    }
+
+    /**
+     * Customizes table creating procedure for PostgreSQL. One difference with
+     * generic implementation is that "bytea" type has no explicit length 
unlike
+     * similar binary types in other databases.
+     *
+     * @since 1.0.2
+     */
+    @Override
+    public String createTable(DbEntity ent) {
+
+        QuotingStrategy context = getQuotingStrategy();
+        StringBuilder buf = new StringBuilder();
+        buf.append("CREATE TABLE 
").append(context.quotedFullyQualifiedName(ent)).append(" (");
+
+        // columns
+        Iterator<DbAttribute> it = ent.getAttributes().iterator();
+        boolean first = true;
+        while (it.hasNext()) {
+            if (first) {
+                first = false;
+            } else {
+                buf.append(", ");
+            }
+
+            createAttribute(ent, context, buf, it.next());
+        }
+
+        // primary key clause
+        Iterator<DbAttribute> pkit = ent.getPrimaryKeys().iterator();
+        if (pkit.hasNext()) {
+            if (first) {
+                first = false;
+            } else {
+                buf.append(", ");
+            }
+
+            buf.append("PRIMARY KEY (");
+            boolean firstPk = true;
+            while (pkit.hasNext()) {
+                if (firstPk) {
+                    firstPk = false;
+                } else {
+                    buf.append(", ");
+                }
+
+                DbAttribute at = pkit.next();
+                buf.append(context.quotedName(at));
+            }
+            buf.append(')');
+        }
+        buf.append(')');
+        return buf.toString();
+    }
+
+    private void createAttribute(DbEntity ent, QuotingStrategy context, 
StringBuilder buf, DbAttribute at) {
+        // attribute may not be fully valid, do a simple check
+        if (at.getType() == TypesMapping.NOT_DEFINED) {
+            throw new CayenneRuntimeException("Undefined type for attribute 
'%s.%s'"
+                    , ent.getFullyQualifiedName(), at.getName());
+        }
+
+        String[] types = externalTypesForJdbcType(at.getType());
+        if (types == null || types.length == 0) {
+            throw new CayenneRuntimeException("Undefined type for attribute 
'%s.%s': %s"
+                    , ent.getFullyQualifiedName(), at.getName(), at.getType());
+        }
+
+        // Checking that attribute is generated and we have alternative types 
in types.xml.
+        // If so, use those autoincremented types. For example serial, 
bigserial, smallserial.
+        String type = (at.isGenerated() && types.length > 1) ? types[1] : 
types[0];
+
+        buf.append(context.quotedName(at)).append(' 
').append(type).append(sizeAndPrecision(this, at))
+                .append(at.isMandatory() ? " NOT" : "").append(" NULL");
+    }
+
+    @Override
+    public boolean typeSupportsLength(int type) {
+        // "bytea" type does not support length
+        if (Types.DOUBLE == type || Types.REAL == type) {
+            return false;
+        }
+        String[] externalTypes = externalTypesForJdbcType(type);
+        if (externalTypes != null && externalTypes.length > 0) {
+            for (String externalType : externalTypes) {
+                if (BYTEA.equalsIgnoreCase(externalType)) {
+                    return false;
+                }
+            }
+        }
+
+        return super.typeSupportsLength(type);
+    }
+
+    /**
+     * Adds the CASCADE option to the DROP TABLE clause.
+     */
+    @Override
+    public Collection<String> dropTableStatements(DbEntity table) {
+        QuotingStrategy context = getQuotingStrategy();
+        return Collections.singleton("DROP TABLE " + 
context.quotedFullyQualifiedName(table) + " CASCADE");
+    }
+
+    @Override
+    public boolean supportsCatalogsOnReverseEngineering() {
+        return false;
+    }
+
+    @Override
+    public List<String> getSystemSchemas() {
+        return SYSTEM_SCHEMAS;
+    }
 
 }

Reply via email to