Mercurial > hg > monetdb-java
diff src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java @ 0:a5a898f6886c
Copy of MonetDB java directory changeset e6e32756ad31.
author | Sjoerd Mullender <sjoerd@acm.org> |
---|---|
date | Wed, 21 Sep 2016 09:34:48 +0200 (2016-09-21) |
parents | |
children | a0e8adf10d41 |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java @@ -0,0 +1,2503 @@ +/* + * 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 - 2016 MonetDB B.V. + */ + +package nl.cwi.monetdb.jdbc; + +import java.sql.*; +import java.util.*; +import java.net.URL; +import java.io.*; +import java.nio.*; +import java.math.*; // BigDecimal, etc. +import java.text.SimpleDateFormat; + +/** + * A {@link PreparedStatement} suitable for the MonetDB database. + * + * This implementation of the PreparedStatement interface uses the + * capabilities of the MonetDB/SQL backend to prepare and execute + * queries. The backend takes care of finding the '?'s in the input and + * returns the types it expects for them. + * + * An example of a server response on a prepare query is: + * <pre> + * % prepare select name from tables where id > ? and id < ?; + * &5 0 2 3 2 + * # prepare, prepare, prepare # table_name + * # type, digits, scale # name + * # varchar, int, int # type + * # 0, 0, 0 # length + * [ "int", 9, 0 ] + * [ "int", 9, 0 ] + * </pre> + * + * @author Fabian Groffen + * @version 0.3 + */ +public class MonetPreparedStatement + extends MonetStatement + implements PreparedStatement +{ + private final String[] monetdbType; + private final int[] javaType; + private final int[] digits; + private final int[] scale; + private final String[] schema; + private final String[] table; + private final String[] column; + private final int id; + private final int size; + private final int rscolcnt; + + private final String[] values; + + private final MonetConnection connection; + + /* only parse the date patterns once, use multiple times */ + /** Format of a timestamp with RFC822 time zone */ + final SimpleDateFormat mTimestampZ = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ"); + /** Format of a timestamp */ + final SimpleDateFormat mTimestamp = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + /** Format of a time with RFC822 time zone */ + final SimpleDateFormat mTimeZ = + new SimpleDateFormat("HH:mm:ss.SSSZ"); + /** Format of a time */ + final SimpleDateFormat mTime = + new SimpleDateFormat("HH:mm:ss.SSS"); + /** Format of a date used by mserver */ + final SimpleDateFormat mDate = + new SimpleDateFormat("yyyy-MM-dd"); + + /** + * MonetPreparedStatement constructor which checks the arguments for + * validity. A MonetPreparedStatement is backed by a + * {@link MonetStatement}, which deals with most of the required stuff of + * this class. + * + * @param connection the connection that created this Statement + * @param resultSetType type of {@link ResultSet} to produce + * @param resultSetConcurrency concurrency of ResultSet to produce + * @param prepareQuery the query string to prepare + * @throws SQLException if an error occurs during login + * @throws IllegalArgumentException is one of the arguments is null or empty + */ + MonetPreparedStatement( + MonetConnection connection, + int resultSetType, + int resultSetConcurrency, + int resultSetHoldability, + String prepareQuery) + throws SQLException, IllegalArgumentException + { + super( + connection, + resultSetType, + resultSetConcurrency, + resultSetHoldability + ); + + if (!super.execute("PREPARE " + prepareQuery)) + throw new SQLException("Unexpected server response", "M0M10"); + + // cheat a bit to get the ID and the number of columns + id = ((MonetConnection.ResultSetResponse)header).id; + size = ((MonetConnection.ResultSetResponse)header).tuplecount; + rscolcnt = ((MonetConnection.ResultSetResponse)header).columncount; + + // initialise blank finals + monetdbType = new String[size]; + javaType = new int[size]; + digits = new int[size]; + scale = new int[size]; + schema = new String[size]; + table = new String[size]; + column = new String[size]; + values = new String[size]; + + this.connection = connection; + + // 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"); + } + rs.close(); + + // PreparedStatements are by default poolable + poolable = true; + } + + /** + * Constructs an empty MonetPreparedStatement. This constructor is + * in particular useful for extensions of this class. + * + * @param connection the connection that created this Statement + * @param resultSetType type of ResultSet to produce + * @param resultSetConcurrency concurrency of ResultSet to produce + * @throws SQLException if an error occurs during login + */ + /* Disabled this constructor code as it is not part of the JDBC interface + It may be enabled again when a subclass is constructed which needs it. + MonetPreparedStatement( + MonetConnection connection, + int resultSetType, + int resultSetConcurrency, + int resultSetHoldability) + throws SQLException + { + super( + connection, + resultSetType, + resultSetConcurrency, + resultSetHoldability + ); + // initialise blank finals + monetdbType = null; + javaType = null; + digits = null; + scale = null; + schema = null; + table = null; + column = null; + values = null; + id = -1; + size = -1; + rscolcnt = -1; + + this.connection = connection; + } + */ + + //== methods interface PreparedStatement + + /** + * Adds a set of parameters to this PreparedStatement object's batch + * of commands. + * + * @throws SQLException if a database access error occurs + */ + @Override + public void addBatch() throws SQLException { + super.addBatch(transform()); + } + + /** override the addBatch from the Statement to throw an SQLException */ + @Override + public void addBatch(String q) throws SQLException { + throw new SQLException("This method is not available in a PreparedStatement!", "M1M05"); + } + + /** + * Clears the current parameter values immediately. + * + * In general, parameter values remain in force for repeated use of a + * statement. Setting a parameter value automatically clears its previous + * value. However, in some cases it is useful to immediately release the + * resources used by the current parameter values; this can be done by + * calling the method clearParameters. + */ + @Override + public void clearParameters() { + for (int i = 0; i < values.length; i++) { + values[i] = null; + } + } + + /** + * Executes the SQL statement in this PreparedStatement object, + * which may be any kind of SQL statement. Some prepared statements + * return multiple results; the execute method handles these complex + * statements as well as the simpler form of statements handled by + * the methods executeQuery and executeUpdate. + * + * The execute method returns a boolean to indicate the form of the + * first result. You must call either the method getResultSet or + * getUpdateCount to retrieve the result; you must call + * getMoreResults to move to any subsequent result(s). + * + * @return true if the first result is a ResultSet object; false if the + * first result is an update count or there is no result + * @throws SQLException if a database access error occurs or an argument + * is supplied to this method + */ + @Override + public boolean execute() throws SQLException { + return super.execute(transform()); + } + + /** override the execute from the Statement to throw an SQLException */ + @Override + public boolean execute(String q) throws SQLException { + throw new SQLException("This method is not available in a PreparedStatement!", "M1M05"); + } + + /** + * Executes the SQL query in this PreparedStatement object and returns the + * ResultSet object generated by the query. + * + * @return a ResultSet object that contains the data produced by the query; + * never null + * @throws SQLException if a database access error occurs or the SQL + * statement does not return a ResultSet object + */ + @Override + public ResultSet executeQuery() throws SQLException{ + if (execute() != true) + throw new SQLException("Query did not produce a result set", "M1M19"); + + return getResultSet(); + } + + /** override the executeQuery from the Statement to throw an SQLException*/ + @Override + public ResultSet executeQuery(String q) throws SQLException { + throw new SQLException("This method is not available in a PreparedStatement!", "M1M05"); + } + + /** + * Executes the SQL statement in this PreparedStatement object, which must + * be an SQL INSERT, UPDATE or DELETE statement; or an SQL statement that + * returns nothing, such as a DDL statement. + * + * @return either (1) the row count for INSERT, UPDATE, or DELETE + * statements or (2) 0 for SQL statements that return nothing + * @throws SQLException if a database access error occurs or the SQL + * statement returns a ResultSet object + */ + @Override + public int executeUpdate() throws SQLException { + if (execute() != false) + throw new SQLException("Query produced a result set", "M1M17"); + + return getUpdateCount(); + } + + /** override the executeUpdate from the Statement to throw an SQLException*/ + @Override + public int executeUpdate(String q) throws SQLException { + throw new SQLException("This method is not available in a PreparedStatement!", "M1M05"); + } + + /** + * Returns the index (0..size-1) in the backing arrays for the given + * resultset column number or an SQLException when not found + */ + private int getColumnIdx(int colnr) throws SQLException { + int curcol = 0; + for (int i = 0; i < size; i++) { + if (column[i] == null) + continue; + curcol++; + if (curcol == colnr) + return i; + } + throw new SQLException("No such column with index: " + colnr, "M1M05"); + } + /** + * Returns the index (0..size-1) in the backing arrays for the given + * parameter number or an SQLException when not found + */ + private int getParamIdx(int paramnr) throws SQLException { + int curparam = 0; + for (int i = 0; i < size; i++) { + if (column[i] != null) + continue; + curparam++; + if (curparam == paramnr) + return i; + } + throw new SQLException("No such parameter with index: " + paramnr, "M1M05"); + } + + + /* helper for the anonymous class inside getMetaData */ + private abstract class rsmdw extends MonetWrapper implements ResultSetMetaData {} + /** + * Retrieves a ResultSetMetaData object that contains information + * about the columns of the ResultSet object that will be returned + * when this PreparedStatement object is executed. + * + * Because a PreparedStatement object is precompiled, it is possible + * to know about the ResultSet object that it will return without + * having to execute it. Consequently, it is possible to invoke the + * method getMetaData on a PreparedStatement object rather than + * waiting to execute it and then invoking the ResultSet.getMetaData + * method on the ResultSet object that is returned. + * + * @return the description of a ResultSet object's columns or null if the + * driver cannot return a ResultSetMetaData object + * @throws SQLException if a database access error occurs + */ + @Override + public ResultSetMetaData getMetaData() { + if (rscolcnt == 3) + return null; // not sufficient data with pre-Dec2011 PREPARE + + // return inner class which implements the ResultSetMetaData interface + return new rsmdw() { + /** + * Returns the number of columns in this ResultSet object. + * + * @returns the number of columns + */ + @Override + public int getColumnCount() { + int cnt = 0; + + for (int i = 0; i < size; i++) { + if (column[i] != null) + cnt++; + } + return cnt; + } + + /** + * Indicates whether the designated column is automatically numbered. + * + * @param column the first column is 1, the second is 2, ... + * @return true if so; false otherwise + * @throws SQLException if a database access error occurs + */ + @Override + public boolean isAutoIncrement(int column) throws SQLException { + /* TODO: in MonetDB only numeric (int, decimal) columns could be autoincrement/serial + * This however requires an expensive dbmd.getColumns(null, schema, table, column) + * query call to pull the IS_AUTOINCREMENT value for this column. + * See also ResultSetMetaData.isAutoIncrement() + */ + // For now we simply allways return false. + return false; + } + + /** + * Indicates whether a column's case matters. + * + * @param column the first column is 1, the second is 2, ... + * @returns false + */ + @Override + public boolean isCaseSensitive(int column) throws SQLException { + switch (javaType[getColumnIdx(column)]) { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness + case Types.CLOB: + return true; + } + + return false; + } + + /** + * Indicates whether the designated column can be used in a + * where clause. + * + * Returning true for all here, even for CLOB, BLOB. + * + * @param column the first column is 1, the second is 2, ... + * @returns true + */ + @Override + public boolean isSearchable(int column) { + return true; + } + + /** + * Indicates whether the designated column is a cash value. + * From the MonetDB database perspective it is by definition + * unknown whether the value is a currency, because there are + * no currency datatypes such as MONEY. With this knowledge + * we can always return false here. + * + * @param column the first column is 1, the second is 2, ... + * @returns false + */ + @Override + public boolean isCurrency(int column) { + return false; + } + + /** + * Indicates whether values in the designated column are signed + * numbers. + * Within MonetDB all numeric types (except oid and ptr) are signed. + * + * @param column the first column is 1, the second is 2, ... + * @return true if so; false otherwise + */ + @Override + public boolean isSigned(int column) throws SQLException { + String monettype = getColumnTypeName(column); + if (monettype != null) { + if ("oid".equals(monettype) + || "ptr".equals(monettype)) + return false; + } + // we can hardcode this, based on the colum type + switch (javaType[getColumnIdx(column)]) { + case Types.NUMERIC: + case Types.DECIMAL: + case Types.TINYINT: + case Types.SMALLINT: + case Types.INTEGER: + case Types.BIGINT: + case Types.REAL: + case Types.FLOAT: + case Types.DOUBLE: + return true; + case Types.BIT: // we don't use type BIT, it's here for completeness + case Types.BOOLEAN: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + default: + return false; + } + } + + /** + * Indicates the designated column's normal maximum width in + * characters. + * + * @param column the first column is 1, the second is 2, ... + * @return the normal maximum number of characters allowed as the + * width of the designated column + * @throws SQLException if there is no such column + */ + @Override + public int getColumnDisplaySize(int column) throws SQLException { + return digits[getColumnIdx(column)]; + } + + /** + * Get the designated column's table's schema. + * + * @param column the first column is 1, the second is 2, ... + * @return schema name or "" if not applicable + * @throws SQLException if a database access error occurs + */ + @Override + public String getSchemaName(int column) throws SQLException { + return schema[getColumnIdx(column)]; + } + + /** + * Gets the designated column's table name. + * + * @param column the first column is 1, the second is 2, ... + * @return table name or "" if not applicable + */ + @Override + public String getTableName(int col) throws SQLException { + return table[getColumnIdx(col)]; + } + + /** + * Get the designated column's number of decimal digits. + * This method is currently very expensive as it needs to + * retrieve the information from the database using an SQL + * query. + * + * @param column the first column is 1, the second is 2, ... + * @return precision + * @throws SQLException if a database access error occurs + */ + @Override + public int getPrecision(int column) throws SQLException { + return digits[getColumnIdx(column)]; + } + + /** + * Gets the designated column's number of digits to right of + * the decimal point. This method is currently very + * expensive as it needs to retrieve the information from + * the database using an SQL query. + * + * @param column the first column is 1, the second is 2, ... + * @return scale + * @throws SQLException if a database access error occurs + */ + @Override + public int getScale(int column) throws SQLException { + return scale[getColumnIdx(column)]; + } + + /** + * Indicates the nullability of values in the designated + * column. This method is currently very expensive as it + * needs to retrieve the information from the database using + * an SQL query. + * + * @param column the first column is 1, the second is 2, ... + * @return nullability + * @throws SQLException if a database access error occurs + */ + @Override + public int isNullable(int column) throws SQLException { + return columnNullableUnknown; + } + + /** + * Gets the designated column's table's catalog name. + * MonetDB does not support the catalog naming concept as in: catalog.schema.table naming scheme + * + * @param column the first column is 1, the second is 2, ... + * @return the name of the catalog for the table in which the given + * column appears or "" if not applicable + */ + @Override + public String getCatalogName(int column) throws SQLException { + return null; // MonetDB does NOT support catalogs + } + + /** + * Indicates whether the designated column is definitely not + * writable. MonetDB does not support cursor updates, so + * nothing is writable. + * + * @param column the first column is 1, the second is 2, ... + * @return true if so; false otherwise + */ + @Override + public boolean isReadOnly(int column) { + return true; + } + + /** + * Indicates whether it is possible for a write on the + * designated column to succeed. + * + * @param column the first column is 1, the second is 2, ... + * @return true if so; false otherwise + */ + @Override + public boolean isWritable(int column) { + return false; + } + + /** + * Indicates whether a write on the designated column will + * definitely succeed. + * + * @param column the first column is 1, the second is 2, ... + * @return true if so; false otherwise + */ + @Override + public boolean isDefinitelyWritable(int column) { + return false; + } + + /** + * Returns the fully-qualified name of the Java class whose + * instances are manufactured if the method + * ResultSet.getObject is called to retrieve a value from + * the column. ResultSet.getObject may return a subclass of + * the class returned by this method. + * + * @param column the first column is 1, the second is 2, ... + * @return the fully-qualified name of the class in the Java + * programming language that would be used by the method + * ResultSet.getObject to retrieve the value in the + * specified column. This is the class name used for custom + * mapping. + * @throws SQLException if there is no such column + */ + @Override + public String getColumnClassName(int column) throws SQLException { + return MonetResultSet.getClassForType(javaType[getColumnIdx(column)]).getName(); + } + + /** + * Gets the designated column's suggested title for use in + * printouts and displays. This is currently equal to + * getColumnName(). + * + * @param column the first column is 1, the second is 2, ... + * @return the suggested column title + * @throws SQLException if there is no such column + */ + @Override + public String getColumnLabel(int column) throws SQLException { + return getColumnName(column); + } + + /** + * Gets the designated column's name + * + * @param column the first column is 1, the second is 2, ... + * @return the column name + * @throws SQLException if there is no such column + */ + @Override + public String getColumnName(int col) throws SQLException { + return column[getColumnIdx(col)]; + } + + /** + * Retrieves the designated column's SQL type. + * + * @param column the first column is 1, the second is 2, ... + * @return SQL type from java.sql.Types + * @throws SQLException if there is no such column + */ + @Override + public int getColumnType(int column) throws SQLException { + return javaType[getColumnIdx(column)]; + } + + /** + * Retrieves the designated column's database-specific type name. + * + * @param column the first column is 1, the second is 2, ... + * @return type name used by the database. If the column type is a + * user-defined type, then a fully-qualified type name is + * returned. + * @throws SQLException if there is no such column + */ + @Override + public String getColumnTypeName(int column) throws SQLException { + return monetdbType[getColumnIdx(column)]; + } + }; + } + + /* helper class for the anonymous class in getParameterMetaData */ + private abstract class pmdw extends MonetWrapper implements ParameterMetaData {} + /** + * Retrieves the number, types and properties of this + * PreparedStatement object's parameters. + * + * @return a ParameterMetaData object that contains information + * about the number, types and properties of this + * PreparedStatement object's parameters + * @throws SQLException if a database access error occurs + */ + @Override + public ParameterMetaData getParameterMetaData() throws SQLException { + return new pmdw() { + /** + * Retrieves the number of parameters in the + * PreparedStatement object for which this ParameterMetaData + * object contains information. + * + * @return the number of parameters + * @throws SQLException if a database access error occurs + */ + @Override + public int getParameterCount() throws SQLException { + int cnt = 0; + + for (int i = 0; i < size; i++) { + if (column[i] == null) + cnt++; + } + + return cnt; + } + + /** + * Retrieves whether null values are allowed in the + * designated parameter. + * + * This is currently always unknown for MonetDB/SQL. + * + * @param param the first parameter is 1, the second is 2, ... + * @return the nullability status of the given parameter; + * one of ParameterMetaData.parameterNoNulls, + * ParameterMetaData.parameterNullable, or + * ParameterMetaData.parameterNullableUnknown + * @throws SQLException if a database access error occurs + */ + @Override + public int isNullable(int param) throws SQLException { + return ParameterMetaData.parameterNullableUnknown; + } + + /** + * Retrieves whether values for the designated parameter can + * be signed numbers. + * + * @param param the first parameter is 1, the second is 2, ... + * @return true if so; false otherwise + * @throws SQLException if a database access error occurs + */ + @Override + public boolean isSigned(int param) throws SQLException { + // we can hardcode this, based on the colum type + // (from ResultSetMetaData.isSigned) + switch (javaType[getParamIdx(param)]) { + case Types.NUMERIC: + case Types.DECIMAL: + case Types.TINYINT: + case Types.SMALLINT: + case Types.INTEGER: + case Types.BIGINT: + case Types.REAL: + case Types.FLOAT: + case Types.DOUBLE: + return true; + case Types.BIT: // we don't use type BIT, it's here for completeness + case Types.BOOLEAN: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + default: + return false; + } + } + + /** + * Retrieves the designated parameter's number of decimal + * digits. + * + * @param param the first parameter is 1, the second is 2, ... + * @return precision + * @throws SQLException if a database access error occurs + */ + @Override + public int getPrecision(int param) throws SQLException { + return digits[getParamIdx(param)]; + } + + /** + * Retrieves the designated parameter's number of digits to + * right of the decimal point. + * + * @param param the first parameter is 1, the second is 2, ... + * @return scale + * @throws SQLException if a database access error occurs + */ + @Override + public int getScale(int param) throws SQLException { + return scale[getParamIdx(param)]; + } + + /** + * Retrieves the designated parameter's SQL type. + * + * @param param the first parameter is 1, the second is 2, ... + * @return SQL type from java.sql.Types + * @throws SQLException if a database access error occurs + */ + @Override + public int getParameterType(int param) throws SQLException { + return javaType[getParamIdx(param)]; + } + + /** + * Retrieves the designated parameter's database-specific + * type name. + * + * @param param the first parameter is 1, the second is 2, ... + * @return type the name used by the database. If the + * parameter type is a user-defined type, then a + * fully-qualified type name is returned. + * @throws SQLException if a database access error occurs + */ + @Override + public String getParameterTypeName(int param) throws SQLException { + return monetdbType[getParamIdx(param)]; + } + + /** + * Retrieves the fully-qualified name of the Java class + * whose instances should be passed to the method + * PreparedStatement.setObject. + * + * @param param the first parameter is 1, the second is 2, ... + * @return the fully-qualified name of the class in the Java + * programming language that would be used by the + * method PreparedStatement.setObject to set the + * value in the specified parameter. This is the + * class name used for custom mapping. + * @throws SQLException if a database access error occurs + */ + @Override + public String getParameterClassName(int param) throws SQLException { + Map<String,Class<?>> map = getConnection().getTypeMap(); + Class<?> c; + if (map.containsKey(monetdbType[getParamIdx(param)])) { + c = (Class)map.get(monetdbType[getParamIdx(param)]); + } else { + c = MonetResultSet.getClassForType( + javaType[getParamIdx(param)] + ); + } + return c.getName(); + } + + /** + * Retrieves the designated parameter's mode. + * For MonetDB/SQL this is currently always unknown. + * + * @param param - the first parameter is 1, the second is 2, ... + * @return mode of the parameter; one of + * ParameterMetaData.parameterModeIn, + * ParameterMetaData.parameterModeOut, or + * ParameterMetaData.parameterModeInOut + * ParameterMetaData.parameterModeUnknown. + * @throws SQLException if a database access error occurs + */ + @Override + public int getParameterMode(int param) throws SQLException { + return ParameterMetaData.parameterModeUnknown; + } + }; + } + + /** + * 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 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 { + throw new SQLException("Operation setArray(int i, Array x) currently not supported!", "0A000"); + } + + /** + * Sets the designated parameter to the given input stream, which will have + * the specified number of bytes. When a very large ASCII value is input to + * a LONGVARCHAR parameter, it may be more practical to send it via a + * java.io.InputStream. Data will be read from the stream as needed until + * end-of-file is reached. The JDBC driver will do any necessary conversion + * from ASCII to the database char format. + * + * Note: This stream object can either be a standard Java stream object or + * your own subclass that implements the standard interface. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param x the Java input stream that contains the ASCII parameter value + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public void setAsciiStream(int parameterIndex, InputStream x) + throws SQLException + { + throw newSQLFeatureNotSupportedException("setAsciiStream"); + } + + /** + * Sets the designated parameter to the given input stream, which will have + * the specified number of bytes. When a very large ASCII value is input to + * a LONGVARCHAR parameter, it may be more practical to send it via a + * java.io.InputStream. Data will be read from the stream as needed until + * end-of-file is reached. The JDBC driver will do any necessary conversion + * from ASCII to the database char format. + * + * Note: This stream object can either be a standard Java stream object or + * your own subclass that implements the standard interface. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param x the Java input stream that contains the ASCII parameter value + * @param length the number of bytes in the stream + * @throws SQLException if a database access error occurs + */ + @Override + public void setAsciiStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + throw newSQLFeatureNotSupportedException("setAsciiStream"); + } + + /** + * Sets the designated parameter to the given input stream, which + * will have the specified number of bytes. When a very large ASCII + * value is input to a LONGVARCHAR parameter, it may be more + * practical to send it via a java.io.InputStream. Data will be read + * from the stream as needed until end-of-file is reached. The JDBC + * driver will do any necessary conversion from ASCII to the + * database char format. + * + * Note: This stream object can either be a standard Java stream object or + * your own subclass that implements the standard interface. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param x the Java input stream that contains the ASCII parameter value + * @param length the number of bytes in the stream + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public void setAsciiStream(int parameterIndex, InputStream x, long length) + throws SQLException + { + throw newSQLFeatureNotSupportedException("setAsciiStream"); + } + + /** + * Sets the designated parameter to the given java.math.BigDecimal value. + * 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 x the parameter value + * @throws SQLException if a database access error occurs + */ + @Override + public void setBigDecimal(int idx, BigDecimal x) + throws SQLException + { + // get array position + int i = getParamIdx(idx); + + // round to the scale of the DB: + x = x.setScale(scale[i], RoundingMode.HALF_UP); + + // if precision is now greater than that of the db, throw an error: + if (x.precision() > digits[i]) { + throw new SQLDataException("DECIMAL value exceeds allowed digits/scale: " + x.toPlainString() + " (" + digits[i] + "/" + scale[i] + ")", "22003"); + } + + // MonetDB doesn't like leading 0's, since it counts them as part of + // the precision, so let's strip them off. (But be careful not to do + // this to the exact number "0".) Also strip off trailing + // numbers that are inherent to the double representation. + String xStr = x.toPlainString(); + int dot = xStr.indexOf("."); + if (dot >= 0) + 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); + } + + /** + * Sets the designated parameter to the given input stream, which will have + * the specified number of bytes. When a very large binary value is input + * to a LONGVARBINARY parameter, it may be more practical to send it via a + * java.io.InputStream object. The data will be read from the stream as + * needed until end-of-file is reached. + * + * Note: This stream object can either be a standard Java stream object or + * your own subclass that implements the standard interface. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param x the java input stream which contains the binary parameter value + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public void setBinaryStream(int parameterIndex, InputStream x) + throws SQLException + { + throw newSQLFeatureNotSupportedException("setBinaryStream"); + } + + /** + * Sets the designated parameter to the given input stream, which will have + * the specified number of bytes. When a very large binary value is input + * to a LONGVARBINARY parameter, it may be more practical to send it via a + * java.io.InputStream object. The data will be read from the stream as + * needed until end-of-file is reached. + * + * Note: This stream object can either be a standard Java stream object or + * your own subclass that implements the standard interface. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param x the java input stream which contains the binary parameter value + * @param length the number of bytes in the stream + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public void setBinaryStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + throw newSQLFeatureNotSupportedException("setBinaryStream"); + } + + /** + * Sets the designated parameter to the given input stream, which will have + * the specified number of bytes. When a very large binary value is input + * to a LONGVARBINARY parameter, it may be more practical to send it via a + * java.io.InputStream object. The data will be read from the stream as + * needed until end-of-file is reached. + * + * Note: This stream object can either be a standard Java stream object or + * your own subclass that implements the standard interface. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param x the java input stream which contains the binary parameter value + * @param length the number of bytes in the stream + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public void setBinaryStream(int parameterIndex, InputStream x, long length) + throws SQLException + { + throw newSQLFeatureNotSupportedException("setBinaryStream"); + } + + /** + * Sets the designated parameter to the given Blob object. The driver + * converts this to an SQL BLOB value when it sends it to the database. + * + * @param i the first parameter is 1, the second is 2, ... + * @param x a Blob object that maps an SQL BLOB value + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public void setBlob(int i, InputStream x) throws SQLException { + throw newSQLFeatureNotSupportedException("setBlob"); + } + + /** + * Sets the designated parameter to the given Blob object. The driver + * converts this to an SQL BLOB value when it sends it to the database. + * + * @param i the first parameter is 1, the second is 2, ... + * @param x a Blob object that maps an SQL BLOB value + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public void setBlob(int i, Blob x) throws SQLException { + throw newSQLFeatureNotSupportedException("setBlob"); + } + + /** + * Sets the designated parameter to a InputStream object. The + * inputstream must contain the number of characters specified by + * length otherwise a SQLException will be generated when the + * PreparedStatement is executed. This method differs from the + * setBinaryStream (int, InputStream, int) method because it informs + * the driver that the parameter value should be sent to the server + * as a BLOB. When the setBinaryStream method is used, the driver + * may have to do extra work to determine whether the parameter data + * should be sent to the server as a LONGVARBINARY or a BLOB. + * + * @param i the first parameter is 1, the second is 2, ... + * @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 + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public void setBlob(int i, InputStream is, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("setBlob"); + } + + /** + * Sets the designated parameter to the given Java boolean value. The + * driver converts this to an SQL BIT value when it sends it to the + * database. + * + * @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 setBoolean(int parameterIndex, boolean x) throws SQLException { + setValue(parameterIndex, "" + x); + } + + /** + * Sets the designated parameter to the given Java byte value. The driver + * converts this to an SQL TINYINT value when it sends it to the database. + * + * @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 setByte(int parameterIndex, byte x) throws SQLException { + setValue(parameterIndex, "" + x); + } + + static final String HEXES = "0123456789ABCDEF"; + /** + * Sets the designated parameter to the given Java array of bytes. The + * driver converts this to an SQL VARBINARY or LONGVARBINARY (depending + * on the argument's size relative to the driver's limits on VARBINARY + * values) when it sends it to the database. + * + * @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 setBytes(int parameterIndex, byte[] x) throws SQLException { + if (x == null) { + setNull(parameterIndex, -1); + return; + } + + StringBuilder hex = new StringBuilder(x.length * 2); + byte b; + for (int i = 0; i < x.length; i++) { + b = x[i]; + hex.append(HEXES.charAt((b & 0xF0) >> 4)) + .append(HEXES.charAt((b & 0x0F))); + } + setValue(parameterIndex, "blob '" + hex.toString() + "'"); + } + + /** + * Sets the designated parameter to the given Reader object, which is the + * given number of characters long. When a very large UNICODE value is + * input to a LONGVARCHAR parameter, it may be more practical to send it + * via a java.io.Reader object. The data will be read from the stream as + * needed until end-of-file is reached. The JDBC driver will do any + * necessary conversion from UNICODE to the database char format. + * + * Note: This stream object can either be a standard Java stream object or + * your own subclass that implements the standard interface. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param reader the java.io.Reader object that contains the Unicode data + * @param length the number of characters in the stream + * @throws SQLException if a database access error occurs + */ + @Override + public void setCharacterStream( + int parameterIndex, + Reader reader, + int length) + throws SQLException + { + if (reader == null) { + setNull(parameterIndex, -1); + return; + } + + CharBuffer tmp = CharBuffer.allocate(length); + try { + reader.read(tmp); + } catch (IOException e) { + throw new SQLException(e.getMessage(), "M1M25"); + } + setString(parameterIndex, tmp.toString()); + } + + /** + * Sets the designated parameter to the given Reader object, which is the + * given number of characters long. When a very large UNICODE value is + * input to a LONGVARCHAR parameter, it may be more practical to send it + * via a java.io.Reader object. The data will be read from the stream as + * needed until end-of-file is reached. The JDBC driver will do any + * necessary conversion from UNICODE to the database char format. + * + * Note: This stream object can either be a standard Java stream object or + * your own subclass that implements the standard interface. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param reader the java.io.Reader object that contains the Unicode data + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public void setCharacterStream(int parameterIndex, Reader reader) + throws SQLException + { + setCharacterStream(parameterIndex, reader, 0); + } + + /** + * Sets the designated parameter to the given Reader object, which is the + * given number of characters long. When a very large UNICODE value is + * input to a LONGVARCHAR parameter, it may be more practical to send it + * via a java.io.Reader object. The data will be read from the stream as + * needed until end-of-file is reached. The JDBC driver will do any + * necessary conversion from UNICODE to the database char format. + * + * Note: This stream object can either be a standard Java stream object or + * your own subclass that implements the standard interface. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param reader the java.io.Reader object that contains the Unicode data + * @param length the number of characters in the stream + * @throws SQLException if a database access error occurs + */ + @Override + public void setCharacterStream( + int parameterIndex, + Reader reader, + long length) + throws SQLException + { + // given the implementation of the int-version, downcast is ok + setCharacterStream(parameterIndex, reader, (int)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 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 { + if (x == null) { + setNull(i, -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()))); + } + + /** + * 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 reader an object that contains the data to set the parameter + * value to + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public void setClob(int i, Reader reader) throws SQLException { + if (reader == null) { + setNull(i, -1); + return; + } + // Some buffer. Size of 8192 is default for BufferedReader, so... + char[] arr = new char[8192]; + StringBuilder buf = new StringBuilder(8192 * 8); + int numChars; + try { + while ((numChars = reader.read(arr, 0, arr.length)) > 0) { + buf.append(arr, 0, numChars); + } + setString(i, buf.toString()); + } catch (IOException e) { + throw new SQLException(e); + } + } + + /** + * Sets the designated parameter to a Reader object. The reader must + * contain the number of characters specified by length otherwise a + * SQLException will be generated when the PreparedStatement is + * executed. This method differs from the setCharacterStream (int, + * Reader, int) method because it informs the driver that the + * parameter value should be sent to the server as a CLOB. When the + * setCharacterStream method is used, the driver may have to do + * 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 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 { + if (reader == null || length < 0) { + setNull(i, -1); + return; + } + // simply serialise the CLOB into a variable for now... far from + // efficient, but might work for a few cases... + CharBuffer buf = CharBuffer.allocate((int)length); // have to down cast :( + try { + reader.read(buf); + } catch (IOException e) { + throw new SQLException("failed to read from stream: " + + e.getMessage(), "M1M25"); + } + // We have to rewind the buffer, because otherwise toString() returns "". + buf.rewind(); + setString(i, buf.toString()); + } + + /** + * Sets the designated parameter to the given java.sql.Date value. The + * driver converts this to an SQL DATE value when it sends it to the + * database. + * + * @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 setDate(int parameterIndex, java.sql.Date x) + throws SQLException + { + setDate(parameterIndex, x, null); + } + + /** + * Sets the designated parameter to the given java.sql.Date value, using + * the given Calendar object. The driver uses the Calendar object to + * construct an SQL DATE value, which the driver then sends to the + * database. With a Calendar object, the driver can calculate the date + * taking into account a custom timezone. If no Calendar object is + * specified, the driver uses the default timezone, which is that of the + * virtual machine running the application. + * + * @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 date + * @throws SQLException if a database access error occurs + */ + @Override + public void setDate(int parameterIndex, java.sql.Date x, Calendar cal) + throws SQLException + { + if (x == null) { + setNull(parameterIndex, -1); + return; + } + + if (cal == null) { + setValue(parameterIndex, "date '" + x.toString() + "'"); + } else { + mDate.setTimeZone(cal.getTimeZone()); + setValue(parameterIndex, "date '" + mDate.format(x) + "'"); + } + } + + /** + * Sets the designated parameter to the given Java double value. The driver + * converts this to an SQL DOUBLE value when it sends it to the database. + * + * @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 setDouble(int parameterIndex, double x) throws SQLException { + setValue(parameterIndex, "" + x); + } + + /** + * Sets the designated parameter to the given Java float value. The driver + * converts this to an SQL FLOAT value when it sends it to the database. + * + * @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 setFloat(int parameterIndex, float x) throws SQLException { + setValue(parameterIndex, "" + x); + } + + /** + * Sets the designated parameter to the given Java int value. The driver + * converts this to an SQL INTEGER value when it sends it to the database. + * + * @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 setInt(int parameterIndex, int x) throws SQLException { + setValue(parameterIndex, "" + x); + } + + /** + * Sets the designated parameter to the given Java long value. The driver + * converts this to an SQL BIGINT value when it sends it to the database. + * + * @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 setLong(int parameterIndex, long x) throws SQLException { + setValue(parameterIndex, "" + x); + } + + /** + * Sets the designated parameter to a Reader object. The Reader + * reads the data till end-of-file is reached. The driver does the + * 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 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"); + } + + /** + * Sets the designated parameter to a Reader object. The Reader + * reads the data till end-of-file is reached. The driver does the + * 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 value the parameter value + * @param length the number of characters in the parameter data. + * @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, long length) + throws SQLException + { + throw newSQLFeatureNotSupportedException("setNCharacterStream"); + } + + /** + * Sets the designated parameter to a java.sql.NClob object. The + * 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 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 { + throw newSQLFeatureNotSupportedException("setNClob"); + } + + /** + * Sets the designated parameter to a java.sql.NClob object. The + * 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 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 { + throw newSQLFeatureNotSupportedException("setNClob"); + } + + /** + * Sets the designated parameter to a Reader object. The reader must + * contain the number of characters specified by length otherwise a + * SQLException will be generated when the PreparedStatement is + * executed. This method differs from the setCharacterStream (int, + * Reader, int) method because it informs the driver that the + * parameter value should be sent to the server as a NCLOB. When the + * setCharacterStream method is used, the driver may have to do + * 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 r 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 + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public void setNClob(int i, Reader r, long length) throws SQLException { + throw newSQLFeatureNotSupportedException("setNClob"); + } + + /** + * Sets the designated paramter to the given String object. The + * driver converts this to a SQL NCHAR or NVARCHAR or LONGNVARCHAR + * 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 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"); + } + + /** + * Sets the designated parameter to SQL NULL. + * + * Note: You must specify the parameter's SQL type. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param sqlType the SQL type code defined in java.sql.Types + * @throws SQLException if a database access error occurs + */ + @Override + public void setNull(int parameterIndex, int sqlType) throws SQLException { + // we discard the given type here, the backend converts the + // value NULL to whatever it needs for the column + setValue(parameterIndex, "NULL"); + } + + /** + * Sets the designated parameter to SQL NULL. This version of the method + * setNull should be used for user-defined types and REF type parameters. + * Examples of user-defined types include: STRUCT, DISTINCT, JAVA_OBJECT, + * and named array types. + * + * Note: To be portable, applications must give the SQL type code and the + * fully-qualified SQL type name when specifying a NULL user-defined or REF + * parameter. In the case of a user-defined type the name is the type name + * of the parameter itself. For a REF parameter, the name is the type name + * of the referenced type. If a JDBC driver does not need the type code or + * type name information, it may ignore it. Although it is intended for + * user-defined and Ref parameters, this method may be used to set a null + * 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 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 + * REF + * @throws SQLException if a database access error occurs + */ + @Override + public void setNull(int paramIndex, int sqlType, String typeName) + throws SQLException + { + // MonetDB/SQL's NULL needs no type + setNull(paramIndex, sqlType); + } + + /** + * Sets the value of the designated parameter using the given + * object. The second parameter must be of type Object; therefore, + * the java.lang equivalent objects should be used for built-in + * types. + * + * The JDBC specification specifies a standard mapping from Java + * Object types to SQL types. The given argument will be converted + * to the corresponding SQL type before being sent to the database. + * + * Note that this method may be used to pass datatabase-specific + * abstract data types, by using a driver-specific Java type. If the + * object is of a class implementing the interface SQLData, the JDBC + * driver should call the method SQLData.writeSQL to write it to the + * SQL data stream. If, on the other hand, the object is of a class + * implementing Ref, Blob, Clob, Struct, or Array, the driver should + * pass it to the database as a value of the corresponding SQL type. + * + * This method throws an exception if there is an ambiguity, for + * 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 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)]); + } + + /** + * Sets the value of the designated parameter with the given object. This + * method is like the method setObject below, except that it assumes a scale + * of zero. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param x the object containing the input parameter value + * @param targetSqlType the SQL type (as defined in java.sql.Types) to be + * sent to the database + * @throws SQLException if a database access error occurs + */ + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType) + throws SQLException + { + setObject(parameterIndex, x, targetSqlType, 0); + } + + /** + * Sets the value of the designated parameter with the given object. The + * second argument must be an object type; for integral values, the + * java.lang equivalent objects should be used. + * + * The given Java object will be converted to the given targetSqlType + * before being sent to the database. If the object has a custom mapping + * (is of a class implementing the interface SQLData), the JDBC driver + * should call the method SQLData.writeSQL to write it to the SQL data + * stream. If, on the other hand, the object is of a class implementing + * Ref, Blob, Clob, Struct, or Array, the driver should pass it to the + * database as a value of the corresponding SQL type. + * + * Note that this method may be used to pass database-specific abstract + * data types. + * + * To meet the requirements of this interface, the Java object is + * converted in the driver, instead of using a SQL CAST construct. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param x the object containing the input parameter value + * @param targetSqlType the SQL type (as defined in java.sql.Types) to + * be sent to the database. The scale argument may + * further qualify this type. + * @param scale for java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types, + * this is the number of digits after the decimal + * point. For Java Object types InputStream and Reader, + * this is the length of the data in the stream or + * reader. For all other types, this value will be + * ignored. + * @throws SQLException if a database access error occurs + * @see Types + */ + @Override + public void setObject( + int parameterIndex, + Object x, + int targetSqlType, + int scale) + throws SQLException + { + // this is according to table B-5 + if (x instanceof String) { + switch (targetSqlType) { + 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.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + setString(parameterIndex, (String)x); + 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"); + } + } else if (x instanceof BigDecimal || + x instanceof Byte || + x instanceof Short || + x instanceof Integer || + x instanceof Long || + x instanceof Float || + x instanceof Double) + { + Number num = (Number)x; + switch (targetSqlType) { + case Types.TINYINT: + setByte(parameterIndex, num.byteValue()); + break; + case Types.SMALLINT: + setShort(parameterIndex, num.shortValue()); + break; + case Types.INTEGER: + setInt(parameterIndex, num.intValue()); + break; + case Types.BIGINT: + if (x instanceof BigDecimal) { + BigDecimal bd = (BigDecimal)x; + setLong(parameterIndex, bd.setScale(scale, BigDecimal.ROUND_HALF_UP).longValue()); + } else { + setLong(parameterIndex, num.longValue()); + } + break; + case Types.REAL: + setFloat(parameterIndex, num.floatValue()); + break; + case Types.FLOAT: + case Types.DOUBLE: + setDouble(parameterIndex, num.doubleValue()); + break; + case Types.DECIMAL: + case Types.NUMERIC: + if (x instanceof BigDecimal) { + setBigDecimal(parameterIndex, (BigDecimal)x); + } else { + setBigDecimal(parameterIndex, + new BigDecimal(num.doubleValue())); + } + break; + case Types.BIT: + case Types.BOOLEAN: + if (num.doubleValue() != 0.0) { + setBoolean(parameterIndex, true); + } else { + setBoolean(parameterIndex, false); + } + break; + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + setString(parameterIndex, x.toString()); + break; + default: + throw new SQLException("Conversion not allowed", "M1M05"); + } + } else if (x instanceof Boolean) { + boolean val = ((Boolean)x).booleanValue(); + switch (targetSqlType) { + case Types.TINYINT: + setByte(parameterIndex, (byte)(val ? 1 : 0)); + break; + case Types.SMALLINT: + setShort(parameterIndex, (short)(val ? 1 : 0)); + break; + case Types.INTEGER: + setInt(parameterIndex, (val ? 1 : 0)); // do not cast to (int) as it generates a compiler warning + break; + case Types.BIGINT: + setLong(parameterIndex, (long)(val ? 1 : 0)); + break; + case Types.REAL: + setFloat(parameterIndex, (float)(val ? 1.0 : 0.0)); + break; + case Types.FLOAT: + case Types.DOUBLE: + setDouble(parameterIndex, (val ? 1.0 : 0.0)); // do no cast to (double) as it generates a compiler warning + break; + case Types.DECIMAL: + case Types.NUMERIC: + { + BigDecimal dec; + try { + dec = new BigDecimal(val ? 1.0 : 0.0); + } catch (NumberFormatException e) { + throw new SQLException("Internal error: unable to create template BigDecimal: " + e.getMessage(), "M0M03"); + } + setBigDecimal(parameterIndex, dec); + } break; + case Types.BIT: + case Types.BOOLEAN: + setBoolean(parameterIndex, val); + break; + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + setString(parameterIndex, "" + val); + break; + default: + throw new SQLException("Conversion not allowed", "M1M05"); + } + } else if (x instanceof BigInteger) { + BigInteger num = (BigInteger)x; + switch (targetSqlType) { + case Types.BIGINT: + setLong(parameterIndex, num.longValue()); + break; + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + setString(parameterIndex, x.toString()); + break; + default: + throw new SQLException("Conversion not allowed", "M1M05"); + } + } else if (x instanceof byte[]) { + switch (targetSqlType) { + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + setBytes(parameterIndex, (byte[])x); + break; + default: + throw new SQLException("Conversion not allowed", "M1M05"); + } + } else if (x instanceof java.sql.Date || + x instanceof Timestamp || + x instanceof Time || + x instanceof Calendar || + x instanceof java.util.Date) + { + switch (targetSqlType) { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + setString(parameterIndex, x.toString()); + break; + case Types.DATE: + if (x instanceof Time) { + throw new SQLException("Conversion not allowed", "M1M05"); + } else if (x instanceof java.sql.Date) { + setDate(parameterIndex, (java.sql.Date)x); + } else if (x instanceof Timestamp) { + setDate(parameterIndex, new java.sql.Date(((Timestamp)x).getTime())); + } else if (x instanceof java.util.Date) { + 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())); + } + break; + case Types.TIME: + if (x instanceof Time) { + setTime(parameterIndex, (Time)x); + } else if (x instanceof java.sql.Date) { + throw new SQLException("Conversion not allowed", "M1M05"); + } else if (x instanceof Timestamp) { + setTime(parameterIndex, new Time(((Timestamp)x).getTime())); + } else if (x instanceof java.util.Date) { + 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())); + } + break; + case Types.TIMESTAMP: + if (x instanceof Time) { + throw new SQLException("Conversion not allowed", "M1M05"); + } else if (x instanceof java.sql.Date) { + setTimestamp(parameterIndex, new Timestamp(((java.sql.Date)x).getTime())); + } else if (x instanceof Timestamp) { + setTimestamp(parameterIndex, (Timestamp)x); + } else if (x instanceof java.util.Date) { + 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())); + } + break; + default: + throw new SQLException("Conversion not allowed", "M1M05"); + } + } else if (x instanceof Array) { + setArray(parameterIndex, (Array)x); + } else if (x instanceof Blob) { + setBlob(parameterIndex, (Blob)x); + } else if (x instanceof Clob) { + setClob(parameterIndex, (Clob)x); + } else if (x instanceof Struct) { + // I have no idea how to do this... + throw newSQLFeatureNotSupportedException("setObject() with object of type Struct"); + } else if (x instanceof Ref) { + setRef(parameterIndex, (Ref)x); + } else if (x instanceof java.net.URL) { + setURL(parameterIndex, (java.net.URL)x); + } else if (x instanceof RowId) { + setRowId(parameterIndex, (RowId)x); + } else if (x instanceof NClob) { + throw newSQLFeatureNotSupportedException("setObject() with object of type NClob"); + } else if (x instanceof SQLXML) { + throw newSQLFeatureNotSupportedException("setObject() with object of type SQLXML"); + } else if (x instanceof SQLData) { // not in JDBC4.1??? + SQLData sx = (SQLData)x; + final int paramnr = parameterIndex; + final String sqltype = sx.getSQLTypeName(); + SQLOutput out = new SQLOutput() { + @Override + public void writeString(String x) throws SQLException { + // special situation, this is when a string + // representation is given, but we need to prefix it + // with the actual sqltype the server expects, or we + // will get an error back + setValue( + paramnr, + sqltype + " '" + x.replaceAll("\\\\", "\\\\\\\\").replaceAll("'", "\\\\'") + "'" + ); + } + + @Override + public void writeBoolean(boolean x) throws SQLException { + setBoolean(paramnr, x); + } + + @Override + public void writeByte(byte x) throws SQLException { + setByte(paramnr, x); + } + + @Override + public void writeShort(short x) throws SQLException { + setShort(paramnr, x); + } + + @Override + public void writeInt(int x) throws SQLException { + setInt(paramnr, x); + } + + @Override + public void writeLong(long x) throws SQLException { + setLong(paramnr, x); + } + + @Override + public void writeFloat(float x) throws SQLException { + setFloat(paramnr, x); + } + + @Override + public void writeDouble(double x) throws SQLException { + setDouble(paramnr, x); + } + + @Override + public void writeBigDecimal(BigDecimal x) throws SQLException { + setBigDecimal(paramnr, x); + } + + @Override + public void writeBytes(byte[] x) throws SQLException { + setBytes(paramnr, x); + } + + @Override + public void writeDate(java.sql.Date x) throws SQLException { + setDate(paramnr, x); + } + + @Override + public void writeTime(java.sql.Time x) throws SQLException { + setTime(paramnr, x); + } + + @Override + public void writeTimestamp(Timestamp x) throws SQLException { + setTimestamp(paramnr, x); + } + + @Override + public void writeCharacterStream(Reader x) throws SQLException { + setCharacterStream(paramnr, x); + } + + @Override + public void writeAsciiStream(InputStream x) throws SQLException { + setAsciiStream(paramnr, x); + } + + @Override + public void writeBinaryStream(InputStream x) throws SQLException { + setBinaryStream(paramnr, x); + } + + @Override + public void writeObject(SQLData x) throws SQLException { + setObject(paramnr, x); + } + + @Override + public void writeRef(Ref x) throws SQLException { + setRef(paramnr, x); + } + + @Override + public void writeBlob(Blob x) throws SQLException { + setBlob(paramnr, x); + } + + @Override + public void writeClob(Clob x) throws SQLException { + setClob(paramnr, x); + } + + @Override + public void writeStruct(Struct x) throws SQLException { + setObject(paramnr, x); + } + + @Override + public void writeArray(Array x) throws SQLException { + setArray(paramnr, x); + } + + @Override + public void writeURL(URL x) throws SQLException { + setURL(paramnr, x); + } + + @Override + public void writeNString(String x) throws SQLException { + setNString(paramnr, x); + } + + @Override + public void writeNClob(NClob x) throws SQLException { + setNClob(paramnr, x); + } + + @Override + public void writeRowId(RowId x) throws SQLException { + setRowId(paramnr, x); + } + + @Override + public void writeSQLXML(SQLXML x) throws SQLException { + setSQLXML(paramnr, x); + } + }; + sx.writeSQL(out); + } else { // java Class + throw newSQLFeatureNotSupportedException("setObject() with object of type Class"); + } + } + + /** + * Sets the designated parameter to the given REF(<structured-type>) value. + * 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 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 { + throw newSQLFeatureNotSupportedException("setRef"); + } + + /** + * Sets the designated parameter to the given java.sql.RowId object. + * 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 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 { + throw newSQLFeatureNotSupportedException("setRowId"); + } + + /** + * Sets the designated parameter to the given Java short value. The driver + * converts this to an SQL SMALLINT value when it sends it to the database. + * + * @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 setShort(int parameterIndex, short x) throws SQLException { + setValue(parameterIndex, "" + x); + } + + /** + * Sets the designated parameter to the given Java String value. The driver + * converts this to an SQL VARCHAR or LONGVARCHAR value (depending on the + * argument's size relative to the driver's limits on VARCHAR values) when + * it sends it to the database. + * + * @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 setString(int parameterIndex, String x) throws SQLException { + if (x == null) { + setNull(parameterIndex, -1); + return; + } + + setValue( + parameterIndex, + "'" + x.replaceAll("\\\\", "\\\\\\\\").replaceAll("'", "\\\\'") + "'" + ); + } + + /** + * Sets the designated parameter to the given java.sql.SQLXML + * object. The driver converts this to an SQL XML value when it + * sends it to the database. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param x a SQLXML object that maps an SQL XML value + * @throws SQLException if a database access error occurs + * @throws SQLFeatureNotSupportedException the JDBC driver does + * not support this method + */ + @Override + public void setSQLXML(int parameterIndex, SQLXML x) throws SQLException { + throw newSQLFeatureNotSupportedException("setSQLXML"); + } + + /** + * Sets the designated parameter to the given java.sql.Time value. + * 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 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); + } + + /** + * Sets the designated parameter to the given java.sql.Time value, + * using the given Calendar object. The driver uses the Calendar + * object to construct an SQL TIME value, which the driver then + * sends to the database. With a Calendar object, the driver can + * calculate the time taking into account a custom timezone. If no + * Calendar object is specified, the driver uses the default + * timezone, which is that of the virtual machine running the + * application. + * + * @param index 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 + { + if (x == null) { + setNull(index, -1); + return; + } + + boolean hasTimeZone = monetdbType[getParamIdx(index)].endsWith("tz"); + 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) + "'"); + } 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() + "'"); + } else { + mTime.setTimeZone(cal.getTimeZone()); + setValue(index, "time '" + mTime.format(x) + "'"); + } + } + } + + /** + * Sets the designated parameter to the given java.sql.Timestamp + * 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 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); + } + + /** + * Sets the designated parameter to the given java.sql.Timestamp + * value, using the given Calendar object. The driver uses the + * Calendar object to construct an SQL TIMESTAMP value, which the + * driver then sends to the database. With a Calendar object, the + * driver can calculate the timestamp taking into account a custom + * timezone. If no Calendar object is specified, the driver uses the + * default timezone, which is that of the virtual machine running + * the application. + * + * @param index 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 + { + if (x == null) { + setNull(index, -1); + return; + } + + boolean hasTimeZone = monetdbType[getParamIdx(index)].endsWith("tz"); + 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) + "'"); + } 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, "timestamp '" + x.toString() + "'"); + } else { + mTimestamp.setTimeZone(cal.getTimeZone()); + setValue(index, "timestamp '" + mTimestamp.format(x) + "'"); + } + } + } + + /** + * Sets the designated parameter to the given input stream, which will have + * the specified number of bytes. A Unicode character has two bytes, with + * the first byte being the high byte, and the second being the low byte. + * When a very large Unicode value is input to a LONGVARCHAR parameter, it + * may be more practical to send it via a java.io.InputStream object. The + * data will be read from the stream as needed until end-of-file is + * reached. The JDBC driver will do any necessary conversion from Unicode + * to the database char format. + * + * Note: This stream object can either be a standard Java stream object or + * your own subclass that implements the standard interface. + * + * @deprecated + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param x a java.io.InputStream object that contains the Unicode + * parameter value as two-byte Unicode characters + * @param length the number of bytes in the stream + * @throws SQLException if a database access error occurs + */ + @Override + @Deprecated + public void setUnicodeStream(int parameterIndex, InputStream x, int length) + throws SQLException + { + throw newSQLFeatureNotSupportedException("setUnicodeStream"); + } + + /** + * Sets the designated parameter to the given java.net.URL value. The + * driver converts this to an SQL DATALINK value when it sends it to the + * database. + * + * @param parameterIndex the first parameter is 1, the second is 2, ... + * @param x the java.net.URL object to be set + * @throws SQLException if a database access error occurs + */ + @Override + public void setURL(int parameterIndex, URL x) throws SQLException { + throw newSQLFeatureNotSupportedException("setURL"); + } + + /** + * Releases this PreparedStatement object's database and JDBC + * resources immediately instead of waiting for this to happen when + * it is automatically closed. It is generally good practice to + * release resources as soon as you are finished with them to avoid + * tying up database resources. + * + * Calling the method close on a PreparedStatement object that is + * already closed has no effect. + * + * <b>Note:</b> A PreparedStatement object is automatically closed + * when it is garbage collected. When a Statement object is closed, + * its current ResultSet object, if one exists, is also closed. + */ + @Override + public void close() { + try { + if (!closed && id != -1) + connection.sendControlCommand("release " + id); + } catch (SQLException e) { + // probably server closed connection + } + super.close(); + } + + /** + * Call close to release the server-sided handle for this + * PreparedStatement. + */ + @Override + protected void finalize() { + close(); + } + + //== end methods interface PreparedStatement + + /** + * 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 val the exact String representation to set + * @throws SQLException if the given index is out of bounds + */ + void setValue(int index, String val) throws SQLException { + values[getParamIdx(index)] = val; + } + + /** + * Transforms the prepare query into a simple SQL query by replacing + * the ?'s with the given column contents. + * Mind that the JDBC specs allow `reuse' of a value for a column over + * multiple executes. + * + * @return the simple SQL string for the prepare query + * @throws SQLException if not all columns are set + */ + private String transform() throws SQLException { + StringBuilder buf = new StringBuilder(8 + 12 * size); + buf.append("exec "); + buf.append(id); + buf.append('('); + // check if all columns are set and do a replace + int col = 0; + for (int i = 0; i < size; i++) { + if (column[i] != null) + continue; + col++; + if (col > 1) + buf.append(','); + if (values[i] == null) throw + new SQLException("Cannot execute, parameter " + col + " is missing.", "M1M05"); + + buf.append(values[i]); + } + buf.append(')'); + + return buf.toString(); + } + + /** + * Small helper method that formats the "Method ... not implemented" message + * and creates a new SQLFeatureNotSupportedException object + * whose SQLState is set to "0A000". + * + * @param name the method name + * @return a new created SQLFeatureNotSupportedException object with SQLState 0A000 + */ + private final static SQLFeatureNotSupportedException newSQLFeatureNotSupportedException(String name) { + return new SQLFeatureNotSupportedException("Method " + name + " not implemented", "0A000"); + } +}