Mercurial > hg > monetdb-java
changeset 159:8ea360b612de
Corrected PreparedStatement methods setString(int, String) and setObject(int, Object, ...) in case the target parameter data type was json or inet or url or uuid.
This fixes bug 6382.
Also added java test program.
author | Martin van Dinther <martin.van.dinther@monetdbsolutions.com> |
---|---|
date | Thu, 31 Aug 2017 18:51:16 +0200 (2017-08-31) |
parents | b016c9e945a0 |
children | 763150ec0a0a |
files | ChangeLog src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java tests/Bug_PrepStmtSetString_6382.java tests/build.xml |
diffstat | 4 files changed, 324 insertions(+), 120 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,12 @@ # ChangeLog file for monetdb-java # This file is updated with Maddlog +* Thu Aug 31 2017 Martin van Dinther <martin.van.dinther@monetdbsolutions.com> +- Corrected PreparedStatement methods setString(int, String) + and setObject(int, Object, ...) in case the target parameter + data type was json or inet or url or uuid. See also + https://www.monetdb.org/bugzilla/show_bug.cgi?id=6382 + * Thu Aug 24 2017 Martin van Dinther <martin.van.dinther@monetdbsolutions.com> - Implemented PreparedStatement method setURL(int, URL). - Implemented PreparedStatement method setNString(int, String).
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java @@ -62,7 +62,7 @@ import java.util.Map; * * @author Fabian Groffen * @author Martin van Dinther - * @version 0.4 + * @version 0.5 */ public class MonetPreparedStatement extends MonetStatement @@ -1773,118 +1773,7 @@ public class MonetPreparedStatement // this is according to table B-5 if (x instanceof String) { - switch (targetSqlType) { - case Types.CHAR: - case Types.VARCHAR: - case Types.LONGVARCHAR: - case Types.CLOB: - setString(parameterIndex, (String)x); - break; - case Types.TINYINT: - case Types.SMALLINT: - case Types.INTEGER: - { - int val; - try { - val = Integer.parseInt((String)x); - } catch (NumberFormatException e) { - val = 0; - } - setInt(parameterIndex, val); - } break; - case Types.BIGINT: - { - long val; - try { - val = Long.parseLong((String)x); - } catch (NumberFormatException e) { - val = 0; - } - setLong(parameterIndex, val); - } break; - case Types.REAL: - { - float val; - try { - val = Float.parseFloat((String)x); - } catch (NumberFormatException e) { - val = 0; - } - setFloat(parameterIndex, val); - } break; - case Types.FLOAT: - case Types.DOUBLE: - { - double val; - try { - val = Double.parseDouble((String)x); - } catch (NumberFormatException e) { - val = 0; - } - setDouble(parameterIndex, val); - } break; - case Types.DECIMAL: - case Types.NUMERIC: - { - BigDecimal val; - try { - val = new BigDecimal((String)x); - } catch (NumberFormatException e) { - try { - val = new BigDecimal(0.0); - } catch (NumberFormatException ex) { - throw new SQLException("Internal error: unable to create template BigDecimal: " + ex.getMessage(), "M0M03"); - } - } - val = val.setScale(scale, BigDecimal.ROUND_HALF_UP); - setBigDecimal(parameterIndex, val); - } break; - case Types.BIT: - case Types.BOOLEAN: - setBoolean(parameterIndex, (Boolean.valueOf((String)x)).booleanValue()); - break; - case Types.BINARY: - case Types.VARBINARY: - case Types.LONGVARBINARY: - setBytes(parameterIndex, ((String)x).getBytes()); - break; - case Types.DATE: - { - java.sql.Date val; - try { - val = java.sql.Date.valueOf((String)x); - } catch (IllegalArgumentException e) { - val = new java.sql.Date(0L); - } - setDate(parameterIndex, val); - } break; - case Types.TIME: - { - Time val; - try { - val = Time.valueOf((String)x); - } catch (IllegalArgumentException e) { - val = new Time(0L); - } - setTime(parameterIndex, val); - } break; - case Types.TIMESTAMP: - { - Timestamp val; - try { - val = Timestamp.valueOf((String)x); - } catch (IllegalArgumentException e) { - val = new Timestamp(0L); - } - setTimestamp(parameterIndex, val); - } break; - case Types.NCHAR: - case Types.NVARCHAR: - case Types.LONGNVARCHAR: - throw newSQLFeatureNotSupportedException("setObject() with targetType N[VAR]CHAR"); - default: - throw new SQLException("Conversion not allowed", "M1M05"); - } + setString(parameterIndex, (String)x); } else if (x instanceof BigDecimal || x instanceof Byte || x instanceof Short || @@ -2090,9 +1979,9 @@ public class MonetPreparedStatement } } else if (x instanceof Array) { setArray(parameterIndex, (Array)x); - } else if (x instanceof Blob) { + } else if (x instanceof Blob || x instanceof MonetBlob) { setBlob(parameterIndex, (Blob)x); - } else if (x instanceof Clob) { + } else if (x instanceof Clob || x instanceof MonetClob) { setClob(parameterIndex, (Clob)x); } else if (x instanceof Struct) { // I have no idea how to do this... @@ -2101,6 +1990,8 @@ public class MonetPreparedStatement setRef(parameterIndex, (Ref)x); } else if (x instanceof java.net.URL) { setURL(parameterIndex, (java.net.URL)x); + } else if (x instanceof java.util.UUID) { + setString(parameterIndex, x.toString()); } else if (x instanceof RowId) { setRowId(parameterIndex, (RowId)x); } else if (x instanceof NClob) { @@ -2256,7 +2147,7 @@ public class MonetPreparedStatement }; sx.writeSQL(out); } else { // java Class - throw newSQLFeatureNotSupportedException("setObject() with object of type Class"); + throw newSQLFeatureNotSupportedException("setObject() with object of type Class " + x.getClass().getName()); } } @@ -2322,10 +2213,164 @@ public class MonetPreparedStatement return; } - setValue( - parameterIndex, - "'" + x.replaceAll("\\\\", "\\\\\\\\").replaceAll("'", "\\\\'") + "'" - ); + int paramIdx = getParamIdx(parameterIndex); // this will throw a SQLException if parameter can not be found + + /* depending on the parameter data type (as expected by MonetDB) we + may need to add the data type as cast prefix to the parameter value */ + int paramJdbcType = javaType[paramIdx]; + String paramMonetdbType = monetdbType[paramIdx]; + + switch (paramJdbcType) { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + case Types.CLOB: + case Types.NCHAR: + case Types.NVARCHAR: + case Types.LONGNVARCHAR: + { + String castprefix = ""; + switch (paramMonetdbType) { + // some MonetDB specific data types require a cast prefix + case "inet": + try { + // check if x represents a valid inet string to prevent + // failing exec #(..., ...) calls which destroy the prepared statement, see bug 6351 + nl.cwi.monetdb.jdbc.types.INET inet_obj = new nl.cwi.monetdb.jdbc.types.INET(); + inet_obj.fromString(x); + } catch (SQLException se) { + throw new SQLException("Conversion of string: " + x + " to parameter data type " + paramMonetdbType + " failed. " + se, "M1M05"); + } + castprefix = "inet "; + break; + case "json": + // There is no support for JSON in standard java class libraries. + // Possibly we could use org.json.simple.JSONObject or other/faster libs + // javax.json.Json is not released yet (see https://json-processing-spec.java.net/) + // see also https://github.com/fabienrenaud/java-json-benchmark + // Note that it would make our JDBC driver dependent of an external jar + // and we don't want that. + + // do simplistic check if x represents a valid json string to prevent + // failing exec #(..., ...) calls which destroy the prepared statement, see bug 6351 + if (x.isEmpty() || + (x.startsWith("{") && !x.endsWith("}")) || + (x.startsWith("[") && !x.endsWith("]")) + // TODO check completely if x represents a valid json string + ) + throw new SQLException("Invalid json string. It does not start with { or [ and end with } or ]", "M1M05"); + + // TODO check completely if x represents a valid json string + + castprefix = "json "; + break; + case "url": + try { + // also check if x represents a valid url string to prevent + // failing exec #(..., ...) calls which destroy the prepared statement, see bug 6351 + java.net.URL url_obj = new java.net.URL(x); + } catch (java.net.MalformedURLException mue) { + throw new SQLException("Conversion of string: " + x + " to parameter data type " + paramMonetdbType + " failed. " + mue, "M1M05"); + } + castprefix = "url "; + break; + case "uuid": + try { + // also check if x represents a valid uuid string to prevent + // failing exec #(..., ...) calls which destroy the prepared statement, see bug 6351 + java.util.UUID uuid_obj = java.util.UUID.fromString(x); + } catch (IllegalArgumentException iae) { + throw new SQLException("Conversion of string: " + x + " to parameter data type " + paramMonetdbType + " failed. " + iae, "M1M05"); + } + castprefix = "uuid "; + break; + } + /* in specific cases prefix the string with: inet or json or url or uuid */ + setValue(parameterIndex, + castprefix + "'" + x.replaceAll("\\\\", "\\\\\\\\").replaceAll("'", "\\\\'") + "'"); + break; + } + case Types.TINYINT: + case Types.SMALLINT: + case Types.INTEGER: + case Types.BIGINT: + case Types.REAL: + case Types.FLOAT: + case Types.DOUBLE: + case Types.DECIMAL: + case Types.NUMERIC: + try { + // check (by calling parse) if the string represents a valid number to prevent + // failing exec #(..., ...) calls which destroy the prepared statement, see bug 6351 + if (paramJdbcType == Types.INTEGER || paramJdbcType == Types.SMALLINT || paramJdbcType == Types.TINYINT) { + int number = Integer.parseInt(x); + } else + if (paramJdbcType == Types.BIGINT) { + long number = Long.parseLong(x); + } else + if (paramJdbcType == Types.REAL || paramJdbcType == Types.DOUBLE || paramJdbcType == Types.FLOAT) { + double number = Double.parseDouble(x); + } else + if (paramJdbcType == Types.DECIMAL || paramJdbcType == Types.NUMERIC) { + BigDecimal number = new BigDecimal(x); + } + } catch (NumberFormatException nfe) { + throw new SQLException("Conversion of string: " + x + " to parameter data type " + paramMonetdbType + " failed. " + nfe, "M1M05"); + } + setValue(parameterIndex, x); + break; + case Types.BIT: + case Types.BOOLEAN: + if (x.equalsIgnoreCase("false") || x.equalsIgnoreCase("true") || x.equals("0") || x.equals("1")) { + setValue(parameterIndex, x); + } else { + throw new SQLException("Conversion of string: " + x + " to parameter data type " + paramMonetdbType + " failed", "M1M05"); + } + break; + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + try { + // check if the string represents a valid calendar date or time or timestamp to prevent + // failing exec #(..., ...) calls which destroy the prepared statement, see bug 6351 + if (paramJdbcType == Types.DATE) { + java.sql.Date datum = java.sql.Date.valueOf(x); + } else + if (paramJdbcType == Types.TIME) { + Time tijdstip = Time.valueOf(x); + } else + if (paramJdbcType == Types.TIMESTAMP) { + Timestamp tijdstip = Timestamp.valueOf(x); + } + } catch (IllegalArgumentException iae) { + throw new SQLException("Conversion of string: " + x + " to parameter data type " + paramMonetdbType + " failed. " + iae, "M1M05"); + } + /* prefix the string with: date or time or timetz or timestamp or timestamptz */ + setValue(parameterIndex, paramMonetdbType + " '" + x + "'"); + break; + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + case Types.BLOB: + // check if the string x contains pairs of hex chars to prevent + // failing exec #(..., ...) calls which destroy the prepared statement, see bug 6351 + int xlen = x.length(); + for (int i = 0; i < xlen; i++) { + char c = x.charAt(i); + if (c < '0' || c > '9') { + if (c < 'A' || c > 'F') { + if (c < 'a' || c > 'f') { + throw new SQLException("Invalid string for parameter data type " + paramMonetdbType + ". The string may contain only hex chars", "M1M05"); + } + } + } + } + /* prefix the string with: blob */ + setValue(parameterIndex, "blob '" + x + "'"); + break; + default: + throw new SQLException("Conversion of string to parameter data type " + paramMonetdbType + " is not (yet) supported", "M1M05"); + } } /**
new file mode 100644 --- /dev/null +++ b/tests/Bug_PrepStmtSetString_6382.java @@ -0,0 +1,146 @@ +/* + * 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. + */ + +import java.sql.*; +import nl.cwi.monetdb.jdbc.types.INET; +import nl.cwi.monetdb.jdbc.types.URL; + +public class Bug_PrepStmtSetString_6382 { + public static void main(String[] args) throws Exception { + // Class.forName("nl.cwi.monetdb.jdbc.MonetDriver"); // not needed anymore for self registering JDBC drivers + Connection con = DriverManager.getConnection(args[0]); + System.out.println("0. true\t" + con.getAutoCommit()); + + Statement stmt = null; + PreparedStatement pstmt = null; + ParameterMetaData pmd = null; + ResultSet rs = null; + ResultSetMetaData rsmd = null; + try { + stmt = con.createStatement(); + String tableName = "PrepStmtSetString_6382"; + System.out.println("Creating table " + tableName); + stmt.executeUpdate("CREATE TABLE " + tableName + " (myint INT, myvarchar VARCHAR(15), myjson JSON, myuuid UUID, myurl URL, myinet INET)"); + + System.out.println("Inserting row 1"); + stmt.executeUpdate("INSERT INTO " + tableName + " VALUES (1, 'row 1', '{}', uuid '34c8deb5-e608-406b-beda-6a951f73d455', 'https://www.monetdb.org/', '128.0.0.1')"); + + System.out.println("Inserting row 2"); + stmt.executeUpdate("INSERT INTO " + tableName + " VALUES (2, 'row 2', '[]', NULL, NULL, NULL)"); + + + System.out.println("Creating a prepared statement with 6 parameters and inserting rows using setInt(), setString(), setNull(), setNString(), setURL(), setObject()."); + pstmt = con.prepareStatement("INSERT INTO " + tableName + " VALUES (?,?, ? ,?,? , ?)"); + pmd = pstmt.getParameterMetaData(); + int pcount = pmd.getParameterCount(); + System.out.println("Prepared Statement has " + pcount + " parameters:" + (pcount != 6 ? " ERROR: Expected 6 parameters!" : "")); + for (int p = 1; p <= pcount; p++) { + System.out.println(" Parameter " + p + " type is: " + pmd.getParameterTypeName(p) + ". JDBC SQL type: " + pmd.getParameterType(p)); + } + + int row = 3; + pstmt.setInt(1, row); + pstmt.setString(2, "row " + row); + pstmt.setString(3, "{\"menu\": {\n \"id\": \"file\",\n \"value\": \"File\",\n \"popup\": {\n \"menuitem\": [\n {\"value\": \"New\", \"onclick\": \"CreateNewDoc()\"},\n {\"value\": \"Open\", \"onclick\": \"OpenDoc()\"},\n {\"value\": \"Close\", \"onclick\": \"CloseDoc()\"}\n ]\n }\n}}"); + pstmt.setNull(4, 0); + pstmt.setNull(5, 0); + pstmt.setNull(6, 0); + System.out.println("Inserting row " + row); + int inserted = pstmt.executeUpdate(); + System.out.println("Inserted " + inserted + " row"); + + row++; // row 4 + pstmt.setShort(1, (short)row); + pstmt.setNString(2, "row " + row); + pstmt.setNull(3, 0); + pstmt.setString(4, "4a148b7d-8d47-4e1e-a21e-09a71abf2215"); + System.out.println("Inserting row " + row); + inserted = pstmt.executeUpdate(); + System.out.println("Inserted " + inserted + " row"); + + row++; // row 5 + pstmt.setLong(1, row); + pstmt.setString(2, "row " + row); + pstmt.setNull(4, 0); + pstmt.setURL(5, new java.net.URL("https://www.cwi.nl/")); + System.out.println("Inserting row " + row); + inserted = pstmt.executeUpdate(); + System.out.println("Inserted " + inserted + " row"); + + row++; // row 6 + pstmt.setBigDecimal(1, new java.math.BigDecimal(row)); + pstmt.setNString(2, "row " + row); + pstmt.setNull(5, 0); + pstmt.setString(6, "127.255.255.255"); + System.out.println("Inserting row " + row); + inserted = pstmt.executeUpdate(); + System.out.println("Inserted " + inserted + " row"); + + /* also test generic setObject(int, String) */ + row++; // row 7 + pstmt.setObject(1, new Integer(row)); + pstmt.setObject(2, "row " + row); + pstmt.setObject(3, "{\"menu\": {\n \"header\": \"SVG Viewer\",\n \"items\": [\n {\"id\": \"Open\"},\n {\"id\": \"OpenNew\", \"label\": \"Open New\"},\n null,\n {\"id\": \"ZoomIn\", \"label\": \"Zoom In\"},\n {\"id\": \"ZoomOut\", \"label\": \"Zoom Out\"},\n {\"id\": \"OriginalView\", \"label\": \"Original View\"},\n null,\n {\"id\": \"Quality\"},\n {\"id\": \"Pause\"},\n {\"id\": \"Mute\"},\n null,\n {\"id\": \"Help\"},\n {\"id\": \"About\", \"label\": \"About Adobe CVG Viewer...\"}\n ]\n}}"); + pstmt.setObject(4, "b39dc76e-4faf-4fd9-bc1e-17df48acf764"); + pstmt.setObject(5, "https://en.wikipedia.org/wiki/IP_address"); + pstmt.setObject(6, "223.255.255.255"); + System.out.println("Inserting row " + row); + inserted = pstmt.executeUpdate(); + System.out.println("Inserted " + inserted + " row"); + + row++; // row 8 + pstmt.setObject(1, new java.math.BigDecimal(row)); + pstmt.setObject(2, "row " + row); + pstmt.setObject(3, null); + pstmt.setObject(4, java.util.UUID.fromString("ff125769-b63c-4c3c-859f-5b84a9349e24")); + URL myURL = new URL(); + myURL.fromString("https://en.wikipedia.org/wiki/IP_address"); + pstmt.setObject(5, myURL); + INET myINET = new INET(); + myINET.fromString("223.234.245.255"); + pstmt.setObject(6, myINET); + System.out.println("Inserting row " + row); + inserted = pstmt.executeUpdate(); + System.out.println("Inserted " + inserted + " row"); + + + System.out.println("List contents of TABLE " + tableName + " after " + row + " rows inserted"); + rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY 1"); + rsmd = rs.getMetaData(); + int colcount = rsmd.getColumnCount(); + System.out.println("Query has " + colcount + " output columns." + (colcount != 6 ? " ERROR: Expected 6 columns!" : "")); + row = 0; + while (rs.next()) { + System.out.print("row " + ++row); + for (int c = 1; c <= colcount; c++) { + System.out.print("\t" + rs.getString(c)); + } + System.out.println(); + } + + System.out.println(); + System.out.println("Cleanup TABLE " + tableName); + stmt.executeUpdate("DROP TABLE " + tableName); + System.out.println("Test completed successfully"); + } catch (SQLException e) { + System.err.println("FAILED :( "+ e.getMessage()); + while ((e = e.getNextException()) != null) + System.err.println("FAILED :( " + e.getMessage()); + System.err.println("ABORTING TEST!!!"); + } finally { + if (rs != null) + rs.close(); + if (pstmt != null) + pstmt.close(); + if (stmt != null) + stmt.close(); + con.close(); + } + } +} +
--- a/tests/build.xml +++ b/tests/build.xml @@ -136,6 +136,7 @@ Copyright 1997 - July 2008 CWI, August 2 <antcall target="BugResultSetMetaData_Bug_6183" /> <antcall target="Bug_PrepStmtSetObject_CLOB_6349" /> <antcall target="Bug_Connect_as_voc_getMetaData_Failure_Bug_6388" /> + <antcall target="Bug_PrepStmtSetString_6382" /> </target> <target name="test_class" depends="compile,jdbc"> @@ -397,4 +398,10 @@ Copyright 1997 - July 2008 CWI, August 2 </antcall> </target> + <target name="Bug_PrepStmtSetString_6382"> + <antcall target="test_class"> + <param name="test.class" value="Bug_PrepStmtSetString_6382" /> + </antcall> + </target> + </project>