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