comparison src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java @ 0:a5a898f6886c

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