From d8ecb6800084d6cec337a553ee036a4268db6c8b Mon Sep 17 00:00:00 2001 From: K Date: Mon, 20 Sep 2021 23:34:37 +0200 Subject: [PATCH 1/3] speed-up SQLite3Result::fetchArray(SQLITE3_ASSOC) by caching column names --- ext/sqlite3/php_sqlite3_structs.h | 3 +++ ext/sqlite3/sqlite3.c | 34 +++++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/ext/sqlite3/php_sqlite3_structs.h b/ext/sqlite3/php_sqlite3_structs.h index cec32691e2ff0..4186ac62f94c0 100644 --- a/ext/sqlite3/php_sqlite3_structs.h +++ b/ext/sqlite3/php_sqlite3_structs.h @@ -107,6 +107,9 @@ struct _php_sqlite3_result_object { php_sqlite3_stmt *stmt_obj; zval stmt_obj_zval; + zend_long column_count; + zend_string **column_names; + int is_prepared_statement; zend_object zo; }; diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index 007eef7a744ab..faaed8ab241e7 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -586,6 +586,8 @@ PHP_METHOD(SQLite3, query) result = Z_SQLITE3_RESULT_P(return_value); result->db_obj = db_obj; result->stmt_obj = stmt_obj; + result->column_names = NULL; + result->column_count = -1; ZVAL_OBJ(&result->stmt_obj_zval, Z_OBJ(stmt)); return_code = sqlite3_step(result->stmt_obj->stmt); @@ -1792,6 +1794,8 @@ PHP_METHOD(SQLite3Stmt, execute) result->is_prepared_statement = 1; result->db_obj = stmt_obj->db_obj; result->stmt_obj = stmt_obj; + result->column_names = NULL; + result->column_count = -1; ZVAL_OBJ_COPY(&result->stmt_obj_zval, Z_OBJ_P(object)); break; @@ -1945,11 +1949,26 @@ PHP_METHOD(SQLite3Result, fetchArray) RETURN_FALSE; } + if (result_obj->column_count == -1) { + result_obj->column_count = sqlite3_column_count(result_obj->stmt_obj->stmt); + } + + zend_long n_cols = result_obj->column_count; + + /* Cache column names to speed up repeated fetchArray calls. + * Names are deallocated in php_sqlite3_result_object_free_storage. */ + if (mode & PHP_SQLITE3_ASSOC && !result_obj->column_names) { + result_obj->column_names = pemalloc(n_cols * sizeof(zend_string*), 0); + + for (int i = 0; i < n_cols; i++) { + const char* column = sqlite3_column_name(result_obj->stmt_obj->stmt, i); + result_obj->column_names[i] = zend_string_init(column, strlen(column), 0); + } + } + array_init(return_value); - - int column_count = sqlite3_data_count(result_obj->stmt_obj->stmt); - for (i = 0; i < column_count; i++) { + for (i = 0; i < n_cols; i++) { zval data; sqlite_value_to_zval(result_obj->stmt_obj->stmt, i, &data); @@ -1964,7 +1983,7 @@ PHP_METHOD(SQLite3Result, fetchArray) Z_ADDREF(data); } } - add_assoc_zval(return_value, (char*)sqlite3_column_name(result_obj->stmt_obj->stmt, i), &data); + zend_symtable_add_new(Z_ARR_P(return_value), result_obj->column_names[i], &data); } } break; @@ -2235,6 +2254,13 @@ static void php_sqlite3_result_object_free_storage(zend_object *object) /* {{{ * return; } + if (intern->column_names) { + for (int i = 0; i < intern->column_count; i++) { + zend_string_release(intern->column_names[i]); + } + efree(intern->column_names); + } + if (!Z_ISNULL(intern->stmt_obj_zval)) { if (intern->stmt_obj && intern->stmt_obj->initialised) { sqlite3_reset(intern->stmt_obj->stmt); From 337b496db35e272bda2f79c12ce94c98581be426 Mon Sep 17 00:00:00 2001 From: K Date: Wed, 22 Sep 2021 15:56:04 +0200 Subject: [PATCH 2/3] fix stale column names after alter table statement --- ext/sqlite3/php_sqlite3_structs.h | 4 ++- ext/sqlite3/sqlite3.c | 30 +++++++++++++------- ext/sqlite3/tests/sqlite3_rename_column.phpt | 30 ++++++++++++++++++++ 3 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 ext/sqlite3/tests/sqlite3_rename_column.phpt diff --git a/ext/sqlite3/php_sqlite3_structs.h b/ext/sqlite3/php_sqlite3_structs.h index 4186ac62f94c0..15cb8a8cb303d 100644 --- a/ext/sqlite3/php_sqlite3_structs.h +++ b/ext/sqlite3/php_sqlite3_structs.h @@ -107,7 +107,9 @@ struct _php_sqlite3_result_object { php_sqlite3_stmt *stmt_obj; zval stmt_obj_zval; - zend_long column_count; + /* Cache of column names to speed up repeated fetchArray(SQLITE3_ASSOC) calls. + * Cache is cleared on reset() and finalize() calls. */ + int column_count; zend_string **column_names; int is_prepared_statement; diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index faaed8ab241e7..6c4edbbe4eb62 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -1955,13 +1955,12 @@ PHP_METHOD(SQLite3Result, fetchArray) zend_long n_cols = result_obj->column_count; - /* Cache column names to speed up repeated fetchArray calls. - * Names are deallocated in php_sqlite3_result_object_free_storage. */ + /* Cache column names to speed up repeated fetchArray calls. */ if (mode & PHP_SQLITE3_ASSOC && !result_obj->column_names) { - result_obj->column_names = pemalloc(n_cols * sizeof(zend_string*), 0); + result_obj->column_names = emalloc(n_cols * sizeof(zend_string*)); for (int i = 0; i < n_cols; i++) { - const char* column = sqlite3_column_name(result_obj->stmt_obj->stmt, i); + const char *column = sqlite3_column_name(result_obj->stmt_obj->stmt, i); result_obj->column_names[i] = zend_string_init(column, strlen(column), 0); } } @@ -1998,6 +1997,18 @@ PHP_METHOD(SQLite3Result, fetchArray) } /* }}} */ +static void sqlite3result_clear_column_names_cache(php_sqlite3_result *result) { + if (result->column_names) { + for (int i = 0; i < result->column_count; i++) { + zend_string_release(result->column_names[i]); + } + efree(result->column_names); + } + result->column_names = NULL; + result->column_count = -1; +} + + /* {{{ Resets the result set back to the first row. */ PHP_METHOD(SQLite3Result, reset) { @@ -2009,6 +2020,8 @@ PHP_METHOD(SQLite3Result, reset) SQLITE3_CHECK_INITIALIZED(result_obj->db_obj, result_obj->stmt_obj->initialised, SQLite3Result) + sqlite3result_clear_column_names_cache(result_obj); + if (sqlite3_reset(result_obj->stmt_obj->stmt) != SQLITE_OK) { RETURN_FALSE; } @@ -2028,6 +2041,8 @@ PHP_METHOD(SQLite3Result, finalize) SQLITE3_CHECK_INITIALIZED(result_obj->db_obj, result_obj->stmt_obj->initialised, SQLite3Result) + sqlite3result_clear_column_names_cache(result_obj); + /* We need to finalize an internal statement */ if (result_obj->is_prepared_statement == 0) { zend_llist_del_element(&(result_obj->db_obj->free_list), &result_obj->stmt_obj_zval, @@ -2254,12 +2269,7 @@ static void php_sqlite3_result_object_free_storage(zend_object *object) /* {{{ * return; } - if (intern->column_names) { - for (int i = 0; i < intern->column_count; i++) { - zend_string_release(intern->column_names[i]); - } - efree(intern->column_names); - } + sqlite3result_clear_column_names_cache(intern); if (!Z_ISNULL(intern->stmt_obj_zval)) { if (intern->stmt_obj && intern->stmt_obj->initialised) { diff --git a/ext/sqlite3/tests/sqlite3_rename_column.phpt b/ext/sqlite3/tests/sqlite3_rename_column.phpt new file mode 100644 index 0000000000000..4f077529be01c --- /dev/null +++ b/ext/sqlite3/tests/sqlite3_rename_column.phpt @@ -0,0 +1,30 @@ +--TEST-- +SQLite3 - rename column while SQLite3Result is open +--EXTENSIONS-- +sqlite3 +--FILE-- +exec('CREATE TABLE tbl (orig text)'); +$db->exec('insert into tbl values ("one"), ("two")'); + +$res1 = $db->prepare('select * from tbl')->execute(); +$res2 = $db->prepare('select * from tbl')->execute(); + +var_dump(array_key_first($res1->fetchArray(SQLITE3_ASSOC))); +var_dump(array_key_first($res2->fetchArray(SQLITE3_ASSOC))); + +$db->exec('alter table tbl rename column orig to changed'); + +$res1->reset(); +var_dump(array_key_first($res1->fetchArray(SQLITE3_ASSOC))); +var_dump(array_key_first($res2->fetchArray(SQLITE3_ASSOC))); + +?> +--EXPECT-- +string(4) "orig" +string(4) "orig" +string(7) "changed" +string(4) "orig" From 20669005f5ca20cbf6b76632d763eae5354f669a Mon Sep 17 00:00:00 2001 From: K Date: Wed, 22 Sep 2021 19:48:00 +0200 Subject: [PATCH 3/3] fix test for old sqlite3 versions --- ext/sqlite3/tests/sqlite3_rename_column.phpt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ext/sqlite3/tests/sqlite3_rename_column.phpt b/ext/sqlite3/tests/sqlite3_rename_column.phpt index 4f077529be01c..6b4e23bc7186b 100644 --- a/ext/sqlite3/tests/sqlite3_rename_column.phpt +++ b/ext/sqlite3/tests/sqlite3_rename_column.phpt @@ -2,6 +2,12 @@ SQLite3 - rename column while SQLite3Result is open --EXTENSIONS-- sqlite3 +--SKIPIF-- + --FILE--