1919import java .sql .PreparedStatement ;
2020
2121/**
22- * This utility class provides facilities that enable you to define collections of database queries and
23- * execute them easily. Query collections are defined as Java enumeration that implement the {@link QueryAPI}
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}
2426 * interface: <ul>
2527 * <li>{@link QueryAPI#getQueryStr() getQueryStr} - Get the query string for this constant. This is the actual query
2628 * that's sent to the database.</li>
3638 * diagnostic messages.</li>
3739 * </ul>
3840 *
41+ * Store procedure collections are defined as Java enumerations that implement the {@link SProcAPI}
42+ * interface: <ul>
43+ * <li>{@link SProcAPI#getSignature() getSignature} - Get the signature for this stored procedure object. This defines
44+ * the name of the stored procedure and
45+ * that's sent to the database.</li>
46+ * <li>{@link SProcAPI#getArgNames() getArgNames} - Get the names of the arguments for this query. This provides
47+ * diagnostic information if the incorrect number of arguments is specified by the client.</li>
48+ * <li>{@link SProcAPI#getArgCount() getArgCount} - Get the number of arguments required by this query. This enables
49+ * {@link #executeQuery(Class, SProcAPI, Object[])} to verify that the correct number of arguments has been
50+ * specified by the client.</li>
51+ * <li>{@link SProcAPI#getConnection() getConnection} - Get the connection string associated with this query. This
52+ * eliminates the need for the client to provide this information.</li>
53+ * <li>{@link SProcAPI#getEnum() getEnum} - Get the enumeration to which this query belongs. This enables {@link
54+ * #executeQuery(Class, SProcAPI, Object[])} to retrieve the name of the query's enumerated constant for
55+ * diagnostic messages.</li>
56+ * </ul>
57+ *
3958 * To maximize usability and configurability, we recommend the following implementation strategy for your query
4059 * collections: <ul>
4160 * <li>Define your query collection as an enumeration that implements {@link QueryAPI}.</li>
177196public class DatabaseUtils {
178197
179198 private static Pattern SPROC_PATTERN =
180- Pattern .compile ("([\\ p{Alpha}_][\\ p{Alpha}\\ p{Digit}@$#_]*)(?:\\ (([<>=](?:,\\ s*[<>=])*)?\\ ))?" );
199+ Pattern .compile ("([\\ p{Alpha}_][\\ p{Alpha}\\ p{Digit}@$#_]*)(?:\\ (([<>=](?:,\\ s*[<>=])*)?([.]{3})? \\ ))?" );
181200
182201 private DatabaseUtils () {
183202 throw new AssertionError ("DatabaseUtils is a static utility class that cannot be instantiated" );
@@ -219,7 +238,7 @@ public static int getInt(QueryAPI query, Object... queryArgs) {
219238 *
220239 * @param query query object to execute
221240 * @param queryArgs replacement values for query place-holders
222- * @return row 1 / column 1 as string; ' null' if no rows were returned
241+ * @return row 1 / column 1 as string; {@code null} if no rows were returned
223242 */
224243 public static String getString (QueryAPI query , Object ... queryArgs ) {
225244 return (String ) executeQuery (String .class , query , queryArgs );
@@ -240,10 +259,10 @@ public static ResultPackage getResultPackage(QueryAPI query, Object... queryArgs
240259 * Execute the specified query with the supplied arguments, returning a result of the indicated type.
241260 * <p>
242261 * <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
243- * <li>' null' - The query is executed as an update operation.</li>
262+ * <li>{@code null} - The query is executed as an update operation.</li>
244263 * <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
245264 * <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
246- * <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise ' null' </li>
265+ * <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null} </li>
247266 * <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
248267 *
249268 * @param resultType desired result type (see TYPES above)
@@ -277,10 +296,10 @@ private static Object executeQuery(Class<?> resultType, QueryAPI query, Object..
277296 * Execute the specified query with the supplied arguments, returning a result of the indicated type.
278297 * <p>
279298 * <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
280- * <li>' null' - The query is executed as an update operation.</li>
299+ * <li>{@code null} - The query is executed as an update operation.</li>
281300 * <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
282301 * <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
283- * <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>
284303 * <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
285304 *
286305 * @param resultType desired result type (see TYPES above)
@@ -312,7 +331,7 @@ public static Object executeQuery(Class<?> resultType, String connectionStr, Str
312331 * <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
313332 * <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
314333 * <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
315- * <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise ' null' </li>
334+ * <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null} </li>
316335 * <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
317336 *
318337 * @param resultType desired result type (see TYPES above)
@@ -325,64 +344,84 @@ public static Object executeQuery(Class<?> resultType, String connectionStr, Str
325344 public static Object executeStoredProcedure (Class <?> resultType , SProcAPI sproc , Object ... parms ) {
326345 Objects .requireNonNull (resultType , "[resultType] argument must be non-null" );
327346
328- String sprocName ;
329347 String [] args = {};
348+ String sprocName = null ;
349+ boolean hasVarArgs = false ;
330350 String signature = sproc .getSignature ();
331351 Matcher matcher = SPROC_PATTERN .matcher (signature );
332352
353+ String message = null ;
333354 if (matcher .matches ()) {
334355 sprocName = matcher .group (1 );
356+ hasVarArgs = (matcher .group (3 ) != null );
335357 if (matcher .group (2 ) != null ) {
336358 args = matcher .group (2 ).split (",\\ s" );
359+ } else {
360+ if (hasVarArgs ) {
361+ message = String .format ("VarArgs indicated with no placeholder in signature for %s: %s" ,
362+ sproc .getEnum ().name (), signature );
363+ }
337364 }
338365 } else {
339- String message = String .format ("Unsupported stored procedure signature for %s: %s" ,
366+ message = String .format ("Unsupported stored procedure signature for %s: %s" ,
340367 sproc .getEnum ().name (), signature );
368+ }
369+
370+ if (message != null ) {
341371 throw new IllegalArgumentException (message );
342372 }
343373
344374 int argsCount = args .length ;
345375 int typesCount = sproc .getArgCount ();
376+ int parmsCount = parms .length ;
377+
378+ int minCount = typesCount ;
346379
347380 // if unbalanced args/types
348381 if (argsCount != typesCount ) {
349- String message = String .format ("Signature argument count differs from declared type count for %s%s: "
350- + "signature: %d; declared: %d" ,
351- sproc .getEnum ().name (), Arrays .toString (sproc .getArgTypes ()), argsCount , typesCount );
352- throw new IllegalArgumentException (message );
353- }
354-
355- int parmsCount = parms .length ;
356-
357- // if parms specified with no matching types
358- if ((parmsCount > 0 ) && (typesCount == 0 )) {
359- throw new IllegalArgumentException ("No arguments expected for " + sproc .getEnum ().name ());
382+ message = String .format (
383+ "Signature argument count differs from declared type count for %s%s: "
384+ + "signature: %d; declared: %d" ,
385+ sproc .getEnum ().name (), Arrays .toString (sproc .getArgTypes ()), argsCount ,
386+ typesCount );
387+ } else if (hasVarArgs ) {
388+ minCount -= 1 ;
389+ if (parmsCount < minCount ) {
390+ message = String .format (
391+ "Insufficient arguments count for %s%s: minimum: %d; actual: %d" ,
392+ sproc .getEnum ().name (), Arrays .toString (sproc .getArgTypes ()),
393+ minCount , parmsCount );
394+ }
395+ } else if (parmsCount != typesCount ) {
396+ if (typesCount == 0 ) {
397+ message = "No arguments expected for " + sproc .getEnum ().name ();
398+ } else {
399+ message = String .format (
400+ "Incorrect arguments count for %s%s: expect: %d; actual: %d" ,
401+ sproc .getEnum ().name (), Arrays .toString (sproc .getArgTypes ()),
402+ typesCount , parmsCount );
403+ }
360404 }
361405
362- // if fewer parms than types
363- if (parmsCount < typesCount ) {
364- String message = String .format ("Insufficient arguments count for %s%s: minimum: %d; actual: %d" ,
365- sproc .getEnum ().name (), Arrays .toString (sproc .getArgTypes ()), typesCount , parmsCount );
406+ if (message != null ) {
366407 throw new IllegalArgumentException (message );
367408 }
368409
369410 int [] argTypes = sproc .getArgTypes ();
370411 Param [] parmArray = Param .array (parmsCount );
371412
372- int i = 0 ;
373- int type = 0 ;
374- Mode mode = Mode .IN ;
413+ int i ;
375414
376415 // process declared parameters
377- for (i = 0 ; i < typesCount ; i ++) {
378- type = argTypes [i ];
379- mode = Mode .fromChar (args [i ].charAt (0 ));
380- parmArray [i ] = Param .create (mode , type , parms [i ]);
416+ for (i = 0 ; i < minCount ; i ++) {
417+ Mode mode = Mode .fromChar (args [i ].charAt (0 ));
418+ parmArray [i ] = Param .create (mode , argTypes [i ], parms [i ]);
381419 }
382420
383421 // handle varargs parameters
384- for (; i < parmsCount ; i ++) {
385- parmArray [i ] = Param .create (mode , type , parms [i ]);
422+ for (int j = i ; j < parmsCount ; j ++) {
423+ Mode mode = Mode .fromChar (args [i ].charAt (0 ));
424+ parmArray [j ] = Param .create (mode , argTypes [i ], parms [j ]);
386425 }
387426
388427 return executeStoredProcedure (resultType , sproc .getConnection (), sprocName , parmArray );
@@ -394,7 +433,7 @@ public static Object executeStoredProcedure(Class<?> resultType, SProcAPI sproc,
394433 * <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
395434 * <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
396435 * <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
397- * <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise ' null' </li>
436+ * <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null} </li>
398437 * <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
399438 *
400439 * @param resultType desired result type (see TYPES above)
@@ -436,10 +475,10 @@ public static Object executeStoredProcedure(Class<?> resultType, String connecti
436475 * Execute the specified prepared statement, returning a result of the indicated type.
437476 * <p>
438477 * <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
439- * <li>' null' - The prepared statement is a query to be executed as an update operation.</li>
478+ * <li>{@code null} - The prepared statement is a query to be executed as an update operation.</li>
440479 * <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
441480 * <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
442- * <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise ' null' </li>
481+ * <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null} </li>
443482 * <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
444483 * <p>
445484 * <b>NOTE</b>: For all result types except {@link ResultPackage}, the specified connection and statement, as well
@@ -598,7 +637,7 @@ public interface SProcAPI {
598637 int [] getArgTypes ();
599638
600639 /**
601- * Get the count of arguments for this stored procedure object.
640+ * Get the count of argument types for this stored procedure object.
602641 *
603642 * @return stored procedure argument count
604643 */
0 commit comments