diff --git a/TODO b/TODO index 9273172..cdacee6 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ * COPY: getAsync & putAsync +* CURSOR: *Async() * fetchInto/fetchCtor? * unlisten? * unregister event handler? diff --git a/config.m4 b/config.m4 index 79b2f89..310382b 100644 --- a/config.m4 +++ b/config.m4 @@ -50,6 +50,7 @@ if test "$PHP_PQ" != "no"; then src/php_pqstm.c\ src/php_pqtxn.c\ src/php_pqtypes.c\ + src/php_pqcur.c\ " PHP_NEW_EXTENSION(pq, $PQ_SRC, $ext_shared) PHP_ADD_BUILD_DIR($ext_builddir/src) diff --git a/php_pq.h b/php_pq.h index be1a720..1974ef3 100644 --- a/php_pq.h +++ b/php_pq.h @@ -23,9 +23,9 @@ zend_module_entry pq_module_entry; #ifdef PHP_WIN32 # define PHP_PQ_API __declspec(dllexport) #elif defined(__GNUC__) && __GNUC__ >= 4 -# define PHP_PQ_API __attribute__ ((visibility("default"))) +# define PHP_PQ_API extern __attribute__ ((visibility("default"))) #else -# define PHP_PQ_API +# define PHP_PQ_API extern #endif #ifdef ZTS diff --git a/src/php_pq_callback.c b/src/php_pq_callback.c index 1a4a917..63f2edc 100644 --- a/src/php_pq_callback.c +++ b/src/php_pq_callback.c @@ -15,19 +15,25 @@ #endif #include +#include #include "php_pq_callback.h" void php_pq_callback_dtor(php_pq_callback_t *cb) { + if (cb->recursion) { + php_pq_callback_dtor(cb->recursion); + efree(cb->recursion); + cb->recursion = NULL; + } if (cb->fci.size > 0) { zend_fcall_info_args_clear(&cb->fci, 1); zval_ptr_dtor(&cb->fci.function_name); if (cb->fci.object_ptr) { zval_ptr_dtor(&cb->fci.object_ptr); } + cb->fci.size = 0; } - cb->fci.size = 0; } void php_pq_callback_addref(php_pq_callback_t *cb) @@ -55,6 +61,41 @@ zval *php_pq_callback_to_zval(php_pq_callback_t *cb) return zcb; } + +zend_bool php_pq_callback_is_locked(php_pq_callback_t *cb TSRMLS_DC) +{ + if (cb->fci.size > 0 && Z_TYPE_P(cb->fci.function_name) == IS_OBJECT) { + const zend_function *closure = zend_get_closure_method_def(cb->fci.function_name TSRMLS_CC); + + if (closure->type == ZEND_USER_FUNCTION) { + zend_execute_data *ex = EG(current_execute_data); + + while (ex) { + if (ex->op_array == &closure->op_array) { + return 1; + } + ex = ex->prev_execute_data; + } + } + } + return 0; +} + +void php_pq_callback_recurse(php_pq_callback_t *old, php_pq_callback_t *new TSRMLS_DC) +{ + if (new && new->fci.size > 0 && php_pq_callback_is_locked(old TSRMLS_CC)) { + new->recursion = emalloc(sizeof(*old)); + memcpy(new->recursion, old, sizeof(*old)); + } else if (new && new->fci.size > 0) { + php_pq_callback_dtor(old); + php_pq_callback_addref(new); + memcpy(old, new, sizeof(*old)); + new->fci.size = 0; + } else { + php_pq_callback_dtor(old); + } +} + /* * Local variables: * tab-width: 4 diff --git a/src/php_pq_callback.h b/src/php_pq_callback.h index 4f2e85b..a5d167a 100644 --- a/src/php_pq_callback.h +++ b/src/php_pq_callback.h @@ -18,12 +18,14 @@ typedef struct php_pq_callback { zend_fcall_info fci; zend_fcall_info_cache fcc; - void *data; + struct php_pq_callback *recursion; } php_pq_callback_t; void php_pq_callback_dtor(php_pq_callback_t *cb); void php_pq_callback_addref(php_pq_callback_t *cb); zval *php_pq_callback_to_zval(php_pq_callback_t *cb); +zend_bool php_pq_callback_is_locked(php_pq_callback_t *cb); +void php_pq_callback_recurse(php_pq_callback_t *old, php_pq_callback_t *new TSRMLS_DC); #endif diff --git a/src/php_pq_module.c b/src/php_pq_module.c index 057bdee..7864304 100644 --- a/src/php_pq_module.c +++ b/src/php_pq_module.c @@ -62,6 +62,7 @@ static PHP_MINIT_FUNCTION(pq) PHP_MINIT_CALL(pqres); PHP_MINIT_CALL(pqstm); PHP_MINIT_CALL(pqtxn); + PHP_MINIT_CALL(pqcur); PHP_MINIT_CALL(pqcopy); PHP_MINIT_CALL(pqlob); diff --git a/src/php_pqconn.c b/src/php_pqconn.c index 79bb570..79fa9a0 100644 --- a/src/php_pqconn.c +++ b/src/php_pqconn.c @@ -31,6 +31,7 @@ #include "php_pqres.h" #include "php_pqstm.h" #include "php_pqtxn.h" +#include "php_pqcur.h" zend_class_entry *php_pqconn_class_entry; static zend_object_handlers php_pqconn_object_handlers; @@ -73,9 +74,9 @@ static void php_pqconn_object_free(void *o TSRMLS_DC) fprintf(stderr, "FREE conn(#%d) %p\n", obj->zv.handle, obj); #endif if (obj->intern) { + php_pq_callback_dtor(&obj->intern->onevent); php_resource_factory_handle_dtor(&obj->intern->factory, obj->intern->conn TSRMLS_CC); php_resource_factory_dtor(&obj->intern->factory); - php_pq_callback_dtor(&obj->intern->onevent); zend_hash_destroy(&obj->intern->listeners); zend_hash_destroy(&obj->intern->converters); zend_hash_destroy(&obj->intern->eventhandlers); @@ -491,7 +492,7 @@ static void php_pqconn_retire(php_persistent_handle_factory_t *f, void **handle zend_hash_apply_with_arguments(&evdata->obj->intern->listeners TSRMLS_CC, apply_unlisten, 1, evdata->obj); /* release instance data */ - memset(evdata, 0, sizeof(*evdata)); + //memset(evdata, 0, sizeof(*evdata)); efree(evdata); } } @@ -626,7 +627,7 @@ static PHP_METHOD(pqconn, listen) { zend_error_handling zeh; char *channel_str = NULL; int channel_len = 0; - php_pq_callback_t listener; + php_pq_callback_t listener = {{0}}; STATUS rv; zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh TSRMLS_CC); @@ -680,7 +681,7 @@ static PHP_METHOD(pqconn, listenAsync) { zend_error_handling zeh; char *channel_str = NULL; int channel_len = 0; - php_pq_callback_t listener; + php_pq_callback_t listener = {{0}}; STATUS rv; zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh TSRMLS_CC); @@ -1035,7 +1036,7 @@ STATUS php_pqconn_prepare(zval *object, php_pqconn_object_t *obj, const char *na } ZEND_BEGIN_ARG_INFO_EX(ai_pqconn_prepare, 0, 0, 2) - ZEND_ARG_INFO(0, type) + ZEND_ARG_INFO(0, name) ZEND_ARG_INFO(0, query) ZEND_ARG_ARRAY_INFO(0, types, 1) ZEND_END_ARG_INFO(); @@ -1100,7 +1101,7 @@ STATUS php_pqconn_prepare_async(zval *object, php_pqconn_object_t *obj, const ch } ZEND_BEGIN_ARG_INFO_EX(ai_pqconn_prepare_async, 0, 0, 2) -ZEND_ARG_INFO(0, type) + ZEND_ARG_INFO(0, name) ZEND_ARG_INFO(0, query) ZEND_ARG_ARRAY_INFO(0, types, 1) ZEND_END_ARG_INFO(); @@ -1141,6 +1142,154 @@ static PHP_METHOD(pqconn, prepareAsync) { } } +static inline char *declare_str(const char *name_str, size_t name_len, unsigned flags, const char *query_str, size_t query_len) +{ + size_t decl_len = name_len + query_len + sizeof("DECLARE BINARY INSENSITIVE NO SCROLL CURSOR WITHOUT HOLD FOR "); + char *decl_str; + + decl_str = emalloc(decl_len); + decl_len = slprintf(decl_str, decl_len, "DECLARE %s %s %s %s CURSOR %s FOR %s", + name_str, + (flags & PHP_PQ_DECLARE_BINARY) ? "BINARY" : "", + (flags & PHP_PQ_DECLARE_INSENSITIVE) ? "INSENSITIVE" : "", + (flags & PHP_PQ_DECLARE_NO_SCROLL) ? "NO SCROLL" : + (flags & PHP_PQ_DECLARE_SCROLL) ? "SCROLL" : "", + (flags & PHP_PQ_DECLARE_WITH_HOLD) ? "WITH HOLD" : "", + query_str + ); + return decl_str; +} + +STATUS php_pqconn_declare(zval *object, php_pqconn_object_t *obj, const char *decl TSRMLS_DC) +{ + PGresult *res; + STATUS rv; + + if (!obj) { + obj = zend_object_store_get_object(object TSRMLS_CC); + } + + res = PQexec(obj->intern->conn, decl); + + if (!res) { + rv = FAILURE; + throw_exce(EX_RUNTIME TSRMLS_CC, "Failed to declare cursor (%s)", PHP_PQerrorMessage(obj->intern->conn)); + } else { + rv = php_pqres_success(res TSRMLS_CC); + PHP_PQclear(res); + php_pqconn_notify_listeners(obj TSRMLS_CC); + } + + return rv; +} + +ZEND_BEGIN_ARG_INFO_EX(ai_pqconn_declare, 0, 0, 3) + ZEND_ARG_INFO(0, name) + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, query) +ZEND_END_ARG_INFO(); +static PHP_METHOD(pqconn, declare) { + zend_error_handling zeh; + char *name_str, *query_str; + int name_len, query_len; + long flags; + STATUS rv; + + zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh TSRMLS_CC); + rv = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sls", &name_str, &name_len, &flags, &query_str, &query_len); + zend_restore_error_handling(&zeh TSRMLS_CC); + + if (SUCCESS == rv) { + php_pqconn_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->intern) { + throw_exce(EX_UNINITIALIZED TSRMLS_CC, "pq\\Connection not initialized"); + } else { + char *decl = declare_str(name_str, name_len, flags, query_str, query_len); + + if (SUCCESS != php_pqconn_declare(getThis(), obj, decl TSRMLS_CC)) { + efree(decl); + } else { + php_pqcur_t *cur = ecalloc(1, sizeof(*cur)); + + php_pq_object_addref(obj TSRMLS_CC); + cur->conn = obj; + cur->open = 1; + cur->name = estrdup(name_str); + cur->decl = decl; + + return_value->type = IS_OBJECT; + return_value->value.obj = php_pqcur_create_object_ex(php_pqcur_class_entry, cur, NULL TSRMLS_CC); + } + } + } +} + +STATUS php_pqconn_declare_async(zval *object, php_pqconn_object_t *obj, const char *decl TSRMLS_DC) +{ + STATUS rv; + + if (!obj) { + obj = zend_object_store_get_object(object TSRMLS_CC); + } + + if (!PQsendQuery(obj->intern->conn, decl)) { + rv = FAILURE; + throw_exce(EX_IO TSRMLS_CC, "Failed to declare cursor (%s)", PHP_PQerrorMessage(obj->intern->conn)); + } else if (obj->intern->unbuffered && !PQsetSingleRowMode(obj->intern->conn)) { + rv = FAILURE; + throw_exce(EX_RUNTIME TSRMLS_CC, "Failed to enable unbuffered mode (%s)", PHP_PQerrorMessage(obj->intern->conn)); + } else { + rv = SUCCESS; + obj->intern->poller = PQconsumeInput; + php_pqconn_notify_listeners(obj TSRMLS_CC); + } + + return rv; +} + +ZEND_BEGIN_ARG_INFO_EX(ai_pqconn_declare_async, 0, 0, 2) + ZEND_ARG_INFO(0, name) + ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, query) +ZEND_END_ARG_INFO(); +static PHP_METHOD(pqconn, declareAsync) { + zend_error_handling zeh; + char *name_str, *query_str; + int name_len, query_len; + long flags; + STATUS rv; + + zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh TSRMLS_CC); + rv = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sls", &name_str, &name_len, &flags, &query_str, &query_len); + zend_restore_error_handling(&zeh TSRMLS_CC); + + if (SUCCESS == rv) { + php_pqconn_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->intern) { + throw_exce(EX_UNINITIALIZED TSRMLS_CC, "pq\\Connection not initialized"); + } else { + char *decl = declare_str(name_str, name_len, flags, query_str, query_len); + + if (SUCCESS != php_pqconn_declare_async(getThis(), obj, decl TSRMLS_CC)) { + efree(decl); + } else { + php_pqcur_t *cur = ecalloc(1, sizeof(*cur)); + + php_pq_object_addref(obj TSRMLS_CC); + cur->conn = obj; + cur->open = 1; + cur->name = estrdup(name_str); + cur->decl = decl; + + return_value->type = IS_OBJECT; + return_value->value.obj = php_pqcur_create_object_ex(php_pqcur_class_entry, cur, NULL TSRMLS_CC); + } + } + } +} + ZEND_BEGIN_ARG_INFO_EX(ai_pqconn_quote, 0, 0, 1) ZEND_ARG_INFO(0, string) ZEND_END_ARG_INFO(); @@ -1435,7 +1584,7 @@ static PHP_METHOD(pqconn, on) { zend_error_handling zeh; char *type_str; int type_len; - php_pq_callback_t cb; + php_pq_callback_t cb = {{0}}; STATUS rv; zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh TSRMLS_CC); @@ -1514,6 +1663,8 @@ static zend_function_entry php_pqconn_methods[] = { PHP_ME(pqconn, execParamsAsync, ai_pqconn_exec_params_async, ZEND_ACC_PUBLIC) PHP_ME(pqconn, prepare, ai_pqconn_prepare, ZEND_ACC_PUBLIC) PHP_ME(pqconn, prepareAsync, ai_pqconn_prepare_async, ZEND_ACC_PUBLIC) + PHP_ME(pqconn, declare, ai_pqconn_declare, ZEND_ACC_PUBLIC) + PHP_ME(pqconn, declareAsync, ai_pqconn_declare_async, ZEND_ACC_PUBLIC) PHP_ME(pqconn, listen, ai_pqconn_listen, ZEND_ACC_PUBLIC) PHP_ME(pqconn, listenAsync, ai_pqconn_listen_async, ZEND_ACC_PUBLIC) PHP_ME(pqconn, notify, ai_pqconn_notify, ZEND_ACC_PUBLIC) diff --git a/src/php_pqconn.h b/src/php_pqconn.h index b3ef4e8..c9f1e15 100644 --- a/src/php_pqconn.h +++ b/src/php_pqconn.h @@ -53,6 +53,8 @@ STATUS php_pqconn_prepare(zval *object, php_pqconn_object_t *obj, const char *na STATUS php_pqconn_prepare_async(zval *object, php_pqconn_object_t *obj, const char *name, const char *query, php_pq_params_t *params TSRMLS_DC); STATUS php_pqconn_start_transaction(zval *zconn, php_pqconn_object_t *conn_obj, long isolation, zend_bool readonly, zend_bool deferrable TSRMLS_DC); STATUS php_pqconn_start_transaction_async(zval *zconn, php_pqconn_object_t *conn_obj, long isolation, zend_bool readonly, zend_bool deferrable TSRMLS_DC); +STATUS php_pqconn_declare(zval *object, php_pqconn_object_t *obj, const char *decl TSRMLS_DC); +STATUS php_pqconn_declare_async(zval *object, php_pqconn_object_t *obj, const char *decl TSRMLS_DC); PHP_MINIT_FUNCTION(pqconn); PHP_MSHUTDOWN_FUNCTION(pqconn); diff --git a/src/php_pqcur.c b/src/php_pqcur.c new file mode 100644 index 0000000..1cb5d58 --- /dev/null +++ b/src/php_pqcur.c @@ -0,0 +1,304 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: pq | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2013, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "php_pq.h" +#include "php_pq_misc.h" +#include "php_pq_object.h" +#include "php_pqexc.h" +#include "php_pqconn.h" +#include "php_pqres.h" +#include "php_pqcur.h" + +zend_class_entry *php_pqcur_class_entry; +static zend_object_handlers php_pqcur_object_handlers; +static HashTable php_pqcur_object_prophandlers; + +static void cur_close(php_pqcur_object_t *obj TSRMLS_DC) +{ + if (obj->intern->open) { + PGresult *res; + smart_str cmd = {0}; + + smart_str_appends(&cmd, "CLOSE "); + smart_str_appends(&cmd, obj->intern->name); + smart_str_0(&cmd); + + if ((res = PQexec(obj->intern->conn->intern->conn, cmd.c))) { + PHP_PQclear(res); + } + smart_str_free(&cmd); + + obj->intern->open = 0; + } +} + +static void cur_fetch_or_move(INTERNAL_FUNCTION_PARAMETERS, const char *action, zend_bool async) +{ + char *spec_str = "1"; + int spec_len = 1; + STATUS rv; + php_pq_callback_t resolver = {{0}}; + zend_error_handling zeh; + + zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh TSRMLS_CC); + rv = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, async ? "|sf" : "|s", &spec_str, &spec_len, &resolver.fci, &resolver.fcc); + zend_restore_error_handling(&zeh TSRMLS_CC); + + if (SUCCESS == rv) { + php_pqcur_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->intern) { + throw_exce(EX_UNINITIALIZED TSRMLS_CC, "pq\\Cursor not initialized"); + } else { + smart_str cmd = {0}; + + smart_str_appends(&cmd, *action == 'f' ? "FETCH " : "MOVE "); + smart_str_appendl(&cmd, spec_str, spec_len); + smart_str_appends(&cmd, " FROM "); + smart_str_appends(&cmd, obj->intern->name); + smart_str_0(&cmd); + + if (async) { + int rc = PQsendQuery(obj->intern->conn->intern->conn, cmd.c); + + if (!rc) { + throw_exce(EX_IO TSRMLS_CC, "Failed to %s cursor (%s)", *action == 'f' ? "fetch from" : "move in", PHP_PQerrorMessage(obj->intern->conn->intern->conn)); + } else if (obj->intern->conn->intern->unbuffered && !PQsetSingleRowMode(obj->intern->conn->intern->conn)) { + throw_exce(EX_RUNTIME TSRMLS_CC, "Failed to enable unbuffered mode (%s)", PHP_PQerrorMessage(obj->intern->conn->intern->conn)); + } else { + php_pq_callback_recurse(&obj->intern->conn->intern->onevent, &resolver); + obj->intern->conn->intern->poller = PQconsumeInput; + } + } else { + PGresult *res = PQexec(obj->intern->conn->intern->conn, cmd.c); + + if (!res) { + throw_exce(EX_RUNTIME TSRMLS_CC, "Failed to %s cursor (%s)", *action == 'f' ? "fetch from" : "move in", PHP_PQerrorMessage(obj->intern->conn->intern->conn)); + } else if (SUCCESS == php_pqres_success(res TSRMLS_CC)) { + php_pq_object_to_zval_no_addref(PQresultInstanceData(res, php_pqconn_event), &return_value TSRMLS_CC); + + } + } + smart_str_free(&cmd); + php_pqconn_notify_listeners(obj->intern->conn TSRMLS_CC); + } + } +} + +static void php_pqcur_object_free(void *o TSRMLS_DC) +{ + php_pqcur_object_t *obj = o; +#if DBG_GC + fprintf(stderr, "FREE cur(#%d) %p (conn: %p)\n", obj->zv.handle, obj, obj->intern->conn); +#endif + if (obj->intern) { + //cur_close(obj TSRMLS_CC); + //php_pq_object_delref(obj->intern->conn TSRMLS_CC); + efree(obj->intern->decl); + efree(obj->intern->name); + efree(obj->intern); + obj->intern = NULL; + } + zend_object_std_dtor((zend_object *) o TSRMLS_CC); + efree(obj); +} + +zend_object_value php_pqcur_create_object_ex(zend_class_entry *ce, php_pqcur_t *intern, php_pqcur_object_t **ptr TSRMLS_DC) +{ + php_pqcur_object_t *o; + + o = ecalloc(1, sizeof(*o)); + zend_object_std_init((zend_object *) o, ce TSRMLS_CC); + object_properties_init((zend_object *) o, ce); + o->prophandler = &php_pqcur_object_prophandlers; + + if (ptr) { + *ptr = o; + } + + if (intern) { + o->intern = intern; + } + + o->zv.handle = zend_objects_store_put((zend_object *) o, NULL, php_pqcur_object_free, NULL TSRMLS_CC); + o->zv.handlers = &php_pqcur_object_handlers; + + return o->zv; +} + +static zend_object_value php_pqcur_create_object(zend_class_entry *class_type TSRMLS_DC) +{ + return php_pqcur_create_object_ex(class_type, NULL, NULL TSRMLS_CC); +} + +static void php_pqcur_object_read_name(zval *object, void *o, zval *return_value TSRMLS_DC) +{ + php_pqcur_object_t *obj = o; + + RETVAL_STRING(obj->intern->name, 1); +} + +static void php_pqcur_object_read_connection(zval *object, void *o, zval *return_value TSRMLS_DC) +{ + php_pqcur_object_t *obj = o; + + php_pq_object_to_zval(obj->intern->conn, &return_value TSRMLS_CC); +} + +ZEND_BEGIN_ARG_INFO_EX(ai_pqcur_open, 0, 0, 0) +ZEND_END_ARG_INFO(); +static PHP_METHOD(pqcur, open) +{ + zend_error_handling zeh; + STATUS rv; + + zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh TSRMLS_CC); + rv = zend_parse_parameters_none(); + zend_restore_error_handling(&zeh TSRMLS_CC); + + if (rv == SUCCESS) { + php_pqcur_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->intern) { + throw_exce(EX_UNINITIALIZED, "pq\\Cursor not initialized"); + } else if (!obj->intern->open) { + if (SUCCESS == php_pqconn_declare(NULL, obj->intern->conn, obj->intern->decl)) { + obj->intern->open = 1; + } + } + } +} + +ZEND_BEGIN_ARG_INFO_EX(ai_pqcur_close, 0, 0, 0) +ZEND_END_ARG_INFO(); +static PHP_METHOD(pqcur, close) +{ + zend_error_handling zeh; + STATUS rv; + + zend_replace_error_handling(EH_THROW, exce(EX_INVALID_ARGUMENT), &zeh TSRMLS_CC); + rv = zend_parse_parameters_none(); + zend_restore_error_handling(&zeh TSRMLS_CC); + + if (rv == SUCCESS) { + php_pqcur_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->intern) { + throw_exce(EX_UNINITIALIZED, "pq\\Cursor not initialized"); + } else { + cur_close(obj TSRMLS_CC); + } + } +} + +ZEND_BEGIN_ARG_INFO_EX(ai_pqcur_fetch, 0, 0, 1) + ZEND_ARG_INFO(0, spec) +ZEND_END_ARG_INFO(); +static PHP_METHOD(pqcur, fetch) +{ + cur_fetch_or_move(INTERNAL_FUNCTION_PARAM_PASSTHRU, "fetch", 0); +} + +ZEND_BEGIN_ARG_INFO_EX(ai_pqcur_move, 0, 0, 0) + ZEND_ARG_INFO(0, spec) +ZEND_END_ARG_INFO(); +static PHP_METHOD(pqcur, move) +{ + cur_fetch_or_move(INTERNAL_FUNCTION_PARAM_PASSTHRU, "move", 0); +} + +ZEND_BEGIN_ARG_INFO_EX(ai_pqcur_fetchAsync, 0, 0, 0) + ZEND_ARG_INFO(0, spec) + ZEND_ARG_INFO(0, callback) +ZEND_END_ARG_INFO(); +static PHP_METHOD(pqcur, fetchAsync) +{ + cur_fetch_or_move(INTERNAL_FUNCTION_PARAM_PASSTHRU, "fetch", 1); +} + +ZEND_BEGIN_ARG_INFO_EX(ai_pqcur_moveAsync, 0, 0, 0) + ZEND_ARG_INFO(0, spec) + ZEND_ARG_INFO(0, callback) +ZEND_END_ARG_INFO(); +static PHP_METHOD(pqcur, moveAsync) +{ + cur_fetch_or_move(INTERNAL_FUNCTION_PARAM_PASSTHRU, "move", 1); +} + +static zend_function_entry php_pqcur_methods[] = { + PHP_ME(pqcur, open, ai_pqcur_open, ZEND_ACC_PUBLIC) + PHP_ME(pqcur, close, ai_pqcur_close, ZEND_ACC_PUBLIC) + PHP_ME(pqcur, fetch, ai_pqcur_fetch, ZEND_ACC_PUBLIC) + PHP_ME(pqcur, move, ai_pqcur_move, ZEND_ACC_PUBLIC) + PHP_ME(pqcur, fetchAsync, ai_pqcur_fetchAsync, ZEND_ACC_PUBLIC) + PHP_ME(pqcur, moveAsync, ai_pqcur_moveAsync, ZEND_ACC_PUBLIC) + {NULL, NULL, NULL} +}; + +PHP_MSHUTDOWN_FUNCTION(pqcur) +{ + zend_hash_destroy(&php_pqcur_object_prophandlers); + return SUCCESS; +} + +PHP_MINIT_FUNCTION(pqcur) +{ + zend_class_entry ce = {0}; + php_pq_object_prophandler_t ph = {0}; + + INIT_NS_CLASS_ENTRY(ce, "pq", "Cursor", php_pqcur_methods); + php_pqcur_class_entry = zend_register_internal_class_ex(&ce, NULL, NULL TSRMLS_CC); + php_pqcur_class_entry->create_object = php_pqcur_create_object; + + memcpy(&php_pqcur_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + php_pqcur_object_handlers.read_property = php_pq_object_read_prop; + php_pqcur_object_handlers.write_property = php_pq_object_write_prop; + php_pqcur_object_handlers.clone_obj = NULL; + php_pqcur_object_handlers.get_property_ptr_ptr = NULL; + php_pqcur_object_handlers.get_gc = NULL; + php_pqcur_object_handlers.get_properties = php_pq_object_properties; + php_pqcur_object_handlers.get_debug_info = php_pq_object_debug_info; + + zend_hash_init(&php_pqcur_object_prophandlers, 2, NULL, NULL, 1); + + zend_declare_class_constant_long(php_pqcur_class_entry, ZEND_STRL("BINARY"), PHP_PQ_DECLARE_BINARY TSRMLS_CC); + zend_declare_class_constant_long(php_pqcur_class_entry, ZEND_STRL("INSENSITIVE"), PHP_PQ_DECLARE_INSENSITIVE TSRMLS_CC); + zend_declare_class_constant_long(php_pqcur_class_entry, ZEND_STRL("WITH_HOLD"), PHP_PQ_DECLARE_WITH_HOLD TSRMLS_CC); + zend_declare_class_constant_long(php_pqcur_class_entry, ZEND_STRL("SCROLL"), PHP_PQ_DECLARE_SCROLL TSRMLS_CC); + zend_declare_class_constant_long(php_pqcur_class_entry, ZEND_STRL("NO_SCROLL"), PHP_PQ_DECLARE_NO_SCROLL TSRMLS_CC); + + zend_declare_property_null(php_pqcur_class_entry, ZEND_STRL("name"), ZEND_ACC_PUBLIC TSRMLS_CC); + ph.read = php_pqcur_object_read_name; + zend_hash_add(&php_pqcur_object_prophandlers, "name", sizeof("name"), (void *) &ph, sizeof(ph), NULL); + + zend_declare_property_null(php_pqcur_class_entry, ZEND_STRL("connection"), ZEND_ACC_PUBLIC TSRMLS_CC); + ph.read = php_pqcur_object_read_connection; + zend_hash_add(&php_pqcur_object_prophandlers, "connection", sizeof("connection"), (void *) &ph, sizeof(ph), NULL); + + return SUCCESS; +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/src/php_pqcur.h b/src/php_pqcur.h new file mode 100644 index 0000000..31641b0 --- /dev/null +++ b/src/php_pqcur.h @@ -0,0 +1,54 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: pq | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2013, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +#ifndef PHP_PQCUR_H +#define PHP_PQCUR_H + +#include "php_pqconn.h" + +#define PHP_PQ_DECLARE_BINARY 0x01 +#define PHP_PQ_DECLARE_INSENSITIVE 0x02 +#define PHP_PQ_DECLARE_WITH_HOLD 0x04 + +#define PHP_PQ_DECLARE_SCROLL 0x10 +#define PHP_PQ_DECLARE_NO_SCROLL 0x20 + +typedef struct php_pqcur { + php_pqconn_object_t *conn; + char *name; + char *decl; + unsigned open:1; +} php_pqcur_t; + +typedef struct php_pqcur_object { + zend_object zo; + zend_object_value zv; + HashTable *prophandler; + php_pqcur_t *intern; +} php_pqcur_object_t; + +zend_class_entry *php_pqcur_class_entry; +zend_object_value php_pqcur_create_object_ex(zend_class_entry *ce, php_pqcur_t *intern, php_pqcur_object_t **ptr TSRMLS_DC); + +PHP_MINIT_FUNCTION(pqcur); +PHP_MSHUTDOWN_FUNCTION(pqcur); + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/src/php_pqres.c b/src/php_pqres.c index de33fd2..3b5d6bf 100644 --- a/src/php_pqres.c +++ b/src/php_pqres.c @@ -670,7 +670,7 @@ static PHP_METHOD(pqres, fetchCol) { RETVAL_FALSE; } else { zval_dtor(zref); - ZVAL_ZVAL(zref, *zres, 1, 1); + ZVAL_ZVAL(zref, *zres, 1, 0); RETVAL_TRUE; } } diff --git a/src/php_pqstm.h b/src/php_pqstm.h index da8c7d9..e40420c 100644 --- a/src/php_pqstm.h +++ b/src/php_pqstm.h @@ -6,7 +6,7 @@ | modification, are permitted provided that the conditions mentioned | | in the accompanying LICENSE file are met. | +--------------------------------------------------------------------+ - | Copyright (c) 2013, Michael Wallner | + | Copyright (c) 2013, Michael Wallner | +--------------------------------------------------------------------+ */ diff --git a/tests/async008.phpt b/tests/async008.phpt new file mode 100644 index 0000000..cebe9a3 --- /dev/null +++ b/tests/async008.phpt @@ -0,0 +1,57 @@ +--TEST-- +async cursor +--SKIPIF-- + +--FILE-- +busy) { + $r = array($c->socket); + $w = $e = null; + if (stream_select($r, $w, $e, null)) { + $c->poll(); + } + } + } while ($c->getResult()); +} + +$c = new pq\Connection(PQ_DSN); +$p = $c->declareAsync("mycursor", pq\Cursor::WITH_HOLD, + "SELECT * FROM generate_series(0,29) s WHERE (s%2)=0"); +complete($c); + +do { + $p->fetchAsync(2, function ($r) { + foreach ($r as $row) { + foreach ($row as $col) { + echo " $col"; + } + echo "\n"; + } + }); + complete($p->connection); + $p->moveAsync(1, function ($r) use(&$keep_going) { + $keep_going = $r->affectedRows; + }); + complete($p->connection); +} while ($keep_going); + +?> +===DONE=== +--EXPECT-- +Test + 0 + 2 + 6 + 8 + 12 + 14 + 18 + 20 + 24 + 26 +===DONE=== diff --git a/tests/cursor001.phpt b/tests/cursor001.phpt new file mode 100644 index 0000000..1e912b5 --- /dev/null +++ b/tests/cursor001.phpt @@ -0,0 +1,36 @@ +--TEST-- +cursor +--SKIPIF-- + +--FILE-- +declare("mycursor", pq\Cursor::WITH_HOLD, + "SELECT * FROM generate_series(0,29) s WHERE (s%2)=0"); +for ($r = $p->fetch(2); $r->numRows; $p->move(1), $r = $p->fetch(2)) { + foreach ($r as $row) { + foreach ($row as $col) { + echo " $col"; + } + echo "\n"; + } +} +?> +===DONE=== +--EXPECT-- +Test + 0 + 2 + 6 + 8 + 12 + 14 + 18 + 20 + 24 + 26 +===DONE===