-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bug: Database\BaseUtils::optimizeTable return values and improper use of getResultArray #4059
Comments
Thank you for looking into this. I guess we should check if other drivers behave the same way and fix it if necessary. |
I believe there may be a problem in BaseConnection::query() at line 662 which is not evident if you are CI_ENVIRONMENT=development or otherwise have DBDebug turned on. I realize this function is absolutely vital and any adjustments could have far-ranging effects, but it will never return EDIT: this problem is not apparent if you have DBDebug turned on. The javadoc comments for I concocted this test in a controller and ran it with CI_ENVIRONMENT=production: function test() {
$db = \Config\Database::connect("tests");
echo get_class($db) . "\n";
$util = (new Database())->loadUtils($db);
$d = $util->optimizeTable('db_job');
var_dump((bool) $d);
} NOTE that
If you then modify the test to try and optimize some table that does not exist: function test() {
$db = \Config\Database::connect("tests");
echo get_class($db) . "\n";
$util = (new Database())->loadUtils($db);
$d = $util->optimizeTable('table_does_not_exist');
var_dump((bool) $d);
} then you get these results:
Basically there is no distinction between trying to optimize a table that does exist versus one that does not. I think what BaseUtil::optimizeTable is truly testing for is whether the query returns an iterable record set or not. I don't think this function should be calling getResultArray on the query result. As a solution, I propose that the BaseUtil::optimizeTable function be modified to have a boolean return type and that it return TRUE when the respective table optimization queries run successfully and FALSE otherwise. We should clarify this in the javadoc comments also because these will guide others in its usage. To accomplish this, we will need to create db-specific code in the various db-specific Utils classes because mysqli_query still returns an iterable mysqli_result object, rather than a boolean value as the PHP docs specify. Both sqlite3_query and sqlsrv_query return FALSE. Note the var_dump results of
As you can see in the case of MySQLi, we must parse the iterable result set to find out if the result succeded or not. In the case of SQLite3 and SQLSRV, it should be sufficient ot check the result for false. In the case of MySQLi, the result returned will depend on success or failure. Calling getResultArray on the iterable mysqli_result yields the following
|
I guess the MySQLi driver was implemented/ported as first, so that's why we have it implemented this way. I will try to look into PostgreSQL. The only thing I'm concerned about is introducing a BC change when we decide to return a bool value every time. But from the other side... it would be actually a bugfix for other drivers than MySQLi. I think it would be enough to add a changelog note about such a change. |
Please note that it returns I am still concerned about the behavior of BaseConnection::query() returning a Result object when the query fails and simpleQuery returns
I don't know what a "BC change" is? Could you clarify?
I would imagine that the ChangeLog has a special process for any changes? It seems like an important document. Should one try to change this document in any pull request? Or should one request changes be made by one of you VIPs? |
Yes, please. We will look into that later on - one issue at a time.
https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/pull_request.md#breaking-changes From what I remember changelog you're referring to is generated automatically before the release and contains all changes being made. The changelog we cherish more is located in the user guide: https://github.com/codeigniter4/CodeIgniter4/blob/develop/user_guide_src/source/changelogs/v4.0.5.rst We list there all the significant changes that have been made that every developer should know about. Usually, if there is such a need, we ask the PR creator to add changes to his PR before merging. |
If anyone knows of other situations where a 'write' query returns a mysqli_result instead of a boolean. It would probably be helpful to know what those are. It has been suggested on the phpdoc mailing list that this is a PHP/MySQLi bug rather than a documentation problem. Note that OPTIMIZE TABLE (and indexing in general) is beyond the scope of standard sql so an ad-hoc solution may be necessary. |
This feature is not documented to begin with. Why? I sent PR #8277 |
Describe the bug
It is not entirely clear what the return value of BaseUtils::optimizeTable is supposed to be, but from its usage in tests/system/Database/Live/DBUtilsTest, it appears to be cast as a boolean value. This function has a few problems.
The first is that it checks the
$query
value returned by$this->db->query()
againstfalse
to determine success. Unless I'm missing something, that$query
value will never be false because the function that handles the query,BaseConnection::query()
will still instantiate and return someDatabase\ResultInterface
with resultID set to thefalse
value returned by the db-specific query function. I wonder if we should ever be instantiating a Result object with resultID offalse
?The second problem is that this optimizeTable function immediately calls getResultArray on the ResultInterface object returned, despite the fact that the query being run isn't really the sort of query that should return a result set. While it is true that a MySQLi
OPTIMIZE TABLE
query does return a fetchable result array on a successful operation, the SQLSRVALTER INDEX...
query that gets run by thisoptimizeTable
function is clearly marked among the isWriteType queries, which means the requested SQL server statement returned will not have a scrollable cursor, and any attempt to iterate it is probably ill-advised. Attempting to call sqlsrv_num_rows on such a resultID will return false, signifying an error condition.The third problem is that
optimizeTable
returnscurrent($query)
, where $query is the (in most cases empty) array returned fromgetResultArray()
. Note thatcurrent([])
isfalse
, which is probably not the desired return value for theoptimizeTable
function. If you runoptimizeTable
using SQLSRV in production mode, the return value ofgetResultArray()
is in fact[]
so that return value ofoptimizeTable
isfalse
. I'm guessing this is not the desired outcome.I think we should modify the handling of the optimizeTable queries to delegate more responsibility to the various db-specific Util classes rather than trying to have so much code in common. The various databases provide pretty different responses and it seems wiser to me to handle the db-specific code in the relevant class. I also think we should have optimizeTable specifically return a boolean value of true or false and nothing else and clarify the javadoc comments.
Finally, I'm not certain but it looked like the original developer was expecting
Database\BaseConnection::query()
to return false if the query returns false. It does not. Instead, it instantiates a Result object in line 662https://github.com/codeigniter4/CodeIgniter4/blob/develop/system/Database/BaseConnection.php#L662. IMHO, this function returningBaseResult|Query|false
is unnecessarily complicated.CodeIgniter 4 version
This issue is present in the CI4 develop branch in the very latest code as of this writing.
Affected module(s)
system\Database\BaseUtils
system\Database*\Utils
system\Database\BaseConnection
tests\system\Database\Live\DbUtilsTest
Expected behavior, and steps to reproduce if appropriate
I think the function
Database\BaseUtil::optimizeTable
should returntrue
if the table optimization succeeded andfalse
otherwise.To reproduce the problem, just download the latest development branch and edit your .env file to specify:
Be sure to set the appropriate credentials to connect to your SQL Server database.
Put this code in a controller and run it:
Despite the successful optimization query being run, the output should be:
You'll also get
false
if you run it with SQLite3. I've not tested PostgresContext
The text was updated successfully, but these errors were encountered: