comparison src/main/java/org/monetdb/jdbc/MonetConnection.java @ 665:8f7d51c478df

Improved implementation of methods ResultSetMetaData.getPrecision() and ResultSetMetaData.getScale(). They now use the Mapi header information as returned by the server when "sizeheader 1" is enabled. This "typesizes" header returns more accurate precision and scale values for columns of types DECIMAL, NUMERIC, CHAR, VARCHAR, CLOB, JSON, URL and BLOB. Also we no longer have to generate and execute a meta data query to retrieve this information, so it also is faster now.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 20 Oct 2022 18:09:07 +0200 (2022-10-20)
parents 7ec8d469fd0b
children 2448ce017593
comparison
equal deleted inserted replaced
664:a6592430c8fc 665:8f7d51c478df
419 if (offsetMinutes < 10) 419 if (offsetMinutes < 10)
420 tz.append('0'); 420 tz.append('0');
421 tz.append(offsetMinutes).append("' HOUR TO MINUTE"); 421 tz.append(offsetMinutes).append("' HOUR TO MINUTE");
422 sendIndependentCommand(tz.toString()); 422 sendIndependentCommand(tz.toString());
423 } 423 }
424
425 // set sizeheader to 1 to enable sending "typesizes" info by the server (see mapi_set_size_header() in mapi.c)
426 sendControlCommand("sizeheader 1");
424 } 427 }
425 428
426 // we're absolutely not closed, since we're brand new 429 // we're absolutely not closed, since we're brand new
427 closed = false; 430 closed = false;
428 } 431 }
2197 * The ResultSetResponse represents a tabular result sent by the 2200 * The ResultSetResponse represents a tabular result sent by the
2198 * server. This is typically an SQL table. The MAPI headers of the 2201 * server. This is typically an SQL table. The MAPI headers of the
2199 * Response look like: 2202 * Response look like:
2200 * <pre> 2203 * <pre>
2201 * &amp;1 1 28 2 10 2204 * &amp;1 1 28 2 10
2202 * # name, value # name 2205 * % sys.props, sys.props # table_name
2203 * # varchar, varchar # type 2206 * % name, value # name
2207 * % varchar, int # type
2208 * % 15, 3 # length
2209 * % 60 0, 32 0 # typesizes
2204 * </pre> 2210 * </pre>
2205 * there the first line consists out of<br /> 2211 * there the first line consists out of<br />
2206 * <tt>&amp;"qt" "id" "tc" "cc" "rc"</tt>. 2212 * <tt>&amp;"qt" "id" "tc" "cc" "rc"</tt>.
2207 */ 2213 */
2208 // {{{ ResultSetResponse class implementation 2214 // {{{ ResultSetResponse class implementation
2213 public final long tuplecount; 2219 public final long tuplecount;
2214 /** The numbers of rows to retrieve per DataBlockResponse */ 2220 /** The numbers of rows to retrieve per DataBlockResponse */
2215 private int cacheSize; 2221 private int cacheSize;
2216 /** The table ID of this result */ 2222 /** The table ID of this result */
2217 public final int id; 2223 public final int id;
2224
2225 /** arrays for the resultset columns metadata */
2218 /** The names of the columns in this result */ 2226 /** The names of the columns in this result */
2219 private String[] name; 2227 private String[] name;
2220 /** The types of the columns in this result */ 2228 /** The types of the columns in this result */
2221 private String[] type; 2229 private String[] type;
2222 /** The max string length for each column in this result */ 2230 /** The max string length for each column in this result */
2223 private int[] columnLengths; 2231 private int[] columnLengths;
2232 /** The precision for each column in this result */
2233 private int[] colPrecisions;
2234 /** The scale for each column in this result */
2235 private int[] colScales;
2224 /** The table for each column in this result */ 2236 /** The table for each column in this result */
2225 private String[] tableNames; 2237 private String[] tableNames;
2226 /** The schema for each column in this result */ 2238 /** The schema for each column in this result */
2227 private String[] schemaNames; 2239 private String[] schemaNames;
2240
2228 /** The query sequence number */ 2241 /** The query sequence number */
2229 private final int seqnr; 2242 private final int seqnr;
2230 /** A List of result blocks (chunks of size fetchSize/cacheSize) */ 2243 /** A List of result blocks (chunks of size fetchSize/cacheSize) */
2231 private DataBlockResponse[] resultBlocks; 2244 private DataBlockResponse[] resultBlocks;
2232 2245
2250 private final boolean[] isSet; 2263 private final boolean[] isSet;
2251 private static final int NAMES = 0; 2264 private static final int NAMES = 0;
2252 private static final int TYPES = 1; 2265 private static final int TYPES = 1;
2253 private static final int TABLES = 2; 2266 private static final int TABLES = 2;
2254 private static final int LENS = 3; 2267 private static final int LENS = 3;
2255 2268 private static final int TYPESIZES = 4;
2256 2269
2257 /** 2270 /**
2258 * Sole constructor, which requires a MonetConnection parent to 2271 * Sole constructor, which requires a MonetConnection parent to
2259 * be given. 2272 * be given.
2260 * 2273 *
2273 final int rowcount, 2286 final int rowcount,
2274 final MonetConnection.ResponseList parent, 2287 final MonetConnection.ResponseList parent,
2275 final int seq) 2288 final int seq)
2276 throws SQLException 2289 throws SQLException
2277 { 2290 {
2278 isSet = new boolean[4]; 2291 isSet = new boolean[5];
2279 this.parent = parent; 2292 this.parent = parent;
2280 if (parent.cachesize == 0) { 2293 if (parent.cachesize == 0) {
2281 /* Below we have to calculate how many "chunks" we need 2294 /* Below we have to calculate how many "chunks" we need
2282 * to allocate to store the entire result. However, if 2295 * to allocate to store the entire result. However, if
2283 * the user didn't set a cache size, as in this case, we 2296 * the user didn't set a cache size, as in this case, we
2319 * 2332 *
2320 * @param tmpLine the string that contains the header 2333 * @param tmpLine the string that contains the header
2321 * @return a non-null String if the header cannot be parsed or 2334 * @return a non-null String if the header cannot be parsed or
2322 * is unknown 2335 * is unknown
2323 */ 2336 */
2324 // {{{ addLine
2325 @Override 2337 @Override
2326 public String addLine(final String tmpLine, final LineType linetype) { 2338 public String addLine(final String tmpLine, final LineType linetype) {
2327 if (isSet[LENS] && isSet[TYPES] && isSet[TABLES] && isSet[NAMES]) { 2339 // System.out.println("In ResultSetResponse.addLine(line, type: " + linetype + ") line: " + tmpLine);
2340 if (linetype == LineType.RESULT ||
2341 (isSet[LENS] && isSet[TYPES] && isSet[TABLES] && isSet[NAMES] && isSet[TYPESIZES])) {
2342 if (!isSet[TYPESIZES])
2343 // this is needed to get proper output when processing a: DEBUG SQL-statement
2344 isSet[TYPESIZES] = true;
2328 return resultBlocks[0].addLine(tmpLine, linetype); 2345 return resultBlocks[0].addLine(tmpLine, linetype);
2329 } 2346 }
2330 2347
2331 if (linetype != LineType.HEADER) 2348 if (linetype != LineType.HEADER) {
2332 return "header expected, got: " + tmpLine; 2349 if (!isSet[TYPESIZES])
2350 isSet[TYPESIZES] = true;
2351 return "Header expected, got: " + tmpLine;
2352 }
2333 2353
2334 // depending on the name of the header, we continue 2354 // depending on the name of the header, we continue
2335 try { 2355 try {
2336 switch (hlp.parse(tmpLine)) { 2356 switch (hlp.parse(tmpLine)) {
2337 case HeaderLineParser.NAME: 2357 case HeaderLineParser.NAME:
2338 name = hlp.values.clone(); 2358 name = hlp.values.clone();
2339 isSet[NAMES] = true; 2359 isSet[NAMES] = true;
2340 break; 2360 break;
2341 case HeaderLineParser.LENGTH: 2361 case HeaderLineParser.LENGTH:
2342 columnLengths = hlp.intValues.clone(); 2362 columnLengths = hlp.intValues.clone();
2343 isSet[LENS] = true; 2363 isSet[LENS] = true;
2344 break; 2364 break;
2345 case HeaderLineParser.TYPE: 2365 case HeaderLineParser.TYPE:
2346 type = hlp.values.clone(); 2366 type = hlp.values.clone();
2347 isSet[TYPES] = true; 2367 isSet[TYPES] = true;
2348 break; 2368 break;
2349 case HeaderLineParser.TABLE: 2369 case HeaderLineParser.TABLE:
2350 { 2370 {
2351 tableNames = hlp.values.clone(); 2371 tableNames = hlp.values.clone();
2352 final int array_size = tableNames.length; 2372 final int array_size = tableNames.length;
2353 schemaNames = new String[array_size]; 2373 schemaNames = new String[array_size];
2366 schemaNames[i] = ""; 2386 schemaNames[i] = "";
2367 tableNames[i] = ""; 2387 tableNames[i] = "";
2368 } 2388 }
2369 } 2389 }
2370 isSet[TABLES] = true; 2390 isSet[TABLES] = true;
2391 break;
2371 } 2392 }
2372 break; 2393 case HeaderLineParser.TYPESIZES:
2394 {
2395 // System.out.println("In ResultSetResponse.addLine() case HeaderLineParser.TYPESIZES: values: " + hlp.values[0]);
2396 final int array_size = hlp.values.length;
2397 colPrecisions = new int[array_size];
2398 colScales = new int[array_size];
2399 // extract the precision and scale integer numbers from the string
2400 for (int i = 0; i < array_size; i++) {
2401 String ps = hlp.values[i];
2402 if (ps != null) {
2403 try {
2404 int separator = ps.indexOf(' ');
2405 if (separator > 0) {
2406 colPrecisions[i] = Integer.parseInt(ps.substring(0, separator));
2407 colScales[i] = Integer.parseInt(ps.substring(separator +1));
2408 } else {
2409 colPrecisions[i] = Integer.parseInt(ps);
2410 colScales[i] = 0;
2411 }
2412 } catch (NumberFormatException nfe) {
2413 return nfe.getMessage();
2414 }
2415 } else {
2416 colPrecisions[i] = 1;
2417 colScales[i] = 0;
2418 }
2419 }
2420 isSet[TYPESIZES] = true;
2421 break;
2422 }
2373 } 2423 }
2374 } catch (MCLParseException e) { 2424 } catch (MCLParseException e) {
2375 return e.getMessage(); 2425 return e.getMessage();
2376 } 2426 }
2377 2427
2378 // all is well 2428 // all is well
2379 return null; 2429 return null;
2380 } 2430 }
2381 // }}}
2382 2431
2383 /** 2432 /**
2384 * Returns whether this ResultSetResponse needs more lines. 2433 * Returns whether this ResultSetResponse needs more lines.
2385 * This method returns true if not all headers are set, or the 2434 * This method returns true if not all headers are set, or the
2386 * first DataBlockResponse reports to want more. 2435 * first DataBlockResponse reports to want more.
2387 */ 2436 */
2388 @Override 2437 @Override
2389 public boolean wantsMore() { 2438 public boolean wantsMore() {
2390 if (isSet[LENS] && isSet[TYPES] && isSet[TABLES] && isSet[NAMES]) { 2439 if (isSet[LENS] && isSet[TYPES] && isSet[TABLES] && isSet[NAMES] && isSet[TYPESIZES]) {
2391 return resultBlocks[0].wantsMore(); 2440 return resultBlocks[0].wantsMore();
2392 } else { 2441 }
2393 return true; 2442 return true;
2394 }
2395 } 2443 }
2396 2444
2397 /** 2445 /**
2398 * Returns an array of Strings containing the values between 2446 * Returns an array of Strings containing the values between
2399 * ',\t' separators. 2447 * ',\t' separators.
2445 final StringBuilder err = new StringBuilder(99); 2493 final StringBuilder err = new StringBuilder(99);
2446 if (!isSet[NAMES]) err.append("name header missing\n"); 2494 if (!isSet[NAMES]) err.append("name header missing\n");
2447 if (!isSet[TYPES]) err.append("type header missing\n"); 2495 if (!isSet[TYPES]) err.append("type header missing\n");
2448 if (!isSet[TABLES]) err.append("table name header missing\n"); 2496 if (!isSet[TABLES]) err.append("table name header missing\n");
2449 if (!isSet[LENS]) err.append("column width header missing\n"); 2497 if (!isSet[LENS]) err.append("column width header missing\n");
2498 if (!isSet[TYPESIZES]) err.append("column precision and scale header missing\n");
2450 if (err.length() > 0) 2499 if (err.length() > 0)
2451 throw new SQLException(err.toString(), "M0M10"); 2500 throw new SQLException(err.toString(), "M0M10");
2452 } 2501 }
2453 */ 2502 */
2454 2503
2469 String[] getTypes() { 2518 String[] getTypes() {
2470 return type; 2519 return type;
2471 } 2520 }
2472 2521
2473 /** 2522 /**
2474 * Returns the tables of the columns 2523 * Returns the table names of the columns
2475 * 2524 *
2476 * @return the tables of the columns 2525 * @return the table names of the columns
2477 */ 2526 */
2478 String[] getTableNames() { 2527 String[] getTableNames() {
2479 return tableNames; 2528 return tableNames;
2480 } 2529 }
2481 2530
2482 /** 2531 /**
2483 * Returns the schemas of the columns 2532 * Returns the schema names of the columns
2484 * 2533 *
2485 * @return the schemas of the columns 2534 * @return the schema names of the columns
2486 */ 2535 */
2487 String[] getSchemaNames() { 2536 String[] getSchemaNames() {
2488 return schemaNames; 2537 return schemaNames;
2489 } 2538 }
2490 2539
2491 /** 2540 /**
2492 * Returns the lengths of the columns 2541 * Returns the display lengths of the columns
2493 * 2542 *
2494 * @return the lengths of the columns 2543 * @return the display lengths of the columns
2495 */ 2544 */
2496 int[] getColumnLengths() { 2545 int[] getColumnLengths() {
2497 return columnLengths; 2546 return columnLengths;
2547 }
2548
2549 /**
2550 * Returns the precisions of the columns
2551 *
2552 * @return the precisions of the columns, it can return null
2553 */
2554 int[] getColumnPrecisions() {
2555 return colPrecisions;
2556 }
2557
2558 /**
2559 * Returns the scales of the columns (0 when scale is not applicable)
2560 *
2561 * @return the scales of the columns, it can return null
2562 */
2563 int[] getColumnScales() {
2564 return colScales;
2498 } 2565 }
2499 2566
2500 /** 2567 /**
2501 * Returns the cache size used within this Response 2568 * Returns the cache size used within this Response
2502 * 2569 *
2635 r.close(); 2702 r.close();
2636 } 2703 }
2637 name = null; 2704 name = null;
2638 type = null; 2705 type = null;
2639 columnLengths = null; 2706 columnLengths = null;
2707 colPrecisions = null;
2708 colScales = null;
2640 tableNames = null; 2709 tableNames = null;
2641 schemaNames = null; 2710 schemaNames = null;
2642 resultBlocks = null; 2711 resultBlocks = null;
2643 closed = true; 2712 closed = true;
2644 } 2713 }
3210 } else { 3279 } else {
3211 error = tmpLine.substring(1); 3280 error = tmpLine.substring(1);
3212 } 3281 }
3213 break; 3282 break;
3214 } // end of switch (linetype) 3283 } // end of switch (linetype)
3215 } // end of while (linetype != BufferedMCLReader.PROMPT) 3284 } // end of while (linetype != LineType.PROMPT)
3216 } // end of synchronized (server) 3285 } // end of synchronized (server)
3217 3286
3218 if (error != null) { 3287 if (error != null) {
3219 SQLException ret = null; 3288 SQLException ret = null;
3220 final String[] errors = error.split("\n"); 3289 final String[] errors = error.split("\n");