Skip to content

Allow calling getQuery() multiple times, and other improvements #5127

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

Merged
merged 3 commits into from
Feb 16, 2022

Conversation

vlakoff
Copy link
Contributor

@vlakoff vlakoff commented Sep 25, 2021

Main fix:

  • getQuery() can now be called multiple times without trouble, compileBinds() will be called only on original SQL string. Previously, calling getQuery() additional times was executing compileBinds() again and again, and on the already compiled SQL string…

Additional improvements and notes:

  • [Performances] If getQuery() is called multiple times, compileBinds() is executed only the first time.
  • Calling setQuery() or setBinds() discard finalQueryString, so they can be called multiple times with new data, and the SQL will be generated with this new data.
  • swapPrefix() will always be executed on the original SQL string (with the bind placeholders), which is a bit safer than on the final SQL string (as the search/replace just operates on the whole SQL string).
  • [BC] As previously, it is possible to call swapPrefix() multiple times, to execute multiple prefix swaps.
  • swapPrefix() can be called before or after setBinds() (but not before setQuery(), as it operates on originalQueryString).
  • debugToolbarDisplay() would fail if called before finalQueryString is assigned, as compileBinds() was relying on getQuery() to assign this property beforehand. (refs [Query] Do not recompile binds if already compiled #4534)

Checklist:

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

@kenjis kenjis added database Issues or pull requests that affect the database layer tests needed Pull requests that need tests labels Sep 26, 2021
@vlakoff
Copy link
Contributor Author

vlakoff commented Oct 1, 2021

I added a handful of tests, as a first draft. Suggestions are welcome.

Maybe it could be polished. Maybe some tests are redundant, maybe some cases are still not covered (combinations)…

Note I couldn't (yet?) write a test where simple binds markers could be erroneously replaced twice, as the method Query::matchSimpleBinds() ensures avoiding bind markers in strings.

@vlakoff
Copy link
Contributor Author

vlakoff commented Oct 2, 2021

To summarize the approach of this PR:

I'm adding an optional, intermediary state "swappedQueryString". Starting from the original query string, two changes can be applied to this original string: swapping the db prefix, and compiling the binds. We have to always keep record of the original string, and we also need a state to know if the binds have been compiled (here "finalQueryString" fills this purpose, by being discarded when compiling has to be done again). We can't apply the swaps to "finalQueryString" as the property would no longer let us know when bind compiling is needed. And of course we should not apply the swaps to "originalQueryString", as its purpose is to keep the user-supplied string untouched. Thus, we need to record an additional state, which is "swappedQueryString" here.

An alternative would have been to store an array of prefix swaps to make (a list of "from → to" pairs), and apply them to the original string, before the binds, when compiling. But nevertheless this would make us record an additional state as well, and the "swappedQueryString" approach is simpler.

}

$sql = $this->finalQueryString;
$sql = $this->getQuery();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase and remove the following line:


This not needed anymore with this change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebased :)

@vlakoff vlakoff marked this pull request as draft October 27, 2021 11:02
@vlakoff
Copy link
Contributor Author

vlakoff commented Oct 27, 2021

On a related note, here is the behavior when setQuery() is called again:

  • In all cases, the original query string is replaced, and the prefix swaps are discarded.
  • If $binds is an array, it replaces the previous binds.
  • If $binds is an empty array, it replaces the previous binds as well, which means the previous binds are deleted.
  • If $binds is not provided or null, the previous binds are kept.

That handling of $binds is exactly the same as with the current code.

So, in the following example, we still have the "42" bind after the second line:

$query->setQuery('SELECT * FROM users WHERE id = ?', [42]);
$query->setQuery('SELECT * FROM users WHERE name = ?');

I don't know what would be the best to do when setQuery() is called again, with only $sql parameter. Should it just update the original query string and don't touch the binds, or should it also reset the binds? Anyway, if ever an change is wanted about this, it should be the subject of another PR.

@kenjis kenjis added the enhancement PRs that improve existing functionalities label Nov 9, 2021
@vlakoff
Copy link
Contributor Author

vlakoff commented Nov 16, 2021

The code itself should be ready. I'm just a bit unsure that the tests are perfect (for instance: remaining uncovered edge cases, and where I split hairs: redundant tests!). I should have a fresh look at these, and I'm open to reviews.

@paulbalandan
Copy link
Member

This has been at draft status for couple of months now. Where are we on this? Or should this be closed?

@vlakoff
Copy link
Contributor Author

vlakoff commented Jan 21, 2022

getQuery(), as its name implies, is expected to be a getter, but currently, executing it several times triggers various errors.

I think it's a serious issue, therefore even if this PR is stale it shouldn't be closed.

Even if I noticed and fixed an error afterwards ("Fix swapPrefix() not effective when called after a first call to getQuery()"), the code should be quite stable.

Some alternative approaches I thought of:

  • instead of the intermediate, optional string swappedQueryString, use an array to store an ordered list of "from/to" prefix replacements.
  • discard the finalQueryString "cache" and just compile every time getQuery() is called.

Feel free to review the code (in particular to ensure the tests are clean!) or even to push changes.

@kenjis kenjis removed the tests needed Pull requests that need tests label Jan 21, 2022
@kenjis
Copy link
Member

kenjis commented Jan 21, 2022

@vlakoff Do you mean this PR is completed and ready for review?
Or do you still have something to do?

@vlakoff vlakoff marked this pull request as ready for review January 21, 2022 03:52
@vlakoff
Copy link
Contributor Author

vlakoff commented Jan 21, 2022

I have squashed the commits.
Information provided in this PR should be quite exhaustive (and exhausting).
Test coverage seems to be pretty complete, all the cases I'm thinking of are covered.

Therefore, marking the PR for review.

@@ -448,7 +605,6 @@ public function testHighlightQueryKeywords($expected, $sql)
{
$query = new Query($this->db);
$query->setQuery($sql);
$query->getQuery();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This had been noticed previously, see above.

It's because getQuery() can now be called multiple times without error, I could remove the ugly hack here. That hack was in place so that getQuery() isn't called an additional time.

But… it required that getQuery() had been be called beforehand, so that finalQueryString is set, as compileBinds() needed it to be set beforehand… hence the "artificial" getQuery() here, just so that finalQueryString is set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see.


$query->setQuery($origSQL);
$query->swapPrefix($origPrefix, $newPrefix1);
$query->swapPrefix($newPrefix1, $newPrefix2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is no use case that calls swapPrefix() more than once.

Copy link
Contributor Author

@vlakoff vlakoff Jan 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to swap prefix several times with the current code, so I made sure it is still possible with my new code, just in order to not introduce any regression.

Still, it should be worth covering this with a test, to ensure the behaviour remains consistent.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay.

@kenjis
Copy link
Member

kenjis commented Jan 24, 2022

I think the user guide should be updated. Just remove the "final" ?

swapPrefix()
Replaces one table prefix with another value in the final SQL. The first parameter is the original prefix that you want replaced, and the second parameter is the value you want it replaced with:
https://codeigniter4.github.io/CodeIgniter4/database/queries.html?highlight=swapprefix#working-with-query-objects

@kenjis
Copy link
Member

kenjis commented Jan 24, 2022

@kenjis
Copy link
Member

kenjis commented Jan 24, 2022

[BC] As previously, it is possible to call swapPrefix() multiple times, to execute multiple prefix swaps.

Why do you need to make it possible?
I think there is no use case.

@kenjis
Copy link
Member

kenjis commented Jan 24, 2022

The branch coverage is 84.75%, good.

Screenshot 2022-01-24 at 13-51-49 Code Coverage for Users kenji work codeigniter CodeIgniter4 system Database Query php

@cijagani
Copy link
Contributor

@kenjis how to do this setup? Can you please guide me to do this? Which editor you are using?

@kenjis
Copy link
Member

kenjis commented Jan 24, 2022

@cijagani The coverage report? It is a HTML file generated by PHPUnit.

$ php -d memory_limit=-1 vendor/bin/phpunit --path-coverage tests/system/Database/BaseQueryTest.php --coverage-html build/coverage

@cijagani
Copy link
Contributor

@kenjis oh wow. thank you.

@vlakoff
Copy link
Contributor Author

vlakoff commented Jan 25, 2022

It has been a bit of a hassle to configure, but these commits are now signed.

@kenjis
Copy link
Member

kenjis commented Jan 31, 2022

My final comment: #5127 (comment)

Although the prefix swap propagates of course up to the final SQL,
the replacements actually occur in the original SQL (i.e. before the binds are applied).
@vlakoff
Copy link
Contributor Author

vlakoff commented Jan 31, 2022

I think the user guide should be updated. Just remove the "final" ?

Done: 0839468

Copy link
Member

@kenjis kenjis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!
LGTM

@kenjis kenjis merged commit c5b5369 into codeigniter4:develop Feb 16, 2022
@kenjis
Copy link
Member

kenjis commented Feb 16, 2022

@vlakoff @samsonasik Thank you!

@vlakoff vlakoff deleted the db-query branch February 21, 2022 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
database Issues or pull requests that affect the database layer enhancement PRs that improve existing functionalities
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants