diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c index a044cd6400519..0d7f0d2a46d80 100644 --- a/ext/pdo_firebird/firebird_driver.c +++ b/ext/pdo_firebird/firebird_driver.c @@ -463,53 +463,6 @@ static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTa #if FB_API_VER >= 40 /* set coercing a data type */ -static void set_coercing_input_data_types(XSQLDA* sqlda) -{ - /* Data types introduced in Firebird 4.0 are difficult to process using the Firebird Legacy API. */ - /* These data types include DECFLOAT(16), DECFLOAT(34), INT128 (NUMERIC/DECIMAL(38, x)), */ - /* TIMESTAMP WITH TIME ZONE, and TIME WITH TIME ZONE. */ - /* This function allows you to ensure minimal performance */ - /* of queries if they contain parameters of the above types. */ - unsigned int i; - short dtype; - short nullable; - XSQLVAR* var; - for (i=0, var = sqlda->sqlvar; i < sqlda->sqld; i++, var++) { - dtype = (var->sqltype & ~1); /* drop flag bit */ - nullable = (var->sqltype & 1); - switch(dtype) { - case SQL_INT128: - var->sqltype = SQL_VARYING + nullable; - var->sqllen = 46; - var->sqlscale = 0; - break; - - case SQL_DEC16: - var->sqltype = SQL_VARYING + nullable; - var->sqllen = 24; - break; - - case SQL_DEC34: - var->sqltype = SQL_VARYING + nullable; - var->sqllen = 43; - break; - - case SQL_TIMESTAMP_TZ: - var->sqltype = SQL_VARYING + nullable; - var->sqllen = 58; - break; - - case SQL_TIME_TZ: - var->sqltype = SQL_VARYING + nullable; - var->sqllen = 46; - break; - - default: - break; - } - } -} - static void set_coercing_output_data_types(XSQLDA* sqlda) { /* Data types introduced in Firebird 4.0 are difficult to process using the Firebird Legacy API. */ @@ -602,14 +555,12 @@ void php_firebird_set_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *state, 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); @@ -730,11 +681,6 @@ static bool firebird_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, /* {{{ */ if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, S->in_sqlda)) { break; } - -#if FB_API_VER >= 40 - /* set coercing a data type */ - set_coercing_input_data_types(S->in_sqlda); -#endif } stmt->driver_data = S; @@ -1333,7 +1279,6 @@ static int pdo_firebird_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) } /* }}} */ -#if FB_API_VER >= 30 /* called by PDO to check liveness */ static zend_result pdo_firebird_check_liveness(pdo_dbh_t *dbh) /* {{{ */ { @@ -1343,7 +1288,6 @@ static zend_result pdo_firebird_check_liveness(pdo_dbh_t *dbh) /* {{{ */ return fb_ping(H->isc_status, &H->db) ? FAILURE : SUCCESS; } /* }}} */ -#endif /* called by PDO to retrieve driver-specific information about an error that has occurred */ static void pdo_firebird_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ @@ -1383,11 +1327,7 @@ static const struct pdo_dbh_methods firebird_methods = { /* {{{ */ NULL, /* last_id not supported */ pdo_firebird_fetch_error_func, pdo_firebird_get_attribute, -#if FB_API_VER >= 30 pdo_firebird_check_liveness, -#else - NULL, -#endif NULL, /* get driver methods */ NULL, /* request shutdown */ pdo_firebird_in_manually_transaction, @@ -1437,9 +1377,21 @@ static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* } do { +#if FB_API_VER >= 40 + zend_string *session_timezone = pdo_attr_strval(driver_options, PDO_FB_SESSION_TIMEZONE, NULL); +#endif + static char const dpb_flags[] = { - isc_dpb_user_name, isc_dpb_password, isc_dpb_lc_ctype, isc_dpb_sql_role_name }; - char const *dpb_values[] = { dbh->username, dbh->password, vars[1].optval, vars[2].optval }; + isc_dpb_user_name, isc_dpb_password, isc_dpb_lc_ctype, isc_dpb_sql_role_name +#if FB_API_VER >= 40 + , isc_dpb_session_time_zone +#endif + }; + char const *dpb_values[] = { dbh->username, dbh->password, vars[1].optval, vars[2].optval +#if FB_API_VER >= 40 + , session_timezone ? ZSTR_VAL(session_timezone) : NULL +#endif + }; char dpb_buffer[256] = { isc_dpb_version1 }, *dpb; dpb = dpb_buffer + 1; @@ -1454,6 +1406,12 @@ static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* } } +#if FB_API_VER >= 40 + if (session_timezone) { + zend_string_release_ex(session_timezone, 0); + } +#endif + H->sql_dialect = PDO_FB_DIALECT; if (vars[3].optval) { H->sql_dialect = atoi(vars[3].optval); diff --git a/ext/pdo_firebird/firebird_statement.c b/ext/pdo_firebird/firebird_statement.c index 5cc8889c65e79..ce7bc822139ee 100644 --- a/ext/pdo_firebird/firebird_statement.c +++ b/ext/pdo_firebird/firebird_statement.c @@ -349,11 +349,11 @@ static int pdo_firebird_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, #endif param_type = PDO_PARAM_INT; break; -#ifdef SQL_BOOLEAN + case SQL_BOOLEAN: param_type = PDO_PARAM_BOOL; break; -#endif + default: param_type = PDO_PARAM_STR; break; @@ -542,11 +542,9 @@ static int pdo_firebird_stmt_get_col( /* TODO: Why is this not returned as the native type? */ ZVAL_STR(result, zend_strpprintf_unchecked(0, "%.16H", php_get_double_from_sqldata(var->sqldata))); break; -#ifdef SQL_BOOLEAN case SQL_BOOLEAN: ZVAL_BOOL(result, *(FB_BOOLEAN*)var->sqldata); break; -#endif case SQL_TYPE_DATE: isc_decode_sql_date((ISC_DATE*)var->sqldata, &t); fmt = S->H->date_format ? S->H->date_format : PDO_FB_DEF_DATE_FMT; @@ -744,7 +742,6 @@ static int pdo_firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param } } -#ifdef SQL_BOOLEAN /* keep native BOOLEAN type */ if ((var->sqltype & ~1) == SQL_BOOLEAN) { switch (Z_TYPE_P(parameter)) { @@ -797,8 +794,6 @@ static int pdo_firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param } break; } -#endif - /* check if a NULL should be inserted */ switch (Z_TYPE_P(parameter)) { @@ -829,6 +824,13 @@ static int pdo_firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param case SQL_TIMESTAMP: case SQL_TYPE_DATE: case SQL_TYPE_TIME: +#if FB_API_VER >= 40 + case SQL_INT128: + case SQL_DEC16: + case SQL_DEC34: + case SQL_TIMESTAMP_TZ: + case SQL_TIME_TZ: +#endif force_null = (Z_STRLEN_P(parameter) == 0); } if (!force_null) { diff --git a/ext/pdo_firebird/pdo_firebird.stub.php b/ext/pdo_firebird/pdo_firebird.stub.php index f8c9ea46fcb07..2338c89089ba6 100644 --- a/ext/pdo_firebird/pdo_firebird.stub.php +++ b/ext/pdo_firebird/pdo_firebird.stub.php @@ -33,6 +33,9 @@ class Firebird extends \PDO /** @cvalue PDO_FB_WRITABLE_TRANSACTION */ public const int WRITABLE_TRANSACTION = UNKNOWN; + + /** @cvalue PDO_FB_SESSION_TIMEZONE */ + public const int SESSION_TIMEZONE = UNKNOWN; public static function getApiVersion(): int {} } diff --git a/ext/pdo_firebird/pdo_firebird_arginfo.h b/ext/pdo_firebird/pdo_firebird_arginfo.h index a751751edb635..5235888d7a225 100644 --- a/ext/pdo_firebird/pdo_firebird_arginfo.h +++ b/ext/pdo_firebird/pdo_firebird_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d36b2055abc48ae91c3442dda68fa2a28eb6d25b */ + * Stub hash: 37ad789e2e14cb6cad05bb1a41adfee4e9194e02 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Firebird_getApiVersion, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -67,5 +67,11 @@ static zend_class_entry *register_class_Pdo_Firebird(zend_class_entry *class_ent zend_declare_typed_class_constant(class_entry, const_WRITABLE_TRANSACTION_name, &const_WRITABLE_TRANSACTION_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release(const_WRITABLE_TRANSACTION_name); + zval const_SESSION_TIMEZONE_value; + ZVAL_LONG(&const_SESSION_TIMEZONE_value, PDO_FB_SESSION_TIMEZONE); + zend_string *const_SESSION_TIMEZONE_name = zend_string_init_interned("SESSION_TIMEZONE", sizeof("SESSION_TIMEZONE") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_SESSION_TIMEZONE_name, &const_SESSION_TIMEZONE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_SESSION_TIMEZONE_name); + return class_entry; } diff --git a/ext/pdo_firebird/php_pdo_firebird_int.h b/ext/pdo_firebird/php_pdo_firebird_int.h index dc1e4bb7f4086..d2f0b410579fe 100644 --- a/ext/pdo_firebird/php_pdo_firebird_int.h +++ b/ext/pdo_firebird/php_pdo_firebird_int.h @@ -154,6 +154,9 @@ enum { /* transaction access mode */ PDO_FB_WRITABLE_TRANSACTION, + + /* session time zone */ + PDO_FB_SESSION_TIMEZONE, }; #endif /* PHP_PDO_FIREBIRD_INT_H */ diff --git a/ext/pdo_firebird/tests/fb4_datatypes.phpt b/ext/pdo_firebird/tests/fb4_datatypes.phpt index 7acf338be27c9..23874285713a6 100644 --- a/ext/pdo_firebird/tests/fb4_datatypes.phpt +++ b/ext/pdo_firebird/tests/fb4_datatypes.phpt @@ -5,15 +5,10 @@ pdo_firebird --SKIPIF-- query("SELECT RDB\$get_context('SYSTEM', 'ENGINE_VERSION') AS VERSION FROM RDB\$DATABASE"); -$data = $stmt->fetch(\PDO::FETCH_ASSOC); -if (!$data || !array_key_exists('VERSION', $data) || version_compare($data['VERSION'], '4.0.0') < 0) { - die("skip Firebird Server version must be greater than or equal to 4.0.0"); -} +checkMinServerVersion('4.0.0'); ?> --XLEAK-- A bug in firebird causes a memory leak when calling `isc_attach_database()`. @@ -23,18 +18,18 @@ See https://github.com/FirebirdSQL/firebird/issues/7849 require 'testdb.inc'; $sql = <<<'SQL' - SELECT - CAST(15 AS BIGINT) AS i64, - CAST(15 AS INT128) AS i128, - 123.97 AS N, - CAST(123.97 AS NUMERIC(38,2)) AS N2, - CAST('2024-05-04 12:59:34.239' AS TIMESTAMP) AS TS, - CAST('2024-05-04 12:59:34.239 Europe/Moscow' AS TIMESTAMP WITH TIME ZONE) AS TS_TZ, - CAST('12:59:34.239' AS TIME) AS T, - CAST('12:59:34.239 Europe/Moscow' AS TIME WITH TIME ZONE) AS T_TZ, - CAST(1.128 AS DECFLOAT(16)) AS df16, - CAST(1.128 AS DECFLOAT(34)) AS df34 - FROM RDB$DATABASE + SELECT + CAST(15 AS BIGINT) AS i64, + CAST(15 AS INT128) AS i128, + 123.97 AS N, + CAST(123.97 AS NUMERIC(38,2)) AS N2, + CAST('2024-05-04 12:59:34.239' AS TIMESTAMP) AS TS, + CAST('2024-05-04 12:59:34.239 Europe/Moscow' AS TIMESTAMP WITH TIME ZONE) AS TS_TZ, + CAST('12:59:34.239' AS TIME) AS T, + CAST('12:59:34.239 Europe/Moscow' AS TIME WITH TIME ZONE) AS T_TZ, + CAST(1.128 AS DECFLOAT(16)) AS df16, + CAST(1.128 AS DECFLOAT(34)) AS df34 + FROM RDB$DATABASE SQL; $dbh = getDbConnection(); diff --git a/ext/pdo_firebird/tests/fb4_datatypes_params.phpt b/ext/pdo_firebird/tests/fb4_datatypes_params.phpt index af43355ce8e8a..86f6a6a4334a1 100644 --- a/ext/pdo_firebird/tests/fb4_datatypes_params.phpt +++ b/ext/pdo_firebird/tests/fb4_datatypes_params.phpt @@ -5,15 +5,10 @@ pdo_firebird --SKIPIF-- query("SELECT RDB\$get_context('SYSTEM', 'ENGINE_VERSION') AS VERSION FROM RDB\$DATABASE"); -$data = $stmt->fetch(\PDO::FETCH_ASSOC); -if (!$data || !array_key_exists('VERSION', $data) || version_compare($data['VERSION'], '4.0.0') < 0) { - die("skip Firebird Server version must be greater than or equal to 4.0.0"); -} +checkMinServerVersion('4.0.0'); ?> --XLEAK-- A bug in firebird causes a memory leak when calling `isc_attach_database()`. @@ -23,15 +18,15 @@ See https://github.com/FirebirdSQL/firebird/issues/7849 require 'testdb.inc'; $sql = <<<'SQL' - SELECT - CAST(? AS INT128) AS i128, - CAST(? AS NUMERIC(18,2)) AS N1, - CAST(? AS NUMERIC(38,2)) AS N2, - CAST(? AS TIMESTAMP WITH TIME ZONE) AS TS_TZ, - CAST(? AS TIME WITH TIME ZONE) AS T_TZ, - CAST(? AS DECFLOAT(16)) AS df16, - CAST(? AS DECFLOAT(34)) AS df34 - FROM RDB$DATABASE + SELECT + CAST(? AS INT128) AS i128, + CAST(? AS NUMERIC(18,2)) AS N1, + CAST(? AS NUMERIC(38,2)) AS N2, + CAST(? AS TIMESTAMP WITH TIME ZONE) AS TS_TZ, + CAST(? AS TIME WITH TIME ZONE) AS T_TZ, + CAST(? AS DECFLOAT(16)) AS df16, + CAST(? AS DECFLOAT(34)) AS df34 + FROM RDB$DATABASE SQL; $dbh = getDbConnection(); diff --git a/ext/pdo_firebird/tests/fb4_session_timezone.phpt b/ext/pdo_firebird/tests/fb4_session_timezone.phpt new file mode 100644 index 0000000000000..f9f3b17a92e2f --- /dev/null +++ b/ext/pdo_firebird/tests/fb4_session_timezone.phpt @@ -0,0 +1,50 @@ +--TEST-- +PDO_Firebird: Firebird 4.0 set session time zone +--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-- + 'Europe/Rome', +]); + +$stmt = $dbh->prepare($sql); +$stmt->execute(); +$data = $stmt->fetch(\PDO::FETCH_ASSOC); +$stmt->closeCursor(); + +var_dump($data); +echo "\ndone\n"; +?> +--EXPECT-- +array(3) { + ["TZ"]=> + string(11) "Europe/Rome" + ["TZ1"]=> + string(11) "Europe/Rome" + ["TZ2"]=> + string(11) "Europe/Rome" +} + +done diff --git a/ext/pdo_firebird/tests/testdb.inc b/ext/pdo_firebird/tests/testdb.inc index 6be882297188b..bbc01596e3d73 100644 --- a/ext/pdo_firebird/tests/testdb.inc +++ b/ext/pdo_firebird/tests/testdb.inc @@ -14,15 +14,25 @@ define('PDO_FIREBIRD_TEST_DSN', getenv('PDO_FIREBIRD_TEST_DSN') ?: ''); if(!PDO_FIREBIRD_TEST_DSN) { - die('Error: PDO_FIREBIRD_TEST_DSN must be set'); + die('Error: PDO_FIREBIRD_TEST_DSN must be set'); } function getDbConnection($class = PDO::class): PDO { return new $class(PDO_FIREBIRD_TEST_DSN, PDO_FIREBIRD_TEST_USER, PDO_FIREBIRD_TEST_PASS); } -function connectToDb(): Pdo\Firebird { - return Pdo\Firebird::connect(PDO_FIREBIRD_TEST_DSN, PDO_FIREBIRD_TEST_USER, PDO_FIREBIRD_TEST_PASS); +function connectToDb(array $params = []): Pdo\Firebird { + return Pdo\Firebird::connect(PDO_FIREBIRD_TEST_DSN, PDO_FIREBIRD_TEST_USER, PDO_FIREBIRD_TEST_PASS, $params); +} + +function checkMinServerVersion(string $version): void +{ + $dbh = connectToDb(); + $stmt = $dbh->query("SELECT RDB\$GET_CONTEXT('SYSTEM', 'ENGINE_VERSION') AS VERSION FROM RDB\$DATABASE"); + $data = $stmt->fetch(\PDO::FETCH_ASSOC); + if (!$data || !array_key_exists('VERSION', $data) || version_compare($data['VERSION'], $version) < 0) { + die("skip Firebird Server version must be greater than or equal to $version"); + } } ?>