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 1f96946ed3aa84fabb35fd95037a7cc75fba5b0d Author: Andrus Adamchik <[email protected]> AuthorDate: Sun May 31 14:49:42 2026 -0400 Translators as stateless services producing immutable TranslatedXyz records * procedure translators --- .../java/org/apache/cayenne/access/DataNode.java | 16 ++ .../cayenne/access/jdbc/ProcedureAction.java | 49 +++-- .../DbAdapterDelegatedProcedureTranslator.java | 38 ++++ .../procedure/DefaultProcedureTranslator.java | 129 ++++++++++++ .../translator/procedure/ProcedureTranslator.java | 232 +-------------------- .../translator/procedure/TranslatedProcedure.java | 31 +++ .../cayenne/configuration/runtime/CoreModule.java | 3 + .../runtime/DefaultDataNodeFactory.java | 5 + .../java/org/apache/cayenne/dba/AutoAdapter.java | 10 + .../java/org/apache/cayenne/dba/DbAdapter.java | 10 + .../java/org/apache/cayenne/dba/JdbcAdapter.java | 8 + .../apache/cayenne/dba/db2/DB2ProcedureAction.java | 10 +- .../cayenne/dba/hsqldb/HSQLActionBuilder.java | 22 -- .../apache/cayenne/dba/hsqldb/HSQLDBAdapter.java | 11 + .../dba/hsqldb/HSQLDBProcedureTranslator.java | 11 +- .../org/apache/cayenne/dba/mysql/MySQLAdapter.java | 11 + .../cayenne/dba/mysql/MySQLProcedureAction.java | 42 +--- .../dba/mysql/MySQLProcedureTranslator.java | 45 ++++ .../cayenne/dba/postgres/PostgresAdapter.java | 11 + .../dba/postgres/PostgresProcedureAction.java | 39 +--- .../dba/postgres/PostgresProcedureTranslator.java | 45 ++++ .../dba/sqlserver/SQLServerProcedureAction.java | 11 +- .../runtime/DataDomainProviderTest.java | 3 + 23 files changed, 449 insertions(+), 343 deletions(-) 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 f1287c41b..049e4fc36 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/DataNode.java @@ -26,6 +26,7 @@ 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.procedure.ProcedureTranslator; import org.apache.cayenne.access.translator.select.SelectTranslator; import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator; import org.apache.cayenne.dba.DbAdapter; @@ -75,6 +76,7 @@ public class DataNode { private BatchTranslator<UpdateBatchQuery> updateBatchTranslator; private BatchTranslator<DeleteBatchQuery> deleteBatchTranslator; private SelectTranslator selectTranslator; + private ProcedureTranslator procedureTranslator; private SQLTemplateTranslator sqlTemplateTranslator; TransactionDataSource readThroughDataSource; @@ -425,6 +427,20 @@ public class DataNode { this.selectTranslator = selectTranslator; } + /** + * @since 5.0 + */ + public ProcedureTranslator getProcedureTranslator() { + return procedureTranslator; + } + + /** + * @since 5.0 + */ + public void setProcedureTranslator(ProcedureTranslator procedureTranslator) { + this.procedureTranslator = procedureTranslator; + } + // a read-through DataSource that ensures returning the same connection // within // transaction. 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 31e661090..9fd59caed 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 @@ -23,8 +23,10 @@ import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.DataRow; import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.OperationObserver; -import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; +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.dba.DbAdapter; import org.apache.cayenne.map.Procedure; import org.apache.cayenne.map.ProcedureParameter; import org.apache.cayenne.query.ProcedureQuery; @@ -66,10 +68,14 @@ public class ProcedureAction extends BaseSQLAction { processedResultSets = 0; - ProcedureTranslator transl = createTranslator(connection); + TranslatedProcedure translated = dataNode.getProcedureTranslator() + .translate(query, dataNode.getAdapter(), dataNode.getEntityResolver()); - try (CallableStatement statement = (CallableStatement) transl.createStatement();) { + dataNode.getJdbcEventLogger().logQuery(translated.sql(), translated.bindings()); + + try (CallableStatement statement = connection.prepareCall(translated.sql());) { initStatement(statement); + bindParameters(statement, translated); // stored procedure may contain a mixture of update counts and // result sets, @@ -114,19 +120,32 @@ public class ProcedureAction extends BaseSQLAction { } /** - * Returns the ProcedureTranslator to use for this ProcedureAction. - * - * @param connection - * JDBC connection + * Applies the translated bindings to the CallableStatement: registers OUT parameters and binds IN parameters. + * A stored procedure parameter can be both IN and OUT at the same time. + * + * @since 5.0 */ - protected ProcedureTranslator createTranslator(Connection connection) { - ProcedureTranslator translator = new ProcedureTranslator(); - translator.setAdapter(dataNode.getAdapter()); - translator.setQuery(query); - translator.setEntityResolver(dataNode.getEntityResolver()); - translator.setConnection(connection); - translator.setJdbcEventLogger(dataNode.getJdbcEventLogger()); - return translator; + protected void bindParameters(CallableStatement statement, TranslatedProcedure translated) throws Exception { + DbAdapter adapter = dataNode.getAdapter(); + ProcedureParameter[] callParams = translated.callParams(); + ParameterBinding[] bindings = translated.bindings(); + + for (int i = 0; i < callParams.length; i++) { + ProcedureParameter param = callParams[i]; + + if (param.isOutParam()) { + int precision = param.getPrecision(); + if (precision >= 0) { + statement.registerOutParameter(i + 1, param.getType(), precision); + } else { + statement.registerOutParameter(i + 1, param.getType()); + } + } + + if (param.isInParameter()) { + adapter.bindParameter(statement, bindings[i]); + } + } } /** diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/DbAdapterDelegatedProcedureTranslator.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/DbAdapterDelegatedProcedureTranslator.java new file mode 100644 index 000000000..ca32a8603 --- /dev/null +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/DbAdapterDelegatedProcedureTranslator.java @@ -0,0 +1,38 @@ +/***************************************************************** + * 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.procedure; + +import org.apache.cayenne.dba.DbAdapter; +import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.query.ProcedureQuery; + +/** + * A {@link ProcedureTranslator} that resolves the actual translator from {@link DbAdapter#getProcedureTranslator} + * allowing adapters to customize translation. + * + * @since 5.0 + */ +public class DbAdapterDelegatedProcedureTranslator implements ProcedureTranslator { + + @Override + public TranslatedProcedure translate(ProcedureQuery query, DbAdapter adapter, EntityResolver resolver) { + return adapter.getProcedureTranslator(query, resolver).translate(query, adapter, resolver); + } +} diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/DefaultProcedureTranslator.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/DefaultProcedureTranslator.java new file mode 100644 index 000000000..d4ebc13c3 --- /dev/null +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/DefaultProcedureTranslator.java @@ -0,0 +1,129 @@ +/***************************************************************** + * 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.procedure; + +import java.util.Map; + +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.EntityResolver; +import org.apache.cayenne.map.Procedure; +import org.apache.cayenne.map.ProcedureParameter; +import org.apache.cayenne.query.ProcedureQuery; + +/** + * A {@link ProcedureTranslator} returned by the base {@link org.apache.cayenne.dba.JdbcAdapter}. Adapters may subclass + * it to customize the generated call string. + * + * @since 5.0 + */ +public class DefaultProcedureTranslator implements ProcedureTranslator { + + /** + * Helper class to make OUT and VOID parameters logger-friendly. + */ + static class NotInParam { + + protected String type; + + public NotInParam(String type) { + this.type = type; + } + + @Override + public String toString() { + return type; + } + } + + private static final NotInParam OUT_PARAM = new NotInParam("[OUT]"); + + @Override + public TranslatedProcedure translate(ProcedureQuery query, DbAdapter adapter, EntityResolver resolver) { + + Procedure procedure = query.getMetaData(resolver).getProcedure(); + ProcedureParameter[] callParams = procedure.getCallParameters().toArray(new ProcedureParameter[0]); + Map<String, ?> queryValues = query.getParameters(); + + ParameterBinding[] bindings = new ParameterBinding[callParams.length]; + for (int i = 0; i < callParams.length; i++) { + bindings[i] = createBinding(adapter, callParams[i], queryValues, i + 1); + } + + String sql = createSqlString(procedure, callParams.length); + return new TranslatedProcedure(sql, callParams, bindings); + } + + /** + * Builds a {@link ParameterBinding} for a single call parameter. IN (and INOUT) parameters carry the actual value + * and its {@link ExtendedType}; pure OUT parameters carry an "[OUT]" marker value so that logging renders nicely. + */ + protected ParameterBinding createBinding(DbAdapter adapter, ProcedureParameter param, + Map<String, ?> queryValues, int position) { + + // match values with parameters in the correct order, assuming a missing value is NULL + if (param.getDirection() == ProcedureParameter.OUT_PARAMETER) { + return new ParameterBinding(param.getType(), param.getPrecision()).reset(position, OUT_PARAM, null); + } + + Object value = queryValues.get(param.getName()); + ExtendedType extendedType = value != null + ? adapter.getExtendedTypes().getRegisteredType(value.getClass()) + : adapter.getExtendedTypes().getDefaultType(); + + return new ParameterBinding(adapter.preferredBindingType(param.getType()), param.getPrecision()) + .reset(position, value, extendedType); + } + + /** + * Creates an SQL String for the stored procedure call. + */ + protected String createSqlString(Procedure procedure, int callParamsSize) { + + StringBuilder buf = new StringBuilder(); + + int totalParams = callParamsSize; + + // check if procedure returns values + if (procedure.isReturningValue()) { + totalParams--; + buf.append("{? = call "); + } else { + buf.append("{call "); + } + + buf.append(procedure.getFullyQualifiedName()); + + if (totalParams > 0) { + // unroll the loop + buf.append("(?"); + + for (int i = 1; i < totalParams; i++) { + buf.append(", ?"); + } + + buf.append(")"); + } + + buf.append("}"); + return buf.toString(); + } +} diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/ProcedureTranslator.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/ProcedureTranslator.java index b04929dd2..9d7df9fd2 100644 --- a/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/ProcedureTranslator.java +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/ProcedureTranslator.java @@ -19,233 +19,19 @@ package org.apache.cayenne.access.translator.procedure; -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.apache.cayenne.access.translator.ParameterBinding; -import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.dba.DbAdapter; -import org.apache.cayenne.log.JdbcEventLogger; -import org.apache.cayenne.log.NoopJdbcEventLogger; import org.apache.cayenne.map.EntityResolver; -import org.apache.cayenne.map.Procedure; -import org.apache.cayenne.map.ProcedureParameter; import org.apache.cayenne.query.ProcedureQuery; /** - * Stored procedure query translator. + * A stateless service that translates a {@link ProcedureQuery} into an immutable {@link TranslatedProcedure}. */ -public class ProcedureTranslator { - - /** - * Helper class to make OUT and VOID parameters logger-friendly. - */ - static class NotInParam { - - protected String type; - - public NotInParam(String type) { - this.type = type; - } - - @Override - public String toString() { - return type; - } - } - - private static NotInParam OUT_PARAM = new NotInParam("[OUT]"); - - protected ProcedureQuery query; - protected Connection connection; - protected DbAdapter adapter; - protected EntityResolver entityResolver; - protected List<ProcedureParameter> callParams; - protected List<Object> values; - protected JdbcEventLogger logger; - - public ProcedureTranslator() { - this.logger = NoopJdbcEventLogger.getInstance(); - } - - public void setQuery(ProcedureQuery query) { - this.query = query; - } - - public void setConnection(Connection connection) { - this.connection = connection; - } - - public void setAdapter(DbAdapter adapter) { - this.adapter = adapter; - } - - /** - * @since 3.1 - */ - public void setJdbcEventLogger(JdbcEventLogger logger) { - this.logger = logger; - } - - /** - * @since 3.1 - */ - public JdbcEventLogger getJdbcEventLogger() { - return logger; - } - - /** - * @since 1.2 - */ - public void setEntityResolver(EntityResolver entityResolver) { - this.entityResolver = entityResolver; - } - - /** - * Creates an SQL String for the stored procedure call. - */ - protected String createSqlString() { - Procedure procedure = getProcedure(); - - StringBuilder buf = new StringBuilder(); - - int totalParams = callParams.size(); - - // check if procedure returns values - if (procedure.isReturningValue()) { - totalParams--; - buf.append("{? = call "); - } else { - buf.append("{call "); - } - - buf.append(procedure.getFullyQualifiedName()); - - if (totalParams > 0) { - // unroll the loop - buf.append("(?"); - - for (int i = 1; i < totalParams; i++) { - buf.append(", ?"); - } - - buf.append(")"); - } - - buf.append("}"); - return buf.toString(); - } - - /** - * Creates and binds a PreparedStatement to execute query SQL via JDBC. - */ - public PreparedStatement createStatement() throws Exception { - this.callParams = getProcedure().getCallParameters(); - this.values = new ArrayList<>(callParams.size()); - - initValues(); - String sqlStr = createSqlString(); - - if (logger.isLoggable()) { - // need to convert OUT/VOID parameters to loggable strings - ParameterBinding[] parameterBindings = new ParameterBinding[values.size()]; - for (int i=0; i<values.size(); i++) { - ProcedureParameter procedureParameter = callParams.get(i); - Object value = values.get(i); - if(value instanceof NotInParam) { - value = value.toString(); - } - parameterBindings[i] = new ParameterBinding(procedureParameter.getType(), procedureParameter.getPrecision()) - .reset(i + 1, value, null); - } - logger.logQuery(sqlStr, parameterBindings); - } - CallableStatement stmt = connection.prepareCall(sqlStr); - initStatement(stmt); - return stmt; - } - - public Procedure getProcedure() { - return query.getMetaData(entityResolver).getProcedure(); - } - - public ProcedureQuery getProcedureQuery() { - return query; - } - - /** - * Set IN and OUT parameters. - */ - protected void initStatement(CallableStatement stmt) throws Exception { - if (values != null && values.size() > 0) { - List<ProcedureParameter> params = getProcedure().getCallParameters(); - - int len = values.size(); - for (int i = 0; i < len; i++) { - ProcedureParameter param = params.get(i); - - // !Stored procedure parameter can be both in and out - // at the same time - if (param.isOutParam()) { - setOutParam(stmt, param, i + 1); - } - - if (param.isInParameter()) { - setInParam(stmt, param, values.get(i), i + 1); - } - } - } - } - - protected void initValues() { - Map<String, ?> queryValues = getProcedureQuery().getParameters(); - - // match values with parameters in the correct order. - // make an assumption that a missing value is NULL - // Any reason why this is bad? - - for (ProcedureParameter param : callParams) { - - if (param.getDirection() == ProcedureParameter.OUT_PARAMETER) { - values.add(OUT_PARAM); - } else { - values.add(queryValues.get(param.getName())); - } - } - } - - /** - * Sets a single IN parameter of the CallableStatement. - */ - protected void setInParam( - CallableStatement stmt, - ProcedureParameter param, - Object val, - int pos) throws Exception { - ExtendedType extendedType = val != null - ? adapter.getExtendedTypes().getRegisteredType(val.getClass()) - : adapter.getExtendedTypes().getDefaultType(); - - ParameterBinding binding = new ParameterBinding(adapter.preferredBindingType(param.getType()), param.getPrecision()) - .reset(pos, val, extendedType); - adapter.bindParameter(stmt, binding); - } - - /** - * Sets a single OUT parameter of the CallableStatement. - */ - protected void setOutParam(CallableStatement stmt, ProcedureParameter param, int pos) - throws Exception { - - int precision = param.getPrecision(); - if (precision >= 0) { - stmt.registerOutParameter(pos, param.getType(), precision); - } else { - stmt.registerOutParameter(pos, param.getType()); - } - } +public interface ProcedureTranslator { + + /** + * Translates the provided query into an immutable {@link TranslatedProcedure}. + * + * @since 5.0 + */ + TranslatedProcedure translate(ProcedureQuery query, DbAdapter adapter, EntityResolver resolver); } diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/TranslatedProcedure.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/TranslatedProcedure.java new file mode 100644 index 000000000..5810ad97c --- /dev/null +++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/procedure/TranslatedProcedure.java @@ -0,0 +1,31 @@ +/***************************************************************** + * 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.procedure; + +import org.apache.cayenne.access.translator.ParameterBinding; +import org.apache.cayenne.map.ProcedureParameter; + +/** + * An immutable result of translating a {@link org.apache.cayenne.query.ProcedureQuery} to a stored procedure call. + * + * @since 5.0 + */ +public record TranslatedProcedure(String sql, ProcedureParameter[] callParams, ParameterBinding[] bindings) { +} 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 562ba503c..d71c9d892 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 @@ -42,6 +42,8 @@ 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.procedure.DbAdapterDelegatedProcedureTranslator; +import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; import org.apache.cayenne.access.translator.select.DbAdapterDelegatedSelectTranslator; import org.apache.cayenne.access.translator.select.SelectTranslator; import org.apache.cayenne.access.types.*; @@ -467,6 +469,7 @@ public class CoreModule implements Module { 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); + binder.bind(ProcedureTranslator.class).to(DbAdapterDelegatedProcedureTranslator.class); // a default ObjectMapRetainStrategy used to create objects map for // ObjectStore 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 fa818cd2e..23349873d 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 @@ -23,6 +23,7 @@ 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.BatchTranslator; +import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; import org.apache.cayenne.access.translator.select.SelectTranslator; import org.apache.cayenne.query.DeleteBatchQuery; import org.apache.cayenne.query.InsertBatchQuery; @@ -60,6 +61,9 @@ public class DefaultDataNodeFactory implements DataNodeFactory { @Inject protected SelectTranslator selectTranslator; + @Inject + protected ProcedureTranslator procedureTranslator; + @Inject protected DbAdapterFactory adapterFactory; @@ -83,6 +87,7 @@ public class DefaultDataNodeFactory implements DataNodeFactory { dataNode.setUpdateBatchTranslator(updateBatchTranslator); dataNode.setDeleteBatchTranslator(deleteBatchTranslator); dataNode.setSelectTranslator(selectTranslator); + dataNode.setProcedureTranslator(procedureTranslator); dataNode.setSqlTemplateTranslator(sqlTemplateTranslator); DataSource dataSource = dataSourceFactory.getDataSource(nodeDescriptor); diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java b/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java index 8c2c7980b..255512782 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java @@ -24,6 +24,7 @@ import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor; import org.apache.cayenne.access.translator.ParameterBinding; import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory; +import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; import org.apache.cayenne.access.translator.select.SelectTranslator; import org.apache.cayenne.access.types.ExtendedTypeMap; import org.apache.cayenne.di.Provider; @@ -32,6 +33,7 @@ import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.map.DbRelationship; import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.query.ProcedureQuery; import org.apache.cayenne.query.Select; import org.apache.cayenne.query.Query; import org.apache.cayenne.query.SQLAction; @@ -104,6 +106,14 @@ public class AutoAdapter implements DbAdapter { return getAdapter().getSelectTranslator(query, entityResolver); } + /** + * @since 5.0 + */ + @Override + public ProcedureTranslator getProcedureTranslator(ProcedureQuery query, EntityResolver entityResolver) { + return getAdapter().getProcedureTranslator(query, entityResolver); + } + @Override public String getBatchTerminator() { return getAdapter().getBatchTerminator(); diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java b/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java index 7d3e399de..ac54919fa 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/DbAdapter.java @@ -22,12 +22,14 @@ import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor; import org.apache.cayenne.access.translator.ParameterBinding; import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory; +import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; import org.apache.cayenne.access.translator.select.SelectTranslator; import org.apache.cayenne.access.types.ExtendedTypeMap; import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.map.DbRelationship; import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.query.ProcedureQuery; import org.apache.cayenne.query.Select; import org.apache.cayenne.query.Query; import org.apache.cayenne.query.SQLAction; @@ -60,6 +62,14 @@ public interface DbAdapter { */ SelectTranslator getSelectTranslator(Select<?> query, EntityResolver entityResolver); + /** + * Returns the {@link ProcedureTranslator} for the given query. The default implementation ignores the + * query, but the parameter is retained so adapters can pick a translator based on it. + * + * @since 5.0 + */ + ProcedureTranslator getProcedureTranslator(ProcedureQuery query, EntityResolver entityResolver); + /** * @since 4.2 * @return {@link SQLTreeProcessor} that can adjust SQL tree to specific database flavour diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java b/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java index ee15761da..f23027b16 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java @@ -25,6 +25,8 @@ import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor; import org.apache.cayenne.access.translator.ParameterBinding; import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory; import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory; +import org.apache.cayenne.access.translator.procedure.DefaultProcedureTranslator; +import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; import org.apache.cayenne.access.translator.select.DefaultSelectTranslator; import org.apache.cayenne.access.translator.select.SelectTranslator; import org.apache.cayenne.access.types.ExtendedType; @@ -41,6 +43,7 @@ import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.map.DbJoin; import org.apache.cayenne.map.DbRelationship; import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.query.ProcedureQuery; import org.apache.cayenne.query.Select; import org.apache.cayenne.query.Query; import org.apache.cayenne.query.SQLAction; @@ -526,6 +529,11 @@ public class JdbcAdapter implements DbAdapter { return new DefaultSelectTranslator(); } + @Override + public ProcedureTranslator getProcedureTranslator(ProcedureQuery query, EntityResolver entityResolver) { + return new DefaultProcedureTranslator(); + } + @Override public SQLTreeProcessor getSqlTreeProcessor() { return node -> node; 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 79f304be4..95848f97e 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 @@ -22,7 +22,7 @@ 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.translator.procedure.ProcedureTranslator; +import org.apache.cayenne.access.translator.procedure.TranslatedProcedure; import org.apache.cayenne.query.ProcedureQuery; import java.sql.CallableStatement; @@ -46,10 +46,14 @@ class DB2ProcedureAction extends ProcedureAction { processedResultSets = 0; - ProcedureTranslator transl = createTranslator(connection); + TranslatedProcedure translated = dataNode.getProcedureTranslator() + .translate(query, dataNode.getAdapter(), dataNode.getEntityResolver()); - try (CallableStatement statement = (CallableStatement) transl.createStatement();) { + dataNode.getJdbcEventLogger().logQuery(translated.sql(), translated.bindings()); + + try (CallableStatement statement = connection.prepareCall(translated.sql());) { initStatement(statement); + bindParameters(statement, translated); boolean hasResultSet = statement.execute(); // read out parameters diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLActionBuilder.java b/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLActionBuilder.java index 6a83b0b30..1d82e8c4b 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLActionBuilder.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLActionBuilder.java @@ -19,14 +19,9 @@ package org.apache.cayenne.dba.hsqldb; -import java.sql.Connection; - import org.apache.cayenne.access.DataNode; -import org.apache.cayenne.access.jdbc.ProcedureAction; -import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; import org.apache.cayenne.dba.JdbcActionBuilder; import org.apache.cayenne.query.FluentSelect; -import org.apache.cayenne.query.ProcedureQuery; import org.apache.cayenne.query.SQLAction; class HSQLActionBuilder extends JdbcActionBuilder { @@ -43,21 +38,4 @@ class HSQLActionBuilder extends JdbcActionBuilder { return new HSQLSelectAction(query, dataNode); } - @Override - public SQLAction procedureAction(ProcedureQuery query) { - return new ProcedureAction(query, dataNode) { - - @Override - protected ProcedureTranslator createTranslator(Connection connection) { - ProcedureTranslator transl = new HSQLDBProcedureTranslator(); - transl.setAdapter(dataNode.getAdapter()); - transl.setQuery(query); - transl.setEntityResolver(dataNode.getEntityResolver()); - transl.setConnection(connection); - transl.setJdbcEventLogger(dataNode.getJdbcEventLogger()); - return transl; - } - }; - } - } diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java b/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java index ad92da895..2ab9b619f 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java @@ -24,6 +24,7 @@ import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor; import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory; import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory; +import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; import org.apache.cayenne.access.types.CharType; import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.access.types.ExtendedTypeFactory; @@ -38,6 +39,8 @@ import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.map.DbJoin; import org.apache.cayenne.map.DbRelationship; +import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.query.ProcedureQuery; import org.apache.cayenne.query.Query; import org.apache.cayenne.query.SQLAction; import org.apache.cayenne.resource.ResourceLocator; @@ -95,6 +98,14 @@ public class HSQLDBAdapter extends JdbcAdapter { return new HSQLTreeProcessor(); } + /** + * @since 5.0 + */ + @Override + public ProcedureTranslator getProcedureTranslator(ProcedureQuery query, EntityResolver entityResolver) { + return new HSQLDBProcedureTranslator(); + } + /** * @since 4.0 */ diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBProcedureTranslator.java b/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBProcedureTranslator.java index a238df78c..68ba31e11 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBProcedureTranslator.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBProcedureTranslator.java @@ -19,26 +19,25 @@ package org.apache.cayenne.dba.hsqldb; -import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; +import org.apache.cayenne.access.translator.procedure.DefaultProcedureTranslator; import org.apache.cayenne.map.Procedure; /** * Works around HSQLDB's pickiness about stored procedure syntax. - * + * * @since 1.2 */ -public class HSQLDBProcedureTranslator extends ProcedureTranslator { +public class HSQLDBProcedureTranslator extends DefaultProcedureTranslator { /** * Creates HSQLDB-compliant SQL to execute a stored procedure. */ @Override - protected String createSqlString() { - Procedure procedure = getProcedure(); + protected String createSqlString(Procedure procedure, int callParamsSize) { StringBuilder buf = new StringBuilder(); - int totalParams = callParams.size(); + int totalParams = callParamsSize; // check if procedure returns values if (procedure.isReturningValue()) { diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java index 907e94e6b..14c537586 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java @@ -22,6 +22,7 @@ package org.apache.cayenne.dba.mysql; import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor; import org.apache.cayenne.access.translator.ParameterBinding; +import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory; import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory; import org.apache.cayenne.access.types.ByteArrayType; @@ -44,6 +45,8 @@ import org.apache.cayenne.dba.TypesMapping; import org.apache.cayenne.di.Inject; import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.query.ProcedureQuery; import org.apache.cayenne.query.Query; import org.apache.cayenne.query.SQLAction; import org.apache.cayenne.resource.ResourceLocator; @@ -122,6 +125,14 @@ public class MySQLAdapter extends JdbcAdapter { return query.createSQLAction(new MySQLActionBuilder(node)); } + /** + * @since 5.0 + */ + @Override + public ProcedureTranslator getProcedureTranslator(ProcedureQuery query, EntityResolver entityResolver) { + return new MySQLProcedureTranslator(); + } + /** * @since 3.0 */ 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 694d3728b..10982a2f5 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 @@ -22,7 +22,7 @@ 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.translator.procedure.ProcedureTranslator; +import org.apache.cayenne.access.translator.procedure.TranslatedProcedure; import org.apache.cayenne.query.ProcedureQuery; import java.sql.CallableStatement; @@ -45,9 +45,13 @@ class MySQLProcedureAction extends ProcedureAction { processedResultSets = 0; - ProcedureTranslator transl = createTranslator(connection); + TranslatedProcedure translated = dataNode.getProcedureTranslator() + .translate(query, dataNode.getAdapter(), dataNode.getEntityResolver()); - try (CallableStatement statement = (CallableStatement) transl.createStatement();) { + dataNode.getJdbcEventLogger().logQuery(translated.sql(), translated.bindings()); + + try (CallableStatement statement = connection.prepareCall(translated.sql());) { + bindParameters(statement, translated); // this is one difference with super - we need to read the first // result set @@ -102,36 +106,4 @@ class MySQLProcedureAction extends ProcedureAction { return true; } - - /** - * Creates a translator that adds parenthesis to no-param queries. - */ - // see CAY-750 for the problem description - @Override - protected ProcedureTranslator createTranslator(Connection connection) { - ProcedureTranslator translator = new MySQLProcedureTranslator(); - translator.setAdapter(dataNode.getAdapter()); - translator.setQuery(query); - translator.setEntityResolver(dataNode.getEntityResolver()); - translator.setConnection(connection); - translator.setJdbcEventLogger(dataNode.getJdbcEventLogger()); - return translator; - } - - // same as postgres translator - should we make this the default? - static class MySQLProcedureTranslator extends ProcedureTranslator { - - @Override - protected String createSqlString() { - - String sql = super.createSqlString(); - - // add empty parameter parenthesis - if (sql.endsWith("}") && !sql.endsWith(")}")) { - sql = sql.substring(0, sql.length() - 1) + "()}"; - } - - return sql; - } - } } diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLProcedureTranslator.java b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLProcedureTranslator.java new file mode 100644 index 000000000..6927e36f1 --- /dev/null +++ b/cayenne/src/main/java/org/apache/cayenne/dba/mysql/MySQLProcedureTranslator.java @@ -0,0 +1,45 @@ +/***************************************************************** + * 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.dba.mysql; + +import org.apache.cayenne.access.translator.procedure.DefaultProcedureTranslator; +import org.apache.cayenne.map.Procedure; + +/** + * A translator that adds parenthesis to no-param queries. + * + * @since 5.0 + */ +// see CAY-750 for the problem description +public class MySQLProcedureTranslator extends DefaultProcedureTranslator { + + @Override + protected String createSqlString(Procedure procedure, int callParamsSize) { + + String sql = super.createSqlString(procedure, callParamsSize); + + // add empty parameter parenthesis + if (sql.endsWith("}") && !sql.endsWith(")}")) { + sql = sql.substring(0, sql.length() - 1) + "()}"; + } + + return sql; + } +} diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java index a18f5d68b..7420887f8 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java @@ -23,6 +23,7 @@ import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.access.DataNode; import org.apache.cayenne.access.sqlbuilder.sqltree.SQLTreeProcessor; import org.apache.cayenne.access.translator.ParameterBinding; +import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; import org.apache.cayenne.access.types.CharType; import org.apache.cayenne.access.types.ExtendedType; import org.apache.cayenne.access.types.ExtendedTypeFactory; @@ -37,6 +38,8 @@ import org.apache.cayenne.dba.TypesMapping; import org.apache.cayenne.di.Inject; import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.query.ProcedureQuery; import org.apache.cayenne.query.Query; import org.apache.cayenne.query.SQLAction; import org.apache.cayenne.resource.ResourceLocator; @@ -95,6 +98,14 @@ public class PostgresAdapter extends JdbcAdapter { return query.createSQLAction(new PostgresActionBuilder(node)); } + /** + * @since 5.0 + */ + @Override + public ProcedureTranslator getProcedureTranslator(ProcedureQuery query, EntityResolver entityResolver) { + return new PostgresProcedureTranslator(); + } + /** * Installs appropriate ExtendedTypes as converters for passing values * between JDBC and Java layers. diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresProcedureAction.java b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresProcedureAction.java index a2a4d8ce1..76dd10352 100644 --- a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresProcedureAction.java +++ b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresProcedureAction.java @@ -19,10 +19,7 @@ package org.apache.cayenne.dba.postgres; -import java.sql.Connection; - import org.apache.cayenne.access.DataNode; -import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; import org.apache.cayenne.dba.sqlserver.SQLServerProcedureAction; import org.apache.cayenne.query.ProcedureQuery; @@ -30,8 +27,9 @@ import org.apache.cayenne.query.ProcedureQuery; * Current implementation simply relies on SQLServerProcedureAction superclass behavior. * Namely that CallableStatement.execute() rewinds result set pointer so * CallableStatement.getMoreResults() shouldn't be invoked until the first result set is - * processed. - * + * processed. The no-param parenthesis quirk (CAY-750) is handled by + * {@link PostgresProcedureTranslator}. + * * @since 1.2 */ class PostgresProcedureAction extends SQLServerProcedureAction { @@ -39,35 +37,4 @@ class PostgresProcedureAction extends SQLServerProcedureAction { PostgresProcedureAction(ProcedureQuery query, DataNode dataNode) { super(query, dataNode); } - - /** - * Creates a translator that adds parenthesis to no-param queries. - */ - // see CAY-750 for the problem description - @Override - protected ProcedureTranslator createTranslator(Connection connection) { - ProcedureTranslator translator = new PostgresProcedureTranslator(); - translator.setAdapter(dataNode.getAdapter()); - translator.setQuery(query); - translator.setEntityResolver(dataNode.getEntityResolver()); - translator.setConnection(connection); - translator.setJdbcEventLogger(dataNode.getJdbcEventLogger()); - return translator; - } - - static class PostgresProcedureTranslator extends ProcedureTranslator { - - @Override - protected String createSqlString() { - - String sql = super.createSqlString(); - - // add empty parameter parenthesis - if (sql.endsWith("}") && !sql.endsWith(")}")) { - sql = sql.substring(0, sql.length() - 1) + "()}"; - } - - return sql; - } - } } diff --git a/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresProcedureTranslator.java b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresProcedureTranslator.java new file mode 100644 index 000000000..eb807e18b --- /dev/null +++ b/cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresProcedureTranslator.java @@ -0,0 +1,45 @@ +/***************************************************************** + * 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.dba.postgres; + +import org.apache.cayenne.access.translator.procedure.DefaultProcedureTranslator; +import org.apache.cayenne.map.Procedure; + +/** + * A translator that adds parenthesis to no-param queries. + * + * @since 5.0 + */ +// see CAY-750 for the problem description +public class PostgresProcedureTranslator extends DefaultProcedureTranslator { + + @Override + protected String createSqlString(Procedure procedure, int callParamsSize) { + + String sql = super.createSqlString(procedure, callParamsSize); + + // add empty parameter parenthesis + if (sql.endsWith("}") && !sql.endsWith(")}")) { + sql = sql.substring(0, sql.length() - 1) + "()}"; + } + + return sql; + } +} 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 0ea3e0cd4..705df6a3d 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 @@ -25,7 +25,7 @@ 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.translator.procedure.ProcedureTranslator; +import org.apache.cayenne.access.translator.procedure.TranslatedProcedure; import org.apache.cayenne.query.ProcedureQuery; import org.apache.cayenne.query.Query; @@ -57,9 +57,14 @@ public class SQLServerProcedureAction extends ProcedureAction { @Override public void performAction(Connection connection, OperationObserver observer) throws Exception { - ProcedureTranslator transl = createTranslator(connection); + TranslatedProcedure translated = dataNode.getProcedureTranslator() + .translate(query, dataNode.getAdapter(), dataNode.getEntityResolver()); + + dataNode.getJdbcEventLogger().logQuery(translated.sql(), translated.bindings()); + + try (CallableStatement statement = connection.prepareCall(translated.sql());) { + bindParameters(statement, translated); - try (CallableStatement statement = (CallableStatement) transl.createStatement();) { // stored procedure may contain a mixture of update counts and // result sets, // and out parameters. Read out parameters first, then 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 cf8ea1cd6..1dfd33bd3 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 @@ -36,6 +36,8 @@ 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.procedure.DbAdapterDelegatedProcedureTranslator; +import org.apache.cayenne.access.translator.procedure.ProcedureTranslator; import org.apache.cayenne.access.translator.select.DbAdapterDelegatedSelectTranslator; import org.apache.cayenne.access.translator.select.SelectTranslator; import org.apache.cayenne.access.types.DefaultValueObjectTypeRegistry; @@ -228,6 +230,7 @@ public class DataDomainProviderTest { 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(ProcedureTranslator.class).to(DbAdapterDelegatedProcedureTranslator.class); b.bind(DataSourceFactory.class).toInstance(new MockDataSourceFactory()); b.bind(JdbcEventLogger.class).to(Slf4jJdbcEventLogger.class);
