From 9579de34d7da0b3d34ace48f8f79b74762811b92 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Sat, 5 Jun 2004 10:03:42 +0000 Subject: [PATCH] Added support for date/time types Added support for compiling/executing PHP code stored in the database Added support for TSRM --- ext/interbase/php_ibase_udf.c | 190 ++++++++++++++++++++++++++++------ 1 file changed, 161 insertions(+), 29 deletions(-) diff --git a/ext/interbase/php_ibase_udf.c b/ext/interbase/php_ibase_udf.c index dafc083e23e..26c8882071b 100644 --- a/ext/interbase/php_ibase_udf.c +++ b/ext/interbase/php_ibase_udf.c @@ -21,53 +21,84 @@ /** * This UDF library adds the ability to call PHP functions from SQL * statements. Because of SQL's strong typing, you will have to declare -* an external function for every combination of input and output parameters -* your application requires. The types of the input arguments and the result -* type can be either [VAR]CHARs, (un)scaled integers or doubles/floats. +* an external function for every combination { output type, #args } that +* your application requires. * * Declare the functions like this: * * DECLARE EXTERNAL FUNCTION CALL_PHP1 * CSTRING(xx), * BY DESCRIPTOR, -* BY DESCRIPTOR +* INTEGER BY DESCRIPTOR * RETURNS PARAMETER 2 * ENTRY_POINT 'udf_call_php1' MODULE_NAME 'php_ibase_udf' * * DECLARE EXTERNAL FUNCTION CALL_PHP2 * CSTRING(xx), * BY DESCRIPTOR, -* BY DESCRIPTOR, -* BY DESCRIPTOR +* INTEGER BY DESCRIPTOR, +* INTEGER BY DESCRIPTOR * RETURNS PARAMETER 2 * ENTRY_POINT 'udf_call_php2' MODULE_NAME 'php_ibase_udf' * * ... and so on. [for up to 8 input arguments] * -* The first input parameter contains the function you want to call. The -* second argument is the result, and should not be passed as an argument. -* [the DB will do this for you] Subsequent arguments are passed to the -* function in argument 1. +* The first input parameter contains the name of the PHP function you want +* to call. The second argument is the result. (omit this argument when calling +* the function) The return type of the function is the declared type of the +* result. The value returned from the PHP function being called will +* automatically be converted if necessary. +* The arguments should have their types declared as well, but you're free +* to pass arguments of other types instead. They will be converted to the +* best matching PHP type before being passed to the PHP function. * * The declared functions can be called from SQL like: * -* SELECT CALL_PHP1('strtolower',rdb$relation_name) FROM rdb$relations +* SELECT * FROM WHERE CALL_PHP1('soundex',) NOT LIKE ? +* or +* UPDATE
SET = CALL_PHP1('ucwords',) * -* THIS UDF WILL ONLY WORK IF YOU ARE RUNNING THE EMBEDDED 'CLASSIC' SERVER -* IN-PROCESS WITH YOUR PHP ENGINE. NETWORK CONNECTIONS TO 'LOCALHOST' ARE -* NOT SUPPORTED. -* -* Compile with: +* Additionally, there's a function 'exec_php' which allows the contents +* of text BLOB fields to be parsed and executed by PHP. This is most useful +* for declaring functions that can then be called with CALL_PHPx. +* +* DECLARE EXTERNAL FUNCTION EXEC_PHP +* BLOB, +* INTEGER BY DESCRIPTOR, +* SMALLINT +* RETURNS PARAMETER 2 +* ENTRY_POINT 'exec_php' MODULE_NAME 'php_ibase_udf' +* +* The function will return 1 if execution succeeded and 0 if an error +* occurred. The result that is returned from the executed PHP code is +* ignored. You can pass a non-zero value as second argument to force +* the embedded PHP engine to re-initialise. +* +* There are several ways to build this library, depending on which way the +* database is accessed. If you're using the classic server on a local +* connection, you should compile the library like this: +* * gcc -shared `php-config --includes` `php-config --ldflags` \ -* -o php_ibase_udf.so php_ibase_udf.c -* -* and copy the resulting file to the folder where your database expects -* to find its UDFs. +* `php-config --libs` -o php_ibase_udf.so php_ibase_udf.c +* +* If you connect to the classic server by TCP/IP, you should build the +* PHP embedded static library and link against that. +* +* gcc -shared `php-config --includes` `php-config --ldflags` \ +* `php-config --libs` -o php_ibase_udf.so php_ibase_udf.c \ +* /usr/lib/libphp5.a +* +* If you use the super server, you should also link against the embedded +* library, but be sure to enable thread safety, as the super server is +* multi-threaded. After building, copy the resulting file to the folder +* where your database expects to find its UDFs. */ #include "zend.h" #include "zend_API.h" + #include "php.h" +#include "php_ini.h" #include "ibase.h" @@ -79,13 +110,80 @@ #define LL_LIT(lit) lit ## ll #endif +#ifdef ZTS +# include + +static void ***tsrm_ls; +pthread_mutex_t mtx_res = PTHREAD_MUTEX_INITIALIZER; + +#define LOCK() do { pthread_mutex_lock(&mtx_res); } while (0) +#define UNLOCK() do { pthread_mutex_unlock(&mtx_res); } while (0) +#else +#define LOCK() +#define UNLOCK() +#endif + +#ifdef PHP_EMBED +# include "php_main.h" +# include "php_embed.h" + +static void __attribute__((constructor)) init() +{ + php_embed_init(0, NULL PTSRMLS_CC); +} + +static void __attribute__((destructor)) fini() +{ + php_embed_shutdown(TSRMLS_C); +} + +#endif + +/** +* Gets the contents of the BLOB b and offers it to Zend for parsing/execution +*/ +void exec_php(BLOBCALLBACK b, PARAMDSC *res, ISC_SHORT *init) +{ + int result, remaining = b->blob_total_length, i = 0; + char *code = malloc(remaining+1); + ISC_USHORT read; + + for (code[remaining] = '\0'; remaining > 0; remaining -= read) + b->blob_get_segment(b->blob_handle, &code[i++<<16],min(0x10000,remaining), &read); + + LOCK(); + + switch (init && *init) { + + default: +#ifdef PHP_EMBED + php_request_shutdown(NULL); + if (FAILURE == (result = php_request_startup(TSRMLS_C))) { + break; + } + case 0: +#endif + /* feed it to the parser */ + zend_first_try { + result = zend_eval_string(code, NULL, "Firebird Embedded PHP engine" TSRMLS_CC); + } zend_end_try(); + } + + UNLOCK(); + + free(code); + + res->dsc_dtype = dtype_long; + *(ISC_LONG*)res->dsc_address = (result == SUCCESS); +} + static ISC_INT64 const scales[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 100000000, 1000000000, 1000000000, LL_LIT(10000000000),LL_LIT(100000000000),LL_LIT(10000000000000),LL_LIT(100000000000000), LL_LIT(1000000000000000),LL_LIT(1000000000000000),LL_LIT(1000000000000000000) }; + static void call_php(char *name, PARAMDSC *r, int argc, PARAMDSC **argv) { - do { zval callback, args[4], *argp[4], return_value; PARAMVARY *res = (PARAMVARY*)r->dsc_address; @@ -113,6 +211,9 @@ static void call_php(char *name, PARAMDSC *r, int argc, PARAMDSC **argv) switch (argv[i]->dsc_dtype) { ISC_INT64 l; + struct tm t; + char const *fmt; + char d[64]; case dtype_cstring: ZVAL_STRING(argp[i], (char*)argv[i]->dsc_address,0); @@ -162,16 +263,45 @@ static void call_php(char *name, PARAMDSC *r, int argc, PARAMDSC **argv) case dtype_double: ZVAL_DOUBLE(argp[i], *(double*)argv[i]->dsc_address); break; + + case dtype_sql_date: + isc_decode_sql_date((ISC_DATE*)argv[i]->dsc_address, &t); + ZVAL_STRINGL(argp[i], d, strftime(d, sizeof(d), INI_STR("ibase.dateformat"), &t),1); + break; + + case dtype_sql_time: + isc_decode_sql_time((ISC_TIME*)argv[i]->dsc_address, &t); + ZVAL_STRINGL(argp[i], d, strftime(d, sizeof(d), INI_STR("ibase.timeformat"), &t),1); + break; + + case dtype_timestamp: + isc_decode_timestamp((ISC_TIMESTAMP*)argv[i]->dsc_address, &t); + ZVAL_STRINGL(argp[i], d, strftime(d, sizeof(d), INI_STR("ibase.timestampformat"), &t),1); + break; + } + } + + LOCK(); + + /* now call the function */ + if (FAILURE == call_user_function(EG(function_table), NULL, + &callback, &return_value, argc, argp TSRMLS_CC)) { + UNLOCK(); + break; + } + + UNLOCK(); + + for (i = 0; i < argc; ++i) { + switch (argv[i]->dsc_dtype) { + case dtype_sql_date: + case dtype_sql_time: + case dtype_timestamp: + zval_dtor(argp[i]); } } - - /* now call the function */ - if (FAILURE == call_user_function(EG(function_table), NULL, - &callback, &return_value, argc, argp)) { - break; - } - + /* return whatever type we got back from the callback: let DB handle conversion */ switch (Z_TYPE(return_value)) { @@ -212,7 +342,9 @@ static void call_php(char *name, PARAMDSC *r, int argc, PARAMDSC **argv) * If we end up here, we should report an error back to the DB engine, but * that's not possible. We can however report it back to PHP. */ - php_error_docref(NULL, E_WARNING, "Error calling function '%s' from database", name); + LOCK(); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error calling function '%s' from database", name); + UNLOCK(); }