Mercurial > hg > monetdb-java
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 * &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>&"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>&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>&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>&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 |