]> granicus.if.org Git - php/commitdiff
implement SQLSTATE style error codes.
authorWez Furlong <wez@php.net>
Fri, 7 Jan 2005 05:23:10 +0000 (05:23 +0000)
committerWez Furlong <wez@php.net>
Fri, 7 Jan 2005 05:23:10 +0000 (05:23 +0000)
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
ext/pdo/config.w32
ext/pdo/pdo.c
ext/pdo/pdo.php
ext/pdo/pdo_dbh.c
ext/pdo/pdo_sqlstate.c [new file with mode: 0644]
ext/pdo/pdo_stmt.c
ext/pdo/php_pdo_driver.h
ext/pdo/php_pdo_int.h

index 60e603955056975cbcbe8e09f570331166a11801..41bf1bf4ad7c015d3346792cb579b7924ce52244 100755 (executable)
@@ -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
index 112c7d9fcef3f538a45bdefe21536c50be1fb952..e678c4af85ea7db98f20417d69569864007ca6df 100755 (executable)
@@ -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');
 }
 
index ea03f61cf35fa821b8c6e62dcf7f7f66c45f75b2..2e3a24fbc125924cc2bcc60bca8de514fd55f4f9 100755 (executable)
@@ -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;
 }
 /* }}} */
index 78300d82b5162a53df955cdbc17ba4a1f21caedc..3a9a0fd2cd8c3a71be873fbd5d02204fae86a3a0 100755 (executable)
@@ -1,20 +1,12 @@
 <?php
+dl('pdo.so');
+dl('pdo_sqlite.so');
 
 //$x = new PDO("oci:dbname=hostname", 'php', 'php');
-$x = new PDO("odbc:ram", 'php', 'php', array(PDO_ATTR_AUTOCOMMIT => 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";
 
 ?>
index 9f2099954c8579c3ae345669fe0712e8c3df6fe5..0f128711443c7df11cfd519d1cef8a628895b8f6 100755 (executable)
 #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 = "<<Unknown>>";
        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 = "<<Invalid>>";
+       /* hash sqlstate to error messages */
+       msg = pdo_sqlstate_state_to_description(*pdo_err);
+       if (!msg) {
+               msg = "<<Unknown error>>";
        }
 
        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 (file)
index 0000000..d927d52
--- /dev/null
@@ -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 <wez@php.net>                                    |
+  +----------------------------------------------------------------------+
+*/
+
+/* $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;
+}
+
index 7383125d3c06141b53530f185e5e044ef9a7c80e..1e5db6d84face16e7ade91acc6f8307bcec21718 100755 (executable)
@@ -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;
 }
index 4bc96d51b33aaa37ebdac9e58890fc8d7115a03c..bbfa7d36bf333e4e768ac6262dcbf815bf276364 100755 (executable)
@@ -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. */
index 51c0fef392b9a16e3311a0aa4a637a856c9cdfd8..3db761e6c6290f8d121f8487eb004632041f113e 100755 (executable)
@@ -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: