Mercurial > hg > monetdb-java
view src/main/java/nl/cwi/monetdb/jdbc/MonetDriver.java.in @ 93:eeb71f7d36bf embedded
Fixed a bug on the JDBC MAPI connection from the old code! Fixed the connection properties for an JDBC Embedded connection. To start a JDBC Embedded connection, the user must start the embedded database beforehand with the method MonetDBEmbeddedDatabase.StartDatabase().
author | Pedro Ferreira <pedro.ferreira@monetdbsolutions.com> |
---|---|
date | Fri, 06 Jan 2017 12:36:33 +0000 (2017-01-06) |
parents | 6f74e01c57da |
children | 64530632dc2a |
line wrap: on
line source
/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 1997 - July 2008 CWI, August 2008 - 2017 MonetDB B.V. */ package nl.cwi.monetdb.jdbc; import nl.cwi.monetdb.mcl.connection.MCLException; import nl.cwi.monetdb.mcl.connection.mapi.MapiConnection; import nl.cwi.monetdb.mcl.connection.mapi.MapiLanguage; import nl.cwi.monetdb.mcl.protocol.ProtocolException; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.net.URISyntaxException; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.DriverPropertyInfo; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Types; import java.util.*; import java.util.Map.Entry; import java.util.logging.Logger; /** * A Driver suitable for the MonetDB database. * * This driver will be used by the DriverManager to determine if an URL * is to be handled by this driver, and if it does, then this driver * will supply a Connection suitable for MonetDB. * * This class has no explicit constructor, the default constructor * generated by the Java compiler will be sufficient since nothing has * to be set in order to use this driver. * * This Driver supports MonetDB database URLs. MonetDB URLs are defined * as: * <tt>jdbc:monetdb://<host>[:<port>]/<database></tt> * where [:<port>] denotes that a port is optional. If not * given the default (@JDBC_DEF_PORT@) will be used. * * @author Fabian Groffen * @version @JDBC_MAJOR@.@JDBC_MINOR@ (@JDBC_VER_SUFFIX@) */ final public class MonetDriver implements Driver { // the url kind will be jdbc:monetdb://<host>[:<port>]/<database> // Chapter 9.2.1 from Sun JDBC 3.0 specification /** The prefix of a MonetDB url */ private static final String MONETURL = "jdbc:monetdb://"; /** Major version of this driver */ private static final int DRIVERMAJOR = @JDBC_MAJOR@; /** Minor version of this driver */ private static final int DRIVERMINOR = @JDBC_MINOR@; /** Version suffix string */ private static final String DRIVERVERSIONSUFFIX = "@JDBC_VER_SUFFIX@ based on MCL v@MCL_MAJOR@.@MCL_MINOR@"; // We're not fully compliant, but what we support is compliant /** Whether this driver is JDBC compliant or not */ private static final boolean MONETJDBCCOMPLIANT = false; /** MonetDB default port to connect to */ private static final String PORT = "@JDBC_DEF_PORT@"; private static Class EmbeddedConnectionClass = null; // initialize this class: register it at the DriverManager // Chapter 9.2 from Sun JDBC 3.0 specification static { try { DriverManager.registerDriver(new MonetDriver()); } catch (SQLException e) { e.printStackTrace(); } } //== methods of interface Driver /** * Retrieves whether the driver thinks that it can open a connection to the * given URL. Typically drivers will return true if they understand the * subprotocol specified in the URL and false if they do not. * * @param url the URL of the database * @return true if this driver understands the given URL; false otherwise */ public boolean acceptsURL(String url) { return url != null && url.startsWith(MONETURL); } /** * Retrieves the driver's major version number. Initially this should be 1. * * @return this driver's major version number */ public int getMajorVersion() { return DRIVERMAJOR; } /** * Gets the driver's minor version number. Initially this should be 0. * * @return this driver's minor version number */ public int getMinorVersion() { return DRIVERMINOR; } /** * Gets information about the possible properties for this driver. * * The getPropertyInfo method is intended to allow a generic GUI tool to * discover what properties it should prompt a human for in order to get * enough information to connect to a database. Note that depending on the * values the human has supplied so far, additional values may become * necessary, so it may be necessary to iterate though several calls to the * getPropertyInfo method. * * @param url the URL of the database to which to connect * @param info a proposed list of tag/value pairs that will be sent on connect open * @return an array of DriverPropertyInfo objects describing possible properties. This array may be an empty array * if no properties are required. */ public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) { if (!acceptsURL(url)) return null; List<DriverPropertyInfo> props = new ArrayList<>(); DriverPropertyInfo prop = new DriverPropertyInfo("user", info.getProperty("user")); prop.required = true; prop.description = "The user name to use when authenticating on the database server"; props.add(prop); prop = new DriverPropertyInfo("password", info.getProperty("password")); prop.required = true; prop.description = "The password to use when authenticating on the database server"; props.add(prop); prop = new DriverPropertyInfo("hash", ""); prop.required = false; prop.description = "Force the use of the given hash algorithm during challenge response (one of SHA1, MD5, plain)"; props.add(prop); prop = new DriverPropertyInfo("host", "localhost"); prop.required = false; prop.description = "The MonetDB server hostname (MAPI connection only)"; props.add(prop); prop = new DriverPropertyInfo("port", PORT); prop.required = false; prop.description = "The port to connect to the MonetDB server (MAPI connection only)"; props.add(prop); prop = new DriverPropertyInfo("so_timeout", "0"); prop.required = false; prop.description = "Defines the maximum time to wait in milliseconds on a blocking read socket call (MAPI connection only)"; // this corresponds to the Connection.setNetworkTimeout() method introduced in JDBC 4.1 props.add(prop); prop = new DriverPropertyInfo("database", ""); prop.required = false; prop.description = "The database name to connect (MAPI connection only)"; props.add(prop); prop = new DriverPropertyInfo("follow_redirects", "true"); prop.required = false; prop.description = "Whether redirects issued by the server should be followed (MAPI connection only)"; props.add(prop); prop = new DriverPropertyInfo("treat_blob_as_binary", "false"); prop.required = false; prop.description = "Whether BLOBs on the server should be treated as LONGVARBINARY types, thus mapped to byte[] (MAPI connection only)"; props.add(prop); prop = new DriverPropertyInfo("treat_clob_as_longvarchar", "false"); prop.required = false; prop.description = "Whether CLOBs on the server should be treated as LONGVARCHAR types, thus mapped to String (MAPI connection only)"; props.add(prop); prop = new DriverPropertyInfo("language", "sql"); prop.required = false; prop.description = "What language to use for MonetDB conversations (experts only)"; props.add(prop); prop = new DriverPropertyInfo("embedded", "false"); prop.required = false; prop.description = "Whether or not to use an embedded MonetDB connection"; props.add(prop); DriverPropertyInfo[] dpi = new DriverPropertyInfo[props.size()]; return props.toArray(dpi); } /** * Reports whether this driver is a genuine JDBC Compliant&tm; driver. A * driver may only report true here if it passes the JDBC compliance tests; * otherwise it is required to return false. * * JDBC compliance requires full support for the JDBC API and full support * for SQL 92 Entry Level. It is expected that JDBC compliant drivers will * be available for all the major commercial databases. * * This method is not intended to encourage the development of non-JDBC * compliant drivers, but is a recognition of the fact that some vendors are * interested in using the JDBC API and framework for lightweight databases * that do not support full database functionality, or for special databases * such as document information retrieval where a SQL implementation may not * be feasible. * * @return true if this driver is JDBC Compliant; false otherwise */ public boolean jdbcCompliant() { return MONETJDBCCOMPLIANT; } //== end methods of interface driver /** A static Map containing the mapping between MonetDB types and Java SQL types */ /* use SELECT sqlname, * FROM sys.types order by 1, id; to view all MonetDB types */ /* see http://docs.oracle.com/javase/7/docs/api/java/sql/Types.html to view all supported java SQL types */ private static Map<String, Integer> typeMap = new HashMap<>(); static { // fill the typeMap once // typeMap.put("any", Types.???); typeMap.put("bigint", Types.BIGINT); typeMap.put("blob", Types.BLOB); typeMap.put("boolean", Types.BOOLEAN); typeMap.put("char", Types.CHAR); typeMap.put("clob", Types.CLOB); typeMap.put("date", Types.DATE); typeMap.put("decimal", Types.DECIMAL); typeMap.put("double", Types.DOUBLE); typeMap.put("geometry", Types.OTHER); typeMap.put("geometrya", Types.OTHER); typeMap.put("hugeint", Types.NUMERIC); //but we will convert to java.math.BigInteger typeMap.put("inet", Types.OTHER); typeMap.put("int", Types.INTEGER); typeMap.put("json", Types.OTHER); // typeMap.put("mbr", Types.???); typeMap.put("month_interval", Types.INTEGER); // typeMap.put("oid", Types.BIGINT); // typeMap.put("ptr", Types.???); typeMap.put("real", Types.REAL); typeMap.put("sec_interval", Types.BIGINT); typeMap.put("smallint", Types.SMALLINT); // typeMap.put("table", Types.???); typeMap.put("time", Types.TIME); typeMap.put("timestamp", Types.TIMESTAMP); typeMap.put("timestamptz", Types.TIMESTAMP_WITH_TIMEZONE); typeMap.put("timetz", Types.TIME_WITH_TIMEZONE); typeMap.put("tinyint", Types.TINYINT); //but we will convert to java.lang.Byte typeMap.put("url", Types.OTHER); typeMap.put("uuid", Types.OTHER); typeMap.put("varchar", Types.VARCHAR); typeMap.put("wrd", Types.BIGINT); } /** * Returns the java.sql.Types equivalent of the given MonetDB type. * * @param type the type as used by MonetDB * @return the matching java.sql.Types constant or java.sql.Types.OTHER if nothing matched on the given string */ public static int getJavaType(String type) { // match the currentColumns type on a java.sql.Types constant Integer tp = typeMap.get(type); if (tp != null) { return tp; } else { // this should not be able to happen do not assert, since maybe future versions introduce new types return Types.OTHER; } } private static String TypeMapppingSQL = null; // cache to optimise getSQLTypeMap() /** * Returns a String usable in an SQL statement to map the server types * to values of java.sql.Types using the global static type map. * The returned string will be a SQL CASE x statement where the x is * replaced with the given currentColumns name (or expression) string. * * @param column a String representing the value that should be evaluated in the SQL CASE statement * @return a SQL CASE statement */ static String getSQLTypeMap(String column) { if (TypeMapppingSQL == null) { // first time, compose TypeMappping SQL string StringBuilder val = new StringBuilder((typeMap.size() * (7 + 7 + 7 + 4)) + 14); for (Entry<String, Integer> entry : typeMap.entrySet()) { val.append(" WHEN '").append(entry.getKey()).append("' THEN ").append(entry.getValue().toString()); } val.append(" ELSE ").append(Types.OTHER).append(" END"); // as the typeMap is static, cache this SQL part for all next calls TypeMapppingSQL = val.toString(); } return "CASE " + column + TypeMapppingSQL; } /** * Returns a touched up identifying version string of this driver. * * @return the version string */ public static String getDriverVersion() { return "" + DRIVERMAJOR + "." + DRIVERMINOR + " (" + DRIVERVERSIONSUFFIX + ")"; } public static int getDriverMajorVersion() { return DRIVERMAJOR; } public static int getDriverMinorVersion() { return DRIVERMINOR; } /** * Return the parent Logger of all the Loggers used by this data * source. This should be the Logger farthest from the root Logger * that is still an ancestor of all of the Loggers used by this data * source. Configuring this Logger will affect all of the log * messages generated by the data source. In the worst case, this * may be the root Logger. * * @return the parent Logger for this data source * @throws SQLFeatureNotSupportedException if the data source does not use java.util.logging */ public Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new SQLFeatureNotSupportedException("java.util.logging not in use", "0A000"); } /** * Attempts to make a database connection to the given URL. The driver * should return "null" if it realizes it is the wrong kind of driver to * connect to the given URL. This will be common, as when the JDBC driver * manager is asked to connect to a given URL it passes the URL to each * loaded driver in turn. * * The driver should throw an SQLException if it is the right driver to * connect to the given URL but has trouble connecting to the database. * * The java.util.Properties argument can be used to pass arbitrary string * tag/value pairs as connection arguments. Normally at least "user" and * "password" properties should be included in the Properties object. * * @param url the URL of the database to which to connect * @param info a list of arbitrary string tag/value pairs as connection * arguments. Normally at least a "user" and "password" property * should be included * @return a Connection object that represents a connection to the URL * @throws SQLException if a database access error occurs */ public Connection connect(String url, Properties info) throws SQLException { int tmp; Properties props = new Properties(); props.put("port", PORT); props.putAll(info); info = props; // url should be of style jdbc:monetdb://<host>/<database> if (!acceptsURL(url)) throw new SQLException("Invalid URL: it does not start with: " + MONETURL, "08M26"); // remove leading "jdbc:" so the rest is a valid hierarchical URI URI uri; try { uri = new URI(url.substring(5)); } catch (URISyntaxException e) { throw new SQLException(e.toString(), "08M26"); } String uri_host = uri.getHost(); if (uri_host == null) throw new SQLException("Invalid URL: no hostname given or unparsable in '" + url + "'", "08M26"); info.put("host", uri_host); int uri_port = uri.getPort(); if (uri_port > 0) info.put("port", "" + uri_port); // check the database String uri_path = uri.getPath(); if (uri_path != null && uri_path.length() != 0) { uri_path = uri_path.substring(1); if (!uri_path.trim().isEmpty()) info.put("database", uri_path); } String uri_query = uri.getQuery(); if (uri_query != null) { // handle additional arguments String args[] = uri_query.split("&"); for (String arg : args) { tmp = arg.indexOf('='); if (tmp > 0) info.put(arg.substring(0, tmp), arg.substring(tmp + 1)); } } // finally return the Connection as requested return CreateMonetDBJDBCConnection(info); } @SuppressWarnings("unchecked") private static MonetConnection CreateMonetDBJDBCConnection(Properties props) throws SQLException, IllegalArgumentException { MonetConnection res; boolean isEmbedded = Boolean.parseBoolean(props.getProperty("embedded", "false")); String language = props.getProperty("language", "sql"); String username = props.getProperty("user"); String password = props.getProperty("password"); String hash = props.getProperty("hash"); int sockTimeout = 0; if(isEmbedded) { //instantiate the connection try { if(EmbeddedConnectionClass == null) { EmbeddedConnectionClass = Class.forName("nl.cwi.monetdb.embedded.jdbc.EmbeddedConnection"); } if(EmbeddedConnectionClass == null) { //if it's still null then there is a problem throw new SQLException("EmbeddedConnection Class not found!"); } res = (MonetConnection) EmbeddedConnectionClass .getDeclaredConstructor(Properties.class, String.class, String.class) .newInstance(props, hash, language); } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) { throw new SQLException(e); } } else { String hostname = props.getProperty("host"); if (hostname == null || hostname.trim().isEmpty()) throw new IllegalArgumentException("hostname should not be null or empty"); if (username == null || username.trim().isEmpty()) throw new IllegalArgumentException("user should not be null or empty"); if (password == null || password.trim().isEmpty()) throw new IllegalArgumentException("password should not be null or empty"); String database = props.getProperty("database"); if (database == null || database.trim().isEmpty()) throw new IllegalArgumentException("database should not be null or empty"); boolean blobIsBinary = Boolean.valueOf(props.getProperty("treat_blob_as_binary", "false")); boolean clobIsLongChar = Boolean.valueOf(props.getProperty("treat_clob_as_longvarchar", "false")); boolean negative1 = false, failedparse1 = false; int port = 0; try { port = Integer.parseInt(props.getProperty("port")); } catch (NumberFormatException e) { failedparse1 = true; props.setProperty("port", PORT); } if (port <= 0) { negative1 = true; port = Integer.parseInt(PORT); props.setProperty("port", PORT); } String timeout = props.getProperty("so_timeout", "0"); boolean negative2 = false, failedparse2 = false; try { sockTimeout = Integer.parseInt(timeout); } catch (NumberFormatException e) { sockTimeout = 0; failedparse2 = true; props.setProperty("so_timeout", "0"); } if (sockTimeout < 0) { negative2 = true; sockTimeout = 0; props.setProperty("so_timeout", "0"); } try { res = new MapiConnection(props, hash, language, blobIsBinary, clobIsLongChar, hostname, port, database); } catch (IOException e) { throw new SQLException(e); } if(failedparse1) { res.addWarning("Unable to parse port number from: " + port, "M1M05"); } if(negative1) { res.addWarning("Negative port not allowed. Value ignored", "M1M05"); } if(failedparse2) { res.addWarning("Unable to parse socket timeout number from: " + timeout, "M1M05"); } if(negative2) { res.addWarning("Negative socket timeout not allowed. Value ignored", "M1M05"); } res.setSoTimeout(sockTimeout); } try { //atempt to connect and authenticate the user List<String> warnings = res.connect(username, password); if(warnings != null) { for (String warning : warnings) { res.addWarning(warning, "01M02"); } } // apply NetworkTimeout value from legacy (pre 4.1) driver so_timeout calls if(!isEmbedded) { res.setSoTimeout(sockTimeout); } } catch (IOException e) { if(!isEmbedded) { MapiConnection con = (MapiConnection) res; throw new SQLException("Unable to connect (" + con.getHostname() + ":" + con.getPort() + "): " + e.getMessage(), "08006"); } else { throw new SQLException("Unable to connect: " + e.getMessage(), "08006"); } } catch (ProtocolException e) { throw new SQLException(e.getMessage(), "08001"); } catch (MCLException e) { String[] connex = e.getMessage().split("\n"); SQLException sqle = new SQLException(connex[0], "08001", e); for (int i = 1; i < connex.length; i++) { sqle.setNextException(new SQLException(connex[1], "08001")); } throw sqle; } if (!isEmbedded && res.getLanguage() == MapiLanguage.LANG_SQL) { //set the timezone only in the MAPI connection // enable auto commit res.setAutoCommit(true); // set our time zone on the server Calendar cal = Calendar.getInstance(); int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET); offset /= (60 * 1000); // milliseconds to minutes String tz = offset < 0 ? "-" : "+"; tz += (Math.abs(offset) / 60 < 10 ? "0" : "") + (Math.abs(offset) / 60) + ":"; offset -= (offset / 60) * 60; tz += (offset < 10 ? "0" : "") + offset; res.sendIndependentCommand("SET TIME ZONE INTERVAL '" + tz + "' HOUR TO MINUTE"); } return res; } }