|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2013-03-27 09:20 UTC] phplists at stanvassilev dot com
Description:
------------
I was quite surprised to find that, but SQLite3 results cause the query to execute
a second time when you try to fetch from them (after the query() call).
This is not just a harmless performance issue. When the query is an INSERT query,
it causes duplicate rows, and creates all kinds of other messes.
IMPORTANT: If you perform the same tests with the SQLite3 driver for PDO, it
doesn't have this issue (fetching results won't cause the query to run again). The
issue is specific to the SQLite3 extension.
Test script:
---------------
EXAMPLE1:
I caught that when I run an INSERT query through a generic routine which always uses query() and then runs fetchArray() in a loop to see if something was returned. Naturally INSERT queries return nothing, but trying to fetch on an empty result set should NOT produce side effects:
$conn = new SQLite3('Log.sqlite', \SQLITE3_OPEN_READWRITE);
$res = $conn->query('INSERT INTO Table VALUES(null, 1, 2, 3'); // Inserts a row here (the null is an auto-incrementing PK).
$res->fetchArray()); // Inserts the *same* row data as above, again (with the next auto-incrementing PK number).
$res->fetchArray()); // And yet again...
EXAMPLE2:
Another way to prove that something is fishy, is by registering a PHP function for use in SQLite3. Let's say we have a table with a column "id", and we have three rows, with "id" values 1, 2, 3.
function testing($val) {
echo 'Testing with: ' . $val . '<br>';
return true;
}
$conn = new SQLite3('Log.sqlite', \SQLITE3_OPEN_READWRITE);
$conn->createFunction('testing', 'testing', 1);
$res = $conn->query('SELECT * FROM Table WHERE testing(id)'); // "Testing with: 1"
$arr = $res->fetchArray(); // "Testing with: 1" (notice the repetition of 1 with the query above, this shouldn't occur).
$arr = $res->fetchArray(); // "Testing with: 2"
$arr = $res->fetchArray(); // "Testing with: 3"
// At this point the next call to fetchArray() will return false, as it should. But what's happening internally? Something else:
$arr = $res->fetchArray(); // "Testing with: 1" again. Huh? Why is it running that again?
$arr = $res->fetchArray(); // "Testing with: 2"
$arr = $res->fetchArray(); // "Testing with: 3"
$arr = $res->fetchArray(); // "Testing with: 1"
$arr = $res->fetchArray(); // "Testing with: 2"
$arr = $res->fetchArray(); // "Testing with: 3"
// ...and so on forever.
Another used has encountered an issue like this over 6 months ago (!) which means this bug has been around for a while: http://stackoverflow.com/questions/12981332/php-sqlite3resultfetcharray-re-executes-query
Expected result:
----------------
The SQLite3 extension should execute a query once, and each row of the result set
should be executed once, like PDO's SQLite3 driver does.
Fetching should cause duplicate INSERTS and twice computed results.
Actual result:
--------------
The SQLite3 extension executes the query again when fetching from the result set.
Patchesfix64531-sqlite3-fetchArray-skipNoColumns (last revision 2020-02-01 00:13 UTC by antoni at friki dot cat)Pull Requests
Pull requests:
HistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sat Oct 25 19:00:01 2025 UTC |
I've run into it trying to do: function sq($query, $params = array()) { if ($params) { $stmt = sdb()->prepare($query); if ( ! $stmt) { die("sqlite: " . sdb()->lastErrorMsg()); } foreach ($params as $param) { $r = call_user_func_array(array($stmt, 'bindValue'), $param); if ( ! $r) { die("sqlite: " . sdb()->lastErrorMsg()); } } $res = $stmt->execute(); if ( ! $res) { die("sqlite: " . sdb()->lastErrorMsg()); } } else { $res = sdb()->query($query); if ( ! $res) { die("sqlite: " . sdb()->lastErrorMsg()); } } $r = array(); while ($row = $res->fetchArray(SQLITE3_ASSOC)) { $r[] = $row; } return $r; } Made it work by wrapping the last part in `if ($res->numColumns()) {`: ... } if ($res->numColumns()) { $r = array(); while ($row = $res->fetchArray(SQLITE3_ASSOC)) { $r[] = $row; } return $r; } }Hi, today I've faced that problem. Here my patch: --- php-7.4.2/ext/sqlite3/sqlite3.c 2020-01-21 12:35:21.000000000 +0100 +++ php-7.4.2-sqlite3fixed/ext/sqlite3/sqlite3.c 2020-01-31 22:12:56.599653191 +0100 @@ -612,8 +612,11 @@ return_code = sqlite3_step(result->stmt_obj->stmt); switch (return_code) { - case SQLITE_ROW: /* Valid Row */ case SQLITE_DONE: /* Valid but no results */ + { + result->complete = 1; + } + case SQLITE_ROW: /* Valid Row */ { php_sqlite3_free_list *free_item; free_item = emalloc(sizeof(php_sqlite3_free_list)); @@ -624,6 +627,7 @@ break; } default: + result->complete = 1; if (!EG(exception)) { php_sqlite3_error(db_obj, "Unable to execute statement: %s", sqlite3_errmsg(db_obj->db)); } @@ -713,6 +717,9 @@ return_code = sqlite3_step(stmt); switch (return_code) { + php_sqlite3_result *result_obj; + zval *object = ZEND_THIS; + result_obj = Z_SQLITE3_RESULT_P(object); case SQLITE_ROW: /* Valid Row */ { if (!entire_row) { @@ -730,6 +737,8 @@ } case SQLITE_DONE: /* Valid but no results */ { + result_obj->complete = 1; + if (!entire_row) { RETVAL_NULL(); } else { @@ -738,10 +747,12 @@ break; } default: - if (!EG(exception)) { - php_sqlite3_error(db_obj, "Unable to execute statement: %s", sqlite3_errmsg(db_obj->db)); - } - RETVAL_FALSE; + result_obj->complete = 1; + + if (!EG(exception)) { + php_sqlite3_error(db_obj, "Unable to execute statement: %s", sqlite3_errmsg(db_obj->db)); + } + RETVAL_FALSE; } sqlite3_finalize(stmt); } @@ -1844,14 +1855,16 @@ return_code = sqlite3_step(stmt_obj->stmt); + sqlite3_reset(stmt_obj->stmt); + object_init_ex(return_value, php_sqlite3_result_entry); + result = Z_SQLITE3_RESULT_P(return_value); switch (return_code) { - case SQLITE_ROW: /* Valid Row */ case SQLITE_DONE: /* Valid but no results */ + { + result->complete = 1; + } + case SQLITE_ROW: /* Valid Row */ { - sqlite3_reset(stmt_obj->stmt); - object_init_ex(return_value, php_sqlite3_result_entry); - result = Z_SQLITE3_RESULT_P(return_value); - result->is_prepared_statement = 1; result->db_obj = stmt_obj->db_obj; result->stmt_obj = stmt_obj; @@ -1861,9 +1874,11 @@ break; } case SQLITE_ERROR: + result->complete = 1; sqlite3_reset(stmt_obj->stmt); default: + result->complete = 1; if (!EG(exception)) { php_sqlite3_error(stmt_obj->db_obj, "Unable to execute statement: %s", sqlite3_errmsg(sqlite3_db_handle(stmt_obj->stmt))); } @@ -1939,6 +1954,10 @@ return; } + if (result_obj->complete) { + RETURN_FALSE; + } + RETURN_LONG(sqlite3_column_count(result_obj->stmt_obj->stmt)); } /* }}} */ @@ -1958,6 +1977,11 @@ if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &column) == FAILURE) { return; } + + if (result_obj->complete) { + RETURN_FALSE; + } + column_name = (char*) sqlite3_column_name(result_obj->stmt_obj->stmt, column); if (column_name == NULL) { @@ -2007,6 +2031,10 @@ return; } + if (result_obj->complete==1) { + return; + } + ret = sqlite3_step(result_obj->stmt_obj->stmt); switch (ret) { case SQLITE_ROW: @@ -2043,6 +2071,7 @@ break; default: + result_obj->complete = 1; php_sqlite3_error(result_obj->db_obj, "Unable to execute statement: %s", sqlite3_errmsg(sqlite3_db_handle(result_obj->stmt_obj->stmt))); } }I think that small patch should fix most problems on fetchArray() usage. It's not a breaking change as no one expects re-run insert/update/create statements using a function to acquire a row of data. IMAO --- php-7.4.2/ext/sqlite3/sqlite3.c 2020-01-21 12:35:21.000000000 +0100 +++ php-7.4.2-sqlite3fixed2/ext/sqlite3/sqlite3.c 2020-01-31 23:37:17.449123405 +0100 @@ -2007,6 +2007,10 @@ PHP_METHOD(sqlite3result, fetchArray) return; } + if (sqlite3_column_count(result_obj->stmt_obj->stmt) == 0) { + return; + } + ret = sqlite3_step(result_obj->stmt_obj->stmt); switch (ret) { case SQLITE_ROW: