diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 27c736348239..68f14ba31bdb 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,6 +203,9 @@ static void pgsql_link_free(pgsql_link_handle *link) FREE_HASHTABLE(link->notices); link->notices = NULL; } + + pgsql_meta_cache_destroy(&link->meta_cache); + } static void pgsql_link_free_obj(zend_object *obj) @@ -637,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 */ @@ -765,7 +786,11 @@ 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; + + zend_hash_update(&PGG(connections), str.s, return_value); + } else { /* Non persistent connection */ zval *index_ptr; @@ -816,6 +841,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 +1208,9 @@ PHP_FUNCTION(pg_query) php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); RETURN_FALSE; } + + pgsql_meta_cache_destroy(&link->meta_cache); + while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); leftover = true; @@ -1310,6 +1339,9 @@ PHP_FUNCTION(pg_query_params) php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); RETURN_FALSE; } + + pgsql_meta_cache_destroy(&link->meta_cache); + while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); leftover = true; @@ -1405,6 +1437,9 @@ PHP_FUNCTION(pg_prepare) php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); RETURN_FALSE; } + + pgsql_meta_cache_destroy(&link->meta_cache); + while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); leftover = true; @@ -1493,6 +1528,9 @@ PHP_FUNCTION(pg_execute) php_error_docref(NULL, E_NOTICE,"Cannot set connection to blocking mode"); RETURN_FALSE; } + + pgsql_meta_cache_destroy(&link->meta_cache); + while ((pgsql_result = PQgetResult(pgsql))) { PQclear(pgsql_result); leftover = true; @@ -1699,6 +1737,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) { @@ -3979,6 +4036,8 @@ PHP_FUNCTION(pg_send_query) RETURN_FALSE; } + pgsql_meta_cache_destroy(&link->meta_cache); + 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"); @@ -4052,6 +4111,8 @@ PHP_FUNCTION(pg_send_query_params) php_error_docref(NULL, E_NOTICE, "Cannot set connection to nonblocking mode"); RETURN_FALSE; } + + pgsql_meta_cache_destroy(&link->meta_cache); if (_php_pgsql_link_has_results(pgsql)) { php_error_docref(NULL, E_NOTICE, @@ -4133,6 +4194,8 @@ PHP_FUNCTION(pg_send_prepare) RETURN_FALSE; } + pgsql_meta_cache_destroy(&link->meta_cache); + 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"); @@ -4208,6 +4271,8 @@ PHP_FUNCTION(pg_send_execute) RETURN_FALSE; } + pgsql_meta_cache_destroy(&link->meta_cache); + 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"); @@ -4539,7 +4604,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) { @@ -4550,6 +4614,7 @@ 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; ZEND_ASSERT(ZSTR_LEN(table_name) != 0); @@ -4620,6 +4685,21 @@ 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); + + 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 +4707,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 +4737,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; @@ -6083,6 +6177,8 @@ 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"); } 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; 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