提交 460a7531 authored 作者: Noel Grandin's avatar Noel Grandin

Merge branch 'NiklasMehner-master'

......@@ -51,6 +51,9 @@ table with an LOB column.
<li>Issue #231: Possible infinite loop when initializing the ObjectDataType class
when concurrently writing into MVStore.
</li>
<ul>
<li>Added support for Connection.setClientInfo() in compatibility modes for DB2, Postgresql, Oracle and MySQL.
</li>
</ul>
<h2>Version 1.4.191 Beta (2016-01-21)</h2>
......
......@@ -5,10 +5,12 @@
*/
package org.h2.engine;
import java.util.HashMap;
import org.h2.util.New;
import org.h2.util.StringUtils;
import java.util.HashMap;
import java.util.regex.Pattern;
/**
* The compatibility modes. There is a fixed set of modes (for example
* PostgreSQL, MySQL). Each mode has different settings.
......@@ -137,6 +139,11 @@ public class Mode {
*/
public boolean onDuplicateKeyUpdate;
/**
* Pattern describing the keys the java.sql.Connection.setClientInfo() method accepts.
*/
public Pattern supportedClientInfoPropertiesRegEx;
/**
* Support the # for column names
*/
......@@ -154,6 +161,9 @@ public class Mode {
mode.supportOffsetFetch = true;
mode.sysDummy1 = true;
mode.isolationLevelInSelectOrInsertStatement = true;
// See https://www.ibm.com/support/knowledgecenter/SSEPEK_11.0.0/com.ibm.db2z11.doc.java/src/tpc/imjcc_r0052001.dita
mode.supportedClientInfoPropertiesRegEx =
Pattern.compile("ApplicationName|ClientAccountingInformation|ClientUser|ClientCorrelationToken");
add(mode);
mode = new Mode("Derby");
......@@ -162,6 +172,8 @@ public class Mode {
mode.supportOffsetFetch = true;
mode.sysDummy1 = true;
mode.isolationLevelInSelectOrInsertStatement = true;
// Derby does not support client info properties as of version 10.12.1.1
mode.supportedClientInfoPropertiesRegEx = null;
add(mode);
mode = new Mode("HSQLDB");
......@@ -170,6 +182,9 @@ public class Mode {
mode.nullConcatIsNull = true;
mode.uniqueIndexSingleNull = true;
mode.allowPlusForStringConcat = true;
// HSQLDB does not support client info properties. See
// http://hsqldb.org/doc/apidocs/org/hsqldb/jdbc/JDBCConnection.html#setClientInfo%28java.lang.String,%20java.lang.String%29
mode.supportedClientInfoPropertiesRegEx = null;
add(mode);
mode = new Mode("MSSQLServer");
......@@ -179,6 +194,9 @@ public class Mode {
mode.allowPlusForStringConcat = true;
mode.swapConvertFunctionParameters = true;
mode.supportPoundSymbolForColumnNames = true;
// MS SQL Server does not support client info properties. See
// https://msdn.microsoft.com/en-Us/library/dd571296%28v=sql.110%29.aspx
mode.supportedClientInfoPropertiesRegEx = null;
add(mode);
mode = new Mode("MySQL");
......@@ -186,6 +204,9 @@ public class Mode {
mode.indexDefinitionInCreateTable = true;
mode.lowerCaseIdentifiers = true;
mode.onDuplicateKeyUpdate = true;
// MySQL allows to use any key for client info entries. See
// http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.24/com/mysql/jdbc/JDBC4CommentClientInfoProvider.java
mode.supportedClientInfoPropertiesRegEx = Pattern.compile(".*");
add(mode);
mode = new Mode("Oracle");
......@@ -194,6 +215,9 @@ public class Mode {
mode.uniqueIndexSingleNullExceptAllColumnsAreNull = true;
mode.treatEmptyStringsAsNull = true;
mode.supportPoundSymbolForColumnNames = true;
// Oracle accepts keys of the form <namespace>.*. See
// https://docs.oracle.com/database/121/JJDBC/jdbcvers.htm#JJDBC29006
mode.supportedClientInfoPropertiesRegEx = Pattern.compile(".*\\..*");
add(mode);
mode = new Mode("PostgreSQL");
......@@ -203,6 +227,9 @@ public class Mode {
mode.systemColumns = true;
mode.logIsLogBase10 = true;
mode.serialColumnIsNotPK = true;
// PostgreSQL only supports the ApplicationName property. See
// https://github.com/hhru/postgres-jdbc/blob/master/postgresql-jdbc-9.2-1002.src/org/postgresql/jdbc4/AbstractJdbc4Connection.java
mode.supportedClientInfoPropertiesRegEx = Pattern.compile("ApplicationName");
add(mode);
}
......
......@@ -12,6 +12,7 @@ import java.io.Reader;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.ClientInfoStatus;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
......@@ -25,13 +26,12 @@ import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.ArrayList;
import java.util.Map;
import java.util.Properties;
import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface;
import org.h2.engine.ConnectionInfo;
import org.h2.engine.Constants;
import org.h2.engine.Mode;
import org.h2.engine.SessionInterface;
import org.h2.engine.SessionRemote;
import org.h2.engine.SysProperties;
......@@ -51,6 +51,13 @@ import org.h2.value.ValueString;
import java.util.concurrent.Executor;
//*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
/**
* <p>
* Represents a connection (session) to a database.
......@@ -63,6 +70,9 @@ import java.util.concurrent.Executor;
*/
public class JdbcConnection extends TraceObject implements Connection {
private static final String NUM_SERVERS = "numServers";
private static final String PREFIX_SERVER = "server";
private static boolean keepOpenStackTrace;
private final String url;
......@@ -83,6 +93,8 @@ public class JdbcConnection extends TraceObject implements Connection {
private final CloseWatcher watcher;
private int queryTimeoutCache = -1;
private Map<String, String> clientInfo;
/**
* INTERNAL
*/
......@@ -116,6 +128,7 @@ public class JdbcConnection extends TraceObject implements Connection {
this.url = ci.getURL();
closeOld();
watcher = CloseWatcher.register(this, session, keepOpenStackTrace);
this.clientInfo = new HashMap<String, String>();
} catch (Exception e) {
throw logAndConvert(e);
}
......@@ -139,6 +152,7 @@ public class JdbcConnection extends TraceObject implements Connection {
this.getReadOnly = clone.getReadOnly;
this.rollback = clone.rollback;
this.watcher = null;
this.clientInfo = new HashMap<String, String>(clone.clientInfo);
}
/**
......@@ -1661,10 +1675,20 @@ public class JdbcConnection extends TraceObject implements Connection {
/**
* Set a client property.
* This method always throws a SQLClientInfoException.
* This method always throws a SQLClientInfoException in standard mode.
* In compatibility mode the following properties are supported:
* <p><ul>
* <li>DB2: The properties: ApplicationName, ClientAccountingInformation, ClientUser and ClientCorrelationToken
* are supported.
* <li>MySQL: All property names are supported.
* <li>Oracle: All properties in the form <namespace>.<key name> are supported.
* <li>PostgreSQL: The ApplicationName property is supported.
* </ul><p>
*
* @param name the name of the property (ignored)
* @param value the value (ignored)
* For unsupported properties a SQLClientInfoException is thrown.
*
* @param name the name of the property
* @param value the value
*/
@Override
public void setClientInfo(String name, String value)
......@@ -1676,13 +1700,32 @@ public class JdbcConnection extends TraceObject implements Connection {
+quote(value)+");");
}
checkClosed();
// we don't have any client properties, so just throw
throw new SQLClientInfoException();
if (isInternalProperty(name)) {
throw new SQLClientInfoException("Property name '" + name + " is used internally by H2.",
Collections.<String, ClientInfoStatus> emptyMap());
}
Pattern clientInfoNameRegEx = getMode().supportedClientInfoPropertiesRegEx;
if (clientInfoNameRegEx != null && clientInfoNameRegEx.matcher(name).matches()) {
clientInfo.put(name, value);
} else {
throw new SQLClientInfoException("Client info name '" + name + "' not supported.",
Collections.<String, ClientInfoStatus> emptyMap());
}
} catch (Exception e) {
throw convertToClientInfoException(logAndConvert(e));
}
}
private boolean isInternalProperty(String name) {
return NUM_SERVERS.equals(name) || name.startsWith(PREFIX_SERVER);
}
private Mode getMode() throws SQLException {
return Mode.getInstance(((JdbcDatabaseMetaData) getMetaData()).getMode());
}
private static SQLClientInfoException convertToClientInfoException(
SQLException x) {
if (x instanceof SQLClientInfoException) {
......@@ -1693,8 +1736,10 @@ public class JdbcConnection extends TraceObject implements Connection {
}
/**
* Set the client properties.
* This method always throws a SQLClientInfoException.
* Set the client properties. This replaces all existing properties.
*
* This method always throws a SQLClientInfoException in standard mode. In compatibility mode
* some properties may be supported (see setProperty(String, String) for details).
*
* @param properties the properties (ignored)
*/
......@@ -1705,8 +1750,10 @@ public class JdbcConnection extends TraceObject implements Connection {
debugCode("setClientInfo(properties);");
}
checkClosed();
// we don't have any client properties, so just throw
throw new SQLClientInfoException();
clientInfo.clear();
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
setClientInfo((String) entry.getKey(), (String) entry.getValue());
}
} catch (Exception e) {
throw convertToClientInfoException(logAndConvert(e));
}
......@@ -1727,10 +1774,16 @@ public class JdbcConnection extends TraceObject implements Connection {
ArrayList<String> serverList = session.getClusterServers();
Properties p = new Properties();
p.setProperty("numServers", String.valueOf(serverList.size()));
for (Map.Entry<String, String> entry : clientInfo.entrySet()) {
p.setProperty(entry.getKey(), entry.getValue());
}
p.setProperty(NUM_SERVERS, String.valueOf(serverList.size()));
for (int i = 0; i < serverList.size(); i++) {
p.setProperty("server" + String.valueOf(i), serverList.get(i));
p.setProperty(PREFIX_SERVER + String.valueOf(i), serverList.get(i));
}
return p;
} catch (Exception e) {
throw logAndConvert(e);
......@@ -1740,8 +1793,8 @@ public class JdbcConnection extends TraceObject implements Connection {
/**
* Get a client property.
*
* @param name the client info name (ignored)
* @return the property value
* @param name the client info name
* @return the property value or null if the property is not found or not supported.
*/
@Override
public String getClientInfo(String name) throws SQLException {
......@@ -1750,12 +1803,7 @@ public class JdbcConnection extends TraceObject implements Connection {
debugCodeCall("getClientInfo", name);
}
checkClosed();
Properties p = getClientInfo();
String s = p.getProperty(name);
if (s == null) {
throw new SQLClientInfoException();
}
return s;
return getClientInfo().getProperty(name);
} catch (Exception e) {
throw logAndConvert(e);
}
......
......@@ -11,6 +11,8 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.RowIdLifetime;
import java.sql.SQLException;
import java.util.Properties;
import org.h2.engine.Constants;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
......@@ -3077,6 +3079,7 @@ public class JdbcDatabaseMetaData extends TraceObject implements
*/
@Override
public ResultSet getClientInfoProperties() throws SQLException {
Properties clientInfo = conn.getClientInfo();
// we don't have any client properties, so return an empty result set
return new SimpleResultSet();
}
......@@ -3163,7 +3166,7 @@ public class JdbcDatabaseMetaData extends TraceObject implements
return getTraceObjectName() + ": " + conn;
}
private String getMode() throws SQLException {
String getMode() throws SQLException {
if (mode == null) {
PreparedStatement prep = conn.prepareStatement(
"SELECT VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE NAME=?");
......
......@@ -86,6 +86,7 @@ import org.h2.test.jdbc.TestBatchUpdates;
import org.h2.test.jdbc.TestCallableStatement;
import org.h2.test.jdbc.TestCancel;
import org.h2.test.jdbc.TestConcurrentConnectionUsage;
import org.h2.test.jdbc.TestConnection;
import org.h2.test.jdbc.TestDatabaseEventListener;
import org.h2.test.jdbc.TestDriver;
import org.h2.test.jdbc.TestJavaObject;
......@@ -182,6 +183,7 @@ import org.h2.test.unit.TestIntPerfectHash;
import org.h2.test.unit.TestJmx;
import org.h2.test.unit.TestLocale;
import org.h2.test.unit.TestMathUtils;
import org.h2.test.unit.TestMode;
import org.h2.test.unit.TestModifyOnWrite;
import org.h2.test.unit.TestNetUtils;
import org.h2.test.unit.TestObjectDeserialization;
......@@ -723,6 +725,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestCallableStatement());
addTest(new TestCancel());
addTest(new TestConcurrentConnectionUsage());
addTest(new TestConnection());
addTest(new TestDatabaseEventListener());
addTest(new TestJavaObject());
addTest(new TestLimitUpdates());
......@@ -835,6 +838,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestIntPerfectHash());
addTest(new TestJmx());
addTest(new TestMathUtils());
addTest(new TestMode());
addTest(new TestModifyOnWrite());
addTest(new TestOldVersion());
addTest(new TestObjectDeserialization());
......
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.jdbc;
import org.h2.test.TestBase;
import java.sql.Connection;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.util.Properties;
/**
* Tests the client info
*/
public class TestConnection extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
testSetSupportedClientInfo();
testSetUnsupportedClientInfo();
testGetUnsupportedClientInfo();
testSetSupportedClientInfoProperties();
testSetUnsupportedClientInfoProperties();
testSetInternalProperty();
}
private void testSetInternalProperty() throws SQLException {
// Use MySQL-mode since this allows all property names (apart from h2 internal names).
Connection conn = getConnection("clientInfoMySQL;MODE=MySQL");
assertThrows(SQLClientInfoException.class, conn).setClientInfo("numServers", "SomeValue");
assertThrows(SQLClientInfoException.class, conn).setClientInfo("server23", "SomeValue");
}
private void testSetUnsupportedClientInfoProperties() throws SQLException {
Connection conn = getConnection("clientInfo");
Properties properties = new Properties();
properties.put("ClientUser", "someuser");
assertThrows(SQLClientInfoException.class, conn).setClientInfo(properties);
}
private void testSetSupportedClientInfoProperties() throws SQLException {
Connection conn = getConnection("clientInfoDB2;MODE=DB2");
conn.setClientInfo("ApplicationName", "Connection Test");
Properties properties = new Properties();
properties.put("ClientUser", "someuser");
conn.setClientInfo(properties);
// old property should have been removed
assertNull(conn.getClientInfo("ApplicationName"));
// new property has been set
assertEquals(conn.getClientInfo("ClientUser"), "someuser");
}
private void testSetSupportedClientInfo() throws SQLException {
Connection conn = getConnection("clientInfoDB2;MODE=DB2");
conn.setClientInfo("ApplicationName", "Connection Test");
assertEquals(conn.getClientInfo("ApplicationName"), "Connection Test");
}
private void testSetUnsupportedClientInfo() throws SQLException {
Connection conn = getConnection("clientInfoDB2;MODE=DB2");
assertThrows(SQLClientInfoException.class, conn).setClientInfo("UnsupportedName", "SomeValue");
}
private void testGetUnsupportedClientInfo() throws SQLException {
Connection conn = getConnection("clientInfo");
assertNull(conn.getClientInfo("UnknownProperty"));
}
}
......@@ -10,7 +10,6 @@ import java.sql.DatabaseMetaData;
import java.sql.Driver;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
......@@ -1199,7 +1198,7 @@ public class TestMetaData extends TestBase {
private void testClientInfo() throws SQLException {
Connection conn = getConnection("metaData");
assertThrows(SQLClientInfoException.class, conn).getClientInfo("xxx");
assertNull(conn.getClientInfo("xxx"));
DatabaseMetaData meta = conn.getMetaData();
ResultSet rs = meta.getClientInfoProperties();
assertFalse(rs.next());
......
......@@ -7,12 +7,10 @@ package org.h2.test.jdbc;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Properties;
import org.h2.api.ErrorCode;
import org.h2.engine.SysProperties;
......@@ -73,11 +71,6 @@ public class TestStatement extends TestBase {
map.put("x", Object.class);
assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, conn).
setTypeMap(map);
assertThrows(SQLClientInfoException.class, conn).
setClientInfo("X", "Y");
assertThrows(SQLClientInfoException.class, conn).
setClientInfo(new Properties());
}
private void testTraceError() throws Exception {
......
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.unit;
import org.h2.engine.Mode;
import org.h2.test.TestBase;
/**
* Unit test for the Mode class.
*/
public class TestMode extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String[] a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
testDb2ClientInfo();
testDerbyClientInfo();
testHsqlDbClientInfo();
testMsSqlServerClientInfo();
testMySqlClientInfo();
testOracleClientInfo();
testPostgresqlClientInfo();
}
private void testDb2ClientInfo() {
Mode db2Mode = Mode.getInstance("DB2");
assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher("ApplicationName").matches());
assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher("ClientAccountingInformation").matches());
assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher("ClientUser").matches());
assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher("ClientCorrelationToken").matches());
assertFalse(db2Mode.supportedClientInfoPropertiesRegEx.matcher("AnyOtherValue").matches());
}
private void testDerbyClientInfo() {
Mode derbyMode = Mode.getInstance("Derby");
assertNull(derbyMode.supportedClientInfoPropertiesRegEx);
}
private void testHsqlDbClientInfo() {
Mode hsqlMode = Mode.getInstance("HSQLDB");
assertNull(hsqlMode.supportedClientInfoPropertiesRegEx);
}
private void testMsSqlServerClientInfo() {
Mode msSqlMode = Mode.getInstance("MSSQLServer");
assertNull(msSqlMode.supportedClientInfoPropertiesRegEx);
}
private void testMySqlClientInfo() {
Mode mySqlMode = Mode.getInstance("MySQL");
assertTrue(mySqlMode.supportedClientInfoPropertiesRegEx.matcher("AnyString").matches());
}
private void testOracleClientInfo() {
Mode oracleMode = Mode.getInstance("Oracle");
assertTrue(oracleMode.supportedClientInfoPropertiesRegEx.matcher("anythingContaining.aDot").matches());
assertFalse(oracleMode.supportedClientInfoPropertiesRegEx.matcher("anythingContainingNoDot").matches());
}
private void testPostgresqlClientInfo() {
Mode postgresqlMode = Mode.getInstance("PostgreSQL");
assertTrue(postgresqlMode.supportedClientInfoPropertiesRegEx.matcher("ApplicationName").matches());
assertFalse(postgresqlMode.supportedClientInfoPropertiesRegEx.matcher("AnyOtherValue").matches());
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论