From a5cf82802d097f7c327ee7e4eafe2224a5c9f78b Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 25 Sep 2020 00:41:21 +0100 Subject: [PATCH] Make various failure conditions in PDO unconditional errors This includes TypeErrors, ValueErrors, Error for uninitialized objects and invalid user classes/callable instanciation Closes GH-6212 --- ext/pdo/pdo_dbh.c | 150 ++--- ext/pdo/pdo_stmt.c | 562 ++++++++++-------- ext/pdo/pdo_stmt.stub.php | 14 +- ext/pdo/pdo_stmt_arginfo.h | 6 +- ext/pdo/php_pdo.h | 10 +- ext/pdo/php_pdo_int.h | 3 +- ext/pdo/tests/bug_44159.phpt | 63 +- ext/pdo/tests/bug_44173.phpt | 61 +- ext/pdo/tests/pdo_038.phpt | 21 +- ext/pdo/tests/pdo_quote_empty_string.phpt | 31 + .../tests/pdo_mysql_attr_errmode.phpt | 48 +- .../tests/pdo_mysql_attr_oracle_nulls.phpt | 30 +- .../tests/pdo_mysql_attr_statement_class.phpt | 57 +- .../tests/pdo_mysql_prepare_emulated.phpt | 9 +- .../tests/pdo_mysql_prepare_native.phpt | 9 +- .../tests/pdo_mysql_stmt_getcolumnmeta.phpt | 8 +- .../tests/bug_44159_sqlite_version.phpt | 22 + ext/pdo_sqlite/tests/pdo_fetch_func_001.phpt | 112 ++-- 18 files changed, 674 insertions(+), 542 deletions(-) create mode 100644 ext/pdo/tests/pdo_quote_empty_string.phpt create mode 100644 ext/pdo_sqlite/tests/bug_44159_sqlite_version.phpt diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index 780de94707..0f5eaab2e6 100644 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -425,12 +425,10 @@ options: static zval *pdo_stmt_instantiate(pdo_dbh_t *dbh, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args) /* {{{ */ { if (!Z_ISUNDEF_P(ctor_args)) { - if (Z_TYPE_P(ctor_args) != IS_ARRAY) { - pdo_raise_impl_error(dbh, NULL, "HY000", "constructor arguments must be passed as an array"); - return NULL; - } + /* This implies an error within PDO if this does not hold */ + ZEND_ASSERT(Z_TYPE_P(ctor_args) == IS_ARRAY); if (!dbstmt_ce->constructor) { - pdo_raise_impl_error(dbh, NULL, "HY000", "user-supplied statement does not accept constructor arguments"); + zend_throw_error(NULL, "User-supplied statement does not accept constructor arguments"); return NULL; } } @@ -487,7 +485,7 @@ PHP_METHOD(PDO, prepare) pdo_stmt_t *stmt; char *statement; size_t statement_len; - zval *options = NULL, *opt, *item, ctor_args; + zval *options = NULL, *value, *item, ctor_args; zend_class_entry *dbstmt_ce, *pce; pdo_dbh_object_t *dbh_obj = Z_PDO_OBJECT_P(ZEND_THIS); pdo_dbh_t *dbh = dbh_obj->inner; @@ -498,42 +496,44 @@ PHP_METHOD(PDO, prepare) Z_PARAM_ARRAY(options) ZEND_PARSE_PARAMETERS_END(); - PDO_DBH_CLEAR_ERR(); PDO_CONSTRUCT_CHECK; - if (ZEND_NUM_ARGS() > 1 && (opt = zend_hash_index_find(Z_ARRVAL_P(options), PDO_ATTR_STATEMENT_CLASS)) != NULL) { - if (Z_TYPE_P(opt) != IS_ARRAY || (item = zend_hash_index_find(Z_ARRVAL_P(opt), 0)) == NULL - || Z_TYPE_P(item) != IS_STRING - || (pce = zend_lookup_class(Z_STR_P(item))) == NULL - ) { - 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(); - RETURN_FALSE; + if (statement_len == 0) { + zend_argument_value_error(1, "cannot be empty"); + RETURN_THROWS(); + } + + PDO_DBH_CLEAR_ERR(); + + if (options && (value = zend_hash_index_find(Z_ARRVAL_P(options), PDO_ATTR_STATEMENT_CLASS)) != NULL) { + if (Z_TYPE_P(value) != IS_ARRAY) { + zend_type_error("PDO::ATTR_STATEMENT_CLASS value must be of type array, %s given", + zend_zval_type_name(value)); + RETURN_THROWS(); + } + if ((item = zend_hash_index_find(Z_ARRVAL_P(value), 0)) == NULL) { + zend_value_error("PDO::ATTR_STATEMENT_CLASS value must be an array with the format " + "array(classname, array(ctor_args))"); + RETURN_THROWS(); + } + if (Z_TYPE_P(item) != IS_STRING || (pce = zend_lookup_class(Z_STR_P(item))) == NULL) { + zend_type_error("PDO::ATTR_STATEMENT_CLASS class must be a valid class"); + RETURN_THROWS(); } dbstmt_ce = pce; if (!instanceof_function(dbstmt_ce, pdo_dbstmt_ce)) { - pdo_raise_impl_error(dbh, NULL, "HY000", - "user-supplied statement class must be derived from PDOStatement"); - PDO_HANDLE_DBH_ERR(); - RETURN_FALSE; + zend_type_error("PDO::ATTR_STATEMENT_CLASS class must be derived from PDOStatement"); + RETURN_THROWS(); } if (dbstmt_ce->constructor && !(dbstmt_ce->constructor->common.fn_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED))) { - pdo_raise_impl_error(dbh, NULL, "HY000", - "user-supplied statement class cannot have a public constructor"); - PDO_HANDLE_DBH_ERR(); - RETURN_FALSE; + zend_type_error("User-supplied statement class cannot have a public constructor"); + RETURN_THROWS(); } - if ((item = zend_hash_index_find(Z_ARRVAL_P(opt), 1)) != NULL) { + if ((item = zend_hash_index_find(Z_ARRVAL_P(value), 1)) != NULL) { if (Z_TYPE_P(item) != IS_ARRAY) { - 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(); - RETURN_FALSE; + zend_type_error("PDO::ATTR_STATEMENT_CLASS ctor_args must be of type ?array, %s given", + zend_zval_type_name(value)); + RETURN_THROWS(); } ZVAL_COPY_VALUE(&ctor_args, item); } else { @@ -544,11 +544,10 @@ PHP_METHOD(PDO, prepare) ZVAL_COPY_VALUE(&ctor_args, &dbh->def_stmt_ctor_args); } + /* Need to check if pdo_stmt_instantiate() throws an exception unconditionally to see if can change the RETURN_FALSE; */ if (!pdo_stmt_instantiate(dbh, return_value, dbstmt_ce, &ctor_args)) { if (EXPECTED(!EG(exception))) { - pdo_raise_impl_error(dbh, NULL, "HY000", - "failed to instantiate user-supplied statement class" - ); + zend_throw_error(NULL, "Cannot instantiate user-supplied statement class"); } PDO_HANDLE_DBH_ERR(); RETURN_FALSE; @@ -679,10 +678,10 @@ static int pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value) /* { zend_long lval; +/* TODO: Make distinction between numeric and non-numeric strings */ #define PDO_LONG_PARAM_CHECK \ if (Z_TYPE_P(value) != IS_LONG && Z_TYPE_P(value) != IS_STRING && Z_TYPE_P(value) != IS_FALSE && Z_TYPE_P(value) != IS_TRUE) { \ - pdo_raise_impl_error(dbh, NULL, "HY000", "attribute value must be an integer"); \ - PDO_HANDLE_DBH_ERR(); \ + zend_type_error("Attribute value must be of type int for selected attribute, %s given", zend_zval_type_name(value)); \ return FAILURE; \ } \ @@ -697,8 +696,7 @@ static int pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value) /* dbh->error_mode = lval; return SUCCESS; default: - pdo_raise_impl_error(dbh, NULL, "HY000", "invalid error mode"); - PDO_HANDLE_DBH_ERR(); + zend_value_error("Error mode must be one of the PDO::ERRMODE_* constants"); return FAILURE; } return FAILURE; @@ -713,8 +711,7 @@ static int pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value) /* dbh->desired_case = lval; return SUCCESS; default: - pdo_raise_impl_error(dbh, NULL, "HY000", "invalid case folding mode"); - PDO_HANDLE_DBH_ERR(); + zend_value_error("Case folding mode must be one of the PDO::CASE_* constants"); return FAILURE; } return FAILURE; @@ -729,7 +726,7 @@ static int pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value) /* zval *tmp; if ((tmp = zend_hash_index_find(Z_ARRVAL_P(value), 0)) != NULL && Z_TYPE_P(tmp) == IS_LONG) { if (Z_LVAL_P(tmp) == PDO_FETCH_INTO || Z_LVAL_P(tmp) == PDO_FETCH_CLASS) { - pdo_raise_impl_error(dbh, NULL, "HY000", "FETCH_INTO and FETCH_CLASS are not yet supported as default fetch modes"); + zend_value_error("PDO::FETCH_INTO and PDO::FETCH_CLASS cannot be set as the default fetch mode"); return FAILURE; } } @@ -738,7 +735,7 @@ static int pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value) /* } lval = zval_get_long(value); if (lval == PDO_FETCH_USE_DEFAULT) { - pdo_raise_impl_error(dbh, NULL, "HY000", "invalid fetch mode type"); + zend_value_error("Fetch mode must be a bitmask of PDO::FETCH_* constants"); return FAILURE; } dbh->default_fetch_type = lval; @@ -761,28 +758,26 @@ static int pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value) /* PDO_HANDLE_DBH_ERR(); return FAILURE; } - if (Z_TYPE_P(value) != IS_ARRAY - || (item = zend_hash_index_find(Z_ARRVAL_P(value), 0)) == NULL - || Z_TYPE_P(item) != IS_STRING - || (pce = zend_lookup_class(Z_STR_P(item))) == NULL - ) { - 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(); + if (Z_TYPE_P(value) != IS_ARRAY) { + zend_type_error("PDO::ATTR_STATEMENT_CLASS value must be of type array, %s given", + zend_zval_type_name(value)); + return FAILURE; + } + if ((item = zend_hash_index_find(Z_ARRVAL_P(value), 0)) == NULL) { + zend_value_error("PDO::ATTR_STATEMENT_CLASS value must be an array with the format " + "array(classname, array(ctor_args))"); + return FAILURE; + } + if (Z_TYPE_P(item) != IS_STRING || (pce = zend_lookup_class(Z_STR_P(item))) == NULL) { + zend_type_error("PDO::ATTR_STATEMENT_CLASS class must be a valid class"); return FAILURE; } if (!instanceof_function(pce, pdo_dbstmt_ce)) { - pdo_raise_impl_error(dbh, NULL, "HY000", - "user-supplied statement class must be derived from PDOStatement"); - PDO_HANDLE_DBH_ERR(); + zend_type_error("PDO::ATTR_STATEMENT_CLASS class must be derived from PDOStatement"); return FAILURE; } if (pce->constructor && !(pce->constructor->common.fn_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED))) { - pdo_raise_impl_error(dbh, NULL, "HY000", - "user-supplied statement class cannot have a public constructor"); - PDO_HANDLE_DBH_ERR(); + zend_type_error("User-supplied statement class cannot have a public constructor"); return FAILURE; } dbh->def_stmt_ce = pce; @@ -792,11 +787,8 @@ static int pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value) /* } if ((item = zend_hash_index_find(Z_ARRVAL_P(value), 1)) != NULL) { if (Z_TYPE_P(item) != IS_ARRAY) { - pdo_raise_impl_error(dbh, NULL, "HY000", - "PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); " - "ctor_args must be an array" - ); - PDO_HANDLE_DBH_ERR(); + zend_type_error("PDO::ATTR_STATEMENT_CLASS ctor_args must be of type ?array, %s given", + zend_zval_type_name(value)); return FAILURE; } ZVAL_COPY(&dbh->def_stmt_ctor_args, item); @@ -927,10 +919,11 @@ PHP_METHOD(PDO, exec) Z_PARAM_STRING(statement, statement_len) ZEND_PARSE_PARAMETERS_END(); - if (!statement_len) { - pdo_raise_impl_error(dbh, NULL, "HY000", "trying to execute an empty query"); - RETURN_FALSE; + if (statement_len == 0) { + zend_argument_value_error(1, "cannot be empty"); + RETURN_THROWS(); } + PDO_DBH_CLEAR_ERR(); PDO_CONSTRUCT_CHECK; ret = dbh->methods->doer(dbh, statement, statement_len); @@ -955,8 +948,10 @@ PHP_METHOD(PDO, lastInsertId) Z_PARAM_STRING_OR_NULL(name, namelen) ZEND_PARSE_PARAMETERS_END(); - PDO_DBH_CLEAR_ERR(); PDO_CONSTRUCT_CHECK; + + PDO_DBH_CLEAR_ERR(); + if (!dbh->methods->last_id) { pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support lastInsertId()"); RETURN_FALSE; @@ -1060,13 +1055,20 @@ PHP_METHOD(PDO, query) pdo_dbh_object_t *dbh_obj = Z_PDO_OBJECT_P(ZEND_THIS); pdo_dbh_t *dbh = dbh_obj->inner; - if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|l!*", &statement, &statement_len, &fetch_mode, &fetch_mode_is_null, &args, &num_args)) { + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|l!*", &statement, &statement_len, + &fetch_mode, &fetch_mode_is_null, &args, &num_args)) { RETURN_THROWS(); } - PDO_DBH_CLEAR_ERR(); PDO_CONSTRUCT_CHECK; + if (statement_len == 0) { + zend_argument_value_error(1, "cannot be empty"); + RETURN_THROWS(); + } + + PDO_DBH_CLEAR_ERR(); + if (!pdo_stmt_instantiate(dbh, return_value, dbh->def_stmt_ce, &dbh->def_stmt_ctor_args)) { if (EXPECTED(!EG(exception))) { pdo_raise_impl_error(dbh, NULL, "HY000", "failed to instantiate user supplied statement class"); @@ -1090,8 +1092,7 @@ PHP_METHOD(PDO, query) if (dbh->methods->preparer(dbh, statement, statement_len, stmt, NULL)) { PDO_STMT_CLEAR_ERR(); - if (fetch_mode_is_null || SUCCESS == pdo_stmt_setup_fetch_mode(stmt, fetch_mode, args, num_args)) { - + if (fetch_mode_is_null || pdo_stmt_setup_fetch_mode(stmt, fetch_mode, 2, args, num_args)) { /* now execute the statement */ PDO_STMT_CLEAR_ERR(); if (stmt->methods->executer(stmt)) { @@ -1139,8 +1140,9 @@ PHP_METHOD(PDO, quote) Z_PARAM_LONG(paramtype) ZEND_PARSE_PARAMETERS_END(); - PDO_DBH_CLEAR_ERR(); PDO_CONSTRUCT_CHECK; + + PDO_DBH_CLEAR_ERR(); if (!dbh->methods->quoter) { pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support quoting"); RETURN_FALSE; diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index 8ed0a77636..0cec5d9955 100644 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -34,11 +34,12 @@ #include "php_memory_streams.h" #include "pdo_stmt_arginfo.h" -#define PHP_STMT_GET_OBJ \ - pdo_stmt_t *stmt = Z_PDO_STMT_P(ZEND_THIS); \ - if (!stmt->dbh) { \ - RETURN_FALSE; \ - } \ +#define PHP_STMT_GET_OBJ \ + pdo_stmt_t *stmt = Z_PDO_STMT_P(ZEND_THIS); \ + if (!stmt->dbh) { \ + zend_throw_error(NULL, "PDO object is uninitialized"); \ + RETURN_THROWS(); \ + } \ static inline int rewrite_name_to_position(pdo_stmt_t *stmt, struct pdo_bound_param_data *param) /* {{{ */ { @@ -62,6 +63,7 @@ static inline int rewrite_name_to_position(pdo_stmt_t *stmt, struct pdo_bound_pa param->name = zend_string_init(name, strlen(name), 0); return 1; } + /* TODO Error? */ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined"); return 0; } @@ -72,12 +74,14 @@ static inline int rewrite_name_to_position(pdo_stmt_t *stmt, struct pdo_bound_pa continue; } if (param->paramno >= 0) { + /* TODO Error? */ 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 -1; } param->paramno = position; return 1; } ZEND_HASH_FOREACH_END(); + /* TODO Error? */ pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined"); return 0; } @@ -256,7 +260,7 @@ static int really_register_bound_param(struct pdo_bound_param_data *param, pdo_s for (i = 0; i < stmt->column_count; i++) { if (ZSTR_LEN(stmt->columns[i].name) == ZSTR_LEN(param->name) && - strncmp(ZSTR_VAL(stmt->columns[i].name), ZSTR_VAL(param->name), ZSTR_LEN(param->name) + 1) == 0) { + strncmp(ZSTR_VAL(stmt->columns[i].name), ZSTR_VAL(param->name), ZSTR_LEN(param->name) + 1) == 0) { param->paramno = i; break; } @@ -265,7 +269,9 @@ static int really_register_bound_param(struct pdo_bound_param_data *param, pdo_s /* 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) { + /* Should this always be an Error? */ char *tmp; + /* TODO Error? */ spprintf(&tmp, 0, "Did not find column name '%s' in the defined columns; it will not be bound", ZSTR_VAL(param->name)); pdo_raise_impl_error(stmt->dbh, stmt, "HY000", tmp); efree(tmp); @@ -398,9 +404,9 @@ PHP_METHOD(PDOStatement, execute) if (PDO_PLACEHOLDER_NONE == stmt->supports_placeholders) { /* handle the emulated parameter binding, - * stmt->active_query_string holds the query with binds expanded and + * stmt->active_query_string holds the query with binds expanded and * quoted. - */ + */ /* string is leftover from previous calls so PDOStatement::debugDumpParams() can access */ if (stmt->active_query_string && stmt->active_query_string != stmt->query_string) { @@ -457,10 +463,15 @@ static inline void fetch_value(pdo_stmt_t *stmt, zval *dest, int colno, int *typ int caller_frees = 0; int type, new_type; - if (colno < 0 || colno >= stmt->column_count) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "Invalid column index"); - ZVAL_FALSE(dest); + if (colno < 0) { + zend_value_error("Column index must be greater than or equal to 0"); + ZVAL_NULL(dest); + return; + } + if (colno >= stmt->column_count) { + zend_value_error("Invalid column index"); + ZVAL_NULL(dest); return; } @@ -666,6 +677,7 @@ static int do_fetch_class_prepare(pdo_stmt_t *stmt) /* {{{ */ fcc->called_scope = ce; return 1; } else if (!Z_ISUNDEF(stmt->fetch.cls.ctor_args)) { + /* TODO Error? */ 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 0; } else { @@ -680,12 +692,12 @@ static int make_callable_ex(pdo_stmt_t *stmt, zval *callable, zend_fcall_info * if (zend_fcall_info_init(callable, 0, fci, fcc, NULL, &is_callable_error) == FAILURE) { if (is_callable_error) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", is_callable_error); + zend_type_error("%s", is_callable_error); efree(is_callable_error); } else { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "user-supplied function must be a valid callback"); + zend_type_error("User-supplied function must be a valid callback"); } - return 0; + return false; } if (is_callable_error) { /* Possible error message */ @@ -695,20 +707,20 @@ static int make_callable_ex(pdo_stmt_t *stmt, zval *callable, zend_fcall_info * fci->param_count = num_args; /* probably less */ fci->params = safe_emalloc(sizeof(zval), num_args, 0); - return 1; + return true; } /* }}} */ -static int do_fetch_func_prepare(pdo_stmt_t *stmt) /* {{{ */ +static bool do_fetch_func_prepare(pdo_stmt_t *stmt) /* {{{ */ { zend_fcall_info *fci = &stmt->fetch.cls.fci; zend_fcall_info_cache *fcc = &stmt->fetch.cls.fcc; if (!make_callable_ex(stmt, &stmt->fetch.func.function, fci, fcc, stmt->column_count)) { - return 0; + return false; } else { stmt->fetch.func.values = safe_emalloc(sizeof(zval), stmt->column_count, 0); - return 1; + return true; } } /* }}} */ @@ -718,7 +730,7 @@ static void do_fetch_opt_finish(pdo_stmt_t *stmt, int free_ctor_agrs) /* {{{ */ /* fci.size is used to check if it is valid */ if (stmt->fetch.cls.fci.size && stmt->fetch.cls.fci.params) { if (!Z_ISUNDEF(stmt->fetch.cls.ctor_args)) { - /* Added to free constructor arguments */ + /* Added to free constructor arguments */ zend_fcall_info_args_clear(&stmt->fetch.cls.fci, 1); } else { efree(stmt->fetch.cls.fci.params); @@ -806,23 +818,27 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h break; case PDO_FETCH_COLUMN: - if (colno >= 0 && colno < stmt->column_count) { - if (flags == PDO_FETCH_GROUP && stmt->fetch.column == -1) { - fetch_value(stmt, return_value, 1, NULL); - } else if (flags == PDO_FETCH_GROUP && colno) { - fetch_value(stmt, return_value, 0, NULL); - } else { - fetch_value(stmt, return_value, colno, NULL); - } - if (!return_all) { - return 1; - } else { - break; - } + if (colno < 0 ) { + zend_value_error("Column index must be greater than or equal to 0"); + return false; + } + + if (colno >= stmt->column_count) { + zend_value_error("Invalid column index"); + return false; + } + + if (flags == PDO_FETCH_GROUP && stmt->fetch.column == -1) { + fetch_value(stmt, return_value, 1, NULL); + } else if (flags == PDO_FETCH_GROUP && colno) { + fetch_value(stmt, return_value, 0, NULL); } else { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "Invalid column index"); + fetch_value(stmt, return_value, colno, NULL); } - return 0; + if (!return_all) { + return 1; + } + break; case PDO_FETCH_OBJ: object_init_ex(return_value, ZEND_STANDARD_CLASS_DEF_PTR); @@ -854,7 +870,9 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h zval_ptr_dtor_str(&val); } ce = stmt->fetch.cls.ce; + /* TODO: Make this an assertion and ensure this is true higher up? */ if (!ce) { + /* TODO Error? */ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "No fetch class specified"); return 0; } @@ -872,6 +890,7 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h stmt->fetch.cls.fci.object = Z_OBJ_P(return_value); stmt->fetch.cls.fcc.object = Z_OBJ_P(return_value); if (zend_call_function(&stmt->fetch.cls.fci, &stmt->fetch.cls.fcc) == FAILURE) { + /* TODO Error? */ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not call class constructor"); return 0; } else { @@ -886,6 +905,7 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h case PDO_FETCH_INTO: if (Z_ISUNDEF(stmt->fetch.into)) { + /* TODO ArgumentCountError? */ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "No fetch-into object specified."); return 0; break; @@ -900,6 +920,7 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h case PDO_FETCH_FUNC: if (Z_ISUNDEF(stmt->fetch.func.function)) { + /* TODO ArgumentCountError? */ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "No fetch function specified"); return 0; } @@ -910,10 +931,7 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h } } break; - - default: - /* shouldn't happen */ - return 0; + EMPTY_SWITCH_DEFAULT_CASE(); } if (return_all && how != PDO_FETCH_KEY_PAIR) { @@ -1039,9 +1057,8 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h default: zval_ptr_dtor(&val); - pdo_raise_impl_error(stmt->dbh, stmt, "22003", "mode is out of range"); + zend_value_error("Fetch mode must be a bitmask of PDO::FETCH_* constants"); return 0; - break; } } @@ -1051,6 +1068,7 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h stmt->fetch.cls.fci.object = Z_OBJ_P(return_value); stmt->fetch.cls.fcc.object = Z_OBJ_P(return_value); if (zend_call_function(&stmt->fetch.cls.fci, &stmt->fetch.cls.fcc) == FAILURE) { + /* TODO Error? */ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not call class constructor"); return 0; } else { @@ -1071,6 +1089,7 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h stmt->fetch.func.fci.param_count = idx; stmt->fetch.func.fci.retval = &retval; if (zend_call_function(&stmt->fetch.func.fci, &stmt->fetch.func.fcc) == FAILURE) { + /* TODO Error? */ pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not call user-supplied function"); return 0; } else { @@ -1110,14 +1129,14 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h } /* }}} */ -static int pdo_stmt_verify_mode(pdo_stmt_t *stmt, zend_long mode, int fetch_all) /* {{{ */ +static bool pdo_stmt_verify_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_arg_num, 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"); + zend_argument_value_error(mode_arg_num, "must be a bitmask of PDO::FETCH_* constants"); return 0; } @@ -1129,28 +1148,28 @@ static int pdo_stmt_verify_mode(pdo_stmt_t *stmt, zend_long mode, int fetch_all) 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()"); + zend_value_error("Can only use PDO::FETCH_FUNC in PDOStatement::fetchAll()"); return 0; } return 1; 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()"); + zend_argument_value_error(mode_arg_num, "cannot be PDO::FETCH_LAZY in PDOStatement::fetchAll()"); return 0; } /* fall through */ 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"); + zend_argument_value_error(mode_arg_num, "must use PDO::FETCH_SERIALIZE with PDO::FETCH_CLASS"); return 0; } 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"); + zend_argument_value_error(mode_arg_num, "must use PDO::FETCH_CLASSTYPE with PDO::FETCH_CLASS"); return 0; } if (mode >= PDO_FETCH__MAX) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "invalid fetch mode"); + zend_argument_value_error(mode_arg_num, "must be a bitmask of PDO::FETCH_* constants"); return 0; } /* no break; */ @@ -1175,14 +1194,14 @@ PHP_METHOD(PDOStatement, fetch) Z_PARAM_LONG(off) ZEND_PARSE_PARAMETERS_END(); - PHP_STMT_GET_OBJ; + PHP_STMT_GET_OBJ; PDO_STMT_CLEAR_ERR(); - if (!pdo_stmt_verify_mode(stmt, how, 0)) { - RETURN_FALSE; + if (!pdo_stmt_verify_mode(stmt, how, 1, false)) { + RETURN_THROWS(); } - if (!do_fetch(stmt, return_value, how, ori, off, 0)) { + if (!do_fetch(stmt, return_value, how, ori, off, NULL)) { PDO_HANDLE_STMT_ERR(); RETURN_FALSE; } @@ -1192,9 +1211,6 @@ PHP_METHOD(PDOStatement, fetch) /* {{{ Fetches the next row and returns it as an object. */ PHP_METHOD(PDOStatement, fetchObject) { - zend_long how = PDO_FETCH_CLASS; - zend_long ori = PDO_FETCH_ORI_NEXT; - zend_long off = 0; zend_class_entry *ce = NULL; zend_class_entry *old_ce; zval old_ctor_args, *ctor_args = NULL; @@ -1209,10 +1225,6 @@ PHP_METHOD(PDOStatement, fetchObject) PHP_STMT_GET_OBJ; PDO_STMT_CLEAR_ERR(); - if (!pdo_stmt_verify_mode(stmt, how, 0)) { - RETURN_FALSE; - } - old_ce = stmt->fetch.cls.ce; ZVAL_COPY_VALUE(&old_ctor_args, &stmt->fetch.cls.ctor_args); old_arg_count = stmt->fetch.cls.fci.param_count; @@ -1232,7 +1244,7 @@ PHP_METHOD(PDOStatement, fetchObject) stmt->fetch.cls.ce = zend_standard_class_def; } - if (!do_fetch(stmt, return_value, how, ori, off, 0)) { + if (!do_fetch(stmt, return_value, PDO_FETCH_CLASS, PDO_FETCH_ORI_NEXT, /* offset */ 0, NULL)) { PDO_HANDLE_STMT_ERR(); RETVAL_FALSE; } @@ -1270,22 +1282,23 @@ PHP_METHOD(PDOStatement, fetchColumn) PHP_METHOD(PDOStatement, fetchAll) { zend_long how = PDO_FETCH_USE_DEFAULT; - zval data, *return_all; + zval data, *return_all = NULL; zval *arg2 = NULL; zend_class_entry *old_ce; zval old_ctor_args, *ctor_args = NULL; - int error = 0, flags, old_arg_count; + bool error = false; + int flags, old_arg_count; ZEND_PARSE_PARAMETERS_START(0, 3) Z_PARAM_OPTIONAL Z_PARAM_LONG(how) - Z_PARAM_ZVAL(arg2) - Z_PARAM_ZVAL(ctor_args) + Z_PARAM_ZVAL_OR_NULL(arg2) + Z_PARAM_ARRAY_OR_NULL(ctor_args) ZEND_PARSE_PARAMETERS_END(); PHP_STMT_GET_OBJ; - if (!pdo_stmt_verify_mode(stmt, how, 1)) { - RETURN_FALSE; + if (!pdo_stmt_verify_mode(stmt, how, 1, true)) { + RETURN_THROWS(); } old_ce = stmt->fetch.cls.ce; @@ -1294,85 +1307,88 @@ PHP_METHOD(PDOStatement, fetchAll) do_fetch_opt_finish(stmt, 0); - switch(how & ~PDO_FETCH_FLAGS) { - case PDO_FETCH_CLASS: - switch(ZEND_NUM_ARGS()) { - case 0: - case 1: - stmt->fetch.cls.ce = zend_standard_class_def; - break; - case 3: - if (Z_TYPE_P(ctor_args) != IS_NULL && Z_TYPE_P(ctor_args) != IS_ARRAY) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "ctor_args must be either NULL or an array"); - error = 1; - break; - } - if (Z_TYPE_P(ctor_args) != IS_ARRAY || !zend_hash_num_elements(Z_ARRVAL_P(ctor_args))) { - ctor_args = NULL; + /* TODO Would be good to reuse part of pdo_stmt_setup_fetch_mode() in some way */ + + switch (how & ~PDO_FETCH_FLAGS) { + case PDO_FETCH_CLASS: + /* Figure out correct class */ + if (arg2) { + if (Z_TYPE_P(arg2) != IS_STRING) { + zend_argument_type_error(2, "must be of type string, %s given", zend_zval_type_name(arg2)); + RETURN_THROWS(); + } + stmt->fetch.cls.ce = zend_fetch_class(Z_STR_P(arg2), ZEND_FETCH_CLASS_AUTO); + if (!stmt->fetch.cls.ce) { + zend_argument_type_error(2, "must be a valid class"); + RETURN_THROWS(); + } + } else { + stmt->fetch.cls.ce = zend_standard_class_def; } - /* no break */ - case 2: - if (ctor_args) { + + if (ctor_args && zend_hash_num_elements(Z_ARRVAL_P(ctor_args)) > 0) { ZVAL_COPY_VALUE(&stmt->fetch.cls.ctor_args, ctor_args); /* we're not going to free these */ } else { ZVAL_UNDEF(&stmt->fetch.cls.ctor_args); } - if (Z_TYPE_P(arg2) != IS_STRING) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "Invalid class name (should be a string)"); - error = 1; - break; - } else { - stmt->fetch.cls.ce = zend_fetch_class(Z_STR_P(arg2), ZEND_FETCH_CLASS_AUTO); - if (!stmt->fetch.cls.ce) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not find user-specified class"); - error = 1; - break; - } - } - } - if (!error) { + do_fetch_class_prepare(stmt); - } - break; - - case PDO_FETCH_FUNC: - switch (ZEND_NUM_ARGS()) { - case 0: - case 1: - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "no fetch function specified"); - error = 1; - break; - case 3: - case 2: - ZVAL_COPY_VALUE(&stmt->fetch.func.function, arg2); - if (do_fetch_func_prepare(stmt) == 0) { - error = 1; - } - break; - } - break; + break; - case PDO_FETCH_COLUMN: - switch(ZEND_NUM_ARGS()) { - case 0: - case 1: - stmt->fetch.column = how & PDO_FETCH_GROUP ? -1 : 0; + case PDO_FETCH_FUNC: /* Cannot be a default fetch mode */ + if (ZEND_NUM_ARGS() != 2) { + zend_string *func = get_active_function_or_method_name(); + zend_argument_count_error("%s() expects exactly 2 argument for PDO::FETCH_FUNC, %d given", + ZSTR_VAL(func), ZEND_NUM_ARGS()); + zend_string_release(func); + RETURN_THROWS(); + } + if (arg2 == NULL) { + /* TODO use "must be of type callable" format? */ + zend_argument_type_error(2, "must be a callable, null given"); + RETURN_THROWS(); + } + /* TODO Check it is a callable? */ + ZVAL_COPY_VALUE(&stmt->fetch.func.function, arg2); + if (do_fetch_func_prepare(stmt) == false) { + RETURN_THROWS(); + } break; - case 2: - convert_to_long(arg2); - stmt->fetch.column = Z_LVAL_P(arg2); + + case PDO_FETCH_COLUMN: + if (ZEND_NUM_ARGS() > 2) { + zend_string *func = get_active_function_or_method_name(); + zend_argument_count_error("%s() expects at most 2 argument for the fetch mode provided, %d given", + ZSTR_VAL(func), ZEND_NUM_ARGS()); + zend_string_release(func); + RETURN_THROWS(); + } + /* Is column index passed? */ + if (arg2) { + // Reuse convert_to_long(arg2); ? + if (Z_TYPE_P(arg2) != IS_LONG) { + zend_argument_type_error(2, "must be of type int, %s given", zend_zval_type_name(arg2)); + RETURN_THROWS(); + } + if (Z_LVAL_P(arg2) < 0) { + zend_argument_value_error(2, "must be greater than or equal to 0"); + RETURN_THROWS(); + } + stmt->fetch.column = Z_LVAL_P(arg2); + } else { + stmt->fetch.column = how & PDO_FETCH_GROUP ? -1 : 0; + } break; - case 3: - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "Third parameter not allowed for PDO::FETCH_COLUMN"); - error = 1; - } - break; - default: - if (ZEND_NUM_ARGS() > 1) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "Extraneous additional parameters"); - error = 1; - } + default: + /* No support for PDO_FETCH_INTO which takes 2 args??? */ + if (ZEND_NUM_ARGS() > 1) { + zend_string *func = get_active_function_or_method_name(); + zend_argument_count_error("%s() expects exactly 1 argument for the fetch mode provided, %d given", + ZSTR_VAL(func), ZEND_NUM_ARGS()); + zend_string_release(func); + RETURN_THROWS(); + } } flags = how & PDO_FETCH_FLAGS; @@ -1382,48 +1398,42 @@ PHP_METHOD(PDOStatement, fetchAll) how |= stmt->default_fetch_type & ~PDO_FETCH_FLAGS; } - if (!error) { - PDO_STMT_CLEAR_ERR(); - if ((how & PDO_FETCH_GROUP) || how == PDO_FETCH_KEY_PAIR || - (how == PDO_FETCH_USE_DEFAULT && stmt->default_fetch_type == PDO_FETCH_KEY_PAIR) - ) { - array_init(return_value); - return_all = return_value; - } else { - return_all = 0; - } - if (!do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, 0, return_all)) { - error = 2; - } + PDO_STMT_CLEAR_ERR(); + if ((how & PDO_FETCH_GROUP) || how == PDO_FETCH_KEY_PAIR || + (how == PDO_FETCH_USE_DEFAULT && stmt->default_fetch_type == PDO_FETCH_KEY_PAIR) + ) { + array_init(return_value); + return_all = return_value; + } + if (!do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, return_all)) { + error = true; } + if (!error) { if ((how & PDO_FETCH_GROUP)) { - while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, 0, return_all)); + while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, return_all)); } else if (how == PDO_FETCH_KEY_PAIR || (how == PDO_FETCH_USE_DEFAULT && stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)) { - while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, 0, return_all)); + while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, return_all)); } else { array_init(return_value); do { zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &data); - } while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, 0, 0)); + } while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, NULL)); } } do_fetch_opt_finish(stmt, 0); + /* Restore defaults which were changed by PDO_FETCH_CLASS mode */ stmt->fetch.cls.ce = old_ce; ZVAL_COPY_VALUE(&stmt->fetch.cls.ctor_args, &old_ctor_args); stmt->fetch.cls.fci.param_count = old_arg_count; + /* on no results, return an empty array */ if (error) { PDO_HANDLE_STMT_ERR(); - if (error != 2) { - RETURN_FALSE; - } else { /* on no results, return an empty array */ - if (Z_TYPE_P(return_value) != IS_ARRAY) { - array_init(return_value); - } - return; + if (Z_TYPE_P(return_value) != IS_ARRAY) { + array_init(return_value); } } } @@ -1451,12 +1461,16 @@ static void register_bound_param(INTERNAL_FUNCTION_PARAMETERS, int is_param) /* param.param_type = (int) param_type; if (param.name) { + if (ZSTR_LEN(param.name) == 0) { + zend_argument_value_error(1, "cannot be empty"); + RETURN_THROWS(); + } param.paramno = -1; } else if (param.paramno > 0) { --param.paramno; /* make it zero-based internally */ } else { - pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "Columns/Parameters are 1-based"); - RETURN_FALSE; + zend_argument_value_error(1, "must be greater than or equal to 1"); + RETURN_THROWS(); } if (driver_params) { @@ -1495,12 +1509,16 @@ PHP_METHOD(PDOStatement, bindValue) param.param_type = (int) param_type; if (param.name) { + if (ZSTR_LEN(param.name) == 0) { + zend_argument_value_error(1, "cannot be empty"); + RETURN_THROWS(); + } param.paramno = -1; } else if (param.paramno > 0) { --param.paramno; /* make it zero-based internally */ } else { - pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "Columns/Parameters are 1-based"); - RETURN_FALSE; + zend_argument_value_error(1, "must be greater than or equal to 1"); + RETURN_THROWS(); } ZVAL_COPY(¶m.parameter, parameter); @@ -1557,7 +1575,7 @@ PHP_METHOD(PDOStatement, errorCode) PHP_METHOD(PDOStatement, errorInfo) { int error_count; - int error_count_diff = 0; + int error_count_diff = 0; int error_expected_count = 3; ZEND_PARSE_PARAMETERS_NONE(); @@ -1595,8 +1613,11 @@ PHP_METHOD(PDOStatement, setAttribute) ZEND_PARSE_PARAMETERS_END(); PHP_STMT_GET_OBJ; + + /* Driver hasn't registered a function for setting attributes */ if (!stmt->methods->set_attribute) { - goto fail; + pdo_raise_impl_error(stmt->dbh, stmt, "IM001", "This driver doesn't support setting attributes"); + RETURN_FALSE; } PDO_STMT_CLEAR_ERR(); @@ -1604,12 +1625,8 @@ PHP_METHOD(PDOStatement, setAttribute) RETURN_TRUE; } -fail: - if (!stmt->methods->set_attribute) { - pdo_raise_impl_error(stmt->dbh, stmt, "IM001", "This driver doesn't support setting attributes"); - } else { - PDO_HANDLE_STMT_ERR(); - } + /* Error while setting attribute */ + PDO_HANDLE_STMT_ERR(); RETURN_FALSE; } /* }}} */ @@ -1686,9 +1703,9 @@ PHP_METHOD(PDOStatement, getColumnMeta) ZEND_PARSE_PARAMETERS_END(); PHP_STMT_GET_OBJ; - if(colno < 0) { - pdo_raise_impl_error(stmt->dbh, stmt, "42P10", "column number must be non-negative"); - RETURN_FALSE; + if (colno < 0) { + zend_argument_value_error(1, "must be greater than or equal to 0"); + RETURN_THROWS(); } if (!stmt->methods->get_column_meta) { @@ -1716,13 +1733,13 @@ PHP_METHOD(PDOStatement, getColumnMeta) /* {{{ Changes the default fetch mode for subsequent fetches (params have different meaning for different fetch modes) */ -int pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, zval *args, uint32_t num_args) +bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_arg_num, + zval *args, uint32_t variadic_num_args) { int flags = 0; - zend_class_entry *cep; - int retval; - - do_fetch_opt_finish(stmt, 1); + uint32_t arg1_arg_num = mode_arg_num + 1; + uint32_t constructor_arg_num = mode_arg_num + 2; + uint32_t total_num_args = mode_arg_num + variadic_num_args; switch (stmt->default_fetch_type) { case PDO_FETCH_INTO: @@ -1739,12 +1756,10 @@ int pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, zval *args, uint flags = mode & PDO_FETCH_FLAGS; - if (!pdo_stmt_verify_mode(stmt, mode, 0)) { - PDO_STMT_CLEAR_ERR(); - return FAILURE; + if (!pdo_stmt_verify_mode(stmt, mode, mode_arg_num, false)) { + return false; } - retval = FAILURE; switch (mode & ~PDO_FETCH_FLAGS) { case PDO_FETCH_USE_DEFAULT: case PDO_FETCH_LAZY: @@ -1755,100 +1770,120 @@ int pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, zval *args, uint case PDO_FETCH_BOUND: case PDO_FETCH_NAMED: case PDO_FETCH_KEY_PAIR: - if (num_args != 0) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "fetch mode doesn't allow any extra arguments"); - } else { - retval = SUCCESS; + if (variadic_num_args != 0) { + zend_string *func = get_active_function_or_method_name(); + zend_argument_count_error("%s() expects exactly %d arguments for the fetch mode provided, %d given", + ZSTR_VAL(func), mode_arg_num, total_num_args); + zend_string_release(func); + return false; } break; case PDO_FETCH_COLUMN: - if (num_args != 1) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "fetch mode requires the colno argument"); - } else if (Z_TYPE(args[0]) != IS_LONG) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "colno must be an integer"); - } else { - stmt->fetch.column = Z_LVAL(args[0]); - retval = SUCCESS; - } + if (variadic_num_args != 1) { + zend_string *func = get_active_function_or_method_name(); + zend_argument_count_error("%s() expects exactly %d arguments for the fetch mode provided, %d given", + ZSTR_VAL(func), arg1_arg_num, total_num_args); + zend_string_release(func); + return false; + } + if (Z_TYPE(args[0]) != IS_LONG) { + zend_argument_type_error(arg1_arg_num, "must be of type int, %s given", zend_zval_type_name(&args[0])); + return false; + } + if (Z_LVAL(args[0]) < 0) { + zend_argument_value_error(arg1_arg_num, "must be greater than or equal to 0"); + return false; + } + stmt->fetch.column = Z_LVAL(args[0]); break; - case PDO_FETCH_CLASS: + case PDO_FETCH_CLASS: { + HashTable *constructor_args = NULL; + /* Undef constructor arguments */ + ZVAL_UNDEF(&stmt->fetch.cls.ctor_args); /* Gets its class name from 1st column */ if ((flags & PDO_FETCH_CLASSTYPE) == PDO_FETCH_CLASSTYPE) { - if (num_args != 0) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "fetch mode doesn't allow any extra arguments"); - } else { - stmt->fetch.cls.ce = NULL; - retval = SUCCESS; + if (variadic_num_args != 0) { + zend_string *func = get_active_function_or_method_name(); + zend_argument_count_error("%s() expects exactly %d arguments for the fetch mode provided, %d given", + ZSTR_VAL(func), mode_arg_num, total_num_args); + zend_string_release(func); + return false; } + stmt->fetch.cls.ce = NULL; } else { - if (num_args < 1) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "fetch mode requires the classname argument"); - } else if (num_args > 2) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "too many arguments"); - } else if (Z_TYPE(args[0]) != IS_STRING) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "classname must be a string"); - } else { - cep = zend_lookup_class(Z_STR(args[0])); - if (cep) { - retval = SUCCESS; - stmt->fetch.cls.ce = cep; - } + zend_class_entry *cep; + if (variadic_num_args == 0) { + zend_string *func = get_active_function_or_method_name(); + zend_argument_count_error("%s() expects at least %d arguments for the fetch mode provided, %d given", + ZSTR_VAL(func), arg1_arg_num, total_num_args); + zend_string_release(func); + return false; } - } - - if (SUCCESS == retval) { - ZVAL_UNDEF(&stmt->fetch.cls.ctor_args); - if (num_args == 2) { + /* constructor_arguments can be null/not passed */ + if (variadic_num_args > 2) { + zend_string *func = get_active_function_or_method_name(); + zend_argument_count_error("%s() expects at most %d arguments for the fetch mode provided, %d given", + ZSTR_VAL(func), constructor_arg_num, total_num_args); + zend_string_release(func); + return false; + } + if (Z_TYPE(args[0]) != IS_STRING) { + zend_argument_type_error(arg1_arg_num, "must be of type string, %s given", zend_zval_type_name(&args[0])); + return false; + } + cep = zend_lookup_class(Z_STR(args[0])); + if (!cep) { + zend_argument_type_error(arg1_arg_num, "must be a valid class"); + return false; + } + /* Verify constructor_args (args[1]) is ?array */ + /* TODO: Improve logic? */ + if (variadic_num_args == 2) { if (Z_TYPE(args[1]) != IS_NULL && Z_TYPE(args[1]) != IS_ARRAY) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "ctor_args must be either NULL or an array"); - retval = FAILURE; - } else if (Z_TYPE(args[1]) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL(args[1]))) { - ZVAL_ARR(&stmt->fetch.cls.ctor_args, zend_array_dup(Z_ARRVAL(args[1]))); + zend_argument_type_error(constructor_arg_num, "must be of type ?array, %s given", + zend_zval_type_name(&args[1])); + return false; + } + if (Z_TYPE(args[1]) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL(args[1]))) { + constructor_args = Z_ARRVAL(args[1]); } } + stmt->fetch.cls.ce = cep; - if (SUCCESS == retval) { - do_fetch_class_prepare(stmt); + /* If constructor arguments are present and not empty */ + if (constructor_args) { + ZVAL_ARR(&stmt->fetch.cls.ctor_args, zend_array_dup(constructor_args)); } } + do_fetch_class_prepare(stmt); break; - + } case PDO_FETCH_INTO: - if (num_args != 1) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "fetch mode requires the object parameter"); - } else if (Z_TYPE(args[0]) != IS_OBJECT) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "object must be an object"); - } else { - retval = SUCCESS; + if (total_num_args != arg1_arg_num) { + zend_string *func = get_active_function_or_method_name(); + zend_argument_count_error("%s() expects exactly %d arguments for the fetch mode provided, %d given", + ZSTR_VAL(func), arg1_arg_num, total_num_args); + zend_string_release(func); + return false; } - - if (SUCCESS == retval) { - ZVAL_COPY(&stmt->fetch.into, &args[0]); + if (Z_TYPE(args[0]) != IS_OBJECT) { + zend_argument_type_error(arg1_arg_num, "must be of type object, %s given", zend_zval_type_name(&args[0])); + return false; } + ZVAL_COPY(&stmt->fetch.into, &args[0]); break; - default: - pdo_raise_impl_error(stmt->dbh, stmt, "22003", "Invalid fetch mode specified"); + zend_argument_value_error(mode_arg_num, "must be one of the PDO::FETCH_* constants"); + return false; } - if (SUCCESS == retval) { - stmt->default_fetch_type = 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. - * - */ - PDO_STMT_CLEAR_ERR(); + stmt->default_fetch_type = mode; - return retval; + return true; } PHP_METHOD(PDOStatement, setFetchMode) @@ -1862,13 +1897,21 @@ PHP_METHOD(PDOStatement, setFetchMode) } PHP_STMT_GET_OBJ; - RETVAL_BOOL(pdo_stmt_setup_fetch_mode(stmt, fetch_mode, args, num_args) == SUCCESS); + + do_fetch_opt_finish(stmt, 1); + + if (!pdo_stmt_setup_fetch_mode(stmt, fetch_mode, 1, args, num_args)) { + RETURN_THROWS(); + } + + // TODO Void return? + RETURN_TRUE; } /* }}} */ /* {{{ Advances to the next rowset in a multi-rowset statement handle. Returns true if it succeeded, false otherwise */ -static int pdo_stmt_do_next_rowset(pdo_stmt_t *stmt) +static bool pdo_stmt_do_next_rowset(pdo_stmt_t *stmt) { /* un-describe */ if (stmt->columns) { @@ -1963,6 +2006,7 @@ PHP_METHOD(PDOStatement, debugDumpParams) ZEND_PARSE_PARAMETERS_NONE(); PHP_STMT_GET_OBJ; + if (out == NULL) { RETURN_FALSE; } @@ -2024,10 +2068,8 @@ PHP_METHOD(PDOStatement, getIterator) /* {{{ overloaded handlers for PDOStatement class */ static zval *dbstmt_prop_write(zend_object *object, zend_string *name, zval *value, void **cache_slot) { - pdo_stmt_t *stmt = php_pdo_stmt_fetch_object(object); - if (strcmp(ZSTR_VAL(name), "queryString") == 0) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "property queryString is read only"); + zend_throw_error(NULL, "Property queryString is read only"); return value; } else { return zend_std_write_property(object, name, value, cache_slot); @@ -2036,10 +2078,8 @@ static zval *dbstmt_prop_write(zend_object *object, zend_string *name, zval *val static void dbstmt_prop_delete(zend_object *object, zend_string *name, void **cache_slot) { - pdo_stmt_t *stmt = php_pdo_stmt_fetch_object(object); - if (strcmp(ZSTR_VAL(name), "queryString") == 0) { - pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "property queryString is read only"); + zend_throw_error(NULL, "Property queryString is read only"); } else { zend_std_unset_property(object, name, cache_slot); } @@ -2225,7 +2265,7 @@ static void pdo_stmt_iter_move_forwards(zend_object_iterator *iter) } if (!do_fetch(stmt, &I->fetch_ahead, PDO_FETCH_USE_DEFAULT, - PDO_FETCH_ORI_NEXT, 0, 0)) { + PDO_FETCH_ORI_NEXT, /* offset */ 0, NULL)) { PDO_HANDLE_STMT_ERR(); I->key = (zend_ulong)-1; @@ -2265,7 +2305,7 @@ zend_object_iterator *pdo_stmt_iter_get(zend_class_entry *ce, zval *object, int ZVAL_OBJ(&I->iter.data, Z_OBJ_P(object)); if (!do_fetch(stmt, &I->fetch_ahead, PDO_FETCH_USE_DEFAULT, - PDO_FETCH_ORI_NEXT, 0, 0)) { + PDO_FETCH_ORI_NEXT, /* offset */ 0, NULL)) { PDO_HANDLE_STMT_ERR(); I->key = (zend_ulong)-1; ZVAL_UNDEF(&I->fetch_ahead); @@ -2296,7 +2336,7 @@ static zval *row_prop_read(zend_object *object, zend_string *name, int type, voi * numbers */ for (colno = 0; colno < stmt->column_count; colno++) { if (ZSTR_LEN(stmt->columns[colno].name) == ZSTR_LEN(name) && - strncmp(ZSTR_VAL(stmt->columns[colno].name), ZSTR_VAL(name), ZSTR_LEN(name)) == 0) { + strncmp(ZSTR_VAL(stmt->columns[colno].name), ZSTR_VAL(name), ZSTR_LEN(name)) == 0) { fetch_value(stmt, rv, colno, NULL); return rv; } @@ -2338,7 +2378,7 @@ static zval *row_dim_read(zend_object *object, zval *member, int type, zval *rv) * numbers */ for (colno = 0; colno < stmt->column_count; colno++) { if (ZSTR_LEN(stmt->columns[colno].name) == Z_STRLEN_P(member) && - strncmp(ZSTR_VAL(stmt->columns[colno].name), Z_STRVAL_P(member), Z_STRLEN_P(member)) == 0) { + strncmp(ZSTR_VAL(stmt->columns[colno].name), Z_STRVAL_P(member), Z_STRLEN_P(member)) == 0) { fetch_value(stmt, rv, colno, NULL); return rv; } @@ -2380,7 +2420,7 @@ static int row_prop_exists(zend_object *object, zend_string *name, int check_emp * numbers */ for (colno = 0; colno < stmt->column_count; colno++) { if (ZSTR_LEN(stmt->columns[colno].name) == ZSTR_LEN(name) && - strncmp(ZSTR_VAL(stmt->columns[colno].name), ZSTR_VAL(name), ZSTR_LEN(name)) == 0) { + strncmp(ZSTR_VAL(stmt->columns[colno].name), ZSTR_VAL(name), ZSTR_LEN(name)) == 0) { int res; zval val; @@ -2420,7 +2460,7 @@ static int row_dim_exists(zend_object *object, zval *member, int check_empty) * numbers */ for (colno = 0; colno < stmt->column_count; colno++) { if (ZSTR_LEN(stmt->columns[colno].name) == Z_STRLEN_P(member) && - strncmp(ZSTR_VAL(stmt->columns[colno].name), Z_STRVAL_P(member), Z_STRLEN_P(member)) == 0) { + strncmp(ZSTR_VAL(stmt->columns[colno].name), Z_STRVAL_P(member), Z_STRLEN_P(member)) == 0) { int res; zval val; diff --git a/ext/pdo/pdo_stmt.stub.php b/ext/pdo/pdo_stmt.stub.php index b280f73ffd..2732c8a71d 100644 --- a/ext/pdo/pdo_stmt.stub.php +++ b/ext/pdo/pdo_stmt.stub.php @@ -16,16 +16,16 @@ class PDOStatement implements IteratorAggregate /** @return bool */ public function closeCursor() {} - /** @return int|false */ + /** @return int */ public function columnCount() {} /** @return bool|null */ public function debugDumpParams() {} - /** @return string|false|null */ + /** @return string|null */ public function errorCode() {} - /** @return array|false */ + /** @return array */ public function errorInfo() {} /** @return bool */ @@ -34,8 +34,8 @@ class PDOStatement implements IteratorAggregate /** @return mixed */ public function fetch(int $fetch_style = PDO::FETCH_BOTH, int $cursor_orientation = PDO::FETCH_ORI_NEXT, int $cursor_offset = 0) {} - /** @return array|false */ - public function fetchAll(int $fetch_style = PDO::FETCH_BOTH, mixed ...$fetch_args) {} + /** @return array */ + public function fetchAll(int $fetch_style = PDO::FETCH_BOTH, mixed ...$fetch_mode_args) {} /** @return mixed */ public function fetchColumn(int $column_number = 0) {} @@ -52,14 +52,14 @@ class PDOStatement implements IteratorAggregate /** @return bool */ public function nextRowset() {} - /** @return int|false */ + /** @return int */ public function rowCount() {} /** @return bool */ public function setAttribute(int $attribute, mixed $value) {} /** @return bool */ - public function setFetchMode(int $mode, mixed ...$params) {} + public function setFetchMode(int $mode, mixed ...$fetch_mode_args) {} public function getIterator(): Iterator {} } diff --git a/ext/pdo/pdo_stmt_arginfo.h b/ext/pdo/pdo_stmt_arginfo.h index 116449ad3a..157507a852 100644 --- a/ext/pdo/pdo_stmt_arginfo.h +++ b/ext/pdo/pdo_stmt_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a35e66ccff5e569f07ae8372e661e005943dfbc7 */ + * Stub hash: c12bc1c5d1e3dbd8cce67e50c974b20ec5564e67 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDOStatement_bindColumn, 0, 0, 2) ZEND_ARG_TYPE_MASK(0, column, MAY_BE_STRING|MAY_BE_LONG, NULL) @@ -46,7 +46,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDOStatement_fetchAll, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fetch_style, IS_LONG, 0, "PDO::FETCH_BOTH") - ZEND_ARG_VARIADIC_TYPE_INFO(0, fetch_args, IS_MIXED, 0) + ZEND_ARG_VARIADIC_TYPE_INFO(0, fetch_mode_args, IS_MIXED, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDOStatement_fetchColumn, 0, 0, 0) @@ -77,7 +77,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDOStatement_setFetchMode, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, mode, IS_LONG, 0) - ZEND_ARG_VARIADIC_TYPE_INFO(0, params, IS_MIXED, 0) + ZEND_ARG_VARIADIC_TYPE_INFO(0, fetch_mode_args, IS_MIXED, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_PDOStatement_getIterator, 0, 0, Iterator, 0) diff --git a/ext/pdo/php_pdo.h b/ext/pdo/php_pdo.h index 9d9e73915d..89cd22c8e8 100644 --- a/ext/pdo/php_pdo.h +++ b/ext/pdo/php_pdo.h @@ -53,11 +53,11 @@ PHP_MINFO_FUNCTION(pdo); #define REGISTER_PDO_CLASS_CONST_STRING(const_name, value) \ zend_declare_class_constant_stringl(php_pdo_get_dbh_ce(), const_name, sizeof(const_name)-1, value, sizeof(value)-1); -#define PDO_CONSTRUCT_CHECK \ - if (!dbh->driver) { \ - pdo_raise_impl_error(dbh, NULL, "00000", "PDO constructor was not called"); \ - return; \ - } \ +#define PDO_CONSTRUCT_CHECK \ + if (!dbh->driver) { \ + zend_throw_error(NULL, "PDO object is not initialized, constructor was not called"); \ + RETURN_THROWS(); \ + } \ #endif /* PHP_PDO_H */ diff --git a/ext/pdo/php_pdo_int.h b/ext/pdo/php_pdo_int.h index 7fe3bfc850..7f992a5fd0 100644 --- a/ext/pdo/php_pdo_int.h +++ b/ext/pdo/php_pdo_int.h @@ -40,7 +40,8 @@ void pdo_dbstmt_free_storage(zend_object *std); zend_object_iterator *pdo_stmt_iter_get(zend_class_entry *ce, zval *object, int by_ref); extern zend_object_handlers pdo_dbstmt_object_handlers; int pdo_stmt_describe_columns(pdo_stmt_t *stmt); -int pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long fetch_mode, zval *args, uint32_t num_args); +bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_arg_num, + zval *args, uint32_t variadic_num_args); extern zend_object *pdo_row_new(zend_class_entry *ce); extern const zend_function_entry pdo_row_functions[]; diff --git a/ext/pdo/tests/bug_44159.phpt b/ext/pdo/tests/bug_44159.phpt index 0e1116d588..b5601eed41 100644 --- a/ext/pdo/tests/bug_44159.phpt +++ b/ext/pdo/tests/bug_44159.phpt @@ -2,51 +2,46 @@ PDO Common: Bug #44159 (Crash: $pdo->setAttribute(PDO::STATEMENT_ATTR_CLASS, NULL)) --SKIPIF-- --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); -$attrs = array(PDO::ATTR_STATEMENT_CLASS, PDO::ATTR_STRINGIFY_FETCHES, PDO::NULL_TO_STRING); +$attrs = array(PDO::ATTR_STATEMENT_CLASS, PDO::ATTR_STRINGIFY_FETCHES); foreach ($attrs as $attr) { - var_dump($pdo->setAttribute($attr, NULL)); - var_dump($pdo->setAttribute($attr, 1)); - var_dump($pdo->setAttribute($attr, 'nonsense')); + try { + var_dump($pdo->setAttribute($attr, NULL)); + } catch (\Error $e) { + echo get_class($e), ': ', $e->getMessage(), \PHP_EOL; + } + try { + var_dump($pdo->setAttribute($attr, 1)); + } catch (\Error $e) { + echo get_class($e), ': ', $e->getMessage(), \PHP_EOL; + } + try { + var_dump($pdo->setAttribute($attr, 'nonsense')); + } catch (\Error $e) { + echo get_class($e), ': ', $e->getMessage(), \PHP_EOL; + } } @unlink(__DIR__."/foo.db"); ?> ---EXPECTF-- -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); the classname must be a string specifying an existing class in %s on line %d - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); the classname must be a string specifying an existing class in %s on line %d - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); the classname must be a string specifying an existing class in %s on line %d - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: attribute value must be an integer in %s on line %d - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) -bool(true) -bool(true) -bool(true) +--EXPECT-- +TypeError: PDO::ATTR_STATEMENT_CLASS value must be of type array, null given +TypeError: PDO::ATTR_STATEMENT_CLASS value must be of type array, int given +TypeError: PDO::ATTR_STATEMENT_CLASS value must be of type array, string given +TypeError: Attribute value must be of type int for selected attribute, null given bool(true) bool(true) diff --git a/ext/pdo/tests/bug_44173.phpt b/ext/pdo/tests/bug_44173.phpt index df98f332fe..0baa7973e5 100644 --- a/ext/pdo/tests/bug_44173.phpt +++ b/ext/pdo/tests/bug_44173.phpt @@ -19,8 +19,12 @@ $db->exec("INSERT INTO test VALUES (1)"); // Bug entry [2] -- 1 is PDO::FETCH_LAZY -$stmt = $db->query("SELECT * FROM test", PDO::FETCH_LAZY, 0, 0); -var_dump($stmt); +try { + $stmt = $db->query("SELECT * FROM test", PDO::FETCH_LAZY, 0, []); + var_dump($stmt); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} // Bug entry [3] @@ -31,39 +35,46 @@ try { } // Bug entry [4] -$stmt = $db->query("SELECT * FROM test", PDO::FETCH_CLASS, 0, 0, 0); -var_dump($stmt); +try { + $stmt = $db->query("SELECT * FROM test", PDO::FETCH_CLASS, 0, 0, 0); + var_dump($stmt); +} catch (\ArgumentCountError $e) { + echo $e->getMessage(), \PHP_EOL; +} // Bug entry [5] -$stmt = $db->query("SELECT * FROM test", PDO::FETCH_INTO); -var_dump($stmt); +try { + $stmt = $db->query("SELECT * FROM test", PDO::FETCH_INTO); + var_dump($stmt); +} catch (\ArgumentCountError $e) { + echo $e->getMessage(), \PHP_EOL; +} // Bug entry [6] -$stmt = $db->query("SELECT * FROM test", PDO::FETCH_COLUMN); -var_dump($stmt); +try { + $stmt = $db->query("SELECT * FROM test", PDO::FETCH_COLUMN); + var_dump($stmt); +} catch (\ArgumentCountError $e) { + echo $e->getMessage(), \PHP_EOL; +} // Bug entry [7] -$stmt = $db->query("SELECT * FROM test", PDO::FETCH_CLASS); -var_dump($stmt); +try { + $stmt = $db->query("SELECT * FROM test", PDO::FETCH_CLASS); + var_dump($stmt); +} catch (\ArgumentCountError $e) { + echo $e->getMessage(), \PHP_EOL; +} ?> ---EXPECTF-- -Warning: PDO::query(): SQLSTATE[HY000]: General error: fetch mode doesn't allow any extra arguments in %s -bool(false) +--EXPECT-- +PDO::query() expects exactly 2 arguments for the fetch mode provided, 4 given PDO::query(): Argument #2 ($fetch_mode) must be of type ?int, string given - -Warning: PDO::query(): SQLSTATE[HY000]: General error: too many arguments in %s -bool(false) - -Warning: PDO::query(): SQLSTATE[HY000]: General error: fetch mode requires the object parameter in %s -bool(false) - -Warning: PDO::query(): SQLSTATE[HY000]: General error: fetch mode requires the colno argument in %s -bool(false) - -Warning: PDO::query(): SQLSTATE[HY000]: General error: fetch mode requires the classname argument in %s -bool(false) +PDO::query() expects at most 4 arguments for the fetch mode provided, 5 given +PDO::query() expects exactly 3 arguments for the fetch mode provided, 2 given +PDO::query() expects exactly 3 arguments for the fetch mode provided, 2 given +PDO::query() expects at least 3 arguments for the fetch mode provided, 2 given diff --git a/ext/pdo/tests/pdo_038.phpt b/ext/pdo/tests/pdo_038.phpt index 3ff2d090a8..a2887f35db 100644 --- a/ext/pdo/tests/pdo_038.phpt +++ b/ext/pdo/tests/pdo_038.phpt @@ -32,14 +32,19 @@ switch ($conn->getAttribute(PDO::ATTR_DRIVER_NAME)) { $stmt = $conn->prepare($query); -var_dump(fetchColumn($stmt, -1)); +try { + var_dump(fetchColumn($stmt, -1)); +} catch (\ValueError $e) { + echo $e->getMessage(), \PHP_EOL; +} var_dump(fetchColumn($stmt, 0)); -var_dump(fetchColumn($stmt, 1)); +try { + var_dump(fetchColumn($stmt, 1)); +} catch (\ValueError $e) { + echo $e->getMessage(), \PHP_EOL; +} ?> ---EXPECTF-- -Warning: PDOStatement::fetchColumn(): SQLSTATE[HY000]: General error: Invalid column index in %s -bool(false) +--EXPECT-- +Column index must be greater than or equal to 0 string(1) "1" - -Warning: PDOStatement::fetchColumn(): SQLSTATE[HY000]: General error: Invalid column index in %s -bool(false) +Invalid column index diff --git a/ext/pdo/tests/pdo_quote_empty_string.phpt b/ext/pdo/tests/pdo_quote_empty_string.phpt new file mode 100644 index 0000000000..214d1014b9 --- /dev/null +++ b/ext/pdo/tests/pdo_quote_empty_string.phpt @@ -0,0 +1,31 @@ +--TEST-- +PDO::quote() must accept empty string for drivers which support this feature +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +try { + $result = $pdo->quote(''); + if (!is_string($result)) { + var_dump($result); + } +} catch (\PDOException) { + // Do nothing as quoting is not supported with this driver +} +?> +DONE + +--EXPECT-- +DONE diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_errmode.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_errmode.phpt index 0d6cee356e..d6ebd87f60 100644 --- a/ext/pdo_mysql/tests/pdo_mysql_attr_errmode.phpt +++ b/ext/pdo_mysql/tests/pdo_mysql_attr_errmode.phpt @@ -14,29 +14,27 @@ error_reporting=E_ALL require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc'); $db = MySQLPDOTest::factory(); - $valid = array(PDO::ERRMODE_SILENT, PDO::ERRMODE_WARNING, PDO::ERRMODE_EXCEPTION); - do { - $invalid = mt_rand(-1000, 1000); - } while (in_array($invalid, $valid)); - - - $tmp = array(); - if (false != @$db->setAttribute(PDO::ATTR_ERRMODE, $tmp)) - printf("[001] Maybe PDO could indicate that this is not a proper way of setting the ERRMODE...\n"); - - $tmp = new stdClass(); - $ret = @$db->setAttribute(PDO::ATTR_ERRMODE, $tmp); - if (false != $ret) - printf("[002] Maybe PDO could indicate that this is not a proper way of setting the ERRMODE...%s\n", - var_export($ret, true)); - - $ret = @$db->setAttribute(PDO::ATTR_ERRMODE, 'pdo'); - if (false != $ret) - printf("[003] Maybe PDO could indicate that this is not a proper way of setting the ERRMODE...%s\n", - var_export($ret, true)); - - if (false != @$db->setAttribute(PDO::ATTR_ERRMODE, $invalid)) - printf("[004] Invalid ERRMODE should be rejected\n"); + try { + $db->setAttribute(PDO::ATTR_ERRMODE, []); + } catch (\Error $e) { + echo get_class($e), ': ', $e->getMessage(), \PHP_EOL; + } + try { + $db->setAttribute(PDO::ATTR_ERRMODE, new stdClass()); + } catch (\Error $e) { + echo get_class($e), ': ', $e->getMessage(), \PHP_EOL; + } + try { + /* This currently passes */ + $db->setAttribute(PDO::ATTR_ERRMODE, 'pdo'); + } catch (\Error $e) { + echo get_class($e), ': ', $e->getMessage(), \PHP_EOL; + } + try { + $db->setAttribute(PDO::ATTR_ERRMODE, 1000); + } catch (\Error $e) { + echo get_class($e), ': ', $e->getMessage(), \PHP_EOL; + } $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); // no message for any PDO call but... @@ -160,7 +158,9 @@ error_reporting=E_ALL print "done!\n"; ?> --EXPECTF-- -[003] Maybe PDO could indicate that this is not a proper way of setting the ERRMODE...true +TypeError: Attribute value must be of type int for selected attribute, array given +TypeError: Attribute value must be of type int for selected attribute, stdClass given +ValueError: Error mode must be one of the PDO::ERRMODE_* constants Warning: PDO::query(): SQLSTATE[42000]: Syntax error or access violation: %d You have an error in your SQL syntax; check the manual that corresponds to your %s server version for the right syntax to use near '%s' at line %d in %s on line %d diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_oracle_nulls.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_oracle_nulls.phpt index 694a039441..6d922a037d 100644 --- a/ext/pdo_mysql/tests/pdo_mysql_attr_oracle_nulls.phpt +++ b/ext/pdo_mysql/tests/pdo_mysql_attr_oracle_nulls.phpt @@ -12,16 +12,22 @@ MySQLPDOTest::skip(); $db = MySQLPDOTest::factory(); MySQLPDOTest::createTestTable($db); - $tmp = array(); - if (false !== @$db->setAttribute(PDO::ATTR_ORACLE_NULLS, $tmp)) - printf("[001] Maybe PDO could indicate that this is not a proper way of setting ATTR_ORACLE_NULLS...\n"); - - $tmp = new stdClass(); - if (false !== @$db->setAttribute(PDO::ATTR_ORACLE_NULLS, $tmp)); - printf("[002] Maybe PDO could indicate that this is not a proper way of setting ATTR_ORACLE_NULLS...\n"); - - if (false !== @$db->setAttribute(PDO::ATTR_ORACLE_NULLS, 'pdo')) - printf("[003] Maybe PDO could indicate that this is not a proper way of setting ATTR_ORACLE_NULLS...\n"); + try { + $db->setAttribute(PDO::ATTR_ORACLE_NULLS, []); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + try { + $db->setAttribute(PDO::ATTR_ORACLE_NULLS, new stdClass()); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + try { + /* Currently passes... */ + $db->setAttribute(PDO::ATTR_ORACLE_NULLS, 'pdo'); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } $db->setAttribute(PDO::ATTR_ORACLE_NULLS, 1); $stmt = $db->query("SELECT NULL AS z, '' AS a, ' ' AS b, TRIM(' ') as c, ' d' AS d, '" . chr(0) . " e' AS e"); @@ -82,8 +88,8 @@ MySQLPDOTest::skip(); print "done!"; ?> --EXPECTF-- -[002] Maybe PDO could indicate that this is not a proper way of setting ATTR_ORACLE_NULLS... -[003] Maybe PDO could indicate that this is not a proper way of setting ATTR_ORACLE_NULLS... +Attribute value must be of type int for selected attribute, array given +Attribute value must be of type int for selected attribute, stdClass given array(1) { [0]=> array(6) { diff --git a/ext/pdo_mysql/tests/pdo_mysql_attr_statement_class.phpt b/ext/pdo_mysql/tests/pdo_mysql_attr_statement_class.phpt index 66df0cda45..6d536f9ded 100644 --- a/ext/pdo_mysql/tests/pdo_mysql_attr_statement_class.phpt +++ b/ext/pdo_mysql/tests/pdo_mysql_attr_statement_class.phpt @@ -16,15 +16,22 @@ $db = MySQLPDOTest::factory(); $default = $db->getAttribute(PDO::ATTR_STATEMENT_CLASS); var_dump($default); - if (false !== ($tmp = @$db->setAttribute(PDO::ATTR_STATEMENT_CLASS, 'foo'))) - printf("[002] Expecting boolean/false got %s\n", var_export($tmp, true)); - - if (false !== ($tmp = @$db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('classname')))) - printf("[003] Expecting boolean/false got %s\n", var_export($tmp, true)); - + try { + $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, 'foo'); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + try { + $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['classname']); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } // unknown class - if (false !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('classname', array())))) - printf("[004] Expecting boolean/false got %s\n", var_export($tmp, true)); + try { + $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['classname', []]); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } // class not derived from PDOStatement class myclass { @@ -32,8 +39,12 @@ $db = MySQLPDOTest::factory(); printf("myclass\n"); } } - if (false !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('myclass', array())))) - printf("[005] Expecting boolean/false got %s\n", var_export($tmp, true)); + + try { + $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['myclass', []]); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } // public constructor not allowed class mystatement extends PDOStatement { @@ -42,8 +53,13 @@ $db = MySQLPDOTest::factory(); } } - if (false !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('mystatement', array())))) - printf("[006] Expecting boolean/false got %s\n", var_export($tmp, true)); + try { + if (false !== ($tmp = $db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['mystatement', []]))) + printf("[006] Expecting boolean/false got %s\n", var_export($tmp, true)); + } catch (\Error $e) { + echo get_class($e), ': ', $e->getMessage(), \PHP_EOL; + } + // ... but a public destructor is allowed class mystatement2 extends PDOStatement { @@ -109,18 +125,11 @@ array(1) { [0]=> string(12) "PDOStatement" } - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: PDO::ATTR_STATEMENT_CLASS requires format array(classname, array(ctor_args)); the classname must be a string specifying an existing class in %s on line %d - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: user-supplied statement class must be derived from PDOStatement in %s on line %d - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error: user-supplied statement class cannot have a public constructor in %s on line %d - -Warning: PDO::setAttribute(): SQLSTATE[HY000]: General error in %s on line %d +PDO::ATTR_STATEMENT_CLASS value must be of type array, string given +PDO::ATTR_STATEMENT_CLASS class must be a valid class +PDO::ATTR_STATEMENT_CLASS class must be a valid class +PDO::ATTR_STATEMENT_CLASS class must be derived from PDOStatement +TypeError: User-supplied statement class cannot have a public constructor array(2) { [0]=> string(12) "mystatement4" diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated.phpt index 6fe2ff20ba..6afd57420f 100644 --- a/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated.phpt +++ b/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated.phpt @@ -88,9 +88,11 @@ $db = MySQLPDOTest::factory(); if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY)) printf("[002] Unable to switch to emulated prepared statements, test will fail\n"); - // TODO - that's PDO - you can prepare empty statements! - prepex(3, $db, '', - array(), array('execute' => array('sqlstate' => '42000'))); + try { + prepex(3, $db, '', [], ['execute' => ['sqlstate' => '42000']]); + } catch (\ValueError $e) { + echo $e->getMessage(), \PHP_EOL; + } // lets be fair and do the most simple SELECT first $stmt = prepex(4, $db, 'SELECT 1 as "one"'); @@ -328,6 +330,7 @@ $db->exec('DROP TABLE IF EXISTS test'); PDO's PS parser has some problems with invalid SQL and crashes from time to time (check with valgrind...) --EXPECT-- +PDO::prepare(): Argument #1 ($statement) cannot be empty array(1) { ["one"]=> string(1) "1" diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_native.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_native.phpt index 5ea3e94c1e..839fc43c15 100644 --- a/ext/pdo_mysql/tests/pdo_mysql_prepare_native.phpt +++ b/ext/pdo_mysql/tests/pdo_mysql_prepare_native.phpt @@ -99,9 +99,11 @@ $db = MySQLPDOTest::factory(); if (0 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY)) printf("[002] Unable to turn off emulated prepared statements\n"); - // TODO - that's PDO - you can prepare empty statements! - prepex(3, $db, '', - array(), array('prepare' => array('sqlstate' => '42000'))); + try { + prepex(3, $db, '', [], ['prepare' => ['sqlstate' => '42000']]); + } catch (\ValueError $e) { + echo $e->getMessage(), \PHP_EOL; + } // lets be fair and do the most simple SELECT first $stmt = prepex(4, $db, 'SELECT 1 as "one"'); @@ -342,6 +344,7 @@ $db = MySQLPDOTest::factory(); $db->exec('DROP TABLE IF EXISTS test'); ?> --EXPECT-- +PDO::prepare(): Argument #1 ($statement) cannot be empty array(1) { [0]=> array(1) { diff --git a/ext/pdo_mysql/tests/pdo_mysql_stmt_getcolumnmeta.phpt b/ext/pdo_mysql/tests/pdo_mysql_stmt_getcolumnmeta.phpt index 44f0a0ebb1..b5b0275f04 100644 --- a/ext/pdo_mysql/tests/pdo_mysql_stmt_getcolumnmeta.phpt +++ b/ext/pdo_mysql/tests/pdo_mysql_stmt_getcolumnmeta.phpt @@ -32,8 +32,11 @@ try { $stmt->execute(); // invalid offset - if (false !== ($tmp = @$stmt->getColumnMeta(-1))) - printf("[004] Expecting false got %s\n", var_export($tmp, true)); + try { + $stmt->getColumnMeta(-1); + } catch (\ValueError $e) { + echo $e->getMessage(), \PHP_EOL; + } $emulated = $stmt->getColumnMeta(0); @@ -299,5 +302,6 @@ $db->exec('DROP TABLE IF EXISTS test'); print "done!"; ?> --EXPECT-- +PDOStatement::getColumnMeta(): Argument #1 ($column) must be greater than or equal to 0 Testing native PS... done! diff --git a/ext/pdo_sqlite/tests/bug_44159_sqlite_version.phpt b/ext/pdo_sqlite/tests/bug_44159_sqlite_version.phpt new file mode 100644 index 0000000000..fc30f1d21c --- /dev/null +++ b/ext/pdo_sqlite/tests/bug_44159_sqlite_version.phpt @@ -0,0 +1,22 @@ +--TEST-- +PDO Common: Bug #44159: SQLite variant +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + +var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, NULL)); +var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, 1)); +var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, 'nonsense')); + +@unlink(__DIR__."/foo.db"); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/ext/pdo_sqlite/tests/pdo_fetch_func_001.phpt b/ext/pdo_sqlite/tests/pdo_fetch_func_001.phpt index 4600b7935b..814a01a647 100644 --- a/ext/pdo_sqlite/tests/pdo_fetch_func_001.phpt +++ b/ext/pdo_sqlite/tests/pdo_fetch_func_001.phpt @@ -20,20 +20,40 @@ $st->fetchAll(PDO::FETCH_FUNC, function($x, $y) use ($st) { var_dump($st); print $st = $db->query('SELECT name FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, 'strtoupper')); -$st = $db->query('SELECT * FROM testing'); -var_dump($st->fetchAll(PDO::FETCH_FUNC, 'nothing')); +try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, 'nothing')); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} -$st = $db->query('SELECT * FROM testing'); -var_dump($st->fetchAll(PDO::FETCH_FUNC, '')); +try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, '')); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} -$st = $db->query('SELECT * FROM testing'); -var_dump($st->fetchAll(PDO::FETCH_FUNC, NULL)); +try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, NULL)); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} -$st = $db->query('SELECT * FROM testing'); -var_dump($st->fetchAll(PDO::FETCH_FUNC, 1)); +try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, 1)); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} -$st = $db->query('SELECT * FROM testing'); -var_dump($st->fetchAll(PDO::FETCH_FUNC, array('self', 'foo'))); +try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('self', 'foo'))); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} class foo { public function method($x) { @@ -64,14 +84,26 @@ new bar($db); $st = $db->query('SELECT * FROM testing'); var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test'))); -$st = $db->query('SELECT * FROM testing'); -var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test2'))); +try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test2'))); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} -$st = $db->query('SELECT * FROM testing'); -var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test3'))); +try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test3'))); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} -$st = $db->query('SELECT * FROM testing'); -var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'inexistent'))); +try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'inexistent'))); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} ?> --EXPECTF-- @@ -91,31 +123,11 @@ array(2) { [1]=> string(0) "" } - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: function "nothing" not found or invalid function name in %s on line %d - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: function "" not found or invalid function name in %s on line %d - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: no array or string given in %s on line %d - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: no array or string given in %s on line %d - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: cannot access "self" when no class scope is active in %s on line %d - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) +function "nothing" not found or invalid function name +function "" not found or invalid function name +PDOStatement::fetchAll(): Argument #2 must be a callable, null given +no array or string given +cannot access "self" when no class scope is active array(2) { [0]=> string(9) "--- 1 ---" @@ -128,18 +140,6 @@ array(2) { [1]=> string(4) "2---" } - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: non-static method bar::test2() cannot be called statically in %s on line %d - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: non-static method bar::test3() cannot be called statically in %s on line %d - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: class bar does not have a method "inexistent" in %s on line %d - -Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d -bool(false) +non-static method bar::test2() cannot be called statically +non-static method bar::test3() cannot be called statically +class bar does not have a method "inexistent" -- 2.40.0