diff --git a/ext/pdo_firebird/config.m4 b/ext/pdo_firebird/config.m4 index bb34df0bec23b..f80c28ba87a12 100644 --- a/ext/pdo_firebird/config.m4 +++ b/ext/pdo_firebird/config.m4 @@ -50,9 +50,24 @@ if test "$PHP_PDO_FIREBIRD" != "no"; then PHP_CHECK_PDO_INCLUDES + + PHP_PDO_FIREBIRD_COMMON_FLAGS="" PHP_NEW_EXTENSION([pdo_firebird], [pdo_firebird.c firebird_driver.c firebird_statement.c], - [$ext_shared]) + [$ext_shared],, + [$PHP_PDO_FIREBIRD_COMMON_FLAGS], + [cxx]) PHP_SUBST([PDO_FIREBIRD_SHARED_LIBADD]) PHP_ADD_EXTENSION_DEP(pdo_firebird, pdo) + + PHP_PDO_FIREBIRD_CXX_SOURCES="pdo_firebird_utils.cpp" + + PHP_REQUIRE_CXX() + PHP_CXX_COMPILE_STDCXX(11, mandatory, PHP_PDO_FIREBIRD_STDCXX) + PHP_PDO_FIREBIRD_CXX_FLAGS="$PHP_PDO_FIREBIRD_COMMON_FLAGS $PHP_PDO_FIREBIRD_STDCXX" + + PHP_ADD_SOURCES([$ext_dir], + [$PHP_PDO_FIREBIRD_CXX_SOURCES], + [$PHP_PDO_FIREBIRD_CXX_FLAGS]) + fi diff --git a/ext/pdo_firebird/config.w32 b/ext/pdo_firebird/config.w32 index 634ac4c8e984c..f7cd342120ff2 100644 --- a/ext/pdo_firebird/config.w32 +++ b/ext/pdo_firebird/config.w32 @@ -10,7 +10,8 @@ if (PHP_PDO_FIREBIRD != "no") { PHP_PHP_BUILD + "\\include\\interbase;" + PHP_PHP_BUILD + "\\interbase\\include;" + PHP_PDO_FIREBIRD) ) { - EXTENSION("pdo_firebird", "pdo_firebird.c firebird_driver.c firebird_statement.c"); + EXTENSION("pdo_firebird", "pdo_firebird.c firebird_driver.c firebird_statement.c pdo_firebird_utils.cpp"); + ADD_FLAG("CFLAGS_PDO_FIREBIRD", "/EHsc"); } else { WARNING("pdo_firebird not enabled; libraries and headers not found"); } diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c index ddb7db9b15b2b..a044cd6400519 100644 --- a/ext/pdo_firebird/firebird_driver.c +++ b/ext/pdo_firebird/firebird_driver.c @@ -30,6 +30,7 @@ #include "ext/pdo/php_pdo_driver.h" #include "php_pdo_firebird.h" #include "php_pdo_firebird_int.h" +#include "pdo_firebird_utils.h" static int php_firebird_alloc_prepare_stmt(pdo_dbh_t*, const zend_string*, XSQLDA*, isc_stmt_handle*, HashTable*); @@ -462,14 +463,13 @@ 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_data_type(XSQLDA* sqlda) +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. In any case, at least the first three data types */ - /* can only be mapped to strings. The last two too, but for them it is potentially possible to set */ - /* the display format, as is done for TIMESTAMP. This function allows you to ensure minimal performance */ - /* of queries if they contain columns and parameters of the above types. */ + /* 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; @@ -485,7 +485,7 @@ static void set_coercing_data_type(XSQLDA* sqlda) break; case SQL_DEC16: - var->sqltype = SQL_VARYING + nullable; + var->sqltype = SQL_VARYING + nullable; var->sqllen = 24; break; @@ -503,8 +503,65 @@ static void set_coercing_data_type(XSQLDA* sqlda) 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. */ + /* These data types include DECFLOAT(16), DECFLOAT(34), INT128 (NUMERIC/DECIMAL(38, x)). */ + /* In any case, at this data types can only be mapped to strings. */ + /* This function allows you to ensure minimal performance of queries if they contain columns of the above types. */ + unsigned int i; + short dtype; + short nullable; + XSQLVAR* var; + unsigned fb_client_version = fb_get_client_version(); + unsigned fb_client_major_version = (fb_client_version >> 8) & 0xFF; + 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: + if (fb_client_major_version < 4) { + /* If the client version is below 4.0, then it is impossible to handle time zones natively, */ + /* so we convert these types to a string. */ + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 58; + } + break; + + case SQL_TIME_TZ: + if (fb_client_major_version < 4) { + /* If the client version is below 4.0, then it is impossible to handle time zones natively, */ + /* so we convert these types to a string. */ + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 46; + } + break; + default: - break; + break; } } } @@ -657,7 +714,7 @@ static bool firebird_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, /* {{{ */ #if FB_API_VER >= 40 /* set coercing a data type */ - set_coercing_data_type(&S->out_sqlda); + set_coercing_output_data_types(&S->out_sqlda); #endif /* allocate the input descriptors */ @@ -676,7 +733,7 @@ static bool firebird_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, /* {{{ */ #if FB_API_VER >= 40 /* set coercing a data type */ - set_coercing_data_type(S->in_sqlda); + set_coercing_input_data_types(S->in_sqlda); #endif } @@ -1245,7 +1302,8 @@ static int pdo_firebird_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) ZVAL_STRING(val, tmp); return 1; } - return -1; + /* TODO Check this is correct? */ + ZEND_FALLTHROUGH; case PDO_ATTR_FETCH_TABLE_NAMES: ZVAL_BOOL(val, H->fetch_table_names); diff --git a/ext/pdo_firebird/firebird_statement.c b/ext/pdo_firebird/firebird_statement.c index 7cc3236563ce7..b2a2a34322df2 100644 --- a/ext/pdo_firebird/firebird_statement.c +++ b/ext/pdo_firebird/firebird_statement.c @@ -25,6 +25,7 @@ #include "ext/pdo/php_pdo_driver.h" #include "php_pdo_firebird.h" #include "php_pdo_firebird_int.h" +#include "pdo_firebird_utils.h" #include @@ -64,6 +65,78 @@ static zend_always_inline ISC_QUAD php_get_isc_quad_from_sqldata(const ISC_SCHAR READ_AND_RETURN_USING_MEMCPY(ISC_QUAD, sqldata); } +#if FB_API_VER >= 40 + +static zend_always_inline ISC_TIME_TZ php_get_isc_time_tz_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_TIME_TZ, sqldata); +} + +static zend_always_inline ISC_TIMESTAMP_TZ php_get_isc_timestamp_tz_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_TIMESTAMP_TZ, sqldata); +} + +/* fetch formatted time with time zone */ +static int get_formatted_time_tz(pdo_stmt_t *stmt, const ISC_TIME_TZ* timeTz, zval *result) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + unsigned hours = 0, minutes = 0, seconds = 0, fractions = 0; + char timeZoneBuffer[40] = {0}; + char *fmt; + struct tm t; + ISC_TIME time; + char timeBuf[80] = {0}; + char timeTzBuf[124] = {0}; + if (fb_decode_time_tz(S->H->isc_status, timeTz, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer)) { + return 1; + } + time = fb_encode_time(hours, minutes, seconds, fractions); + isc_decode_sql_time(&time, &t); + fmt = S->H->time_format ? S->H->time_format : PDO_FB_DEF_TIME_FMT; + + size_t len = strftime(timeBuf, sizeof(timeBuf), fmt, &t); + if (len == 0) { + return 1; + } + + size_t time_tz_len = sprintf(timeTzBuf, "%s %s", timeBuf, timeZoneBuffer); + ZVAL_STRINGL(result, timeTzBuf, time_tz_len); + return 0; +} + +/* fetch formatted timestamp with time zone */ +static int get_formatted_timestamp_tz(pdo_stmt_t *stmt, const ISC_TIMESTAMP_TZ* timestampTz, zval *result) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + unsigned year, month, day, hours, minutes, seconds, fractions; + char timeZoneBuffer[40] = {0}; + char *fmt; + struct tm t; + ISC_TIMESTAMP ts; + char timestampBuf[80] = {0}; + char timestampTzBuf[124] = {0}; + if (fb_decode_timestamp_tz(S->H->isc_status, timestampTz, &year, &month, &day, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer)) { + return 1; + } + ts.timestamp_date = fb_encode_date(year, month, day); + ts.timestamp_time = fb_encode_time(hours, minutes, seconds, fractions); + isc_decode_timestamp(&ts, &t); + + fmt = S->H->timestamp_format ? S->H->timestamp_format : PDO_FB_DEF_TIMESTAMP_FMT; + + size_t len = strftime(timestampBuf, sizeof(timestampBuf), fmt, &t); + if (len == 0) { + return 1; + } + + size_t timestamp_tz_len = sprintf(timestampTzBuf, "%s %s", timestampBuf, timeZoneBuffer); + ZVAL_STRINGL(result, timestampTzBuf, timestamp_tz_len); + return 0; +} + +#endif + /* free the allocated space for passing field values to the db and back */ static void php_firebird_free_sqlda(XSQLDA const *sqlda) /* {{{ */ { @@ -494,6 +567,16 @@ static int pdo_firebird_stmt_get_col( size_t len = strftime(buf, sizeof(buf), fmt, &t); ZVAL_STRINGL(result, buf, len); break; +#if FB_API_VER >= 40 + case SQL_TIME_TZ: { + ISC_TIME_TZ time = php_get_isc_time_tz_from_sqldata(var->sqldata); + return get_formatted_time_tz(stmt, &time, result); + } + case SQL_TIMESTAMP_TZ: { + ISC_TIMESTAMP_TZ ts = php_get_isc_timestamp_tz_from_sqldata(var->sqldata); + return get_formatted_timestamp_tz(stmt, &ts, result); + } +#endif case SQL_BLOB: { ISC_QUAD quad = php_get_isc_quad_from_sqldata(var->sqldata); return php_firebird_fetch_blob(stmt, colno, result, &quad); diff --git a/ext/pdo_firebird/pdo_firebird_utils.cpp b/ext/pdo_firebird/pdo_firebird_utils.cpp new file mode 100644 index 0000000000000..1ec10e0a2a082 --- /dev/null +++ b/ext/pdo_firebird/pdo_firebird_utils.cpp @@ -0,0 +1,92 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 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: | + | https://www.php.net/license/3_01.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: Simonov Denis | + +----------------------------------------------------------------------+ +*/ + +#include "pdo_firebird_utils.h" +#include +#include + +static void fb_copy_status(const ISC_STATUS* from, ISC_STATUS* to, size_t maxLength) +{ + for(size_t i=0; i < maxLength; ++i) { + memcpy(to + i, from + i, sizeof(ISC_STATUS)); + if (from[i] == isc_arg_end) { + break; + } + } +} + +/* Returns the client version. 0 bytes are minor version, 1 bytes are major version. */ +extern "C" unsigned fb_get_client_version(void) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + return util->getClientVersion(); +} + +extern "C" ISC_TIME fb_encode_time(unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + return util->encodeTime(hours, minutes, seconds, fractions); +} + +extern "C" ISC_DATE fb_encode_date(unsigned year, unsigned month, unsigned day) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + return util->encodeDate(year, month, day); +} + +#if FB_API_VER >= 40 + +/* Decodes a time with time zone into its time components. */ +extern "C" ISC_STATUS fb_decode_time_tz(ISC_STATUS* isc_status, const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + Firebird::IStatus* status = master->getStatus(); + Firebird::CheckStatusWrapper st(status); + util->decodeTimeTz(&st, timeTz, hours, minutes, seconds, fractions, + timeZoneBufferLength, timeZoneBuffer); + if (st.hasData()) { + fb_copy_status((const ISC_STATUS*)st.getErrors(), isc_status, 20); + } + status->dispose(); + return isc_status[1]; +} + +/* Decodes a timestamp with time zone into its date and time components */ +extern "C" ISC_STATUS fb_decode_timestamp_tz(ISC_STATUS* isc_status, const ISC_TIMESTAMP_TZ* timestampTz, + unsigned* year, unsigned* month, unsigned* day, + unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + Firebird::IStatus* status = master->getStatus(); + Firebird::CheckStatusWrapper st(status); + util->decodeTimeStampTz(&st, timestampTz, year, month, day, + hours, minutes, seconds, fractions, + timeZoneBufferLength, timeZoneBuffer); + if (st.hasData()) { + fb_copy_status((const ISC_STATUS*)st.getErrors(), isc_status, 20); + } + status->dispose(); + return isc_status[1]; +} + +#endif diff --git a/ext/pdo_firebird/pdo_firebird_utils.h b/ext/pdo_firebird/pdo_firebird_utils.h new file mode 100644 index 0000000000000..9fe4e2eca5948 --- /dev/null +++ b/ext/pdo_firebird/pdo_firebird_utils.h @@ -0,0 +1,48 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 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: | + | https://www.php.net/license/3_01.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: Simonov Denis | + +----------------------------------------------------------------------+ +*/ + +#ifndef PDO_FIREBIRD_UTILS_H +#define PDO_FIREBIRD_UTILS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +unsigned fb_get_client_version(void); + +ISC_TIME fb_encode_time(unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions); + +ISC_DATE fb_encode_date(unsigned year, unsigned month, unsigned day); + +#if FB_API_VER >= 40 + +ISC_STATUS fb_decode_time_tz(ISC_STATUS* isc_status, const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer); + +ISC_STATUS fb_decode_timestamp_tz(ISC_STATUS* isc_status, const ISC_TIMESTAMP_TZ* timestampTz, + unsigned* year, unsigned* month, unsigned* day, + unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* PDO_FIREBIRD_UTILS_H */ diff --git a/ext/pdo_firebird/tests/fb4_datatypes.phpt b/ext/pdo_firebird/tests/fb4_datatypes.phpt index 3ae452d5eb4e4..02bde1e7b2c25 100644 --- a/ext/pdo_firebird/tests/fb4_datatypes.phpt +++ b/ext/pdo_firebird/tests/fb4_datatypes.phpt @@ -47,9 +47,9 @@ echo "\ndone\n"; "N": "123.97", "N2": "123.97", "TS": "2024-05-04 12:59:34", - "TS_TZ": "%s 12:59:34.2390 Europe\/Moscow", + "TS_TZ": "2024-05-04 12:59:34 Europe\/Moscow", "T": "12:59:34", - "T_TZ": "12:59:34.2390 Europe\/Moscow", + "T_TZ": "12:59:34 Europe\/Moscow", "DF16": "1.128", "DF34": "1.128" } diff --git a/ext/pdo_firebird/tests/fb4_datatypes_params.phpt b/ext/pdo_firebird/tests/fb4_datatypes_params.phpt index f1b4827b9e724..adf049560d3ac 100644 --- a/ext/pdo_firebird/tests/fb4_datatypes_params.phpt +++ b/ext/pdo_firebird/tests/fb4_datatypes_params.phpt @@ -42,8 +42,8 @@ echo "\ndone\n"; "I128": "12", "N1": "12.34", "N2": "12.34", - "TS_TZ": "%s 12:59:34.2390 Europe\/Moscow", - "T_TZ": "12:59:00.0000 Europe\/Moscow", + "TS_TZ": "2024-05-04 12:59:34 Europe\/Moscow", + "T_TZ": "12:59:00 Europe\/Moscow", "DF16": "12.34", "DF34": "12.34" }