diff --git a/ext/sqlite3/php_sqlite3_structs.h b/ext/sqlite3/php_sqlite3_structs.h index cec32691e2ff0..15cb8a8cb303d 100644 --- a/ext/sqlite3/php_sqlite3_structs.h +++ b/ext/sqlite3/php_sqlite3_structs.h @@ -107,6 +107,11 @@ struct _php_sqlite3_result_object { php_sqlite3_stmt *stmt_obj; 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; zend_object zo; }; diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index 007eef7a744ab..6c4edbbe4eb62 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,25 @@ 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. */ + 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); - - 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 +1982,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; @@ -1979,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) { @@ -1990,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; } @@ -2009,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, @@ -2235,6 +2269,8 @@ static void php_sqlite3_result_object_free_storage(zend_object *object) /* {{{ * return; } + sqlite3result_clear_column_names_cache(intern); + if (!Z_ISNULL(intern->stmt_obj_zval)) { if (intern->stmt_obj && intern->stmt_obj->initialised) { sqlite3_reset(intern->stmt_obj->stmt); diff --git a/ext/sqlite3/tests/sqlite3_rename_column.phpt b/ext/sqlite3/tests/sqlite3_rename_column.phpt new file mode 100644 index 0000000000000..6b4e23bc7186b --- /dev/null +++ b/ext/sqlite3/tests/sqlite3_rename_column.phpt @@ -0,0 +1,36 @@ +--TEST-- +SQLite3 - rename column while SQLite3Result is open +--EXTENSIONS-- +sqlite3 +--SKIPIF-- + +--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"