From 6e0d8dd06f4e5fe63d9c8c28ced70b79c62a7b28 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Fri, 7 Jan 2005 05:23:10 +0000 Subject: [PATCH] implement SQLSTATE style error codes. Allow drivers to add methods to dbh and stmt objects (note that we can't use a class, because the use only sees the PDO class). Clarify the api slightly: PDO::exec() is used for one-shot queries that don't return rows PDO::query() is a convenience function for returning a rowset without having to go through the steps of preparing and executing. --- ext/pdo/config.m4 | 2 +- ext/pdo/config.w32 | 2 +- ext/pdo/pdo.c | 10 ++- ext/pdo/pdo.php | 53 ++++++++--- ext/pdo/pdo_dbh.c | 159 +++++++++++++++++++++----------- ext/pdo/pdo_sqlstate.c | 190 +++++++++++++++++++++++++++++++++++++++ ext/pdo/pdo_stmt.c | 27 ++++-- ext/pdo/php_pdo_driver.h | 70 +++++++++++---- ext/pdo/php_pdo_int.h | 9 +- 9 files changed, 430 insertions(+), 92 deletions(-) create mode 100644 ext/pdo/pdo_sqlstate.c diff --git a/ext/pdo/config.m4 b/ext/pdo/config.m4 index 60e6039550..41bf1bf4ad 100755 --- a/ext/pdo/config.m4 +++ b/ext/pdo/config.m4 @@ -5,6 +5,6 @@ PHP_ARG_ENABLE(pdo, whether to enable PDO support, [ --enable-pdo Enable PHP Data Objects support]) if test "$PHP_PDO" != "no"; then - PHP_NEW_EXTENSION(pdo, pdo.c pdo_dbh.c pdo_stmt.c pdo_sql_parser.c, $ext_shared) + PHP_NEW_EXTENSION(pdo, pdo.c pdo_dbh.c pdo_stmt.c pdo_sql_parser.c pdo_sqlstate.c, $ext_shared) PHP_ADD_MAKEFILE_FRAGMENT fi diff --git a/ext/pdo/config.w32 b/ext/pdo/config.w32 index 112c7d9fce..e678c4af85 100755 --- a/ext/pdo/config.w32 +++ b/ext/pdo/config.w32 @@ -4,6 +4,6 @@ ARG_ENABLE("pdo", "Enable PHP Data Objects support", "no"); if (PHP_PDO != "no") { - EXTENSION('pdo', 'pdo.c pdo_dbh.c pdo_stmt.c pdo_sql_parser.c'); + EXTENSION('pdo', 'pdo.c pdo_dbh.c pdo_stmt.c pdo_sql_parser.c pdo_sqlstate.c'); } diff --git a/ext/pdo/pdo.c b/ext/pdo/pdo.c index ea03f61cf3..2e3a24fbc1 100755 --- a/ext/pdo/pdo.c +++ b/ext/pdo/pdo.c @@ -208,6 +208,10 @@ PHP_MINIT_FUNCTION(pdo) ZEND_INIT_MODULE_GLOBALS(pdo, php_pdo_init_globals, NULL); REGISTER_INI_ENTRIES(); + if (FAILURE == pdo_sqlstate_init_error_table()) { + return FAILURE; + } + zend_hash_init(&pdo_driver_hash, 0, NULL, NULL, 1); le_ppdo = zend_register_list_destructors_ex(NULL, php_pdo_pdbh_dtor, @@ -252,7 +256,9 @@ PHP_MINIT_FUNCTION(pdo) REGISTER_LONG_CONSTANT("PDO_CASE_LOWER", (long)PDO_CASE_LOWER, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PDO_CASE_UPPER", (long)PDO_CASE_UPPER, CONST_CS|CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("PDO_ERR_NONE", (long)PDO_ERR_NONE, CONST_CS|CONST_PERSISTENT); + REGISTER_STRING_CONSTANT("PDO_ERR_NONE", PDO_ERR_NONE, CONST_CS|CONST_PERSISTENT); + +#if 0 REGISTER_LONG_CONSTANT("PDO_ERR_CANT_MAP", (long)PDO_ERR_CANT_MAP, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PDO_ERR_SYNTAX", (long)PDO_ERR_SYNTAX, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PDO_ERR_CONSTRAINT", (long)PDO_ERR_CONSTRAINT, CONST_CS|CONST_PERSISTENT); @@ -263,6 +269,7 @@ PHP_MINIT_FUNCTION(pdo) REGISTER_LONG_CONSTANT("PDO_ERR_TRUNCATED", (long)PDO_ERR_TRUNCATED, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PDO_ERR_DISCONNECTED", (long)PDO_ERR_DISCONNECTED, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PDO_ERR_NO_PERM", (long)PDO_ERR_NO_PERM, CONST_CS|CONST_PERSISTENT); +#endif INIT_CLASS_ENTRY(ce, "PDOException", NULL); pdo_exception_ce = zend_register_internal_class_ex(&ce, zend_exception_get_default(), NULL TSRMLS_CC); @@ -290,6 +297,7 @@ PHP_MSHUTDOWN_FUNCTION(pdo) { UNREGISTER_INI_ENTRIES(); zend_hash_destroy(&pdo_driver_hash); + pdo_sqlstate_fini_error_table(); return SUCCESS; } /* }}} */ diff --git a/ext/pdo/pdo.php b/ext/pdo/pdo.php index 78300d82b5..3a9a0fd2cd 100755 --- a/ext/pdo/pdo.php +++ b/ext/pdo/pdo.php @@ -1,20 +1,12 @@ 0)); -$stmt = $x->prepare("select NAME, VALUE from test where value like ?"); - -$the_name = 'bar%'; -$stmt->execute(array($the_name)) or die("failed to execute!"); -$stmt->bindColumn('VALUE', $value); - -while ($row = $stmt->fetch()) { - echo "name=$row[NAME] value=$row[VALUE]\n"; - echo "value is $value\n"; - echo "\n"; -} +$x = new PDO("sqlite::memory:"); -echo "Let's try an update\n"; +$x->query("create table test(name string, value string)"); +debug_zval_dump($x); $stmt = $x->prepare("INSERT INTO test (NAME, VALUE) VALUES (:name, :value)"); @@ -30,6 +22,41 @@ for ($i = 0; $i < 4; $i++) { } } +$stmt = null; + +echo "DEFAULT:\n"; +foreach ($x->queryAndIterate("select NAME, VALUE from test") as $row) { + print_r($row); +} + +echo "OBJ:\n"; + +class Foo { + public $NAME = "Don't change me"; +} + +$foo = new foo; + +foreach ($x->queryAndIterate("select NAME, VALUE from test", PDO_FETCH_COLUMN, 1) as $row) { + debug_zval_dump($row); +} + +echo "Done\n"; +exit; + +$stmt = $x->prepare("select NAME, VALUE from test where value like ?"); +$the_name = 'bar%'; +$stmt->execute(array($the_name)) or die("failed to execute!"); +$stmt->bindColumn('VALUE', $value); + +while ($row = $stmt->fetch()) { + echo "name=$row[NAME] value=$row[VALUE]\n"; + echo "value is $value\n"; + echo "\n"; +} + +echo "Let's try an update\n"; + echo "All done\n"; ?> diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index 9f2099954c..0f12871144 100755 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -34,10 +34,11 @@ #include "php_pdo_int.h" #include "zend_exceptions.h" #include "zend_object_handlers.h" +#include "zend_hash.h" void pdo_handle_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt TSRMLS_DC) { - enum pdo_error_type *pdo_err = &dbh->error_code; + pdo_error_type *pdo_err = &dbh->error_code; const char *msg = "<>"; char *supp = NULL; long native_code = 0; @@ -52,19 +53,10 @@ void pdo_handle_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt TSRMLS_DC) pdo_err = &stmt->error_code; } - switch (*pdo_err) { - case PDO_ERR_NONE: msg = "No error"; break; - case PDO_ERR_CANT_MAP: msg = "Consult errorInfo() for more details"; break; - case PDO_ERR_SYNTAX: msg = "Syntax Error"; break; - case PDO_ERR_CONSTRAINT:msg = "Constraint violation"; break; - case PDO_ERR_NOT_FOUND: msg = "Not found"; break; - case PDO_ERR_ALREADY_EXISTS: msg = "Already exists"; break; - case PDO_ERR_NOT_IMPLEMENTED: msg = "Not Implemented"; break; - case PDO_ERR_MISMATCH: msg = "Mismatch"; break; - case PDO_ERR_TRUNCATED: msg = "Truncated"; break; - case PDO_ERR_DISCONNECTED: msg = "Disconnected"; break; - case PDO_ERR_NO_PERM: msg = "No permission"; break; - default: msg = "<>"; + /* hash sqlstate to error messages */ + msg = pdo_sqlstate_state_to_description(*pdo_err); + if (!msg) { + msg = "<>"; } if (dbh->methods->fetch_err) { @@ -72,7 +64,7 @@ void pdo_handle_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt TSRMLS_DC) MAKE_STD_ZVAL(info); array_init(info); - add_next_index_long(info, *pdo_err); + add_next_index_string(info, *pdo_err, 1); if (dbh->methods->fetch_err(dbh, stmt, info TSRMLS_CC)) { zval **item; @@ -87,12 +79,10 @@ void pdo_handle_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt TSRMLS_DC) } } - if (supp && *pdo_err == PDO_ERR_CANT_MAP) { - spprintf(&message, 0, "%ld %s", native_code, supp); - } else if (supp) { - spprintf(&message, 0, "%s: %ld %s", msg, native_code, supp); + if (supp) { + spprintf(&message, 0, "SQLSTATE[%s]: %s: %ld %s", *pdo_err, msg, native_code, supp); } else { - spprintf(&message, 0, "%s", msg); + spprintf(&message, 0, "SQLSTATE[%s]: %s", *pdo_err, msg); } if (dbh->error_mode == PDO_ERRMODE_WARNING) { @@ -109,7 +99,7 @@ void pdo_handle_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt TSRMLS_DC) object_init_ex(ex, pdo_ex); zend_update_property_string(def_ex, ex, "message", sizeof("message")-1, message TSRMLS_CC); - zend_update_property_long(def_ex, ex, "code", sizeof("code")-1, *pdo_err TSRMLS_CC); + zend_update_property_string(def_ex, ex, "code", sizeof("code")-1, *pdo_err TSRMLS_CC); if (info) { zend_update_property(pdo_ex, ex, "errorInfo", sizeof("errorInfo")-1, info TSRMLS_CC); @@ -171,7 +161,7 @@ static PHP_FUNCTION(dbh_constructor) snprintf(alt_dsn, sizeof(alt_dsn), "pdo.dsn.%s", data_source); if (FAILURE == cfg_get_string(alt_dsn, &ini_dsn)) { - zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_SYNTAX TSRMLS_CC, "invalid data source name"); + zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "invalid data source name"); ZVAL_NULL(object); return; } @@ -180,7 +170,7 @@ static PHP_FUNCTION(dbh_constructor) colon = strchr(data_source, ':'); if (!colon) { - zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_SYNTAX TSRMLS_CC, "invalid data source name (via INI: %s)", alt_dsn); + zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "invalid data source name (via INI: %s)", alt_dsn); ZVAL_NULL(object); return; } @@ -190,13 +180,13 @@ static PHP_FUNCTION(dbh_constructor) /* the specified URI holds connection details */ data_source = dsn_from_uri(data_source, alt_dsn, sizeof(alt_dsn) TSRMLS_CC); if (!data_source) { - zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_SYNTAX TSRMLS_CC, "invalid data source URI"); + zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "invalid data source URI"); ZVAL_NULL(object); return; } colon = strchr(data_source, ':'); if (!colon) { - zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_SYNTAX TSRMLS_CC, "invalid data source name (via uri)"); + zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "invalid data source name (via URI)"); ZVAL_NULL(object); return; } @@ -207,7 +197,7 @@ static PHP_FUNCTION(dbh_constructor) if (!driver) { /* NB: don't want to include the data_source in the error message as * it might contain a password */ - zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_NOT_FOUND TSRMLS_CC, "could not find driver"); + zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "could not find driver"); ZVAL_NULL(object); return; } @@ -249,7 +239,6 @@ static PHP_FUNCTION(dbh_constructor) /* nope... need to kill it */ pdbh = NULL; } - } } @@ -380,14 +369,14 @@ static PHP_METHOD(PDO, beginTransaction) pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC); if (dbh->in_txn) { - zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_ALREADY_EXISTS TSRMLS_CC, "There is already an active transaction"); + zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "There is already an active transaction"); RETURN_FALSE; } if (!dbh->methods->begin) { /* TODO: this should be an exception; see the auto-commit mode * comments below */ - zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_NOT_IMPLEMENTED TSRMLS_CC, "This driver doesn't support transactions"); + zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "This driver doesn't support transactions"); RETURN_FALSE; } @@ -408,7 +397,7 @@ static PHP_METHOD(PDO, commit) pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC); if (!dbh->in_txn) { - zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_NONE TSRMLS_CC, "There is no active transaction"); + zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "There is no active transaction"); RETURN_FALSE; } @@ -429,7 +418,7 @@ static PHP_METHOD(PDO, rollBack) pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC); if (!dbh->in_txn) { - zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_NOT_FOUND TSRMLS_CC, "There is no active transaction"); + zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "There is no active transaction"); RETURN_FALSE; } @@ -465,7 +454,7 @@ static PHP_METHOD(PDO, setAttribute) dbh->error_mode = Z_LVAL_P(value); RETURN_TRUE; default: - zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_SYNTAX TSRMLS_CC, "Error mode %d is invalid", Z_LVAL_P(value)); + zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "Error mode %d is invalid", Z_LVAL_P(value)); } RETURN_FALSE; @@ -478,7 +467,7 @@ static PHP_METHOD(PDO, setAttribute) dbh->desired_case = Z_LVAL_P(value); RETURN_TRUE; default: - zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_SYNTAX TSRMLS_CC, "Case folding mode %d is invalid", Z_LVAL_P(value)); + zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "Case folding mode %d is invalid", Z_LVAL_P(value)); } RETURN_FALSE; @@ -502,7 +491,7 @@ static PHP_METHOD(PDO, setAttribute) fail: if (attr == PDO_ATTR_AUTOCOMMIT) { - zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_NONE TSRMLS_CC, "The auto-commit mode cannot be changed for this driver"); + zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "The auto-commit mode cannot be changed for this driver"); } else if (!dbh->methods->set_attribute) { /* XXX: do something better here */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "This driver doesn't support setting attributes"); @@ -546,9 +535,9 @@ static PHP_METHOD(PDO, getAttribute) } /* }}} */ -/* {{{ proto long PDO::query(string query) +/* {{{ proto long PDO::exec(string query) Execute a query that does not return a row set, returning the number of affected rows */ -static PHP_METHOD(PDO, query) +static PHP_METHOD(PDO, exec) { pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC); char *statement; @@ -571,12 +560,6 @@ static PHP_METHOD(PDO, query) RETURN_LONG(ret); } } - -static PHP_METHOD(PDO, exec) /* FIXME: nuke on release */ -{ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "This is name deprecated, use PDO::query instead"); - PHP_FN(PDO_query)(INTERNAL_FUNCTION_PARAM_PASSTHRU); -} /* }}} */ @@ -599,7 +582,7 @@ static PHP_METHOD(PDO, lastInsertId) } /* }}} */ -/* {{{ proto int PDO::errorCode() +/* {{{ proto string PDO::errorCode() Fetch the error code associated with the last operation on the database handle */ static PHP_METHOD(PDO, errorCode) { @@ -609,7 +592,7 @@ static PHP_METHOD(PDO, errorCode) RETURN_FALSE; } - RETURN_LONG(dbh->error_code); + RETURN_STRING(dbh->error_code, 1); } /* }}} */ @@ -624,7 +607,7 @@ static PHP_METHOD(PDO, errorInfo) } array_init(return_value); - add_next_index_long(return_value, dbh->error_code); + add_next_index_string(return_value, dbh->error_code, 1); if (dbh->methods->fetch_err) { dbh->methods->fetch_err(dbh, NULL, return_value TSRMLS_CC); @@ -632,9 +615,9 @@ static PHP_METHOD(PDO, errorInfo) } /* }}} */ -/* {{{ proto object PDO::queryAndIterate(string sql [, PDOStatement::setFetchMode() args]) +/* {{{ proto object PDO::query(string sql [, PDOStatement::setFetchMode() args]) Prepare and execute $sql; returns the statement object for iteration */ -static PHP_METHOD(PDO, queryAndIterate) +static PHP_METHOD(PDO, query) { pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC); pdo_stmt_t *stmt; @@ -710,14 +693,12 @@ function_entry pdo_dbh_functions[] = { PHP_ME(PDO, commit, NULL, ZEND_ACC_PUBLIC) PHP_ME(PDO, rollBack, NULL, ZEND_ACC_PUBLIC) PHP_ME(PDO, setAttribute, NULL, ZEND_ACC_PUBLIC) - PHP_ME(PDO, exec, NULL, ZEND_ACC_PUBLIC) /* FIXME: nuke on stable release */ + PHP_ME(PDO, exec, NULL, ZEND_ACC_PUBLIC) PHP_ME(PDO, query, NULL, ZEND_ACC_PUBLIC) PHP_ME(PDO, lastInsertId, NULL, ZEND_ACC_PUBLIC) PHP_ME(PDO, errorCode, NULL, ZEND_ACC_PUBLIC) PHP_ME(PDO, errorInfo, NULL, ZEND_ACC_PUBLIC) PHP_ME(PDO, getAttribute, NULL, ZEND_ACC_PUBLIC) - PHP_ME(PDO, queryAndIterate,NULL, ZEND_ACC_PUBLIC) - {NULL, NULL, NULL} }; @@ -777,6 +758,64 @@ static HashTable *dbh_get_properties(zval *object TSRMLS_DC) return NULL; } +int pdo_hash_methods(pdo_dbh_t *dbh, int kind TSRMLS_DC) +{ + function_entry *funcs; + zend_function func; + zend_internal_function *ifunc = (zend_internal_function*)&func; + int namelen; + char *lc_name; + + if (!dbh->methods->get_driver_methods) { + return 0; + } + funcs = dbh->methods->get_driver_methods(dbh, + PDO_DBH_DRIVER_METHOD_KIND_DBH TSRMLS_DC); + if (!funcs) { + return 0; + } + + dbh->cls_methods[kind] = pemalloc(sizeof(HashTable), dbh->is_persistent); + zend_hash_init_ex(dbh->cls_methods[kind], 8, NULL, NULL, dbh->is_persistent, 0); + + while (funcs->fname) { + ifunc->handler = funcs->handler; + ifunc->function_name = funcs->fname; + ifunc->scope = dbh->ce; + ifunc->prototype = NULL; + if (funcs->arg_info) { + ifunc->arg_info = funcs->arg_info + 1; + ifunc->num_args = funcs->num_args; + if (funcs->arg_info[0].required_num_args == -1) { + ifunc->required_num_args = funcs->num_args; + } else { + ifunc->required_num_args = funcs->arg_info[0].required_num_args; + } + ifunc->pass_rest_by_reference = funcs->arg_info[0].pass_by_reference; + ifunc->return_reference = funcs->arg_info[0].return_reference; + } else { + ifunc->arg_info = NULL; + ifunc->num_args = 0; + ifunc->required_num_args = 0; + ifunc->pass_rest_by_reference = 0; + ifunc->return_reference = 0; + } + if (funcs->flags) { + ifunc->fn_flags = funcs->flags; + } else { + ifunc->fn_flags = ZEND_ACC_PUBLIC; + } + namelen = strlen(funcs->fname); + lc_name = emalloc(namelen+1); + zend_str_tolower_copy(lc_name, funcs->fname, namelen); + zend_hash_add(dbh->cls_methods[kind], lc_name, namelen+1, &func, sizeof(func), NULL); + efree(lc_name); + funcs++; + } + + return 1; +} + static union _zend_function *dbh_method_get( #if PHP_API_VERSION >= 20041225 zval **object_pp, @@ -785,7 +824,7 @@ static union _zend_function *dbh_method_get( #endif char *method_name, int method_len TSRMLS_DC) { - zend_function *fbc; + zend_function *fbc = NULL; char *lc_method_name; #if PHP_API_VERSION >= 20041225 zval *object = *object_pp; @@ -796,10 +835,24 @@ static union _zend_function *dbh_method_get( zend_str_tolower_copy(lc_method_name, method_name, method_len); if (zend_hash_find(&dbh->ce->function_table, lc_method_name, method_len+1, (void**)&fbc) == FAILURE) { - efree(lc_method_name); - return NULL; + + /* not a pre-defined method, nor a user-defined method; check + * the driver specific methods */ + if (!dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH]) { + if (!pdo_hash_methods(dbh, PDO_DBH_DRIVER_METHOD_KIND_DBH TSRMLS_CC)) { + goto out; + } + } + + if (zend_hash_find(dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH], + lc_method_name, method_len+1, (void**)&fbc) == FAILURE) { + fbc = NULL; + goto out; + } + /* got it */ } +out: efree(lc_method_name); return fbc; } diff --git a/ext/pdo/pdo_sqlstate.c b/ext/pdo/pdo_sqlstate.c new file mode 100644 index 0000000000..d927d5202f --- /dev/null +++ b/ext/pdo/pdo_sqlstate.c @@ -0,0 +1,190 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2004 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 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_0.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. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_pdo.h" +#include "php_pdo_driver.h" + +struct pdo_sqlstate_info { + char state[6]; + const char *desc; +}; + +static HashTable err_hash; + +static struct pdo_sqlstate_info err_initializer[] = { + { "00000", "No error" }, + { "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" }, + { "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" }, + { "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" }, + { "08001", "Client unable to establish connection" }, + { "08002", "Connection name in use" }, + { "08003", "Connection does not exist" }, + { "08004", "Server rejected the connection" }, + { "08007", "Connection failure during transaction" }, + { "08S01", "Communication link failure" }, + { "21S01", "Insert value list does not match column list" }, + { "21S02", "Degree of derived table does not match column list" }, + { "22001", "String data, right truncated" }, + { "22002", "Indicator variable required but not supplied" }, + { "22003", "Numeric value out of range" }, + { "22007", "Invalid datetime format" }, + { "22008", "Datetime field overflow" }, + { "22012", "Division by zero" }, + { "22015", "Interval field overflow" }, + { "22018", "Invalid character value for cast specification" }, + { "22019", "Invalid escape character" }, + { "22025", "Invalid escape sequence" }, + { "22026", "String data, length mismatch" }, + { "23000", "Integrity constraint violation" }, + { "24000", "Invalid cursor state" }, + { "25000", "Invalid transaction state" }, + { "25S01", "Transaction state" }, + { "25S02", "Transaction is still active" }, + { "25S03", "Transaction is rolled back" }, + { "28000", "Invalid authorization specification" }, + { "34000", "Invalid cursor name" }, + { "3C000", "Duplicate cursor name" }, + { "3D000", "Invalid catalog name" }, + { "3F000", "Invalid schema name" }, + { "40001", "Serialization failure" }, + { "40003", "Statement completion unknown" }, + { "42000", "Syntax error or access violation" }, + { "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" }, + { "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" } +}; + +void pdo_sqlstate_fini_error_table(void) +{ + zend_hash_destroy(&err_hash); +} + +int pdo_sqlstate_init_error_table(void) +{ + int i; + struct pdo_sqlstate_info *info; + + if (FAILURE == zend_hash_init(&err_hash, + sizeof(err_initializer)/sizeof(err_initializer[0]), NULL, NULL, 1)) { + return FAILURE; + } + + for (i = 0; i < sizeof(err_initializer)/sizeof(err_initializer[0]); i++) { + info = &err_initializer[i]; + + zend_hash_add(&err_hash, info->state, sizeof(info->state), &info, sizeof(info), NULL); + } + + return SUCCESS; +} + +const char *pdo_sqlstate_state_to_description(char *state) +{ + struct pdo_sqlstate_info **info; + if (SUCCESS == zend_hash_find(&err_hash, state, sizeof(err_initializer[0].state), + (void**)&info)) { + return (*info)->desc; + } + return NULL; +} + diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index 7383125d3c..1e5db6d84f 100755 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -669,7 +669,7 @@ static PHP_METHOD(PDOStatement, errorCode) RETURN_FALSE; } - RETURN_LONG(stmt->error_code); + RETURN_STRING(stmt->error_code, 1); } /* }}} */ @@ -684,7 +684,7 @@ static PHP_METHOD(PDOStatement, errorInfo) } array_init(return_value); - add_next_index_long(return_value, stmt->error_code); + add_next_index_string(return_value, stmt->error_code, 1); if (stmt->dbh->methods->fetch_err) { stmt->dbh->methods->fetch_err(stmt->dbh, stmt, return_value TSRMLS_CC); @@ -1024,7 +1024,7 @@ static union _zend_function *dbstmt_method_get( #endif char *method_name, int method_len TSRMLS_DC) { - zend_function *fbc; + zend_function *fbc = NULL; char *lc_method_name; #if PHP_API_VERSION >= 20041225 zval *object = *object_pp; @@ -1033,11 +1033,26 @@ static union _zend_function *dbstmt_method_get( lc_method_name = emalloc(method_len + 1); zend_str_tolower_copy(lc_method_name, method_name, method_len); - if (zend_hash_find(&pdo_dbstmt_ce->function_table, lc_method_name, method_len+1, (void**)&fbc) == FAILURE) { - efree(lc_method_name); - return NULL; + if (zend_hash_find(&pdo_dbstmt_ce->function_table, lc_method_name, + method_len+1, (void**)&fbc) == FAILURE) { + pdo_stmt_t *stmt = (pdo_stmt_t*)zend_object_store_get_object(object TSRMLS_CC); + /* not a pre-defined method, nor a user-defined method; check + * the driver specific methods */ + if (!stmt->dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_STMT]) { + if (!pdo_hash_methods(stmt->dbh, PDO_DBH_DRIVER_METHOD_KIND_STMT TSRMLS_CC)) { + goto out; + } + } + + if (zend_hash_find(stmt->dbh->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH], + lc_method_name, method_len+1, (void**)&fbc) == FAILURE) { + fbc = NULL; + goto out; + } + /* got it */ } +out: efree(lc_method_name); return fbc; } diff --git a/ext/pdo/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h index 4bc96d51b3..bbfa7d36bf 100755 --- a/ext/pdo/php_pdo_driver.h +++ b/ext/pdo/php_pdo_driver.h @@ -35,7 +35,7 @@ struct pdo_bound_param_data; # define FALSE 0 #endif -#define PDO_DRIVER_API 20041027 +#define PDO_DRIVER_API 20050105 enum pdo_param_type { PDO_PARAM_NULL, @@ -86,22 +86,45 @@ enum pdo_cursor_type { PDO_CURSOR_SCROLL, /* scrollable cursor */ }; +/* SQL-92 SQLSTATE error codes. + +The character string value returned for an SQLSTATE consists of a two-character +class value followed by a three-character subclass value. A class value of 01 +indicates a warning and is accompanied by a return code of +SQL_SUCCESS_WITH_INFO. + +Class values other than '01', except for the class 'IM', +indicate an error and are accompanied by a return code of SQL_ERROR. The class +'IM' is specific to warnings and errors that derive from the implementation of +ODBC itself. + +The subclass value '000' in any class indicates that there is no +subclass for that SQLSTATE. The assignment of class and subclass values is +defined by SQL-92. +*/ + +typedef char pdo_error_type[6]; /* SQLSTATE */ + + +#define PDO_ERR_NONE "00000" +#if 0 /* generic error code values. * Don't want to go overboard with these. * */ -enum pdo_error_type { - PDO_ERR_NONE, /* no error condition */ - PDO_ERR_CANT_MAP, /* no way to map native error to the generic codes; consult the native error for more info */ - PDO_ERR_SYNTAX, - PDO_ERR_CONSTRAINT, - PDO_ERR_NOT_FOUND, - PDO_ERR_ALREADY_EXISTS, - PDO_ERR_NOT_IMPLEMENTED, - PDO_ERR_MISMATCH, - PDO_ERR_TRUNCATED, - PDO_ERR_DISCONNECTED, - PDO_ERR_NO_PERM, +#define PDO_ERR_SYNTAX "42000" +#define PDO_ERR_CONSTRAINT "23000" +#define PDO_ERR_NOT_FOUND "" +#define PDO_ERR_ALREADY_EXISTS, +#define PDO_ERR_NOT_IMPLEMENTED, +#define PDO_ERR_MISMATCH, +#define PDO_ERR_TRUNCATED, +#define PDO_ERR_DISCONNECTED, +#define PDO_ERR_NO_PERM, + + PDO_ERR_CANT_MAP, /* no way to map native error to the generic + * codes; consult the native error for more info */ }; +#endif enum pdo_error_mode { PDO_ERRMODE_SILENT, /* just set error codes */ @@ -189,6 +212,20 @@ typedef int (*pdo_dbh_get_attr_func)(pdo_dbh_t *dbh, long attr, zval *val TSRMLS * You may set this handler to NULL, which is equivalent to returning SUCCESS. */ typedef int (*pdo_dbh_check_liveness_func)(pdo_dbh_t *dbh TSRMLS_DC); +/* for adding methods to the dbh or stmt objects +pointer to a list of driver specific functions. The convention is +to prefix the function names using the PDO driver name; this will +reduce the chance of collisions with future functionality in the +PDO class or in user code (they can extend the PDO object). +*/ +enum { + PDO_DBH_DRIVER_METHOD_KIND_DBH = 0, + PDO_DBH_DRIVER_METHOD_KIND_STMT, + PDO_DBH_DRIVER_METHOD_KIND__MAX +}; + +typedef function_entry *(*pdo_dbh_get_driver_methods_func)(pdo_dbh_t *dbh, int kind TSRMLS_DC); + struct pdo_dbh_methods { pdo_dbh_close_func closer; pdo_dbh_prepare_func preparer; @@ -202,6 +239,7 @@ struct pdo_dbh_methods { pdo_dbh_fetch_error_func fetch_err; pdo_dbh_get_attr_func get_attribute; pdo_dbh_check_liveness_func check_liveness; + pdo_dbh_get_driver_methods_func get_driver_methods; }; /* }}} */ @@ -352,7 +390,7 @@ struct _pdo_dbh_t { unsigned long data_source_len; /* the global error code. */ - enum pdo_error_type error_code; + pdo_error_type error_code; enum pdo_error_mode error_mode; @@ -363,6 +401,8 @@ struct _pdo_dbh_t { int persistent_id_len; unsigned int refcount; + /* driver specific "class" methods for the dbh and stmt */ + HashTable *cls_methods[PDO_DBH_DRIVER_METHOD_KIND__MAX-1]; }; /* describes a column */ @@ -433,7 +473,7 @@ struct _pdo_stmt_t { int active_query_stringlen; /* the cursor specific error code. */ - enum pdo_error_type error_code; + pdo_error_type error_code; /* for lazy fetches, we always return the same lazy object handle. * Let's keep it here. */ diff --git a/ext/pdo/php_pdo_int.h b/ext/pdo/php_pdo_int.h index 51c0fef392..3db761e6c6 100755 --- a/ext/pdo/php_pdo_int.h +++ b/ext/pdo/php_pdo_int.h @@ -51,11 +51,16 @@ extern pdo_driver_t *pdo_find_driver(const char *name, int namelen); extern void pdo_handle_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt TSRMLS_DC); -#define PDO_DBH_CLEAR_ERR() dbh->error_code = PDO_ERR_NONE -#define PDO_STMT_CLEAR_ERR() stmt->error_code = PDO_ERR_NONE +#define PDO_DBH_CLEAR_ERR() strcpy(dbh->error_code, PDO_ERR_NONE) +#define PDO_STMT_CLEAR_ERR() strcpy(stmt->error_code, PDO_ERR_NONE) #define PDO_HANDLE_DBH_ERR() if (dbh->error_code) { pdo_handle_error(dbh, NULL TSRMLS_CC); } #define PDO_HANDLE_STMT_ERR() if (stmt->error_code) { pdo_handle_error(stmt->dbh, stmt TSRMLS_CC); } +int pdo_sqlstate_init_error_table(void); +void pdo_sqlstate_fini_error_table(void); +const char *pdo_sqlstate_state_to_description(char *state); +int pdo_hash_methods(pdo_dbh_t *dbh, int kind TSRMLS_DC); + /* * Local variables: -- 2.50.1