comparison src/main/java/org/monetdb/jdbc/MonetConnection.java @ 391:f523727db392

Moved Java classes from packages starting with nl.cwi.monetdb.* to package org.monetdb.* This naming complies to the Java Package Naming convention as MonetDB's main website is www.monetdb.org.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 12 Nov 2020 22:02:01 +0100 (2020-11-12)
parents src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java@11c30e3b7966
children bf9f6b6ecf40
comparison
equal deleted inserted replaced
390:6199e0be3c6e 391:f523727db392
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 - 2020 MonetDB B.V.
7 */
8
9 package org.monetdb.jdbc;
10
11 import java.io.File;
12 import java.io.IOException;
13 import java.net.SocketException;
14 import java.net.SocketTimeoutException;
15 import java.sql.CallableStatement;
16 import java.sql.Connection;
17 import java.sql.DatabaseMetaData;
18 import java.sql.PreparedStatement;
19 import java.sql.ResultSet;
20 import java.sql.SQLClientInfoException;
21 import java.sql.SQLException;
22 import java.sql.SQLFeatureNotSupportedException;
23 import java.sql.SQLNonTransientConnectionException;
24 import java.sql.SQLWarning;
25 import java.sql.Savepoint;
26 import java.sql.Statement;
27 import java.util.ArrayList;
28 import java.util.Calendar;
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.Map.Entry;
32 import java.util.Properties;
33 import java.util.WeakHashMap;
34 import java.util.concurrent.Executor;
35
36 import org.monetdb.mcl.io.BufferedMCLReader;
37 import org.monetdb.mcl.io.BufferedMCLWriter;
38 import org.monetdb.mcl.net.MapiSocket;
39 import org.monetdb.mcl.parser.HeaderLineParser;
40 import org.monetdb.mcl.parser.MCLParseException;
41 import org.monetdb.mcl.parser.StartOfHeaderParser;
42
43 /**
44 * A {@link Connection} suitable for the MonetDB database.
45 *
46 * This connection represents a connection (session) to a MonetDB
47 * database. SQL statements are executed and results are returned within
48 * the context of a connection. This Connection object holds a physical
49 * connection to the MonetDB database.
50 *
51 * A Connection object's database should able to provide information
52 * describing its tables, its supported SQL grammar, its stored
53 * procedures, the capabilities of this connection, and so on. This
54 * information is obtained with the getMetaData method.
55 *
56 * Note: By default a Connection object is in auto-commit mode, which
57 * means that it automatically commits changes after executing each
58 * statement. If auto-commit mode has been disabled, the method commit
59 * must be called explicitly in order to commit changes; otherwise,
60 * database changes will not be saved.
61 *
62 * The current state of this connection is that it nearly implements the
63 * whole Connection interface.
64 *
65 * @author Fabian Groffen
66 * @author Martin van Dinther
67 * @version 1.5
68 */
69 public class MonetConnection
70 extends MonetWrapper
71 implements Connection, AutoCloseable
72 {
73 /** the successful processed input properties */
74 private final Properties conn_props = new Properties();
75
76 /** The hostname to connect to */
77 private final String hostname;
78 /** The port to connect on the host to */
79 private int port = 0;
80 /** The database to use (currently not used) */
81 private final String database;
82 /** The username to use when authenticating */
83 private final String username;
84 /** The password to use when authenticating */
85 private final String password;
86
87 /** A connection to mserver5 using a TCP socket */
88 private final MapiSocket server;
89 /** The Reader from the server */
90 private final BufferedMCLReader in;
91 /** The Writer to the server */
92 private final BufferedMCLWriter out;
93
94 /** A StartOfHeaderParser declared for reuse. */
95 private StartOfHeaderParser sohp = new StartOfHeaderParser();
96
97 /** Whether this Connection is closed (and cannot be used anymore) */
98 private boolean closed;
99
100 /** Whether this Connection is in autocommit mode */
101 private boolean autoCommit = true;
102
103 /** The stack of warnings for this Connection object */
104 private SQLWarning warnings = null;
105
106 /** The Connection specific mapping of user defined types to Java types */
107 private Map<String,Class<?>> typeMap = new HashMap<String,Class<?>>() {
108 private static final long serialVersionUID = 1L;
109 {
110 put("inet", org.monetdb.jdbc.types.INET.class);
111 put("url", org.monetdb.jdbc.types.URL.class);
112 }
113 };
114
115 // See javadoc for documentation about WeakHashMap if you don't know what
116 // it does !!!NOW!!! (only when you deal with it of course)
117 /** A Map containing all (active) Statements created from this Connection */
118 private WeakHashMap<Statement,?> statements = new WeakHashMap<Statement, Object>();
119
120 /** The number of results we receive from the server at once */
121 private int curReplySize = -1; // the server by default uses -1 (all)
122
123 /** A template to apply to each query (like pre and post fixes), filled in constructor */
124 // note: it is made public to the package as queryTempl[2] is used from MonetStatement
125 final String[] queryTempl = new String[3]; // pre, post, sep
126
127 /** A template to apply to each command (like pre and post fixes), filled in constructor */
128 private final String[] commandTempl = new String[2]; // pre, post
129
130 /** the SQL language */
131 private static final int LANG_SQL = 0;
132 /** the MAL language (officially *NOT* supported) */
133 private static final int LANG_MAL = 3;
134 /** an unknown language */
135 private static final int LANG_UNKNOWN = -1;
136 /** The language which is used */
137 private final int lang;
138
139 /** Whether or not BLOB is mapped to Types.VARBINARY instead of Types.BLOB within this connection */
140 private boolean treatBlobAsVarBinary = true;
141 /** Whether or not CLOB is mapped to Types.VARCHAR instead of Types.CLOB within this connection */
142 private boolean treatClobAsVarChar = true;
143
144 /** The last set query timeout on the server as used by Statement, PreparedStatement and CallableStatement */
145 protected int lastSetQueryTimeout = 0; // 0 means no timeout, which is the default on the server
146
147
148 /**
149 * Constructor of a Connection for MonetDB. At this moment the
150 * current implementation limits itself to storing the given host,
151 * database, username and password for later use by the
152 * createStatement() call. This constructor is only accessible to
153 * classes from the jdbc package.
154 *
155 * @param props a Property hashtable holding the properties needed for connecting
156 * @throws SQLException if a database error occurs
157 * @throws IllegalArgumentException is one of the arguments is null or empty
158 */
159 MonetConnection(final Properties props)
160 throws SQLException, IllegalArgumentException
161 {
162 // for debug: System.out.println("New connection object. Received properties are: " + props.toString());
163 // get supported property values from the props argument.
164 // When a value is found add it to the internal conn_props list for use by getClientInfo().
165 this.hostname = props.getProperty("host");
166 if (this.hostname != null)
167 conn_props.setProperty("host", this.hostname);
168
169 final String port_prop = props.getProperty("port");
170 if (port_prop != null) {
171 try {
172 this.port = Integer.parseInt(port_prop);
173 } catch (NumberFormatException e) {
174 addWarning("Unable to parse port number from: " + port_prop, "M1M05");
175 }
176 conn_props.setProperty("port", Integer.toString(this.port));
177 }
178
179 this.database = props.getProperty("database");
180 if (this.database != null)
181 conn_props.setProperty("database", this.database);
182
183 this.username = props.getProperty("user");
184 if (this.username != null)
185 conn_props.setProperty("user", this.username);
186
187 this.password = props.getProperty("password");
188 if (this.password != null)
189 conn_props.setProperty("password", this.password);
190
191 String language = props.getProperty("language");
192 if (language != null)
193 conn_props.setProperty("language", language);
194
195 boolean debug = false;
196 String debug_prop = props.getProperty("debug");
197 if (debug_prop != null) {
198 debug = Boolean.parseBoolean(debug_prop);
199 conn_props.setProperty("debug", Boolean.toString(debug));
200 }
201
202 final String hash = props.getProperty("hash");
203 if (hash != null)
204 conn_props.setProperty("hash", hash);
205
206 final String treatBlobAsVarBinary_prop = props.getProperty("treat_blob_as_binary");
207 if (treatBlobAsVarBinary_prop != null) {
208 treatBlobAsVarBinary = Boolean.parseBoolean(treatBlobAsVarBinary_prop);
209 conn_props.setProperty("treat_blob_as_binary", Boolean.toString(treatBlobAsVarBinary));
210 if (treatBlobAsVarBinary)
211 typeMap.put("blob", Byte[].class);
212 }
213
214 final String treatClobAsVarChar_prop = props.getProperty("treat_clob_as_varchar");
215 if (treatClobAsVarChar_prop != null) {
216 treatClobAsVarChar = Boolean.parseBoolean(treatClobAsVarChar_prop);
217 conn_props.setProperty("treat_clob_as_varchar", Boolean.toString(treatClobAsVarChar));
218 if (treatClobAsVarChar)
219 typeMap.put("clob", String.class);
220 }
221
222 int sockTimeout = 0;
223 final String so_timeout_prop = props.getProperty("so_timeout");
224 if (so_timeout_prop != null) {
225 try {
226 sockTimeout = Integer.parseInt(so_timeout_prop);
227 if (sockTimeout < 0) {
228 addWarning("Negative socket timeout not allowed. Value ignored", "M1M05");
229 sockTimeout = 0;
230 }
231 } catch (NumberFormatException e) {
232 addWarning("Unable to parse socket timeout number from: " + so_timeout_prop, "M1M05");
233 }
234 conn_props.setProperty("so_timeout", Integer.toString(sockTimeout));
235 }
236
237 // check mandatory input arguments
238 if (hostname == null || hostname.isEmpty())
239 throw new IllegalArgumentException("Missing or empty host name");
240 if (port <= 0 || port > 65535)
241 throw new IllegalArgumentException("Invalid port number: " + port
242 + ". It should not be " + (port < 0 ? "negative" : (port > 65535 ? "larger than 65535" : "0")));
243 if (username == null || username.isEmpty())
244 throw new IllegalArgumentException("Missing or empty user name");
245 if (password == null || password.isEmpty())
246 throw new IllegalArgumentException("Missing or empty password");
247 if (language == null || language.isEmpty()) {
248 // fallback to default language: sql
249 language = "sql";
250 addWarning("No language specified, defaulting to 'sql'", "M1M05");
251 }
252
253 server = new MapiSocket();
254 if (hash != null)
255 server.setHash(hash);
256 if (database != null)
257 server.setDatabase(database);
258 server.setLanguage(language);
259
260 // we're debugging here... uhm, should be off in real life
261 if (debug) {
262 try {
263 final String fname = props.getProperty("logfile", "monet_" + System.currentTimeMillis() + ".log");
264 File f = new File(fname);
265
266 int ext = fname.lastIndexOf('.');
267 if (ext < 0)
268 ext = fname.length();
269 final String pre = fname.substring(0, ext);
270 final String suf = fname.substring(ext);
271
272 for (int i = 1; f.exists(); i++) {
273 f = new File(pre + "-" + i + suf);
274 }
275
276 server.debug(f.getAbsolutePath()); // enable logging on the MapiSocket level
277 } catch (IOException ex) {
278 throw new SQLNonTransientConnectionException("Opening logfile failed: " + ex.getMessage(), "08M01");
279 }
280 }
281
282 try {
283 final java.util.List<String> warnings = server.connect(hostname, port, username, password);
284 for (String warning : warnings) {
285 addWarning(warning, "01M02");
286 }
287
288 // apply NetworkTimeout value from legacy (pre 4.1) driver
289 // so_timeout calls
290 server.setSoTimeout(sockTimeout);
291
292 in = server.getReader();
293 out = server.getWriter();
294
295 final String error = in.waitForPrompt();
296 if (error != null)
297 throw new SQLNonTransientConnectionException((error.length() > 6) ? error.substring(6) : error, "08001");
298 } catch (java.net.UnknownHostException e) {
299 throw new SQLNonTransientConnectionException("Unknown Host (" + hostname + "): " + e.getMessage(), "08006");
300 } catch (IOException e) {
301 throw new SQLNonTransientConnectionException("Unable to connect (" + hostname + ":" + port + "): " + e.getMessage(), "08006");
302 } catch (MCLParseException e) {
303 throw new SQLNonTransientConnectionException(e.getMessage(), "08001");
304 } catch (org.monetdb.mcl.MCLException e) {
305 final String[] connex = e.getMessage().split("\n");
306 final SQLException sqle = new SQLNonTransientConnectionException(connex[0], "08001", e);
307 for (int i = 1; i < connex.length; i++) {
308 sqle.setNextException(new SQLNonTransientConnectionException(connex[1], "08001"));
309 }
310 throw sqle;
311 }
312
313 // we seem to have managed to log in, let's store the
314 // language used and language specific query templates
315 if ("sql".equals(language)) {
316 lang = LANG_SQL;
317
318 queryTempl[0] = "s"; // pre
319 queryTempl[1] = "\n;"; // post
320 queryTempl[2] = "\n;\n"; // separator
321
322 commandTempl[0] = "X"; // pre
323 commandTempl[1] = ""; // post
324 //commandTempl[2] = "\nX"; // separator (is not used)
325 } else if ("mal".equals(language)) {
326 lang = LANG_MAL;
327
328 queryTempl[0] = ""; // pre
329 queryTempl[1] = ";\n"; // post
330 queryTempl[2] = ";\n"; // separator
331
332 commandTempl[0] = ""; // pre
333 commandTempl[1] = ""; // post
334 //commandTempl[2] = ""; // separator (is not used)
335 } else {
336 lang = LANG_UNKNOWN;
337 }
338
339 // the following initialisers are only valid when the language is SQL...
340 if (lang == LANG_SQL) {
341 // enable auto commit
342 setAutoCommit(true);
343
344 // set our time zone on the server
345 final Calendar cal = Calendar.getInstance();
346 int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
347 offset /= (60 * 1000); // milliseconds to minutes
348 String tz = offset < 0 ? "-" : "+";
349 tz += (Math.abs(offset) / 60 < 10 ? "0" : "") + (Math.abs(offset) / 60) + ":";
350 offset -= (offset / 60) * 60;
351 tz += (offset < 10 ? "0" : "") + offset;
352 sendIndependentCommand("SET TIME ZONE INTERVAL '" + tz + "' HOUR TO MINUTE");
353 }
354
355 // we're absolutely not closed, since we're brand new
356 closed = false;
357 }
358
359 //== methods of interface Connection
360
361 /**
362 * Clears all warnings reported for this Connection object. After a
363 * call to this method, the method getWarnings returns null until a
364 * new warning is reported for this Connection object.
365 */
366 @Override
367 public void clearWarnings() {
368 warnings = null;
369 }
370
371 /**
372 * Releases this Connection object's database and JDBC resources
373 * immediately instead of waiting for them to be automatically
374 * released. All Statements created from this Connection will be
375 * closed when this method is called.
376 *
377 * Calling the method close on a Connection object that is already
378 * closed is a no-op.
379 */
380 @Override
381 public void close() {
382 synchronized (server) {
383 for (Statement st : statements.keySet()) {
384 try {
385 st.close();
386 } catch (SQLException e) {
387 // better luck next time!
388 }
389 }
390 // close the socket
391 server.close();
392 // report ourselves as closed
393 closed = true;
394 }
395 }
396
397 /**
398 * Makes all changes made since the previous commit/rollback
399 * permanent and releases any database locks currently held by this
400 * Connection object. This method should be used only when
401 * auto-commit mode has been disabled.
402 *
403 * @throws SQLException if a database access error occurs or this
404 * Connection object is in auto-commit mode
405 * @see #setAutoCommit(boolean)
406 */
407 @Override
408 public void commit() throws SQLException {
409 // note: can't use sendIndependentCommand here because we need
410 // to process the auto_commit state the server gives
411 sendTransactionCommand("COMMIT");
412 }
413
414 /**
415 * Creates a Statement object for sending SQL statements to the
416 * database. SQL statements without parameters are normally
417 * executed using Statement objects. If the same SQL statement is
418 * executed many times, it may be more efficient to use a
419 * PreparedStatement object.
420 *
421 * Result sets created using the returned Statement object will by
422 * default be type TYPE_FORWARD_ONLY and have a concurrency level of
423 * CONCUR_READ_ONLY and a holdability of HOLD_CURSORS_OVER_COMMIT.
424 *
425 * @return a new default Statement object
426 * @throws SQLException if a database access error occurs
427 */
428 @Override
429 public Statement createStatement() throws SQLException {
430 return createStatement(MonetResultSet.DEF_RESULTSETTYPE, MonetResultSet.DEF_CONCURRENCY, MonetResultSet.DEF_HOLDABILITY);
431 }
432
433 /**
434 * Creates a Statement object that will generate ResultSet objects
435 * with the given type and concurrency. This method is the same as
436 * the createStatement method above, but it allows the default
437 * result set type and concurrency to be overridden.
438 *
439 * @param resultSetType a result set type; one of
440 * ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,
441 * or ResultSet.TYPE_SCROLL_SENSITIVE
442 * @param resultSetConcurrency a concurrency type; one of
443 * ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
444 * @return a new Statement object that will generate ResultSet objects with
445 * the given type and concurrency
446 * @throws SQLException if a database access error occurs
447 */
448 @Override
449 public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException {
450 return createStatement(resultSetType, resultSetConcurrency, MonetResultSet.DEF_HOLDABILITY);
451 }
452
453 /**
454 * Creates a Statement object that will generate ResultSet objects
455 * with the given type, concurrency, and holdability. This method
456 * is the same as the createStatement method above, but it allows
457 * the default result set type, concurrency, and holdability to be
458 * overridden.
459 *
460 * @param resultSetType one of the following ResultSet constants:
461 * ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,
462 * or ResultSet.TYPE_SCROLL_SENSITIVE
463 * @param resultSetConcurrency one of the following ResultSet
464 * constants: ResultSet.CONCUR_READ_ONLY or
465 * ResultSet.CONCUR_UPDATABLE
466 * @param resultSetHoldability one of the following ResultSet
467 * constants: ResultSet.HOLD_CURSORS_OVER_COMMIT or
468 * ResultSet.CLOSE_CURSORS_AT_COMMIT
469 *
470 * @return a new Statement object that will generate ResultSet
471 * objects with the given type, concurrency, and holdability
472 * @throws SQLException if a database access error occurs or the
473 * given parameters are not ResultSet constants indicating type,
474 * concurrency, and holdability
475 */
476 @Override
477 public Statement createStatement(final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException {
478 try {
479 final Statement ret = new MonetStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability);
480 // store it in the map for when we close...
481 statements.put(ret, null);
482 return ret;
483 } catch (IllegalArgumentException e) {
484 throw new SQLException(e.toString(), "M0M03");
485 }
486 // we don't have to catch SQLException because that is declared to be thrown
487 }
488
489 /**
490 * Retrieves the current auto-commit mode for this Connection object.
491 *
492 * @return the current state of this Connection object's auto-commit mode
493 * @see #setAutoCommit(boolean)
494 */
495 @Override
496 public boolean getAutoCommit() {
497 return autoCommit;
498 }
499
500 /**
501 * Retrieves this Connection object's current catalog name.
502 *
503 * @return the current catalog name or null if there is none
504 */
505 @Override
506 public String getCatalog() {
507 // MonetDB does NOT support catalogs
508 return null;
509 }
510
511 /**
512 * Retrieves the current holdability of ResultSet objects created
513 * using this Connection object.
514 *
515 * @return the holdability, one of
516 * ResultSet.HOLD_CURSORS_OVER_COMMIT or
517 * ResultSet.CLOSE_CURSORS_AT_COMMIT
518 * @see #setHoldability(int)
519 */
520 @Override
521 public int getHoldability() {
522 // TODO: perhaps it is better to have the server implement
523 // CLOSE_CURSORS_AT_COMMIT
524 return ResultSet.HOLD_CURSORS_OVER_COMMIT;
525 }
526
527 /**
528 * Retrieves a DatabaseMetaData object that contains metadata about
529 * the database to which this Connection object represents a
530 * connection. The metadata includes information about the
531 * database's tables, its supported SQL grammar, its stored
532 * procedures, the capabilities of this connection, and so on.
533 *
534 * @throws SQLException if the current language is not SQL
535 * @return a DatabaseMetaData object for this Connection object
536 */
537 @Override
538 public DatabaseMetaData getMetaData() throws SQLException {
539 if (lang != LANG_SQL)
540 throw new SQLException("This method is only supported in SQL mode", "M0M04");
541
542 return new MonetDatabaseMetaData(this);
543 }
544
545 /**
546 * Retrieves this Connection object's current transaction isolation
547 * level.
548 *
549 * @return the current transaction isolation level, which will be
550 * Connection.TRANSACTION_SERIALIZABLE
551 */
552 @Override
553 public int getTransactionIsolation() {
554 return TRANSACTION_SERIALIZABLE;
555 }
556
557 /**
558 * Retrieves the Map object associated with this Connection object.
559 * Unless the application has added an entry, the type map returned
560 * will be empty.
561 *
562 * @return the java.util.Map object associated with this Connection
563 * object
564 */
565 @Override
566 public Map<String,Class<?>> getTypeMap() {
567 return typeMap;
568 }
569
570 /**
571 * Retrieves the first warning reported by calls on this Connection
572 * object. If there is more than one warning, subsequent warnings
573 * will be chained to the first one and can be retrieved by calling
574 * the method SQLWarning.getNextWarning on the warning that was
575 * retrieved previously.
576 *
577 * This method may not be called on a closed connection; doing so
578 * will cause an SQLException to be thrown.
579 *
580 * Note: Subsequent warnings will be chained to this SQLWarning.
581 *
582 * @return the first SQLWarning object or null if there are none
583 * @throws SQLException if a database access error occurs or this method is
584 * called on a closed connection
585 */
586 @Override
587 public SQLWarning getWarnings() throws SQLException {
588 checkNotClosed();
589 // if there are no warnings, this will be null, which fits with the
590 // specification.
591 return warnings;
592 }
593
594 /**
595 * Retrieves whether this Connection object has been closed. A
596 * connection is closed if the method close has been called on it or
597 * if certain fatal errors have occurred. This method is guaranteed
598 * to return true only when it is called after the method
599 * Connection.close has been called.
600 *
601 * This method generally cannot be called to determine whether a
602 * connection to a database is valid or invalid. A typical client
603 * can determine that a connection is invalid by catching any
604 * exceptions that might be thrown when an operation is attempted.
605 *
606 * @return true if this Connection object is closed; false if it is
607 * still open
608 */
609 @Override
610 public boolean isClosed() {
611 return closed;
612 }
613
614 /**
615 * Retrieves whether this Connection object is in read-only mode.
616 * MonetDB currently doesn't support updateable result sets, but
617 * updates are possible. Hence the Connection object is never in
618 * read-only mode.
619 *
620 * @return true if this Connection object is read-only; false otherwise
621 */
622 @Override
623 public boolean isReadOnly() {
624 return false;
625 }
626
627 /**
628 * Converts the given SQL statement into the system's native SQL grammar.
629 * A driver may convert the JDBC SQL grammar into its system's native SQL grammar prior to sending it.
630 * This method returns the native form of the statement that the driver would have sent.
631 *
632 * @param sql - an SQL statement that may contain one or more '?' parameter placeholders.
633 * @return the native form of this statement
634 */
635 @Override
636 public String nativeSQL(final String sql) {
637 /* there is currently no way to get the native MonetDB rewritten SQL string back, so just return the original string */
638 /* in future we may replace/remove the escape sequences { <escape-type> ...} before sending it to the server */
639 return sql;
640 }
641
642 /**
643 * Creates a CallableStatement object for calling database stored procedures.
644 * The CallableStatement object provides methods for setting up its IN and OUT parameters,
645 * and methods for executing the call to a stored procedure.
646 *
647 * Note: This method is optimized for handling stored procedure call statements.
648 * Some drivers may send the call statement to the database when the method prepareCall is done;
649 * others may wait until the CallableStatement object is executed. This has no direct effect
650 * on users; however, it does affect which method throws certain SQLExceptions.
651 *
652 * Result sets created using the returned CallableStatement object will by default be type TYPE_FORWARD_ONLY
653 * and have a concurrency level of CONCUR_READ_ONLY.
654 * The holdability of the created result sets can be determined by calling getHoldability().
655 *
656 * @param sql - an SQL statement that may contain one or more '?' parameter placeholders.
657 * Typically this statement is specified using JDBC call escape syntax.
658 * @return a new default CallableStatement object containing the pre-compiled SQL statement
659 * @throws SQLException - if a database access error occurs or this method is called on a closed connection
660 */
661 @Override
662 public CallableStatement prepareCall(final String sql) throws SQLException {
663 return prepareCall(sql, MonetResultSet.DEF_RESULTSETTYPE, MonetResultSet.DEF_CONCURRENCY, MonetResultSet.DEF_HOLDABILITY);
664 }
665
666 /**
667 * Creates a CallableStatement object that will generate ResultSet objects with the given type and concurrency.
668 * This method is the same as the prepareCall method above, but it allows the default result set type and concurrency to be overridden.
669 * The holdability of the created result sets can be determined by calling getHoldability().
670 *
671 * @param sql - a String object that is the SQL statement to be sent to the database; may contain on or more '?' parameters
672 * Typically this statement is specified using JDBC call escape syntax.
673 * @param resultSetType - a result set type; one of ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, or ResultSet.TYPE_SCROLL_SENSITIVE
674 * @param resultSetConcurrency - a concurrency type; one of ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
675 * @return a new CallableStatement object containing the pre-compiled SQL statement that
676 * will produce ResultSet objects with the given type and concurrency
677 * @throws SQLException - if a database access error occurs, this method is called on a closed connection or
678 * the given parameters are not ResultSet constants indicating type and concurrency
679 */
680 @Override
681 public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException {
682 return prepareCall(sql, resultSetType, resultSetConcurrency, MonetResultSet.DEF_HOLDABILITY);
683 }
684
685 /**
686 * Creates a CallableStatement object that will generate ResultSet objects with the given type and concurrency.
687 * This method is the same as the prepareCall method above, but it allows the default result set type, result set concurrency type and holdability to be overridden.
688 *
689 * @param sql - a String object that is the SQL statement to be sent to the database; may contain on or more '?' parameters
690 * Typically this statement is specified using JDBC call escape syntax.
691 * @param resultSetType - a result set type; one of ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, or ResultSet.TYPE_SCROLL_SENSITIVE
692 * @param resultSetConcurrency - a concurrency type; one of ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
693 * @param resultSetHoldability - one of the following ResultSet constants: ResultSet.HOLD_CURSORS_OVER_COMMIT or ResultSet.CLOSE_CURSORS_AT_COMMIT
694 * @return a new CallableStatement object, containing the pre-compiled SQL statement, that will generate ResultSet objects with the given type, concurrency, and holdability
695 * @throws SQLException - if a database access error occurs, this method is called on a closed connection or
696 * the given parameters are not ResultSet constants indicating type, concurrency, and holdability
697 */
698 @Override
699 public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability)
700 throws SQLException
701 {
702 checkNotClosed();
703 try {
704 final CallableStatement ret = new MonetCallableStatement(
705 this,
706 resultSetType,
707 resultSetConcurrency,
708 resultSetHoldability,
709 sql
710 );
711 // store it in the map for when we close...
712 statements.put(ret, null);
713 return ret;
714 } catch (IllegalArgumentException e) {
715 throw new SQLException(e.toString(), "M0M03");
716 }
717 // we don't have to catch SQLException because that is declared to be thrown
718 }
719
720 /**
721 * Creates a PreparedStatement object for sending parameterized SQL
722 * statements to the database.
723 *
724 * A SQL statement with or without IN parameters can be pre-compiled
725 * and stored in a PreparedStatement object. This object can then be
726 * used to efficiently execute this statement multiple times.
727 *
728 * Note: This method is optimized for handling parametric SQL
729 * statements that benefit from precompilation. If the driver
730 * supports precompilation, the method prepareStatement will send
731 * the statement to the database for precompilation. Some drivers
732 * may not support precompilation. In this case, the statement may
733 * not be sent to the database until the PreparedStatement object is
734 * executed. This has no direct effect on users; however, it does
735 * affect which methods throw certain SQLException objects.
736 *
737 * Result sets created using the returned PreparedStatement object
738 * will by default be type TYPE_FORWARD_ONLY and have a concurrency
739 * level of CONCUR_READ_ONLY.
740 *
741 * @param sql an SQL statement that may contain one or more '?' IN
742 * parameter placeholders
743 * @return a new default PreparedStatement object containing the
744 * pre-compiled SQL statement
745 * @throws SQLException if a database access error occurs
746 */
747 @Override
748 public PreparedStatement prepareStatement(final String sql) throws SQLException {
749 return prepareStatement(sql, MonetResultSet.DEF_RESULTSETTYPE, MonetResultSet.DEF_CONCURRENCY, MonetResultSet.DEF_HOLDABILITY);
750 }
751
752 /**
753 * Creates a PreparedStatement object that will generate ResultSet
754 * objects with the given type and concurrency. This method is the
755 * same as the prepareStatement method above, but it allows the
756 * default result set type and concurrency to be overridden.
757 *
758 * @param sql a String object that is the SQL statement to be sent to the
759 * database; may contain one or more ? IN parameters
760 * @param resultSetType a result set type; one of
761 * ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,
762 * or ResultSet.TYPE_SCROLL_SENSITIVE
763 * @param resultSetConcurrency a concurrency type; one of
764 * ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
765 * @return a new PreparedStatement object containing the pre-compiled SQL
766 * statement that will produce ResultSet objects with the given
767 * type and concurrency
768 * @throws SQLException if a database access error occurs or the given
769 * parameters are not ResultSet constants indicating
770 * type and concurrency
771 */
772 @Override
773 public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException {
774 return prepareStatement(sql, resultSetType, resultSetConcurrency, MonetResultSet.DEF_HOLDABILITY);
775 }
776
777 /**
778 * Creates a PreparedStatement object that will generate ResultSet
779 * objects with the given type, concurrency, and holdability.
780 *
781 * This method is the same as the prepareStatement method above, but
782 * it allows the default result set type, concurrency, and
783 * holdability to be overridden.
784 *
785 * @param sql a String object that is the SQL statement to be sent
786 * to the database; may contain one or more ? IN parameters
787 * @param resultSetType one of the following ResultSet constants:
788 * ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,
789 * or ResultSet.TYPE_SCROLL_SENSITIVE
790 * @param resultSetConcurrency one of the following ResultSet
791 * constants: ResultSet.CONCUR_READ_ONLY or
792 * ResultSet.CONCUR_UPDATABLE
793 * @param resultSetHoldability one of the following ResultSet
794 * constants: ResultSet.HOLD_CURSORS_OVER_COMMIT or
795 * ResultSet.CLOSE_CURSORS_AT_COMMIT
796 * @return a new PreparedStatement object, containing the
797 * pre-compiled SQL statement, that will generate ResultSet objects
798 * with the given type, concurrency, and holdability
799 * @throws SQLException if a database access error occurs or the
800 * given parameters are not ResultSet constants indicating type,
801 * concurrency, and holdability
802 */
803 @Override
804 public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability)
805 throws SQLException
806 {
807 checkNotClosed();
808 try {
809 final PreparedStatement ret = new MonetPreparedStatement(
810 this,
811 resultSetType,
812 resultSetConcurrency,
813 resultSetHoldability,
814 sql
815 );
816 // store it in the map for when we close...
817 statements.put(ret, null);
818 return ret;
819 } catch (IllegalArgumentException e) {
820 throw new SQLException(e.toString(), "M0M03");
821 }
822 // we don't have to catch SQLException because that is declared to be thrown
823 }
824
825 /**
826 * Creates a default PreparedStatement object that has the
827 * capability to retrieve auto-generated keys. The given constant
828 * tells the driver whether it should make auto-generated keys
829 * available for retrieval. This parameter is ignored if the SQL
830 * statement is not an INSERT statement.
831 *
832 * Note: This method is optimized for handling parametric SQL
833 * statements that benefit from precompilation. If the driver
834 * supports precompilation, the method prepareStatement will send
835 * the statement to the database for precompilation. Some drivers
836 * may not support precompilation. In this case, the statement may
837 * not be sent to the database until the PreparedStatement object is
838 * executed. This has no direct effect on users; however, it does
839 * affect which methods throw certain SQLExceptions.
840 *
841 * Result sets created using the returned PreparedStatement object
842 * will by default be type TYPE_FORWARD_ONLY and have a concurrency
843 * level of CONCUR_READ_ONLY.
844 *
845 * @param sql an SQL statement that may contain one or more '?' IN
846 * parameter placeholders
847 * @param autoGeneratedKeys a flag indicating whether auto-generated
848 * keys should be returned; one of
849 * Statement.RETURN_GENERATED_KEYS or
850 * Statement.NO_GENERATED_KEYS
851 * @return a new PreparedStatement object, containing the
852 * pre-compiled SQL statement, that will have the capability
853 * of returning auto-generated keys
854 * @throws SQLException - if a database access error occurs or the
855 * given parameter is not a Statement constant indicating
856 * whether auto-generated keys should be returned
857 */
858 @Override
859 public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
860 if (autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS &&
861 autoGeneratedKeys != Statement.NO_GENERATED_KEYS)
862 throw new SQLException("Invalid argument, expected RETURN_GENERATED_KEYS or NO_GENERATED_KEYS", "M1M05");
863
864 /* MonetDB has no way to disable this, so just do the normal thing ;) */
865 return prepareStatement(sql, MonetResultSet.DEF_RESULTSETTYPE, MonetResultSet.DEF_CONCURRENCY, MonetResultSet.DEF_HOLDABILITY);
866 }
867
868 /**
869 * Creates a default PreparedStatement object capable of returning the auto-generated keys designated by the given array.
870 * This array contains the indexes of the columns in the target table that contain the auto-generated keys that should be made available.
871 * The driver will ignore the array if the SQL statement is not an INSERT statement, or an SQL statement able to
872 * return auto-generated keys (the list of such statements is vendor-specific).
873 *
874 * An SQL statement with or without IN parameters can be pre-compiled and stored in a PreparedStatement object.
875 * This object can then be used to efficiently execute this statement multiple times.
876 *
877 * Note: This method is optimized for handling parametric SQL statements that benefit from precompilation.
878 * If the driver supports precompilation, the method prepareStatement will send the statement to the database for precompilation.
879 * Some drivers may not support precompilation. In this case, the statement may not be sent to the database until the PreparedStatement
880 * object is executed. This has no direct effect on users; however, it does affect which methods throw certain SQLExceptions.
881 *
882 * Result sets created using the returned PreparedStatement object will by default be type TYPE_FORWARD_ONLY and have
883 * a concurrency level of CONCUR_READ_ONLY. The holdability of the created result sets can be determined by calling getHoldability().
884 *
885 * @param sql - an SQL statement that may contain one or more '?' IN parameter placeholders
886 * @param columnIndexes - an array of column indexes indicating the columns that should be returned from the inserted row or rows
887 * @return a new PreparedStatement object, containing the pre-compiled statement, that is capable of
888 * returning the auto-generated keys designated by the given array of column indexes
889 * @throws SQLException - if a database access error occurs or this method is called on a closed connection
890 * @throws SQLFeatureNotSupportedException - if the JDBC driver does not support this method
891 */
892 @Override
893 public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
894 throw newSQLFeatureNotSupportedException("prepareStatement(String sql, int[] columnIndexes)");
895 }
896
897 /**
898 * Creates a default PreparedStatement object capable of returning the auto-generated keys designated by the given array.
899 * This array contains the names of the columns in the target table that contain the auto-generated keys that should be returned.
900 * The driver will ignore the array if the SQL statement is not an INSERT statement, or an SQL statement able to
901 * return auto-generated keys (the list of such statements is vendor-specific).
902 *
903 * An SQL statement with or without IN parameters can be pre-compiled and stored in a PreparedStatement object.
904 * This object can then be used to efficiently execute this statement multiple times.
905 *
906 * Note: This method is optimized for handling parametric SQL statements that benefit from precompilation.
907 * If the driver supports precompilation, the method prepareStatement will send the statement to the database for precompilation.
908 * Some drivers may not support precompilation. In this case, the statement may not be sent to the database until the PreparedStatement
909 * object is executed. This has no direct effect on users; however, it does affect which methods throw certain SQLExceptions.
910 *
911 * Result sets created using the returned PreparedStatement object will by default be type TYPE_FORWARD_ONLY and have
912 * a concurrency level of CONCUR_READ_ONLY. The holdability of the created result sets can be determined by calling getHoldability().
913 *
914 * @param sql - an SQL statement that may contain one or more '?' IN parameter placeholders
915 * @param columnNames - an array of column names indicating the columns that should be returned from the inserted row or rows
916 * @return a new PreparedStatement object, containing the pre-compiled statement, that is capable of
917 * returning the auto-generated keys designated by the given array of column names
918 * @throws SQLException - if a database access error occurs or this method is called on a closed connection
919 * @throws SQLFeatureNotSupportedException - if the JDBC driver does not support this method
920 */
921 @Override
922 public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
923 throw newSQLFeatureNotSupportedException("prepareStatement(String sql, String[] columnNames)");
924 }
925
926 /**
927 * Removes the given Savepoint object from the current transaction.
928 * Any reference to the savepoint after it have been removed will
929 * cause an SQLException to be thrown.
930 *
931 * @param savepoint the Savepoint object to be removed
932 * @throws SQLException if a database access error occurs or the given
933 * Savepoint object is not a valid savepoint in the current
934 * transaction
935 */
936 @Override
937 public void releaseSavepoint(final Savepoint savepoint) throws SQLException {
938 checkNotClosed();
939 if (!(savepoint instanceof MonetSavepoint))
940 throw new SQLException("This driver can only handle savepoints it created itself", "M0M06");
941
942 final MonetSavepoint sp = (MonetSavepoint)savepoint;
943
944 // note: can't use sendIndependentCommand here because we need
945 // to process the auto_commit state the server gives
946 sendTransactionCommand("RELEASE SAVEPOINT " + sp.getName());
947 }
948
949 /**
950 * Undoes all changes made in the current transaction and releases
951 * any database locks currently held by this Connection object.
952 * This method should be used only when auto-commit mode has been disabled.
953 *
954 * @throws SQLException if a database access error occurs or this
955 * Connection object is in auto-commit mode
956 * @see #setAutoCommit(boolean)
957 */
958 @Override
959 public void rollback() throws SQLException {
960 checkNotClosed();
961 // note: can't use sendIndependentCommand here because we need
962 // to process the auto_commit state the server gives
963 sendTransactionCommand("ROLLBACK");
964 }
965
966 /**
967 * Undoes all changes made after the given Savepoint object was set.
968 *
969 * This method should be used only when auto-commit has been disabled.
970 *
971 * @param savepoint the Savepoint object to roll back to
972 * @throws SQLException if a database access error occurs, the
973 * Savepoint object is no longer valid, or this Connection
974 * object is currently in auto-commit mode
975 */
976 @Override
977 public void rollback(final Savepoint savepoint) throws SQLException {
978 checkNotClosed();
979 if (!(savepoint instanceof MonetSavepoint))
980 throw new SQLException("This driver can only handle savepoints it created itself", "M0M06");
981
982 final MonetSavepoint sp = (MonetSavepoint)savepoint;
983
984 // note: can't use sendIndependentCommand here because we need
985 // to process the auto_commit state the server gives
986 sendTransactionCommand("ROLLBACK TO SAVEPOINT " + sp.getName());
987 }
988
989 /**
990 * Sets this connection's auto-commit mode to the given state. If a
991 * connection is in auto-commit mode, then all its SQL statements
992 * will be executed and committed as individual transactions.
993 * Otherwise, its SQL statements are grouped into transactions that
994 * are terminated by a call to either the method commit or the
995 * method rollback. By default, new connections are in auto-commit mode.
996 *
997 * The commit occurs when the statement completes or the next
998 * execute occurs, whichever comes first. In the case of statements
999 * returning a ResultSet object, the statement completes when the
1000 * last row of the ResultSet object has been retrieved or the
1001 * ResultSet object has been closed. In advanced cases, a single
1002 * statement may return multiple results as well as output parameter
1003 * values. In these cases, the commit occurs when all results and
1004 * output parameter values have been retrieved.
1005 *
1006 * NOTE: If this method is called during a transaction, the
1007 * transaction is committed.
1008 *
1009 * @param autoCommit true to enable auto-commit mode; false to disable it
1010 * @throws SQLException if a database access error occurs
1011 * @see #getAutoCommit()
1012 */
1013 @Override
1014 public void setAutoCommit(final boolean autoCommit) throws SQLException {
1015 checkNotClosed();
1016 if (this.autoCommit != autoCommit) {
1017 sendControlCommand("auto_commit " + (autoCommit ? "1" : "0"));
1018 this.autoCommit = autoCommit;
1019 }
1020 }
1021
1022 /**
1023 * Sets the given catalog name in order to select a subspace of this
1024 * Connection object's database in which to work. If the driver
1025 * does not support catalogs, it will silently ignore this request.
1026 */
1027 @Override
1028 public void setCatalog(final String catalog) {
1029 // silently ignore this request as MonetDB does not support catalogs
1030 }
1031
1032 /**
1033 * Changes the default holdability of ResultSet objects created using this
1034 * Connection object to the given holdability. The default holdability of
1035 * ResultSet objects can be be determined by invoking DatabaseMetaData.getResultSetHoldability().
1036 *
1037 * @param holdability - a ResultSet holdability constant; one of
1038 * ResultSet.HOLD_CURSORS_OVER_COMMIT or
1039 * ResultSet.CLOSE_CURSORS_AT_COMMIT
1040 * @throws SQLException - if a database access error occurs or this method is called on a closed connection
1041 * @throws SQLFeatureNotSupportedException - if the JDBC driver does not support this method or argument
1042 * @see #getHoldability()
1043 */
1044 @Override
1045 public void setHoldability(final int holdability) throws SQLException {
1046 // we only support ResultSet.HOLD_CURSORS_OVER_COMMIT
1047 if (holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT)
1048 throw newSQLFeatureNotSupportedException("setHoldability(CLOSE_CURSORS_AT_COMMIT)");
1049 }
1050
1051 /**
1052 * Puts this connection in read-only mode as a hint to the driver to
1053 * enable database optimizations. MonetDB doesn't support any mode
1054 * here, hence an SQLWarning is generated if attempted to set
1055 * to true here.
1056 *
1057 * @param readOnly true enables read-only mode; false disables it
1058 * @throws SQLException if a database access error occurs or this
1059 * method is called during a transaction.
1060 */
1061 @Override
1062 public void setReadOnly(final boolean readOnly) throws SQLException {
1063 if (readOnly == true)
1064 addWarning("cannot setReadOnly(true): read-only Connection mode not supported", "01M08");
1065 }
1066
1067 /**
1068 * Creates an unnamed savepoint in the current transaction and
1069 * returns the new Savepoint object that represents it.
1070 *
1071 * @return the new Savepoint object
1072 * @throws SQLException if a database access error occurs or this Connection
1073 * object is currently in auto-commit mode
1074 */
1075 @Override
1076 public Savepoint setSavepoint() throws SQLException {
1077 checkNotClosed();
1078 // create a new Savepoint object
1079 final MonetSavepoint sp = new MonetSavepoint();
1080
1081 // note: can't use sendIndependentCommand here because we need
1082 // to process the auto_commit state the server gives
1083 sendTransactionCommand("SAVEPOINT " + sp.getName());
1084 return sp;
1085 }
1086
1087 /**
1088 * Creates a savepoint with the given name in the current transaction
1089 * and returns the new Savepoint object that represents it.
1090 *
1091 * @param name a String containing the name of the savepoint
1092 * @return the new Savepoint object
1093 * @throws SQLException if a database access error occurs or this Connection
1094 * object is currently in auto-commit mode
1095 */
1096 @Override
1097 public Savepoint setSavepoint(final String name) throws SQLException {
1098 checkNotClosed();
1099 // create a new Savepoint object
1100 final MonetSavepoint sp;
1101 try {
1102 sp = new MonetSavepoint(name);
1103 } catch (IllegalArgumentException e) {
1104 throw new SQLException(e.getMessage(), "M0M03");
1105 }
1106
1107 // note: can't use sendIndependentCommand here because we need
1108 // to process the auto_commit state the server gives
1109 sendTransactionCommand("SAVEPOINT " + sp.getName());
1110 return sp;
1111 }
1112
1113 /**
1114 * Attempts to change the transaction isolation level for this
1115 * Connection object to the one given. The constants defined in the
1116 * interface Connection are the possible transaction isolation levels.
1117 *
1118 * @param level one of the following Connection constants:
1119 * Connection.TRANSACTION_READ_UNCOMMITTED,
1120 * Connection.TRANSACTION_READ_COMMITTED,
1121 * Connection.TRANSACTION_REPEATABLE_READ, or
1122 * Connection.TRANSACTION_SERIALIZABLE.
1123 */
1124 @Override
1125 public void setTransactionIsolation(final int level) {
1126 if (level != TRANSACTION_SERIALIZABLE) {
1127 addWarning("MonetDB only supports fully serializable " +
1128 "transactions, continuing with transaction level " +
1129 "raised to TRANSACTION_SERIALIZABLE", "01M09");
1130 }
1131 }
1132
1133 /**
1134 * Installs the given TypeMap object as the type map for this
1135 * Connection object. The type map will be used for the custom
1136 * mapping of SQL structured types and distinct types.
1137 *
1138 * @param map the java.util.Map object to install as the replacement for
1139 * this Connection object's default type map
1140 */
1141 @Override
1142 public void setTypeMap(final Map<String, Class<?>> map) {
1143 typeMap = map;
1144 }
1145
1146 /**
1147 * Returns a string identifying this Connection to the MonetDB server.
1148 *
1149 * @return a String representing this Object
1150 */
1151 @Override
1152 public String toString() {
1153 return "MonetDB Connection (" + getJDBCURL() + ") " +
1154 (closed ? "disconnected" : "connected");
1155 }
1156
1157 //== Java 1.6 methods (JDBC 4.0)
1158
1159 /**
1160 * Factory method for creating Array objects.
1161 *
1162 * Note: When createArrayOf is used to create an array object that
1163 * maps to a primitive data type, then it is implementation-defined
1164 * whether the Array object is an array of that primitive data type
1165 * or an array of Object.
1166 *
1167 * Note: The JDBC driver is responsible for mapping the elements
1168 * Object array to the default JDBC SQL type defined in
1169 * java.sql.Types for the given class of Object. The default mapping
1170 * is specified in Appendix B of the JDBC specification. If the
1171 * resulting JDBC type is not the appropriate type for the given
1172 * typeName then it is implementation defined whether an
1173 * SQLException is thrown or the driver supports the resulting conversion.
1174 *
1175 * @param typeName the SQL name of the type the elements of the
1176 * array map to. The typeName is a database-specific name
1177 * which may be the name of a built-in type, a user-defined
1178 * type or a standard SQL type supported by this database.
1179 * This is the value returned by Array.getBaseTypeName
1180 * @return an Array object whose elements map to the specified SQL type
1181 * @throws SQLException - if a database error occurs, the JDBC type is not appropriate for the typeName and
1182 * the conversion is not supported, the typeName is null or this method is called on a closed connection
1183 * @throws SQLFeatureNotSupportedException - if the JDBC driver does not support this data type
1184 * @since 1.6
1185 */
1186 @Override
1187 public java.sql.Array createArrayOf(final String typeName, final Object[] elements) throws SQLException {
1188 throw newSQLFeatureNotSupportedException("createArrayOf");
1189 }
1190
1191
1192 /**
1193 * Constructs an object that implements the Clob interface. The
1194 * object returned initially contains no data. The setAsciiStream,
1195 * setCharacterStream and setString methods of the Clob interface
1196 * may be used to add data to the Clob.
1197 *
1198 * @return a MonetClob instance
1199 * @throws SQLException - if an object that implements the Clob interface can not be constructed,
1200 * this method is called on a closed connection or a database access error occurs.
1201 * @since 1.6
1202 */
1203 @Override
1204 public java.sql.Clob createClob() throws SQLException {
1205 return new MonetClob("");
1206 }
1207
1208 /**
1209 * Constructs an object that implements the Blob interface. The
1210 * object returned initially contains no data. The setBinaryStream
1211 * and setBytes methods of the Blob interface may be used to add
1212 * data to the Blob.
1213 *
1214 * @return a MonetBlob instance
1215 * @throws SQLException - if an object that implements the Blob interface can not be constructed,
1216 * this method is called on a closed connection or a database access error occurs.
1217 * @since 1.6
1218 */
1219 @Override
1220 public java.sql.Blob createBlob() throws SQLException {
1221 return new MonetBlob(new byte[1]);
1222 }
1223
1224 /**
1225 * Constructs an object that implements the NClob interface. The
1226 * object returned initially contains no data. The setAsciiStream,
1227 * setCharacterStream and setString methods of the NClob interface
1228 * may be used to add data to the NClob.
1229 *
1230 * @return an NClob instance
1231 * @throws SQLException - if an object that implements the NClob interface can not be constructed,
1232 * this method is called on a closed connection or a database access error occurs.
1233 * @throws SQLFeatureNotSupportedException - if the JDBC driver does not support this data type
1234 * @since 1.6
1235 */
1236 @Override
1237 public java.sql.NClob createNClob() throws SQLException {
1238 throw newSQLFeatureNotSupportedException("createNClob");
1239 }
1240
1241 /**
1242 * Factory method for creating Struct objects.
1243 *
1244 * @param typeName the SQL type name of the SQL structured type that
1245 * this Struct object maps to. The typeName is the name of a
1246 * user-defined type that has been defined for this database.
1247 * It is the value returned by Struct.getSQLTypeName.
1248 * @param attributes the attributes that populate the returned object
1249 * @return a Struct object that maps to the given SQL type and is
1250 * populated with the given attributes
1251 * @throws SQLException - if an object that implements the Struct interface can not be constructed,
1252 * this method is called on a closed connection or a database access error occurs.
1253 * @throws SQLFeatureNotSupportedException - if the JDBC driver does not support this data type
1254 * @since 1.6
1255 */
1256 @Override
1257 public java.sql.Struct createStruct(String typeName, Object[] attributes) throws SQLException {
1258 throw newSQLFeatureNotSupportedException("createStruct");
1259 }
1260
1261 /**
1262 * Constructs an object that implements the SQLXML interface. The
1263 * object returned initially contains no data. The
1264 * createXmlStreamWriter object and setString method of the SQLXML
1265 * interface may be used to add data to the SQLXML object.
1266 *
1267 * @return An object that implements the SQLXML interface
1268 * @throws SQLException - if an object that implements the SQLXML interface can not be constructed,
1269 * this method is called on a closed connection or a database access error occurs.
1270 * @throws SQLFeatureNotSupportedException - if the JDBC driver does not support this data type
1271 * @since 1.6
1272 */
1273 @Override
1274 public java.sql.SQLXML createSQLXML() throws SQLException {
1275 throw newSQLFeatureNotSupportedException("createSQLXML");
1276 }
1277
1278 /**
1279 * Returns true if the connection has not been closed and is still
1280 * valid. The driver shall submit a query on the connection or use
1281 * some other mechanism that positively verifies the connection is
1282 * still valid when this method is called.
1283 *
1284 * The query submitted by the driver to validate the connection
1285 * shall be executed in the context of the current transaction.
1286 *
1287 * @param timeout The time in seconds to wait for the database
1288 * operation used to validate the connection to complete. If
1289 * the timeout period expires before the operation completes,
1290 * this method returns false. A value of 0 indicates a
1291 * timeout is not applied to the database operation.
1292 * @return true if the connection is valid, false otherwise
1293 * @throws SQLException if the value supplied for timeout is less than 0
1294 * @since 1.6
1295 */
1296 @Override
1297 public boolean isValid(final int timeout) throws SQLException {
1298 if (timeout < 0)
1299 throw new SQLException("timeout is less than 0", "M1M05");
1300 if (closed)
1301 return false;
1302
1303 // ping monetdb server using query: select 1;
1304 Statement stmt = null;
1305 ResultSet rs = null;
1306 boolean isValid = false;
1307 final int original_timeout = this.lastSetQueryTimeout;
1308 try {
1309 stmt = createStatement();
1310 if (stmt != null) {
1311 if (timeout > 0 && original_timeout != timeout) {
1312 // we need to change the requested timeout for this test query
1313 stmt.setQueryTimeout(timeout);
1314 }
1315 rs = stmt.executeQuery("SELECT 1");
1316 if (rs != null && rs.next()) {
1317 isValid = true;
1318 }
1319 }
1320 } catch (SQLException se) {
1321 String msg = se.getMessage();
1322 // System.out.println(se.getSQLState() + " Con.isValid(): " + msg);
1323 if (msg != null && msg.equalsIgnoreCase("Current transaction is aborted (please ROLLBACK)")) {
1324 // Must use equalsIgnoreCase() here because up to Jul2017 release 'Current' was 'current' so with lowercase c.
1325 // It changed to 'Current' after Jul2017 release. We need to support all server versions.
1326 // SQLState = 25005
1327 isValid = true;
1328 }
1329 /* ignore stmt errors/exceptions, we are only testing if the connection is still alive and usable */
1330 } finally {
1331 /* when changed, reset the original server timeout value on the server */
1332 if (timeout > 0 && original_timeout != this.lastSetQueryTimeout) {
1333 this.lastSetQueryTimeout = original_timeout;
1334 Statement stmt2 = null;
1335 try {
1336 /* we have to set in the server explicitly, because the test 'queryTimeout != connection.lastSetQueryTimeout'
1337 on MonetStatement.internalExecute(sql) won't pass and the server won't be set back */
1338 stmt2 = this.createStatement();
1339 stmt2.execute("CALL \"sys\".\"settimeout\"(" + this.lastSetQueryTimeout + ")");
1340 } catch (SQLException se) {
1341 /* ignore stmt errors/exceptions, we are only testing if the connection is still alive and usable */
1342 } finally {
1343 closeResultsetStatement(null, stmt2);
1344 }
1345 }
1346 closeResultsetStatement(rs, stmt);
1347 }
1348 return isValid;
1349 }
1350
1351 /**
1352 * Returns the value of the client info property specified by name.
1353 * This method may return null if the specified client info property
1354 * has not been set and does not have a default value.
1355 * This method will also return null if the specified client info
1356 * property name is not supported by the driver.
1357 * Applications may use the DatabaseMetaData.getClientInfoProperties method
1358 * to determine the client info properties supported by the driver.
1359 *
1360 * @param name - The name of the client info property to retrieve
1361 * @return The value of the client info property specified or null
1362 * @throws SQLException - if the database server returns an error
1363 * when fetching the client info value from the database
1364 * or this method is called on a closed connection
1365 * @since 1.6
1366 */
1367 @Override
1368 public String getClientInfo(final String name) throws SQLException {
1369 if (name == null || name.isEmpty())
1370 return null;
1371 checkNotClosed();
1372 return conn_props.getProperty(name);
1373 }
1374
1375 /**
1376 * Returns a list containing the name and current value of each client info
1377 * property supported by the driver. The value of a client info property may
1378 * be null if the property has not been set and does not have a default value.
1379 *
1380 * @return A Properties object that contains the name and current value
1381 * of each of the client info properties supported by the driver.
1382 * @throws SQLException - if the database server returns an error
1383 * when fetching the client info value from the database
1384 * or this method is called on a closed connection
1385 * @since 1.6
1386 */
1387 @Override
1388 public Properties getClientInfo() throws SQLException {
1389 checkNotClosed();
1390 // return a clone of the connection properties object
1391 return new Properties(conn_props);
1392 }
1393
1394 /**
1395 * Sets the value of the client info property specified by name to the value specified by value.
1396 * Applications may use the DatabaseMetaData.getClientInfoProperties method to determine
1397 * the client info properties supported by the driver and the maximum length that may be specified
1398 * for each property.
1399 *
1400 * The driver stores the value specified in a suitable location in the database. For example
1401 * in a special register, session parameter, or system table column. For efficiency the driver
1402 * may defer setting the value in the database until the next time a statement is executed
1403 * or prepared. Other than storing the client information in the appropriate place in the
1404 * database, these methods shall not alter the behavior of the connection in anyway.
1405 * The values supplied to these methods are used for accounting, diagnostics and debugging purposes only.
1406 *
1407 * The driver shall generate a warning if the client info name specified is not recognized by the driver.
1408 *
1409 * If the value specified to this method is greater than the maximum length for the property
1410 * the driver may either truncate the value and generate a warning or generate a SQLClientInfoException.
1411 * If the driver generates a SQLClientInfoException, the value specified was not set on the connection.
1412 *
1413 * The following are standard client info properties. Drivers are not required to support these
1414 * properties however if the driver supports a client info property that can be described by one
1415 * of the standard properties, the standard property name should be used.
1416 *
1417 * ApplicationName - The name of the application currently utilizing the connection
1418 * ClientUser - The name of the user that the application using the connection is performing work for.
1419 * This may not be the same as the user name that was used in establishing the connection.
1420 * ClientHostname - The hostname of the computer the application using the connection is running on.
1421 *
1422 * @param name - The name of the client info property to set
1423 * @param value - The value to set the client info property to. If the
1424 * value is null, the current value of the specified property is cleared.
1425 * @throws SQLClientInfoException - if the database server returns an error
1426 * while setting the clientInfo values on the database server
1427 * or this method is called on a closed connection
1428 * @since 1.6
1429 */
1430 @Override
1431 public void setClientInfo(final String name, final String value) throws SQLClientInfoException {
1432 if (name == null || name.isEmpty()) {
1433 addWarning("setClientInfo: missing property name", "01M07");
1434 return;
1435 }
1436 // If the value is null, the current value of the specified property is cleared.
1437 if (value == null) {
1438 if (conn_props.containsKey(name))
1439 conn_props.remove(name);
1440 return;
1441 }
1442 // only set value for supported property names
1443 if (name.equals("host") ||
1444 name.equals("port") ||
1445 name.equals("user") ||
1446 name.equals("password") ||
1447 name.equals("database") ||
1448 name.equals("language") ||
1449 name.equals("so_timeout") ||
1450 name.equals("debug") ||
1451 name.equals("hash") ||
1452 name.equals("treat_blob_as_binary") ||
1453 name.equals("treat_clob_as_varchar"))
1454 {
1455 conn_props.setProperty(name, value);
1456 } else {
1457 addWarning("setClientInfo: " + name + "is not a recognised property", "01M07");
1458 }
1459 }
1460
1461 /**
1462 * Sets the value of the connection's client info properties.
1463 * The Properties object contains the names and values of the client info
1464 * properties to be set. The set of client info properties contained in the
1465 * properties list replaces the current set of client info properties on the connection.
1466 * If a property that is currently set on the connection is not present in the
1467 * properties list, that property is cleared. Specifying an empty properties list
1468 * will clear all of the properties on the connection.
1469 * See setClientInfo (String, String) for more information.
1470 *
1471 * If an error occurs in setting any of the client info properties, a
1472 * SQLClientInfoException is thrown. The SQLClientInfoException contains information
1473 * indicating which client info properties were not set. The state of the client
1474 * information is unknown because some databases do not allow multiple client info
1475 * properties to be set atomically. For those databases, one or more properties may
1476 * have been set before the error occurred.
1477 *
1478 * @param props - The list of client info properties to set
1479 * @throws SQLClientInfoException - if the database server returns an error
1480 * while setting the clientInfo values on the database server
1481 * or this method is called on a closed connection
1482 * @since 1.6
1483 */
1484 @Override
1485 public void setClientInfo(final Properties props) throws SQLClientInfoException {
1486 if (props != null) {
1487 for (Entry<Object, Object> entry : props.entrySet()) {
1488 setClientInfo(entry.getKey().toString(), entry.getValue().toString());
1489 }
1490 }
1491 }
1492
1493
1494 //== Java 1.7 methods (JDBC 4.1)
1495
1496 /**
1497 * Sets the given schema name to access.
1498 *
1499 * @param schema the name of a schema in which to work
1500 * @throws SQLException if a database access error occurs or this
1501 * method is called on a closed connection
1502 * @since 1.7
1503 */
1504 @Override
1505 public void setSchema(final String schema) throws SQLException {
1506 checkNotClosed();
1507 if (schema == null || schema.isEmpty())
1508 throw new SQLException("Missing schema name", "M1M05");
1509
1510 Statement st = null;
1511 try {
1512 st = createStatement();
1513 if (st != null)
1514 st.execute("SET SCHEMA \"" + schema + "\"");
1515 // do not catch any Exception, just let it propagate
1516 } finally {
1517 closeResultsetStatement(null, st);
1518 }
1519 }
1520
1521 /**
1522 * Retrieves this Connection object's current schema name.
1523 *
1524 * @return the current schema name or null if there is none
1525 * @throws SQLException if a database access error occurs or this
1526 * method is called on a closed connection
1527 * @since 1.7
1528 */
1529 @Override
1530 public String getSchema() throws SQLException {
1531 checkNotClosed();
1532
1533 String cur_schema = null;
1534 Statement st = null;
1535 ResultSet rs = null;
1536 try {
1537 st = createStatement();
1538 if (st != null) {
1539 rs = st.executeQuery("SELECT CURRENT_SCHEMA");
1540 if (rs != null) {
1541 if (rs.next())
1542 cur_schema = rs.getString(1);
1543 }
1544 }
1545 // do not catch any Exception, just let it propagate
1546 } finally {
1547 closeResultsetStatement(rs, st);
1548 }
1549 if (cur_schema == null)
1550 throw new SQLException("Failed to fetch schema name", "02000");
1551 return cur_schema;
1552 }
1553
1554 /**
1555 * Terminates an open connection. Calling abort results in:
1556 * * The connection marked as closed
1557 * * Closes any physical connection to the database
1558 * * Releases resources used by the connection
1559 * * Insures that any thread that is currently accessing the
1560 * connection will either progress to completion or throw an
1561 * SQLException.
1562 * Calling abort marks the connection closed and releases any
1563 * resources. Calling abort on a closed connection is a no-op.
1564 *
1565 * @param executor The Executor implementation which will be used by
1566 * abort
1567 * @throws SQLException if a database access error occurs or the
1568 * executor is null
1569 * @throws SecurityException if a security manager exists and
1570 * its checkPermission method denies calling abort
1571 * @since 1.7
1572 */
1573 @Override
1574 public void abort(final Executor executor) throws SQLException {
1575 if (closed)
1576 return;
1577 if (executor == null)
1578 throw new SQLException("executor is null", "M1M05");
1579 // this is really the simplest thing to do, it destroys
1580 // everything (in particular the server connection)
1581 close();
1582 }
1583
1584 /**
1585 * Sets the maximum period a Connection or objects created from the
1586 * Connection will wait for the database to reply to any one
1587 * request. If any request remains unanswered, the waiting method
1588 * will return with a SQLException, and the Connection or objects
1589 * created from the Connection will be marked as closed. Any
1590 * subsequent use of the objects, with the exception of the close,
1591 * isClosed or Connection.isValid methods, will result in a
1592 * SQLException.
1593 *
1594 * @param executor The Executor implementation which will be used by
1595 * setNetworkTimeout
1596 * @param millis The time in milliseconds to wait for the
1597 * database operation to complete
1598 * @throws SQLException if a database access error occurs, this
1599 * method is called on a closed connection, the executor is
1600 * null, or the value specified for seconds is less than 0.
1601 * @since 1.7
1602 */
1603 @Override
1604 public void setNetworkTimeout(final Executor executor, final int millis) throws SQLException {
1605 checkNotClosed();
1606 // executor object is not used yet, so no need to test it.
1607 // if (executor == null)
1608 // throw new SQLException("executor is null", "M1M05");
1609 if (millis < 0)
1610 throw new SQLException("milliseconds is less than zero", "M1M05");
1611
1612 try {
1613 server.setSoTimeout(millis);
1614 } catch (SocketException e) {
1615 throw new SQLNonTransientConnectionException(e.getMessage(), "08000");
1616 }
1617 }
1618
1619 /**
1620 * Retrieves the number of milliseconds the driver will wait for a
1621 * database request to complete. If the limit is exceeded, a
1622 * SQLException is thrown.
1623 *
1624 * @return the current timeout limit in milliseconds; zero means
1625 * there is no limit
1626 * @throws SQLException if a database access error occurs or
1627 * this method is called on a closed Connection
1628 * @since 1.7
1629 */
1630 @Override
1631 public int getNetworkTimeout() throws SQLException {
1632 checkNotClosed();
1633 try {
1634 return server.getSoTimeout();
1635 } catch (SocketException e) {
1636 throw new SQLNonTransientConnectionException(e.getMessage(), "08000");
1637 }
1638 }
1639
1640 //== end methods of interface java.sql.Connection
1641
1642
1643 //== internal helper methods which do not belong to the JDBC interface
1644
1645 /**
1646 * @return whether the JDBC BLOB type should be mapped to VARBINARY type.
1647 * This allows generic JDBC programs to fetch Blob data via getBytes()
1648 * instead of getBlob() and Blob.getBinaryStream() to reduce overhead.
1649 * It is called from: MonetResultSet and MonetPreparedStatement
1650 */
1651 boolean mapBlobAsVarBinary() {
1652 return treatBlobAsVarBinary;
1653 }
1654
1655 /**
1656 * @return whether the JDBC CLOB type should be mapped to VARCHAR type.
1657 * This allows generic JDBC programs to fetch Clob data via getString()
1658 * instead of getClob() and Clob.getCharacterStream() to reduce overhead.
1659 * It is called from: MonetResultSet and MonetPreparedStatement
1660 */
1661 boolean mapClobAsVarChar() {
1662 return treatClobAsVarChar;
1663 }
1664
1665 /**
1666 * Local helper method to test whether the Connection object is closed
1667 * When closed it throws an SQLException
1668 */
1669 private void checkNotClosed() throws SQLException {
1670 if (closed)
1671 throw new SQLException("Connection is closed", "M1M20");
1672 }
1673
1674 /**
1675 * @return the MonetDB JDBC Connection URL (without user name and password).
1676 * It is called from: getURL()in MonetDatabaseMetaData
1677 */
1678 String getJDBCURL() {
1679 final StringBuilder sb = new StringBuilder(128);
1680 sb.append(MonetDriver.MONETURL).append(hostname)
1681 .append(':').append(port)
1682 .append('/').append(database);
1683 if (lang == LANG_MAL)
1684 sb.append("?language=mal");
1685 return sb.toString();
1686 }
1687
1688 /**
1689 * Utility method to escape all ocurrences of special characters
1690 * (double slashes and single quotes) in a string literal
1691 * It is called from: MonetDatabaseMetaData and MonetPreparedStatement
1692 */
1693 final String escapeSpecialChars(final String in) {
1694 String ret = in;
1695 if (ret.contains("\\\\"))
1696 // all double slashes in input need to be escaped.
1697 ret = ret.replaceAll("\\\\", "\\\\\\\\");
1698 if (ret.contains("'"))
1699 // all single quotes in input need to be escaped.
1700 ret = ret.replaceAll("'", "\\\\'");
1701 return ret;
1702 }
1703
1704
1705 // Internal cache for 3 static mserver environment values, so they aren't queried from mserver again and again
1706 private String env_current_user = null;
1707 private String env_monet_version = null;
1708 private String env_max_clients = null;
1709
1710 /**
1711 * Utility method to fetch 3 mserver environment values combined in one query for efficiency.
1712 * We currently fetch the env values of: current_user, monet_version and max_clients.
1713 * We cache them such that we do not need to query the server again and again.
1714 */
1715 private synchronized void getEnvValues() throws SQLException {
1716 Statement st = null;
1717 ResultSet rs = null;
1718 try {
1719 st = createStatement();
1720 if (st != null) {
1721 rs = st.executeQuery(
1722 "SELECT \"name\", \"value\" FROM \"sys\".\"env\"()" +
1723 " WHERE \"name\" IN ('monet_version', 'max_clients')" +
1724 " UNION SELECT 'current_user' as \"name\", current_user as \"value\"");
1725 if (rs != null) {
1726 while (rs.next()) {
1727 final String prop = rs.getString(1);
1728 final String value = rs.getString(2);
1729 if ("current_user".equals(prop)) {
1730 env_current_user = value;
1731 } else
1732 if ("monet_version".equals(prop)) {
1733 env_monet_version = value;
1734 } else
1735 if ("max_clients".equals(prop)) {
1736 env_max_clients = value;
1737 }
1738 }
1739 }
1740 }
1741 /* do not catch SQLException here, as we need to know it when it fails */
1742 } finally {
1743 closeResultsetStatement(rs, st);
1744 }
1745 // for debug: System.out.println("Read: env_current_user: " + env_current_user + " env_monet_version: " + env_monet_version + " env_max_clients: " + env_max_clients);
1746 }
1747
1748 /**
1749 * @return the current User Name.
1750 * It is called from: MonetDatabaseMetaData
1751 */
1752 String getUserName() throws SQLException {
1753 if (env_current_user == null)
1754 getEnvValues();
1755 return env_current_user;
1756 }
1757
1758 /**
1759 * @return the MonetDB Database Server version string.
1760 * It is called from: MonetDatabaseMetaData
1761 */
1762 String getDatabaseProductVersion() throws SQLException {
1763 if (env_monet_version == null)
1764 getEnvValues();
1765 // always return a valid String to prevent NPE in getTables() and getTableTypes()
1766 if (env_monet_version != null)
1767 return env_monet_version;
1768 return "";
1769 }
1770
1771 /**
1772 * @return the MonetDB Database Server major version number.
1773 * It is called from: MonetDatabaseMetaData
1774 */
1775 int getDatabaseMajorVersion() throws SQLException {
1776 if (env_monet_version == null)
1777 getEnvValues();
1778 if (env_monet_version != null) {
1779 try {
1780 // from version string such as 11.33.9 extract number: 11
1781 final int start = env_monet_version.indexOf('.');
1782 return Integer.parseInt((start >= 0) ? env_monet_version.substring(0, start) : env_monet_version);
1783 } catch (NumberFormatException nfe) {
1784 // ignore
1785 }
1786 }
1787 return 0;
1788 }
1789
1790 /**
1791 * @return the MonetDB Database Server minor version number.
1792 * It is called from: MonetDatabaseMetaData
1793 */
1794 int getDatabaseMinorVersion() throws SQLException {
1795 if (env_monet_version == null)
1796 getEnvValues();
1797 if (env_monet_version != null) {
1798 try {
1799 // from version string such as 11.33.9 extract number: 33
1800 int start = env_monet_version.indexOf('.');
1801 if (start >= 0) {
1802 start++;
1803 final int end = env_monet_version.indexOf('.', start);
1804 return Integer.parseInt((end > 0) ? env_monet_version.substring(start, end) : env_monet_version.substring(start));
1805 }
1806 } catch (NumberFormatException nfe) {
1807 // ignore
1808 }
1809 }
1810 return 0;
1811 }
1812
1813 /**
1814 * @return the maximum number of active connections possible at one time;
1815 * a result of zero means that there is no limit or the limit is not known
1816 * It is called from: MonetDatabaseMetaData
1817 */
1818 int getMaxConnections() throws SQLException {
1819 if (env_max_clients == null)
1820 getEnvValues();
1821 if (env_max_clients != null) {
1822 try {
1823 return Integer.parseInt(env_max_clients);
1824 } catch (NumberFormatException nfe) {
1825 /* ignore */
1826 }
1827 }
1828 return 0;
1829 }
1830
1831
1832 // Internal cache for determining if system table sys.privilege_codes (new as of Jul2017 release) exists on connected server
1833 private boolean queriedPrivilege_codesTable = false;
1834 private boolean hasPrivilege_codesTable = false;
1835 /**
1836 * Utility method to query the server to find out if it has
1837 * the system table sys.privilege_codes (which is new as of Jul2017 release).
1838 * The result is cached and reused, so that we only test the query once per connection.
1839 * This method is used by methods from MonetDatabaseMetaData.
1840 */
1841 boolean privilege_codesTableExists() {
1842 if (!queriedPrivilege_codesTable) {
1843 hasPrivilege_codesTable = existsSysTable("privilege_codes");
1844 queriedPrivilege_codesTable = true; // set flag, so the querying is done only at first invocation.
1845 }
1846 return hasPrivilege_codesTable;
1847 }
1848
1849 // Internal cache for determining if system table sys.comments (new as of Mar2018 release) exists on connected server
1850 private boolean queriedCommentsTable = false;
1851 private boolean hasCommentsTable = false;
1852 /**
1853 * Utility method to query the server to find out if it has
1854 * the system table sys.comments (which is new as of Mar2018 release).
1855 * The result is cached and reused, so that we only test the query once per connection.
1856 * This method is used by methods from MonetDatabaseMetaData.
1857 */
1858 boolean commentsTableExists() {
1859 if (!queriedCommentsTable) {
1860 hasCommentsTable = existsSysTable("comments");
1861 queriedCommentsTable = true; // set flag, so the querying is done only at first invocation.
1862 }
1863 return hasCommentsTable;
1864 }
1865
1866
1867 /**
1868 * Internal utility method to query the server to find out if it has a specific system table sys.<tablename>.
1869 */
1870 private boolean existsSysTable(final String tablename) {
1871 boolean exists = false;
1872 Statement stmt = null;
1873 ResultSet rs = null;
1874 try {
1875 stmt = createStatement();
1876 if (stmt != null) {
1877 rs = stmt.executeQuery("SELECT id FROM sys._tables WHERE name = '"
1878 + tablename
1879 + "' AND schema_id IN (SELECT id FROM sys.schemas WHERE name = 'sys')");
1880 if (rs != null) {
1881 exists = rs.next(); // if a row is available it exists, else not
1882 }
1883 }
1884 } catch (SQLException se) {
1885 /* ignore */
1886 } finally {
1887 closeResultsetStatement(rs, stmt);
1888 }
1889 // for debug: System.out.println("testTableExists(" + tablename + ") returns: " + exists);
1890 return exists;
1891 }
1892
1893 /**
1894 * Closes a ResultSet and/or Statement object without throwing any SQLExceptions
1895 * It can be used in the finally clause after creating a Statement and
1896 * (optionally) executed a query which produced a ResultSet.
1897 *
1898 * @param rs ResultSet object to be closed. It may be null
1899 * @param st Statement object to be closed. It may be null
1900 */
1901 static final void closeResultsetStatement(final ResultSet rs, final Statement st) {
1902 if (rs != null) {
1903 try {
1904 rs.close();
1905 } catch (SQLException e) { /* ignore */ }
1906 }
1907 if (st != null) {
1908 try {
1909 st.close();
1910 } catch (SQLException e) { /* ignore */ }
1911 }
1912 }
1913
1914 /**
1915 * Sends the given string to MonetDB as special transaction command.
1916 * All possible returned information is discarded.
1917 * Encountered errors are reported.
1918 *
1919 * @param command the exact string to send to MonetDB
1920 * @throws SQLException if an IO exception or a database error occurs
1921 */
1922 private void sendTransactionCommand(final String command) throws SQLException {
1923 // create a container for the result
1924 final ResponseList l = new ResponseList(0, 0, ResultSet.FETCH_FORWARD, ResultSet.CONCUR_READ_ONLY);
1925 // send the appropriate query string to the database
1926 try {
1927 l.processQuery(command);
1928 } finally {
1929 l.close();
1930 }
1931 }
1932
1933 /**
1934 * Sends the given string to MonetDB as regular SQL statement/query using queryTempl
1935 * Making sure there is a prompt after the command is sent. All possible
1936 * returned information is discarded. Encountered errors are reported.
1937 *
1938 * @param command the exact string to send to MonetDB
1939 * @throws SQLException if an IO exception or a database error occurs
1940 */
1941 private void sendIndependentCommand(String command) throws SQLException {
1942 sendCommand(command, true);
1943 }
1944
1945 /**
1946 * Sends the given string to MonetDB as control statement using commandTempl
1947 * Making sure there is a prompt after the command is sent. All possible
1948 * returned information is discarded. Encountered errors are reported.
1949 *
1950 * @param command the exact string to send to MonetDB
1951 * @throws SQLException if an IO exception or a database error occurs
1952 */
1953 void sendControlCommand(String command) throws SQLException {
1954 // send X command
1955 sendCommand(command, false);
1956 }
1957
1958 /**
1959 * Sends the given string to MonetDB as command/query using commandTempl or queryTempl
1960 * Making sure there is a prompt after the command is sent. All possible
1961 * returned information is discarded. Encountered errors are reported.
1962 *
1963 * @param command the exact string to send to MonetDB
1964 * @param usequeryTempl send the command using queryTempl or else using commandTempl
1965 * @throws SQLException if an IO exception or a database error occurs
1966 */
1967 private void sendCommand(final String command, final boolean usequeryTempl) throws SQLException {
1968 synchronized (server) {
1969 try {
1970 out.writeLine(usequeryTempl ? (queryTempl[0] + command + queryTempl[1])
1971 : (commandTempl[0] + command + commandTempl[1]) );
1972 final String error = in.waitForPrompt();
1973 if (error != null)
1974 throw new SQLException(error.substring(6), error.substring(0, 5));
1975 } catch (SocketTimeoutException e) {
1976 close(); // JDBC 4.1 semantics, abort()
1977 throw new SQLNonTransientConnectionException("connection timed out", "08M33");
1978 } catch (IOException e) {
1979 throw new SQLNonTransientConnectionException(e.getMessage(), "08000");
1980 }
1981 }
1982 }
1983
1984 /**
1985 * Adds a warning to the pile of warnings this Connection object
1986 * has. If there were no warnings (or clearWarnings was called)
1987 * this warning will be the first, otherwise this warning will get
1988 * appended to the current warning.
1989 *
1990 * @param reason the warning message
1991 */
1992 private final void addWarning(final String reason, final String sqlstate) {
1993 final SQLWarning warng = new SQLWarning(reason, sqlstate);
1994 if (warnings == null) {
1995 warnings = warng;
1996 } else {
1997 warnings.setNextWarning(warng);
1998 }
1999 }
2000
2001 /** the default number of rows that are (attempted to) read at once */
2002 private static final int DEF_FETCHSIZE = 250;
2003 /** The sequence counter */
2004 private static int seqCounter = 0;
2005
2006 /**
2007 * A Response is a message sent by the server to indicate some
2008 * action has taken place, and possible results of that action.
2009 */
2010 // {{{ interface Response
2011 interface Response {
2012 /**
2013 * Adds a line to the underlying Response implementation.
2014 *
2015 * @param line the header line as String
2016 * @param linetype the line type according to the MAPI protocol
2017 * @return a non-null String if the line is invalid,
2018 * or additional lines are not allowed.
2019 */
2020 public abstract String addLine(String line, int linetype);
2021
2022 /**
2023 * Returns whether this Response expects more lines to be added
2024 * to it.
2025 *
2026 * @return true if a next line should be added, false otherwise
2027 */
2028 public abstract boolean wantsMore();
2029
2030 /**
2031 * Indicates that no more header lines will be added to this
2032 * Response implementation.
2033 *
2034 * @throws SQLException if the contents of the Response is not
2035 * consistent or sufficient.
2036 */
2037 /* MvD: disabled not used/needed code
2038 public abstract void complete() throws SQLException;
2039 */
2040
2041 /**
2042 * Instructs the Response implementation to close and do the
2043 * necessary clean up procedures.
2044 */
2045 public abstract void close();
2046 }
2047 // }}}
2048
2049 /**
2050 * The ResultSetResponse represents a tabular result sent by the
2051 * server. This is typically an SQL table. The MAPI headers of the
2052 * Response look like:
2053 * <pre>
2054 * &amp;1 1 28 2 10
2055 * # name, value # name
2056 * # varchar, varchar # type
2057 * </pre>
2058 * there the first line consists out of<br />
2059 * <tt>&amp;"qt" "id" "tc" "cc" "rc"</tt>.
2060 */
2061 // {{{ ResultSetResponse class implementation
2062 final class ResultSetResponse implements Response {
2063 /** The number of columns in this result */
2064 public final int columncount;
2065 /** The total number of rows this result set has */
2066 public final long tuplecount;
2067 /** The numbers of rows to retrieve per DataBlockResponse */
2068 private int cacheSize;
2069 /** The table ID of this result */
2070 public final int id;
2071 /** The names of the columns in this result */
2072 private String[] name;
2073 /** The types of the columns in this result */
2074 private String[] type;
2075 /** The max string length for each column in this result */
2076 private int[] columnLengths;
2077 /** The table for each column in this result */
2078 private String[] tableNames;
2079 /** The query sequence number */
2080 private final int seqnr;
2081 /** A List of result blocks (chunks of size fetchSize/cacheSize) */
2082 private DataBlockResponse[] resultBlocks;
2083
2084 /** Whether this Response is closed */
2085 private boolean closed;
2086
2087 /** The Connection that we should use when requesting a new block */
2088 private final MonetConnection.ResponseList parent;
2089 /** Whether the fetchSize was explitly set by the user */
2090 private final boolean cacheSizeSetExplicitly;
2091 /** Whether we should send an Xclose command to the server
2092 * if we close this Response */
2093 private boolean destroyOnClose;
2094 /** the offset to be used on Xexport queries */
2095 private int blockOffset = 0;
2096
2097 /** A parser for header lines */
2098 private final HeaderLineParser hlp;
2099
2100 /** A boolean array telling whether the headers are set or not */
2101 private final boolean[] isSet;
2102 private static final int NAMES = 0;
2103 private static final int TYPES = 1;
2104 private static final int TABLES = 2;
2105 private static final int LENS = 3;
2106
2107
2108 /**
2109 * Sole constructor, which requires a MonetConnection parent to
2110 * be given.
2111 *
2112 * @param id the ID of the result set
2113 * @param tuplecount the total number of tuples in the result set
2114 * @param columncount the number of columns in the result set
2115 * @param rowcount the number of rows in the current block
2116 * @param parent the parent that created this Response and will
2117 * supply new result blocks when necessary
2118 * @param seq the query sequence number
2119 */
2120 ResultSetResponse(
2121 final int id,
2122 final long tuplecount,
2123 final int columncount,
2124 final int rowcount,
2125 final MonetConnection.ResponseList parent,
2126 final int seq)
2127 throws SQLException
2128 {
2129 isSet = new boolean[4];
2130 this.parent = parent;
2131 if (parent.cachesize == 0) {
2132 /* Below we have to calculate how many "chunks" we need
2133 * to allocate to store the entire result. However, if
2134 * the user didn't set a cache size, as in this case, we
2135 * need to stick to our defaults. */
2136 cacheSize = MonetConnection.DEF_FETCHSIZE;
2137 cacheSizeSetExplicitly = false;
2138 } else {
2139 cacheSize = parent.cachesize;
2140 cacheSizeSetExplicitly = true;
2141 }
2142 /* So far, so good. Now the problem with EXPLAIN, DOT, etc
2143 * queries is, that they don't support any block fetching,
2144 * so we need to always fetch everything at once. For that
2145 * reason, the cache size is here set to the rowcount if
2146 * it's larger, such that we do a full fetch at once.
2147 * (Because we always set a reply_size, we can only get a
2148 * larger rowcount from the server if it doesn't paginate,
2149 * because it's a pseudo SQL result.) */
2150 if (rowcount > cacheSize)
2151 cacheSize = rowcount;
2152 seqnr = seq;
2153 closed = false;
2154 destroyOnClose = id > 0 && tuplecount > rowcount;
2155
2156 this.id = id;
2157 this.tuplecount = tuplecount;
2158 this.columncount = columncount;
2159 this.resultBlocks = new DataBlockResponse[(int)(tuplecount / cacheSize) + 1];
2160
2161 hlp = new HeaderLineParser(columncount);
2162
2163 resultBlocks[0] = new DataBlockResponse(rowcount, parent.rstype == ResultSet.TYPE_FORWARD_ONLY);
2164 }
2165
2166 /**
2167 * Parses the given string and changes the value of the matching
2168 * header appropriately, or passes it on to the underlying
2169 * DataResponse.
2170 *
2171 * @param tmpLine the string that contains the header
2172 * @return a non-null String if the header cannot be parsed or
2173 * is unknown
2174 */
2175 // {{{ addLine
2176 @Override
2177 public String addLine(final String tmpLine, final int linetype) {
2178 if (isSet[LENS] && isSet[TYPES] && isSet[TABLES] && isSet[NAMES]) {
2179 return resultBlocks[0].addLine(tmpLine, linetype);
2180 }
2181
2182 if (linetype != BufferedMCLReader.HEADER)
2183 return "header expected, got: " + tmpLine;
2184
2185 // depending on the name of the header, we continue
2186 try {
2187 switch (hlp.parse(tmpLine)) {
2188 case HeaderLineParser.NAME:
2189 name = hlp.values.clone();
2190 isSet[NAMES] = true;
2191 break;
2192 case HeaderLineParser.LENGTH:
2193 columnLengths = hlp.intValues.clone();
2194 isSet[LENS] = true;
2195 break;
2196 case HeaderLineParser.TYPE:
2197 type = hlp.values.clone();
2198 isSet[TYPES] = true;
2199 break;
2200 case HeaderLineParser.TABLE:
2201 tableNames = hlp.values.clone();
2202 isSet[TABLES] = true;
2203 break;
2204 }
2205 } catch (MCLParseException e) {
2206 return e.getMessage();
2207 }
2208
2209 // all is well
2210 return null;
2211 }
2212 // }}}
2213
2214 /**
2215 * Returns whether this ResultSetResponse needs more lines.
2216 * This method returns true if not all headers are set, or the
2217 * first DataBlockResponse reports to want more.
2218 */
2219 @Override
2220 public boolean wantsMore() {
2221 if (isSet[LENS] && isSet[TYPES] && isSet[TABLES] && isSet[NAMES]) {
2222 return resultBlocks[0].wantsMore();
2223 } else {
2224 return true;
2225 }
2226 }
2227
2228 /**
2229 * Returns an array of Strings containing the values between
2230 * ',\t' separators.
2231 *
2232 * @param chrLine a character array holding the input data
2233 * @param start where the relevant data starts
2234 * @param stop where the relevant data stops
2235 * @return an array of Strings
2236 */
2237 private final String[] getValues(final char[] chrLine, int start, final int stop) {
2238 int elem = 0;
2239 final String[] values = new String[columncount];
2240
2241 for (int i = start; i < stop; i++) {
2242 if (chrLine[i] == '\t' && chrLine[i - 1] == ',') {
2243 values[elem++] =
2244 new String(chrLine, start, i - 1 - start);
2245 start = i + 1;
2246 }
2247 }
2248 // at the left over part
2249 values[elem++] = new String(chrLine, start, stop - start);
2250
2251 return values;
2252 }
2253
2254 /**
2255 * Adds the given DataBlockResponse to this ResultSetResponse at
2256 * the given block position.
2257 *
2258 * @param offset the offset number of rows for this block
2259 * @param rr the DataBlockResponse to add
2260 */
2261 void addDataBlockResponse(final int offset, final DataBlockResponse rr) {
2262 final int block = (offset - blockOffset) / cacheSize;
2263 resultBlocks[block] = rr;
2264 }
2265
2266 /**
2267 * Marks this Response as being completed. A complete Response
2268 * needs to be consistent with regard to its internal data.
2269 *
2270 * @throws SQLException if the data currently in this Response is not
2271 * sufficient to be consistant
2272 */
2273 /* MvD: disabled not used/needed code
2274 @Override
2275 public void complete() throws SQLException {
2276 final StringBuilder err = new StringBuilder(99);
2277 if (!isSet[NAMES]) err.append("name header missing\n");
2278 if (!isSet[TYPES]) err.append("type header missing\n");
2279 if (!isSet[TABLES]) err.append("table name header missing\n");
2280 if (!isSet[LENS]) err.append("column width header missing\n");
2281 if (err.length() > 0)
2282 throw new SQLException(err.toString(), "M0M10");
2283 }
2284 */
2285
2286 /**
2287 * Returns the names of the columns
2288 *
2289 * @return the names of the columns
2290 */
2291 String[] getNames() {
2292 return name;
2293 }
2294
2295 /**
2296 * Returns the types of the columns
2297 *
2298 * @return the types of the columns
2299 */
2300 String[] getTypes() {
2301 return type;
2302 }
2303
2304 /**
2305 * Returns the tables of the columns
2306 *
2307 * @return the tables of the columns
2308 */
2309 String[] getTableNames() {
2310 return tableNames;
2311 }
2312
2313 /**
2314 * Returns the lengths of the columns
2315 *
2316 * @return the lengths of the columns
2317 */
2318 int[] getColumnLengths() {
2319 return columnLengths;
2320 }
2321
2322 /**
2323 * Returns the cache size used within this Response
2324 *
2325 * @return the cache size
2326 */
2327 int getCacheSize() {
2328 return cacheSize;
2329 }
2330
2331 /**
2332 * Returns the current block offset
2333 *
2334 * @return the current block offset
2335 */
2336 int getBlockOffset() {
2337 return blockOffset;
2338 }
2339
2340 /**
2341 * Returns the ResultSet type, FORWARD_ONLY or not.
2342 *
2343 * @return the ResultSet type
2344 */
2345 int getRSType() {
2346 return parent.rstype;
2347 }
2348
2349 /**
2350 * Returns the concurrency of the ResultSet.
2351 *
2352 * @return the ResultSet concurrency
2353 */
2354 int getRSConcur() {
2355 return parent.rsconcur;
2356 }
2357
2358 /**
2359 * Returns a line from the cache. If the line is already present in the
2360 * cache, it is returned, if not appropriate actions are taken to make
2361 * sure the right block is being fetched and as soon as the requested
2362 * line is fetched it is returned.
2363 *
2364 * @param row the row in the result set to return
2365 * @return the exact row read as requested or null if the requested row
2366 * is out of the scope of the result set
2367 * @throws SQLException if an database error occurs
2368 */
2369 String getLine(final int row) throws SQLException {
2370 if (row >= tuplecount || row < 0)
2371 return null;
2372
2373 int block = (row - blockOffset) / cacheSize;
2374 int blockLine = (row - blockOffset) % cacheSize;
2375
2376 // do we have the right block loaded? (optimistic try)
2377 DataBlockResponse rawr = resultBlocks[block];
2378 if (rawr == null) {
2379 // load block
2380 /// TODO: ponder about a maximum number of blocks to keep
2381 /// in memory when dealing with random access to
2382 /// reduce memory blow-up
2383
2384 // if we're running forward only, we can discard the old
2385 // block loaded
2386 if (parent.rstype == ResultSet.TYPE_FORWARD_ONLY) {
2387 for (int i = 0; i < block; i++)
2388 resultBlocks[i] = null;
2389
2390 if (MonetConnection.seqCounter - 1 == seqnr &&
2391 !cacheSizeSetExplicitly &&
2392 tuplecount - row > cacheSize &&
2393 cacheSize < MonetConnection.DEF_FETCHSIZE * 10)
2394 {
2395 // there has no query been issued after this
2396 // one, so we can consider this an uninterrupted
2397 // continuation request. Let's once increase
2398 // the cacheSize as it was not explicitly set,
2399 // since the chances are high that we won't
2400 // bother anyone else by doing so, and just
2401 // gaining some performance.
2402
2403 // store the previous position in the
2404 // blockOffset variable
2405 blockOffset += cacheSize;
2406
2407 // increase the cache size (a lot)
2408 cacheSize *= 10;
2409
2410 // by changing the cacheSize, we also
2411 // change the block measures. Luckily
2412 // we don't care about previous blocks
2413 // because we have a forward running
2414 // pointer only. However, we do have
2415 // to recalculate the block number, to
2416 // ensure the next call to find this
2417 // new block.
2418 block = (row - blockOffset) / cacheSize;
2419 blockLine = (row - blockOffset) % cacheSize;
2420 }
2421 }
2422
2423 // ok, need to fetch cache block first
2424 parent.executeQuery(commandTempl,
2425 "export " + id + " " + ((block * cacheSize) + blockOffset) + " " + cacheSize);
2426 rawr = resultBlocks[block];
2427 if (rawr == null)
2428 throw new SQLException("resultBlocks[" + block + "] should have been fetched by now", "M0M10");
2429 }
2430
2431 return rawr.getRow(blockLine);
2432 }
2433
2434 /**
2435 * Closes this Response by sending an Xclose to the server indicating
2436 * that the result can be closed at the server side as well.
2437 */
2438 @Override
2439 public void close() {
2440 if (closed) return;
2441
2442 // send command to server indicating we're done with this
2443 // result only if we had an ID in the header and this result
2444 // was larger than the reply size
2445 try {
2446 if (destroyOnClose)
2447 sendControlCommand("close " + id);
2448 } catch (SQLException e) {
2449 // probably a connection error...
2450 }
2451
2452 // close the data block associated with us
2453 for (int i = 1; i < resultBlocks.length; i++) {
2454 DataBlockResponse r = resultBlocks[i];
2455 if (r != null)
2456 r.close();
2457 }
2458
2459 closed = true;
2460 }
2461
2462 /**
2463 * Returns whether this Response is closed
2464 *
2465 * @return whether this Response is closed
2466 */
2467 boolean isClosed() {
2468 return closed;
2469 }
2470 }
2471 // }}}
2472
2473 /**
2474 * The DataBlockResponse is tabular data belonging to a
2475 * ResultSetResponse. Tabular data from the server typically looks
2476 * like:
2477 * <pre>
2478 * [ "value", 56 ]
2479 * </pre>
2480 * where each column is separated by ",\t" and each tuple surrounded
2481 * by brackets ("[" and "]"). A DataBlockResponse object holds the
2482 * raw data as read from the server, in a parsed manner, ready for
2483 * easy retrieval.
2484 *
2485 * This object is not intended to be queried by multiple threads
2486 * synchronously. It is designed to work for one thread retrieving
2487 * rows from it. When multiple threads will retrieve rows from this
2488 * object, it is possible for threads to get the same data.
2489 */
2490 // {{{ DataBlockResponse class implementation
2491 private final static class DataBlockResponse implements Response {
2492 /** The String array to keep the data in */
2493 private final String[] data;
2494
2495 /** The counter which keeps the current position in the data array */
2496 private int pos;
2497 /** Whether we can discard lines as soon as we have read them */
2498 private final boolean forwardOnly;
2499
2500 /**
2501 * Constructs a DataBlockResponse object
2502 * @param size the size of the data array to create
2503 * @param forward whether this is a forward only result
2504 */
2505 DataBlockResponse(final int size, final boolean forward) {
2506 pos = -1;
2507 data = new String[size];
2508 forwardOnly = forward;
2509 }
2510
2511 /**
2512 * addLine adds a String of data to this object's data array.
2513 * Note that an IndexOutOfBoundsException can be thrown when an
2514 * attempt is made to add more than the original construction size
2515 * specified.
2516 *
2517 * @param line the header line as String
2518 * @param linetype the line type according to the MAPI protocol
2519 * @return a non-null String if the line is invalid,
2520 * or additional lines are not allowed.
2521 */
2522 @Override
2523 public String addLine(final String line, final int linetype) {
2524 if (linetype != BufferedMCLReader.RESULT)
2525 return "protocol violation: unexpected line in data block: " + line;
2526 // add to the backing array
2527 data[++pos] = line;
2528
2529 // all is well
2530 return null;
2531 }
2532
2533 /**
2534 * Returns whether this Response expects more lines to be added
2535 * to it.
2536 *
2537 * @return true if a next line should be added, false otherwise
2538 */
2539 @Override
2540 public boolean wantsMore() {
2541 // remember: pos is the value already stored
2542 return pos + 1 < data.length;
2543 }
2544
2545 /**
2546 * Indicates that no more header lines will be added to this
2547 * Response implementation. In most cases this is a redundant
2548 * operation because the data array is full. However... it can
2549 * happen that this is NOT the case!
2550 *
2551 * @throws SQLException if not all rows are filled
2552 */
2553 /* MvD: disabled not used/needed code
2554 @Override
2555 public void complete() throws SQLException {
2556 if ((pos + 1) != data.length)
2557 throw new SQLException("Inconsistent state detected! Current block capacity: "
2558 + data.length + ", block usage: " + (pos + 1)
2559 + ". Did MonetDB send what it promised to?", "M0M10");
2560 }
2561 */
2562
2563 /**
2564 * Instructs the Response implementation to close and do the
2565 * necessary clean up procedures.
2566 */
2567 @Override
2568 public void close() {
2569 // feed all rows to the garbage collector
2570 for (int i = 0; i < data.length; i++)
2571 data[i] = null;
2572 }
2573
2574 /**
2575 * Retrieves the required row. Warning: if the requested rows
2576 * is out of bounds, an IndexOutOfBoundsException will be
2577 * thrown.
2578 *
2579 * @param line the row to retrieve
2580 * @return the requested row as String
2581 */
2582 String getRow(final int line) {
2583 if (forwardOnly) {
2584 final String ret = data[line];
2585 data[line] = null;
2586 return ret;
2587 } else {
2588 return data[line];
2589 }
2590 }
2591 }
2592 // }}}
2593
2594 /**
2595 * The UpdateResponse represents an update statement response. It
2596 * is issued on an UPDATE, INSERT or DELETE SQL statement. This
2597 * response keeps a count field that represents the affected rows
2598 * and a field that contains the last inserted auto-generated ID, or
2599 * -1 if not applicable.<br />
2600 * <tt>&amp;2 0 -1</tt>
2601 */
2602 // {{{ UpdateResponse class implementation
2603 final static class UpdateResponse implements Response {
2604 public final long count;
2605 public final String lastid;
2606
2607 public UpdateResponse(final long cnt, final String id) {
2608 // fill the blank finals
2609 this.count = cnt;
2610 this.lastid = id;
2611 }
2612
2613 @Override
2614 public String addLine(final String line, final int linetype) {
2615 return "Header lines are not supported for an UpdateResponse";
2616 }
2617
2618 @Override
2619 public boolean wantsMore() {
2620 return false;
2621 }
2622
2623 /* MvD: disabled not used/needed code
2624 @Override
2625 public void complete() {
2626 // empty, because there is nothing to check
2627 }
2628 */
2629
2630 @Override
2631 public void close() {
2632 // nothing to do here...
2633 }
2634 }
2635 // }}}
2636
2637 /**
2638 * The SchemaResponse represents an schema modification response.
2639 * It is issued on statements like CREATE, DROP or ALTER TABLE.
2640 * This response keeps a field that represents the success state, as
2641 * defined by JDBC, which is currently in MonetDB's case alwats
2642 * SUCCESS_NO_INFO. Note that this state is not sent by the
2643 * server.<br />
2644 * <tt>&amp;3</tt>
2645 */
2646 // {{{ SchemaResponse class implementation
2647 class SchemaResponse implements Response {
2648 public final int state = Statement.SUCCESS_NO_INFO;
2649
2650 @Override
2651 public String addLine(final String line, final int linetype) {
2652 return "Header lines are not supported for a SchemaResponse";
2653 }
2654
2655 @Override
2656 public boolean wantsMore() {
2657 return false;
2658 }
2659
2660 /* MvD: disabled not used/needed code
2661 @Override
2662 public void complete() {
2663 // empty, because there is nothing to check
2664 }
2665 */
2666
2667 @Override
2668 public void close() {
2669 // nothing to do here...
2670 }
2671 }
2672 // }}}
2673
2674 /**
2675 * The AutoCommitResponse represents a transaction message. It
2676 * stores (a change in) the server side auto commit mode.<br />
2677 * <tt>&amp;4 (t|f)</tt>
2678 */
2679 // {{{ AutoCommitResponse class implementation
2680 private final class AutoCommitResponse extends SchemaResponse {
2681 public final boolean autocommit;
2682
2683 public AutoCommitResponse(final boolean ac) {
2684 // fill the blank final
2685 this.autocommit = ac;
2686 }
2687 }
2688 // }}}
2689
2690 /**
2691 * A list of Response objects. Responses are added to this list.
2692 * Methods of this class are not synchronized. This is left as
2693 * responsibility to the caller to prevent concurrent access.
2694 */
2695 // {{{ ResponseList class implementation
2696 final class ResponseList {
2697 /** The cache size (number of rows in a DataBlockResponse object) */
2698 private final int cachesize;
2699 /** The maximum number of results for this query */
2700 private final long maxrows;
2701 /** The ResultSet type to produce */
2702 private final int rstype;
2703 /** The ResultSet concurrency to produce */
2704 private final int rsconcur;
2705 /** The sequence number of this ResponseList */
2706 private final int seqnr;
2707 /** A list of the Responses associated with the query,
2708 * in the right order */
2709 private final ArrayList<Response> responses;
2710 /** A map of ResultSetResponses, used for additional
2711 * DataBlockResponse mapping */
2712 private HashMap<Integer, ResultSetResponse> rsresponses;
2713
2714 /** The current header returned by getNextResponse() */
2715 private int curResponse;
2716
2717 /**
2718 * Main constructor. The query argument can either be a String
2719 * or List. An SQLException is thrown if another object
2720 * instance is supplied.
2721 *
2722 * @param cachesize overall cachesize to use
2723 * @param maxrows maximum number of rows to allow in the set
2724 * @param rstype the type of result sets to produce
2725 * @param rsconcur the concurrency of result sets to produce
2726 */
2727 ResponseList(
2728 final int cachesize,
2729 final long maxrows,
2730 final int rstype,
2731 final int rsconcur
2732 ) throws SQLException {
2733 this.cachesize = cachesize;
2734 this.maxrows = maxrows;
2735 this.rstype = rstype;
2736 this.rsconcur = rsconcur;
2737 responses = new ArrayList<Response>();
2738 curResponse = -1;
2739 seqnr = MonetConnection.seqCounter++;
2740 }
2741
2742 /**
2743 * Retrieves the next available response, or null if there are
2744 * no more responses.
2745 *
2746 * @return the next Response available or null
2747 */
2748 Response getNextResponse() throws SQLException {
2749 if (rstype == ResultSet.TYPE_FORWARD_ONLY) {
2750 // free resources if we're running forward only
2751 if (curResponse >= 0 && curResponse < responses.size()) {
2752 final Response tmp = responses.get(curResponse);
2753 if (tmp != null)
2754 tmp.close();
2755 responses.set(curResponse, null);
2756 }
2757 }
2758 curResponse++;
2759 if (curResponse >= responses.size()) {
2760 // ResponseList is obviously completed so, there are no
2761 // more responses
2762 return null;
2763 } else {
2764 // return this response
2765 return responses.get(curResponse);
2766 }
2767 }
2768
2769 /**
2770 * Closes the Response at index i, if not null.
2771 *
2772 * @param i the index position of the header to close
2773 */
2774 void closeResponse(final int i) {
2775 if (i < 0 || i >= responses.size())
2776 return;
2777 final Response tmp = responses.set(i, null);
2778 if (tmp != null)
2779 tmp.close();
2780 }
2781
2782 /**
2783 * Closes the current response.
2784 */
2785 void closeCurrentResponse() {
2786 closeResponse(curResponse);
2787 }
2788
2789 /**
2790 * Closes the current and previous responses.
2791 */
2792 void closeCurOldResponses() {
2793 for (int i = curResponse; i >= 0; i--) {
2794 closeResponse(i);
2795 }
2796 }
2797
2798 /**
2799 * Closes this ResponseList by closing all the Responses in this
2800 * ResponseList.
2801 */
2802 void close() {
2803 for (int i = 0; i < responses.size(); i++) {
2804 closeResponse(i);
2805 }
2806 }
2807
2808 /**
2809 * Returns whether this ResponseList has still unclosed
2810 * Responses.
2811 */
2812 boolean hasUnclosedResponses() {
2813 for (Response r : responses) {
2814 if (r != null)
2815 return true;
2816 }
2817 return false;
2818 }
2819
2820 /**
2821 * Executes the query contained in this ResponseList, and
2822 * stores the Responses resulting from this query in this
2823 * ResponseList.
2824 *
2825 * @throws SQLException if a database error occurs
2826 */
2827 void processQuery(final String query) throws SQLException {
2828 executeQuery(queryTempl, query);
2829 }
2830
2831 /**
2832 * Internal executor of queries.
2833 *
2834 * @param templ the template to fill in
2835 * @param the query to execute
2836 * @throws SQLException if a database error occurs
2837 */
2838 @SuppressWarnings("fallthrough")
2839 void executeQuery(final String[] templ, final String query)
2840 throws SQLException
2841 {
2842 String error = null;
2843
2844 try {
2845 synchronized (server) {
2846 // make sure we're ready to send query; read data till we
2847 // have the prompt it is possible (and most likely) that we
2848 // already have the prompt and do not have to skip any
2849 // lines. Ignore errors from previous result sets.
2850 in.waitForPrompt();
2851
2852 // {{{ set reply size
2853 /**
2854 * Change the reply size of the server. If the given
2855 * value is the same as the current value known to use,
2856 * then ignore this call. If it is set to 0 we get a
2857 * prompt after the server sent it's header.
2858 */
2859 int size = (cachesize == 0 ? DEF_FETCHSIZE : cachesize);
2860 if (maxrows > 0 && maxrows < size)
2861 size = (int)maxrows;
2862 // don't do work if it's not needed
2863 if (lang == LANG_SQL && size != curReplySize && templ != commandTempl) {
2864 sendControlCommand("reply_size " + size);
2865
2866 // store the reply size after a successful change
2867 curReplySize = size;
2868 }
2869 // }}} set reply size
2870
2871 // send query to the server
2872 out.writeLine( (templ[0] == null ? "" : templ[0]) + query + (templ[1] == null ? "" : templ[1]) );
2873
2874 // go for new results
2875 String tmpLine = in.readLine();
2876 int linetype = in.getLineType();
2877 Response res = null;
2878 while (linetype != BufferedMCLReader.PROMPT) {
2879 // each response should start with a start of header (or error)
2880 switch (linetype) {
2881 case BufferedMCLReader.SOHEADER:
2882 // make the response object, and fill it
2883 try {
2884 switch (sohp.parse(tmpLine)) {
2885 case StartOfHeaderParser.Q_PARSE:
2886 throw new MCLParseException("Q_PARSE header not allowed here", 1);
2887 case StartOfHeaderParser.Q_TABLE:
2888 case StartOfHeaderParser.Q_PREPARE: {
2889 final int id = sohp.getNextAsInt();
2890 long tuplecount = sohp.getNextAsLong();
2891 final int columncount = sohp.getNextAsInt();
2892 final int rowcount = sohp.getNextAsInt();
2893 // enforce the maxrows setting
2894 if (maxrows != 0 && tuplecount > maxrows)
2895 tuplecount = maxrows;
2896 res = new ResultSetResponse(id, tuplecount, columncount, rowcount, this, seqnr);
2897 // only add this resultset to the hashmap if it can possibly have an additional datablock
2898 if (rowcount < tuplecount) {
2899 if (rsresponses == null)
2900 rsresponses = new HashMap<Integer, ResultSetResponse>();
2901 rsresponses.put(Integer.valueOf(id), (ResultSetResponse) res);
2902 }
2903 } break;
2904 case StartOfHeaderParser.Q_UPDATE:
2905 res = new UpdateResponse(sohp.getNextAsLong(), // count
2906 sohp.getNextAsString() // key-id
2907 );
2908 break;
2909 case StartOfHeaderParser.Q_SCHEMA:
2910 res = new SchemaResponse();
2911 break;
2912 case StartOfHeaderParser.Q_TRANS:
2913 final boolean ac = sohp.getNextAsString().equals("t") ? true : false;
2914 if (autoCommit && ac) {
2915 addWarning("Server enabled auto commit mode " +
2916 "while local state already was auto commit.", "01M11");
2917 }
2918 autoCommit = ac;
2919 res = new AutoCommitResponse(ac);
2920 break;
2921 case StartOfHeaderParser.Q_BLOCK: {
2922 // a new block of results for a response...
2923 final int id = sohp.getNextAsInt();
2924 sohp.getNextAsInt(); // columncount
2925 final int rowcount = sohp.getNextAsInt();
2926 final int offset = sohp.getNextAsInt();
2927 final ResultSetResponse t = (rsresponses != null) ? rsresponses.get(Integer.valueOf(id)) : null;
2928 if (t == null) {
2929 error = "M0M12!no ResultSetResponse with id " + id + " found";
2930 break;
2931 }
2932 final DataBlockResponse r = new DataBlockResponse(rowcount, t.getRSType() == ResultSet.TYPE_FORWARD_ONLY);
2933 t.addDataBlockResponse(offset, r);
2934 res = r;
2935 } break;
2936 } // end of switch (sohp.parse(tmpLine))
2937 } catch (MCLParseException e) {
2938 error = "M0M10!error while parsing start of header:\n" +
2939 e.getMessage() +
2940 " found: '" + tmpLine.charAt(e.getErrorOffset()) + "'" +
2941 " in: \"" + tmpLine + "\"" +
2942 " at pos: " + e.getErrorOffset();
2943 // flush all the rest
2944 in.waitForPrompt();
2945 linetype = in.getLineType();
2946 break;
2947 }
2948
2949 // immediately handle errors after parsing the header (res may be null)
2950 if (error != null) {
2951 in.waitForPrompt();
2952 linetype = in.getLineType();
2953 break;
2954 }
2955
2956 // here we have a res object, which we can start filling
2957 while (res.wantsMore()) {
2958 error = res.addLine(in.readLine(), in.getLineType());
2959 if (error != null) {
2960 // right, some protocol violation,
2961 // skip the rest of the result
2962 error = "M0M10!" + error;
2963 in.waitForPrompt();
2964 linetype = in.getLineType();
2965 break;
2966 }
2967 }
2968 if (error != null)
2969 break;
2970
2971 // it is of no use to store DataBlockResponses, you never want to
2972 // retrieve them directly anyway
2973 if (!(res instanceof DataBlockResponse))
2974 responses.add(res);
2975
2976 // read the next line (can be prompt, new result, error, etc.)
2977 // before we start the loop over
2978 tmpLine = in.readLine();
2979 linetype = in.getLineType();
2980 break;
2981 case BufferedMCLReader.INFO:
2982 addWarning(tmpLine.substring(1), "01000");
2983 // read the next line (can be prompt, new result, error, etc.)
2984 // before we start the loop over
2985 tmpLine = in.readLine();
2986 linetype = in.getLineType();
2987 break;
2988 default: // Yeah... in Java this is correct!
2989 // we have something we don't expect/understand, let's make it an error message
2990 tmpLine = "!M0M10!protocol violation, unexpected line: " + tmpLine;
2991 // don't break; fall through...
2992 case BufferedMCLReader.ERROR:
2993 // read everything till the prompt (should be
2994 // error) we don't know if we ignore some
2995 // garbage here... but the log should reveal that
2996 error = in.waitForPrompt();
2997 linetype = in.getLineType();
2998 if (error != null) {
2999 error = tmpLine.substring(1) + "\n" + error;
3000 } else {
3001 error = tmpLine.substring(1);
3002 }
3003 break;
3004 } // end of switch (linetype)
3005 } // end of while (linetype != BufferedMCLReader.PROMPT)
3006 } // end of synchronized (server)
3007
3008 if (error != null) {
3009 SQLException ret = null;
3010 final String[] errors = error.split("\n");
3011 for (int i = 0; i < errors.length; i++) {
3012 final SQLException newErr;
3013 if (errors[i].length() >= 6) {
3014 newErr = new SQLException(errors[i].substring(6), errors[i].substring(0, 5));
3015 } else {
3016 newErr = new SQLNonTransientConnectionException(errors[i], "08000");
3017 }
3018 if (ret == null) {
3019 ret = newErr;
3020 } else {
3021 ret.setNextException(newErr);
3022 }
3023 }
3024 throw ret;
3025 }
3026 } catch (SocketTimeoutException e) {
3027 close(); // JDBC 4.1 semantics, abort()
3028 throw new SQLNonTransientConnectionException("connection timed out", "08M33");
3029 } catch (IOException e) {
3030 closed = true;
3031 throw new SQLNonTransientConnectionException(e.getMessage() + " (mserver5 still alive?)", "08006");
3032 }
3033 }
3034 }
3035 // }}}
3036 }