Mercurial > hg > monetdb-java
diff src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java @ 172:60063c67f9e7 embedded
Merged with default
author | Pedro Ferreira <pedro.ferreira@monetdbsolutions.com> |
---|---|
date | Tue, 19 Sep 2017 13:49:34 +0200 (2017-09-19) |
parents | 890dce2d5305 5c575fb21be0 |
children | 89c285fc0a49 |
line wrap: on
line diff
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java @@ -46,9 +46,9 @@ import java.util.Map; * </pre> * * @author Fabian Groffen, Martin van Dinther, Pedro Ferreira - * @version 0.4 + * @version 0.5v */ -public class MonetPreparedStatement extends MonetStatement implements PreparedStatement { +public class MonetPreparedStatement extends MonetStatement implements PreparedStatement, AutoCloseable { private final MonetConnection connection; private final String[] monetdbType; @@ -106,18 +106,30 @@ public class MonetPreparedStatement exte // fill the arrays ResultSet rs = super.getResultSet(); - for (int i = 0; rs.next(); i++) { - monetdbType[i] = rs.getString("type"); - javaType[i] = MonetDriver.getJavaType(monetdbType[i]); - digits[i] = rs.getInt("digits"); - scale[i] = rs.getInt("scale"); - if (rscolcnt == 3) - continue; - schema[i] = rs.getString("schema"); - table[i] = rs.getString("table"); - column[i] = rs.getString("column"); + if (rs != null) { + // System.out.println("After super.getResultSet();"); + int type_colnr = rs.findColumn("type"); + int digits_colnr = rs.findColumn("digits"); + int scale_colnr = rs.findColumn("scale"); + int schema_colnr = rs.findColumn("schema"); + int table_colnr = rs.findColumn("table"); + int column_colnr = rs.findColumn("column"); + for (int i = 0; rs.next(); i++) { + monetdbType[i] = rs.getString(type_colnr); + javaType[i] = MonetDriver.getJavaType(monetdbType[i]); + digits[i] = rs.getInt(digits_colnr); + scale[i] = rs.getInt(scale_colnr); + if (rscolcnt == 3) + continue; + schema[i] = rs.getString(schema_colnr); + table[i] = rs.getString(table_colnr); + column[i] = rs.getString(column_colnr); + /* when column[i] != null it is a result column of the prepared query, see getColumnIdx(int), + when column[i] == null it is a parameter for the prepared statement, see getParamIdx(int). */ + // System.out.println("column " + i + " has value: " + column[i]); + } + rs.close(); } - rs.close(); // PreparedStatements are by default poolable poolable = true; @@ -238,6 +250,7 @@ public class MonetPreparedStatement exte private int getColumnIdx(int colnr) throws SQLException { int curcol = 0; for (int i = 0; i < size; i++) { + /* when column[i] == null it is a parameter, when column[i] != null it is a result column of the prepared query */ if (column[i] == null) continue; curcol++; @@ -254,6 +267,7 @@ public class MonetPreparedStatement exte private int getParamIdx(int paramnr) throws SQLException { int curparam = 0; for (int i = 0; i < size; i++) { + /* when column[i] == null it is a parameter, when column[i] != null it is a result column of the prepared query */ if (column[i] != null) continue; curparam++; @@ -800,13 +814,13 @@ public class MonetPreparedStatement exte * Sets the designated parameter to the given Array object. The * driver converts this to an SQL ARRAY value when it sends it to * the database. - * - * @param i the first parameter is 1, the second is 2, ... + * + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param x an Array object that maps an SQL ARRAY value * @throws SQLException if a database access error occurs */ @Override - public void setArray(int i, Array x) throws SQLException { + public void setArray(int parameterIndex, Array x) throws SQLException { throw newSQLFeatureNotSupportedException("setArray"); } @@ -882,14 +896,14 @@ public class MonetPreparedStatement exte * The driver converts this to an SQL NUMERIC value when it sends it to the * database. * - * @param idx the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param x the parameter value * @throws SQLException if a database access error occurs */ @Override - public void setBigDecimal(int idx, BigDecimal x) throws SQLException { + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { // get array position - int i = getParamIdx(idx); + int i = getParamIdx(parameterIndex); // round to the scale of the DB: x = x.setScale(scale[i], RoundingMode.HALF_UP); @@ -910,7 +924,7 @@ public class MonetPreparedStatement exte xStr = xStr.substring(0, Math.min(xStr.length(), dot + 1 + scale[i])); while (xStr.startsWith("0") && xStr.length() > 1) xStr = xStr.substring(1); - setValue(idx, xStr); + setValue(parameterIndex, xStr); } /** @@ -983,12 +997,12 @@ public class MonetPreparedStatement exte * converts this to an SQL BLOB value when it sends it to the database. * * @param parameterIndex the first parameter is 1, the second is 2, ... - * @param stream an object that contains the data to set the parameter value to + * @param x a Blob object that maps an SQL BLOB value * @throws SQLException if a database access error occurs */ @Override - public void setBlob(int parameterIndex, InputStream stream) throws SQLException { - if (stream == null) { + public void setBlob(int parameterIndex, InputStream x) throws SQLException { + if (x == null) { setNull(parameterIndex, -1); return; } @@ -997,7 +1011,7 @@ public class MonetPreparedStatement exte ByteArrayOutputStream buf = new ByteArrayOutputStream(); int numChars; try { - while ((numChars = stream.read(arr, 0, arr.length)) > 0) { + while ((numChars = x.read(arr, 0, arr.length)) > 0) { buf.write(arr, 0, numChars); } setBytes(parameterIndex, buf.toByteArray()); @@ -1035,13 +1049,13 @@ public class MonetPreparedStatement exte * should be sent to the server as a LONGVARBINARY or a BLOB. * * @param parameterIndex the first parameter is 1, the second is 2, ... - * @param stream an object that contains the data to set the parameter value to + * @param is an object that contains the data to set the parameter value to * @param length the number of bytes in the parameter data * @throws SQLException if a database access error occurs */ @Override - public void setBlob(int parameterIndex, InputStream stream, long length) throws SQLException { - if (stream == null) { + public void setBlob(int parameterIndex, InputStream is, long length) throws SQLException { + if (is == null) { setNull(parameterIndex, -1); return; } @@ -1049,7 +1063,7 @@ public class MonetPreparedStatement exte byte[] arr = new byte[(int) length]; ByteArrayOutputStream buf = new ByteArrayOutputStream((int) length); - int numChars = stream.read(arr, 0, (int) length); + int numChars = is.read(arr, 0, (int) length); buf.write(arr, 0, numChars); setBytes(parameterIndex, buf.toByteArray()); } catch (IOException e) { @@ -1187,36 +1201,36 @@ public class MonetPreparedStatement exte * Sets the designated parameter to the given Clob object. The driver * converts this to an SQL CLOB value when it sends it to the database. * - * @param i the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param x a Clob object that maps an SQL CLOB value * @throws SQLException if a database access error occurs */ @Override - public void setClob(int i, Clob x) throws SQLException { + public void setClob(int parameterIndex, Clob x) throws SQLException { if (x == null) { - setNull(i, -1); + setNull(parameterIndex, -1); return; } // simply serialise the CLOB into a variable for now... far from // efficient, but might work for a few cases... // be on your marks: we have to cast the length down! - setString(i, x.getSubString(1L, (int)(x.length()))); + setString(parameterIndex, x.getSubString(1L, (int)(x.length()))); } /** * Sets the designated parameter to the given Clob object. The driver * converts this to an SQL CLOB value when it sends it to the database. * - * @param i the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param reader an object that contains the data to set the parameter * value to * @throws SQLException if a database access error occurs */ @Override - public void setClob(int i, Reader reader) throws SQLException { + public void setClob(int parameterIndex, Reader reader) throws SQLException { if (reader == null) { - setNull(i, -1); + setNull(parameterIndex, -1); return; } // Some buffer. Size of 8192 is default for BufferedReader, so... @@ -1227,7 +1241,7 @@ public class MonetPreparedStatement exte while ((numChars = reader.read(arr, 0, arr.length)) > 0) { buf.append(arr, 0, numChars); } - setString(i, buf.toString()); + setString(parameterIndex, buf.toString()); } catch (IOException e) { throw new SQLException(e); } @@ -1244,16 +1258,16 @@ public class MonetPreparedStatement exte * extra work to determine whether the parameter data should be sent * to the server as a LONGVARCHAR or a CLOB. * - * @param i the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param reader An object that contains the data to set the * parameter value to. * @param length the number of characters in the parameter data. * @throws SQLException if a database access error occurs */ @Override - public void setClob(int i, Reader reader, long length) throws SQLException { + public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { if (reader == null || length < 0) { - setNull(i, -1); + setNull(parameterIndex, -1); return; } // simply serialise the CLOB into a variable for now... far from @@ -1267,7 +1281,7 @@ public class MonetPreparedStatement exte } // We have to rewind the buffer, because otherwise toString() returns "". buf.rewind(); - setString(i, buf.toString()); + setString(parameterIndex, buf.toString()); } /** @@ -1371,15 +1385,15 @@ public class MonetPreparedStatement exte * necessary conversion from Java character format to the national * character set in the database. * - * @param i the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param value the parameter value * @throws SQLException if a database access error occurs * @throws SQLFeatureNotSupportedException the JDBC driver does * not support this method */ @Override - public void setNCharacterStream(int i, Reader value) throws SQLException { - throw newSQLFeatureNotSupportedException("setNCharacterStream"); + public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { + setCharacterStream(parameterIndex, value, 0); } /** @@ -1388,7 +1402,7 @@ public class MonetPreparedStatement exte * necessary conversion from Java character format to the national * character set in the database. * - * @param i the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param value the parameter value * @param length the number of characters in the parameter data. * @throws SQLException if a database access error occurs @@ -1396,8 +1410,8 @@ public class MonetPreparedStatement exte * not support this method */ @Override - public void setNCharacterStream(int i, Reader value, long length) throws SQLException { - throw newSQLFeatureNotSupportedException("setNCharacterStream"); + public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { + setCharacterStream(parameterIndex, value, length); } /** @@ -1405,14 +1419,14 @@ public class MonetPreparedStatement exte * driver converts this to a SQL NCLOB value when it sends it to the * database. * - * @param i the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param value the parameter value * @throws SQLException if a database access error occurs * @throws SQLFeatureNotSupportedException the JDBC driver does * not support this method */ @Override - public void setNClob(int i, Reader value) throws SQLException { + public void setNClob(int parameterIndex, Reader value) throws SQLException { throw newSQLFeatureNotSupportedException("setNClob"); } @@ -1421,14 +1435,14 @@ public class MonetPreparedStatement exte * driver converts this to a SQL NCLOB value when it sends it to the * database. * - * @param i the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param value the parameter value * @throws SQLException if a database access error occurs * @throws SQLFeatureNotSupportedException the JDBC driver does * not support this method */ @Override - public void setNClob(int i, NClob value) throws SQLException { + public void setNClob(int parameterIndex, NClob value) throws SQLException { throw newSQLFeatureNotSupportedException("setNClob"); } @@ -1443,7 +1457,7 @@ public class MonetPreparedStatement exte * extra work to determine whether the parameter data should be sent * to the server as a LONGNVARCHAR or a NCLOB. * - * @param i the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param r An object that contains the data to set the parameter * value to * @param length the number of characters in the parameter data @@ -1452,7 +1466,7 @@ public class MonetPreparedStatement exte * not support this method */ @Override - public void setNClob(int i, Reader r, long length) throws SQLException { + public void setNClob(int parameterIndex, Reader r, long length) throws SQLException { throw newSQLFeatureNotSupportedException("setNClob"); } @@ -1462,15 +1476,15 @@ public class MonetPreparedStatement exte * value (depending on the argument's size relative to the driver's * limits on NVARCHAR values) when it sends it to the database. * - * @param i the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param value the parameter value * @throws SQLException if a database access error occurs * @throws SQLFeatureNotSupportedException the JDBC driver does * not support this method */ @Override - public void setNString(int i, String value) throws SQLException { - throw newSQLFeatureNotSupportedException("setNString"); + public void setNString(int parameterIndex, String value) throws SQLException { + setString(parameterIndex, value); } /** @@ -1505,7 +1519,7 @@ public class MonetPreparedStatement exte * parameter of any JDBC type. If the parameter does not have a * user-defined or REF type, the given typeName is ignored. * - * @param paramIndex the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param sqlType a value from java.sql.Types * @param typeName the fully-qualified name of an SQL user-defined type; * ignored if the parameter is not a user-defined type or @@ -1513,9 +1527,9 @@ public class MonetPreparedStatement exte * @throws SQLException if a database access error occurs */ @Override - public void setNull(int paramIndex, int sqlType, String typeName) throws SQLException { + public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { // MonetDB/SQL's NULL needs no type - setNull(paramIndex, sqlType); + setNull(parameterIndex, sqlType); } /** @@ -1540,14 +1554,14 @@ public class MonetPreparedStatement exte * example, if the object is of a class implementing more than one * of the interfaces named above. * - * @param index the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param x the object containing the input parameter value * @throws SQLException if a database access error occurs or the type of * the given object is ambiguous */ @Override - public void setObject(int index, Object x) throws SQLException { - setObject(index, x, javaType[getParamIdx(index)]); + public void setObject(int parameterIndex, Object x) throws SQLException { + setObject(parameterIndex, x, javaType[getParamIdx(parameterIndex)], 0); } /** @@ -1600,127 +1614,14 @@ public class MonetPreparedStatement exte * @see Types */ @Override - public void setObject( - int parameterIndex, - Object x, - int targetSqlType, - int scale) - throws SQLException - { + public void setObject(int parameterIndex, Object x, int targetSqlType, int scale) throws SQLException { + if (x == null) { + setNull(parameterIndex, -1); + return; + } // 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 || @@ -1879,13 +1780,13 @@ public class MonetPreparedStatement exte setDate(parameterIndex, new java.sql.Date( ((java.util.Date)x).getTime())); } else if (x instanceof Calendar) { - setDate(parameterIndex, new java.sql.Date( - ((Calendar)x).getTimeInMillis())); + setDate(parameterIndex, new java.sql.Date(((Calendar)x).getTimeInMillis())); } else { throw new SQLException("Conversion not allowed", "M1M05"); } break; case Types.TIME: + case 2013: //Types.TIME_WITH_TIMEZONE: if (x instanceof Time) { setTime(parameterIndex, (Time)x); } else if (x instanceof Timestamp) { @@ -1894,13 +1795,13 @@ public class MonetPreparedStatement exte setTime(parameterIndex, new java.sql.Time( ((java.util.Date)x).getTime())); } else if (x instanceof Calendar) { - setTime(parameterIndex, new java.sql.Time( - ((Calendar)x).getTimeInMillis())); + setTime(parameterIndex, new java.sql.Time(((Calendar)x).getTimeInMillis())); } else { throw new SQLException("Conversion not allowed", "M1M05"); } break; case Types.TIMESTAMP: + case 2014: //Types.TIMESTAMP_WITH_TIMEZONE: if (x instanceof Timestamp) { setTimestamp(parameterIndex, (Timestamp)x); } else if (x instanceof java.sql.Date) { @@ -1909,8 +1810,7 @@ public class MonetPreparedStatement exte setTimestamp(parameterIndex, new java.sql.Timestamp( ((java.util.Date)x).getTime())); } else if (x instanceof Calendar) { - setTimestamp(parameterIndex, new java.sql.Timestamp( - ((Calendar)x).getTimeInMillis())); + setTimestamp(parameterIndex, new java.sql.Timestamp(((Calendar)x).getTimeInMillis())); } else { throw new SQLException("Conversion not allowed", "M1M05"); } @@ -1926,9 +1826,9 @@ public class MonetPreparedStatement exte } } 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... @@ -1937,12 +1837,14 @@ public class MonetPreparedStatement exte 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) { - throw newSQLFeatureNotSupportedException("setObject() with object of type NClob"); + setNClob(parameterIndex, (NClob)x); } else if (x instanceof SQLXML) { - throw newSQLFeatureNotSupportedException("setObject() with object of type SQLXML"); + setSQLXML(parameterIndex, (SQLXML)x); } else if (x instanceof SQLData) { // not in JDBC4.1??? SQLData sx = (SQLData)x; final int paramnr = parameterIndex; @@ -2022,7 +1924,7 @@ public class MonetPreparedStatement exte @Override public void writeCharacterStream(Reader x) throws SQLException { - setCharacterStream(paramnr, x); + setCharacterStream(paramnr, x, 0); } @Override @@ -2092,7 +1994,7 @@ public class MonetPreparedStatement exte }; 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()); } } @@ -2101,14 +2003,14 @@ public class MonetPreparedStatement exte * The driver converts this to an SQL REF value when it sends it to the * database. * - * @param i the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param x an SQL REF value * @throws SQLException if a database access error occurs * @throws SQLFeatureNotSupportedException the JDBC driver does * not support this method */ @Override - public void setRef(int i, Ref x) throws SQLException { + public void setRef(int parameterIndex, Ref x) throws SQLException { throw newSQLFeatureNotSupportedException("setRef"); } @@ -2117,14 +2019,14 @@ public class MonetPreparedStatement exte * The driver converts this to a SQL ROWID value when it sends it to * the database. * - * @param i the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param x the parameter value * @throws SQLException if a database access error occurs * @throws SQLFeatureNotSupportedException the JDBC driver does * not support this method */ @Override - public void setRowId(int i, RowId x) throws SQLException { + public void setRowId(int parameterIndex, RowId x) throws SQLException { throw newSQLFeatureNotSupportedException("setRowId"); } @@ -2157,7 +2059,164 @@ public class MonetPreparedStatement exte setNull(parameterIndex, -1); 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.MonetINET inet_obj = new nl.cwi.monetdb.jdbc.MonetINET(x); + } catch (Exception se) { + throw new SQLDataException("Conversion of string: " + x + " to parameter data type " + paramMonetdbType + " failed. " + se.getMessage(), "22M29"); + } + 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 SQLDataException("Invalid json string. It does not start with { or [ and end with } or ]", "22M32"); + + // 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 SQLDataException("Conversion of string: " + x + " to parameter data type " + paramMonetdbType + " failed. " + mue.getMessage(), "22M30"); + } + 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 SQLDataException("Conversion of string: " + x + " to parameter data type " + paramMonetdbType + " failed. " + iae.getMessage(), "22M31"); + } + 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.TINYINT) { + int number = Byte.parseByte(x); + } else if (paramJdbcType == Types.SMALLINT ) { + int number = Short.parseShort(x); + } else if (paramJdbcType == Types.INTEGER) { + 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 { + BigDecimal number = new BigDecimal(x); + } + } catch (NumberFormatException nfe) { + throw new SQLDataException("Conversion of string: " + x + " to parameter data type " + paramMonetdbType + " failed. " + nfe.getMessage(), "22003"); + } + 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 SQLDataException("Conversion of string: " + x + " to parameter data type " + paramMonetdbType + " failed", "22000"); + } + break; + case Types.DATE: + case Types.TIME: + case 2013: //Types.TIME_WITH_TIMEZONE: + case Types.TIMESTAMP: + case 2014: //Types.TIMESTAMP_WITH_TIMEZONE: + 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 || paramJdbcType == 2013) { + Time tijdstip = Time.valueOf(x); + } else { + Timestamp tijdstip = Timestamp.valueOf(x); + } + } catch (IllegalArgumentException iae) { + throw new SQLDataException("Conversion of string: " + x + " to parameter data type " + paramMonetdbType + " failed. " + iae.getMessage(), "22007"); + } + /* 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 SQLDataException("Invalid string for parameter data type " + paramMonetdbType + ". The string may contain only hex chars", "22M28"); + } + } + } + } + /* 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"); + } } /** @@ -2181,13 +2240,13 @@ public class MonetPreparedStatement exte * The driver converts this to an SQL TIME value when it sends it to * the database. * - * @param index the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param x the parameter value * @throws SQLException if a database access error occurs */ @Override - public void setTime(int index, Time x) throws SQLException { - setTime(index, x, null); + public void setTime(int parameterIndex, Time x) throws SQLException { + setTime(parameterIndex, x, null); } /** @@ -2200,34 +2259,35 @@ public class MonetPreparedStatement exte * timezone, which is that of the virtual machine running the * application. * - * @param index the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param x the parameter value * @param cal the Calendar object the driver will use to construct the time * @throws SQLException if a database access error occurs */ @Override - public void setTime(int index, Time x, Calendar cal) throws SQLException { + public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { if (x == null) { - setNull(index, -1); + setNull(parameterIndex, -1); return; } - boolean hasTimeZone = monetdbType[getParamIdx(index)].endsWith("tz"); + String MonetDBType = monetdbType[getParamIdx(parameterIndex)]; + boolean hasTimeZone = ("timetz".equals(MonetDBType) || "timestamptz".equals(MonetDBType)); if (hasTimeZone) { // timezone shouldn't matter, since the server is timezone // aware in this case String RFC822 = mTimeZ.format(x); - setValue(index, "timetz '" + RFC822.substring(0, 15) + ":" + RFC822.substring(15) + "'"); + setValue(parameterIndex, "timetz '" + RFC822.substring(0, 15) + ":" + RFC822.substring(15) + "'"); } else { // server is not timezone aware for this field, and no // calendar given, since we told the server our timezone at // connection creation, we can just write a plain timestamp // here if (cal == null) { - setValue(index, "time '" + x.toString() + "'"); + setValue(parameterIndex, "time '" + x.toString() + "'"); } else { mTime.setTimeZone(cal.getTimeZone()); - setValue(index, "time '" + mTime.format(x) + "'"); + setValue(parameterIndex, "time '" + mTime.format(x) + "'"); } } } @@ -2237,13 +2297,13 @@ public class MonetPreparedStatement exte * value. The driver converts this to an SQL TIMESTAMP value when * it sends it to the database. * - * @param index the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param x the parameter value * @throws SQLException if a database access error occurs */ @Override - public void setTimestamp(int index, Timestamp x) throws SQLException { - setTimestamp(index, x, null); + public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { + setTimestamp(parameterIndex, x, null); } /** @@ -2256,35 +2316,35 @@ public class MonetPreparedStatement exte * default timezone, which is that of the virtual machine running * the application. * - * @param index the first parameter is 1, the second is 2, ... + * @param parameterIndex the first parameter is 1, the second is 2, ... * @param x the parameter value * @param cal the Calendar object the driver will use to construct the * timestamp * @throws SQLException if a database access error occurs */ @Override - public void setTimestamp(int index, Timestamp x, Calendar cal) throws SQLException { + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { if (x == null) { - setNull(index, -1); + setNull(parameterIndex, -1); return; } - boolean hasTimeZone = monetdbType[getParamIdx(index)].endsWith("tz"); + String MonetDBType = monetdbType[getParamIdx(parameterIndex)]; + boolean hasTimeZone = ("timestamptz".equals(MonetDBType) || "timetz".equals(MonetDBType)); if (hasTimeZone) { // timezone shouldn't matter, since the server is timezone // aware in this case String RFC822 = mTimestampZ.format(x); - setValue(index, "timestamptz '" + RFC822.substring(0, 26) + ":" + RFC822.substring(26) + "'"); + setValue(parameterIndex, "timestamptz '" + RFC822.substring(0, 26) + ":" + RFC822.substring(26) + "'"); } else { // server is not timezone aware for this field, and no // calendar given, since we told the server our timezone at - // connection creation, we can just write a plain timestamp - // here + // connection creation, we can just write a plain timestamp here if (cal == null) { - setValue(index, "timestamp '" + x.toString() + "'"); + setValue(parameterIndex, "timestamp '" + x.toString() + "'"); } else { mTimestamp.setTimeZone(cal.getTimeZone()); - setValue(index, "timestamp '" + mTimestamp.format(x) + "'"); + setValue(parameterIndex, "timestamp '" + mTimestamp.format(x) + "'"); } } } @@ -2326,8 +2386,14 @@ public class MonetPreparedStatement exte */ @Override public void setURL(int parameterIndex, URL x) throws SQLException { - setString(parameterIndex, x.toString()); - values[getParamIdx(parameterIndex)] = "url " + values[getParamIdx(parameterIndex)]; + if (x == null) { + setNull(parameterIndex, -1); + return; + } + + String val = x.toString(); + setValue(parameterIndex, "url '" + val.replaceAll("\\\\", "\\\\\\\\") + .replaceAll("'", "\\\\'") + "'"); } /** @@ -2369,12 +2435,12 @@ public class MonetPreparedStatement exte * Sets the given index with the supplied value. If the given index is out of bounds, and SQLException is thrown. * The given value should never be null. * - * @param index the parameter index + * @param parameterIndex the parameter index * @param val the exact String representation to set * @throws SQLException if the given index is out of bounds */ - private void setValue(int index, String val) throws SQLException { - values[getParamIdx(index)] = val; + private void setValue(int parameterIndex, String val) throws SQLException { + values[getParamIdx(parameterIndex)] = (val == null ? "NULL" : val); } /** @@ -2388,9 +2454,7 @@ public class MonetPreparedStatement exte */ private String transform() throws SQLException { StringBuilder buf = new StringBuilder(8 + 12 * size); - buf.append("exec "); - buf.append(id); - buf.append('('); + buf.append("exec ").append(id).append('('); // check if all columns are set and do a replace int col = 0; for (int i = 0; i < size; i++) { @@ -2399,8 +2463,8 @@ public class MonetPreparedStatement exte col++; if (col > 1) buf.append(','); - if (values[i] == null) throw - new SQLException("Cannot execute, parameter " + col + " is missing.", "M1M05"); + if (values[i] == null) + throw new SQLException("Cannot execute, parameter " + col + " is missing.", "M1M05"); buf.append(values[i]); } @@ -2411,19 +2475,20 @@ public class MonetPreparedStatement exte /** * Small helper method that formats the "Invalid Parameter Index number ..." message - * and creates a new SQLException object whose SQLState is set to "M1M05". + * and creates a new SQLDataException object whose SQLState is set + * to "22010": invalid indicator parameter value. * * @param paramIdx the parameter index number - * @return a new created SQLException object with SQLState M1M05 + * @return a new created SQLDataException object with SQLState 22010 */ - private static SQLException newSQLInvalidParameterIndexException(int paramIdx) { - return new SQLException("Invalid Parameter Index number: " + paramIdx, "M1M05"); + private static SQLDataException newSQLInvalidParameterIndexException(int paramIdx) { + return new SQLDataException("Invalid Parameter Index number: " + paramIdx, "22010"); } /** * Small helper method that formats the "Method ... not implemented" message * and creates a new SQLFeatureNotSupportedException object - * whose SQLState is set to "0A000". + * whose SQLState is set to "0A000": feature not supported. * * @param name the method name * @return a new created SQLFeatureNotSupportedException object with SQLState 0A000