diff --git a/src/php_pqconn.c b/src/php_pqconn.c index 976e336..d6cccb5 100644 --- a/src/php_pqconn.c +++ b/src/php_pqconn.c @@ -86,6 +86,7 @@ static void php_pqconn_object_free(zend_object *o) php_resource_factory_handle_dtor(&obj->intern->factory, obj->intern->conn); php_resource_factory_dtor(&obj->intern->factory); zend_hash_destroy(&obj->intern->listeners); + zend_hash_destroy(&obj->intern->statements); zend_hash_destroy(&obj->intern->converters); zend_hash_destroy(&obj->intern->eventhandlers); efree(obj->intern); @@ -668,6 +669,7 @@ static PHP_METHOD(pqconn, __construct) { obj->intern->default_auto_convert = PHP_PQRES_CONV_ALL; zend_hash_init(&obj->intern->listeners, 0, NULL, ZVAL_PTR_DTOR, 0); + zend_hash_init(&obj->intern->statements, 0, NULL, NULL, 0); zend_hash_init(&obj->intern->converters, 0, NULL, ZVAL_PTR_DTOR, 0); zend_hash_init(&obj->intern->eventhandlers, 0, NULL, ZVAL_PTR_DTOR, 0); diff --git a/src/php_pqconn.h b/src/php_pqconn.h index 3a26a5a..775451f 100644 --- a/src/php_pqconn.h +++ b/src/php_pqconn.h @@ -28,6 +28,7 @@ typedef struct php_pqconn { int (*poller)(PGconn *); php_resource_factory_t factory; HashTable listeners; + HashTable statements; HashTable converters; HashTable eventhandlers; php_pq_callback_t onevent; diff --git a/src/php_pqconn_event.c b/src/php_pqconn_event.c index 8cdb761..802a9e4 100644 --- a/src/php_pqconn_event.c +++ b/src/php_pqconn_event.c @@ -16,12 +16,15 @@ #include +#include + #include #include "php_pq.h" #include "php_pq_misc.h" #include "php_pq_object.h" #include "php_pqconn_event.h" +#include "php_pqstm.h" #include "php_pqres.h" static int apply_event(zval *p, void *a) @@ -39,6 +42,50 @@ static int apply_event(zval *p, void *a) return ZEND_HASH_APPLY_KEEP; } + +static inline PGresult *relisten(PGconn *conn, const char *channel_str, size_t channel_len) +{ + char *quoted_channel = PQescapeIdentifier(conn, channel_str, channel_len); + PGresult *res = NULL; + + if (quoted_channel) { + smart_str cmd = {0}; + + smart_str_appends(&cmd, "LISTEN "); + smart_str_appends(&cmd, quoted_channel); + smart_str_0(&cmd); + + res = PQexec(conn, smart_str_v(&cmd)); + + smart_str_free(&cmd); + PQfreemem(quoted_channel); + } + + return res; +} + +static int apply_relisten(zval *p, int argc, va_list argv, zend_hash_key *key) +{ + php_pqconn_object_t *obj = va_arg(argv, php_pqconn_object_t *); + PGresult *res = relisten(obj->intern->conn, key->key->val, key->key->len); + + if (res) { + php_pqres_clear(res); + } + + return ZEND_HASH_APPLY_KEEP; +} + +static int apply_reprepare(zval *p, int argc, va_list argv, zend_hash_key *key) +{ + php_pqconn_object_t *obj = va_arg(argv, php_pqconn_object_t *); + php_pqstm_t *stm = Z_PTR_P(p); + + php_pqconn_prepare(NULL, obj, stm->name, stm->query, stm->params); + + return ZEND_HASH_APPLY_KEEP; +} + static void php_pqconn_event_connreset(PGEventConnReset *event) { php_pqconn_event_data_t *data = PQinstanceData(event->conn, php_pqconn_event); @@ -46,6 +93,13 @@ static void php_pqconn_event_connreset(PGEventConnReset *event) if (data) { zval *zevhs; + /* restore listeners */ + zend_hash_apply_with_arguments(&data->obj->intern->listeners, apply_relisten, 1, data->obj); + + /* restore statements */ + zend_hash_apply_with_arguments(&data->obj->intern->statements, apply_reprepare, 1, data->obj); + + /* eventhandler */ if ((zevhs = zend_hash_str_find(&data->obj->intern->eventhandlers, ZEND_STRL("reset")))) { zval args, connection; diff --git a/src/php_pqstm.c b/src/php_pqstm.c index 4eec3b8..9e927dc 100644 --- a/src/php_pqstm.c +++ b/src/php_pqstm.c @@ -63,6 +63,7 @@ static void php_pqstm_deallocate(php_pqstm_object_t *obj, zend_bool async, zend_ } obj->intern->allocated = 0; + zend_hash_str_del(&obj->intern->conn->intern->statements, obj->intern->name, strlen(obj->intern->name)); } } @@ -155,6 +156,8 @@ php_pqstm_t *php_pqstm_init(php_pqconn_object_t *conn, const char *name, const c ZEND_INIT_SYMTABLE(&stm->bound); + zend_hash_str_add_ptr(&conn->intern->statements, name, strlen(name), stm); + return stm; } @@ -410,7 +413,7 @@ static PHP_METHOD(pqstm, deallocateAsync) php_pqstm_deallocate_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } -static zend_always_inline void php_pqstm_prepare_handler(INTERNAL_FUNCTION_PARAMETERS, zend_bool async) +static inline void php_pqstm_prepare_handler(INTERNAL_FUNCTION_PARAMETERS, zend_bool async) { zend_error_handling zeh; ZEND_RESULT_CODE rv; @@ -433,6 +436,9 @@ static zend_always_inline void php_pqstm_prepare_handler(INTERNAL_FUNCTION_PARAM if (SUCCESS == rv) { obj->intern->allocated = 1; + + zend_hash_str_add_ptr(&obj->intern->conn->intern->statements, + obj->intern->name, strlen(obj->intern->name), obj->intern); } } } diff --git a/tests/gh-issue015_listeners.phpt b/tests/gh-issue015_listeners.phpt new file mode 100644 index 0000000..5506b21 --- /dev/null +++ b/tests/gh-issue015_listeners.phpt @@ -0,0 +1,70 @@ +--TEST-- +restore listeners on reset +--SKIPIF-- + +--INI-- +date.timezone=UTC +--FILE-- +listen("notify", function($channel, $message) { + printf("%s: %s\n", $channel, $message); +}); +$c->on(pq\Connection::EVENT_RESET, function($conn) { + printf("Connection was reset\n"); +}); +$c->notify("notify", "Gotcha!"); +$c->resetAsync(); + +// wait until the stream becomes writable +$w = array($c->socket); +$r = $e = null; + +if (stream_select($r, $w, $e, null)) { + + // loop until the connection is established + while (true) { + + switch ($c->poll()) { + + case pq\Connection::POLLING_READING: + // we should wait for the stream to be read-ready + $r = array($c->socket); + stream_select($r, $w, $e, NULL); + break; + + case pq\Connection::POLLING_WRITING: + // we should wait for the stream to be write-ready + $w = array($c->socket); + $r = $e = null; + stream_select($r, $w, $e, null); + break; + + case pq\Connection::POLLING_FAILED: + printf("Connection failed: %s\n", $c->errorMessage); + break 2; + + case pq\Connection::POLLING_OK: + printf("Connection completed\n"); + break 2; + } + } +} +$c->notify("notify", "Do you miss me?"); +$c->exec(""); +?> +===DONE=== +--EXPECT-- +Test +notify: Gotcha! +Connection was reset +Connection completed +notify: Do you miss me? +===DONE=== \ No newline at end of file diff --git a/tests/gh-issue015_statements.phpt b/tests/gh-issue015_statements.phpt new file mode 100644 index 0000000..b54664a --- /dev/null +++ b/tests/gh-issue015_statements.phpt @@ -0,0 +1,42 @@ +--TEST-- +restore statements on reset +--SKIPIF-- + +--INI-- +date.timezone=UTC +--FILE-- +prepare("test", "SELECT 1"); +$c->on(pq\Connection::EVENT_RESET, function($conn) { + printf("Connection was reset\n"); +}); + +var_dump($s->exec()->fetchRow()); + +$c->reset(); + +// Fatal error: Uncaught exception 'pq\Exception\DomainException' with message 'ERROR: prepared statement "test" does not exist' +var_dump($s->exec()->fetchRow()); + +?> +===DONE=== +--EXPECT-- +Test +array(1) { + [0]=> + int(1) +} +Connection was reset +array(1) { + [0]=> + int(1) +} +===DONE=== \ No newline at end of file