diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c index cbd7ee7cfd377..00398a7116127 100644 --- a/ext/pdo_firebird/firebird_driver.c +++ b/ext/pdo_firebird/firebird_driver.c @@ -460,16 +460,67 @@ int preprocess(const zend_string* sql, char* sql_out, HashTable* named_params) } /* map driver specific error message to PDO error */ -void _firebird_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, char const *file, zend_long line) /* {{{ */ +void _firebird_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *state, const size_t state_len, + const char *msg, const size_t msg_len) /* {{{ */ { pdo_error_type *const error_code = stmt ? &stmt->error_code : &dbh->error_code; + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + pdo_firebird_error_info *einfo = &H->einfo; + int sqlcode = -999; + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + einfo->errmsg_length = 0; + } + + if (H->isc_status && (H->isc_status[0] == 1 && H->isc_status[1] > 0)) { + char buf[512]; + size_t buf_size = sizeof(buf), read_len = 0; + ssize_t tmp_len; + const ISC_STATUS *s = H->isc_status; + sqlcode = isc_sqlcode(s); + + while ((buf_size > (read_len + 1)) && (tmp_len = fb_interpret(&buf[read_len], (buf_size - read_len - 1), &s)) && tmp_len > 0) { + read_len += tmp_len; + buf[read_len++] = ' '; + } - strcpy(*error_code, "HY000"); + /* remove last space */ + if (read_len) { + buf[read_len--] = '\0'; + } + + einfo->errmsg_length = read_len; + einfo->errmsg = pestrndup(buf, read_len, dbh->is_persistent); + +#if FB_API_VER >= 25 + char sqlstate[sizeof(pdo_error_type)]; + fb_sqlstate(sqlstate, H->isc_status); + if (sqlstate != NULL && strlen(sqlstate) < sizeof(pdo_error_type)) { + strcpy(*error_code, sqlstate); + goto end; + } +#endif + } else if (msg && msg_len) { + einfo->errmsg_length = msg_len; + einfo->errmsg = pestrndup(msg, einfo->errmsg_length, dbh->is_persistent); + } + + if (state && state_len && state_len < sizeof(pdo_error_type)) { + memcpy(*error_code, state, state_len + 1); + } else { + memcpy(*error_code, "HY000", sizeof("HY000")); + } + +end: + einfo->sqlcode = sqlcode; + if (!dbh->methods) { + pdo_throw_exception(0, einfo->errmsg, error_code); + } } /* }}} */ -#define RECORD_ERROR(dbh) _firebird_error(dbh, NULL, __FILE__, __LINE__) - /* called by PDO to close a db handle */ static void firebird_handle_closer(pdo_dbh_t *dbh) /* {{{ */ { @@ -478,17 +529,17 @@ static void firebird_handle_closer(pdo_dbh_t *dbh) /* {{{ */ if (dbh->in_txn) { if (dbh->auto_commit) { if (isc_commit_transaction(H->isc_status, &H->tr)) { - RECORD_ERROR(dbh); + firebird_error(dbh); } } else { if (isc_rollback_transaction(H->isc_status, &H->tr)) { - RECORD_ERROR(dbh); + firebird_error(dbh); } } } if (isc_detach_database(H->isc_status, &H->db)) { - RECORD_ERROR(dbh); + firebird_error(dbh); } if (H->date_format) { @@ -501,6 +552,11 @@ static void firebird_handle_closer(pdo_dbh_t *dbh) /* {{{ */ efree(H->timestamp_format); } + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + pefree(H, dbh->is_persistent); } /* }}} */ @@ -547,7 +603,7 @@ static bool firebird_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, /* {{{ */ /* fill the output sqlda with information about the prepared query */ if (isc_dsql_describe(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) { - RECORD_ERROR(dbh); + firebird_error(dbh); break; } @@ -574,7 +630,7 @@ static bool firebird_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, /* {{{ */ } while (0); - RECORD_ERROR(dbh); + firebird_error(dbh); zend_hash_destroy(np); FREE_HASHTABLE(np); @@ -612,7 +668,7 @@ static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* /* execute the statement */ if (isc_dsql_execute2(H->isc_status, &H->tr, &stmt, PDO_FB_SQLDA_VERSION, &in_sqlda, &out_sqlda)) { - RECORD_ERROR(dbh); + firebird_error(dbh); ret = -1; goto free_statement; } @@ -620,7 +676,7 @@ static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* /* find out how many rows were affected */ if (isc_dsql_sql_info(H->isc_status, &stmt, sizeof(info_count), const_cast(info_count), sizeof(result), result)) { - RECORD_ERROR(dbh); + firebird_error(dbh); ret = -1; goto free_statement; } @@ -648,13 +704,13 @@ static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* /* commit if we're in auto_commit mode */ if (dbh->auto_commit && isc_commit_retaining(H->isc_status, &H->tr)) { - RECORD_ERROR(dbh); + firebird_error(dbh); } free_statement: if (isc_dsql_free_statement(H->isc_status, &stmt, DSQL_drop)) { - RECORD_ERROR(dbh); + firebird_error(dbh); } return ret; @@ -746,7 +802,7 @@ static bool firebird_handle_begin(pdo_dbh_t *dbh) /* {{{ */ } #endif if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, (unsigned short)(ptpb-tpb), tpb)) { - RECORD_ERROR(dbh); + firebird_error(dbh); return false; } return true; @@ -759,7 +815,7 @@ static bool firebird_handle_commit(pdo_dbh_t *dbh) /* {{{ */ pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; if (isc_commit_transaction(H->isc_status, &H->tr)) { - RECORD_ERROR(dbh); + firebird_error(dbh); return false; } return true; @@ -772,7 +828,7 @@ static bool firebird_handle_rollback(pdo_dbh_t *dbh) /* {{{ */ pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; if (isc_rollback_transaction(H->isc_status, &H->tr)) { - RECORD_ERROR(dbh); + firebird_error(dbh); return false; } return true; @@ -804,7 +860,7 @@ static int firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const zend_string *sql, /* allocate the statement */ if (isc_dsql_allocate_statement(H->isc_status, &H->db, s)) { - RECORD_ERROR(dbh); + firebird_error(dbh); return 0; } @@ -820,7 +876,7 @@ static int firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const zend_string *sql, /* prepare the statement */ if (isc_dsql_prepare(H->isc_status, &H->tr, s, 0, new_sql, H->sql_dialect, out_sqlda)) { - RECORD_ERROR(dbh); + firebird_error(dbh); efree(new_sql); return 0; } @@ -848,7 +904,8 @@ static bool firebird_handle_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval * if (bval) { /* turning on auto_commit with an open transaction is illegal, because we won't know what to do with it */ - H->last_app_error = "Cannot enable auto-commit while a transaction is already open"; + const char *msg = "Cannot enable auto-commit while a transaction is already open"; + firebird_error_with_info(dbh, "HY000", strlen("HY000"), msg, strlen(msg)); return false; } else { /* close the transaction */ @@ -992,21 +1049,11 @@ static int firebird_handle_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *v static void pdo_firebird_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ { pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; - const ISC_STATUS *s = H->isc_status; - char buf[400]; - zend_long i = 0, l, sqlcode = isc_sqlcode(s); - - if (sqlcode) { - add_next_index_long(info, sqlcode); - - while ((sizeof(buf)>(i+2))&&(l = fb_interpret(&buf[i],(sizeof(buf)-i-2),&s))) { - i += l; - strcpy(&buf[i++], " "); - } - add_next_index_string(info, buf); - } else if (H->last_app_error) { - add_next_index_long(info, -999); - add_next_index_string(info, const_cast(H->last_app_error)); + if (H->einfo.sqlcode != IS_NULL) { + add_next_index_long(info, H->einfo.sqlcode); + } + if (H->einfo.errmsg && H->einfo.errmsg_length) { + add_next_index_stringl(info, H->einfo.errmsg, H->einfo.errmsg_length); } } /* }}} */ diff --git a/ext/pdo_firebird/firebird_statement.c b/ext/pdo_firebird/firebird_statement.c index c8f1154c47f31..686ab0c3d1793 100644 --- a/ext/pdo_firebird/firebird_statement.c +++ b/ext/pdo_firebird/firebird_statement.c @@ -28,8 +28,6 @@ #include -#define RECORD_ERROR(stmt) _firebird_error(NULL, stmt, __FILE__, __LINE__) - #define READ_AND_RETURN_USING_MEMCPY(type, sqldata) do { \ type ret; \ memcpy(&ret, sqldata, sizeof(ret)); \ @@ -89,7 +87,7 @@ static int firebird_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */ /* release the statement */ if (isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_drop)) { - RECORD_ERROR(stmt); + firebird_error_stmt(stmt); result = 0; } @@ -195,7 +193,7 @@ static int firebird_stmt_execute(pdo_stmt_t *stmt) /* {{{ */ } while (0); error: - RECORD_ERROR(stmt); + firebird_error_stmt(stmt); return 0; } @@ -209,8 +207,8 @@ static int firebird_stmt_fetch(pdo_stmt_t *stmt, /* {{{ */ pdo_firebird_db_handle *H = S->H; if (!stmt->executed) { - strcpy(stmt->error_code, "HY000"); - H->last_app_error = "Cannot fetch from a closed cursor"; + const char *msg = "Cannot fetch from a closed cursor"; + firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); } else if (!S->exhausted) { if (S->statement_type == isc_info_sql_stmt_exec_procedure) { stmt->row_count = 1; @@ -219,7 +217,7 @@ static int firebird_stmt_fetch(pdo_stmt_t *stmt, /* {{{ */ } if (isc_dsql_fetch(H->isc_status, &S->stmt, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) { if (H->isc_status[0] && H->isc_status[1]) { - RECORD_ERROR(stmt); + firebird_error_stmt(stmt); } S->exhausted = 1; return 0; @@ -308,13 +306,13 @@ static int firebird_fetch_blob(pdo_stmt_t *stmt, int colno, zval *result, ISC_QU size_t len = 0; if (isc_open_blob(H->isc_status, &H->db, &H->tr, &blobh, blob_id)) { - RECORD_ERROR(stmt); + firebird_error_stmt(stmt); return 0; } if (isc_blob_info(H->isc_status, &blobh, 1, const_cast(&bl_item), sizeof(bl_info), bl_info)) { - RECORD_ERROR(stmt); + firebird_error_stmt(stmt); goto fetch_blob_end; } @@ -325,7 +323,8 @@ static int firebird_fetch_blob(pdo_stmt_t *stmt, int colno, zval *result, ISC_QU if (item == isc_info_end || item == isc_info_truncated || item == isc_info_error || i >= sizeof(bl_info)) { - H->last_app_error = "Couldn't determine BLOB size"; + const char *msg = "Couldn't determine BLOB size"; + firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); goto fetch_blob_end; } @@ -366,7 +365,8 @@ static int firebird_fetch_blob(pdo_stmt_t *stmt, int colno, zval *result, ISC_QU ZVAL_STR(result, str); if (H->isc_status[0] == 1 && (stat != 0 && stat != isc_segstr_eof && stat != isc_segment)) { - H->last_app_error = "Error reading from BLOB"; + const char *msg = "Error reading from BLOB"; + firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); goto fetch_blob_end; } } @@ -374,7 +374,7 @@ static int firebird_fetch_blob(pdo_stmt_t *stmt, int colno, zval *result, ISC_QU fetch_blob_end: if (isc_close_blob(H->isc_status, &blobh)) { - RECORD_ERROR(stmt); + firebird_error_stmt(stmt); return 0; } return retval; @@ -516,7 +516,7 @@ static int firebird_bind_blob(pdo_stmt_t *stmt, ISC_QUAD *blob_id, zval *param) int result = 1; if (isc_create_blob(H->isc_status, &H->db, &H->tr, &h, blob_id)) { - RECORD_ERROR(stmt); + firebird_error_stmt(stmt); return 0; } @@ -529,7 +529,7 @@ static int firebird_bind_blob(pdo_stmt_t *stmt, ISC_QUAD *blob_id, zval *param) for (rem_cnt = Z_STRLEN(data); rem_cnt > 0; rem_cnt -= chunk_size) { chunk_size = rem_cnt > USHRT_MAX ? USHRT_MAX : (unsigned short)rem_cnt; if (isc_put_segment(H->isc_status, &h, chunk_size, &Z_STRVAL(data)[put_cnt])) { - RECORD_ERROR(stmt); + firebird_error_stmt(stmt); result = 0; break; } @@ -541,7 +541,7 @@ static int firebird_bind_blob(pdo_stmt_t *stmt, ISC_QUAD *blob_id, zval *param) } if (isc_close_blob(H->isc_status, &h)) { - RECORD_ERROR(stmt); + firebird_error_stmt(stmt); return 0; } return result; @@ -559,8 +559,8 @@ static int firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_dat } if (!sqlda || param->paramno >= sqlda->sqld) { - strcpy(stmt->error_code, "HY093"); - S->H->last_app_error = "Invalid parameter index"; + const char *msg = "Invalid parameter index"; + firebird_error_stmt_with_info(stmt, "HY093", strlen("HY093"), msg, strlen(msg)); return 0; } if (param->is_param && param->paramno == -1) { @@ -585,8 +585,8 @@ static int firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_dat } } if (i >= sqlda->sqld) { - strcpy(stmt->error_code, "HY093"); - S->H->last_app_error = "Invalid parameter name"; + const char *msg = "Invalid parameter name"; + firebird_error_stmt_with_info(stmt, "HY093", strlen("HY093"), msg, strlen(msg)); return 0; } } @@ -636,16 +636,18 @@ static int firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_dat switch (var->sqltype & ~1) { case SQL_ARRAY: - strcpy(stmt->error_code, "HY000"); - S->H->last_app_error = "Cannot bind to array field"; + { + const char *msg = "Cannot bind to array field"; + firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + } return 0; case SQL_BLOB: { if (Z_TYPE_P(parameter) == IS_NULL) { /* Check if field allow NULL values */ if (~var->sqltype & 1) { - strcpy(stmt->error_code, "HY105"); - S->H->last_app_error = "Parameter requires non-null value"; + const char *msg = "Parameter requires non-null value"; + firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); return 0; } *var->sqlind = -1; @@ -693,8 +695,8 @@ static int firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_dat } else if (!zend_binary_strncasecmp(Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), "false", 5, 5)) { *(FB_BOOLEAN*)var->sqldata = FB_FALSE; } else { - strcpy(stmt->error_code, "HY105"); - S->H->last_app_error = "Cannot convert string to boolean"; + const char *msg = "Cannot convert string to boolean"; + firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); return 0; } @@ -705,8 +707,10 @@ static int firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_dat *var->sqlind = -1; break; default: - strcpy(stmt->error_code, "HY105"); - S->H->last_app_error = "Binding arrays/objects is not supported"; + { + const char *msg = "Binding arrays/objects is not supported"; + firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + } return 0; } break; @@ -756,15 +760,17 @@ static int firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_dat case IS_NULL: /* complain if this field doesn't allow NULL values */ if (~var->sqltype & 1) { - strcpy(stmt->error_code, "HY105"); - S->H->last_app_error = "Parameter requires non-null value"; + const char *msg = "Parameter requires non-null value"; + firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); return 0; } *var->sqlind = -1; break; default: - strcpy(stmt->error_code, "HY105"); - S->H->last_app_error = "Binding arrays/objects is not supported"; + { + const char *msg = "Binding arrays/objects is not supported"; + firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + } return 0; } break; @@ -804,7 +810,7 @@ static int firebird_stmt_set_attribute(pdo_stmt_t *stmt, zend_long attr, zval *v } if (isc_dsql_set_cursor_name(S->H->isc_status, &S->stmt, Z_STRVAL_P(val),0)) { - RECORD_ERROR(stmt); + firebird_error_stmt(stmt); return 0; } strlcpy(S->name, Z_STRVAL_P(val), sizeof(S->name)); @@ -839,7 +845,7 @@ static int firebird_stmt_cursor_closer(pdo_stmt_t *stmt) /* {{{ */ /* close the statement handle */ if ((*S->name || S->cursor_open) && isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_close)) { - RECORD_ERROR(stmt); + firebird_error_stmt(stmt); return 0; } *S->name = 0; diff --git a/ext/pdo_firebird/php_pdo_firebird_int.h b/ext/pdo_firebird/php_pdo_firebird_int.h index 243163b8a08ad..39e954ffc2acd 100644 --- a/ext/pdo_firebird/php_pdo_firebird_int.h +++ b/ext/pdo_firebird/php_pdo_firebird_int.h @@ -59,6 +59,12 @@ typedef void (*info_func_t)(char*); # define PDO_FIREBIRD_HANDLE_INITIALIZER NULL #endif +typedef struct { + int sqlcode; + char *errmsg; + size_t errmsg_length; +} pdo_firebird_error_info; + typedef struct { /* the result of the last API call */ @@ -70,9 +76,6 @@ typedef struct { /* the transaction handle */ isc_tr_handle tr; - /* the last error that didn't come from the API */ - char const *last_app_error; - /* date and time format strings, can be set by the set_attribute method */ char *date_format; char *time_format; @@ -85,6 +88,7 @@ typedef struct { unsigned _reserved:29; + pdo_firebird_error_info einfo; } pdo_firebird_db_handle; @@ -125,7 +129,12 @@ extern const pdo_driver_t pdo_firebird_driver; extern const struct pdo_stmt_methods firebird_stmt_methods; -void _firebird_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, char const *file, zend_long line); +extern void _firebird_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *state, const size_t state_len, + const char *msg, const size_t msg_len); +#define firebird_error(d) _firebird_error(d, NULL, NULL, 0, NULL, 0) +#define firebird_error_stmt(s) _firebird_error(s->dbh, s, NULL, 0, NULL, 0) +#define firebird_error_with_info(d,e,el,m,ml) _firebird_error(d, NULL, e, el, m, ml) +#define firebird_error_stmt_with_info(s,e,el,m,ml) _firebird_error(s->dbh, s, e, el, m, ml) enum { PDO_FB_ATTR_DATE_FORMAT = PDO_ATTR_DRIVER_SPECIFIC, diff --git a/ext/pdo_firebird/tests/error_handle.phpt b/ext/pdo_firebird/tests/error_handle.phpt new file mode 100644 index 0000000000000..d070d61a1e70a --- /dev/null +++ b/ext/pdo_firebird/tests/error_handle.phpt @@ -0,0 +1,41 @@ +--TEST-- +PDO_Firebird: error handle +--EXTENSIONS-- +pdo_firebird +--SKIPIF-- + +--XLEAK-- +A bug in firebird causes a memory leak when calling `isc_attach_database()`. +See https://github.com/FirebirdSQL/firebird/issues/7849 +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + +$table = 'error_handle'; +$dbh->query("CREATE TABLE {$table} (val int)"); + +echo "dbh error"; +$dbh->query("INSERT INTO {$table} VALUES ('str')"); + +echo "\n"; + +echo "stmt error"; +$stmt = $dbh->prepare("INSERT INTO {$table} VALUES ('str')"); +$stmt->execute(); + +unset($dbh); +?> +--CLEAN-- +exec('DROP TABLE error_handle'); +unset($dbh); +?> +--EXPECTF-- +dbh error +Warning: PDO::query(): SQLSTATE[22018]: Invalid character value for cast specification: -413 conversion error from string "str" in %s on line 10 + +stmt error +Warning: PDOStatement::execute(): SQLSTATE[22018]: Invalid character value for cast specification: -413 conversion error from string "str" in %s on line 16