/* Copyright (C) 2002 MySQL AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.mysql.jdbc; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * A result set that is updatable. * * @author Mark Matthews */ public class UpdatableResultSet extends ResultSet { /** Marker for 'stream' data when doing INSERT rows */ private final static byte[] STREAM_DATA_MARKER = "** STREAM DATA **" .getBytes(); /** List of primary keys */ private List primaryKeyIndicies = null; /** PreparedStatement used to delete data */ private com.mysql.jdbc.PreparedStatement deleter = null; /** PreparedStatement used to insert data */ private com.mysql.jdbc.PreparedStatement inserter = null; /** PreparedStatement used to refresh data */ private com.mysql.jdbc.PreparedStatement refresher; /** PreparedStatement used to delete data */ private com.mysql.jdbc.PreparedStatement updater = null; private SingleByteCharsetConverter charConverter; private String charEncoding; private String deleteSQL = null; private String insertSQL = null; private String quotedIdChar = null; private String refreshSQL = null; private String tableName; /** SQL for in-place modifcation */ private String updateSQL = null; /** What is the default value for the column? */ private byte[][] defaultColumnValue; /** The binary data for the 'current' row */ private byte[][] savedCurrentRow; /** Is this result set updateable? */ private boolean isUpdatable = false; /** * Creates a new UpdatableResultSet object. * * @param updateCount DOCUMENT ME! * @param updateID DOCUMENT ME! */ public UpdatableResultSet(long updateCount, long updateID) { super(updateCount, updateID); } // **************************************************************** // // END OF PUBLIC INTERFACE // // **************************************************************** /** * Create a new ResultSet - Note that we create ResultSets to represent the * results of everything. * * @param catalog the database in use when this result set was created * @param fields an array of Field objects (basically, the ResultSet * MetaData) * @param rows Vector of the actual data * @param conn the status string returned from the back end * * @throws SQLException DOCUMENT ME! */ public UpdatableResultSet(String catalog, Field[] fields, RowData rows, com.mysql.jdbc.Connection conn) throws SQLException { super(catalog, fields, rows, conn); isUpdatable = checkUpdatability(); } /** * Creates a new UpdatableResultSet object. * * @param fields DOCUMENT ME! * @param rows DOCUMENT ME! * * @throws SQLException DOCUMENT ME! */ public UpdatableResultSet(Field[] fields, RowData rows) throws SQLException { super(fields, rows); isUpdatable = checkUpdatability(); } /** * JDBC 2.0 * * <p> * Determine if the cursor is after the last row in the result set. * </p> * * @return true if after the last row, false otherwise. Returns false when * the result set contains no rows. * * @exception SQLException if a database-access error occurs. */ public synchronized boolean isAfterLast() throws SQLException { return super.isAfterLast(); } /** * JDBC 2.0 * * <p> * Determine if the cursor is before the first row in the result set. * </p> * * @return true if before the first row, false otherwise. Returns false * when the result set contains no rows. * * @exception SQLException if a database-access error occurs. */ public synchronized boolean isBeforeFirst() throws SQLException { return super.isBeforeFirst(); } /** * JDBC 2.0 Return the concurrency of this result set. The concurrency * used is determined by the statement that created the result set. * * @return the concurrency type, CONCUR_READ_ONLY, etc. * * @exception SQLException if a database-access error occurs */ public int getConcurrency() throws SQLException { return (isUpdatable ? CONCUR_UPDATABLE : CONCUR_READ_ONLY); } /** * JDBC 2.0 * * <p> * Determine if the cursor is on the first row of the result set. * </p> * * @return true if on the first row, false otherwise. * * @exception SQLException if a database-access error occurs. */ public synchronized boolean isFirst() throws SQLException { return super.isFirst(); } /** * JDBC 2.0 * * <p> * Determine if the cursor is on the last row of the result set. Note: * Calling isLast() may be expensive since the JDBC driver might need to * fetch ahead one row in order to determine whether the current row is * the last row in the result set. * </p> * * @return true if on the last row, false otherwise. * * @exception SQLException if a database-access error occurs. */ public synchronized boolean isLast() throws SQLException { return super.isLast(); } /** * JDBC 2.0 * * <p> * Move to an absolute row number in the result set. * </p> * * <p> * If row is positive, moves to an absolute row with respect to the * beginning of the result set. The first row is row 1, the second is row * 2, etc. * </p> * * <p> * If row is negative, moves to an absolute row position with respect to * the end of result set. For example, calling absolute(-1) positions the * cursor on the last row, absolute(-2) indicates the next-to-last row, * etc. * </p> * * <p> * An attempt to position the cursor beyond the first/last row in the * result set, leaves the cursor before/after the first/last row, * respectively. * </p> * * <p> * Note: Calling absolute(1) is the same as calling first(). Calling * absolute(-1) is the same as calling last(). * </p> * * @param row DOCUMENT ME! * * @return true if on the result set, false if off. * * @exception SQLException if a database-access error occurs, or row is 0, * or result set type is TYPE_FORWARD_ONLY. */ public synchronized boolean absolute(int row) throws SQLException { return super.absolute(row); } /** * JDBC 2.0 * * <p> * Moves to the end of the result set, just after the last row. Has no * effect if the result set contains no rows. * </p> * * @exception SQLException if a database-access error occurs, or result set * type is TYPE_FORWARD_ONLY. */ public synchronized void afterLast() throws SQLException { super.afterLast(); } /** * JDBC 2.0 * * <p> * Moves to the front of the result set, just before the first row. Has no * effect if the result set contains no rows. * </p> * * @exception SQLException if a database-access error occurs, or result set * type is TYPE_FORWARD_ONLY */ public synchronized void beforeFirst() throws SQLException { super.beforeFirst(); } /** * JDBC 2.0 The cancelRowUpdates() method may be called after calling an * updateXXX() method(s) and before calling updateRow() to rollback the * updates made to a row. If no updates have been made or updateRow() * has already been called, then this method has no effect. * * @exception SQLException if a database-access error occurs, or if called * when on the insert row. */ public synchronized void cancelRowUpdates() throws SQLException { if (doingUpdates) { doingUpdates = false; updater.clearParameters(); } } /** * After this call, getWarnings returns null until a new warning is * reported for this ResultSet * * @exception java.sql.SQLException if a database access error occurs */ public synchronized void clearWarnings() throws java.sql.SQLException { warningChain = null; } /** * In some cases, it is desirable to immediately release a ResultSet * database and JDBC resources instead of waiting for this to happen when * it is automatically closed. The close method provides this immediate * release. * * <p> * <B>Note:</B> A ResultSet is automatically closed by the Statement the * Statement that generated it when that Statement is closed, re-executed, * or is used to retrieve the next result from a sequence of multiple * results. A ResultSet is also automatically closed when it is garbage * collected. * </p> * * @exception java.sql.SQLException if a database access error occurs */ public synchronized void close() throws java.sql.SQLException { super.close(); } /** * JDBC 2.0 Delete the current row from the result set and the underlying * database. Cannot be called when on the insert row. * * @exception SQLException if a database-access error occurs, or if called * when on the insert row. * @throws SQLException if the ResultSet is not updatable or some other * error occurs */ public synchronized void deleteRow() throws SQLException { if (!isUpdatable) { throw new NotUpdatable(); } if (onInsertRow) { throw new SQLException( "Can not call deleteRow() when on insert row"); } else if (rowData.size() == 0) { throw new SQLException("Can't deleteRow() on empty result set"); } else if (isBeforeFirst()) { throw new SQLException( "Before start of result set. Can not call deleteRow()."); } else if (isAfterLast()) { throw new SQLException( "After end of result set. Can not call deleteRow()."); } if (deleter == null) { if (deleteSQL == null) { generateStatements(); } deleter = (com.mysql.jdbc.PreparedStatement) connection .prepareStatement(deleteSQL); if (deleter.getMaxRows() != 0) { deleter.setMaxRows(0); } } deleter.clearParameters(); String characterEncoding = null; if (connection.useUnicode()) { characterEncoding = connection.getEncoding(); } // // FIXME: Use internal routines where possible for character // conversion! try { int numKeys = primaryKeyIndicies.size(); if (numKeys == 1) { int index = ((Integer) primaryKeyIndicies.get(0)).intValue(); String currentVal = ((characterEncoding == null) ? new String(thisRow[index]) : new String(thisRow[index], characterEncoding)); deleter.setString(1, currentVal); } else { for (int i = 0; i < numKeys; i++) { int index = ((Integer) primaryKeyIndicies.get(i)).intValue(); String currentVal = ((characterEncoding == null) ? new String(thisRow[index]) : new String(thisRow[index], characterEncoding)); deleter.setString(i + 1, currentVal); } } deleter.executeUpdate(); rowData.removeRow(rowData.getCurrentRowNumber()); } catch (java.io.UnsupportedEncodingException encodingEx) { throw new SQLException("Unsupported character encoding '" + connection.getEncoding() + "'"); } } /** * JDBC 2.0 * * <p> * Moves to the first row in the result set. * </p> * * @return true if on a valid row, false if no rows in the result set. * * @exception SQLException if a database-access error occurs, or result set * type is TYPE_FORWARD_ONLY. */ public synchronized boolean first() throws SQLException { return super.first(); } /** * JDBC 2.0 Insert the contents of the insert row into the result set and * the database. Must be on the insert row when this method is called. * * @exception SQLException if a database-access error occurs, if called * when not on the insert row, or if all non-nullable columns * in the insert row have not been given a value */ public synchronized void insertRow() throws SQLException { if (!onInsertRow) { throw new SQLException("Not on insert row"); } else { inserter.executeUpdate(); int numPrimaryKeys = 0; if (primaryKeyIndicies != null) { numPrimaryKeys = primaryKeyIndicies.size(); } long autoIncrementId = inserter.getLastInsertID(); int numFields = fields.length; byte[][] newRow = new byte[numFields][]; for (int i = 0; i < numFields; i++) { if (inserter.isNull(i)) { newRow[i] = null; } else { newRow[i] = inserter.getBytes(i); } if ((numPrimaryKeys == 1) && fields[i].isPrimaryKey() && (autoIncrementId > 0)) { newRow[i] = String.valueOf(autoIncrementId).getBytes(); } } rowData.addRow(newRow); resetInserter(); } } /** * JDBC 2.0 * * <p> * Moves to the last row in the result set. * </p> * * @return true if on a valid row, false if no rows in the result set. * * @exception SQLException if a database-access error occurs, or result set * type is TYPE_FORWARD_ONLY. */ public synchronized boolean last() throws SQLException { return super.last(); } /** * JDBC 2.0 Move the cursor to the remembered cursor position, usually the * current row. Has no effect unless the cursor is on the insert row. * * @exception SQLException if a database-access error occurs, or the result * set is not updatable * @throws SQLException if the ResultSet is not updatable or some other * error occurs */ public synchronized void moveToCurrentRow() throws SQLException { if (!isUpdatable) { throw new NotUpdatable(); } if (this.onInsertRow) { onInsertRow = false; this.thisRow = this.savedCurrentRow; } } /** * JDBC 2.0 Move to the insert row. The current cursor position is * remembered while the cursor is positioned on the insert row. The insert * row is a special row associated with an updatable result set. It is * essentially a buffer where a new row may be constructed by calling the * updateXXX() methods prior to inserting the row into the result set. * Only the updateXXX(), getXXX(), and insertRow() methods may be called * when the cursor is on the insert row. All of the columns in a result * set must be given a value each time this method is called before * calling insertRow(). UpdateXXX()must be called before getXXX() on a * column. * * @exception SQLException if a database-access error occurs, or the result * set is not updatable * @throws NotUpdatable DOCUMENT ME! */ public synchronized void moveToInsertRow() throws SQLException { if (!this.isUpdatable) { throw new NotUpdatable(); } if (this.inserter == null) { generateStatements(); this.inserter = (com.mysql.jdbc.PreparedStatement) connection .prepareStatement(this.insertSQL); if (this.inserter.getMaxRows() != 0) { this.inserter.setMaxRows(0); } extractDefaultValues(); resetInserter(); } else { resetInserter(); } int numFields = this.fields.length; this.onInsertRow = true; this.doingUpdates = false; this.savedCurrentRow = this.thisRow; this.thisRow = new byte[numFields][]; for (int i = 0; i < numFields; i++) { if (this.defaultColumnValue[i] != null) { this.inserter.setBytes(i + 1, this.defaultColumnValue[i]); // This value _could_ be changed from a getBytes(), so we // need a copy.... byte[] defaultValueCopy = new byte[this.defaultColumnValue[i].length]; System.arraycopy(defaultColumnValue[i], 0, defaultValueCopy, 0, defaultValueCopy.length); this.thisRow[i] = defaultValueCopy; } else { this.inserter.setNull(i + 1, java.sql.Types.NULL); this.thisRow[i] = null; } } } /** * A ResultSet is initially positioned before its first row, the first call * to next makes the first row the current row; the second call makes the * second row the current row, etc. * * <p> * If an input stream from the previous row is open, it is implicitly * closed. The ResultSet's warning chain is cleared when a new row is * read * </p> * * @return true if the new current is valid; false if there are no more * rows * * @exception java.sql.SQLException if a database access error occurs */ public synchronized boolean next() throws java.sql.SQLException { return super.next(); } /** * The prev method is not part of JDBC, but because of the architecture of * this driver it is possible to move both forward and backward within the * result set. * * <p> * If an input stream from the previous row is open, it is implicitly * closed. The ResultSet's warning chain is cleared when a new row is * read * </p> * * @return true if the new current is valid; false if there are no more * rows * * @exception java.sql.SQLException if a database access error occurs */ public synchronized boolean prev() throws java.sql.SQLException { return super.prev(); } /** * JDBC 2.0 * * <p> * Moves to the previous row in the result set. * </p> * * <p> * Note: previous() is not the same as relative(-1) since it makes sense to * call previous() when there is no current row. * </p> * * @return true if on a valid row, false if off the result set. * * @exception SQLException if a database-access error occurs, or result set * type is TYPE_FORWAR_DONLY. */ public synchronized boolean previous() throws SQLException { return super.previous(); } /** * JDBC 2.0 Refresh the value of the current row with its current value in * the database. Cannot be called when on the insert row. The * refreshRow() method provides a way for an application to explicitly * tell the JDBC driver to refetch a row(s) from the database. An * application may want to call refreshRow() when caching or prefetching * is being done by the JDBC driver to fetch the latest value of a row * from the database. The JDBC driver may actually refresh multiple rows * at once if the fetch size is greater than one. All values are * refetched subject to the transaction isolation level and cursor * sensitivity. If refreshRow() is called after calling updateXXX(), but * before calling updateRow() then the updates made to the row are lost. * Calling refreshRow() frequently will likely slow performance. * * @exception SQLException if a database-access error occurs, or if called * when on the insert row. * @throws NotUpdatable DOCUMENT ME! */ public synchronized void refreshRow() throws SQLException { if (!isUpdatable) { throw new NotUpdatable(); } if (onInsertRow) { throw new SQLException( "Can not call refreshRow() when on insert row"); } else if (rowData.size() == 0) { throw new SQLException("Can't refreshRow() on empty result set"); } else if (isBeforeFirst()) { throw new SQLException( "Before start of result set. Can not call refreshRow()."); } else if (isAfterLast()) { throw new SQLException( "After end of result set. Can not call refreshRow()."); } if (refresher == null) { if (refreshSQL == null) { generateStatements(); } refresher = (com.mysql.jdbc.PreparedStatement) connection .prepareStatement(refreshSQL); if (refresher.getMaxRows() != 0) { refresher.setMaxRows(0); } } refresher.clearParameters(); int numKeys = primaryKeyIndicies.size(); if (numKeys == 1) { byte[] dataFrom = null; int index = ((Integer) primaryKeyIndicies.get(0)).intValue(); if (!doingUpdates) { dataFrom = thisRow[index]; } else { dataFrom = updater.getBytes(index); // Primary keys not set? if (updater.isNull(index) || (dataFrom.length == 0)) { dataFrom = thisRow[index]; } } refresher.setBytesNoEscape(1, dataFrom); } else { for (int i = 0; i < numKeys; i++) { byte[] dataFrom = null; int index = ((Integer) primaryKeyIndicies.get(i)).intValue(); if (!doingUpdates) { dataFrom = thisRow[index]; } else { dataFrom = updater.getBytes(index); // Primary keys not set? if (updater.isNull(index) || (dataFrom.length == 0)) { dataFrom = thisRow[index]; } } refresher.setBytesNoEscape(i + 1, dataFrom); } } java.sql.ResultSet rs = null; try { rs = refresher.executeQuery(); int numCols = rs.getMetaData().getColumnCount(); if (rs.next()) { for (int i = 0; i < numCols; i++) { byte[] val = rs.getBytes(i + 1); if ((val == null) || rs.wasNull()) { thisRow[i] = null; } else { thisRow[i] = rs.getBytes(i + 1); } } } else { throw new SQLException("refreshRow() called on row that has been deleted or had primary key changed", SQLError.SQL_STATE_GENERAL_ERROR); } } finally { if (rs != null) { try { rs.close(); } catch (SQLException ex) { ; // ignore } } } } /** * JDBC 2.0 * * <p> * Moves a relative number of rows, either positive or negative. Attempting * to move beyond the first/last row in the result set positions the * cursor before/after the the first/last row. Calling relative(0) is * valid, but does not change the cursor position. * </p> * * <p> * Note: Calling relative(1) is different than calling next() since is * makes sense to call next() when there is no current row, for example, * when the cursor is positioned before the first row or after the last * row of the result set. * </p> * * @param rows DOCUMENT ME! * * @return true if on a row, false otherwise. * * @exception SQLException if a database-access error occurs, or there is * no current row, or result set type is TYPE_FORWARD_ONLY. */ public synchronized boolean relative(int rows) throws SQLException { return super.relative(rows); } /** * JDBC 2.0 Determine if this row has been deleted. A deleted row may * leave a visible "hole" in a result set. This method can be used to * detect holes in a result set. The value returned depends on whether or * not the result set can detect deletions. * * @return true if deleted and deletes are detected * * @exception SQLException if a database-access error occurs * @throws NotImplemented DOCUMENT ME! * * @see DatabaseMetaData#deletesAreDetected */ public synchronized boolean rowDeleted() throws SQLException { throw new NotImplemented(); } /** * JDBC 2.0 Determine if the current row has been inserted. The value * returned depends on whether or not the result set can detect visible * inserts. * * @return true if inserted and inserts are detected * * @exception SQLException if a database-access error occurs * @throws NotImplemented DOCUMENT ME! * * @see DatabaseMetaData#insertsAreDetected */ public synchronized boolean rowInserted() throws SQLException { throw new NotImplemented(); } //--------------------------------------------------------------------- // Updates //--------------------------------------------------------------------- /** * JDBC 2.0 Determine if the current row has been updated. The value * returned depends on whether or not the result set can detect updates. * * @return true if the row has been visibly updated by the owner or * another, and updates are detected * * @exception SQLException if a database-access error occurs * @throws NotImplemented DOCUMENT ME! * * @see DatabaseMetaData#updatesAreDetected */ public synchronized boolean rowUpdated() throws SQLException { throw new NotImplemented(); } /** * JDBC 2.0 Update a column with an ascii stream value. The updateXXX() * methods are used to update column values in the current row, or the * insert row. The updateXXX() methods do not update the underlying * database, instead the updateRow() or insertRow() methods are called to * update the database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * @param length the length of the stream * * @exception SQLException if a database-access error occurs */ public synchronized void updateAsciiStream(int columnIndex, java.io.InputStream x, int length) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setAsciiStream(columnIndex, x, length); } else { inserter.setAsciiStream(columnIndex, x, length); this.thisRow[columnIndex - 1] = STREAM_DATA_MARKER; } } /** * JDBC 2.0 Update a column with an ascii stream value. The updateXXX() * methods are used to update column values in the current row, or the * insert row. The updateXXX() methods do not update the underlying * database, instead the updateRow() or insertRow() methods are called to * update the database. * * @param columnName the name of the column * @param x the new column value * @param length of the stream * * @exception SQLException if a database-access error occurs */ public synchronized void updateAsciiStream(String columnName, java.io.InputStream x, int length) throws SQLException { updateAsciiStream(findColumn(columnName), x, length); } /** * JDBC 2.0 Update a column with a BigDecimal value. The updateXXX() * methods are used to update column values in the current row, or the * insert row. The updateXXX() methods do not update the underlying * database, instead the updateRow() or insertRow() methods are called to * update the database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setBigDecimal(columnIndex, x); } else { inserter.setBigDecimal(columnIndex, x); if (x == null) { this.thisRow[columnIndex - 1] = null; } else { this.thisRow[columnIndex - 1] = x.toString().getBytes(); } } } /** * JDBC 2.0 Update a column with a BigDecimal value. The updateXXX() * methods are used to update column values in the current row, or the * insert row. The updateXXX() methods do not update the underlying * database, instead the updateRow() or insertRow() methods are called to * update the database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateBigDecimal(String columnName, BigDecimal x) throws SQLException { updateBigDecimal(findColumn(columnName), x); } /** * JDBC 2.0 Update a column with a binary stream value. The updateXXX() * methods are used to update column values in the current row, or the * insert row. The updateXXX() methods do not update the underlying * database, instead the updateRow() or insertRow() methods are called to * update the database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * @param length the length of the stream * * @exception SQLException if a database-access error occurs */ public synchronized void updateBinaryStream(int columnIndex, java.io.InputStream x, int length) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setBinaryStream(columnIndex, x, length); } else { inserter.setBinaryStream(columnIndex, x, length); if (x == null) { this.thisRow[columnIndex - 1] = null; } else { this.thisRow[columnIndex - 1] = STREAM_DATA_MARKER; } } } /** * JDBC 2.0 Update a column with a binary stream value. The updateXXX() * methods are used to update column values in the current row, or the * insert row. The updateXXX() methods do not update the underlying * database, instead the updateRow() or insertRow() methods are called to * update the database. * * @param columnName the name of the column * @param x the new column value * @param length of the stream * * @exception SQLException if a database-access error occurs */ public synchronized void updateBinaryStream(String columnName, java.io.InputStream x, int length) throws SQLException { updateBinaryStream(findColumn(columnName), x, length); } /** * @see ResultSet#updateBlob(int, Blob) */ public void updateBlob(int columnIndex, java.sql.Blob blob) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setBlob(columnIndex, blob); } else { inserter.setBlob(columnIndex, blob); if (blob == null) { this.thisRow[columnIndex - 1] = null; } else { this.thisRow[columnIndex - 1] = STREAM_DATA_MARKER; } } } /** * @see ResultSet#updateBlob(String, Blob) */ public void updateBlob(String columnName, java.sql.Blob blob) throws SQLException { updateBlob(findColumn(columnName), blob); } /** * JDBC 2.0 Update a column with a boolean value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateBoolean(int columnIndex, boolean x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setBoolean(columnIndex, x); } else { inserter.setBoolean(columnIndex, x); this.thisRow[columnIndex - 1] = inserter.getBytes(1); } } /** * JDBC 2.0 Update a column with a boolean value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateBoolean(String columnName, boolean x) throws SQLException { updateBoolean(findColumn(columnName), x); } /** * JDBC 2.0 Update a column with a byte value. The updateXXX() methods are * used to update column values in the current row, or the insert row. The * updateXXX() methods do not update the underlying database, instead the * updateRow() or insertRow() methods are called to update the database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateByte(int columnIndex, byte x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setByte(columnIndex, x); } else { inserter.setByte(columnIndex, x); this.thisRow[columnIndex - 1] = inserter.getBytes(columnIndex); } } /** * JDBC 2.0 Update a column with a byte value. The updateXXX() methods are * used to update column values in the current row, or the insert row. The * updateXXX() methods do not update the underlying database, instead the * updateRow() or insertRow() methods are called to update the database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateByte(String columnName, byte x) throws SQLException { updateByte(findColumn(columnName), x); } /** * JDBC 2.0 Update a column with a byte array value. The updateXXX() * methods are used to update column values in the current row, or the * insert row. The updateXXX() methods do not update the underlying * database, instead the updateRow() or insertRow() methods are called to * update the database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateBytes(int columnIndex, byte[] x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setBytes(columnIndex, x); } else { inserter.setBytes(columnIndex, x); this.thisRow[columnIndex - 1] = x; } } /** * JDBC 2.0 Update a column with a byte array value. The updateXXX() * methods are used to update column values in the current row, or the * insert row. The updateXXX() methods do not update the underlying * database, instead the updateRow() or insertRow() methods are called to * update the database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateBytes(String columnName, byte[] x) throws SQLException { updateBytes(findColumn(columnName), x); } /** * JDBC 2.0 Update a column with a character stream value. The updateXXX() * methods are used to update column values in the current row, or the * insert row. The updateXXX() methods do not update the underlying * database, instead the updateRow() or insertRow() methods are called to * update the database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * @param length the length of the stream * * @exception SQLException if a database-access error occurs */ public synchronized void updateCharacterStream(int columnIndex, java.io.Reader x, int length) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setCharacterStream(columnIndex, x, length); } else { inserter.setCharacterStream(columnIndex, x, length); if (x == null) { this.thisRow[columnIndex - 1] = null; } else { this.thisRow[columnIndex - 1] = STREAM_DATA_MARKER; } } } /** * JDBC 2.0 Update a column with a character stream value. The updateXXX() * methods are used to update column values in the current row, or the * insert row. The updateXXX() methods do not update the underlying * database, instead the updateRow() or insertRow() methods are called to * update the database. * * @param columnName the name of the column * @param reader the new column value * @param length of the stream * * @exception SQLException if a database-access error occurs */ public synchronized void updateCharacterStream(String columnName, java.io.Reader reader, int length) throws SQLException { updateCharacterStream(findColumn(columnName), reader, length); } /** * @see ResultSet#updateClob(int, Clob) */ public void updateClob(int columnIndex, java.sql.Clob clob) throws SQLException { if (clob == null) { updateNull(columnIndex); } else { updateCharacterStream(columnIndex, clob.getCharacterStream(), (int) clob.length()); } } /** * JDBC 2.0 Update a column with a Date value. The updateXXX() methods are * used to update column values in the current row, or the insert row. The * updateXXX() methods do not update the underlying database, instead the * updateRow() or insertRow() methods are called to update the database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateDate(int columnIndex, java.sql.Date x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setDate(columnIndex, x); } else { inserter.setDate(columnIndex, x); this.thisRow[columnIndex - 1] = this.inserter.getBytes(columnIndex - 1); } } /** * JDBC 2.0 Update a column with a Date value. The updateXXX() methods are * used to update column values in the current row, or the insert row. The * updateXXX() methods do not update the underlying database, instead the * updateRow() or insertRow() methods are called to update the database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateDate(String columnName, java.sql.Date x) throws SQLException { updateDate(findColumn(columnName), x); } /** * JDBC 2.0 Update a column with a Double value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateDouble(int columnIndex, double x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setDouble(columnIndex, x); } else { inserter.setDouble(columnIndex, x); this.thisRow[columnIndex - 1] = this.inserter.getBytes(columnIndex - 1); } } /** * JDBC 2.0 Update a column with a double value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateDouble(String columnName, double x) throws SQLException { updateDouble(findColumn(columnName), x); } /** * JDBC 2.0 Update a column with a float value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateFloat(int columnIndex, float x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setFloat(columnIndex, x); } else { inserter.setFloat(columnIndex, x); this.thisRow[columnIndex - 1] = this.inserter.getBytes(columnIndex - 1); } } /** * JDBC 2.0 Update a column with a float value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateFloat(String columnName, float x) throws SQLException { updateFloat(findColumn(columnName), x); } /** * JDBC 2.0 Update a column with an integer value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateInt(int columnIndex, int x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setInt(columnIndex, x); } else { inserter.setInt(columnIndex, x); this.thisRow[columnIndex - 1] = this.inserter.getBytes(columnIndex - 1); } } /** * JDBC 2.0 Update a column with an integer value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateInt(String columnName, int x) throws SQLException { updateInt(findColumn(columnName), x); } /** * JDBC 2.0 Update a column with a long value. The updateXXX() methods are * used to update column values in the current row, or the insert row. The * updateXXX() methods do not update the underlying database, instead the * updateRow() or insertRow() methods are called to update the database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateLong(int columnIndex, long x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setLong(columnIndex, x); } else { inserter.setLong(columnIndex, x); this.thisRow[columnIndex - 1] = this.inserter.getBytes(columnIndex - 1); } } /** * JDBC 2.0 Update a column with a long value. The updateXXX() methods are * used to update column values in the current row, or the insert row. The * updateXXX() methods do not update the underlying database, instead the * updateRow() or insertRow() methods are called to update the database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateLong(String columnName, long x) throws SQLException { updateLong(findColumn(columnName), x); } /** * JDBC 2.0 Give a nullable column a null value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnIndex the first column is 1, the second is 2, ... * * @exception SQLException if a database-access error occurs */ public synchronized void updateNull(int columnIndex) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setNull(columnIndex, 0); } else { inserter.setNull(columnIndex, 0); this.thisRow[columnIndex - 1] = null; } } /** * JDBC 2.0 Update a column with a null value. The updateXXX() methods are * used to update column values in the current row, or the insert row. The * updateXXX() methods do not update the underlying database, instead the * updateRow() or insertRow() methods are called to update the database. * * @param columnName the name of the column * * @exception SQLException if a database-access error occurs */ public synchronized void updateNull(String columnName) throws SQLException { updateNull(findColumn(columnName)); } /** * JDBC 2.0 Update a column with an Object value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * @param scale For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types * this is the number of digits after the decimal. For all other * types this value will be ignored. * * @exception SQLException if a database-access error occurs */ public synchronized void updateObject(int columnIndex, Object x, int scale) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setObject(columnIndex, x); } else { inserter.setObject(columnIndex, x); this.thisRow[columnIndex - 1] = this.inserter.getBytes(columnIndex - 1); } } /** * JDBC 2.0 Update a column with an Object value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateObject(int columnIndex, Object x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setObject(columnIndex, x); } else { inserter.setObject(columnIndex, x); this.thisRow[columnIndex - 1] = this.inserter.getBytes(columnIndex - 1); } } /** * JDBC 2.0 Update a column with an Object value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnName the name of the column * @param x the new column value * @param scale For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types * this is the number of digits after the decimal. For all other * types this value will be ignored. * * @exception SQLException if a database-access error occurs */ public synchronized void updateObject(String columnName, Object x, int scale) throws SQLException { updateObject(findColumn(columnName), x); } /** * JDBC 2.0 Update a column with an Object value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateObject(String columnName, Object x) throws SQLException { updateObject(findColumn(columnName), x); } /** * JDBC 2.0 Update the underlying database with the new contents of the * current row. Cannot be called when on the insert row. * * @exception SQLException if a database-access error occurs, or if called * when on the insert row * @throws NotUpdatable DOCUMENT ME! */ public synchronized void updateRow() throws SQLException { if (!isUpdatable) { throw new NotUpdatable(); } if (doingUpdates) { updater.executeUpdate(); refreshRow(); doingUpdates = false; } // // fixes calling updateRow() and then doing more // updates on same row... syncUpdate(); } /** * JDBC 2.0 Update a column with a short value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateShort(int columnIndex, short x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setShort(columnIndex, x); } else { inserter.setShort(columnIndex, x); this.thisRow[columnIndex - 1] = this.inserter.getBytes(columnIndex - 1); } } /** * JDBC 2.0 Update a column with a short value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateShort(String columnName, short x) throws SQLException { updateShort(findColumn(columnName), x); } /** * JDBC 2.0 Update a column with a String value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateString(int columnIndex, String x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setString(columnIndex, x); } else { inserter.setString(columnIndex, x); if (x == null) { this.thisRow[columnIndex - 1] = null; } else { if (getCharConverter() != null) { try { this.thisRow[columnIndex - 1] = StringUtils.getBytes(x, this.charConverter, this.charEncoding); } catch (UnsupportedEncodingException uEE) { throw new SQLException( "Unsupported character encoding '" + this.charEncoding + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } } else { this.thisRow[columnIndex - 1] = x.getBytes(); } } } } /** * JDBC 2.0 Update a column with a String value. The updateXXX() methods * are used to update column values in the current row, or the insert row. * The updateXXX() methods do not update the underlying database, instead * the updateRow() or insertRow() methods are called to update the * database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateString(String columnName, String x) throws SQLException { updateString(findColumn(columnName), x); } /** * JDBC 2.0 Update a column with a Time value. The updateXXX() methods are * used to update column values in the current row, or the insert row. The * updateXXX() methods do not update the underlying database, instead the * updateRow() or insertRow() methods are called to update the database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateTime(int columnIndex, java.sql.Time x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setTime(columnIndex, x); } else { inserter.setTime(columnIndex, x); this.thisRow[columnIndex - 1] = this.inserter.getBytes(columnIndex - 1); } } /** * JDBC 2.0 Update a column with a Time value. The updateXXX() methods are * used to update column values in the current row, or the insert row. The * updateXXX() methods do not update the underlying database, instead the * updateRow() or insertRow() methods are called to update the database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateTime(String columnName, java.sql.Time x) throws SQLException { updateTime(findColumn(columnName), x); } /** * JDBC 2.0 Update a column with a Timestamp value. The updateXXX() * methods are used to update column values in the current row, or the * insert row. The updateXXX() methods do not update the underlying * database, instead the updateRow() or insertRow() methods are called to * update the database. * * @param columnIndex the first column is 1, the second is 2, ... * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateTimestamp(int columnIndex, java.sql.Timestamp x) throws SQLException { if (!onInsertRow) { if (!doingUpdates) { doingUpdates = true; syncUpdate(); } updater.setTimestamp(columnIndex, x); } else { inserter.setTimestamp(columnIndex, x); this.thisRow[columnIndex - 1] = this.inserter.getBytes(columnIndex - 1); } } /** * JDBC 2.0 Update a column with a Timestamp value. The updateXXX() * methods are used to update column values in the current row, or the * insert row. The updateXXX() methods do not update the underlying * database, instead the updateRow() or insertRow() methods are called to * update the database. * * @param columnName the name of the column * @param x the new column value * * @exception SQLException if a database-access error occurs */ public synchronized void updateTimestamp(String columnName, java.sql.Timestamp x) throws SQLException { updateTimestamp(findColumn(columnName), x); } /** * Sets the concurrency type of this result set * * @param concurrencyFlag the type of concurrency that this ResultSet * should support. */ protected void setResultSetConcurrency(int concurrencyFlag) { super.setResultSetConcurrency(concurrencyFlag); // // FIXME: Issue warning when asked for updateable result set, but result set is not // updatable // //if ((concurrencyFlag == CONCUR_UPDATABLE) && !isUpdatable()) { //java.sql.SQLWarning warning = new java.sql.SQLWarning( //NotUpdatable.NOT_UPDATEABLE_MESSAGE); //} } protected void checkRowPos() throws SQLException { // don't use RowData's idea of // row bounds when we're doing // inserts... if (!this.onInsertRow) { super.checkRowPos(); } } /** * Figure out whether or not this ResultSet is updateable, and if so, * generate the PreparedStatements to support updates. * * @throws SQLException DOCUMENT ME! * @throws NotUpdatable DOCUMENT ME! */ protected void generateStatements() throws SQLException { if (!isUpdatable) { this.doingUpdates = false; this.onInsertRow = false; throw new NotUpdatable(); } String quotedId = getQuotedIdChar(); this.tableName = null; if (fields[0].getOriginalTableName() != null) { StringBuffer tableNameBuffer = new StringBuffer(); String databaseName = fields[0].getDatabaseName(); if ((databaseName != null) && (databaseName.length() > 0)) { tableNameBuffer.append(quotedId); tableNameBuffer.append(databaseName); tableNameBuffer.append(quotedId); tableNameBuffer.append('.'); } tableNameBuffer.append(quotedId); tableNameBuffer.append(fields[0].getOriginalTableName()); tableNameBuffer.append(quotedId); this.tableName = tableNameBuffer.toString(); } else { StringBuffer tableNameBuffer = new StringBuffer(); tableNameBuffer.append(quotedId); tableNameBuffer.append(fields[0].getTableName()); tableNameBuffer.append(quotedId); this.tableName = tableNameBuffer.toString(); } primaryKeyIndicies = new ArrayList(); StringBuffer fieldValues = new StringBuffer(); StringBuffer keyValues = new StringBuffer(); StringBuffer columnNames = new StringBuffer(); StringBuffer insertPlaceHolders = new StringBuffer(); boolean firstTime = true; boolean keysFirstTime = true; for (int i = 0; i < fields.length; i++) { String originalColumnName = fields[i].getOriginalName(); String columnName = null; if (this.connection.getIO().hasLongColumnInfo() && (originalColumnName != null) && (originalColumnName.length() > 0)) { columnName = originalColumnName; } else { columnName = fields[i].getName(); } if (fields[i].isPrimaryKey()) { primaryKeyIndicies.add(new Integer(i)); if (!keysFirstTime) { keyValues.append(" AND "); } else { keysFirstTime = false; } keyValues.append(quotedId); keyValues.append(columnName); keyValues.append(quotedId); keyValues.append("=?"); } if (firstTime) { firstTime = false; fieldValues.append("SET "); } else { fieldValues.append(","); columnNames.append(","); insertPlaceHolders.append(","); } insertPlaceHolders.append("?"); columnNames.append(quotedId); columnNames.append(columnName); columnNames.append(quotedId); fieldValues.append(quotedId); fieldValues.append(columnName); fieldValues.append(quotedId); fieldValues.append("=?"); } updateSQL = "UPDATE " + this.tableName + " " + fieldValues.toString() + " WHERE " + keyValues.toString(); insertSQL = "INSERT INTO " + this.tableName + " (" + columnNames.toString() + ") VALUES (" + insertPlaceHolders.toString() + ")"; refreshSQL = "SELECT " + columnNames.toString() + " FROM " + tableName + " WHERE " + keyValues.toString(); deleteSQL = "DELETE FROM " + this.tableName + " WHERE " + keyValues.toString(); } boolean isUpdatable() { return this.isUpdatable; } /** * Reset UPDATE prepared statement to value in current row. This_Row MUST * point to current, valid row. * * @throws SQLException DOCUMENT ME! */ void syncUpdate() throws SQLException { if (updater == null) { if (updateSQL == null) { generateStatements(); } updater = (com.mysql.jdbc.PreparedStatement) connection .prepareStatement(updateSQL); if (updater.getMaxRows() != 0) { updater.setMaxRows(0); } } int numFields = fields.length; updater.clearParameters(); for (int i = 0; i < numFields; i++) { if (thisRow[i] != null) { updater.setBytes(i + 1, thisRow[i]); } else { updater.setNull(i + 1, 0); } } int numKeys = primaryKeyIndicies.size(); if (numKeys == 1) { int index = ((Integer) primaryKeyIndicies.get(0)).intValue(); byte[] keyData = thisRow[index]; updater.setBytes(numFields + 1, keyData); } else { for (int i = 0; i < numKeys; i++) { byte[] currentVal = thisRow[((Integer) primaryKeyIndicies.get(i)) .intValue()]; if (currentVal != null) { updater.setBytes(numFields + i + 1, currentVal); } else { updater.setNull(numFields + i + 1, 0); } } } } private boolean initializedCharConverter = false; private synchronized SingleByteCharsetConverter getCharConverter() throws SQLException { if (!this.initializedCharConverter) { this.initializedCharConverter = true; if (this.connection.useUnicode()) { this.charEncoding = connection.getEncoding(); this.charConverter = this.connection.getCharsetConverter(this.charEncoding); } } return this.charConverter; } private synchronized String getQuotedIdChar() throws SQLException { if (this.quotedIdChar == null) { boolean useQuotedIdentifiers = connection.supportsQuotedIdentifiers(); if (useQuotedIdentifiers) { java.sql.DatabaseMetaData dbmd = connection.getMetaData(); this.quotedIdChar = dbmd.getIdentifierQuoteString(); } else { this.quotedIdChar = ""; } } return this.quotedIdChar; } /** * DOCUMENT ME! * * @param field * * @return String */ private String getTableName(Field field) { String originalTableName = field.getOriginalTableName(); if ((originalTableName != null) && (originalTableName.length() > 0)) { return originalTableName; } else { return field.getTableName(); } } /** * Is this ResultSet updateable? * * @return DOCUMENT ME! * * @throws SQLException DOCUMENT ME! */ private boolean checkUpdatability() throws SQLException { String tableName = null; String catalogName = null; int primaryKeyCount = 0; if (fields.length > 0) { tableName = fields[0].getOriginalTableName(); catalogName = fields[0].getDatabaseName(); if (tableName == null) { tableName = fields[0].getTableName(); catalogName = this.catalog; } if (fields[0].isPrimaryKey()) { primaryKeyCount++; } // // References only one table? // for (int i = 1; i < fields.length; i++) { String otherTableName = fields[i].getOriginalTableName(); String otherCatalogName = fields[i].getDatabaseName(); if (otherTableName == null) { otherTableName = fields[i].getTableName(); otherCatalogName = this.catalog; } // Can't have more than one table if ((tableName == null) || !otherTableName.equals(tableName)) { return false; } // Can't reference more than one database if ((catalogName == null) || !otherCatalogName.equals(catalogName)) { return false; } if (fields[i].isPrimaryKey()) { primaryKeyCount++; } } if ((tableName == null) || (tableName.length() == 0)) { return false; } } else { return false; } // // Must have at least one primary key // if (primaryKeyCount == 0) { return false; } // We can only do this if we know that there is a currently // selected database, or if we're talking to a > 4.1 version // of MySQL server (as it returns database names in field // info) // if ((this.catalog == null) || (this.catalog.length() == 0)) { this.catalog = fields[0].getDatabaseName(); if ((this.catalog == null) || (this.catalog.length() == 0)) { throw new SQLException( "Can not create updatable result sets when there is no currently selected database" + " and MySQL server version < 4.1", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } } if (this.connection.useStrictUpdates()) { java.sql.DatabaseMetaData dbmd = this.connection.getMetaData(); java.sql.ResultSet rs = null; HashMap primaryKeyNames = new HashMap(); try { rs = dbmd.getPrimaryKeys(catalogName, null, tableName); while (rs.next()) { String keyName = rs.getString(4); keyName = keyName.toUpperCase(); primaryKeyNames.put(keyName, keyName); } } finally { if (rs != null) { try { rs.close(); } catch (Exception ex) { AssertionFailedException.shouldNotHappen(ex); } rs = null; } } if (primaryKeyNames.size() == 0) { return false; // we can't update tables w/o keys } // // Contains all primary keys? // for (int i = 0; i < fields.length; i++) { if (fields[i].isPrimaryKey()) { String columnNameUC = fields[i].getName().toUpperCase(); if (primaryKeyNames.remove(columnNameUC) == null) { // try original name String originalName = fields[i].getOriginalName(); if (originalName != null) { if (primaryKeyNames.remove( originalName.toUpperCase()) == null) { // we don't know about this key, so give up :( return false; } } } } } return primaryKeyNames.isEmpty(); } return true; } private synchronized void extractDefaultValues() throws SQLException { java.sql.DatabaseMetaData dbmd = this.connection.getMetaData(); java.sql.ResultSet columnsResultSet = null; try { String unquotedTableName = this.tableName; if (unquotedTableName.startsWith(this.quotedIdChar)) { unquotedTableName = unquotedTableName.substring(1); } if (unquotedTableName.endsWith(this.quotedIdChar)) { unquotedTableName = unquotedTableName.substring(0, unquotedTableName.length() - 1); } columnsResultSet = dbmd.getColumns(this.catalog, null, unquotedTableName, "%"); HashMap columnNameToDefaultValueMap = new HashMap(this.fields.length /* at least this big... */); while (columnsResultSet.next()) { String columnName = columnsResultSet.getString("COLUMN_NAME"); byte[] defaultValue = columnsResultSet.getBytes("COLUMN_DEF"); columnNameToDefaultValueMap.put(columnName, defaultValue); } int numFields = this.fields.length; this.defaultColumnValue = new byte[numFields][]; for (int i = 0; i < numFields; i++) { String tableName = this.fields[i].getOriginalName(); if ((tableName == null) || (tableName.length() == 0)) { tableName = this.fields[i].getName(); } if (tableName != null) { byte[] defaultVal = (byte[]) columnNameToDefaultValueMap .get(tableName); this.defaultColumnValue[i] = defaultVal; } } } finally { if (columnsResultSet != null) { columnsResultSet.close(); columnsResultSet = null; } } } private void resetInserter() throws SQLException { inserter.clearParameters(); for (int i = 0; i < fields.length; i++) { inserter.setNull(i + 1, 0); } } }