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-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..745449e 100644 --- a/build_scripts/php-fb-build.bat +++ b/build_scripts/php-fb-build.bat @@ -1,128 +1,124 @@ @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.dist.bat 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%" == "1" ( + 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 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% -(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% equ 1 ( + set build_root=%build_root%Release_TS\ +) else ( + set build_root=%build_root%Release\ + set php_interbase=%php_interbase%-nts +) + +@REM Build +call :log "Building %php_interbase%.dll..." +call phpsdk-%pfb_cpp_vers%-%pfb_arch%.bat -t %~dp0php-fb-sdk-build.bat || goto :error + +@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); } - 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" - ) +if "%pfb_arch%" == "x86" ( + set vb_libs=%PFB_FB32_DIR% +) else ( + set vb_libs=%PFB_FB64_DIR% +) - if "!php_exe!" == "" ( - echo BUG: php_exe should be set at this point - exit 1 - ) +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 - call phpsdk-%pfb_cpp_vers%-%%a.bat -t %~dp0php-fb-sdk-build.bat || goto :error +call :log "Copying %php_interbase%.dll..." +copy "%build_root%php_interbase.dll" "%PFB_OUTPUT_DIR%%php_interbase%.dll" || goto :error - call :validate_build !php_exe! !pfb_arch! !pfb_ts! || goto :error - )) -)) +call :log "Build OK" "%pfb_php_tag% %pfb_cpp_vers% %pfb_arch% Thread Safety %pfb_ts%" "%php_interbase%.dll" -echo. -call :log "%pfb_php_tag% build OK" +exit /B -@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 +@REM log ========================================================================================= +@REM log +@REM example> call :log "" +:log + echo --------------------------------------------------------------------- + for %%a in (%*) do ( echo %%~a ) + echo --------------------------------------------------------------------- +exit /B -exit /B 0 +@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 "%pfb_php_tag% build FAILED" - + 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 deleted file mode 100644 index 076ace2..0000000 --- a/build_scripts/php-fb-config.bat +++ /dev/null @@ -1,18 +0,0 @@ -@REM -@REM git command must be in PATH -@REM - -@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 Directory where all compiled files will be copied -set PFB_OUTPUT_DIR=D:\php-firebird\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 diff --git a/build_scripts/php-fb-config.dist.bat b/build_scripts/php-fb-config.dist.bat new file mode 100644 index 0000000..ddc4ff2 --- /dev/null +++ b/build_scripts/php-fb-config.dist.bat @@ -0,0 +1,22 @@ +@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\ +set PFB_SOURCE_DIR=%~dp0..\..\ + +@REM Directory where all compiled files will be copied +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 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 diff --git a/build_scripts/php-fb-sdk-build.bat b/build_scripts/php-fb-sdk-build.bat index 14e3944..ef43fd5 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" ( @@ -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 diff --git a/ibase_query.c b/ibase_query.c index bab016e..78d363d 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" @@ -48,73 +50,16 @@ #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_alloc_xsqlda_vars(XSQLDA *sqlda, ISC_SHORT *nullinds); static void _php_ibase_free_xsqlda(XSQLDA *sqlda) /* {{{ */ { int i; @@ -126,81 +71,28 @@ 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); } } /* }}} */ -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..."); - 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); } /* }}} */ @@ -211,17 +103,13 @@ 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); } } /* }}} */ 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); } @@ -359,126 +247,93 @@ 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, zend_resource *trans_res, 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->query = estrdup(query); ib_query->trans_res = trans_res; - ib_query->out_sqlda = NULL; - ib_query->in_sqlda = NULL; + ib_query->dialect = link->dialect; + ib_query->query = estrdup(query); 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; } /* }}} */ @@ -580,7 +435,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 @@ -703,9 +558,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 */ @@ -713,7 +570,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 */ @@ -747,7 +604,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; @@ -756,7 +613,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; @@ -819,7 +676,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)) { @@ -880,7 +738,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); @@ -931,14 +789,14 @@ static int _php_ibase_bind(XSQLDA *sqlda, zval *b_vars, BIND_BUF *buf, /* {{{ */ /* 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; } /* }}} */ -static void _php_ibase_alloc_xsqlda(XSQLDA *sqlda) /* {{{ */ +static void _php_ibase_alloc_xsqlda_vars(XSQLDA *sqlda, ISC_SHORT *nullinds) /* {{{ */ { int i; @@ -1003,12 +861,14 @@ static void _php_ibase_alloc_xsqlda(XSQLDA *sqlda) /* {{{ */ 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 */ if (var->sqltype & 1) { /* sql NULL flag */ - var->sqlind = emalloc(sizeof(short)); + var->sqlind = &nullinds[i]; } else { var->sqlind = NULL; } @@ -1016,18 +876,37 @@ static void _php_ibase_alloc_xsqlda(XSQLDA *sqlda) /* {{{ */ } /* }}} */ -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]); @@ -1046,7 +925,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)); @@ -1078,7 +957,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) { @@ -1096,55 +975,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; @@ -1157,7 +1022,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; @@ -1190,24 +1055,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; } /* }}} */ @@ -1223,8 +1071,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; @@ -1260,13 +1106,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(); @@ -1286,8 +1132,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; } @@ -1304,46 +1151,34 @@ 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, trans_res, 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; + ib_query->was_result_once = 1; - /* 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); } /* }}} */ @@ -1442,7 +1277,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, @@ -1538,20 +1373,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); } @@ -1564,12 +1406,12 @@ 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 - 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; @@ -1603,7 +1445,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 @@ -1650,183 +1492,211 @@ 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) +{ + 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) { + 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; + zval *res_arg, *result; 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; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &res_arg, &flag)) { + RETURN_FALSE; } - ib_result = (ibase_result *)zend_fetch_resource_ex(result_arg, LE_RESULT, le_result); + if(_php_ibase_fetch_query_res(res_arg, &ib_query)) { + RETURN_FALSE; + } - 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; + 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; + 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 */ + assert(ib_query->out_fields_count == ib_query->out_sqlda->sqld); - /** - * 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) { - - 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); + } - if (((var->sqltype & 1) == 0) || *var->sqlind != -1) { - zval result; - - switch (var->sqltype & ~1) { + for(i = 0; i < ib_query->out_fields_count; ++i) { + XSQLVAR *var = &ib_query->out_sqlda->sqlvar[i]; - 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 */ + // NULLs are already set + if (!(((var->sqltype & 1) == 0) || *var->sqlind != -1)) { + zend_hash_move_forward(ht_ret); + continue; + } - 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; + result = zend_hash_get_current_data(ht_ret); - blob_handle.bl_handle = 0; - blob_handle.bl_qd = *(ISC_QUAD *) var->sqldata; + switch (var->sqltype & ~1) { - if (isc_open_blob(IB_STATUS, &ib_result->link->handle, &ib_result->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_result->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, - 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: - zval_ptr_dtor_nogc(&result); - RETURN_FALSE; - } /* switch */ + efree(ar_data); - 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); - } - } - } /* for field */ + } 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); + } + + RETVAL_ARR(ht_ret); } /* }}} */ @@ -1866,7 +1736,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; @@ -1874,44 +1744,24 @@ 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) { - 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; + _php_ibase_free_query_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ @@ -1956,13 +1806,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, trans_res, query)){ RETURN_FALSE; } - RETVAL_RES(zend_register_resource(ib_query, le_query)); + + RETVAL_RES(ib_query->res); Z_TRY_ADDREF_P(return_value); } /* }}} */ @@ -1973,69 +1821,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); } /* }}} */ @@ -2043,23 +1850,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, 0); } /* }}} */ @@ -2068,8 +1859,8 @@ PHP_FUNCTION(ibase_free_query) PHP_FUNCTION(ibase_num_fields) { zval *result; - int type; XSQLDA *sqlda; + ibase_query *ib_query; RESET_ERRMSG; @@ -2077,20 +1868,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 { @@ -2099,21 +1882,65 @@ 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); + // 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(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; + } - add_index_stringl(return_value, 1, var->aliasname, var->aliasname_length); - add_assoc_stringl(return_value, "alias", var->aliasname, var->aliasname_length); + if(fb_insert_field_info(IBG(master_instance), IB_STATUS, is_outvar, num, return_value, statement)){ + _php_ibase_error(); + RETURN_FALSE; + } + } else { +#endif + // Old API + 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, strlen(var->aliasname)); + add_assoc_stringl(return_value, "alias", var->aliasname, strlen(var->aliasname)); - 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, strlen(var->relname)); + add_assoc_stringl(return_value, "relation", var->relname, strlen(var->relname)); +#if FB_API_VER >= 40 + } +#endif len = slprintf(buf, 16, "%d", var->sqllen); add_index_stringl(return_value, 3, buf, len); @@ -2196,6 +2023,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: @@ -2219,8 +2047,7 @@ PHP_FUNCTION(ibase_field_info) { zval *result_arg; zend_long field_arg; - int type; - XSQLDA *sqlda; + ibase_query *ib_query; RESET_ERRMSG; @@ -2228,29 +2055,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); } /* }}} */ @@ -2267,7 +2076,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); @@ -2291,18 +2102,180 @@ PHP_FUNCTION(ibase_param_info) return; } - ib_query = (ibase_query *)zend_fetch_resource_ex(result_arg, LE_QUERY, le_query); + 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) +{ + int rv = FAILURE; + // size_t buf_size = 128; + // ISC_UCHAR *buf = emalloc(buf_size); - if (ib_query->in_sqlda == NULL) { + 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_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); + // 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(buf[pos] != isc_info_end) { + fbp_fatal("BUG: sql_info unexpected end of buffer at pos: %lu", pos); + goto _php_ibase_parse_info_fail; + } + + rv = SUCCESS; + +_php_ibase_parse_info_fail: + // efree(buf); + 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? + // fbp_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(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 { +#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); + } +#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, int as_result) +{ + zval *query_arg; + ibase_query *ib_query; + + RESET_ERRMSG; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &query_arg) == FAILURE) { RETURN_FALSE; } - if (field_arg < 0 || field_arg >= ib_query->in_sqlda->sqld) { + if(_php_ibase_fetch_query_res(query_arg, &ib_query)) { RETURN_FALSE; } - _php_ibase_field_info(return_value,ib_query->in_sqlda->sqlvar + field_arg); + if(!as_result || ib_query->was_result_once) { + zend_list_close(Z_RES_P(query_arg)); + } + + RETURN_TRUE; } -/* }}} */ #endif /* HAVE_IBASE */ diff --git a/interbase.c b/interbase.c index 8ee97f9..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 @@ -317,6 +318,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 +382,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 +447,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 }; @@ -453,7 +461,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, @@ -491,6 +499,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) @@ -524,7 +544,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; @@ -783,6 +803,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 +831,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->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) @@ -807,6 +861,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_VER", PHP_INTERBASE_VER, CONST_PERSISTENT); /* transactions */ REGISTER_LONG_CONSTANT("IBASE_WRITE", PHP_IBASE_WRITE, CONST_PERSISTENT); @@ -880,7 +935,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); @@ -948,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, strlen(compat), 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 @@ -1590,26 +1659,44 @@ 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) php_printf("0x%02x ", buffer[i]); else - php_printf("%c", buffer[i]); - } - if (i > 0) { - php_printf("\n"); + php_printf(" [%c] ", buffer[i]); + if(i % 16 == 15)php_printf("\n"); } + if(i > 0)php_printf("\n"); } -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, const 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/pdo_firebird_utils.cpp b/pdo_firebird_utils.cpp index 861fa14..c2b1db4 100644 --- a/pdo_firebird_utils.cpp +++ b/pdo_firebird_utils.cpp @@ -18,28 +18,30 @@ #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) +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(); } -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); } @@ -55,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); @@ -67,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); @@ -81,4 +83,70 @@ extern "C" void fb_decode_timestamp_tz(const ISC_TIMESTAMP_TZ* timestampTz, timeZoneBufferLength, timeZoneBuffer); } -#endif +extern "C" int fb_insert_aliases(void *master_ptr, ISC_STATUS* st, ibase_query *ib_query, void *statement_ptr) +{ + Firebird::IMaster* master = (Firebird::IMaster*)master_ptr; + Firebird::ThrowStatusWrapper status(master->getStatus()); + Firebird::IStatement* statement = (Firebird::IStatement *)statement_ptr; + Firebird::IMessageMetadata* meta = NULL; + ISC_STATUS res; + + try { + meta = statement->getOutputMetadata(&status); + unsigned cols = meta->getCount(&status); + + assert(cols == ib_query->out_fields_count); + + for (unsigned i = 0; i < cols; ++i) + { + _php_ibase_insert_alias(ib_query->ht_aliases, 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; +} + +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::IMaster*)master_ptr; + Firebird::ThrowStatusWrapper status(master->getStatus()); + Firebird::IStatement* statement = (Firebird::IStatement *)statement_ptr; + Firebird::IMessageMetadata* meta = NULL; + ISC_STATUS 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 // FB_API_VER >= 40 diff --git a/pdo_firebird_utils.h b/pdo_firebird_utils.h index 197b279..7503353 100644 --- a/pdo_firebird_utils.h +++ b/pdo_firebird_utils.h @@ -17,32 +17,34 @@ #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" { #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); +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); -#if FB_API_VER >= 40 - -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); -#endif +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 // FB_API_VER >= 40 + #endif /* PDO_FIREBIRD_UTILS_H */ diff --git a/php_ibase_includes.h b/php_ibase_includes.h index 23c5930..f5f08e8 100644 --- a/php_ibase_includes.h +++ b/php_ibase_includes.h @@ -33,7 +33,11 @@ #endif #ifndef METADATALENGTH -#define METADATALENGTH 68 +#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) @@ -71,6 +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 *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) @@ -113,6 +123,52 @@ 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 *trans_res; + 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_*() + int was_result_once; +} ibase_query; + enum php_interbase_option { PHP_IBASE_DEFAULT = 0, PHP_IBASE_CREATE = 0, @@ -163,7 +219,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 */ @@ -199,6 +255,21 @@ 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); +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, int as_result); + +#ifdef __cplusplus +} +#endif + #ifndef max #define max(a,b) ((a)>(b)?(a):(b)) #endif @@ -208,4 +279,23 @@ 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, const 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 + +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 2715ca3..0ae61f7 100644 --- a/php_interbase.h +++ b/php_interbase.h @@ -30,9 +30,27 @@ 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 + static_assert(false, "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 +#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) \ + PHP_INTERBASE_VER_PRE PHP_MINIT_FUNCTION(ibase); PHP_RINIT_FUNCTION(ibase); @@ -101,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/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/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-- $r) { + print "---- Batch $batch ----\n"; + dump_rows($r); + ibase_free_result($r); + } +} 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/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-- $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) { + 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); + } + } +} + +/** @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 new file mode 100644 index 0000000..c50df53 --- /dev/null +++ b/tests/ibase_trans_004.phpt @@ -0,0 +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_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..7494ab9 --- /dev/null +++ b/tests/ibase_trans_006.phpt @@ -0,0 +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_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) 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) 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 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) diff --git a/tests/long_names_001.phpt b/tests/long_names_001.phpt index 85db77f..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-- --EXPECT-- +int(63) Table:TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT array(2) { - ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"]=> + ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"]=> int(1) ["🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰"]=> int(2) } Table:😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂 array(2) { - ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"]=> + ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"]=> int(1) ["🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰🥰"]=> int(2) 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) 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"); 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 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..637cbed --- /dev/null +++ b/tests/use_after_free-002.phpt @@ -0,0 +1,27 @@ +--TEST-- +InterBase: use after ibase_free_query() +--SKIPIF-- + +--FILE-- + +--EXPECT-- +---- Batch 1 ---- +Fatal error: Uncaught TypeError: ibase_fetch_assoc(): supplied resource is not a valid Firebird/InterBase query resource