Speed up SQLite3Result::fetchArray() by caching column names

Closes GH-7505.
This commit is contained in:
git@k47.cz 2021-09-20 23:34:37 +02:00 committed by Nikita Popov
parent 1ea58832e2
commit 1487dd03bc
3 changed files with 81 additions and 5 deletions

View file

@ -107,6 +107,11 @@ struct _php_sqlite3_result_object {
php_sqlite3_stmt *stmt_obj; php_sqlite3_stmt *stmt_obj;
zval stmt_obj_zval; zval stmt_obj_zval;
/* 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; int is_prepared_statement;
zend_object zo; zend_object zo;
}; };

View file

@ -586,6 +586,8 @@ PHP_METHOD(SQLite3, query)
result = Z_SQLITE3_RESULT_P(return_value); result = Z_SQLITE3_RESULT_P(return_value);
result->db_obj = db_obj; result->db_obj = db_obj;
result->stmt_obj = stmt_obj; result->stmt_obj = stmt_obj;
result->column_names = NULL;
result->column_count = -1;
ZVAL_OBJ(&result->stmt_obj_zval, Z_OBJ(stmt)); ZVAL_OBJ(&result->stmt_obj_zval, Z_OBJ(stmt));
return_code = sqlite3_step(result->stmt_obj->stmt); return_code = sqlite3_step(result->stmt_obj->stmt);
@ -1792,6 +1794,8 @@ PHP_METHOD(SQLite3Stmt, execute)
result->is_prepared_statement = 1; result->is_prepared_statement = 1;
result->db_obj = stmt_obj->db_obj; result->db_obj = stmt_obj->db_obj;
result->stmt_obj = stmt_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)); ZVAL_OBJ_COPY(&result->stmt_obj_zval, Z_OBJ_P(object));
break; break;
@ -1945,11 +1949,25 @@ PHP_METHOD(SQLite3Result, fetchArray)
RETURN_FALSE; RETURN_FALSE;
} }
if (result_obj->column_count == -1) {
result_obj->column_count = sqlite3_column_count(result_obj->stmt_obj->stmt);
}
int n_cols = result_obj->column_count;
/* Cache column names to speed up repeated fetchArray calls. */
if (mode & PHP_SQLITE3_ASSOC && !result_obj->column_names) {
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);
result_obj->column_names[i] = zend_string_init(column, strlen(column), 0);
}
}
array_init(return_value); array_init(return_value);
int column_count = sqlite3_data_count(result_obj->stmt_obj->stmt); for (i = 0; i < n_cols; i++) {
for (i = 0; i < column_count; i++) {
zval data; zval data;
sqlite_value_to_zval(result_obj->stmt_obj->stmt, i, &data); sqlite_value_to_zval(result_obj->stmt_obj->stmt, i, &data);
@ -1964,7 +1982,7 @@ PHP_METHOD(SQLite3Result, fetchArray)
Z_ADDREF(data); 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; break;
@ -1979,6 +1997,17 @@ 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. */ /* {{{ Resets the result set back to the first row. */
PHP_METHOD(SQLite3Result, reset) PHP_METHOD(SQLite3Result, reset)
{ {
@ -1990,6 +2019,8 @@ PHP_METHOD(SQLite3Result, reset)
SQLITE3_CHECK_INITIALIZED(result_obj->db_obj, result_obj->stmt_obj->initialised, SQLite3Result) 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) { if (sqlite3_reset(result_obj->stmt_obj->stmt) != SQLITE_OK) {
RETURN_FALSE; RETURN_FALSE;
} }
@ -2009,6 +2040,8 @@ PHP_METHOD(SQLite3Result, finalize)
SQLITE3_CHECK_INITIALIZED(result_obj->db_obj, result_obj->stmt_obj->initialised, SQLite3Result) 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 */ /* We need to finalize an internal statement */
if (result_obj->is_prepared_statement == 0) { if (result_obj->is_prepared_statement == 0) {
zend_llist_del_element(&(result_obj->db_obj->free_list), &result_obj->stmt_obj_zval, zend_llist_del_element(&(result_obj->db_obj->free_list), &result_obj->stmt_obj_zval,
@ -2235,6 +2268,8 @@ static void php_sqlite3_result_object_free_storage(zend_object *object) /* {{{ *
return; return;
} }
sqlite3result_clear_column_names_cache(intern);
if (!Z_ISNULL(intern->stmt_obj_zval)) { if (!Z_ISNULL(intern->stmt_obj_zval)) {
if (intern->stmt_obj && intern->stmt_obj->initialised) { if (intern->stmt_obj && intern->stmt_obj->initialised) {
sqlite3_reset(intern->stmt_obj->stmt); sqlite3_reset(intern->stmt_obj->stmt);

View file

@ -0,0 +1,36 @@
--TEST--
SQLite3 - rename column while SQLite3Result is open
--EXTENSIONS--
sqlite3
--SKIPIF--
<?php
if (SQLite3::version()['versionNumber'] < 3025000) {
die("skip: sqlite3 library version < 3.25: no support for rename column");
}
?>
--FILE--
<?php
$db = new SQLite3(':memory:');
$db->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"