pdo_firebird: Formatting time zone types

As a follow-up to the commit which introduced support for Firebird 4.0+
data types[1], we add support for formats for types with time zones.

Since this uses the newer Firebird C++ API, pdo_firebird now requires a
C++ compiler to be built.

[1] <https://github.com/php/php-src/pull/14897>

Co-authored-by: Christoph M. Becker <cmbecker69@gmx.de>

Closes GH-15230.
This commit is contained in:
Simonov Denis 2024-08-12 18:59:12 +02:00 committed by Christoph M. Becker
parent 5478d4b2c1
commit 225034dbbc
No known key found for this signature in database
GPG key ID: D66C9593118BCCB6
10 changed files with 318 additions and 16 deletions

3
NEWS
View file

@ -50,6 +50,9 @@ PHP NEWS
deprecated. As the MYSQLI_STORE_RESULT_COPY_DATA constant was only used in
conjunction with this function it has also been deprecated. (Girgias)
- PDO_Firebird:
. Support proper formatting of time zone types. (sim1984)
- PHPDBG:
. array out of bounds, stack overflow handled for segfault handler on windows.
(David Carlier)

View file

@ -121,6 +121,8 @@ PHP 8.4 UPGRADE NOTES
have been changed to set value as a bool.
- PDO_FIREBIRD:
. Since some Firebird C++ APIs are used now, this extension requires a C++
compiler to be built.
. getAttribute, ATTR_AUTOCOMMIT has been changed to get the value as a bool.
- PDO_MYSQL:

View file

@ -45,9 +45,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

View file

@ -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");
}

View file

@ -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;
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;
}
}
}
@ -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);

View file

@ -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 <time.h>
@ -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);

View file

@ -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 <sim-mail@list.ru> |
+----------------------------------------------------------------------+
*/
#include "pdo_firebird_utils.h"
#include <firebird/Interface.h>
#include <cstring>
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

View file

@ -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 <sim-mail@list.ru> |
+----------------------------------------------------------------------+
*/
#ifndef PDO_FIREBIRD_UTILS_H
#define PDO_FIREBIRD_UTILS_H
#include <ibase.h>
#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 */

View file

@ -54,9 +54,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"
}

View file

@ -49,8 +49,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"
}