/*
* Copyright (c) 1998-2008 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 Sam
*/
package com.caucho.quercus.lib.db;
import com.caucho.quercus.UnimplementedException;
import com.caucho.quercus.annotation.Optional;
import com.caucho.quercus.annotation.ReadOnly;
import com.caucho.quercus.annotation.Reference;
import com.caucho.quercus.env.*;
import com.caucho.quercus.lib.file.FileReadValue;
import com.caucho.util.IntMap;
import com.caucho.util.L10N;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.TempBuffer;
import com.caucho.vfs.TempReadStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* PDO object oriented API facade.
*/
public class PDOStatement
implements Iterable<Value>, EnvCleanup
{
private static final Logger log = Logger.getLogger(PDOStatement.class.getName());
private static final L10N L = new L10N(PDOStatement.class);
private static final Value[] NULL_VALUES = new Value[0];
//private static final Value FETCH_FAILURE = new NullValue() {};
//private static final Value FETCH_EXHAUSTED = new NullValue() {};
//private static final Value FETCH_CONTINUE = new NullValue() {};
//private static final Value FETCH_SUCCESS = new NullValue() {};
private static final int FETCH_FAILURE = 0;
private static final int FETCH_EXHAUSTED = 1;
private static final int FETCH_CONTINUE = 2;
private static final int FETCH_SUCCESS = 3;
private int _fetchErrorCode;
private final Env _env;
private final PDOError _error;
private final String _query;
private Statement _statement;
private PreparedStatement _preparedStatement;
private ResultSet _resultSet;
private ResultSetMetaData _resultSetMetaData;
private boolean _resultSetExhausted = true;
private String _lastInsertId;
private int _fetchMode = PDO.FETCH_BOTH;
private Value[] _fetchModeArgs = NULL_VALUES;
private ArrayList<BindColumn> _bindColumns;
private ArrayList<BindParam> _bindParams;
private IntMap _parameterNameMap;
PDOStatement(Env env, Connection conn,
String query, boolean isPrepared,
ArrayValue options)
throws SQLException
{
_env = env;
_error = new PDOError(_env);
_query = query;
env.addCleanup(this);
if (options != null && options.getSize() > 0) {
_env.notice(L.l("PDOStatement options unsupported"));
}
query = parseQueryString(query);
if (isPrepared) {
_statement = null;
int ch;
if (query.length() > 4
&& ((ch = query.charAt(0)) == 'c' || ch == 'C')
&& ((ch = query.charAt(1)) == 'a' || ch == 'A')
&& ((ch = query.charAt(2)) == 'l' || ch == 'L')
&& ((ch = query.charAt(3)) == 'l' || ch == 'L')) {
_preparedStatement = conn.prepareCall(query);
}
else
_preparedStatement = conn.prepareStatement(query);
// php/1s41 - oracle can't handle this
//_preparedStatement.setEscapeProcessing(false);
}
else {
_preparedStatement = null;
Statement statement = null;
try {
statement = conn.createStatement();
statement.setEscapeProcessing(false);
if (statement.execute(query)) {
_resultSet = statement.getResultSet();
_resultSetExhausted = false;
}
_statement = statement;
statement = null;
} finally {
try {
if (statement != null)
statement.close();
} catch (SQLException e) {
log.log(Level.FINE, e.toString(), e);
}
}
}
}
// side-effect, updates _parameterNameMap
private String parseQueryString(String query)
{
final int queryLength = query.length();
StringBuilder parsedQuery = new StringBuilder(queryLength);
int parameterCount = 0;
StringBuilder name = null;
int quote = 0;
for (int i = 0; i < queryLength + 1; i++) {
int ch = -1;
if (i < queryLength)
ch = query.charAt(i);
if (ch == '\'' || ch == '"') {
if (quote == 0)
quote = ch;
else if (quote == ch)
quote = 0;
}
else if (quote == 0 && ch == '?') {
parameterCount++;
}
else if (quote == 0 && ch == ':') {
parameterCount++;
name = new StringBuilder();
continue;
}
// XXX: check what characters are allowed
else if (name != null && (ch == -1 || !Character.isJavaIdentifierPart(ch))) {
if (_parameterNameMap == null)
_parameterNameMap = new IntMap();
_parameterNameMap.put(name.toString(), parameterCount);
parsedQuery.append('?');
name = null;
}
if (ch != -1) {
if (name != null)
name.append((char) ch);
else
parsedQuery.append((char) ch);
}
}
return parsedQuery.toString();
}
private boolean advanceResultSet()
{
if (_resultSet == null || _resultSetExhausted)
return false;
try {
boolean isNext = _resultSet.next();
if (!isNext)
_resultSetExhausted = true;
if (!isNext)
return false;
if (_bindColumns != null) {
for (BindColumn bindColumn : _bindColumns)
if (!bindColumn.bind())
return false;
}
return isNext;
}
catch (SQLException ex) {
_error.error(ex);
return false;
}
}
public boolean bindColumn(Value column, @Reference Value var, @Optional("-1") int type)
{
if (_bindColumns == null)
_bindColumns = new ArrayList<BindColumn>();
try {
_bindColumns.add(new BindColumn(column, var, type));
}
catch (SQLException ex) {
_error.error(ex);
return false;
}
return true;
}
public boolean bindParam(Value parameter,
@Reference Value variable,
@Optional("-1") int dataType,
@Optional("-1") int length,
@Optional Value driverOptions)
{
if (length != -1)
throw new UnimplementedException("length");
if (!(driverOptions == null || driverOptions.isNull()))
throw new UnimplementedException("driverOptions");
if (dataType == -1)
dataType = PDO.PARAM_STR;
boolean isInputOutput = (dataType & PDO.PARAM_INPUT_OUTPUT) != 0;
if (isInputOutput) {
dataType = dataType & (~PDO.PARAM_INPUT_OUTPUT);
if (true) throw new UnimplementedException("PARAM_INPUT_OUTPUT");
}
switch (dataType) {
case PDO.PARAM_BOOL:
case PDO.PARAM_INT:
case PDO.PARAM_LOB:
case PDO.PARAM_NULL:
case PDO.PARAM_STMT:
case PDO.PARAM_STR:
break;
default:
_error.warning(L.l("unknown dataType `{0}'", dataType));
return false;
}
if (_bindParams == null)
_bindParams = new ArrayList<BindParam>();
BindParam bindParam = new BindParam(parameter, variable, dataType, length, driverOptions);
_bindParams.add(bindParam);
return true;
}
public boolean bindValue(Value parameter,
Value value,
@Optional("-1") int dataType)
{
return bindParam(parameter, value.toValue(), dataType, -1, null);
}
/**
* Closes the current cursor.
*/
public boolean closeCursor()
{
if (_resultSet == null)
return false;
ResultSet resultSet = _resultSet;
_resultSet = null;
_resultSetMetaData = null;
_resultSetExhausted = true;
_lastInsertId = null;
try {
resultSet.close();
}
catch (SQLException e) {
_error.error(e);
return false;
}
return true;
}
/**
* Returns the number of columns.
*/
public int columnCount()
{
if (_resultSet == null)
return 0;
try {
return getResultSetMetaData().getColumnCount();
}
catch (SQLException e) {
_error.error(e);
return 0;
}
}
public BindParam createBindParam(Value parameter, Value value, int dataType, int length, Value driverOptions)
{
return new BindParam(parameter, value, dataType, length, driverOptions);
}
public void close()
{
cleanup();
}
/**
* Implements the EnvCleanup interface.
*/
public void cleanup()
{
ResultSet resultSet = _resultSet;
Statement statement = _statement;
PreparedStatement preparedStatement = _preparedStatement;
_resultSet = null;
_resultSetMetaData = null;
_resultSetExhausted = true;
_lastInsertId = null;
_statement = null;
_preparedStatement = null;
if (resultSet != null) {
try {
resultSet.close();
}
catch (SQLException e) {
log.log(Level.WARNING, e.toString(), e);
}
}
if (statement != null) {
try {
statement.close();
}
catch (SQLException e) {
log.log(Level.WARNING, e.toString(), e);
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
}
catch (SQLException e) {
log.log(Level.WARNING, e.toString(), e);
}
}
}
public String errorCode()
{
return _error.errorCode();
}
public ArrayValue errorInfo()
{
return _error.errorInfo();
}
/**
* Execute the statement.
*
* @param inputParameters an array containing input values to correspond to
* the bound parameters for the statement.
*
* @return true for success, false for failure
*/
public boolean execute(@Optional @ReadOnly Value inputParameters)
{
// XXX: s/b to do this with ArrayValue arg, but cannot differentiate between
// no args and bad arg that isn't an ArrayValue
ArrayValue parameters;
if (inputParameters instanceof ArrayValue)
parameters = (ArrayValue) inputParameters;
else if (inputParameters instanceof DefaultValue)
parameters = null;
else {
_env.warning(L.l("'{0}' is an unexpected argument, expected ArrayValue", inputParameters));
return false;
}
closeCursor();
try {
_preparedStatement.clearParameters();
_preparedStatement.clearWarnings();
if (parameters != null) {
for (Map.Entry<Value, Value> entry : parameters.entrySet()) {
Value key = entry.getKey();
if (key.isNumberConvertible()) {
if (! setParameter(key.toInt() + 1, entry.getValue(), -1))
return false;
}
else {
if (! setParameter(resolveParameter(key), entry.getValue(), -1))
return false;
}
}
}
else if (_bindParams != null) {
for (BindParam bindParam : _bindParams) {
if (!bindParam.apply())
return false;
}
}
if (_preparedStatement.execute()) {
_resultSet = _preparedStatement.getResultSet();
_resultSetExhausted = false;
}
SQLWarning sqlWarning = _preparedStatement.getWarnings();
if (sqlWarning != null) {
_error.error(sqlWarning);
return false;
}
return true;
} catch (SQLException e) {
_error.error(e);
return false;
}
}
/**
* Fetch the next row.
*
* @param fetchMode the mode, 0 to use the value set by {@link #setFetchMode}.
* @return a value, BooleanValue.FALSE if there are no more rows or an error occurs.
*/
public Value fetch(@Optional int fetchMode,
@Optional("-1") int cursorOrientation,
@Optional("-1") int cursorOffset)
{
if (cursorOrientation != -1)
throw new UnimplementedException("fetch with cursorOrientation");
if (cursorOffset != -1)
throw new UnimplementedException("fetch with cursorOffset");
return fetchImpl(fetchMode, -1);
}
/**
*
* @param fetchMode
* @param columnIndex 0-based column index when fetchMode is FETCH_BOTH
*/
public Value fetchAll(@Optional("0") int fetchMode, @Optional("-1") int columnIndex)
{
int effectiveFetchMode;
if (fetchMode == 0) {
effectiveFetchMode = _fetchMode;
}
else {
effectiveFetchMode = fetchMode;
}
boolean isGroup = (fetchMode & PDO.FETCH_GROUP) != 0;
boolean isUnique = (fetchMode & PDO.FETCH_UNIQUE) != 0;
if (isGroup)
throw new UnimplementedException("PDO.FETCH_GROUP");
if (isUnique)
throw new UnimplementedException("PDO.FETCH_UNIQUE");
effectiveFetchMode = effectiveFetchMode & (~(PDO.FETCH_GROUP | PDO.FETCH_UNIQUE));
switch (effectiveFetchMode) {
case PDO.FETCH_COLUMN:
break;
case PDO.FETCH_LAZY:
_error.warning(L.l("PDO::FETCH_LAZY can't be used with PDOStatement::fetchAll()"));
return BooleanValue.FALSE;
default:
if (columnIndex != -1) {
_error.warning(L.l("unexpected arguments"));
return BooleanValue.FALSE;
}
}
ArrayValueImpl rows = new ArrayValueImpl();
while (true) {
Value value = fetchImpl(effectiveFetchMode, columnIndex);
if (_fetchErrorCode == FETCH_FAILURE) {
rows.clear();
return rows;
}
if (_fetchErrorCode == FETCH_EXHAUSTED)
break;
if (_fetchErrorCode == FETCH_CONTINUE)
continue;
rows.put(value);
}
return rows;
}
private Value fetchAssoc()
{
try {
if (!advanceResultSet()) {
_fetchErrorCode = FETCH_EXHAUSTED;
return BooleanValue.FALSE;
}
if (_fetchModeArgs.length != 0) {
_error.notice(L.l("unexpected arguments"));
_fetchErrorCode = FETCH_FAILURE;
return BooleanValue.FALSE;
}
ArrayValueImpl array = new ArrayValueImpl();
int columnCount = getResultSetMetaData().getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String name = getResultSetMetaData().getColumnName(i);
Value value = getColumnValue(i);
array.put(_env.createString(name, "UTF-8" /* XXX */), value);
}
return array;
}
catch (SQLException ex) {
_error.error(ex);
_fetchErrorCode = FETCH_FAILURE;
return BooleanValue.FALSE;
}
}
private Value fetchBoth()
{
try {
if (! advanceResultSet()) {
_fetchErrorCode = FETCH_EXHAUSTED;
return BooleanValue.FALSE;
}
if (_fetchModeArgs.length != 0) {
_error.notice(L.l("unexpected arguments"));
_fetchErrorCode = FETCH_FAILURE;
return BooleanValue.FALSE;
}
ArrayValueImpl array = new ArrayValueImpl();
int columnCount = getResultSetMetaData().getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String name = getResultSetMetaData().getColumnName(i);
Value value = getColumnValue(i);
array.put(_env.createString(name, "UTF-8" /* XXX */), value);
array.put(LongValue.create(i - 1), value);
}
return array;
}
catch (SQLException ex) {
_error.error(ex);
_fetchErrorCode = FETCH_FAILURE;
return BooleanValue.FALSE;
}
}
private Value fetchBound()
{
if (!advanceResultSet()) {
_fetchErrorCode = FETCH_EXHAUSTED;
return BooleanValue.FALSE;
}
_fetchErrorCode = FETCH_SUCCESS;
return BooleanValue.TRUE;
}
private Value fetchClass()
{
String className;
Value[] ctorArgs;
if (_fetchModeArgs.length == 0 || _fetchModeArgs.length > 2)
return fetchBoth();
className = _fetchModeArgs[0].toString();
if (_fetchModeArgs.length == 2) {
if (_fetchModeArgs[1].isArray()) {
// XXX: inefiicient, but args[1].getValueArray(_env) doesn't handle references
ArrayValue argsArray = (ArrayValue) _fetchModeArgs[1];
ctorArgs = new Value[argsArray.getSize()];
int i = 0;
for (Value key : argsArray.keySet())
ctorArgs[i++] = argsArray.getRef(key);
}
else
return fetchBoth();
}
else
ctorArgs = NULL_VALUES;
return fetchObject(className, ctorArgs);
}
/**
* @param column 0-based column number
*/
public Value fetchColumn(@Optional int column)
{
if (!advanceResultSet()) {
_fetchErrorCode = FETCH_EXHAUSTED;
return BooleanValue.FALSE;
}
if (column < 0 && _fetchModeArgs.length > 0)
column = _fetchModeArgs[0].toInt();
try {
if (column < 0 || column >= getResultSetMetaData().getColumnCount()) {
_fetchErrorCode = FETCH_CONTINUE;
return BooleanValue.FALSE;
}
return getColumnValue(column + 1);
}
catch (SQLException ex) {
_error.error(ex);
_fetchErrorCode = FETCH_FAILURE;
return BooleanValue.FALSE;
}
}
private Value fetchFunc()
{
throw new UnimplementedException();
}
/**
* Fetch the next row.
*
* @param fetchMode the mode, 0 to use the value set by {@link #setFetchMode}.
* @return a value, BooleanValue.FALSE if there are no more rows or an error occurs.
*/
private Value fetchImpl(int fetchMode, int columnIndex)
{
if (fetchMode == 0) {
fetchMode = _fetchMode;
fetchMode = fetchMode & (~(PDO.FETCH_GROUP | PDO.FETCH_UNIQUE));
}
else {
if ((fetchMode & PDO.FETCH_GROUP) != 0) {
_error.warning(L.l("FETCH_GROUP is not allowed"));
return BooleanValue.FALSE;
}
else if ((fetchMode & PDO.FETCH_UNIQUE) != 0) {
_error.warning(L.l("FETCH_UNIQUE is not allowed"));
return BooleanValue.FALSE;
}
}
fetchMode = fetchMode & (~(PDO.FETCH_CLASSTYPE | PDO.FETCH_SERIALIZE));
_fetchErrorCode = FETCH_SUCCESS;
switch (fetchMode) {
case PDO.FETCH_ASSOC:
return fetchAssoc();
case PDO.FETCH_BOTH:
return fetchBoth();
case PDO.FETCH_BOUND:
return fetchBound();
case PDO.FETCH_COLUMN:
return fetchColumn(columnIndex);
case PDO.FETCH_CLASS:
return fetchClass();
case PDO.FETCH_FUNC:
return fetchFunc();
case PDO.FETCH_INTO:
return fetchInto();
case PDO.FETCH_LAZY:
return fetchLazy();
case PDO.FETCH_NAMED:
return fetchNamed();
case PDO.FETCH_NUM:
return fetchNum();
case PDO.FETCH_OBJ:
return fetchObject();
default:
_error.warning(L.l("invalid fetch mode {0}", fetchMode));
closeCursor();
return BooleanValue.FALSE;
}
}
private Value fetchInto()
{
assert _fetchModeArgs.length > 0;
assert _fetchModeArgs[0].isObject();
Value var = _fetchModeArgs[0];
if (!advanceResultSet()) {
_fetchErrorCode = FETCH_EXHAUSTED;
return BooleanValue.FALSE;
}
try {
int columnCount = getResultSetMetaData().getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String name = getResultSetMetaData().getColumnName(i);
Value value = getColumnValue(i);
var.putField(_env, name, value);
}
}
catch (SQLException ex) {
_error.error(ex);
_fetchErrorCode = FETCH_FAILURE;
return BooleanValue.FALSE;
}
return var;
}
private Value fetchLazy()
{
// XXX: need to check why lazy is no different than object
return fetchObject(null, NULL_VALUES);
}
private Value fetchNamed()
{
try {
if (!advanceResultSet()) {
_fetchErrorCode = FETCH_EXHAUSTED;
return BooleanValue.FALSE;
}
ArrayValue array = new ArrayValueImpl();
int columnCount = getResultSetMetaData().getColumnCount();
for (int i = 1; i <= columnCount; i++) {
Value name = _env.createString(getResultSetMetaData().getColumnName(i), "UTF-8" /* XXX */);
Value value = getColumnValue(i);
Value existingValue = array.get(name);
if (! (existingValue instanceof UnsetValue)) {
if (! existingValue.isArray()) {
ArrayValue arrayValue = new ArrayValueImpl();
arrayValue.put(existingValue);
array.put(name, arrayValue);
existingValue = arrayValue;
}
existingValue.put(value);
}
else
array.put(name, value);
}
return array;
}
catch (SQLException ex) {
_error.error(ex);
_fetchErrorCode = FETCH_FAILURE;
return BooleanValue.FALSE;
}
}
private Value fetchNum()
{
try {
if (!advanceResultSet()) {
_fetchErrorCode = FETCH_EXHAUSTED;
return BooleanValue.FALSE;
}
if (_fetchModeArgs.length != 0) {
_error.notice(L.l("unexpected arguments"));
_fetchErrorCode = FETCH_FAILURE;
return BooleanValue.FALSE;
}
ArrayValueImpl array = new ArrayValueImpl();
int columnCount = getResultSetMetaData().getColumnCount();
for (int i = 1; i <= columnCount; i++) {
Value value = getColumnValue(i);
array.put(value);
}
return array;
}
catch (SQLException ex) {
_error.error(ex);
_fetchErrorCode = FETCH_FAILURE;
return BooleanValue.FALSE;
}
}
private Value fetchObject()
{
return fetchObject(null, NULL_VALUES);
}
public Value fetchObject(@Optional String className, @Optional Value[] args)
{
QuercusClass cl;
if (className != null) {
cl = _env.findAbstractClass(className);
if (cl == null)
return fetchBoth();
}
else {
cl = null;
if (args.length != 0) {
advanceResultSet();
_error.warning(L.l("unexpected arguments"));
return BooleanValue.FALSE;
}
}
if (!advanceResultSet()) {
_fetchErrorCode = FETCH_EXHAUSTED;
return BooleanValue.FALSE;
}
try {
Value object;
if (cl != null)
object = cl.callNew(_env, args);
else
object = _env.createObject();
int columnCount = getResultSetMetaData().getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String name = getResultSetMetaData().getColumnName(i);
Value value = getColumnValue(i);
object.putField(_env, name, value);
}
return object;
}
catch (Throwable ex) {
_error.error(ex);
_fetchErrorCode = FETCH_FAILURE;
return BooleanValue.FALSE;
}
}
public Value getAttribute(int attribute)
{
_error.unsupportedAttribute(attribute);
return BooleanValue.FALSE;
}
/**
* @param column 0-based column index
*/
public Value getColumnMeta(int column)
{
throw new UnimplementedException();
}
/**
* @param column 1-based column index
*/
private Value getColumnValue(int column)
throws SQLException
{
return getColumnValue(column, -1, -1);
}
/**
* @param column 1-based column index
* @param jdbcType a jdbc type, or -1 if it is unknown
* @param returnType a PDO.PARAM_* type, or -1
*/
private Value getColumnValue(int column, int jdbcType, int returnType)
throws SQLException
{
if (returnType != -1)
throw new UnimplementedException("parm type " + returnType);
if (jdbcType == -1)
jdbcType = getResultSetMetaData().getColumnType(column);
// XXX: needs tests
switch (jdbcType) {
case Types.NULL:
return NullValue.NULL;
case Types.BIT:
case Types.TINYINT:
case Types.SMALLINT:
case Types.INTEGER:
case Types.BIGINT:
{
String value = _resultSet.getString(column);
if (value == null || _resultSet.wasNull())
return NullValue.NULL;
else
return _env.createString(value, "UTF-8" /* XXX */);
}
case Types.DOUBLE:
{
double value = _resultSet.getDouble(column);
if (_resultSet.wasNull())
return NullValue.NULL;
else
return (new DoubleValue(value)).toStringValue(_env);
}
// XXX: lob
default:
{
String value = _resultSet.getString(column);
if (value == null || _resultSet.wasNull())
return NullValue.NULL;
else
return _env.createString(value, "UTF-8" /* XXX */);
}
}
}
private ResultSetMetaData getResultSetMetaData()
throws SQLException
{
if (_resultSetMetaData == null)
_resultSetMetaData = _resultSet.getMetaData();
return _resultSetMetaData;
}
/**
* Returns an iterator of the values.
*/
public Iterator<Value> iterator()
{
Value value = fetchAll(0, -1);
if (value instanceof ArrayValue)
return ((ArrayValue) value).values().iterator();
else {
Set<Value> emptySet = Collections.emptySet();
return emptySet.iterator();
}
}
String lastInsertId(String name)
{
if (!(name == null || name.length() == 0))
throw new UnimplementedException("lastInsertId with name ");
if (_lastInsertId != null)
return _lastInsertId;
String lastInsertId = null;
Statement stmt;
if (_preparedStatement != null)
stmt = _preparedStatement;
else
stmt = _statement;
ResultSet resultSet = null;
try {
resultSet = stmt.getGeneratedKeys();
if (resultSet.next())
lastInsertId = resultSet.getString(1);
}
catch (SQLException ex) {
_error.error(ex);
}
finally {
try {
if (resultSet != null)
resultSet.close();
}
catch (SQLException ex) {
log.log(Level.WARNING, ex.toString(), ex);
}
}
_lastInsertId = lastInsertId == null ? "0" : lastInsertId;
return _lastInsertId;
}
public boolean nextRowset()
{
throw new UnimplementedException();
}
private int resolveParameter(Value parameter)
{
int index = -1;
if (parameter instanceof LongValue) {
// slight optimization for normal case
index = parameter.toInt();
}
else {
String name = parameter.toString();
if (name.length() > 1 && name.charAt(0) == ':') {
name = name.substring(1);
if (_parameterNameMap != null)
index = _parameterNameMap.get(name);
}
else
index = parameter.toInt();
}
return index;
}
public int rowCount()
{
if (_resultSet == null)
return 0;
try {
int row = _resultSet.getRow();
try {
_resultSet.last();
return _resultSet.getRow();
}
finally {
if (row == 0)
_resultSet.beforeFirst();
else
_resultSet.absolute(row);
}
}
catch (SQLException ex) {
_error.error(ex);
return 0;
}
}
public boolean setAttribute(int attribute, Value value)
{
return setAttribute(attribute, value, false);
}
public boolean setAttribute(int attribute, Value value, boolean isFromConstructor)
{
if (isFromConstructor) {
switch (attribute) {
case PDO.CURSOR_FWDONLY:
case PDO.CURSOR_SCROLL:
return setCursor(attribute);
}
}
_error.unsupportedAttribute(attribute);
return false;
}
private boolean setCursor(int attribute)
{
switch (attribute) {
case PDO.CURSOR_FWDONLY:
throw new UnimplementedException();
case PDO.CURSOR_SCROLL:
throw new UnimplementedException();
default:
_error.unsupportedAttribute(attribute);
return false;
}
}
/**
* Sets the fetch mode, the default is {@link PDO.FETCH_BOTH}.
*/
public boolean setFetchMode(int fetchMode, Value[] args)
{
_fetchMode = PDO.FETCH_BOTH;
_fetchModeArgs = NULL_VALUES;
int fetchStyle = fetchMode;
boolean isGroup = (fetchMode & PDO.FETCH_GROUP) != 0;
boolean isUnique = (fetchMode & PDO.FETCH_UNIQUE) != 0;
if (isGroup)
throw new UnimplementedException("PDO.FETCH_GROUP");
if (isUnique)
throw new UnimplementedException("PDO.FETCH_UNIQUE");
fetchStyle = fetchStyle & (~(PDO.FETCH_GROUP | PDO.FETCH_UNIQUE));
fetchStyle = fetchStyle & (~(PDO.FETCH_CLASSTYPE | PDO.FETCH_SERIALIZE));
switch (fetchStyle) {
case PDO.FETCH_ASSOC:
case PDO.FETCH_BOTH:
case PDO.FETCH_BOUND:
case PDO.FETCH_LAZY:
case PDO.FETCH_NAMED:
case PDO.FETCH_NUM:
case PDO.FETCH_OBJ:
break;
case PDO.FETCH_CLASS:
if (args.length < 1 || args.length > 2)
return false;
if (_env.findClass(args[0].toString()) == null)
return false;
if (args.length == 2 && !(args[1].isNull() || args[1].isArray())) {
_env.warning(L.l("constructor args must be an array"));
return false;
}
break;
case PDO.FETCH_COLUMN:
if (args.length != 1)
return false;
break;
case PDO.FETCH_FUNC:
_error.warning(L.l("PDO::FETCH_FUNC can only be used with PDOStatement::fetchAll()"));
return false;
case PDO.FETCH_INTO:
if (args.length != 1 || !args[0].isObject())
return false;
break;
default:
_error.warning(L.l("invalid fetch mode"));
break;
}
_fetchModeArgs = args;
_fetchMode = fetchMode;
return true;
}
/**
* @param index 1-based position number
* @param value the value for the parameter
*
* @return true for success, false for failure
*/
private boolean setLobParameter(int index, Value value, long length)
{
try {
if (value == null || value.isNull()) {
_preparedStatement.setObject(index, null);
}
else if (value instanceof StringValue) {
if (length < 0) {
_preparedStatement.setBinaryStream(index, value.toInputStream(), value.toString().length());
}
else
_preparedStatement.setBinaryStream(index, value.toInputStream(), (int) length);
}
else {
InputStream inputStream = value.toInputStream();
if (inputStream == null) {
_error.warning(L.l("type {0} ({1}) for parameter index {2} cannot be used for lob", value.getType(), value.getClass(), index));
return false;
}
if (length < 0 && (value instanceof FileReadValue)) {
length = ((FileReadValue) value).getLength();
if (length <= 0)
length = -1;
}
if (length < 0) {
TempBuffer tempBuffer = TempBuffer.allocate();
try {
byte[] bytes = new byte[1024];
int len;
while ((len = inputStream.read(bytes, 0, 1024)) != -1)
tempBuffer.write(bytes, 0, len);
}
catch (IOException ex) {
_error.error(ex);
return false;
}
TempReadStream tempReadStream = new TempReadStream(tempBuffer);
tempReadStream.setFreeWhenDone(true);
_preparedStatement.setBinaryStream(index, new ReadStream(tempReadStream), tempBuffer.getLength());
}
else
_preparedStatement.setBinaryStream(index, inputStream, (int) length);
}
}
catch (SQLException ex) {
_error.error(ex);
return false;
}
return true;
}
/**
* @param index 1-based position number
* @param value the value for the parameter
*
* @return true for success, false for failure
*/
private boolean setParameter(int index, Value value, long length)
{
try {
if (value instanceof DoubleValue) {
_preparedStatement.setDouble(index, value.toDouble());
}
else if (value instanceof LongValue) {
long v = value.toLong();
_preparedStatement.setLong(index, v);
}
else if (value instanceof StringValue) {
String string = value.toString();
if (length >= 0)
string = string.substring(0, (int) length);
_preparedStatement.setString(index, string);
}
else if (value instanceof NullValue) {
_preparedStatement.setObject(index, null);
}
else {
_error.warning(L.l("unknown type {0} ({1}) for parameter index {2}", value.getType(), value.getClass(), index));
return false;
}
}
catch (SQLException ex) {
_error.error(ex);
return false;
}
return true;
}
public String toString()
{
return "PDOStatement[" + _query + "]";
}
/**
* Bind a value from a resultSet to a variable.
*/
private class BindColumn {
private final String _columnAsName;
private final Value _var;
private final int _type;
private int _column;
private int _jdbcType;
private boolean _isInit;
private boolean _isValid;
/**
* @param column 1-based column index
* @param var reference that receives the value
* @param type a PARM_* type, -1 for default
*/
private BindColumn(Value column, Value var, int type)
throws SQLException
{
assert column != null;
assert var != null;
if (column.isNumberConvertible()) {
_column = column.toInt();
_columnAsName = null;
}
else {
_columnAsName = column.toString();
}
_var = var;
_type = type;
if (_resultSet != null)
init();
}
private boolean init()
throws SQLException
{
if (_isInit)
return true;
ResultSetMetaData resultSetMetaData = getResultSetMetaData();
int columnCount = resultSetMetaData.getColumnCount();
if (_columnAsName != null) {
for (int i = 1; i <= columnCount; i++) {
String name = resultSetMetaData.getColumnName(i);
if (name.equals(_columnAsName)) {
_column = i;
break;
}
}
}
_isValid = _column > 0 && _column <= columnCount;
if (_isValid) {
_jdbcType = resultSetMetaData.getColumnType(_column);
}
_isInit = true;
return true;
}
public boolean bind()
throws SQLException
{
if (!init())
return false;
if (!_isValid) {
// this matches php behaviour
_var.set(_env.getEmptyString());
}
else {
Value value = getColumnValue(_column, _jdbcType, _type);
_var.set(value);
}
return true;
}
}
/**
* Bind a value to a parameter when the statement is executed.
*/
private class BindParam {
private final int _index;
private final Value _value;
private final int _dataType;
private final int _length;
@SuppressWarnings("unused")
private final Value _driverOptions;
public BindParam(Value parameter, Value value, int dataType, int length, Value driverOptions)
{
int index = resolveParameter(parameter);
_index = index;
_value = value;
_dataType = dataType;
_length = length;
_driverOptions = driverOptions;
}
public boolean apply()
throws SQLException
{
switch (_dataType) {
case PDO.PARAM_BOOL:
case PDO.PARAM_INT:
case PDO.PARAM_STR:
return setParameter(_index, _value.toValue(), _length);
case PDO.PARAM_LOB:
return setLobParameter(_index, _value.toValue(), _length);
case PDO.PARAM_NULL:
return setParameter(_index, NullValue.NULL, _length);
case PDO.PARAM_STMT:
throw new UnimplementedException("PDO.PARAM_STMT");
default:
throw new AssertionError();
}
}
}
}