This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 6b4838d56dbdedce9a1fe3c2ccd8dc71d5504513 Author: Andrus Adamchik <[email protected]> AuthorDate: Sun Jun 28 15:28:54 2026 -0400 Refactoring row descriptors: * ColumnDescriptor: ExtendedType type property * Getting rid of RowDescriptor ... ColumnDescriptor[] now gives us a full row * Inline RowDescriptorBuilder into ColumnDescriptor --- .../reader/CryptoRowReaderFactoryDecorator.java | 86 +++--- .../apache/cayenne/velocity/ResultDirective.java | 8 +- .../VelocitySQLTemplateTranslator_SelectTest.java | 8 +- .../java/org/apache/cayenne/access/DataNode.java | 29 --- .../apache/cayenne/access/jdbc/BaseSQLAction.java | 4 +- .../apache/cayenne/access/jdbc/BatchAction.java | 20 +- .../cayenne/access/jdbc/ColumnDescriptor.java | 264 ++++++++++++++++++- .../cayenne/access/jdbc/ProcedureAction.java | 25 +- .../apache/cayenne/access/jdbc/RowDescriptor.java | 71 ----- .../cayenne/access/jdbc/RowDescriptorBuilder.java | 287 --------------------- .../cayenne/access/jdbc/SQLTemplateAction.java | 24 +- .../apache/cayenne/access/jdbc/SelectAction.java | 8 +- .../cayenne/access/jdbc/reader/BaseRowReader.java | 8 +- .../jdbc/reader/DefaultRowReaderFactory.java | 44 ++-- .../access/jdbc/reader/EmbeddableRowReader.java | 7 +- .../access/jdbc/reader/EntityRowReader.java | 7 +- .../cayenne/access/jdbc/reader/FullRowReader.java | 8 +- .../cayenne/access/jdbc/reader/IdRowReader.java | 7 +- .../reader/InheritanceAwareEntityRowReader.java | 6 +- .../jdbc/reader/InheritanceAwareRowReader.java | 6 +- .../access/jdbc/reader/RowReaderFactory.java | 4 +- .../access/jdbc/reader/ScalarRowReader.java | 8 +- .../translator/select/ColumnDescriptorStage.java | 4 +- .../access/translator/sqltemplate/Context.java | 10 + .../translator/sqltemplate/directive/Result.java | 2 +- .../apache/cayenne/dba/db2/DB2ProcedureAction.java | 6 +- .../cayenne/dba/mysql/MySQLProcedureAction.java | 6 +- .../cayenne/dba/oracle/OracleProcedureAction.java | 6 +- .../dba/oracle/OracleSQLTemplateAction.java | 24 +- .../dba/sqlserver/SQLServerProcedureAction.java | 6 +- .../access/jdbc/JDBCResultIteratorTest.java | 4 +- .../select/MockSelectTranslatorContext.java | 12 +- 32 files changed, 442 insertions(+), 577 deletions(-) diff --git a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/reader/CryptoRowReaderFactoryDecorator.java b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/reader/CryptoRowReaderFactoryDecorator.java index b48cdb692..d8a6893fd 100644 --- a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/reader/CryptoRowReaderFactoryDecorator.java +++ b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/reader/CryptoRowReaderFactoryDecorator.java @@ -22,7 +22,6 @@ import java.sql.ResultSet; import java.util.Map; import org.apache.cayenne.access.jdbc.ColumnDescriptor; -import org.apache.cayenne.access.jdbc.RowDescriptor; import org.apache.cayenne.access.jdbc.reader.DefaultRowReaderFactory; import org.apache.cayenne.access.jdbc.reader.RowReader; import org.apache.cayenne.access.types.ExtendedType; @@ -60,80 +59,63 @@ public class CryptoRowReaderFactoryDecorator extends DefaultRowReaderFactory { } @Override - public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, DbAdapter adapter) { - RowDescriptor encryptedRowDescriptor = encryptedRowDescriptor(descriptor, adapter.getExtendedTypes()); - return super.rowReader(encryptedRowDescriptor, queryMetadata, adapter); + public RowReader<?> rowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata, DbAdapter adapter) { + return super.rowReader(encryptedColumns(columns, adapter.getExtendedTypes()), queryMetadata, adapter); } @Override - protected RowReader<?> createScalarRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, + protected RowReader<?> createScalarRowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata, ScalarResultSegment segment) { RowReader<?> scalarRowReader = super - .createScalarRowReader(descriptor, queryMetadata, segment); - return new DecoratedScalarRowReader(descriptor.getColumns()[segment.getColumnOffset()], scalarRowReader); + .createScalarRowReader(columns, queryMetadata, segment); + return new DecoratedScalarRowReader(columns[segment.getColumnOffset()], scalarRowReader); } @Override - protected RowReader<?> createEntityRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, + protected RowReader<?> createEntityRowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata, EntityResultSegment resultMetadata) { RowReader<?> entityRowReader = super - .createEntityRowReader(descriptor, queryMetadata, resultMetadata); - return new DecoratedEntityRowReader(descriptor, entityRowReader, resultMetadata); + .createEntityRowReader(columns, queryMetadata, resultMetadata); + return new DecoratedEntityRowReader(columns, entityRowReader, resultMetadata); } @Override - protected RowReader<?> createFullRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata) { + protected RowReader<?> createFullRowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata) { RowReader<?> fullRowReader = super - .createFullRowReader(descriptor, queryMetadata); - return new DecoratedFullRowReader(descriptor, fullRowReader); + .createFullRowReader(columns, queryMetadata); + return new DecoratedFullRowReader(columns, fullRowReader); } - protected RowDescriptor encryptedRowDescriptor(RowDescriptor descriptor, ExtendedTypeMap typeMap) { + protected ColumnDescriptor[] encryptedColumns(ColumnDescriptor[] columns, ExtendedTypeMap typeMap) { - // need to tweak the original descriptor to ensure binary columns are read as binary, eben if the plain Java - // type is not a byte[] + // need to tweak the columns to ensure encrypted columns are read as binary or char, even if the plain Java + // type is not a byte[] / String - ColumnDescriptor[] originalColumns = descriptor.getColumns(); - int len = originalColumns.length; + ColumnDescriptor[] encrypted = new ColumnDescriptor[columns.length]; - ExtendedType[] originalConverters = descriptor.getConverters(); - ExtendedType[] encryptedConverters = new ExtendedType[len]; + for (int i = 0; i < columns.length; i++) { + ColumnDescriptor column = columns[i]; + DbAttribute attribute = column.attribute(); - for (int i = 0; i < len; i++) { - DbAttribute attribute = originalColumns[i].attribute(); - - ExtendedType t = originalConverters[i]; + ExtendedType type = column.type(); if (attribute != null && columnMapper.isEncrypted(attribute)) { // only char or binary columns can store encrypted data if (TypesMapping.isBinary(attribute.getType())) { - t = typeMap.getRegisteredType(byte[].class); + type = typeMap.getRegisteredType(byte[].class); } else if (TypesMapping.isCharacter(attribute.getType())) { - t = typeMap.getRegisteredType(String.class); + type = typeMap.getRegisteredType(String.class); } // else - warning? } - encryptedConverters[i] = t; - } - - return new DecoratedRowDescriptor(descriptor, originalColumns, encryptedConverters); - } - - private static class DecoratedRowDescriptor extends RowDescriptor { - - private final RowDescriptor original; - - DecoratedRowDescriptor(RowDescriptor rowDescriptor, ColumnDescriptor[] columns, ExtendedType[] converters) { - this.original = rowDescriptor; - this.columns = columns; - this.converters = converters; + encrypted[i] = type == column.type() + ? column + : new ColumnDescriptor(column.name(), column.dataRowKey(), column.jdbcType(), type, attribute); } - public RowDescriptor unwrap() { - return original; - } + return encrypted; } private class DecoratedScalarRowReader implements RowReader<Object> { @@ -164,16 +146,16 @@ public class CryptoRowReaderFactoryDecorator extends DefaultRowReaderFactory { private abstract class DecoratedEntityFullRowReader implements RowReader<Object> { - final RowDescriptor descriptor; + final ColumnDescriptor[] columns; final RowReader<?> delegateReader; final EntityResultSegment resultMetadata; boolean decryptorCompiled; MapTransformer decryptor; - DecoratedEntityFullRowReader(RowDescriptor descriptor, + DecoratedEntityFullRowReader(ColumnDescriptor[] columns, RowReader<?> delegateReader, EntityResultSegment resultMetadata) { - this.descriptor = descriptor; + this.columns = columns; this.delegateReader = delegateReader; this.resultMetadata = resultMetadata; } @@ -198,10 +180,10 @@ public class CryptoRowReaderFactoryDecorator extends DefaultRowReaderFactory { private class DecoratedEntityRowReader extends DecoratedEntityFullRowReader { - DecoratedEntityRowReader(RowDescriptor descriptor, + DecoratedEntityRowReader(ColumnDescriptor[] columns, RowReader<?> delegateReader, EntityResultSegment resultMetadata) { - super(descriptor, delegateReader, resultMetadata); + super(columns, delegateReader, resultMetadata); } void ensureDecryptorCompiled(Object row) { @@ -211,7 +193,7 @@ public class CryptoRowReaderFactoryDecorator extends DefaultRowReaderFactory { ColumnDescriptor[] columnDescriptors = new ColumnDescriptor[fieldsSize]; for(int i = offset, j = 0; i < offset + fieldsSize; i++) { - columnDescriptors[j++] = descriptor.getColumns()[i]; + columnDescriptors[j++] = columns[i]; } decryptor = transformerFactory.decryptor(columnDescriptors, row); decryptorCompiled = true; @@ -226,14 +208,14 @@ public class CryptoRowReaderFactoryDecorator extends DefaultRowReaderFactory { private class DecoratedFullRowReader extends DecoratedEntityFullRowReader { - DecoratedFullRowReader(RowDescriptor descriptor, + DecoratedFullRowReader(ColumnDescriptor[] columns, RowReader<?> delegateReader) { - super(descriptor, delegateReader, null); + super(columns, delegateReader, null); } void ensureDecryptorCompiled(Object row) { if (!decryptorCompiled) { - decryptor = transformerFactory.decryptor(descriptor.getColumns(), row); + decryptor = transformerFactory.decryptor(columns, row); decryptorCompiled = true; } } diff --git a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/ResultDirective.java b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/ResultDirective.java index c8a869160..cb7b5be88 100644 --- a/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/ResultDirective.java +++ b/cayenne-velocity/src/main/java/org/apache/cayenne/velocity/ResultDirective.java @@ -31,6 +31,8 @@ import java.util.HashMap; import java.util.Map; import org.apache.cayenne.access.jdbc.ColumnDescriptor; +import org.apache.cayenne.access.types.ExtendedType; +import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.util.Util; import org.apache.velocity.context.InternalContextAdapter; import org.apache.velocity.exception.MethodInvocationException; @@ -129,6 +131,10 @@ public class ResultDirective extends Directive { String type = getChildAsString(context, node, 1); String javaClass = (type != null) ? guessType(type) : null; + DbAdapter adapter = (DbAdapter) context.getInternalUserContext() + .get(VelocitySQLTemplateTranslator.ADAPTER_KEY); + ExtendedType extendedType = adapter.getExtendedTypes().getRegisteredType(javaClass); + // TODO: andrus 6/27/2007 - this is an unofficial jdbcType parameter // that is added // temporarily pending CAY-813 implementation for the sake of EJBQL @@ -136,7 +142,7 @@ public class ResultDirective extends Directive { Object jdbcTypeChild = getChild(context, node, 4); int jdbcType = (jdbcTypeChild instanceof Number) ? ((Number) jdbcTypeChild).intValue() : 0; - ColumnDescriptor columnDescriptor = new ColumnDescriptor(column, label, jdbcType, javaClass, null); + ColumnDescriptor columnDescriptor = new ColumnDescriptor(column, label, jdbcType, extendedType, null); writer.write(column); diff --git a/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator_SelectTest.java b/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator_SelectTest.java index 01d659d52..1f8578bcf 100644 --- a/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator_SelectTest.java +++ b/cayenne-velocity/src/test/java/org/apache/cayenne/velocity/VelocitySQLTemplateTranslator_SelectTest.java @@ -20,7 +20,7 @@ package org.apache.cayenne.velocity; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -68,7 +68,7 @@ public class VelocitySQLTemplateTranslator_SelectTest { assertEquals(0, compiled.bindings().length); assertEquals(1, compiled.resultColumns().length); assertEquals("A", compiled.resultColumns()[0].name()); - assertNull(compiled.resultColumns()[0].javaClass()); + assertSame(adapter.getExtendedTypes().getDefaultType(), compiled.resultColumns()[0].type()); } @Test @@ -82,7 +82,7 @@ public class VelocitySQLTemplateTranslator_SelectTest { assertEquals(1, compiled.resultColumns().length); assertEquals("A", compiled.resultColumns()[0].name()); - assertEquals("java.lang.String", compiled.resultColumns()[0].javaClass()); + assertSame(adapter.getExtendedTypes().getRegisteredType(String.class), compiled.resultColumns()[0].type()); } @Test @@ -98,7 +98,7 @@ public class VelocitySQLTemplateTranslator_SelectTest { ColumnDescriptor column = compiled.resultColumns()[0]; assertEquals("A", column.name()); assertEquals("B", column.dataRowKey()); - assertEquals("java.lang.String", column.javaClass()); + assertSame(adapter.getExtendedTypes().getRegisteredType(String.class), column.type()); } @Test diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java b/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java index 22c9273c8..6f496b821 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java @@ -21,9 +21,6 @@ package org.apache.cayenne.access; import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.access.dbsync.SchemaUpdateStrategy; -import org.apache.cayenne.access.jdbc.ColumnDescriptor; -import org.apache.cayenne.access.jdbc.RowDescriptor; -import org.apache.cayenne.access.jdbc.reader.RowReader; import org.apache.cayenne.access.jdbc.reader.RowReaderFactory; import org.apache.cayenne.access.translator.batch.BatchTranslator; import org.apache.cayenne.access.translator.ejbql.EJBQLTranslator; @@ -36,11 +33,9 @@ import org.apache.cayenne.log.JdbcEventLogger; import org.apache.cayenne.log.NoopJdbcEventLogger; import org.apache.cayenne.map.DataMap; import org.apache.cayenne.map.EntityResolver; -import org.apache.cayenne.map.ObjAttribute; import org.apache.cayenne.query.DeleteBatchQuery; import org.apache.cayenne.query.InsertBatchQuery; import org.apache.cayenne.query.Query; -import org.apache.cayenne.query.QueryMetadata; import org.apache.cayenne.query.UpdateBatchQuery; import org.apache.cayenne.tx.BaseTransaction; import org.apache.cayenne.tx.Transaction; @@ -317,30 +312,6 @@ public class DataNode { return new ToStringBuilder(this).append("name", getName()).toString(); } - - /** - * Creates a {@link RowReader} using internal {@link RowReaderFactory}. - * - * @since 4.0 - * @deprecated in favor of obtaining the factory via {@link #getRowReaderFactory()} and calling it directly. - */ - @Deprecated(since = "5.0", forRemoval = true) - public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata) { - return rowReaderFactory.rowReader(descriptor, queryMetadata, getAdapter()); - } - - /** - * Creates a {@link RowReader} using internal {@link RowReaderFactory}. - * - * @since 4.0 - * @deprecated in favor of obtaining the factory via {@link #getRowReaderFactory()} and calling it directly. - */ - @Deprecated(since = "5.0", forRemoval = true) - public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, - Map<ObjAttribute, ColumnDescriptor> attributeOverrides) { - return rowReader(descriptor, queryMetadata); - } - /** * @since 4.0 */ diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BaseSQLAction.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BaseSQLAction.java index 1a63b8610..6f484f41b 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BaseSQLAction.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BaseSQLAction.java @@ -49,14 +49,14 @@ public abstract class BaseSQLAction implements SQLAction { * Helper method to process a ResultSet. */ @SuppressWarnings({ "rawtypes", "unchecked" }) - protected void readResultSet(ResultSet resultSet, RowDescriptor descriptor, Query query, OperationObserver delegate) + protected void readResultSet(ResultSet resultSet, ColumnDescriptor[] columns, Query query, OperationObserver delegate) throws Exception { long t1 = System.currentTimeMillis(); QueryMetadata metadata = query.getMetaData(dataNode.getEntityResolver()); - RowReader<?> rowReader = dataNode.getRowReaderFactory().rowReader(descriptor, metadata, dataNode.getAdapter()); + RowReader<?> rowReader = dataNode.getRowReaderFactory().rowReader(columns, metadata, dataNode.getAdapter()); JDBCResultIterator resultReader = new JDBCResultIterator(null, resultSet, rowReader); diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java index a5bb47938..15c6e8ace 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/BatchAction.java @@ -27,6 +27,7 @@ import org.apache.cayenne.access.OperationObserver; import org.apache.cayenne.access.OptimisticLockException; import org.apache.cayenne.access.jdbc.reader.RowReader; import org.apache.cayenne.access.translator.ParameterBinding; +import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.access.translator.batch.TranslatedBatch; import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.log.JdbcEventLogger; @@ -57,7 +58,7 @@ public class BatchAction extends BaseSQLAction { protected boolean runningAsBatch; protected BatchQuery query; - protected RowDescriptor keyRowDescriptor; + protected ColumnDescriptor[] keyColumns; /** * @since 4.0 @@ -267,9 +268,9 @@ public class BatchAction extends BaseSQLAction { // (this way we can support multiple columns.. // although need to check how well this works with most common drivers) - RowDescriptorBuilder builder = new RowDescriptorBuilder(); + ColumnDescriptor.RowBuilder rowBuilder = ColumnDescriptor.rowBuilder(); - if (this.keyRowDescriptor == null) { + if (this.keyColumns == null) { // attempt to figure out the right descriptor from the mapping... Collection<DbAttribute> generated = query.getDbEntity().getGeneratedAttributes(); if (generated.size() == 1 && keysRS.getMetaData().getColumnCount() == 1) { @@ -286,17 +287,18 @@ public class BatchAction extends BaseSQLAction { columnName = "column_1"; } } - columns[0] = new ColumnDescriptor(columnName, columnName, key.getType(), typeForGeneratedPK(key), null); - builder.setColumns(columns); + ExtendedType type = dataNode.getAdapter().getExtendedTypes().getRegisteredType(typeForGeneratedPK(key)); + columns[0] = new ColumnDescriptor(columnName, columnName, key.getType(), type, null); + rowBuilder.columns(columns); } else { - builder.setResultSet(keysRS); + rowBuilder.resultSet(keysRS); } - this.keyRowDescriptor = builder.getDescriptor(dataNode.getAdapter().getExtendedTypes()); + this.keyColumns = rowBuilder.build(dataNode.getAdapter().getExtendedTypes()); } RowReader<?> rowReader = dataNode.getRowReaderFactory() - .rowReader(keyRowDescriptor, query.getMetaData(dataNode.getEntityResolver()), dataNode.getAdapter()); + .rowReader(keyColumns, query.getMetaData(dataNode.getEntityResolver()), dataNode.getAdapter()); ResultIterator iterator = new JDBCResultIterator(null, keysRS, rowReader); List<ObjectId> objectIds = new ArrayList<>(rows.size()); @@ -307,7 +309,7 @@ public class BatchAction extends BaseSQLAction { } private String typeForGeneratedPK(DbAttribute key) { - String entityName = getQuery().getRows().get(0).getObjectId().getEntityName(); + String entityName = getQuery().getRows().getFirst().getObjectId().getEntityName(); ObjEntity objEntity = dataNode.getEntityResolver().getObjEntity(entityName); if(objEntity != null) { ObjAttribute attributeForDbAttribute = objEntity.getAttributeForDbAttribute(key); diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java index 8b1df6876..0664f7ee2 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java @@ -19,14 +19,31 @@ package org.apache.cayenne.access.jdbc; +import org.apache.cayenne.CayenneRuntimeException; +import org.apache.cayenne.access.types.ExtendedType; +import org.apache.cayenne.access.types.ExtendedTypeMap; +import org.apache.cayenne.dba.TypesMapping; import org.apache.cayenne.map.DbAttribute; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.function.Function; /** - * A descriptor of a ResultSet column. + * A descriptor of a ResultSet column, carrying the {@link ExtendedType} used to materialize the column value. */ -public record ColumnDescriptor(String name, String dataRowKey, int jdbcType, String javaClass, DbAttribute attribute) { +public record ColumnDescriptor(String name, String dataRowKey, int jdbcType, ExtendedType<?> type, + DbAttribute attribute) { public ColumnDescriptor { if (dataRowKey == null) { @@ -55,4 +72,247 @@ public record ColumnDescriptor(String name, String dataRowKey, int jdbcType, Str public int hashCode() { return Objects.hash(name, dataRowKey); } + + /** + * Creates a {@link RowBuilder} that assembles the array of {@link ColumnDescriptor}s describing a result row. + * + * @since 5.0 + */ + public static RowBuilder rowBuilder() { + return new RowBuilder(); + } + + public static class RowBuilder { + + private static final Logger LOGGER = LoggerFactory.getLogger(RowBuilder.class); + + private static final Function<String, String> UPPERCASE_TRANSFORMER = input -> + input != null ? input.toUpperCase() : null; + + private static final Function<String, String> LOWERCASE_TRANSFORMER = input -> + input != null ? input.toLowerCase() : null; + + protected ColumnDescriptor[] columns; + protected ResultSetMetaData resultSetMetadata; + + protected Function<String, String> caseTransformer; + protected Map<String, String> typeOverrides; + + private boolean mergeColumnsWithRsMetadata; + + protected boolean validateDuplicateColumnNames; + + /** + * Returns the array of {@link ColumnDescriptor}s describing the result row, with an {@link ExtendedType} + * resolved for each column. + */ + public ColumnDescriptor[] build(ExtendedTypeMap typeMap) throws SQLException, IllegalStateException { + + ColumnDescriptor[] columnsForRD; + + if (this.resultSetMetadata != null) { + // do merge between explicitly-set columns and ResultSetMetadata + // explicitly-set columns take precedence + columnsForRD = mergeResultSetAndPresetColumns(typeMap); + } else if (this.columns != null) { + // use explicitly-set columns + columnsForRD = this.columns; + } else { + throw new IllegalStateException( + "Can't build row descriptor, both 'columns' and 'resultSetMetadata' are null"); + } + + return performTransformAndTypeOverride(columnsForRD, typeMap); + } + + /** + * @return array of columns for ResultSet with overriding ColumnDescriptors from + * 'columns' Note: column will be overlooked, if column name is empty + */ + protected ColumnDescriptor[] mergeResultSetAndPresetColumns(ExtendedTypeMap typeMap) throws SQLException { + + int rsLen = resultSetMetadata.getColumnCount(); + if (rsLen == 0) { + throw new CayenneRuntimeException("'ResultSetMetadata' is empty."); + } + + int columnLen = (columns != null) ? columns.length : 0; + + if (mergeColumnsWithRsMetadata && rsLen != columnLen) { + throw new CayenneRuntimeException("Size of 'ResultSetMetadata' not equals to size of 'columns'."); + } else if (rsLen < columnLen) { + throw new CayenneRuntimeException("'ResultSetMetadata' has less elements then 'columns'."); + } else if (rsLen == columnLen && !mergeColumnsWithRsMetadata) { + // 'columns' contains ColumnDescriptor for every column + // in resultSetMetadata. This return is for optimization. + return columns; + } + + ColumnDescriptor[] rsColumns = new ColumnDescriptor[rsLen]; + List<String> duplicates = null; + Set<String> uniqueNames = null; + + if (validateDuplicateColumnNames) { + duplicates = new ArrayList<>(); + uniqueNames = new HashSet<>(); + } + + int outputLen = 0; + for (int i = 0; i < rsLen; i++) { + String rowkey = resolveDataRowKeyFromResultSet(i + 1); + + // resolve column descriptor from 'columns' or create new + ColumnDescriptor column = getOrCreateColumn(rowkey, columns, i + 1, typeMap); + + // validate uniqueness of names + if (validateDuplicateColumnNames) { + if (!uniqueNames.add(column.dataRowKey())) { + duplicates.add(column.dataRowKey()); + } + } + rsColumns[outputLen] = column; + outputLen++; + } + + if (validateDuplicateColumnNames && !duplicates.isEmpty()) { + LOGGER.warn("Found duplicated columns '{}' in row descriptor. " + + "This can lead to errors when converting result to persistent objects.", + String.join("', '", duplicates)); + } + + if (outputLen < rsLen) { + // cut ColumnDescriptor array + ColumnDescriptor[] rsColumnsCut = new ColumnDescriptor[outputLen]; + System.arraycopy(rsColumns, 0, rsColumnsCut, 0, outputLen); + return rsColumnsCut; + } + + return rsColumns; + } + + private ColumnDescriptor getOrCreateColumn( + String rowKey, + ColumnDescriptor[] columnArray, + int position, + ExtendedTypeMap typeMap) throws SQLException { + int len = (columnArray != null) ? columnArray.length : 0; + // go through columnArray to find ColumnDescriptor for specified column + for (int i = 0; i < len; i++) { + if (columnArray[i] != null) { + if (mergeColumnsWithRsMetadata) { + return new ColumnDescriptor(rowKey, rowKey, resultSetMetadata.getColumnType(position), columnArray[position - 1].type(), null); + } else { + String columnRowKey = columnArray[i].dataRowKey(); + + // TODO: andrus, 10/14/2009 - 'equalsIgnoreCase' check can result in + // subtle bugs in DBs with case-sensitive column names (or when quotes are + // used to force case sensitivity). Alternatively using 'equals' may miss + // columns in case-insensitive situations. + if (columnRowKey != null && columnRowKey.equalsIgnoreCase(rowKey)) { + return columnArray[i]; + } + } + } + } + // columnArray doesn't contain ColumnDescriptor for specified column + int jdbcType = resultSetMetadata.getColumnType(position); + ExtendedType<?> type = typeMap.getRegisteredType(TypesMapping.getJavaBySqlType(jdbcType)); + return new ColumnDescriptor(rowKey, rowKey, jdbcType, type, null); + } + + /** + * Return not empty string with ColumnLabel or ColumnName or "column_" + position for + * for specified (by it's position) column in ResultSetMetaData. + */ + private String resolveDataRowKeyFromResultSet(int position) throws SQLException { + String name = resultSetMetadata.getColumnLabel(position); + if (name == null || name.isEmpty()) { + name = resultSetMetadata.getColumnName(position); + if (name == null) { + name = ""; + } + } + return name; + } + + private ColumnDescriptor[] performTransformAndTypeOverride(ColumnDescriptor[] columnArray, ExtendedTypeMap typeMap) { + if (caseTransformer == null && typeOverrides == null) { + return columnArray; + } + + ColumnDescriptor[] result = new ColumnDescriptor[columnArray.length]; + for (int i = 0; i < columnArray.length; i++) { + ColumnDescriptor column = columnArray[i]; + + String name = column.name(); + String dataRowKey = column.dataRowKey(); + ExtendedType<?> type = column.type(); + + if (caseTransformer != null) { + name = caseTransformer.apply(name); + dataRowKey = caseTransformer.apply(dataRowKey); + } + + // type overrides are keyed by the (possibly transformed) column name + if (typeOverrides != null) { + String javaClass = typeOverrides.get(name); + if (javaClass != null) { + type = typeMap.getRegisteredType(javaClass); + } + } + + result[i] = new ColumnDescriptor(name, dataRowKey, column.jdbcType(), type, column.attribute()); + } + return result; + } + + /** + * Sets an explicit set of columns. The builder may replace these with new instances to enforce the column + * capitalization policy and column Java type overrides. + */ + public RowBuilder columns(ColumnDescriptor[] columns) { + this.columns = columns; + return this; + } + + public RowBuilder resultSet(ResultSet resultSet) throws SQLException { + this.resultSetMetadata = resultSet.getMetaData(); + return this; + } + + public RowBuilder useLowercaseColumnNames() { + this.caseTransformer = LOWERCASE_TRANSFORMER; + return this; + } + + public RowBuilder useUppercaseColumnNames() { + this.caseTransformer = UPPERCASE_TRANSFORMER; + return this; + } + + /** + * Registers a Java type override for the named column. The first override registered for a given column wins: + * subsequent calls for the same column are ignored, so callers can register higher-priority overrides first and + * follow with fallback defaults without having to check what is already registered. + */ + public RowBuilder overrideColumnType(String columnName, String type) { + + if (typeOverrides == null) { + typeOverrides = new HashMap<>(); + } + + typeOverrides.putIfAbsent(columnName, type); + return this; + } + + public RowBuilder validateDuplicateColumnNames() { + this.validateDuplicateColumnNames = true; + return this; + } + + public RowBuilder mergeColumnsWithRsMetadata() { + this.mergeColumnsWithRsMetadata = true; + return this; + } + } } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java index cb5914825..1f6d1da1f 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java @@ -26,6 +26,7 @@ import org.apache.cayenne.access.OperationObserver; import org.apache.cayenne.access.translator.ParameterBinding; import org.apache.cayenne.access.translator.procedure.TranslatedProcedure; import org.apache.cayenne.access.types.ExtendedType; +import org.apache.cayenne.access.types.ExtendedTypeMap; import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.dba.TypesMapping; import org.apache.cayenne.map.Procedure; @@ -106,8 +107,8 @@ public class ProcedureAction extends BaseSQLAction { if (statement.getMoreResults()) { try (ResultSet rs = statement.getResultSet()) { - RowDescriptor descriptor = describeResultSet(rs, processedResultSets++); - readResultSet(rs, descriptor, query, observer); + ColumnDescriptor[] columns = describeResultSet(rs, processedResultSets++); + readResultSet(rs, columns, query, observer); } } else { int updateCount = statement.getUpdateCount(); @@ -151,25 +152,25 @@ public class ProcedureAction extends BaseSQLAction { } /** - * Creates a RowDescriptor for result set. - * + * Describes the result set columns, resolving an {@link ExtendedType} for each. + * * @param resultSet * JDBC ResultSet * @param setIndex * a zero-based index of the ResultSet in the query results. */ - protected RowDescriptor describeResultSet(ResultSet resultSet, int setIndex) throws SQLException { + protected ColumnDescriptor[] describeResultSet(ResultSet resultSet, int setIndex) throws SQLException { if (setIndex < 0) { throw new IllegalArgumentException("Expected a non-negative result set index. Got: " + setIndex); } - RowDescriptorBuilder builder = new RowDescriptorBuilder(); + ColumnDescriptor.RowBuilder builder = ColumnDescriptor.rowBuilder(); List<ProcedureColumn[]> descriptors = query.getResultDescriptors(); if (descriptors.isEmpty()) { - builder.setResultSet(resultSet); + builder.resultSet(resultSet); } else { // if one result is described, all of them must be present... @@ -177,7 +178,8 @@ public class ProcedureAction extends BaseSQLAction { throw new CayenneRuntimeException("No descriptor for result set at index '%d' configured.", setIndex); } - builder.setColumns(toColumnDescriptors(descriptors.get(setIndex))); + builder.columns(toColumnDescriptors(descriptors.get(setIndex), + dataNode.getAdapter().getExtendedTypes())); } switch (query.getColumnNamesCapitalization()) { @@ -189,7 +191,7 @@ public class ProcedureAction extends BaseSQLAction { break; } - return builder.getDescriptor(dataNode.getAdapter().getExtendedTypes()); + return builder.build(dataNode.getAdapter().getExtendedTypes()); } /** @@ -198,11 +200,12 @@ public class ProcedureAction extends BaseSQLAction { * * @since 5.0 */ - private static ColumnDescriptor[] toColumnDescriptors(ProcedureColumn[] columns) { + private static ColumnDescriptor[] toColumnDescriptors(ProcedureColumn[] columns, ExtendedTypeMap typeMap) { ColumnDescriptor[] result = new ColumnDescriptor[columns.length]; for (int i = 0; i < columns.length; i++) { ProcedureColumn c = columns[i]; - result[i] = new ColumnDescriptor(c.name(), c.dataRowKey(), c.jdbcType(), c.javaClass(), null); + ExtendedType type = typeMap.getRegisteredType(c.javaClass()); + result[i] = new ColumnDescriptor(c.name(), c.dataRowKey(), c.jdbcType(), type, null); } return result; } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/RowDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/RowDescriptor.java deleted file mode 100644 index 57a07c99a..000000000 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/RowDescriptor.java +++ /dev/null @@ -1,71 +0,0 @@ -/***************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - ****************************************************************/ - -package org.apache.cayenne.access.jdbc; - -import org.apache.cayenne.access.types.ExtendedType; - -/** - * A descriptor of a result row obtained from a database. - * - * @since 1.2 - */ -public class RowDescriptor { - - protected ColumnDescriptor[] columns; - protected ExtendedType[] converters; - - /** - * Creates an empty RowDescriptor. Intended mainly for testing and use by subclasses. - */ - protected RowDescriptor() { - - } - - /** - * Creates a fully initialized RowDescriptor. - * - * @since 3.0 - */ - public RowDescriptor(ColumnDescriptor[] columns, ExtendedType[] converters) { - this.columns = columns; - this.converters = converters; - } - - /** - * Returns a number of columns in a row. - */ - public int getWidth() { - return columns.length; - } - - /** - * Returns column descriptors. - */ - public ColumnDescriptor[] getColumns() { - return columns; - } - - /** - * Returns extended types for columns. - */ - public ExtendedType[] getConverters() { - return converters; - } -} diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/RowDescriptorBuilder.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/RowDescriptorBuilder.java deleted file mode 100644 index 68c6a7b28..000000000 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/RowDescriptorBuilder.java +++ /dev/null @@ -1,287 +0,0 @@ -/***************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - ****************************************************************/ -package org.apache.cayenne.access.jdbc; - -import org.apache.cayenne.CayenneRuntimeException; -import org.apache.cayenne.access.types.ExtendedType; -import org.apache.cayenne.access.types.ExtendedTypeMap; -import org.apache.cayenne.dba.TypesMapping; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -/** - * A builder class that helps to assemble {@link RowDescriptor} instances from various - * types of inputs. - * - * @since 3.0 - */ -public class RowDescriptorBuilder { - - private static final Logger LOGGER = LoggerFactory.getLogger(RowDescriptorBuilder.class); - - private static final Function<String, String> UPPERCASE_TRANSFORMER = input -> - input != null ? input.toUpperCase() : null; - - private static final Function<String, String> LOWERCASE_TRANSFORMER = input -> - input != null ? input.toLowerCase() : null; - - protected ColumnDescriptor[] columns; - protected ResultSetMetaData resultSetMetadata; - - protected Function<String, String> caseTransformer; - protected Map<String, String> typeOverrides; - - private boolean mergeColumnsWithRsMetadata; - - protected boolean validateDuplicateColumnNames; - - /** - * Returns a RowDescriptor built based on the builder internal state. - */ - public RowDescriptor getDescriptor(ExtendedTypeMap typeMap) throws SQLException, IllegalStateException { - - ColumnDescriptor[] columnsForRD; - - if (this.resultSetMetadata != null) { - // do merge between explicitly-set columns and ResultSetMetadata - // explicitly-set columns take precedence - columnsForRD = mergeResultSetAndPresetColumns(); - } else if (this.columns != null) { - // use explicitly-set columns - columnsForRD = this.columns; - } else { - throw new IllegalStateException( - "Can't build RowDescriptor, both 'columns' and 'resultSetMetadata' are null"); - } - - columnsForRD = performTransformAndTypeOverride(columnsForRD); - ExtendedType[] converters = new ExtendedType[columnsForRD.length]; - for (int i = 0; i < columnsForRD.length; i++) { - converters[i] = typeMap.getRegisteredType(columnsForRD[i].javaClass()); - } - - return new RowDescriptor(columnsForRD, converters); - } - - /** - * @return array of columns for ResultSet with overriding ColumnDescriptors from - * 'columns' Note: column will be overlooked, if column name is empty - */ - protected ColumnDescriptor[] mergeResultSetAndPresetColumns() throws SQLException { - - int rsLen = resultSetMetadata.getColumnCount(); - if (rsLen == 0) { - throw new CayenneRuntimeException("'ResultSetMetadata' is empty."); - } - - int columnLen = (columns != null) ? columns.length : 0; - - if(mergeColumnsWithRsMetadata && rsLen != columnLen) { - throw new CayenneRuntimeException("Size of 'ResultSetMetadata' not equals to size of 'columns'."); - } else if (rsLen < columnLen) { - throw new CayenneRuntimeException("'ResultSetMetadata' has less elements then 'columns'."); - } else if(rsLen == columnLen && !mergeColumnsWithRsMetadata) { - // 'columns' contains ColumnDescriptor for every column - // in resultSetMetadata. This return is for optimization. - return columns; - } - - ColumnDescriptor[] rsColumns = new ColumnDescriptor[rsLen]; - List<String> duplicates = null; - Set<String> uniqueNames = null; - if(validateDuplicateColumnNames) { - duplicates = new ArrayList<>(); - uniqueNames = new HashSet<>(); - } - - int outputLen = 0; - for (int i = 0; i < rsLen; i++) { - String rowkey = resolveDataRowKeyFromResultSet(i + 1); - - // resolve column descriptor from 'columns' or create new - ColumnDescriptor descriptor = getColumnDescriptor(rowkey, columns, i + 1); - - // validate uniqueness of names - if(validateDuplicateColumnNames) { - if(!uniqueNames.add(descriptor.dataRowKey())) { - duplicates.add(descriptor.dataRowKey()); - } - } - rsColumns[outputLen] = descriptor; - outputLen++; - } - - if(validateDuplicateColumnNames && !duplicates.isEmpty()) { - LOGGER.warn("Found duplicated columns '{}' in row descriptor. " + - "This can lead to errors when converting result to persistent objects.", - String.join("', '", duplicates)); - } - - if (outputLen < rsLen) { - // cut ColumnDescriptor array - ColumnDescriptor[] rsColumnsCut = new ColumnDescriptor[outputLen]; - System.arraycopy(rsColumns, 0, rsColumnsCut, 0, outputLen); - return rsColumnsCut; - } - - return rsColumns; - } - - /** - * @return ColumnDescriptor from columnArray, if columnArray contains descriptor for - * this column, or new ColumnDescriptor. - */ - private ColumnDescriptor getColumnDescriptor( - String rowKey, - ColumnDescriptor[] columnArray, - int position) throws SQLException { - int len = (columnArray != null) ? columnArray.length : 0; - // go through columnArray to find ColumnDescriptor for specified column - for (int i = 0; i < len; i++) { - if (columnArray[i] != null) { - if(mergeColumnsWithRsMetadata) { - return new ColumnDescriptor(rowKey, rowKey, resultSetMetadata.getColumnType(position), columnArray[position - 1].javaClass(), null); - } else { - String columnRowKey = columnArray[i].dataRowKey(); - - // TODO: andrus, 10/14/2009 - 'equalsIgnoreCase' check can result in - // subtle bugs in DBs with case-sensitive column names (or when quotes are - // used to force case sensitivity). Alternatively using 'equals' may miss - // columns in case-insensitive situations. - if (columnRowKey != null && columnRowKey.equalsIgnoreCase(rowKey)) { - return columnArray[i]; - } - } - } - } - // columnArray doesn't contain ColumnDescriptor for specified column - int jdbcType = resultSetMetadata.getColumnType(position); - return new ColumnDescriptor(rowKey, rowKey, jdbcType, TypesMapping.getJavaBySqlType(jdbcType), null); - } - - /** - * Return not empty string with ColumnLabel or ColumnName or "column_" + position for - * for specified (by it's position) column in ResultSetMetaData. - */ - private String resolveDataRowKeyFromResultSet(int position) throws SQLException { - String name = resultSetMetadata.getColumnLabel(position); - if (name == null || name.length() == 0) { - name = resultSetMetadata.getColumnName(position); - if (name == null) { - name = ""; - } - } - return name; - } - - private ColumnDescriptor[] performTransformAndTypeOverride(ColumnDescriptor[] columnArray) { - if (caseTransformer == null && typeOverrides == null) { - return columnArray; - } - - ColumnDescriptor[] result = new ColumnDescriptor[columnArray.length]; - for (int i = 0; i < columnArray.length; i++) { - ColumnDescriptor column = columnArray[i]; - - String name = column.name(); - String dataRowKey = column.dataRowKey(); - String javaClass = column.javaClass(); - - if (caseTransformer != null) { - name = caseTransformer.apply(name); - dataRowKey = caseTransformer.apply(dataRowKey); - } - - // type overrides are keyed by the (possibly transformed) column name - if (typeOverrides != null) { - String type = typeOverrides.get(name); - if (type != null) { - javaClass = type; - } - } - - result[i] = new ColumnDescriptor(name, dataRowKey, column.jdbcType(), javaClass, column.attribute()); - } - return result; - } - - /** - * Sets an explicit set of columns. Note that the array passed as an argument can - * later be modified by the build to enforce column capitalization policy and columns - * Java types overrides. - */ - public RowDescriptorBuilder setColumns(ColumnDescriptor[] columns) { - this.columns = columns; - return this; - } - - public RowDescriptorBuilder setResultSet(ResultSet resultSet) throws SQLException { - this.resultSetMetadata = resultSet.getMetaData(); - return this; - } - - public RowDescriptorBuilder useLowercaseColumnNames() { - this.caseTransformer = LOWERCASE_TRANSFORMER; - return this; - } - - public RowDescriptorBuilder useUppercaseColumnNames() { - this.caseTransformer = UPPERCASE_TRANSFORMER; - return this; - } - - public RowDescriptorBuilder overrideColumnType(String columnName, String type) { - - if (typeOverrides == null) { - typeOverrides = new HashMap<>(); - } - - typeOverrides.put(columnName, type); - return this; - } - - /** - * Validate and report duplicate names of columns. - * @return this builder - */ - public RowDescriptorBuilder validateDuplicateColumnNames() { - this.validateDuplicateColumnNames = true; - return this; - } - - public boolean isOverriden(String columnName) { - return typeOverrides != null && typeOverrides.containsKey(columnName); - } - - public RowDescriptorBuilder mergeColumnsWithRsMetadata() { - this.mergeColumnsWithRsMetadata = true; - return this; - } -} 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 a5afa378f..7044cae0a 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 @@ -26,6 +26,7 @@ import org.apache.cayenne.access.OperationObserver; import org.apache.cayenne.access.jdbc.reader.RowReader; import org.apache.cayenne.access.translator.ParameterBinding; import org.apache.cayenne.access.translator.sqltemplate.TranslatedSQL; +import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.access.types.ExtendedTypeMap; import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.dba.TypesMapping; @@ -239,10 +240,10 @@ public class SQLTemplateAction implements SQLAction { boolean iteratedResult = callback.isIteratedResult(); ExtendedTypeMap types = dataNode.getAdapter().getExtendedTypes(); - RowDescriptorBuilder builder = configureRowDescriptorBuilder(compiled, resultSet); + ColumnDescriptor.RowBuilder rowBuilder = rowBuilder(compiled, resultSet); recreateQueryMetadata(resultSet); RowReader<?> rowReader = dataNode.getRowReaderFactory() - .rowReader(builder.getDescriptor(types), queryMetadata, dataNode.getAdapter()); + .rowReader(rowBuilder.build(types), queryMetadata, dataNode.getAdapter()); ResultIterator<?> it = new JDBCResultIterator<>(statement, resultSet, rowReader); if (iteratedResult) { @@ -303,11 +304,13 @@ public class SQLTemplateAction implements SQLAction { return null; } + ExtendedTypeMap extendedTypes = dataNode.getAdapter().getExtendedTypes(); int size = query.getResultColumnsTypes().size(); ColumnDescriptor[] columnDescriptors = new ColumnDescriptor[size]; for (int i = 0; i < size; i++) { - // only the Java class is known here; name and type are resolved later from ResultSet metadata - columnDescriptors[i] = new ColumnDescriptor(null, null, 0, query.getResultColumnsTypes().get(i).getCanonicalName(), null); + // only the Java class is known here; name and jdbcType are resolved later from ResultSet metadata + ExtendedType type = extendedTypes.getRegisteredType(query.getResultColumnsTypes().get(i)); + columnDescriptors[i] = new ColumnDescriptor(null, null, 0, type, null); } return columnDescriptors; } @@ -315,11 +318,11 @@ public class SQLTemplateAction implements SQLAction { /** * @since 3.0 */ - protected RowDescriptorBuilder configureRowDescriptorBuilder(TranslatedSQL compiled, ResultSet resultSet) + protected ColumnDescriptor.RowBuilder rowBuilder(TranslatedSQL compiled, ResultSet resultSet) throws SQLException { - RowDescriptorBuilder builder = new RowDescriptorBuilder() - .setResultSet(resultSet) - .setColumns(createColumnDescriptors(compiled)) + ColumnDescriptor.RowBuilder builder = ColumnDescriptor.rowBuilder() + .resultSet(resultSet) + .columns(createColumnDescriptors(compiled)) .validateDuplicateColumnNames(); if (query.getResultColumnsTypes() != null) { @@ -340,10 +343,11 @@ public class SQLTemplateAction implements SQLAction { // 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) + // (BigDecimal vs Long vs. Integer). These are fallbacks: the ObjAttribute overrides + // registered above take precedence, as the builder keeps the first override per column. if (dbEntity != null && isResultColumnTypesEmpty()) { for (DbAttribute attribute : dbEntity.getAttributes()) { - if (!builder.isOverriden(attribute.getName()) && TypesMapping.isNumeric(attribute.getType())) { + if (TypesMapping.isNumeric(attribute.getType())) { builder.overrideColumnType(attribute.getName(), TypesMapping.getJavaBySqlType(attribute)); } } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java index feac78c3a..135d3d8c7 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java @@ -107,11 +107,11 @@ public class SelectAction extends BaseSQLAction { throw ex; } - RowDescriptor descriptor = new RowDescriptorBuilder() - .setColumns(translated.resultColumns()) - .getDescriptor(dataNode.getAdapter().getExtendedTypes()); + ColumnDescriptor[] columns = ColumnDescriptor.rowBuilder() + .columns(translated.resultColumns()) + .build(dataNode.getAdapter().getExtendedTypes()); - RowReader<?> rowReader = dataNode.getRowReaderFactory().rowReader(descriptor, queryMetadata, dataNode.getAdapter()); + RowReader<?> rowReader = dataNode.getRowReaderFactory().rowReader(columns, queryMetadata, dataNode.getAdapter()); ResultIterator<?> it = new JDBCResultIterator<>(statement, rs, rowReader); it = forIteratedResult(it, observer, connection, t1, translated.sql()); diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/BaseRowReader.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/BaseRowReader.java index fb26058dd..d1d5ae2a4 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/BaseRowReader.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/BaseRowReader.java @@ -21,7 +21,6 @@ package org.apache.cayenne.access.jdbc.reader; import java.sql.ResultSet; import org.apache.cayenne.access.jdbc.ColumnDescriptor; -import org.apache.cayenne.access.jdbc.RowDescriptor; import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.map.ObjEntity; import org.apache.cayenne.query.QueryMetadata; @@ -36,20 +35,19 @@ abstract class BaseRowReader<T> implements RowReader<T> { int[] types; String entityName; - BaseRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata) { + BaseRowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata) { ObjEntity rootObjEntity = queryMetadata.getObjEntity(); if (rootObjEntity != null) { this.entityName = rootObjEntity.getName(); } - this.converters = descriptor.getConverters(); - - ColumnDescriptor[] columns = descriptor.getColumns(); int width = columns.length; + this.converters = new ExtendedType[width]; this.labels = new String[width]; this.types = new int[width]; for (int i = 0; i < width; i++) { + converters[i] = columns[i].type(); labels[i] = columns[i].dataRowKey(); types[i] = columns[i].jdbcType(); } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java index fdca01cdb..c2800b2b6 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java @@ -21,7 +21,7 @@ package org.apache.cayenne.access.jdbc.reader; import java.util.List; import org.apache.cayenne.CayenneRuntimeException; -import org.apache.cayenne.access.jdbc.RowDescriptor; +import org.apache.cayenne.access.jdbc.ColumnDescriptor; import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.query.EmbeddableResultSegment; import org.apache.cayenne.query.EntityResultSegment; @@ -34,16 +34,16 @@ import org.apache.cayenne.query.ScalarResultSegment; public class DefaultRowReaderFactory implements RowReaderFactory { @Override - public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, DbAdapter adapter) { + public RowReader<?> rowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata, DbAdapter adapter) { List<Object> rsMapping = queryMetadata.getResultSetMapping(); if (rsMapping == null) { - return createFullRowReader(descriptor, queryMetadata); + return createFullRowReader(columns, queryMetadata); } int resultWidth = rsMapping.size(); if (resultWidth == 0) { - throw new CayenneRuntimeException("Empty result descriptor"); + throw new CayenneRuntimeException("Empty result columns"); } if (queryMetadata.isSingleResultSetMapping()) { @@ -51,11 +51,11 @@ public class DefaultRowReaderFactory implements RowReaderFactory { Object segment = rsMapping.get(0); if (segment instanceof EntityResultSegment) { - return createEntityRowReader(descriptor, queryMetadata, (EntityResultSegment) segment); + return createEntityRowReader(columns, queryMetadata, (EntityResultSegment) segment); } else if (segment instanceof EmbeddableResultSegment) { - return createEmbeddableRowReader(descriptor, queryMetadata, (EmbeddableResultSegment) segment); + return createEmbeddableRowReader(columns, queryMetadata, (EmbeddableResultSegment) segment); } else { - return createScalarRowReader(descriptor, queryMetadata, (ScalarResultSegment) segment); + return createScalarRowReader(columns, queryMetadata, (ScalarResultSegment) segment); } } else { CompoundRowReader reader = new CompoundRowReader(resultWidth); @@ -65,11 +65,11 @@ public class DefaultRowReaderFactory implements RowReaderFactory { if (segment instanceof EntityResultSegment) { reader.addRowReader(i, - createEntityRowReader(descriptor, queryMetadata, (EntityResultSegment) segment)); + createEntityRowReader(columns, queryMetadata, (EntityResultSegment) segment)); } else if(segment instanceof EmbeddableResultSegment) { - reader.addRowReader(i, createEmbeddableRowReader(descriptor, queryMetadata, (EmbeddableResultSegment) segment)); + reader.addRowReader(i, createEmbeddableRowReader(columns, queryMetadata, (EmbeddableResultSegment) segment)); } else { - reader.addRowReader(i, createScalarRowReader(descriptor, queryMetadata, (ScalarResultSegment) segment)); + reader.addRowReader(i, createScalarRowReader(columns, queryMetadata, (ScalarResultSegment) segment)); } } @@ -77,34 +77,34 @@ public class DefaultRowReaderFactory implements RowReaderFactory { } } - private RowReader<?> createEmbeddableRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, EmbeddableResultSegment segment) { - return new EmbeddableRowReader(descriptor, queryMetadata, segment); + private RowReader<?> createEmbeddableRowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata, EmbeddableResultSegment segment) { + return new EmbeddableRowReader(columns, queryMetadata, segment); } - protected RowReader<?> createScalarRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, ScalarResultSegment segment) { - return new ScalarRowReader<>(descriptor, segment); + protected RowReader<?> createScalarRowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata, ScalarResultSegment segment) { + return new ScalarRowReader<>(columns, segment); } - protected RowReader<?> createEntityRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, + protected RowReader<?> createEntityRowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata, EntityResultSegment resultMetadata) { if (queryMetadata.getPageSize() > 0) { - return new IdRowReader<>(descriptor, queryMetadata, resultMetadata); + return new IdRowReader<>(columns, queryMetadata, resultMetadata); } else if (resultMetadata.getClassDescriptor() != null && resultMetadata.getClassDescriptor().hasSubclasses()) { - return new InheritanceAwareEntityRowReader(descriptor, resultMetadata); + return new InheritanceAwareEntityRowReader(columns, resultMetadata); } else { - return new EntityRowReader(descriptor, resultMetadata); + return new EntityRowReader(columns, resultMetadata); } } - protected RowReader<?> createFullRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata) { + protected RowReader<?> createFullRowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata) { if (queryMetadata.getPageSize() > 0) { - return new IdRowReader<>(descriptor, queryMetadata, null); + return new IdRowReader<>(columns, queryMetadata, null); } else if (queryMetadata.getClassDescriptor() != null && queryMetadata.getClassDescriptor().hasSubclasses()) { - return new InheritanceAwareRowReader(descriptor, queryMetadata); + return new InheritanceAwareRowReader(columns, queryMetadata); } else { - return new FullRowReader(descriptor, queryMetadata); + return new FullRowReader(columns, queryMetadata); } } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/EmbeddableRowReader.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/EmbeddableRowReader.java index c08025669..79e77ca4e 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/EmbeddableRowReader.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/EmbeddableRowReader.java @@ -24,7 +24,6 @@ import java.sql.ResultSet; import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.DataRow; import org.apache.cayenne.access.jdbc.ColumnDescriptor; -import org.apache.cayenne.access.jdbc.RowDescriptor; import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.query.EmbeddableResultSegment; import org.apache.cayenne.query.QueryMetadata; @@ -41,17 +40,15 @@ class EmbeddableRowReader implements RowReader<DataRow> { private final String[] labels; private final int[] types; - EmbeddableRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, EmbeddableResultSegment segment) { + EmbeddableRowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata, EmbeddableResultSegment segment) { int segmentWidth = segment.getFields().size(); this.startIndex = segment.getColumnOffset(); this.converters = new ExtendedType[segmentWidth]; this.types = new int[segmentWidth]; this.labels = new String[segmentWidth]; - ExtendedType[] converters = descriptor.getConverters(); - ColumnDescriptor[] columns = descriptor.getColumns(); for (int i = 0; i < segmentWidth; i++) { - this.converters[i] = converters[startIndex + i]; + this.converters[i] = columns[startIndex + i].type(); types[i] = columns[startIndex + i].jdbcType(); labels[i] = segment.getFields().get(columns[startIndex +i].name()); } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/EntityRowReader.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/EntityRowReader.java index 4edb97a5f..e6b1bd2e4 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/EntityRowReader.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/EntityRowReader.java @@ -23,7 +23,6 @@ import java.sql.ResultSet; import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.DataRow; import org.apache.cayenne.access.jdbc.ColumnDescriptor; -import org.apache.cayenne.access.jdbc.RowDescriptor; import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.query.EntityResultSegment; import org.apache.cayenne.reflect.ClassDescriptor; @@ -42,7 +41,7 @@ class EntityRowReader implements RowReader<DataRow> { private int mapCapacity; private int startIndex; - EntityRowReader(RowDescriptor descriptor, EntityResultSegment segmentMetadata) { + EntityRowReader(ColumnDescriptor[] columns, EntityResultSegment segmentMetadata) { ClassDescriptor classDescriptor = segmentMetadata.getClassDescriptor(); @@ -56,10 +55,8 @@ class EntityRowReader implements RowReader<DataRow> { this.types = new int[segmentWidth]; this.labels = new String[segmentWidth]; - ExtendedType[] converters = descriptor.getConverters(); - ColumnDescriptor[] columns = descriptor.getColumns(); for (int i = 0; i < segmentWidth; i++) { - this.converters[i] = converters[startIndex + i]; + this.converters[i] = columns[startIndex + i].type(); types[i] = columns[startIndex + i].jdbcType(); // query translator may change the order of fields compare to the entity diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/FullRowReader.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/FullRowReader.java index cafab68aa..6634b6276 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/FullRowReader.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/FullRowReader.java @@ -22,7 +22,7 @@ import java.sql.ResultSet; import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.DataRow; -import org.apache.cayenne.access.jdbc.RowDescriptor; +import org.apache.cayenne.access.jdbc.ColumnDescriptor; import org.apache.cayenne.query.QueryMetadata; import org.apache.cayenne.util.Util; @@ -33,9 +33,9 @@ class FullRowReader extends BaseRowReader<DataRow> { int mapCapacity; - FullRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata) { - super(descriptor, queryMetadata); - this.mapCapacity = (int) Math.ceil((descriptor.getWidth()) / 0.75); + FullRowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata) { + super(columns, queryMetadata); + this.mapCapacity = (int) Math.ceil(columns.length / 0.75); } @Override diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/IdRowReader.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/IdRowReader.java index af004af66..cb5add474 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/IdRowReader.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/IdRowReader.java @@ -23,7 +23,6 @@ import java.sql.ResultSet; import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.DataRow; import org.apache.cayenne.access.jdbc.ColumnDescriptor; -import org.apache.cayenne.access.jdbc.RowDescriptor; import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.query.EntityResultSegment; @@ -37,8 +36,8 @@ class IdRowReader<T> extends BaseRowReader<T> { protected int[] pkIndices; - public IdRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, EntityResultSegment resultMetadata) { - super(descriptor, queryMetadata); + public IdRowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata, EntityResultSegment resultMetadata) { + super(columns, queryMetadata); DbEntity dbEntity = resultMetadata == null ? queryMetadata.getDbEntity() @@ -54,8 +53,6 @@ class IdRowReader<T> extends BaseRowReader<T> { throw new CayenneRuntimeException("Root DBEntity has no PK defined: %s", dbEntity); } - ColumnDescriptor[] columns = descriptor.getColumns(); - int[] pk = new int[len]; int offset = resultMetadata != null ? resultMetadata.getColumnOffset() : diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/InheritanceAwareEntityRowReader.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/InheritanceAwareEntityRowReader.java index f51ad058e..2afe70fc4 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/InheritanceAwareEntityRowReader.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/InheritanceAwareEntityRowReader.java @@ -21,7 +21,7 @@ package org.apache.cayenne.access.jdbc.reader; import java.sql.ResultSet; import org.apache.cayenne.DataRow; -import org.apache.cayenne.access.jdbc.RowDescriptor; +import org.apache.cayenne.access.jdbc.ColumnDescriptor; import org.apache.cayenne.map.EntityInheritanceTree; import org.apache.cayenne.map.ObjEntity; import org.apache.cayenne.query.EntityResultSegment; @@ -33,9 +33,9 @@ class InheritanceAwareEntityRowReader extends EntityRowReader { private EntityInheritanceTree entityInheritanceTree; - public InheritanceAwareEntityRowReader(RowDescriptor descriptor, EntityResultSegment segmentMetadata) { + public InheritanceAwareEntityRowReader(ColumnDescriptor[] columns, EntityResultSegment segmentMetadata) { - super(descriptor, segmentMetadata); + super(columns, segmentMetadata); this.entityInheritanceTree = segmentMetadata.getClassDescriptor().getEntityInheritanceTree(); } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/InheritanceAwareRowReader.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/InheritanceAwareRowReader.java index 06c61a20b..fdcdc3262 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/InheritanceAwareRowReader.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/InheritanceAwareRowReader.java @@ -21,7 +21,7 @@ package org.apache.cayenne.access.jdbc.reader; import java.sql.ResultSet; import org.apache.cayenne.DataRow; -import org.apache.cayenne.access.jdbc.RowDescriptor; +import org.apache.cayenne.access.jdbc.ColumnDescriptor; import org.apache.cayenne.map.EntityInheritanceTree; import org.apache.cayenne.map.ObjEntity; import org.apache.cayenne.query.QueryMetadata; @@ -33,8 +33,8 @@ class InheritanceAwareRowReader extends FullRowReader { private EntityInheritanceTree entityInheritanceTree; - InheritanceAwareRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata) { - super(descriptor, queryMetadata); + InheritanceAwareRowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata) { + super(columns, queryMetadata); this.entityInheritanceTree = queryMetadata.getClassDescriptor().getEntityInheritanceTree(); } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/RowReaderFactory.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/RowReaderFactory.java index 75a9b21b5..39bdc792c 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/RowReaderFactory.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/RowReaderFactory.java @@ -18,7 +18,7 @@ ****************************************************************/ package org.apache.cayenne.access.jdbc.reader; -import org.apache.cayenne.access.jdbc.RowDescriptor; +import org.apache.cayenne.access.jdbc.ColumnDescriptor; import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.query.QueryMetadata; @@ -29,5 +29,5 @@ import org.apache.cayenne.query.QueryMetadata; */ public interface RowReaderFactory { - RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, DbAdapter adapter); + RowReader<?> rowReader(ColumnDescriptor[] columns, QueryMetadata queryMetadata, DbAdapter adapter); } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/ScalarRowReader.java b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/ScalarRowReader.java index bb36b6e86..4f6db5cbe 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/ScalarRowReader.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/jdbc/reader/ScalarRowReader.java @@ -21,7 +21,7 @@ package org.apache.cayenne.access.jdbc.reader; import java.sql.ResultSet; import org.apache.cayenne.CayenneRuntimeException; -import org.apache.cayenne.access.jdbc.RowDescriptor; +import org.apache.cayenne.access.jdbc.ColumnDescriptor; import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.query.ScalarResultSegment; import org.apache.cayenne.util.Util; @@ -36,10 +36,10 @@ class ScalarRowReader<T> implements RowReader<T> { private final int type; @SuppressWarnings("unchecked") - ScalarRowReader(RowDescriptor descriptor, ScalarResultSegment segmentMetadata) { + ScalarRowReader(ColumnDescriptor[] columns, ScalarResultSegment segmentMetadata) { int scalarIndex = segmentMetadata.getColumnOffset(); - this.converter = descriptor.getConverters()[scalarIndex]; - this.type = descriptor.getColumns()[scalarIndex].jdbcType(); + this.converter = (ExtendedType<T>) columns[scalarIndex].type(); + this.type = columns[scalarIndex].jdbcType(); this.index = scalarIndex + 1; } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStage.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStage.java index eb43855aa..0ec865f86 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStage.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/ColumnDescriptorStage.java @@ -20,6 +20,7 @@ package org.apache.cayenne.access.translator.select; import org.apache.cayenne.access.jdbc.ColumnDescriptor; +import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.map.DbAttribute; /** @@ -46,8 +47,9 @@ class ColumnDescriptorStage implements TranslationStage { name = "__c" + i++; } + ExtendedType type = context.getAdapter().getExtendedTypes().getRegisteredType(resultNode.getJavaType()); ColumnDescriptor descriptor = new ColumnDescriptor( - name, resultNode.getDataRowKey(), resultNode.getJdbcType(), resultNode.getJavaType(), attribute); + name, resultNode.getDataRowKey(), resultNode.getJdbcType(), type, attribute); context.getColumnDescriptors().add(descriptor); } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/Context.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/Context.java index 61bac2832..53d5a71ee 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/Context.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/Context.java @@ -68,6 +68,16 @@ public class Context { return adapter.preferredBindingType(jdbcType); } + /** + * Resolves the {@link ExtendedType} for the given Java class name via the target adapter. A null class name resolves + * to the adapter's default type. + * + * @since 5.0 + */ + public ExtendedType<?> getExtendedType(String javaClassName) { + return adapter.getExtendedTypes().getRegisteredType(javaClassName); + } + public Directive getDirective(String name) { return directives.get(name); } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/Result.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/Result.java index 19c7cb978..96337f2ff 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/Result.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/sqltemplate/directive/Result.java @@ -100,7 +100,7 @@ public class Result implements Directive { jdbcType = (int) expressions[4].evaluateAsLong(context); } - context.addColumnDescriptor(new ColumnDescriptor(column, label, jdbcType, javaClass, null)); + context.addColumnDescriptor(new ColumnDescriptor(column, label, jdbcType, context.getExtendedType(javaClass), null)); context.getBuilder().append(column); if (!Util.isEmptyString(alias) && !alias.equals(column)) { diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/db2/DB2ProcedureAction.java b/cayenne/src/main/java/org/apache/cayenne/dba/db2/DB2ProcedureAction.java index 95848f97e..0d13c7010 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/db2/DB2ProcedureAction.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/db2/DB2ProcedureAction.java @@ -21,7 +21,7 @@ package org.apache.cayenne.dba.db2; import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.OperationObserver; import org.apache.cayenne.access.jdbc.ProcedureAction; -import org.apache.cayenne.access.jdbc.RowDescriptor; +import org.apache.cayenne.access.jdbc.ColumnDescriptor; import org.apache.cayenne.access.translator.procedure.TranslatedProcedure; import org.apache.cayenne.query.ProcedureQuery; @@ -64,8 +64,8 @@ class DB2ProcedureAction extends ProcedureAction { if (hasResultSet) { try (ResultSet rs = statement.getResultSet();) { - RowDescriptor descriptor = describeResultSet(rs, processedResultSets++); - readResultSet(rs, descriptor, query, observer); + ColumnDescriptor[] columns = describeResultSet(rs, processedResultSets++); + readResultSet(rs, columns, query, observer); } } else { int updateCount = statement.getUpdateCount(); diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLProcedureAction.java b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLProcedureAction.java index 10982a2f5..55df3c3a2 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLProcedureAction.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLProcedureAction.java @@ -21,7 +21,7 @@ package org.apache.cayenne.dba.mysql; import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.OperationObserver; import org.apache.cayenne.access.jdbc.ProcedureAction; -import org.apache.cayenne.access.jdbc.RowDescriptor; +import org.apache.cayenne.access.jdbc.ColumnDescriptor; import org.apache.cayenne.access.translator.procedure.TranslatedProcedure; import org.apache.cayenne.query.ProcedureQuery; @@ -86,8 +86,8 @@ class MySQLProcedureAction extends ProcedureAction { ResultSet rs = Objects.requireNonNull(statement.getResultSet()); try { - RowDescriptor descriptor = describeResultSet(rs, processedResultSets++); - readResultSet(rs, descriptor, query, observer); + ColumnDescriptor[] columns = describeResultSet(rs, processedResultSets++); + readResultSet(rs, columns, query, observer); } finally { try { rs.close(); diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleProcedureAction.java b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleProcedureAction.java index 7d25a2cf8..37db32592 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleProcedureAction.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleProcedureAction.java @@ -28,8 +28,8 @@ import java.util.List; import org.apache.cayenne.DataRow; import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.OperationObserver; +import org.apache.cayenne.access.jdbc.ColumnDescriptor; import org.apache.cayenne.access.jdbc.ProcedureAction; -import org.apache.cayenne.access.jdbc.RowDescriptor; import org.apache.cayenne.dba.TypesMapping; import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.map.ProcedureParameter; @@ -69,8 +69,8 @@ class OracleProcedureAction extends ProcedureAction { if (parameter.getType() == OracleAdapter.getOracleCursorType()) { try (ResultSet rs = (ResultSet) statement.getObject(i + 1);) { - RowDescriptor rsDescriptor = describeResultSet(rs, processedResultSets++); - readResultSet(rs, rsDescriptor, query, delegate); + ColumnDescriptor[] rsColumns = describeResultSet(rs, processedResultSets++); + readResultSet(rs, rsColumns, query, delegate); } } // ==== end Oracle-specific part diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleSQLTemplateAction.java b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleSQLTemplateAction.java index cb8c39c46..e301a0784 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleSQLTemplateAction.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleSQLTemplateAction.java @@ -18,19 +18,17 @@ ****************************************************************/ package org.apache.cayenne.dba.oracle; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.OperationObserver; -import org.apache.cayenne.access.jdbc.RowDescriptorBuilder; -import org.apache.cayenne.access.translator.sqltemplate.TranslatedSQL; import org.apache.cayenne.access.jdbc.SQLTemplateAction; +import org.apache.cayenne.access.translator.sqltemplate.TranslatedSQL; import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.query.SQLTemplate; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; + /** * @since 3.0 */ @@ -57,16 +55,4 @@ class OracleSQLTemplateAction extends SQLTemplateAction { super.processSelectResult(compiled, connection, statement, resultSet, callback, startTime); } - - /** - * @since 3.0 - */ - @Override - protected RowDescriptorBuilder configureRowDescriptorBuilder(TranslatedSQL compiled, ResultSet resultSet) - throws SQLException { - - RowDescriptorBuilder builder = super.configureRowDescriptorBuilder(compiled, resultSet); - - return builder; - } } diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerProcedureAction.java b/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerProcedureAction.java index 705df6a3d..35674b832 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerProcedureAction.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerProcedureAction.java @@ -24,7 +24,7 @@ import org.apache.cayenne.ResultIterator; import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.OperationObserver; import org.apache.cayenne.access.jdbc.ProcedureAction; -import org.apache.cayenne.access.jdbc.RowDescriptor; +import org.apache.cayenne.access.jdbc.ColumnDescriptor; import org.apache.cayenne.access.translator.procedure.TranslatedProcedure; import org.apache.cayenne.query.ProcedureQuery; import org.apache.cayenne.query.Query; @@ -83,8 +83,8 @@ public class SQLServerProcedureAction extends ProcedureAction { if (hasResultSet) { try (ResultSet rs = statement.getResultSet();) { - RowDescriptor descriptor = describeResultSet(rs, processedResultSets++); - readResultSet(rs, descriptor, query, localObserver); + ColumnDescriptor[] columns = describeResultSet(rs, processedResultSets++); + readResultSet(rs, columns, query, localObserver); } } else { int updateCount = statement.getUpdateCount(); diff --git a/cayenne/src/test/java/org/apache/cayenne/access/jdbc/JDBCResultIteratorTest.java b/cayenne/src/test/java/org/apache/cayenne/access/jdbc/JDBCResultIteratorTest.java index 077dffcfa..f88d9ddae 100644 --- a/cayenne/src/test/java/org/apache/cayenne/access/jdbc/JDBCResultIteratorTest.java +++ b/cayenne/src/test/java/org/apache/cayenne/access/jdbc/JDBCResultIteratorTest.java @@ -48,8 +48,8 @@ public class JDBCResultIteratorTest { TestResultSet rs = new TestResultSet("rs"); rs.addColumn("a", new Object[] { "1", "2", "3" }); - RowDescriptor descriptor = new RowDescriptorBuilder().setResultSet(rs).getDescriptor(new ExtendedTypeMap()); - RowReader<?> rowReader = new DefaultRowReaderFactory().rowReader(descriptor, new MockQueryMetadata(), + ColumnDescriptor[] columns = ColumnDescriptor.rowBuilder().resultSet(rs).build(new ExtendedTypeMap()); + RowReader<?> rowReader = new DefaultRowReaderFactory().rowReader(columns, new MockQueryMetadata(), mock(DbAdapter.class)); JDBCResultIterator it = new JDBCResultIterator(s, rs, rowReader); diff --git a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/MockSelectTranslatorContext.java b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/MockSelectTranslatorContext.java index 4bb1b61d3..e7cb994bf 100644 --- a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/MockSelectTranslatorContext.java +++ b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/MockSelectTranslatorContext.java @@ -19,17 +19,25 @@ package org.apache.cayenne.access.translator.select; +import org.apache.cayenne.access.types.ExtendedTypeMap; import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.map.EntityResolver; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class MockSelectTranslatorContext extends SelectTranslatorContext { MockSelectTranslatorContext(TranslatableQueryWrapper query) { - super(query, mock(DbAdapter.class), null, null); + super(query, mockAdapter(), null, null); } MockSelectTranslatorContext(TranslatableQueryWrapper query, EntityResolver resolver) { - super(query, mock(DbAdapter.class), resolver, null); + super(query, mockAdapter(), resolver, null); + } + + private static DbAdapter mockAdapter() { + DbAdapter adapter = mock(DbAdapter.class); + when(adapter.getExtendedTypes()).thenReturn(new ExtendedTypeMap()); + return adapter; } }
