0001: //jTDS JDBC Driver for Microsoft SQL Server and Sybase
0002: //Copyright (C) 2004 The jTDS Project
0003: //
0004: //This library is free software; you can redistribute it and/or
0005: //modify it under the terms of the GNU Lesser General Public
0006: //License as published by the Free Software Foundation; either
0007: //version 2.1 of the License, or (at your option) any later version.
0008: //
0009: //This library is distributed in the hope that it will be useful,
0010: //but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: //Lesser General Public License for more details.
0013: //
0014: //You should have received a copy of the GNU Lesser General Public
0015: //License along with this library; if not, write to the Free Software
0016: //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0017: //
0018: package net.sourceforge.jtds.jdbc;
0019:
0020: import java.io.UnsupportedEncodingException;
0021: import java.math.BigDecimal;
0022: import java.sql.ResultSet;
0023: import java.sql.SQLException;
0024: import java.sql.SQLWarning;
0025: import java.sql.Types;
0026: import java.util.ArrayList;
0027: import java.util.HashSet;
0028:
0029: /**
0030: * A memory cached scrollable/updateable result set.
0031: * <p/>
0032: * Notes:
0033: * <ol>
0034: * <li>For maximum performance use the scroll insensitive result set type.
0035: * <li>As the result set is cached in memory this implementation is limited
0036: * to small result sets.
0037: * <li>Updateable or scroll sensitive result sets are limited to selects
0038: * which reference one table only.
0039: * <li>Scroll sensitive result sets must have primary keys.
0040: * <li>Updates are optimistic. To guard against lost updates it is
0041: * recommended that the table includes a timestamp column.
0042: * <li>This class is a plug-in replacement for the MSCursorResultSet class
0043: * which may be advantageous in certain applications as the scroll
0044: * insensitive result set implemented here is much faster than the server
0045: * side cursor.
0046: * <li>Updateable result sets cannot be built from the output of stored
0047: * procedures.
0048: * <li>This implementation uses 'select ... for browse' to obtain the column
0049: * meta data needed to generate update statements etc.
0050: * <li>Named forward updateable cursors are also supported in which case
0051: * positioned updates and deletes are used referencing a server side
0052: * declared cursor.
0053: * <li>Named forward read only declared cursors can have a larger fetch size
0054: * specified allowing a cursor alternative to the default direct select
0055: * method.
0056: * </ol>
0057: *
0058: * @author Mike Hutchinson
0059: * @version $Id: CachedResultSet.java,v 1.26 2007/07/08 17:28:23 bheineman Exp $
0060: * @todo Should add a "close statement" flag to the constructors
0061: */
0062: public class CachedResultSet extends JtdsResultSet {
0063:
0064: /** Indicates currently inserting. */
0065: protected boolean onInsertRow;
0066: /** Buffer row used for inserts. */
0067: protected ParamInfo[] insertRow;
0068: /** The "update" row. */
0069: protected ParamInfo[] updateRow;
0070: // FIXME Remember if the row was updated/deleted for each row in the ResultSet
0071: /** Indicates that row has been updated. */
0072: protected boolean rowUpdated;
0073: /** Indicates that row has been deleted. */
0074: protected boolean rowDeleted;
0075: /** The row count of the initial result set. */
0076: protected int initialRowCnt;
0077: /** True if this is a local temporary result set. */
0078: protected final boolean tempResultSet;
0079: /** Cursor TdsCore object. */
0080: protected final TdsCore cursorTds;
0081: /** Updates TdsCore object used for positioned updates. */
0082: protected final TdsCore updateTds;
0083: /** Flag to indicate Sybase. */
0084: protected boolean isSybase;
0085: /** Fetch size has been changed. */
0086: protected boolean sizeChanged;
0087: /** Original SQL statement. */
0088: protected String sql;
0089: /** Original procedure name. */
0090: protected final String procName;
0091: /** Original parameters. */
0092: protected final ParamInfo[] procedureParams;
0093: /** Table is keyed. */
0094: protected boolean isKeyed;
0095: /** First table name in select. */
0096: protected String tableName;
0097: /** The parent connection object */
0098: protected ConnectionJDBC2 connection;
0099:
0100: /**
0101: * Constructs a new cached result set.
0102: * <p/>
0103: * This result set will either be cached in memory or, if the cursor name
0104: * is set, can be a forward only server side cursor. This latter form of
0105: * cursor can also support positioned updates.
0106: *
0107: * @param statement the parent statement object
0108: * @param sql the SQL statement used to build the result set
0109: * @param procName an optional stored procedure name
0110: * @param procedureParams parameters for prepared statements
0111: * @param resultSetType the result set type eg scrollable
0112: * @param concurrency the result set concurrency eg updateable
0113: * @exception SQLException if an error occurs
0114: */
0115: CachedResultSet(JtdsStatement statement, String sql,
0116: String procName, ParamInfo[] procedureParams,
0117: int resultSetType, int concurrency) throws SQLException {
0118: super (statement, resultSetType, concurrency, null);
0119: this .connection = (ConnectionJDBC2) statement.getConnection();
0120: this .cursorTds = statement.getTds();
0121: this .sql = sql;
0122: this .procName = procName;
0123: this .procedureParams = procedureParams;
0124: if (resultSetType == ResultSet.TYPE_FORWARD_ONLY
0125: && concurrency != ResultSet.CONCUR_READ_ONLY
0126: && cursorName != null) {
0127: // Need an addtional TDS for positioned updates
0128: this .updateTds = new TdsCore(connection, statement
0129: .getMessages());
0130: } else {
0131: this .updateTds = this .cursorTds;
0132: }
0133: this .isSybase = Driver.SYBASE == connection.getServerType();
0134: this .tempResultSet = false;
0135: //
0136: // Now create the specified type of cursor
0137: //
0138: cursorCreate();
0139: }
0140:
0141: /**
0142: * Constructs a cached result set based on locally generated data.
0143: *
0144: * @param statement the parent statement object
0145: * @param colName array of column names
0146: * @param colType array of corresponding data types
0147: * @exception SQLException if an error occurs
0148: */
0149: CachedResultSet(JtdsStatement statement, String[] colName,
0150: int[] colType) throws SQLException {
0151: super (statement, ResultSet.TYPE_FORWARD_ONLY,
0152: ResultSet.CONCUR_UPDATABLE, null);
0153: //
0154: // Construct the column descriptor array
0155: //
0156: this .columns = new ColInfo[colName.length];
0157: for (int i = 0; i < colName.length; i++) {
0158: ColInfo ci = new ColInfo();
0159: ci.name = colName[i];
0160: ci.realName = colName[i];
0161: ci.jdbcType = colType[i];
0162: ci.isCaseSensitive = false;
0163: ci.isIdentity = false;
0164: ci.isWriteable = false;
0165: ci.nullable = 2;
0166: ci.scale = 0;
0167: TdsData.fillInType(ci);
0168: columns[i] = ci;
0169: }
0170: this .columnCount = getColumnCount(columns);
0171: this .rowData = new ArrayList(INITIAL_ROW_COUNT);
0172: this .rowsInResult = 0;
0173: this .initialRowCnt = 0;
0174: this .pos = POS_BEFORE_FIRST;
0175: this .tempResultSet = true;
0176: this .cursorName = null;
0177: this .cursorTds = null;
0178: this .updateTds = null;
0179: this .procName = null;
0180: this .procedureParams = null;
0181: }
0182:
0183: /**
0184: * Creates a cached result set with the same columns (and optionally data)
0185: * as an existing result set.
0186: *
0187: * @param rs the result set to copy
0188: * @param load load data from the supplied result set
0189: * @throws SQLException if an error occurs
0190: */
0191: CachedResultSet(JtdsResultSet rs, boolean load) throws SQLException {
0192: super ((JtdsStatement) rs.getStatement(), rs.getStatement()
0193: .getResultSetType(), rs.getStatement()
0194: .getResultSetConcurrency(), null);
0195: //
0196: JtdsStatement stmt = ((JtdsStatement) rs.getStatement());
0197: //
0198: // OK If the user requested an updateable result set tell them
0199: // they can't have one!
0200: //
0201: if (concurrency != ResultSet.CONCUR_READ_ONLY) {
0202: concurrency = ResultSet.CONCUR_READ_ONLY;
0203: stmt.addWarning(new SQLWarning(Messages.get(
0204: "warning.cursordowngraded", "CONCUR_READ_ONLY"),
0205: "01000"));
0206: }
0207: //
0208: // If the user requested a scroll sensitive cursor tell them
0209: // they can't have that either!
0210: //
0211: if (resultSetType >= ResultSet.TYPE_SCROLL_SENSITIVE) {
0212: resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE;
0213: stmt.addWarning(new SQLWarning(Messages.get(
0214: "warning.cursordowngraded",
0215: "TYPE_SCROLL_INSENSITIVE"), "01000"));
0216: }
0217:
0218: this .columns = rs.getColumns();
0219: this .columnCount = getColumnCount(columns);
0220: this .rowData = new ArrayList(INITIAL_ROW_COUNT);
0221: this .rowsInResult = 0;
0222: this .initialRowCnt = 0;
0223: this .pos = POS_BEFORE_FIRST;
0224: this .tempResultSet = true;
0225: this .cursorName = null;
0226: this .cursorTds = null;
0227: this .updateTds = null;
0228: this .procName = null;
0229: this .procedureParams = null;
0230: //
0231: // Load result set into buffer
0232: //
0233: if (load) {
0234: while (rs.next()) {
0235: rowData.add(copyRow(rs.getCurrentRow()));
0236: }
0237: this .rowsInResult = rowData.size();
0238: this .initialRowCnt = rowsInResult;
0239: }
0240: }
0241:
0242: /**
0243: * Creates a cached result set containing one row.
0244: *
0245: * @param statement the parent statement object
0246: * @param columns the column descriptor array
0247: * @param data the row data
0248: * @throws SQLException if an error occurs
0249: */
0250: CachedResultSet(JtdsStatement statement, ColInfo columns[],
0251: Object data[]) throws SQLException {
0252: super (statement, ResultSet.TYPE_FORWARD_ONLY,
0253: ResultSet.CONCUR_READ_ONLY, null);
0254: this .columns = columns;
0255: this .columnCount = getColumnCount(columns);
0256: this .rowData = new ArrayList(1);
0257: this .rowsInResult = 1;
0258: this .initialRowCnt = 1;
0259: this .pos = POS_BEFORE_FIRST;
0260: this .tempResultSet = true;
0261: this .cursorName = null;
0262: this .rowData.add(copyRow(data));
0263: this .cursorTds = null;
0264: this .updateTds = null;
0265: this .procName = null;
0266: this .procedureParams = null;
0267: }
0268:
0269: /**
0270: * Modify the concurrency of the result set.
0271: * <p/>
0272: * Use to make result set read only once loaded.
0273: *
0274: * @param concurrency the concurrency value eg
0275: * <code>ResultSet.CONCUR_READ_ONLY</code>
0276: */
0277: void setConcurrency(int concurrency) {
0278: this .concurrency = concurrency;
0279: }
0280:
0281: /**
0282: * Creates a new scrollable result set in memory or a named server cursor.
0283: *
0284: * @exception SQLException if an error occurs
0285: */
0286: private void cursorCreate() throws SQLException {
0287: //
0288: boolean isSelect = false;
0289: int requestedConcurrency = concurrency;
0290: int requestedType = resultSetType;
0291:
0292: //
0293: // If the useCursor property is set we will try and use a server
0294: // side cursor for forward read only cursors. With the default
0295: // fetch size of 100 this is a reasonable emulation of the
0296: // MS fast forward cursor.
0297: //
0298: if (cursorName == null && connection.getUseCursors()
0299: && resultSetType == ResultSet.TYPE_FORWARD_ONLY
0300: && concurrency == ResultSet.CONCUR_READ_ONLY) {
0301: // The useCursors connection property was set true
0302: // so we need to create a private cursor name
0303: cursorName = connection.getCursorName();
0304: }
0305: //
0306: // Validate the SQL statement to ensure we have a select.
0307: //
0308: if (resultSetType != ResultSet.TYPE_FORWARD_ONLY
0309: || concurrency != ResultSet.CONCUR_READ_ONLY
0310: || cursorName != null) {
0311: //
0312: // We are going to need access to a SELECT statement for
0313: // this to work. Reparse the SQL now and check.
0314: //
0315: String tmp[] = SQLParser.parse(sql, new ArrayList(),
0316: (ConnectionJDBC2) statement.getConnection(), true);
0317:
0318: if ("select".equals(tmp[2])) {
0319: isSelect = true;
0320: if (tmp[3] != null && tmp[3].length() > 0) {
0321: // OK We have a select with at least one table.
0322: tableName = tmp[3];
0323: } else {
0324: // Can't find a table name so can't update
0325: concurrency = ResultSet.CONCUR_READ_ONLY;
0326: }
0327: } else {
0328: // No good we can't update and we can't declare a cursor
0329: cursorName = null;
0330: concurrency = ResultSet.CONCUR_READ_ONLY;
0331: if (resultSetType != ResultSet.TYPE_FORWARD_ONLY) {
0332: resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE;
0333: }
0334: }
0335: }
0336: //
0337: // If a cursor name is specified we try and declare a conventional cursor.
0338: // A server error will occur if we try to create a named cursor on a non
0339: // select statement.
0340: //
0341: if (cursorName != null) {
0342: //
0343: // Create and execute DECLARE CURSOR
0344: //
0345: StringBuffer cursorSQL = new StringBuffer(sql.length()
0346: + cursorName.length() + 128);
0347: cursorSQL.append("DECLARE ").append(cursorName).append(
0348: " CURSOR FOR ");
0349: //
0350: // We need to adjust any parameter offsets now as the prepended
0351: // DECLARE CURSOR will throw the parameter positions off.
0352: //
0353: ParamInfo[] parameters = procedureParams;
0354: if (procedureParams != null && procedureParams.length > 0) {
0355: parameters = new ParamInfo[procedureParams.length];
0356: int offset = cursorSQL.length();
0357: for (int i = 0; i < parameters.length; i++) {
0358: // Clone parameters to avoid corrupting offsets in original
0359: parameters[i] = (ParamInfo) procedureParams[i]
0360: .clone();
0361: parameters[i].markerPos += offset;
0362: }
0363: }
0364: cursorSQL.append(sql);
0365: cursorTds.executeSQL(cursorSQL.toString(), null,
0366: parameters, false, statement.getQueryTimeout(),
0367: statement.getMaxRows(),
0368: statement.getMaxFieldSize(), true);
0369: cursorTds.clearResponseQueue();
0370: cursorTds.getMessages().checkErrors();
0371: //
0372: // OK now open cursor and fetch the first set (fetchSize) rows
0373: //
0374: cursorSQL.setLength(0);
0375: cursorSQL.append("\r\nOPEN ").append(cursorName);
0376: if (fetchSize > 1 && isSybase) {
0377: cursorSQL.append("\r\nSET CURSOR ROWS ").append(
0378: fetchSize);
0379: cursorSQL.append(" FOR ").append(cursorName);
0380: }
0381: cursorSQL.append("\r\nFETCH ").append(cursorName);
0382: cursorTds.executeSQL(cursorSQL.toString(), null, null,
0383: false, statement.getQueryTimeout(), statement
0384: .getMaxRows(), statement.getMaxFieldSize(),
0385: true);
0386: //
0387: // Check we have a result set
0388: //
0389: while (!cursorTds.getMoreResults()
0390: && !cursorTds.isEndOfResponse())
0391: ;
0392:
0393: if (!cursorTds.isResultSet()) {
0394: // Throw exception but queue up any others
0395: SQLException ex = new SQLException(Messages
0396: .get("error.statement.noresult"), "24000");
0397: ex.setNextException(statement.getMessages().exceptions);
0398: throw ex;
0399: }
0400: columns = cursorTds.getColumns();
0401: if (connection.getServerType() == Driver.SQLSERVER) {
0402: // Last column will be rowstat but will not be marked as hidden
0403: // as we do not have the Column meta data returned by the API
0404: // cursor.
0405: // Hide it now to avoid confusion (also should not be updated).
0406: if (columns.length > 0) {
0407: columns[columns.length - 1].isHidden = true;
0408: }
0409: }
0410: columnCount = getColumnCount(columns);
0411: rowsInResult = cursorTds.isDataInResultSet() ? 1 : 0;
0412: } else {
0413: //
0414: // Open a memory cached scrollable or forward only possibly updateable cursor
0415: //
0416: if (isSelect
0417: && (concurrency != ResultSet.CONCUR_READ_ONLY || resultSetType >= ResultSet.TYPE_SCROLL_SENSITIVE)) {
0418: // Need to execute SELECT .. FOR BROWSE to get
0419: // the MetaData we require for updates etc
0420: // OK Should have an SQL select statement
0421: // append " FOR BROWSE" to obtain table names
0422: // NB. We can't use any jTDS temporary stored proc
0423: cursorTds.executeSQL(sql + " FOR BROWSE", null,
0424: procedureParams, false, statement
0425: .getQueryTimeout(), statement
0426: .getMaxRows(), statement
0427: .getMaxFieldSize(), true);
0428: while (!cursorTds.getMoreResults()
0429: && !cursorTds.isEndOfResponse())
0430: ;
0431: if (!cursorTds.isResultSet()) {
0432: // Throw exception but queue up any others
0433: SQLException ex = new SQLException(Messages
0434: .get("error.statement.noresult"), "24000");
0435: ex
0436: .setNextException(statement.getMessages().exceptions);
0437: throw ex;
0438: }
0439: columns = cursorTds.getColumns();
0440: columnCount = getColumnCount(columns);
0441: rowData = new ArrayList(INITIAL_ROW_COUNT);
0442: //
0443: // Load result set into buffer
0444: //
0445: cacheResultSetRows();
0446: rowsInResult = rowData.size();
0447: initialRowCnt = rowsInResult;
0448: pos = POS_BEFORE_FIRST;
0449: //
0450: // If cursor is built over one table and the table has
0451: // key columns then the result set is updateable and / or
0452: // can be used as a scroll sensitive result set.
0453: //
0454: if (!isCursorUpdateable()) {
0455: // No so downgrade
0456: concurrency = ResultSet.CONCUR_READ_ONLY;
0457: if (resultSetType != ResultSet.TYPE_FORWARD_ONLY) {
0458: resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE;
0459: }
0460: }
0461: } else {
0462: //
0463: // Create a read only cursor using direct SQL
0464: //
0465: cursorTds.executeSQL(sql, procName, procedureParams,
0466: false, statement.getQueryTimeout(), statement
0467: .getMaxRows(), statement
0468: .getMaxFieldSize(), true);
0469: while (!cursorTds.getMoreResults()
0470: && !cursorTds.isEndOfResponse())
0471: ;
0472:
0473: if (!cursorTds.isResultSet()) {
0474: // Throw exception but queue up any others
0475: SQLException ex = new SQLException(Messages
0476: .get("error.statement.noresult"), "24000");
0477: ex
0478: .setNextException(statement.getMessages().exceptions);
0479: throw ex;
0480: }
0481: columns = cursorTds.getColumns();
0482: columnCount = getColumnCount(columns);
0483: rowData = new ArrayList(INITIAL_ROW_COUNT);
0484: //
0485: // Load result set into buffer
0486: //
0487: cacheResultSetRows();
0488: rowsInResult = rowData.size();
0489: initialRowCnt = rowsInResult;
0490: pos = POS_BEFORE_FIRST;
0491: }
0492: }
0493: //
0494: // Report any cursor downgrade warnings
0495: //
0496: if (concurrency < requestedConcurrency) {
0497: statement.addWarning(new SQLWarning(Messages.get(
0498: "warning.cursordowngraded", "CONCUR_READ_ONLY"),
0499: "01000"));
0500: }
0501: if (resultSetType < requestedType) {
0502: statement.addWarning(new SQLWarning(Messages.get(
0503: "warning.cursordowngraded",
0504: "TYPE_SCROLL_INSENSITIVE"), "01000"));
0505: }
0506: //
0507: // Report any SQLExceptions
0508: //
0509: statement.getMessages().checkErrors();
0510: }
0511:
0512: /**
0513: * Analyses the tables in the result set and determines if the primary key
0514: * columns needed to make it updateable exist.
0515: * <p/>
0516: * Sybase (and SQL 6.5) will automatically include any additional key and
0517: * timestamp columns as hidden fields even if the user does not reference
0518: * them in the select statement.
0519: * <p/>
0520: * If the table is unkeyed but there is an identity column then this is
0521: * promoted to a key.
0522: * <p/>
0523: * Alternatively we can update, provided all the columns in the table row
0524: * have been selected, by regarding all of them as keys.
0525: * <p/>
0526: * SQL Server 7+ does not return the correct primary key meta data for
0527: * temporary tables so the driver has to query the catalog to locate any
0528: * keys.
0529: *
0530: * @return <code>true<code> if there is one table and it is keyed
0531: */
0532: boolean isCursorUpdateable() throws SQLException {
0533: //
0534: // Get fully qualified table names and check keys
0535: //
0536: isKeyed = false;
0537: HashSet tableSet = new HashSet();
0538: for (int i = 0; i < columns.length; i++) {
0539: ColInfo ci = columns[i];
0540: if (ci.isKey) {
0541: // If a table lacks a key Sybase flags all columns except timestamps as keys.
0542: // This does not make much sense in the case of text or image fields!
0543: if ("text".equals(ci.sqlType)
0544: || "image".equals(ci.sqlType)) {
0545: ci.isKey = false;
0546: } else {
0547: isKeyed = true;
0548: }
0549: } else if (ci.isIdentity) {
0550: // This is a good choice for a row identifier!
0551: ci.isKey = true;
0552: isKeyed = true;
0553: }
0554: StringBuffer key = new StringBuffer();
0555: if (ci.tableName != null && ci.tableName.length() > 0) {
0556: key.setLength(0);
0557: if (ci.catalog != null) {
0558: key.append(ci.catalog).append('.');
0559: if (ci.schema == null) {
0560: key.append('.');
0561: }
0562: }
0563: if (ci.schema != null) {
0564: key.append(ci.schema).append('.');
0565: }
0566: key.append(ci.tableName);
0567: tableName = key.toString();
0568: tableSet.add(tableName);
0569: }
0570: }
0571: //
0572: // MJH - SQL Server 7/2000 does not return key information for temporary tables.
0573: // I regard this as a bug!
0574: // See if we can find up to the first 8 index columns for ourselves.
0575: //
0576: if (tableName.startsWith("#")
0577: && cursorTds.getTdsVersion() >= Driver.TDS70) {
0578: StringBuffer sql = new StringBuffer(1024);
0579: sql.append("SELECT ");
0580: for (int i = 1; i <= 8; i++) {
0581: if (i > 1) {
0582: sql.append(',');
0583: }
0584: sql.append("index_col('tempdb..").append(tableName);
0585: sql.append("', indid, ").append(i).append(')');
0586: }
0587: sql
0588: .append(" FROM tempdb..sysindexes WHERE id = object_id('tempdb..");
0589: sql.append(tableName).append("') AND indid > 0 AND ");
0590: sql.append("(status & 2048) = 2048");
0591: cursorTds.executeSQL(sql.toString(), null, null, false, 0,
0592: statement.getMaxRows(),
0593: statement.getMaxFieldSize(), true);
0594: while (!cursorTds.getMoreResults()
0595: && !cursorTds.isEndOfResponse())
0596: ;
0597:
0598: if (cursorTds.isResultSet() && cursorTds.getNextRow()) {
0599: Object row[] = cursorTds.getRowData();
0600: for (int i = 0; i < row.length; i++) {
0601: String name = (String) row[i];
0602: if (name != null) {
0603: for (int c = 0; c < columns.length; c++) {
0604: if (columns[c].realName != null
0605: && columns[c].realName
0606: .equalsIgnoreCase(name)) {
0607: columns[c].isKey = true;
0608: isKeyed = true;
0609: break;
0610: }
0611: }
0612: }
0613: }
0614: }
0615: // Report any errors found
0616: statement.getMessages().checkErrors();
0617: }
0618: //
0619: // Final fall back make all columns pseudo keys!
0620: // Sybase seems to do this automatically.
0621: //
0622: if (!isKeyed) {
0623: for (int i = 0; i < columns.length; i++) {
0624: String type = columns[i].sqlType;
0625: if (!"ntext".equals(type) && !"text".equals(type)
0626: && !"image".equals(type)
0627: && !"timestamp".equals(type)
0628: && columns[i].tableName != null) {
0629: columns[i].isKey = true;
0630: isKeyed = true;
0631: }
0632: }
0633: }
0634:
0635: return (tableSet.size() == 1 && isKeyed);
0636: }
0637:
0638: /**
0639: * Fetches the next result row from the internal row array.
0640: *
0641: * @param rowNum the row number to fetch
0642: * @return <code>true</code> if a result set row is returned
0643: * @throws SQLException if an error occurs
0644: */
0645: private boolean cursorFetch(int rowNum) throws SQLException {
0646: rowUpdated = false;
0647: //
0648: if (cursorName != null) {
0649: //
0650: // Using a conventional forward only server cursor
0651: //
0652: if (!cursorTds.getNextRow()) {
0653: // Need to fetch more rows from server
0654: StringBuffer sql = new StringBuffer(128);
0655: if (isSybase && sizeChanged) {
0656: // Sybase allows us to set a fetch size
0657: sql.append("SET CURSOR ROWS ").append(fetchSize);
0658: sql.append(" FOR ").append(cursorName);
0659: sql.append("\r\n");
0660: }
0661: sql.append("FETCH ").append(cursorName);
0662: // Get the next row or block of rows.
0663: cursorTds.executeSQL(sql.toString(), null, null, false,
0664: statement.getQueryTimeout(), statement
0665: .getMaxRows(), statement
0666: .getMaxFieldSize(), true);
0667: while (!cursorTds.getMoreResults()
0668: && !cursorTds.isEndOfResponse())
0669: ;
0670:
0671: sizeChanged = false; // Indicate fetch size updated
0672:
0673: if (!cursorTds.isResultSet() || !cursorTds.getNextRow()) {
0674: pos = POS_AFTER_LAST;
0675: currentRow = null;
0676: statement.getMessages().checkErrors();
0677: return false;
0678: }
0679: }
0680: currentRow = statement.getTds().getRowData();
0681: pos++;
0682: rowsInResult = pos;
0683: statement.getMessages().checkErrors();
0684:
0685: return currentRow != null;
0686:
0687: }
0688: //
0689: // JDBC2 style Scrollable and/or Updateable cursor
0690: //
0691: if (rowsInResult == 0) {
0692: pos = POS_BEFORE_FIRST;
0693: currentRow = null;
0694: return false;
0695: }
0696: if (rowNum == pos) {
0697: // On current row
0698: //
0699: return true;
0700: }
0701: if (rowNum < 1) {
0702: currentRow = null;
0703: pos = POS_BEFORE_FIRST;
0704: return false;
0705: }
0706: if (rowNum > rowsInResult) {
0707: currentRow = null;
0708: pos = POS_AFTER_LAST;
0709: return false;
0710: }
0711: pos = rowNum;
0712: currentRow = (Object[]) rowData.get(rowNum - 1);
0713: rowDeleted = currentRow == null;
0714:
0715: if (resultSetType >= ResultSet.TYPE_SCROLL_SENSITIVE
0716: && currentRow != null) {
0717: refreshRow();
0718: }
0719:
0720: return true;
0721: }
0722:
0723: /**
0724: * Closes the result set.
0725: */
0726: private void cursorClose() throws SQLException {
0727: if (cursorName != null) {
0728: statement.clearWarnings();
0729: String sql;
0730: if (isSybase) {
0731: sql = "CLOSE " + cursorName + "\r\nDEALLOCATE CURSOR "
0732: + cursorName;
0733: } else {
0734: sql = "CLOSE " + cursorName + "\r\nDEALLOCATE "
0735: + cursorName;
0736: }
0737: cursorTds.submitSQL(sql);
0738: }
0739: rowData = null;
0740: }
0741:
0742: /**
0743: * Creates a parameter object for an UPDATE, DELETE or INSERT statement.
0744: *
0745: * @param pos the substitution position of the parameter marker in the SQL
0746: * @param info the <code>ColInfo</code> column descriptor
0747: * @param value the column data item
0748: * @return the new parameter as a <code>ParamInfo</code> object
0749: */
0750: protected static ParamInfo buildParameter(int pos, ColInfo info,
0751: Object value, boolean isUnicode) throws SQLException {
0752:
0753: int length = 0;
0754: if (value instanceof String) {
0755: length = ((String) value).length();
0756: } else if (value instanceof byte[]) {
0757: length = ((byte[]) value).length;
0758: } else if (value instanceof BlobImpl) {
0759: BlobImpl blob = (BlobImpl) value;
0760: value = blob.getBinaryStream();
0761: length = (int) blob.length();
0762: } else if (value instanceof ClobImpl) {
0763: ClobImpl clob = (ClobImpl) value;
0764: value = clob.getCharacterStream();
0765: length = (int) clob.length();
0766: }
0767: ParamInfo param = new ParamInfo(info, null, value, length);
0768: param.isUnicode = "nvarchar".equals(info.sqlType)
0769: || "nchar".equals(info.sqlType)
0770: || "ntext".equals(info.sqlType) || isUnicode;
0771: param.markerPos = pos;
0772:
0773: return param;
0774: }
0775:
0776: /**
0777: * Sets the specified column's data value.
0778: *
0779: * @param colIndex index of the column
0780: * @param value new column value
0781: * @return the value, possibly converted to an internal type
0782: */
0783: protected Object setColValue(int colIndex, int jdbcType,
0784: Object value, int length) throws SQLException {
0785:
0786: value = super .setColValue(colIndex, jdbcType, value, length);
0787:
0788: if (!onInsertRow && currentRow == null) {
0789: throw new SQLException(Messages
0790: .get("error.resultset.norow"), "24000");
0791: }
0792: colIndex--;
0793: ParamInfo pi;
0794: ColInfo ci = columns[colIndex];
0795: boolean isUnicode = TdsData.isUnicode(ci);
0796:
0797: if (onInsertRow) {
0798: pi = insertRow[colIndex];
0799: if (pi == null) {
0800: pi = new ParamInfo(-1, isUnicode);
0801: pi.collation = ci.collation;
0802: pi.charsetInfo = ci.charsetInfo;
0803: insertRow[colIndex] = pi;
0804: }
0805: } else {
0806: if (updateRow == null) {
0807: updateRow = new ParamInfo[columnCount];
0808: }
0809: pi = updateRow[colIndex];
0810: if (pi == null) {
0811: pi = new ParamInfo(-1, isUnicode);
0812: pi.collation = ci.collation;
0813: pi.charsetInfo = ci.charsetInfo;
0814: updateRow[colIndex] = pi;
0815: }
0816: }
0817:
0818: if (value == null) {
0819: pi.value = null;
0820: pi.length = 0;
0821: pi.jdbcType = ci.jdbcType;
0822: pi.isSet = true;
0823: if (pi.jdbcType == Types.NUMERIC
0824: || pi.jdbcType == Types.DECIMAL) {
0825: pi.scale = TdsData.DEFAULT_SCALE;
0826: } else {
0827: pi.scale = 0;
0828: }
0829: } else {
0830: pi.value = value;
0831: pi.length = length;
0832: pi.isSet = true;
0833: pi.jdbcType = jdbcType;
0834: if (pi.value instanceof BigDecimal) {
0835: pi.scale = ((BigDecimal) pi.value).scale();
0836: } else {
0837: pi.scale = 0;
0838: }
0839: }
0840:
0841: return value;
0842: }
0843:
0844: /**
0845: * Builds a WHERE clause for UPDATE or DELETE statements.
0846: *
0847: * @param sql the SQL Statement to append the WHERE clause to
0848: * @param params the parameter descriptor array for this statement
0849: * @param select true if this WHERE clause will be used in a select
0850: * statement
0851: * @return the parameter list as a <code>ParamInfo[]</code>
0852: * @throws SQLException if an error occurs
0853: */
0854: ParamInfo[] buildWhereClause(StringBuffer sql, ArrayList params,
0855: boolean select) throws SQLException {
0856: //
0857: // Now construct where clause
0858: //
0859: sql.append(" WHERE ");
0860: if (cursorName != null) {
0861: //
0862: // Use a positioned update
0863: //
0864: sql.append(" CURRENT OF ").append(cursorName);
0865: } else {
0866: int count = 0;
0867: for (int i = 0; i < columns.length; i++) {
0868: if (currentRow[i] == null) {
0869: if (!"text".equals(columns[i].sqlType)
0870: && !"ntext".equals(columns[i].sqlType)
0871: && !"image".equals(columns[i].sqlType)
0872: && columns[i].tableName != null) {
0873: if (count > 0) {
0874: sql.append(" AND ");
0875: }
0876: sql.append(columns[i].realName);
0877: sql.append(" IS NULL");
0878: }
0879: } else {
0880: if (isKeyed && select) {
0881: // For refresh select only include key columns
0882: if (columns[i].isKey) {
0883: if (count > 0) {
0884: sql.append(" AND ");
0885: }
0886: sql.append(columns[i].realName);
0887: sql.append("=?");
0888: count++;
0889: params.add(buildParameter(sql.length() - 1,
0890: columns[i], currentRow[i],
0891: connection.getUseUnicode()));
0892: }
0893: } else {
0894: // Include all available 'searchable' columns in updates/deletes to protect
0895: // against lost updates.
0896: if (!"text".equals(columns[i].sqlType)
0897: && !"ntext".equals(columns[i].sqlType)
0898: && !"image".equals(columns[i].sqlType)
0899: && columns[i].tableName != null) {
0900: if (count > 0) {
0901: sql.append(" AND ");
0902: }
0903: sql.append(columns[i].realName);
0904: sql.append("=?");
0905: count++;
0906: params.add(buildParameter(sql.length() - 1,
0907: columns[i], currentRow[i],
0908: connection.getUseUnicode()));
0909: }
0910: }
0911: }
0912: }
0913: }
0914: return (ParamInfo[]) params
0915: .toArray(new ParamInfo[params.size()]);
0916: }
0917:
0918: /**
0919: * Refreshes a result set row from keyed tables.
0920: * <p/>
0921: * If all the tables in the result set have primary keys then the result
0922: * set row can be refreshed by refetching the individual table rows.
0923: *
0924: * @throws SQLException if an error occurs
0925: */
0926: protected void refreshKeyedRows() throws SQLException {
0927: //
0928: // Construct a SELECT statement
0929: //
0930: StringBuffer sql = new StringBuffer(100 + columns.length * 10);
0931: sql.append("SELECT ");
0932: int count = 0;
0933: for (int i = 0; i < columns.length; i++) {
0934: if (!columns[i].isKey && columns[i].tableName != null) {
0935: if (count > 0) {
0936: sql.append(',');
0937: }
0938: sql.append(columns[i].realName);
0939: count++;
0940: }
0941: }
0942: if (count == 0) {
0943: // No non key columns in this table?
0944: return;
0945: }
0946: sql.append(" FROM ");
0947: sql.append(tableName);
0948: //
0949: // Construct a where clause using keyed columns only
0950: //
0951: ArrayList params = new ArrayList();
0952: buildWhereClause(sql, params, true);
0953: ParamInfo parameters[] = (ParamInfo[]) params
0954: .toArray(new ParamInfo[params.size()]);
0955: //
0956: // Execute the select
0957: //
0958: TdsCore tds = statement.getTds();
0959: tds.executeSQL(sql.toString(), null, parameters, false, 0,
0960: statement.getMaxRows(), statement.getMaxFieldSize(),
0961: true);
0962: if (!tds.isEndOfResponse()) {
0963: if (tds.getMoreResults() && tds.getNextRow()) {
0964: // refresh the row data
0965: Object col[] = tds.getRowData();
0966: count = 0;
0967: for (int i = 0; i < columns.length; i++) {
0968: if (!columns[i].isKey) {
0969: currentRow[i] = col[count++];
0970: }
0971: }
0972: } else {
0973: currentRow = null;
0974: }
0975: } else {
0976: currentRow = null;
0977: }
0978: tds.clearResponseQueue();
0979: statement.getMessages().checkErrors();
0980: if (currentRow == null) {
0981: rowData.set(pos - 1, null);
0982: rowDeleted = true;
0983: }
0984: }
0985:
0986: /**
0987: * Refreshes the row by rereading the result set.
0988: * <p/>
0989: * Obviously very slow on large result sets but may be the only option if
0990: * tables do not have keys.
0991: */
0992: protected void refreshReRead() throws SQLException {
0993: int savePos = pos;
0994: cursorCreate();
0995: absolute(savePos);
0996: }
0997:
0998: //
0999: // -------------------- java.sql.ResultSet methods -------------------
1000: //
1001:
1002: public void setFetchSize(int size) throws SQLException {
1003: sizeChanged = size != fetchSize;
1004: super .setFetchSize(size);
1005: }
1006:
1007: public void afterLast() throws SQLException {
1008: checkOpen();
1009: checkScrollable();
1010: if (pos != POS_AFTER_LAST) {
1011: cursorFetch(rowsInResult + 1);
1012: }
1013: }
1014:
1015: public void beforeFirst() throws SQLException {
1016: checkOpen();
1017: checkScrollable();
1018:
1019: if (pos != POS_BEFORE_FIRST) {
1020: cursorFetch(0);
1021: }
1022: }
1023:
1024: public void cancelRowUpdates() throws SQLException {
1025: checkOpen();
1026: checkUpdateable();
1027: if (onInsertRow) {
1028: throw new SQLException(Messages
1029: .get("error.resultset.insrow"), "24000");
1030: }
1031: if (updateRow != null) {
1032: rowUpdated = false;
1033: for (int i = 0; i < updateRow.length; i++) {
1034: if (updateRow[i] != null) {
1035: updateRow[i].clearInValue();
1036: }
1037: }
1038: }
1039: }
1040:
1041: public void close() throws SQLException {
1042: if (!closed) {
1043: try {
1044: cursorClose();
1045: } finally {
1046: closed = true;
1047: statement = null;
1048: }
1049: }
1050: }
1051:
1052: public void deleteRow() throws SQLException {
1053: checkOpen();
1054: checkUpdateable();
1055:
1056: if (currentRow == null) {
1057: throw new SQLException(Messages
1058: .get("error.resultset.norow"), "24000");
1059: }
1060:
1061: if (onInsertRow) {
1062: throw new SQLException(Messages
1063: .get("error.resultset.insrow"), "24000");
1064: }
1065:
1066: //
1067: // Construct an SQL DELETE statement
1068: //
1069: StringBuffer sql = new StringBuffer(128);
1070: ArrayList params = new ArrayList();
1071: sql.append("DELETE FROM ");
1072: sql.append(tableName);
1073: //
1074: // Create the WHERE clause
1075: //
1076: ParamInfo parameters[] = buildWhereClause(sql, params, false);
1077: //
1078: // Execute the delete statement
1079: //
1080: updateTds.executeSQL(sql.toString(), null, parameters, false,
1081: 0, statement.getMaxRows(), statement.getMaxFieldSize(),
1082: true);
1083: int updateCount = 0;
1084: while (!updateTds.isEndOfResponse()) {
1085: if (!updateTds.getMoreResults()) {
1086: if (updateTds.isUpdateCount()) {
1087: updateCount = updateTds.getUpdateCount();
1088: }
1089: }
1090: }
1091: updateTds.clearResponseQueue();
1092: statement.getMessages().checkErrors();
1093: if (updateCount == 0) {
1094: // No delete. Possibly row was changed on database by another user?
1095: throw new SQLException(Messages
1096: .get("error.resultset.deletefail"), "24000");
1097: }
1098: rowDeleted = true;
1099: currentRow = null;
1100: if (resultSetType != ResultSet.TYPE_FORWARD_ONLY) {
1101: // Leave a 'hole' in the result set array.
1102: rowData.set(pos - 1, null);
1103: }
1104: }
1105:
1106: public void insertRow() throws SQLException {
1107: checkOpen();
1108:
1109: checkUpdateable();
1110:
1111: if (!onInsertRow) {
1112: throw new SQLException(Messages
1113: .get("error.resultset.notinsrow"), "24000");
1114: }
1115:
1116: if (!tempResultSet) {
1117: //
1118: // Construct an SQL INSERT statement
1119: //
1120: StringBuffer sql = new StringBuffer(128);
1121: ArrayList params = new ArrayList();
1122: sql.append("INSERT INTO ");
1123: sql.append(tableName);
1124: int sqlLen = sql.length();
1125: //
1126: // Create column list
1127: //
1128: sql.append(" (");
1129: int count = 0;
1130: for (int i = 0; i < columnCount; i++) {
1131: if (insertRow[i] != null) {
1132: if (count > 0) {
1133: sql.append(", ");
1134: }
1135: sql.append(columns[i].realName);
1136: count++;
1137: }
1138: }
1139: //
1140: // Create new values list
1141: //
1142: sql.append(") VALUES(");
1143: count = 0;
1144: for (int i = 0; i < columnCount; i++) {
1145: if (insertRow[i] != null) {
1146: if (count > 0) {
1147: sql.append(", ");
1148: }
1149: sql.append('?');
1150: insertRow[i].markerPos = sql.length() - 1;
1151: params.add(insertRow[i]);
1152: count++;
1153: }
1154: }
1155: sql.append(')');
1156: if (count == 0) {
1157: // Empty insert
1158: sql.setLength(sqlLen);
1159: if (isSybase) {
1160: sql.append(" VALUES()");
1161: } else {
1162: sql.append(" DEFAULT VALUES");
1163: }
1164: }
1165: ParamInfo parameters[] = (ParamInfo[]) params
1166: .toArray(new ParamInfo[params.size()]);
1167: //
1168: // execute the insert statement
1169: //
1170: updateTds.executeSQL(sql.toString(), null, parameters,
1171: false, 0, statement.getMaxRows(), statement
1172: .getMaxFieldSize(), true);
1173: int updateCount = 0;
1174: while (!updateTds.isEndOfResponse()) {
1175: if (!updateTds.getMoreResults()) {
1176: if (updateTds.isUpdateCount()) {
1177: updateCount = updateTds.getUpdateCount();
1178: }
1179: }
1180: }
1181: updateTds.clearResponseQueue();
1182: statement.getMessages().checkErrors();
1183: if (updateCount < 1) {
1184: // No Insert. Probably will not get here as duplicate key etc
1185: // will have already been reported as an exception.
1186: throw new SQLException(Messages
1187: .get("error.resultset.insertfail"), "24000");
1188: }
1189: }
1190: //
1191: if (resultSetType >= ResultSet.TYPE_SCROLL_SENSITIVE
1192: || (resultSetType == ResultSet.TYPE_FORWARD_ONLY && cursorName == null)) {
1193: //
1194: // Now insert copy of row into result set buffer
1195: //
1196: ConnectionJDBC2 con = (ConnectionJDBC2) statement
1197: .getConnection();
1198: Object row[] = newRow();
1199: for (int i = 0; i < insertRow.length; i++) {
1200: if (insertRow[i] != null) {
1201: row[i] = Support.convert(con, insertRow[i].value,
1202: columns[i].jdbcType, con.getCharset());
1203: }
1204: }
1205: rowData.add(row);
1206: }
1207: rowsInResult++;
1208: initialRowCnt++;
1209: //
1210: // Clear row data
1211: //
1212: for (int i = 0; insertRow != null && i < insertRow.length; i++) {
1213: if (insertRow[i] != null) {
1214: insertRow[i].clearInValue();
1215: }
1216: }
1217: }
1218:
1219: public void moveToCurrentRow() throws SQLException {
1220: checkOpen();
1221: checkUpdateable();
1222: insertRow = null;
1223: onInsertRow = false;
1224: }
1225:
1226: public void moveToInsertRow() throws SQLException {
1227: checkOpen();
1228: checkUpdateable();
1229: insertRow = new ParamInfo[columnCount];
1230: onInsertRow = true;
1231: }
1232:
1233: public void refreshRow() throws SQLException {
1234: checkOpen();
1235:
1236: if (onInsertRow) {
1237: throw new SQLException(Messages
1238: .get("error.resultset.insrow"), "24000");
1239: }
1240:
1241: //
1242: // If row is being updated discard updates now
1243: //
1244: if (concurrency != ResultSet.CONCUR_READ_ONLY) {
1245: cancelRowUpdates();
1246: rowUpdated = false;
1247: }
1248: if (resultSetType == ResultSet.TYPE_FORWARD_ONLY
1249: || currentRow == null) {
1250: // Do not try and refresh the row in these cases.
1251: return;
1252: }
1253: //
1254: // If result set is keyed we can refresh the row data from the
1255: // database using the key.
1256: // NB. MS SQL Server #Temporary tables with keys are not identified correctly
1257: // in the column meta data sent after 'for browse'. This means that
1258: // temporary tables can not be used with this logic.
1259: //
1260: if (isKeyed) {
1261: // OK all tables are keyed
1262: refreshKeyedRows();
1263: } else {
1264: // No good have to use brute force approach
1265: refreshReRead();
1266: }
1267: }
1268:
1269: public void updateRow() throws SQLException {
1270: checkOpen();
1271: checkUpdateable();
1272:
1273: rowUpdated = false;
1274: rowDeleted = false;
1275: if (currentRow == null) {
1276: throw new SQLException(Messages
1277: .get("error.resultset.norow"), "24000");
1278: }
1279:
1280: if (onInsertRow) {
1281: throw new SQLException(Messages
1282: .get("error.resultset.insrow"), "24000");
1283: }
1284:
1285: if (updateRow == null) {
1286: // Nothing to update
1287: return;
1288: }
1289: boolean keysChanged = false;
1290: //
1291: // Construct an SQL UPDATE statement
1292: //
1293: StringBuffer sql = new StringBuffer(128);
1294: ArrayList params = new ArrayList();
1295: sql.append("UPDATE ");
1296: sql.append(tableName);
1297: //
1298: // OK now create assign new values
1299: //
1300: sql.append(" SET ");
1301: int count = 0;
1302: for (int i = 0; i < columnCount; i++) {
1303: if (updateRow[i] != null) {
1304: if (count > 0) {
1305: sql.append(", ");
1306: }
1307: sql.append(columns[i].realName);
1308: sql.append("=?");
1309: updateRow[i].markerPos = sql.length() - 1;
1310: params.add(updateRow[i]);
1311: count++;
1312: if (columns[i].isKey) {
1313: // Key is changing so in memory row will need to be deleted
1314: // and reinserted at end of row buffer.
1315: keysChanged = true;
1316: }
1317: }
1318: }
1319: if (count == 0) {
1320: // There are no columns to update in this table
1321: // so bail out now.
1322: return;
1323: }
1324: //
1325: // Now construct where clause
1326: //
1327: ParamInfo parameters[] = buildWhereClause(sql, params, false);
1328: //
1329: // Now execute update
1330: //
1331: updateTds.executeSQL(sql.toString(), null, parameters, false,
1332: 0, statement.getMaxRows(), statement.getMaxFieldSize(),
1333: true);
1334: int updateCount = 0;
1335: while (!updateTds.isEndOfResponse()) {
1336: if (!updateTds.getMoreResults()) {
1337: if (updateTds.isUpdateCount()) {
1338: updateCount = updateTds.getUpdateCount();
1339: }
1340: }
1341: }
1342: updateTds.clearResponseQueue();
1343: statement.getMessages().checkErrors();
1344:
1345: if (updateCount == 0) {
1346: // No update. Possibly row was changed on database by another user?
1347: throw new SQLException(Messages
1348: .get("error.resultset.updatefail"), "24000");
1349: }
1350: //
1351: // Update local copy of data
1352: //
1353: if (resultSetType != ResultSet.TYPE_SCROLL_INSENSITIVE) {
1354: // Make in memory copy reflect database update
1355: // Could use refreshRow but this is much faster.
1356: ConnectionJDBC2 con = (ConnectionJDBC2) statement
1357: .getConnection();
1358: for (int i = 0; i < updateRow.length; i++) {
1359: if (updateRow[i] != null) {
1360: if (updateRow[i].value instanceof byte[]
1361: && (columns[i].jdbcType == Types.CHAR
1362: || columns[i].jdbcType == Types.VARCHAR || columns[i].jdbcType == Types.LONGVARCHAR)) {
1363: // Need to handle byte[] to varchar otherwise field
1364: // will be set to hex string rather than characters.
1365: try {
1366: currentRow[i] = new String(
1367: (byte[]) updateRow[i].value, con
1368: .getCharset());
1369: } catch (UnsupportedEncodingException e) {
1370: currentRow[i] = new String(
1371: (byte[]) updateRow[i].value);
1372: }
1373: } else {
1374: currentRow[i] = Support.convert(con,
1375: updateRow[i].value,
1376: columns[i].jdbcType, con.getCharset());
1377: }
1378: }
1379: }
1380: }
1381: //
1382: // Update state of cached row data
1383: //
1384: if (keysChanged
1385: && resultSetType >= ResultSet.TYPE_SCROLL_SENSITIVE) {
1386: // Leave hole at current position and add updated row to end of set
1387: rowData.add(currentRow);
1388: rowsInResult = rowData.size();
1389: rowData.set(pos - 1, null);
1390: currentRow = null;
1391: rowDeleted = true;
1392: } else {
1393: rowUpdated = true;
1394: }
1395: //
1396: // Clear update values
1397: //
1398: cancelRowUpdates();
1399: }
1400:
1401: public boolean first() throws SQLException {
1402: checkOpen();
1403: checkScrollable();
1404: return cursorFetch(1);
1405: }
1406:
1407: public boolean isLast() throws SQLException {
1408: checkOpen();
1409:
1410: return (pos == rowsInResult) && (rowsInResult != 0);
1411: }
1412:
1413: public boolean last() throws SQLException {
1414: checkOpen();
1415: checkScrollable();
1416: return cursorFetch(rowsInResult);
1417: }
1418:
1419: public boolean next() throws SQLException {
1420: checkOpen();
1421: if (pos != POS_AFTER_LAST) {
1422: return cursorFetch(pos + 1);
1423: } else {
1424: return false;
1425: }
1426: }
1427:
1428: public boolean previous() throws SQLException {
1429: checkOpen();
1430: checkScrollable();
1431: if (pos == POS_AFTER_LAST) {
1432: pos = rowsInResult + 1;
1433: }
1434: return cursorFetch(pos - 1);
1435: }
1436:
1437: public boolean rowDeleted() throws SQLException {
1438: checkOpen();
1439:
1440: return rowDeleted;
1441: }
1442:
1443: public boolean rowInserted() throws SQLException {
1444: checkOpen();
1445:
1446: // return pos > initialRowCnt;
1447: return false; // Same as MSCursorResultSet
1448: }
1449:
1450: public boolean rowUpdated() throws SQLException {
1451: checkOpen();
1452:
1453: // return rowUpdated;
1454: return false; // Same as MSCursorResultSet
1455: }
1456:
1457: public boolean absolute(int row) throws SQLException {
1458: checkOpen();
1459: checkScrollable();
1460: if (row < 1) {
1461: row = (rowsInResult + 1) + row;
1462: }
1463:
1464: return cursorFetch(row);
1465: }
1466:
1467: public boolean relative(int row) throws SQLException {
1468: checkScrollable();
1469: if (pos == POS_AFTER_LAST) {
1470: return absolute((rowsInResult + 1) + row);
1471: } else {
1472: return absolute(pos + row);
1473: }
1474: }
1475:
1476: public String getCursorName() throws SQLException {
1477: checkOpen();
1478: // Hide internal cursor names
1479: if (cursorName != null && !cursorName.startsWith("_jtds")) {
1480: return this .cursorName;
1481: }
1482: throw new SQLException(Messages
1483: .get("error.resultset.noposupdate"), "24000");
1484: }
1485: }
|