Problem/Motivation
At the moment all 3 by core supported driver can work with the same implementation of Drupal\Core\Database\Query\Condition.
The MSSQL database driver has to for #3113203: Kernel Test testDbLike mandates specific escape behavior. override the default implementation of the Condition class. Also there is for the SQLite driver an override necessary (see: #2031261: Make SQLite faster by combining multiple inserts and updates in a single query). The way in which the override is implemented for the SQLite driver is very different to the other query operators. We should database driver override the default implementation of the Condition class in the same way as is done for: select, delete, insert, merge, schema, truncate, update and upsert.
If there are other examples of custom implementations of the class Drupal\Core\Database\Query\Condition, please add them here.
Proposed resolution
Change the calling of the Drupal\Core\Database\Query\Condition to the same way as is done for: select, delete, insert, merge, schema, truncate, update and upsert.
User interface changes
none
API changes
Add the method Drupal\Core\Database\Connection::condition().
All code like: new Condition('AND') must change to: Database::getConnection()->condition('AND').
Data model changes
none
| Comment | File | Size | Author |
|---|---|---|---|
| #96 | 3113403-8.9.x-95.patch | 11.24 KB | alexpott |
| #96 | 3113403-9.0.x-95.patch | 11.45 KB | alexpott |
| #96 | 90-95-interdiff.txt | 891 bytes | alexpott |
Comments
Comment #2
daffie commentedComment #3
beakerboyI was able to use a custom Condition by overriding the Select constructor. My new constructor first called the parent, then replaced $this->condition with my custom condition class.
Comment #4
daffie commented@beakerboy: I think that your solution does not work with nested conditions like (from Drupal\KernelTests\Core\Database\SelectComplexTest):
Comment #5
beakerboyYou might be right. I’ll have to make a custom test case that uses a regular expression within a nested Condition.
Comment #6
beakerboyIt looks like Select, Delete, Update, and Merge need to call
$connection->condition()inside their constructors.Comment #7
beakerboyComment #8
daffie commentedHere is an example for you.
Comment #9
daffie commentedComment #10
beakerboyI already made one:
https://github.com/Beakerboy/sqlsrv/blob/Custom-condition/tests/src/Kern...
This test passes with your patch in #2, but after removing my custom constructor, all the core REGEXP tests fail, which led me to marking this issue as “needs work”.
Comment #11
beakerboyUpdated Patch. I was making this against 8.8.x, but it'll probably work as-is on 9.x
Comment #12
beakerboyIt looks like the Unit Test OrderBy::setUp() needs to be able to get a Condition stub from the mocked connection.
Comment #13
beakerboyComment #14
beakerboyUpdated Patch
Comment #15
beakerboyComment #16
daffie commentedI have tried to make an interdiff.txt file, but that was not possible because #2799911: Make the Connection parameter the first parameter in Select Query Builder. has landed. A partial interdiff.txt was the most I could get.
I added some extra code so that whole database subsystem uses the driver overridable conditions.
Also added testing for all of that.
Comment #17
daffie commentedThe issue #2031261: Make SQLite faster by combining multiple inserts and updates in a single query has been postponed on this issue. That issue has a priority of major, so upgrading this issue priority to also major.
Comment #18
effulgentsia commentedThanks for opening this and for the patch! Since this is necessary to fix #2031261: Make SQLite faster by combining multiple inserts and updates in a single query, recategorizing it as a task, and in-scope for 9.0.x (and 8.9.x, but it needs to get into 9.0.x first).
Comment #19
beakerboyHas anybody reviewed the 8.8.x patch (#14)? It would be great to have it moved to RTBC.
Comment #20
daffie commented@Beakerboy: You can review the patch. Here is an howto: https://www.drupal.org/patch/review. You can do it. I have faith in you!
Comment #21
beakerboy@daffie...That is my patch, I can't review my own patch. Feel free to move this issue forward. I have faith in you!
Comment #22
daffie commented@Beakerboy: Forgot about that. About the reviewing: the whole idea is that there are at least 2 people who think that the current patch good enough is for the core committer to let him/her review it. For me is your patch with my changes good enough to be RTBC. If you think the same, then say so. You can then set the status to RTBC or let me do it.
Comment #23
beakerboy@daffie, yes, I know about the review process...we've both been working with drupal for nearly the same length of time. I don't have a good test set-up for D9, so I've been working off D8.8. Your patch appears fine, but I have not actually tested it myself. In reviewing the code it looks like I missed the ConditionGroupFactory, so I can add that to my patch, test it, consider it the same as yours, but re-rolled to D8 and give it RTBC.
Comment #24
beakerboy@daffie would if make sense to change Query/Connection.php to
Wouldn’t this prevent each class having to implement this function.
Comment #25
daffie commented@Beakerboy: I do not think that what you are suggesting in comment #24 will work. But you could create a patch and we will see what the testbot thinks of it.
Comment #26
beakerboyPatch, #16 rolled back to 8.8 and using late static binding.
Comment #27
beakerboyTry late static binding.
Comment #28
beakerboy@daffie I reviewed your #16 patch, and I thought it looked good. I made my suggested changes and that patch has several failures. However, I retested yours and it also has many failures.
Comment #29
beakerboyMySQL 5.5 is not supported by D9, moving back to Needs Review.
Comment #30
beakerboyI made the patch wrong. I needed to run `git diff HEAD`. This is with late static binding.
Comment #31
daffie commented@Beakerboy: I think you forgot to remove one static.
Comment #32
beakerboy@daffie I didn’t forget it. Calling `new static()` is the whole point of this. By taking advantage of late static binding, the parent will always return an instance of the child class, and we do not have to implement
Condition::conditionGroupFactory()in every driver. If every child must donew self(), you can instead have the parent donew static(). As an extra safety precaution, should the Query\Condition class be marked abstract(Also Delete, Merge, Select, Transaction, Truncate, Update, and Upsert)?Comment #33
beakerboyHere's the same concept as #30 on 8.8.x-dev
Comment #34
beakerboyI tested #16 with SQL Server and it passes my custom condition tests along with the core kernel test suite. I feel #30 is an improvement though. @daffie, could you offer your opinion?
Comment #35
daffie commentedTesting to make sure that the patch combined with the patch from #2031261-91: Make SQLite faster by combining multiple inserts and updates in a single query works.
Comment #36
daffie commentedI am re-uploading the patch from comment #30. As it the patch that should be committed. @Beakerboy worked on the changes that I have made in the patch from comment #16. Therefor I feel that I can do a review and give this issue an RTBC.
The switch to using late static binding looks good to me. The combination patch with the patch from #2031261-91: Make SQLite faster by combining multiple inserts and updates in a single query shows that the solution with the late static binding works.
For me is the patch RTBC.
Comment #37
alexpottThis needs a change record.
Comment #38
beakerboyDraft change record has been created. The only people I can imagine this would impact are database driver developers. It should be invisible to everybody else.
@alexpott if the Condition class is now a required component of each database driver, should it be an abstract class? What about Delete, Select, Update, etc? Marking them abstract would make it clear to developers that it must be implemented at the driver level, but would require the unit testing suite to stub each of them.
Comment #39
beakerboyComment #40
daffie commented@Beakerboy: The rule is that an issue that was RTBC and only needs a change record, you can put it back to RTBC after you created the change record. BTW thank you for creating the change record.
Comment #41
alexpottWe also need to put this in D8.9. Here's a patch that fixes the conflicts in
core/tests/Drupal/Tests/Core/Database/OrderByTest.phpComment #42
alexpottCommitted 7a943a3 and pushed to 9.0.x. Thanks!
Committed 9397737 and pushed to 8.9.x. Thanks!
Fixed unused uses on commit.
Comment #47
alexpottHad to revert this to get a bit more discussion on BC layers.
So \Drupal\Core\Database\Connection::getDriverClass() is not helping us like it should. Unfortunately decisions were taken in #2461239: Database driver class fallback is broken and stubbed instead to require code to duplicated rather than to have it resolve to the base class. This was in an effort to keep thing simple. However that means this change will break every contrib driver because it is missing a condition class. Given that the condition class is just going to be
That feels really really wrong.
Comment #48
alexpottSo the simplest thing in order to maintain BC and comply with policy is to so something like:
That way if we can't resolve the Condition class we trigger a deprecation error (only once) and contrib db drivers get to continue. We can even add the @trigger_error in a follow-up for 9.1 so we can commit this to 8.9.x - we would need a test of the fallback though.
Comment #49
beakerboy@alexpott Most of the classes in the MySQL driver namespace are stubs. The new condition class will just be following precedent.
Comment #50
daffie commented@Beakerboy: You know that this patch is coming. It should also work for contrib driver who does not know that this is coming and do nothing. See: https://www.drupal.org/core/d8-bc-policy.
Comment #51
beakerboyIs there a way
getDriverClass()can be changed which would be backwards compatible, while also not creating a special exception for the Condition class? It would be nice to allow all drivers to drop any unextended Query classes.Comment #52
alexpott@Beakerboy I totally think we should do that I disagree with the reasoning giving in the other issue about not doing that. But that's a follow-up. If we want to get this in D9 (and D8) then the path suggested above is the quickest and scoped correctly for this issue.
Comment #53
beakerboyNew patch with changes suggested by @alexpott
Comment #55
beakerboyEdit:I have no idea why this patch is not applying. It looks right...
OrderByTest.php changed a couple hours before I uploaded, but after I tested. Need to reroll.
Comment #56
beakerboyUpdated patch against latest 9.0 branch
Comment #57
daffie commented@beakerboy: Your patch file is empty.
Comment #58
beakerboyodd...try uploading again.
Comment #59
beakerboyMy use statement was above my namespace declaration.
Edit:
If anyone knows why this is failing, post below. I can’t figure it out. As far as I can see, Condition::class can only refer to one namespace, the Query\Condition from the use statement. Any other case requires that the class actually exists, so there should never be a “Class 'Condition' not found” error.
Comment #60
alexpottWe need to put the same BC layer in \Drupal\database_statement_monitoring_test\LoggedStatementsTrait::getDriverClass(). We're going to be able to get rid of that in #3115388: PP-1: Deprecate \Drupal\Core\Database\Database::getDatabaseDriverNamespace as it is no longer needed but for now we need to duplicate :( but at least it is only test code.
Comment #61
alexpottNeed a @todo pointing to a 9.1.x followup to add an @trigger_error.
Comment #62
beakerboyupdating \Drupal\database_statement_monitoring_test\LoggedStatementsTrait::getDriverClass()
Comment #63
beakerboyAdded @todo
Comment #64
alexpottHere i'd inline the @todo... something like
Comment #65
beakerboyChanged with suggestions from @alexpott
Comment #66
daffie commented@Beakerboy: The added @todo looks good. Only one space too many between "in" and the link. An other problem with the current patch is that I am missing the added tests. Can you put them back in the patch. Thank you for working on this patch. We are almost there.
Comment #67
beakerboy@daffie, the tests are missing because this latest version no longer requires all drivers to have a stubbed Connection class. I’ve moved the tests to my driver code to ensure that it returns its custom Condition. I’m sure there’s a way to mock a custom driver with a custom Condition to prove that it is returned when appropriate, but I do not know how to do that.
Comment #68
daffie commentedYou are right @Beakerboy. All the previous added tests now fail for that reason. We can/should test that the added method exists and what is returns. Could we add the test to Drupal\KernelTests\Core\Database\ConnectionTest for that reason.
If you add that and fix the nitpick from comment #66, I will set the status back to RTBC.
Comment #69
neelam_wadhwani commentedHello @daffie
I have added the test.
Please review the patch.
Comment #70
neelam_wadhwani commentedComment #71
beakerboy@neelam_wadhwani thank you for your swift assistance. This patch still needs the fix for the whitespace issue referred to in #66. There is also 2 coding standards issues with your patch (https://www.drupal.org/pift-ci-job/1617547)
Also @daffie,
This just tests that the default Condition will be returned. Since the point of this issue is to allow a contrib driver to override the Condition, shouldn’t we test THAT. Maybe something like this:
Comment #72
neelam_wadhwani commentedHello @Beakerboy
Updated the coding standard.
Please review patch.
Comment #73
daffie commentedMy test from comment #68 test that the added method exists and that it now will return. If driver overrides the default condition implementation the test will still pass. The test from @Beakerboy tests with a mock that when there is an override for the condition then the added method will return the override. My suggestion is to add both tests.
@neelam_wadhwani: There is still a coding standard violation in your patch.
Comment #74
neelam_wadhwani commentedHello @daffie
I have fixed the coding standard issue and attach the other test of @Beakerboy.
KIndly review patch.
Comment #75
neelam_wadhwani commentedComment #77
neelam_wadhwani commentedHello @daffie
After adding that test in ConnectionTest.php, it got failed.
So i removed that test and updated the patch.
Comment #78
neelam_wadhwani commentedComment #79
beakerboy@neelam_wadhwani My test was just written off the top of my head and typed in on my phone, so I know there would be a couple problems. The error you received can be resolved with an appropriate use statement to the core Connection object. I think it also needs:
Comment #80
neelam_wadhwani commentedHello @Beakerboy
I have updated changes.
Kindly review patch.
Comment #82
beakerboyWe cannot instantiate an abstract class (Core\Connection). Mocking it instead. Should this be in Unit Tests instead of Kernel Tests? This new test passes the namespace in with the options parameter. The whole situation would be more realistic if, instead, the connection and the Condition shared the same namespace and the options array did not have a namespace set. Opinions anyone?
Comment #84
daffie commented@Beakerboy: Thank you for adding the test. Some minor stuff and we are done.
You need to use getMockForAbstractClass() instead of getMock().
You do not use the variable $contrib_namespace anywhere else, it can be removed.
This belong with the other unit tests. Drupal\Tests\Core\Database\ConditionTest is a good location.
For PHPUnit it is better to use:
$mockCondition = $this->getMockBuilder(Condition::class)Comment #85
beakerboy$contrib_namespace is assigned to $options['namespace'], so it is used in two places.
Comment #87
beakerboyThis error is saying that the assertIdentical() method is undefined. I fail to see the problem. There must be a typo somewhere that my brain just is not picking up.
Edit: Unit tests have migrated away from the legacy assertions. All of these should be changed to assertSame().
Comment #88
beakerboyUsing
assertSame().Comment #90
beakerboyUsing the correct class name.
Comment #91
daffie commentedI have cleaned up your test a bit. I did not actually test the code. As I told you on Slack, your test is good. Great work.
Comment #92
beakerboyClass alias is necessary.
Comment #93
daffie commentedThe code looks good. All changes I asked for a done.
All the testing is in order.
For me it is RTBC.
@Beakerboy: Thank you for all your hard work!
Comment #95
beakerboyComment #96
alexpottWe need a 8.9.x patch and \Drupal\Tests\Core\Database\OrderByTest no longer needs to change because we fallback now. We might need to change it when we deprecate the fallback but that's okay we handle it then.
Comment #97
alexpottCommitted and pushed f156b5c6b1 to 9.1.x and e02296ec61 to 9.0.x. Thanks!
Committed 6f339f2 and pushed to 8.9.x. Thanks!
Comment #98
daffie commentedIn #2031261: Make SQLite faster by combining multiple inserts and updates in a single query returns the testbot the following error when used with MySQL:
Comment #99
beakerboyThe commit hashes in the comment above lead to 404 pages. Is there a staging area where commits sit before being pushed to GitLab? Maybe the testbot also pulls from GitLab and they are not fully public yet.
Comment #100
alexpott@beakerboy @daffie I forgot to push so we're back to rtbc.
Comment #102
alexpottOkay I've committed this to 9.1.x. We need a release manager +1 to backport this to 8.9.x and 9.0.x
Comment #103
daffie commented@alexpott wants a release manager go for backporting this issue back to 9.0.x and 8.9.x. This issue is a prerequisite for #2031261: Make SQLite faster by combining multiple inserts and updates in a single query.
Comment #104
catchThis looks safe to backport and enables fixing a bug. Removing tag.
Comment #105
alexpottThanks @catch. Committed and pushed e02296ec61 to 9.0.x and 6f339f2 and pushed to 8.9.x. Thanks!