add cursor support
authorMichael Wallner <mike@php.net>
Fri, 4 Apr 2014 13:48:20 +0000 (15:48 +0200)
committerMichael Wallner <mike@php.net>
Fri, 4 Apr 2014 13:50:42 +0000 (15:50 +0200)
14 files changed:
TODO
config.m4
php_pq.h
src/php_pq_callback.c
src/php_pq_callback.h
src/php_pq_module.c
src/php_pqconn.c
src/php_pqconn.h
src/php_pqcur.c [new file with mode: 0644]
src/php_pqcur.h [new file with mode: 0644]
src/php_pqres.c
src/php_pqstm.h
tests/async008.phpt [new file with mode: 0644]
tests/cursor001.phpt [new file with mode: 0644]

diff --git a/TODO b/TODO
index 9273172..cdacee6 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,4 +1,5 @@
 * COPY: getAsync & putAsync
+* CURSOR: *Async()
 * fetchInto/fetchCtor?
 * unlisten?
 * unregister event handler?
index 79b2f89..310382b 100644 (file)
--- 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)
index be1a720..1974ef3 100644 (file)
--- 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
index 1a4a917..63f2edc 100644 (file)
 #endif
 
 #include <php.h>
+#include <Zend/zend_closures.h>
 
 #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
index 4f2e85b..a5d167a 100644 (file)
 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
 
index 057bdee..7864304 100644 (file)
@@ -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);
index 79bb570..79fa9a0 100644 (file)
@@ -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)
index b3ef4e8..c9f1e15 100644 (file)
@@ -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 (file)
index 0000000..1cb5d58
--- /dev/null
@@ -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 <mike@php.net>                 |
+    +--------------------------------------------------------------------+
+*/
+
+#ifdef HAVE_CONFIG_H
+#      include "config.h"
+#endif
+
+#include <php.h>
+#include <ext/standard/php_smart_str.h>
+
+#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 (file)
index 0000000..31641b0
--- /dev/null
@@ -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 <mike@php.net>                 |
+    +--------------------------------------------------------------------+
+*/
+
+#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
+ */
index de33fd2..3b5d6bf 100644 (file)
@@ -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;
                                        }
                                }
index da8c7d9..e40420c 100644 (file)
@@ -6,7 +6,7 @@
     | modification, are permitted provided that the conditions mentioned |
     | in the accompanying LICENSE file are met.                          |
     +--------------------------------------------------------------------+
-    | Copyright (c) 2013, Michael Wallner <mike@php.net>                |
+    | Copyright (c) 2013, Michael Wallner <mike@php.net>                 |
     +--------------------------------------------------------------------+
 */
 
diff --git a/tests/async008.phpt b/tests/async008.phpt
new file mode 100644 (file)
index 0000000..cebe9a3
--- /dev/null
@@ -0,0 +1,57 @@
+--TEST--
+async cursor
+--SKIPIF--
+<?php include "_skipif.inc"; ?>
+--FILE--
+<?php
+echo "Test\n";
+include "_setup.inc";
+
+function complete($c) {
+       do {
+               while ($c->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 (file)
index 0000000..1e912b5
--- /dev/null
@@ -0,0 +1,36 @@
+--TEST--
+cursor
+--SKIPIF--
+<?php include "_skipif.inc"; ?>
+--FILE--
+<?php
+echo "Test\n";
+
+include "_setup.inc";
+
+$c = new pq\Connection(PQ_DSN);
+$p = $c->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===