/* * Copyright (c) 2004, 2005, 2006 TADA AB - Taby Sweden * Copyright (c) 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.sql.Date; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.util.Arrays; import java.util.Calendar; import org.postgresql.pljava.internal.Tuple; import org.postgresql.pljava.internal.TupleDesc; /** * A single row, updateable ResultSet, specially made for functions and * procedures that returns complex types or sets. * * @author Thomas Hallgren */ public class SingleRowWriter extends SingleRowResultSet { private final TupleDesc m_tupleDesc; private final Object[] m_values; private Tuple m_tuple; public SingleRowWriter(TupleDesc tupleDesc) throws SQLException { m_tupleDesc = tupleDesc; m_values = new Object[tupleDesc.size()]; } protected Object getObjectValue(int columnIndex) throws SQLException { if(columnIndex < 1) throw new SQLException("System columns cannot be obtained from this type of ResultSet"); return m_values[columnIndex - 1]; } /** * Returns <code>true</code> if the row contains any non <code>null</code> * values since all values of the row are <code>null</code> initially. */ public boolean rowUpdated() throws SQLException { int top = m_values.length; while(--top >= 0) if(m_values[top] != null) return true; return false; } public void updateObject(int columnIndex, Object x) throws SQLException { if(columnIndex < 1) throw new SQLException("System columns cannot be updated"); if(x == null) m_values[columnIndex-1] = x; Class c = m_tupleDesc.getColumnClass(columnIndex); if(!c.isInstance(x) && !(c == byte[].class && (x instanceof BlobValue))) { if(Number.class.isAssignableFrom(c)) x = SPIConnection.basicNumericCoersion(c, x); else if(Time.class.isAssignableFrom(c) || Date.class.isAssignableFrom(c) || Timestamp.class.isAssignableFrom(c)) x = SPIConnection.basicCalendricalCoersion(c, x, Calendar.getInstance()); else x = SPIConnection.basicCoersion(c, x); } m_values[columnIndex-1] = x; } public void cancelRowUpdates() throws SQLException { Arrays.fill(m_values, null); } /** * Cancels all changes but doesn't really close the set. */ public void close() throws SQLException { Arrays.fill(m_values, null); m_tuple = null; // Feel free to garbage collect... } public void copyRowFrom(ResultSet rs) throws SQLException { int top = m_values.length; for(int idx = 0; idx < top; ++idx) m_values[idx] = rs.getObject(idx+1); } /** * Creates a tuple from the current row values and then cancel all row * updates to prepare for a new row. This method is called automatically by * the trigger handler and should not be called in any other way. * * @return The native pointer of the Tuple reflecting the current row * values. * @throws SQLException */ public long getTupleAndClear() throws SQLException { // We hold on to the tuple as an instance variable so that it doesn't // get garbage collected until this result set is closed or we create // another tuple. This behavior is connected to the internal behavior // of Set Returning Functions (SRF) in the backend. // m_tuple = this.getTupleDesc().formTuple(m_values); Arrays.fill(m_values, null); return m_tuple.getNativePointer(); } protected final TupleDesc getTupleDesc() { return m_tupleDesc; } // ************************************************************ // Implementation of JDBC 4 methods. // ************************************************************ public boolean isClosed() throws SQLException { return m_tuple == null; } /** * Returns {@link ResultSet#CLOSE_CURSORS_AT_COMMIT}. Cursors * are actually closed when a function returns to SQL. */ public int getHoldability() { return ResultSet.CLOSE_CURSORS_AT_COMMIT; } // ************************************************************ // End of implementation of JDBC 4 methods. // ************************************************************ }