Skip to content

Commit ce0b521

Browse files
authored
Implement Oracle generated keys retrieval (#1083)
* Initial version of OracleGeneratedKeysTest Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * Refactor commands There were many duplicated fragments in commands. Besides, prepared statements were not always prepared on a worker thread and SQL exception were sometimes unhandled. This commit is a first attempt to minimize code duplication and blocking code / failure management issues. Also, it fixes generated keys retrieval (see #1074). Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * OracleConnectOptions are not needed for query commands Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * OraclePreparedStatement is already unwrapped Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * Make QueryCommand#doExecute abstract Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * Simplified OracleCursorQueryCommand implementation Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * Removed ResultDecoder It was not providing much and this will avoid creating an extra object for every query Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * Verify all generated keys retrieval methods Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * Move Oracle property kinds to OracleClient interface The purpose is to have an API similar to the API of the Reactive MySQL client. Signed-off-by: Thomas Segismont <tsegismont@gmail.com> * Document Oracle generated key retrieval Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
1 parent bec6046 commit ce0b521

File tree

14 files changed

+662
-583
lines changed

14 files changed

+662
-583
lines changed

vertx-oracle-client/src/main/asciidoc/index.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,27 @@ You can configure the client to retry when a connection fails to be established.
113113

114114
include::queries.adoc[leveloffset=1]
115115

116+
== Retrieving generated key values
117+
118+
When executing `INSERT` queries, you can retrieve the generated key values.
119+
120+
The values are returned as a {@link io.vertx.sqlclient.Row} instance.
121+
This instance is accessible by invoking {@link io.vertx.sqlclient.SqlResult#property SqlResult.property(kind)} using the {@link io.vertx.oracleclient.OracleClient#GENERATED_KEYS} property kind.
122+
123+
The key values can be retrieved by column name:
124+
125+
[source,$lang]
126+
----
127+
{@link examples.OracleClientExamples#retrieveGeneratedKeyByName}
128+
----
129+
130+
Or, they can be retrieved by column index:
131+
132+
[source,$lang]
133+
----
134+
{@link examples.OracleClientExamples#retrieveGeneratedKeyByIndex}
135+
----
136+
116137
include::connections.adoc[]
117138

118139
include::transactions.adoc[]

vertx-oracle-client/src/main/java/examples/OracleClientExamples.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313

1414
import io.vertx.core.Vertx;
1515
import io.vertx.core.buffer.Buffer;
16+
import io.vertx.core.json.JsonArray;
1617
import io.vertx.core.json.JsonObject;
1718
import io.vertx.docgen.Source;
19+
import io.vertx.oracleclient.OracleClient;
1820
import io.vertx.oracleclient.OracleConnectOptions;
1921
import io.vertx.oracleclient.OraclePool;
22+
import io.vertx.oracleclient.OraclePrepareOptions;
2023
import io.vertx.sqlclient.*;
2124
import io.vertx.sqlclient.data.Numeric;
2225

@@ -392,4 +395,38 @@ public void storedProcedureExample(SqlClient client) {
392395
}
393396
});
394397
}
398+
399+
public void retrieveGeneratedKeyByName(SqlClient client) {
400+
String sql = "INSERT INTO EntityWithIdentity (name, position) VALUES (?, ?)";
401+
402+
// Retrieve generated key column value by name
403+
OraclePrepareOptions options = new OraclePrepareOptions()
404+
.setAutoGeneratedKeysIndexes(new JsonArray().add("ID"));
405+
406+
client.preparedQuery(sql, options).execute(Tuple.of("john", 3), ar -> {
407+
if (ar.succeeded()) {
408+
RowSet<Row> result = ar.result();
409+
410+
Row generated = result.property(OracleClient.GENERATED_KEYS);
411+
Long id = generated.getLong("ID");
412+
}
413+
});
414+
}
415+
416+
public void retrieveGeneratedKeyByIndex(SqlClient client) {
417+
String sql = "INSERT INTO EntityWithIdentity (name, position) VALUES (?, ?)";
418+
419+
// Retrieve generated key column value by index
420+
OraclePrepareOptions options = new OraclePrepareOptions()
421+
.setAutoGeneratedKeysIndexes(new JsonArray().add("1"));
422+
423+
client.preparedQuery(sql, options).execute(Tuple.of("john", 3), ar -> {
424+
if (ar.succeeded()) {
425+
RowSet<Row> result = ar.result();
426+
427+
Row generated = result.property(OracleClient.GENERATED_KEYS);
428+
Long id = generated.getLong("ID");
429+
}
430+
});
431+
}
395432
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (c) 2011-2021 Contributors to the Eclipse Foundation
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*/
11+
12+
package io.vertx.oracleclient;
13+
14+
import io.vertx.codegen.annotations.VertxGen;
15+
import io.vertx.sqlclient.PropertyKind;
16+
import io.vertx.sqlclient.Row;
17+
18+
/**
19+
* An interface to define Oracle specific constants or behaviors.
20+
*/
21+
@VertxGen
22+
public interface OracleClient {
23+
24+
/**
25+
* The property to be used to retrieve the generated keys
26+
*/
27+
PropertyKind<Row> GENERATED_KEYS = PropertyKind.create("generated-keys", Row.class);
28+
29+
/**
30+
* The property to be used to retrieve the output of the callable statement
31+
*/
32+
PropertyKind<Boolean> OUTPUT = PropertyKind.create("callable-statement-output", Boolean.class);
33+
}

vertx-oracle-client/src/main/java/io/vertx/oracleclient/OraclePool.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
import io.vertx.oracleclient.impl.OraclePoolImpl;
1919
import io.vertx.sqlclient.Pool;
2020
import io.vertx.sqlclient.PoolOptions;
21-
import io.vertx.sqlclient.PropertyKind;
22-
import io.vertx.sqlclient.Row;
2321
import io.vertx.sqlclient.impl.tracing.QueryTracer;
2422

2523
/**
@@ -28,16 +26,6 @@
2826
@VertxGen
2927
public interface OraclePool extends Pool {
3028

31-
/**
32-
* The property to be used to retrieve the generated keys
33-
*/
34-
PropertyKind<Row> GENERATED_KEYS = PropertyKind.create("generated-keys", Row.class);
35-
36-
/**
37-
* The property to be used to retrieve the output of the callable statement
38-
*/
39-
PropertyKind<Boolean> OUTPUT = PropertyKind.create("callable-statement-output", Boolean.class);
40-
4129
static OraclePool pool(OracleConnectOptions connectOptions, PoolOptions poolOptions) {
4230
if (Vertx.currentContext() != null) {
4331
throw new IllegalStateException(

vertx-oracle-client/src/main/java/io/vertx/oracleclient/impl/CommandHandler.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private Future<Integer> handle(PingCommand ping) {
104104

105105
@SuppressWarnings({ "unchecked", "rawtypes" })
106106
private <R> Future<Boolean> handle(io.vertx.sqlclient.impl.command.SimpleQueryCommand command) {
107-
QueryCommand<?, R> action = new SimpleQueryCommand<>(options, command.sql(), command.collector());
107+
QueryCommand<?, R> action = new SimpleQueryCommand<>(command.sql(), command.collector());
108108
return handle(action, command.resultHandler());
109109
}
110110

@@ -124,15 +124,15 @@ private <R> Future<Boolean> handle(QueryCommand<?, R> action, QueryResultHandler
124124

125125
private <R> Future<Boolean> handle(ExtendedQueryCommand<R> command) {
126126
if (command.cursorId() != null) {
127-
QueryCommand<?, R> cmd = new OracleCursorQueryCommand<>(options, command, command.params());
127+
QueryCommand<?, R> cmd = new OracleCursorQueryCommand<>(command, command.params());
128128
return cmd.execute(connection, context)
129129
.map(false);
130130
}
131131

132132
QueryCommand<?, R> action =
133133
command.isBatch() ?
134-
new OraclePreparedBatch<>(options, command, command.collector(), command.paramsList())
135-
: new OraclePreparedQuery<>(options, command, command.collector(), command.params());
134+
new OraclePreparedBatch<>(command, command.collector(), command.paramsList())
135+
: new OraclePreparedQuery<>(command, command.collector(), command.params());
136136

137137
return handle(action, command.resultHandler());
138138
}

vertx-oracle-client/src/main/java/io/vertx/oracleclient/impl/Helper.java

Lines changed: 107 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
import io.vertx.core.Promise;
1515
import io.vertx.core.VertxException;
1616
import io.vertx.core.impl.ContextInternal;
17+
import io.vertx.sqlclient.Tuple;
1718
import oracle.jdbc.OraclePreparedStatement;
1819

20+
import java.math.BigDecimal;
1921
import java.sql.*;
22+
import java.time.ZoneOffset;
2023
import java.util.ArrayList;
2124
import java.util.List;
2225
import java.util.concurrent.CompletionStage;
@@ -27,20 +30,11 @@
2730

2831
public class Helper {
2932

30-
public static <T> Future<T> completeOrFail(ThrowingSupplier<T> supplier) {
31-
try {
32-
return Future.succeededFuture(supplier.getOrThrow());
33-
} catch (SQLException throwables) {
34-
return Future.failedFuture(throwables);
35-
}
36-
}
37-
38-
public static void closeQuietly(Statement ps) {
39-
if (ps != null) {
33+
public static void closeQuietly(AutoCloseable autoCloseable) {
34+
if (autoCloseable != null) {
4035
try {
41-
ps.close();
42-
} catch (SQLException throwables) {
43-
// ignore me.
36+
autoCloseable.close();
37+
} catch (Exception ignore) {
4438
}
4539
}
4640
}
@@ -57,22 +51,6 @@ public static <T> Future<T> contextualize(CompletionStage<T> stage, ContextInter
5751
return future.future();
5852
}
5953

60-
/**
61-
* Returns a {@code PreparedStatement}
62-
* {@linkplain Wrapper#unwrap(Class) unwrapped} as an
63-
* {@code OraclePreparedStatement}, or throws an {@code R2dbcException} if it
64-
* does not wrap or implement the Oracle JDBC interface.
65-
*
66-
* @param preparedStatement A JDBC prepared statement
67-
* @return An Oracle JDBC prepared statement
68-
* @throws VertxException If an Oracle JDBC prepared statement is not wrapped.
69-
*/
70-
public static OraclePreparedStatement unwrapOraclePreparedStatement(
71-
PreparedStatement preparedStatement) {
72-
return getOrHandleSQLException(() ->
73-
preparedStatement.unwrap(OraclePreparedStatement.class));
74-
}
75-
7654
/**
7755
* Returns the specified {@code supplier}'s output, or throws a
7856
* {@link VertxException} if the function throws a {@link SQLException}. This
@@ -174,6 +152,106 @@ public void onComplete() {
174152
return promise.future();
175153
}
176154

155+
public static Object convertSqlValue(Object value) throws SQLException {
156+
if (value == null) {
157+
return null;
158+
}
159+
160+
// valid json types are just returned as is
161+
if (value instanceof Boolean || value instanceof String || value instanceof byte[]) {
162+
return value;
163+
}
164+
165+
// numeric values
166+
if (value instanceof Number) {
167+
if (value instanceof BigDecimal) {
168+
BigDecimal d = (BigDecimal) value;
169+
if (d.scale() == 0) {
170+
return ((BigDecimal) value).toBigInteger();
171+
} else {
172+
// we might loose precision here
173+
return ((BigDecimal) value).doubleValue();
174+
}
175+
}
176+
177+
return value;
178+
}
179+
180+
// JDBC temporal values
181+
182+
if (value instanceof Time) {
183+
return ((Time) value).toLocalTime();
184+
}
185+
186+
if (value instanceof Date) {
187+
return ((Date) value).toLocalDate();
188+
}
189+
190+
if (value instanceof Timestamp) {
191+
return ((Timestamp) value).toInstant().atOffset(ZoneOffset.UTC);
192+
}
193+
194+
// large objects
195+
if (value instanceof Clob) {
196+
Clob c = (Clob) value;
197+
try {
198+
// result might be truncated due to downcasting to int
199+
return c.getSubString(1, (int) c.length());
200+
} finally {
201+
try {
202+
c.free();
203+
} catch (AbstractMethodError | SQLFeatureNotSupportedException e) {
204+
// ignore since it is an optional feature since 1.6 and non existing before 1.6
205+
}
206+
}
207+
}
208+
209+
if (value instanceof Blob) {
210+
Blob b = (Blob) value;
211+
try {
212+
// result might be truncated due to downcasting to int
213+
return b.getBytes(1, (int) b.length());
214+
} finally {
215+
try {
216+
b.free();
217+
} catch (AbstractMethodError | SQLFeatureNotSupportedException e) {
218+
// ignore since it is an optional feature since 1.6 and non existing before 1.6
219+
}
220+
}
221+
}
222+
223+
// arrays
224+
if (value instanceof Array) {
225+
Array a = (Array) value;
226+
try {
227+
Object arr = a.getArray();
228+
if (arr != null) {
229+
int len = java.lang.reflect.Array.getLength(arr);
230+
Object[] castedArray = new Object[len];
231+
for (int i = 0; i < len; i++) {
232+
castedArray[i] = convertSqlValue(java.lang.reflect.Array.get(arr, i));
233+
}
234+
return castedArray;
235+
}
236+
} finally {
237+
a.free();
238+
}
239+
}
240+
241+
// RowId
242+
if (value instanceof RowId) {
243+
return ((RowId) value).getBytes();
244+
}
245+
246+
// Struct
247+
if (value instanceof Struct) {
248+
return Tuple.of(((Struct) value).getAttributes());
249+
}
250+
251+
// fallback to String
252+
return value.toString();
253+
}
254+
177255
/**
178256
* <p>
179257
* Function type that returns a value or throws a {@link SQLException}. This

0 commit comments

Comments
 (0)