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 f797ef016a356c189651a0234809ca10a60f124c Author: Andrus Adamchik <[email protected]> AuthorDate: Sun May 31 13:12:29 2026 -0400 Translators as stateless services producing immutable TranslatedXyz records * batch translators --- .../org/apache/cayenne/crypto/CryptoModule.java | 9 +- .../crypto/batch/CryptoBatchTranslator.java | 66 ++++++++++ .../CryptoBatchTranslatorFactoryDecorator.java | 88 ------------- .../dbsync/reverse/configuration/ToolsModule.java | 10 +- .../java/org/apache/cayenne/access/DataNode.java | 61 ++++----- .../apache/cayenne/access/jdbc/BaseSQLAction.java | 2 +- .../apache/cayenne/access/jdbc/BatchAction.java | 36 +++--- .../cayenne/access/jdbc/SQLTemplateAction.java | 3 +- .../apache/cayenne/access/jdbc/SelectAction.java | 5 +- .../translator/batch/BaseBatchTranslator.java | 54 ++++---- ...hTranslatorFactory.java => BatchRowBinder.java} | 22 ++-- .../access/translator/batch/BatchTranslator.java | 44 ++++--- .../batch/DefaultBatchTranslatorFactory.java | 57 --------- .../translator/batch/DeleteBatchTranslator.java | 33 ++--- .../translator/batch/InsertBatchTranslator.java | 22 ++-- .../batch/SoftDeleteBatchTranslator.java | 54 ++++++-- .../batch/SoftDeleteTranslatorFactory.java | 69 ---------- .../translator/batch/TranslatedBatch.java} | 27 ++-- .../translator/batch/UpdateBatchTranslator.java | 18 +-- .../batch/legacy/DefaultBatchTranslator.java | 112 ---------------- .../cayenne/configuration/runtime/CoreModule.java | 10 +- .../runtime/DefaultDataNodeFactory.java | 21 ++- .../cayenne/dba/oracle/OracleActionBuilder.java | 13 -- .../cayenne/access/jdbc/BatchActionLockingIT.java | 9 +- .../translator/batch/DeleteBatchTranslatorIT.java | 24 +--- .../translator/batch/InsertBatchTranslatorIT.java | 19 +-- .../batch/SoftDeleteBatchTranslatorIT.java | 23 ++-- .../translator/batch/UpdateBatchTranslatorIT.java | 23 +--- .../batch/legacy/DefaultBatchTranslatorIT.java | 141 --------------------- .../runtime/DataDomainProviderTest.java | 10 +- .../runtime/DefaultDbAdapterFactoryTest.java | 18 ++- 31 files changed, 362 insertions(+), 741 deletions(-) diff --git a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java index 9f619393e..d8beb738a 100644 --- a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java +++ b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/CryptoModule.java @@ -19,9 +19,10 @@ package org.apache.cayenne.crypto; import org.apache.cayenne.access.jdbc.reader.RowReaderFactory; -import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory; +import org.apache.cayenne.access.translator.batch.BatchTranslator; import org.apache.cayenne.configuration.DataMapLoader; -import org.apache.cayenne.crypto.batch.CryptoBatchTranslatorFactoryDecorator; +import org.apache.cayenne.crypto.batch.CryptoBatchTranslator; +import org.apache.cayenne.di.Key; import org.apache.cayenne.crypto.cipher.DefaultCipherFactory; import org.apache.cayenne.crypto.key.JceksKeySource; import org.apache.cayenne.crypto.map.CryptoDataMapLoader; @@ -152,7 +153,9 @@ public class CryptoModule implements Module { binder.bind(TransformerFactory.class).to(DefaultTransformerFactory.class); binder.decorate(DataMapLoader.class).before(CryptoDataMapLoader.class); - binder.decorate(BatchTranslatorFactory.class).before(CryptoBatchTranslatorFactoryDecorator.class); + binder.decorate(Key.get(BatchTranslator.class, BatchTranslator.INSERT)).before(CryptoBatchTranslator.class); + binder.decorate(Key.get(BatchTranslator.class, BatchTranslator.UPDATE)).before(CryptoBatchTranslator.class); + binder.decorate(Key.get(BatchTranslator.class, BatchTranslator.DELETE)).before(CryptoBatchTranslator.class); binder.bind(RowReaderFactory.class).to(CryptoRowReaderFactoryDecorator.class); // decorate Crypto's own services to allow Cayenne to operate over plaintext entities even if crypto keys are diff --git a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslator.java b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslator.java new file mode 100644 index 000000000..c27423bcd --- /dev/null +++ b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslator.java @@ -0,0 +1,66 @@ +/***************************************************************** + * 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.crypto.batch; + +import org.apache.cayenne.access.translator.ParameterBinding; +import org.apache.cayenne.access.translator.batch.BatchTranslator; +import org.apache.cayenne.access.translator.batch.TranslatedBatch; +import org.apache.cayenne.crypto.transformer.BindingsTransformer; +import org.apache.cayenne.crypto.transformer.TransformerFactory; +import org.apache.cayenne.dba.DbAdapter; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.query.BatchQuery; +import org.apache.cayenne.query.BatchQueryRow; + +/** + * @since 5.0 + */ +public class CryptoBatchTranslator<T extends BatchQuery> implements BatchTranslator<T> { + + private final TransformerFactory transformerFactory; + private final BatchTranslator<T> delegate; + + public CryptoBatchTranslator( + @Inject BatchTranslator<T> delegate, + @Inject TransformerFactory transformerFactory) { + + this.transformerFactory = transformerFactory; + this.delegate = delegate; + } + + @Override + public TranslatedBatch translate(T query, DbAdapter adapter) { + TranslatedBatch translated = delegate.translate(query, adapter); + BindingsTransformer encryptor = transformerFactory.encryptor(translated.bindings(), adapter.getExtendedTypes()); + + return encryptor != null + ? new TranslatedBatch(translated.sql(), translated.bindings(), (bindings, row) -> transform(bindings, row, translated, encryptor)) + : translated; + } + + private static ParameterBinding[] transform( + ParameterBinding[] bindings, + BatchQueryRow row, + TranslatedBatch translated, + BindingsTransformer encryptor) { + ParameterBinding[] updated = translated.binder().bind(bindings, row); + encryptor.transform(updated); + return updated; + } +} diff --git a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslatorFactoryDecorator.java b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslatorFactoryDecorator.java deleted file mode 100644 index ddc021d6c..000000000 --- a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/batch/CryptoBatchTranslatorFactoryDecorator.java +++ /dev/null @@ -1,88 +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.crypto.batch; - -import org.apache.cayenne.access.translator.ParameterBinding; -import org.apache.cayenne.access.translator.batch.BatchTranslator; -import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory; -import org.apache.cayenne.crypto.transformer.BindingsTransformer; -import org.apache.cayenne.crypto.transformer.TransformerFactory; -import org.apache.cayenne.dba.DbAdapter; -import org.apache.cayenne.di.Inject; -import org.apache.cayenne.query.BatchQuery; -import org.apache.cayenne.query.BatchQueryRow; - -/** - * @since 4.0 - */ -public class CryptoBatchTranslatorFactoryDecorator implements BatchTranslatorFactory { - - private TransformerFactory cryptoFactory; - private BatchTranslatorFactory delegate; - - public CryptoBatchTranslatorFactoryDecorator(@Inject BatchTranslatorFactory delegate, - @Inject TransformerFactory cryptoFactory) { - - this.cryptoFactory = cryptoFactory; - this.delegate = delegate; - } - - @Override - public BatchTranslator translator(BatchQuery query, final DbAdapter adapter, String trimFunction) { - final BatchTranslator delegateTranslator = delegate.translator(query, adapter, trimFunction); - - return new BatchTranslator() { - - private boolean encryptorCompiled; - private BindingsTransformer encryptor; - - private void ensureEncryptorCompiled() { - if (!encryptorCompiled) { - encryptor = cryptoFactory.encryptor(getBindings(), adapter.getExtendedTypes()); - encryptorCompiled = true; - } - } - - @Override - public String getSql() { - return delegateTranslator.getSql(); - } - - @Override - public ParameterBinding[] getBindings() { - return delegateTranslator.getBindings(); - } - - @Override - public ParameterBinding[] updateBindings(BatchQueryRow row) { - - ensureEncryptorCompiled(); - - ParameterBinding[] bindings = delegateTranslator.updateBindings(row); - - if (encryptor != null) { - encryptor.transform(bindings); - } - - return bindings; - } - }; - } - -} diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java index 033d39187..1515f5c14 100644 --- a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java +++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/configuration/ToolsModule.java @@ -23,8 +23,10 @@ import org.apache.cayenne.access.flush.DataDomainFlushActionFactory; import org.apache.cayenne.access.flush.DefaultDataDomainFlushActionFactory; import org.apache.cayenne.access.flush.operation.DbRowOpSorter; import org.apache.cayenne.access.flush.operation.DefaultDbRowOpSorter; -import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory; -import org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory; +import org.apache.cayenne.access.translator.batch.BatchTranslator; +import org.apache.cayenne.access.translator.batch.DeleteBatchTranslator; +import org.apache.cayenne.access.translator.batch.InsertBatchTranslator; +import org.apache.cayenne.access.translator.batch.UpdateBatchTranslator; import org.apache.cayenne.access.types.DefaultValueObjectTypeRegistry; import org.apache.cayenne.access.types.ValueObjectTypeRegistry; import org.apache.cayenne.configuration.Constants; @@ -153,7 +155,9 @@ public class ToolsModule implements Module { binder.bind(Key.get(ResourceLocator.class, Constants.RESOURCE_LOCATOR)).to(ClassLoaderResourceLocator.class); binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class); - binder.bind(BatchTranslatorFactory.class).to(DefaultBatchTranslatorFactory.class); + binder.bind(Key.get(BatchTranslator.class, BatchTranslator.INSERT)).to(InsertBatchTranslator.class); + binder.bind(Key.get(BatchTranslator.class, BatchTranslator.UPDATE)).to(UpdateBatchTranslator.class); + binder.bind(Key.get(BatchTranslator.class, BatchTranslator.DELETE)).to(DeleteBatchTranslator.class); binder.bind(JdbcEventLogger.class).to(Slf4jJdbcEventLogger.class); binder.bind(PkGeneratorFactoryProvider.class).to(PkGeneratorFactoryProvider.class); binder.bind(PkGenerator.class).to(JdbcPkGenerator.class); 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 5ba5b83b3..739140ed5 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java @@ -21,24 +21,20 @@ package org.apache.cayenne.access; import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.access.dbsync.SchemaUpdateStrategy; -import org.apache.cayenne.access.jdbc.RowDescriptor; -import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator; -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.batch.BatchTranslatorFactory; import org.apache.cayenne.access.translator.select.SelectTranslator; -import org.apache.cayenne.access.translator.select.TranslatedSelect; +import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator; import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.dba.JdbcAdapter; 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.query.BatchQuery; +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.Select; +import org.apache.cayenne.query.UpdateBatchQuery; import org.apache.cayenne.tx.BaseTransaction; import org.apache.cayenne.tx.Transaction; import org.apache.cayenne.util.ToStringBuilder; @@ -70,7 +66,9 @@ public class DataNode { private JdbcEventLogger jdbcEventLogger; private RowReaderFactory rowReaderFactory; - private BatchTranslatorFactory batchTranslatorFactory; + private BatchTranslator<InsertBatchQuery> insertBatchTranslator; + private BatchTranslator<UpdateBatchQuery> updateBatchTranslator; + private BatchTranslator<DeleteBatchQuery> deleteBatchTranslator; private SelectTranslator selectTranslator; private SQLTemplateTranslator sqlTemplateTranslator; @@ -316,54 +314,59 @@ public class DataNode { /** - * Creates a {@link RowReader} using internal {@link RowReaderFactory}. - * * @since 4.0 */ - public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata) { - return rowReaderFactory.rowReader(descriptor, queryMetadata, getAdapter()); + public RowReaderFactory getRowReaderFactory() { + return rowReaderFactory; } /** * @since 4.0 */ - public BatchTranslator batchTranslator(BatchQuery query, String trimFunction) { - return batchTranslatorFactory.translator(query, getAdapter(), trimFunction); + public void setRowReaderFactory(RowReaderFactory rowReaderFactory) { + this.rowReaderFactory = rowReaderFactory; } /** * @since 5.0 */ - public TranslatedSelect translateSelect(Select<?> query) { - return selectTranslator.translate(query, getAdapter(), getEntityResolver()); + public BatchTranslator<InsertBatchQuery> getInsertBatchTranslator() { + return insertBatchTranslator; } /** - * @since 4.0 + * @since 5.0 */ - public RowReaderFactory getRowReaderFactory() { - return rowReaderFactory; + public void setInsertBatchTranslator(BatchTranslator<InsertBatchQuery> insertBatchTranslator) { + this.insertBatchTranslator = insertBatchTranslator; } /** - * @since 4.0 + * @since 5.0 */ - public void setRowReaderFactory(RowReaderFactory rowReaderFactory) { - this.rowReaderFactory = rowReaderFactory; + public BatchTranslator<UpdateBatchQuery> getUpdateBatchTranslator() { + return updateBatchTranslator; } /** - * @since 4.0 + * @since 5.0 */ - public BatchTranslatorFactory getBatchTranslatorFactory() { - return batchTranslatorFactory; + public void setUpdateBatchTranslator(BatchTranslator<UpdateBatchQuery> updateBatchTranslator) { + this.updateBatchTranslator = updateBatchTranslator; } /** - * @since 4.0 + * @since 5.0 + */ + public BatchTranslator<DeleteBatchQuery> getDeleteBatchTranslator() { + return deleteBatchTranslator; + } + + /** + * @since 5.0 */ - public void setBatchTranslatorFactory(BatchTranslatorFactory batchTranslatorFactory) { - this.batchTranslatorFactory = batchTranslatorFactory; + public void setDeleteBatchTranslator(BatchTranslator<DeleteBatchQuery> deleteBatchTranslator) { + this.deleteBatchTranslator = deleteBatchTranslator; } /** 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 d81eb258e..1a63b8610 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 @@ -56,7 +56,7 @@ public abstract class BaseSQLAction implements SQLAction { QueryMetadata metadata = query.getMetaData(dataNode.getEntityResolver()); - RowReader<?> rowReader = dataNode.rowReader(descriptor, metadata); + RowReader<?> rowReader = dataNode.getRowReaderFactory().rowReader(descriptor, 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 203f07693..ada634f2a 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 @@ -19,6 +19,7 @@ package org.apache.cayenne.access.jdbc; +import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.ObjectId; import org.apache.cayenne.ResultIterator; import org.apache.cayenne.access.DataNode; @@ -26,7 +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.translator.batch.BatchTranslator; +import org.apache.cayenne.access.translator.batch.TranslatedBatch; import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.log.JdbcEventLogger; import org.apache.cayenne.map.DbAttribute; @@ -34,7 +35,9 @@ import org.apache.cayenne.map.ObjAttribute; import org.apache.cayenne.map.ObjEntity; import org.apache.cayenne.query.BatchQuery; import org.apache.cayenne.query.BatchQueryRow; +import org.apache.cayenne.query.DeleteBatchQuery; import org.apache.cayenne.query.InsertBatchQuery; +import org.apache.cayenne.query.UpdateBatchQuery; import java.sql.Connection; import java.sql.PreparedStatement; @@ -73,15 +76,21 @@ public class BatchAction extends BaseSQLAction { @Override public void performAction(Connection connection, OperationObserver observer) throws Exception { - BatchTranslator translator = createTranslator(); + DbAdapter adapter = dataNode.getAdapter(); + TranslatedBatch translated = switch (query) { + case InsertBatchQuery insert -> dataNode.getInsertBatchTranslator().translate(insert, adapter); + case UpdateBatchQuery update -> dataNode.getUpdateBatchTranslator().translate(update, adapter); + case DeleteBatchQuery delete -> dataNode.getDeleteBatchTranslator().translate(delete, adapter); + case null, default -> throw new CayenneRuntimeException("Unsupported batch query: %s", query); + }; boolean isBatch = canRunAsBatch(); boolean generatesKeys = hasGeneratedKeys() && supportsGeneratedKeys(isBatch); if (isBatch) { - runAsBatch(connection, translator, observer, generatesKeys); + runAsBatch(connection, translated, observer, generatesKeys); } else { - runAsIndividualQueries(connection, translator, observer, generatesKeys); + runAsIndividualQueries(connection, translated, observer, generatesKeys); } } @@ -99,14 +108,10 @@ public class BatchAction extends BaseSQLAction { return true; } - protected BatchTranslator createTranslator() { - return dataNode.batchTranslator(query, null); - } - - protected void runAsBatch(Connection con, BatchTranslator translator, OperationObserver delegate, boolean generatesKeys) + protected void runAsBatch(Connection con, TranslatedBatch translated, OperationObserver delegate, boolean generatesKeys) throws Exception { - String sql = translator.getSql(); + String sql = translated.sql(); JdbcEventLogger logger = dataNode.getJdbcEventLogger(); boolean isLoggable = logger.isLoggable(); @@ -120,7 +125,7 @@ public class BatchAction extends BaseSQLAction { try (PreparedStatement statement = prepareStatement(con, sql, adapter, generatesKeys)) { for (BatchQueryRow row : query.getRows()) { - ParameterBinding[] bindings = translator.updateBindings(row); + ParameterBinding[] bindings = translated.updateBindings(row); logger.logQueryParameters("batch bind", bindings); for (ParameterBinding b : bindings) { adapter.bindParameter(statement, b); @@ -159,7 +164,7 @@ public class BatchAction extends BaseSQLAction { /** * Executes batch as individual queries over the same prepared statement. */ - protected void runAsIndividualQueries(Connection connection, BatchTranslator translator, + protected void runAsIndividualQueries(Connection connection, TranslatedBatch translated, OperationObserver delegate, boolean generatesKeys) throws Exception { if(query.getRows().isEmpty()) { @@ -169,7 +174,7 @@ public class BatchAction extends BaseSQLAction { JdbcEventLogger logger = dataNode.getJdbcEventLogger(); boolean useOptimisticLock = query.isUsingOptimisticLocking(); - String queryStr = translator.getSql(); + String queryStr = translated.sql(); // log batch SQL execution logger.log(queryStr); @@ -181,7 +186,7 @@ public class BatchAction extends BaseSQLAction { try (PreparedStatement statement = prepareStatement(connection, queryStr, adapter, generatesKeys)) { for (BatchQueryRow row : query.getRows()) { - ParameterBinding[] bindings = translator.updateBindings(row); + ParameterBinding[] bindings = translated.updateBindings(row); logger.logQueryParameters("bind", bindings); for (ParameterBinding b : bindings) { @@ -283,7 +288,8 @@ public class BatchAction extends BaseSQLAction { this.keyRowDescriptor = builder.getDescriptor(dataNode.getAdapter().getExtendedTypes()); } - RowReader<?> rowReader = dataNode.rowReader(keyRowDescriptor, query.getMetaData(dataNode.getEntityResolver())); + RowReader<?> rowReader = dataNode.getRowReaderFactory() + .rowReader(keyRowDescriptor, query.getMetaData(dataNode.getEntityResolver()), dataNode.getAdapter()); ResultIterator iterator = new JDBCResultIterator(null, keysRS, rowReader); List<ObjectId> objectIds = new ArrayList<>(rows.size()); 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 c4bd62c6d..5f8a22974 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 @@ -239,7 +239,8 @@ public class SQLTemplateAction implements SQLAction { ExtendedTypeMap types = dataNode.getAdapter().getExtendedTypes(); RowDescriptorBuilder builder = configureRowDescriptorBuilder(compiled, resultSet); recreateQueryMetadata(resultSet); - RowReader<?> rowReader = dataNode.rowReader(builder.getDescriptor(types), queryMetadata); + RowReader<?> rowReader = dataNode.getRowReaderFactory() + .rowReader(builder.getDescriptor(types), queryMetadata, dataNode.getAdapter()); ResultIterator<?> it = new JDBCResultIterator<>(statement, resultSet, rowReader); if (iteratedResult) { 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 7553e7aa9..e3e2b5aeb 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 @@ -79,7 +79,8 @@ public class SelectAction extends BaseSQLAction { long t1 = System.currentTimeMillis(); JdbcEventLogger logger = dataNode.getJdbcEventLogger(); - TranslatedSelect translated = dataNode.translateSelect(query); + TranslatedSelect translated = dataNode.getSelectTranslator() + .translate(query, dataNode.getAdapter(), dataNode.getEntityResolver()); logger.logQuery(translated.sql(), translated.bindings()); @@ -111,7 +112,7 @@ public class SelectAction extends BaseSQLAction { .setColumns(translated.resultColumns()) .getDescriptor(dataNode.getAdapter().getExtendedTypes()); - RowReader<?> rowReader = dataNode.rowReader(descriptor, queryMetadata); + RowReader<?> rowReader = dataNode.getRowReaderFactory().rowReader(descriptor, 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/translator/batch/BaseBatchTranslator.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java index d5ab3fa1b..365d3c924 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BaseBatchTranslator.java @@ -19,8 +19,6 @@ package org.apache.cayenne.access.translator.batch; -import java.util.List; - import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder; import org.apache.cayenne.access.sqlbuilder.NodeBuilder; import org.apache.cayenne.access.sqlbuilder.SQLBuilder; @@ -31,54 +29,56 @@ import org.apache.cayenne.access.translator.select.DefaultQuotingAppendable; import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.query.BatchQuery; +import org.apache.cayenne.query.BatchQueryRow; + +import java.util.List; /** - * @since 4.2 * @param <T> type of the batch query to translate + * @since 4.2 */ -public abstract class BaseBatchTranslator<T extends BatchQuery> { +public abstract class BaseBatchTranslator<T extends BatchQuery> implements BatchTranslator<T> { - protected final BatchTranslatorContext<T> context; + @Override + public TranslatedBatch translate(T query, DbAdapter adapter) { + BatchTranslatorContext<T> context = new BatchTranslatorContext<>(query, adapter); - protected ParameterBinding[] bindings; + String sql = createSql(context); + ParameterBinding[] bindings = createBindings(context); - public BaseBatchTranslator(T query, DbAdapter adapter) { - this.context = new BatchTranslatorContext<>(query, adapter); + return new TranslatedBatch(sql, bindings, (b, row) -> updateBindings(context, b, row)); } - public ParameterBinding[] getBindings() { - return bindings; + protected abstract String createSql(BatchTranslatorContext<T> context); + + protected ParameterBinding[] createBindings(BatchTranslatorContext<T> context) { + return context.getBindings().toArray(new ParameterBinding[0]); } - /** - * This method applies {@link org.apache.cayenne.access.translator.select.BaseSQLTreeProcessor} to the - * provided SQL tree node and generates SQL string from it. - * - * @param nodeBuilder SQL tree node builder - * @return SQL string - */ - protected String doTranslate(NodeBuilder nodeBuilder) { - Node node = nodeBuilder.build(); - // convert to database flavour - node = context.getAdapter().getSqlTreeProcessor().process(node); - // generate SQL + protected abstract ParameterBinding[] updateBindings( + BatchTranslatorContext<T> context, + ParameterBinding[] bindings, + BatchQueryRow row); + + protected String doTranslate(BatchTranslatorContext<T> context, NodeBuilder nodeBuilder) { + Node node = context.getAdapter().getSqlTreeProcessor().process(nodeBuilder.build()); + SQLGenerationVisitor visitor = new SQLGenerationVisitor(new DefaultQuotingAppendable(context)); node.visit(visitor); - bindings = context.getBindings().toArray(new ParameterBinding[0]); return visitor.getSQLString(); } - abstract protected boolean isNullAttribute(DbAttribute attribute); + protected abstract boolean isNullAttribute(BatchTranslatorContext<T> context, DbAttribute attribute); - protected ExpressionNodeBuilder buildQualifier(List<DbAttribute> attributeList) { + protected ExpressionNodeBuilder buildQualifier(BatchTranslatorContext<T> context, List<DbAttribute> attributeList) { ExpressionNodeBuilder eq = null; for (DbAttribute attr : attributeList) { - Integer value = isNullAttribute(attr) ? null : 1; + Integer value = isNullAttribute(context, attr) ? null : 1; ExpressionNodeBuilder next = SQLBuilder .column(attr.getName()).attribute(attr) .eq(SQLBuilder.value(value).attribute(attr)); - if(eq == null) { + if (eq == null) { eq = next; } else { eq = eq.and(next); diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorFactory.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchRowBinder.java similarity index 66% rename from cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorFactory.java rename to cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchRowBinder.java index 951ef9482..f0f8a3329 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslatorFactory.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchRowBinder.java @@ -16,21 +16,21 @@ * specific language governing permissions and limitations * under the License. ****************************************************************/ + package org.apache.cayenne.access.translator.batch; -import org.apache.cayenne.dba.DbAdapter; -import org.apache.cayenne.query.BatchQuery; +import org.apache.cayenne.access.translator.ParameterBinding; +import org.apache.cayenne.query.BatchQueryRow; /** - * Factory which creates BatchQueryBuilders for different types of queries, - * which, in their turn, create SQL strings for batch queries. - * - * @since 4.0 + * A stateless strategy that populates a batch query's binding template with the values of a single + * {@link BatchQueryRow}. The same template array is reused for every row, so the returned array is the + * one passed in, only with the per-row state applied. + * + * @since 5.0 */ -public interface BatchTranslatorFactory { +@FunctionalInterface +public interface BatchRowBinder { - /** - * Creates a proper translator for a BatchQuery - */ - BatchTranslator translator(BatchQuery query, DbAdapter adapter, String trimFunction); + ParameterBinding[] bind(ParameterBinding[] bindings, BatchQueryRow row); } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslator.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslator.java index 73a75fdbc..d6150c1b0 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslator.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/BatchTranslator.java @@ -19,33 +19,45 @@ package org.apache.cayenne.access.translator.batch; -import org.apache.cayenne.access.translator.ParameterBinding; -import org.apache.cayenne.query.BatchQueryRow; +import org.apache.cayenne.dba.DbAdapter; +import org.apache.cayenne.query.BatchQuery; /** - * Superclass of batch query translators. - * + * A stateless service that translates a batch query of type {@code T} into an immutable + * {@link TranslatedBatch}. Each batch query flavor (insert, update, delete) is handled by its own + * translator, configured individually in the DI container under the {@link #INSERT}, {@link #UPDATE} + * and {@link #DELETE} binding names. + * + * @param <T> type of the batch query to translate * @since 4.0 */ -public interface BatchTranslator { +public interface BatchTranslator<T extends BatchQuery> { + + /** + * DI binding name of the translator for {@link org.apache.cayenne.query.InsertBatchQuery}. + * + * @since 5.0 + */ + String INSERT = "insert"; /** - * Returns SQL String that can be used to init a PreparedStatement. + * DI binding name of the translator for {@link org.apache.cayenne.query.UpdateBatchQuery}. + * + * @since 5.0 */ - String getSql(); + String UPDATE = "update"; /** - * Returns the widest possible array of bindings for this query. + * DI binding name of the translator for {@link org.apache.cayenne.query.DeleteBatchQuery}. + * + * @since 5.0 */ - ParameterBinding[] getBindings(); + String DELETE = "delete"; /** - * Updates internal bindings to be used with a given row, returning updated - * bindings array. This method guarantees that the returned array contains - * the same bindings in the same order as in the array returned from - * {@link #getBindings()} (but in a state corresponding to the 'row' - * parameter). Usually the returned array is actually the same object reused - * for every iteration, only with changed object state. + * Translates the provided batch query into an immutable {@link TranslatedBatch}. + * + * @since 5.0 */ - ParameterBinding[] updateBindings(BatchQueryRow row); + TranslatedBatch translate(T query, DbAdapter adapter); } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java deleted file mode 100644 index 56ff01969..000000000 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DefaultBatchTranslatorFactory.java +++ /dev/null @@ -1,57 +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.translator.batch; - -import org.apache.cayenne.CayenneRuntimeException; -import org.apache.cayenne.dba.DbAdapter; -import org.apache.cayenne.query.BatchQuery; -import org.apache.cayenne.query.DeleteBatchQuery; -import org.apache.cayenne.query.InsertBatchQuery; -import org.apache.cayenne.query.UpdateBatchQuery; - -/** - * Default implementation of {@link BatchTranslatorFactory}. - * - * @since 4.2 - */ -public class DefaultBatchTranslatorFactory implements BatchTranslatorFactory { - - @Override - public BatchTranslator translator(BatchQuery query, DbAdapter adapter, String trimFunction) { - return switch (query) { - case InsertBatchQuery insertBatchQuery -> insertTranslator(insertBatchQuery, adapter); - case UpdateBatchQuery updateBatchQuery -> updateTranslator(updateBatchQuery, adapter); - case DeleteBatchQuery deleteBatchQuery -> deleteTranslator(deleteBatchQuery, adapter); - case null, default -> throw new CayenneRuntimeException("Unsupported batch query: %s", query); - }; - } - - protected BatchTranslator deleteTranslator(DeleteBatchQuery query, DbAdapter adapter) { - return new DeleteBatchTranslator(query, adapter); - } - - protected BatchTranslator insertTranslator(InsertBatchQuery query, DbAdapter adapter) { - return new InsertBatchTranslator(query, adapter); - } - - protected BatchTranslator updateTranslator(UpdateBatchQuery query, DbAdapter adapter) { - return new UpdateBatchTranslator(query, adapter); - } - -} diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java index 414b86cbb..830a3e7af 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslator.java @@ -23,7 +23,6 @@ import org.apache.cayenne.access.sqlbuilder.DeleteBuilder; import org.apache.cayenne.access.sqlbuilder.SQLBuilder; import org.apache.cayenne.access.translator.ParameterBinding; import org.apache.cayenne.access.types.ExtendedType; -import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.query.BatchQueryRow; import org.apache.cayenne.query.DeleteBatchQuery; @@ -31,37 +30,41 @@ import org.apache.cayenne.query.DeleteBatchQuery; /** * @since 4.2 */ -public class DeleteBatchTranslator extends BaseBatchTranslator<DeleteBatchQuery> implements BatchTranslator { - - public DeleteBatchTranslator(DeleteBatchQuery query, DbAdapter adapter) { - super(query, adapter); - } +public class DeleteBatchTranslator extends BaseBatchTranslator<DeleteBatchQuery> { @Override - public String getSql() { + protected String createSql(BatchTranslatorContext<DeleteBatchQuery> context) { DeleteBuilder deleteBuilder = SQLBuilder .delete(context.getRootDbEntity()) - .where(buildQualifier(context.getQuery().getDbAttributes())); - return doTranslate(deleteBuilder); + .where(buildQualifier(context, context.getQuery().getDbAttributes())); + return doTranslate(context, deleteBuilder); } @Override - protected boolean isNullAttribute(DbAttribute attribute) { + protected boolean isNullAttribute(BatchTranslatorContext<DeleteBatchQuery> context, DbAttribute attribute) { return context.getQuery().isNull(attribute); } @Override - public ParameterBinding[] updateBindings(BatchQueryRow row) { + protected ParameterBinding[] updateBindings( + BatchTranslatorContext<DeleteBatchQuery> context, + ParameterBinding[] bindings, + BatchQueryRow row) { + DeleteBatchQuery deleteBatch = context.getQuery(); - for(int i=0, position=0; i<deleteBatch.getDbAttributes().size(); i++) { - position = updateBinding(row.getValue(i), position); + for (int i = 0, position = 0; i < deleteBatch.getDbAttributes().size(); i++) { + position = updateBinding(context, bindings, row.getValue(i), position); } return bindings; } - protected int updateBinding(Object value, int position) { + protected int updateBinding( + BatchTranslatorContext<DeleteBatchQuery> context, + ParameterBinding[] bindings, + Object value, int position) { + // skip null attributes... they are translated as "IS NULL" - if(value != null) { + if (value != null) { ExtendedType<?> extendedType = context.getAdapter().getExtendedTypes().getRegisteredType(value.getClass()); bindings[position].reset(++position, value, extendedType); } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java index 2330ef872..f67545e26 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslator.java @@ -23,7 +23,6 @@ import org.apache.cayenne.access.sqlbuilder.InsertBuilder; import org.apache.cayenne.access.sqlbuilder.SQLBuilder; import org.apache.cayenne.access.translator.ParameterBinding; import org.apache.cayenne.access.types.ExtendedType; -import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.query.BatchQueryRow; import org.apache.cayenne.query.InsertBatchQuery; @@ -31,20 +30,16 @@ import org.apache.cayenne.query.InsertBatchQuery; /** * @since 4.2 */ -public class InsertBatchTranslator extends BaseBatchTranslator<InsertBatchQuery> implements BatchTranslator { - - public InsertBatchTranslator(InsertBatchQuery query, DbAdapter adapter) { - super(query, adapter); - } +public class InsertBatchTranslator extends BaseBatchTranslator<InsertBatchQuery> { @Override - public String getSql() { + protected String createSql(BatchTranslatorContext<InsertBatchQuery> context) { InsertBatchQuery query = context.getQuery(); InsertBuilder insertBuilder = SQLBuilder.insert(context.getRootDbEntity()); for(DbAttribute attribute : query.getDbAttributes()) { // skip generated attributes, if needed - if(excludeInBatch(attribute)) { + if(excludeInBatch(context, attribute)) { continue; } insertBuilder @@ -54,16 +49,17 @@ public class InsertBatchTranslator extends BaseBatchTranslator<InsertBatchQuery> .value(SQLBuilder.value(1).attribute(attribute)); } - return doTranslate(insertBuilder); + return doTranslate(context, insertBuilder); } @Override - public ParameterBinding[] updateBindings(BatchQueryRow row) { + protected ParameterBinding[] updateBindings(BatchTranslatorContext<InsertBatchQuery> context, + ParameterBinding[] bindings, BatchQueryRow row) { InsertBatchQuery query = context.getQuery(); int i=0; int j=0; for(DbAttribute attribute : query.getDbAttributes()) { - if(excludeInBatch(attribute)) { + if(excludeInBatch(context, attribute)) { i++; continue; } @@ -77,7 +73,7 @@ public class InsertBatchTranslator extends BaseBatchTranslator<InsertBatchQuery> return bindings; } - protected boolean excludeInBatch(DbAttribute attribute) { + protected boolean excludeInBatch(BatchTranslatorContext<InsertBatchQuery> context, DbAttribute attribute) { // attribute inclusion rule - one of the rules below must be true: // (1) attribute not generated // (2) attribute is generated and PK and adapter does not support generated keys @@ -85,7 +81,7 @@ public class InsertBatchTranslator extends BaseBatchTranslator<InsertBatchQuery> } @Override - protected boolean isNullAttribute(DbAttribute attribute) { + protected boolean isNullAttribute(BatchTranslatorContext<InsertBatchQuery> context, DbAttribute attribute) { return false; } } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java index d866fdadf..ced812e00 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslator.java @@ -23,51 +23,85 @@ import org.apache.cayenne.access.sqlbuilder.SQLBuilder; import org.apache.cayenne.access.sqlbuilder.UpdateBuilder; import org.apache.cayenne.access.translator.ParameterBinding; import org.apache.cayenne.access.types.ExtendedType; -import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.dba.TypesMapping; import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.query.BatchQueryRow; import org.apache.cayenne.query.DeleteBatchQuery; +import java.sql.Types; + import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*; /** + * A delete {@link BatchTranslator} that performs a 'soft' delete (an UPDATE setting the 'deleted' field + * to true) for entities that have a boolean 'deleted' field, and falls back to the regular SQL DELETE of + * the {@link DeleteBatchTranslator} superclass for the rest. Bind it under the {@link BatchTranslator#DELETE} + * name to enable soft deletes. + * * @since 4.2 */ public class SoftDeleteBatchTranslator extends DeleteBatchTranslator { + public static final String DEFAULT_DELETED_FIELD_NAME = "DELETED"; + private final String deletedFieldName; - public SoftDeleteBatchTranslator(DeleteBatchQuery query, DbAdapter adapter, String deletedFieldName) { - super(query, adapter); + public SoftDeleteBatchTranslator() { + this(DEFAULT_DELETED_FIELD_NAME); + } + + public SoftDeleteBatchTranslator(String deletedFieldName) { this.deletedFieldName = deletedFieldName; } + protected boolean isHardDelete(DeleteBatchQuery query) { + DbAttribute attr = query.getDbEntity().getAttribute(deletedFieldName); + return attr == null || attr.getType() != Types.BOOLEAN; + } + @Override - public String getSql() { + protected String createSql(BatchTranslatorContext<DeleteBatchQuery> context) { DeleteBatchQuery query = context.getQuery(); - DbAttribute deleteAttribute = query.getDbEntity().getAttribute(deletedFieldName); + if (isHardDelete(query)) { + return super.createSql(context); + } + DbAttribute deleteAttribute = query.getDbEntity().getAttribute(deletedFieldName); UpdateBuilder updateBuilder = update(context.getRootDbEntity()) .set(column(deletedFieldName).attribute(deleteAttribute) .eq(SQLBuilder.value(true).attribute(deleteAttribute))) - .where(buildQualifier(query.getDbAttributes())); + .where(buildQualifier(context, query.getDbAttributes())); + + return doTranslate(context, updateBuilder); + } - String sql = doTranslate(updateBuilder); + @Override + protected ParameterBinding[] createBindings(BatchTranslatorContext<DeleteBatchQuery> context) { + ParameterBinding[] bindings = super.createBindings(context); + if (isHardDelete(context.getQuery())) { + return bindings; + } + // the 'deleted' flag is the first binding and stays constant across all rows of the batch + DbAttribute deleteAttribute = context.getQuery().getDbEntity().getAttribute(deletedFieldName); String typeName = TypesMapping.getJavaBySqlType(deleteAttribute); ExtendedType<?> extendedType = context.getAdapter().getExtendedTypes().getRegisteredType(typeName); bindings[0].reset(1, true, extendedType); - return sql; + return bindings; } @Override - public ParameterBinding[] updateBindings(BatchQueryRow row) { + protected ParameterBinding[] updateBindings(BatchTranslatorContext<DeleteBatchQuery> context, + ParameterBinding[] bindings, BatchQueryRow row) { DeleteBatchQuery deleteBatch = context.getQuery(); + if (isHardDelete(deleteBatch)) { + return super.updateBindings(context, bindings, row); + } + // bindings[0] holds the constant 'deleted' flag, so qualifier values start at position 1 for(int i=0, position=1; i<deleteBatch.getDbAttributes().size(); i++) { - position = updateBinding(row.getValue(i), position); + position = updateBinding(context, bindings, row.getValue(i), position); } return bindings; diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java deleted file mode 100644 index c0eec5aad..000000000 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/SoftDeleteTranslatorFactory.java +++ /dev/null @@ -1,69 +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.translator.batch; - -import java.sql.Types; - -import org.apache.cayenne.dba.DbAdapter; -import org.apache.cayenne.map.DbAttribute; -import org.apache.cayenne.query.DeleteBatchQuery; - -/** - * Implementation of {link #BatchTranslator}, which uses 'soft' delete - * (runs UPDATE and sets 'deleted' field to true instead-of running SQL DELETE) - * - * @since 4.2 - */ -public class SoftDeleteTranslatorFactory extends DefaultBatchTranslatorFactory { - /** - * Default name of 'deleted' field - */ - public static final String DEFAULT_DELETED_FIELD_NAME = "DELETED"; - - /** - * Name of 'deleted' field - */ - private String deletedFieldName; - - public SoftDeleteTranslatorFactory() { - this(DEFAULT_DELETED_FIELD_NAME); - } - - public SoftDeleteTranslatorFactory(String deletedFieldName) { - this.deletedFieldName = deletedFieldName; - } - - @Override - protected BatchTranslator deleteTranslator(DeleteBatchQuery query, DbAdapter adapter) { - - DbAttribute attr = query.getDbEntity().getAttribute(deletedFieldName); - boolean needsSoftDelete = attr != null && attr.getType() == Types.BOOLEAN; - - return needsSoftDelete - ? new SoftDeleteBatchTranslator(query, adapter, deletedFieldName) - : super.deleteTranslator(query, adapter); - } - - /** - * @return name of 'deleted' field - */ - public String getDeletedFieldName() { - return deletedFieldName; - } -} diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleBatchAction.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/TranslatedBatch.java similarity index 57% rename from cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleBatchAction.java rename to cayenne/src/main/java/org/apache/cayenne/access/translator/batch/TranslatedBatch.java index f70d2a95e..4c81af7b2 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleBatchAction.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/TranslatedBatch.java @@ -17,27 +17,24 @@ * under the License. ****************************************************************/ -package org.apache.cayenne.dba.oracle; +package org.apache.cayenne.access.translator.batch; -import org.apache.cayenne.access.DataNode; -import org.apache.cayenne.access.jdbc.BatchAction; -import org.apache.cayenne.access.translator.batch.BatchTranslator; -import org.apache.cayenne.query.BatchQuery; +import org.apache.cayenne.access.translator.ParameterBinding; +import org.apache.cayenne.query.BatchQueryRow; /** - * @since 1.2 + * An immutable result of translating a batch query: the SQL String shared by all rows of the batch, + * the widest possible array of parameter bindings, and a stateless {@link BatchRowBinder} that applies + * a single row's state to those bindings. + * + * @since 5.0 */ -class OracleBatchAction extends BatchAction { +public record TranslatedBatch(String sql, ParameterBinding[] bindings, BatchRowBinder binder) { /** - * @since 4.0 + * Applies the given row's state to the binding template, returning the (reused) bindings array. */ - OracleBatchAction(BatchQuery batchQuery, DataNode dataNode, boolean runningAsBatch) { - super(batchQuery, dataNode, runningAsBatch); - } - - @Override - protected BatchTranslator createTranslator() { - return dataNode.batchTranslator(query, OracleAdapter.TRIM_FUNCTION); + public ParameterBinding[] updateBindings(BatchQueryRow row) { + return binder.bind(bindings, row); } } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java index 6c9eca408..7adf35a0a 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslator.java @@ -23,7 +23,6 @@ import org.apache.cayenne.access.sqlbuilder.SQLBuilder; import org.apache.cayenne.access.sqlbuilder.UpdateBuilder; import org.apache.cayenne.access.translator.ParameterBinding; import org.apache.cayenne.access.types.ExtendedType; -import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.query.BatchQueryRow; import org.apache.cayenne.query.UpdateBatchQuery; @@ -31,14 +30,10 @@ import org.apache.cayenne.query.UpdateBatchQuery; /** * @since 4.2 */ -public class UpdateBatchTranslator extends BaseBatchTranslator<UpdateBatchQuery> implements BatchTranslator { - - public UpdateBatchTranslator(UpdateBatchQuery query, DbAdapter adapter) { - super(query, adapter); - } +public class UpdateBatchTranslator extends BaseBatchTranslator<UpdateBatchQuery> { @Override - public String getSql() { + protected String createSql(BatchTranslatorContext<UpdateBatchQuery> context) { UpdateBatchQuery query = context.getQuery(); UpdateBuilder updateBuilder = SQLBuilder.update(context.getRootDbEntity()); @@ -48,18 +43,19 @@ public class UpdateBatchTranslator extends BaseBatchTranslator<UpdateBatchQuery> .eq(SQLBuilder.value(1).attribute(attr)) ); } - updateBuilder.where(buildQualifier(query.getQualifierAttributes())); + updateBuilder.where(buildQualifier(context, query.getQualifierAttributes())); - return doTranslate(updateBuilder); + return doTranslate(context, updateBuilder); } @Override - protected boolean isNullAttribute(DbAttribute attribute) { + protected boolean isNullAttribute(BatchTranslatorContext<UpdateBatchQuery> context, DbAttribute attribute) { return context.getQuery().isNull(attribute); } @Override - public ParameterBinding[] updateBindings(BatchQueryRow row) { + protected ParameterBinding[] updateBindings(BatchTranslatorContext<UpdateBatchQuery> context, + ParameterBinding[] bindings, BatchQueryRow row) { UpdateBatchQuery updateBatch = context.getQuery(); int i = 0; diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java deleted file mode 100644 index aa8435253..000000000 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslator.java +++ /dev/null @@ -1,112 +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.translator.batch.legacy; - -import java.sql.Types; - -import org.apache.cayenne.access.translator.ParameterBinding; -import org.apache.cayenne.access.translator.batch.BatchTranslator; -import org.apache.cayenne.dba.DbAdapter; -import org.apache.cayenne.dba.QuotingStrategy; -import org.apache.cayenne.map.DbAttribute; -import org.apache.cayenne.query.BatchQuery; -import org.apache.cayenne.query.BatchQueryRow; - -/** - * Superclass of batch query translators. - * - * @since 4.0 - * @deprecated since 4.2 - */ -@Deprecated(since = "4.2") -public abstract class DefaultBatchTranslator implements BatchTranslator { - - protected BatchQuery query; - protected DbAdapter adapter; - protected String trimFunction; - - protected boolean translated; - protected String sql; - protected ParameterBinding[] bindings; - - public DefaultBatchTranslator(BatchQuery query, DbAdapter adapter, String trimFunction) { - this.query = query; - this.adapter = adapter; - this.trimFunction = trimFunction; - } - - protected void ensureTranslated() { - if (!translated) { - this.sql = createSql(); - this.bindings = createBindings(); - translated = true; - } - } - - /** - * Translates BatchQuery into an SQL string formatted to use in a - * PreparedStatement. - */ - @Override - public String getSql() { - ensureTranslated(); - return sql; - } - - @Override - public ParameterBinding[] getBindings() { - ensureTranslated(); - return bindings; - } - - @Override - public ParameterBinding[] updateBindings(BatchQueryRow row) { - ensureTranslated(); - return doUpdateBindings(row); - } - - protected abstract String createSql(); - - protected abstract ParameterBinding[] createBindings(); - - protected abstract ParameterBinding[] doUpdateBindings(BatchQueryRow row); - - /** - * Appends the name of the column to the query buffer. Subclasses use this - * method to append column names in the WHERE clause, i.e. for the columns - * that are not being updated. - */ - protected void appendDbAttribute(StringBuilder buf, DbAttribute dbAttribute) { - - // TODO: (Andrus) is there a need for trimming binary types? - boolean trim = dbAttribute.getType() == Types.CHAR && trimFunction != null; - if (trim) { - buf.append(trimFunction).append('('); - } - - QuotingStrategy strategy = adapter.getQuotingStrategy(); - - buf.append(strategy.quotedName(dbAttribute)); - - if (trim) { - buf.append(')'); - } - } - -} diff --git a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java index 50dc4f8df..562ba503c 100644 --- a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java +++ b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java @@ -38,8 +38,10 @@ import org.apache.cayenne.access.flush.operation.DefaultDbRowOpSorter; import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator; import org.apache.cayenne.access.jdbc.reader.DefaultRowReaderFactory; import org.apache.cayenne.access.jdbc.reader.RowReaderFactory; -import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory; -import org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory; +import org.apache.cayenne.access.translator.batch.BatchTranslator; +import org.apache.cayenne.access.translator.batch.DeleteBatchTranslator; +import org.apache.cayenne.access.translator.batch.InsertBatchTranslator; +import org.apache.cayenne.access.translator.batch.UpdateBatchTranslator; import org.apache.cayenne.access.translator.select.DbAdapterDelegatedSelectTranslator; import org.apache.cayenne.access.translator.select.SelectTranslator; import org.apache.cayenne.access.types.*; @@ -461,7 +463,9 @@ public class CoreModule implements Module { // configured by the owning domain binder.bind(EntitySorter.class).to(AshwoodEntitySorter.class).withoutScope(); - binder.bind(BatchTranslatorFactory.class).to(DefaultBatchTranslatorFactory.class); + binder.bind(Key.get(BatchTranslator.class, BatchTranslator.INSERT)).to(InsertBatchTranslator.class); + binder.bind(Key.get(BatchTranslator.class, BatchTranslator.UPDATE)).to(UpdateBatchTranslator.class); + binder.bind(Key.get(BatchTranslator.class, BatchTranslator.DELETE)).to(DeleteBatchTranslator.class); binder.bind(SelectTranslator.class).to(DbAdapterDelegatedSelectTranslator.class); // a default ObjectMapRetainStrategy used to create objects map for diff --git a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DefaultDataNodeFactory.java b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DefaultDataNodeFactory.java index 765e3a2e7..fa818cd2e 100644 --- a/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DefaultDataNodeFactory.java +++ b/cayenne/src/main/java/org/apache/cayenne/configuration/runtime/DefaultDataNodeFactory.java @@ -22,8 +22,11 @@ import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.dbsync.SchemaUpdateStrategyFactory; import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator; import org.apache.cayenne.access.jdbc.reader.RowReaderFactory; -import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory; +import org.apache.cayenne.access.translator.batch.BatchTranslator; import org.apache.cayenne.access.translator.select.SelectTranslator; +import org.apache.cayenne.query.DeleteBatchQuery; +import org.apache.cayenne.query.InsertBatchQuery; +import org.apache.cayenne.query.UpdateBatchQuery; import org.apache.cayenne.configuration.DataNodeDescriptor; import org.apache.cayenne.di.AdhocObjectFactory; import org.apache.cayenne.di.Inject; @@ -45,9 +48,15 @@ public class DefaultDataNodeFactory implements DataNodeFactory { @Inject protected DataSourceFactory dataSourceFactory; - @Inject - protected BatchTranslatorFactory batchTranslatorFactory; - + @Inject(BatchTranslator.INSERT) + protected BatchTranslator<InsertBatchQuery> insertBatchTranslator; + + @Inject(BatchTranslator.UPDATE) + protected BatchTranslator<UpdateBatchQuery> updateBatchTranslator; + + @Inject(BatchTranslator.DELETE) + protected BatchTranslator<DeleteBatchQuery> deleteBatchTranslator; + @Inject protected SelectTranslator selectTranslator; @@ -70,7 +79,9 @@ public class DefaultDataNodeFactory implements DataNodeFactory { dataNode.setJdbcEventLogger(jdbcEventLogger); dataNode.setRowReaderFactory(rowReaderFactory); - dataNode.setBatchTranslatorFactory(batchTranslatorFactory); + dataNode.setInsertBatchTranslator(insertBatchTranslator); + dataNode.setUpdateBatchTranslator(updateBatchTranslator); + dataNode.setDeleteBatchTranslator(deleteBatchTranslator); dataNode.setSelectTranslator(selectTranslator); dataNode.setSqlTemplateTranslator(sqlTemplateTranslator); diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleActionBuilder.java b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleActionBuilder.java index 94fa21547..02537b827 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleActionBuilder.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/oracle/OracleActionBuilder.java @@ -21,7 +21,6 @@ package org.apache.cayenne.dba.oracle; import org.apache.cayenne.access.DataNode; import org.apache.cayenne.dba.JdbcActionBuilder; -import org.apache.cayenne.query.BatchQuery; import org.apache.cayenne.query.FluentSelect; import org.apache.cayenne.query.ProcedureQuery; import org.apache.cayenne.query.SQLAction; @@ -41,18 +40,6 @@ class OracleActionBuilder extends JdbcActionBuilder { return new OracleSQLTemplateAction(query, dataNode); } - @Override - public SQLAction batchAction(BatchQuery query) { - - // optimistic locking is not supported in batches due to JDBC driver - // limitations - // TODO: is this still true with ojdbc6.jar? - boolean useOptimisticLock = query.isUsingOptimisticLocking(); - boolean runningAsBatch = !useOptimisticLock && dataNode.getAdapter().supportsBatchUpdates(); - - return new OracleBatchAction(query, dataNode, runningAsBatch); - } - @Override public SQLAction procedureAction(ProcedureQuery query) { return new OracleProcedureAction(query, dataNode); diff --git a/cayenne/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingIT.java b/cayenne/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingIT.java index 87fb2996d..5ecf042d6 100644 --- a/cayenne/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingIT.java +++ b/cayenne/src/test/java/org/apache/cayenne/access/jdbc/BatchActionLockingIT.java @@ -24,6 +24,7 @@ import org.apache.cayenne.access.MockOperationObserver; import org.apache.cayenne.access.OptimisticLockException; import org.apache.cayenne.access.jdbc.reader.RowReaderFactory; import org.apache.cayenne.access.translator.batch.DeleteBatchTranslator; +import org.apache.cayenne.access.translator.batch.TranslatedBatch; import org.apache.cayenne.dba.JdbcAdapter; import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.map.DbEntity; @@ -72,7 +73,7 @@ public class BatchActionLockingIT { batchQuery.setUsingOptimisticLocking(true); batchQuery.add(qualifierSnapshot); - DeleteBatchTranslator batchQueryBuilder = new DeleteBatchTranslator(batchQuery, adapter); + TranslatedBatch translated = new DeleteBatchTranslator().translate(batchQuery, adapter); TestConnection mockConnection = new TestConnection(); mockConnection.prepareUpdateCount("DELETE", 1); @@ -84,7 +85,7 @@ public class BatchActionLockingIT { node.setEntityResolver(resolver); node.setRowReaderFactory(mock(RowReaderFactory.class)); BatchAction action = new BatchAction(batchQuery, node, false); - action.runAsIndividualQueries(mockConnection, batchQueryBuilder, new MockOperationObserver(), generatesKeys); + action.runAsIndividualQueries(mockConnection, translated, new MockOperationObserver(), generatesKeys); assertEquals(0, mockConnection.getNumberCommits()); assertEquals(0, mockConnection.getNumberRollbacks()); } @@ -110,7 +111,7 @@ public class BatchActionLockingIT { batchQuery.setUsingOptimisticLocking(true); batchQuery.add(qualifierSnapshot); - DeleteBatchTranslator batchQueryBuilder = new DeleteBatchTranslator(batchQuery, adapter); + TranslatedBatch translated = new DeleteBatchTranslator().translate(batchQuery, adapter); TestConnection mockConnection = new TestConnection(); mockConnection.prepareUpdateCount("DELETE", 0); @@ -122,7 +123,7 @@ public class BatchActionLockingIT { node.setRowReaderFactory(mock(RowReaderFactory.class)); BatchAction action = new BatchAction(batchQuery, node, false); assertThrows(OptimisticLockException.class, () -> - action.runAsIndividualQueries(mockConnection, batchQueryBuilder, new MockOperationObserver(), generatesKeys)); + action.runAsIndividualQueries(mockConnection, translated, new MockOperationObserver(), generatesKeys)); assertEquals(0, mockConnection.getNumberCommits()); assertEquals(0, mockConnection.getNumberRollbacks()); } diff --git a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java index 6814d1f8d..ba646a9a1 100644 --- a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java +++ b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/DeleteBatchTranslatorIT.java @@ -40,7 +40,6 @@ import java.util.Collections; import java.util.List; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; public class DeleteBatchTranslatorIT { @@ -61,17 +60,6 @@ public class DeleteBatchTranslatorIT { objectFactory = env.adhocObjectFactory(); } - @Test - public void constructor() { - DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName()); - - DeleteBatchQuery query = mock(DeleteBatchQuery.class); - DeleteBatchTranslator builder = new DeleteBatchTranslator(query, adapter); - - assertSame(adapter, builder.context.getAdapter()); - assertSame(query, builder.context.getQuery()); - } - @Test public void createSqlString() { DbEntity entity = runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class) @@ -82,8 +70,7 @@ public class DeleteBatchTranslatorIT { DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, idAttributes, Collections.emptySet(), 1); DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName()); - DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, adapter); - String generatedSql = builder.getSql(); + String generatedSql = new DeleteBatchTranslator().translate(deleteQuery, adapter).sql(); assertNotNull(generatedSql); assertEquals("DELETE FROM " + entity.getName() + " WHERE LOCKING_TEST_ID = ?", generatedSql); } @@ -101,8 +88,7 @@ public class DeleteBatchTranslatorIT { DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, idAttributes, nullAttributes, 1); DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName()); - DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, adapter); - String generatedSql = builder.getSql(); + String generatedSql = new DeleteBatchTranslator().translate(deleteQuery, adapter).sql(); assertNotNull(generatedSql); assertEquals("DELETE FROM " + entity.getName() + " WHERE ( LOCKING_TEST_ID = ? ) AND ( NAME IS NULL )", generatedSql); } @@ -117,8 +103,7 @@ public class DeleteBatchTranslatorIT { List<DbAttribute> idAttributes = Collections.singletonList(entity.getAttribute("LOCKING_TEST_ID")); DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, idAttributes, Collections.emptySet(), 1); - DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, adapter); - String generatedSql = builder.getSql(); + String generatedSql = new DeleteBatchTranslator().translate(deleteQuery, adapter).sql(); String charStart = unitAdapter.getIdentifiersStartQuote(); String charEnd = unitAdapter.getIdentifiersEndQuote(); @@ -147,8 +132,7 @@ public class DeleteBatchTranslatorIT { DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, idAttributes, nullAttributes, 1); - DeleteBatchTranslator builder = new DeleteBatchTranslator(deleteQuery, adapter); - String generatedSql = builder.getSql(); + String generatedSql = new DeleteBatchTranslator().translate(deleteQuery, adapter).sql(); String charStart = unitAdapter.getIdentifiersStartQuote(); String charEnd = unitAdapter.getIdentifiersEndQuote(); diff --git a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java index 4efb2566f..fcd4e7a9f 100644 --- a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java +++ b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/InsertBatchTranslatorIT.java @@ -34,8 +34,6 @@ import org.junit.jupiter.api.BeforeEach; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.mockito.Mockito.mock; public class InsertBatchTranslatorIT { @@ -56,17 +54,6 @@ public class InsertBatchTranslatorIT { objectFactory = env.adhocObjectFactory(); } - @Test - public void constructor() { - DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName()); - - InsertBatchQuery query = mock(InsertBatchQuery.class); - InsertBatchTranslator builder = new InsertBatchTranslator(query, adapter); - - assertSame(adapter, builder.context.getAdapter()); - assertSame(query, builder.context.getQuery()); - } - @Test public void createSqlString() { DbEntity entity = runtime.getDataDomain().getEntityResolver() @@ -75,8 +62,7 @@ public class InsertBatchTranslatorIT { DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName()); InsertBatchQuery insertQuery = new InsertBatchQuery(entity, 1); - InsertBatchTranslator builder = new InsertBatchTranslator(insertQuery, adapter); - String generatedSql = builder.getSql(); + String generatedSql = new InsertBatchTranslator().translate(insertQuery, adapter).sql(); assertNotNull(generatedSql); assertEquals("INSERT INTO " + entity.getName() + "( DESCRIPTION, INT_COLUMN_NOTNULL, INT_COLUMN_NULL, LOCKING_TEST_ID, NAME) " + "VALUES( ?, ?, ?, ?, ?)", @@ -92,8 +78,7 @@ public class InsertBatchTranslatorIT { entity.getDataMap().setQuotingSQLIdentifiers(true); InsertBatchQuery insertQuery = new InsertBatchQuery(entity, 1); - InsertBatchTranslator builder = new InsertBatchTranslator(insertQuery, adapter); - String generatedSql = builder.getSql(); + String generatedSql = new InsertBatchTranslator().translate(insertQuery, adapter).sql(); String charStart = unitAdapter.getIdentifiersStartQuote(); String charEnd = unitAdapter.getIdentifiersEndQuote(); assertNotNull(generatedSql); diff --git a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java index c5cd3a691..b8ecf8423 100644 --- a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java +++ b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/SoftDeleteBatchTranslatorIT.java @@ -56,13 +56,13 @@ public class SoftDeleteBatchTranslatorIT { private TestDbAdapter unitAdapter; private AdhocObjectFactory objectFactory; - private DeleteBatchTranslator createTranslator(DeleteBatchQuery query) { + private String createSql(DeleteBatchQuery query) { DbAdapter adapter = objectFactory.newInstance(JdbcAdapter.class, JdbcAdapter.class.getName()); - return createTranslator(query, adapter); + return createSql(query, adapter); } - private DeleteBatchTranslator createTranslator(DeleteBatchQuery query, DbAdapter adapter) { - return (DeleteBatchTranslator) new SoftDeleteTranslatorFactory().translator(query, adapter, null); + private String createSql(DeleteBatchQuery query, DbAdapter adapter) { + return new SoftDeleteBatchTranslator().translate(query, adapter).sql(); } @BeforeEach @@ -79,8 +79,7 @@ public class SoftDeleteBatchTranslatorIT { List<DbAttribute> idAttributes = Collections.singletonList(entity.getAttribute("ID")); DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, idAttributes, Collections.emptySet(), 1); - DeleteBatchTranslator builder = createTranslator(deleteQuery); - String generatedSql = builder.getSql(); + String generatedSql = createSql(deleteQuery); assertNotNull(generatedSql); assertEquals("UPDATE " + entity.getName() + " SET DELETED = ? WHERE ID = ?", generatedSql); } @@ -94,8 +93,7 @@ public class SoftDeleteBatchTranslatorIT { Collection<String> nullAttributes = Collections.singleton("NAME"); DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, idAttributes, nullAttributes, 1); - DeleteBatchTranslator builder = createTranslator(deleteQuery); - String generatedSql = builder.getSql(); + String generatedSql = createSql(deleteQuery); assertNotNull(generatedSql); assertEquals("UPDATE " + entity.getName() + " SET DELETED = ? WHERE ( ID = ? ) AND ( NAME IS NULL )", generatedSql); } @@ -111,8 +109,7 @@ public class SoftDeleteBatchTranslatorIT { DeleteBatchQuery deleteQuery = new DeleteBatchQuery(entity, idAttributes, Collections.emptySet(), 1); DbAdapter adapter = node.getAdapter(); - DeleteBatchTranslator builder = createTranslator(deleteQuery, adapter); - String generatedSql = builder.getSql(); + String generatedSql = createSql(deleteQuery, adapter); String charStart = unitAdapter.getIdentifiersStartQuote(); String charEnd = unitAdapter.getIdentifiersEndQuote(); @@ -131,9 +128,9 @@ public class SoftDeleteBatchTranslatorIT { DbEntity entity = env.context().getEntityResolver().getObjEntity(SoftDelete.class).getDbEntity(); - BatchTranslatorFactory oldFactory = node.getBatchTranslatorFactory(); + BatchTranslator<DeleteBatchQuery> oldTranslator = node.getDeleteBatchTranslator(); try { - node.setBatchTranslatorFactory(new SoftDeleteTranslatorFactory()); + node.setDeleteBatchTranslator(new SoftDeleteBatchTranslator()); final SoftDelete test = env.context().newObject(SoftDelete.class); test.setName("SoftDeleteBatchQueryBuilderTest"); @@ -169,7 +166,7 @@ public class SoftDeleteBatchTranslatorIT { }.runTest(200); } finally { env.context().performQuery(new SQLTemplate(entity, "DELETE FROM SOFT_DELETE")); - node.setBatchTranslatorFactory(oldFactory); + node.setDeleteBatchTranslator(oldTranslator); } } diff --git a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java index 13150be93..053a03d22 100644 --- a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java +++ b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/UpdateBatchTranslatorIT.java @@ -40,7 +40,6 @@ import java.util.Collections; import java.util.List; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; public class UpdateBatchTranslatorIT { @@ -61,16 +60,6 @@ public class UpdateBatchTranslatorIT { objectFactory = env.adhocObjectFactory(); } - @Test - public void constructor() { - DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName()); - UpdateBatchQuery query = mock(UpdateBatchQuery.class); - UpdateBatchTranslator builder = new UpdateBatchTranslator(query, adapter); - - assertSame(adapter, builder.context.getAdapter()); - assertSame(query, builder.context.getQuery()); - } - @Test public void createSqlString() { DbEntity entity = runtime.getDataDomain().getEntityResolver().getObjEntity(SimpleLockingTestEntity.class) @@ -83,8 +72,7 @@ public class UpdateBatchTranslatorIT { Collections.emptySet(), 1); DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName()); - UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, adapter); - String generatedSql = builder.getSql(); + String generatedSql = new UpdateBatchTranslator().translate(updateQuery, adapter).sql(); assertNotNull(generatedSql); assertEquals("UPDATE " + entity.getName() + " SET DESCRIPTION = ? WHERE LOCKING_TEST_ID = ?", generatedSql); } @@ -103,8 +91,7 @@ public class UpdateBatchTranslatorIT { UpdateBatchQuery updateQuery = new UpdateBatchQuery(entity, idAttributes, updatedAttributes, nullAttributes, 1); DbAdapter adapter = objectFactory.newInstance(DbAdapter.class, JdbcAdapter.class.getName()); - UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, adapter); - String generatedSql = builder.getSql(); + String generatedSql = new UpdateBatchTranslator().translate(updateQuery, adapter).sql(); assertNotNull(generatedSql); assertEquals("UPDATE " + entity.getName() + " SET DESCRIPTION = ? WHERE ( LOCKING_TEST_ID = ? ) AND ( NAME IS NULL )", @@ -123,8 +110,7 @@ public class UpdateBatchTranslatorIT { UpdateBatchQuery updateQuery = new UpdateBatchQuery(entity, idAttributes, updatedAttributes, Collections.emptySet(), 1); - UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, adapter); - String generatedSql = builder.getSql(); + String generatedSql = new UpdateBatchTranslator().translate(updateQuery, adapter).sql(); String charStart = unitAdapter.getIdentifiersStartQuote(); String charEnd = unitAdapter.getIdentifiersEndQuote(); @@ -153,8 +139,7 @@ public class UpdateBatchTranslatorIT { UpdateBatchQuery updateQuery = new UpdateBatchQuery(entity, idAttributes, updatedAttributes, nullAttributes, 1); - UpdateBatchTranslator builder = new UpdateBatchTranslator(updateQuery, adapter); - String generatedSql = builder.getSql(); + String generatedSql = new UpdateBatchTranslator().translate(updateQuery, adapter).sql(); assertNotNull(generatedSql); String charStart = unitAdapter.getIdentifiersStartQuote(); diff --git a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java b/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java deleted file mode 100644 index 3db0aa773..000000000 --- a/cayenne/src/test/java/org/apache/cayenne/access/translator/batch/legacy/DefaultBatchTranslatorIT.java +++ /dev/null @@ -1,141 +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.translator.batch.legacy; - -import org.apache.cayenne.access.translator.ParameterBinding; -import org.apache.cayenne.dba.DbAdapter; -import org.apache.cayenne.dba.JdbcAdapter; -import org.apache.cayenne.map.DbAttribute; -import org.apache.cayenne.map.DbEntity; -import org.apache.cayenne.query.BatchQuery; -import org.apache.cayenne.query.BatchQueryRow; -import org.apache.cayenne.unit.CayenneProjects; -import org.apache.cayenne.unit.CayenneTestsEnv; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.Test; -import java.sql.Types; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.mockito.Mockito.mock; - -@Deprecated -public class DefaultBatchTranslatorIT { - - @RegisterExtension - static final CayenneTestsEnv env = CayenneTestsEnv.forProject(CayenneProjects.TESTMAP_PROJECT); - - @Test - public void constructor() throws Exception { - DbAdapter adapter = env.adhocObjectFactory().newInstance(DbAdapter.class, JdbcAdapter.class.getName()); - DefaultBatchTranslator builder = new DefaultBatchTranslator(mock(BatchQuery.class), adapter, null) { - @Override - protected String createSql() { - return null; - } - - @Override - protected ParameterBinding[] createBindings() { - return new ParameterBinding[0]; - } - - @Override - protected ParameterBinding[] doUpdateBindings(BatchQueryRow row) { - return new ParameterBinding[0]; - } - }; - - assertSame(adapter, builder.adapter); - } - - @Test - public void appendDbAttribute1() throws Exception { - DbAdapter adapter = env.adhocObjectFactory().newInstance(DbAdapter.class, JdbcAdapter.class.getName()); - String trimFunction = "testTrim"; - - DefaultBatchTranslator builder = new DefaultBatchTranslator(mock(BatchQuery.class), adapter, trimFunction) { - - @Override - protected String createSql() { - return null; - } - - @Override - protected ParameterBinding[] createBindings() { - return new ParameterBinding[0]; - } - - @Override - protected ParameterBinding[] doUpdateBindings(BatchQueryRow row) { - return new ParameterBinding[0]; - } - }; - - StringBuilder buf = new StringBuilder(); - DbEntity entity = new DbEntity("Test"); - DbAttribute attr = new DbAttribute("testAttr", Types.CHAR, null); - attr.setEntity(entity); - builder.appendDbAttribute(buf, attr); - assertEquals("testTrim(testAttr)", buf.toString()); - - buf = new StringBuilder(); - attr = new DbAttribute("testAttr", Types.VARCHAR, null); - attr.setEntity(entity); - builder.appendDbAttribute(buf, attr); - assertEquals("testAttr", buf.toString()); - } - - @Test - public void appendDbAttribute2() throws Exception { - DbAdapter adapter = env.adhocObjectFactory().newInstance(DbAdapter.class, JdbcAdapter.class.getName()); - - DefaultBatchTranslator builder = new DefaultBatchTranslator(mock(BatchQuery.class), adapter, null) { - @Override - protected String createSql() { - return null; - } - - @Override - protected ParameterBinding[] createBindings() { - return new ParameterBinding[0]; - } - - @Override - protected ParameterBinding[] doUpdateBindings(BatchQueryRow row) { - return new ParameterBinding[0]; - } - }; - - StringBuilder buf = new StringBuilder(); - DbEntity entity = new DbEntity("Test"); - - DbAttribute attr = new DbAttribute("testAttr", Types.CHAR, null); - attr.setEntity(entity); - builder.appendDbAttribute(buf, attr); - assertEquals("testAttr", buf.toString()); - - buf = new StringBuilder(); - attr = new DbAttribute("testAttr", Types.VARCHAR, null); - attr.setEntity(entity); - - builder.appendDbAttribute(buf, attr); - assertEquals("testAttr", buf.toString()); - } -} diff --git a/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataDomainProviderTest.java b/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataDomainProviderTest.java index 81c9b61f2..cf8ea1cd6 100644 --- a/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataDomainProviderTest.java +++ b/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DataDomainProviderTest.java @@ -32,8 +32,10 @@ import org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy; import org.apache.cayenne.access.dbsync.ThrowOnPartialOrCreateSchemaStrategy; import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator; import org.apache.cayenne.access.jdbc.reader.RowReaderFactory; -import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory; -import org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory; +import org.apache.cayenne.access.translator.batch.BatchTranslator; +import org.apache.cayenne.access.translator.batch.DeleteBatchTranslator; +import org.apache.cayenne.access.translator.batch.InsertBatchTranslator; +import org.apache.cayenne.access.translator.batch.UpdateBatchTranslator; import org.apache.cayenne.access.translator.select.DbAdapterDelegatedSelectTranslator; import org.apache.cayenne.access.translator.select.SelectTranslator; import org.apache.cayenne.access.types.DefaultValueObjectTypeRegistry; @@ -222,7 +224,9 @@ public class DataDomainProviderTest { b.bind(DataChannelDescriptorLoader.class).toInstance(testLoader); b.bind(DbAdapterFactory.class).to(DefaultDbAdapterFactory.class); b.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class); - b.bind(BatchTranslatorFactory.class).to(DefaultBatchTranslatorFactory.class); + b.bind(Key.get(BatchTranslator.class, BatchTranslator.INSERT)).to(InsertBatchTranslator.class); + b.bind(Key.get(BatchTranslator.class, BatchTranslator.UPDATE)).to(UpdateBatchTranslator.class); + b.bind(Key.get(BatchTranslator.class, BatchTranslator.DELETE)).to(DeleteBatchTranslator.class); b.bind(SelectTranslator.class).to(DbAdapterDelegatedSelectTranslator.class); b.bind(DataSourceFactory.class).toInstance(new MockDataSourceFactory()); diff --git a/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DefaultDbAdapterFactoryTest.java b/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DefaultDbAdapterFactoryTest.java index 75cf74369..06b48b562 100644 --- a/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DefaultDbAdapterFactoryTest.java +++ b/cayenne/src/test/java/org/apache/cayenne/configuration/runtime/DefaultDbAdapterFactoryTest.java @@ -18,7 +18,7 @@ ****************************************************************/ package org.apache.cayenne.configuration.runtime; -import org.apache.cayenne.access.translator.batch.BatchTranslatorFactory; +import org.apache.cayenne.access.translator.batch.BatchTranslator; import org.apache.cayenne.access.types.DefaultValueObjectTypeRegistry; import org.apache.cayenne.access.types.ValueObjectTypeRegistry; import org.apache.cayenne.configuration.Constants; @@ -84,7 +84,9 @@ public class DefaultDbAdapterFactoryTest { binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class); binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class); binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class); - binder.bind(BatchTranslatorFactory.class).toInstance(mock(BatchTranslatorFactory.class)); + binder.bind(Key.get(BatchTranslator.class, BatchTranslator.INSERT)).toInstance(mock(BatchTranslator.class)); + binder.bind(Key.get(BatchTranslator.class, BatchTranslator.UPDATE)).toInstance(mock(BatchTranslator.class)); + binder.bind(Key.get(BatchTranslator.class, BatchTranslator.DELETE)).toInstance(mock(BatchTranslator.class)); }; Injector injector = DIBootstrap.createInjector(testModule); @@ -114,7 +116,9 @@ public class DefaultDbAdapterFactoryTest { b.bind(ResourceLocator.class).to(ClassLoaderResourceLocator.class); b.bind(Key.get(ResourceLocator.class, Constants.RESOURCE_LOCATOR)).to(ClassLoaderResourceLocator.class); b.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class); - b.bind(BatchTranslatorFactory.class).toInstance(mock(BatchTranslatorFactory.class)); + b.bind(Key.get(BatchTranslator.class, BatchTranslator.INSERT)).toInstance(mock(BatchTranslator.class)); + b.bind(Key.get(BatchTranslator.class, BatchTranslator.UPDATE)).toInstance(mock(BatchTranslator.class)); + b.bind(Key.get(BatchTranslator.class, BatchTranslator.DELETE)).toInstance(mock(BatchTranslator.class)); b.bind(ValueObjectTypeRegistry.class).to(DefaultValueObjectTypeRegistry.class); }; @@ -149,7 +153,9 @@ public class DefaultDbAdapterFactoryTest { b.bind(ResourceLocator.class).to(ClassLoaderResourceLocator.class); b.bind(Key.get(ResourceLocator.class, Constants.RESOURCE_LOCATOR)).to(ClassLoaderResourceLocator.class); b.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class); - b.bind(BatchTranslatorFactory.class).toInstance(mock(BatchTranslatorFactory.class)); + b.bind(Key.get(BatchTranslator.class, BatchTranslator.INSERT)).toInstance(mock(BatchTranslator.class)); + b.bind(Key.get(BatchTranslator.class, BatchTranslator.UPDATE)).toInstance(mock(BatchTranslator.class)); + b.bind(Key.get(BatchTranslator.class, BatchTranslator.DELETE)).toInstance(mock(BatchTranslator.class)); b.bind(ValueObjectTypeRegistry.class).to(DefaultValueObjectTypeRegistry.class); b.bind(ValueComparisonStrategyFactory.class).to(DefaultValueComparisonStrategyFactory.class); @@ -189,7 +195,9 @@ public class DefaultDbAdapterFactoryTest { binder.bind(JdbcEventLogger.class).to(Slf4jJdbcEventLogger.class); binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class); binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class); - binder.bind(BatchTranslatorFactory.class).toInstance(mock(BatchTranslatorFactory.class)); + binder.bind(Key.get(BatchTranslator.class, BatchTranslator.INSERT)).toInstance(mock(BatchTranslator.class)); + binder.bind(Key.get(BatchTranslator.class, BatchTranslator.UPDATE)).toInstance(mock(BatchTranslator.class)); + binder.bind(Key.get(BatchTranslator.class, BatchTranslator.DELETE)).toInstance(mock(BatchTranslator.class)); }; Injector injector = DIBootstrap.createInjector(testModule);
