hphp/runtime/ext/pdo/ext_pdo.cpp (2,934 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1997-2010 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include <set>
#include <string>
#include <unordered_map>
#include "hphp/system/systemlib.h"
#include "hphp/util/hphp-config.h"
#include "hphp/util/rds-local.h"
#include "hphp/util/string-vsnprintf.h"
#include "hphp/runtime/base/array-init.h"
#include "hphp/runtime/base/array-iterator.h"
#include "hphp/runtime/base/comparisons.h"
#include "hphp/runtime/base/file.h"
#include "hphp/runtime/base/ini-setting.h"
#include "hphp/runtime/base/string-buffer.h"
#include "hphp/runtime/base/tv-refcount.h"
#include "hphp/runtime/vm/jit/translator-inline.h"
#include "hphp/runtime/vm/interp-helpers.h"
#include "hphp/runtime/ext/extension.h"
#include "hphp/runtime/ext/array/ext_array.h"
#include "hphp/runtime/ext/pdo/pdo_driver.h"
#ifdef ENABLE_EXTENSION_PDO_MYSQL
#include "hphp/runtime/ext/pdo_mysql/pdo_mysql.h"
#endif
#ifdef ENABLE_EXTENSION_PDO_SQLITE
#include "hphp/runtime/ext/pdo_sqlite/pdo_sqlite.h"
#endif
#include "hphp/runtime/ext/std/ext_std_classobj.h"
#include "hphp/runtime/ext/std/ext_std_function.h"
#include "hphp/runtime/ext/stream/ext_stream.h"
#include "hphp/runtime/ext/string/ext_string.h"
#define PDO_HANDLE_DBH_ERR(dbh) \
if (strcmp(dbh->conn()->error_code, PDO_ERR_NONE)) { \
pdo_handle_error(dbh, nullptr); \
} \
#define PDO_HANDLE_STMT_ERR(stmt) \
if (strcmp(stmt->error_code, PDO_ERR_NONE)) { \
pdo_handle_error(stmt->dbh, stmt); \
} \
namespace HPHP {
struct PDOData {
sp_PDOResource m_dbh;
};
struct PDOStatementData {
PDOStatementData();
~PDOStatementData();
sp_PDOStatement m_stmt;
Variant m_row;
int m_rowIndex;
};
using std::string;
///////////////////////////////////////////////////////////////////////////////
// extension functions
Array HHVM_FUNCTION(pdo_drivers) {
const auto& drivers = PDODriver::GetDrivers();
VecInit ret{drivers.size()};
for (const auto& driver : drivers) {
ret.append(driver.second->getName());
}
return ret.toArray();
}
///////////////////////////////////////////////////////////////////////////////
// error handling
struct pdo_sqlstate_info {
const char *state;
const char *desc;
};
static const struct pdo_sqlstate_info err_initializer[] = {
{ "00000", "No error" },
{ "01000", "Warning" },
{ "01001", "Cursor operation conflict" },
{ "01002", "Disconnect error" },
{ "01003", "NULL value eliminated in set function" },
{ "01004", "String data, right truncated" },
{ "01006", "Privilege not revoked" },
{ "01007", "Privilege not granted" },
{ "01008", "Implicit zero bit padding" },
{ "0100C", "Dynamic result sets returned" },
{ "01P01", "Deprecated feature" },
{ "01S00", "Invalid connection string attribute" },
{ "01S01", "Error in row" },
{ "01S02", "Option value changed" },
{ "01S06",
"Attempt to fetch before the result set returned the first rowset" },
{ "01S07", "Fractional truncation" },
{ "01S08", "Error saving File DSN" },
{ "01S09", "Invalid keyword" },
{ "02000", "No data" },
{ "02001", "No additional dynamic result sets returned" },
{ "03000", "Sql statement not yet complete" },
{ "07002", "COUNT field incorrect" },
{ "07005", "Prepared statement not a cursor-specification" },
{ "07006", "Restricted data type attribute violation" },
{ "07009", "Invalid descriptor index" },
{ "07S01", "Invalid use of default parameter" },
{ "08000", "Connection exception" },
{ "08001", "Client unable to establish connection" },
{ "08002", "Connection name in use" },
{ "08003", "Connection does not exist" },
{ "08004", "Server rejected the connection" },
{ "08006", "Connection failure" },
{ "08007", "Connection failure during transaction" },
{ "08S01", "Communication link failure" },
{ "09000", "Triggered action exception" },
{ "0A000", "Feature not supported" },
{ "0B000", "Invalid transaction initiation" },
{ "0F000", "Locator exception" },
{ "0F001", "Invalid locator specification" },
{ "0L000", "Invalid grantor" },
{ "0LP01", "Invalid grant operation" },
{ "0P000", "Invalid role specification" },
{ "21000", "Cardinality violation" },
{ "21S01", "Insert value list does not match column list" },
{ "21S02", "Degree of derived table does not match column list" },
{ "22000", "Data exception" },
{ "22001", "String data, right truncated" },
{ "22002", "Indicator variable required but not supplied" },
{ "22003", "Numeric value out of range" },
{ "22004", "Null value not allowed" },
{ "22005", "Error in assignment" },
{ "22007", "Invalid datetime format" },
{ "22008", "Datetime field overflow" },
{ "22009", "Invalid time zone displacement value" },
{ "2200B", "Escape character conflict" },
{ "2200C", "Invalid use of escape character" },
{ "2200D", "Invalid escape octet" },
{ "2200F", "Zero length character string" },
{ "2200G", "Most specific type mismatch" },
{ "22010", "Invalid indicator parameter value" },
{ "22011", "Substring error" },
{ "22012", "Division by zero" },
{ "22015", "Interval field overflow" },
{ "22018", "Invalid character value for cast specification" },
{ "22019", "Invalid escape character" },
{ "2201B", "Invalid regular expression" },
{ "2201E", "Invalid argument for logarithm" },
{ "2201F", "Invalid argument for power function" },
{ "2201G", "Invalid argument for width bucket function" },
{ "22020", "Invalid limit value" },
{ "22021", "Character not in repertoire" },
{ "22022", "Indicator overflow" },
{ "22023", "Invalid parameter value" },
{ "22024", "Unterminated c string" },
{ "22025", "Invalid escape sequence" },
{ "22026", "String data, length mismatch" },
{ "22027", "Trim error" },
{ "2202E", "Array subscript error" },
{ "22P01", "Floating point exception" },
{ "22P02", "Invalid text representation" },
{ "22P03", "Invalid binary representation" },
{ "22P04", "Bad copy file format" },
{ "22P05", "Untranslatable character" },
{ "23000", "Integrity constraint violation" },
{ "23001", "Restrict violation" },
{ "23502", "Not null violation" },
{ "23503", "Foreign key violation" },
{ "23505", "Unique violation" },
{ "23514", "Check violation" },
{ "24000", "Invalid cursor state" },
{ "25000", "Invalid transaction state" },
{ "25001", "Active sql transaction" },
{ "25002", "Branch transaction already active" },
{ "25003", "Inappropriate access mode for branch transaction" },
{ "25004", "Inappropriate isolation level for branch transaction" },
{ "25005", "No active sql transaction for branch transaction" },
{ "25006", "Read only sql transaction" },
{ "25007", "Schema and data statement mixing not supported" },
{ "25008", "Held cursor requires same isolation level" },
{ "25P01", "No active sql transaction" },
{ "25P02", "In failed sql transaction" },
{ "25S01", "Transaction state" },
{ "25S02", "Transaction is still active" },
{ "25S03", "Transaction is rolled back" },
{ "26000", "Invalid sql statement name" },
{ "27000", "Triggered data change violation" },
{ "28000", "Invalid authorization specification" },
{ "2B000", "Dependent privilege descriptors still exist" },
{ "2BP01", "Dependent objects still exist" },
{ "2D000", "Invalid transaction termination" },
{ "2F000", "Sql routine exception" },
{ "2F002", "Modifying sql data not permitted" },
{ "2F003", "Prohibited sql statement attempted" },
{ "2F004", "Reading sql data not permitted" },
{ "2F005", "Function executed no return statement" },
{ "34000", "Invalid cursor name" },
{ "38000", "External routine exception" },
{ "38001", "Containing sql not permitted" },
{ "38002", "Modifying sql data not permitted" },
{ "38003", "Prohibited sql statement attempted" },
{ "38004", "Reading sql data not permitted" },
{ "39000", "External routine invocation exception" },
{ "39001", "Invalid sqlstate returned" },
{ "39004", "Null value not allowed" },
{ "39P01", "Trigger protocol violated" },
{ "39P02", "Srf protocol violated" },
{ "3B000", "Savepoint exception" },
{ "3B001", "Invalid savepoint specification" },
{ "3C000", "Duplicate cursor name" },
{ "3D000", "Invalid catalog name" },
{ "3F000", "Invalid schema name" },
{ "40000", "Transaction rollback" },
{ "40001", "Serialization failure" },
{ "40002", "Transaction integrity constraint violation" },
{ "40003", "Statement completion unknown" },
{ "40P01", "Deadlock detected" },
{ "42000", "Syntax error or access violation" },
{ "42501", "Insufficient privilege" },
{ "42601", "Syntax error" },
{ "42602", "Invalid name" },
{ "42611", "Invalid column definition" },
{ "42622", "Name too long" },
{ "42701", "Duplicate column" },
{ "42702", "Ambiguous column" },
{ "42703", "Undefined column" },
{ "42704", "Undefined object" },
{ "42710", "Duplicate object" },
{ "42712", "Duplicate alias" },
{ "42723", "Duplicate function" },
{ "42725", "Ambiguous function" },
{ "42803", "Grouping error" },
{ "42804", "Datatype mismatch" },
{ "42809", "Wrong object type" },
{ "42830", "Invalid foreign key" },
{ "42846", "Cannot coerce" },
{ "42883", "Undefined function" },
{ "42939", "Reserved name" },
{ "42P01", "Undefined table" },
{ "42P02", "Undefined parameter" },
{ "42P03", "Duplicate cursor" },
{ "42P04", "Duplicate database" },
{ "42P05", "Duplicate prepared statement" },
{ "42P06", "Duplicate schema" },
{ "42P07", "Duplicate table" },
{ "42P08", "Ambiguous parameter" },
{ "42P09", "Ambiguous alias" },
{ "42P10", "Invalid column reference" },
{ "42P11", "Invalid cursor definition" },
{ "42P12", "Invalid database definition" },
{ "42P13", "Invalid function definition" },
{ "42P14", "Invalid prepared statement definition" },
{ "42P15", "Invalid schema definition" },
{ "42P16", "Invalid table definition" },
{ "42P17", "Invalid object definition" },
{ "42P18", "Indeterminate datatype" },
{ "42S01", "Base table or view already exists" },
{ "42S02", "Base table or view not found" },
{ "42S11", "Index already exists" },
{ "42S12", "Index not found" },
{ "42S21", "Column already exists" },
{ "42S22", "Column not found" },
{ "44000", "WITH CHECK OPTION violation" },
{ "53000", "Insufficient resources" },
{ "53100", "Disk full" },
{ "53200", "Out of memory" },
{ "53300", "Too many connections" },
{ "54000", "Program limit exceeded" },
{ "54001", "Statement too complex" },
{ "54011", "Too many columns" },
{ "54023", "Too many arguments" },
{ "55000", "Object not in prerequisite state" },
{ "55006", "Object in use" },
{ "55P02", "Cant change runtime param" },
{ "55P03", "Lock not available" },
{ "57000", "Operator intervention" },
{ "57014", "Query canceled" },
{ "57P01", "Admin shutdown" },
{ "57P02", "Crash shutdown" },
{ "57P03", "Cannot connect now" },
{ "58030", "Io error" },
{ "58P01", "Undefined file" },
{ "58P02", "Duplicate file" },
{ "F0000", "Config file error" },
{ "F0001", "Lock file exists" },
{ "HY000", "General error" },
{ "HY001", "Memory allocation error" },
{ "HY003", "Invalid application buffer type" },
{ "HY004", "Invalid SQL data type" },
{ "HY007", "Associated statement is not prepared" },
{ "HY008", "Operation canceled" },
{ "HY009", "Invalid use of null pointer" },
{ "HY010", "Function sequence error" },
{ "HY011", "Attribute cannot be set now" },
{ "HY012", "Invalid transaction operation code" },
{ "HY013", "Memory management error" },
{ "HY014", "Limit on the number of handles exceeded" },
{ "HY015", "No cursor name available" },
{ "HY016", "Cannot modify an implementation row descriptor" },
{ "HY017", "Invalid use of an automatically allocated descriptor handle" },
{ "HY018", "Server declined cancel request" },
{ "HY019", "Non-character and non-binary data sent in pieces" },
{ "HY020", "Attempt to concatenate a null value" },
{ "HY021", "Inconsistent descriptor information" },
{ "HY024", "Invalid attribute value" },
{ "HY090", "Invalid string or buffer length" },
{ "HY091", "Invalid descriptor field identifier" },
{ "HY092", "Invalid attribute/option identifier" },
{ "HY093", "Invalid parameter number" },
{ "HY095", "Function type out of range" },
{ "HY096", "Invalid information type" },
{ "HY097", "Column type out of range" },
{ "HY098", "Scope type out of range" },
{ "HY099", "Nullable type out of range" },
{ "HY100", "Uniqueness option type out of range" },
{ "HY101", "Accuracy option type out of range" },
{ "HY103", "Invalid retrieval code" },
{ "HY104", "Invalid precision or scale value" },
{ "HY105", "Invalid parameter type" },
{ "HY106", "Fetch type out of range" },
{ "HY107", "Row value out of range" },
{ "HY109", "Invalid cursor position" },
{ "HY110", "Invalid driver completion" },
{ "HY111", "Invalid bookmark value" },
{ "HYC00", "Optional feature not implemented" },
{ "HYT00", "Timeout expired" },
{ "HYT01", "Connection timeout expired" },
{ "IM001", "Driver does not support this function" },
{ "IM002", "Data source name not found and no default driver specified" },
{ "IM003", "Specified driver could not be loaded" },
{ "IM004", "Driver's SQLAllocHandle on SQL_HANDLE_ENV failed" },
{ "IM005", "Driver's SQLAllocHandle on SQL_HANDLE_DBC failed" },
{ "IM006", "Driver's SQLSetConnectAttr failed" },
{ "IM007", "No data source or driver specified; dialog prohibited" },
{ "IM008", "Dialog failed" },
{ "IM009", "Unable to load translation DLL" },
{ "IM010", "Data source name too long" },
{ "IM011", "Driver name too long" },
{ "IM012", "DRIVER keyword syntax error" },
{ "IM013", "Trace file error" },
{ "IM014", "Invalid name of File DSN" },
{ "IM015", "Corrupt file data source" },
{ "P0000", "Plpgsql error" },
{ "P0001", "Raise exception" },
{ "XX000", "Internal error" },
{ "XX001", "Data corrupted" },
{ "XX002", "Index corrupted" }
};
struct PDOErrorHash : private hphp_const_char_map<const char *> {
PDOErrorHash() {
for (unsigned int i = 0;
i < sizeof(err_initializer)/sizeof(err_initializer[0]); i++) {
const struct pdo_sqlstate_info *info = &err_initializer[i];
(*this)[info->state] = info->desc;
}
}
const char *description(const char *state) {
const_iterator iter = find(state);
if (iter != end()) {
return iter->second;
}
return "<<Unknown error>>";
}
};
static PDOErrorHash s_err_hash;
const StaticString
s_code("code"),
s_message("message"),
s_errorInfo("errorInfo"),
s_PDOException("PDOException");
void throw_pdo_exception(const Variant& info, const char *fmt, ...) {
auto obj = SystemLib::AllocPDOExceptionObject();
obj->o_set(s_code, 0, s_PDOException);
va_list ap;
va_start(ap, fmt);
string msg;
string_vsnprintf(msg, fmt, ap);
obj->o_set(s_message, String(msg), s_PDOException);
va_end(ap);
if (!info.isNull()) {
obj->o_set(s_errorInfo, info, s_PDOException);
}
throw_object(obj);
}
void pdo_raise_impl_error(sp_PDOResource rsrc, PDOStatement* stmt,
const char *sqlstate, const char *supp) {
auto const& dbh = rsrc->conn();
PDOErrorType *pdo_err = &dbh->error_code;
if (stmt) {
pdo_err = &stmt->error_code;
}
setPDOError(*pdo_err, sqlstate);
const char *msg = s_err_hash.description(sqlstate);
string err = "SQLSTATE["; err += sqlstate; err += "]: "; err += msg;
if (supp) {
err += ": "; err += supp;
}
if (dbh->error_mode != PDO_ERRMODE_EXCEPTION) {
raise_warning("%s", err.c_str());
} else {
VecInit info(2);
info.append(String(*pdo_err, CopyString));
info.append(0LL);
throw_pdo_exception(info.toArray(), "%s",
err.c_str());
}
}
void pdo_raise_impl_error(sp_PDOResource rsrc, sp_PDOStatement stmt,
const char *sqlstate, const char *supp) {
pdo_raise_impl_error(rsrc, stmt.get(), sqlstate, supp);
}
namespace {
void pdo_handle_error(sp_PDOResource rsrc, PDOStatement* stmt) {
auto const& dbh = rsrc->conn();
if (dbh->error_mode == PDO_ERRMODE_SILENT) {
return;
}
PDOErrorType *pdo_err = &dbh->error_code;
if (stmt) {
pdo_err = &stmt->error_code;
}
/* hash sqlstate to error messages */
const char *msg = s_err_hash.description(*pdo_err);
int64_t native_code = 0;
String supp;
Array info;
if (dbh->support(PDOConnection::MethodFetchErr)) {
info = make_vec_array(String(*pdo_err, CopyString));
if (dbh->fetchErr(stmt, info)) {
if (info.exists(1)) {
native_code = info[1].toInt64();
}
if (info.exists(2)) {
supp = info[2].toString();
}
}
}
string err = "SQLSTATE["; err += *pdo_err; err += "]: "; err += msg;
if (!supp.empty()) {
err += ": "; err += String(native_code).data();
err += " "; err += supp.data();
}
if (dbh->error_mode != PDO_ERRMODE_EXCEPTION) {
raise_warning("%s", err.c_str());
} else {
throw_pdo_exception(info, "%s", err.c_str());
}
}
void pdo_handle_error(sp_PDOResource rsrc, sp_PDOStatement stmt) {
pdo_handle_error(rsrc, stmt.get());
}
}
///////////////////////////////////////////////////////////////////////////////
// helpers for PDO class
static inline int64_t pdo_attr_lval(const Array& options, PDOAttributeType name,
int64_t defval) {
if (options.exists(name)) {
return options[name].toInt64();
}
return defval;
}
static Object pdo_stmt_instantiate(sp_PDOResource dbh, const String& clsname,
const Variant& ctor_args) {
String name = clsname;
if (name.empty()) {
name = "PDOStatement";
}
if (!ctor_args.isNull() && !ctor_args.isArray()) {
pdo_raise_impl_error(dbh, nullptr, "HY000",
"constructor arguments must be passed as an array");
return Object();
}
Class* cls = Class::load(name.get());
if (!cls) {
return Object();
}
callerDynamicConstructChecks(cls);
return Object{cls};
}
const StaticString s_queryString("queryString");
static void pdo_stmt_construct(sp_PDOStatement stmt, Object object,
const String& clsname,
const Variant& ctor_args) {
object->setProp(nullptr, s_queryString.get(), stmt->query_string.asTypedValue());
if (clsname.empty()) {
return;
}
Class* cls = Class::load(clsname.get());
if (!cls) {
return;
}
ObjectData* inst = object.get();
tvDecRefGen(
g_context->invokeFunc(cls->getCtor(), ctor_args.toArray(), inst)
);
}
static bool valid_statement_class(sp_PDOResource dbh, const Variant& opt,
String &clsname, Variant &ctor_args) {
if (!opt.isArray() || !opt.toArray().exists(0) ||
!opt.toArray()[0].isString() ||
!HHVM_FN(class_exists)(opt.toArray()[0].toString())) {
pdo_raise_impl_error
(dbh, nullptr, "HY000",
"PDO::ATTR_STATEMENT_CLASS requires format array(classname, "
"array(ctor_args)); the classname must be a string specifying "
"an existing class");
PDO_HANDLE_DBH_ERR(dbh);
return false;
}
clsname = opt.toArray()[0].toString();
if (clsname == String("PDOStatement")) {
ctor_args = Variant(Array());
return true;
}
if (!HHVM_FN(is_a)(clsname, "PDOStatement", /* allow_string */ true)) {
pdo_raise_impl_error
(dbh, nullptr, "HY000",
"user-supplied statement class must be derived from PDOStatement");
PDO_HANDLE_DBH_ERR(dbh);
return false;
}
HPHP::Class* cls = HPHP::Class::load(clsname.get());
if (cls) {
const HPHP::Func* method = cls->getDeclaredCtor();
if (method && method->isPublic()) {
pdo_raise_impl_error
(dbh, nullptr, "HY000",
"user-supplied statement class cannot have a public constructor");
PDO_HANDLE_DBH_ERR(dbh);
return false;
}
}
if (opt.toArray().exists(1)) {
Variant item = opt.toArray()[1];
if (!item.isArray()) {
pdo_raise_impl_error
(dbh, nullptr, "HY000",
"PDO::ATTR_STATEMENT_CLASS requires format array(classname, "
"ctor_args); ctor_args must be an array");
PDO_HANDLE_DBH_ERR(dbh);
return false;
}
ctor_args = item;
}
return true;
}
static bool pdo_stmt_describe_columns(sp_PDOStatement stmt) {
for (int col = 0; col < stmt->column_count; col++) {
if (!stmt->describer(col)) {
return false;
}
auto column = cast<PDOColumn>(stmt->columns[col]);
/* if we are applying case conversions on column names, do so now */
if (stmt->dbh->conn()->native_case != stmt->dbh->conn()->desired_case &&
stmt->dbh->conn()->desired_case != PDO_CASE_NATURAL) {
switch (stmt->dbh->conn()->desired_case) {
case PDO_CASE_UPPER:
column->name = HHVM_FN(strtoupper)(column->name);
break;
case PDO_CASE_LOWER:
column->name = HHVM_FN(strtolower)(column->name);
break;
default:;
}
}
auto const column_key =
stmt->bound_columns.convertKey<IntishCast::Cast>(column->name);
if (stmt->bound_columns.exists(column_key)) {
auto param = cast<PDOBoundParam>(stmt->bound_columns[column_key]);
param->paramno = col;
}
}
return true;
}
static bool pdo_stmt_verify_mode(sp_PDOStatement stmt, int64_t mode,
bool fetch_all) {
int flags = mode & PDO_FETCH_FLAGS;
mode = mode & ~PDO_FETCH_FLAGS;
if (mode < 0 || mode > PDO_FETCH__MAX) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "invalid fetch mode");
return false;
}
if (mode == PDO_FETCH_USE_DEFAULT) {
flags = stmt->default_fetch_type & PDO_FETCH_FLAGS;
mode = stmt->default_fetch_type & ~PDO_FETCH_FLAGS;
}
switch (mode) {
case PDO_FETCH_FUNC:
if (!fetch_all) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"PDO::FETCH_FUNC is only allowed in "
"PDOStatement::fetchAll()");
return false;
}
return true;
case PDO_FETCH_LAZY:
if (fetch_all) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"PDO::FETCH_LAZY can't be used with "
"PDOStatement::fetchAll()");
return false;
}
default:
if ((flags & PDO_FETCH_SERIALIZE) == PDO_FETCH_SERIALIZE) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"PDO::FETCH_SERIALIZE can only be used "
"together with PDO::FETCH_CLASS");
return false;
}
if ((flags & PDO_FETCH_CLASSTYPE) == PDO_FETCH_CLASSTYPE) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"PDO::FETCH_CLASSTYPE can only be used "
"together with PDO::FETCH_CLASS");
return false;
}
if (mode >= PDO_FETCH__MAX) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "invalid fetch mode");
return false;
}
/* no break; */
case PDO_FETCH_CLASS:
break;
}
return true;
}
static bool do_fetch_class_prepare(sp_PDOStatement stmt) {
String clsname = stmt->fetch.clsname;
if (clsname.empty()) {
stmt->fetch.clsname = "stdclass";
}
stmt->fetch.constructor = empty_string(); //NULL;
HPHP::Class* cls = HPHP::Class::load(clsname.get());
if (cls) {
const HPHP::Func* method = cls->getDeclaredCtor();
if (method) {
stmt->fetch.constructor = method->nameStr();
return true;
}
}
if (!stmt->fetch.ctor_args.isNull()) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"user-supplied class does not have a constructor, "
"use NULL for the ctor_params parameter, or simply "
"omit it");
return false;
}
return true; /* no ctor no args is also ok */
}
static bool pdo_stmt_set_fetch_mode(sp_PDOStatement stmt, int _argc,
int64_t mode, const Array& _argv) {
_argc = _argv.size() + 1;
if (stmt->default_fetch_type == PDO_FETCH_INTO) {
stmt->fetch.into.unset();
}
stmt->default_fetch_type = PDO_FETCH_BOTH;
if (!pdo_stmt_verify_mode(stmt, mode, false)) {
setPDOErrorNone(stmt->error_code);
return false;
}
int flags = mode & PDO_FETCH_FLAGS;
bool retval = false;
switch (mode & ~PDO_FETCH_FLAGS) {
case PDO_FETCH_USE_DEFAULT:
case PDO_FETCH_LAZY:
case PDO_FETCH_ASSOC:
case PDO_FETCH_NUM:
case PDO_FETCH_BOTH:
case PDO_FETCH_OBJ:
case PDO_FETCH_BOUND:
case PDO_FETCH_NAMED:
case PDO_FETCH_KEY_PAIR:
if (_argc != 1) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"fetch mode doesn't allow any extra arguments");
} else {
retval = true;
}
break;
case PDO_FETCH_COLUMN:
if (_argc != 2) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"fetch mode requires the colno argument");
} else if (!_argv[0].isInteger()) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"colno must be an integer");
} else {
stmt->fetch.column = _argv[0].toInt64();
retval = true;
}
break;
case PDO_FETCH_CLASS:
/* Gets its class name from 1st column */
if ((flags & PDO_FETCH_CLASSTYPE) == PDO_FETCH_CLASSTYPE) {
if (_argc != 1) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"fetch mode doesn't allow any extra arguments");
} else {
stmt->fetch.clsname.clear();
retval = true;
}
} else {
if (_argc < 2) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"fetch mode requires the classname argument");
} else if (_argc > 3) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"too many arguments");
} else if (!_argv[0].isString()) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"classname must be a string");
} else {
retval = HHVM_FN(class_exists)(_argv[0].toString());
if (retval) {
stmt->fetch.clsname = _argv[0].toString();
}
}
}
if (retval) {
stmt->fetch.ctor_args.unset();
if (_argc == 3) {
if (!_argv[1].isNull() && !_argv[1].isArray()) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"ctor_args must be either NULL or an array");
retval = false;
} else {
stmt->fetch.ctor_args = _argv[1];
}
}
if (retval) {
do_fetch_class_prepare(stmt);
}
}
break;
case PDO_FETCH_INTO:
if (_argc != 2) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"fetch mode requires the object parameter");
} else if (!_argv[0].isObject()) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"object must be an object");
} else {
retval = true;
}
if (retval) {
stmt->fetch.into = _argv[0];
}
break;
default:
pdo_raise_impl_error(stmt->dbh, stmt, "22003",
"Invalid fetch mode specified");
}
if (retval) {
stmt->default_fetch_type = (PDOFetchType)mode;
}
/*
* PDO error (if any) has already been raised at this point.
*
* The error_code is cleared, otherwise the caller will read the
* last error message from the driver.
*
*/
setPDOErrorNone(stmt->error_code);
return retval;
}
///////////////////////////////////////////////////////////////////////////////
// forward declarations
bool HHVM_METHOD(PDO, setattribute, int64_t attribute,
const Variant& value);
///////////////////////////////////////////////////////////////////////////////
// PDO
namespace {
using StorageT = std::unordered_map<std::string, sp_PDOConnection>;
RDS_LOCAL(StorageT, s_connections);
}
const StaticString s_PDO("PDO");
void HHVM_METHOD(PDO, __construct, const String& dsn,
const String& username /* = null_string */,
const String& password /* = null_string */,
const Variant& optionsV /* = null_array */) {
auto data = Native::data<PDOData>(this_);
auto options = optionsV.isNull() ? null_array : optionsV.toArray();
String data_source = dsn;
/* parse the data source name */
const char *colon = strchr(data_source.data(), ':');
if (!colon) {
/* let's see if this string has a matching dsn in the php.ini */
String name = "pdo.dsn."; name += data_source;
String ini_dsn;
if (!IniSetting::Get(name, ini_dsn)) {
throw_pdo_exception(uninit_null(), "invalid data source name");
}
data_source = ini_dsn;
colon = strchr(data_source.data(), ':');
if (!colon) {
throw_pdo_exception(uninit_null(),
"invalid data source name (via INI: %s)",
ini_dsn.data());
}
}
if (!strncmp(data_source.data(), "uri:", 4)) {
/* the specified URI holds connection details */
auto file = File::Open(data_source.substr(4), "rb");
if (!file || file->isInvalid()) {
throw_pdo_exception(uninit_null(), "invalid data source URI");
}
data_source = file->readLine(1024);
colon = strchr(data_source.data(), ':');
if (!colon) {
throw_pdo_exception(uninit_null(), "invalid data source name (via URI)");
}
}
const PDODriverMap &drivers = PDODriver::GetDrivers();
String name = data_source.substr(0, colon - data_source.data());
PDODriverMap::const_iterator iter = drivers.find(name.data());
if (iter == drivers.end()) {
/* NB: don't want to include the data_source in the error message as
* it might contain a password */
throw_pdo_exception(uninit_null(), "could not find driver");
}
PDODriver *driver = iter->second;
/* is this supposed to be a persistent connection ? */
bool is_persistent = false;
bool call_factory = true;
std::string shashkey;
if (!options.empty()) {
StringBuffer hashkey;
if (options.exists(PDO_ATTR_PERSISTENT)) {
Variant v = options[PDO_ATTR_PERSISTENT];
String sv = v.toString();
if (v.isString() && !sv.isNumeric() && !sv.empty()) {
/* user specified key */
hashkey.printf("PDO:DBH:DSN=%s:%s:%s:%s",
data_source.data(), username.data(),
password.data(), sv.data());
is_persistent = true;
} else {
is_persistent = v.toInt64();
hashkey.printf("PDO:DBH:DSN=%s:%s:%s",
data_source.data(), username.data(),
password.data());
}
}
if (is_persistent) {
shashkey = hashkey.detach().toCppString();
/* let's see if we have one cached.... */
if (s_connections->count(shashkey)) {
auto const conn = (*s_connections)[shashkey];
data->m_dbh = driver->createResource(conn);
/* is the connection still alive ? */
if (conn->support(PDOConnection::MethodCheckLiveness) &&
!conn->checkLiveness()) {
/* nope... need to kill it */
data->m_dbh = nullptr;
}
}
if (data->m_dbh) {
call_factory = false;
} else {
/* need a brand new pdbh */
data->m_dbh = driver->createResource(colon + 1, username,
password, options);
if (!data->m_dbh) {
throw_pdo_exception(uninit_null(), "unable to create a connection");
}
data->m_dbh->conn()->persistent_id = shashkey;
}
}
}
if (!data->m_dbh) {
data->m_dbh = driver->createResource(colon + 1, username,
password, options);
if (!data->m_dbh) {
throw_pdo_exception(uninit_null(), "unable to create a connection");
}
}
if (call_factory) {
data->m_dbh->conn()->default_fetch_type = PDO_FETCH_BOTH;
}
data->m_dbh->conn()->auto_commit =
pdo_attr_lval(options, PDO_ATTR_AUTOCOMMIT, 1);
if (!call_factory) {
/* we got a persistent guy from our cache */
for (ArrayIter iter(options); iter; ++iter) {
HHVM_MN(PDO, setattribute)(this_, iter.first().toInt64(),
iter.second());
}
} else if (data->m_dbh) {
if (is_persistent) {
assertx(!shashkey.empty());
(*s_connections)[shashkey] = data->m_dbh->conn();
}
data->m_dbh->conn()->driver = driver;
for (ArrayIter iter(options); iter; ++iter) {
HHVM_MN(PDO, setattribute)(this_, iter.first().toInt64(),
iter.second());
}
}
}
Variant HHVM_METHOD(PDO, prepare, const String& statement,
const Array& options = null_array) {
auto data = Native::data<PDOData>(this_);
assertx(data->m_dbh->conn()->driver);
setPDOErrorNone(data->m_dbh->conn()->error_code);
data->m_dbh->query_stmt = nullptr;
String clsname;
Variant ctor_args;
if (options.exists(PDO_ATTR_STATEMENT_CLASS)) {
Variant opt = options[PDO_ATTR_STATEMENT_CLASS];
if (!valid_statement_class(data->m_dbh, opt, clsname, ctor_args)) {
return false;
}
} else {
clsname = data->m_dbh->conn()->def_stmt_clsname;
ctor_args = data->m_dbh->def_stmt_ctor_args;
}
Object ret = pdo_stmt_instantiate(data->m_dbh, clsname, ctor_args);
if (ret.isNull()) {
pdo_raise_impl_error
(data->m_dbh, nullptr, "HY000",
"failed to instantiate user-supplied statement class");
PDO_HANDLE_DBH_ERR(data->m_dbh);
return false;
}
PDOStatementData *pdostmt = Native::data<PDOStatementData>(ret);
if (data->m_dbh->conn()->preparer(statement, &pdostmt->m_stmt, options)) {
auto stmt = pdostmt->m_stmt;
assertx(stmt);
/* unconditionally keep this for later reference */
stmt->query_string = statement;
stmt->default_fetch_type = data->m_dbh->conn()->default_fetch_type;
stmt->dbh = data->m_dbh;
pdo_stmt_construct(stmt, ret, clsname, ctor_args);
return ret;
}
PDO_HANDLE_DBH_ERR(data->m_dbh);
return false;
}
static bool HHVM_METHOD(PDO, begintransaction) {
auto data = Native::data<PDOData>(this_);
if (data->m_dbh->conn()->in_txn) {
throw_pdo_exception(uninit_null(),
"There is already an active transaction");
}
if (data->m_dbh->conn()->begin()) {
data->m_dbh->conn()->in_txn = 1;
return true;
}
if (strcmp(data->m_dbh->conn()->error_code, PDO_ERR_NONE)) {
pdo_handle_error(data->m_dbh, nullptr);
}
return false;
}
static bool HHVM_METHOD(PDO, commit) {
auto data = Native::data<PDOData>(this_);
assertx(data->m_dbh->conn()->driver);
if (!data->m_dbh->conn()->in_txn) {
throw_pdo_exception(uninit_null(), "There is no active transaction");
}
if (data->m_dbh->conn()->commit()) {
data->m_dbh->conn()->in_txn = 0;
return true;
}
PDO_HANDLE_DBH_ERR(data->m_dbh);
return false;
}
static bool HHVM_METHOD(PDO, intransaction) {
auto data = Native::data<PDOData>(this_);
assertx(data->m_dbh->conn()->driver);
return data->m_dbh->conn()->in_txn;
}
static bool HHVM_METHOD(PDO, rollback) {
auto data = Native::data<PDOData>(this_);
assertx(data->m_dbh->conn()->driver);
if (!data->m_dbh->conn()->in_txn) {
throw_pdo_exception(uninit_null(), "There is no active transaction");
}
if (data->m_dbh->conn()->rollback()) {
data->m_dbh->conn()->in_txn = 0;
return true;
}
PDO_HANDLE_DBH_ERR(data->m_dbh);
return false;
}
bool HHVM_METHOD(PDO, setattribute, int64_t attribute,
const Variant& value) {
auto data = Native::data<PDOData>(this_);
assertx(data->m_dbh->conn()->driver);
#define PDO_LONG_PARAM_CHECK \
if (!value.isInteger() && !value.isString() && !value.isBoolean()) { \
pdo_raise_impl_error(data->m_dbh, nullptr, "HY000", \
"attribute value must be an integer"); \
PDO_HANDLE_DBH_ERR(data->m_dbh); \
return false; \
} \
switch (attribute) {
case PDO_ATTR_ERRMODE:
PDO_LONG_PARAM_CHECK;
switch (value.toInt64()) {
case PDO_ERRMODE_SILENT:
case PDO_ERRMODE_WARNING:
case PDO_ERRMODE_EXCEPTION:
data->m_dbh->conn()->error_mode = (PDOErrorMode)value.toInt64();
return true;
default:
pdo_raise_impl_error(data->m_dbh, nullptr, "HY000", "invalid error mode");
PDO_HANDLE_DBH_ERR(data->m_dbh);
return false;
}
return false;
case PDO_ATTR_CASE:
PDO_LONG_PARAM_CHECK;
switch (value.toInt64()) {
case PDO_CASE_NATURAL:
case PDO_CASE_UPPER:
case PDO_CASE_LOWER:
data->m_dbh->conn()->desired_case = (PDOCaseConversion)value.toInt64();
return true;
default:
pdo_raise_impl_error(data->m_dbh, nullptr, "HY000",
"invalid case folding mode");
PDO_HANDLE_DBH_ERR(data->m_dbh);
return false;
}
return false;
case PDO_ATTR_ORACLE_NULLS:
PDO_LONG_PARAM_CHECK;
data->m_dbh->conn()->oracle_nulls = value.toInt64();
return true;
case PDO_ATTR_DEFAULT_FETCH_MODE:
if (value.isArray()) {
if (value.asCArrRef().exists(0)) {
Variant tmp = value.asCArrRef()[0];
if (tmp.isInteger() && ((tmp.toInt64() == PDO_FETCH_INTO ||
tmp.toInt64() == PDO_FETCH_CLASS))) {
pdo_raise_impl_error(data->m_dbh, nullptr, "HY000",
"FETCH_INTO and FETCH_CLASS are not yet "
"supported as default fetch modes");
return false;
}
}
} else {
PDO_LONG_PARAM_CHECK;
}
if (value.toInt64() == PDO_FETCH_USE_DEFAULT) {
pdo_raise_impl_error(data->m_dbh, nullptr,
"HY000", "invalid fetch mode type");
return false;
}
data->m_dbh->conn()->default_fetch_type = (PDOFetchType)value.toInt64();
return true;
case PDO_ATTR_STRINGIFY_FETCHES:
PDO_LONG_PARAM_CHECK;
data->m_dbh->conn()->stringify = value.toInt64() ? 1 : 0;
return true;
case PDO_ATTR_STATEMENT_CLASS:
{
if (data->m_dbh->conn()->is_persistent) {
pdo_raise_impl_error(data->m_dbh, nullptr, "HY000",
"PDO::ATTR_STATEMENT_CLASS cannot be used "
"with persistent PDO instances");
PDO_HANDLE_DBH_ERR(data->m_dbh);
return false;
}
String clsname;
if (!valid_statement_class(data->m_dbh, value, clsname,
data->m_dbh->def_stmt_ctor_args)) {
return false;
}
data->m_dbh->conn()->def_stmt_clsname = clsname.c_str();
return true;
}
}
if (data->m_dbh->conn()->support(PDOConnection::MethodSetAttribute)) {
setPDOErrorNone(data->m_dbh->conn()->error_code);
data->m_dbh->query_stmt = nullptr;
if (data->m_dbh->conn()->setAttribute(attribute, value)) {
return true;
}
}
if (attribute == PDO_ATTR_AUTOCOMMIT) {
throw_pdo_exception(uninit_null(),
"The auto-commit mode cannot be changed for this "
"driver");
} else if (!data->m_dbh->conn()->support(PDOConnection::MethodSetAttribute)) {
pdo_raise_impl_error(data->m_dbh, nullptr, "IM001",
"driver does not support setting attributes");
} else {
PDO_HANDLE_DBH_ERR(data->m_dbh);
}
return false;
}
Variant HHVM_METHOD(PDO, getAttribute, int64_t attribute) {
auto data = Native::data<PDOData>(this_);
assertx(data->m_dbh->conn()->driver);
setPDOErrorNone(data->m_dbh->conn()->error_code);
data->m_dbh->query_stmt = nullptr;
/* handle generic PDO-level attributes */
switch (attribute) {
case PDO_ATTR_PERSISTENT:
return (bool)data->m_dbh->conn()->is_persistent;
case PDO_ATTR_CASE:
return (int64_t)data->m_dbh->conn()->desired_case;
case PDO_ATTR_ORACLE_NULLS:
return (int64_t)data->m_dbh->conn()->oracle_nulls;
case PDO_ATTR_ERRMODE:
return (int64_t)data->m_dbh->conn()->error_mode;
case PDO_ATTR_DRIVER_NAME:
return String(data->m_dbh->conn()->driver->getName());
case PDO_ATTR_STATEMENT_CLASS: {
Array ret;
ret.append(String(data->m_dbh->conn()->def_stmt_clsname));
if (!data->m_dbh->def_stmt_ctor_args.isNull()) {
ret.append(data->m_dbh->def_stmt_ctor_args);
}
return ret;
}
case PDO_ATTR_DEFAULT_FETCH_MODE:
return (int64_t)data->m_dbh->conn()->default_fetch_type;
}
if (!data->m_dbh->conn()->support(PDOConnection::MethodGetAttribute)) {
pdo_raise_impl_error(data->m_dbh, nullptr, "IM001",
"driver does not support getting attributes");
return false;
}
Variant ret;
switch (data->m_dbh->conn()->getAttribute(attribute, ret)) {
case -1:
PDO_HANDLE_DBH_ERR(data->m_dbh);
return false;
case 0:
pdo_raise_impl_error(data->m_dbh, nullptr, "IM001",
"driver does not support that attribute");
return false;
}
return ret;
}
Variant HHVM_METHOD(PDO, exec, const String& query) {
auto data = Native::data<PDOData>(this_);
SYNC_VM_REGS_SCOPED();
if (query.empty()) {
pdo_raise_impl_error(data->m_dbh, nullptr, "HY000",
"trying to execute an empty query");
return false;
}
assertx(data->m_dbh->conn()->driver);
setPDOErrorNone(data->m_dbh->conn()->error_code);
data->m_dbh->query_stmt = nullptr;
int64_t ret = data->m_dbh->conn()->doer(query);
if (ret == -1) {
PDO_HANDLE_DBH_ERR(data->m_dbh);
return false;
}
return ret;
}
static Variant HHVM_METHOD(PDO, lastinsertid,
const String& seqname /* = null_string */) {
auto data = Native::data<PDOData>(this_);
assertx(data->m_dbh->conn()->driver);
setPDOErrorNone(data->m_dbh->conn()->error_code);
data->m_dbh->query_stmt = nullptr;
if (!data->m_dbh->conn()->support(PDOConnection::MethodLastId)) {
pdo_raise_impl_error(data->m_dbh, nullptr, "IM001",
"driver does not support lastInsertId()");
return false;
}
String ret = data->m_dbh->conn()->lastId(seqname.data());
if (ret.empty()) {
PDO_HANDLE_DBH_ERR(data->m_dbh);
return false;
}
return ret;
}
static Variant HHVM_METHOD(PDO, errorcode) {
auto data = Native::data<PDOData>(this_);
assertx(data->m_dbh->conn()->driver);
if (data->m_dbh->query_stmt) {
return String(data->m_dbh->query_stmt->error_code, CopyString);
}
if (data->m_dbh->conn()->error_code[0] == '\0') {
return init_null();
}
/**
* Making sure that we fallback to the default implementation
* if the dbh->error_code is not null.
*/
return String(data->m_dbh->conn()->error_code, CopyString);
}
static Array HHVM_METHOD(PDO, errorinfo) {
auto data = Native::data<PDOData>(this_);
assertx(data->m_dbh->conn()->driver);
Array ret = Array::CreateVec();
if (data->m_dbh->query_stmt) {
ret.append(String(data->m_dbh->query_stmt->error_code, CopyString));
} else {
ret.append(String(data->m_dbh->conn()->error_code, CopyString));
}
if (data->m_dbh->conn()->support(PDOConnection::MethodFetchErr)) {
data->m_dbh->conn()->fetchErr(data->m_dbh->query_stmt, ret);
}
/**
* In order to be consistent, we have to make sure we add the good amount
* of nulls depending on the current number of elements. We make a simple
* difference and add the needed elements
*/
int error_count = ret.size();
int error_expected_count = 3;
if (error_expected_count > error_count) {
int error_count_diff = error_expected_count - error_count;
for (int i = 0; i < error_count_diff; i++) {
ret.append(init_null_variant);
}
}
return ret;
}
Variant HHVM_METHOD(PDO, query, const String& sql, const Array& _argv) {
auto data = Native::data<PDOData>(this_);
SYNC_VM_REGS_SCOPED();
assertx(data->m_dbh->conn()->driver);
setPDOErrorNone(data->m_dbh->conn()->error_code);
data->m_dbh->query_stmt = nullptr;
Object ret = pdo_stmt_instantiate(data->m_dbh,
data->m_dbh->conn()->def_stmt_clsname,
data->m_dbh->def_stmt_ctor_args);
if (ret.isNull()) {
pdo_raise_impl_error
(data->m_dbh, nullptr, "HY000",
"failed to instantiate user supplied statement class");
return init_null();
}
PDOStatementData *pdostmt = Native::data<PDOStatementData>(ret);
if (data->m_dbh->conn()->preparer(sql, &pdostmt->m_stmt, Array())) {
auto stmt = pdostmt->m_stmt;
assertx(stmt);
/* unconditionally keep this for later reference */
stmt->query_string = sql;
stmt->default_fetch_type = data->m_dbh->conn()->default_fetch_type;
stmt->active_query_string = stmt->query_string;
stmt->dbh = data->m_dbh;
stmt->lazy_object_ref.unset();
setPDOErrorNone(stmt->error_code);
// when we add support for varargs here, we only need to set the stmt if
// the argument count is > 1
int argc = _argv.size() + 1;
Variant argv_variant = _argv;
if (argc == 1 ||
pdo_stmt_set_fetch_mode(
stmt,
0,
tvCastToInt64(_argv.lookup(0)),
Variant::attach(HHVM_FN(array_splice)(argv_variant, 1)).toArray()
)) {
/* now execute the statement */
setPDOErrorNone(stmt->error_code);
if (stmt->executer()) {
int ok = 1;
if (!stmt->executed) {
if (stmt->dbh->conn()->alloc_own_columns) {
ok = pdo_stmt_describe_columns(stmt);
}
stmt->executed = 1;
}
if (ok) {
pdo_stmt_construct(stmt, ret, data->m_dbh->conn()->def_stmt_clsname,
data->m_dbh->def_stmt_ctor_args);
return ret;
}
}
}
/* something broke */
data->m_dbh->query_stmt = stmt.get();
PDO_HANDLE_STMT_ERR(stmt);
} else {
PDO_HANDLE_DBH_ERR(data->m_dbh);
}
return false;
}
static Variant HHVM_METHOD(PDO, quote, const String& str,
int64_t paramtype /* = PDO_PARAM_STR */) {
auto data = Native::data<PDOData>(this_);
assertx(data->m_dbh->conn()->driver);
setPDOErrorNone(data->m_dbh->conn()->error_code);
data->m_dbh->query_stmt = nullptr;
if (!data->m_dbh->conn()->support(PDOConnection::MethodQuoter)) {
pdo_raise_impl_error(data->m_dbh, nullptr, "IM001",
"driver does not support quoting");
return false;
}
String quoted;
if (data->m_dbh->conn()->quoter(str, quoted, (PDOParamType)paramtype)) {
return quoted;
}
PDO_HANDLE_DBH_ERR(data->m_dbh);
return false;
}
static bool HHVM_METHOD(PDO, sqlitecreatefunction, const String& name,
const Variant& callback, int64_t argcount /* = -1 */) {
#ifdef ENABLE_EXTENSION_PDO_SQLITE
auto data = Native::data<PDOData>(this_);
auto res = dynamic_cast<PDOSqliteResource*>(data->m_dbh.get());
if (res == nullptr) {
return false;
}
return res->createFunction(name, callback, argcount);
#else
raise_recoverable_error("PDO::sqliteCreateFunction not implemented");
return false;
#endif
}
static bool HHVM_METHOD(PDO, sqlitecreateaggregate, const String& /*name*/,
const Variant& /*step*/, const Variant& /*final*/,
int64_t /*argcount*/ /* = -1 */) {
raise_recoverable_error("PDO::sqliteCreateAggregate not implemented");
return false;
}
static Variant HHVM_METHOD(PDO, __wakeup) {
throw_pdo_exception(uninit_null(),
"You cannot serialize or unserialize PDO instances");
return init_null();
}
static Variant HHVM_METHOD(PDO, __sleep) {
throw_pdo_exception(uninit_null(),
"You cannot serialize or unserialize PDO instances");
return init_null();
}
static Array HHVM_STATIC_METHOD(PDO, getAvailableDrivers) {
return HHVM_FN(pdo_drivers)();
}
///////////////////////////////////////////////////////////////////////////////
static inline bool rewrite_name_to_position(sp_PDOStatement stmt,
sp_PDOBoundParam param) {
if (!stmt->bound_param_map.empty()) {
/* rewriting :name to ? style.
* We need to fixup the parameter numbers on the parameters.
* If we find that a given named parameter has been used twice,
* we will raise an error, as we can't be sure that it is safe
* to bind multiple parameters onto the same zval in the underlying
* driver */
if (stmt->named_rewrite_template) {
/* this is not an error here */
return true;
}
if (param->name.empty()) {
/* do the reverse; map the parameter number to the name */
if (stmt->bound_param_map.exists(param->paramno)) {
param->name = stmt->bound_param_map[param->paramno].toString();
return true;
}
pdo_raise_impl_error(stmt->dbh, stmt, "HY093",
"parameter was not defined");
return false;
}
int position = 0;
for (ArrayIter iter(stmt->bound_param_map); iter; ++iter, ++position) {
if (iter.second().toString() == param->name) {
if (param->paramno >= 0) {
pdo_raise_impl_error
(stmt->dbh, stmt, "IM001",
"PDO refuses to handle repeating the same :named parameter "
"for multiple positions with this driver, as it might be "
"unsafe to do so. Consider using a separate name for each "
"parameter instead");
return true;
}
param->paramno = position;
return true;
}
}
pdo_raise_impl_error(stmt->dbh, stmt, "HY093",
"parameter was not defined");
return false;
}
return true;
}
/* trigger callback hook for parameters */
static bool dispatch_param_event(sp_PDOStatement stmt,
PDOParamEvent event_type) {
if (!stmt->support(PDOStatement::MethodParamHook)) {
return true;
}
for (ArrayIter iter(stmt->bound_params); iter; ++iter) {
auto param = cast<PDOBoundParam>(iter.second());
if (!stmt->paramHook(param.get(), event_type)) {
return false;
}
}
for (ArrayIter iter(stmt->bound_columns); iter; ++iter) {
auto param = cast<PDOBoundParam>(iter.second());
if (!stmt->paramHook(param.get(), event_type)) {
return false;
}
}
return true;
}
static void get_lazy_object(sp_PDOStatement stmt, Variant &ret) {
if (stmt->lazy_object_ref.isNull()) {
stmt->lazy_object_ref = stmt;
}
ret = stmt->lazy_object_ref;
}
static bool really_register_bound_param(sp_PDOBoundParam param,
sp_PDOStatement stmt) {
Array &hash = stmt->bound_params;
if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_STR &&
!param->parameter.isNull()) {
param->parameter = param->parameter.toString();
} else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_INT &&
param->parameter.isBoolean()) {
param->parameter = param->parameter.toInt64();
} else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BOOL &&
param->parameter.isInteger()) {
param->parameter = param->parameter.toBoolean();
}
param->stmt = stmt.get();
if (!param->name.empty() && param->name[0] != ':') {
param->name = String(":") + param->name;
}
if (!rewrite_name_to_position(stmt, param)) {
param->name.reset();
return false;
}
/* ask the driver to perform any normalization it needs on the
* parameter name. Note that it is illegal for the driver to take
* a reference to param, as it resides in transient storage only
* at this time. */
if (stmt->support(PDOStatement::MethodParamHook)) {
if (!stmt->paramHook(param.get(), PDO_PARAM_EVT_NORMALIZE)) {
param->name.reset();
return false;
}
}
/* delete any other parameter registered with this number.
* If the parameter is named, it will be removed and correctly
* disposed of by the hash_update call that follows */
if (param->paramno >= 0) {
hash.remove(param->paramno);
}
/* allocate storage for the parameter, keyed by its "canonical" name */
if (!param->name.empty()) {
hash.set(param->name, Variant(param));
} else {
hash.set(param->paramno, Variant(param));
}
/* tell the driver we just created a parameter */
if (stmt->support(PDOStatement::MethodParamHook)) {
if (!stmt->paramHook(param.get(), PDO_PARAM_EVT_ALLOC)) {
/* undo storage allocation; the hash will free the parameter
* name if required */
if (!param->name.empty()) {
hash.remove(param->name);
} else {
hash.remove(param->paramno);
}
param->parameter.unset();
return false;
}
}
return true;
}
static inline void fetch_value(sp_PDOStatement stmt, Variant &dest, int colno,
int *type_override) {
if (colno < 0 || colno >= stmt->column_count) {
return;
}
auto col = cast<PDOColumn>(stmt->columns[colno]);
int type = PDO_PARAM_TYPE(col->param_type);
int new_type = type_override ? PDO_PARAM_TYPE(*type_override) : type;
stmt->getColumn(colno, dest);
if (type != new_type) {
switch (new_type) {
case PDO_PARAM_INT: dest = dest.toInt64(); break;
case PDO_PARAM_BOOL: dest = dest.toBoolean(); break;
case PDO_PARAM_STR: dest = dest.toString(); break;
case PDO_PARAM_NULL: dest = init_null(); break;
}
}
if (stmt->dbh->conn()->stringify && (dest.isInteger() || dest.isDouble())) {
dest = dest.toString();
}
if (dest.isNull() && stmt->dbh->conn()->oracle_nulls == PDO_NULL_TO_STRING) {
dest = empty_string_variant();
}
}
static bool do_fetch_common(sp_PDOStatement stmt, PDOFetchOrientation ori,
long offset) {
if (!stmt->executed) {
return false;
}
if (!dispatch_param_event(stmt, PDO_PARAM_EVT_FETCH_PRE)) {
return false;
}
if (!stmt->fetcher(ori, offset)) {
return false;
}
/* some drivers might need to describe the columns now */
if (stmt->columns.empty() && !pdo_stmt_describe_columns(stmt)) {
return false;
}
if (!dispatch_param_event(stmt, PDO_PARAM_EVT_FETCH_POST)) {
return false;
}
if (!stmt->bound_columns.empty()) {
/* update those bound column variables now */
for (ArrayIter iter(stmt->bound_columns); iter; ++iter) {
auto param = cast<PDOBoundParam>(iter.second());
if (param->paramno >= 0) {
param->parameter.setNull();
/* set new value */
fetch_value(stmt, param->parameter, param->paramno,
(int *)¶m->param_type);
/* TODO: some smart thing that avoids duplicating the value in the
* general loop below. For now, if you're binding output columns,
* it's better to use LAZY or BOUND fetches if you want to shave
* off those cycles */
}
}
}
return true;
}
static bool do_fetch_func_prepare(sp_PDOStatement stmt) {
if (!is_callable(stmt->fetch.func)) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"user-supplied function must be a valid callback");
return false;
}
return true;
}
/* perform a fetch. If do_bind is true, update any bound columns.
* If return_value is not null, store values into it according to HOW. */
static bool do_fetch(sp_PDOStatement stmt,
Variant& ret,
PDOFetchType how,
PDOFetchOrientation ori,
long offset,
Variant *return_all) {
if (how == PDO_FETCH_USE_DEFAULT) {
how = stmt->default_fetch_type;
}
int flags = how & PDO_FETCH_FLAGS;
how = (PDOFetchType)(how & ~PDO_FETCH_FLAGS);
if (!do_fetch_common(stmt, ori, offset)) {
return false;
}
if (how == PDO_FETCH_BOUND) {
ret = true;
return true;
}
int colno;
if ((flags & PDO_FETCH_GROUP) && stmt->fetch.column == -1) {
colno = 1;
} else {
colno = stmt->fetch.column;
}
if (how == PDO_FETCH_LAZY) {
get_lazy_object(stmt, ret);
return true;
}
String clsname, old_clsname;
Variant old_ctor_args;
ret = false;
int i = 0;
switch (how) {
case PDO_FETCH_USE_DEFAULT:
case PDO_FETCH_ASSOC:
case PDO_FETCH_BOTH:
case PDO_FETCH_NUM:
case PDO_FETCH_NAMED:
ret = Array::CreateDict();
break;
case PDO_FETCH_KEY_PAIR:
if (stmt->column_count != 2) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"PDO::FETCH_KEY_PAIR fetch mode requires the "
"result set to contain extactly 2 columns.");
return false;
}
if (!return_all) {
ret = Array::CreateDict();
}
break;
case PDO_FETCH_COLUMN:
if (colno >= 0 && colno < stmt->column_count) {
if (flags == PDO_FETCH_GROUP && stmt->fetch.column == -1) {
fetch_value(stmt, ret, 1, NULL);
} else if (flags == PDO_FETCH_GROUP && colno) {
fetch_value(stmt, ret, 0, NULL);
} else {
fetch_value(stmt, ret, colno, NULL);
}
if (!return_all) {
return true;
}
break;
} else {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "Invalid column index");
}
return false;
case PDO_FETCH_OBJ:
ret = SystemLib::AllocStdClassObject();
break;
case PDO_FETCH_CLASS:
if (flags & PDO_FETCH_CLASSTYPE) {
old_clsname = stmt->fetch.clsname;
old_ctor_args = stmt->fetch.ctor_args;
Variant val;
fetch_value(stmt, val, i++, NULL);
if (!val.isNull()) {
if (!HHVM_FN(class_exists)(val.toString())) {
stmt->fetch.clsname = "stdclass";
} else {
stmt->fetch.clsname = val.toString();
}
}
do_fetch_class_prepare(stmt);
}
clsname = stmt->fetch.clsname;
if (clsname.empty()) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"No fetch class specified");
return false;
}
if ((flags & PDO_FETCH_SERIALIZE) == 0) {
ret = create_object_only(clsname);
if (!do_fetch_class_prepare(stmt)) {
return false;
}
if (!stmt->fetch.constructor.empty() &&
(flags & PDO_FETCH_PROPS_LATE)) {
ret.asCObjRef()->o_invoke(stmt->fetch.constructor,
stmt->fetch.ctor_args.toArray());
}
}
break;
case PDO_FETCH_INTO:
if (stmt->fetch.into.isNull()) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"No fetch-into object specified.");
return false;
}
ret = stmt->fetch.into;
if (ret.isObject() &&
ret.getObjectData()->instanceof(SystemLib::s_stdclassClass)) {
how = PDO_FETCH_OBJ;
}
break;
case PDO_FETCH_FUNC:
if (stmt->fetch.func.empty()) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"No fetch function specified");
return false;
}
if (!do_fetch_func_prepare(stmt)) {
return false;
}
break;
default:
assertx(false);
return false;
}
Variant grp_val;
if (return_all && how != PDO_FETCH_KEY_PAIR) {
if (flags == PDO_FETCH_GROUP && how == PDO_FETCH_COLUMN &&
stmt->fetch.column > 0) {
fetch_value(stmt, grp_val, colno, NULL);
} else {
fetch_value(stmt, grp_val, i, NULL);
}
grp_val = grp_val.toString();
if (how == PDO_FETCH_COLUMN) {
i = stmt->column_count; /* no more data to fetch */
} else {
i++;
}
}
for (int idx = 0; i < stmt->column_count; i++, idx++) {
const String& name = cast<PDOColumn>(stmt->columns[i])->name;
Variant val;
fetch_value(stmt, val, i, NULL);
switch (how) {
case PDO_FETCH_ASSOC: {
auto const name_key =
ret.asArrRef().convertKey<IntishCast::Cast>(name);
ret.asArrRef().set(name_key, *val.asTypedValue());
break;
}
case PDO_FETCH_KEY_PAIR: {
Variant tmp;
fetch_value(stmt, tmp, ++i, NULL);
if (return_all) {
auto const val_key_ret =
return_all->asArrRef().convertKey<IntishCast::Cast>(val);
return_all->asArrRef().set(val_key_ret, *tmp.asTypedValue());
} else {
auto const val_key =
ret.asArrRef().convertKey<IntishCast::Cast>(val);
ret.asArrRef().set(val_key, *tmp.asTypedValue());
}
return true;
}
case PDO_FETCH_USE_DEFAULT:
case PDO_FETCH_BOTH: {
auto const name_key =
ret.asArrRef().convertKey<IntishCast::Cast>(name);
ret.asArrRef().set(name_key, *val.asTypedValue());
ret.asArrRef().append(val);
break;
}
case PDO_FETCH_NAMED: {
auto const name_key =
ret.asArrRef().convertKey<IntishCast::Cast>(name);
/* already have an item with this name? */
forceToDict(ret);
if (ret.asArrRef().exists(name_key)) {
auto const curr_val = ret.asArrRef().lval(name_key);
if (!isArrayLikeType(curr_val.type())) {
Array arr = Array::CreateVec();
arr.append(curr_val.tv());
arr.append(val);
ret.toArray().set(name_key, make_array_like_tv(arr.get()));
} else {
asArrRef(curr_val).append(val);
}
} else {
ret.asArrRef().set(name_key, *val.asTypedValue());
}
break;
}
case PDO_FETCH_NUM:
ret.asArrRef().append(val);
break;
case PDO_FETCH_OBJ:
case PDO_FETCH_INTO:
ret.toObject()->o_set(name, val);
break;
case PDO_FETCH_CLASS:
if ((flags & PDO_FETCH_SERIALIZE) == 0 || idx) {
ret.toObject()->o_set(name, val);
} else {
#ifdef MBO_0
ret = unserialize_from_string(val);
if (same(ret, false)) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"cannot unserialize data");
return false;
}
#endif
// hzhao: not sure how we support class serialization
pdo_raise_impl_error(stmt->dbh, stmt, "HY000",
"cannot unserialize class");
return false;
}
break;
case PDO_FETCH_FUNC:
forceToDict(stmt->fetch.values).set(idx, val);
break;
default:
pdo_raise_impl_error(stmt->dbh, stmt, "22003", "mode is out of range");
return false;
}
}
switch (how) {
case PDO_FETCH_CLASS:
if (!stmt->fetch.constructor.empty() &&
!(flags & (PDO_FETCH_PROPS_LATE | PDO_FETCH_SERIALIZE))) {
ret.toObject()->o_invoke(stmt->fetch.constructor,
stmt->fetch.ctor_args.toArray());
}
if (flags & PDO_FETCH_CLASSTYPE) {
stmt->fetch.clsname = old_clsname;
stmt->fetch.ctor_args = old_ctor_args;
}
break;
case PDO_FETCH_FUNC:
ret = vm_call_user_func(stmt->fetch.func,
stmt->fetch.values.toArray());
break;
default:
break;
}
if (return_all) {
auto const grp_key =
return_all->asArrRef().convertKey<IntishCast::Cast>(grp_val);
if ((flags & PDO_FETCH_UNIQUE) == PDO_FETCH_UNIQUE) {
return_all->asArrRef().set(grp_key, *ret.asTypedValue());
} else {
auto const lval = return_all->asArrRef().lval(grp_key);
forceToArray(lval).append(ret);
}
}
return true;
}
bool HHVM_METHOD(PDOStatement, bindvalue, const Variant& paramno,
const Variant& param,
int64_t type /* = PDO_PARAM_STR */) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return false;
}
auto& stmt = data->m_stmt;
auto p = req::make<PDOBoundParam>();
// need to make sure this is NULL, in case a fatal errors occurs before it's
// set inside really_register_bound_param
p->stmt = NULL;
if (paramno.isNumeric()) {
p->paramno = paramno.toInt64();
} else {
p->paramno = -1;
p->name = paramno.toString();
}
p->parameter = param;
p->param_type = (PDOParamType)type;
if (p->paramno > 0) {
--p->paramno; /* make it zero-based internally */
} else if (p->name.empty()) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY093",
"Columns/Parameters are 1-based");
return false;
}
if (!really_register_bound_param(p, stmt)) {
p->parameter.unset();
return false;
}
return true;
}
static bool generic_stmt_attr_get(sp_PDOStatement stmt, Variant &ret,
long attr) {
if (attr == PDO_ATTR_EMULATE_PREPARES) {
ret = (bool)(stmt->supports_placeholders == PDO_PLACEHOLDER_NONE);
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
// SQL parser
namespace {
#define PDO_PARSER_TEXT 1
#define PDO_PARSER_BIND 2
#define PDO_PARSER_BIND_POS 3
#define PDO_PARSER_EOI 4
#define RET(i) {s->cur = cursor; return i; }
#define SKIP_ONE(i) {s->cur = s->tok + 1; return 1; }
#define YYCTYPE unsigned char
#define YYCURSOR cursor
#define YYLIMIT limit
#define YYMARKER s->ptr
#define YYFILL(n) RET(PDO_PARSER_EOI)
typedef struct Scanner {
char *ptr, *cur, *lim, *tok;
} Scanner;
static int scan(Scanner *s) {
char* cursor = s->cur;
char* limit = s->lim;
s->tok = cursor;
{
YYCTYPE yych;
if ((YYLIMIT - YYCURSOR) < 2) { YYFILL(2); }
yych = *YYCURSOR;
switch (yych) {
case 0x00: goto yy11;
case '"': goto yy2;
case '\'': goto yy4;
case ':': goto yy5;
case '?': goto yy6;
default: goto yy8;
}
yy2:
yych = *(YYMARKER = ++YYCURSOR);
if (yych >= 0x01) goto yy26;
yy3:
{ SKIP_ONE(PDO_PARSER_TEXT); }
yy4:
yych = *(YYMARKER = ++YYCURSOR);
if (yych <= 0x00) goto yy3;
goto yy20;
yy5:
yych = *++YYCURSOR;
switch (yych) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
case 'Y':
case 'Z':
case '_':
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z': goto yy16;
case ':':
case '?': goto yy13;
default: goto yy3;
}
yy6:
++YYCURSOR;
switch ((yych = *YYCURSOR)) {
case ':':
case '?': goto yy13;
default: goto yy7;
}
yy7:
{ RET(PDO_PARSER_BIND_POS); }
yy8:
++YYCURSOR;
if (YYLIMIT <= YYCURSOR) { YYFILL(1); }
yych = *YYCURSOR;
switch (yych) {
case 0x00:
case '"':
case '\'':
case ':':
case '?': goto yy10;
default: goto yy8;
}
yy10:
{ RET(PDO_PARSER_TEXT); }
yy11:
++YYCURSOR;
{ RET(PDO_PARSER_EOI); }
yy13:
++YYCURSOR;
if (YYLIMIT <= YYCURSOR) { YYFILL(1); }
yych = *YYCURSOR;
switch (yych) {
case ':':
case '?': goto yy13;
default: goto yy15;
}
yy15:
{ RET(PDO_PARSER_TEXT); }
yy16:
++YYCURSOR;
if (YYLIMIT <= YYCURSOR) { YYFILL(1); }
yych = *YYCURSOR;
switch (yych) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
case 'Y':
case 'Z':
case '_':
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z': goto yy16;
default: goto yy18;
}
yy18:
{ RET(PDO_PARSER_BIND); }
yy19:
++YYCURSOR;
if (YYLIMIT <= YYCURSOR) { YYFILL(1); }
yych = *YYCURSOR;
yy20:
switch (yych) {
case 0x00: goto yy21;
case '\'': goto yy23;
case '\\': goto yy22;
default: goto yy19;
}
yy21:
YYCURSOR = YYMARKER;
goto yy3;
yy22:
++YYCURSOR;
if (YYLIMIT <= YYCURSOR) { YYFILL(1); }
yych = *YYCURSOR;
if (yych <= 0x00) goto yy21;
goto yy19;
yy23:
++YYCURSOR;
{ RET(PDO_PARSER_TEXT); }
yy25:
++YYCURSOR;
if (YYLIMIT <= YYCURSOR) { YYFILL(1); }
yych = *YYCURSOR;
yy26:
switch (yych) {
case 0x00: goto yy21;
case '"': goto yy28;
case '\\': goto yy27;
default: goto yy25;
}
yy27:
++YYCURSOR;
if (YYLIMIT <= YYCURSOR) { YYFILL(1); }
yych = *YYCURSOR;
if (yych <= 0x00) goto yy21;
goto yy25;
yy28:
++YYCURSOR;
{ RET(PDO_PARSER_TEXT); }
}
}
}
struct placeholder {
char *pos;
int len;
int bindno;
String quoted; /* quoted value */
struct placeholder *next;
};
int pdo_parse_params(sp_PDOStatement stmt, const String& in, String &out) {
Scanner s;
const char *ptr;
char *newbuffer;
int t;
int bindno = 0;
int ret = 0;
int newbuffer_len;
Array params;
req::ptr<PDOBoundParam> param;
int query_type = PDO_PLACEHOLDER_NONE;
struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;
s.cur = (char*)in.data();
s.lim = (char*)in.data() + in.size() + 1;
/* phase 1: look for args */
while ((t = scan(&s)) != PDO_PARSER_EOI) {
if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS) {
if (t == PDO_PARSER_BIND) {
int len = s.cur - s.tok;
if ((in.data() < (s.cur - len)) && isalnum(*(s.cur - len - 1))) {
continue;
}
query_type |= PDO_PLACEHOLDER_NAMED;
} else {
query_type |= PDO_PLACEHOLDER_POSITIONAL;
}
plc = req::make_raw<placeholder>();
memset(plc, 0, sizeof(*plc));
plc->next = NULL;
plc->pos = s.tok;
plc->len = s.cur - s.tok;
plc->bindno = bindno++;
if (placetail) {
placetail->next = plc;
} else {
placeholders = plc;
}
placetail = plc;
}
}
if (bindno == 0) {
/* nothing to do; good! */
return 0;
}
/* did the query make sense to me? */
if (query_type == (PDO_PLACEHOLDER_NAMED|PDO_PLACEHOLDER_POSITIONAL)) {
/* they mixed both types; punt */
pdo_raise_impl_error(stmt->dbh, stmt, "HY093",
"mixed named and positional parameters");
ret = -1;
goto clean_up;
}
if ((int)stmt->supports_placeholders == query_type &&
!stmt->named_rewrite_template) {
/* query matches native syntax */
ret = 0;
goto clean_up;
}
if (stmt->named_rewrite_template) {
/* magic/hack.
* We we pretend that the query was positional even if
* it was named so that we fall into the
* named rewrite case below. Not too pretty,
* but it works. */
query_type = PDO_PLACEHOLDER_POSITIONAL;
}
params = stmt->bound_params;
/* Do we have placeholders but no bound params */
if (bindno && params.empty() &&
stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "no parameters were bound");
ret = -1;
goto clean_up;
}
if (!params.empty() && bindno != params.size() &&
stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
/* extra bit of validation for instances when same params are bound
more then once */
if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > params.size()) {
int ok = 1;
for (plc = placeholders; plc; plc = plc->next) {
if (!params.exists(String(plc->pos, plc->len, CopyString))) {
ok = 0;
break;
}
}
if (ok) {
goto safe;
}
}
pdo_raise_impl_error(stmt->dbh, stmt, "HY093",
"number of bound variables does not match number "
"of tokens");
ret = -1;
goto clean_up;
}
safe:
/* what are we going to do ? */
if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
/* query generation */
newbuffer_len = in.size();
/* let's quote all the values */
for (plc = placeholders; plc; plc = plc->next) {
Variant vparam;
if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
vparam = params[plc->bindno];
} else {
String str(plc->pos, plc->len, CopyString);
auto const arrkey = params.convertKey<IntishCast::Cast>(str);
vparam = params[arrkey];
}
if (vparam.isNull()) {
/* parameter was not defined */
ret = -1;
pdo_raise_impl_error(stmt->dbh, stmt, "HY093",
"parameter was not defined");
goto clean_up;
}
param = cast<PDOBoundParam>(vparam);
if (stmt->dbh->conn()->support(PDOConnection::MethodQuoter)) {
if (param->param_type == PDO_PARAM_LOB &&
param->parameter.isResource()) {
Variant buf = HHVM_FN(stream_get_contents)(
param->parameter.toResource());
if (!same(buf, false)) {
if (!stmt->dbh->conn()->quoter(buf.toString(), plc->quoted,
param->param_type)) {
/* bork */
ret = -1;
setPDOError(stmt->error_code, stmt->dbh->conn()->error_code);
goto clean_up;
}
} else {
pdo_raise_impl_error(stmt->dbh, stmt, "HY105",
"Expected a stream resource");
ret = -1;
goto clean_up;
}
} else {
do {
switch (param->parameter.getType()) {
case KindOfUninit:
case KindOfNull:
plc->quoted = "NULL";
continue;
case KindOfInt64:
case KindOfDouble:
plc->quoted = param->parameter.toString();
continue;
case KindOfBoolean:
param->parameter = param->parameter.toInt64();
// fallthru
case KindOfPersistentString:
case KindOfString:
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
case KindOfObject:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfClass:
case KindOfLazyClass:
case KindOfClsMeth:
case KindOfRClsMeth:
if (!stmt->dbh->conn()->quoter(
param->parameter.toString(),
plc->quoted,
param->param_type)) {
/* bork */
ret = -1;
setPDOError(stmt->error_code, stmt->dbh->conn()->error_code);
goto clean_up;
}
continue;
}
not_reached();
} while (0);
}
} else {
plc->quoted = param->parameter.toString();
}
newbuffer_len += plc->quoted.size();
}
rewrite:
/* allocate output buffer */
out = String(newbuffer_len, ReserveString);
newbuffer = out.mutableData();
/* and build the query */
plc = placeholders;
ptr = in.data();
do {
t = plc->pos - ptr;
if (t) {
memcpy(newbuffer, ptr, t);
newbuffer += t;
}
memcpy(newbuffer, plc->quoted.data(), plc->quoted.size());
newbuffer += plc->quoted.size();
ptr = plc->pos + plc->len;
plc = plc->next;
} while (plc);
t = (in.data() + in.size()) - ptr;
if (t) {
memcpy(newbuffer, ptr, t);
newbuffer += t;
}
out.setSize(newbuffer - out.data());
ret = 1;
goto clean_up;
} else if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
/* rewrite ? to :pdoX */
StringBuffer idxbuf;
const char *tmpl = stmt->named_rewrite_template ?
stmt->named_rewrite_template : ":pdo%d";
int bind_no = 1;
newbuffer_len = in.size();
for (plc = placeholders; plc; plc = plc->next) {
int skip_map = 0;
String name(plc->pos, plc->len, CopyString);
auto const name_key =
stmt->bound_param_map.convertKey<IntishCast::Cast>(name);
/* check if bound parameter is already available */
if (!strcmp(name.c_str(), "?") ||
!stmt->bound_param_map.exists(name_key)) {
idxbuf.printf(tmpl, bind_no++);
} else {
idxbuf.clear();
idxbuf.append(stmt->bound_param_map[name_key].toString());
skip_map = 1;
}
plc->quoted = idxbuf.detach();
newbuffer_len += plc->quoted.size();
if (!skip_map && stmt->named_rewrite_template) {
/* create a mapping */
stmt->bound_param_map.set(name_key,
make_tv<KindOfString>(plc->quoted.get()));
}
/* map number to name */
stmt->bound_param_map.set(plc->bindno, plc->quoted);
}
goto rewrite;
} else {
/* rewrite :name to ? */
newbuffer_len = in.size();
for (plc = placeholders; plc; plc = plc->next) {
String name(plc->pos, plc->len, CopyString);
stmt->bound_param_map.set(plc->bindno, name);
plc->quoted = "?";
}
goto rewrite;
}
clean_up:
while (placeholders) {
plc = placeholders;
placeholders = plc->next;
plc->quoted.reset();
req::free(plc);
}
return ret;
}
///////////////////////////////////////////////////////////////////////////////
// PDOStatement
const StaticString s_PDOStatement("PDOStatement");
PDOStatementData::PDOStatementData() : m_rowIndex(-1) {
}
PDOStatementData::~PDOStatementData() { }
Variant HHVM_METHOD(PDOStatement, execute,
const Variant& paramsV /* = null_array */) {
auto data = Native::data<PDOStatementData>(this_);
auto params = paramsV.isNull() ? null_array : paramsV.toArray();
SYNC_VM_REGS_SCOPED();
if (data->m_stmt == nullptr) {
return init_null_variant;
}
setPDOErrorNone(data->m_stmt->error_code);
if (!params.empty()) {
data->m_stmt->bound_params.reset();
for (ArrayIter iter(params); iter; ++iter) {
auto param = req::make<PDOBoundParam>();
param->param_type = PDO_PARAM_STR;
param->parameter = iter.second();
param->stmt = NULL;
if (iter.first().isString()) {
param->name = iter.first().toString();
param->paramno = -1;
} else {
int64_t num_index = iter.first().toInt64();
/* we're okay to be zero based here */
if (num_index < 0) {
pdo_raise_impl_error(data->m_stmt->dbh, data->m_stmt,
"HY093", nullptr);
return false;
}
param->paramno = num_index;
}
if (!really_register_bound_param(param, data->m_stmt)) {
return false;
}
}
}
int ret = 1;
if (PDO_PLACEHOLDER_NONE == data->m_stmt->supports_placeholders) {
/* handle the emulated parameter binding, m_stmt->active_query_string
holds the query with binds expanded and quoted. */
ret = pdo_parse_params(data->m_stmt, data->m_stmt->query_string,
data->m_stmt->active_query_string);
if (ret == 0) { /* no changes were made */
data->m_stmt->active_query_string = data->m_stmt->query_string;
ret = 1;
} else if (ret == -1) {
/* something broke */
PDO_HANDLE_STMT_ERR(data->m_stmt);
return false;
}
} else if (!dispatch_param_event(data->m_stmt, PDO_PARAM_EVT_EXEC_PRE)) {
PDO_HANDLE_STMT_ERR(data->m_stmt);
return false;
}
if (data->m_stmt->executer()) {
data->m_stmt->active_query_string.reset();
if (!data->m_stmt->executed) {
/* this is the first execute */
if (data->m_stmt->dbh->conn()->alloc_own_columns
&& data->m_stmt->columns.empty()) {
/* for "big boy" drivers, we need to allocate memory to fetch
* the results into, so lets do that now */
ret = pdo_stmt_describe_columns(data->m_stmt);
}
data->m_stmt->executed = 1;
}
if (ret && !dispatch_param_event(data->m_stmt, PDO_PARAM_EVT_EXEC_POST)) {
return false;
}
return (bool)ret;
}
data->m_stmt->active_query_string.reset();
PDO_HANDLE_STMT_ERR(data->m_stmt);
return false;
}
static Variant HHVM_METHOD(PDOStatement, fetch, int64_t how = 0,
int64_t orientation = PDO_FETCH_ORI_NEXT,
int64_t offset = 0) {
auto data = Native::data<PDOStatementData>(this_);
SYNC_VM_REGS_SCOPED();
if (data->m_stmt == nullptr) {
return false;
}
setPDOErrorNone(data->m_stmt->error_code);
if (!pdo_stmt_verify_mode(data->m_stmt, how, false)) {
return false;
}
Variant ret;
if (!do_fetch(data->m_stmt, ret, (PDOFetchType)how,
(PDOFetchOrientation)orientation, offset, NULL)) {
PDO_HANDLE_STMT_ERR(data->m_stmt);
return false;
}
return ret;
}
static Variant HHVM_METHOD(PDOStatement, fetchobject,
const String& class_name /* = null_string */,
const Variant& ctor_args /* = null */) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return false;
}
setPDOErrorNone(data->m_stmt->error_code);
if (!pdo_stmt_verify_mode(data->m_stmt, PDO_FETCH_CLASS, false)) {
return false;
}
String old_clsname = data->m_stmt->fetch.clsname;
Variant old_ctor_args = data->m_stmt->fetch.ctor_args;
bool error = false;
data->m_stmt->fetch.clsname = class_name;
if (class_name.empty()) {
data->m_stmt->fetch.clsname = "stdclass";
}
if (!HHVM_FN(class_exists)(data->m_stmt->fetch.clsname)) {
pdo_raise_impl_error(data->m_stmt->dbh, data->m_stmt, "HY000",
"Could not find user-supplied class");
error = true;
}
if (!ctor_args.isNull() && !ctor_args.isArray()) {
pdo_raise_impl_error(data->m_stmt->dbh, data->m_stmt, "HY000",
"ctor_args must be either NULL or an array");
error = true;
}
data->m_stmt->fetch.ctor_args = ctor_args;
Variant ret;
if (!error && !do_fetch(data->m_stmt, ret, PDO_FETCH_CLASS,
PDO_FETCH_ORI_NEXT, 0, NULL)) {
error = true;
}
if (error) {
PDO_HANDLE_STMT_ERR(data->m_stmt);
}
data->m_stmt->fetch.clsname = old_clsname;
data->m_stmt->fetch.ctor_args = old_ctor_args;
if (error) {
return false;
}
return ret;
}
static Variant HHVM_METHOD(PDOStatement, fetchcolumn,
int64_t column_numner /* = 0 */) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return false;
}
setPDOErrorNone(data->m_stmt->error_code);
if (!do_fetch_common(data->m_stmt, PDO_FETCH_ORI_NEXT, 0)) {
PDO_HANDLE_STMT_ERR(data->m_stmt);
return false;
}
Variant ret;
fetch_value(data->m_stmt, ret, column_numner, nullptr);
return ret;
}
Variant HHVM_METHOD(PDOStatement, fetchall, int64_t how /* = 0 */,
const Variant& class_name /* = null */,
const Variant& ctor_args /* = null */) {
auto self = Native::data<PDOStatementData>(this_);
if (self->m_stmt == nullptr) {
return false;
}
if (!pdo_stmt_verify_mode(self->m_stmt, how, true)) {
return false;
}
String old_clsname = self->m_stmt->fetch.clsname;
Variant old_ctor_args = self->m_stmt->fetch.ctor_args;
int error = 0;
switch (how & ~PDO_FETCH_FLAGS) {
case PDO_FETCH_CLASS:
self->m_stmt->fetch.clsname = class_name.toString();
if (class_name.isNull()) {
self->m_stmt->fetch.clsname = "stdclass";
}
if (!HHVM_FN(class_exists)(self->m_stmt->fetch.clsname)) {
pdo_raise_impl_error(self->m_stmt->dbh, self->m_stmt, "HY000",
"Could not find user-supplied class");
error = 1;
}
if (!ctor_args.isNull() && !ctor_args.isArray()) {
pdo_raise_impl_error(self->m_stmt->dbh, self->m_stmt, "HY000",
"ctor_args must be either NULL or an array");
error = 1;
break;
}
self->m_stmt->fetch.ctor_args = ctor_args;
if (!error) {
do_fetch_class_prepare(self->m_stmt);
}
break;
case PDO_FETCH_FUNC:
if (!HHVM_FN(function_exists)(class_name.toString())) {
pdo_raise_impl_error(self->m_stmt->dbh, self->m_stmt, "HY000",
"no fetch function specified");
error = 1;
} else {
self->m_stmt->fetch.func = class_name.toString();
do_fetch_func_prepare(self->m_stmt);
}
break;
case PDO_FETCH_COLUMN:
if (class_name.isNull()) {
self->m_stmt->fetch.column = how & PDO_FETCH_GROUP ? -1 : 0;
} else {
self->m_stmt->fetch.column = class_name.toInt64();
}
if (!ctor_args.isNull()) {
pdo_raise_impl_error(self->m_stmt->dbh, self->m_stmt, "HY000",
"Third parameter not allowed for "
"PDO::FETCH_COLUMN");
error = 1;
}
break;
}
int flags = how & PDO_FETCH_FLAGS;
if ((how & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT) {
flags |= self->m_stmt->default_fetch_type & PDO_FETCH_FLAGS;
how |= self->m_stmt->default_fetch_type & ~PDO_FETCH_FLAGS;
}
Variant *return_all = NULL;
Variant return_value;
Variant data;
if (!error) {
setPDOErrorNone(self->m_stmt->error_code);
if ((how & PDO_FETCH_GROUP) || how == PDO_FETCH_KEY_PAIR ||
(how == PDO_FETCH_USE_DEFAULT &&
self->m_stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)) {
return_value = Array::CreateDict();
return_all = &return_value;
}
if (!do_fetch(self->m_stmt, data, (PDOFetchType)(how | flags),
PDO_FETCH_ORI_NEXT, 0, return_all)) {
error = 2;
}
}
if (!error) {
if ((how & PDO_FETCH_GROUP)) {
do {
data.unset();
} while (do_fetch(self->m_stmt, data, (PDOFetchType)(how | flags),
PDO_FETCH_ORI_NEXT, 0, return_all));
} else if (how == PDO_FETCH_KEY_PAIR ||
(how == PDO_FETCH_USE_DEFAULT &&
self->m_stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)) {
while (do_fetch(self->m_stmt, data, (PDOFetchType)(how | flags),
PDO_FETCH_ORI_NEXT, 0, return_all)) {
continue;
}
} else {
return_value = Array::CreateVec();
do {
return_value.asArrRef().append(data);
data.unset();
} while (do_fetch(self->m_stmt, data, (PDOFetchType)(how | flags),
PDO_FETCH_ORI_NEXT, 0, NULL));
}
}
self->m_stmt->fetch.clsname = old_clsname;
self->m_stmt->fetch.ctor_args = old_ctor_args;
if (error) {
PDO_HANDLE_STMT_ERR(self->m_stmt);
if (error != 2) {
return false;
}
/* on no results, return an empty array */
if (!return_value.isArray()) {
return_value = Array::CreateDict();
}
}
return return_value;
}
static int64_t HHVM_METHOD(PDOStatement, rowcount) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return 0;
}
return data->m_stmt->row_count;
}
static Variant HHVM_METHOD(PDOStatement, errorcode) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return false;
}
if (data->m_stmt->error_code[0] == '\0') {
return init_null();
}
return String(data->m_stmt->error_code, CopyString);
}
static Array HHVM_METHOD(PDOStatement, errorinfo) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return null_array;
}
Array ret = Array::CreateVec();
ret.append(String(data->m_stmt->error_code, CopyString));
if (data->m_stmt->dbh->conn()->support(PDOConnection::MethodFetchErr)) {
data->m_stmt->dbh->conn()->fetchErr(data->m_stmt.get(), ret);
}
int error_count = ret.size();
int error_expected_count = 3;
if (error_expected_count > error_count) {
int error_count_diff = error_expected_count - error_count;
for (int i = 0; i < error_count_diff; i++) {
ret.append(uninit_null());
}
}
return ret;
}
static Variant HHVM_METHOD(PDOStatement, setattribute, int64_t attribute,
const Variant& value) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return false;
}
if (!data->m_stmt->support(PDOStatement::MethodSetAttribute)) {
pdo_raise_impl_error(data->m_stmt->dbh, data->m_stmt, "IM001",
"This driver doesn't support setting attributes");
return false;
}
setPDOErrorNone(data->m_stmt->error_code);
if (data->m_stmt->setAttribute(attribute, value)) {
return true;
}
PDO_HANDLE_STMT_ERR(data->m_stmt);
return false;
}
static Variant HHVM_METHOD(PDOStatement, getAttribute, int64_t attribute) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return false;
}
Variant ret;
if (!data->m_stmt->support(PDOStatement::MethodGetAttribute)) {
if (!generic_stmt_attr_get(data->m_stmt, ret, attribute)) {
pdo_raise_impl_error(data->m_stmt->dbh, data->m_stmt, "IM001",
"This driver doesn't support getting attributes");
return false;
}
return ret;
}
setPDOErrorNone(data->m_stmt->error_code);
switch (data->m_stmt->getAttribute(attribute, ret)) {
case -1:
PDO_HANDLE_STMT_ERR(data->m_stmt);
return false;
case 0:
if (!generic_stmt_attr_get(data->m_stmt, ret, attribute)) {
/* XXX: should do something better here */
pdo_raise_impl_error(data->m_stmt->dbh, data->m_stmt, "IM001",
"driver doesn't support getting that attribute");
return false;
}
break;
default:
break;
}
return ret;
}
static int64_t HHVM_METHOD(PDOStatement, columncount) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return 0;
}
return data->m_stmt->column_count;
}
const StaticString
s_name("name"),
s_len("len"),
s_precision("precision"),
s_pdo_type("pdo_type");
static Variant HHVM_METHOD(PDOStatement, getcolumnmeta, int64_t column) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return false;
}
if (column < 0) {
pdo_raise_impl_error(data->m_stmt->dbh, data->m_stmt, "42P10",
"column number must be non-negative");
return false;
}
if (!data->m_stmt->support(PDOStatement::MethodGetColumnMeta)) {
pdo_raise_impl_error(data->m_stmt->dbh, data->m_stmt, "IM001",
"driver doesn't support meta data");
return false;
}
setPDOErrorNone(data->m_stmt->error_code);
auto ret = Array::CreateDict();
if (!data->m_stmt->getColumnMeta(column, ret)) {
PDO_HANDLE_STMT_ERR(data->m_stmt);
return false;
}
/* add stock items */
auto col = cast<PDOColumn>(data->m_stmt->columns[column]);
ret.set(s_name, col->name);
ret.set(s_len, (int64_t)col->maxlen); /* FIXME: unsigned ? */
ret.set(s_precision, (int64_t)col->precision);
if (col->param_type != PDO_PARAM_ZVAL) {
// if param_type is PDO_PARAM_ZVAL the driver has to provide correct data
ret.set(s_pdo_type, (int64_t)col->param_type);
}
return ret;
}
static bool HHVM_METHOD(PDOStatement, setfetchmode,
int64_t mode, const Array& _argv /* = null_array */) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return false;
}
int argc = _argv.size() + 1;
return pdo_stmt_set_fetch_mode(data->m_stmt, argc, mode, _argv);
}
static bool HHVM_METHOD(PDOStatement, nextrowset) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return false;
}
if (!data->m_stmt->support(PDOStatement::MethodNextRowset)) {
pdo_raise_impl_error(data->m_stmt->dbh, data->m_stmt, "IM001",
"driver does not support multiple rowsets");
return false;
}
setPDOErrorNone(data->m_stmt->error_code);
/* un-describe */
if (!data->m_stmt->columns.empty()) {
data->m_stmt->columns.clear();
data->m_stmt->column_count = 0;
}
if (!data->m_stmt->nextRowset()) {
PDO_HANDLE_STMT_ERR(data->m_stmt);
return false;
}
pdo_stmt_describe_columns(data->m_stmt);
return true;
}
static bool HHVM_METHOD(PDOStatement, closecursor) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return false;
}
if (!data->m_stmt->support(PDOStatement::MethodCursorCloser)) {
/* emulate it by fetching and discarding rows */
do {
while (data->m_stmt->fetcher(PDO_FETCH_ORI_NEXT, 0));
// if (!data->t_nextrowset()) {
if (HHVM_MN(PDOStatement, nextrowset)(this_)) {
break;
}
} while (true);
data->m_stmt->executed = 0;
return true;
}
setPDOErrorNone(data->m_stmt->error_code);
if (!data->m_stmt->cursorCloser()) {
PDO_HANDLE_STMT_ERR(data->m_stmt);
return false;
}
data->m_stmt->executed = 0;
return true;
}
static Variant HHVM_METHOD(PDOStatement, debugdumpparams) {
auto data = Native::data<PDOStatementData>(this_);
if (data->m_stmt == nullptr) {
return false;
}
auto f = File::Open("php://output", "w");
if (!f || f->isInvalid()) {
return false;
}
f->printf(
"SQL: [%d] %.*s\n",
make_vec_array(
data->m_stmt->query_string.size(),
data->m_stmt->query_string.size(),
data->m_stmt->query_string.data()
)
);
f->printf("Params: %d\n",
make_vec_array(data->m_stmt->bound_params.size()));
for (ArrayIter iter(data->m_stmt->bound_params); iter; ++iter) {
if (iter.first().isString()) {
String key = iter.first().toString();
f->printf(
"Key: Name: [%d] %.*s\n",
make_vec_array(key.size(), key.size(), key.data())
);
} else {
f->printf("Key: Position #%ld:\n",
make_vec_array(iter.first().toInt64()));
}
auto param = cast<PDOBoundParam>(iter.second());
f->printf(
"paramno=%d\nname=[%d] \"%.*s\"\nparam_type=%d\n",
make_vec_array(
param->paramno,
param->name.size(),
param->name.size(),
param->name.data(),
param->param_type
)
);
}
return true;
}
static Variant HHVM_METHOD(PDOStatement, current) {
auto data = Native::data<PDOStatementData>(this_);
return data->m_row;
}
static Variant HHVM_METHOD(PDOStatement, key) {
auto data = Native::data<PDOStatementData>(this_);
return data->m_rowIndex;
}
static Variant HHVM_METHOD(PDOStatement, next) {
auto data = Native::data<PDOStatementData>(this_);
data->m_row = HHVM_MN(PDOStatement, fetch)(this_, PDO_FETCH_USE_DEFAULT);
if (same(data->m_row, false)) {
data->m_rowIndex = -1;
} else {
++data->m_rowIndex;
}
return init_null();
}
static Variant HHVM_METHOD(PDOStatement, rewind) {
auto data = Native::data<PDOStatementData>(this_);
data->m_rowIndex = -1;
HHVM_MN(PDOStatement, next)(this_);
return init_null();
}
static Variant HHVM_METHOD(PDOStatement, valid) {
auto data = Native::data<PDOStatementData>(this_);
return data->m_rowIndex >= 0;
}
static Variant HHVM_METHOD(PDOStatement, __wakeup) {
throw_pdo_exception(uninit_null(),
"You cannot serialize or unserialize "
"PDOStatement instances");
return init_null();
}
static Variant HHVM_METHOD(PDOStatement, __sleep) {
throw_pdo_exception(uninit_null(),
"You cannot serialize or unserialize "
"PDOStatement instances");
return init_null();
}
///////////////////////////////////////////////////////////////////////////////
static struct PDOExtension final : Extension {
PDOExtension() : Extension("pdo", " 1.0.4dev") {}
#ifdef ENABLE_EXTENSION_PDO_MYSQL
std::string mysql_default_socket;
void moduleLoad(const IniSetting::Map& /*ini*/, Hdf /*config*/) override {
IniSetting::Bind(this, IniSetting::PHP_INI_SYSTEM,
"pdo_mysql.default_socket", nullptr,
&mysql_default_socket);
}
#endif
void moduleInit() override {
HHVM_FE(pdo_drivers);
HHVM_ME(PDO, __construct);
HHVM_ME(PDO, prepare);
HHVM_ME(PDO, begintransaction);
HHVM_ME(PDO, commit);
HHVM_ME(PDO, intransaction);
HHVM_ME(PDO, rollback);
HHVM_ME(PDO, setattribute);
HHVM_ME(PDO, getAttribute);
HHVM_ME(PDO, exec);
HHVM_ME(PDO, lastinsertid);
HHVM_ME(PDO, errorcode);
HHVM_ME(PDO, errorinfo);
HHVM_ME(PDO, query);
HHVM_ME(PDO, quote);
HHVM_ME(PDO, sqlitecreatefunction);
HHVM_ME(PDO, sqlitecreateaggregate);
HHVM_ME(PDO, __wakeup);
HHVM_ME(PDO, __sleep);
HHVM_STATIC_ME(PDO, getAvailableDrivers);
HHVM_ME(PDOStatement, execute);
HHVM_ME(PDOStatement, fetch);
HHVM_ME(PDOStatement, fetchobject);
HHVM_ME(PDOStatement, fetchcolumn);
HHVM_ME(PDOStatement, fetchall);
HHVM_ME(PDOStatement, bindvalue);
HHVM_ME(PDOStatement, rowcount);
HHVM_ME(PDOStatement, errorcode);
HHVM_ME(PDOStatement, errorinfo);
HHVM_ME(PDOStatement, setattribute);
HHVM_ME(PDOStatement, getAttribute);
HHVM_ME(PDOStatement, columncount);
HHVM_ME(PDOStatement, getcolumnmeta);
HHVM_ME(PDOStatement, setfetchmode);
HHVM_ME(PDOStatement, nextrowset);
HHVM_ME(PDOStatement, closecursor);
HHVM_ME(PDOStatement, debugdumpparams);
HHVM_ME(PDOStatement, current);
HHVM_ME(PDOStatement, key);
HHVM_ME(PDOStatement, next);
HHVM_ME(PDOStatement, rewind);
HHVM_ME(PDOStatement, valid);
HHVM_ME(PDOStatement, __wakeup);
HHVM_ME(PDOStatement, __sleep);
HHVM_RCC_INT(PDO, PARAM_BOOL, PDO_PARAM_BOOL);
HHVM_RCC_INT(PDO, PARAM_NULL, PDO_PARAM_NULL);
HHVM_RCC_INT(PDO, PARAM_INT, PDO_PARAM_INT);
HHVM_RCC_INT(PDO, PARAM_STR, PDO_PARAM_STR);
HHVM_RCC_INT(PDO, PARAM_LOB, PDO_PARAM_LOB);
HHVM_RCC_INT(PDO, PARAM_STMT, PDO_PARAM_STMT);
HHVM_RCC_INT(PDO, PARAM_INPUT_OUTPUT, PDO_PARAM_INPUT_OUTPUT);
HHVM_RCC_INT(PDO, PARAM_EVT_ALLOC, PDO_PARAM_EVT_ALLOC);
HHVM_RCC_INT(PDO, PARAM_EVT_FREE, PDO_PARAM_EVT_FREE);
HHVM_RCC_INT(PDO, PARAM_EVT_EXEC_PRE, PDO_PARAM_EVT_EXEC_PRE);
HHVM_RCC_INT(PDO, PARAM_EVT_EXEC_POST, PDO_PARAM_EVT_EXEC_POST);
HHVM_RCC_INT(PDO, PARAM_EVT_FETCH_PRE, PDO_PARAM_EVT_FETCH_PRE);
HHVM_RCC_INT(PDO, PARAM_EVT_FETCH_POST, PDO_PARAM_EVT_FETCH_POST);
HHVM_RCC_INT(PDO, PARAM_EVT_NORMALIZE, PDO_PARAM_EVT_NORMALIZE);
HHVM_RCC_INT(PDO, FETCH_USE_DEFAULT, PDO_FETCH_USE_DEFAULT);
HHVM_RCC_INT(PDO, FETCH_LAZY, PDO_FETCH_LAZY);
HHVM_RCC_INT(PDO, FETCH_ASSOC, PDO_FETCH_ASSOC);
HHVM_RCC_INT(PDO, FETCH_NUM, PDO_FETCH_NUM);
HHVM_RCC_INT(PDO, FETCH_BOTH, PDO_FETCH_BOTH);
HHVM_RCC_INT(PDO, FETCH_OBJ, PDO_FETCH_OBJ);
HHVM_RCC_INT(PDO, FETCH_BOUND, PDO_FETCH_BOUND);
HHVM_RCC_INT(PDO, FETCH_COLUMN, PDO_FETCH_COLUMN);
HHVM_RCC_INT(PDO, FETCH_CLASS, PDO_FETCH_CLASS);
HHVM_RCC_INT(PDO, FETCH_INTO, PDO_FETCH_INTO);
HHVM_RCC_INT(PDO, FETCH_FUNC, PDO_FETCH_FUNC);
HHVM_RCC_INT(PDO, FETCH_GROUP, PDO_FETCH_GROUP);
HHVM_RCC_INT(PDO, FETCH_UNIQUE, PDO_FETCH_UNIQUE);
HHVM_RCC_INT(PDO, FETCH_KEY_PAIR, PDO_FETCH_KEY_PAIR);
HHVM_RCC_INT(PDO, FETCH_CLASSTYPE, PDO_FETCH_CLASSTYPE);
HHVM_RCC_INT(PDO, FETCH_SERIALIZE, PDO_FETCH_SERIALIZE);
HHVM_RCC_INT(PDO, FETCH_PROPS_LATE, PDO_FETCH_PROPS_LATE);
HHVM_RCC_INT(PDO, FETCH_NAMED, PDO_FETCH_NAMED);
HHVM_RCC_INT(PDO, ATTR_AUTOCOMMIT, PDO_ATTR_AUTOCOMMIT);
HHVM_RCC_INT(PDO, ATTR_PREFETCH, PDO_ATTR_PREFETCH);
HHVM_RCC_INT(PDO, ATTR_TIMEOUT, PDO_ATTR_TIMEOUT);
HHVM_RCC_INT(PDO, ATTR_ERRMODE, PDO_ATTR_ERRMODE);
HHVM_RCC_INT(PDO, ATTR_SERVER_VERSION, PDO_ATTR_SERVER_VERSION);
HHVM_RCC_INT(PDO, ATTR_CLIENT_VERSION, PDO_ATTR_CLIENT_VERSION);
HHVM_RCC_INT(PDO, ATTR_SERVER_INFO, PDO_ATTR_SERVER_INFO);
HHVM_RCC_INT(PDO, ATTR_CONNECTION_STATUS, PDO_ATTR_CONNECTION_STATUS);
HHVM_RCC_INT(PDO, ATTR_CASE, PDO_ATTR_CASE);
HHVM_RCC_INT(PDO, ATTR_CURSOR_NAME, PDO_ATTR_CURSOR_NAME);
HHVM_RCC_INT(PDO, ATTR_CURSOR, PDO_ATTR_CURSOR);
HHVM_RCC_INT(PDO, ATTR_ORACLE_NULLS, PDO_ATTR_ORACLE_NULLS);
HHVM_RCC_INT(PDO, ATTR_PERSISTENT, PDO_ATTR_PERSISTENT);
HHVM_RCC_INT(PDO, ATTR_STATEMENT_CLASS, PDO_ATTR_STATEMENT_CLASS);
HHVM_RCC_INT(PDO, ATTR_FETCH_TABLE_NAMES, PDO_ATTR_FETCH_TABLE_NAMES);
HHVM_RCC_INT(PDO, ATTR_FETCH_CATALOG_NAMES, PDO_ATTR_FETCH_CATALOG_NAMES);
HHVM_RCC_INT(PDO, ATTR_DRIVER_NAME, PDO_ATTR_DRIVER_NAME);
HHVM_RCC_INT(PDO, ATTR_STRINGIFY_FETCHES, PDO_ATTR_STRINGIFY_FETCHES);
HHVM_RCC_INT(PDO, ATTR_MAX_COLUMN_LEN, PDO_ATTR_MAX_COLUMN_LEN);
HHVM_RCC_INT(PDO, ATTR_EMULATE_PREPARES, PDO_ATTR_EMULATE_PREPARES);
HHVM_RCC_INT(PDO, ATTR_DEFAULT_FETCH_MODE, PDO_ATTR_DEFAULT_FETCH_MODE);
HHVM_RCC_INT(PDO, ERRMODE_SILENT, PDO_ERRMODE_SILENT);
HHVM_RCC_INT(PDO, ERRMODE_WARNING, PDO_ERRMODE_WARNING);
HHVM_RCC_INT(PDO, ERRMODE_EXCEPTION, PDO_ERRMODE_EXCEPTION);
HHVM_RCC_INT(PDO, CASE_NATURAL, PDO_CASE_NATURAL);
HHVM_RCC_INT(PDO, CASE_LOWER, PDO_CASE_LOWER);
HHVM_RCC_INT(PDO, CASE_UPPER, PDO_CASE_UPPER);
HHVM_RCC_INT(PDO, NULL_NATURAL, PDO_NULL_NATURAL);
HHVM_RCC_INT(PDO, NULL_EMPTY_STRING, PDO_NULL_EMPTY_STRING);
HHVM_RCC_INT(PDO, NULL_TO_STRING, PDO_NULL_TO_STRING);
HHVM_RCC_INT(PDO, FETCH_ORI_NEXT, PDO_FETCH_ORI_NEXT);
HHVM_RCC_INT(PDO, FETCH_ORI_PRIOR, PDO_FETCH_ORI_PRIOR);
HHVM_RCC_INT(PDO, FETCH_ORI_FIRST, PDO_FETCH_ORI_FIRST);
HHVM_RCC_INT(PDO, FETCH_ORI_LAST, PDO_FETCH_ORI_LAST);
HHVM_RCC_INT(PDO, FETCH_ORI_ABS, PDO_FETCH_ORI_ABS);
HHVM_RCC_INT(PDO, FETCH_ORI_REL, PDO_FETCH_ORI_REL);
HHVM_RCC_INT(PDO, CURSOR_FWDONLY, PDO_CURSOR_FWDONLY);
HHVM_RCC_INT(PDO, CURSOR_SCROLL, PDO_CURSOR_SCROLL);
#ifdef ENABLE_EXTENSION_PDO_MYSQL
HHVM_RCC_INT(PDO, MYSQL_ATTR_USE_BUFFERED_QUERY,
PDO_MYSQL_ATTR_USE_BUFFERED_QUERY);
HHVM_RCC_INT(PDO, MYSQL_ATTR_LOCAL_INFILE, PDO_MYSQL_ATTR_LOCAL_INFILE);
HHVM_RCC_INT(PDO, MYSQL_ATTR_MAX_BUFFER_SIZE,
PDO_MYSQL_ATTR_MAX_BUFFER_SIZE);
HHVM_RCC_INT(PDO, MYSQL_ATTR_INIT_COMMAND, PDO_MYSQL_ATTR_INIT_COMMAND);
HHVM_RCC_INT(PDO, MYSQL_ATTR_READ_DEFAULT_FILE,
PDO_MYSQL_ATTR_READ_DEFAULT_FILE);
HHVM_RCC_INT(PDO, MYSQL_ATTR_READ_DEFAULT_GROUP,
PDO_MYSQL_ATTR_READ_DEFAULT_GROUP);
HHVM_RCC_INT(PDO, MYSQL_ATTR_COMPRESS, PDO_MYSQL_ATTR_COMPRESS);
HHVM_RCC_INT(PDO, MYSQL_ATTR_DIRECT_QUERY, PDO_MYSQL_ATTR_DIRECT_QUERY);
HHVM_RCC_INT(PDO, MYSQL_ATTR_FOUND_ROWS, PDO_MYSQL_ATTR_FOUND_ROWS);
HHVM_RCC_INT(PDO, MYSQL_ATTR_IGNORE_SPACE, PDO_MYSQL_ATTR_IGNORE_SPACE);
HHVM_RCC_INT(PDO, MYSQL_ATTR_SSL_CA, PDO_MYSQL_ATTR_SSL_CA);
HHVM_RCC_INT(PDO, MYSQL_ATTR_SSL_CAPATH, PDO_MYSQL_ATTR_SSL_CAPATH);
HHVM_RCC_INT(PDO, MYSQL_ATTR_SSL_CERT, PDO_MYSQL_ATTR_SSL_CERT);
HHVM_RCC_INT(PDO, MYSQL_ATTR_SSL_KEY, PDO_MYSQL_ATTR_SSL_KEY);
HHVM_RCC_INT(PDO, MYSQL_ATTR_SSL_CIPHER, PDO_MYSQL_ATTR_SSL_CIPHER);
HHVM_RCC_INT(PDO, MYSQL_ATTR_MULTI_STATEMENTS,
PDO_MYSQL_ATTR_MULTI_STATEMENTS);
HHVM_RCC_INT(PDO, HH_MYSQL_ATTR_READ_TIMEOUT,
HH_PDO_MYSQL_ATTR_READ_TIMEOUT);
HHVM_RCC_INT(PDO, HH_MYSQL_ATTR_WRITE_TIMEOUT,
HH_PDO_MYSQL_ATTR_WRITE_TIMEOUT);
#endif
HHVM_RCC_STR(PDO, ERR_NONE, PDO_ERR_NONE);
Native::registerNativeDataInfo<PDOData>(
s_PDO.get(), Native::NDIFlags::NO_SWEEP);
Native::registerNativeDataInfo<PDOStatementData>(
s_PDOStatement.get(), Native::NDIFlags::NO_SWEEP);
loadSystemlib("pdo");
}
} s_pdo_extension;
//////////////////////////////////////////////////////////////////////////////
}