/* * Copyright (c) 2004, 2005, 2006 TADA AB - Taby Sweden * Copyright (c) 2007, 2010, 2011 PostgreSQL Global Development Group * * Distributed under the terms shown in the file COPYRIGHT * found in the root folder of this project or at * http://wiki.tada.se/index.php?title=PLJava_License */ package org.postgresql.pljava.jdbc; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.net.URL; import java.sql.Array; import java.sql.Blob; import java.sql.Clob; import java.sql.Date; import java.sql.NClob; import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.Ref; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.RowId; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.sql.SQLXML; import java.util.Arrays; import java.util.Calendar; import org.postgresql.pljava.internal.ExecutionPlan; import org.postgresql.pljava.internal.Oid; /** * * @author Thomas Hallgren */ public class SPIPreparedStatement extends SPIStatement implements PreparedStatement { private final Oid[] m_typeIds; private final Object[] m_values; private final int[] m_sqlTypes; private final String m_statement; private ExecutionPlan m_plan; public SPIPreparedStatement(SPIConnection conn, String statement, int paramCount) { super(conn); m_statement = statement; m_typeIds = new Oid[paramCount]; m_values = new Object[paramCount]; m_sqlTypes = new int[paramCount]; Arrays.fill(m_sqlTypes, Types.NULL); } public void close() throws SQLException { if(m_plan != null) { m_plan.close(); m_plan = null; } this.clearParameters(); super.close(); Invocation.current().forgetStatement(this); } public ResultSet executeQuery() throws SQLException { this.execute(); return this.getResultSet(); } public int executeUpdate() throws SQLException { this.execute(); return this.getUpdateCount(); } public void setNull(int columnIndex, int sqlType) throws SQLException { this.setObject(columnIndex, null, sqlType); } public void setBoolean(int columnIndex, boolean value) throws SQLException { this.setObject(columnIndex, value ? Boolean.TRUE : Boolean.FALSE, Types.BOOLEAN); } public void setByte(int columnIndex, byte value) throws SQLException { this.setObject(columnIndex, new Byte(value), Types.TINYINT); } public void setShort(int columnIndex, short value) throws SQLException { this.setObject(columnIndex, new Short(value), Types.SMALLINT); } public void setInt(int columnIndex, int value) throws SQLException { this.setObject(columnIndex, new Integer(value), Types.INTEGER); } public void setLong(int columnIndex, long value) throws SQLException { this.setObject(columnIndex, new Long(value), Types.BIGINT); } public void setFloat(int columnIndex, float value) throws SQLException { this.setObject(columnIndex, new Float(value), Types.FLOAT); } public void setDouble(int columnIndex, double value) throws SQLException { this.setObject(columnIndex, new Double(value), Types.DOUBLE); } public void setBigDecimal(int columnIndex, BigDecimal value) throws SQLException { this.setObject(columnIndex, value, Types.DECIMAL); } public void setString(int columnIndex, String value) throws SQLException { this.setObject(columnIndex, value, Types.VARCHAR); } public void setBytes(int columnIndex, byte[] value) throws SQLException { this.setObject(columnIndex, value, Types.VARBINARY); } public void setDate(int columnIndex, Date value) throws SQLException { this.setObject(columnIndex, value, Types.DATE); } public void setTime(int columnIndex, Time value) throws SQLException { this.setObject(columnIndex, value, Types.TIME); } public void setTimestamp(int columnIndex, Timestamp value) throws SQLException { this.setObject(columnIndex, value, Types.TIMESTAMP); } public void setAsciiStream(int columnIndex, InputStream value, int length) throws SQLException { try { this.setObject(columnIndex, new ClobValue(new InputStreamReader(value, "US-ASCII"), length), Types.CLOB); } catch(UnsupportedEncodingException e) { throw new SQLException("US-ASCII encoding is not supported by this JVM"); } } /** * @deprecated */ public void setUnicodeStream(int columnIndex, InputStream value, int arg2) throws SQLException { throw new UnsupportedFeatureException("PreparedStatement.setUnicodeStream"); } public void setBinaryStream(int columnIndex, InputStream value, int length) throws SQLException { this.setObject(columnIndex, new BlobValue(value, length), Types.BLOB); } public void clearParameters() throws SQLException { Arrays.fill(m_values, null); Arrays.fill(m_sqlTypes, Types.NULL); } public void setObject(int columnIndex, Object value, int sqlType, int scale) throws SQLException { this.setObject(columnIndex, value, sqlType); } public void setObject(int columnIndex, Object value, int sqlType) throws SQLException { if(columnIndex < 1 || columnIndex > m_sqlTypes.length) throw new SQLException("Illegal parameter index"); Oid id = (sqlType == Types.OTHER) ? Oid.forJavaClass(value.getClass()) : Oid.forSqlType(sqlType); // Default to String. // if(id == null) id = Oid.forSqlType(Types.VARCHAR); Oid op = m_typeIds[--columnIndex]; if(op == null) m_typeIds[columnIndex] = id; else if(!op.equals(id)) { m_typeIds[columnIndex] = id; // We must re-prepare // if(m_plan != null) m_plan.close(); m_plan = null; } m_sqlTypes[columnIndex] = sqlType; m_values[columnIndex] = value; } public void setObject(int columnIndex, Object value) throws SQLException { if(value == null) throw new SQLException("Can't assign null unless the SQL type is known"); this.setObject(columnIndex, value, SPIConnection.getTypeForClass(value.getClass())); } /** * Obtains the XOPEN SQL types for the parameters. * @return The array of types. */ private int[] getSqlTypes() { int idx = m_sqlTypes.length; int[] types = (int[])m_sqlTypes.clone(); while(--idx >= 0) { if(types[idx] == Types.NULL) types[idx] = Types.VARCHAR; // Default. } return types; } public boolean execute() throws SQLException { int[] sqlTypes = m_sqlTypes; int idx = sqlTypes.length; while(--idx >= 0) if(sqlTypes[idx] == Types.NULL) throw new SQLException("Not all parameters have been set"); if(m_plan == null) m_plan = ExecutionPlan.prepare(m_statement, m_typeIds); boolean result = this.executePlan(m_plan, m_values); this.clearParameters(); // Parameters are cleared upon successful completion. return result; } /** * The prepared statement cannot be used for executing oter statements. * @throws SQLException indicating that this feature is not supported. */ public boolean execute(String statement) throws SQLException { throw new UnsupportedFeatureException("Can't execute other statements using a prepared statement"); } public void addBatch() throws SQLException { this.internalAddBatch(new Object[]{m_values.clone(), m_sqlTypes.clone(), m_typeIds.clone()}); this.clearParameters(); // Parameters are cleared upon successful completion. } /** * The prepared statement cannot have other statements added too it. * @throws SQLException indicating that this feature is not supported. */ public void addBatch(String statement) throws SQLException { throw new UnsupportedFeatureException("Can't add batch statements to a prepared statement"); } public void setCharacterStream(int columnIndex, Reader value, int length) throws SQLException { this.setObject(columnIndex, new ClobValue(value, length), Types.CLOB); } public void setRef(int columnIndex, Ref value) throws SQLException { this.setObject(columnIndex, value, Types.REF); } public void setBlob(int columnIndex, Blob value) throws SQLException { this.setObject(columnIndex, value, Types.BLOB); } public void setClob(int columnIndex, Clob value) throws SQLException { this.setObject(columnIndex, value, Types.CLOB); } public void setArray(int columnIndex, Array value) throws SQLException { this.setObject(columnIndex, value, Types.ARRAY); } /** * ResultSetMetaData is not yet supported. * @throws SQLException indicating that this feature is not supported. */ public ResultSetMetaData getMetaData() throws SQLException { throw new UnsupportedFeatureException("ResultSet meta data is not yet implemented"); } public void setDate(int columnIndex, Date value, Calendar cal) throws SQLException { if(cal == null || cal == Calendar.getInstance()) this.setObject(columnIndex, value, Types.DATE); throw new UnsupportedFeatureException("Setting date using explicit Calendar"); } public void setTime(int columnIndex, Time value, Calendar cal) throws SQLException { if(cal == null || cal == Calendar.getInstance()) this.setObject(columnIndex, value, Types.TIME); throw new UnsupportedFeatureException("Setting time using explicit Calendar"); } public void setTimestamp(int columnIndex, Timestamp value, Calendar cal) throws SQLException { if(cal == null || cal == Calendar.getInstance()) this.setObject(columnIndex, value, Types.TIMESTAMP); throw new UnsupportedFeatureException("Setting time using explicit Calendar"); } public void setNull(int columnIndex, int sqlType, String typeName) throws SQLException { this.setNull(columnIndex, sqlType); } public void setURL(int columnIndex, URL value) throws SQLException { this.setObject(columnIndex, value, Types.DATALINK); } public String toString() { return m_statement; } /** * Due to the design of the <code>SPI_prepare</code>, it is currently impossible to * obtain the correct parameter meta data before all the parameters have been * set, hence a ParameterMetaData obtained prior to setting the paramteres * will have all parameters set to the default type {@link Types#VARCHAR}. * Once the parameters have been set, a fair attempt is made to generate this * object based on the supplied values. * @return The meta data for parameter values. */ public ParameterMetaData getParameterMetaData() throws SQLException { return new SPIParameterMetaData(this.getSqlTypes()); } protected int executeBatchEntry(Object batchEntry) throws SQLException { int ret = SUCCESS_NO_INFO; Object batchParams[] = (Object[])batchEntry; Object batchValues = batchParams[0]; Object batchSqlTypes = batchParams[1]; Object batchTypeIds[] = (Object[])batchParams[2]; System.arraycopy(batchValues, 0, m_values, 0, m_values.length); System.arraycopy(batchSqlTypes, 0, m_sqlTypes, 0, m_sqlTypes.length); // Determine if we need to replan the query because the // types have changed from the last execution. // for (int i=0; i<m_typeIds.length; i++) { if (m_typeIds[i] != batchTypeIds[i]) { // We must re-prepare // if(m_plan != null) { m_plan.close(); m_plan = null; } System.arraycopy(batchTypeIds, 0, m_typeIds, 0, m_typeIds.length); break; } } if(this.execute()) this.getResultSet().close(); else { int updCount = this.getUpdateCount(); if(updCount >= 0) ret = updCount; } return ret; } // ************************************************************ // Non-implementation of JDBC 4 methods. // ************************************************************ public void setNClob(int parameterIndex, Reader reader) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setNClob( int, Reader ) not implemented yet.", "0A000" ); } public void setNClob(int parameterIndex, NClob value) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setNClob( int, NClob ) not implemented yet.", "0A000" ); } public void setNClob(int parameterIndex, Reader reader,long length) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setNClob( int, Reader, long ) not " + "implemented yet.", "0A000" ); } public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setBlob( int, InputStream ) not " + "implemented yet.", "0A000" ); } public void setBlob(int parameterIndex, InputStream inputStream,long length) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setBlob( int, InputStream, long ) not " + "implemented yet.", "0A000" ); } public void setClob(int parameterIndex, Reader reader) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setClob( int, Reader ) not implemented yet.", "0A000" ); } public void setClob(int parameterIndex, Reader reader,long length) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setClob( int, Reader, long ) not " + "implemented yet.", "0A000" ); } public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setNCharacterStream( int, Reader ) not " + "implemented yet.", "0A000" ); } public void setNCharacterStream(int parameterIndex, Reader value,long length) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setNCharacterStream( int, Reader, long ) not " + "implemented yet.", "0A000" ); } public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setCharacterStream( int, Reader ) not " + "implemented yet.", "0A000" ); } public void setCharacterStream(int parameterIndex, Reader reader,long lenght) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setCharacterStream( int, Reader, long ) not " + "implemented yet.", "0A000" ); } public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setBinaryStream( int, InputStream ) not " + "implemented yet.", "0A000" ); } public void setBinaryStream(int parameterIndex, InputStream x,long length) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setBinaryStream( int, InputStream, long ) not " + "implemented yet.", "0A000" ); } public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setAsciiStream( int, InputStream ) not " + "implemented yet.", "0A000" ); } public void setAsciiStream(int parameterIndex, InputStream x,long length) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setAsciiStream( int, InputStream, long ) not " + "implemented yet.", "0A000" ); } public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setSQLXML( int, SQLXML ) not implemented yet.", "0A000" ); } public void setNString(int parameterIndex, String value) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setNString( int, String ) not implemented yet.", "0A000" ); } public void setRowId(int parameterIndex, RowId x) throws SQLException { throw new SQLFeatureNotSupportedException ( this.getClass() + ".setRowId( int, RowId ) not implemented yet.", "0A000" ); } }