From: Wez Furlong Date: Thu, 20 May 2004 00:05:22 +0000 (+0000) Subject: First cut at a "unified" error handling API. The main thing that is missing X-Git-Tag: RELEASE_0_1~86 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=e7c72f8455ed26087bbf974723179fbf77696280;p=php First cut at a "unified" error handling API. The main thing that is missing currently is a switch in the dbh to indicate what to do with the errors. --- diff --git a/ext/pdo/pdo.c b/ext/pdo/pdo.c index fa5a1dc6ca..cecd1882bb 100755 --- a/ext/pdo/pdo.c +++ b/ext/pdo/pdo.c @@ -46,6 +46,11 @@ static int le_ppdo; /* for exceptional circumstances */ zend_class_entry *pdo_exception_ce; +PDO_API zend_class_entry *php_pdo_get_exception(void) +{ + return pdo_exception_ce; +} + zend_class_entry *pdo_dbh_ce, *pdo_dbstmt_ce; /* {{{ pdo_functions[] */ @@ -213,7 +218,19 @@ PHP_MINIT_FUNCTION(pdo) REGISTER_LONG_CONSTANT("PDO_ATTR_AUTOCOMMIT", (long)PDO_ATTR_AUTOCOMMIT, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PDO_ATTR_SCROLL", (long)PDO_ATTR_SCROLL, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PDO_ATTR_PREFETCH", (long)PDO_ATTR_PREFETCH, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PDO_ATTR_TIMEOUT", (long)PDO_ATTR_TIMEOUT, CONST_CS|CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("PDO_ERR_NONE", (long)PDO_ERR_NONE, CONST_CS|CONST_PERSISTENT); + 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); + REGISTER_LONG_CONSTANT("PDO_ERR_NOT_FOUND", (long)PDO_ERR_NOT_FOUND, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PDO_ERR_ALREADY_EXISTS", (long)PDO_ERR_ALREADY_EXISTS, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PDO_ERR_NOT_IMPLEMENTED", (long)PDO_ERR_NOT_IMPLEMENTED, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PDO_ERR_MISMATCH", (long)PDO_ERR_MISMATCH, CONST_CS|CONST_PERSISTENT); + 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); INIT_CLASS_ENTRY(ce, "PDOException", NULL); pdo_exception_ce = zend_register_internal_class_ex(&ce, zend_exception_get_default(), NULL TSRMLS_CC); diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index caa0d8c428..cbf9358cef 100755 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -34,6 +34,70 @@ #include "php_pdo_int.h" #include "zend_exceptions.h" +void pdo_handle_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt TSRMLS_DC) +{ + enum pdo_error_type *pdo_err = &dbh->error_code; + const char *msg = "<>"; + char *supp = NULL; + long native_code = 0; + + /* TODO: if the dbh->error_mode is set to "silent" mode, just return */ + + if (stmt) { + pdo_err = &stmt->error_code; + } + + switch (*pdo_err) { + case PDO_ERR_NONE: msg = "No erro"; 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; + default: msg = "<>"; + } + + if (dbh->methods->fetch_err) { + zval *info; + + MAKE_STD_ZVAL(info); + array_init(info); + + if (dbh->methods->fetch_err(dbh, stmt, info TSRMLS_CC)) { + zval **item; + + if (SUCCESS == zend_hash_index_find(Z_ARRVAL_P(info), 0, (void**)&item)) { + native_code = Z_LVAL_PP(item); + } + + if (SUCCESS == zend_hash_index_find(Z_ARRVAL_P(info), 1, (void**)&item)) { + supp = estrndup(Z_STRVAL_PP(item), Z_STRLEN_PP(item)); + } + + } + FREE_ZVAL(info); + } + + /* TODO: if the dbh->error_mode is set to exception mode, set up an + * exception instead */ + + if (supp && *pdo_err == PDO_ERR_CANT_MAP) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%d %s", native_code, supp); + } else if (supp) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s: %d %s", msg, native_code, supp); + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", msg); + } + + if (supp) { + efree(supp); + } +} + /* {{{ proto object PDO::__construct(string dsn, string username, string passwd [, array driver_opts]) */ static PHP_FUNCTION(dbh_constructor) @@ -60,7 +124,7 @@ static PHP_FUNCTION(dbh_constructor) colon = strchr(data_source, ':'); if (!colon) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid data source name"); + zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_SYNTAX TSRMLS_CC, "invalid data source name"); ZVAL_NULL(object); return; } @@ -70,7 +134,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 */ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "could not find driver"); + zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_NOT_FOUND TSRMLS_CC, "could not find driver"); ZVAL_NULL(object); return; } @@ -79,7 +143,7 @@ static PHP_FUNCTION(dbh_constructor) if (dbh == NULL) { /* need this check for persistent allocations */ - php_error_docref(NULL TSRMLS_CC, E_ERROR, "out of memory"); + zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_NONE TSRMLS_CC, "out of memory!?"); } memset(dbh, 0, sizeof(*dbh)); @@ -137,6 +201,7 @@ static PHP_METHOD(PDO, prepare) RETURN_FALSE; } + PDO_DBH_CLEAR_ERR(); stmt = ecalloc(1, sizeof(*stmt)); /* unconditionally keep this for later reference */ stmt->query_string = estrndup(statement, statement_len); @@ -154,6 +219,7 @@ static PHP_METHOD(PDO, prepare) return; } efree(stmt); + PDO_HANDLE_DBH_ERR(); RETURN_FALSE; } @@ -164,14 +230,14 @@ static PHP_METHOD(PDO, beginTransaction) pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC); if (dbh->in_txn) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "There is already an active transaction"); + zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_NONE 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 */ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "This driver does not support transactions"); + zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_NONE TSRMLS_CC, "This driver doesn't support transactions"); RETURN_FALSE; } @@ -180,6 +246,7 @@ static PHP_METHOD(PDO, beginTransaction) RETURN_TRUE; } + PDO_HANDLE_DBH_ERR(); RETURN_FALSE; } /* }}} */ @@ -191,7 +258,7 @@ static PHP_METHOD(PDO, commit) pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC); if (!dbh->in_txn) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "There is no active transaction"); + zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_NONE TSRMLS_CC, "There is no active transaction"); RETURN_FALSE; } @@ -200,6 +267,7 @@ static PHP_METHOD(PDO, commit) RETURN_TRUE; } + PDO_HANDLE_DBH_ERR(); RETURN_FALSE; } /* }}} */ @@ -211,7 +279,7 @@ static PHP_METHOD(PDO, rollBack) pdo_dbh_t *dbh = zend_object_store_get_object(getThis() TSRMLS_CC); if (!dbh->in_txn) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "There is no active transaction"); + zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_NONE TSRMLS_CC, "There is no active transaction"); RETURN_FALSE; } @@ -220,6 +288,7 @@ static PHP_METHOD(PDO, rollBack) RETURN_TRUE; } + PDO_HANDLE_DBH_ERR(); RETURN_FALSE; } /* }}} */ @@ -240,16 +309,14 @@ static PHP_METHOD(PDO, setAttribute) goto fail; } + PDO_DBH_CLEAR_ERR(); if (dbh->methods->set_attribute(dbh, attr, value TSRMLS_CC)) { RETURN_TRUE; } fail: if (attr == PDO_ATTR_AUTOCOMMIT) { - /* Feature: if the auto-commit mode cannot be changed, throw an - * exception. Until I've added the code for that, raise an - * E_ERROR */ - php_error_docref(NULL TSRMLS_CC, E_ERROR, "The auto commit mode cannot be changed for this driver"); + zend_throw_exception_ex(php_pdo_get_exception(), PDO_ERR_NONE TSRMLS_CC, "The auto-commit mode cannot be changed for this driver"); } else if (!dbh->methods->set_attribute) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "This driver doesn't support setting attributes"); } @@ -273,11 +340,12 @@ static PHP_METHOD(PDO, exec) if (!statement_len) { RETURN_FALSE; } + PDO_DBH_CLEAR_ERR(); ret = dbh->methods->doer(dbh, statement, statement_len TSRMLS_CC); if(ret == -1) { + PDO_HANDLE_DBH_ERR(); RETURN_FALSE; - } - else { + } else { RETURN_LONG(ret); } } @@ -294,6 +362,7 @@ static PHP_METHOD(PDO, lastInsertId) RETURN_FALSE; } + PDO_DBH_CLEAR_ERR(); if (!dbh->methods->last_id) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "This driver last inserted id retrieval."); } else { diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index 2c48a65989..494570fcab 100755 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -235,6 +235,8 @@ static PHP_METHOD(PDOStatement, execute) if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!", &input_params)) { RETURN_FALSE; } + + PDO_STMT_CLEAR_ERR(); if (input_params) { struct pdo_bound_param_data param; @@ -315,6 +317,7 @@ static PHP_METHOD(PDOStatement, execute) efree(stmt->active_query_string); stmt->active_query_string = NULL; } + PDO_HANDLE_STMT_ERR(); RETURN_FALSE; } /* }}} */ @@ -379,7 +382,7 @@ static int do_fetch_common(pdo_stmt_t *stmt, int do_bind TSRMLS_DC) /* TODO: some smart thing that avoids duplicating the value in the * general loop below. For now, if you're binding output columns, - * it's better to use LAZY or NONE fetches if you want to shave + * it's better to use LAZY or BOUND fetches if you want to shave * off those cycles */ } @@ -439,7 +442,9 @@ static PHP_METHOD(PDOStatement, fetch) RETURN_FALSE; } + PDO_STMT_CLEAR_ERR(); if (!do_fetch(stmt, TRUE, return_value, how TSRMLS_CC)) { + PDO_HANDLE_STMT_ERR(); RETURN_FALSE; } } @@ -455,7 +460,9 @@ static PHP_METHOD(PDOStatement, fetchSingle) RETURN_FALSE; } + PDO_STMT_CLEAR_ERR(); if (!do_fetch_common(stmt, TRUE TSRMLS_CC)) { + PDO_HANDLE_STMT_ERR(); RETURN_FALSE; } @@ -475,8 +482,10 @@ static PHP_METHOD(PDOStatement, fetchAll) RETURN_FALSE; } + PDO_STMT_CLEAR_ERR(); MAKE_STD_ZVAL(data); if (!do_fetch(stmt, TRUE, data, how TSRMLS_CC)) { + PDO_HANDLE_STMT_ERR(); RETURN_FALSE; } @@ -552,7 +561,8 @@ static PHP_METHOD(PDOStatement, rowCount) { pdo_stmt_t *stmt = (pdo_stmt_t*)zend_object_store_get_object(getThis() TSRMLS_CC); - RETURN_LONG(stmt->row_count); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "This statement is not a scrollable cursor and does not know the row count"); + RETURN_FALSE; } /* }}} */ diff --git a/ext/pdo/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h index b1006d1f0c..53e379b3cf 100755 --- a/ext/pdo/php_pdo_driver.h +++ b/ext/pdo/php_pdo_driver.h @@ -74,6 +74,7 @@ enum pdo_error_type { PDO_ERR_NOT_IMPLEMENTED, PDO_ERR_MISMATCH, PDO_ERR_TRUNCATED, + PDO_ERR_DISCONNECTED, }; /* {{{ utils for reading attributes set as driver_options */ @@ -104,6 +105,7 @@ typedef struct { * the dbh. dbh contains the data source string and flags for this * instance */ int (*db_handle_factory)(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC); + } pdo_driver_t; /* {{{ methods for a database handle */ @@ -129,6 +131,14 @@ typedef int (*pdo_dbh_set_attr_func)(pdo_dbh_t *dbh, long attr, zval *val TSRMLS /* return last insert id */ typedef long (*pdo_dbh_last_id_func)(pdo_dbh_t *dbh TSRMLS_DC); +/* fetch error information. if stmt is not null, fetch information pertaining + * to the statement, otherwise fetch global error information. The driver + * should add the following information to the array "info" in this order: + * - native error code + * - string representation of the error code ... any other optional driver + * specific data ... */ +typedef int (*pdo_dbh_fetch_error_func)(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info TSRMLS_DC); + struct pdo_dbh_methods { pdo_dbh_close_func closer; pdo_dbh_prepare_func preparer; @@ -139,6 +149,7 @@ struct pdo_dbh_methods { pdo_dbh_txn_func rollback; pdo_dbh_set_attr_func set_attribute; pdo_dbh_last_id_func last_id; + pdo_dbh_fetch_error_func fetch_err; }; /* }}} */ @@ -334,6 +345,9 @@ struct pdo_data_src_parser { PDO_API int php_pdo_parse_data_source(const char *data_source, unsigned long data_source_len, struct pdo_data_src_parser *parsed, int nparams); + +PDO_API zend_class_entry *php_pdo_get_exception(void); + #endif /* PHP_PDO_DRIVER_H */ /* * Local variables: diff --git a/ext/pdo/php_pdo_int.h b/ext/pdo/php_pdo_int.h index 97e969889b..2915ee2017 100755 --- a/ext/pdo/php_pdo_int.h +++ b/ext/pdo/php_pdo_int.h @@ -36,6 +36,14 @@ extern zend_object_handlers pdo_dbstmt_object_handlers; 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_HANDLE_DBH_ERR() pdo_handle_error(dbh, NULL TSRMLS_CC) +#define PDO_HANDLE_STMT_ERR() pdo_handle_error(stmt->dbh, stmt TSRMLS_CC) + + /* * Local variables: * tab-width: 4