Skip to content

Commit 58978fa

Browse files
committed
Translators as stateless services producing immutable TranslatedXyz records
* procedure translators
1 parent 520d466 commit 58978fa

23 files changed

Lines changed: 449 additions & 343 deletions

cayenne/src/main/java/org/apache/cayenne/access/DataNode.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.apache.cayenne.access.jdbc.reader.RowReader;
2727
import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
2828
import org.apache.cayenne.access.translator.batch.BatchTranslator;
29+
import org.apache.cayenne.access.translator.procedure.ProcedureTranslator;
2930
import org.apache.cayenne.access.translator.select.SelectTranslator;
3031
import org.apache.cayenne.access.translator.sqltemplate.SQLTemplateTranslator;
3132
import org.apache.cayenne.dba.DbAdapter;
@@ -75,6 +76,7 @@ public class DataNode {
7576
private BatchTranslator<UpdateBatchQuery> updateBatchTranslator;
7677
private BatchTranslator<DeleteBatchQuery> deleteBatchTranslator;
7778
private SelectTranslator selectTranslator;
79+
private ProcedureTranslator procedureTranslator;
7880
private SQLTemplateTranslator sqlTemplateTranslator;
7981

8082
TransactionDataSource readThroughDataSource;
@@ -425,6 +427,20 @@ public void setSelectTranslator(SelectTranslator selectTranslator) {
425427
this.selectTranslator = selectTranslator;
426428
}
427429

430+
/**
431+
* @since 5.0
432+
*/
433+
public ProcedureTranslator getProcedureTranslator() {
434+
return procedureTranslator;
435+
}
436+
437+
/**
438+
* @since 5.0
439+
*/
440+
public void setProcedureTranslator(ProcedureTranslator procedureTranslator) {
441+
this.procedureTranslator = procedureTranslator;
442+
}
443+
428444
// a read-through DataSource that ensures returning the same connection
429445
// within
430446
// transaction.

cayenne/src/main/java/org/apache/cayenne/access/jdbc/ProcedureAction.java

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import org.apache.cayenne.DataRow;
2424
import org.apache.cayenne.access.DataNode;
2525
import org.apache.cayenne.access.OperationObserver;
26-
import org.apache.cayenne.access.translator.procedure.ProcedureTranslator;
26+
import org.apache.cayenne.access.translator.ParameterBinding;
27+
import org.apache.cayenne.access.translator.procedure.TranslatedProcedure;
2728
import org.apache.cayenne.access.types.ExtendedType;
29+
import org.apache.cayenne.dba.DbAdapter;
2830
import org.apache.cayenne.map.Procedure;
2931
import org.apache.cayenne.map.ProcedureParameter;
3032
import org.apache.cayenne.query.ProcedureQuery;
@@ -66,10 +68,14 @@ public void performAction(Connection connection, OperationObserver observer) thr
6668

6769
processedResultSets = 0;
6870

69-
ProcedureTranslator transl = createTranslator(connection);
71+
TranslatedProcedure translated = dataNode.getProcedureTranslator()
72+
.translate(query, dataNode.getAdapter(), dataNode.getEntityResolver());
7073

71-
try (CallableStatement statement = (CallableStatement) transl.createStatement();) {
74+
dataNode.getJdbcEventLogger().logQuery(translated.sql(), translated.bindings());
75+
76+
try (CallableStatement statement = connection.prepareCall(translated.sql());) {
7277
initStatement(statement);
78+
bindParameters(statement, translated);
7379

7480
// stored procedure may contain a mixture of update counts and
7581
// result sets,
@@ -114,19 +120,32 @@ public void performAction(Connection connection, OperationObserver observer) thr
114120
}
115121

116122
/**
117-
* Returns the ProcedureTranslator to use for this ProcedureAction.
118-
*
119-
* @param connection
120-
* JDBC connection
123+
* Applies the translated bindings to the CallableStatement: registers OUT parameters and binds IN parameters.
124+
* A stored procedure parameter can be both IN and OUT at the same time.
125+
*
126+
* @since 5.0
121127
*/
122-
protected ProcedureTranslator createTranslator(Connection connection) {
123-
ProcedureTranslator translator = new ProcedureTranslator();
124-
translator.setAdapter(dataNode.getAdapter());
125-
translator.setQuery(query);
126-
translator.setEntityResolver(dataNode.getEntityResolver());
127-
translator.setConnection(connection);
128-
translator.setJdbcEventLogger(dataNode.getJdbcEventLogger());
129-
return translator;
128+
protected void bindParameters(CallableStatement statement, TranslatedProcedure translated) throws Exception {
129+
DbAdapter adapter = dataNode.getAdapter();
130+
ProcedureParameter[] callParams = translated.callParams();
131+
ParameterBinding[] bindings = translated.bindings();
132+
133+
for (int i = 0; i < callParams.length; i++) {
134+
ProcedureParameter param = callParams[i];
135+
136+
if (param.isOutParam()) {
137+
int precision = param.getPrecision();
138+
if (precision >= 0) {
139+
statement.registerOutParameter(i + 1, param.getType(), precision);
140+
} else {
141+
statement.registerOutParameter(i + 1, param.getType());
142+
}
143+
}
144+
145+
if (param.isInParameter()) {
146+
adapter.bindParameter(statement, bindings[i]);
147+
}
148+
}
130149
}
131150

132151
/**
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*****************************************************************
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
****************************************************************/
19+
20+
package org.apache.cayenne.access.translator.procedure;
21+
22+
import org.apache.cayenne.dba.DbAdapter;
23+
import org.apache.cayenne.map.EntityResolver;
24+
import org.apache.cayenne.query.ProcedureQuery;
25+
26+
/**
27+
* A {@link ProcedureTranslator} that resolves the actual translator from {@link DbAdapter#getProcedureTranslator}
28+
* allowing adapters to customize translation.
29+
*
30+
* @since 5.0
31+
*/
32+
public class DbAdapterDelegatedProcedureTranslator implements ProcedureTranslator {
33+
34+
@Override
35+
public TranslatedProcedure translate(ProcedureQuery query, DbAdapter adapter, EntityResolver resolver) {
36+
return adapter.getProcedureTranslator(query, resolver).translate(query, adapter, resolver);
37+
}
38+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*****************************************************************
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
****************************************************************/
19+
20+
package org.apache.cayenne.access.translator.procedure;
21+
22+
import java.util.Map;
23+
24+
import org.apache.cayenne.access.translator.ParameterBinding;
25+
import org.apache.cayenne.access.types.ExtendedType;
26+
import org.apache.cayenne.dba.DbAdapter;
27+
import org.apache.cayenne.map.EntityResolver;
28+
import org.apache.cayenne.map.Procedure;
29+
import org.apache.cayenne.map.ProcedureParameter;
30+
import org.apache.cayenne.query.ProcedureQuery;
31+
32+
/**
33+
* A {@link ProcedureTranslator} returned by the base {@link org.apache.cayenne.dba.JdbcAdapter}. Adapters may subclass
34+
* it to customize the generated call string.
35+
*
36+
* @since 5.0
37+
*/
38+
public class DefaultProcedureTranslator implements ProcedureTranslator {
39+
40+
/**
41+
* Helper class to make OUT and VOID parameters logger-friendly.
42+
*/
43+
static class NotInParam {
44+
45+
protected String type;
46+
47+
public NotInParam(String type) {
48+
this.type = type;
49+
}
50+
51+
@Override
52+
public String toString() {
53+
return type;
54+
}
55+
}
56+
57+
private static final NotInParam OUT_PARAM = new NotInParam("[OUT]");
58+
59+
@Override
60+
public TranslatedProcedure translate(ProcedureQuery query, DbAdapter adapter, EntityResolver resolver) {
61+
62+
Procedure procedure = query.getMetaData(resolver).getProcedure();
63+
ProcedureParameter[] callParams = procedure.getCallParameters().toArray(new ProcedureParameter[0]);
64+
Map<String, ?> queryValues = query.getParameters();
65+
66+
ParameterBinding[] bindings = new ParameterBinding[callParams.length];
67+
for (int i = 0; i < callParams.length; i++) {
68+
bindings[i] = createBinding(adapter, callParams[i], queryValues, i + 1);
69+
}
70+
71+
String sql = createSqlString(procedure, callParams.length);
72+
return new TranslatedProcedure(sql, callParams, bindings);
73+
}
74+
75+
/**
76+
* Builds a {@link ParameterBinding} for a single call parameter. IN (and INOUT) parameters carry the actual value
77+
* and its {@link ExtendedType}; pure OUT parameters carry an "[OUT]" marker value so that logging renders nicely.
78+
*/
79+
protected ParameterBinding createBinding(DbAdapter adapter, ProcedureParameter param,
80+
Map<String, ?> queryValues, int position) {
81+
82+
// match values with parameters in the correct order, assuming a missing value is NULL
83+
if (param.getDirection() == ProcedureParameter.OUT_PARAMETER) {
84+
return new ParameterBinding(param.getType(), param.getPrecision()).reset(position, OUT_PARAM, null);
85+
}
86+
87+
Object value = queryValues.get(param.getName());
88+
ExtendedType extendedType = value != null
89+
? adapter.getExtendedTypes().getRegisteredType(value.getClass())
90+
: adapter.getExtendedTypes().getDefaultType();
91+
92+
return new ParameterBinding(adapter.preferredBindingType(param.getType()), param.getPrecision())
93+
.reset(position, value, extendedType);
94+
}
95+
96+
/**
97+
* Creates an SQL String for the stored procedure call.
98+
*/
99+
protected String createSqlString(Procedure procedure, int callParamsSize) {
100+
101+
StringBuilder buf = new StringBuilder();
102+
103+
int totalParams = callParamsSize;
104+
105+
// check if procedure returns values
106+
if (procedure.isReturningValue()) {
107+
totalParams--;
108+
buf.append("{? = call ");
109+
} else {
110+
buf.append("{call ");
111+
}
112+
113+
buf.append(procedure.getFullyQualifiedName());
114+
115+
if (totalParams > 0) {
116+
// unroll the loop
117+
buf.append("(?");
118+
119+
for (int i = 1; i < totalParams; i++) {
120+
buf.append(", ?");
121+
}
122+
123+
buf.append(")");
124+
}
125+
126+
buf.append("}");
127+
return buf.toString();
128+
}
129+
}

0 commit comments

Comments
 (0)