Skip to content

Commit 3553d71

Browse files
cmb69maaarghk
authored andcommitted
Fix #64531: SQLite3Result::fetchArray runs the query again
`SQLite3::query()` and `SQLite3Stmt::execute()` call `sqlite3_step()` to determine whether to return a `SQLite3Result` or `false`. Then they call `sqlite3_reset()`, so that `SQLite3Result::fetchArray()` can fetch values from the first row. This obviously leads to issues if the SQL query causes any side effects. We solve this by not calling `sqlite_reset()` anymore, but instead storing the last fetch result code on the statement object, and to use this in `::fetchArray()` when the first row is to be fetched. This also allows us to prevent an SQLite3 quirk which automatically resets a statement when `sqlite_fetch()` is called after the last fetch returned anything else than `SQLITE_ROW`; thus fixing bug #79293.
1 parent d9c49ae commit 3553d71

File tree

5 files changed

+114
-7
lines changed

5 files changed

+114
-7
lines changed

ext/sqlite3/php_sqlite3_structs.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ struct _php_sqlite3_stmt_object {
128128
php_sqlite3_db_object *db_obj;
129129
zval db_obj_zval;
130130

131-
int initialised;
131+
unsigned initialised:1;
132+
unsigned has_stepped:1;
133+
unsigned last_step_result:30;
132134

133135
/* Keep track of the zvals for bound parameters */
134136
HashTable *bound_params;

ext/sqlite3/sqlite3.c

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,9 @@ PHP_METHOD(SQLite3, query)
591591
ZVAL_OBJ(&result->stmt_obj_zval, Z_OBJ(stmt));
592592

593593
return_code = sqlite3_step(result->stmt_obj->stmt);
594+
result->stmt_obj->has_stepped = 1;
595+
ZEND_ASSERT(return_code >= 0 && return_code < (1 << 30));
596+
result->stmt_obj->last_step_result = return_code;
594597

595598
switch (return_code) {
596599
case SQLITE_ROW: /* Valid Row */
@@ -601,7 +604,6 @@ PHP_METHOD(SQLite3, query)
601604
free_item->stmt_obj = stmt_obj;
602605
free_item->stmt_obj_zval = stmt;
603606
zend_llist_add_element(&(db_obj->free_list), &free_item);
604-
sqlite3_reset(result->stmt_obj->stmt);
605607
break;
606608
}
607609
default:
@@ -610,6 +612,7 @@ PHP_METHOD(SQLite3, query)
610612
}
611613
sqlite3_finalize(stmt_obj->stmt);
612614
stmt_obj->initialised = 0;
615+
stmt_obj->has_stepped = 0;
613616
zval_ptr_dtor(return_value);
614617
RETURN_FALSE;
615618
}
@@ -1432,6 +1435,7 @@ PHP_METHOD(SQLite3Stmt, reset)
14321435
SQLITE3_CHECK_INITIALIZED(stmt_obj->db_obj, stmt_obj->initialised, SQLite3);
14331436
SQLITE3_CHECK_INITIALIZED_STMT(stmt_obj->stmt, SQLite3Stmt);
14341437

1438+
stmt_obj->has_stepped = 0;
14351439
if (sqlite3_reset(stmt_obj->stmt) != SQLITE_OK) {
14361440
php_sqlite3_error(stmt_obj->db_obj, "Unable to reset statement: %s", sqlite3_errmsg(sqlite3_db_handle(stmt_obj->stmt)));
14371441
RETURN_FALSE;
@@ -1782,12 +1786,14 @@ PHP_METHOD(SQLite3Stmt, execute)
17821786
}
17831787

17841788
return_code = sqlite3_step(stmt_obj->stmt);
1789+
stmt_obj->has_stepped = 1;
1790+
ZEND_ASSERT(return_code >= 0 && return_code < (1 << 30));
1791+
stmt_obj->last_step_result = return_code;
17851792

17861793
switch (return_code) {
17871794
case SQLITE_ROW: /* Valid Row */
17881795
case SQLITE_DONE: /* Valid but no results */
17891796
{
1790-
sqlite3_reset(stmt_obj->stmt);
17911797
object_init_ex(return_value, php_sqlite3_result_entry);
17921798
result = Z_SQLITE3_RESULT_P(return_value);
17931799

@@ -1800,9 +1806,6 @@ PHP_METHOD(SQLite3Stmt, execute)
18001806

18011807
break;
18021808
}
1803-
case SQLITE_ERROR:
1804-
sqlite3_reset(stmt_obj->stmt);
1805-
ZEND_FALLTHROUGH;
18061809
default:
18071810
if (!EG(exception)) {
18081811
php_sqlite3_error(stmt_obj->db_obj, "Unable to execute statement: %s", sqlite3_errmsg(sqlite3_db_handle(stmt_obj->stmt)));
@@ -1941,7 +1944,14 @@ PHP_METHOD(SQLite3Result, fetchArray)
19411944

19421945
SQLITE3_CHECK_INITIALIZED(result_obj->db_obj, result_obj->stmt_obj->initialised, SQLite3Result)
19431946

1944-
ret = sqlite3_step(result_obj->stmt_obj->stmt);
1947+
if (result_obj->stmt_obj->has_stepped) {
1948+
ret = result_obj->stmt_obj->last_step_result;
1949+
} else {
1950+
ret = sqlite3_step(result_obj->stmt_obj->stmt);
1951+
result_obj->stmt_obj->has_stepped = 1;
1952+
ZEND_ASSERT(ret >= 0 && ret < (1 << 30));
1953+
result_obj->stmt_obj->last_step_result = ret;
1954+
}
19451955
switch (ret) {
19461956
case SQLITE_ROW:
19471957
/* If there was no return value then just skip fetching */
@@ -1985,6 +1995,7 @@ PHP_METHOD(SQLite3Result, fetchArray)
19851995
zend_symtable_add_new(Z_ARR_P(return_value), result_obj->column_names[i], &data);
19861996
}
19871997
}
1998+
result_obj->stmt_obj->has_stepped = 0;
19881999
break;
19892000

19902001
case SQLITE_DONE:
@@ -2021,6 +2032,7 @@ PHP_METHOD(SQLite3Result, reset)
20212032

20222033
sqlite3result_clear_column_names_cache(result_obj);
20232034

2035+
result_obj->stmt_obj->has_stepped = 0;
20242036
if (sqlite3_reset(result_obj->stmt_obj->stmt) != SQLITE_OK) {
20252037
RETURN_FALSE;
20262038
}
@@ -2047,6 +2059,7 @@ PHP_METHOD(SQLite3Result, finalize)
20472059
zend_llist_del_element(&(result_obj->db_obj->free_list), &result_obj->stmt_obj_zval,
20482060
(int (*)(void *, void *)) php_sqlite3_compare_stmt_zval_free);
20492061
} else {
2062+
result_obj->stmt_obj->has_stepped = 0;
20502063
sqlite3_reset(result_obj->stmt_obj->stmt);
20512064
}
20522065

@@ -2273,6 +2286,7 @@ static void php_sqlite3_result_object_free_storage(zend_object *object) /* {{{ *
22732286
if (!Z_ISNULL(intern->stmt_obj_zval)) {
22742287
if (intern->stmt_obj && intern->stmt_obj->initialised) {
22752288
sqlite3_reset(intern->stmt_obj->stmt);
2289+
intern->stmt_obj->has_stepped = 0;
22762290
}
22772291

22782292
zval_ptr_dtor(&intern->stmt_obj_zval);
@@ -2312,6 +2326,7 @@ static zend_object *php_sqlite3_stmt_object_new(zend_class_entry *class_type) /*
23122326
object_properties_init(&intern->zo, class_type);
23132327

23142328
intern->zo.handlers = &sqlite3_stmt_object_handlers;
2329+
intern->has_stepped = 0;
23152330

23162331
return &intern->zo;
23172332
}

ext/sqlite3/tests/bug64531_1.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Bug #64531 (SQLite3Result::fetchArray runs the query again)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('sqlite3')) die('skip sqlite3 extension not available');
6+
?>
7+
--FILE--
8+
<?php
9+
$conn = new SQLite3(':memory:');
10+
$conn->exec("CREATE TABLE foo (id INT)");
11+
12+
$res = $conn->query("INSERT INTO foo VALUES (1)");
13+
14+
$res->fetchArray();
15+
$res->fetchArray();
16+
17+
$res = $conn->query("SELECT * FROM foo");
18+
while (($row = $res->fetchArray(SQLITE3_NUM))) {
19+
var_dump($row);
20+
}
21+
?>
22+
--EXPECT--
23+
array(1) {
24+
[0]=>
25+
int(1)
26+
}

ext/sqlite3/tests/bug64531_2.phpt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
Bug #64531 (SQLite3Result::fetchArray runs the query again)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('sqlite3')) die('skip sqlite3 extension not available');
6+
?>
7+
--FILE--
8+
<?php
9+
function testing($val) {
10+
echo "Testing with: $val\n";
11+
return true;
12+
}
13+
14+
$conn = new SQLite3(':memory:');
15+
$conn->exec("CREATE TABLE foo (id INT)");
16+
for ($i = 1; $i <= 3; $i++) {
17+
$conn->exec("INSERT INTO foo VALUES ($i)");
18+
}
19+
$conn->createFunction('testing', 'testing', 1);
20+
21+
$res = $conn->query('SELECT * FROM foo WHERE testing(id)');
22+
$arr = $res->fetchArray();
23+
$arr = $res->fetchArray();
24+
$arr = $res->fetchArray();
25+
$arr = $res->fetchArray();
26+
?>
27+
--EXPECT--
28+
Testing with: 1
29+
Testing with: 2
30+
Testing with: 3

ext/sqlite3/tests/bug79293.phpt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
Bug #79293 (SQLite3Result::fetchArray() may fetch rows after last row)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('sqlite3')) die('skip sqlite3 extension not available');
6+
?>
7+
--FILE--
8+
<?php
9+
$db = new SQLite3(':memory:');
10+
$db->exec("CREATE TABLE foo (bar INT)");
11+
for ($i = 1; $i <= 3; $i++) {
12+
$db->exec("INSERT INTO foo VALUES ($i)");
13+
}
14+
15+
$res = $db->query("SELECT * FROM foo");
16+
while (($row = $res->fetchArray(SQLITE3_ASSOC))) {
17+
var_dump($row);
18+
}
19+
var_dump($res->fetchArray(SQLITE3_ASSOC));
20+
?>
21+
--EXPECT--
22+
array(1) {
23+
["bar"]=>
24+
int(1)
25+
}
26+
array(1) {
27+
["bar"]=>
28+
int(2)
29+
}
30+
array(1) {
31+
["bar"]=>
32+
int(3)
33+
}
34+
bool(false)

0 commit comments

Comments
 (0)