Skip to content

Commit 225034d

Browse files
sim1984cmb69
authored andcommitted
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] <php#14897> Co-authored-by: Christoph M. Becker <[email protected]> Closes phpGH-15230.
1 parent 5478d4b commit 225034d

10 files changed

+318
-16
lines changed

NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ PHP NEWS
5050
deprecated. As the MYSQLI_STORE_RESULT_COPY_DATA constant was only used in
5151
conjunction with this function it has also been deprecated. (Girgias)
5252

53+
- PDO_Firebird:
54+
. Support proper formatting of time zone types. (sim1984)
55+
5356
- PHPDBG:
5457
. array out of bounds, stack overflow handled for segfault handler on windows.
5558
(David Carlier)

UPGRADING

+2
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ PHP 8.4 UPGRADE NOTES
121121
have been changed to set value as a bool.
122122

123123
- PDO_FIREBIRD:
124+
. Since some Firebird C++ APIs are used now, this extension requires a C++
125+
compiler to be built.
124126
. getAttribute, ATTR_AUTOCOMMIT has been changed to get the value as a bool.
125127

126128
- PDO_MYSQL:

ext/pdo_firebird/config.m4

+16-1
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,24 @@ if test "$PHP_PDO_FIREBIRD" != "no"; then
4545

4646
PHP_CHECK_PDO_INCLUDES
4747

48+
49+
PHP_PDO_FIREBIRD_COMMON_FLAGS=""
4850
PHP_NEW_EXTENSION([pdo_firebird],
4951
[pdo_firebird.c firebird_driver.c firebird_statement.c],
50-
[$ext_shared])
52+
[$ext_shared],,
53+
[$PHP_PDO_FIREBIRD_COMMON_FLAGS],
54+
[cxx])
5155
PHP_SUBST([PDO_FIREBIRD_SHARED_LIBADD])
5256
PHP_ADD_EXTENSION_DEP(pdo_firebird, pdo)
57+
58+
PHP_PDO_FIREBIRD_CXX_SOURCES="pdo_firebird_utils.cpp"
59+
60+
PHP_REQUIRE_CXX()
61+
PHP_CXX_COMPILE_STDCXX(11, mandatory, PHP_PDO_FIREBIRD_STDCXX)
62+
PHP_PDO_FIREBIRD_CXX_FLAGS="$PHP_PDO_FIREBIRD_COMMON_FLAGS $PHP_PDO_FIREBIRD_STDCXX"
63+
64+
PHP_ADD_SOURCES([$ext_dir],
65+
[$PHP_PDO_FIREBIRD_CXX_SOURCES],
66+
[$PHP_PDO_FIREBIRD_CXX_FLAGS])
67+
5368
fi

ext/pdo_firebird/config.w32

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ if (PHP_PDO_FIREBIRD != "no") {
1010
PHP_PHP_BUILD + "\\include\\interbase;" + PHP_PHP_BUILD + "\\interbase\\include;" + PHP_PDO_FIREBIRD)
1111
) {
1212

13-
EXTENSION("pdo_firebird", "pdo_firebird.c firebird_driver.c firebird_statement.c");
13+
EXTENSION("pdo_firebird", "pdo_firebird.c firebird_driver.c firebird_statement.c pdo_firebird_utils.cpp");
14+
ADD_FLAG("CFLAGS_PDO_FIREBIRD", "/EHsc");
1415
} else {
1516
WARNING("pdo_firebird not enabled; libraries and headers not found");
1617
}

ext/pdo_firebird/firebird_driver.c

+68-10
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "ext/pdo/php_pdo_driver.h"
3131
#include "php_pdo_firebird.h"
3232
#include "php_pdo_firebird_int.h"
33+
#include "pdo_firebird_utils.h"
3334

3435
static int php_firebird_alloc_prepare_stmt(pdo_dbh_t*, const zend_string*, XSQLDA*, isc_stmt_handle*,
3536
HashTable*);
@@ -462,14 +463,13 @@ static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTa
462463

463464
#if FB_API_VER >= 40
464465
/* set coercing a data type */
465-
static void set_coercing_data_type(XSQLDA* sqlda)
466+
static void set_coercing_input_data_types(XSQLDA* sqlda)
466467
{
467468
/* Data types introduced in Firebird 4.0 are difficult to process using the Firebird Legacy API. */
468469
/* These data types include DECFLOAT(16), DECFLOAT(34), INT128 (NUMERIC/DECIMAL(38, x)), */
469-
/* TIMESTAMP WITH TIME ZONE, and TIME WITH TIME ZONE. In any case, at least the first three data types */
470-
/* can only be mapped to strings. The last two too, but for them it is potentially possible to set */
471-
/* the display format, as is done for TIMESTAMP. This function allows you to ensure minimal performance */
472-
/* of queries if they contain columns and parameters of the above types. */
470+
/* TIMESTAMP WITH TIME ZONE, and TIME WITH TIME ZONE. */
471+
/* This function allows you to ensure minimal performance */
472+
/* of queries if they contain parameters of the above types. */
473473
unsigned int i;
474474
short dtype;
475475
short nullable;
@@ -485,7 +485,7 @@ static void set_coercing_data_type(XSQLDA* sqlda)
485485
break;
486486

487487
case SQL_DEC16:
488-
var->sqltype = SQL_VARYING + nullable;
488+
var->sqltype = SQL_VARYING + nullable;
489489
var->sqllen = 24;
490490
break;
491491

@@ -503,8 +503,65 @@ static void set_coercing_data_type(XSQLDA* sqlda)
503503
var->sqltype = SQL_VARYING + nullable;
504504
var->sqllen = 46;
505505
break;
506+
507+
default:
508+
break;
509+
}
510+
}
511+
}
512+
513+
static void set_coercing_output_data_types(XSQLDA* sqlda)
514+
{
515+
/* Data types introduced in Firebird 4.0 are difficult to process using the Firebird Legacy API. */
516+
/* These data types include DECFLOAT(16), DECFLOAT(34), INT128 (NUMERIC/DECIMAL(38, x)). */
517+
/* In any case, at this data types can only be mapped to strings. */
518+
/* This function allows you to ensure minimal performance of queries if they contain columns of the above types. */
519+
unsigned int i;
520+
short dtype;
521+
short nullable;
522+
XSQLVAR* var;
523+
unsigned fb_client_version = fb_get_client_version();
524+
unsigned fb_client_major_version = (fb_client_version >> 8) & 0xFF;
525+
for (i=0, var = sqlda->sqlvar; i < sqlda->sqld; i++, var++) {
526+
dtype = (var->sqltype & ~1); /* drop flag bit */
527+
nullable = (var->sqltype & 1);
528+
switch(dtype) {
529+
case SQL_INT128:
530+
var->sqltype = SQL_VARYING + nullable;
531+
var->sqllen = 46;
532+
var->sqlscale = 0;
533+
break;
534+
535+
case SQL_DEC16:
536+
var->sqltype = SQL_VARYING + nullable;
537+
var->sqllen = 24;
538+
break;
539+
540+
case SQL_DEC34:
541+
var->sqltype = SQL_VARYING + nullable;
542+
var->sqllen = 43;
543+
break;
544+
545+
case SQL_TIMESTAMP_TZ:
546+
if (fb_client_major_version < 4) {
547+
/* If the client version is below 4.0, then it is impossible to handle time zones natively, */
548+
/* so we convert these types to a string. */
549+
var->sqltype = SQL_VARYING + nullable;
550+
var->sqllen = 58;
551+
}
552+
break;
553+
554+
case SQL_TIME_TZ:
555+
if (fb_client_major_version < 4) {
556+
/* If the client version is below 4.0, then it is impossible to handle time zones natively, */
557+
/* so we convert these types to a string. */
558+
var->sqltype = SQL_VARYING + nullable;
559+
var->sqllen = 46;
560+
}
561+
break;
562+
506563
default:
507-
break;
564+
break;
508565
}
509566
}
510567
}
@@ -657,7 +714,7 @@ static bool firebird_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, /* {{{ */
657714

658715
#if FB_API_VER >= 40
659716
/* set coercing a data type */
660-
set_coercing_data_type(&S->out_sqlda);
717+
set_coercing_output_data_types(&S->out_sqlda);
661718
#endif
662719

663720
/* allocate the input descriptors */
@@ -676,7 +733,7 @@ static bool firebird_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, /* {{{ */
676733

677734
#if FB_API_VER >= 40
678735
/* set coercing a data type */
679-
set_coercing_data_type(S->in_sqlda);
736+
set_coercing_input_data_types(S->in_sqlda);
680737
#endif
681738
}
682739

@@ -1245,7 +1302,8 @@ static int pdo_firebird_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val)
12451302
ZVAL_STRING(val, tmp);
12461303
return 1;
12471304
}
1248-
return -1;
1305+
/* TODO Check this is correct? */
1306+
ZEND_FALLTHROUGH;
12491307

12501308
case PDO_ATTR_FETCH_TABLE_NAMES:
12511309
ZVAL_BOOL(val, H->fetch_table_names);

ext/pdo_firebird/firebird_statement.c

+83
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "ext/pdo/php_pdo_driver.h"
2626
#include "php_pdo_firebird.h"
2727
#include "php_pdo_firebird_int.h"
28+
#include "pdo_firebird_utils.h"
2829

2930
#include <time.h>
3031

@@ -64,6 +65,78 @@ static zend_always_inline ISC_QUAD php_get_isc_quad_from_sqldata(const ISC_SCHAR
6465
READ_AND_RETURN_USING_MEMCPY(ISC_QUAD, sqldata);
6566
}
6667

68+
#if FB_API_VER >= 40
69+
70+
static zend_always_inline ISC_TIME_TZ php_get_isc_time_tz_from_sqldata(const ISC_SCHAR *sqldata)
71+
{
72+
READ_AND_RETURN_USING_MEMCPY(ISC_TIME_TZ, sqldata);
73+
}
74+
75+
static zend_always_inline ISC_TIMESTAMP_TZ php_get_isc_timestamp_tz_from_sqldata(const ISC_SCHAR *sqldata)
76+
{
77+
READ_AND_RETURN_USING_MEMCPY(ISC_TIMESTAMP_TZ, sqldata);
78+
}
79+
80+
/* fetch formatted time with time zone */
81+
static int get_formatted_time_tz(pdo_stmt_t *stmt, const ISC_TIME_TZ* timeTz, zval *result)
82+
{
83+
pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data;
84+
unsigned hours = 0, minutes = 0, seconds = 0, fractions = 0;
85+
char timeZoneBuffer[40] = {0};
86+
char *fmt;
87+
struct tm t;
88+
ISC_TIME time;
89+
char timeBuf[80] = {0};
90+
char timeTzBuf[124] = {0};
91+
if (fb_decode_time_tz(S->H->isc_status, timeTz, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer)) {
92+
return 1;
93+
}
94+
time = fb_encode_time(hours, minutes, seconds, fractions);
95+
isc_decode_sql_time(&time, &t);
96+
fmt = S->H->time_format ? S->H->time_format : PDO_FB_DEF_TIME_FMT;
97+
98+
size_t len = strftime(timeBuf, sizeof(timeBuf), fmt, &t);
99+
if (len == 0) {
100+
return 1;
101+
}
102+
103+
size_t time_tz_len = sprintf(timeTzBuf, "%s %s", timeBuf, timeZoneBuffer);
104+
ZVAL_STRINGL(result, timeTzBuf, time_tz_len);
105+
return 0;
106+
}
107+
108+
/* fetch formatted timestamp with time zone */
109+
static int get_formatted_timestamp_tz(pdo_stmt_t *stmt, const ISC_TIMESTAMP_TZ* timestampTz, zval *result)
110+
{
111+
pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data;
112+
unsigned year, month, day, hours, minutes, seconds, fractions;
113+
char timeZoneBuffer[40] = {0};
114+
char *fmt;
115+
struct tm t;
116+
ISC_TIMESTAMP ts;
117+
char timestampBuf[80] = {0};
118+
char timestampTzBuf[124] = {0};
119+
if (fb_decode_timestamp_tz(S->H->isc_status, timestampTz, &year, &month, &day, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer)) {
120+
return 1;
121+
}
122+
ts.timestamp_date = fb_encode_date(year, month, day);
123+
ts.timestamp_time = fb_encode_time(hours, minutes, seconds, fractions);
124+
isc_decode_timestamp(&ts, &t);
125+
126+
fmt = S->H->timestamp_format ? S->H->timestamp_format : PDO_FB_DEF_TIMESTAMP_FMT;
127+
128+
size_t len = strftime(timestampBuf, sizeof(timestampBuf), fmt, &t);
129+
if (len == 0) {
130+
return 1;
131+
}
132+
133+
size_t timestamp_tz_len = sprintf(timestampTzBuf, "%s %s", timestampBuf, timeZoneBuffer);
134+
ZVAL_STRINGL(result, timestampTzBuf, timestamp_tz_len);
135+
return 0;
136+
}
137+
138+
#endif
139+
67140
/* free the allocated space for passing field values to the db and back */
68141
static void php_firebird_free_sqlda(XSQLDA const *sqlda) /* {{{ */
69142
{
@@ -494,6 +567,16 @@ static int pdo_firebird_stmt_get_col(
494567
size_t len = strftime(buf, sizeof(buf), fmt, &t);
495568
ZVAL_STRINGL(result, buf, len);
496569
break;
570+
#if FB_API_VER >= 40
571+
case SQL_TIME_TZ: {
572+
ISC_TIME_TZ time = php_get_isc_time_tz_from_sqldata(var->sqldata);
573+
return get_formatted_time_tz(stmt, &time, result);
574+
}
575+
case SQL_TIMESTAMP_TZ: {
576+
ISC_TIMESTAMP_TZ ts = php_get_isc_timestamp_tz_from_sqldata(var->sqldata);
577+
return get_formatted_timestamp_tz(stmt, &ts, result);
578+
}
579+
#endif
497580
case SQL_BLOB: {
498581
ISC_QUAD quad = php_get_isc_quad_from_sqldata(var->sqldata);
499582
return php_firebird_fetch_blob(stmt, colno, result, &quad);
+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) The PHP Group |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available through the world-wide-web at the following url: |
8+
| https://www.php.net/license/3_01.txt |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| [email protected] so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: Simonov Denis <[email protected]> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#include "pdo_firebird_utils.h"
18+
#include <firebird/Interface.h>
19+
#include <cstring>
20+
21+
static void fb_copy_status(const ISC_STATUS* from, ISC_STATUS* to, size_t maxLength)
22+
{
23+
for(size_t i=0; i < maxLength; ++i) {
24+
memcpy(to + i, from + i, sizeof(ISC_STATUS));
25+
if (from[i] == isc_arg_end) {
26+
break;
27+
}
28+
}
29+
}
30+
31+
/* Returns the client version. 0 bytes are minor version, 1 bytes are major version. */
32+
extern "C" unsigned fb_get_client_version(void)
33+
{
34+
Firebird::IMaster* master = Firebird::fb_get_master_interface();
35+
Firebird::IUtil* util = master->getUtilInterface();
36+
return util->getClientVersion();
37+
}
38+
39+
extern "C" ISC_TIME fb_encode_time(unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions)
40+
{
41+
Firebird::IMaster* master = Firebird::fb_get_master_interface();
42+
Firebird::IUtil* util = master->getUtilInterface();
43+
return util->encodeTime(hours, minutes, seconds, fractions);
44+
}
45+
46+
extern "C" ISC_DATE fb_encode_date(unsigned year, unsigned month, unsigned day)
47+
{
48+
Firebird::IMaster* master = Firebird::fb_get_master_interface();
49+
Firebird::IUtil* util = master->getUtilInterface();
50+
return util->encodeDate(year, month, day);
51+
}
52+
53+
#if FB_API_VER >= 40
54+
55+
/* Decodes a time with time zone into its time components. */
56+
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,
57+
unsigned timeZoneBufferLength, char* timeZoneBuffer)
58+
{
59+
Firebird::IMaster* master = Firebird::fb_get_master_interface();
60+
Firebird::IUtil* util = master->getUtilInterface();
61+
Firebird::IStatus* status = master->getStatus();
62+
Firebird::CheckStatusWrapper st(status);
63+
util->decodeTimeTz(&st, timeTz, hours, minutes, seconds, fractions,
64+
timeZoneBufferLength, timeZoneBuffer);
65+
if (st.hasData()) {
66+
fb_copy_status((const ISC_STATUS*)st.getErrors(), isc_status, 20);
67+
}
68+
status->dispose();
69+
return isc_status[1];
70+
}
71+
72+
/* Decodes a timestamp with time zone into its date and time components */
73+
extern "C" ISC_STATUS fb_decode_timestamp_tz(ISC_STATUS* isc_status, const ISC_TIMESTAMP_TZ* timestampTz,
74+
unsigned* year, unsigned* month, unsigned* day,
75+
unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions,
76+
unsigned timeZoneBufferLength, char* timeZoneBuffer)
77+
{
78+
Firebird::IMaster* master = Firebird::fb_get_master_interface();
79+
Firebird::IUtil* util = master->getUtilInterface();
80+
Firebird::IStatus* status = master->getStatus();
81+
Firebird::CheckStatusWrapper st(status);
82+
util->decodeTimeStampTz(&st, timestampTz, year, month, day,
83+
hours, minutes, seconds, fractions,
84+
timeZoneBufferLength, timeZoneBuffer);
85+
if (st.hasData()) {
86+
fb_copy_status((const ISC_STATUS*)st.getErrors(), isc_status, 20);
87+
}
88+
status->dispose();
89+
return isc_status[1];
90+
}
91+
92+
#endif

0 commit comments

Comments
 (0)