comparison src/main/java/org/monetdb/jdbc/MonetParameterMetaData.java @ 719:2f42195e9c58

Improved implementation of PreparedStatement.getParameterMetaData(). The previous implementation created a new ParameterMetaData object each time this method is called which is quite costly if it is called from inside a loop. As the ParameterMetaData is static for a PreparedStatement it is better to create it once, cache it in the PreparedStatement object and return the cached object for next calls to PreparedStatement.getParameterMetaData(). We also now create dedicated 1-based meta data arrays, such that we no longer have to call getParamIdx(param) in methods of ParameterMetaData.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 19 Jan 2023 16:46:44 +0100 (2023-01-19)
parents
children 99baab703566
comparison
equal deleted inserted replaced
718:7138bbc6952e 719:2f42195e9c58
1 /*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 *
6 * Copyright 1997 - July 2008 CWI, August 2008 - 2023 MonetDB B.V.
7 */
8
9 package org.monetdb.jdbc;
10
11 import java.sql.ParameterMetaData;
12 import java.sql.SQLDataException;
13 import java.sql.SQLException;
14 import java.sql.Types;
15
16 /**
17 *<pre>
18 * A {@link ParameterMetaData} suitable for the MonetDB database.
19 *
20 * An object that can be used to get information about the types and properties
21 * for each parameter marker in a PreparedStatement or CallableStatement object.
22 *</pre>
23 *
24 * @author Martin van Dinther
25 * @version 1.0
26 */
27 final class MonetParameterMetaData
28 extends MonetWrapper
29 implements ParameterMetaData
30 {
31 /** The parental Connection object */
32 private final MonetConnection conn;
33
34 /** The number of parameters, it can be zero !! */
35 private final int paramCount;
36
37 /** The MonetDB type names of the parameters in the PreparedStatement */
38 private final String[] monetdbTypes;
39 /** The JDBC SQL type codes of the parameters in the PreparedStatement */
40 private final int[] JdbcSQLTypes;
41 /** The precisions of the parameters in the PreparedStatement */
42 private final int[] precisions;
43 /** The scales of the parameters in the PreparedStatement */
44 private final int[] scales;
45
46 /**
47 * Constructor backed by the given connection and metadata arrays.
48 * It is used by MonetPreparedStatement.
49 *
50 * @param connection the parent connection
51 * @param paramCount the number of parameters, it can be zero !!
52 * @param types the MonetDB type names
53 * @param jdbcTypes the JDBC SQL type codes
54 * @param precisions the precision for each parameter
55 * @param scales the scale for each parameter
56 * @throws IllegalArgumentException if called with null for one of the arguments
57 */
58 MonetParameterMetaData(
59 final MonetConnection connection,
60 final int paramcount,
61 final String[] types,
62 final int[] jdbcTypes,
63 final int[] precisions,
64 final int[] scales)
65 throws IllegalArgumentException
66 {
67 if (connection == null) {
68 throw new IllegalArgumentException("Connection may not be null!");
69 }
70 if (types == null) {
71 throw new IllegalArgumentException("MonetDB Types may not be null!");
72 }
73 if (jdbcTypes == null) {
74 throw new IllegalArgumentException("JDBC Types may not be null!");
75 }
76 if (precisions == null) {
77 throw new IllegalArgumentException("Precisions may not be null!");
78 }
79 if (scales == null) {
80 throw new IllegalArgumentException("Scales may not be null!");
81 }
82 if (types.length != precisions.length || types.length != (paramcount +1)) {
83 throw new IllegalArgumentException("Inconsistent Parameters metadata");
84 }
85 this.conn = connection;
86 this.paramCount = paramcount;
87 this.monetdbTypes = types;
88 this.JdbcSQLTypes = jdbcTypes;
89 this.precisions = precisions;
90 this.scales = scales;
91 }
92
93 /**
94 * Retrieves the number of parameters in the PreparedStatement object
95 * for which this ParameterMetaData object contains information.
96 *
97 * @return the number of parameters
98 */
99 @Override
100 public int getParameterCount() {
101 return paramCount;
102 }
103
104 /**
105 * Retrieves whether null values are allowed in the
106 * designated parameter.
107 *
108 * This is currently always unknown for MonetDB/SQL.
109 *
110 * @param param - the first parameter is 1, the second is 2, ...
111 * @return the nullability status of the given parameter;
112 * one of ParameterMetaData.parameterNoNulls,
113 * ParameterMetaData.parameterNullable, or
114 * ParameterMetaData.parameterNullableUnknown
115 */
116 @Override
117 public int isNullable(final int param) throws SQLException {
118 checkParameterIndexValidity(param);
119 return ParameterMetaData.parameterNullableUnknown;
120 }
121
122 /**
123 * Retrieves whether values for the designated parameter can
124 * be signed numbers.
125 *
126 * @param param - the first parameter is 1, the second is 2, ...
127 * @return true if so; false otherwise
128 * @throws SQLException if a database access error occurs
129 */
130 @Override
131 public boolean isSigned(final int param) throws SQLException {
132 // we can hardcode this, based on the parameter type
133 switch (getParameterType(param)) {
134 case Types.TINYINT:
135 case Types.SMALLINT:
136 case Types.INTEGER:
137 case Types.REAL:
138 case Types.FLOAT:
139 case Types.DOUBLE:
140 case Types.DECIMAL:
141 case Types.NUMERIC:
142 return true;
143 case Types.BIGINT:
144 final String monettype = getParameterTypeName(param);
145 if (monettype != null && monettype.length() == 3) {
146 // data of type oid or ptr is not signed
147 if ("oid".equals(monettype)
148 || "ptr".equals(monettype))
149 return false;
150 }
151 return true;
152 // All other types should return false
153 // case Types.BOOLEAN:
154 // case Types.DATE: // can year be negative?
155 // case Types.TIME: // can time be negative?
156 // case Types.TIME_WITH_TIMEZONE:
157 // case Types.TIMESTAMP: // can year be negative?
158 // case Types.TIMESTAMP_WITH_TIMEZONE:
159 default:
160 return false;
161 }
162 }
163
164 /**
165 * Retrieves the designated parameter's specified column size.
166 * The returned value represents the maximum column size for
167 * the given parameter.
168 * For numeric data, this is the maximum precision.
169 * For character data, this is the length in characters.
170 * For datetime datatypes, this is the length in characters
171 * of the String representation (assuming the maximum allowed
172 * precision of the fractional seconds component).
173 * For binary data, this is the length in bytes.
174 * For the ROWID datatype, this is the length in bytes.
175 * 0 is returned for data types where the column size is not applicable.
176 *
177 * @param param - the first parameter is 1, the second is 2, ...
178 * @return precision
179 * @throws SQLException if a database access error occurs
180 */
181 @Override
182 public int getPrecision(final int param) throws SQLException {
183 switch (getParameterType(param)) {
184 case Types.BIGINT:
185 return 19;
186 case Types.INTEGER:
187 return 10;
188 case Types.SMALLINT:
189 return 5;
190 case Types.TINYINT:
191 return 3;
192 case Types.REAL:
193 return 7;
194 case Types.FLOAT:
195 case Types.DOUBLE:
196 return 15;
197 case Types.DECIMAL:
198 case Types.NUMERIC:
199 // these data types have a variable precision (max precision is 38)
200 try {
201 return precisions[param];
202 } catch (IndexOutOfBoundsException e) {
203 throw newSQLInvalidParameterIndexException(param);
204 }
205 case Types.CHAR:
206 case Types.VARCHAR:
207 case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness
208 case Types.CLOB:
209 // these data types have a variable length
210 try {
211 return precisions[param];
212 } catch (IndexOutOfBoundsException e) {
213 throw newSQLInvalidParameterIndexException(param);
214 }
215 case Types.BINARY:
216 case Types.VARBINARY:
217 case Types.BLOB:
218 // these data types have a variable length
219 // It expect number of bytes, not number of hex chars
220 try {
221 return precisions[param];
222 } catch (IndexOutOfBoundsException e) {
223 throw newSQLInvalidParameterIndexException(param);
224 }
225 case Types.DATE:
226 return 10; // 2020-10-08
227 case Types.TIME:
228 return 15; // 21:51:34.399753
229 case Types.TIME_WITH_TIMEZONE:
230 return 21; // 21:51:34.399753+02:00
231 case Types.TIMESTAMP:
232 return 26; // 2020-10-08 21:51:34.399753
233 case Types.TIMESTAMP_WITH_TIMEZONE:
234 return 32; // 2020-10-08 21:51:34.399753+02:00
235 case Types.BOOLEAN:
236 return 1;
237 default:
238 // All other types should return 0
239 return 0;
240 }
241 }
242
243 /**
244 * Retrieves the designated parameter's number of digits to
245 * right of the decimal point.
246 * 0 is returned for data types where the scale is not applicable.
247 *
248 * @param param - the first parameter is 1, the second is 2, ...
249 * @return scale
250 * @throws SQLException if a database access error occurs
251 */
252 @Override
253 public int getScale(final int param) throws SQLException {
254 checkParameterIndexValidity(param);
255 try {
256 return scales[param];
257 } catch (IndexOutOfBoundsException e) {
258 throw newSQLInvalidParameterIndexException(param);
259 }
260 }
261
262 /**
263 * Retrieves the designated parameter's SQL type.
264 *
265 * @param param - the first parameter is 1, the second is 2, ...
266 * @return SQL type from java.sql.Types
267 * @throws SQLException if a database access error occurs
268 */
269 @Override
270 public int getParameterType(final int param) throws SQLException {
271 checkParameterIndexValidity(param);
272 try {
273 return JdbcSQLTypes[param];
274 } catch (IndexOutOfBoundsException e) {
275 throw newSQLInvalidParameterIndexException(param);
276 }
277 }
278
279 /**
280 * Retrieves the designated parameter's database-specific type name.
281 *
282 * @param param - the first parameter is 1, the second is 2, ...
283 * @return type the name used by the database. If the
284 * parameter type is a user-defined type, then a
285 * fully-qualified type name is returned.
286 * @throws SQLException if a database access error occurs
287 */
288 @Override
289 public String getParameterTypeName(final int param) throws SQLException {
290 checkParameterIndexValidity(param);
291 try {
292 final String monettype = monetdbTypes[param];
293 if (monettype.endsWith("_interval")) {
294 /* convert the interval type names to valid SQL data type names */
295 if ("day_interval".equals(monettype))
296 return "interval day";
297 if ("month_interval".equals(monettype))
298 return "interval month";
299 if ("sec_interval".equals(monettype))
300 return "interval second";
301 }
302 return monettype;
303 } catch (IndexOutOfBoundsException e) {
304 throw newSQLInvalidParameterIndexException(param);
305 }
306 }
307
308 /**
309 * Retrieves the fully-qualified name of the Java class whose instances
310 * should be passed to the method PreparedStatement.setObject.
311 *
312 * @param param - the first parameter is 1, the second is 2, ...
313 * @return the fully-qualified name of the class in the Java
314 * programming language that would be used by the
315 * method PreparedStatement.setObject to set the
316 * value in the specified parameter. This is the
317 * class name used for custom mapping.
318 * @throws SQLException if a database access error occurs
319 */
320 @Override
321 public String getParameterClassName(final int param) throws SQLException {
322 final String MonetDBType = getParameterTypeName(param);
323 final java.util.Map<String,Class<?>> map = conn.getTypeMap();
324 final Class<?> type;
325 if (map != null && map.containsKey(MonetDBType)) {
326 type = (Class)map.get(MonetDBType);
327 } else {
328 type = MonetDriver.getClassForType(getParameterType(param));
329 }
330 if (type != null) {
331 return type.getCanonicalName();
332 }
333 throw new SQLException("parameter type mapping null: " + MonetDBType, "M0M03");
334 }
335
336 /**
337 * Retrieves the designated parameter's mode.
338 * For MonetDB/SQL we currently only support INput parameters.
339 *
340 * @param param - the first parameter is 1, the second is 2, ...
341 * @return mode of the parameter; one of
342 * ParameterMetaData.parameterModeIn,
343 * ParameterMetaData.parameterModeOut,
344 * ParameterMetaData.parameterModeInOut or
345 * ParameterMetaData.parameterModeUnknown.
346 */
347 @Override
348 public int getParameterMode(final int param) throws SQLException {
349 checkParameterIndexValidity(param);
350 return ParameterMetaData.parameterModeIn;
351 }
352
353
354 /**
355 * A private utility method to check validity of parameter index number
356 *
357 * @param param - the first parameter is 1, the second is 2, ...
358 * @throws SQLDataException when invalid parameter index number
359 */
360 private final void checkParameterIndexValidity(final int param) throws SQLDataException {
361 if (param < 1 || param > paramCount)
362 throw newSQLInvalidParameterIndexException(param);
363 }
364
365 /**
366 * Small helper method that formats the "Invalid Parameter Index number ..."
367 * message and creates a new SQLDataException object whose SQLState is set
368 * to "22010": invalid indicator parameter value.
369 *
370 * @param paramIdx the parameter index number
371 * @return a new created SQLDataException object with SQLState 22010
372 */
373 private final SQLDataException newSQLInvalidParameterIndexException(final int paramIdx) {
374 return new SQLDataException("Invalid Parameter Index number: " + paramIdx, "22010");
375 }
376 }