From 5dc432b6c8001cc6674581c671a204a426e8c840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Fri, 17 Oct 2025 23:01:04 +0300 Subject: [PATCH 01/66] Introduce error msg functions --- interbase.c | 23 +++++++++++++++++++++-- php_ibase_includes.h | 13 +++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/interbase.c b/interbase.c index 8ee97f9..6122d7b 100644 --- a/interbase.c +++ b/interbase.c @@ -1590,7 +1590,8 @@ PHP_FUNCTION(ibase_gen_id) } #if PHP_DEBUG -void fbp_dump_buffer(int len, const unsigned char *buffer){ +void fbp_dump_buffer(int len, const unsigned char *buffer) +{ int i; for (i = 0; i < len; i++) { if(buffer[i] < 32 || buffer[i] > 126) @@ -1603,13 +1604,31 @@ void fbp_dump_buffer(int len, const unsigned char *buffer){ } } -void fbp_dump_buffer_raw(int len, const unsigned char *buffer){ +void fbp_dump_buffer_raw(int len, const unsigned char *buffer) +{ int i; for (i = 0; i < len; i++) { php_printf("%c", buffer[i]); } } #endif + +void fbp_error_ex(long level, char *msg, ...) +{ + va_list ap; + char buf[1024] = {0}; + + va_start(ap, msg); + + /* vsnprintf NUL terminates the buf and writes at most n-1 chars+NUL */ + vsnprintf(buf, sizeof(buf), msg, ap); + va_end(ap); + + // IBG(sql_code) = -999; /* no SQL error */ + + php_error(level, "%s", buf); +} + /* }}} */ #endif /* HAVE_IBASE */ diff --git a/php_ibase_includes.h b/php_ibase_includes.h index 23c5930..8dc4c23 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -208,4 +208,17 @@ void fbp_dump_buffer(int len, const unsigned char *buffer); void fbp_dump_buffer_raw(int len, const unsigned char *buffer); #endif +void fbp_error_ex(long level, char *, ...) + PHP_ATTRIBUTE_FORMAT(printf,2,3); + +#ifdef PHP_WIN32 +#define fbp_fatal(msg, ...) fbp_error_ex(E_ERROR, msg " (%s:%d)\n", ## __VA_ARGS__, __FILE__, __LINE__) +#define fbp_warning(msg, ...) fbp_error_ex(E_WARNING, msg " (%s:%d)\n", ## __VA_ARGS__, __FILE__, __LINE__) +#define fbp_notice(msg, ...) fbp_error_ex(E_NOTICE, msg " (%s:%d)\n", ## __VA_ARGS__, __FILE__, __LINE__) +#else +#define fbp_fatal(msg, ...) fbp_error_ex(E_ERROR, msg " (%s:%d)\n" __VA_OPT__(,) __VA_ARGS__, __FILE__, __LINE__) +#define fbp_warning(msg, ...) fbp_error_ex(E_WARNING, msg " (%s:%d)\n" __VA_OPT__(,) __VA_ARGS__, __FILE__, __LINE__) +#define fbp_notice(msg, ...) fbp_error_ex(E_NOTICE, msg " (%s:%d)\n" __VA_OPT__(,) __VA_ARGS__, __FILE__, __LINE__) +#endif + #endif /* PHP_IBASE_INCLUDES_H */ From 160738e43ad792cd646c9933301d5a271fab8736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Sun, 19 Oct 2025 02:52:34 +0300 Subject: [PATCH 02/66] Pretty print --- interbase.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interbase.c b/interbase.c index 6122d7b..2cac1b3 100644 --- a/interbase.c +++ b/interbase.c @@ -1597,7 +1597,7 @@ void fbp_dump_buffer(int len, const unsigned char *buffer) if(buffer[i] < 32 || buffer[i] > 126) php_printf("0x%02x ", buffer[i]); else - php_printf("%c", buffer[i]); + php_printf(" [%c] ", buffer[i]); } if (i > 0) { php_printf("\n"); From 0997be3b89fd5b6c05729c66a2bbd926ff5cc367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Sun, 19 Oct 2025 20:21:14 +0300 Subject: [PATCH 03/66] Fix field name --- tests/long_names_001.phpt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/long_names_001.phpt b/tests/long_names_001.phpt index 85db77f..1ff71c3 100644 --- a/tests/long_names_001.phpt +++ b/tests/long_names_001.phpt @@ -50,16 +50,17 @@ function test_table(string $table){ ?> --EXPECT-- +int(63) Table:TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT array(2) { - ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"]=> + ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"]=> int(1) ["🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰"]=> int(2) } Table:😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂 array(2) { - ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"]=> + ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"]=> int(1) ["🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰"]=> int(2) From cebccfdf799b04407c38febd48f518442be29063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Sun, 19 Oct 2025 20:21:34 +0300 Subject: [PATCH 04/66] Pretty print --- interbase.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/interbase.c b/interbase.c index 2cac1b3..dbdd8ed 100644 --- a/interbase.c +++ b/interbase.c @@ -1598,10 +1598,9 @@ void fbp_dump_buffer(int len, const unsigned char *buffer) php_printf("0x%02x ", buffer[i]); else php_printf(" [%c] ", buffer[i]); + if(i % 16 == 15)php_printf("\n"); } - if (i > 0) { - php_printf("\n"); - } + if(i > 0)php_printf("\n"); } void fbp_dump_buffer_raw(int len, const unsigned char *buffer) From a6f3601c4b13898675d10e9f14b428fc6f53fa29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Wed, 22 Oct 2025 03:21:28 +0300 Subject: [PATCH 05/66] Increase metadata size --- php_ibase_includes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php_ibase_includes.h b/php_ibase_includes.h index 8dc4c23..1b60f13 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -33,7 +33,7 @@ #endif #ifndef METADATALENGTH -#define METADATALENGTH 68 +#define METADATALENGTH 63*4 #endif #define RESET_ERRMSG do { IBG(errmsg)[0] = '\0'; IBG(sql_code) = 0; } while (0) From d17269e6aebe43f40adc3d559846b6252e1436bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Wed, 22 Oct 2025 03:23:36 +0300 Subject: [PATCH 06/66] No need for non-const char* --- interbase.c | 4 ++-- php_ibase_includes.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interbase.c b/interbase.c index dbdd8ed..14390a1 100644 --- a/interbase.c +++ b/interbase.c @@ -524,7 +524,7 @@ void _php_ibase_error(void) /* {{{ */ /* }}} */ /* print php interbase module error and save it for ibase_errmsg() */ -void _php_ibase_module_error(char *msg, ...) /* {{{ */ +void _php_ibase_module_error(const char *msg, ...) /* {{{ */ { va_list ap; @@ -1612,7 +1612,7 @@ void fbp_dump_buffer_raw(int len, const unsigned char *buffer) } #endif -void fbp_error_ex(long level, char *msg, ...) +void fbp_error_ex(long level, const char *msg, ...) { va_list ap; char buf[1024] = {0}; diff --git a/php_ibase_includes.h b/php_ibase_includes.h index 1b60f13..89ed8a6 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -163,7 +163,7 @@ typedef void (*info_func_t)(char*); #endif void _php_ibase_error(void); -void _php_ibase_module_error(char *, ...) +void _php_ibase_module_error(const char *, ...) PHP_ATTRIBUTE_FORMAT(printf,1,2); /* determine if a resource is a link or transaction handle */ @@ -208,7 +208,7 @@ void fbp_dump_buffer(int len, const unsigned char *buffer); void fbp_dump_buffer_raw(int len, const unsigned char *buffer); #endif -void fbp_error_ex(long level, char *, ...) +void fbp_error_ex(long level, const char *, ...) PHP_ATTRIBUTE_FORMAT(printf,2,3); #ifdef PHP_WIN32 From 5724a366edb00333e7f74f806372f67caf3053b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Wed, 22 Oct 2025 03:23:52 +0300 Subject: [PATCH 07/66] Replace strlen() with sizeof() --- interbase.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interbase.c b/interbase.c index 14390a1..8b21966 100644 --- a/interbase.c +++ b/interbase.c @@ -949,8 +949,8 @@ int _php_ibase_attach_db(char **args, size_t *len, zend_long *largs, isc_db_hand #if FB_API_VER >= 40 // Do not handle directly INT128 or DECFLOAT, convert to VARCHAR at server instead - const char *compat = "int128 to varchar;decfloat to varchar"; - dpb_len = slprintf(dpb, buf_len, "%c%c%s", isc_dpb_set_bind, strlen(compat), compat); + const char compat[] = "int128 to varchar;decfloat to varchar"; + dpb_len = slprintf(dpb, buf_len, "%c%c%s", isc_dpb_set_bind, (char)(sizeof(compat) - 1), compat); dpb += dpb_len; buf_len -= dpb_len; #endif From e8d2470146c41a25685206d50741b67ff6348d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Wed, 22 Oct 2025 03:24:34 +0300 Subject: [PATCH 08/66] Quote fields --- tests/functions.inc | 10 +++++++--- tests/long_names_001.phpt | 8 ++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/functions.inc b/tests/functions.inc index 7fb6ab6..bbd06f5 100644 --- a/tests/functions.inc +++ b/tests/functions.inc @@ -83,10 +83,14 @@ function rand_number($len , $prec = -1, $sign = 1) return $n; } +function fields2sql_parts($fields) { + $fields_str = '"'.join('","', $fields).'"'; + $q_str = join(",", array_fill(0, count($fields), "?")); + return [$fields_str, $q_str]; +} + function array2sql_parts($data) { - $fields_str = join(",", array_keys($data)); - $q_str = join(",", array_fill(0, count($data), "?")); - return [$fields_str, $q_str, $data]; + return [...fields2sql_parts(array_keys($data)), $data]; } function dump_rows($q) { diff --git a/tests/long_names_001.phpt b/tests/long_names_001.phpt index 1ff71c3..9261982 100644 --- a/tests/long_names_001.phpt +++ b/tests/long_names_001.phpt @@ -20,11 +20,11 @@ function test_table(string $table){ $c = 0; $fields = [ - '"'.str_repeat("F", $MAX_LEN).'"', - '"'.str_repeat("🥰", $MAX_LEN).'"', + str_repeat("F", $MAX_LEN), + str_repeat("🥰", $MAX_LEN), ]; - $fields_str = join(" INTEGER, ", $fields)." INTEGER"; - $create_sql = sprintf('CREATE TABLE "%s" (%s)', $table, $fields_str); + $fields_str = join('" INTEGER,"', $fields); + $create_sql = sprintf('CREATE TABLE "%s" ("%s" INTEGER)', $table, $fields_str); if(ibase_query($create_sql)){ ibase_commit(); From a5e81aa32fe90b457f41f6c10fd71a555f85c6a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Wed, 22 Oct 2025 03:26:24 +0300 Subject: [PATCH 09/66] Add fb_get_sql_info() wrapper --- pdo_firebird_utils.cpp | 22 ++++++++++++++++++++++ pdo_firebird_utils.h | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/pdo_firebird_utils.cpp b/pdo_firebird_utils.cpp index 861fa14..3bc2402 100644 --- a/pdo_firebird_utils.cpp +++ b/pdo_firebird_utils.cpp @@ -81,4 +81,26 @@ extern "C" void fb_decode_timestamp_tz(const ISC_TIMESTAMP_TZ* timestampTz, timeZoneBufferLength, timeZoneBuffer); } +extern "C" int fb_get_sql_info(ISC_STATUS* st, isc_stmt_handle* stmt, + unsigned itemsLength, const unsigned char* items, + unsigned bufferLength, unsigned char* buffer) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::ThrowStatusWrapper status(master->getStatus()); + Firebird::IStatement* statement = NULL; + + if (fb_get_statement_interface(st, &statement, stmt)){ + // if (sv[0] == 1 && sv[1] > 0) + return st[1]; + } + + // TODO: check status; + statement->getInfo(&status, itemsLength, items, bufferLength, buffer); + + statement->release(); + statement = NULL; + + return 0; +} + #endif diff --git a/pdo_firebird_utils.h b/pdo_firebird_utils.h index 197b279..855e140 100644 --- a/pdo_firebird_utils.h +++ b/pdo_firebird_utils.h @@ -39,6 +39,10 @@ void fb_decode_timestamp_tz(const ISC_TIMESTAMP_TZ* timestampTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, unsigned timeZoneBufferLength, char* timeZoneBuffer); +int fb_get_sql_info(ISC_STATUS* st, isc_stmt_handle* stmt, + unsigned itemsLength, const unsigned char* items, + unsigned bufferLength, unsigned char* buffer); + #endif #ifdef __cplusplus From 0c0458afe2c845aab1456155affd36ee204c608e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Wed, 22 Oct 2025 21:55:36 +0300 Subject: [PATCH 10/66] Re-anable test 005.phpt on >= 5.0 --- tests/005.phpt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/005.phpt b/tests/005.phpt index 4cbff64..703b540 100644 --- a/tests/005.phpt +++ b/tests/005.phpt @@ -3,7 +3,6 @@ InterBase: transactions --SKIPIF-- = 5.0)print 'skip: FB >= 5.0'; ?> --FILE-- Date: Thu, 23 Oct 2025 16:08:30 +0300 Subject: [PATCH 11/66] Add optional transaction args --- tests/functions.inc | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/functions.inc b/tests/functions.inc index bbd06f5..a1b1dcc 100644 --- a/tests/functions.inc +++ b/tests/functions.inc @@ -99,15 +99,21 @@ function dump_rows($q) { } } -function dump_table_rows($table) { - dump_rows(ibase_query(sprintf('SELECT * FROM "%s"', $table))); +function dump_table_rows($table, $tr = null) { + if($tr)$args[] = $tr; + $args[] = sprintf('SELECT * FROM "%s"', $table); + + dump_rows(ibase_query(...$args)); } -function insert_into($table, $data) { +function insert_into($table, $data, $tr = null) { + if($tr) $args[] = $tr; [$fields_str, $q_str] = array2sql_parts($data); - return ibase_query(sprintf('INSERT INTO "%s" (%s) VALUES (%s)', + $args[] = sprintf('INSERT INTO "%s" (%s) VALUES (%s)', $table, $fields_str, $q_str - ), ...array_values($data)); + ); + + return ibase_query(...array_merge($args, array_values($data))); } /** @var float $v */ From 7def2e28a85a45c48a9664f9838feca4aa9188e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 23 Oct 2025 17:58:11 +0300 Subject: [PATCH 12/66] Introduce versioning schema and create IBASE_VER constant --- interbase.c | 5 +++-- php_interbase.h | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/interbase.c b/interbase.c index 8b21966..e7ef159 100644 --- a/interbase.c +++ b/interbase.c @@ -453,7 +453,7 @@ zend_module_entry ibase_module_entry = { NULL, PHP_RSHUTDOWN(ibase), PHP_MINFO(ibase), - PHP_INTERBASE_VERSION, + PHP_INTERBASE_VER_STR, PHP_MODULE_GLOBALS(ibase), PHP_GINIT(ibase), NULL, @@ -807,6 +807,7 @@ PHP_MINIT_FUNCTION(ibase) REGISTER_LONG_CONSTANT("IBASE_FETCH_BLOBS", PHP_IBASE_FETCH_BLOBS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IBASE_FETCH_ARRAYS", PHP_IBASE_FETCH_ARRAYS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IBASE_UNIXTIME", PHP_IBASE_UNIXTIME, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IBASE_VERS", PHP_INTERBASE_VER, CONST_PERSISTENT); /* transactions */ REGISTER_LONG_CONSTANT("IBASE_WRITE", PHP_IBASE_WRITE, CONST_PERSISTENT); @@ -880,7 +881,7 @@ PHP_MINFO_FUNCTION(ibase) "static"); #endif - php_info_print_table_row(2, "Interbase extension version", PHP_INTERBASE_VERSION); + php_info_print_table_row(2, "Interbase extension version", PHP_INTERBASE_VER_STR); #ifdef FB_API_VER snprintf( (s = tmp), sizeof(tmp), "Firebird API version %d", FB_API_VER); diff --git a/php_interbase.h b/php_interbase.h index 2715ca3..912e27c 100644 --- a/php_interbase.h +++ b/php_interbase.h @@ -30,9 +30,22 @@ extern zend_module_entry ibase_module_entry; #define phpext_interbase_ptr &ibase_module_entry -#include "php_version.h" -// Keep version in track with Firebird -#define PHP_INTERBASE_VERSION "5.0.3" +#include "ibase.h" + +#define TO_STRING_(x) #x +#define TO_STRING(x) TO_STRING_(x) + +#ifndef FB_API_VER + FATAL: FB_API_VER is not defined. Assumed very old, unsupported client library +#endif + +#define PHP_INTERBASE_VER_MAJOR 6 +#define PHP_INTERBASE_VER_MINOR 1 +#define PHP_INTERBASE_VER_REV 1 + +// Keep FB_API_VER two digit style +#define PHP_INTERBASE_VER PHP_INTERBASE_VER_MAJOR * 10 + PHP_INTERBASE_VER_MINOR +#define PHP_INTERBASE_VER_STR TO_STRING(PHP_INTERBASE_VER_MAJOR) "." TO_STRING(PHP_INTERBASE_VER_MINOR) "." TO_STRING(PHP_INTERBASE_VER_REV) PHP_MINIT_FUNCTION(ibase); PHP_RINIT_FUNCTION(ibase); From c1c62f5afcc530d3f9a9f57282dc7ad616504ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 23 Oct 2025 18:01:06 +0300 Subject: [PATCH 13/66] Typo --- interbase.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interbase.c b/interbase.c index e7ef159..e8cc19e 100644 --- a/interbase.c +++ b/interbase.c @@ -807,7 +807,7 @@ PHP_MINIT_FUNCTION(ibase) REGISTER_LONG_CONSTANT("IBASE_FETCH_BLOBS", PHP_IBASE_FETCH_BLOBS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IBASE_FETCH_ARRAYS", PHP_IBASE_FETCH_ARRAYS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IBASE_UNIXTIME", PHP_IBASE_UNIXTIME, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("IBASE_VERS", PHP_INTERBASE_VER, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IBASE_VER", PHP_INTERBASE_VER, CONST_PERSISTENT); /* transactions */ REGISTER_LONG_CONSTANT("IBASE_WRITE", PHP_IBASE_WRITE, CONST_PERSISTENT); From 13a3431f5c1216b4fb19b4f0acd50dd77db167f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Fri, 24 Oct 2025 02:27:07 +0300 Subject: [PATCH 14/66] Sync with long_names_001.phpt --- tests/long_names_002.phpt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/long_names_002.phpt b/tests/long_names_002.phpt index 2a18fca..e4cf9a5 100644 --- a/tests/long_names_002.phpt +++ b/tests/long_names_002.phpt @@ -19,11 +19,11 @@ function test_table(string $table){ $c = 0; $fields = [ - '"'.str_repeat("F", $MAX_LEN).'"', - '"'.str_repeat("🥰", intdiv($MAX_LEN, 4)).'ppp"', // 7*(utf 4 bytes)+3 padding + str_repeat("F", $MAX_LEN), + str_repeat("🥰", intdiv($MAX_LEN, 4)).'ppp', // 7*(utf 4 bytes)+3 padding ]; - $fields_str = join(" INTEGER, ", $fields)." INTEGER"; - $create_sql = sprintf('CREATE TABLE "%s" (%s)', $table, $fields_str); + $fields_str = join('" INTEGER,"', $fields); + $create_sql = sprintf('CREATE TABLE "%s" ("%s" INTEGER)', $table, $fields_str); if(ibase_query($create_sql)){ ibase_commit(); @@ -41,12 +41,23 @@ function test_table(string $table){ (function(){ global $MAX_LEN; + var_dump($MAX_LEN); + test_table(str_repeat('T', $MAX_LEN)); + test_table(str_repeat('😂', intdiv($MAX_LEN, 4))); })(); ?> --EXPECT-- +int(31) Table:TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT +array(2) { + ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"]=> + int(1) + ["🥰🥰🥰🥰🥰🥰🥰ppp"]=> + int(2) +} +Table:😂😂😂😂😂😂😂 array(2) { ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"]=> int(1) From cfa20d8d6c6aa8761e64789c34b5e06e6b38fc99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Fri, 24 Oct 2025 03:56:05 +0300 Subject: [PATCH 15/66] Add TODO --- tests/datatype_int128.phpt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/datatype_int128.phpt b/tests/datatype_int128.phpt index e75d03e..998109e 100644 --- a/tests/datatype_int128.phpt +++ b/tests/datatype_int128.phpt @@ -3,7 +3,9 @@ Check for data type INT128 (Firebird 4.0 or above) --SKIPIF-- = 4.0. Perhaps runtime +// client lib checking also needed. +skip_if_fb_lt(4.0); ?> --FILE-- Date: Fri, 24 Oct 2025 03:56:34 +0300 Subject: [PATCH 16/66] Check connection before drop --- tests/interbase.inc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/interbase.inc b/tests/interbase.inc index edfdfcb..19915c8 100644 --- a/tests/interbase.inc +++ b/tests/interbase.inc @@ -11,9 +11,13 @@ function init_db() { global $test_base, $user, $password; - $test_db = ibase_query(IBASE_CREATE, - sprintf("CREATE SCHEMA '%s' USER '%s' PASSWORD '%s' DEFAULT CHARACTER SET %s",$test_base, - $user, $password, ($charset = ini_get('ibase.default_charset')) ? $charset : 'NONE')); + $sql = sprintf("CREATE SCHEMA '%s' USER '%s' PASSWORD '%s' DEFAULT CHARACTER SET %s", + $test_base, $user, $password, + ($charset = ini_get('ibase.default_charset')) ? $charset : 'NONE' + ); + + $test_db = ibase_query(IBASE_CREATE, $sql); + $tr = ibase_trans($test_db); ibase_query($tr,"create table test1 (i integer, c varchar(100))"); ibase_commit_ret($tr); @@ -26,8 +30,9 @@ function cleanup_db() { global $test_base; - $r = ibase_connect($test_base); - ibase_drop_db($r); + if($r = ibase_connect($test_base)){ + ibase_drop_db($r); + } } function get_fb_version(): float From 77c0e41a4f62e78144d0b274b32a78911c5daaf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Fri, 24 Oct 2025 03:57:06 +0300 Subject: [PATCH 17/66] Set METADATALENGTH according to FB_API_VER --- php_ibase_includes.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/php_ibase_includes.h b/php_ibase_includes.h index 89ed8a6..cfc8305 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -33,7 +33,11 @@ #endif #ifndef METADATALENGTH -#define METADATALENGTH 63*4 +#if FB_API_VER >= 40 +# define METADATALENGTH 63*4 +#else +# define METADATALENGTH 31 +#endif #endif #define RESET_ERRMSG do { IBG(errmsg)[0] = '\0'; IBG(sql_code) = 0; } while (0) From 4f2edac9bf2ca75f06cbbab7f8032b961f4c534a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 18:42:44 +0200 Subject: [PATCH 18/66] Move ibase_query, ibase_array to php_ibase_includes.h; remove ibase_result, ibase_statement structs --- ibase_query.c | 106 ++----------------------------------------- php_ibase_includes.h | 44 ++++++++++++++++++ 2 files changed, 47 insertions(+), 103 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index bab016e..5138f20 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -48,71 +48,13 @@ #define FETCH_ROW 1 #define FETCH_ARRAY 2 -typedef struct { - ISC_ARRAY_DESC ar_desc; - ISC_LONG ar_size; /* size of entire array in bytes */ - unsigned short el_type, el_size; -} ibase_array; - -typedef struct { - ibase_db_link* link; - isc_stmt_handle stmt; -} ibase_statement; - -typedef struct { - ibase_db_link *link; - ibase_trans *trans; - isc_stmt_handle stmt; - zend_resource* stmt_res; - unsigned short type; - unsigned char has_more_rows, statement_type; - XSQLDA *out_sqlda; - ibase_array out_array[1]; /* last member */ -} ibase_result; - -typedef struct _ib_query { - ibase_db_link *link; - ibase_trans *trans; - zend_resource *result_res; - isc_stmt_handle stmt; - zend_resource *stmt_res; - XSQLDA *in_sqlda, *out_sqlda; - ibase_array *in_array, *out_array; - unsigned short in_array_cnt, out_array_cnt; - unsigned short dialect; - char statement_type; - char *query; - zend_resource *trans_res; -} ibase_query; - typedef struct { unsigned short vary_length; char vary_string[1]; } IBVARY; -/* sql variables union - * used for convert and binding input variables - */ -typedef struct { - union { -// Boolean data type exists since FB 3.0 -#ifdef SQL_BOOLEAN - FB_BOOLEAN bval; -#endif - short sval; - float fval; - ISC_LONG lval; - ISC_QUAD qval; - ISC_TIMESTAMP tsval; - ISC_DATE dtval; - ISC_TIME tmval; - } val; - short sqlind; -} BIND_BUF; - -static int le_statement, le_result, le_query; - -#define LE_RESULT "Firebird/InterBase result" +static int le_query; + #define LE_QUERY "Firebird/InterBase query" static void _php_ibase_free_xsqlda(XSQLDA *sqlda) /* {{{ */ @@ -135,45 +77,6 @@ static void _php_ibase_free_xsqlda(XSQLDA *sqlda) /* {{{ */ } /* }}} */ -static void _php_ibase_free_statement(zend_resource* rsrc) /* {{{ */ -{ - ibase_statement* ib_stmt = (ibase_statement*)rsrc->ptr; - - IBDEBUG("Freeing statement by dtor..."); - if (ib_stmt) { - if (ib_stmt->stmt) { - static char info[] = { isc_info_base_level, isc_info_end }; - char res_buf[8]; - IBDEBUG("Dropping statement handle (free_stmt_handle)..."); - /* Only free statement if db-connection is still open */ - if (SUCCESS == isc_database_info(IB_STATUS, &ib_stmt->link->handle, - sizeof(info), info, sizeof(res_buf), res_buf)) { - if (isc_dsql_free_statement(IB_STATUS, &ib_stmt->stmt, DSQL_drop)) { - _php_ibase_error(); - } - } - } - efree(ib_stmt); - } -} -/* }}} */ - -static void _php_ibase_free_result(zend_resource *rsrc) /* {{{ */ -{ - ibase_result *ib_result = (ibase_result *) rsrc->ptr; - - IBDEBUG("Freeing result by dtor..."); - if (ib_result) { - _php_ibase_free_xsqlda(ib_result->out_sqlda); - if (ib_result->stmt_res != NULL) { - zend_list_delete(ib_result->stmt_res); - ib_result->stmt_res = NULL; - } - efree(ib_result); - } -} -/* }}} */ - static void _php_ibase_free_query(ibase_query *ib_query) /* {{{ */ { IBDEBUG("Freeing query..."); @@ -218,10 +121,7 @@ static void php_ibase_free_query_rsrc(zend_resource *rsrc) /* {{{ */ void php_ibase_query_minit(INIT_FUNC_ARGS) /* {{{ */ { - le_statement = zend_register_list_destructors_ex(_php_ibase_free_statement, NULL, - "interbase statement", module_number); - le_result = zend_register_list_destructors_ex(_php_ibase_free_result, NULL, - "interbase result", module_number); + (void)type; le_query = zend_register_list_destructors_ex(php_ibase_free_query_rsrc, NULL, "interbase query", module_number); } diff --git a/php_ibase_includes.h b/php_ibase_includes.h index cfc8305..61d6528 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -117,6 +117,50 @@ typedef struct event { enum event_state { NEW, ACTIVE, DEAD } state; } ibase_event; +/* sql variables union + * used for convert and binding input variables + */ +typedef struct { + union { +// Boolean data type exists since FB 3.0 +#ifdef SQL_BOOLEAN + FB_BOOLEAN bval; +#endif + short sval; + float fval; + ISC_LONG lval; + ISC_QUAD qval; + ISC_TIMESTAMP tsval; + ISC_DATE dtval; + ISC_TIME tmval; + } val; + short nullind; +} BIND_BUF; + +typedef struct { + ISC_ARRAY_DESC ar_desc; + ISC_LONG ar_size; /* size of entire array in bytes */ + unsigned short el_type, el_size; +} ibase_array; + +typedef struct _ib_query { + ibase_db_link *link; + ibase_trans *trans; + zend_resource *res; + isc_stmt_handle stmt; + XSQLDA *in_sqlda, *out_sqlda; + ibase_array *in_array, *out_array; + unsigned short type, has_more_rows, is_open; + unsigned short in_array_cnt, out_array_cnt; + unsigned short dialect; + char *query; + ISC_UCHAR statement_type; + BIND_BUF *bind_buf; + ISC_SHORT *in_nullind, *out_nullind; + ISC_USHORT in_fields_count, out_fields_count; + HashTable *ht_aliases, *ht_ind; // Precomputed for ibase_fetch_*() +} ibase_query; + enum php_interbase_option { PHP_IBASE_DEFAULT = 0, PHP_IBASE_CREATE = 0, From b0bfc80bebe66c76f3a82fb6e2b52d7e863724e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 18:50:20 +0200 Subject: [PATCH 19/66] Introduce _php_ibase_get_fbclient_symbol --- interbase.c | 22 ++++++++++++++++++++++ php_ibase_includes.h | 2 ++ 2 files changed, 24 insertions(+) diff --git a/interbase.c b/interbase.c index e8cc19e..91ba8a4 100644 --- a/interbase.c +++ b/interbase.c @@ -783,6 +783,26 @@ PHP_INI_BEGIN() STD_PHP_INI_ENTRY_EX("ibase.default_lock_timeout", "0", PHP_INI_ALL, OnUpdateLongGEZero, default_lock_timeout, zend_ibase_globals, ibase_globals, display_link_numbers) PHP_INI_END() +#ifdef __GNUC__ +void* _php_ibase_get_fbclient_symbol(const char* sym) +{ + return dlsym(RTLD_DEFAULT, sym); +} +#elif defined(PHP_WIN32) +void* _php_ibase_get_fbclient_symbol(const char* sym) +{ + HMODULE l = GetModuleHandle("fbclient"); + + if (!l && !(l = GetModuleHandle("gds32"))) { + return NULL; + } + + return GetProcAddress(l, sym); +} +#else + static_assert(false, "TODO: implement dynamic symbol name lookup for your platform"); +#endif + static PHP_GINIT_FUNCTION(ibase) { #if defined(COMPILE_DL_INTERBASE) && defined(ZTS) @@ -791,6 +811,8 @@ static PHP_GINIT_FUNCTION(ibase) ibase_globals->num_persistent = ibase_globals->num_links = 0; ibase_globals->sql_code = *ibase_globals->errmsg = 0; ibase_globals->default_link = NULL; + ibase_globals->fb_get_master_interface = _php_ibase_get_fbclient_symbol("fb_get_master_interface"); + ibase_globals->fb_get_statement_interface = _php_ibase_get_fbclient_symbol("fb_get_statement_interface"); } PHP_MINIT_FUNCTION(ibase) diff --git a/php_ibase_includes.h b/php_ibase_includes.h index 61d6528..5da30cf 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -75,6 +75,8 @@ ZEND_BEGIN_MODULE_GLOBALS(ibase) zend_long sql_code; zend_long default_trans_params; zend_long default_lock_timeout; // only used togetger with trans_param IBASE_LOCK_TIMEOUT + void *fb_get_master_interface; + void *fb_get_statement_interface; ZEND_END_MODULE_GLOBALS(ibase) ZEND_EXTERN_MODULE_GLOBALS(ibase) From 6851f29ad8b7f18311839af6b5ccdbba0dde8a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 18:50:41 +0200 Subject: [PATCH 20/66] assert FB_API_VER --- php_interbase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php_interbase.h b/php_interbase.h index 912e27c..929c52c 100644 --- a/php_interbase.h +++ b/php_interbase.h @@ -36,7 +36,7 @@ extern zend_module_entry ibase_module_entry; #define TO_STRING(x) TO_STRING_(x) #ifndef FB_API_VER - FATAL: FB_API_VER is not defined. Assumed very old, unsupported client library + static_assert(false, "FATAL: FB_API_VER is not defined. Assumed very old, unsupported client library"); #endif #define PHP_INTERBASE_VER_MAJOR 6 From bb15fc6e2ce8820234a7ae382d08cb48f5dfbda8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 18:57:25 +0200 Subject: [PATCH 21/66] Introduce fb_insert_aliases --- ibase_query.c | 30 ++++++++++++++++++++++++++++++ pdo_firebird_utils.cpp | 38 +++++++++++++++++++++++++++----------- pdo_firebird_utils.h | 12 +++++------- php_ibase_includes.h | 10 ++++++++++ 4 files changed, 72 insertions(+), 18 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 5138f20..f609305 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1550,6 +1550,36 @@ static int _php_ibase_arr_zval(zval *ar_zval, char *data, zend_ulong data_size, } /* }}} */ +void _php_ibase_insert_alias(HashTable *ht, const char *alias, size_t alias_len) +{ + char buf[METADATALENGTH + 3 + 1]; // _00 + \0 + zval t2; + int i = 0; + char const *base = "FIELD"; /* use 'FIELD' if name is empty */ + size_t alias_len_w_suff = alias_len + 3; + + switch (*alias) { + void *p; + + default: + i = 1; + base = alias; + + while ((p = zend_symtable_str_find_ptr( + ht, alias, alias_len)) != NULL) { + + case '\0': + // TODO: i > 99? + snprintf(buf, sizeof(buf), "%s_%02d", base, i++); + alias = buf; + alias_len = alias_len_w_suff; + } + } + + ZVAL_NULL(&t2); + zend_hash_str_add_new(ht, alias, alias_len, &t2); +} + static void _php_ibase_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int fetch_type) /* {{{ */ { zval *result_arg; diff --git a/pdo_firebird_utils.cpp b/pdo_firebird_utils.cpp index 3bc2402..9d32c10 100644 --- a/pdo_firebird_utils.cpp +++ b/pdo_firebird_utils.cpp @@ -18,9 +18,11 @@ #if FB_API_VER >= 40 -#include "pdo_firebird_utils.h" #include #include +#include "php.h" +#include "pdo_firebird_utils.h" +#include "php_ibase_includes.h" /* Returns the client version. 0 bytes are minor version, 1 bytes are major version. */ extern "C" unsigned fb_get_client_version(void) @@ -81,24 +83,38 @@ extern "C" void fb_decode_timestamp_tz(const ISC_TIMESTAMP_TZ* timestampTz, timeZoneBufferLength, timeZoneBuffer); } -extern "C" int fb_get_sql_info(ISC_STATUS* st, isc_stmt_handle* stmt, - unsigned itemsLength, const unsigned char* items, - unsigned bufferLength, unsigned char* buffer) +extern "C" int fb_insert_aliases(ISC_STATUS* st, ibase_query *ib_query) { Firebird::IMaster* master = Firebird::fb_get_master_interface(); Firebird::ThrowStatusWrapper status(master->getStatus()); Firebird::IStatement* statement = NULL; + Firebird::IMessageMetadata* meta = NULL; + ISC_STATUS res; - if (fb_get_statement_interface(st, &statement, stmt)){ - // if (sv[0] == 1 && sv[1] > 0) - return st[1]; + if (res = fb_get_statement_interface(st, &statement, &ib_query->stmt)){ + return res; } - // TODO: check status; - statement->getInfo(&status, itemsLength, items, bufferLength, buffer); + try { + meta = statement->getOutputMetadata(&status); + unsigned cols = meta->getCount(&status); - statement->release(); - statement = NULL; + assert(cols == ib_query->out_fields_count); + + zval t; + for (unsigned i = 0; i < cols; ++i) + { + _php_ibase_insert_alias(ib_query->ht_aliases, + meta->getAlias(&status, i), strlen(meta->getAlias(&status, i))); + } + } + catch (const Firebird::FbException& error) + { + if (status.hasData()) { + fb_copy_status((const ISC_STATUS*)status.getErrors(), st, 20); + return st[1]; + } + } return 0; } diff --git a/pdo_firebird_utils.h b/pdo_firebird_utils.h index 855e140..7fd1203 100644 --- a/pdo_firebird_utils.h +++ b/pdo_firebird_utils.h @@ -17,7 +17,10 @@ #ifndef PDO_FIREBIRD_UTILS_H #define PDO_FIREBIRD_UTILS_H +#if FB_API_VER >= 40 + #include +#include "php_ibase_includes.h" #ifdef __cplusplus extern "C" { @@ -29,8 +32,6 @@ ISC_TIME fb_encode_time(unsigned hours, unsigned minutes, unsigned seconds, unsi ISC_DATE fb_encode_date(unsigned year, unsigned month, unsigned day); -#if FB_API_VER >= 40 - void fb_decode_time_tz(const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, unsigned timeZoneBufferLength, char* timeZoneBuffer); @@ -39,14 +40,11 @@ void fb_decode_timestamp_tz(const ISC_TIMESTAMP_TZ* timestampTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, unsigned timeZoneBufferLength, char* timeZoneBuffer); -int fb_get_sql_info(ISC_STATUS* st, isc_stmt_handle* stmt, - unsigned itemsLength, const unsigned char* items, - unsigned bufferLength, unsigned char* buffer); - -#endif +int fb_insert_aliases(ISC_STATUS* st, ibase_query *ib_query); #ifdef __cplusplus } #endif +#endif #endif /* PDO_FIREBIRD_UTILS_H */ diff --git a/php_ibase_includes.h b/php_ibase_includes.h index 5da30cf..46dc4f6 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -249,6 +249,16 @@ void _php_ibase_free_event(ibase_event *event); /* provided by ibase_service.c */ void php_ibase_service_minit(INIT_FUNC_ARGS); +#ifdef __cplusplus +extern "C" { +#endif + +void _php_ibase_insert_alias(HashTable *ht, const char *alias, size_t alias_len); + +#ifdef __cplusplus +} +#endif + #ifndef max #define max(a,b) ((a)>(b)?(a):(b)) #endif From 2feccbbc70a622d2d18f7a4486a0b8edec08e9d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 18:57:41 +0200 Subject: [PATCH 22/66] Remove stdbool.h --- ibase_query.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ibase_query.c b/ibase_query.c index f609305..3b5fdaf 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -23,7 +23,9 @@ +----------------------------------------------------------------------+ */ - #include +// #pragma GCC diagnostic error "-Wextra" +// #pragma GCC diagnostic error "-Wall" +// #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" #ifdef HAVE_CONFIG_H #include "config.h" From e51da3a16308f6004924857b4928636f9c028488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:06:09 +0200 Subject: [PATCH 23/66] Introduce _php_ibase_get_vars_count() --- ibase_query.c | 84 ++++++++++++++++++++++++++++++++++++++++---- php_ibase_includes.h | 1 + 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 3b5fdaf..aa484d4 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -2223,18 +2223,88 @@ PHP_FUNCTION(ibase_param_info) return; } - ib_query = (ibase_query *)zend_fetch_resource_ex(result_arg, LE_QUERY, le_query); - if (ib_query->in_sqlda == NULL) { - RETURN_FALSE; +static int _php_ibase_get_vars_count(ibase_query *ib_query) +{ + int rv = FAILURE; + // size_t buf_size = 128; + // ISC_UCHAR *buf = emalloc(buf_size); + + ISC_UCHAR buf[64] = {0}; + size_t buf_size = sizeof(buf); + + size_t pos; + + static ISC_UCHAR info_req[] = { + isc_info_sql_stmt_type, + isc_info_sql_select, + isc_info_sql_num_variables, + isc_info_sql_bind, + isc_info_sql_num_variables, + }; +// _php_ibase_parse_info_retry: +// memset(buf, 0, buf_size); + pos = 0; + + // Assume buf will be tagged with `isc_info_truncated` and later in parsing + // we will catch that. Until `isc_info_truncated` is reached assume pos += + // 2, etc are safe. + if (isc_dsql_sql_info(IB_STATUS, &ib_query->stmt, sizeof(info_req), (ISC_SCHAR *)info_req, buf_size, (ISC_SCHAR *)buf)) { + _php_ibase_error(); + goto _php_ibase_parse_info_fail; + } + + int ctx = 0; + while((buf[pos] != isc_info_end) && (pos < buf_size)) + { + const ISC_UCHAR tag = buf[pos++]; + switch(tag) { + case isc_info_sql_stmt_type: { + const ISC_USHORT size = (ISC_USHORT)isc_portable_integer(&buf[pos], 2); pos += 2; + ib_query->statement_type = (ISC_UCHAR)isc_portable_integer(&buf[pos], size); pos += size; + } break; + case isc_info_sql_select: ctx = 1; break; + case isc_info_sql_bind: ctx = 2; break; + case isc_info_sql_num_variables: { + const ISC_USHORT size = (ISC_USHORT)isc_portable_integer(&buf[pos], 2); pos += 2; + const ISC_USHORT count = (ISC_USHORT)isc_portable_integer(&buf[pos], size); pos += size; + if(ctx == 1) { + ib_query->out_fields_count = count; + } else if(ctx == 2) { + ib_query->in_fields_count = count; + } else { + fbp_fatal("isc_info_sql_num_variables: unknown ctx %d", ctx); + } + ctx = 0; + } break; + case isc_info_truncated: { + fbp_notice("BUG: sql_info buffer truncated, current capacity: %ld", buf_size); + // Dynamic resize + // buf_size *= 2; + // buf = erealloc(buf, buf_size); + // goto _php_ibase_parse_info_retry; + } break; + case isc_info_error: { + fbp_fatal("sql_info buffer error, pos: %lu", pos); + goto _php_ibase_parse_info_fail; + } break; + default: { + fbp_fatal("BUG: unrecognized sql_info entry: %d, pos: %lu", tag, pos); + goto _php_ibase_parse_info_fail; + } break; + } } - if (field_arg < 0 || field_arg >= ib_query->in_sqlda->sqld) { - RETURN_FALSE; + if(buf[pos] != isc_info_end) { + fbp_fatal("BUG: sql_info unexpected end of buffer at pos: %lu", pos); + goto _php_ibase_parse_info_fail; } - _php_ibase_field_info(return_value,ib_query->in_sqlda->sqlvar + field_arg); + rv = SUCCESS; + +_php_ibase_parse_info_fail: + // efree(buf); + return rv; } -/* }}} */ #endif /* HAVE_IBASE */ diff --git a/php_ibase_includes.h b/php_ibase_includes.h index 46dc4f6..f6c9165 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -254,6 +254,7 @@ extern "C" { #endif void _php_ibase_insert_alias(HashTable *ht, const char *alias, size_t alias_len); +static int _php_ibase_get_vars_count(ibase_query *ib_query); #ifdef __cplusplus } From 1528cb30d1e03218d1e13706a3bfe52946e27849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:17:04 +0200 Subject: [PATCH 24/66] Refactor _php_ibase_alloc_query --- ibase_query.c | 168 ++++++++++++++++++-------------------------------- 1 file changed, 59 insertions(+), 109 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index aa484d4..f74e8bd 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -261,126 +261,92 @@ static int _php_ibase_alloc_array(ibase_array **ib_arrayp, XSQLDA *sqlda, /* {{{ /* }}} */ /* allocate and prepare query */ -static int _php_ibase_alloc_query(ibase_query *ib_query, ibase_db_link *link, /* {{{ */ - ibase_trans *trans, char *query, unsigned short dialect, zend_resource *trans_res) +static int _php_ibase_prepare(ibase_query **new_query, ibase_db_link *link, /* {{{ */ + ibase_trans *trans, char *query) { - static char info_type[] = {isc_info_sql_stmt_type}; - char result[8]; - /* Return FAILURE, if querystring is empty */ if (*query == '\0') { php_error_docref(NULL, E_WARNING, "Querystring empty."); return FAILURE; } + ibase_query *ib_query = ecalloc(1, sizeof(ibase_query)); + + ib_query->res = zend_register_resource(ib_query, le_query); ib_query->link = link; ib_query->trans = trans; - ib_query->result_res = NULL; - ib_query->stmt = 0; - ib_query->stmt_res = NULL; - ib_query->in_array = NULL; - ib_query->out_array = NULL; - ib_query->dialect = dialect; + ib_query->dialect = link->dialect; ib_query->query = estrdup(query); - ib_query->trans_res = trans_res; - ib_query->out_sqlda = NULL; - ib_query->in_sqlda = NULL; if (isc_dsql_allocate_statement(IB_STATUS, &link->handle, &ib_query->stmt)) { _php_ibase_error(); goto _php_ibase_alloc_query_error; } - { /* allocate zend resource for statement*/ - ibase_statement* ib_stmt = emalloc(sizeof(ibase_statement)); - ib_stmt->link = link; - ib_stmt->stmt = ib_query->stmt; - ib_query->stmt_res = zend_register_resource(ib_stmt, le_statement); - } - - ib_query->out_sqlda = (XSQLDA *) emalloc(XSQLDA_LENGTH(1)); - ib_query->out_sqlda->sqln = 1; - ib_query->out_sqlda->version = SQLDA_CURRENT_VERSION; if (isc_dsql_prepare(IB_STATUS, &ib_query->trans->handle, &ib_query->stmt, - 0, query, dialect, ib_query->out_sqlda)) { + 0, query, link->dialect, NULL)) { + IBDEBUG("isc_dsql_prepare() failed\n"); _php_ibase_error(); goto _php_ibase_alloc_query_error; } - /* find out what kind of statement was prepared */ - if (isc_dsql_sql_info(IB_STATUS, &ib_query->stmt, sizeof(info_type), - info_type, sizeof(result), result)) { - _php_ibase_error(); + + // TODO: Rename. It also sets statement_type + if(_php_ibase_get_vars_count(ib_query)){ goto _php_ibase_alloc_query_error; } - ib_query->statement_type = result[3]; - /* not enough output variables ? */ - if (ib_query->out_sqlda->sqld > ib_query->out_sqlda->sqln) { - ib_query->out_sqlda = erealloc(ib_query->out_sqlda, XSQLDA_LENGTH(ib_query->out_sqlda->sqld)); - ib_query->out_sqlda->sqln = ib_query->out_sqlda->sqld; + if(ib_query->out_fields_count) { + ib_query->out_sqlda = (XSQLDA *) emalloc(XSQLDA_LENGTH(ib_query->out_fields_count)); + ib_query->out_sqlda->sqln = ib_query->out_fields_count; ib_query->out_sqlda->version = SQLDA_CURRENT_VERSION; + if (isc_dsql_describe(IB_STATUS, &ib_query->stmt, SQLDA_CURRENT_VERSION, ib_query->out_sqlda)) { + IBDEBUG("isc_dsql_describe() failed\n"); _php_ibase_error(); goto _php_ibase_alloc_query_error; } - } - /* maybe have input placeholders? */ - ib_query->in_sqlda = emalloc(XSQLDA_LENGTH(1)); - ib_query->in_sqlda->sqln = 1; - ib_query->in_sqlda->version = SQLDA_CURRENT_VERSION; - if (isc_dsql_describe_bind(IB_STATUS, &ib_query->stmt, SQLDA_CURRENT_VERSION, ib_query->in_sqlda)) { - _php_ibase_error(); - goto _php_ibase_alloc_query_error; + assert(ib_query->out_sqlda->sqln == ib_query->out_sqlda->sqld); + assert(ib_query->out_sqlda->sqld == ib_query->out_fields_count); + + ib_query->out_nullind = safe_emalloc(sizeof(*ib_query->out_nullind), ib_query->out_sqlda->sqld, 0); + _php_ibase_alloc_xsqlda_vars(ib_query->out_sqlda, ib_query->out_nullind); + if (FAILURE == _php_ibase_alloc_array(&ib_query->out_array, ib_query->out_sqlda, + link->handle, trans->handle, &ib_query->out_array_cnt)) { + goto _php_ibase_alloc_query_error; + } } - /* not enough input variables ? */ - if (ib_query->in_sqlda->sqln < ib_query->in_sqlda->sqld) { - ib_query->in_sqlda = erealloc(ib_query->in_sqlda, XSQLDA_LENGTH(ib_query->in_sqlda->sqld)); - ib_query->in_sqlda->sqln = ib_query->in_sqlda->sqld; + if(ib_query->in_fields_count) { + ib_query->in_sqlda = emalloc(XSQLDA_LENGTH(ib_query->in_fields_count)); + ib_query->in_sqlda->sqln = ib_query->in_fields_count; ib_query->in_sqlda->version = SQLDA_CURRENT_VERSION; - if (isc_dsql_describe_bind(IB_STATUS, &ib_query->stmt, - SQLDA_CURRENT_VERSION, ib_query->in_sqlda)) { + if (isc_dsql_describe_bind(IB_STATUS, &ib_query->stmt, SQLDA_CURRENT_VERSION, ib_query->in_sqlda)) { + IBDEBUG("isc_dsql_describe_bind() failed\n"); _php_ibase_error(); goto _php_ibase_alloc_query_error; } - } - /* no, haven't placeholders at all */ - if (ib_query->in_sqlda->sqld == 0) { - efree(ib_query->in_sqlda); - ib_query->in_sqlda = NULL; - } else if (FAILURE == _php_ibase_alloc_array(&ib_query->in_array, ib_query->in_sqlda, + assert(ib_query->in_sqlda->sqln == ib_query->in_sqlda->sqld); + assert(ib_query->in_sqlda->sqld == ib_query->in_fields_count); + + ib_query->bind_buf = safe_emalloc(sizeof(BIND_BUF), ib_query->in_sqlda->sqld, 0); + ib_query->in_nullind = safe_emalloc(sizeof(*ib_query->in_nullind), ib_query->in_sqlda->sqld, 0); + if (FAILURE == _php_ibase_alloc_array(&ib_query->in_array, ib_query->in_sqlda, link->handle, trans->handle, &ib_query->in_array_cnt)) { - goto _php_ibase_alloc_query_error; + goto _php_ibase_alloc_query_error; + } } - if (ib_query->out_sqlda->sqld == 0) { - efree(ib_query->out_sqlda); - ib_query->out_sqlda = NULL; - } else if (FAILURE == _php_ibase_alloc_array(&ib_query->out_array, ib_query->out_sqlda, - link->handle, trans->handle, &ib_query->out_array_cnt)) { - goto _php_ibase_alloc_query_error; - } + *new_query = ib_query; return SUCCESS; _php_ibase_alloc_query_error: + zend_list_delete(ib_query->res); - if (ib_query->out_sqlda) { - efree(ib_query->out_sqlda); - } - if (ib_query->in_sqlda) { - efree(ib_query->in_sqlda); - } - if (ib_query->out_array) { - efree(ib_query->out_array); - } - if (ib_query->query) { - efree(ib_query->query); - } return FAILURE; } /* }}} */ @@ -1206,46 +1172,32 @@ PHP_FUNCTION(ibase_query) } /* open default transaction */ - if (ib_link == NULL || FAILURE == _php_ibase_def_trans(ib_link, &trans) - || FAILURE == _php_ibase_alloc_query(&ib_query, ib_link, trans, query, ib_link->dialect, trans_res)) { + if (ib_link == NULL || FAILURE == _php_ibase_def_trans(ib_link, &trans)){ return; } - do { - int bind_n = ZEND_NUM_ARGS() - bind_i, - expected_n = ib_query.in_sqlda ? ib_query.in_sqlda->sqld : 0; + ibase_query *ib_query; + if(FAILURE == _php_ibase_prepare(&ib_query, ib_link, trans, query)) { + return; + } - if (bind_n != expected_n) { - php_error_docref(NULL, (bind_n < expected_n) ? E_WARNING : E_NOTICE, - "Statement expects %d arguments, %d given", expected_n, bind_n); - if (bind_n < expected_n) { - break; - } - } else if (bind_n > 0) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &bind_args, &bind_num) == FAILURE) { - return; - } + { // was while + int bind_n = ZEND_NUM_ARGS() - bind_i; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &bind_args, &bind_num) == FAILURE) { + goto _php_ibase_query_error; } - if (FAILURE == _php_ibase_exec(INTERNAL_FUNCTION_PARAM_PASSTHRU, &result, &ib_query, - &bind_args[bind_i])) { - break; + if (FAILURE == _php_ibase_exec(INTERNAL_FUNCTION_PARAM_PASSTHRU, ib_query, &bind_args[bind_i], bind_n)) { + goto _php_ibase_query_error; } - if (result != NULL) { /* statement returns a result */ - result->type = QUERY_RESULT; - - /* EXECUTE PROCEDURE returns only one row => statement can be released immediately */ - if (ib_query.statement_type != isc_info_sql_stmt_exec_procedure) { - ib_query.stmt = 0; /* keep stmt when free query */ - } - RETVAL_RES(zend_register_resource(result, le_result)); - Z_TRY_ADDREF_P(return_value); - } - } while (0); + return; + } - _php_ibase_free_query(&ib_query); + assert(false && "UNREACHABLE"); +_php_ibase_query_error: + zend_list_delete(ib_query->res); } /* }}} */ @@ -1888,13 +1840,11 @@ PHP_FUNCTION(ibase_prepare) RETURN_FALSE; } - ib_query = (ibase_query *) emalloc(sizeof(ibase_query)); - - if (FAILURE == _php_ibase_alloc_query(ib_query, ib_link, trans, query, ib_link->dialect, trans_res)) { - efree(ib_query); + if(FAILURE == _php_ibase_prepare(&ib_query, ib_link, trans, query)){ RETURN_FALSE; } - RETVAL_RES(zend_register_resource(ib_query, le_query)); + + RETVAL_RES(ib_query->res); Z_TRY_ADDREF_P(return_value); } /* }}} */ From 7aceb55d3fe57f6bb95e98834792507f05996c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:19:03 +0200 Subject: [PATCH 25/66] Rename --- ibase_query.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ibase_query.c b/ibase_query.c index f74e8bd..1edc943 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -59,6 +59,7 @@ static int le_query; #define LE_QUERY "Firebird/InterBase query" +static void _php_ibase_alloc_xsqlda_vars(XSQLDA *sqlda, ISC_SHORT *nullinds); static void _php_ibase_free_xsqlda(XSQLDA *sqlda) /* {{{ */ { int i; @@ -806,7 +807,7 @@ static int _php_ibase_bind(XSQLDA *sqlda, zval *b_vars, BIND_BUF *buf, /* {{{ */ } /* }}} */ -static void _php_ibase_alloc_xsqlda(XSQLDA *sqlda) /* {{{ */ +static void _php_ibase_alloc_xsqlda_vars(XSQLDA *sqlda, ISC_SHORT *nullinds) /* {{{ */ { int i; From 1a0a96e02a1b9b35aab9241bcd7210488e17fa27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:28:06 +0200 Subject: [PATCH 26/66] Refactor ibase_execute --- ibase_query.c | 167 +++++++++++++++++--------------------------------- 1 file changed, 57 insertions(+), 110 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 1edc943..7c0c8ad 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -885,18 +885,37 @@ static void _php_ibase_alloc_xsqlda_vars(XSQLDA *sqlda, ISC_SHORT *nullinds) /* } /* }}} */ -static int _php_ibase_exec(INTERNAL_FUNCTION_PARAMETERS, ibase_result **ib_resultp, /* {{{ */ - ibase_query *ib_query, zval *args) +static int _php_ibase_exec(INTERNAL_FUNCTION_PARAMETERS, ibase_query *ib_query, zval *args, int bind_n) /* {{{ */ { - XSQLDA *in_sqlda = NULL, *out_sqlda = NULL; - BIND_BUF *bind_buf = NULL; int i, rv = FAILURE; static char info_count[] = { isc_info_sql_records }; char result[64]; ISC_STATUS isc_result; - int argc = ib_query->in_sqlda ? ib_query->in_sqlda->sqld : 0; + int argc = ib_query->in_fields_count; + + (void)execute_data; RESET_ERRMSG; + RETVAL_FALSE; + + if (bind_n != argc) { + php_error_docref(NULL, (bind_n < argc) ? E_WARNING : E_NOTICE, + "Statement expects %d arguments, %d given", argc, bind_n); + + if (bind_n < argc) { + return FAILURE; + } + } + + /* Have we used this cursor before and it's still open (exec proc has no cursor) ? */ + if (ib_query->is_open && ib_query->statement_type != isc_info_sql_stmt_exec_procedure) { + IBDEBUG("Implicitly closing a cursor"); + if (isc_dsql_free_statement(IB_STATUS, &ib_query->stmt, DSQL_close)) { + _php_ibase_error(); + return FAILURE; + } + ib_query->is_open = 0; + } for (i = 0; i < argc; ++i) { SEPARATE_ZVAL(&args[i]); @@ -915,7 +934,7 @@ static int _php_ibase_exec(INTERNAL_FUNCTION_PARAMETERS, ibase_result **ib_resul if (isc_dsql_execute_immediate(IB_STATUS, &ib_query->link->handle, &tr, 0, ib_query->query, ib_query->dialect, NULL)) { _php_ibase_error(); - goto _php_ibase_exec_error; + goto _php_ibase_ex_error; } trans = (ibase_trans *) emalloc(sizeof(ibase_trans)); @@ -947,7 +966,7 @@ static int _php_ibase_exec(INTERNAL_FUNCTION_PARAMETERS, ibase_result **ib_resul if (isc_dsql_execute_immediate(IB_STATUS, &ib_query->link->handle, &ib_query->trans->handle, 0, ib_query->query, ib_query->dialect, NULL)) { _php_ibase_error(); - goto _php_ibase_exec_error; + goto _php_ibase_ex_error; } if (ib_query->trans->handle == 0 && ib_query->trans_res != NULL) { @@ -965,55 +984,41 @@ static int _php_ibase_exec(INTERNAL_FUNCTION_PARAMETERS, ibase_result **ib_resul RETVAL_FALSE; } - /* allocate sqlda and output buffers */ - if (ib_query->out_sqlda) { /* output variables in select, select for update */ - ibase_result *res; - - IBDEBUG("Query wants XSQLDA for output"); - res = emalloc(sizeof(ibase_result)+sizeof(ibase_array)*max(0,ib_query->out_array_cnt-1)); - res->link = ib_query->link; - res->trans = ib_query->trans; - res->stmt = ib_query->stmt; - GC_ADDREF(res->stmt_res = ib_query->stmt_res); - - res->statement_type = ib_query->statement_type; - res->has_more_rows = 1; - - out_sqlda = res->out_sqlda = emalloc(XSQLDA_LENGTH(ib_query->out_sqlda->sqld)); - memcpy(out_sqlda, ib_query->out_sqlda, XSQLDA_LENGTH(ib_query->out_sqlda->sqld)); - _php_ibase_alloc_xsqlda(out_sqlda); - - if (ib_query->out_array) { - memcpy(&res->out_array, ib_query->out_array, sizeof(ibase_array)*ib_query->out_array_cnt); - } - *ib_resultp = res; - } - - if (ib_query->in_sqlda) { /* has placeholders */ + if (ib_query->in_fields_count) { /* has placeholders */ IBDEBUG("Query wants XSQLDA for input"); - in_sqlda = emalloc(XSQLDA_LENGTH(ib_query->in_sqlda->sqld)); - memcpy(in_sqlda, ib_query->in_sqlda, XSQLDA_LENGTH(ib_query->in_sqlda->sqld)); - bind_buf = safe_emalloc(sizeof(BIND_BUF), ib_query->in_sqlda->sqld, 0); - if (_php_ibase_bind(in_sqlda, args, bind_buf, ib_query) == FAILURE) { + if (_php_ibase_bind(ib_query, args) == FAILURE) { IBDEBUG("Could not bind input XSQLDA"); - goto _php_ibase_exec_error; + goto _php_ibase_ex_error; } } if (ib_query->statement_type == isc_info_sql_stmt_exec_procedure) { isc_result = isc_dsql_execute2(IB_STATUS, &ib_query->trans->handle, - &ib_query->stmt, SQLDA_CURRENT_VERSION, in_sqlda, out_sqlda); + &ib_query->stmt, SQLDA_CURRENT_VERSION, ib_query->in_sqlda, ib_query->out_sqlda); } else { isc_result = isc_dsql_execute(IB_STATUS, &ib_query->trans->handle, - &ib_query->stmt, SQLDA_CURRENT_VERSION, in_sqlda); + &ib_query->stmt, SQLDA_CURRENT_VERSION, ib_query->in_sqlda); } + if (isc_result) { IBDEBUG("Could not execute query"); _php_ibase_error(); - goto _php_ibase_exec_error; + goto _php_ibase_ex_error; } + ib_query->trans->affected_rows = 0; + // TODO: test INSERT / UPDATE / UPDATE OR INSERT with ... RETURNING + if (ib_query->out_sqlda) { /* output variables in select, select for update */ + ib_query->has_more_rows = 1; + ib_query->is_open = 1; + + RETVAL_RES(ib_query->res); + Z_TRY_ADDREF_P(return_value); + + return SUCCESS; + } + switch (ib_query->statement_type) { unsigned long affected_rows; @@ -1026,7 +1031,7 @@ static int _php_ibase_exec(INTERNAL_FUNCTION_PARAMETERS, ibase_result **ib_resul if (isc_dsql_sql_info(IB_STATUS, &ib_query->stmt, sizeof(info_count), info_count, sizeof(result), result)) { _php_ibase_error(); - goto _php_ibase_exec_error; + goto _php_ibase_ex_error; } affected_rows = 0; @@ -1059,24 +1064,7 @@ static int _php_ibase_exec(INTERNAL_FUNCTION_PARAMETERS, ibase_result **ib_resul rv = SUCCESS; -_php_ibase_exec_error: - - if (in_sqlda) { - efree(in_sqlda); - } - if (bind_buf) - efree(bind_buf); - - if (rv == FAILURE) { - if (*ib_resultp) { - efree(*ib_resultp); - *ib_resultp = NULL; - } - if (out_sqlda) { - _php_ibase_free_xsqlda(out_sqlda); - } - } - +_php_ibase_ex_error: return rv; } /* }}} */ @@ -1856,69 +1844,28 @@ PHP_FUNCTION(ibase_execute) { zval *query, *args = NULL; ibase_query *ib_query; - ibase_result *result = NULL; int bind_n = 0; RESET_ERRMSG; RETVAL_FALSE; - if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "|r*", &query, &args, &bind_n)) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|r*", &query, &args, &bind_n)) { return; } - ib_query = (ibase_query *)zend_fetch_resource_ex(query, LE_QUERY, le_query); - - do { - int expected_n = ib_query->in_sqlda ? ib_query->in_sqlda->sqld : 0; - - if (bind_n != expected_n) { - php_error_docref(NULL, (bind_n < expected_n) ? E_WARNING : E_NOTICE, - "Statement expects %d arguments, %d given", expected_n, bind_n); - - if (bind_n < expected_n) { - break; - } - } - - /* Have we used this cursor before and it's still open (exec proc has no cursor) ? */ - if (ib_query->result_res != NULL) { - if (ib_query->statement_type != isc_info_sql_stmt_exec_procedure) { - IBDEBUG("Implicitly closing a cursor"); - - if (isc_dsql_free_statement(IB_STATUS, &ib_query->stmt, DSQL_close)) { - _php_ibase_error(); - break; - } - } - zend_list_delete(ib_query->result_res); - ib_query->result_res = NULL; - } + if(_php_ibase_fetch_query_res(query, &ib_query)) { + return; + } - if (FAILURE == _php_ibase_exec(INTERNAL_FUNCTION_PARAM_PASSTHRU, &result, ib_query, args)) { - break; - } + // was do { + _php_ibase_exec(INTERNAL_FUNCTION_PARAM_PASSTHRU, ib_query, args, bind_n); /* free the query if trans handle was released */ - if (ib_query->trans->handle == 0) { - zend_list_delete(Z_RES_P(query)); - } - - if (result != NULL) { - zval *ret; - - result->type = EXECUTE_RESULT; - if (ib_query->statement_type == isc_info_sql_stmt_exec_procedure) { - result->stmt = 0; - } - - ret = zend_list_insert(result, le_result); - ib_query->result_res = Z_RES_P(ret); - ZVAL_COPY_VALUE(return_value, ret); - Z_TRY_ADDREF_P(return_value); - Z_TRY_ADDREF_P(return_value); - } - } while (0); + // if (ib_query->trans->handle == 0) { + // zend_list_delete(Z_RES_P(query)); + // } + // } while (0); } /* }}} */ From f24057ed0e9f046a942af1fae823dc27f3b584bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:30:45 +0200 Subject: [PATCH 27/66] Refactor ibase_query --- ibase_query.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 7c0c8ad..a87a852 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1080,8 +1080,6 @@ PHP_FUNCTION(ibase_query) zend_resource *trans_res = NULL; ibase_db_link *ib_link = NULL; ibase_trans *trans = NULL; - ibase_query ib_query = { NULL, NULL, 0, 0 }; - ibase_result *result = NULL; RESET_ERRMSG; @@ -1117,13 +1115,13 @@ PHP_FUNCTION(ibase_query) if (SUCCESS == zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "ls", &l, &query, &query_len) && l == PHP_IBASE_CREATE) { isc_db_handle db = 0; - isc_tr_handle trans = 0; + isc_tr_handle trh = 0; if (((l = INI_INT("ibase.max_links")) != -1) && (IBG(num_links) >= l)) { _php_ibase_module_error("CREATE DATABASE is not allowed: maximum link count " "(" ZEND_LONG_FMT ") reached", l); - } else if (isc_dsql_execute_immediate(IB_STATUS, &db, &trans, (short)query_len, + } else if (isc_dsql_execute_immediate(IB_STATUS, &db, &trh, (short)query_len, query, SQL_DIALECT_CURRENT, NULL)) { _php_ibase_error(); @@ -1143,8 +1141,9 @@ PHP_FUNCTION(ibase_query) RETVAL_RES(zend_register_resource(ib_link, le_link)); Z_TRY_ADDREF_P(return_value); - Z_TRY_ADDREF_P(return_value); + IBG(default_link) = Z_RES_P(return_value); + Z_TRY_ADDREF_P(return_value); } return; } From 91c72df58d5bda36e71fd2eb6ad101dcbe4ac327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:41:15 +0200 Subject: [PATCH 28/66] Refactor _php_ibase_fetch_hash() --- ibase_query.c | 187 +++++++++++++++++++++++++++++-------------- php_ibase_includes.h | 3 + 2 files changed, 132 insertions(+), 58 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index a87a852..60463f3 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1527,69 +1527,71 @@ static void _php_ibase_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int fetch_type) zval *result_arg; zend_long flag = 0; zend_long i, array_cnt = 0; - ibase_result *ib_result; + ibase_query *ib_query; RESET_ERRMSG; if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &result_arg, &flag)) { - return; + RETURN_FALSE; } - ib_result = (ibase_result *)zend_fetch_resource_ex(result_arg, LE_RESULT, le_result); + if(_php_ibase_fetch_query_res(result_arg, &ib_query)) { + RETURN_FALSE; + } + + assert(ib_query->out_fields_count > 0); - if (ib_result->out_sqlda == NULL || !ib_result->has_more_rows) { + if (ib_query->out_sqlda == NULL || !ib_query->has_more_rows || !ib_query->is_open) { RETURN_FALSE; } - if (ib_result->statement_type != isc_info_sql_stmt_exec_procedure) { - if (isc_dsql_fetch(IB_STATUS, &ib_result->stmt, 1, ib_result->out_sqlda)) { - ib_result->has_more_rows = 0; + if (ib_query->statement_type != isc_info_sql_stmt_exec_procedure) { + if (isc_dsql_fetch(IB_STATUS, &ib_query->stmt, 1, ib_query->out_sqlda)) { + ib_query->has_more_rows = 0; + ib_query->is_open = 0; + if (IB_STATUS[0] && IB_STATUS[1]) { /* error in fetch */ _php_ibase_error(); } + + if(isc_dsql_free_statement(IB_STATUS, &ib_query->stmt, DSQL_close)){ + _php_ibase_error(); + } + RETURN_FALSE; } } else { - ib_result->has_more_rows = 0; + ib_query->has_more_rows = 0; + ib_query->is_open = 0; } - array_init(return_value); - - for (i = 0; i < ib_result->out_sqlda->sqld; ++i) { - XSQLVAR *var = &ib_result->out_sqlda->sqlvar[i]; - char buf[METADATALENGTH+4], *alias = var->aliasname; - - if (! (fetch_type & FETCH_ROW)) { - int i = 0; - char const *base = "FIELD"; /* use 'FIELD' if name is empty */ - - /** - * Ensure no two columns have identical names: - * keep generating new names until we find one that is unique. - */ - switch (*alias) { - void *p; - - default: - i = 1; - base = alias; - - while ((p = zend_symtable_str_find_ptr( - Z_ARRVAL_P(return_value), alias, strlen(alias))) != NULL) { + assert(ib_query->out_fields_count == ib_query->out_sqlda->sqld); - case '\0': - snprintf(alias = buf, sizeof(buf), "%s_%02d", base, i++); - } + HashTable *ht_ret; + if(!(fetch_type & FETCH_ROW)) { + if(!ib_query->ht_aliases){ + if(_php_ibase_alloc_ht_aliases(ib_query)){ + _php_ibase_error(); + RETURN_FALSE; } } + ht_ret = zend_array_dup(ib_query->ht_aliases); + } else { + if(!ib_query->ht_ind)_php_ibase_alloc_ht_ind(ib_query); + ht_ret = zend_array_dup(ib_query->ht_ind); + } + + for(i = 0; i < ib_query->out_fields_count; ++i) { + XSQLVAR *var = &ib_query->out_sqlda->sqlvar[i]; + // TODO: just continue and unnest. All fields are set to NULL already if (((var->sqltype & 1) == 0) || *var->sqlind != -1) { - zval result; + zval *result = zend_hash_get_current_data(ht_ret); switch (var->sqltype & ~1) { default: - _php_ibase_var_zval(&result, var->sqldata, var->sqltype, var->sqllen, + _php_ibase_var_zval(result, var->sqldata, var->sqltype, var->sqllen, var->sqlscale, flag); break; case SQL_BLOB: @@ -1604,7 +1606,7 @@ static void _php_ibase_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int fetch_type) blob_handle.bl_handle = 0; blob_handle.bl_qd = *(ISC_QUAD *) var->sqldata; - if (isc_open_blob(IB_STATUS, &ib_result->link->handle, &ib_result->trans->handle, + if (isc_open_blob(IB_STATUS, &ib_query->link->handle, &ib_query->trans->handle, &blob_handle.bl_handle, &blob_handle.bl_qd)) { _php_ibase_error(); goto _php_ibase_fetch_error; @@ -1639,8 +1641,8 @@ static void _php_ibase_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int fetch_type) } if (max_len == 0) { - ZVAL_STRING(&result, ""); - } else if (SUCCESS != _php_ibase_blob_get(&result, &blob_handle, + ZVAL_STRING(result, ""); + } else if (SUCCESS != _php_ibase_blob_get(result, &blob_handle, max_len)) { goto _php_ibase_fetch_error; } @@ -1652,24 +1654,24 @@ static void _php_ibase_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int fetch_type) } else { /* blob id only */ ISC_QUAD bl_qd = *(ISC_QUAD *) var->sqldata; - ZVAL_NEW_STR(&result, _php_ibase_quad_to_string(bl_qd)); + ZVAL_NEW_STR(result, _php_ibase_quad_to_string(bl_qd)); } break; case SQL_ARRAY: if (flag & PHP_IBASE_FETCH_ARRAYS) { /* array can be *huge* so only fetch if asked */ ISC_QUAD ar_qd = *(ISC_QUAD *) var->sqldata; - ibase_array *ib_array = &ib_result->out_array[array_cnt++]; + ibase_array *ib_array = &ib_query->out_array[array_cnt++]; void *ar_data = emalloc(ib_array->ar_size); - if (isc_array_get_slice(IB_STATUS, &ib_result->link->handle, - &ib_result->trans->handle, &ar_qd, &ib_array->ar_desc, + if (isc_array_get_slice(IB_STATUS, &ib_query->link->handle, + &ib_query->trans->handle, &ar_qd, &ib_array->ar_desc, ar_data, &ib_array->ar_size)) { _php_ibase_error(); efree(ar_data); goto _php_ibase_fetch_error; } - if (FAILURE == _php_ibase_arr_zval(&result, ar_data, ib_array->ar_size, ib_array, + if (FAILURE == _php_ibase_arr_zval(result, ar_data, ib_array->ar_size, ib_array, 0, flag)) { efree(ar_data); goto _php_ibase_fetch_error; @@ -1678,27 +1680,18 @@ static void _php_ibase_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int fetch_type) } else { /* blob id only */ ISC_QUAD ar_qd = *(ISC_QUAD *) var->sqldata; - ZVAL_NEW_STR(&result, _php_ibase_quad_to_string(ar_qd)); + ZVAL_NEW_STR(result, _php_ibase_quad_to_string(ar_qd)); } break; _php_ibase_fetch_error: - zval_ptr_dtor_nogc(&result); RETURN_FALSE; } /* switch */ - - if (fetch_type & FETCH_ROW) { - add_index_zval(return_value, i, &result); - } else { - add_assoc_zval(return_value, alias, &result); - } - } else { - if (fetch_type & FETCH_ROW) { - add_index_null(return_value, i); - } else { - add_assoc_null(return_value, alias); - } } + + zend_hash_move_forward(ht_ret); } /* for field */ + + RETVAL_ARR(ht_ret); } /* }}} */ @@ -2204,4 +2197,82 @@ static int _php_ibase_get_vars_count(ibase_query *ib_query) return rv; } +static int _php_ibase_fetch_query_res(zval *from, ibase_query **ib_query) +{ + *ib_query = zend_fetch_resource_ex(from, LE_QUERY, le_query); + + if(*ib_query == NULL) { + // TODO: throw something or not? notice? warning? + php_error_docref(NULL, E_NOTICE, "query already freed"); + return FAILURE; + } + + return SUCCESS; +} + +// We can't rely on aliasname coming from XSQLVAR if we want long field names +// (>31). We also can't rely on parsing buffer from isc_dsql_sql_info() because +// it's 32KB limit can be easily overflown with combination of long field names +// and large amounts of fields. So I added wrapper to use newer API but that +// also require runtime fbclient > 40 hence the runtime checks. Ideally rewrite +// everything using newer API but that's a bit of work. +static int _php_ibase_alloc_ht_aliases(ibase_query *ib_query) +{ + ALLOC_HASHTABLE(ib_query->ht_aliases); + zend_hash_init(ib_query->ht_aliases, ib_query->out_fields_count, NULL, ZVAL_PTR_DTOR, 0); + +#if FB_API_VER >= 40 + if(IBG(fb_get_master_interface) && IBG(fb_get_statement_interface)) { + if(fb_insert_aliases(IB_STATUS, ib_query)){ + return FAILURE; + } + } else { +#endif + // Old API + for(size_t i = 0; i < ib_query->out_fields_count; i++){ + XSQLVAR *var = &ib_query->out_sqlda->sqlvar[i]; + + _php_ibase_insert_alias(ib_query->ht_aliases, + var->aliasname, MIN(31, var->aliasname_length)); + } +#if FB_API_VER >= 40 + } +#endif + + return SUCCESS; +} + +static void _php_ibase_alloc_ht_ind(ibase_query *ib_query) +{ + ALLOC_HASHTABLE(ib_query->ht_ind); + zend_hash_init(ib_query->ht_ind, ib_query->out_fields_count, NULL, ZVAL_PTR_DTOR, 0); + + zval t2; + ZVAL_NULL(&t2); + + for(size_t i = 0; i < ib_query->out_fields_count; i++) { + zend_hash_index_add(ib_query->ht_ind, i, &t2); + } +} + +static void _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAMETERS) +{ + zval *query_arg; + ibase_query *ib_query; + + RESET_ERRMSG; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &query_arg) == FAILURE) { + return; + } + + if(_php_ibase_fetch_query_res(query_arg, &ib_query)) { + return; + } + + zend_list_close(Z_RES_P(query_arg)); + + RETURN_TRUE; +} + #endif /* HAVE_IBASE */ diff --git a/php_ibase_includes.h b/php_ibase_includes.h index f6c9165..6b186e1 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -255,6 +255,9 @@ extern "C" { void _php_ibase_insert_alias(HashTable *ht, const char *alias, size_t alias_len); static int _php_ibase_get_vars_count(ibase_query *ib_query); +static int _php_ibase_fetch_query_res(zval *from, ibase_query **ib_query); +static int _php_ibase_alloc_ht_aliases(ibase_query *ib_query); +static void _php_ibase_alloc_ht_ind(ibase_query *ib_query); #ifdef __cplusplus } From 03dc5053f07e2c7a1e6a625529b81d795c9d67e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:41:41 +0200 Subject: [PATCH 29/66] Refactor ibase_name_result() --- ibase_query.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 60463f3..879e58f 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1731,7 +1731,7 @@ PHP_FUNCTION(ibase_name_result) zval *result_arg; char *name_arg; size_t name_arg_len; - ibase_result *ib_result; + ibase_query *ib_query; RESET_ERRMSG; @@ -1739,17 +1739,19 @@ PHP_FUNCTION(ibase_name_result) return; } - ib_result = (ibase_result *)zend_fetch_resource_ex(result_arg, LE_RESULT, le_result); + if(_php_ibase_fetch_query_res(result_arg, &ib_query)) { + return; + } - if (isc_dsql_set_cursor_name(IB_STATUS, &ib_result->stmt, name_arg, 0)) { + if (isc_dsql_set_cursor_name(IB_STATUS, &ib_query->stmt, name_arg, 0)) { _php_ibase_error(); RETURN_FALSE; } + RETURN_TRUE; } /* }}} */ - /* {{{ proto bool ibase_free_result(resource result) Free the memory used by a result */ PHP_FUNCTION(ibase_free_result) From a5e5fb5dd0a830eba55aabba86545f2958ecf3f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:45:39 +0200 Subject: [PATCH 30/66] Refactor ibase_num_fields(), ibase_num_params(), ibase_field_info(), ibase_param_info() --- ibase_query.c | 104 +++++++++++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 44 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 879e58f..dc45527 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1892,8 +1892,8 @@ PHP_FUNCTION(ibase_free_query) PHP_FUNCTION(ibase_num_fields) { zval *result; - int type; XSQLDA *sqlda; + ibase_query *ib_query; RESET_ERRMSG; @@ -1901,20 +1901,12 @@ PHP_FUNCTION(ibase_num_fields) return; } - type = Z_RES_P(result)->type; - - if (type == le_query) { - ibase_query *ib_query; - - ib_query = (ibase_query *)zend_fetch_resource_ex(result, LE_QUERY, le_query); - sqlda = ib_query->out_sqlda; - } else { - ibase_result *ib_result; - - ib_result = (ibase_result *)zend_fetch_resource_ex(result, LE_RESULT, le_result); - sqlda = ib_result->out_sqlda; + if(_php_ibase_fetch_query_res(result, &ib_query)) { + return; } + sqlda = ib_query->out_sqlda; + if (sqlda == NULL) { RETURN_LONG(0); } else { @@ -1923,21 +1915,54 @@ PHP_FUNCTION(ibase_num_fields) } /* }}} */ -static void _php_ibase_field_info(zval *return_value, XSQLVAR *var) /* {{{ */ +static void _php_ibase_field_info(zval *return_value, ibase_query *ib_query, int is_outvar, int num) /* {{{ */ { unsigned short len; char buf[16], *s = buf; + XSQLDA *sqlda; + XSQLVAR *var; + + if(is_outvar){ + sqlda = ib_query->out_sqlda; + if (sqlda == NULL) { + _php_ibase_module_error("Trying to get field info from a non-select query"); + RETURN_FALSE; + } + } else { + sqlda = ib_query->in_sqlda; + if (sqlda == NULL) { + // TODO: Add warning? Remove above warning? + RETURN_FALSE; + } + } + + var = sqlda->sqlvar; + + if (!var || num < 0 || num >= sqlda->sqld)RETURN_FALSE; + + var += num; array_init(return_value); - add_index_stringl(return_value, 0, var->sqlname, var->sqlname_length); - add_assoc_stringl(return_value, "name", var->sqlname, var->sqlname_length); + if (is_outvar) { + // TODO: use newer API for long names + add_index_stringl(return_value, 0, var->sqlname, MIN(31, var->sqlname_length)); + add_assoc_stringl(return_value, "name", var->aliasname, MIN(31, var->aliasname_length)); - add_index_stringl(return_value, 1, var->aliasname, var->aliasname_length); - add_assoc_stringl(return_value, "alias", var->aliasname, var->aliasname_length); + add_index_stringl(return_value, 1, var->aliasname, MIN(31, var->aliasname_length)); + add_assoc_stringl(return_value, "alias", var->aliasname, MIN(31, var->aliasname_length)); - add_index_stringl(return_value, 2, var->relname, var->relname_length); - add_assoc_stringl(return_value, "relation", var->relname, var->relname_length); + add_index_stringl(return_value, 2, var->relname, MIN(31, var->relname_length)); + add_assoc_stringl(return_value, "relation", var->relname, MIN(31, var->relname_length)); + } else { + // AFAIK describe bind does not set these. Confirmation pending. + add_index_stringl(return_value, 0, "", 0); + add_assoc_stringl(return_value, "name", "", 0); + add_index_stringl(return_value, 1, "", 0); + add_assoc_stringl(return_value, "alias", "", 0); + add_index_stringl(return_value, 2, "", 0); + add_assoc_stringl(return_value, "relation", "", 0); + } len = slprintf(buf, 16, "%d", var->sqllen); add_index_stringl(return_value, 3, buf, len); @@ -2020,6 +2045,7 @@ static void _php_ibase_field_info(zval *return_value, XSQLVAR *var) /* {{{ */ break; #if FB_API_VER >= 40 // These are converted to VARCHAR via isc_dpb_set_bind tag at connect and will appear to clients as VARCHAR + // TODO: add info regardless // case SQL_DEC16: // case SQL_DEC34: // case SQL_INT128: @@ -2043,8 +2069,7 @@ PHP_FUNCTION(ibase_field_info) { zval *result_arg; zend_long field_arg; - int type; - XSQLDA *sqlda; + ibase_query *ib_query; RESET_ERRMSG; @@ -2052,29 +2077,11 @@ PHP_FUNCTION(ibase_field_info) return; } - type = Z_RES_P(result_arg)->type; - - if (type == le_query) { - ibase_query *ib_query; - - ib_query= (ibase_query *)zend_fetch_resource_ex(result_arg, LE_QUERY, le_query); - sqlda = ib_query->out_sqlda; - } else { - ibase_result *ib_result; - - ib_result = (ibase_result *)zend_fetch_resource_ex(result_arg, LE_RESULT, le_result); - sqlda = ib_result->out_sqlda; - } - - if (sqlda == NULL) { - _php_ibase_module_error("Trying to get field info from a non-select query"); - RETURN_FALSE; + if(_php_ibase_fetch_query_res(result_arg, &ib_query)) { + return; } - if (field_arg < 0 || field_arg >= sqlda->sqld) { - RETURN_FALSE; - } - _php_ibase_field_info(return_value, sqlda->sqlvar + field_arg); + _php_ibase_field_info(return_value, ib_query, 1, (ISC_SHORT)field_arg); } /* }}} */ @@ -2091,7 +2098,9 @@ PHP_FUNCTION(ibase_num_params) return; } - ib_query = (ibase_query *)zend_fetch_resource_ex(result, LE_QUERY, le_query); + if(_php_ibase_fetch_query_res(result, &ib_query)) { + return; + } if (ib_query->in_sqlda == NULL) { RETURN_LONG(0); @@ -2115,6 +2124,13 @@ PHP_FUNCTION(ibase_param_info) return; } + if(_php_ibase_fetch_query_res(result_arg, &ib_query)) { + return; + } + + _php_ibase_field_info(return_value, ib_query, 0, field_arg); +} +/* }}} */ static int _php_ibase_get_vars_count(ibase_query *ib_query) { From a0393837a0303f06c587687c8938e2f363037125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:49:47 +0200 Subject: [PATCH 31/66] Refactor ibase_free_query(), ibase_free_result() --- ibase_query.c | 78 ++++++++------------------------------------ php_ibase_includes.h | 1 + 2 files changed, 15 insertions(+), 64 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index dc45527..e43f2f7 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -84,29 +84,18 @@ static void _php_ibase_free_query(ibase_query *ib_query) /* {{{ */ { IBDEBUG("Freeing query..."); - if (ib_query->in_sqlda) { - efree(ib_query->in_sqlda); - } - if (ib_query->out_sqlda) { - efree(ib_query->out_sqlda); - } - if (ib_query->stmt_res != NULL) { - zend_list_delete(ib_query->stmt_res); - ib_query->stmt_res = NULL; - } - if (ib_query->result_res != NULL) { - zend_list_delete(ib_query->result_res); - ib_query->result_res = NULL; - } - if (ib_query->in_array) { - efree(ib_query->in_array); - } - if (ib_query->out_array) { - efree(ib_query->out_array); - } - if (ib_query->query) { - efree(ib_query->query); - } + if(ib_query->in_nullind)efree(ib_query->in_nullind); + if(ib_query->out_nullind)efree(ib_query->out_nullind); + if(ib_query->bind_buf)efree(ib_query->bind_buf); + if(ib_query->in_sqlda)efree(ib_query->in_sqlda); // Note to myself: no need for _php_ibase_free_xsqlda() + if(ib_query->out_sqlda)_php_ibase_free_xsqlda(ib_query->out_sqlda); + if(ib_query->in_array)efree(ib_query->in_array); + if(ib_query->out_array)efree(ib_query->out_array); + if(ib_query->query)efree(ib_query->query); + if(ib_query->ht_aliases)zend_array_destroy(ib_query->ht_aliases); + if(ib_query->ht_ind)zend_array_destroy(ib_query->ht_ind); + + efree(ib_query); } /* }}} */ @@ -117,7 +106,6 @@ static void php_ibase_free_query_rsrc(zend_resource *rsrc) /* {{{ */ if (ib_query != NULL) { IBDEBUG("Preparing to free query by dtor..."); _php_ibase_free_query(ib_query); - efree(ib_query); } } /* }}} */ @@ -1756,29 +1744,7 @@ PHP_FUNCTION(ibase_name_result) Free the memory used by a result */ PHP_FUNCTION(ibase_free_result) { - zval *result_arg; - ibase_result *ib_result; - - RESET_ERRMSG; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &result_arg) == FAILURE) { - return; - } - - ib_result = (ibase_result *)zend_fetch_resource_ex(result_arg, LE_RESULT, le_result); - - _php_ibase_free_xsqlda(ib_result->out_sqlda); - efree(ib_result); - - zend_list_delete(Z_RES_P(result_arg)); - - /* - * Bugfix of issue #40 - * Reset pointer after freeing to NULL - */ - Z_RES_P(result_arg)->ptr = NULL; - - RETURN_TRUE; + // ibase_result was removed, nothing to be done here } /* }}} */ @@ -1867,23 +1833,7 @@ PHP_FUNCTION(ibase_execute) Free memory used by a query */ PHP_FUNCTION(ibase_free_query) { - zval *query_arg; - ibase_query *ib_query; - - RESET_ERRMSG; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &query_arg) == FAILURE) { - return; - } - - ib_query = (ibase_query *)zend_fetch_resource_ex(query_arg, LE_QUERY, le_query); - if (!ib_query) { - RETURN_FALSE; - } - - zend_list_close(Z_RES_P(query_arg)); - zend_list_delete(Z_RES_P(query_arg)); - RETURN_TRUE; + _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU); } /* }}} */ diff --git a/php_ibase_includes.h b/php_ibase_includes.h index 6b186e1..d12d713 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -258,6 +258,7 @@ static int _php_ibase_get_vars_count(ibase_query *ib_query); static int _php_ibase_fetch_query_res(zval *from, ibase_query **ib_query); static int _php_ibase_alloc_ht_aliases(ibase_query *ib_query); static void _php_ibase_alloc_ht_ind(ibase_query *ib_query); +static void _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAMETERS); #ifdef __cplusplus } From 5a4f704cc1286d9e04976c88ab19d00e449138ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:53:36 +0200 Subject: [PATCH 32/66] Use bind_buf instead of single allocations for null indicators --- ibase_query.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index e43f2f7..7af923a 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -71,9 +71,6 @@ static void _php_ibase_free_xsqlda(XSQLDA *sqlda) /* {{{ */ var = sqlda->sqlvar; for (i = 0; i < sqlda->sqld; i++, var++) { efree(var->sqldata); - if (var->sqlind) { - efree(var->sqlind); - } } efree(sqlda); } @@ -563,6 +560,7 @@ static int _php_ibase_bind_array(zval *val, char *buf, zend_ulong buf_size, /* { static int _php_ibase_bind(XSQLDA *sqlda, zval *b_vars, BIND_BUF *buf, /* {{{ */ ibase_query *ib_query) { + BIND_BUF *buf = ib_query->bind_buf; int i, array_cnt = 0, rv = SUCCESS; for (i = 0; i < sqlda->sqld; ++i) { /* bound vars */ @@ -570,7 +568,7 @@ static int _php_ibase_bind(XSQLDA *sqlda, zval *b_vars, BIND_BUF *buf, /* {{{ */ zval *b_var = &b_vars[i]; XSQLVAR *var = &sqlda->sqlvar[i]; - var->sqlind = &buf[i].sqlind; + var->sqlind = &buf[i].nullind; var->sqldata = (void*)&buf[i].val; /* check if a NULL should be inserted */ @@ -604,7 +602,7 @@ static int _php_ibase_bind(XSQLDA *sqlda, zval *b_vars, BIND_BUF *buf, /* {{{ */ if (! force_null) break; case IS_NULL: - buf[i].sqlind = -1; + buf[i].nullind = -1; if (var->sqltype & SQL_ARRAY) ++array_cnt; @@ -613,7 +611,7 @@ static int _php_ibase_bind(XSQLDA *sqlda, zval *b_vars, BIND_BUF *buf, /* {{{ */ /* if we make it to this point, we must provide a value for the parameter */ - buf[i].sqlind = 0; + buf[i].nullind = 0; switch (var->sqltype & ~1) { struct tm t; @@ -737,7 +735,7 @@ static int _php_ibase_bind(XSQLDA *sqlda, zval *b_vars, BIND_BUF *buf, /* {{{ */ break; } case IS_NULL: - buf[i].sqlind = -1; + buf[i].nullind = -1; break; default: _php_ibase_module_error("Parameter %d: must be boolean", i+1); @@ -865,7 +863,7 @@ static void _php_ibase_alloc_xsqlda_vars(XSQLDA *sqlda, ISC_SHORT *nullinds) /* } /* switch */ if (var->sqltype & 1) { /* sql NULL flag */ - var->sqlind = emalloc(sizeof(short)); + var->sqlind = &nullinds[i]; } else { var->sqlind = NULL; } From 2f23b11390e417ce5f1c0b5215d7c37851ee50c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:54:34 +0200 Subject: [PATCH 33/66] Fully zero-initialize structs --- ibase_query.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 7af923a..9d1492f 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -434,7 +434,7 @@ static int _php_ibase_bind_array(zval *val, char *buf, zend_ulong buf_size, /* { break; } } else { - struct tm t = { 0, 0, 0, 0, 0, 0 }; + struct tm t = { 0 }; switch (array->el_type) { #ifndef HAVE_STRPTIME @@ -674,7 +674,8 @@ static int _php_ibase_bind(XSQLDA *sqlda, zval *b_vars, BIND_BUF *buf, /* {{{ */ if (Z_STRLEN_P(b_var) != BLOB_ID_LEN || !_php_ibase_string_to_quad(Z_STRVAL_P(b_var), &buf[i].val.qval)) { - ibase_blob ib_blob = { 0, BLOB_INPUT }; + ibase_blob ib_blob = { 0 }; + ib_blob.type = BLOB_INPUT; if (isc_create_blob(IB_STATUS, &ib_query->link->handle, &ib_query->trans->handle, &ib_blob.bl_handle, &ib_blob.bl_qd)) { From 6e6ae9dbfa4edce302d6b8fe28f5af3733917d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:56:03 +0200 Subject: [PATCH 34/66] Use sqlda from ib_query --- ibase_query.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 9d1492f..b09c73c 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -557,10 +557,11 @@ static int _php_ibase_bind_array(zval *val, char *buf, zend_ulong buf_size, /* { } /* }}} */ -static int _php_ibase_bind(XSQLDA *sqlda, zval *b_vars, BIND_BUF *buf, /* {{{ */ - ibase_query *ib_query) +static int _php_ibase_bind(ibase_query *ib_query, zval *b_vars) /* {{{ */ { BIND_BUF *buf = ib_query->bind_buf; + XSQLDA *sqlda = ib_query->in_sqlda; + int i, array_cnt = 0, rv = SUCCESS; for (i = 0; i < sqlda->sqld; ++i) { /* bound vars */ From 2f7c1188e3ec131afc6bc23a84f7ceb68e0fcd6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 19:59:29 +0200 Subject: [PATCH 35/66] Adjust types --- ibase_query.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index b09c73c..9502f73 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -788,7 +788,7 @@ static int _php_ibase_bind(ibase_query *ib_query, zval *b_vars) /* {{{ */ /* we end up here if none of the switch cases handled the field */ convert_to_string(b_var); var->sqldata = Z_STRVAL_P(b_var); - var->sqllen = Z_STRLEN_P(b_var); + var->sqllen = (ISC_SHORT)Z_STRLEN_P(b_var); var->sqltype = SQL_TEXT; } /* for */ return rv; @@ -1272,7 +1272,7 @@ PHP_FUNCTION(ibase_num_rows) /* }}} */ static int _php_ibase_var_zval(zval *val, void *data, int type, int len, /* {{{ */ - int scale, int flag) + int scale, size_t flag) { static ISC_INT64 const scales[] = { 1, 10, 100, 1000, 10000, @@ -1394,8 +1394,8 @@ static int _php_ibase_var_zval(zval *val, void *data, int type, int len, /* {{{ return FAILURE; } - size_t l = sprintf(string_data, "%s %s", timeBuf, timeZoneBuffer); - ZVAL_STRINGL(val, string_data, l); + size_t tz_len = sprintf(string_data, "%s %s", timeBuf, timeZoneBuffer); + ZVAL_STRINGL(val, string_data, tz_len); } break; #endif @@ -1433,7 +1433,7 @@ static int _php_ibase_var_zval(zval *val, void *data, int type, int len, /* {{{ /* }}} */ static int _php_ibase_arr_zval(zval *ar_zval, char *data, zend_ulong data_size, /* {{{ */ - ibase_array *ib_array, int dim, int flag) + ibase_array *ib_array, int dim, size_t flag) { /** * Create multidimension array - recursion function From db2bd182c4314a8db4c95b9bf26485c481250d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 20:00:10 +0200 Subject: [PATCH 36/66] Improve diagnostic msg --- ibase_query.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ibase_query.c b/ibase_query.c index 9502f73..9652c45 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -860,7 +860,9 @@ static void _php_ibase_alloc_xsqlda_vars(XSQLDA *sqlda, ISC_SHORT *nullinds) /* break; #endif default: - php_error(E_WARNING, "Unhandled sqltype: %d for sqlname %s %s:%d", var->sqltype, var->sqlname, __FILE__, __LINE__); + // TODO: report human readable type. Grab ints from sqlda_pub.h + // and just map to char * + php_error(E_WARNING, "Unhandled sqltype: %d for sqlname %s %s:%d. Probably compiled against old fbclient library (%d)", var->sqltype, var->sqlname, __FILE__, __LINE__, FB_API_VER); break; } /* switch */ From be0a1bdcd73c20072d4e24a8738d82b2cb83bb13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 20:00:35 +0200 Subject: [PATCH 37/66] Adjust types --- ibase_query.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ibase_query.c b/ibase_query.c index 9652c45..5761b9a 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1746,6 +1746,8 @@ PHP_FUNCTION(ibase_name_result) Free the memory used by a result */ PHP_FUNCTION(ibase_free_result) { + (void)execute_data; + RETVAL_TRUE; // ibase_result was removed, nothing to be done here } /* }}} */ From 2700ac03ff04e8819b97e80c5b10c9ac0cf8d3ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 21:42:37 +0200 Subject: [PATCH 38/66] Tidy up --- ibase_query.c | 174 ++++++++++++++++++++++++-------------------------- 1 file changed, 85 insertions(+), 89 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 5761b9a..c38fced 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1514,18 +1514,18 @@ void _php_ibase_insert_alias(HashTable *ht, const char *alias, size_t alias_len) static void _php_ibase_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int fetch_type) /* {{{ */ { - zval *result_arg; + zval *res_arg, *result; zend_long flag = 0; zend_long i, array_cnt = 0; ibase_query *ib_query; RESET_ERRMSG; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &result_arg, &flag)) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &res_arg, &flag)) { RETURN_FALSE; } - if(_php_ibase_fetch_query_res(result_arg, &ib_query)) { + if(_php_ibase_fetch_query_res(res_arg, &ib_query)) { RETURN_FALSE; } @@ -1573,113 +1573,109 @@ static void _php_ibase_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int fetch_type) for(i = 0; i < ib_query->out_fields_count; ++i) { XSQLVAR *var = &ib_query->out_sqlda->sqlvar[i]; + result = zend_hash_get_current_data(ht_ret); - // TODO: just continue and unnest. All fields are set to NULL already - if (((var->sqltype & 1) == 0) || *var->sqlind != -1) { - zval *result = zend_hash_get_current_data(ht_ret); - - switch (var->sqltype & ~1) { - - default: - _php_ibase_var_zval(result, var->sqldata, var->sqltype, var->sqllen, - var->sqlscale, flag); - break; - case SQL_BLOB: - if (flag & PHP_IBASE_FETCH_BLOBS) { /* fetch blob contents into hash */ - - ibase_blob blob_handle; - zend_ulong max_len = 0; - static char bl_items[] = {isc_info_blob_total_length}; - char bl_info[20]; - unsigned short i; - - blob_handle.bl_handle = 0; - blob_handle.bl_qd = *(ISC_QUAD *) var->sqldata; + switch (var->sqltype & ~1) { - if (isc_open_blob(IB_STATUS, &ib_query->link->handle, &ib_query->trans->handle, - &blob_handle.bl_handle, &blob_handle.bl_qd)) { - _php_ibase_error(); - goto _php_ibase_fetch_error; - } + default: + _php_ibase_var_zval(result, var->sqldata, var->sqltype, var->sqllen, + var->sqlscale, flag); + break; + case SQL_BLOB: + if (flag & PHP_IBASE_FETCH_BLOBS) { /* fetch blob contents into hash */ - if (isc_blob_info(IB_STATUS, &blob_handle.bl_handle, sizeof(bl_items), - bl_items, sizeof(bl_info), bl_info)) { - _php_ibase_error(); - goto _php_ibase_fetch_error; - } + ibase_blob blob_handle; + zend_ulong max_len = 0; + static char bl_items[] = {isc_info_blob_total_length}; + char bl_info[20]; + unsigned short i; - /* find total length of blob's data */ - for (i = 0; i < sizeof(bl_info); ) { - unsigned short item_len; - char item = bl_info[i++]; + blob_handle.bl_handle = 0; + blob_handle.bl_qd = *(ISC_QUAD *) var->sqldata; - if (item == isc_info_end || item == isc_info_truncated || - item == isc_info_error || i >= sizeof(bl_info)) { + if (isc_open_blob(IB_STATUS, &ib_query->link->handle, &ib_query->trans->handle, + &blob_handle.bl_handle, &blob_handle.bl_qd)) { + _php_ibase_error(); + goto _php_ibase_fetch_error; + } - _php_ibase_module_error("Could not determine BLOB size (internal error)" - ); - goto _php_ibase_fetch_error; - } + if (isc_blob_info(IB_STATUS, &blob_handle.bl_handle, sizeof(bl_items), + bl_items, sizeof(bl_info), bl_info)) { + _php_ibase_error(); + goto _php_ibase_fetch_error; + } - item_len = (unsigned short) isc_vax_integer(&bl_info[i], 2); + /* find total length of blob's data */ + for (i = 0; i < sizeof(bl_info); ) { + unsigned short item_len; + char item = bl_info[i++]; - if (item == isc_info_blob_total_length) { - max_len = isc_vax_integer(&bl_info[i+2], item_len); - break; - } - i += item_len+2; - } + if (item == isc_info_end || item == isc_info_truncated || + item == isc_info_error || i >= sizeof(bl_info)) { - if (max_len == 0) { - ZVAL_STRING(result, ""); - } else if (SUCCESS != _php_ibase_blob_get(result, &blob_handle, - max_len)) { + _php_ibase_module_error("Could not determine BLOB size (internal error)" + ); goto _php_ibase_fetch_error; } - if (isc_close_blob(IB_STATUS, &blob_handle.bl_handle)) { - _php_ibase_error(); - goto _php_ibase_fetch_error; + item_len = (unsigned short) isc_vax_integer(&bl_info[i], 2); + + if (item == isc_info_blob_total_length) { + max_len = isc_vax_integer(&bl_info[i+2], item_len); + break; } + i += item_len+2; + } - } else { /* blob id only */ - ISC_QUAD bl_qd = *(ISC_QUAD *) var->sqldata; - ZVAL_NEW_STR(result, _php_ibase_quad_to_string(bl_qd)); + if (max_len == 0) { + ZVAL_STRING(result, ""); + } else if (SUCCESS != _php_ibase_blob_get(result, &blob_handle, + max_len)) { + goto _php_ibase_fetch_error; } - break; - case SQL_ARRAY: - if (flag & PHP_IBASE_FETCH_ARRAYS) { /* array can be *huge* so only fetch if asked */ - ISC_QUAD ar_qd = *(ISC_QUAD *) var->sqldata; - ibase_array *ib_array = &ib_query->out_array[array_cnt++]; - void *ar_data = emalloc(ib_array->ar_size); - - if (isc_array_get_slice(IB_STATUS, &ib_query->link->handle, - &ib_query->trans->handle, &ar_qd, &ib_array->ar_desc, - ar_data, &ib_array->ar_size)) { - _php_ibase_error(); - efree(ar_data); - goto _php_ibase_fetch_error; - } - if (FAILURE == _php_ibase_arr_zval(result, ar_data, ib_array->ar_size, ib_array, - 0, flag)) { - efree(ar_data); - goto _php_ibase_fetch_error; - } + if (isc_close_blob(IB_STATUS, &blob_handle.bl_handle)) { + _php_ibase_error(); + goto _php_ibase_fetch_error; + } + + } else { /* blob id only */ + ISC_QUAD bl_qd = *(ISC_QUAD *) var->sqldata; + ZVAL_NEW_STR(result, _php_ibase_quad_to_string(bl_qd)); + } + break; + case SQL_ARRAY: + if (flag & PHP_IBASE_FETCH_ARRAYS) { /* array can be *huge* so only fetch if asked */ + ISC_QUAD ar_qd = *(ISC_QUAD *) var->sqldata; + ibase_array *ib_array = &ib_query->out_array[array_cnt++]; + void *ar_data = emalloc(ib_array->ar_size); + + if (isc_array_get_slice(IB_STATUS, &ib_query->link->handle, + &ib_query->trans->handle, &ar_qd, &ib_array->ar_desc, + ar_data, &ib_array->ar_size)) { + _php_ibase_error(); efree(ar_data); + goto _php_ibase_fetch_error; + } - } else { /* blob id only */ - ISC_QUAD ar_qd = *(ISC_QUAD *) var->sqldata; - ZVAL_NEW_STR(result, _php_ibase_quad_to_string(ar_qd)); + if (FAILURE == _php_ibase_arr_zval(result, ar_data, ib_array->ar_size, ib_array, + 0, flag)) { + efree(ar_data); + goto _php_ibase_fetch_error; } - break; - _php_ibase_fetch_error: - RETURN_FALSE; - } /* switch */ - } + efree(ar_data); + + } else { /* blob id only */ + ISC_QUAD ar_qd = *(ISC_QUAD *) var->sqldata; + ZVAL_NEW_STR(result, _php_ibase_quad_to_string(ar_qd)); + } + break; + _php_ibase_fetch_error: + RETURN_FALSE; + } /* switch */ zend_hash_move_forward(ht_ret); - } /* for field */ + } RETVAL_ARR(ht_ret); } From e2155f3dac68f3596e049f07b86b063703378350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Mon, 27 Oct 2025 23:24:58 +0200 Subject: [PATCH 39/66] Correct improper field --- ibase_query.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ibase_query.c b/ibase_query.c index c38fced..28fe214 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1897,7 +1897,7 @@ static void _php_ibase_field_info(zval *return_value, ibase_query *ib_query, int if (is_outvar) { // TODO: use newer API for long names add_index_stringl(return_value, 0, var->sqlname, MIN(31, var->sqlname_length)); - add_assoc_stringl(return_value, "name", var->aliasname, MIN(31, var->aliasname_length)); + add_assoc_stringl(return_value, "name", var->sqlname, MIN(31, var->sqlname_length)); add_index_stringl(return_value, 1, var->aliasname, MIN(31, var->aliasname_length)); add_assoc_stringl(return_value, "alias", var->aliasname, MIN(31, var->aliasname_length)); From f2a30b7c22ee29501cb7d71e9112ed1ccdd2592e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Tue, 28 Oct 2025 16:36:34 +0200 Subject: [PATCH 40/66] Introduce fb_insert_field_info() --- ibase_query.c | 25 +++++++++++++++---------- pdo_firebird_utils.cpp | 40 +++++++++++++++++++++++++++++++++++++++- pdo_firebird_utils.h | 1 + 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 28fe214..aa50f7e 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1894,8 +1894,19 @@ static void _php_ibase_field_info(zval *return_value, ibase_query *ib_query, int array_init(return_value); - if (is_outvar) { - // TODO: use newer API for long names + // AFAIK describe bind does not set sqlname, aliasname, relname? + // Confirmation needed so I leave this as is. After that we can check + // is_outvar + +#if FB_API_VER >= 40 + if(IBG(fb_get_master_interface) && IBG(fb_get_statement_interface)) { + if(fb_insert_field_info(IB_STATUS, ib_query, is_outvar, num, return_value)){ + _php_ibase_error(); + RETURN_FALSE; + } + } else { +#endif + // Old API add_index_stringl(return_value, 0, var->sqlname, MIN(31, var->sqlname_length)); add_assoc_stringl(return_value, "name", var->sqlname, MIN(31, var->sqlname_length)); @@ -1904,15 +1915,9 @@ static void _php_ibase_field_info(zval *return_value, ibase_query *ib_query, int add_index_stringl(return_value, 2, var->relname, MIN(31, var->relname_length)); add_assoc_stringl(return_value, "relation", var->relname, MIN(31, var->relname_length)); - } else { - // AFAIK describe bind does not set these. Confirmation pending. - add_index_stringl(return_value, 0, "", 0); - add_assoc_stringl(return_value, "name", "", 0); - add_index_stringl(return_value, 1, "", 0); - add_assoc_stringl(return_value, "alias", "", 0); - add_index_stringl(return_value, 2, "", 0); - add_assoc_stringl(return_value, "relation", "", 0); +#if FB_API_VER >= 40 } +#endif len = slprintf(buf, 16, "%d", var->sqllen); add_index_stringl(return_value, 3, buf, len); diff --git a/pdo_firebird_utils.cpp b/pdo_firebird_utils.cpp index 9d32c10..d24f142 100644 --- a/pdo_firebird_utils.cpp +++ b/pdo_firebird_utils.cpp @@ -101,7 +101,6 @@ extern "C" int fb_insert_aliases(ISC_STATUS* st, ibase_query *ib_query) assert(cols == ib_query->out_fields_count); - zval t; for (unsigned i = 0; i < cols; ++i) { _php_ibase_insert_alias(ib_query->ht_aliases, @@ -119,4 +118,43 @@ extern "C" int fb_insert_aliases(ISC_STATUS* st, ibase_query *ib_query) return 0; } +extern "C" int fb_insert_field_info(ISC_STATUS* st, ibase_query *ib_query, int is_outvar, int num, zval *into_array) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::ThrowStatusWrapper status(master->getStatus()); + Firebird::IStatement* statement = NULL; + Firebird::IMessageMetadata* meta = NULL; + ISC_STATUS res; + + if (res = fb_get_statement_interface(st, &statement, &ib_query->stmt)){ + return res; + } + + try { + if(is_outvar) { + meta = statement->getOutputMetadata(&status); + } else { + meta = statement->getInputMetadata(&status); + } + + add_index_string(into_array, 0, meta->getField(&status, num)); + add_assoc_string(into_array, "name", meta->getField(&status, num)); + + add_index_string(into_array, 1, meta->getAlias(&status, num)); + add_assoc_string(into_array, "alias", meta->getAlias(&status, num)); + + add_index_string(into_array, 2, meta->getRelation(&status, num)); + add_assoc_string(into_array, "relation", meta->getRelation(&status, num)); + } + catch (const Firebird::FbException& error) + { + if (status.hasData()) { + fb_copy_status((const ISC_STATUS*)status.getErrors(), st, 20); + return st[1]; + } + } + + return 0; +} + #endif diff --git a/pdo_firebird_utils.h b/pdo_firebird_utils.h index 7fd1203..b5699dc 100644 --- a/pdo_firebird_utils.h +++ b/pdo_firebird_utils.h @@ -41,6 +41,7 @@ void fb_decode_timestamp_tz(const ISC_TIMESTAMP_TZ* timestampTz, unsigned timeZoneBufferLength, char* timeZoneBuffer); int fb_insert_aliases(ISC_STATUS* st, ibase_query *ib_query); +int fb_insert_field_info(ISC_STATUS* st, ibase_query *ib_query, int is_outvar, int num, zval *into_array); #ifdef __cplusplus } From dc5ecaed806a1e5e36ce8d78074574864319c5cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Tue, 28 Oct 2025 16:37:22 +0200 Subject: [PATCH 41/66] Move assertion after out_sqlda check --- ibase_query.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index aa50f7e..5ce011f 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1529,12 +1529,12 @@ static void _php_ibase_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int fetch_type) RETURN_FALSE; } - assert(ib_query->out_fields_count > 0); - if (ib_query->out_sqlda == NULL || !ib_query->has_more_rows || !ib_query->is_open) { RETURN_FALSE; } + assert(ib_query->out_fields_count > 0); + if (ib_query->statement_type != isc_info_sql_stmt_exec_procedure) { if (isc_dsql_fetch(IB_STATUS, &ib_query->stmt, 1, ib_query->out_sqlda)) { ib_query->has_more_rows = 0; From 18ab7884ca1e44df30d52fe8cc901a51671a4ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Tue, 28 Oct 2025 16:38:10 +0200 Subject: [PATCH 42/66] Bring back field's NULL checking --- ibase_query.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ibase_query.c b/ibase_query.c index 5ce011f..2e26b57 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1573,6 +1573,13 @@ static void _php_ibase_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int fetch_type) for(i = 0; i < ib_query->out_fields_count; ++i) { XSQLVAR *var = &ib_query->out_sqlda->sqlvar[i]; + + // NULLs are already set + if (!(((var->sqltype & 1) == 0) || *var->sqlind != -1)) { + zend_hash_move_forward(ht_ret); + continue; + } + result = zend_hash_get_current_data(ht_ret); switch (var->sqltype & ~1) { From 5e99e4736cdc6d5909117486b415106293a28563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Tue, 28 Oct 2025 16:38:34 +0200 Subject: [PATCH 43/66] Abort on isc_info_truncated --- ibase_query.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ibase_query.c b/ibase_query.c index 2e26b57..fb4ab6a 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -2148,7 +2148,8 @@ static int _php_ibase_get_vars_count(ibase_query *ib_query) ctx = 0; } break; case isc_info_truncated: { - fbp_notice("BUG: sql_info buffer truncated, current capacity: %ld", buf_size); + fbp_fatal("BUG: sql_info buffer truncated, current capacity: %ld", buf_size); + // fbp_notice("BUG: sql_info buffer truncated, current capacity: %ld", buf_size); // Dynamic resize // buf_size *= 2; // buf = erealloc(buf, buf_size); From aeffd3dc8e1896b2f4a7ed712de769314891e957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Tue, 28 Oct 2025 17:21:37 +0200 Subject: [PATCH 44/66] Restore ibase_free_result() behaviour --- ibase_query.c | 14 ++++++++------ php_ibase_includes.h | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index fb4ab6a..9b991f0 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1169,6 +1169,8 @@ PHP_FUNCTION(ibase_query) goto _php_ibase_query_error; } + ib_query->was_result_once = 1; + return; } @@ -1749,9 +1751,7 @@ PHP_FUNCTION(ibase_name_result) Free the memory used by a result */ PHP_FUNCTION(ibase_free_result) { - (void)execute_data; - RETVAL_TRUE; - // ibase_result was removed, nothing to be done here + _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ @@ -1840,7 +1840,7 @@ PHP_FUNCTION(ibase_execute) Free memory used by a query */ PHP_FUNCTION(ibase_free_query) { - _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU); + _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ @@ -2236,7 +2236,7 @@ static void _php_ibase_alloc_ht_ind(ibase_query *ib_query) } } -static void _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAMETERS) +static void _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAMETERS, int as_result) { zval *query_arg; ibase_query *ib_query; @@ -2251,7 +2251,9 @@ static void _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAMETERS) return; } - zend_list_close(Z_RES_P(query_arg)); + if(!as_result || ib_query->was_result_once) { + zend_list_close(Z_RES_P(query_arg)); + } RETURN_TRUE; } diff --git a/php_ibase_includes.h b/php_ibase_includes.h index d12d713..9cd1bf2 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -161,6 +161,7 @@ typedef struct _ib_query { ISC_SHORT *in_nullind, *out_nullind; ISC_USHORT in_fields_count, out_fields_count; HashTable *ht_aliases, *ht_ind; // Precomputed for ibase_fetch_*() + int was_result_once; } ibase_query; enum php_interbase_option { @@ -258,7 +259,7 @@ static int _php_ibase_get_vars_count(ibase_query *ib_query); static int _php_ibase_fetch_query_res(zval *from, ibase_query **ib_query); static int _php_ibase_alloc_ht_aliases(ibase_query *ib_query); static void _php_ibase_alloc_ht_ind(ibase_query *ib_query); -static void _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAMETERS); +static void _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAMETERS, int as_result); #ifdef __cplusplus } From 16896a5469d65f1fdd6b701c5dcf28bedbdfede3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Tue, 28 Oct 2025 17:45:03 +0200 Subject: [PATCH 45/66] Disable notice for now --- ibase_query.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ibase_query.c b/ibase_query.c index 9b991f0..1665025 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -2184,7 +2184,7 @@ static int _php_ibase_fetch_query_res(zval *from, ibase_query **ib_query) if(*ib_query == NULL) { // TODO: throw something or not? notice? warning? - php_error_docref(NULL, E_NOTICE, "query already freed"); + // fbp_notice("query already freed"); return FAILURE; } From 39dbe8ccc4623088f2cfec2955c7cc332cff803e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Wed, 29 Oct 2025 23:26:59 +0200 Subject: [PATCH 46/66] Keep track of trans_res --- ibase_query.c | 7 ++++--- php_ibase_includes.h | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 1665025..bab3eed 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -248,7 +248,7 @@ static int _php_ibase_alloc_array(ibase_array **ib_arrayp, XSQLDA *sqlda, /* {{{ /* allocate and prepare query */ static int _php_ibase_prepare(ibase_query **new_query, ibase_db_link *link, /* {{{ */ - ibase_trans *trans, char *query) + ibase_trans *trans, zend_resource *trans_res, char *query) { /* Return FAILURE, if querystring is empty */ if (*query == '\0') { @@ -261,6 +261,7 @@ static int _php_ibase_prepare(ibase_query **new_query, ibase_db_link *link, /* { ib_query->res = zend_register_resource(ib_query, le_query); ib_query->link = link; ib_query->trans = trans; + ib_query->trans_res = trans_res; ib_query->dialect = link->dialect; ib_query->query = estrdup(query); @@ -1155,7 +1156,7 @@ PHP_FUNCTION(ibase_query) } ibase_query *ib_query; - if(FAILURE == _php_ibase_prepare(&ib_query, ib_link, trans, query)) { + if(FAILURE == _php_ibase_prepare(&ib_query, ib_link, trans, trans_res, query)) { return; } @@ -1796,7 +1797,7 @@ PHP_FUNCTION(ibase_prepare) RETURN_FALSE; } - if(FAILURE == _php_ibase_prepare(&ib_query, ib_link, trans, query)){ + if(FAILURE == _php_ibase_prepare(&ib_query, ib_link, trans, trans_res, query)){ RETURN_FALSE; } diff --git a/php_ibase_includes.h b/php_ibase_includes.h index 9cd1bf2..ce88266 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -148,6 +148,7 @@ typedef struct { typedef struct _ib_query { ibase_db_link *link; ibase_trans *trans; + zend_resource *trans_res; zend_resource *res; isc_stmt_handle stmt; XSQLDA *in_sqlda, *out_sqlda; From 7a44bbd6c0b1f7ef8ffb93c86cc1939243fb253a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Wed, 29 Oct 2025 23:51:46 +0200 Subject: [PATCH 47/66] Add more transaction tests --- tests/functions.inc | 17 ++++++++++++ tests/ibase_trans_004.phpt | 22 ++++++++++++++++ tests/ibase_trans_005.phpt | 17 ++++++++++++ tests/ibase_trans_006.phpt | 21 +++++++++++++++ tests/ibase_trans_007.phpt | 47 +++++++++++++++++++++++++++++++++ tests/ibase_trans_008.phpt | 32 +++++++++++++++++++++++ tests/ibase_trans_009.phpt | 53 ++++++++++++++++++++++++++++++++++++++ tests/ibase_trans_010.phpt | 18 +++++++++++++ tests/ibase_trans_011.phpt | 22 ++++++++++++++++ 9 files changed, 249 insertions(+) create mode 100644 tests/ibase_trans_004.phpt create mode 100644 tests/ibase_trans_005.phpt create mode 100644 tests/ibase_trans_006.phpt create mode 100644 tests/ibase_trans_007.phpt create mode 100644 tests/ibase_trans_008.phpt create mode 100644 tests/ibase_trans_009.phpt create mode 100644 tests/ibase_trans_010.phpt create mode 100644 tests/ibase_trans_011.phpt diff --git a/tests/functions.inc b/tests/functions.inc index a1b1dcc..dd80eba 100644 --- a/tests/functions.inc +++ b/tests/functions.inc @@ -130,3 +130,20 @@ function skip_if_fb_gt($v) { function skip_if_fb_gte($v) { if(($cv = get_fb_version()) >= $v)die("skip: Firebird version $cv >= $v"); } + +function ibase_query_bulk(array $queries, $tr = null) { + foreach($queries as $q){ + if(is_array($q)){ + [$sql, $args] = $q; + } else { + $sql = $q; + $args = []; + } + + if($tr) { + ibase_query($tr, $sql, ...$args); + } else { + ibase_query($sql, ...$args); + } + } +} diff --git a/tests/ibase_trans_004.phpt b/tests/ibase_trans_004.phpt new file mode 100644 index 0000000..f31a0d5 --- /dev/null +++ b/tests/ibase_trans_004.phpt @@ -0,0 +1,22 @@ +--TEST-- +ibase_trans(): handles +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) +resource(%d) of type (Firebird/InterBase transaction) + +Warning: ibase_query(): invalid transaction handle (expecting explicit transaction start)%s diff --git a/tests/ibase_trans_005.phpt b/tests/ibase_trans_005.phpt new file mode 100644 index 0000000..336b032 --- /dev/null +++ b/tests/ibase_trans_005.phpt @@ -0,0 +1,17 @@ +--TEST-- +ibase_trans(): handles +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +resource(%d) of type (Firebird/InterBase transaction) diff --git a/tests/ibase_trans_006.phpt b/tests/ibase_trans_006.phpt new file mode 100644 index 0000000..ca526eb --- /dev/null +++ b/tests/ibase_trans_006.phpt @@ -0,0 +1,21 @@ +--TEST-- +ibase_trans(): handles +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +resource(%d) of type (Firebird/InterBase transaction) + +Warning: ibase_query(): invalid transaction handle (expecting explicit transaction start)%s diff --git a/tests/ibase_trans_007.phpt b/tests/ibase_trans_007.phpt new file mode 100644 index 0000000..daa6138 --- /dev/null +++ b/tests/ibase_trans_007.phpt @@ -0,0 +1,47 @@ +--TEST-- +ibase_trans(): handles +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(1) +|---- TEST1 default transaction +array(2) { + ["I"]=> + int(1) + ["C"]=> + string(32) "test table not created with isql" +} +|-------- +|---- TEST1 t1 transaction +|-------- +|---- TEST1 default transaction +array(2) { + ["I"]=> + int(1) + ["C"]=> + string(32) "test table not created with isql" +} +|-------- diff --git a/tests/ibase_trans_008.phpt b/tests/ibase_trans_008.phpt new file mode 100644 index 0000000..f82055d --- /dev/null +++ b/tests/ibase_trans_008.phpt @@ -0,0 +1,32 @@ +--TEST-- +ibase_trans(): transaction control with SQL +--SKIPIF-- + +--FILE-- + +--EXPECT-- +array(2) { + ["I"]=> + int(1) + ["C"]=> + string(8) "test1(1)" +} \ No newline at end of file diff --git a/tests/ibase_trans_009.phpt b/tests/ibase_trans_009.phpt new file mode 100644 index 0000000..8dc9758 --- /dev/null +++ b/tests/ibase_trans_009.phpt @@ -0,0 +1,53 @@ +--TEST-- +ibase_trans(): transaction control with SQL +--SKIPIF-- + +--FILE-- + +--EXPECT-- +---- current status +array(2) { + ["I"]=> + int(1) + ["C"]=> + string(8) "test2(1)" +} +array(2) { + ["I"]=> + int(2) + ["C"]=> + string(8) "test2(2)" +} +---- now rollback +array(2) { + ["I"]=> + int(1) + ["C"]=> + string(8) "test2(1)" +} \ No newline at end of file diff --git a/tests/ibase_trans_010.phpt b/tests/ibase_trans_010.phpt new file mode 100644 index 0000000..6aeb31d --- /dev/null +++ b/tests/ibase_trans_010.phpt @@ -0,0 +1,18 @@ +--TEST-- +ibase_trans(): transaction control with SQL - commit default transaction +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) diff --git a/tests/ibase_trans_011.phpt b/tests/ibase_trans_011.phpt new file mode 100644 index 0000000..2256136 --- /dev/null +++ b/tests/ibase_trans_011.phpt @@ -0,0 +1,22 @@ +--TEST-- +ibase_trans(): transaction control with SQL - commit explicitly +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +resource(%d) of type (Firebird/InterBase transaction) +bool(true) + +Warning: ibase_query(): Dynamic SQL Error SQL error code = -901 invalid transaction handle (expecting explicit transaction start)%s +bool(false) From 96ab6e877c6cf1d53ead4ee21bf97328bf3c8988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 30 Oct 2025 15:14:59 +0200 Subject: [PATCH 48/66] More transaction tests --- tests/functions.inc | 5 +++++ tests/ibase_trans_004.phpt | 14 +++++++++++--- tests/ibase_trans_006.phpt | 17 +++++++++++++---- tests/ibase_trans_012.phpt | 32 ++++++++++++++++++++++++++++++++ tests/ibase_trans_013.phpt | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 tests/ibase_trans_012.phpt create mode 100644 tests/ibase_trans_013.phpt diff --git a/tests/functions.inc b/tests/functions.inc index dd80eba..921aa7a 100644 --- a/tests/functions.inc +++ b/tests/functions.inc @@ -147,3 +147,8 @@ function ibase_query_bulk(array $queries, $tr = null) { } } } + +/** @var Exception $e */ +function php_ibase_exception_handler($e) { + echo "Fatal error: Uncaught ".get_class($e).": ", $e->getMessage(), "\n"; +} diff --git a/tests/ibase_trans_004.phpt b/tests/ibase_trans_004.phpt index f31a0d5..c50df53 100644 --- a/tests/ibase_trans_004.phpt +++ b/tests/ibase_trans_004.phpt @@ -1,22 +1,30 @@ --TEST-- ibase_trans(): handles --SKIPIF-- - + --FILE-- --EXPECTF-- +resource(12) of type (Firebird/InterBase transaction) bool(true) resource(%d) of type (Firebird/InterBase transaction) Warning: ibase_query(): invalid transaction handle (expecting explicit transaction start)%s +bool(false) diff --git a/tests/ibase_trans_006.phpt b/tests/ibase_trans_006.phpt index ca526eb..7494ab9 100644 --- a/tests/ibase_trans_006.phpt +++ b/tests/ibase_trans_006.phpt @@ -1,21 +1,30 @@ --TEST-- ibase_trans(): handles --SKIPIF-- - + --FILE-- --EXPECTF-- resource(%d) of type (Firebird/InterBase transaction) +bool(true) +resource(%d) of type (Firebird/InterBase transaction) Warning: ibase_query(): invalid transaction handle (expecting explicit transaction start)%s +bool(false) diff --git a/tests/ibase_trans_012.phpt b/tests/ibase_trans_012.phpt new file mode 100644 index 0000000..3c02af4 --- /dev/null +++ b/tests/ibase_trans_012.phpt @@ -0,0 +1,32 @@ +--TEST-- +ibase_trans(): handles +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +resource(%d) of type (Firebird/InterBase transaction) +bool(true) +resource(%d) of type (Firebird/InterBase transaction) +resource(%d) of type (interbase %s) + +Warning: ibase_fetch_assoc(): Dynamic SQL Error SQL error code = -901 invalid transaction handle (expecting explicit transaction start)%s +bool(false) diff --git a/tests/ibase_trans_013.phpt b/tests/ibase_trans_013.phpt new file mode 100644 index 0000000..0d226f2 --- /dev/null +++ b/tests/ibase_trans_013.phpt @@ -0,0 +1,32 @@ +--TEST-- +ibase_trans(): handles +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +resource(%d) of type (Firebird/InterBase transaction) +bool(true) +resource(%d) of type (Firebird/InterBase transaction) +resource(%d) of type (interbase %s) + +Warning: ibase_fetch_assoc(): Dynamic SQL Error SQL error code = -901 invalid transaction handle (expecting explicit transaction start) %s +bool(false) From 3b15999a1ee7db938f7f7931250b9adeb34c0d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 30 Oct 2025 16:41:24 +0200 Subject: [PATCH 49/66] Add use after free tests --- tests/common.inc | 26 ++++++++++++++++++++++++ tests/use_after_free-001.phpt | 38 +++++++++++++++++++++++++++++++++++ tests/use_after_free-002.phpt | 26 ++++++++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 tests/common.inc create mode 100644 tests/use_after_free-001.phpt create mode 100644 tests/use_after_free-002.phpt diff --git a/tests/common.inc b/tests/common.inc new file mode 100644 index 0000000..b59b620 --- /dev/null +++ b/tests/common.inc @@ -0,0 +1,26 @@ +$r) { + print "---- Batch $batch ----\n"; + dump_rows($r); + ibase_free_result($r); + } +} diff --git a/tests/use_after_free-001.phpt b/tests/use_after_free-001.phpt new file mode 100644 index 0000000..56c942f --- /dev/null +++ b/tests/use_after_free-001.phpt @@ -0,0 +1,38 @@ +--TEST-- +InterBase: use after ibase_free_query() +--SKIPIF-- += 61)) print "Skip IBASE_VER < 6.1"; +?> +--FILE-- + +--EXPECT-- +---- Batch 1 ---- +array(2) { + ["I"]=> + int(5) + ["C"]=> + string(15) "ROW 1 (batch 5)" +} +array(2) { + ["I"]=> + int(5) + ["C"]=> + string(15) "ROW 2 (batch 5)" +} +---- Batch 2 ---- +---- Batch 3 ---- +---- Batch 4 ---- +---- Batch 5 ---- \ No newline at end of file diff --git a/tests/use_after_free-002.phpt b/tests/use_after_free-002.phpt new file mode 100644 index 0000000..ca3944f --- /dev/null +++ b/tests/use_after_free-002.phpt @@ -0,0 +1,26 @@ +--TEST-- +InterBase: use after ibase_free_query() +--SKIPIF-- += 6.1"; +?> +--FILE-- + +--EXPECT-- +---- Batch 1 ---- +Fatal error: Uncaught TypeError: ibase_fetch_assoc(): supplied resource is not a valid Firebird/InterBase query resource From c65479b7684d4ca650e967fc9f0233f7c3f47f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 30 Oct 2025 16:41:59 +0200 Subject: [PATCH 50/66] Update comments --- php_interbase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php_interbase.h b/php_interbase.h index 929c52c..53034cf 100644 --- a/php_interbase.h +++ b/php_interbase.h @@ -43,7 +43,7 @@ extern zend_module_entry ibase_module_entry; #define PHP_INTERBASE_VER_MINOR 1 #define PHP_INTERBASE_VER_REV 1 -// Keep FB_API_VER two digit style +// Keep two digit style similar to FB_API_VER #define PHP_INTERBASE_VER PHP_INTERBASE_VER_MAJOR * 10 + PHP_INTERBASE_VER_MINOR #define PHP_INTERBASE_VER_STR TO_STRING(PHP_INTERBASE_VER_MAJOR) "." TO_STRING(PHP_INTERBASE_VER_MINOR) "." TO_STRING(PHP_INTERBASE_VER_REV) From 01e2ff5e87e4734ebd41d8e2c1d248c7c5e37ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 30 Oct 2025 18:19:37 +0200 Subject: [PATCH 51/66] Skip tests/use_after_free-002.phpt for PHP version < 8 Do not want to create a separate test for Warning: instead of Fatal: --- tests/use_after_free-002.phpt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/use_after_free-002.phpt b/tests/use_after_free-002.phpt index ca3944f..637cbed 100644 --- a/tests/use_after_free-002.phpt +++ b/tests/use_after_free-002.phpt @@ -3,7 +3,8 @@ InterBase: use after ibase_free_query() --SKIPIF-- = 6.1"; +if(!defined('IBASE_VER') || (IBASE_VER < 61)) print "Skip IBASE_VER < 6.1"; +if(PHP_MAJOR_VERSION < 8) print "Skip PHP < 8"; ?> --FILE-- Date: Wed, 5 Nov 2025 14:11:47 +0200 Subject: [PATCH 52/66] Refactor out inner loops in php-fb-build.bat --- build_scripts/php-fb-build-all.bat | 22 ++-- build_scripts/php-fb-build.bat | 164 +++++++++++++---------------- build_scripts/php-fb-config.bat | 28 +++-- 3 files changed, 105 insertions(+), 109 deletions(-) diff --git a/build_scripts/php-fb-build-all.bat b/build_scripts/php-fb-build-all.bat index 2620cf9..e01bfc7 100644 --- a/build_scripts/php-fb-build-all.bat +++ b/build_scripts/php-fb-build-all.bat @@ -1,10 +1,16 @@ @echo off -call %~dp0php-fb-build.bat php-7.3.33 vc15 || exit %ERRORLEVEL% -call %~dp0php-fb-build.bat php-7.4.13 vc15 || exit %ERRORLEVEL% -call %~dp0php-fb-build.bat php-8.0.30 vs16 || exit %ERRORLEVEL% -call %~dp0php-fb-build.bat php-8.1.33 vs16 || exit %ERRORLEVEL% -call %~dp0php-fb-build.bat php-8.2.29 vs16 || exit %ERRORLEVEL% -call %~dp0php-fb-build.bat php-8.3.26 vs16 || exit %ERRORLEVEL% -call %~dp0php-fb-build.bat php-8.4.13 vs17 || exit %ERRORLEVEL% -call %~dp0php-fb-build.bat php-8.5.0RC2 vs17 || exit %ERRORLEVEL% +set "phps="php-7.4.13 vc15" "php-8.0.30 vs16" "php-8.1.33 vs16" "php-8.2.29 vs16" "php-8.3.26 vs16" "php-8.4.13 vs17" "php-8.5.0RC2 vs17"" + +setlocal enabledelayedexpansion +for %%p in (%phps%) do ( + for /f "tokens=1,2" %%a in (%%p) do ( + set php_vers=%%a + set cpp_vers=%%b + call %~dp0php-fb-build.bat !php_vers! !cpp_vers! 1 x64 || exit %ERRORLEVEL% + call %~dp0php-fb-build.bat !php_vers! !cpp_vers! 0 x64 || exit %ERRORLEVEL% + call %~dp0php-fb-build.bat !php_vers! !cpp_vers! 1 x86 || exit %ERRORLEVEL% + call %~dp0php-fb-build.bat !php_vers! !cpp_vers! 0 x86 || exit %ERRORLEVEL% + ) +) +endlocal diff --git a/build_scripts/php-fb-build.bat b/build_scripts/php-fb-build.bat index 58872d4..f03931a 100644 --- a/build_scripts/php-fb-build.bat +++ b/build_scripts/php-fb-build.bat @@ -1,128 +1,112 @@ @echo off +@REM php-fb-build.bat +@REM php-fb-build.bat php-7.4.13 vc15 [0|1] [x64|x86] +@REM @REM config ====================================================================================== call %~dp0php-fb-config.bat -goto :MAIN - -@REM log ========================================================================================= -@REM log -@REM example> call :log "" -:log - set msg=%~1 - echo --------------------------------------------------------------------- - echo %msg% - echo --------------------------------------------------------------------- -exit /B - -@REM usage ======================================================================================= -:usage - call :log "Usage: %~nx0 php_tag cpp_vers" -exit /B - -@REM validate_build =============================================================================== -@REM validate_build :string :string :int -:validate_build -setlocal disabledelayedexpansion -set vb_php=%~1 -set vb_arch=%~2 -set vb_ts=%~3 - -set vb_check_code=^ -if(!extension_loaded('interbase')){ print \"Extension not loaded\n\"; exit(1); }^ -if('php-'.PHP_VERSION != '%pfb_php_tag%'){ printf(\"Version mismatch: expected '%pfb_php_tag%', but got '%%s' \n\", 'php-'.PHP_VERSION); exit(1); }^ -if((int)ZEND_THREAD_SAFE != %vb_ts%){ printf(\"Thread Safety mismatch: expected %vb_ts%, but got %%d \n\", ZEND_THREAD_SAFE); exit(1); }^ -if((PHP_INT_SIZE == 8 ? 'x64' : 'x86') != '%vb_arch%'){ printf(\"Architecture mismatch: expected '%vb_arch%', but got '%%s' \n\", (PHP_INT_SIZE == 8 ? 'x64' : 'x86')); exit(1); } - -if "%vb_arch%" == "x86" ( - set vb_libs=%PFB_FB32_DIR% -) else ( - set vb_libs=%PFB_FB64_DIR% -) - -call :log "Validating %pfb_php_tag% %vb_arch% Thread Safety %vb_ts%" - -set vb_cmd=cmd /c set "PATH=%vb_libs%;%PATH%" %php_exe% -dextension=.\php_interbase.dll -r "%vb_check_code%" -%vb_cmd% || exit /B 1 - -echo Validated OK -echo --------------------------------------------------------------------- - -exit /B - -:MAIN - set pfb_php_tag=%1 set pfb_cpp_vers=%2 +set pfb_ts=%3 +set pfb_arch=%4 if "%pfb_php_tag%" == "" ( + call :log "pfb_php_tag varible not set" call :usage - echo pfb_php_tag varible not set exit 1 ) if "%pfb_cpp_vers%" == "" ( + call :log "pfb_cpp_vers varible not set" call :usage - echo pfb_cpp_vers varible not set exit 1 ) +if "%pfb_ts%" gtr "0" ( + set pfb_ts=1 +) else ( + set pfb_ts=0 +) + +if not "%pfb_arch%" == "x86" ( + set pfb_arch=x64 +) + @REM Convert php-8.4.13 -> 8.4 for /f "tokens=2,3 delims=-." %%a in ("%pfb_php_tag%") do set pfb_php_vers=%%a.%%b if "%pfb_php_vers%" == "" ( - echo BUG: pfb_php_vers should be set at this point + call :log "BUG: pfb_php_vers should be set at this point" exit 1 ) -set pfb_build_root=php%pfb_php_vers%\%pfb_cpp_vers%\ +@REM Initialize +set php_root=php%pfb_php_vers%\%pfb_cpp_vers%\%pfb_arch%\php-src\ +set php_interbase=php_interbase-%PFB_VERS%-%pfb_php_vers%-%pfb_cpp_vers% -(for %%a in (x64 x86) do ( - set pfb_arch=%%a +if not exist "%php_root%.git\" ( + call :log "Cloning %pfb_php_tag% %pfb_arch%" + call phpsdk-%pfb_cpp_vers%-%pfb_arch%.bat -t %~dp0php-fb-sdk-init.bat || goto :error +) - if not exist "%pfb_build_root%\%%a\php-src\.git\" ( - call :log "Cloning %pfb_php_tag% %%a" - call phpsdk-%pfb_cpp_vers%-%%a.bat -t %~dp0php-fb-sdk-init.bat || goto :error - ) +if "%pfb_arch%" == "x86" ( + set build_root=%php_root% + set php_interbase=%php_interbase%-x86 +) else ( + set build_root=%php_root%x64\ +) - if "%%a" == "x86" ( - set php_exe_arch=%pfb_build_root%%%a\php-src\ - ) else ( - set php_exe_arch=%pfb_build_root%%%a\php-src\x64\ - ) +if "%pfb_ts%" gtr "0" ( + set build_root=%build_root%Release_TS\ +) else ( + set build_root=%build_root%Release\ + set php_interbase=%php_interbase%-nts +) - setlocal enabledelayedexpansion - (for %%t in (0 1) do ( - set pfb_ts=%%t - if "%%t" equ "1" ( - set php_exe="!php_exe_arch!Release_TS\php.exe" - ) else ( - set php_exe="!php_exe_arch!Release\php.exe" - ) +@REM Build +call :log "Building %php_interbase%.dll..." +call phpsdk-%pfb_cpp_vers%-%pfb_arch%.bat -t %~dp0php-fb-sdk-build.bat || goto :error - if "!php_exe!" == "" ( - echo BUG: php_exe should be set at this point - exit 1 - ) +@REM Validate +set vb_check_code=^ +if(!extension_loaded('interbase')){ print \"Extension not loaded\n\"; exit(1); }^ +if('php-'.PHP_VERSION != '%pfb_php_tag%'){ printf(\"Version mismatch: expected '%pfb_php_tag%', but got '%%s' \n\", 'php-'.PHP_VERSION); exit(1); }^ +if((int)ZEND_THREAD_SAFE != %pfb_ts%){ printf(\"Thread Safety mismatch: expected %pfb_ts%, but got %%d \n\", ZEND_THREAD_SAFE); exit(1); }^ +if((PHP_INT_SIZE == 8 ? 'x64' : 'x86') != '%pfb_arch%'){ printf(\"Architecture mismatch: expected '%pfb_arch%', but got '%%s' \n\", (PHP_INT_SIZE == 8 ? 'x64' : 'x86')); exit(1); } - call phpsdk-%pfb_cpp_vers%-%%a.bat -t %~dp0php-fb-sdk-build.bat || goto :error +if "%pfb_arch%" == "x86" ( + set vb_libs=%PFB_FB32_DIR% +) else ( + set vb_libs=%PFB_FB64_DIR% +) - call :validate_build !php_exe! !pfb_arch! !pfb_ts! || goto :error - )) -)) +call :log "Validating %php_interbase%.dll..." +set vb_cmd=cmd /c set "PATH=%vb_libs%;%PATH%" %build_root%php_exe -dextension=.\php_interbase.dll -r "%vb_check_code%" +%vb_cmd% || goto :error -echo. -call :log "%pfb_php_tag% build OK" +call :log "Copying %php_interbase%.dll..." +copy "%build_root%php_interbase.dll" "%PFB_OUTPUT_DIR%%php_interbase%.dll" || goto :error -@REM copy compiled extension to target directory -copy %pfb_build_root%x64\php-src\x64\Release_TS\php_interbase.dll %PFB_OUTPUT_DIR%php_interbase-%PFB_VERS%-%pfb_php_vers%-%pfb_cpp_vers%-x64.dll -copy %pfb_build_root%x64\php-src\x64\Release\php_interbase.dll %PFB_OUTPUT_DIR%php_interbase-%PFB_VERS%-%pfb_php_vers%-%pfb_cpp_vers%-nts-x64.dll -copy %pfb_build_root%x86\php-src\Release_TS\php_interbase.dll %PFB_OUTPUT_DIR%php_interbase-%PFB_VERS%-%pfb_php_vers%-%pfb_cpp_vers%.dll -copy %pfb_build_root%x86\php-src\Release\php_interbase.dll %PFB_OUTPUT_DIR%php_interbase-%PFB_VERS%-%pfb_php_vers%-%pfb_cpp_vers%-nts.dll +call :log "Build OK" "%pfb_php_tag% %pfb_cpp_vers% %pfb_arch% Thread Safety %pfb_ts%" "%php_interbase%.dll" -exit /B 0 +exit /B -:error - call :log "%pfb_php_tag% build FAILED" +@REM log ========================================================================================= +@REM log +@REM example> call :log "" +:log + echo --------------------------------------------------------------------- + for %%a in (%*) do ( echo %%~a ) + echo --------------------------------------------------------------------- +exit /B +@REM usage ======================================================================================= +:usage + call :log "Usage: %~nx0 php_tag cpp_vers [ts=0|1] [arch=x86|x64]" " Example: %~nx0 php-8.4.13 vs17 1 x86" +exit /B + +@REM error ======================================================================================= +:error + call :log "Build FAILED" "%pfb_php_tag% %pfb_cpp_vers% %pfb_arch% Thread Safety %pfb_ts%" "%php_interbase%.dll" exit /B 1 diff --git a/build_scripts/php-fb-config.bat b/build_scripts/php-fb-config.bat index 076ace2..5cfe45c 100644 --- a/build_scripts/php-fb-config.bat +++ b/build_scripts/php-fb-config.bat @@ -1,18 +1,24 @@ -@REM -@REM git command must be in PATH -@REM - +@echo off @REM php-firebird source directory -set PFB_SOURCE_DIR=D:\php-firebird\ - -for /f %%i in ('git -C %PFB_SOURCE_DIR%\php-firebird\ rev-parse --short HEAD') do set GIT_HASH=%%i - -@REM sets php-firebird version part in extension file, for example, php_interbase-<<3.0.1-ba8e63b>>-7.3-vc15.dll -set PFB_VERS=3.0.1-%GIT_HASH% +@REM Should point one level up. For example +@REM PFB_SOURCE_DIR=D:\php-firebird\ then your source should reside in D:\php-firebird\php-firebird\ +set PFB_SOURCE_DIR=%~dp0..\..\ @REM Directory where all compiled files will be copied -set PFB_OUTPUT_DIR=D:\php-firebird\releases\ +set PFB_OUTPUT_DIR=%PFB_SOURCE_DIR%releases\ @REM FB 32-bit and 64-bit libraries set PFB_FB32_DIR=C:\Program Files\Firebird\Firebird_5_0-x86 set PFB_FB64_DIR=C:\Program Files\Firebird\Firebird_5_0 + +@REM Attach current git commit hash. git command must be in PATH +set USE_GIT_HASH=0 + +for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_MAJOR" %~dp0..\php_interbase.h') do set VER_MAJOR=%%i +for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_MINOR" %~dp0..\php_interbase.h') do set VER_MINOR=%%i +for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_REV" %~dp0..\php_interbase.h') do set VER_REV=%%i +set PFB_VERS=%VER_MAJOR%.%VER_MINOR%.%VER_REV%-beta + +if "%USE_GIT_HASH%" gtr "0" ( + for /f %%i in ('git -C %~dp0..\ rev-parse --short HEAD') do set PFB_VERS=%PFB_VERS%-%%i +) From 4f23140a7e9edd8c79f7d2b20c75d7bc0307665a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 6 Nov 2025 02:05:22 +0200 Subject: [PATCH 53/66] Fix nts check --- build_scripts/php-fb-build.bat | 4 ++-- build_scripts/php-fb-config.bat | 4 ++-- build_scripts/php-fb-sdk-build.bat | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build_scripts/php-fb-build.bat b/build_scripts/php-fb-build.bat index f03931a..e236b30 100644 --- a/build_scripts/php-fb-build.bat +++ b/build_scripts/php-fb-build.bat @@ -23,7 +23,7 @@ if "%pfb_cpp_vers%" == "" ( exit 1 ) -if "%pfb_ts%" gtr "0" ( +if "%pfb_ts%" == "1" ( set pfb_ts=1 ) else ( set pfb_ts=0 @@ -57,7 +57,7 @@ if "%pfb_arch%" == "x86" ( set build_root=%php_root%x64\ ) -if "%pfb_ts%" gtr "0" ( +if %pfb_ts% equ 1 ( set build_root=%build_root%Release_TS\ ) else ( set build_root=%build_root%Release\ diff --git a/build_scripts/php-fb-config.bat b/build_scripts/php-fb-config.bat index 5cfe45c..0c16bb7 100644 --- a/build_scripts/php-fb-config.bat +++ b/build_scripts/php-fb-config.bat @@ -17,8 +17,8 @@ set USE_GIT_HASH=0 for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_MAJOR" %~dp0..\php_interbase.h') do set VER_MAJOR=%%i for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_MINOR" %~dp0..\php_interbase.h') do set VER_MINOR=%%i for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_REV" %~dp0..\php_interbase.h') do set VER_REV=%%i -set PFB_VERS=%VER_MAJOR%.%VER_MINOR%.%VER_REV%-beta +set PFB_VERS=%VER_MAJOR%.%VER_MINOR%.%VER_REV%-RC0 -if "%USE_GIT_HASH%" gtr "0" ( +if %USE_GIT_HASH% equ 1 ( for /f %%i in ('git -C %~dp0..\ rev-parse --short HEAD') do set PFB_VERS=%PFB_VERS%-%%i ) diff --git a/build_scripts/php-fb-sdk-build.bat b/build_scripts/php-fb-sdk-build.bat index 14e3944..eb3e4e2 100644 --- a/build_scripts/php-fb-sdk-build.bat +++ b/build_scripts/php-fb-sdk-build.bat @@ -32,12 +32,12 @@ exit /B set build_msg=Building PHP-%pfb_php_vers% - if "%pfb_ts%" gtr "0" ( - set build_msg=%build_msg% non-TS - set extra_args=--disable-zts - ) else ( + if %pfb_ts% equ 1 ( set build_msg=%build_msg% TS set extra_args= + ) else ( + set build_msg=%build_msg% non-TS + set extra_args=--disable-zts ) if "%pfb_arch%" == "x86" ( From e5843f32f6ec77c19efa4d754458124f12473591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 6 Nov 2025 03:03:40 +0200 Subject: [PATCH 54/66] Fix variable names --- tests/proc-001.phpt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/proc-001.phpt b/tests/proc-001.phpt index e9698ad..70b92f6 100644 --- a/tests/proc-001.phpt +++ b/tests/proc-001.phpt @@ -14,11 +14,11 @@ require("interbase.inc"); AS DECLARE VARIABLE I INTEGER; BEGIN - :I = 1; + I = 1; WHILE (:I <= 5) DO BEGIN - :N = :I; - :RESULT = :ARG + :I; - :I =:I + 1; + N = :I; + RESULT = :ARG + :I; + I = :I + 1; SUSPEND; END END"); From 266a4a5fad75bc6f5b72a8467f28ba0eafa30a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 6 Nov 2025 18:27:27 +0200 Subject: [PATCH 55/66] Add PFB_CONFIGURE_FLAGS config --- build_scripts/php-fb-config.bat | 2 ++ build_scripts/php-fb-sdk-build.bat | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build_scripts/php-fb-config.bat b/build_scripts/php-fb-config.bat index 0c16bb7..faaa125 100644 --- a/build_scripts/php-fb-config.bat +++ b/build_scripts/php-fb-config.bat @@ -22,3 +22,5 @@ set PFB_VERS=%VER_MAJOR%.%VER_MINOR%.%VER_REV%-RC0 if %USE_GIT_HASH% equ 1 ( for /f %%i in ('git -C %~dp0..\ rev-parse --short HEAD') do set PFB_VERS=%PFB_VERS%-%%i ) + +set PFB_CONFIGURE_FLAGS=--enable-debug diff --git a/build_scripts/php-fb-sdk-build.bat b/build_scripts/php-fb-sdk-build.bat index eb3e4e2..ef43fd5 100644 --- a/build_scripts/php-fb-sdk-build.bat +++ b/build_scripts/php-fb-sdk-build.bat @@ -53,5 +53,5 @@ exit /B call phpsdk_buildtree php%pfb_php_vers% cd /D php-src call buildconf.bat --force --add-modules-dir=%PFB_SOURCE_DIR% - call configure.bat --disable-all --enable-cli %extra_args% --with-interbase=%with_interbase% + call configure.bat --disable-all --enable-cli %PFB_CONFIGURE_FLAGS% %extra_args% --with-interbase=%with_interbase% nmake From 23e2a4b2929438dde0c4cbd385c85a97fb7cbe82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 6 Nov 2025 18:28:30 +0200 Subject: [PATCH 56/66] Add PHP_INTERBASE_VER_PRE tag --- build_scripts/php-fb-config.bat | 4 +++- php_interbase.h | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/build_scripts/php-fb-config.bat b/build_scripts/php-fb-config.bat index faaa125..ffc2109 100644 --- a/build_scripts/php-fb-config.bat +++ b/build_scripts/php-fb-config.bat @@ -14,10 +14,12 @@ set PFB_FB64_DIR=C:\Program Files\Firebird\Firebird_5_0 @REM Attach current git commit hash. git command must be in PATH set USE_GIT_HASH=0 +@REM Grab version for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_MAJOR" %~dp0..\php_interbase.h') do set VER_MAJOR=%%i for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_MINOR" %~dp0..\php_interbase.h') do set VER_MINOR=%%i for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_REV" %~dp0..\php_interbase.h') do set VER_REV=%%i -set PFB_VERS=%VER_MAJOR%.%VER_MINOR%.%VER_REV%-RC0 +for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_PRE" %~dp0..\php_interbase.h') do set VER_PRE=%%~i +set PFB_VERS=%VER_MAJOR%.%VER_MINOR%.%VER_REV%%VER_PRE% if %USE_GIT_HASH% equ 1 ( for /f %%i in ('git -C %~dp0..\ rev-parse --short HEAD') do set PFB_VERS=%PFB_VERS%-%%i diff --git a/php_interbase.h b/php_interbase.h index 53034cf..7a9c500 100644 --- a/php_interbase.h +++ b/php_interbase.h @@ -42,10 +42,15 @@ extern zend_module_entry ibase_module_entry; #define PHP_INTERBASE_VER_MAJOR 6 #define PHP_INTERBASE_VER_MINOR 1 #define PHP_INTERBASE_VER_REV 1 +#define PHP_INTERBASE_VER_PRE "-RC1" // Keep two digit style similar to FB_API_VER #define PHP_INTERBASE_VER PHP_INTERBASE_VER_MAJOR * 10 + PHP_INTERBASE_VER_MINOR -#define PHP_INTERBASE_VER_STR TO_STRING(PHP_INTERBASE_VER_MAJOR) "." TO_STRING(PHP_INTERBASE_VER_MINOR) "." TO_STRING(PHP_INTERBASE_VER_REV) +#define PHP_INTERBASE_VER_STR \ + TO_STRING(PHP_INTERBASE_VER_MAJOR) "." \ + TO_STRING(PHP_INTERBASE_VER_MINOR) "." \ + TO_STRING(PHP_INTERBASE_VER_REV) \ + PHP_INTERBASE_VER_PRE PHP_MINIT_FUNCTION(ibase); PHP_RINIT_FUNCTION(ibase); From 58a74b806b70060082457786442419ce9751851b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 6 Nov 2025 18:29:45 +0200 Subject: [PATCH 57/66] Add flags argument --- tests/functions.inc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/functions.inc b/tests/functions.inc index 921aa7a..c56319e 100644 --- a/tests/functions.inc +++ b/tests/functions.inc @@ -93,17 +93,17 @@ function array2sql_parts($data) { return [...fields2sql_parts(array_keys($data)), $data]; } -function dump_rows($q) { - while($row = ibase_fetch_assoc($q)){ +function dump_rows($q, int $flags = 0) { + while($row = ibase_fetch_assoc($q, $flags)){ var_dump($row); } } -function dump_table_rows($table, $tr = null) { +function dump_table_rows($table, $tr = null, int $flags = 0) { if($tr)$args[] = $tr; $args[] = sprintf('SELECT * FROM "%s"', $table); - dump_rows(ibase_query(...$args)); + dump_rows(ibase_query(...$args), $flags); } function insert_into($table, $data, $tr = null) { From 27265a5924ae7a648202ff08be04e1b743981316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 6 Nov 2025 18:33:34 +0200 Subject: [PATCH 58/66] Introduce ibase_get_client_version() --- interbase.c | 35 +++++++++++++++++++++++++++++++++-- pdo_firebird_utils.cpp | 4 ++-- pdo_firebird_utils.h | 6 +----- php_ibase_includes.h | 14 ++++++++++++-- php_interbase.h | 2 ++ tests/functions.inc | 12 +++++++++--- tests/long_names_001.phpt | 2 +- 7 files changed, 60 insertions(+), 15 deletions(-) diff --git a/interbase.c b/interbase.c index 91ba8a4..059f42e 100644 --- a/interbase.c +++ b/interbase.c @@ -317,6 +317,9 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_ibase_free_event_handler, 0, 0, 1) ZEND_ARG_INFO(0, event) ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_ibase_get_client_version, 0) +ZEND_END_ARG_INFO() /* }}} */ /* {{{ extension definition structures */ @@ -378,6 +381,8 @@ static const zend_function_entry ibase_functions[] = { PHP_FE(ibase_set_event_handler, arginfo_ibase_set_event_handler) PHP_FE(ibase_free_event_handler, arginfo_ibase_free_event_handler) + PHP_FE(ibase_get_client_version, arginfo_ibase_get_client_version) + /** * These aliases are provided in order to maintain forward compatibility. As Firebird * and InterBase are developed independently, functionality might be different between @@ -441,6 +446,8 @@ static const zend_function_entry ibase_functions[] = { PHP_FALIAS(fbird_wait_event, ibase_wait_event, arginfo_ibase_wait_event) PHP_FALIAS(fbird_set_event_handler, ibase_set_event_handler, arginfo_ibase_set_event_handler) PHP_FALIAS(fbird_free_event_handler, ibase_free_event_handler, arginfo_ibase_free_event_handler) + + PHP_FALIAS(fbird_get_client_version, ibase_get_client_version, arginfo_ibase_get_client_version) PHP_FE_END }; @@ -491,6 +498,18 @@ PHP_FUNCTION(ibase_errmsg) } /* }}} */ +/* {{{ proto float ibase_get_client_version(void) + Return client version in form major.minor */ +PHP_FUNCTION(ibase_get_client_version) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + RETURN_DOUBLE((double)IBG(client_major_version) + (double)IBG(client_minor_version) / 10); +} +/* }}} */ + /* {{{ proto int ibase_errcode(void) Return error code */ PHP_FUNCTION(ibase_errcode) @@ -811,8 +830,20 @@ static PHP_GINIT_FUNCTION(ibase) ibase_globals->num_persistent = ibase_globals->num_links = 0; ibase_globals->sql_code = *ibase_globals->errmsg = 0; ibase_globals->default_link = NULL; - ibase_globals->fb_get_master_interface = _php_ibase_get_fbclient_symbol("fb_get_master_interface"); - ibase_globals->fb_get_statement_interface = _php_ibase_get_fbclient_symbol("fb_get_statement_interface"); + ibase_globals->get_master_interface = _php_ibase_get_fbclient_symbol("fb_get_master_interface"); + ibase_globals->get_statement_interface = _php_ibase_get_fbclient_symbol("fb_get_statement_interface"); + + if (ibase_globals->get_master_interface) { + ibase_globals->master_instance = ((fb_get_master_interface_t)(ibase_globals->get_master_interface))(); + ibase_globals->client_version = fb_get_client_version(ibase_globals->master_instance); + ibase_globals->client_major_version = ibase_globals->client_version >> 8; + ibase_globals->client_minor_version = ibase_globals->client_version & 0xFF; + } else { + ibase_globals->master_instance = NULL; + ibase_globals->client_version = -1; + ibase_globals->client_major_version = -1; + ibase_globals->client_minor_version = -1; + } } PHP_MINIT_FUNCTION(ibase) diff --git a/pdo_firebird_utils.cpp b/pdo_firebird_utils.cpp index d24f142..fab67c7 100644 --- a/pdo_firebird_utils.cpp +++ b/pdo_firebird_utils.cpp @@ -25,9 +25,9 @@ #include "php_ibase_includes.h" /* Returns the client version. 0 bytes are minor version, 1 bytes are major version. */ -extern "C" unsigned fb_get_client_version(void) +extern "C" unsigned fb_get_client_version(void *master_ptr) { - Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IMaster* master = (Firebird::IMaster*)master_ptr; Firebird::IUtil* util = master->getUtilInterface(); return util->getClientVersion(); } diff --git a/pdo_firebird_utils.h b/pdo_firebird_utils.h index b5699dc..199a57d 100644 --- a/pdo_firebird_utils.h +++ b/pdo_firebird_utils.h @@ -26,11 +26,7 @@ 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); +unsigned fb_get_client_version(void *master_ptr); void fb_decode_time_tz(const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, unsigned timeZoneBufferLength, char* timeZoneBuffer); diff --git a/php_ibase_includes.h b/php_ibase_includes.h index ce88266..0851e6b 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -75,8 +75,12 @@ ZEND_BEGIN_MODULE_GLOBALS(ibase) zend_long sql_code; zend_long default_trans_params; zend_long default_lock_timeout; // only used togetger with trans_param IBASE_LOCK_TIMEOUT - void *fb_get_master_interface; - void *fb_get_statement_interface; + void *get_master_interface; + void *master_instance; + void *get_statement_interface; + int client_version; + int client_major_version; + int client_minor_version; ZEND_END_MODULE_GLOBALS(ibase) ZEND_EXTERN_MODULE_GLOBALS(ibase) @@ -288,4 +292,10 @@ void fbp_error_ex(long level, const char *, ...) #define fbp_notice(msg, ...) fbp_error_ex(E_NOTICE, msg " (%s:%d)\n" __VA_OPT__(,) __VA_ARGS__, __FILE__, __LINE__) #endif +typedef ISC_STATUS (ISC_EXPORT *fb_get_statement_interface_t)( + ISC_STATUS* status_vector, void* db_handle, isc_stmt_handle* stmt_handle +); + +typedef void* (ISC_EXPORT *fb_get_master_interface_t)(void); + #endif /* PHP_IBASE_INCLUDES_H */ diff --git a/php_interbase.h b/php_interbase.h index 7a9c500..0ae61f7 100644 --- a/php_interbase.h +++ b/php_interbase.h @@ -119,6 +119,8 @@ PHP_FUNCTION(ibase_wait_event); PHP_FUNCTION(ibase_set_event_handler); PHP_FUNCTION(ibase_free_event_handler); +PHP_FUNCTION(ibase_get_client_version); + #else #define phpext_interbase_ptr NULL diff --git a/tests/functions.inc b/tests/functions.inc index c56319e..7c2765e 100644 --- a/tests/functions.inc +++ b/tests/functions.inc @@ -118,17 +118,23 @@ function insert_into($table, $data, $tr = null) { /** @var float $v */ function skip_if_fb_lt($v) { - if(($cv = get_fb_version()) < $v)die("skip: Firebird version $cv < $v"); + if(($cv = get_fb_version()) < $v)die("skip Firebird server version $cv < $v"); } /** @var float $v */ function skip_if_fb_gt($v) { - if(($cv = get_fb_version()) > $v)die("skip: Firebird version $cv > $v"); + if(($cv = get_fb_version()) > $v)die("skip Firebird server version $cv > $v"); } /** @var float $v */ function skip_if_fb_gte($v) { - if(($cv = get_fb_version()) >= $v)die("skip: Firebird version $cv >= $v"); + if(($cv = get_fb_version()) >= $v)die("skip Firebird server version $cv >= $v"); +} + +/** @var float $v */ +function skip_if_fbclient_lt($v) { + if(!function_exists("ibase_get_client_version"))die("skip Unable to determine Firebird client library version"); + if(($cv = ibase_get_client_version()) < $v)die("skip Firebird client library version $cv < $v"); } function ibase_query_bulk(array $queries, $tr = null) { diff --git a/tests/long_names_001.phpt b/tests/long_names_001.phpt index 9261982..9c96596 100644 --- a/tests/long_names_001.phpt +++ b/tests/long_names_001.phpt @@ -3,7 +3,7 @@ Long names: Firebird 4.0 or newer --SKIPIF-- --FILE-- Date: Thu, 6 Nov 2025 18:35:33 +0200 Subject: [PATCH 59/66] Add some tests --- tests/001-table.sql | 20 ++++++++++++++ tests/datatype_001.phpt | 60 +++++++++++++++++++++++++++++++++++++++++ tests/time_001.phpt | 41 ++++++++++++++++++++++++++++ tests/time_002.phpt | 42 +++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+) create mode 100644 tests/001-table.sql create mode 100644 tests/datatype_001.phpt create mode 100644 tests/time_001.phpt create mode 100644 tests/time_002.phpt diff --git a/tests/001-table.sql b/tests/001-table.sql new file mode 100644 index 0000000..3ef9e63 --- /dev/null +++ b/tests/001-table.sql @@ -0,0 +1,20 @@ +RECREATE TABLE TEST_001 +( + ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL PRIMARY KEY, + BLOB_0 BLOB DEFAULT 'BLOB_0', + BLOB_1 BLOB SUB_TYPE 1 DEFAULT 'BLOB_1', + BOOL_1 BOOLEAN DEFAULT TRUE, + DATE_1 DATE DEFAULT '2025-11-06', + TIME_1 TIME WITHOUT TIME ZONE DEFAULT '15:45:59', + DECFLOAT_16 DECFLOAT(16) DEFAULT 3.141592653589793, -- MAX 9.999999999999999E+384 + DECFLOAT_34 DECFLOAT(34) DEFAULT 3.141592653589793238462643383279502, -- MAX 9.999999999999999999999999999999999E+6144 + INT_NOT_NULL INTEGER DEFAULT 1 NOT NULL, + DOUBLE_PRECISION_1 DOUBLE PRECISION DEFAULT 3.141592653589793, + FLOAT_1 FLOAT DEFAULT 3.141592653589793, + INT_1 INTEGER DEFAULT 1, + INT_128 INT128 DEFAULT 170141183460469231731687303715884105727, + VARCHAR_1 VARCHAR(100) DEFAULT 'VARCHAR_1', + SMALLINT_1 SMALLINT DEFAULT 1, + TIME_TZ TIME WITH TIME ZONE DEFAULT '15:45:59 Europe/Riga', + TIMESTAMP_TZ TIMESTAMP WITH TIME ZONE DEFAULT '2025-11-06 15:45:59 Europe/Riga' +); diff --git a/tests/datatype_001.phpt b/tests/datatype_001.phpt new file mode 100644 index 0000000..53c2b2b --- /dev/null +++ b/tests/datatype_001.phpt @@ -0,0 +1,60 @@ +--TEST-- +Check for data types using old clients +--SKIPIF-- + +--FILE-- + +--EXPECT-- +array(17) { + ["ID"]=> + int(1) + ["BLOB_0"]=> + string(6) "BLOB_0" + ["BLOB_1"]=> + string(6) "BLOB_1" + ["BOOL_1"]=> + bool(true) + ["DATE_1"]=> + string(10) "2025-11-06" + ["TIME_1"]=> + string(8) "15:45:59" + ["DECFLOAT_16"]=> + string(17) "3.141592653589793" + ["DECFLOAT_34"]=> + string(35) "3.141592653589793238462643383279502" + ["INT_NOT_NULL"]=> + int(1) + ["DOUBLE_PRECISION_1"]=> + float(3.141592653589793) + ["FLOAT_1"]=> + float(3.1415927410125732) + ["INT_1"]=> + int(1) + ["INT_128"]=> + string(39) "170141183460469231731687303715884105727" + ["VARCHAR_1"]=> + string(9) "VARCHAR_1" + ["SMALLINT_1"]=> + int(1) + ["TIME_TZ"]=> + string(20) "15:45:59 Europe/Riga" + ["TIMESTAMP_TZ"]=> + string(31) "2025-11-06 15:45:59 Europe/Riga" +} \ No newline at end of file diff --git a/tests/time_001.phpt b/tests/time_001.phpt new file mode 100644 index 0000000..8bb1c4d --- /dev/null +++ b/tests/time_001.phpt @@ -0,0 +1,41 @@ +--TEST-- +Test unixtimestamp +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +array(3) { + ["ID"]=> + int(1) + ["T1"]=> + string(8) "15:45:59" + ["T2"]=> + string(19) "2025-11-06 15:45:59" +} +array(3) { + ["ID"]=> + int(1) + ["T1"]=> + int(-%d) + ["T2"]=> + int(1762436759) +} \ No newline at end of file diff --git a/tests/time_002.phpt b/tests/time_002.phpt new file mode 100644 index 0000000..c624f78 --- /dev/null +++ b/tests/time_002.phpt @@ -0,0 +1,42 @@ +--TEST-- +Test unixtimestamp +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +array(3) { + ["ID"]=> + int(1) + ["T1"]=> + string(20) "15:45:59 Europe/Riga" + ["T2"]=> + string(31) "2025-11-06 15:45:59 Europe/Riga" +} +array(3) { + ["ID"]=> + int(1) + ["T1"]=> + int(-%d) + ["T2"]=> + int(1762436759) +} \ No newline at end of file From cb0a5771d73ee02f76e48b219dcedd448cdee294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 6 Nov 2025 18:37:47 +0200 Subject: [PATCH 60/66] Cap negative XSQLVAR lengths (#87) --- ibase_query.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index bab3eed..3ef7140 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -50,6 +50,10 @@ #define FETCH_ROW 1 #define FETCH_ARRAY 2 +// Appearantly XSQLVAR len fields can come in > 31 and < 0 depending on +// fbclient-server combination +#define CAP_XSQLVAR_LEN(len, str) ((len) > 31 || (len) < 0 ? MIN(31, strlen((str))) : (len)) + typedef struct { unsigned short vary_length; char vary_string[1]; @@ -1915,14 +1919,14 @@ static void _php_ibase_field_info(zval *return_value, ibase_query *ib_query, int } else { #endif // Old API - add_index_stringl(return_value, 0, var->sqlname, MIN(31, var->sqlname_length)); - add_assoc_stringl(return_value, "name", var->sqlname, MIN(31, var->sqlname_length)); + add_index_stringl(return_value, 0, var->sqlname, CAP_XSQLVAR_LEN(var->sqlname_length, var->sqlname)); + add_assoc_stringl(return_value, "name", var->sqlname, CAP_XSQLVAR_LEN(var->sqlname_length, var->sqlname)); - add_index_stringl(return_value, 1, var->aliasname, MIN(31, var->aliasname_length)); - add_assoc_stringl(return_value, "alias", var->aliasname, MIN(31, var->aliasname_length)); + add_index_stringl(return_value, 1, var->aliasname, CAP_XSQLVAR_LEN(var->aliasname_length, var->aliasname)); + add_assoc_stringl(return_value, "alias", var->aliasname, CAP_XSQLVAR_LEN(var->aliasname_length, var->aliasname)); - add_index_stringl(return_value, 2, var->relname, MIN(31, var->relname_length)); - add_assoc_stringl(return_value, "relation", var->relname, MIN(31, var->relname_length)); + add_index_stringl(return_value, 2, var->relname, CAP_XSQLVAR_LEN(var->relname_length, var->relname)); + add_assoc_stringl(return_value, "relation", var->relname, CAP_XSQLVAR_LEN(var->relname_length, var->relname)); #if FB_API_VER >= 40 } #endif @@ -2215,7 +2219,7 @@ static int _php_ibase_alloc_ht_aliases(ibase_query *ib_query) XSQLVAR *var = &ib_query->out_sqlda->sqlvar[i]; _php_ibase_insert_alias(ib_query->ht_aliases, - var->aliasname, MIN(31, var->aliasname_length)); + var->aliasname, CAP_XSQLVAR_LEN(var->aliasname_length, var->aliasname)); } #if FB_API_VER >= 40 } From 4d459ff9c74440270c0f7805da58607bd338fca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Thu, 6 Nov 2025 18:41:55 +0200 Subject: [PATCH 61/66] Don't depend on fb_get_master_interface() and fb_get_statement_interface() at compile time. Rather dynamically check if fbclient has them. --- ibase_query.c | 38 ++++++++++++++++++++++++++++---------- interbase.c | 21 ++++++++++++++++++--- pdo_firebird_utils.cpp | 39 ++++++++++++++++----------------------- pdo_firebird_utils.h | 13 ++++++++----- 4 files changed, 70 insertions(+), 41 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 3ef7140..dbc7335 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -1377,20 +1377,27 @@ static int _php_ibase_var_zval(zval *val, void *data, int type, int len, /* {{{ // case SQL_INT128: case SQL_TIME_TZ: case SQL_TIMESTAMP_TZ: + // Should be converted to VARCHAR via isc_dpb_set_bind tag at + // connect if fbclient does not have fb_get_master_instance(). + // Assert this just in case. + if(!IBG(master_instance)) { + assert(false && "UNREACHABLE"); + } + char timeZoneBuffer[40] = {0}; unsigned year, month, day, hours, minutes, seconds, fractions; if((type & ~1) == SQL_TIME_TZ){ format = INI_STR("ibase.timeformat"); - fb_decode_time_tz((ISC_TIME_TZ *) data, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer); - ISC_TIME time = fb_encode_time(hours, minutes, seconds, fractions); + fb_decode_time_tz(IBG(master_instance), (ISC_TIME_TZ *) data, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer); + ISC_TIME time = fb_encode_time(IBG(master_instance), hours, minutes, seconds, fractions); isc_decode_sql_time(&time, &t); } else { format = INI_STR("ibase.timestampformat"); - fb_decode_timestamp_tz((ISC_TIMESTAMP_TZ *) data, &year, &month, &day, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer); + fb_decode_timestamp_tz(IBG(master_instance), (ISC_TIMESTAMP_TZ *) data, &year, &month, &day, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer); ISC_TIMESTAMP ts; - ts.timestamp_date = fb_encode_date(year, month, day); - ts.timestamp_time = fb_encode_time(hours, minutes, seconds, fractions); + ts.timestamp_date = fb_encode_date(IBG(master_instance), year, month, day); + ts.timestamp_time = fb_encode_time(IBG(master_instance), hours, minutes, seconds, fractions); isc_decode_timestamp(&ts, &t); } @@ -1408,7 +1415,7 @@ static int _php_ibase_var_zval(zval *val, void *data, int type, int len, /* {{{ } break; #endif - case SQL_DATE: /* == case SQL_TIMESTAMP: */ + case SQL_TIMESTAMP: format = INI_STR("ibase.timestampformat"); isc_decode_timestamp((ISC_TIMESTAMP *) data, &t); goto format_date_time; @@ -1911,8 +1918,14 @@ static void _php_ibase_field_info(zval *return_value, ibase_query *ib_query, int // is_outvar #if FB_API_VER >= 40 - if(IBG(fb_get_master_interface) && IBG(fb_get_statement_interface)) { - if(fb_insert_field_info(IB_STATUS, ib_query, is_outvar, num, return_value)){ + if(IBG(master_instance) && IBG(get_statement_interface)) { + void *statement = NULL; + if(((fb_get_statement_interface_t)IBG(get_statement_interface))(IB_STATUS, &statement, &ib_query->stmt)){ + _php_ibase_error(); + RETURN_FALSE; + } + + if(fb_insert_field_info(IBG(master_instance), IB_STATUS, is_outvar, num, return_value, statement)){ _php_ibase_error(); RETURN_FALSE; } @@ -2208,8 +2221,13 @@ static int _php_ibase_alloc_ht_aliases(ibase_query *ib_query) zend_hash_init(ib_query->ht_aliases, ib_query->out_fields_count, NULL, ZVAL_PTR_DTOR, 0); #if FB_API_VER >= 40 - if(IBG(fb_get_master_interface) && IBG(fb_get_statement_interface)) { - if(fb_insert_aliases(IB_STATUS, ib_query)){ + if(IBG(master_instance) && IBG(get_statement_interface)) { + void *statement = NULL; + if(((fb_get_statement_interface_t)IBG(get_statement_interface))(IB_STATUS, &statement, &ib_query->stmt)){ + return FAILURE; + } + + if(fb_insert_aliases(IBG(master_instance), IB_STATUS, ib_query, statement)){ return FAILURE; } } else { diff --git a/interbase.c b/interbase.c index 059f42e..f6ace9d 100644 --- a/interbase.c +++ b/interbase.c @@ -44,6 +44,7 @@ #include "SAPI.h" #include #include +#include "pdo_firebird_utils.h" #define ROLLBACK 0 #define COMMIT 1 @@ -1002,9 +1003,23 @@ int _php_ibase_attach_db(char **args, size_t *len, zend_long *largs, isc_db_hand } #if FB_API_VER >= 40 - // Do not handle directly INT128 or DECFLOAT, convert to VARCHAR at server instead - const char compat[] = "int128 to varchar;decfloat to varchar"; - dpb_len = slprintf(dpb, buf_len, "%c%c%s", isc_dpb_set_bind, (char)(sizeof(compat) - 1), compat); + const char *compat_buf; + char compat_buf_size; + + // ibase_query(): Data type unknown + // If fbclient >= 4 then convert to VARCHAR at server only INT128 and DECFLOAT + // If we have older client, convert also timezone types + if(IBG(client_major_version) >= 4) { + const char compat[] = "INT128 TO VARCHAR;DECFLOAT TO VARCHAR"; + compat_buf = compat; + compat_buf_size = sizeof(compat) - 1; + } else { + const char compat[] = "INT128 TO VARCHAR;DECFLOAT TO VARCHAR;TIME WITH TIME ZONE TO TIME WITHOUT TIME ZONE;TIMESTAMP WITH TIME ZONE TO TIMESTAMP WITHOUT TIME ZONE"; + compat_buf = compat; + compat_buf_size = sizeof(compat) - 1; + } + + dpb_len = slprintf(dpb, buf_len, "%c%c%s", isc_dpb_set_bind, compat_buf_size, compat_buf); dpb += dpb_len; buf_len -= dpb_len; #endif diff --git a/pdo_firebird_utils.cpp b/pdo_firebird_utils.cpp index fab67c7..c2bb2f2 100644 --- a/pdo_firebird_utils.cpp +++ b/pdo_firebird_utils.cpp @@ -32,16 +32,16 @@ extern "C" unsigned fb_get_client_version(void *master_ptr) return util->getClientVersion(); } -extern "C" ISC_TIME fb_encode_time(unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions) +extern "C" ISC_TIME fb_encode_time(void *master_ptr, unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions) { - Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IMaster* master = (Firebird::IMaster*)master_ptr; 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) +extern "C" ISC_DATE fb_encode_date(void *master_ptr, unsigned year, unsigned month, unsigned day) { - Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IMaster* master = (Firebird::IMaster*)master_ptr; Firebird::IUtil* util = master->getUtilInterface(); return util->encodeDate(year, month, day); } @@ -57,10 +57,10 @@ static void fb_copy_status(const ISC_STATUS* from, ISC_STATUS* to, size_t maxLen } /* Decodes a time with time zone into its time components. */ -extern "C" void fb_decode_time_tz(const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, +extern "C" void fb_decode_time_tz(void *master_ptr, 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::IMaster* master = (Firebird::IMaster*)master_ptr; Firebird::IUtil* util = master->getUtilInterface(); Firebird::IStatus* status = master->getStatus(); Firebird::CheckStatusWrapper st(status); @@ -69,12 +69,12 @@ extern "C" void fb_decode_time_tz(const ISC_TIME_TZ* timeTz, unsigned* hours, un } /* Decodes a timestamp with time zone into its date and time components */ -extern "C" void fb_decode_timestamp_tz(const ISC_TIMESTAMP_TZ* timestampTz, +extern "C" void fb_decode_timestamp_tz(void *master_ptr, 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::IMaster* master = (Firebird::IMaster*)master_ptr; Firebird::IUtil* util = master->getUtilInterface(); Firebird::IStatus* status = master->getStatus(); Firebird::CheckStatusWrapper st(status); @@ -83,18 +83,14 @@ extern "C" void fb_decode_timestamp_tz(const ISC_TIMESTAMP_TZ* timestampTz, timeZoneBufferLength, timeZoneBuffer); } -extern "C" int fb_insert_aliases(ISC_STATUS* st, ibase_query *ib_query) +extern "C" int fb_insert_aliases(void *master_ptr, ISC_STATUS* st, ibase_query *ib_query, void *statement_ptr) { - Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IMaster* master = (Firebird::IMaster*)master_ptr; Firebird::ThrowStatusWrapper status(master->getStatus()); - Firebird::IStatement* statement = NULL; + Firebird::IStatement* statement = (Firebird::IStatement *)statement_ptr; Firebird::IMessageMetadata* meta = NULL; ISC_STATUS res; - if (res = fb_get_statement_interface(st, &statement, &ib_query->stmt)){ - return res; - } - try { meta = statement->getOutputMetadata(&status); unsigned cols = meta->getCount(&status); @@ -118,18 +114,15 @@ extern "C" int fb_insert_aliases(ISC_STATUS* st, ibase_query *ib_query) return 0; } -extern "C" int fb_insert_field_info(ISC_STATUS* st, ibase_query *ib_query, int is_outvar, int num, zval *into_array) +extern "C" int fb_insert_field_info(void *master_ptr, ISC_STATUS* st, int is_outvar, int num, + zval *into_array, void *statement_ptr) { - Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IMaster* master = (Firebird::IMaster*)master_ptr; Firebird::ThrowStatusWrapper status(master->getStatus()); - Firebird::IStatement* statement = NULL; + Firebird::IStatement* statement = (Firebird::IStatement *)statement_ptr; Firebird::IMessageMetadata* meta = NULL; ISC_STATUS res; - if (res = fb_get_statement_interface(st, &statement, &ib_query->stmt)){ - return res; - } - try { if(is_outvar) { meta = statement->getOutputMetadata(&status); @@ -157,4 +150,4 @@ extern "C" int fb_insert_field_info(ISC_STATUS* st, ibase_query *ib_query, int i return 0; } -#endif +#endif // FB_API_VER >= 40 diff --git a/pdo_firebird_utils.h b/pdo_firebird_utils.h index 199a57d..7503353 100644 --- a/pdo_firebird_utils.h +++ b/pdo_firebird_utils.h @@ -27,21 +27,24 @@ extern "C" { #endif unsigned fb_get_client_version(void *master_ptr); +ISC_TIME fb_encode_time(void *master_ptr, unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions); +ISC_DATE fb_encode_date(void *master_ptr, unsigned year, unsigned month, unsigned day); -void fb_decode_time_tz(const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, +void fb_decode_time_tz(void *master_ptr, const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, unsigned timeZoneBufferLength, char* timeZoneBuffer); -void fb_decode_timestamp_tz(const ISC_TIMESTAMP_TZ* timestampTz, +void fb_decode_timestamp_tz(void *master_ptr, const ISC_TIMESTAMP_TZ* timestampTz, unsigned* year, unsigned* month, unsigned* day, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, unsigned timeZoneBufferLength, char* timeZoneBuffer); -int fb_insert_aliases(ISC_STATUS* st, ibase_query *ib_query); -int fb_insert_field_info(ISC_STATUS* st, ibase_query *ib_query, int is_outvar, int num, zval *into_array); +int fb_insert_aliases(void *master_ptr, ISC_STATUS* st, ibase_query *ib_query, void *statement_ptr); +int fb_insert_field_info(void *master_ptr, ISC_STATUS* st, int is_outvar, int num, zval *into_array, void *statement_ptr); #ifdef __cplusplus } #endif -#endif + +#endif // FB_API_VER >= 40 #endif /* PDO_FIREBIRD_UTILS_H */ From 50ac79f89c952ba6e4327f4f65f9a2e46da45576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Fri, 7 Nov 2025 00:39:33 +0200 Subject: [PATCH 62/66] Fix return value --- ibase_query.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index dbc7335..1b3e847 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -2267,11 +2267,11 @@ static void _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAMETERS, int as_resu RESET_ERRMSG; if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &query_arg) == FAILURE) { - return; + RETURN_FALSE; } if(_php_ibase_fetch_query_res(query_arg, &ib_query)) { - return; + RETURN_FALSE; } if(!as_result || ib_query->was_result_once) { From b7e6cfca2e2fc3706e92a7e3ded8bf2fdf00aa2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Fri, 7 Nov 2025 00:46:39 +0200 Subject: [PATCH 63/66] Get rid of CAP_XSQLVAR_LEN --- ibase_query.c | 23 ++++++++++------------- pdo_firebird_utils.cpp | 3 +-- php_ibase_includes.h | 2 +- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/ibase_query.c b/ibase_query.c index 1b3e847..78d363d 100644 --- a/ibase_query.c +++ b/ibase_query.c @@ -50,10 +50,6 @@ #define FETCH_ROW 1 #define FETCH_ARRAY 2 -// Appearantly XSQLVAR len fields can come in > 31 and < 0 depending on -// fbclient-server combination -#define CAP_XSQLVAR_LEN(len, str) ((len) > 31 || (len) < 0 ? MIN(31, strlen((str))) : (len)) - typedef struct { unsigned short vary_length; char vary_string[1]; @@ -1496,12 +1492,14 @@ static int _php_ibase_arr_zval(zval *ar_zval, char *data, zend_ulong data_size, } /* }}} */ -void _php_ibase_insert_alias(HashTable *ht, const char *alias, size_t alias_len) +void _php_ibase_insert_alias(HashTable *ht, const char *alias) { char buf[METADATALENGTH + 3 + 1]; // _00 + \0 zval t2; int i = 0; char const *base = "FIELD"; /* use 'FIELD' if name is empty */ + + size_t alias_len = strlen(alias); size_t alias_len_w_suff = alias_len + 3; switch (*alias) { @@ -1932,14 +1930,14 @@ static void _php_ibase_field_info(zval *return_value, ibase_query *ib_query, int } else { #endif // Old API - add_index_stringl(return_value, 0, var->sqlname, CAP_XSQLVAR_LEN(var->sqlname_length, var->sqlname)); - add_assoc_stringl(return_value, "name", var->sqlname, CAP_XSQLVAR_LEN(var->sqlname_length, var->sqlname)); + add_index_stringl(return_value, 0, var->sqlname, strlen(var->sqlname)); + add_assoc_stringl(return_value, "name", var->sqlname, strlen(var->sqlname)); - add_index_stringl(return_value, 1, var->aliasname, CAP_XSQLVAR_LEN(var->aliasname_length, var->aliasname)); - add_assoc_stringl(return_value, "alias", var->aliasname, CAP_XSQLVAR_LEN(var->aliasname_length, var->aliasname)); + add_index_stringl(return_value, 1, var->aliasname, strlen(var->aliasname)); + add_assoc_stringl(return_value, "alias", var->aliasname, strlen(var->aliasname)); - add_index_stringl(return_value, 2, var->relname, CAP_XSQLVAR_LEN(var->relname_length, var->relname)); - add_assoc_stringl(return_value, "relation", var->relname, CAP_XSQLVAR_LEN(var->relname_length, var->relname)); + add_index_stringl(return_value, 2, var->relname, strlen(var->relname)); + add_assoc_stringl(return_value, "relation", var->relname, strlen(var->relname)); #if FB_API_VER >= 40 } #endif @@ -2236,8 +2234,7 @@ static int _php_ibase_alloc_ht_aliases(ibase_query *ib_query) for(size_t i = 0; i < ib_query->out_fields_count; i++){ XSQLVAR *var = &ib_query->out_sqlda->sqlvar[i]; - _php_ibase_insert_alias(ib_query->ht_aliases, - var->aliasname, CAP_XSQLVAR_LEN(var->aliasname_length, var->aliasname)); + _php_ibase_insert_alias(ib_query->ht_aliases, var->aliasname); } #if FB_API_VER >= 40 } diff --git a/pdo_firebird_utils.cpp b/pdo_firebird_utils.cpp index c2bb2f2..c2b1db4 100644 --- a/pdo_firebird_utils.cpp +++ b/pdo_firebird_utils.cpp @@ -99,8 +99,7 @@ extern "C" int fb_insert_aliases(void *master_ptr, ISC_STATUS* st, ibase_query * for (unsigned i = 0; i < cols; ++i) { - _php_ibase_insert_alias(ib_query->ht_aliases, - meta->getAlias(&status, i), strlen(meta->getAlias(&status, i))); + _php_ibase_insert_alias(ib_query->ht_aliases, meta->getAlias(&status, i)); } } catch (const Firebird::FbException& error) diff --git a/php_ibase_includes.h b/php_ibase_includes.h index 0851e6b..f5f08e8 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -259,7 +259,7 @@ void php_ibase_service_minit(INIT_FUNC_ARGS); extern "C" { #endif -void _php_ibase_insert_alias(HashTable *ht, const char *alias, size_t alias_len); +void _php_ibase_insert_alias(HashTable *ht, const char *alias); static int _php_ibase_get_vars_count(ibase_query *ib_query); static int _php_ibase_fetch_query_res(zval *from, ibase_query **ib_query); static int _php_ibase_alloc_ht_aliases(ibase_query *ib_query); From 85a9c3c642c2824849fd6022cb4139249a3a9d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Fri, 7 Nov 2025 00:49:53 +0200 Subject: [PATCH 64/66] Do not track php-fb-config.bat, move it to .dist --- .gitignore | 1 + build_scripts/{php-fb-config.bat => php-fb-config.dist.bat} | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename build_scripts/{php-fb-config.bat => php-fb-config.dist.bat} (96%) diff --git a/.gitignore b/.gitignore index c4d9372..6f19695 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ tests/*.sh /reports win_build_scripts .vscode-win +build_scripts/php-fb-config.bat diff --git a/build_scripts/php-fb-config.bat b/build_scripts/php-fb-config.dist.bat similarity index 96% rename from build_scripts/php-fb-config.bat rename to build_scripts/php-fb-config.dist.bat index ffc2109..57d9655 100644 --- a/build_scripts/php-fb-config.bat +++ b/build_scripts/php-fb-config.dist.bat @@ -25,4 +25,4 @@ if %USE_GIT_HASH% equ 1 ( for /f %%i in ('git -C %~dp0..\ rev-parse --short HEAD') do set PFB_VERS=%PFB_VERS%-%%i ) -set PFB_CONFIGURE_FLAGS=--enable-debug +@REM set PFB_CONFIGURE_FLAGS=--enable-debug From 36c3d2c01ec8028b5cf24ef551fae2853fc759ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Fri, 7 Nov 2025 01:08:03 +0200 Subject: [PATCH 65/66] Move git hash logic away from config --- build_scripts/php-fb-build.bat | 12 ++++++++++++ build_scripts/php-fb-config.dist.bat | 20 +++++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/build_scripts/php-fb-build.bat b/build_scripts/php-fb-build.bat index e236b30..745449e 100644 --- a/build_scripts/php-fb-build.bat +++ b/build_scripts/php-fb-build.bat @@ -4,6 +4,7 @@ @REM @REM config ====================================================================================== +call %~dp0php-fb-config.dist.bat call %~dp0php-fb-config.bat set pfb_php_tag=%1 @@ -41,6 +42,17 @@ if "%pfb_php_vers%" == "" ( exit 1 ) +@REM Grab version +for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_MAJOR" %~dp0..\php_interbase.h') do set VER_MAJOR=%%i +for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_MINOR" %~dp0..\php_interbase.h') do set VER_MINOR=%%i +for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_REV" %~dp0..\php_interbase.h') do set VER_REV=%%i +for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_PRE" %~dp0..\php_interbase.h') do set VER_PRE=%%~i +set PFB_VERS=%VER_MAJOR%.%VER_MINOR%.%VER_REV%%VER_PRE% + +if %PFB_ATTACH_GIT_HASH_TO_VERS% equ 1 ( + for /f %%i in ('git -C %~dp0..\ rev-parse --short HEAD') do set PFB_VERS=%PFB_VERS%-%%i +) + @REM Initialize set php_root=php%pfb_php_vers%\%pfb_cpp_vers%\%pfb_arch%\php-src\ set php_interbase=php_interbase-%PFB_VERS%-%pfb_php_vers%-%pfb_cpp_vers% diff --git a/build_scripts/php-fb-config.dist.bat b/build_scripts/php-fb-config.dist.bat index 57d9655..ddc4ff2 100644 --- a/build_scripts/php-fb-config.dist.bat +++ b/build_scripts/php-fb-config.dist.bat @@ -1,4 +1,8 @@ @echo off +@REM Do not modify this file directly. +@REM Create build_scripts/php-fb-config.bat if you need to change any variable +@REM and put overrides there + @REM php-firebird source directory @REM Should point one level up. For example @REM PFB_SOURCE_DIR=D:\php-firebird\ then your source should reside in D:\php-firebird\php-firebird\ @@ -11,18 +15,8 @@ set PFB_OUTPUT_DIR=%PFB_SOURCE_DIR%releases\ set PFB_FB32_DIR=C:\Program Files\Firebird\Firebird_5_0-x86 set PFB_FB64_DIR=C:\Program Files\Firebird\Firebird_5_0 -@REM Attach current git commit hash. git command must be in PATH -set USE_GIT_HASH=0 - -@REM Grab version -for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_MAJOR" %~dp0..\php_interbase.h') do set VER_MAJOR=%%i -for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_MINOR" %~dp0..\php_interbase.h') do set VER_MINOR=%%i -for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_REV" %~dp0..\php_interbase.h') do set VER_REV=%%i -for /f "tokens=3" %%i in ('findstr /b /c:"#define PHP_INTERBASE_VER_PRE" %~dp0..\php_interbase.h') do set VER_PRE=%%~i -set PFB_VERS=%VER_MAJOR%.%VER_MINOR%.%VER_REV%%VER_PRE% - -if %USE_GIT_HASH% equ 1 ( - for /f %%i in ('git -C %~dp0..\ rev-parse --short HEAD') do set PFB_VERS=%PFB_VERS%-%%i -) +@REM Attach current git commit hash to version. git command must be in PATH +set PFB_ATTACH_GIT_HASH_TO_VERS=0 +@REM Additional flags for ./configure @REM set PFB_CONFIGURE_FLAGS=--enable-debug From 33e02b0e17f599f361dcc33f43f35a540edcfe91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Lazd=C4=81ns?= Date: Fri, 7 Nov 2025 01:09:54 +0200 Subject: [PATCH 66/66] Add test for Issue #89 --- tests/issue89_001.phpt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/issue89_001.phpt diff --git a/tests/issue89_001.phpt b/tests/issue89_001.phpt new file mode 100644 index 0000000..e753bb6 --- /dev/null +++ b/tests/issue89_001.phpt @@ -0,0 +1,19 @@ +--TEST-- +Issue #89: Passing result from ibase_prepare() to ibase_fetch_*() causes segfault. +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(false)