e70567994c
- fix shutdown crash when using PDO Closes #710
3156 linhas
94 KiB
C++
3156 linhas
94 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 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 "hphp/runtime/ext/ext_pdo.h"
|
|
#include "hphp/runtime/ext/pdo_driver.h"
|
|
#include "hphp/runtime/ext/pdo_mysql.h"
|
|
#include "hphp/runtime/ext/ext_class.h"
|
|
#include "hphp/runtime/ext/ext_function.h"
|
|
#include "hphp/runtime/ext/ext_stream.h"
|
|
#include "hphp/runtime/base/class_info.h"
|
|
#include "hphp/runtime/base/ini_setting.h"
|
|
#include "hphp/runtime/base/string_buffer.h"
|
|
#include "hphp/runtime/base/request_local.h"
|
|
#include "hphp/runtime/base/macros.h"
|
|
|
|
#include "hphp/system/systemlib.h"
|
|
|
|
#define PDO_HANDLE_DBH_ERR(dbh) \
|
|
if (strcmp(dbh->error_code, PDO_ERR_NONE)) { \
|
|
pdo_handle_error(dbh, NULL); \
|
|
} \
|
|
|
|
#define PDO_HANDLE_STMT_ERR(stmt) \
|
|
if (strcmp(stmt->error_code, PDO_ERR_NONE)) { \
|
|
pdo_handle_error(stmt->dbh, stmt); \
|
|
} \
|
|
|
|
namespace HPHP {
|
|
IMPLEMENT_DEFAULT_EXTENSION(PDO);
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// PDO constants
|
|
|
|
const int64_t q_PDO$$PARAM_BOOL = PDO_PARAM_BOOL;
|
|
const int64_t q_PDO$$PARAM_NULL = PDO_PARAM_NULL;
|
|
const int64_t q_PDO$$PARAM_INT = PDO_PARAM_INT;
|
|
const int64_t q_PDO$$PARAM_STR = PDO_PARAM_STR;
|
|
const int64_t q_PDO$$PARAM_LOB = PDO_PARAM_LOB;
|
|
const int64_t q_PDO$$PARAM_STMT = PDO_PARAM_STMT;
|
|
const int64_t q_PDO$$PARAM_INPUT_OUTPUT = PDO_PARAM_INPUT_OUTPUT;
|
|
|
|
const int64_t q_PDO$$PARAM_EVT_ALLOC = PDO_PARAM_EVT_ALLOC;
|
|
const int64_t q_PDO$$PARAM_EVT_FREE = PDO_PARAM_EVT_FREE;
|
|
const int64_t q_PDO$$PARAM_EVT_EXEC_PRE = PDO_PARAM_EVT_EXEC_PRE;
|
|
const int64_t q_PDO$$PARAM_EVT_EXEC_POST = PDO_PARAM_EVT_EXEC_POST;
|
|
const int64_t q_PDO$$PARAM_EVT_FETCH_PRE = PDO_PARAM_EVT_FETCH_PRE;
|
|
const int64_t q_PDO$$PARAM_EVT_FETCH_POST = PDO_PARAM_EVT_FETCH_POST;
|
|
const int64_t q_PDO$$PARAM_EVT_NORMALIZE = PDO_PARAM_EVT_NORMALIZE;
|
|
|
|
const int64_t q_PDO$$FETCH_USE_DEFAULT = PDO_FETCH_USE_DEFAULT;
|
|
const int64_t q_PDO$$FETCH_LAZY = PDO_FETCH_LAZY;
|
|
const int64_t q_PDO$$FETCH_ASSOC = PDO_FETCH_ASSOC;
|
|
const int64_t q_PDO$$FETCH_NUM = PDO_FETCH_NUM;
|
|
const int64_t q_PDO$$FETCH_BOTH = PDO_FETCH_BOTH;
|
|
const int64_t q_PDO$$FETCH_OBJ = PDO_FETCH_OBJ;
|
|
const int64_t q_PDO$$FETCH_BOUND = PDO_FETCH_BOUND;
|
|
const int64_t q_PDO$$FETCH_COLUMN = PDO_FETCH_COLUMN;
|
|
const int64_t q_PDO$$FETCH_CLASS = PDO_FETCH_CLASS;
|
|
const int64_t q_PDO$$FETCH_INTO = PDO_FETCH_INTO;
|
|
const int64_t q_PDO$$FETCH_FUNC = PDO_FETCH_FUNC;
|
|
const int64_t q_PDO$$FETCH_GROUP = PDO_FETCH_GROUP;
|
|
const int64_t q_PDO$$FETCH_UNIQUE = PDO_FETCH_UNIQUE;
|
|
const int64_t q_PDO$$FETCH_KEY_PAIR = PDO_FETCH_KEY_PAIR;
|
|
const int64_t q_PDO$$FETCH_CLASSTYPE = PDO_FETCH_CLASSTYPE;
|
|
const int64_t q_PDO$$FETCH_SERIALIZE = PDO_FETCH_SERIALIZE;
|
|
const int64_t q_PDO$$FETCH_PROPS_LATE = PDO_FETCH_PROPS_LATE;
|
|
const int64_t q_PDO$$FETCH_NAMED = PDO_FETCH_NAMED;
|
|
|
|
const int64_t q_PDO$$ATTR_AUTOCOMMIT = PDO_ATTR_AUTOCOMMIT;
|
|
const int64_t q_PDO$$ATTR_PREFETCH = PDO_ATTR_PREFETCH;
|
|
const int64_t q_PDO$$ATTR_TIMEOUT = PDO_ATTR_TIMEOUT;
|
|
const int64_t q_PDO$$ATTR_ERRMODE = PDO_ATTR_ERRMODE;
|
|
const int64_t q_PDO$$ATTR_SERVER_VERSION = PDO_ATTR_SERVER_VERSION;
|
|
const int64_t q_PDO$$ATTR_CLIENT_VERSION = PDO_ATTR_CLIENT_VERSION;
|
|
const int64_t q_PDO$$ATTR_SERVER_INFO = PDO_ATTR_SERVER_INFO;
|
|
const int64_t q_PDO$$ATTR_CONNECTION_STATUS = PDO_ATTR_CONNECTION_STATUS;
|
|
const int64_t q_PDO$$ATTR_CASE = PDO_ATTR_CASE;
|
|
const int64_t q_PDO$$ATTR_CURSOR_NAME = PDO_ATTR_CURSOR_NAME;
|
|
const int64_t q_PDO$$ATTR_CURSOR = PDO_ATTR_CURSOR;
|
|
const int64_t q_PDO$$ATTR_ORACLE_NULLS = PDO_ATTR_ORACLE_NULLS;
|
|
const int64_t q_PDO$$ATTR_PERSISTENT = PDO_ATTR_PERSISTENT;
|
|
const int64_t q_PDO$$ATTR_STATEMENT_CLASS = PDO_ATTR_STATEMENT_CLASS;
|
|
const int64_t q_PDO$$ATTR_FETCH_TABLE_NAMES = PDO_ATTR_FETCH_TABLE_NAMES;
|
|
const int64_t q_PDO$$ATTR_FETCH_CATALOG_NAMES = PDO_ATTR_FETCH_CATALOG_NAMES;
|
|
const int64_t q_PDO$$ATTR_DRIVER_NAME = PDO_ATTR_DRIVER_NAME;
|
|
const int64_t q_PDO$$ATTR_STRINGIFY_FETCHES = PDO_ATTR_STRINGIFY_FETCHES;
|
|
const int64_t q_PDO$$ATTR_MAX_COLUMN_LEN = PDO_ATTR_MAX_COLUMN_LEN;
|
|
const int64_t q_PDO$$ATTR_EMULATE_PREPARES = PDO_ATTR_EMULATE_PREPARES;
|
|
const int64_t q_PDO$$ATTR_DEFAULT_FETCH_MODE = PDO_ATTR_DEFAULT_FETCH_MODE;
|
|
|
|
const int64_t q_PDO$$ERRMODE_SILENT = PDO_ERRMODE_SILENT;
|
|
const int64_t q_PDO$$ERRMODE_WARNING = PDO_ERRMODE_WARNING;
|
|
const int64_t q_PDO$$ERRMODE_EXCEPTION = PDO_ERRMODE_EXCEPTION;
|
|
|
|
const int64_t q_PDO$$CASE_NATURAL = PDO_CASE_NATURAL;
|
|
const int64_t q_PDO$$CASE_LOWER = PDO_CASE_LOWER;
|
|
const int64_t q_PDO$$CASE_UPPER = PDO_CASE_UPPER;
|
|
|
|
const int64_t q_PDO$$NULL_NATURAL = PDO_NULL_NATURAL;
|
|
const int64_t q_PDO$$NULL_EMPTY_STRING = PDO_NULL_EMPTY_STRING;
|
|
const int64_t q_PDO$$NULL_TO_STRING = PDO_NULL_TO_STRING;
|
|
|
|
const StaticString q_PDO$$ERR_NONE(LITSTR_INIT(PDO_ERR_NONE));
|
|
|
|
const int64_t q_PDO$$FETCH_ORI_NEXT = PDO_FETCH_ORI_NEXT;
|
|
const int64_t q_PDO$$FETCH_ORI_PRIOR = PDO_FETCH_ORI_PRIOR;
|
|
const int64_t q_PDO$$FETCH_ORI_FIRST = PDO_FETCH_ORI_FIRST;
|
|
const int64_t q_PDO$$FETCH_ORI_LAST = PDO_FETCH_ORI_LAST;
|
|
const int64_t q_PDO$$FETCH_ORI_ABS = PDO_FETCH_ORI_ABS;
|
|
const int64_t q_PDO$$FETCH_ORI_REL = PDO_FETCH_ORI_REL;
|
|
|
|
const int64_t q_PDO$$CURSOR_FWDONLY = PDO_CURSOR_FWDONLY;
|
|
const int64_t q_PDO$$CURSOR_SCROLL = PDO_CURSOR_SCROLL;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
const int64_t q_PDO$$MYSQL_ATTR_USE_BUFFERED_QUERY =
|
|
PDO_MYSQL_ATTR_USE_BUFFERED_QUERY;
|
|
const int64_t q_PDO$$MYSQL_ATTR_LOCAL_INFILE = PDO_MYSQL_ATTR_LOCAL_INFILE;
|
|
const int64_t q_PDO$$MYSQL_ATTR_MAX_BUFFER_SIZE =
|
|
PDO_MYSQL_ATTR_MAX_BUFFER_SIZE;
|
|
const int64_t q_PDO$$MYSQL_ATTR_INIT_COMMAND = PDO_MYSQL_ATTR_INIT_COMMAND;
|
|
const int64_t q_PDO$$MYSQL_ATTR_READ_DEFAULT_FILE =
|
|
PDO_MYSQL_ATTR_READ_DEFAULT_FILE;
|
|
const int64_t q_PDO$$MYSQL_ATTR_READ_DEFAULT_GROUP =
|
|
PDO_MYSQL_ATTR_READ_DEFAULT_GROUP;
|
|
const int64_t q_PDO$$MYSQL_ATTR_COMPRESS = PDO_MYSQL_ATTR_COMPRESS;
|
|
const int64_t q_PDO$$MYSQL_ATTR_DIRECT_QUERY = PDO_MYSQL_ATTR_DIRECT_QUERY;
|
|
const int64_t q_PDO$$MYSQL_ATTR_FOUND_ROWS = PDO_MYSQL_ATTR_FOUND_ROWS;
|
|
const int64_t q_PDO$$MYSQL_ATTR_IGNORE_SPACE = PDO_MYSQL_ATTR_IGNORE_SPACE;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// extension functions
|
|
|
|
Array f_pdo_drivers() {
|
|
Array ret = Array::Create();
|
|
const PDODriverMap &drivers = PDODriver::GetDrivers();
|
|
for (PDODriverMap::const_iterator iter = drivers.begin();
|
|
iter != drivers.end(); ++iter) {
|
|
ret.append(iter->second->getName());
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// 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" }
|
|
};
|
|
|
|
class PDOErrorHash : private hphp_const_char_map<const char *> {
|
|
public:
|
|
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;
|
|
|
|
static const StaticString s_code("code");
|
|
static const StaticString s_message("message");
|
|
static const StaticString s_errorInfo("errorInfo");
|
|
static const StaticString s_PDOException("PDOException");
|
|
|
|
void throw_pdo_exception(CVarRef code, CVarRef info, const char *fmt, ...) {
|
|
ObjectData *obj = SystemLib::AllocPDOExceptionObject();
|
|
obj->o_set(s_code, code, s_PDOException);
|
|
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
string msg;
|
|
Util::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_PDOConnection dbh, sp_PDOStatement stmt,
|
|
const char *sqlstate, const char *supp) {
|
|
PDOErrorType *pdo_err = &dbh->error_code;
|
|
if (stmt.get()) {
|
|
pdo_err = &stmt->error_code;
|
|
}
|
|
strcpy(*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 {
|
|
Array info;
|
|
info.append(String(*pdo_err, CopyString));
|
|
info.append(0LL);
|
|
throw_pdo_exception(String(sqlstate, CopyString), info, "%s", err.c_str());
|
|
}
|
|
}
|
|
|
|
static void pdo_handle_error(sp_PDOConnection dbh, sp_PDOStatement stmt) {
|
|
if (dbh->error_mode == PDO_ERRMODE_SILENT) {
|
|
return;
|
|
}
|
|
PDOErrorType *pdo_err = &dbh->error_code;
|
|
if (stmt.get()) {
|
|
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 = Array::Create();
|
|
info.append(String(*pdo_err, CopyString));
|
|
if (dbh->fetchErr(stmt.get(), 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(String(*pdo_err, CopyString), info, "%s", err.c_str());
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// helpers for PDO class
|
|
|
|
static inline int64_t pdo_attr_lval(CArrRef options, PDOAttributeType name,
|
|
int64_t defval) {
|
|
if (options.exists(name)) {
|
|
return options[name].toInt64();
|
|
}
|
|
return defval;
|
|
}
|
|
|
|
static inline String pdo_attr_strval(CArrRef options, PDOAttributeType name,
|
|
CStrRef defval) {
|
|
if (options.exists(name)) {
|
|
return options[name].toString();
|
|
}
|
|
return defval;
|
|
}
|
|
|
|
static Object pdo_stmt_instantiate(sp_PDOConnection dbh, CStrRef clsname,
|
|
CVarRef ctor_args) {
|
|
String name = clsname;
|
|
if (name.empty()) {
|
|
name = "PDOStatement";
|
|
}
|
|
if (!ctor_args.isNull() && !ctor_args.isArray()) {
|
|
pdo_raise_impl_error(dbh, NULL, "HY000",
|
|
"constructor arguments must be passed as an array");
|
|
return Object();
|
|
}
|
|
Class* cls = Unit::loadClass(name.get());
|
|
if (!cls) {
|
|
return Object();
|
|
}
|
|
return ObjectData::newInstance(cls);
|
|
}
|
|
|
|
static void pdo_stmt_construct(sp_PDOStatement stmt, Object object,
|
|
CStrRef clsname, CVarRef ctor_args) {
|
|
if (clsname.empty()) {
|
|
return;
|
|
}
|
|
Class* cls = Unit::loadClass(clsname.get());
|
|
if (!cls) {
|
|
return;
|
|
}
|
|
object->o_set("queryString", stmt->query_string);
|
|
TypedValue ret;
|
|
ObjectData* inst = object.get();
|
|
inst->invokeUserMethod(&ret, cls->getCtor(), ctor_args.toArray());
|
|
tvRefcountedDecRef(&ret);
|
|
}
|
|
|
|
static bool valid_statement_class(sp_PDOConnection dbh, CVarRef opt,
|
|
String &clsname, Variant &ctor_args) {
|
|
if (!opt.isArray() || !opt.toArray().exists(0) || !opt[0].isString() ||
|
|
!f_class_exists(opt[0].toString())) {
|
|
pdo_raise_impl_error
|
|
(dbh, NULL, "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[0].toString();
|
|
if (!f_is_subclass_of(clsname, "PDOStatement")) {
|
|
pdo_raise_impl_error
|
|
(dbh, NULL, "HY000",
|
|
"user-supplied statement class must be derived from PDOStatement");
|
|
PDO_HANDLE_DBH_ERR(dbh);
|
|
return false;
|
|
}
|
|
HPHP::Class* cls = HPHP::Unit::loadClass(clsname.get());
|
|
if (cls) {
|
|
const HPHP::Func* method = cls->getDeclaredCtor();
|
|
if (method && method->isPublic()) {
|
|
pdo_raise_impl_error
|
|
(dbh, NULL, "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[1];
|
|
if (!item.isArray()) {
|
|
pdo_raise_impl_error
|
|
(dbh, NULL, "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;
|
|
}
|
|
|
|
String &name = stmt->columns[col].toResource().getTyped<PDOColumn>()->name;
|
|
|
|
/* if we are applying case conversions on column names, do so now */
|
|
if (stmt->dbh->native_case != stmt->dbh->desired_case &&
|
|
stmt->dbh->desired_case != PDO_CASE_NATURAL) {
|
|
switch (stmt->dbh->desired_case) {
|
|
case PDO_CASE_UPPER:
|
|
name = StringUtil::ToUpper(name);
|
|
break;
|
|
case PDO_CASE_LOWER:
|
|
name = StringUtil::ToLower(name);
|
|
break;
|
|
default:;
|
|
}
|
|
}
|
|
|
|
if (stmt->bound_columns.exists(name)) {
|
|
PDOBoundParam *param =
|
|
stmt->bound_columns[name].toResource().getTyped<PDOBoundParam>();
|
|
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::Unit::loadClass(clsname.get());
|
|
if (cls) {
|
|
const HPHP::Func* method = cls->getDeclaredCtor();
|
|
if (method) {
|
|
stmt->fetch.constructor = method->nameRef();
|
|
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,
|
|
CArrRef _argv) {
|
|
_argc = _argv.size() + 1;
|
|
|
|
if (stmt->default_fetch_type == PDO_FETCH_INTO) {
|
|
stmt->fetch.into.reset();
|
|
}
|
|
stmt->default_fetch_type = PDO_FETCH_BOTH;
|
|
|
|
if (!pdo_stmt_verify_mode(stmt, mode, false)) {
|
|
strcpy(stmt->error_code, PDO_ERR_NONE);
|
|
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 = f_class_exists(_argv[0].toString());
|
|
if (retval) {
|
|
stmt->fetch.clsname = _argv[0].toString();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (retval) {
|
|
stmt->fetch.ctor_args.reset();
|
|
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.
|
|
*
|
|
*/
|
|
strcpy(stmt->error_code, PDO_ERR_NONE);
|
|
return retval;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class PDORequestData : public RequestEventHandler {
|
|
public:
|
|
virtual void requestInit() {
|
|
}
|
|
|
|
virtual void requestShutdown() {
|
|
for (std::set<PDOConnection*>::iterator iter =
|
|
m_persistent_connections.begin();
|
|
iter != m_persistent_connections.end(); ++iter) {
|
|
PDOConnection *conn = *iter;
|
|
if (!conn) {
|
|
// Dead handle in the set
|
|
continue;
|
|
}
|
|
if (conn->support(PDOConnection::MethodCheckLiveness) &&
|
|
!conn->checkLiveness()) {
|
|
// Dead connection in the handle
|
|
continue;
|
|
}
|
|
// All seems right, save it
|
|
conn->persistentSave();
|
|
}
|
|
}
|
|
|
|
public:
|
|
std::set<PDOConnection*> m_persistent_connections;
|
|
};
|
|
IMPLEMENT_STATIC_REQUEST_LOCAL(PDORequestData, s_pdo_request_data);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// PDO
|
|
|
|
c_PDO::c_PDO(Class* cb) : ExtObjectData(cb) {
|
|
}
|
|
|
|
c_PDO::~c_PDO() {
|
|
m_dbh.reset(); // needed for sweeping
|
|
}
|
|
|
|
void c_PDO::t___construct(CStrRef dsn, CStrRef username /* = null_string */,
|
|
CStrRef password /* = null_string */,
|
|
CArrRef options /* = null_array */) {
|
|
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(), uninit_null(), "invalid data source name");
|
|
}
|
|
data_source = ini_dsn;
|
|
colon = strchr(data_source.data(), ':');
|
|
if (!colon) {
|
|
throw_pdo_exception(uninit_null(), 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 */
|
|
Variant stream = File::Open(data_source.substr(4), "rb");
|
|
if (same(stream, false)) {
|
|
throw_pdo_exception(uninit_null(), uninit_null(), "invalid data source URI");
|
|
}
|
|
data_source = stream.toResource().getTyped<File>()->readLine(1024);
|
|
colon = strchr(data_source.data(), ':');
|
|
if (!colon) {
|
|
throw_pdo_exception(uninit_null(), 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(), 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;
|
|
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();
|
|
/* let's see if we have one cached.... */
|
|
m_dbh = dynamic_cast<PDOConnection*>
|
|
(g_persistentObjects->get(PDOConnection::PersistentKey,
|
|
shashkey.data()));
|
|
|
|
if (m_dbh.get()) {
|
|
m_dbh->persistentRestore();
|
|
|
|
/* is the connection still alive ? */
|
|
if (m_dbh->support(PDOConnection::MethodCheckLiveness) &&
|
|
!m_dbh->checkLiveness()) {
|
|
/* nope... need to kill it */
|
|
s_pdo_request_data->m_persistent_connections.erase(m_dbh.get());
|
|
m_dbh = NULL;
|
|
} else {
|
|
/* Yep, use it and mark it for saving at rshutdown */
|
|
s_pdo_request_data->m_persistent_connections.insert(m_dbh.get());
|
|
}
|
|
}
|
|
|
|
if (m_dbh.get()) {
|
|
call_factory = false;
|
|
} else {
|
|
/* need a brand new pdbh */
|
|
m_dbh = driver->createConnection(colon+1, username, password, options);
|
|
if (m_dbh.get() == NULL) {
|
|
throw_pdo_exception(uninit_null(), uninit_null(), "unable to create a connection");
|
|
}
|
|
m_dbh->persistent_id = string(shashkey.data(), shashkey.size());
|
|
}
|
|
}
|
|
}
|
|
if (!m_dbh.get()) {
|
|
m_dbh = driver->createConnection(colon+1, username, password, options);
|
|
if (m_dbh.get() == NULL) {
|
|
throw_pdo_exception(uninit_null(), uninit_null(), "unable to create a connection");
|
|
}
|
|
}
|
|
|
|
if (call_factory) {
|
|
m_dbh->default_fetch_type = PDO_FETCH_BOTH;
|
|
}
|
|
|
|
m_dbh->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) {
|
|
t_setattribute(iter.first().toInt64(), iter.second());
|
|
}
|
|
} else if (m_dbh.get()) {
|
|
if (is_persistent) {
|
|
assert(!shashkey.empty());
|
|
g_persistentObjects->set(PDOConnection::PersistentKey, shashkey.data(),
|
|
m_dbh.get());
|
|
s_pdo_request_data->m_persistent_connections.insert(m_dbh.get());
|
|
}
|
|
|
|
m_dbh->driver = driver;
|
|
for (ArrayIter iter(options); iter; ++iter) {
|
|
t_setattribute(iter.first().toInt64(), iter.second());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Variant c_PDO::t_prepare(CStrRef statement,
|
|
CArrRef options /* = null_array */) {
|
|
assert(m_dbh->driver);
|
|
strcpy(m_dbh->error_code, PDO_ERR_NONE);
|
|
m_dbh->query_stmt = NULL;
|
|
|
|
String clsname;
|
|
Variant ctor_args;
|
|
if (options.exists(PDO_ATTR_STATEMENT_CLASS)) {
|
|
Variant opt = options[PDO_ATTR_STATEMENT_CLASS];
|
|
if (!valid_statement_class(m_dbh, opt, clsname, ctor_args)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
clsname = m_dbh->def_stmt_clsname;
|
|
ctor_args = m_dbh->def_stmt_ctor_args;
|
|
}
|
|
|
|
Object ret = pdo_stmt_instantiate(m_dbh, clsname, ctor_args);
|
|
if (ret.isNull()) {
|
|
pdo_raise_impl_error
|
|
(m_dbh, NULL, "HY000",
|
|
"failed to instantiate user-supplied statement class");
|
|
PDO_HANDLE_DBH_ERR(m_dbh);
|
|
return false;
|
|
}
|
|
c_PDOStatement *pdostmt = ret.getTyped<c_PDOStatement>();
|
|
|
|
if (m_dbh->preparer(statement, &pdostmt->m_stmt, options)) {
|
|
PDOStatement *stmt = pdostmt->m_stmt.get();
|
|
assert(stmt);
|
|
|
|
/* unconditionally keep this for later reference */
|
|
stmt->query_string = statement;
|
|
stmt->default_fetch_type = m_dbh->default_fetch_type;
|
|
stmt->dbh = m_dbh;
|
|
|
|
pdo_stmt_construct(stmt, ret, clsname, ctor_args);
|
|
return ret;
|
|
}
|
|
|
|
PDO_HANDLE_DBH_ERR(m_dbh);
|
|
return false;
|
|
}
|
|
|
|
bool c_PDO::t_begintransaction() {
|
|
if (m_dbh->in_txn) {
|
|
throw_pdo_exception(uninit_null(), uninit_null(), "There is already an active transaction");
|
|
}
|
|
if (m_dbh->begin()) {
|
|
m_dbh->in_txn = 1;
|
|
return true;
|
|
}
|
|
if (strcmp(m_dbh->error_code, PDO_ERR_NONE)) {
|
|
pdo_handle_error(m_dbh, NULL);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool c_PDO::t_commit() {
|
|
assert(m_dbh->driver);
|
|
if (!m_dbh->in_txn) {
|
|
throw_pdo_exception(uninit_null(), uninit_null(), "There is no active transaction");
|
|
}
|
|
if (m_dbh->commit()) {
|
|
m_dbh->in_txn = 0;
|
|
return true;
|
|
}
|
|
PDO_HANDLE_DBH_ERR(m_dbh);
|
|
return false;
|
|
}
|
|
|
|
bool c_PDO::t_rollback() {
|
|
assert(m_dbh->driver);
|
|
if (!m_dbh->in_txn) {
|
|
throw_pdo_exception(uninit_null(), uninit_null(), "There is no active transaction");
|
|
}
|
|
if (m_dbh->rollback()) {
|
|
m_dbh->in_txn = 0;
|
|
return true;
|
|
}
|
|
PDO_HANDLE_DBH_ERR(m_dbh);
|
|
return false;
|
|
}
|
|
|
|
bool c_PDO::t_setattribute(int64_t attribute, CVarRef value) {
|
|
assert(m_dbh->driver);
|
|
|
|
#define PDO_LONG_PARAM_CHECK \
|
|
if (!value.isInteger() && !value.isString() && !value.isBoolean()) { \
|
|
pdo_raise_impl_error(m_dbh, NULL, "HY000", \
|
|
"attribute value must be an integer"); \
|
|
PDO_HANDLE_DBH_ERR(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:
|
|
m_dbh->error_mode = (PDOErrorMode)value.toInt64();
|
|
return true;
|
|
default:
|
|
pdo_raise_impl_error(m_dbh, NULL, "HY000", "invalid error mode");
|
|
PDO_HANDLE_DBH_ERR(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:
|
|
m_dbh->desired_case = (PDOCaseConversion)value.toInt64();
|
|
return true;
|
|
default:
|
|
pdo_raise_impl_error(m_dbh, NULL, "HY000",
|
|
"invalid case folding mode");
|
|
PDO_HANDLE_DBH_ERR(m_dbh);
|
|
return false;
|
|
}
|
|
return false;
|
|
|
|
case PDO_ATTR_ORACLE_NULLS:
|
|
PDO_LONG_PARAM_CHECK;
|
|
m_dbh->oracle_nulls = value.toInt64();
|
|
return true;
|
|
|
|
case PDO_ATTR_DEFAULT_FETCH_MODE:
|
|
if (value.isArray()) {
|
|
if (value.toArray().exists(0)) {
|
|
Variant tmp = value[0];
|
|
if (tmp.isInteger() && ((tmp.toInt64() == PDO_FETCH_INTO ||
|
|
tmp.toInt64() == PDO_FETCH_CLASS))) {
|
|
pdo_raise_impl_error(m_dbh, NULL, "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(m_dbh, NULL, "HY000", "invalid fetch mode type");
|
|
return false;
|
|
}
|
|
m_dbh->default_fetch_type = (PDOFetchType)value.toInt64();
|
|
return true;
|
|
|
|
case PDO_ATTR_STRINGIFY_FETCHES:
|
|
PDO_LONG_PARAM_CHECK;
|
|
m_dbh->stringify = value.toInt64() ? 1 : 0;
|
|
return true;
|
|
|
|
case PDO_ATTR_STATEMENT_CLASS:
|
|
{
|
|
if (m_dbh->is_persistent) {
|
|
pdo_raise_impl_error(m_dbh, NULL, "HY000",
|
|
"PDO::ATTR_STATEMENT_CLASS cannot be used "
|
|
"with persistent PDO instances");
|
|
PDO_HANDLE_DBH_ERR(m_dbh);
|
|
return false;
|
|
}
|
|
String clsname;
|
|
if (!valid_statement_class(m_dbh, value, clsname,
|
|
m_dbh->def_stmt_ctor_args)) {
|
|
return false;
|
|
}
|
|
m_dbh->def_stmt_clsname = clsname.c_str();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (m_dbh->support(PDOConnection::MethodSetAttribute)) {
|
|
strcpy(m_dbh->error_code, PDO_ERR_NONE);
|
|
m_dbh->query_stmt = NULL;
|
|
if (m_dbh->setAttribute(attribute, value)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (attribute == PDO_ATTR_AUTOCOMMIT) {
|
|
throw_pdo_exception(uninit_null(), uninit_null(),
|
|
"The auto-commit mode cannot be changed for this "
|
|
"driver");
|
|
} else if (!m_dbh->support(PDOConnection::MethodSetAttribute)) {
|
|
pdo_raise_impl_error(m_dbh, NULL, "IM001",
|
|
"driver does not support setting attributes");
|
|
} else {
|
|
PDO_HANDLE_DBH_ERR(m_dbh);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Variant c_PDO::t_getattribute(int64_t attribute) {
|
|
assert(m_dbh->driver);
|
|
strcpy(m_dbh->error_code, PDO_ERR_NONE);
|
|
m_dbh->query_stmt = NULL;
|
|
|
|
/* handle generic PDO-level atributes */
|
|
switch (attribute) {
|
|
case PDO_ATTR_PERSISTENT:
|
|
return (bool)m_dbh->is_persistent;
|
|
|
|
case PDO_ATTR_CASE:
|
|
return (int64_t)m_dbh->desired_case;
|
|
|
|
case PDO_ATTR_ORACLE_NULLS:
|
|
return (int64_t)m_dbh->oracle_nulls;
|
|
|
|
case PDO_ATTR_ERRMODE:
|
|
return (int64_t)m_dbh->error_mode;
|
|
|
|
case PDO_ATTR_DRIVER_NAME:
|
|
return String(m_dbh->driver->getName());
|
|
|
|
case PDO_ATTR_STATEMENT_CLASS: {
|
|
Array ret;
|
|
ret.append(String(m_dbh->def_stmt_clsname));
|
|
if (!m_dbh->def_stmt_ctor_args.isNull()) {
|
|
ret.append(m_dbh->def_stmt_ctor_args);
|
|
}
|
|
return ret;
|
|
}
|
|
case PDO_ATTR_DEFAULT_FETCH_MODE:
|
|
return (int64_t)m_dbh->default_fetch_type;
|
|
}
|
|
|
|
if (!m_dbh->support(PDOConnection::MethodGetAttribute)) {
|
|
pdo_raise_impl_error(m_dbh, NULL, "IM001",
|
|
"driver does not support getting attributes");
|
|
return false;
|
|
}
|
|
|
|
Variant ret;
|
|
switch (m_dbh->getAttribute(attribute, ret)) {
|
|
case -1:
|
|
PDO_HANDLE_DBH_ERR(m_dbh);
|
|
return false;
|
|
case 0:
|
|
pdo_raise_impl_error(m_dbh, NULL, "IM001",
|
|
"driver does not support that attribute");
|
|
return false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Variant c_PDO::t_exec(CStrRef query) {
|
|
if (query.empty()) {
|
|
pdo_raise_impl_error(m_dbh, NULL, "HY000",
|
|
"trying to execute an empty query");
|
|
return false;
|
|
}
|
|
|
|
assert(m_dbh->driver);
|
|
strcpy(m_dbh->error_code, PDO_ERR_NONE);
|
|
m_dbh->query_stmt = NULL;
|
|
|
|
int64_t ret = m_dbh->doer(query);
|
|
if (ret == -1) {
|
|
PDO_HANDLE_DBH_ERR(m_dbh);
|
|
return false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Variant c_PDO::t_lastinsertid(CStrRef seqname /* = null_string */) {
|
|
assert(m_dbh->driver);
|
|
strcpy(m_dbh->error_code, PDO_ERR_NONE);
|
|
m_dbh->query_stmt = NULL;
|
|
|
|
if (!m_dbh->support(PDOConnection::MethodLastId)) {
|
|
pdo_raise_impl_error(m_dbh, NULL, "IM001",
|
|
"driver does not support lastInsertId()");
|
|
return false;
|
|
}
|
|
|
|
String ret = m_dbh->lastId(seqname.data());
|
|
if (ret.empty()) {
|
|
PDO_HANDLE_DBH_ERR(m_dbh);
|
|
return false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Variant c_PDO::t_errorcode() {
|
|
assert(m_dbh->driver);
|
|
if (m_dbh->query_stmt) {
|
|
return String(m_dbh->query_stmt->error_code, CopyString);
|
|
}
|
|
|
|
if (m_dbh->error_code[0] == '\0') {
|
|
return uninit_null();
|
|
}
|
|
|
|
/**
|
|
* Making sure that we fallback to the default implementation
|
|
* if the dbh->error_code is not null.
|
|
*/
|
|
return String(m_dbh->error_code, CopyString);
|
|
}
|
|
|
|
Array c_PDO::t_errorinfo() {
|
|
assert(m_dbh->driver);
|
|
|
|
Array ret;
|
|
if (m_dbh->query_stmt) {
|
|
ret.append(String(m_dbh->query_stmt->error_code, CopyString));
|
|
} else {
|
|
ret.append(String(m_dbh->error_code, CopyString));
|
|
}
|
|
|
|
if (m_dbh->support(PDOConnection::MethodFetchErr)) {
|
|
m_dbh->fetchErr(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(uninit_null());
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Variant c_PDO::t_query(CStrRef sql) {
|
|
assert(m_dbh->driver);
|
|
strcpy(m_dbh->error_code, PDO_ERR_NONE);
|
|
m_dbh->query_stmt = NULL;
|
|
|
|
Object ret = pdo_stmt_instantiate(m_dbh, m_dbh->def_stmt_clsname,
|
|
m_dbh->def_stmt_ctor_args);
|
|
if (ret.isNull()) {
|
|
pdo_raise_impl_error
|
|
(m_dbh, NULL, "HY000",
|
|
"failed to instantiate user supplied statement class");
|
|
return uninit_null();
|
|
}
|
|
c_PDOStatement *pdostmt = ret.getTyped<c_PDOStatement>();
|
|
|
|
if (m_dbh->preparer(sql, &pdostmt->m_stmt, Array())) {
|
|
PDOStatement *stmt = pdostmt->m_stmt.get();
|
|
assert(stmt);
|
|
|
|
/* unconditionally keep this for later reference */
|
|
stmt->query_string = sql;
|
|
stmt->default_fetch_type = m_dbh->default_fetch_type;
|
|
stmt->active_query_string = stmt->query_string;
|
|
stmt->dbh = m_dbh;
|
|
stmt->lazy_object_ref = uninit_null();
|
|
|
|
strcpy(stmt->error_code, PDO_ERR_NONE);
|
|
|
|
// when we add support for varargs here, we only need to set the stmt if
|
|
// the argument count is > 1
|
|
if (true || pdo_stmt_set_fetch_mode(stmt, 0, PDO_FETCH_BOTH, Array())) {
|
|
/* now execute the statement */
|
|
strcpy(stmt->error_code, PDO_ERR_NONE);
|
|
if (stmt->executer()) {
|
|
int ok = 1;
|
|
if (!stmt->executed) {
|
|
if (stmt->dbh->alloc_own_columns) {
|
|
ok = pdo_stmt_describe_columns(stmt);
|
|
}
|
|
stmt->executed = 1;
|
|
}
|
|
if (ok) {
|
|
pdo_stmt_construct(stmt, ret, m_dbh->def_stmt_clsname,
|
|
m_dbh->def_stmt_ctor_args);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
/* something broke */
|
|
m_dbh->query_stmt = stmt;
|
|
PDO_HANDLE_STMT_ERR(stmt);
|
|
} else {
|
|
PDO_HANDLE_DBH_ERR(m_dbh);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Variant c_PDO::t_quote(CStrRef str, int64_t paramtype /* = q_PDO$$PARAM_STR */) {
|
|
assert(m_dbh->driver);
|
|
strcpy(m_dbh->error_code, PDO_ERR_NONE);
|
|
m_dbh->query_stmt = NULL;
|
|
|
|
if (!m_dbh->support(PDOConnection::MethodQuoter)) {
|
|
pdo_raise_impl_error(m_dbh, NULL, "IM001",
|
|
"driver does not support quoting");
|
|
return false;
|
|
}
|
|
|
|
String quoted;
|
|
if (m_dbh->quoter(str, quoted, (PDOParamType)paramtype)) {
|
|
return quoted;
|
|
}
|
|
PDO_HANDLE_DBH_ERR(m_dbh);
|
|
return false;
|
|
}
|
|
|
|
Variant c_PDO::t___wakeup() {
|
|
throw_pdo_exception(uninit_null(), uninit_null(),
|
|
"You cannot serialize or unserialize PDO instances");
|
|
return uninit_null();
|
|
}
|
|
|
|
Variant c_PDO::t___sleep() {
|
|
throw_pdo_exception(uninit_null(), uninit_null(),
|
|
"You cannot serialize or unserialize PDO instances");
|
|
return uninit_null();
|
|
}
|
|
|
|
Array c_PDO::ti_getavailabledrivers() {
|
|
return f_pdo_drivers();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
static inline bool rewrite_name_to_position(sp_PDOStatement stmt,
|
|
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) {
|
|
PDOBoundParam *param = iter.second().toResource().getTyped<PDOBoundParam>();
|
|
if (!stmt->paramHook(param, event_type)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (ArrayIter iter(stmt->bound_columns); iter; ++iter) {
|
|
PDOBoundParam *param = iter.second().toResource().getTyped<PDOBoundParam>();
|
|
if (!stmt->paramHook(param, 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(PDOBoundParam *param,
|
|
sp_PDOStatement stmt, bool is_param) {
|
|
Array &hash = is_param ? stmt->bound_params : stmt->bound_columns;
|
|
|
|
if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_STR &&
|
|
param->max_value_len <= 0 && !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();
|
|
param->is_param = is_param;
|
|
|
|
if (!is_param && !param->name.empty() && !stmt->columns.empty()) {
|
|
/* try to map the name to the column */
|
|
for (int i = 0; i < stmt->column_count; i++) {
|
|
if (stmt->columns[i].toResource().getTyped<PDOColumn>()->name ==
|
|
param->name) {
|
|
param->paramno = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* if you prepare and then execute passing an array of params keyed by
|
|
names, then this will trigger, and we don't want that */
|
|
if (param->paramno == -1) {
|
|
raise_warning("Did not found column name '%s' in the defined columns;"
|
|
" it will not be bound", param->name.data());
|
|
}
|
|
}
|
|
|
|
if (is_param && !param->name.empty() && param->name[0] != ':') {
|
|
param->name = String(":") + param->name;
|
|
}
|
|
|
|
if (is_param && !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, 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, param);
|
|
} else {
|
|
hash.set(param->paramno, param);
|
|
}
|
|
|
|
/* tell the driver we just created a parameter */
|
|
if (stmt->support(PDOStatement::MethodParamHook)) {
|
|
if (!stmt->paramHook(param, 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 is freed by hash dtor */
|
|
param->parameter.reset();
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static inline void fetch_value(sp_PDOStatement stmt, Variant &dest, int colno,
|
|
int *type_override) {
|
|
PDOColumn *col = stmt->columns[colno].toResource().getTyped<PDOColumn>();
|
|
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 = uninit_null(); break;
|
|
}
|
|
}
|
|
if (stmt->dbh->stringify && (dest.isInteger() || dest.isDouble())) {
|
|
dest = dest.toString();
|
|
}
|
|
if (dest.isNull() && stmt->dbh->oracle_nulls == PDO_NULL_TO_STRING) {
|
|
dest = empty_string;
|
|
}
|
|
}
|
|
|
|
static bool do_fetch_common(sp_PDOStatement stmt, PDOFetchOrientation ori,
|
|
long offset, bool do_bind) {
|
|
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 (do_bind && !stmt->bound_columns.empty()) {
|
|
/* update those bound column variables now */
|
|
for (ArrayIter iter(stmt->bound_columns); iter; ++iter) {
|
|
PDOBoundParam *param =
|
|
iter.second().toResource().getTyped<PDOBoundParam>();
|
|
if (param->paramno >= 0) {
|
|
param->parameter.reset();
|
|
/* 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 (!f_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, bool do_bind, 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, do_bind)) {
|
|
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::Create();
|
|
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::Create();
|
|
}
|
|
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 (!f_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().get()->o_invoke(stmt->fetch.constructor,
|
|
stmt->fetch.ctor_args.toArray());
|
|
ret.asCObjRef().get()->clearNoDestruct();
|
|
}
|
|
}
|
|
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.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:
|
|
assert(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++) {
|
|
String name = stmt->columns[i].toResource().getTyped<PDOColumn>()->name;
|
|
Variant val;
|
|
fetch_value(stmt, val, i, NULL);
|
|
|
|
switch (how) {
|
|
case PDO_FETCH_ASSOC:
|
|
ret.set(name, val);
|
|
break;
|
|
|
|
case PDO_FETCH_KEY_PAIR: {
|
|
Variant tmp;
|
|
fetch_value(stmt, tmp, ++i, NULL);
|
|
if (return_all) {
|
|
return_all->set(val, tmp);
|
|
} else {
|
|
ret.set(val, tmp);
|
|
}
|
|
return true;
|
|
}
|
|
case PDO_FETCH_USE_DEFAULT:
|
|
case PDO_FETCH_BOTH:
|
|
ret.set(name, val);
|
|
ret.append(val);
|
|
break;
|
|
|
|
case PDO_FETCH_NAMED: {
|
|
/* already have an item with this name? */
|
|
if (ret.toArray().exists(name)) {
|
|
Variant &curr_val = ret.lvalAt(name);
|
|
if (!curr_val.isArray()) {
|
|
Array arr = Array::Create();
|
|
arr.append(curr_val);
|
|
arr.append(val);
|
|
ret.set(name, arr);
|
|
} else {
|
|
curr_val.append(val);
|
|
}
|
|
} else {
|
|
ret.set(name, val);
|
|
}
|
|
break;
|
|
}
|
|
case PDO_FETCH_NUM:
|
|
ret.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:
|
|
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());
|
|
ret.toObject()->clearNoDestruct();
|
|
}
|
|
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) {
|
|
if ((flags & PDO_FETCH_UNIQUE) == PDO_FETCH_UNIQUE) {
|
|
return_all->set(grp_val, ret);
|
|
} else {
|
|
return_all->lvalAt(grp_val).append(ret);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int register_bound_param(CVarRef paramno, VRefParam param, int64_t type,
|
|
int64_t max_value_len, CVarRef driver_params,
|
|
sp_PDOStatement stmt, bool is_param) {
|
|
SmartResource<PDOBoundParam> p(new PDOBoundParam);
|
|
// need to make sure this is NULL, in case a fatal errors occurs before its 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.setWithRef(param);
|
|
p->param_type = (PDOParamType)type;
|
|
p->max_value_len = max_value_len;
|
|
p->driver_params = driver_params;
|
|
|
|
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.get(), stmt, is_param)) {
|
|
p->parameter.reset();
|
|
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
|
|
|
|
#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 cursor
|
|
#define YYMARKER s->ptr
|
|
#define YYFILL(n)
|
|
|
|
typedef struct Scanner {
|
|
char *ptr, *cur, *tok;
|
|
} Scanner;
|
|
|
|
static int scan(Scanner *s) {
|
|
char *cursor = s->cur;
|
|
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(PDOStatement *stmt, CStrRef in, String &out) {
|
|
Scanner s;
|
|
const char *ptr;
|
|
char *newbuffer;
|
|
int t;
|
|
int bindno = 0;
|
|
int ret = 0;
|
|
int newbuffer_len;
|
|
Array params;
|
|
PDOBoundParam *param;
|
|
int query_type = PDO_PLACEHOLDER_NONE;
|
|
struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;
|
|
|
|
s.cur = (char*)in.data();
|
|
|
|
/* 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 = (placeholder*)malloc(sizeof(*plc));
|
|
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 {
|
|
vparam = params[String(plc->pos, plc->len, CopyString)];
|
|
}
|
|
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 = vparam.toResource().getTyped<PDOBoundParam>();
|
|
if (stmt->dbh->support(PDOConnection::MethodQuoter)) {
|
|
if (param->param_type == PDO_PARAM_LOB &&
|
|
param->parameter.isResource()) {
|
|
Variant buf = f_stream_get_contents(param->parameter.toResource());
|
|
if (!same(buf, false)) {
|
|
if (!stmt->dbh->quoter(buf.toString(), plc->quoted,
|
|
param->param_type)) {
|
|
/* bork */
|
|
ret = -1;
|
|
strcpy(stmt->error_code, stmt->dbh->error_code);
|
|
goto clean_up;
|
|
}
|
|
} else {
|
|
pdo_raise_impl_error(stmt->dbh, stmt, "HY105",
|
|
"Expected a stream resource");
|
|
ret = -1;
|
|
goto clean_up;
|
|
}
|
|
} else {
|
|
switch (param->parameter.getType()) {
|
|
case KindOfUninit:
|
|
case KindOfNull:
|
|
plc->quoted = "NULL";
|
|
break;
|
|
|
|
case KindOfInt64:
|
|
case KindOfDouble:
|
|
plc->quoted = param->parameter.toString();
|
|
break;
|
|
|
|
case KindOfBoolean:
|
|
param->parameter = param->parameter.toInt64();
|
|
default:
|
|
if (!stmt->dbh->quoter(param->parameter.toString(), plc->quoted,
|
|
param->param_type)) {
|
|
/* bork */
|
|
ret = -1;
|
|
strcpy(stmt->error_code, stmt->dbh->error_code);
|
|
goto clean_up;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
plc->quoted = param->parameter;
|
|
}
|
|
newbuffer_len += plc->quoted.size();
|
|
}
|
|
|
|
rewrite:
|
|
/* allocate output buffer */
|
|
out = String(newbuffer_len, ReserveString);
|
|
newbuffer = out.mutableSlice().ptr;
|
|
out.setSize(newbuffer_len);
|
|
|
|
/* 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;
|
|
}
|
|
*newbuffer = '\0';
|
|
out = out.substr(0, 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);
|
|
|
|
/* check if bound parameter is already available */
|
|
if (!strcmp(name.c_str(), "?") ||
|
|
!stmt->bound_param_map.exists(name)) {
|
|
idxbuf.printf(tmpl, bind_no++);
|
|
} else {
|
|
idxbuf.clear();
|
|
idxbuf.append(stmt->bound_param_map[name].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, plc->quoted);
|
|
}
|
|
|
|
/* 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;
|
|
free(plc);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// PDOStatement
|
|
|
|
c_PDOStatement::c_PDOStatement(Class* cb) :
|
|
ExtObjectData(cb), m_rowIndex(-1) {
|
|
}
|
|
|
|
c_PDOStatement::~c_PDOStatement() {
|
|
// needed for sweeping
|
|
m_stmt.reset();
|
|
m_row.reset();
|
|
}
|
|
|
|
void c_PDOStatement::t___construct() {
|
|
raise_error("You should not create a PDOStatement manually");
|
|
}
|
|
|
|
Variant c_PDOStatement::t_execute(CArrRef params /* = null_array */) {
|
|
strcpy(m_stmt->error_code, PDO_ERR_NONE);
|
|
|
|
if (!params.empty()) {
|
|
m_stmt->bound_params.reset();
|
|
for (ArrayIter iter(params); iter; ++iter) {
|
|
SmartResource<PDOBoundParam> param(new PDOBoundParam);
|
|
param->param_type = PDO_PARAM_STR;
|
|
param->parameter = iter.second();
|
|
param->stmt = NULL;
|
|
|
|
if (iter.first().isString()) {
|
|
param->name = iter.first();
|
|
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(m_stmt->dbh, m_stmt, "HY093", NULL);
|
|
return false;
|
|
}
|
|
param->paramno = num_index;
|
|
}
|
|
|
|
if (!really_register_bound_param(param.get(), m_stmt, true)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
int ret = 1;
|
|
if (PDO_PLACEHOLDER_NONE == 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(m_stmt.get(), m_stmt->query_string,
|
|
m_stmt->active_query_string);
|
|
if (ret == 0) { /* no changes were made */
|
|
m_stmt->active_query_string = m_stmt->query_string;
|
|
} else if (ret == -1) {
|
|
/* something broke */
|
|
PDO_HANDLE_STMT_ERR(m_stmt);
|
|
return false;
|
|
}
|
|
} else if (!dispatch_param_event(m_stmt, PDO_PARAM_EVT_EXEC_PRE)) {
|
|
PDO_HANDLE_STMT_ERR(m_stmt);
|
|
return false;
|
|
}
|
|
if (m_stmt->executer()) {
|
|
m_stmt->active_query_string.reset();
|
|
if (!m_stmt->executed) {
|
|
/* this is the first execute */
|
|
|
|
if (m_stmt->dbh->alloc_own_columns && 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(m_stmt);
|
|
}
|
|
|
|
m_stmt->executed = 1;
|
|
}
|
|
|
|
if (ret && !dispatch_param_event(m_stmt, PDO_PARAM_EVT_EXEC_POST)) {
|
|
return false;
|
|
}
|
|
|
|
return (bool)ret;
|
|
}
|
|
m_stmt->active_query_string.reset();
|
|
PDO_HANDLE_STMT_ERR(m_stmt);
|
|
return false;
|
|
}
|
|
|
|
Variant c_PDOStatement::t_fetch(int64_t how /* = 0 */,
|
|
int64_t orientation /* = q_PDO$$FETCH_ORI_NEXT */,
|
|
int64_t offset /* = 0 */) {
|
|
strcpy(m_stmt->error_code, PDO_ERR_NONE);
|
|
if (!pdo_stmt_verify_mode(m_stmt, how, false)) {
|
|
return false;
|
|
}
|
|
|
|
Variant ret;
|
|
if (!do_fetch(m_stmt, true, ret, (PDOFetchType)how,
|
|
(PDOFetchOrientation)orientation, offset, NULL)) {
|
|
PDO_HANDLE_STMT_ERR(m_stmt);
|
|
return false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Variant c_PDOStatement::t_fetchobject(CStrRef class_name /* = null_string */,
|
|
CVarRef ctor_args /* = null */) {
|
|
strcpy(m_stmt->error_code, PDO_ERR_NONE);
|
|
if (!pdo_stmt_verify_mode(m_stmt, PDO_FETCH_CLASS, false)) {
|
|
return false;
|
|
}
|
|
|
|
String old_clsname = m_stmt->fetch.clsname;
|
|
Variant old_ctor_args = m_stmt->fetch.ctor_args;
|
|
bool error = false;
|
|
|
|
m_stmt->fetch.clsname = class_name;
|
|
if (class_name.isNull()) {
|
|
m_stmt->fetch.clsname = "stdclass";
|
|
}
|
|
if (!f_class_exists(m_stmt->fetch.clsname)) {
|
|
pdo_raise_impl_error(m_stmt->dbh, m_stmt, "HY000",
|
|
"Could not find user-supplied class");
|
|
error = true;
|
|
}
|
|
if (!ctor_args.isNull() && !ctor_args.isArray()) {
|
|
pdo_raise_impl_error(m_stmt->dbh, m_stmt, "HY000",
|
|
"ctor_args must be either NULL or an array");
|
|
error = true;
|
|
}
|
|
m_stmt->fetch.ctor_args = ctor_args;
|
|
|
|
Variant ret;
|
|
if (!error && !do_fetch(m_stmt, true, ret, PDO_FETCH_CLASS,
|
|
PDO_FETCH_ORI_NEXT, 0, NULL)) {
|
|
error = true;
|
|
}
|
|
if (error) {
|
|
PDO_HANDLE_STMT_ERR(m_stmt);
|
|
}
|
|
|
|
m_stmt->fetch.clsname = old_clsname;
|
|
m_stmt->fetch.ctor_args = old_ctor_args;
|
|
if (error) {
|
|
return false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Variant c_PDOStatement::t_fetchcolumn(int64_t column_numner /* = 0 */) {
|
|
strcpy(m_stmt->error_code, PDO_ERR_NONE);
|
|
if (!do_fetch_common(m_stmt, PDO_FETCH_ORI_NEXT, 0, true)) {
|
|
PDO_HANDLE_STMT_ERR(m_stmt);
|
|
return false;
|
|
}
|
|
Variant ret;
|
|
fetch_value(m_stmt, ret, column_numner, NULL);
|
|
return ret;
|
|
}
|
|
|
|
Variant c_PDOStatement::t_fetchall(int64_t how /* = 0 */,
|
|
CVarRef class_name /* = null */,
|
|
CVarRef ctor_args /* = null */) {
|
|
if (!pdo_stmt_verify_mode(m_stmt, how, true)) {
|
|
return false;
|
|
}
|
|
|
|
String old_clsname = m_stmt->fetch.clsname;
|
|
Variant old_ctor_args = m_stmt->fetch.ctor_args;
|
|
int error = 0;
|
|
|
|
switch (how & ~PDO_FETCH_FLAGS) {
|
|
case PDO_FETCH_CLASS:
|
|
m_stmt->fetch.clsname = class_name;
|
|
if (class_name.isNull()) {
|
|
m_stmt->fetch.clsname = "stdclass";
|
|
}
|
|
if (!f_class_exists(m_stmt->fetch.clsname)) {
|
|
pdo_raise_impl_error(m_stmt->dbh, m_stmt, "HY000",
|
|
"Could not find user-supplied class");
|
|
error = 1;
|
|
}
|
|
if (!ctor_args.isNull() && !ctor_args.isArray()) {
|
|
pdo_raise_impl_error(m_stmt->dbh, m_stmt, "HY000",
|
|
"ctor_args must be either NULL or an array");
|
|
error = 1;
|
|
break;
|
|
}
|
|
m_stmt->fetch.ctor_args = ctor_args;
|
|
|
|
if (!error) {
|
|
do_fetch_class_prepare(m_stmt);
|
|
}
|
|
break;
|
|
|
|
case PDO_FETCH_FUNC:
|
|
if (!f_function_exists(class_name.toString())) {
|
|
pdo_raise_impl_error(m_stmt->dbh, m_stmt, "HY000",
|
|
"no fetch function specified");
|
|
error = 1;
|
|
} else {
|
|
m_stmt->fetch.func = class_name;
|
|
do_fetch_func_prepare(m_stmt);
|
|
}
|
|
break;
|
|
|
|
case PDO_FETCH_COLUMN:
|
|
if (class_name.isNull()) {
|
|
m_stmt->fetch.column = how & PDO_FETCH_GROUP ? -1 : 0;
|
|
} else {
|
|
m_stmt->fetch.column = class_name.toInt64();
|
|
}
|
|
if (!ctor_args.isNull()) {
|
|
pdo_raise_impl_error(m_stmt->dbh, 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 |= m_stmt->default_fetch_type & PDO_FETCH_FLAGS;
|
|
how |= m_stmt->default_fetch_type & ~PDO_FETCH_FLAGS;
|
|
}
|
|
|
|
Variant *return_all = NULL;
|
|
Variant return_value;
|
|
Variant data;
|
|
if (!error) {
|
|
strcpy(m_stmt->error_code, PDO_ERR_NONE);
|
|
|
|
if ((how & PDO_FETCH_GROUP) || how == PDO_FETCH_KEY_PAIR ||
|
|
(how == PDO_FETCH_USE_DEFAULT &&
|
|
m_stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)) {
|
|
return_value = Array::Create();
|
|
return_all = &return_value;
|
|
}
|
|
if (!do_fetch(m_stmt, true, data, (PDOFetchType)(how | flags),
|
|
PDO_FETCH_ORI_NEXT, 0, return_all)) {
|
|
error = 2;
|
|
}
|
|
}
|
|
if (!error) {
|
|
if ((how & PDO_FETCH_GROUP)) {
|
|
do {
|
|
data.reset();
|
|
} while (do_fetch(m_stmt, true, data, (PDOFetchType)(how | flags),
|
|
PDO_FETCH_ORI_NEXT, 0, return_all));
|
|
} else if (how == PDO_FETCH_KEY_PAIR ||
|
|
(how == PDO_FETCH_USE_DEFAULT &&
|
|
m_stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)) {
|
|
while (do_fetch(m_stmt, true, data, (PDOFetchType)(how | flags),
|
|
PDO_FETCH_ORI_NEXT, 0, return_all));
|
|
} else {
|
|
return_value = Array::Create();
|
|
do {
|
|
return_value.append(data);
|
|
data.reset();
|
|
} while (do_fetch(m_stmt, true, data, (PDOFetchType)(how | flags),
|
|
PDO_FETCH_ORI_NEXT, 0, NULL));
|
|
}
|
|
}
|
|
|
|
m_stmt->fetch.clsname = old_clsname;
|
|
m_stmt->fetch.ctor_args = old_ctor_args;
|
|
|
|
if (error) {
|
|
PDO_HANDLE_STMT_ERR(m_stmt);
|
|
if (error != 2) {
|
|
return false;
|
|
}
|
|
|
|
/* on no results, return an empty array */
|
|
if (!return_value.isArray()) {
|
|
return_value = Array::Create();
|
|
}
|
|
}
|
|
return return_value;
|
|
}
|
|
|
|
bool c_PDOStatement::t_bindvalue(CVarRef paramno, CVarRef param,
|
|
int64_t type /* = q_PDO$$PARAM_STR */) {
|
|
return register_bound_param(paramno, param, type, 0, uninit_null(), m_stmt, true);
|
|
}
|
|
|
|
bool c_PDOStatement::t_bindparam(CVarRef paramno, VRefParam param,
|
|
int64_t type /* = q_PDO$$PARAM_STR */,
|
|
int64_t max_value_len /* = 0 */,
|
|
CVarRef driver_params /*= null */) {
|
|
return register_bound_param(paramno, ref(param), type, max_value_len,
|
|
driver_params, m_stmt, true);
|
|
}
|
|
|
|
bool c_PDOStatement::t_bindcolumn(CVarRef paramno, VRefParam param,
|
|
int64_t type /* = q_PDO$$PARAM_STR */,
|
|
int64_t max_value_len /* = 0 */,
|
|
CVarRef driver_params /* = null */) {
|
|
return register_bound_param(paramno, ref(param), type, max_value_len,
|
|
driver_params, m_stmt, false);
|
|
}
|
|
|
|
int64_t c_PDOStatement::t_rowcount() {
|
|
return m_stmt->row_count;
|
|
}
|
|
|
|
Variant c_PDOStatement::t_errorcode() {
|
|
if (m_stmt->error_code[0] == '\0') {
|
|
return uninit_null();
|
|
}
|
|
return String(m_stmt->error_code, CopyString);
|
|
}
|
|
|
|
Array c_PDOStatement::t_errorinfo() {
|
|
Array ret;
|
|
ret.append(String(m_stmt->error_code, CopyString));
|
|
|
|
if (m_stmt->dbh->support(PDOConnection::MethodFetchErr)) {
|
|
m_stmt->dbh->fetchErr(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;
|
|
}
|
|
|
|
Variant c_PDOStatement::t_setattribute(int64_t attribute, CVarRef value) {
|
|
if (!m_stmt->support(PDOStatement::MethodSetAttribute)) {
|
|
pdo_raise_impl_error(m_stmt->dbh, m_stmt, "IM001",
|
|
"This driver doesn't support setting attributes");
|
|
return false;
|
|
}
|
|
|
|
strcpy(m_stmt->error_code, PDO_ERR_NONE);
|
|
if (m_stmt->setAttribute(attribute, value)) {
|
|
return true;
|
|
}
|
|
PDO_HANDLE_STMT_ERR(m_stmt);
|
|
return false;
|
|
}
|
|
|
|
Variant c_PDOStatement::t_getattribute(int64_t attribute) {
|
|
Variant ret;
|
|
if (!m_stmt->support(PDOStatement::MethodGetAttribute)) {
|
|
if (!generic_stmt_attr_get(m_stmt, ret, attribute)) {
|
|
pdo_raise_impl_error(m_stmt->dbh, m_stmt, "IM001",
|
|
"This driver doesn't support getting attributes");
|
|
return false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
strcpy(m_stmt->error_code, PDO_ERR_NONE);
|
|
switch (m_stmt->getAttribute(attribute, ret)) {
|
|
case -1:
|
|
PDO_HANDLE_STMT_ERR(m_stmt);
|
|
return false;
|
|
case 0:
|
|
if (!generic_stmt_attr_get(m_stmt, ret, attribute)) {
|
|
/* XXX: should do something better here */
|
|
pdo_raise_impl_error(m_stmt->dbh, m_stmt, "IM001",
|
|
"driver doesn't support getting that attribute");
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int64_t c_PDOStatement::t_columncount() {
|
|
return m_stmt->column_count;
|
|
}
|
|
|
|
static const StaticString s_name("name");
|
|
static const StaticString s_len("len");
|
|
static const StaticString s_precision("precision");
|
|
static const StaticString s_pdo_type("pdo_type");
|
|
|
|
Variant c_PDOStatement::t_getcolumnmeta(int64_t column) {
|
|
if (column < 0) {
|
|
pdo_raise_impl_error(m_stmt->dbh, m_stmt, "42P10",
|
|
"column number must be non-negative");
|
|
return false;
|
|
}
|
|
|
|
if (!m_stmt->support(PDOStatement::MethodGetColumnMeta)) {
|
|
pdo_raise_impl_error(m_stmt->dbh, m_stmt, "IM001",
|
|
"driver doesn't support meta data");
|
|
return false;
|
|
}
|
|
|
|
strcpy(m_stmt->error_code, PDO_ERR_NONE);
|
|
Array ret;
|
|
if (!m_stmt->getColumnMeta(column, ret)) {
|
|
PDO_HANDLE_STMT_ERR(m_stmt);
|
|
return false;
|
|
}
|
|
|
|
/* add stock items */
|
|
PDOColumn *col = m_stmt->columns[column].toResource().getTyped<PDOColumn>();
|
|
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;
|
|
}
|
|
|
|
bool c_PDOStatement::t_setfetchmode(int _argc, int64_t mode,
|
|
CArrRef _argv /* = null_array */) {
|
|
return pdo_stmt_set_fetch_mode(m_stmt, _argc, mode, _argv);
|
|
}
|
|
|
|
bool c_PDOStatement::t_nextrowset() {
|
|
if (!m_stmt->support(PDOStatement::MethodNextRowset)) {
|
|
pdo_raise_impl_error(m_stmt->dbh, m_stmt, "IM001",
|
|
"driver does not support multiple rowsets");
|
|
return false;
|
|
}
|
|
|
|
strcpy(m_stmt->error_code, PDO_ERR_NONE);
|
|
|
|
/* un-describe */
|
|
if (!m_stmt->columns.empty()) {
|
|
m_stmt->columns.clear();
|
|
m_stmt->column_count = 0;
|
|
}
|
|
|
|
if (!m_stmt->nextRowset()) {
|
|
PDO_HANDLE_STMT_ERR(m_stmt);
|
|
return false;
|
|
}
|
|
|
|
pdo_stmt_describe_columns(m_stmt);
|
|
return true;
|
|
}
|
|
|
|
bool c_PDOStatement::t_closecursor() {
|
|
if (!m_stmt->support(PDOStatement::MethodCursorCloser)) {
|
|
/* emulate it by fetching and discarding rows */
|
|
do {
|
|
while (m_stmt->fetcher(PDO_FETCH_ORI_NEXT, 0));
|
|
if (!t_nextrowset()) {
|
|
break;
|
|
}
|
|
} while (true);
|
|
m_stmt->executed = 0;
|
|
return true;
|
|
}
|
|
|
|
strcpy(m_stmt->error_code, PDO_ERR_NONE);
|
|
if (!m_stmt->cursorCloser()) {
|
|
PDO_HANDLE_STMT_ERR(m_stmt);
|
|
return false;
|
|
}
|
|
m_stmt->executed = 0;
|
|
return true;
|
|
}
|
|
|
|
Variant c_PDOStatement::t_debugdumpparams() {
|
|
Variant fobj = File::Open("php://output", "w");
|
|
if (same(fobj, false)) {
|
|
return false;
|
|
}
|
|
File *f = fobj.toResource().getTyped<File>();
|
|
|
|
Array params;
|
|
params.append(m_stmt->query_string.size());
|
|
params.append(m_stmt->query_string.size());
|
|
params.append(m_stmt->query_string.data());
|
|
f->printf("SQL: [%d] %.*s\n", params);
|
|
|
|
f->printf("Params: %d\n", CREATE_VECTOR1(m_stmt->bound_params.size()));
|
|
for (ArrayIter iter(m_stmt->bound_params); iter; ++iter) {
|
|
if (iter.first().isString()) {
|
|
String key = iter.first().toString();
|
|
params = CREATE_VECTOR3(key.size(), key.size(), key.data());
|
|
f->printf("Key: Name: [%d] %.*s\n", params);
|
|
} else {
|
|
f->printf("Key: Position #%ld:\n",
|
|
CREATE_VECTOR1(iter.first().toInt64()));
|
|
}
|
|
|
|
PDOBoundParam *param = iter.second().toResource().getTyped<PDOBoundParam>();
|
|
params.clear();
|
|
params.append(param->paramno);
|
|
params.append(param->name.size());
|
|
params.append(param->name.size());
|
|
params.append(param->name.data());
|
|
params.append(param->is_param);
|
|
params.append(param->param_type);
|
|
f->printf("paramno=%d\nname=[%d] \"%.*s\"\nis_param=%d\nparam_type=%d\n",
|
|
params);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Variant c_PDOStatement::t_current() {
|
|
return m_row;
|
|
}
|
|
|
|
Variant c_PDOStatement::t_key() {
|
|
return m_rowIndex;
|
|
}
|
|
|
|
Variant c_PDOStatement::t_next() {
|
|
m_row = t_fetch(PDO_FETCH_USE_DEFAULT);
|
|
if (same(m_row, false)) {
|
|
m_rowIndex = -1;
|
|
} else {
|
|
++m_rowIndex;
|
|
}
|
|
return uninit_null();
|
|
}
|
|
|
|
Variant c_PDOStatement::t_rewind() {
|
|
m_rowIndex = -1;
|
|
t_next();
|
|
return uninit_null();
|
|
}
|
|
|
|
Variant c_PDOStatement::t_valid() {
|
|
return m_rowIndex >= 0;
|
|
}
|
|
|
|
Variant c_PDOStatement::t___wakeup() {
|
|
throw_pdo_exception(uninit_null(), uninit_null(), "You cannot serialize or unserialize "
|
|
"PDOStatement instances");
|
|
return uninit_null();
|
|
}
|
|
|
|
Variant c_PDOStatement::t___sleep() {
|
|
throw_pdo_exception(uninit_null(), uninit_null(), "You cannot serialize or unserialize "
|
|
"PDOStatement instances");
|
|
return uninit_null();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|