IGNITE-8148: JDBC thin: semicolon as delimiter for properties. This closes #3794.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/14402e40 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/14402e40 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/14402e40 Branch: refs/heads/ignite-7708 Commit: 14402e4030cf0257b72c636894f140f346266299 Parents: 5a29276 Author: devozerov <voze...@gridgain.com> Authored: Wed Apr 11 16:44:33 2018 +0300 Committer: devozerov <voze...@gridgain.com> Committed: Wed Apr 11 16:44:33 2018 +0300 ---------------------------------------------------------------------- .../jdbc/thin/JdbcThinConnectionSelfTest.java | 233 +++++++++++++++---- .../jdbc/thin/ConnectionPropertiesImpl.java | 161 +++++++++---- 2 files changed, 300 insertions(+), 94 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/14402e40/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java index 14b91b2..ed0b324 100644 --- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinConnectionSelfTest.java @@ -185,86 +185,136 @@ public class JdbcThinConnectionSelfTest extends JdbcThinAbstractSelfTest { } /** + * Test invalid socket buffer sizes with semicolon. + * + * @throws Exception If failed. + */ + public void testSocketBuffersSemicolon() throws Exception { + final int dfltDufSize = 64 * 1024; + + assertInvalid("jdbc:ignite:thin://127.0.0.1;socketSendBuffer=-1", + "Property cannot be lower than 0 [name=socketSendBuffer, value=-1]"); + + assertInvalid("jdbc:ignite:thin://127.0.0.1;socketReceiveBuffer=-1", + "Property cannot be lower than 0 [name=socketReceiveBuffer, value=-1]"); + + // Note that SO_* options are hints, so we check that value is equals to either what we set or to default. + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;socketSendBuffer=1024")) { + assertEquals(1024, io(conn).connectionProperties().getSocketSendBuffer()); + assertEquals(dfltDufSize, io(conn).connectionProperties().getSocketReceiveBuffer()); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;socketReceiveBuffer=1024")) { + assertEquals(dfltDufSize, io(conn).connectionProperties().getSocketSendBuffer()); + assertEquals(1024, io(conn).connectionProperties().getSocketReceiveBuffer()); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;" + + "socketSendBuffer=1024;socketReceiveBuffer=2048")) { + assertEquals(1024, io(conn).connectionProperties().getSocketSendBuffer()); + assertEquals(2048, io(conn).connectionProperties().getSocketReceiveBuffer()); + } + } + + /** * Test SQL hints. * * @throws Exception If failed. */ public void testSqlHints() throws Exception { try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1")) { - assertFalse(io(conn).connectionProperties().isDistributedJoins()); - assertFalse(io(conn).connectionProperties().isEnforceJoinOrder()); - assertFalse(io(conn).connectionProperties().isCollocated()); - assertFalse(io(conn).connectionProperties().isReplicatedOnly()); - assertFalse(io(conn).connectionProperties().isLazy()); - assertFalse(io(conn).connectionProperties().isSkipReducerOnUpdate()); + assertHints(conn, false, false, false, false, false, false); } try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1?distributedJoins=true")) { - assertTrue(io(conn).connectionProperties().isDistributedJoins()); - assertFalse(io(conn).connectionProperties().isEnforceJoinOrder()); - assertFalse(io(conn).connectionProperties().isCollocated()); - assertFalse(io(conn).connectionProperties().isReplicatedOnly()); - assertFalse(io(conn).connectionProperties().isLazy()); - assertFalse(io(conn).connectionProperties().isSkipReducerOnUpdate()); + assertHints(conn, true, false, false, false, false, false); } try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1?enforceJoinOrder=true")) { - assertFalse(io(conn).connectionProperties().isDistributedJoins()); - assertTrue(io(conn).connectionProperties().isEnforceJoinOrder()); - assertFalse(io(conn).connectionProperties().isCollocated()); - assertFalse(io(conn).connectionProperties().isReplicatedOnly()); - assertFalse(io(conn).connectionProperties().isLazy()); - assertFalse(io(conn).connectionProperties().isSkipReducerOnUpdate()); + assertHints(conn, false, true, false, false, false, false); } try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1?collocated=true")) { - assertFalse(io(conn).connectionProperties().isDistributedJoins()); - assertFalse(io(conn).connectionProperties().isEnforceJoinOrder()); - assertTrue(io(conn).connectionProperties().isCollocated()); - assertFalse(io(conn).connectionProperties().isReplicatedOnly()); - assertFalse(io(conn).connectionProperties().isLazy()); - assertFalse(io(conn).connectionProperties().isSkipReducerOnUpdate()); + assertHints(conn, false, false, true, false, false, false); } try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1?replicatedOnly=true")) { - assertFalse(io(conn).connectionProperties().isDistributedJoins()); - assertFalse(io(conn).connectionProperties().isEnforceJoinOrder()); - assertFalse(io(conn).connectionProperties().isCollocated()); - assertTrue(io(conn).connectionProperties().isReplicatedOnly()); - assertFalse(io(conn).connectionProperties().isLazy()); - assertFalse(io(conn).connectionProperties().isSkipReducerOnUpdate()); + assertHints(conn, false, false, false, true, false, false); } try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1?lazy=true")) { - assertFalse(io(conn).connectionProperties().isDistributedJoins()); - assertFalse(io(conn).connectionProperties().isEnforceJoinOrder()); - assertFalse(io(conn).connectionProperties().isCollocated()); - assertFalse(io(conn).connectionProperties().isReplicatedOnly()); - assertTrue(io(conn).connectionProperties().isLazy()); - assertFalse(io(conn).connectionProperties().isSkipReducerOnUpdate()); + assertHints(conn, false, false, false, false, true, false); } try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1?skipReducerOnUpdate=true")) { - assertFalse(io(conn).connectionProperties().isDistributedJoins()); - assertFalse(io(conn).connectionProperties().isEnforceJoinOrder()); - assertFalse(io(conn).connectionProperties().isCollocated()); - assertFalse(io(conn).connectionProperties().isReplicatedOnly()); - assertFalse(io(conn).connectionProperties().isLazy()); - assertTrue(io(conn).connectionProperties().isSkipReducerOnUpdate()); + assertHints(conn, false, false, false, false, false, true); } try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1?distributedJoins=true&" + "enforceJoinOrder=true&collocated=true&replicatedOnly=true&lazy=true&skipReducerOnUpdate=true")) { - assertTrue(io(conn).connectionProperties().isDistributedJoins()); - assertTrue(io(conn).connectionProperties().isEnforceJoinOrder()); - assertTrue(io(conn).connectionProperties().isCollocated()); - assertTrue(io(conn).connectionProperties().isReplicatedOnly()); - assertTrue(io(conn).connectionProperties().isLazy()); - assertTrue(io(conn).connectionProperties().isSkipReducerOnUpdate()); + assertHints(conn, true, true, true, true, true, true); } } /** + * Test SQL hints with semicolon. + * + * @throws Exception If failed. + */ + public void testSqlHintsSemicolon() throws Exception { + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;distributedJoins=true")) { + assertHints(conn, true, false, false, false, false, false); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;enforceJoinOrder=true")) { + assertHints(conn, false, true, false, false, false, false); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;collocated=true")) { + assertHints(conn, false, false, true, false, false, false); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;replicatedOnly=true")) { + assertHints(conn, false, false, false, true, false, false); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;lazy=true")) { + assertHints(conn, false, false, false, false, true, false); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;skipReducerOnUpdate=true")) { + assertHints(conn, false, false, false, false, false, true); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;distributedJoins=true;" + + "enforceJoinOrder=true;collocated=true;replicatedOnly=true;lazy=true;skipReducerOnUpdate=true")) { + assertHints(conn, true, true, true, true, true, true); + } + } + + /** + * Assert hints. + * + * @param conn Connection. + * @param distributedJoins Distributed joins. + * @param enforceJoinOrder Enforce join order. + * @param collocated Co-located. + * @param replicatedOnly Replicated only. + * @param lazy Lazy. + * @param skipReducerOnUpdate Skip reducer on update. + * @throws Exception If failed. + */ + private void assertHints(Connection conn, boolean distributedJoins, boolean enforceJoinOrder, boolean collocated, + boolean replicatedOnly, boolean lazy, boolean skipReducerOnUpdate)throws Exception { + assertEquals(distributedJoins, io(conn).connectionProperties().isDistributedJoins()); + assertEquals(enforceJoinOrder, io(conn).connectionProperties().isEnforceJoinOrder()); + assertEquals(collocated, io(conn).connectionProperties().isCollocated()); + assertEquals(replicatedOnly, io(conn).connectionProperties().isReplicatedOnly()); + assertEquals(lazy, io(conn).connectionProperties().isLazy()); + assertEquals(skipReducerOnUpdate, io(conn).connectionProperties().isSkipReducerOnUpdate()); + } + + /** * Test TCP no delay property handling. * * @throws Exception If failed. @@ -304,6 +354,41 @@ public class JdbcThinConnectionSelfTest extends JdbcThinAbstractSelfTest { } /** + * Test TCP no delay property handling with semicolon. + * + * @throws Exception If failed. + */ + public void testTcpNoDelaySemicolon() throws Exception { + assertInvalid("jdbc:ignite:thin://127.0.0.1;tcpNoDelay=0", + "Invalid property value. [name=tcpNoDelay, val=0, choices=[true, false]]"); + + assertInvalid("jdbc:ignite:thin://127.0.0.1;tcpNoDelay=1", + "Invalid property value. [name=tcpNoDelay, val=1, choices=[true, false]]"); + + assertInvalid("jdbc:ignite:thin://127.0.0.1;tcpNoDelay=false1", + "Invalid property value. [name=tcpNoDelay, val=false1, choices=[true, false]]"); + + assertInvalid("jdbc:ignite:thin://127.0.0.1;tcpNoDelay=true1", + "Invalid property value. [name=tcpNoDelay, val=true1, choices=[true, false]]"); + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;tcpNoDelay=true")) { + assertTrue(io(conn).connectionProperties().isTcpNoDelay()); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;tcpNoDelay=True")) { + assertTrue(io(conn).connectionProperties().isTcpNoDelay()); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;tcpNoDelay=false")) { + assertFalse(io(conn).connectionProperties().isTcpNoDelay()); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;tcpNoDelay=False")) { + assertFalse(io(conn).connectionProperties().isTcpNoDelay()); + } + } + + /** * Test autoCloseServerCursor property handling. * * @throws Exception If failed. @@ -340,6 +425,38 @@ public class JdbcThinConnectionSelfTest extends JdbcThinAbstractSelfTest { } /** + * Test autoCloseServerCursor property handling with semicolon. + * + * @throws Exception If failed. + */ + public void testAutoCloseServerCursorPropertySemicolon() throws Exception { + String url = "jdbc:ignite:thin://127.0.0.1;autoCloseServerCursor"; + + String err = "Invalid property value. [name=autoCloseServerCursor"; + + assertInvalid(url + "=0", err); + assertInvalid(url + "=1", err); + assertInvalid(url + "=false1", err); + assertInvalid(url + "=true1", err); + + try (Connection conn = DriverManager.getConnection(url + "=true")) { + assertTrue(io(conn).connectionProperties().isAutoCloseServerCursor()); + } + + try (Connection conn = DriverManager.getConnection(url + "=True")) { + assertTrue(io(conn).connectionProperties().isAutoCloseServerCursor()); + } + + try (Connection conn = DriverManager.getConnection(url + "=false")) { + assertFalse(io(conn).connectionProperties().isAutoCloseServerCursor()); + } + + try (Connection conn = DriverManager.getConnection(url + "=False")) { + assertFalse(io(conn).connectionProperties().isAutoCloseServerCursor()); + } + } + + /** * Test schema property in URL. * * @throws Exception If failed. @@ -362,6 +479,25 @@ public class JdbcThinConnectionSelfTest extends JdbcThinAbstractSelfTest { } /** + * Test schema property in URL with semicolon. + * + * @throws Exception If failed. + */ + public void testSchemaSemicolon() throws Exception { + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;schema=public")) { + assertEquals("Invalid schema", "PUBLIC", conn.getSchema()); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;schema=\"" + DEFAULT_CACHE_NAME + '"')) { + assertEquals("Invalid schema", DEFAULT_CACHE_NAME, conn.getSchema()); + } + + try (Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1;schema=_not_exist_schema_")) { + assertEquals("Invalid schema", "_NOT_EXIST_SCHEMA_", conn.getSchema()); + } + } + + /** * Get client socket for connection. * * @param conn Connection. @@ -1010,6 +1146,7 @@ public class JdbcThinConnectionSelfTest extends JdbcThinAbstractSelfTest { // Invalid parameter value GridTestUtils.assertThrows(log, new Callable<Object>() { + @SuppressWarnings("MagicConstant") @Override public Object call() throws Exception { conn.setTransactionIsolation(-1); http://git-wip-us.apache.org/repos/asf/ignite/blob/14402e40/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java index 5d77005..86dc298 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/ConnectionPropertiesImpl.java @@ -23,8 +23,6 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Properties; import java.util.StringTokenizer; -import javax.naming.RefAddr; -import javax.naming.Reference; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.configuration.ClientConnectorConfiguration; import org.apache.ignite.internal.processors.odbc.SqlStateCode; @@ -44,6 +42,9 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa /** Default socket buffer size. */ private static final int DFLT_SOCK_BUFFER_SIZE = 64 * 1024; + /** Property: schema. */ + private static final String PROP_SCHEMA = "schema"; + /** Connection URL. */ private String url; @@ -51,7 +52,7 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa private HostAndPortRange [] addrs; /** Schema name. Hidden property. Is used to set default schema name part of the URL. */ - private StringProperty schema = new StringProperty("schema", + private StringProperty schema = new StringProperty(PROP_SCHEMA, "Schema name of the connection", "PUBLIC", null, false, null); /** Distributed joins property. */ @@ -487,21 +488,113 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa String nakedUrl = url.substring(JdbcThinUtils.URL_PREFIX.length()).trim(); - int pathPartEndPos = nakedUrl.indexOf('?'); + parseUrl0(nakedUrl, props); + } + + /** + * Parse naked URL (i.e. without {@link JdbcThinUtils#URL_PREFIX}). + * + * @param url Naked URL. + * @param props Properties. + * @throws SQLException If failed. + */ + private void parseUrl0(String url, Properties props) throws SQLException { + // Determine mode - semicolon or ampersand. + int semicolonPos = url.indexOf(";"); + int slashPos = url.indexOf("/"); + int queryPos = url.indexOf("?"); + + boolean semicolonMode; + + if (semicolonPos == -1 && slashPos == -1 && queryPos == -1) + // No special char -> any mode could be used, choose semicolon for simplicity. + semicolonMode = true; + else { + if (semicolonPos != -1) { + // Use semicolon mode if it appears earlier than slash or query. + semicolonMode = + (slashPos == -1 || semicolonPos < slashPos) && (queryPos == -1 || semicolonPos < queryPos); + } + else + // Semicolon is not found. + semicolonMode = false; + } + + if (semicolonMode) + parseUrlWithSemicolon(url, props); + else + parseUrlWithQuery(url, props); + } + + /** + * Parse URL in semicolon mode. + * + * @param url Naked URL + * @param props Properties. + * @throws SQLException If failed. + */ + private void parseUrlWithSemicolon(String url, Properties props) throws SQLException { + int pathPartEndPos = url.indexOf(';'); if (pathPartEndPos == -1) - pathPartEndPos = nakedUrl.length(); + pathPartEndPos = url.length(); - String pathPart = nakedUrl.substring(0, pathPartEndPos); + String pathPart = url.substring(0, pathPartEndPos); String paramPart = null; - if (pathPartEndPos > 0 && pathPartEndPos < nakedUrl.length()) - paramPart = nakedUrl.substring(pathPartEndPos + 1, nakedUrl.length()); + if (pathPartEndPos > 0 && pathPartEndPos < url.length()) + paramPart = url.substring(pathPartEndPos + 1, url.length()); + + parseEndpoints(pathPart); + + if (!F.isEmpty(paramPart)) + parseParameters(paramPart, props, ";"); + } + + /** + * Parse URL in query mode. + * + * @param url Naked URL + * @param props Properties. + * @throws SQLException If failed. + */ + private void parseUrlWithQuery(String url, Properties props) throws SQLException { + int pathPartEndPos = url.indexOf('?'); + + if (pathPartEndPos == -1) + pathPartEndPos = url.length(); + + String pathPart = url.substring(0, pathPartEndPos); + + String paramPart = null; + + if (pathPartEndPos > 0 && pathPartEndPos < url.length()) + paramPart = url.substring(pathPartEndPos + 1, url.length()); String[] pathParts = pathPart.split("/"); - String [] endpoints = pathParts[0].split(","); + parseEndpoints(pathParts[0]); + + if (pathParts.length > 2) { + throw new SQLException("Invalid URL format (only schema name is allowed in URL path parameter " + + "'host:port[/schemaName]'): " + this.url, SqlStateCode.CLIENT_CONNECTION_FAILED); + } + + setSchema(pathParts.length == 2 ? pathParts[1] : null); + + if (!F.isEmpty(paramPart)) + parseParameters(paramPart, props, "&"); + } + + /** + * Parse endpoints. + * + * @param endpointStr Endpoint string. + * @throws SQLException If failed. + */ + private void parseEndpoints(String endpointStr) throws SQLException { + String [] endpoints = endpointStr.split(","); if (endpoints.length > 0) addrs = new HostAndPortRange[endpoints.length]; @@ -519,16 +612,6 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa if (F.isEmpty(addrs) || F.isEmpty(addrs[0].host())) throw new SQLException("Host name is empty", SqlStateCode.CLIENT_CONNECTION_FAILED); - - if (pathParts.length > 2) { - throw new SQLException("Invalid URL format (only schema name is allowed in URL path parameter " + - "'host:port[/schemaName]'): " + url, SqlStateCode.CLIENT_CONNECTION_FAILED); - } - - setSchema(pathParts.length == 2 ? pathParts[1] : null); - - if (!F.isEmpty(paramPart)) - parseParameters(paramPart, props); } /** @@ -536,10 +619,11 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa * * @param paramStr Parameters string. * @param props Properties. + * @param delimChar Delimiter character. * @throws SQLException If failed. */ - private void parseParameters(String paramStr, Properties props) throws SQLException { - StringTokenizer st = new StringTokenizer(paramStr, "&"); + private void parseParameters(String paramStr, Properties props, String delimChar) throws SQLException { + StringTokenizer st = new StringTokenizer(paramStr, delimChar); boolean insideBrace = false; @@ -553,8 +637,8 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa int eqSymPos = token.indexOf('='); if (eqSymPos < 0) { - throw new SQLException("Invalid parameter format " + - "(URL properties format: key0=value0&key1=value1&... etc. pair: " + token); + throw new SQLException("Invalid parameter format (should be \"key1=val1" + delimChar + + "key2=val2" + delimChar + "...\"): " + token); } if (eqSymPos == token.length()) @@ -570,7 +654,7 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa } } else - val += "&" + token; + val += delimChar + token; if (val.endsWith("}")) { insideBrace = false; @@ -587,22 +671,24 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa if (key.isEmpty() || val.isEmpty()) throw new SQLException("Invalid parameter format (key and value cannot be empty): " + token); - props.setProperty(PROP_PREFIX + key, val); + if (PROP_SCHEMA.equalsIgnoreCase(key)) + setSchema(val); + else + props.setProperty(PROP_PREFIX + key, val); } } } - /** * @return Driver's properties info array. */ public DriverPropertyInfo[] getDriverPropertyInfo() { - DriverPropertyInfo[] dpis = new DriverPropertyInfo[propsArray.length]; + DriverPropertyInfo[] infos = new DriverPropertyInfo[propsArray.length]; for (int i = 0; i < propsArray.length; ++i) - dpis[i] = propsArray[i].getDriverPropertyInfo(); + infos[i] = propsArray[i].getDriverPropertyInfo(); - return dpis; + return infos; } /** @@ -741,23 +827,6 @@ public class ConnectionPropertiesImpl implements ConnectionProperties, Serializa } /** - * @param ref Reference object. - * @throws SQLException On error. - */ - void init(Reference ref) throws SQLException { - RefAddr refAddr = ref.get(name); - - if (refAddr != null) { - String str = (String) refAddr.getContent(); - - if (validator != null) - validator.validate(str); - - init(str); - } - } - - /** * @param str String representation of the * @throws SQLException on error. */