Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 65d2f66

Browse files
authoredMay 30, 2018
Merge pull request #4 from Nordstrom/pr/add-sproc-support
Pr/add sproc support
2 parents d531de3 + 77237f7 commit 65d2f66

File tree

4 files changed

+1133
-64
lines changed

4 files changed

+1133
-64
lines changed
 

‎src/main/java/com/nordstrom/common/jdbc/DatabaseUtils.java

Lines changed: 398 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,76 @@
11
package com.nordstrom.common.jdbc;
22

3+
import java.sql.CallableStatement;
34
import java.sql.Connection;
45
import java.sql.Driver;
56
import java.sql.DriverManager;
67
import java.sql.ResultSet;
78
import java.sql.SQLException;
89
import java.util.Arrays;
910
import java.util.Iterator;
11+
import java.util.Objects;
1012
import java.util.ServiceLoader;
13+
import java.util.regex.Matcher;
14+
import java.util.regex.Pattern;
1115

1216
import com.nordstrom.common.base.UncheckedThrow;
17+
import com.nordstrom.common.jdbc.Param.Mode;
1318

1419
import java.sql.PreparedStatement;
1520

1621
/**
17-
* This utility class provides facilities that enable you to define collections of database queries and
18-
* execute them easily. Query collections are defined as Java enumeration that implement the {@link QueryAPI}
19-
* interface: <ul>
22+
* This utility class provides facilities that enable you to define collections of database queries and stored
23+
* procedures in an easy-to-execute format.
24+
* <p>
25+
* Query collections are defined as Java enumerations that implement the {@link QueryAPI}
26+
* interface:
27+
* <ul>
2028
* <li>{@link QueryAPI#getQueryStr() getQueryStr} - Get the query string for this constant. This is the actual query
2129
* that's sent to the database.</li>
2230
* <li>{@link QueryAPI#getArgNames() getArgNames} - Get the names of the arguments for this query. This provides
2331
* diagnostic information if the incorrect number of arguments is specified by the client.</li>
24-
* <li>{@link QueryAPI#getArgCount() getArgCount} - Get the number of arguments required by this query. This enables
25-
* {@link #executeQuery(Class, QueryAPI, Object[])} to verify that the correct number of arguments has been
26-
* specified by the client.</li>
2732
* <li>{@link QueryAPI#getConnection() getConnection} - Get the connection string associated with this query. This
2833
* eliminates the need for the client to provide this information.</li>
2934
* <li>{@link QueryAPI#getEnum() getEnum} - Get the enumeration to which this query belongs. This enables {@link
3035
* #executeQuery(Class, QueryAPI, Object[])} to retrieve the name of the query's enumerated constant for
3136
* diagnostic messages.</li>
3237
* </ul>
33-
*
34-
* To maximize usability and configurability, we recommend the following implementation strategy for your query
35-
* collections: <ul>
36-
* <li>Define your query collection as an enumeration that implements {@link QueryAPI}.</li>
37-
* <li>Define each query constant with a property name and a name for each argument (if any).</li>
38+
* <p>
39+
* Store procedure collections are defined as Java enumerations that implement the {@link SProcAPI}
40+
* interface:
41+
* <ul>
42+
* <li>{@link SProcAPI#getSignature() getSignature} - Get the signature for this stored procedure object. This defines
43+
* the name of the stored procedure and the modes of its arguments. If the stored procedure accepts varargs, this
44+
* will also be indicated.</li>
45+
* <li>{@link SProcAPI#getArgTypes() getArgTypes} - Get the argument types for this stored procedure object. </li>
46+
* <li>{@link SProcAPI#getConnection() getConnection} - Get the connection string associated with this stored
47+
* procedure. This eliminates the need for the client to provide this information.</li>
48+
* <li>{@link SProcAPI#getEnum() getEnum} - Get the enumeration to which this stored procedure belongs. This enables
49+
* {@link #executeStoredProcedure(Class, SProcAPI, Object[])} to retrieve the name of the stored procedured's
50+
* enumerated constant for diagnostic messages.</li>
51+
* </ul>
52+
* <p>
53+
* To maximize usability and configurability, we recommend the following implementation strategy: <ul>
54+
* <li>Define your collection as an enumeration: <ul>
55+
* <li>Query collections implement {@link QueryAPI}.</li>
56+
* <li>Stored procedure collections implement {@link SProcAPI}.</li>
57+
* </ul></li>
58+
* <li>Define each constant: <ul>
59+
* <li>(query) Specify a property name and a name for each argument (if any).</li>
60+
* <li>(sproc) Declare the signature and the type for each argument (if any).</li>
61+
* </ul></li>
3862
* <li>To assist users of your queries, preface their names with a type indicator (<b>GET</b> or <b>UPDATE</b>).</li>
39-
* <li>Back the query collection with a configuration that implements the <b>{@code Settings API}</b>: <ul>
63+
* <li>Back query collections with configurations that implement the <b>{@code Settings API}</b>: <ul>
4064
* <li>groupId: com.nordstrom.test-automation.tools</li>
4165
* <li>artifactId: settings</li>
4266
* <li>className: com.nordstrom.automation.settings.SettingsCore</li>
43-
* </ul>
44-
* </li>
45-
* <li>To support execution on multiple endpoints, implement {@link QueryAPI#getConnection() getConnection} with
46-
* sub-configurations or other dynamic data sources (e.g. - web service).</li>
67+
* </ul></li>
68+
* <li>To support execution on multiple endpoints, implement {@link QueryAPI#getConnection()} or {@link
69+
* SProcAPI#getConnection()} with sub-configurations or other dynamic data sources (e.g.
70+
* - web service).</li>
4771
* </ul>
4872
* <b>Query Collection Example</b>
49-
* <br><br>
73+
*
5074
* <pre>
5175
* public class OpctConfig extends {@code SettingsCore<OpctConfig.OpctValues>} {
5276
*
@@ -110,11 +134,6 @@
110134
* }
111135
*
112136
* {@code @Override}
113-
* public int getArgCount() {
114-
* return args.length;
115-
* }
116-
*
117-
* {@code @Override}
118137
* public String getConnection() {
119138
* if (rmsQueries.contains(this)) {
120139
* return getRmsConnect();
@@ -166,11 +185,56 @@
166185
* public static OpctConfig getConfig() {
167186
* return OpctValues.config;
168187
* }
188+
*
189+
* public enum SProcValues implements SProcAPI {
190+
* /** args: [ ] *&#47;
191+
* SHOW_SUPPLIERS("SHOW_SUPPLIERS()"),
192+
* /** args: [ coffee_name, supplier_name ] *&#47;
193+
* GET_SUPPLIER_OF_COFFEE("GET_SUPPLIER_OF_COFFEE(&gt;, &lt;)", Types.VARCHAR, Types.VARCHAR),
194+
* /** args: [ coffee_name, max_percent, new_price ] *&#47;
195+
* RAISE_PRICE("RAISE_PRICE(&gt;, &gt;, =)", Types.VARCHAR, Types.REAL, Types.NUMERIC),
196+
* /** args: [ str, val... ] *&#47;
197+
* IN_VARARGS("IN_VARARGS(&lt;, &gt;:)", Types.VARCHAR, Types.INTEGER),
198+
* /** args: [ val, str... ] *&#47;
199+
* OUT_VARARGS("OUT_VARARGS(&gt;, &lt;:)", Types.INTEGER, Types.VARCHAR);
200+
*
201+
* private int[] argTypes;
202+
* private String signature;
203+
*
204+
* SProcValues(String signature, int... argTypes) {
205+
* this.signature = signature;
206+
*
207+
* this.argTypes = argTypes;
208+
* }
209+
*
210+
* {@code @Override}
211+
* public String getSignature() {
212+
* return signature;
213+
* }
214+
*
215+
* {@code @Override}
216+
* public int[] getArgTypes () {
217+
* return argTypes;
218+
* }
219+
*
220+
* {@code @Override}
221+
* public String getConnection() {
222+
return OpctValues.getRmsConnect();
223+
* }
224+
*
225+
* {@code @Override}
226+
* public {@code Enum<SProcValues>} getEnum() {
227+
* return this;
228+
* }
229+
* }
169230
* }
170231
* </pre>
171232
*/
172233
public class DatabaseUtils {
173234

235+
private static Pattern SPROC_PATTERN =
236+
Pattern.compile("([\\p{Alpha}_][\\p{Alpha}\\p{Digit}@$#_]*)(?:\\(([<>=](?:,\\s*[<>=])*)?(:)?\\))?");
237+
174238
private DatabaseUtils() {
175239
throw new AssertionError("DatabaseUtils is a static utility class that cannot be instantiated");
176240
}
@@ -211,7 +275,7 @@ public static int getInt(QueryAPI query, Object... queryArgs) {
211275
*
212276
* @param query query object to execute
213277
* @param queryArgs replacement values for query place-holders
214-
* @return row 1 / column 1 as string; 'null' if no rows were returned
278+
* @return row 1 / column 1 as string; {@code null} if no rows were returned
215279
*/
216280
public static String getString(QueryAPI query, Object... queryArgs) {
217281
return (String) executeQuery(String.class, query, queryArgs);
@@ -230,12 +294,12 @@ public static ResultPackage getResultPackage(QueryAPI query, Object... queryArgs
230294

231295
/**
232296
* Execute the specified query with the supplied arguments, returning a result of the indicated type.
233-
* <br><br>
297+
* <p>
234298
* <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
235-
* <li>'null' - The query is executed as an update operation.</li>
299+
* <li>{@code null} - The query is executed as an update operation.</li>
236300
* <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
237301
* <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
238-
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise 'null'</li>
302+
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
239303
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
240304
*
241305
* @param resultType desired result type (see TYPES above)
@@ -246,7 +310,7 @@ public static ResultPackage getResultPackage(QueryAPI query, Object... queryArgs
246310
* when you're done with it to free up database and JDBC resources that were allocated for it.
247311
*/
248312
private static Object executeQuery(Class<?> resultType, QueryAPI query, Object... queryArgs) {
249-
int expectCount = query.getArgCount();
313+
int expectCount = query.getArgNames().length;
250314
int actualCount = queryArgs.length;
251315

252316
if (actualCount != expectCount) {
@@ -267,51 +331,270 @@ private static Object executeQuery(Class<?> resultType, QueryAPI query, Object..
267331

268332
/**
269333
* Execute the specified query with the supplied arguments, returning a result of the indicated type.
270-
* <br><br>
334+
* <p>
271335
* <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
272-
* <li>'null' - The query is executed as an update operation.</li>
336+
* <li>{@code null} - The query is executed as an update operation.</li>
273337
* <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
274338
* <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
275-
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise 'null'</li>
339+
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
276340
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
277341
*
278342
* @param resultType desired result type (see TYPES above)
279343
* @param connectionStr database connection string
280344
* @param queryStr a SQL statement that may contain one or more '?' IN parameter placeholders
281-
* @param param an array of objects containing the input parameter values
345+
* @param params an array of objects containing the input parameter values
282346
* @return for update operations, the number of rows affected; for query operations, an object of the indicated type<br>
283347
* <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object
284348
* when you're done with it to free up database and JDBC resources that were allocated for it.
285349
*/
286-
public static Object executeQuery(Class<?> resultType, String connectionStr, String queryStr, Object... param) {
287-
Object result = null;
288-
boolean failed = false;
350+
public static Object executeQuery(Class<?> resultType, String connectionStr, String queryStr, Object... params) {
351+
try {
352+
Connection connection = getConnection(connectionStr);
353+
PreparedStatement statement = connection.prepareStatement(queryStr);
354+
355+
for (int i = 0; i < params.length; i++) {
356+
statement.setObject(i + 1, params[i]);
357+
}
358+
359+
return executeStatement(resultType, connection, statement);
360+
} catch (SQLException e) {
361+
throw UncheckedThrow.throwUnchecked(e);
362+
}
363+
}
364+
365+
/**
366+
* Execute the specified stored procedure object with supplied parameters
367+
*
368+
* @param sproc stored procedure object to execute
369+
* @param params an array of objects containing the input parameter values
370+
* @return row 1 / column 1 as integer; -1 if no rows were returned
371+
*/
372+
public static int getInt(SProcAPI sproc, Object... params) {
373+
Integer result = (Integer) executeStoredProcedure(Integer.class, sproc, params);
374+
return (result != null) ? result.intValue() : -1;
375+
}
376+
377+
/**
378+
* Execute the specified stored procedure object with supplied parameters
379+
*
380+
* @param sproc stored procedure object to execute
381+
* @param params an array of objects containing the input parameter values
382+
* @return row 1 / column 1 as string; {@code null} if no rows were returned
383+
*/
384+
public static String getString(SProcAPI sproc, Object... params) {
385+
return (String) executeStoredProcedure(String.class, sproc, params);
386+
}
387+
388+
/**
389+
* Execute the specified stored procedure object with supplied parameters
390+
*
391+
* @param sproc stored procedure object to execute
392+
* @param params an array of objects containing the input parameter values
393+
* @return {@link ResultPackage} object
394+
*/
395+
public static ResultPackage getResultPackage(SProcAPI sproc, Object... params) {
396+
return (ResultPackage) executeStoredProcedure(ResultPackage.class, sproc, params);
397+
}
398+
399+
/**
400+
* Execute the specified stored procedure with the specified arguments, returning a result of the indicated type.
401+
* <p>
402+
* <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
403+
* <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
404+
* <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
405+
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
406+
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
407+
*
408+
* @param resultType desired result type (see TYPES above)
409+
* @param sproc stored procedure object to execute
410+
* @param params an array of objects containing the input parameter values
411+
* @return an object of the indicated type<br>
412+
* <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object
413+
* when you're done with it to free up database and JDBC resources that were allocated for it.
414+
*/
415+
public static Object executeStoredProcedure(Class<?> resultType, SProcAPI sproc, Object... params) {
416+
Objects.requireNonNull(resultType, "[resultType] argument must be non-null");
289417

290-
Connection connection = null;
291-
PreparedStatement statement = null;
292-
ResultSet resultSet = null;
418+
String[] args = {};
419+
String sprocName = null;
420+
boolean hasVarArgs = false;
421+
int[] argTypes = sproc.getArgTypes();
422+
String signature = sproc.getSignature();
423+
Matcher matcher = SPROC_PATTERN.matcher(signature);
424+
425+
String message = null;
426+
if (matcher.matches()) {
427+
sprocName = matcher.group(1);
428+
hasVarArgs = (matcher.group(3) != null);
429+
if (matcher.group(2) != null) {
430+
args = matcher.group(2).split(",\\s");
431+
} else {
432+
if (hasVarArgs) {
433+
message = String.format("VarArgs indicated with no placeholder in signature for %s: %s",
434+
sproc.getEnum().name(), signature);
435+
}
436+
}
437+
} else {
438+
message = String.format("Unsupported stored procedure signature for %s: %s",
439+
sproc.getEnum().name(), signature);
440+
}
441+
442+
if (message != null) {
443+
throw new IllegalArgumentException(message);
444+
}
445+
446+
int argsCount = args.length;
447+
int typesCount = argTypes.length;
448+
int parmsCount = params.length;
449+
450+
int minCount = typesCount;
451+
452+
// if unbalanced args/types
453+
if (argsCount != typesCount) {
454+
message = String.format(
455+
"Signature argument count differs from declared type count for %s%s: "
456+
+ "signature: %d; declared: %d",
457+
sproc.getEnum().name(), Arrays.toString(argTypes), argsCount, typesCount);
458+
} else if (hasVarArgs) {
459+
minCount -= 1;
460+
if (parmsCount < minCount) {
461+
message = String.format(
462+
"Insufficient arguments count for %s%s: minimum: %d; actual: %d",
463+
sproc.getEnum().name(), Arrays.toString(argTypes), minCount, parmsCount);
464+
}
465+
} else if (parmsCount != typesCount) {
466+
if (typesCount == 0) {
467+
message = "No arguments expected for " + sproc.getEnum().name();
468+
} else {
469+
message = String.format(
470+
"Incorrect arguments count for %s%s: expect: %d; actual: %d",
471+
sproc.getEnum().name(), Arrays.toString(argTypes), typesCount, parmsCount);
472+
}
473+
}
474+
475+
if (message != null) {
476+
throw new IllegalArgumentException(message);
477+
}
478+
479+
Param[] parmArray = Param.array(parmsCount);
480+
481+
int i;
482+
483+
// process declared parameters
484+
for (i = 0; i < minCount; i++) {
485+
Mode mode = Mode.fromChar(args[i].charAt(0));
486+
parmArray[i] = Param.create(mode, argTypes[i], params[i]);
487+
}
488+
489+
// handle varargs parameters
490+
for (int j = i; j < parmsCount; j++) {
491+
Mode mode = Mode.fromChar(args[i].charAt(0));
492+
parmArray[j] = Param.create(mode, argTypes[i], params[j]);
493+
}
494+
495+
return executeStoredProcedure(resultType, sproc.getConnection(), sprocName, parmArray);
496+
}
497+
498+
/**
499+
* Execute the specified stored procedure with the supplied arguments, returning a result of the indicated type.
500+
* <p>
501+
* <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
502+
* <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
503+
* <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
504+
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
505+
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
506+
*
507+
* @param resultType desired result type (see TYPES above)
508+
* @param connectionStr database connection string
509+
* @param sprocName name of the stored procedure to be executed
510+
* @param params an array of objects containing the input parameter values
511+
* @return an object of the indicated type<br>
512+
* <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object
513+
* when you're done with it to free up database and JDBC resources that were allocated for it.
514+
*/
515+
public static Object executeStoredProcedure(Class<?> resultType, String connectionStr, String sprocName, Param... params) {
516+
Objects.requireNonNull(resultType, "[resultType] argument must be non-null");
517+
518+
StringBuilder sprocStr = new StringBuilder("{call ").append(sprocName).append("(");
519+
520+
String placeholder = "?";
521+
for (int i = 0; i < params.length; i++) {
522+
sprocStr.append(placeholder);
523+
placeholder = ",?";
524+
}
525+
526+
sprocStr.append(")}");
293527

294528
try {
295-
connection = getConnection(connectionStr);
296-
statement = connection.prepareStatement(queryStr); //NOSONAR
529+
Connection connection = getConnection(connectionStr);
530+
CallableStatement statement = connection.prepareCall(sprocStr.toString());
297531

298-
for (int i = 0; i < param.length; i++) {
299-
statement.setObject(i + 1, param[i]);
532+
for (int i = 0; i < params.length; i++) {
533+
params[i].set(statement, i + 1);
300534
}
301535

536+
return executeStatement(resultType, connection, statement);
537+
} catch (SQLException e) {
538+
throw UncheckedThrow.throwUnchecked(e);
539+
}
540+
}
541+
542+
/**
543+
* Execute the specified prepared statement, returning a result of the indicated type.
544+
* <p>
545+
* <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
546+
* <li>{@code null} - The prepared statement is a query to be executed as an update operation.</li>
547+
* <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
548+
* <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
549+
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
550+
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
551+
* <p>
552+
* <b>NOTE</b>: For all result types except {@link ResultPackage}, the specified connection and statement, as well
553+
* as the result set from executing the statement, are closed prior to returning the result.
554+
*
555+
* @param resultType desired result type (see TYPES above)
556+
* @param connectionStr database connection string
557+
* @param statement prepared statement to be executed (query or store procedure)
558+
* @return for update operations, the number of rows affected; for query operations, an object of the indicated type<br>
559+
* <b>NOTE</b>: If you specify {@link ResultPackage} as the result type, it's recommended that you close this object
560+
* when you're done with it to free up database and JDBC resources that were allocated for it.
561+
*/
562+
private static Object executeStatement(Class<?> resultType, Connection connection, PreparedStatement statement) {
563+
Object result = null;
564+
boolean failed = false;
565+
566+
ResultSet resultSet = null;
567+
568+
try {
302569
if (resultType == null) {
303570
result = Integer.valueOf(statement.executeUpdate());
304571
} else {
305-
resultSet = statement.executeQuery(); //NOSONAR
306-
307-
if (resultType == ResultPackage.class) {
308-
result = new ResultPackage(connection, statement, resultSet); //NOSONAR
309-
} else if (resultType == Integer.class) {
310-
result = Integer.valueOf((resultSet.next()) ? resultSet.getInt(1) : -1);
311-
} else if (resultType == String.class) {
312-
result = (resultSet.next()) ? resultSet.getString(1) : null;
572+
if (statement instanceof CallableStatement) {
573+
if (statement.execute()) {
574+
resultSet = statement.getResultSet(); //NOSONAR
575+
}
576+
577+
if (resultType == ResultPackage.class) {
578+
result = new ResultPackage(connection, statement, resultSet); //NOSONAR
579+
} else if (resultType == Integer.class) {
580+
result = ((CallableStatement) statement).getInt(1);
581+
} else if (resultType == String.class) {
582+
result = ((CallableStatement) statement).getString(1);
583+
} else {
584+
result = ((CallableStatement) statement).getObject(1);
585+
}
313586
} else {
314-
result = (resultSet.next()) ? resultSet.getObject(1, resultType) : null;
587+
resultSet = statement.executeQuery(); //NOSONAR
588+
589+
if (resultType == ResultPackage.class) {
590+
result = new ResultPackage(connection, statement, resultSet); //NOSONAR
591+
} else if (resultType == Integer.class) {
592+
result = Integer.valueOf((resultSet.next()) ? resultSet.getInt(1) : -1);
593+
} else if (resultType == String.class) {
594+
result = (resultSet.next()) ? resultSet.getString(1) : null;
595+
} else {
596+
result = (resultSet.next()) ? resultSet.getObject(1, resultType) : null;
597+
}
315598
}
316599
}
317600

@@ -375,19 +658,12 @@ public interface QueryAPI {
375658
String getQueryStr();
376659

377660
/**
378-
* Get the argument name for this query object
661+
* Get the argument names for this query object.
379662
*
380663
* @return query object argument names
381664
*/
382665
String[] getArgNames();
383666

384-
/**
385-
* Get the count of arguments for this query object.
386-
*
387-
* @return query object argument count
388-
*/
389-
int getArgCount();
390-
391667
/**
392668
* Get the database connection string for this query object.
393669
*
@@ -403,6 +679,55 @@ public interface QueryAPI {
403679
Enum<? extends QueryAPI> getEnum(); //NOSONAR
404680
}
405681

682+
/**
683+
* This interface defines the API supported by database stored procedure collections
684+
*/
685+
public interface SProcAPI {
686+
687+
/**
688+
* Get the signature for this stored procedure object.
689+
* <p>
690+
* Each argument place holder in the stored procedure signature indicates the mode of the corresponding
691+
* parameter:
692+
*
693+
* <ul>
694+
* <li>'&gt;' : This argument is an IN parameter</li>
695+
* <li>'&lt;' : This argument is an OUT parameter</li>
696+
* <li>'=' : This argument is an INOUT parameter</li>
697+
* </ul>
698+
*
699+
* For example:
700+
*
701+
* <blockquote>RAISE_PRICE(&gt;, &lt;, =)</blockquote>
702+
*
703+
* The first and second arguments are IN parameters, and the third argument is an INOUT parameter.
704+
*
705+
* @return stored procedure signature
706+
*/
707+
String getSignature();
708+
709+
/**
710+
* Get the argument types for this stored procedure object.
711+
*
712+
* @return stored procedure argument types
713+
*/
714+
int[] getArgTypes();
715+
716+
/**
717+
* Get the database connection string for this stored procedure object.
718+
*
719+
* @return stored procedure connection string
720+
*/
721+
String getConnection();
722+
723+
/**
724+
* Get the implementing enumerated constant for this stored procedure object.
725+
*
726+
* @return stored procedure enumerated constant
727+
*/
728+
Enum<? extends SProcAPI> getEnum(); //NOSONAR
729+
}
730+
406731
/**
407732
* This class defines a package of database objects associated with a query. These include:<ul>
408733
* <li>{@link Connection} object</li>
@@ -428,6 +753,21 @@ private ResultPackage(Connection connection, PreparedStatement statement, Result
428753
this.resultSet = resultSet;
429754
}
430755

756+
public Connection getConnection() {
757+
return connection;
758+
}
759+
760+
public PreparedStatement getStatement() {
761+
return statement;
762+
}
763+
764+
public CallableStatement getCallable() {
765+
if (statement instanceof CallableStatement) {
766+
return (CallableStatement) statement;
767+
}
768+
throw new UnsupportedOperationException("The statement of this package is not a CallableStatement");
769+
}
770+
431771
/**
432772
* Get the result set object of this package.
433773
*
Lines changed: 533 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,533 @@
1+
package com.nordstrom.common.jdbc;
2+
3+
import java.math.BigDecimal;
4+
import java.sql.CallableStatement;
5+
import java.sql.SQLException;
6+
import java.sql.Types;
7+
8+
/**
9+
* This class is used to encapsulate parameters for stored procedure calls. In addition to parameter value, instances
10+
* of this class define {@link Mode parameter mode} (IN/OUT/INOUT) and {@link Types parameter type} (e.g. - INTEGER).
11+
*/
12+
public class Param {
13+
14+
private Mode mode = Mode.IN;
15+
private int paramType;
16+
private Object inputValue;
17+
18+
/**
19+
* Constructor: Private, to discourage direct instantiation.
20+
*/
21+
private Param() {
22+
}
23+
24+
/**
25+
* Instantiate a parameter of the indicated mode and type with the specified value.
26+
*
27+
* @param mode parameter {@link Mode mode}
28+
* @param paramType parameter {@link Types type}
29+
* @param inputValue parameter value
30+
* @return new {@link Param} object
31+
*/
32+
public static Param create(Mode mode, int paramType, Object inputValue) {
33+
if (mode == Mode.OUT) {
34+
return Param.out(paramType);
35+
} else if (mode == Mode.INOUT) {
36+
return Param.inOut(paramType, inputValue);
37+
}
38+
return Param.in(paramType, inputValue);
39+
}
40+
41+
/**
42+
* Instantiate an IN parameter of the indicated type with the specified value.
43+
*
44+
* @param paramType parameter {@link Types type}
45+
* @param inputValue parameter value
46+
* @return new {@link Param} object
47+
*/
48+
public static Param in(int paramType, Object inputValue) {
49+
Param parameter = new Param();
50+
parameter.mode = Mode.IN;
51+
parameter.paramType = paramType;
52+
parameter.inputValue = inputValue;
53+
return parameter;
54+
}
55+
56+
/**
57+
* Instantiate an OUT parameter of the indicated type.
58+
*
59+
* @param paramType parameter {@link Types type}
60+
* @return new {@link Param} object
61+
*/
62+
public static Param out(int paramType) {
63+
Param parameter = new Param();
64+
parameter.mode = Mode.OUT;
65+
parameter.paramType = paramType;
66+
return parameter;
67+
}
68+
69+
/**
70+
* Instantiate an INOUT parameter of the indicated type with the specified value.
71+
*
72+
* @param paramType parameter {@link Types type}
73+
* @param inputValue parameter value
74+
* @return new {@link Param} object
75+
*/
76+
public static Param inOut(int paramType, Object inputValue) {
77+
Param parameter = new Param();
78+
parameter.mode = Mode.INOUT;
79+
parameter.inputValue = inputValue;
80+
parameter.paramType = paramType;
81+
return parameter;
82+
}
83+
84+
/**
85+
* Allocate an array with the specified capacity of {@link Param} objects.
86+
*
87+
* @param size desired array capacity
88+
* @return {@link Param} array of specified capacity
89+
*/
90+
static Param[] array(int size) {
91+
return new Param[size];
92+
}
93+
94+
/**
95+
* Store this parameter at the indicated index for the specified callable statement.
96+
*
97+
* @param sproc target {@link CallableStatement} object
98+
* @param index parameter index
99+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
100+
* called on a closed {@link CallableStatement}
101+
*/
102+
public void set(CallableStatement sproc, int index) throws SQLException {
103+
if (isOutput()) {
104+
sproc.registerOutParameter(index, paramType);
105+
}
106+
107+
if (isInput()) {
108+
if (inputValue == null) {
109+
sproc.setNull(index, paramType);
110+
} else {
111+
switch (paramType) {
112+
113+
case Types.CHAR:
114+
case Types.VARCHAR:
115+
case Types.LONGVARCHAR:
116+
setCharString(sproc, index);
117+
break;
118+
119+
case Types.NCHAR:
120+
case Types.NVARCHAR:
121+
case Types.LONGNVARCHAR:
122+
setNCharString(sproc, index);
123+
break;
124+
125+
case Types.BINARY:
126+
case Types.VARBINARY:
127+
case Types.LONGVARBINARY:
128+
setBinary(sproc, index);
129+
break;
130+
131+
case Types.BIT:
132+
case Types.BOOLEAN:
133+
setBoolean(sproc, index);
134+
break;
135+
136+
case Types.SMALLINT:
137+
setSmallInt(sproc, index);
138+
break;
139+
140+
case Types.INTEGER:
141+
setInteger(sproc, index);
142+
break;
143+
144+
case Types.BIGINT:
145+
setBigInt(sproc, index);
146+
break;
147+
148+
case Types.REAL:
149+
setReal(sproc, index);
150+
break;
151+
152+
case Types.DOUBLE:
153+
case Types.FLOAT:
154+
setDouble(sproc, index);
155+
break;
156+
157+
case Types.DECIMAL:
158+
case Types.NUMERIC:
159+
setDecimal(sproc, index);
160+
break;
161+
162+
case Types.DATE:
163+
setDate(sproc, index);
164+
break;
165+
166+
case Types.TIME:
167+
setTime(sproc, index);
168+
break;
169+
170+
case Types.TIMESTAMP:
171+
setTimestamp(sproc, index);
172+
break;
173+
174+
case Types.OTHER:
175+
case Types.JAVA_OBJECT:
176+
setJavaObject(sproc, index);
177+
break;
178+
179+
default:
180+
throw new UnsupportedOperationException("Specified parameter type ["
181+
+ paramType + "] is unsupported");
182+
}
183+
}
184+
}
185+
}
186+
187+
/**
188+
* Store this character string parameter at the indicated index for the specified callable statement.
189+
*
190+
* @param sproc target {@link CallableStatement} object
191+
* @param index parameter index
192+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
193+
* called on a closed {@link CallableStatement}
194+
*/
195+
private void setCharString(CallableStatement sproc, int index) throws SQLException {
196+
if (inputValue instanceof String) {
197+
sproc.setString(index, (String) inputValue);
198+
} else {
199+
throw new IllegalArgumentException("Specified parameter value is not a string");
200+
}
201+
}
202+
203+
/**
204+
* Store this character string parameter at the indicated index for the specified callable statement.
205+
*
206+
* @param sproc target {@link CallableStatement} object
207+
* @param index parameter index
208+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
209+
* called on a closed {@link CallableStatement}
210+
*/
211+
private void setNCharString(CallableStatement sproc, int index) throws SQLException {
212+
if (inputValue instanceof String) {
213+
sproc.setNString(index, (String) inputValue);
214+
} else {
215+
throw new IllegalArgumentException("Specified parameter value is not a string");
216+
}
217+
}
218+
219+
/**
220+
* Store this binary parameter at the indicated index for the specified callable statement.
221+
*
222+
* @param sproc target {@link CallableStatement} object
223+
* @param index parameter index
224+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
225+
* called on a closed {@link CallableStatement}
226+
*/
227+
private void setBinary(CallableStatement sproc, int index) throws SQLException {
228+
if (inputValue instanceof byte[]) {
229+
sproc.setBytes(index, (byte[]) inputValue);
230+
} else {
231+
throw new IllegalArgumentException("Specified parameter value is not an array of bytes");
232+
}
233+
}
234+
235+
/**
236+
* Store this boolean parameter at the indicated index for the specified callable statement.
237+
*
238+
* @param sproc target {@link CallableStatement} object
239+
* @param index parameter index
240+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
241+
* called on a closed {@link CallableStatement}
242+
*/
243+
private void setBoolean(CallableStatement sproc, int index) throws SQLException {
244+
if (inputValue instanceof Boolean) {
245+
sproc.setBoolean(index, (Boolean) inputValue);
246+
} else {
247+
throw new IllegalArgumentException("Specified parameter value is not a boolean");
248+
}
249+
}
250+
251+
/**
252+
* Store this small integer parameter at the indicated index for the specified callable statement.
253+
*
254+
* @param sproc target {@link CallableStatement} object
255+
* @param index parameter index
256+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
257+
* called on a closed {@link CallableStatement}
258+
*/
259+
private void setSmallInt(CallableStatement sproc, int index) throws SQLException {
260+
if (inputValue instanceof Short) {
261+
sproc.setShort(index, (Short) inputValue);
262+
} else {
263+
throw new IllegalArgumentException("Specified parameter value is not a small integer (short)");
264+
}
265+
}
266+
267+
/**
268+
* Store this integer parameter at the indicated index for the specified callable statement.
269+
*
270+
* @param sproc target {@link CallableStatement} object
271+
* @param index parameter index
272+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
273+
* called on a closed {@link CallableStatement}
274+
*/
275+
private void setInteger(CallableStatement sproc, int index) throws SQLException {
276+
if (inputValue instanceof Integer) {
277+
sproc.setInt(index, (Integer) inputValue);
278+
} else {
279+
throw new IllegalArgumentException("Specified parameter value is not an integer");
280+
}
281+
}
282+
283+
/**
284+
* Store this big integer parameter at the indicated index for the specified callable statement.
285+
*
286+
* @param sproc target {@link CallableStatement} object
287+
* @param index parameter index
288+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
289+
* called on a closed {@link CallableStatement}
290+
*/
291+
private void setBigInt(CallableStatement sproc, int index) throws SQLException {
292+
if (inputValue instanceof Long) {
293+
sproc.setLong(index, (Long) inputValue);
294+
} else {
295+
throw new IllegalArgumentException("Specified parameter value is not a big integer (long)");
296+
}
297+
}
298+
299+
/**
300+
* Store this single-precision floating-point parameter at the indicated index for the specified callable
301+
* statement.
302+
*
303+
* @param sproc target {@link CallableStatement} object
304+
* @param index parameter index
305+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
306+
* called on a closed {@link CallableStatement}
307+
*/
308+
private void setReal(CallableStatement sproc, int index) throws SQLException {
309+
if (inputValue instanceof Float) {
310+
sproc.setFloat(index, (Float) inputValue);
311+
} else {
312+
throw new IllegalArgumentException("Specified parameter value is not a single-precision float (float)");
313+
}
314+
}
315+
316+
/**
317+
* Store this double-precision floating-point parameter at the indicated index for the specified callable
318+
* statement.
319+
*
320+
* @param sproc target {@link CallableStatement} object
321+
* @param index parameter index
322+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
323+
* called on a closed {@link CallableStatement}
324+
*/
325+
private void setDouble(CallableStatement sproc, int index) throws SQLException {
326+
if (inputValue instanceof Double) {
327+
sproc.setDouble(index, (Double) inputValue);
328+
} else {
329+
throw new IllegalArgumentException("Specified parameter value is not a double-precision float (double)");
330+
}
331+
}
332+
333+
/**
334+
* Store this decimal parameter at the indicated index for the specified callable statement.
335+
*
336+
* @param sproc target {@link CallableStatement} object
337+
* @param index parameter index
338+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
339+
* called on a closed {@link CallableStatement}
340+
*/
341+
private void setDecimal(CallableStatement sproc, int index) throws SQLException {
342+
if (inputValue instanceof BigDecimal) {
343+
sproc.setBigDecimal(index, (BigDecimal) inputValue);
344+
} else {
345+
throw new IllegalArgumentException("Specified parameter value is not a decimal (BigDecimal)");
346+
}
347+
}
348+
349+
/**
350+
* Store this SQL Date parameter at the indicated index for the specified callable statement.
351+
*
352+
* @param sproc target {@link CallableStatement} object
353+
* @param index parameter index
354+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
355+
* called on a closed {@link CallableStatement}
356+
*/
357+
private void setDate(CallableStatement sproc, int index) throws SQLException {
358+
if (inputValue instanceof java.sql.Date) {
359+
sproc.setDate(index, (java.sql.Date) inputValue);
360+
} else {
361+
throw new IllegalArgumentException("Specified parameter value is not a SQL Date object");
362+
}
363+
}
364+
365+
/**
366+
* Store this SQL Time parameter at the indicated index for the specified callable statement.
367+
*
368+
* @param sproc target {@link CallableStatement} object
369+
* @param index parameter index
370+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
371+
* called on a closed {@link CallableStatement}
372+
*/
373+
private void setTime(CallableStatement sproc, int index) throws SQLException {
374+
if (inputValue instanceof java.sql.Time) {
375+
sproc.setTime(index, (java.sql.Time) inputValue);
376+
} else {
377+
throw new IllegalArgumentException("Specified parameter value is not a SQL Time object");
378+
}
379+
}
380+
381+
/**
382+
* Store this SQL Timestamp parameter at the indicated index for the specified callable statement.
383+
*
384+
* @param sproc target {@link CallableStatement} object
385+
* @param index parameter index
386+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
387+
* called on a closed {@link CallableStatement}
388+
*/
389+
private void setTimestamp(CallableStatement sproc, int index) throws SQLException {
390+
if (inputValue instanceof java.sql.Timestamp) {
391+
sproc.setTimestamp(index, (java.sql.Timestamp) inputValue);
392+
} else {
393+
throw new IllegalArgumentException("Specified parameter value is not a SQL Timestamp object");
394+
}
395+
}
396+
397+
/**
398+
* Store this Java object parameter at the indicated index for the specified callable statement.
399+
*
400+
* @param sproc target {@link CallableStatement} object
401+
* @param index parameter index
402+
* @throws SQLException if the specified index is not valid; if a database access error occurs or this method is
403+
* called on a closed {@link CallableStatement}
404+
*/
405+
private void setJavaObject(CallableStatement sproc, int index) throws SQLException {
406+
sproc.setObject(index, inputValue);
407+
}
408+
409+
/**
410+
* Get the {@link Mode} of this parameter (IN/OUT/INOUT)
411+
*
412+
* @return parameter mode
413+
*/
414+
public Mode getMode() {
415+
return mode;
416+
}
417+
418+
/**
419+
* Determine if this parameter is an input.
420+
*
421+
* @return {@code true} if this parameter is an input; otherwise {@code false}
422+
*/
423+
public boolean isInput() {
424+
return mode.isInput();
425+
}
426+
427+
/**
428+
* Determine if this parameter is an output.
429+
*
430+
* @return {@code true} if this parameter is an output; otherwise {@code false}
431+
*/
432+
public boolean isOutput() {
433+
return mode.isOutput();
434+
}
435+
436+
/**
437+
* Get the input value of this parameter.
438+
*
439+
* @return parameter input value
440+
*/
441+
public Object getInValue() {
442+
return inputValue;
443+
}
444+
445+
/**
446+
* Get the value {@link Types type} of this parameter.
447+
*
448+
* @return parameter {@link Types type}
449+
*/
450+
public int getParamType() {
451+
return paramType;
452+
}
453+
454+
/**
455+
* This enumeration defines the stored procedure parameter modes with their associated placeholder characters.
456+
*/
457+
public enum Mode {
458+
/** placeholder: '&gt;' */
459+
IN('>', Mode.INPUT),
460+
/** placeholder: '&lt;' */
461+
OUT('<', Mode.OUTPUT),
462+
/** placeholder: '=' */
463+
INOUT('=', Mode.INPUT | Mode.OUTPUT);
464+
465+
private static final int INPUT = 1;
466+
private static final int OUTPUT = 2;
467+
468+
private char chr;
469+
private int val;
470+
471+
/**
472+
* Constructor
473+
*
474+
* @param chr placeholder character
475+
* @param val directionality flags
476+
*/
477+
Mode(char chr, int val) {
478+
this.chr = chr;
479+
this.val = val;
480+
}
481+
482+
/**
483+
* Get the placeholder character for this parameter mode.
484+
*
485+
* @return parameter mode placeholder
486+
*/
487+
public char chr() {
488+
return chr;
489+
}
490+
491+
/**
492+
* Get the directionality flags for this parameter mode.
493+
*
494+
* @return parameter mode directionality
495+
*/
496+
public int val() {
497+
return val;
498+
}
499+
500+
/**
501+
* Determine if this mode represents an input parameter.
502+
*
503+
* @return {@code true} if this is an input mode; otherwise {@code false}
504+
*/
505+
public boolean isInput() {
506+
return ((val & INPUT) == INPUT);
507+
}
508+
509+
/**
510+
* Determine if this mode represents an output parameter.
511+
*
512+
* @return {@code true} if this is an output mode; otherwise {@code false}
513+
*/
514+
public boolean isOutput() {
515+
return ((val & OUTPUT) == OUTPUT);
516+
}
517+
518+
/**
519+
* Get the parameter mode constant that corresponds to the specified placeholder character.
520+
*
521+
* @param chr parameter mode placeholder
522+
* @return parameter mode constant
523+
*/
524+
public static Mode fromChar(char chr) {
525+
for (Mode thisMode : values()) {
526+
if (thisMode.chr == chr) {
527+
return thisMode;
528+
}
529+
}
530+
throw new IllegalArgumentException("Specified parameter mode placeholder '" + chr + "' is unsupported");
531+
}
532+
}
533+
}

‎src/test/java/com/nordstrom/common/jdbc/DatabaseUtilsTest.java

Lines changed: 138 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
package com.nordstrom.common.jdbc;
22

3+
import static org.testng.Assert.assertEquals;
4+
5+
import java.sql.CallableStatement;
36
import java.sql.Connection;
47
import java.sql.DriverManager;
58
import java.sql.SQLException;
9+
import java.sql.Types;
610
import org.testng.annotations.AfterClass;
711
import org.testng.annotations.BeforeClass;
812
import org.testng.annotations.Test;
913

1014
import com.nordstrom.common.jdbc.DatabaseUtils.QueryAPI;
1115
import com.nordstrom.common.jdbc.DatabaseUtils.ResultPackage;
16+
import com.nordstrom.common.jdbc.DatabaseUtils.SProcAPI;
1217

1318
public class DatabaseUtilsTest {
1419

@@ -76,6 +81,31 @@ public void updateRows() {
7681
}
7782

7883
@Test(dependsOnMethods={"updateRows"})
84+
public void showAddresses() {
85+
try {
86+
DatabaseUtils.update(TestQuery.SHOW_ADDRESSES);
87+
} catch (Exception e) {
88+
}
89+
90+
ResultPackage pkg = DatabaseUtils.getResultPackage(TestSProc.SHOW_ADDRESSES);
91+
92+
int rowCount = 0;
93+
try {
94+
while (pkg.getResultSet().next()) {
95+
rowCount++;
96+
int num = pkg.getResultSet().getInt("num");
97+
String addr = pkg.getResultSet().getString("addr");
98+
System.out.println("addr" + rowCount + ": " + num + " " + addr);
99+
}
100+
} catch (SQLException e) {
101+
}
102+
pkg.close();
103+
104+
DatabaseUtils.update(TestQuery.DROP_PROC_SHOW);
105+
assertEquals(rowCount, 2);
106+
}
107+
108+
@Test(dependsOnMethods={"showAddresses"})
79109
public void getInt() {
80110
DatabaseUtils.getInt(TestQuery.GET_NUM);
81111
}
@@ -96,14 +126,86 @@ public void dropTable() {
96126
DatabaseUtils.update(TestQuery.DROP);
97127
}
98128

129+
@Test
130+
public void testInVarargs() {
131+
try {
132+
DatabaseUtils.update(TestQuery.IN_VARARGS);
133+
} catch (Exception e) {
134+
}
135+
136+
String result = DatabaseUtils.getString(TestSProc.IN_VARARGS, "", 5, 4, 3);
137+
DatabaseUtils.update(TestQuery.DROP_PROC_IN);
138+
assertEquals(result, "RESULT: 5 4 3");
139+
}
140+
141+
@Test()
142+
public void testOutVarargs() throws SQLException {
143+
try {
144+
DatabaseUtils.update(TestQuery.OUT_VARARGS);
145+
} catch (Exception e) {
146+
}
147+
148+
ResultPackage pkg = DatabaseUtils.getResultPackage(TestSProc.OUT_VARARGS, 5, 0, 0, 0);
149+
150+
int[] out = new int[3];
151+
out[0] = ((CallableStatement) pkg.getStatement()).getInt(2);
152+
out[1] = ((CallableStatement) pkg.getStatement()).getInt(3);
153+
out[2] = ((CallableStatement) pkg.getStatement()).getInt(4);
154+
pkg.close();
155+
156+
DatabaseUtils.update(TestQuery.DROP_PROC_OUT);
157+
158+
assertEquals(out[0], 5);
159+
assertEquals(out[1], 6);
160+
assertEquals(out[2], 7);
161+
}
162+
163+
@Test
164+
public void testInOutVarargs() throws SQLException {
165+
try {
166+
DatabaseUtils.update(TestQuery.INOUT_VARARGS);
167+
} catch (Exception e) {
168+
}
169+
170+
ResultPackage pkg = DatabaseUtils.getResultPackage(TestSProc.INOUT_VARARGS, 5, 3, 10, 100);
171+
172+
int[] out = new int[3];
173+
out[0] = pkg.getCallable().getInt(2);
174+
out[1] = pkg.getCallable().getInt(3);
175+
out[2] = pkg.getCallable().getInt(4);
176+
pkg.close();
177+
178+
DatabaseUtils.update(TestQuery.DROP_PROC_INOUT);
179+
180+
assertEquals(out[0], 8);
181+
assertEquals(out[1], 15);
182+
assertEquals(out[2], 105);
183+
}
184+
99185
enum TestQuery implements QueryAPI {
100186
CREATE("create table location(num int, addr varchar(40))"),
101187
INSERT("insert into location values (?, ?)", "num", "addr"),
102188
UPDATE("update location set num=?, addr=? where num=?", "num", "addr", "whereNum"),
189+
SHOW_ADDRESSES("create procedure SHOW_ADDRESSES() parameter style java "
190+
+ "language java dynamic result sets 1 "
191+
+ "external name 'com.nordstrom.common.jdbc.StoredProcedure.showAddresses'"),
192+
DROP_PROC_SHOW("drop procedure SHOW_ADDRESSES"),
103193
GET_NUM("select num from location where addr='Union St.'"),
104194
GET_STR("select addr from location where num=1910"),
105195
GET_RESULT_PACKAGE("select * from location"),
106-
DROP("drop table location");
196+
DROP("drop table location"),
197+
IN_VARARGS("create procedure IN_VARARGS(out result varchar( 32672 ), b int ...) "
198+
+ "language java parameter style derby no sql deterministic "
199+
+ "external name 'com.nordstrom.common.jdbc.StoredProcedure.inVarargs'"),
200+
DROP_PROC_IN("drop procedure IN_VARARGS"),
201+
OUT_VARARGS("create procedure OUT_VARARGS(seed int, out b int ...) "
202+
+ "language java parameter style derby no sql deterministic "
203+
+ "external name 'com.nordstrom.common.jdbc.StoredProcedure.outVarargs'"),
204+
DROP_PROC_OUT("drop procedure OUT_VARARGS"),
205+
INOUT_VARARGS("create procedure INOUT_VARARGS(seed int, inout b int ...) "
206+
+ "language java parameter style derby no sql deterministic "
207+
+ "external name 'com.nordstrom.common.jdbc.StoredProcedure.inoutVarargs'"),
208+
DROP_PROC_INOUT("drop procedure INOUT_VARARGS");
107209

108210
private String query;
109211
private String[] args;
@@ -123,11 +225,6 @@ public String[] getArgNames() {
123225
return args;
124226
}
125227

126-
@Override
127-
public int getArgCount() {
128-
return args.length;
129-
}
130-
131228
@Override
132229
public String getConnection() {
133230
return connection();
@@ -142,6 +239,41 @@ public static String connection() {
142239
return "jdbc:derby:@TestDB";
143240
}
144241
}
242+
243+
enum TestSProc implements SProcAPI {
244+
SHOW_ADDRESSES("SHOW_ADDRESSES()"),
245+
IN_VARARGS("IN_VARARGS(<, >:)", Types.VARCHAR, Types.INTEGER),
246+
OUT_VARARGS("OUT_VARARGS(>, <:)", Types.INTEGER, Types.INTEGER),
247+
INOUT_VARARGS("INOUT_VARARGS(>, =:)", Types.INTEGER, Types.INTEGER);
248+
249+
private int[] argTypes;
250+
private String signature;
251+
252+
TestSProc(String signature, int... argTypes) {
253+
this.signature = signature;
254+
this.argTypes = argTypes;
255+
}
256+
257+
@Override
258+
public String getSignature() {
259+
return signature;
260+
}
261+
262+
@Override
263+
public int[] getArgTypes() {
264+
return argTypes;
265+
}
266+
267+
@Override
268+
public String getConnection() {
269+
return TestQuery.connection();
270+
}
271+
272+
@Override
273+
public Enum<? extends SProcAPI> getEnum() {
274+
return this;
275+
}
276+
}
145277

146278
/**
147279
* Prints details of an SQLException chain to <code>System.err</code>.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.nordstrom.common.jdbc;
2+
3+
import java.sql.Connection;
4+
import java.sql.DriverManager;
5+
import java.sql.ResultSet;
6+
import java.sql.SQLException;
7+
import java.sql.Statement;
8+
9+
public class StoredProcedure {
10+
11+
public static void showAddresses(ResultSet[] rs) throws SQLException {
12+
Connection con = DriverManager.getConnection("jdbc:default:connection");
13+
String query = "select NUM, ADDR from LOCATION";
14+
Statement stmt = con.createStatement();
15+
rs[0] = stmt.executeQuery(query);
16+
}
17+
18+
//////////////////////////
19+
//
20+
// IN, OUT, IN/OUT PARAMETERS
21+
//
22+
//////////////////////////
23+
24+
public static void inVarargs(String[] result, int... values) {
25+
String retval;
26+
if (values == null) {
27+
retval = null;
28+
} else if (values.length == 0) {
29+
retval = null;
30+
} else {
31+
StringBuilder buffer = new StringBuilder();
32+
33+
buffer.append("RESULT: ");
34+
35+
for (int value : values) {
36+
buffer.append(" " + Integer.toString(value));
37+
}
38+
39+
retval = buffer.toString();
40+
}
41+
42+
result[0] = retval;
43+
}
44+
45+
public static void outVarargs(int seed, int[]... values) throws Exception {
46+
if (values == null) {
47+
return;
48+
} else {
49+
for (int i = 0; i < values.length; i++) {
50+
values[i][0] = seed + i;
51+
}
52+
}
53+
}
54+
55+
public static void inoutVarargs(int seed, int[]... values) throws Exception {
56+
if (values == null) {
57+
return;
58+
} else {
59+
for (int i = 0; i < values.length; i++) {
60+
values[i][0] += seed;
61+
}
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)
Please sign in to comment.