Source code: com/mysql/jdbc/NonRegisteringDriver.java
1 /*
2 Copyright (C) 2002-2004 MySQL AB
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of version 2 of the GNU General Public License as
6 published by the Free Software Foundation.
7
8
9 There are special exceptions to the terms and conditions of the GPL
10 as it is applied to this software. View the full text of the
11 exception exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
12 software distribution.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
23 */
24 package com.mysql.jdbc;
25
26 import java.sql.DriverPropertyInfo;
27 import java.sql.SQLException;
28
29 import java.util.Properties;
30 import java.util.StringTokenizer;
31
32
33 /**
34 * The Java SQL framework allows for multiple database drivers. Each driver
35 * should supply a class that implements the Driver interface
36 *
37 * <p>
38 * The DriverManager will try to load as many drivers as it can find and then
39 * for any given connection request, it will ask each driver in turn to try to
40 * connect to the target URL.
41 * </p>
42 *
43 * <p>
44 * It is strongly recommended that each Driver class should be small and
45 * standalone so that the Driver class can be loaded and queried without
46 * bringing in vast quantities of supporting code.
47 * </p>
48 *
49 * <p>
50 * When a Driver class is loaded, it should create an instance of itself and
51 * register it with the DriverManager. This means that a user can load and
52 * register a driver by doing Class.forName("foo.bah.Driver")
53 * </p>
54 *
55 * @author Mark Matthews
56 * @version $Id: NonRegisteringDriver.java,v 1.1.2.20 2004/08/09 22:15:12 mmatthew Exp $
57 *
58 * @see org.gjt.mm.mysql.Connection
59 * @see java.sql.Driver
60 */
61 public class NonRegisteringDriver implements java.sql.Driver {
62 /** Should the driver generate debugging output? */
63 public static final boolean DEBUG = false;
64
65 /** Should the driver generate method-call traces? */
66 public static final boolean TRACE = false;
67
68 /** Index for hostname coming out of parseHostPortPair(). */
69 protected final static int HOST_NAME_INDEX = 0;
70
71 /** Index for port # coming out of parseHostPortPair(). */
72 protected final static int PORT_NUMBER_INDEX = 1;
73
74 /**
75 * Construct a new driver and register it with DriverManager
76 *
77 * @throws java.sql.SQLException if a database error occurs.
78 */
79 public NonRegisteringDriver() throws java.sql.SQLException {
80 // Required for Class.forName().newInstance()
81 }
82
83 /**
84 * Gets the drivers major version number
85 *
86 * @return the drivers major version number
87 */
88 public int getMajorVersion() {
89 return getMajorVersionInternal();
90 }
91
92 /**
93 * Get the drivers minor version number
94 *
95 * @return the drivers minor version number
96 */
97 public int getMinorVersion() {
98 return getMinorVersionInternal();
99 }
100
101 /**
102 * The getPropertyInfo method is intended to allow a generic GUI tool to
103 * discover what properties it should prompt a human for in order to get
104 * enough information to connect to a database.
105 *
106 * <p>
107 * Note that depending on the values the human has supplied so far,
108 * additional values may become necessary, so it may be necessary to
109 * iterate through several calls to getPropertyInfo
110 * </p>
111 *
112 * @param url the Url of the database to connect to
113 * @param info a proposed list of tag/value pairs that will be sent on
114 * connect open.
115 *
116 * @return An array of DriverPropertyInfo objects describing possible
117 * properties. This array may be an empty array if no properties
118 * are required
119 *
120 * @exception java.sql.SQLException if a database-access error occurs
121 *
122 * @see java.sql.Driver#getPropertyInfo
123 */
124 public DriverPropertyInfo[] getPropertyInfo(String url, Properties info)
125 throws java.sql.SQLException {
126 if (info == null) {
127 info = new Properties();
128 }
129
130 if ((url != null) && url.startsWith("jdbc:mysql://")) {
131 info = parseURL(url, info);
132 }
133
134 DriverPropertyInfo hostProp = new DriverPropertyInfo("HOST",
135 info.getProperty("HOST"));
136 hostProp.required = true;
137 hostProp.description = "Hostname of MySQL Server";
138
139 DriverPropertyInfo portProp = new DriverPropertyInfo("PORT",
140 info.getProperty("PORT", "3306"));
141 portProp.required = false;
142 portProp.description = "Port number of MySQL Server";
143
144 DriverPropertyInfo dbProp = new DriverPropertyInfo("DBNAME",
145 info.getProperty("DBNAME"));
146 dbProp.required = false;
147 dbProp.description = "Database name";
148
149 DriverPropertyInfo userProp = new DriverPropertyInfo("user",
150 info.getProperty("user"));
151 userProp.required = true;
152 userProp.description = "Username to authenticate as";
153
154 DriverPropertyInfo passwordProp = new DriverPropertyInfo("password",
155 info.getProperty("password"));
156 passwordProp.required = true;
157 passwordProp.description = "Password to use for authentication";
158
159 DriverPropertyInfo autoReconnect = new DriverPropertyInfo("autoReconnect",
160 info.getProperty("autoReconnect", "false"));
161 autoReconnect.required = false;
162 autoReconnect.choices = new String[] { "true", "false" };
163 autoReconnect.description = "Should the driver try to re-establish bad connections?";
164
165 DriverPropertyInfo maxReconnects = new DriverPropertyInfo("maxReconnects",
166 info.getProperty("maxReconnects", "3"));
167 maxReconnects.required = false;
168 maxReconnects.description = "Maximum number of reconnects to attempt if autoReconnect is true";
169 ;
170
171 DriverPropertyInfo initialTimeout = new DriverPropertyInfo("initialTimeout",
172 info.getProperty("initialTimeout", "2"));
173 initialTimeout.required = false;
174 initialTimeout.description = "Initial timeout (seconds) to wait between failed connections";
175
176 DriverPropertyInfo profileSql = new DriverPropertyInfo("profileSql",
177 info.getProperty("profileSql", "false"));
178 profileSql.required = false;
179 profileSql.choices = new String[] { "true", "false" };
180 profileSql.description = "Trace queries and their execution/fetch times on STDERR (true/false) defaults to false";
181 ;
182
183 DriverPropertyInfo socketTimeout = new DriverPropertyInfo("socketTimeout",
184 info.getProperty("socketTimeout", "0"));
185 socketTimeout.required = false;
186 socketTimeout.description = "Timeout on network socket operations (0 means no timeout)";
187 ;
188
189 DriverPropertyInfo useSSL = new DriverPropertyInfo("useSSL",
190 info.getProperty("useSSL", "false"));
191 useSSL.required = false;
192 useSSL.choices = new String[] { "true", "false" };
193 useSSL.description = "Use SSL when communicating with the server?";
194 ;
195
196 DriverPropertyInfo useCompression = new DriverPropertyInfo("useCompression",
197 info.getProperty("useCompression", "false"));
198 useCompression.required = false;
199 useCompression.choices = new String[] { "true", "false" };
200 useCompression.description = "Use zlib compression when communicating with the server?";
201 ;
202
203 DriverPropertyInfo paranoid = new DriverPropertyInfo("paranoid",
204 info.getProperty("paranoid", "false"));
205 paranoid.required = false;
206 paranoid.choices = new String[] { "true", "false" };
207 paranoid.description = "Expose sensitive information in error messages and clear "
208 + "data structures holding sensitiven data when possible?";
209 ;
210
211 DriverPropertyInfo useHostsInPrivileges = new DriverPropertyInfo("useHostsInPrivileges",
212 info.getProperty("useHostsInPrivileges", "true"));
213 useHostsInPrivileges.required = false;
214 useHostsInPrivileges.choices = new String[] { "true", "false" };
215 useHostsInPrivileges.description = "Add '@hostname' to users in DatabaseMetaData.getColumn/TablePrivileges()";
216 ;
217
218 DriverPropertyInfo interactiveClient = new DriverPropertyInfo("interactiveClient",
219 info.getProperty("interactiveClient", "false"));
220 interactiveClient.required = false;
221 interactiveClient.choices = new String[] { "true", "false" };
222 interactiveClient.description = "Set the CLIENT_INTERACTIVE flag, which tells MySQL "
223 + "to timeout connections based on INTERACTIVE_TIMEOUT instead of WAIT_TIMEOUT";
224 ;
225
226 DriverPropertyInfo useTimezone = new DriverPropertyInfo("useTimezone",
227 info.getProperty("useTimezone", "false"));
228 useTimezone.required = false;
229 useTimezone.choices = new String[] { "true", "false" };
230 useTimezone.description = "Convert time/date types between client and server timezones";
231
232 DriverPropertyInfo serverTimezone = new DriverPropertyInfo("serverTimezone",
233 info.getProperty("serverTimezone", ""));
234 serverTimezone.required = false;
235 serverTimezone.description = "Override detection/mapping of timezone. Used when timezone from server doesn't map to Java timezone";
236
237 DriverPropertyInfo connectTimeout = new DriverPropertyInfo("connectTimeout",
238 info.getProperty("connectTimeout", "0"));
239 connectTimeout.required = false;
240 connectTimeout.description = "Timeout for socket connect (in milliseconds), with 0 being no timeout. Only works on JDK-1.4 or newer. Defaults to '0'.";
241
242 DriverPropertyInfo queriesBeforeRetryMaster = new DriverPropertyInfo("queriesBeforeRetryMaster",
243 info.getProperty("queriesBeforeRetryMaster", "50"));
244 queriesBeforeRetryMaster.required = false;
245 queriesBeforeRetryMaster.description = "Number of queries to issue before falling back to master when failed over "
246 + "(when using multi-host failover). Whichever condition is met first, "
247 + "'queriesBeforeRetryMaster' or 'secondsBeforeRetryMaster' will cause an "
248 + "attempt to be made to reconnect to the master. Defaults to 50.";
249 ;
250
251 DriverPropertyInfo secondsBeforeRetryMaster = new DriverPropertyInfo("secondsBeforeRetryMaster",
252 info.getProperty("secondsBeforeRetryMaster", "30"));
253 secondsBeforeRetryMaster.required = false;
254 secondsBeforeRetryMaster.description = "How long should the driver wait, when failed over, before attempting "
255 + "to reconnect to the master server? Whichever condition is met first, "
256 + "'queriesBeforeRetryMaster' or 'secondsBeforeRetryMaster' will cause an "
257 + "attempt to be made to reconnect to the master. Time in seconds, defaults to 30";
258
259 DriverPropertyInfo useStreamLengthsInPrepStmts = new DriverPropertyInfo("useStreamLengthsInPrepStmts",
260 info.getProperty("useStreamLengthsInPrepStmts", "true"));
261 useStreamLengthsInPrepStmts.required = false;
262 useStreamLengthsInPrepStmts.choices = new String[] { "true", "false" };
263 useStreamLengthsInPrepStmts.description = "Honor stream length parameter in "
264 + "PreparedStatement/ResultSet.setXXXStream() method calls (defaults to 'true')";
265
266 DriverPropertyInfo continueBatchOnError = new DriverPropertyInfo("continueBatchOnError",
267 info.getProperty("continueBatchOnError", "true"));
268 continueBatchOnError.required = false;
269 continueBatchOnError.choices = new String[] { "true", "false" };
270 continueBatchOnError.description = "Should the driver continue processing batch commands if "
271 + "one statement fails. The JDBC spec allows either way (defaults to 'true').";
272
273 DriverPropertyInfo allowLoadLocalInfile = new DriverPropertyInfo("allowLoadLocalInfile",
274 info.getProperty("allowLoadLocalInfile", "true"));
275 allowLoadLocalInfile.required = false;
276 allowLoadLocalInfile.choices = new String[] { "true", "false" };
277 allowLoadLocalInfile.description = "Should the driver allow use of 'LOAD DATA LOCAL INFILE...' (defaults to 'true').";
278
279 DriverPropertyInfo strictUpdates = new DriverPropertyInfo("strictUpdates",
280 info.getProperty("strictUpdates", "true"));
281 strictUpdates.required = false;
282 strictUpdates.choices = new String[] { "true", "false" };
283 strictUpdates.description = "Should the driver do strict checking (all primary keys selected) of updatable result sets?...' (defaults to 'true').";
284
285 DriverPropertyInfo ignoreNonTxTables = new DriverPropertyInfo("ignoreNonTxTables",
286 info.getProperty("ignoreNonTxTables", "false"));
287 ignoreNonTxTables.required = false;
288 ignoreNonTxTables.choices = new String[] { "true", "false" };
289 ignoreNonTxTables.description = "Ignore non-transactional table warning for rollback? (defaults to 'false').";
290
291 DriverPropertyInfo clobberStreamingResults = new DriverPropertyInfo("clobberStreamingResults",
292 info.getProperty("clobberStreamingResults", "false"));
293 clobberStreamingResults.required = false;
294 clobberStreamingResults.choices = new String[] { "true", "false" };
295 clobberStreamingResults.description = "This will cause a 'streaming' ResultSet to be automatically closed, "
296 + "and any oustanding data still streaming from the server to be discarded if another query is executed "
297 + "before all the data has been read from the server.";
298
299 DriverPropertyInfo reconnectAtTxEnd = new DriverPropertyInfo("reconnectAtTxEnd",
300 info.getProperty("reconnectAtTxEnd", "false"));
301 reconnectAtTxEnd.required = false;
302 reconnectAtTxEnd.choices = new String[] { "true", "false" };
303 reconnectAtTxEnd.description = "If autoReconnect is set to true, should the driver attempt reconnections"
304 + "at the end of every transaction? (true/false, defaults to false)";
305
306 DriverPropertyInfo alwaysClearStream = new DriverPropertyInfo("alwaysClearStream",
307 info.getProperty("alwaysClearStream", "false"));
308 alwaysClearStream.required = false;
309 alwaysClearStream.choices = new String[] { "true", "false" };
310 alwaysClearStream.description = "Should the driver clear any remaining data from the input stream before issuing"
311 + " a query? Normally not needed (approx 1-2% perf. penalty, true/false, defaults to false)";
312
313 DriverPropertyInfo cachePrepStmts = new DriverPropertyInfo("cachePrepStmts",
314 info.getProperty("cachePrepStmts", "false"));
315 cachePrepStmts.required = false;
316 cachePrepStmts.choices = new String[] { "true", "false" };
317 cachePrepStmts.description = "Should the driver cache the parsing stage of PreparedStatements (true/false, default is 'false')";
318
319 DriverPropertyInfo prepStmtCacheSize = new DriverPropertyInfo("prepStmtCacheSize",
320 info.getProperty("prepStmtCacheSize", "25"));
321 prepStmtCacheSize.required = false;
322 prepStmtCacheSize.description = "If prepared statement caching is enabled, "
323 + "how many prepared statements should be cached? (default is '25')";
324
325 DriverPropertyInfo prepStmtCacheSqlLimit = new DriverPropertyInfo("prepStmtCacheSqlLimit",
326 info.getProperty("prepStmtCacheSqlLimit", "256"));
327 prepStmtCacheSqlLimit.required = false;
328 prepStmtCacheSqlLimit.description = "If prepared statement caching is enabled, "
329 + "what's the largest SQL the driver will cache the parsing for? (in chars, default is '256')";
330
331 DriverPropertyInfo useUnbufferedInput = new DriverPropertyInfo("useUnbufferedInput",
332 info.getProperty("useUnbufferedInput", "true"));
333 useUnbufferedInput.required = false;
334 useUnbufferedInput.description = "Don't use BufferedInputStream for reading data from the server true/false (default is 'true')";
335
336 DriverPropertyInfo[] dpi = {
337 hostProp, portProp, dbProp, userProp, passwordProp, autoReconnect,
338 maxReconnects, initialTimeout, profileSql, socketTimeout, useSSL,
339 paranoid, useHostsInPrivileges, interactiveClient, useCompression,
340 useTimezone, serverTimezone, connectTimeout,
341 secondsBeforeRetryMaster, queriesBeforeRetryMaster,
342 useStreamLengthsInPrepStmts, continueBatchOnError,
343 allowLoadLocalInfile, strictUpdates, ignoreNonTxTables,
344 reconnectAtTxEnd, alwaysClearStream, cachePrepStmts,
345 prepStmtCacheSize, prepStmtCacheSqlLimit, useUnbufferedInput
346 };
347
348 return dpi;
349 }
350
351 /**
352 * Typically, drivers will return true if they understand the subprotocol
353 * specified in the URL and false if they don't. This driver's protocols
354 * start with jdbc:mysql:
355 *
356 * @param url the URL of the driver
357 *
358 * @return true if this driver accepts the given URL
359 *
360 * @exception java.sql.SQLException if a database-access error occurs
361 *
362 * @see java.sql.Driver#acceptsURL
363 */
364 public boolean acceptsURL(String url) throws java.sql.SQLException {
365 return (parseURL(url, null) != null);
366 }
367
368 /**
369 * Try to make a database connection to the given URL. The driver should
370 * return "null" if it realizes it is the wrong kind of driver to connect
371 * to the given URL. This will be common, as when the JDBC driverManager
372 * is asked to connect to a given URL, it passes the URL to each loaded
373 * driver in turn.
374 *
375 * <p>
376 * The driver should raise an java.sql.SQLException if it is the right
377 * driver to connect to the given URL, but has trouble connecting to the
378 * database.
379 * </p>
380 *
381 * <p>
382 * The java.util.Properties argument can be used to pass arbitrary string
383 * tag/value pairs as connection arguments.
384 * </p>
385 *
386 * <p>
387 * My protocol takes the form:
388 * <PRE>
389 * jdbc:mysql://host:port/database
390 * </PRE>
391 * </p>
392 *
393 * @param url the URL of the database to connect to
394 * @param info a list of arbitrary tag/value pairs as connection arguments
395 *
396 * @return a connection to the URL or null if it isnt us
397 *
398 * @exception java.sql.SQLException if a database access error occurs
399 * @throws SQLException DOCUMENT ME!
400 *
401 * @see java.sql.Driver#connect
402 */
403 public java.sql.Connection connect(String url, Properties info)
404 throws java.sql.SQLException {
405 Properties props = null;
406
407 if ((props = parseURL(url, info)) == null) {
408 return null;
409 } else {
410 try {
411 Connection newConn = new com.mysql.jdbc.Connection(host(props),
412 port(props), props, database(props), url, this);
413
414 return (java.sql.Connection) newConn;
415 } catch (SQLException sqlEx) {
416 // Don't wrap SQLExceptions, throw
417 // them un-changed.
418 throw sqlEx;
419 } catch (Exception ex) {
420 throw new SQLException(
421 "Cannot load connection class because of underlying exception: '"
422 + ex.toString() + "'.",
423 SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE);
424 }
425 }
426 }
427
428 //
429 // return the database name property
430 //
431
432 /**
433 * Returns the database property from <code>props</code>
434 *
435 * @param props the Properties to look for the database property.
436 *
437 * @return the database name.
438 */
439 public String database(Properties props) {
440 return props.getProperty("DBNAME");
441 }
442
443 /**
444 * Returns the hostname property
445 *
446 * @param props the java.util.Properties instance to retrieve the hostname
447 * from.
448 *
449 * @return the hostname
450 */
451 public String host(Properties props) {
452 return props.getProperty("HOST", "localhost");
453 }
454
455 /**
456 * Report whether the driver is a genuine JDBC compliant driver. A driver
457 * may only report "true" here if it passes the JDBC compliance tests,
458 * otherwise it is required to return false. JDBC compliance requires
459 * full support for the JDBC API and full support for SQL 92 Entry Level.
460 *
461 * <p>
462 * MySQL is not SQL92 compliant
463 * </p>
464 *
465 * @return is this driver JDBC compliant?
466 */
467 public boolean jdbcCompliant() {
468 return false;
469 }
470
471 /**
472 * Constructs a new DriverURL, splitting the specified URL into its
473 * component parts
474 *
475 * @param url JDBC URL to parse
476 * @param defaults Default properties
477 *
478 * @return Properties with elements added from the url
479 *
480 * @exception java.sql.SQLException
481 */
482 public Properties parseURL(String url, Properties defaults)
483 throws java.sql.SQLException {
484 Properties urlProps = (defaults != null) ? defaults
485 : new Properties(defaults);
486
487 if (url == null) {
488 return null;
489 } else {
490 /*
491 * Parse parameters after the ? in the URL and remove
492 * them from the original URL.
493 */
494 int index = url.indexOf("?");
495
496 if (index != -1) {
497 String paramString = url.substring(index + 1, url.length());
498 url = url.substring(0, index);
499
500 StringTokenizer queryParams = new StringTokenizer(paramString,
501 "&");
502
503 while (queryParams.hasMoreTokens()) {
504 StringTokenizer vp = new StringTokenizer(queryParams
505 .nextToken(), "=");
506 String param = "";
507
508 if (vp.hasMoreTokens()) {
509 param = vp.nextToken();
510 }
511
512 String value = "";
513
514 if (vp.hasMoreTokens()) {
515 value = vp.nextToken();
516 }
517
518 if ((value.length() > 0) && (param.length() > 0)) {
519 urlProps.put(param, value);
520 }
521 }
522 }
523 }
524
525 if (!StringUtils.startsWithIgnoreCase(url, "jdbc:mysql://")) {
526 return null;
527 }
528
529 url = url.substring(13);
530
531 String hostStuff = null;
532
533 int slashIndex = url.indexOf("/");
534
535 if (slashIndex != -1) {
536 hostStuff = url.substring(0, slashIndex);
537
538 if ((slashIndex + 1) < url.length()) {
539 urlProps.put("DBNAME",
540 url.substring((slashIndex + 1), url.length()));
541 }
542 } else {
543 return null;
544 }
545
546 if ((hostStuff != null) && (hostStuff.length() > 0)) {
547 urlProps.put("HOST", hostStuff);
548 }
549
550 return urlProps;
551 }
552
553 /**
554 * Returns the port number property
555 *
556 * @param props the properties to get the port number from
557 *
558 * @return the port number
559 */
560 public int port(Properties props) {
561 return Integer.parseInt(props.getProperty("PORT", "3306"));
562 }
563
564 //
565 // return the value of any property this driver knows about
566 //
567
568 /**
569 * Returns the given property from <code>props</code>
570 *
571 * @param name the property name
572 * @param props the property instance to look in
573 *
574 * @return the property value, or null if not found.
575 */
576 public String property(String name, Properties props) {
577 return props.getProperty(name);
578 }
579
580 /**
581 * Parses hostPortPair in the form of [host][:port] into an array, with
582 * the element of index HOST_NAME_INDEX being the host (or null if not
583 * specified), and the element of index PORT_NUMBER_INDEX being the port
584 * (or null if not specified).
585 *
586 * @param hostPortPair host and port in form of of [host][:port]
587 *
588 * @return array containing host and port as Strings
589 *
590 * @throws SQLException if a parse error occurs
591 */
592 protected static String[] parseHostPortPair(String hostPortPair)
593 throws SQLException {
594 int portIndex = hostPortPair.indexOf(":");
595
596 String[] splitValues = new String[2];
597
598 String hostname = null;
599
600 if (portIndex != -1) {
601 if ((portIndex + 1) < hostPortPair.length()) {
602 String portAsString = hostPortPair.substring(portIndex + 1);
603 hostname = hostPortPair.substring(0, portIndex);
604
605 splitValues[HOST_NAME_INDEX] = hostname;
606
607 splitValues[PORT_NUMBER_INDEX] = portAsString;
608 } else {
609 throw new SQLException("Must specify port after ':' in connection string",
610 SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE);
611 }
612 } else {
613 splitValues[HOST_NAME_INDEX] = hostPortPair;
614 splitValues[PORT_NUMBER_INDEX] = null;
615 }
616
617 return splitValues;
618 }
619
620 /**
621 * Gets the drivers major version number
622 *
623 * @return the drivers major version number
624 */
625 static int getMajorVersionInternal() {
626 return safeIntParse("3");
627 }
628
629 /**
630 * Get the drivers minor version number
631 *
632 * @return the drivers minor version number
633 */
634 static int getMinorVersionInternal() {
635 return safeIntParse("0");
636 }
637
638 private static int safeIntParse(String intAsString) {
639 try {
640 return Integer.parseInt(intAsString);
641 } catch (NumberFormatException nfe) {
642 return 0;
643 }
644 }
645 }