/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source 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. * * Resin Open Source 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, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.quercus.lib.db; import com.caucho.quercus.env.*; import com.caucho.util.L10N; import com.caucho.util.Log; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; import java.sql.Date; import java.sql.Timestamp; import java.sql.Time; import java.util.logging.Level; import java.util.logging.Logger; /** * Represents a JDBC Result value. */ public class JdbcResultResource { private static final Logger log = Logger.getLogger(JdbcResultResource.class.getName()); private static final L10N L = new L10N(JdbcResultResource.class); public static final int FETCH_ASSOC = 0x1; public static final int FETCH_NUM = 0x2; public static final int FETCH_BOTH = FETCH_ASSOC | FETCH_NUM; public static final String INTEGER = "int"; public static final String BLOB = "blob"; public static final String STRING = "string"; public static final String DATE = "date"; public static final String DATETIME = "datetime"; public static final String REAL = "real"; public static final String TIME = "time"; public static final String TIMESTAMP = "timestamp"; public static final String UNKNOWN = "unknown"; public static final String YEAR = "year"; private Statement _stmt; protected ResultSet _rs; private boolean _isValid; private int _fieldOffset; private JdbcConnectionResource _conn; private Env _env; protected ResultSetMetaData _metaData; private Value[] _columnNames; private int _affectedRows; /** * Constructor for JdbcResultResource * * @param stmt the corresponding statement * @param rs the corresponding result set * @param conn the corresponding connection */ public JdbcResultResource(Env env, Statement stmt, ResultSet rs, JdbcConnectionResource conn) { _env = env; _stmt = stmt; _rs = rs; _conn = conn; } /** * Constructor for JdbcResultResource * * @param metaData the corresponding result set meta data * @param conn the corresponding connection */ public JdbcResultResource(Env env, ResultSetMetaData metaData, JdbcConnectionResource conn) { _env = env; _metaData = metaData; _conn = conn; _env = conn.getEnv(); } /** * Closes the result set. */ public void close() { try { ResultSet rs = _rs; _rs = null; Statement stmt = _stmt; _stmt = null; JdbcConnectionResource conn = _conn; _conn = null; if (rs != null) rs.close(); // XXX: statement no longer reused? if (stmt != null && conn != null) conn.closeStatement(stmt); _env = null; } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); } } /** * Fetch the next line as an array. * * @param type one of FETCH_ASSOC, FETCH_NUM, or FETCH_BOTH (default) * By using the FETCH_ASSOC constant this function will behave * identically to the mysqli_fetch_assoc(), while FETCH_NUM will * behave identically to the mysqli_fetch_row() function. The final * option FETCH_BOTH will create a single array with the attributes * of both. * * @return the next result row as an associative, * a numeric array, or both. */ public ArrayValue fetchArray(Env env, int type) { try { if (_rs == null) return null; if (_rs.next()) { _isValid = true; ArrayValue array = new ArrayValueImpl(); ResultSetMetaData md = getMetaData(); int count = md.getColumnCount(); if ((type & FETCH_ASSOC) != 0) { _columnNames = new Value[count]; for (int i = 0; i < count; i++) { String columnName = md.getColumnLabel(i + 1); _columnNames[i] = env.createString(columnName); } } for (int i = 0; i < count; i++) { Value value = getColumnValue(env, _rs, md, i + 1); if ((type & FETCH_NUM) != 0) array.put(LongValue.create(i), value); if ((type & FETCH_ASSOC) != 0) array.put(_columnNames[i], value); } return array; } else { return null; } } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return null; } } /** * Returns an associative array representing the row. * * @return an associative array representing the row * or null if there are no more rows in the result set */ public ArrayValue fetchAssoc(Env env) { return fetchArray(env, JdbcResultResource.FETCH_ASSOC); } /** * Returns an object with the following fields: name, table, max_length, * not_null, primary_key, multiple_key, numeric, * blob, type, unsigned, zerofill. * <p/> * NOTE: does not have a field for unique_key. * * @param env the PHP executing environment * @param maxLength the field maximum length * @param tableName the field table name * @param type the field type * @return the next field in the result set or * false if no information is available */ public Value fetchField(Env env, int maxLength, String tableName, String type) { if (_rs == null) return null; ObjectValue result = env.createObject(); try { if (! _isValid) { _isValid = true; _rs.next(); } result.putField(env, "name", env.createString(_rs.getString(1))); result.putField(env, "table", env.createString(tableName)); result.putField(env, "max_length", LongValue.create(maxLength)); if (! isInResultString(4, "YES")) result.putField(env, "not_null", LongValue.ONE); else result.putField(env, "not_null", LongValue.ZERO); if (isInResultString(5, "PRI")) result.putField(env, "primary_key", LongValue.ONE); else result.putField(env, "primary_key", LongValue.ZERO); if (isInResultString(5, "MUL")) result.putField(env, "multiple_key", LongValue.ONE); else result.putField(env, "multiple_key", LongValue.ZERO); if (isInResultString(2, "int") || isInResultString(2, "real")) result.putField(env, "numeric", LongValue.ONE); else result.putField(env, "numeric", LongValue.ZERO); if (isInResultString(2, "blob")) result.putField(env, "blob", LongValue.ONE); else result.putField(env, "blob", LongValue.ZERO); result.putField(env, "type", env.createString(type)); if (isInResultString(2, "unsigned")) result.putField(env, "unsigned", LongValue.ONE); else result.putField(env, "unsigned", LongValue.ZERO); if (isInResultString(2, "zerofill")) result.putField(env, "zerofill", LongValue.ONE); else result.putField(env, "zerofill", LongValue.ZERO); return result; } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } } /** * Returns an object with properties that correspond to the fetched row and * moves the internal data pointer ahead. * * @param env the PHP executing environment * @return an object representing the current fetched row */ public Value fetchObject(Env env) { if (_rs == null) return NullValue.NULL; try { if (_rs.next()) { _isValid = true; Value result = env.createObject(); ResultSetMetaData md = getMetaData(); int count = md.getColumnCount(); for (int i = 0; i < count; i++) { String name = md.getColumnLabel(i + 1); Value value = getColumnValue(env, _rs, md, i + 1); result.putField(env, name, value); } return result; } else { return NullValue.NULL; } } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return NullValue.NULL; } } /** * Returns an array representing the row. * * @return an array containing the fecthed row */ public ArrayValue fetchRow(Env env) { return fetchArray(env, JdbcResultResource.FETCH_NUM); } /** * Get the number of affected rows. * * @return the number of affected rows */ public int getAffectedRows() { return _affectedRows; } /** * Gets the column number based on a generic Value. * * @param fieldNameOrNumber the field index or it's name * @param base the numbering base: 0 or 1 (usually zero). * @return the column number (always 0-based) or -1 on error */ protected int getColumnNumber(Value fieldNameOrNumber, int base) throws SQLException { int fieldNumber = -1; if ((fieldNameOrNumber != null) && fieldNameOrNumber.isLongConvertible()) { // fieldNameOrNumber is the field number. // Convert it to 0-based. fieldNumber = fieldNameOrNumber.toInt() - base; } if (fieldNumber < 0) { // fieldNameOrNumber is the field name // Get column number (0-based). fieldNumber = getColumnNumber(fieldNameOrNumber.toString()); } return fieldNumber; } /** * Gets the column number. * * @return the column number (0-based) or -1 on error */ protected int getColumnNumber(String colName) throws SQLException { return getColumnNumber(colName, getMetaData()); } /** * Helper function for getResultField returns a 0-based column number * * @param colName the column name * @param rsmd the result set meta data * @return the column number (0-based) or -1 on error */ private int getColumnNumber(String colName, ResultSetMetaData rsmd) throws SQLException { int numColumns = rsmd.getColumnCount(); if (colName.indexOf('.') == -1) { for (int i = 1; i <= numColumns; i++) { if (colName.equals(rsmd.getColumnLabel(i))) return (i - 1); } return -1; } else { for (int i = 1; i <= numColumns; i++) { if (colName.equals(rsmd.getTableName(i) + '.' + rsmd.getColumnLabel(i))) return (i - 1); } return -1; } } /** * Get the column value in the specified result set. * * @param env the PHP executing environment * @param rs the result set * @param metaData the result set meta data * @param column the column number * @return the column value */ public Value getColumnValue(Env env, ResultSet rs, ResultSetMetaData metaData, int column) throws SQLException { // Note: typically, the PHP column value is returned as // a String, except for binary values. try { switch (metaData.getColumnType(column)) { case Types.NULL: return NullValue.NULL; case Types.BIT: { String typeName = metaData.getColumnTypeName(column); // Postgres matches BIT for BOOL columns if (! typeName.equals("bool")) { String value = rs.getString(column); if (rs.wasNull()) return NullValue.NULL; else return _env.createString(value); } // else fall to boolean } case Types.BOOLEAN: { boolean b = rs.getBoolean(column); if (rs.wasNull()) return NullValue.NULL; else return env.createString(b ? "t" : "f"); } case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: case Types.BIGINT: { long value = rs.getLong(column); if (rs.wasNull()) return NullValue.NULL; else return _env.createString(String.valueOf(value)); } case Types.REAL: case Types.DOUBLE: { double value = rs.getDouble(column); if (rs.wasNull()) return NullValue.NULL; else if (metaData.isCurrency(column)) { StringValue sb = _env.createUnicodeBuilder(); sb.append("$"); return sb.append(value); } else if (value == 0.0) { StringValue sb = _env.createUnicodeBuilder(); return sb.append("0"); } else { StringValue sb = _env.createUnicodeBuilder(); return sb.append(value); } } case Types.BLOB: { Object object = rs.getBlob(column); if (object.getClass().getName().equals("oracle.sql.BLOB")) { OracleOciLob ociLob = new OracleOciLob((Oracle) _conn, OracleModule.OCI_D_LOB); ociLob.setLob(object); object = ociLob; } return env.wrapJava(object); } case Types.CLOB: { Object object = rs.getClob(column); if (object.getClass().getName().equals("oracle.sql.CLOB")) { OracleOciLob ociLob = new OracleOciLob((Oracle) _conn, OracleModule.OCI_D_LOB); ociLob.setLob(object); object = ociLob; } return env.wrapJava(object); } case Types.LONGVARBINARY: case Types.VARBINARY: case Types.BINARY: { StringValue bb = env.createBinaryBuilder(); InputStream is = rs.getBinaryStream(column); if (is == null) // || rs.wasNull()) return NullValue.NULL; try { bb.appendReadAll(is, Long.MAX_VALUE / 2); } catch (RuntimeException e) { log.log(Level.WARNING, e.toString(), e); return NullValue.NULL; } return bb; } case Types.VARCHAR: case Types.LONGVARCHAR: if (env.isUnicodeSemantics()) return getUnicodeColumnString(env, rs, metaData, column); else return getColumnString(env, rs, metaData, column); case Types.TIME: return getColumnTime(env, rs, column); case Types.TIMESTAMP: return getColumnTimestamp(env, rs, column); case Types.DATE: return getColumnDate(env, rs, column); default: { String strValue = rs.getString(column); if (strValue == null) // || rs.wasNull()) return NullValue.NULL; else return env.createString(strValue); } } } catch (IOException e) { log.log(Level.FINE, e.toString(), e); return NullValue.NULL; } catch (SQLException e) { // php/141e log.log(Level.FINE, e.toString(), e); return NullValue.NULL; } } protected Value getUnicodeColumnString(Env env, ResultSet rs, ResultSetMetaData md, int column) throws IOException, SQLException { Reader reader = rs.getCharacterStream(column); if (reader == null) // || rs.wasNull()) return NullValue.NULL; StringValue bb = env.createUnicodeBuilder(); bb.append(reader); return bb; } protected Value getColumnString(Env env, ResultSet rs, ResultSetMetaData md, int column) throws SQLException { // php/1464, php/144f, php/144g // php/144b // calling getString() will decode using the database encoding, so // get bytes directly. Also, getBytes is faster for MySQL since // getString converts from bytes to string. byte []bytes = rs.getBytes(column); if (bytes == null) return NullValue.NULL; StringValue bb = env.createUnicodeBuilder(); bb.append(bytes); return bb; } protected Value getColumnTime(Env env, ResultSet rs, int column) throws SQLException { Time time = rs.getTime(column); if (time == null) return NullValue.NULL; else return env.createString(String.valueOf(time)); } protected Value getColumnDate(Env env, ResultSet rs, int column) throws SQLException { Date date = rs.getDate(column); if (date == null) return NullValue.NULL; else return env.createString(String.valueOf(date)); } protected Value getColumnTimestamp(Env env, ResultSet rs, int column) throws SQLException { try { Timestamp timestamp = rs.getTimestamp(column); if (timestamp == null) return NullValue.NULL; else { String time = String.valueOf(timestamp); // the .0 nanoseconds at the end may not matter, but strip it out // anyways to match php (postgresql) if (time.endsWith(".0")) time = time.substring(0, time.length() - 2); return env.createString(time); } } catch (SQLException e) { if (log.isLoggable(Level.FINER)) log.log(Level.FINER, e.toString(), e); // php/1f0a - mysql jdbc driver issue with zero timestamp return env.createString("0000-00-00 00:00:00"); } } /** * Get the connection corresponding to this result resource. * * @return a JDBC connection resource */ public JdbcConnectionResource getConnection() { return _conn; } /** * Get the field catalog name. * * @param fieldOffset the field number * @return the field catalog name */ public Value getFieldCatalog(int fieldOffset) { try { ResultSetMetaData md = getMetaData(); if (md.getColumnCount() <= fieldOffset || fieldOffset < 0) return BooleanValue.FALSE; else return _env.createString(md.getCatalogName(fieldOffset + 1)); } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } } /** * Returns column count. * * @return the number of columns in the result set */ public int getFieldCount() { try { if (getMetaData() != null) return getMetaData().getColumnCount(); else return -1; } catch (SQLException e) { throw new RuntimeException(e); } } /** * Return true is the field offset is valid, meaning it * is larger than 0 and is less that the max number * of fields in this result resource. */ protected boolean isValidFieldOffset(int fieldOffset) { try { ResultSetMetaData md = getMetaData(); if (fieldOffset < 0 || md.getColumnCount() <= fieldOffset) return false; else return true; } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return false; } } /** * Return true if the String result at the * given index of the ResultSet contains * the substring. */ protected boolean isInResultString(int columnIndex, String substring) throws SQLException { String resultString = _rs.getString(columnIndex); if (resultString == null) return false; int index = resultString.indexOf(substring); if (index == -1) return false; else return true; } /** * Get field length. This is the length of the field * as defined in the table declaration. * * @param env the PHP executing environment * @param fieldOffset the field number (0-based) * @return length of field for specified column */ public Value getFieldLength(Env env, int fieldOffset) { try { ResultSetMetaData md = getMetaData(); if (md.getColumnCount() <= fieldOffset || fieldOffset < 0) { env.invalidArgument("field", fieldOffset); return BooleanValue.FALSE; } else return LongValue.create((long) md.getPrecision(fieldOffset + 1)); } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } } /** * Returns the column name. * * @param env the PHP executing environment * @param fieldOffset 0-based field offset * * @return a StringValue containing the column name */ public Value getFieldName(Env env, int fieldOffset) { try { ResultSetMetaData md = getMetaData(); if (md.getColumnCount() <= fieldOffset || fieldOffset < 0) { env.invalidArgument("field", fieldOffset); return BooleanValue.FALSE; } else return env.createString(md.getColumnLabel(fieldOffset + 1)); } catch (Exception e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } } /** * Returns a StringValue containing the column Alias. * * @param fieldOffset 0-based field offset * * @return the column alias */ public Value getFieldNameAlias(int fieldOffset) { try { ResultSetMetaData md = getMetaData(); if (md.getColumnCount() <= fieldOffset || fieldOffset < 0) return BooleanValue.FALSE; else return _env.createString(md.getColumnLabel(fieldOffset + 1)); } catch (Exception e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } } /** * Returns the column name. * * @param env the PHP executing environment * @param fieldOffset 0-based field offset * * @return int(1) if the column is nullable, int(1) if it is not */ public Value getFieldNotNull(Env env, int fieldOffset) { try { ResultSetMetaData md = getMetaData(); if (md.getColumnCount() <= fieldOffset || fieldOffset < 0) { env.invalidArgument("field", fieldOffset); return BooleanValue.FALSE; } else if (md.isNullable(fieldOffset + 1) == ResultSetMetaData.columnNoNulls) return LongValue.ONE; else return LongValue.ZERO; } catch (Exception e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } } /** * Get field offset. * * @return the current field offset */ public int getFieldOffset() { return _fieldOffset; } /** * Get field scale. * * @param fieldOffset the field offset * @return number of digits to the right of the decimal point */ public Value getFieldScale(int fieldOffset) { try { ResultSetMetaData md = getMetaData(); if (md.getColumnCount() <= fieldOffset || fieldOffset < 0) return BooleanValue.FALSE; else return LongValue.create((long) md.getScale(fieldOffset + 1)); } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } } /** * Returns the table corresponding to the field. * * @param env the PHP executing environment * @param fieldOffset the field number * @return the field table name */ public Value getFieldTable(Env env, int fieldOffset) { try { ResultSetMetaData md = getMetaData(); if (md.getColumnCount() <= fieldOffset || fieldOffset < 0) { env.invalidArgument("field", fieldOffset); return BooleanValue.FALSE; } else { String tableName = md.getTableName(fieldOffset + 1); if (tableName == null || tableName.equals("")) return BooleanValue.FALSE; else return env.createString(tableName); } } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } } /** * Returns the table corresponding to the field. * * @param env the PHP executing environment * @param fieldOffset the field number * @return the field table name */ public Value getFieldSchema(Env env, int fieldOffset) { try { ResultSetMetaData md = getMetaData(); if (md.getColumnCount() <= fieldOffset || fieldOffset < 0) { env.invalidArgument("schema", fieldOffset); return BooleanValue.FALSE; } else { String tableName = md.getSchemaName(fieldOffset + 1); if (tableName == null || tableName.equals("")) return BooleanValue.FALSE; else return env.createString(tableName); } } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } } /** * Get a StringValue with the column type. * * @param env the PHP executing environment * @param fieldOffset need to add 1 because java * is 1 based index and quercus is 0 based * * @return a StringValue containing the column type */ public Value getFieldType(Env env, int fieldOffset) { try { ResultSetMetaData md = getMetaData(); if (md.getColumnCount() <= fieldOffset || fieldOffset < 0) { env.invalidArgument("field", fieldOffset); return BooleanValue.FALSE; } else { int jdbcType = md.getColumnType(fieldOffset + 1); return env.createString(getFieldType(fieldOffset, jdbcType)); } } catch (Exception e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } } /** * Given the JDBC type of the field at the given offset, * return a PHP type string. */ protected String getFieldType(int fieldOffset, int jdbcType) { switch (jdbcType) { case Types.BIGINT: case Types.BIT: case Types.INTEGER: case Types.SMALLINT: case Types.TINYINT: return INTEGER; case Types.LONGVARBINARY: case Types.LONGVARCHAR: return BLOB; case Types.CHAR: case Types.VARCHAR: case Types.BINARY: case Types.VARBINARY: return STRING; case Types.TIME: return TIME; case Types.DATE: return DATE; case Types.TIMESTAMP: return DATETIME; case Types.DECIMAL: case Types.DOUBLE: case Types.REAL: return REAL; default: return UNKNOWN; } } /** * Returns the underlying SQL statement * associated to this result resource. */ protected Statement getJavaStatement() { return _conn.getEnv().getQuercus().getStatement(getStatement()); } /** * Get type from Types enumeration * * @param fieldOffset the field number (0-based) * @return the JDBC type */ protected Value getJdbcType(int fieldOffset) { try { ResultSetMetaData md = getMetaData(); return LongValue.create(md.getColumnType(fieldOffset + 1)); } catch (Exception e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } } /** * Returns an ArrayValue column lengths in the most * recently accessed row. If a fetch function has not * yet been called this will return BooleanValue.FALSE * * @return an ArrayValue of column lengths in the most * recently accessed row */ public Value getLengths() { Value result; ArrayValue array = new ArrayValueImpl(); try { ResultSetMetaData md = getMetaData(); int numColumns = md.getColumnCount(); for (int i = 1; i <= numColumns; i++) { array.put(LongValue.create(_rs.getObject(i).toString().length())); } result = array; } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } return result; } /** * Get the result set meta data. * * @return the meta data for this result set */ public ResultSetMetaData getMetaData() throws SQLException { if (_metaData != null) return _metaData; /* if (_rs != null && ! _isValid) { if (! _rs.next()) return null; _isValid = true; } */ if (_metaData == null && _rs != null) _metaData = _rs.getMetaData(); return _metaData; } /** * Returns the number of columns returned in query. * * @return the number of columns for this result set */ public Value getNumFields() { try { Value result = NullValue.NULL; ResultSetMetaData md = getMetaData(); int count = md.getColumnCount(); if (count != 0) { result = LongValue.create((long) count); } return result; } catch (Exception e) { log.log(Level.FINE, e.toString(), e); return NullValue.NULL; } } /** * Get the number of rows in this result set. * * @return the number of rows in this result set */ public int getNumRows() { return getNumRows(_rs); } /** * Returns number of rows returned in query. * last() call is efficient for Mysql because the driver just adjusts * the result index. It is very inefficient for Postgres because that * driver iterates over the result set. * * @param rs a result set * @return the number of rows in the specified result set */ public static int getNumRows(ResultSet rs) { if (rs == null) return -1; try { int currentRow = rs.getRow(); try { rs.last(); return rs.getRow(); } catch (Exception e) { log.log(Level.FINE, e.toString(), e); return -1; } finally { if (currentRow == 0) rs.beforeFirst(); else rs.absolute(currentRow); } } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return -1; } } /** * Returns the value at a particular row and column. * * @param env the PHP executing environment * @param row a particular row to get the field value from * @param field the field name or number * @return the value of the specified field */ public Value getResultField(Env env, int row, Value field) { try { ResultSetMetaData md = getMetaData(); int colNumber; if (field.isNumberConvertible()) colNumber = field.toInt(); else colNumber = getColumnNumber(field.toString(), md); if (colNumber < 0 || colNumber >= md.getColumnCount()) { env.invalidArgument("field", field); return BooleanValue.FALSE; } int currentRow = _rs.getRow(); if ((row < 0) || (!_rs.absolute(row + 1)) || _rs.isAfterLast()) { if (currentRow > 0) _rs.absolute(currentRow); else _rs.beforeFirst(); env.invalidArgument("row", row); return BooleanValue.FALSE; } return getColumnValue(env, _rs, md, colNumber + 1); } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return BooleanValue.FALSE; } } /** * Get the underlying result set. * * @return the underlying ResultSet object */ public ResultSet getResultSet() { return _rs; } /** * Get the underlying statement. * * @return the underlying Statement object */ public Statement getStatement() { return _stmt; } /** * Seeks to an arbitrary result pointer specified * by the offset in the result set represented by result. * Returns TRUE on success or FALSE on failure * * @param env the PHP executing environment * @param rowNumber the row offset * @return true on success or false on failure */ public boolean seek(Env env, int rowNumber) { if (setRowNumber(rowNumber)) return true; return false; } /** * Set the number of affected rows to the specified value. * * @param affectedRows the new number of affected rows */ public void setAffectedRows(int affectedRows) { _affectedRows = affectedRows; } /** * Set a value for field offset. This method will * return true when the field offset is valid, * otherwise it will set the field offset * to the invalid value and return false. * * @param fieldOffset PHP is 0-based */ public boolean setFieldOffset(int fieldOffset) { _fieldOffset = fieldOffset; if (fieldOffset < 0 || fieldOffset >= getNumFields().toInt()) return false; else return true; } /** * Points to the row right before "rowNumber". * Next fetchArray will increment to proper row. * * @param rowNumber the row offset * @return true on success or false on failure */ public boolean setRowNumber(int rowNumber) { return setRowNumber(_rs, rowNumber); } /** * Points to the row right before "rowNumber". * Next fetchArray will increment to proper row. * * @param rs the result set to move the row pointer * @param rowNumber the row offset * @return true on success or false on failure */ public static boolean setRowNumber(ResultSet rs, int rowNumber) { // throw error if rowNumber is after last row int numRows = getNumRows(rs); if (numRows <= rowNumber || rowNumber < 0) { return false; } try { if (rowNumber == 0) rs.beforeFirst(); else rs.absolute(rowNumber); } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); return false; } return true; } /** * Convert this JDBC result resource to a hash code. * * @return a hash code of this JDBC result resource */ public Value toKey() { // XXX: phpbb seems to want this? return _env .createString("JdbcResultResource$" + System.identityHashCode(this)); } /** * Returns a string representation for this object. * * @return a string representation for this object */ public String toString() { if (_rs != null) return getClass() .getSimpleName() + "[" + _rs.getClass().getSimpleName() + "]"; else return getClass().getSimpleName() + "[]"; } /** * Validate this result set and return it. * * @return the validated result set */ public JdbcResultResource validateResult() { return this; } }