From 3bbec1ba26dafd8c958e304706d29ca698ab726f Mon Sep 17 00:00:00 2001 From: arshidkv12 Date: Mon, 27 Apr 2026 17:16:34 +0530 Subject: [PATCH 1/5] ext/pgsql: add meta_cache per-link metadata caching --- ext/pgsql/pgsql.c | 49 ++++++++++++++++++++++++++++++++++++++++++- ext/pgsql/php_pgsql.h | 1 + 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 27c736348239..bff82fb1c218 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -194,6 +194,11 @@ static void pgsql_link_free(pgsql_link_handle *link) FREE_HASHTABLE(link->notices); link->notices = NULL; } + if (link->meta_cache) { + zend_hash_destroy(link->meta_cache); + FREE_HASHTABLE(link->meta_cache); + link->meta_cache = NULL; + } } static void pgsql_link_free_obj(zend_object *obj) @@ -765,6 +770,7 @@ static void php_pgsql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) link->conn = pgsql; link->hash = zend_string_copy(str.s); link->notices = NULL; + link->meta_cache = NULL; link->persistent = 1; } else { /* Non persistent connection */ zval *index_ptr; @@ -816,6 +822,7 @@ static void php_pgsql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) link->conn = pgsql; link->hash = zend_string_copy(str.s); link->notices = NULL; + link->meta_cache = NULL; link->persistent = 0; /* add it to the hash */ @@ -1182,6 +1189,12 @@ PHP_FUNCTION(pg_query) php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); RETURN_FALSE; } + + if (link->meta_cache) { + zend_hash_destroy(link->meta_cache); + FREE_HASHTABLE(link->meta_cache); + link->meta_cache = NULL; + } while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); leftover = true; @@ -4550,6 +4563,8 @@ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string size_t new_len, len; int i, num_rows, err; zval elem; + pgsql_link_handle *link; + link = FETCH_DEFAULT_LINK_NO_WARNING(); ZEND_ASSERT(ZSTR_LEN(table_name) != 0); @@ -4620,6 +4635,19 @@ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string smart_str_0(&querystr); efree(src); + if (link && link->meta_cache) { + zval *meta_cache = zend_hash_find(link->meta_cache, querystr.s); + + if (meta_cache) { + if (Z_TYPE_P(meta) != IS_UNDEF) { + zval_ptr_dtor(meta); + } + ZVAL_COPY(meta, meta_cache); + smart_str_free(&querystr); + return SUCCESS; + } + } + pg_result = PQexec(pg_link, ZSTR_VAL(querystr.s)); if (PQresultStatus(pg_result) != PGRES_TUPLES_OK || (num_rows = PQntuples(pg_result)) == 0) { php_error_docref(NULL, E_WARNING, "Table '%s' doesn't exists", ZSTR_VAL(table_name)); @@ -4627,7 +4655,6 @@ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string PQclear(pg_result); return FAILURE; } - smart_str_free(&querystr); for (i = 0; i < num_rows; i++) { char *name; @@ -4658,6 +4685,21 @@ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string name = PQgetvalue(pg_result,i,0); add_assoc_zval(meta, name, &elem); } + + if(link) { + if (!link->meta_cache) { + ALLOC_HASHTABLE(link->meta_cache); + zend_hash_init(link->meta_cache, 8, NULL, ZVAL_PTR_DTOR, 0); + } + + zval meta_copy; + ZVAL_COPY(&meta_copy, meta); + zend_string *key = zend_string_copy(querystr.s); + + zend_hash_update( link->meta_cache, key, &meta_copy); + zend_string_release(key); + } + smart_str_free(&querystr); PQclear(pg_result); return SUCCESS; @@ -6089,6 +6131,11 @@ PHP_FUNCTION(pg_delete) if (php_pgsql_delete(pg_link, table, ids, option, &sql) == FAILURE) { RETURN_FALSE; } + if (link->meta_cache) { + zend_hash_destroy(link->meta_cache); + FREE_HASHTABLE(link->meta_cache); + link->meta_cache = NULL; + } if (option & PGSQL_DML_STRING) { RETURN_STR(sql); } diff --git a/ext/pgsql/php_pgsql.h b/ext/pgsql/php_pgsql.h index a865a7ce73a4..05f13c7eabf6 100644 --- a/ext/pgsql/php_pgsql.h +++ b/ext/pgsql/php_pgsql.h @@ -139,6 +139,7 @@ typedef struct pgsql_link_handle { PGconn *conn; zend_string *hash; HashTable *notices; + HashTable *meta_cache; bool persistent; zend_object std; } pgsql_link_handle; From 884dcbda64df84d086504275590d0422382474b9 Mon Sep 17 00:00:00 2001 From: arshidkv12 Date: Tue, 28 Apr 2026 11:08:41 +0530 Subject: [PATCH 2/5] ext/pgsql: add meta_cache per-link metadata caching --- ext/pgsql/pgsql.c | 65 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index bff82fb1c218..3bee6fbf99e2 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -1195,6 +1195,7 @@ PHP_FUNCTION(pg_query) FREE_HASHTABLE(link->meta_cache); link->meta_cache = NULL; } + while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); leftover = true; @@ -1323,6 +1324,13 @@ PHP_FUNCTION(pg_query_params) php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); RETURN_FALSE; } + + if (link->meta_cache) { + zend_hash_destroy(link->meta_cache); + FREE_HASHTABLE(link->meta_cache); + link->meta_cache = NULL; + } + while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); leftover = true; @@ -1418,6 +1426,11 @@ PHP_FUNCTION(pg_prepare) php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); RETURN_FALSE; } + if (link->meta_cache) { + zend_hash_destroy(link->meta_cache); + FREE_HASHTABLE(link->meta_cache); + link->meta_cache = NULL; + } while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); leftover = true; @@ -1506,6 +1519,11 @@ PHP_FUNCTION(pg_execute) php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); RETURN_FALSE; } + if (link->meta_cache) { + zend_hash_destroy(link->meta_cache); + FREE_HASHTABLE(link->meta_cache); + link->meta_cache = NULL; + } while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); leftover = true; @@ -1712,6 +1730,25 @@ static zend_string *get_field_name(PGconn *pgsql, Oid oid) return ret; } +static pgsql_link_handle *pgsql_get_link_from_conn(PGconn *conn) +{ + zval *pgsql_link; + + if (!conn) { + return NULL; + } + + ZEND_HASH_FOREACH_VAL(&PGG(connections), pgsql_link) { + pgsql_link_handle *link = Z_PGSQL_LINK_P(pgsql_link); + + if (link && link->conn == conn) { + return link; + } + } ZEND_HASH_FOREACH_END(); + + return NULL; +} + /* Returns the name of the table field belongs to, or table's oid if oid_only is true */ PHP_FUNCTION(pg_field_table) { @@ -3992,6 +4029,12 @@ PHP_FUNCTION(pg_send_query) RETURN_FALSE; } + if (link->meta_cache) { + zend_hash_destroy(link->meta_cache); + FREE_HASHTABLE(link->meta_cache); + link->meta_cache = NULL; + } + if (_php_pgsql_link_has_results(pgsql)) { php_error_docref(NULL, E_NOTICE, "There are results on this connection. Call pg_get_result() until it returns FALSE"); @@ -4066,6 +4109,12 @@ PHP_FUNCTION(pg_send_query_params) RETURN_FALSE; } + if (link->meta_cache) { + zend_hash_destroy(link->meta_cache); + FREE_HASHTABLE(link->meta_cache); + link->meta_cache = NULL; + } + if (_php_pgsql_link_has_results(pgsql)) { php_error_docref(NULL, E_NOTICE, "There are results on this connection. Call pg_get_result() until it returns FALSE"); @@ -4146,6 +4195,12 @@ PHP_FUNCTION(pg_send_prepare) RETURN_FALSE; } + if (link->meta_cache) { + zend_hash_destroy(link->meta_cache); + FREE_HASHTABLE(link->meta_cache); + link->meta_cache = NULL; + } + if (_php_pgsql_link_has_results(pgsql)) { php_error_docref(NULL, E_NOTICE, "There are results on this connection. Call pg_get_result() until it returns FALSE"); @@ -4221,6 +4276,12 @@ PHP_FUNCTION(pg_send_execute) RETURN_FALSE; } + if (link->meta_cache) { + zend_hash_destroy(link->meta_cache); + FREE_HASHTABLE(link->meta_cache); + link->meta_cache = NULL; + } + if (_php_pgsql_link_has_results(pgsql)) { php_error_docref(NULL, E_NOTICE, "There are results on this connection. Call pg_get_result() until it returns FALSE"); @@ -4552,7 +4613,6 @@ PHP_FUNCTION(pg_flush) /* {{{ php_pgsql_meta_data * table_name must not be empty - * TODO: Add meta_data cache for better performance */ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string *table_name, zval *meta, bool extended) { @@ -4564,7 +4624,6 @@ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string int i, num_rows, err; zval elem; pgsql_link_handle *link; - link = FETCH_DEFAULT_LINK_NO_WARNING(); ZEND_ASSERT(ZSTR_LEN(table_name) != 0); @@ -4635,6 +4694,8 @@ PHP_PGSQL_API zend_result php_pgsql_meta_data(PGconn *pg_link, const zend_string smart_str_0(&querystr); efree(src); + link = pgsql_get_link_from_conn(pg_link); + if (link && link->meta_cache) { zval *meta_cache = zend_hash_find(link->meta_cache, querystr.s); From 5c0e4a9dc0d6fbbde29bcdd43edc4594b55e162e Mon Sep 17 00:00:00 2001 From: arshidkv12 Date: Tue, 28 Apr 2026 12:27:20 +0530 Subject: [PATCH 3/5] ext/pgsql: add meta_cache per-link metadata caching --- ext/pgsql/pgsql.c | 78 ++++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 51 deletions(-) diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 3bee6fbf99e2..9ac42da603b3 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -171,6 +171,15 @@ static zend_function *pgsql_link_get_constructor(zend_object *object) { return NULL; } +static inline void pgsql_meta_cache_destroy(HashTable **cache) +{ + if (*cache) { + zend_hash_destroy(*cache); + FREE_HASHTABLE(*cache); + *cache = NULL; + } +} + static void pgsql_link_free(pgsql_link_handle *link) { PGresult *res; @@ -194,11 +203,9 @@ static void pgsql_link_free(pgsql_link_handle *link) FREE_HASHTABLE(link->notices); link->notices = NULL; } - if (link->meta_cache) { - zend_hash_destroy(link->meta_cache); - FREE_HASHTABLE(link->meta_cache); - link->meta_cache = NULL; - } + + pgsql_meta_cache_destroy(&link->meta_cache); + } static void pgsql_link_free_obj(zend_object *obj) @@ -1190,11 +1197,7 @@ PHP_FUNCTION(pg_query) RETURN_FALSE; } - if (link->meta_cache) { - zend_hash_destroy(link->meta_cache); - FREE_HASHTABLE(link->meta_cache); - link->meta_cache = NULL; - } + pgsql_meta_cache_destroy(&link->meta_cache); while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); @@ -1325,11 +1328,7 @@ PHP_FUNCTION(pg_query_params) RETURN_FALSE; } - if (link->meta_cache) { - zend_hash_destroy(link->meta_cache); - FREE_HASHTABLE(link->meta_cache); - link->meta_cache = NULL; - } + pgsql_meta_cache_destroy(&link->meta_cache); while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); @@ -1426,11 +1425,9 @@ PHP_FUNCTION(pg_prepare) php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); RETURN_FALSE; } - if (link->meta_cache) { - zend_hash_destroy(link->meta_cache); - FREE_HASHTABLE(link->meta_cache); - link->meta_cache = NULL; - } + + pgsql_meta_cache_destroy(&link->meta_cache); + while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); leftover = true; @@ -1519,11 +1516,9 @@ PHP_FUNCTION(pg_execute) php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); RETURN_FALSE; } - if (link->meta_cache) { - zend_hash_destroy(link->meta_cache); - FREE_HASHTABLE(link->meta_cache); - link->meta_cache = NULL; - } + + pgsql_meta_cache_destroy(&link->meta_cache); + while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); leftover = true; @@ -4029,11 +4024,7 @@ PHP_FUNCTION(pg_send_query) RETURN_FALSE; } - if (link->meta_cache) { - zend_hash_destroy(link->meta_cache); - FREE_HASHTABLE(link->meta_cache); - link->meta_cache = NULL; - } + pgsql_meta_cache_destroy(&link->meta_cache); if (_php_pgsql_link_has_results(pgsql)) { php_error_docref(NULL, E_NOTICE, @@ -4108,12 +4099,8 @@ PHP_FUNCTION(pg_send_query_params) php_error_docref(NULL, E_NOTICE, "Cannot set connection to nonblocking mode"); RETURN_FALSE; } - - if (link->meta_cache) { - zend_hash_destroy(link->meta_cache); - FREE_HASHTABLE(link->meta_cache); - link->meta_cache = NULL; - } + + pgsql_meta_cache_destroy(&link->meta_cache); if (_php_pgsql_link_has_results(pgsql)) { php_error_docref(NULL, E_NOTICE, @@ -4195,11 +4182,7 @@ PHP_FUNCTION(pg_send_prepare) RETURN_FALSE; } - if (link->meta_cache) { - zend_hash_destroy(link->meta_cache); - FREE_HASHTABLE(link->meta_cache); - link->meta_cache = NULL; - } + pgsql_meta_cache_destroy(&link->meta_cache); if (_php_pgsql_link_has_results(pgsql)) { php_error_docref(NULL, E_NOTICE, @@ -4276,11 +4259,7 @@ PHP_FUNCTION(pg_send_execute) RETURN_FALSE; } - if (link->meta_cache) { - zend_hash_destroy(link->meta_cache); - FREE_HASHTABLE(link->meta_cache); - link->meta_cache = NULL; - } + pgsql_meta_cache_destroy(&link->meta_cache); if (_php_pgsql_link_has_results(pgsql)) { php_error_docref(NULL, E_NOTICE, @@ -6186,17 +6165,14 @@ PHP_FUNCTION(pg_delete) CHECK_PGSQL_LINK(link); pg_link = link->conn; + pgsql_meta_cache_destroy(&link->meta_cache); + if (php_pgsql_flush_query(pg_link)) { php_error_docref(NULL, E_NOTICE, "Detected unhandled result(s) in connection"); } if (php_pgsql_delete(pg_link, table, ids, option, &sql) == FAILURE) { RETURN_FALSE; } - if (link->meta_cache) { - zend_hash_destroy(link->meta_cache); - FREE_HASHTABLE(link->meta_cache); - link->meta_cache = NULL; - } if (option & PGSQL_DML_STRING) { RETURN_STR(sql); } From 638e354a243df2b1f62675887282ae14edc6b42b Mon Sep 17 00:00:00 2001 From: arshidkv12 Date: Tue, 28 Apr 2026 17:16:00 +0530 Subject: [PATCH 4/5] ext/pgsql: add meta_cache per-link metadata caching --- ext/pgsql/pgsql.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 9ac42da603b3..b1fdeffc73a5 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -779,6 +779,9 @@ static void php_pgsql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) link->notices = NULL; link->meta_cache = NULL; link->persistent = 1; + + zend_hash_update(&PGG(connections), str.s, return_value); + } else { /* Non persistent connection */ zval *index_ptr; From 0bb7d6d31e3390517575e2817d1d35b053436c28 Mon Sep 17 00:00:00 2001 From: arshidkv12 Date: Tue, 28 Apr 2026 21:55:28 +0530 Subject: [PATCH 5/5] ext/pgsql: add meta_cache per-link metadata caching --- ext/pgsql/pgsql.c | 9 +++ ext/pgsql/tests/pg_meta_data_cache.phpt | 78 +++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 ext/pgsql/tests/pg_meta_data_cache.phpt diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index b1fdeffc73a5..68f14ba31bdb 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -649,6 +649,15 @@ PHP_RSHUTDOWN_FUNCTION(pgsql) PGG(default_link) = NULL; } + zval *pgsql_link; + + ZEND_HASH_FOREACH_VAL(&PGG(connections), pgsql_link) { + pgsql_link_handle *link = Z_PGSQL_LINK_P(pgsql_link); + if (link) { + pgsql_meta_cache_destroy(&link->meta_cache); + } + } ZEND_HASH_FOREACH_END(); + zend_hash_destroy(&PGG(field_oids)); zend_hash_destroy(&PGG(table_oids)); /* clean up persistent connection */ diff --git a/ext/pgsql/tests/pg_meta_data_cache.phpt b/ext/pgsql/tests/pg_meta_data_cache.phpt new file mode 100644 index 000000000000..3b13f07346e4 --- /dev/null +++ b/ext/pgsql/tests/pg_meta_data_cache.phpt @@ -0,0 +1,78 @@ +--TEST-- +pg_meta_data() - cache behavior and invalidation +--EXTENSIONS-- +pgsql +--SKIPIF-- + +--FILE-- + +--CLEAN-- + +--EXPECTF-- +Basic metadata: OK (columns: %s) +ALTER invalidation: OK ('age' found) +DROP/CREATE invalidation: OK (only 'only_column' present) +Extended metadata: OK (has 'is enum', 'description') +Schema-qualified name: OK +Warning: pg_meta_data(): Table 'non_existent_table' doesn't exists in %s on line %d +Done \ No newline at end of file