Problem/Motivation
Over many years Drupal has held off checking queries for reserved words in column names, table names and aliases because it was a design decision. The idea is that we just just should not use reserved words. However, time has made it clear that this position (as stated in the long discussions in #371: resolve ANSI SQL-92/99/2003 reserved words conflict in query statements) is not really tenable. Here are the problems:
- The list of reserved words is different for different database drivers even those the support the various ANSI SQL standards. For example
OFFSET
is reserved in PostgreSQL but not in MySQL - Database drivers add new reserved keywords in new versions. MySQL has been doing this in every new version for example 5.7 and 8
Proposed resolution
- Add a new syntax square bracket syntax to the query builder to know what identifiers (column names/ aliases) are. Leverage work in #371: resolve ANSI SQL-92/99/2003 reserved words conflict in query statements suggested using
[COLUMN_NAME]
to match{TABLE_NAME}
. This supportsdb_query()
,addExpression()
andaddWhere()
- Automatically quote fields and aliases in escapeField() and escapeAlias().
- All identifiers should be quoted. This helps us ensure that the database API is properly used when query building and will make edge-cases less likely.
Using []
to identify identifiers (field names / aliases)
There is precedence - this is the quote character in MSSQL.
The one consideration is that :variable[]
is used to identify and expand array variables.
Discarded resolution(s)
We could deprecate SELECT, UPDATE, INSERT queries via \Drupal\Core\Database\Connection::query()
since the methods like \Drupal\Core\Database\Connection::select()
require fields to be specified. If we do that we have to worry about performance - see #1067802: Compare $connection->query() and $connection->select() performance. Also this does not work for columns added using ->addExpression()
or ->addWhere()
. Therefore this resolution has been discarded.
Remaining tasks
Fix the blockers:
- #2986334: Add a way to enforce an empty result set for a database condition query
- #2986539: \Drupal\user\Plugin\EntityReferenceSelection\UserSelection::entityQueryAlter() should escape the fake condition column on replacement
File follow-ups:
- add coder rule to try to check for missing
[]
- Ensure core uses new square bracket syntax
User interface changes
None
API changes
At the moment \Drupal\Core\Database\Connection::escapeField()
and \Drupal\Core\Database\Connection::escapeAlias()
in their core implementations are re-entrant. And some core code relies on this. That said the docs hint that this should not be case. In the new implementation methods also now quote identifiers BUT because the code removes the quotes before adding them they still are re-entrant. The new implementations result in strings like users_data.uid
becoming "users_data"."uid"
.
\Drupal\Core\Database\Query\ConditionInterface::condition()
's first argument $field
must be a field and cannot be (ab)used to do ->condition('1 <> 1')
. The way to do this is to use the new API added by #2986334: Add a way to enforce an empty result set for a database condition query
\Drupal\Core\Database\Connection::prepareQuery() now takes additionally takes a boolean that is derived from a query's $options['allow_square_brackets']
value that allows specific queries to use square brackets and to not them for identifier quotes. The default value for $options['allow_square_brackets']
is FALSE
.
\Drupal\Core\Database\Connection::prefixTables
now quotes tables so {node}
becomes "node"
.
Data model changes
None
Release notes snippet
All identifiers (table names, database names, field names and aliases) are quoted in database queries. Column names or aliases should be wrapped in square brackets when using Connection::query()
, db_query()
, ConditionInterface::where()
or SelectInterface::addExpression
. Custom or contrib database drivers should read https://www.drupal.org/node/2986894 to see what changes are necessary.
Comment | File | Size | Author |
---|---|---|---|
#138 | drupal-allow-dash-in-table-name-2986452.patch | 739 bytes | Alexey.Samsonov |
Comments
Comment #2
alexpottComment #3
alexpottFor me the SQLite manual has this right
https://www.sqlite.org/lang_keywords.html
Comment #4
catchThis doesn't seem like the worst suggestion given all the other options.
Not really keen on completely deprecating
\Drupal\Core\Database\Connection::query()
, the query builder already supports where/expression for gnarly syntax stuff and that can include columns just as much (say aREPLACE(foo, 'bar', 'baz')
query in MySQL).Comment #5
alexpott@catch yep totally agree with #4 -
Comment #6
alexpottComment #7
alexpottI think the one remaining design choice before it's worth starting to write patches is: do we use a dictionary of reserved words?
On the plus side that would mean that the query strings are not inundated with quotes. However I feel that the SQLite docs in #3 have it right. We quote all identifiers and then we don't have to worry about changes in the list of reserved words. We have less to maintain and writing contrib and custom database drivers becomes easier because you know that all identifiers are quoted.
Comment #8
alexpottAnother benefit of always quoting and not using a dictionary is that bugs like #2986539: \Drupal\user\Plugin\EntityReferenceSelection\UserSelection::entityQueryAlter() should escape the fake condition column on replacement will become much harder to have.
Comment #9
alexpottHere's a first cut. I don't expect it to be green. Trying on MySQL first and then once that is green I'll test queuing up runs on the other database types.
Patch includes:
#2986334: Add a way to enforce an empty result set for a database condition query
#2986539: \Drupal\user\Plugin\EntityReferenceSelection\UserSelection::entityQueryAlter() should escape the fake condition column on replacement
Comment #10
alexpottDiscussed the reserved word dictionary with @catch and @amateescu in slack. After much discussion we decided that we should
db_query()
,SelectInterface::addExpression()
andConditionInterface::where()
This resolves #7
Comment #11
alexpottComment #12
alexpottForgot one thing. We can't mix escaped field and table name in the same static now. Because they have slightly different strategies due to table name prefixing.
Comment #13
alexpottThis is the type of super tricky bug that this change will help us catch.
Comment #14
alexpottFixing escaping/quoting where the field name is empty.
Comment #18
alexpottOkay... think I've fixed MySQL and Postgres and SQLite both install. Let's see what's borked.
Comment #19
alexpottWhoops small mistake on the hacky
real_square_brackets
option.Comment #20
alexpottWe need a bot with Postgres > 9.5 - my local is but on less than that a different upsert class is used.
Also lol SQLite supports square brackets as quotes cause MSSQL so although the implementation is wrong it is not totally broken.
Comment #22
alexpottI was not testing locally what I thought I was lolz. Postgres should be greener now.
Comment #23
alexpottRe-uploading #22 to get a test run in.
Comment #24
alexpottAdding some of the API implications of this change.
I wonder if we should make
escapeField()
andescapeAlias()
re-entrant - i.e only add quotes if the first character is not a quote.Comment #25
alexpottFixing postgres and more cleanup since the postgres driver implementation is not API no point deprecating things.
Comment #26
alexpottHo hum. Testing raw SQL strings is icky.
Comment #28
alexpottSo I decided to track down why the where in PreviewTest was not being quoted. In \Drupal\views\Plugin\views\argument\NumericArgument::query() we see:
This dumping in of
$this->tableAlias.$this->realField
into a query without passing throughescapeAlias()
andescapeField()
is a problem. I think we should consider this part of the followup that is going to ensure we use the new square bracket syntax. So this will become[$this->tableAlias].[$this->realField]
and then views will quote identifiers properly. Atm if the table name had had an upper case letter in it then postgres would be broken :(Comment #29
catchQuick review. Generally this looks good and the amount of crap it removes from the postgresql driver shows what we'd be dealing with if we went the dictionary route.
Why is it using escapeAlias here when the method is escapeField()? If escapeAlias() is doing two different things (actually escaping aliases, and also escaping fields) then it should probably be renamed.
Nice.
cruft.
Comment #30
alexpottThanks @catch. Patch attached addresses #29.
Comment #31
alexpottMade a start on the change record - https://www.drupal.org/node/2986894
Help on naming these methods would be great.
Comment #32
alexpottAdding some unit test coverage (as well as moving some) and resolve some of the @todos I added.
Comment #34
alexpottFixing the tests.
Comment #35
alexpottBored of fighting with PreviewTest - opened #2987009: Remove unnecessary space in queries generated by Select::__toString() to make it a bit simpler.
Comment #36
alexpottRerolled on top of #2966523: MySQL 8 Support - and now we're able to remove the private code added by that workaround for 8.6.x.
Comment #37
alexpottComment #38
alexpottComment #39
alexpottComment #40
sjerdoSome review comments:
This should be "List of escaped field names, keyed..."
The default value (FALSE) isn't returned in the defaultOptions() method.
Since allow_delimiter_in_query also has a default value of FALSE and is returned by the method, this default value also should be add.
Documentation is incorrect: The $options array isn't merged with self::defaultOptions() in this method. I suggest adding $options += $this->defaultOptions();
Comment #41
alexpottRerolled now that 8.7.x has the blockers committed.
@sjerdo thanks for the review.
Comment #43
alexpottWhoops.
Comment #46
alexpottLogic fail.
Comment #47
alexpottUpdating issue summary to reflect recent changes.
Comment #48
borisson_Is this a duplicate of #1426084: Provide backtick escaping for MySQL in DB abstraction layer?
Comment #53
alexpottCrediting people who worked on #1426084: Provide backtick escaping for MySQL in DB abstraction layer as I've mark it as a duplicate.
Comment #54
alexpottAdding some more related issues found by @joachim.
Comment #55
kriboogh CreditAttribution: kriboogh at Calibrate commentedThanks for the crediting. I did part of the patching on the backtick escaping for MySQL layer. (#1426084)
Does the patch in this issue also take into account that the prefix replacement patterns also incorrectly replace curly brackets in data, for example if a query is run with JSON data in it's value. In the #1426084 latest patch we added more complex regex's to tackle that problem (it's commented in the code what the regex does).
Comment #56
alexpott@kriboogh whilst that sounds like a valid issue is also sounds like scope-creep and something that can be tackled in a follow-up issue. This issue is purely attempting to quote columns, tables and aliases properly so a database's particular set of reserved words do not cause Drupal problems.
Comment #57
kriboogh CreditAttribution: kriboogh at Calibrate commentedOk good to know than. I also saw this patch also doesn't allow a dash (-) in the table and column names, which is allowed in MySQL as long as your names are quoted (backticked). Would you consider that also a followup issue, or something that needs to be added in this patch?
Comment #58
alexpott@kriboogh tricky. That's on the edge for me. I'd be happy to do as follow-up. But it does suggest that we should use
`
as the quote identifier for MySQL. But also we need to account for postgres and sqlite support. So it could be good to do in follow-up so we can focus on just that one issue and add good test coverage.ie. I think we should do a separate issue to explore all the ramifications of changing this. It's annoying that the " and ` don't work the same when the ANSI_QUOTE option is on.
This suggests that we should probably re-open #2767595: Support "-" in database table names
Comment #59
alexpott@kriboogh scratch what I said in #58. Because we set the SQL_MODE to ANSI in Connection.php yes this patch does take care of - in table and database names.
With the mysql client...
Yay.
Comment #60
kriboogh CreditAttribution: kriboogh at Calibrate commentedThat's good to know, but the thing is, the escapeTable method in the base Connection.php doesn't take the dash into account when escaping the table names, so the - will be filtered out if not overridden. To allow it in the MySQL connection, the extended MySQL Connection should include the '-' then
(untested)
Comment #61
alexpott@kriboogh good point. I think then given that this issue get's us most of the way to supporting dashes in table names we should do #60 in #2767595: Support "-" in database table names so we can address all three db at the same time and ensure we're not introducing more problems by allowing them. I've changed the status of that issue from closed to postponed.
Comment #62
borisson_Just nitpicks, nothing functional. Not sure what is holding this issue back.
The now here is good for this patch (because it makes it clear that this is a change that happened here), but I don't think it should end up in core.
Something like this seems better I think?
We alway quote table names to make sure that we allow for tables that are reserved keywords.
We use another example of another database in these comments, should we align those?
will have to implement it?
Does this work for non-query statements as well? It seems like it does, so we should probably update the language here as well?
Should we figure out the postgres thing for HEAD in this issue as well? Or should we postpone this on that? Or can that be a followup?
In any case, this comment isn't really helpful.
Comment #63
alexpott1. Yep - the point here is that the we need to strip the quotes so updated the comment. The interesting thing here is the hard-coding of the identifier quote. Might need to think on this one. More work to do here.
2. The examples are based on the type of things you'd expect as values. I.e this is about table specific prefixes (database.users) vs generic whole database prefixes like (prefix or test12345678).
3. Fixed
4. Non-query statements - I'm not sure I get why you'd be passing non-query strings to this?
5. Opened #3002184: \Drupal\Core\Database\Connection::makeSequenceName() looks like it might have interesting behaviour on Postrgres if the database prefix has a dot in it and yep you are right no need to add an @todo here - that's all pretty theoretical.
Comment #64
alexpottDone some more thinking about #63.1
At the moment DbDumpCommand is hard coding the identifier quote so it can remove it from the result of
$connection->prefixTables()
. This patch also adds similar code to \Drupal\Core\Database\Driver\pgsql\Schema. \Drupal\Core\Database\Driver\pgsql\Schema is an interesting class because it already has plenty of examples of stripping identifier quotes from things - see \Drupal\Core\Database\Driver\pgsql\Schema::indexExists() for example. This file also hardcodes"
all over the place.So a couple of thoughts emerge:
I'm not sure which way to go atm.
Comment #65
neclimdulWhile I understand the intention, {} already makes it incredibly annoying to take queries in and out of Drupal to build and debug them. Adding invalid syntax to support a processed escaping of columns breaks syntax linters and development tools like PhpStorm. [] compounds that, do we _really_ _absolutely_ _positively_ have to degrade site builder and developer experience here and re-invent the wheel? Like this is really bad. :(
Comment #66
alexpott@neclimdul if you stringify the query all the escaping will already be done. The table prefixes get replaced much later in the process so it's more harmful there. But for example the views queries displayed on the views preview have plenty of quotes now but they'll still be runnable against a db if you replace the {}. Also [] is the quoting syntax in mssql. The problem is different SQL flavours have different quoting strategies so if we want to support multiple database engines then we need something. Plus [] is only really needed for db_query() which in most circumstances is not really the correct API to use. Nearly all instances in core especially where columns are involved should be replaced with db_select(). Also there are not that many run-time usages in core. So I'm not really sure that #65 is an issue.
Comment #67
kriboogh CreditAttribution: kriboogh at Calibrate commentedTested and verified patch #63 with our project and seems to work.
For our particular situation to work I also added a followup issue for #50 (dash issue) and #60 (prefix in json data) based on the latest patch (#63) of this issue and provided a patch for it at https://www.drupal.org/project/drupal/issues/3011987
Comment #68
kristiaanvandeneyndeThe new reserved keyword 'groups' in MySQL 8.0.2 completely breaks the Group module which uses said keyword as its main table name (because it never was an issue until mySQL 8.0.2). So as far as I'm concerned, this issue is quite critical :o
Comment #69
alexpott@kristiaanvandeneynde reviews welcome - and yes I agree that for a site experiencing a problem caused by not quoting identifiers this is totally a critical issue.
Comment #71
kristiaanvandeneyndeThe test fail seems unrelated to the issue at hand (order of array values).
While I can't speak for the correctness of the patch as a whole - I've not worked with the inner layers of the database API - I can say that the code additions, removals and changes in the patch seem to make sense. They are well documented, the tests seem to reflect what has changed nicely and I simply love the removal of all the reserved keywords in favor of simply specifying an identifier quote.
+1 from me as far as code quality and sanity goes. I'll leave the RTBC up to someone who knows the ins and outs of the database layer more than I do.
Comment #72
nevergone CreditAttribution: nevergone commentedComment #73
larowlanshould we remove this property and add a __get to trigger a deprecated error?
Comment #74
alexpott@larowlan initial thought was good idea as this is a base class that is built for extension. But isn't there an issue with scope as implementing __get() would make $connection->escapedNames work whereas before it wouldn't - admittedly we'd only return an empty array so there's nothing really leaking but it feels strange to do this - especially if contrib / custom is still use the property because then stuff can leak out of the class.
Comment #75
larowlanFrom slack:
Is there a chance that a sub-class could end up with unescaped stuff if they rely on the old property?
In which case, we could hard throw an exception from __get, which would also prevent leakage
Comment #76
alexpott@larowlan if they were accessing $escapedNames directly then they really are doing it wrong. Because you'd never been able to assume that something was in there. So you'd always access via the escapeTable() / escapeField() / escapeDatabase() methods - alias escaping didn't use this.
Given that you'd always have to handle the empty case I think you're likely to have code like
In your implementation. If you do that right now this patch doesn't break you and you are still quoting - note I'm not sure this has much to do with security - it is about avoid clashes with database reserved words. If we implement
__get()
and an exception I think we are being unnecessarily disruptive because we'll break working code unnecessarily.I've added a release notes section.
Comment #77
larowlanyep, fair enough
Comment #79
joachim CreditAttribution: joachim as a volunteer commentedA whole bunch of issues about escaping or quoting database identifiers have been closed as duplicates, but either they were closed incorrectly, or the patch here doesn't go far enough.
Migrations into a D8 database that has hyphens in the DB name (e.g. 'drupal-8') fails because of #2845333: can't JOIN to a foreign database whose name contains a hyphen. The patch there fixes the problem, but the patch here does not.
Comment #80
joachim CreditAttribution: joachim as a volunteer commentedOk, the patch on this issue is currently definitely NOT handling #2767595: Support "-" in database table names, because dumping the contents of $this->escapedTables in Connection::escapeTable() is showing me this:
> "drupal-8.migrate_map_client" => "drupal8.migrate_map_client"
Where's my hyphen??
Comment #81
joachim CreditAttribution: joachim as a volunteer commentedOk so the problem is partly with DB API and partly with Migrate.
Migrate's SqlBase does this:
which boils down to something like this:
Note how we are joining BACK to the Drupal 8 site's database, in a query that is being run on a different connection.
Several problems here:
1. If the local Drupal 8 site uses a database table prefix, then the query will be broken, because the {} don't get added to the 'drupal-8.migrate_map_foo' identifier. Presumably, that is because DB API doesn't touch it because it supposes it's an external database. And normally it would be right! So I think DB API needs to have a way of joining to a 2nd database by specifying its connection rather than giving its actual database name. (But that's a whole other issue.)
2. It looks like this patch is breaking Migrate in several ways.
- 2a. When migrate's map query joins to the foreign database, it needs to wrap the 'database.table' in '[]', if I am correctly reading the docs in this patch?
- 2b. With this patch applied, the migrate map query looks like this, which isn't valid SQL:
Comment #82
alexpott@joachim is there any chance you can create a test case that replicates #81.2 - because if this does break migrate map joining then we definitely are missing test coverage.
Also #79. This patch doesn't really claim to fix database names - it is mainly focused on table names and other identifiers such as indexes using reserved words. It does solve that case and has tests to prove it. It does quote database names which might solve some issues but I think it is okay to re-open that issue and add further fixes and test coverage there.
Re-rolled for 8.8.x - it would be great to get this issue in as it solves real world problems that people are having and prevents a heap of future problems as DBs add reserved words.
Comment #83
alexpottLooking into the cross database query issue... it seems that the interesting interplay is between
\Drupal\Core\Database\Connection::getFullQualifiedTableName()
and\Drupal\Core\Database\Query\Select::__toString()
It looks extremely worth making one of the related issues - perhaps #2767595: Support "-" in database table names to fully test multi database querying because it looks like this code path is extremely lightly tested. Whilst I can't see how changes made make the situation in HEAD worse there's no doubt that this can be improved and perhaps resolve issues like #2767595: Support "-" in database table names
Comment #84
alexpott@joachim fyi the code snippet also points to why hyphen dbs are struggling with migrate btw -
$table_string = $this->connection->escapeTable($table['table']);
with strip the hyphens. This is not the fault of this patch and is currently occurring in HEAD - as pointed out in #83 we need better test coverage for testing multiple databases via fully qualified identifiers over a single connection.Comment #85
kriboogh CreditAttribution: kriboogh at Calibrate commented@alexpott and @joachim if you want hyphens in a table name there is a patch for that in:
https://www.drupal.org/project/drupal/issues/3011987
Comment #86
joachim CreditAttribution: joachim as a volunteer commented> @joachim is there any chance you can create a test case that replicates #81.2 - because if this does break migrate map joining then we definitely are missing test coverage.
It looks like there is test coverage of the query that joins to a map table, for example in /core/modules/migrate/tests/src/Kernel/HighWaterTest.php. A dump of $query->__toString() in SqlBase::initializeIterator() gets me:
With the patch from #82 applied, that test fails for me with:
but then it's also failing for me locally WITHOUT the patch. Not sure why!
(Incidentally, and off-topic, the tests for migrate look like they could do with some work, as MigrateTestBase for instance has a TODO referencing an issue now closed, and many references to simpletest despite being a phpunit test class.)
Comment #87
andypostqueued for all databases
reserved words are removed, so it makes useless #3020839: Drupal\Core\Database\Driver\mysql\Connection::quoteIdentifier in_array performance improvements
Comment #88
andypostHere's reroll
Comment #90
andypostFix deprecated "getMock" to pass tests
Comment #91
andypostFix CS
Comment #92
andypostFix CS for messages, the second one still needs proper wording
Comment #94
idebr CreditAttribution: idebr at iO commentedPatch no longer applies against 8.9.x
Comment #95
kostyashupenkoComment #97
alexpottFixing the test. It shows we have some work to do on ensure that joins are properly quoted.
Comment #98
daffie CreditAttribution: daffie commented@alexpott: I think that your 95-97-interdiff.txt is the same as your patch file.
Comment #99
alexpottWhoops here's the right interdiff
Comment #100
alexpottThis addresses the entity query issue with joins not being quoted. I've also looked at all the other
addJoin()
in core and none of the others are dynamic.Comment #102
daffie CreditAttribution: daffie commentedThe views module uses a lot of $query->where(), expressions, and joins with sql strings as input. Only in the patch there is nothing about the views module? If you create a view with preserved word as a table name or column the query will not quote the table names and column names.
Comment #103
alexpott@daffie - sure and that's the same as now. We can handle that in follow-ups. This patch is not making the situation worse - and it gives us a path to make it better.
Comment #104
andypostMaybe
query()
method (or kind of it) of database layer could use assert to test it properly for the most of queries?Comment #105
alexpottHmmm.... so if we change the queries then any alters or funky stuff like \Drupal\workspaces\EntityQuery\Tables might break... but this stuff is very very fragile :(
Comment #106
daffie CreditAttribution: daffie commentedMy review of the change record:
My review for the patch:
There is no testing for this new method.
What is exactly wrong with 'prefix.'? Is it the name prefix or is it the dot or both?
Why do we not just create the method and let contrib database driver override this method when they need to? Now the impression is that the default value is an empty string.
I would like to see a lot more testing for this option being TRUE. At least database tests and tests for situations where one might use this option being TRUE.
Can we add a comment what this code actually does.
Can we add testing for when the $quote_identifiers = FALSE.
Can we do in 2 lines of code to increase readability.
Now we are not only sanitizing the database name, but also adding quotes to it. This is a public method. This is a BC break!
Now we are not only sanitizing the field name, but also adding quotes to it. This is a public method. This is a BC break!
Now we are not only sanitizing the alias name, but also adding quotes to it. This is a public method. This is a BC break!
Why this change. Is it because method Connection::prefixTables() has changed. If so, then that would be a BC break!
Why this change? And why in this patch?
Can we change this from a single line to multi line to improve readability.
This removes the testing with the special column names "offset" and "function". Please do not change this test, just add another test.
Can we change this from a single line to multi line to improve readability.
Why not use a real connection?
Nit: Can we put the provider methods next to their test methods. Improves readability.
$saved_value = $this->connection->query('SELECT [update] FROM {select} WHERE id = :id', [':id' => 2])->fetchField();
Solutions like the above piece of code, with sometimes the square brackets and sometimes the curly brackets, looks like to me as a ugly drupalism.
Other developers are not going to like this.
Comment #107
daffie CreditAttribution: daffie commentedWhat happens to the current code that has not been updated to use the square brackets? Can we add testing for that so we a sure that contrib/custom code does not break!
In the patch are a lot of little changes that can break existing contrib/custom code. I also think they will be very nasty little bugs to debug.
I know that the alternatives like do nothing or deprecate all methods that take raw sql strings as a parameter are not very attractive.
I am not sure what the best solution is.
Comment #108
daffie CreditAttribution: daffie commentedThe method
SelectInterface::having()
is not mentioned in the CR and properly some work needs to be done on the patch.Comment #109
alexpott@daffie thanks for the review.
Re #106
"other_db"."table"
and not"other_db.table"
This is reserved in all SQL dialects.
Re #107 - if you don't use square brackets nothing changes and nothing breaks unless you are already broken on an alternate db driver you are not testing against.
Comment #110
andypostComment #111
catchWhat would happen if workspaces didn't make this change? Query failure? Error?
Trying to figure out what the impact on contrib would be - not that many contrib modules do anything as evil as workspaces has to.
Comment #112
alexpottRe #111 We need to remove the square brackets from the identifiers so that:
$base_table
is found in the list of base tables ie.if (isset($this->baseTablesEntityType[$base_table])) {
$id_field
correctly matchesif ($id_field === $revision_key || $id_field === 'revision_id') {
$base_table
is found in the list of workspace tables ie.$workspace_association_table = $this->contentWorkspaceTables[$base_table];
Note that \Drupal\workspaces\EntityQuery\Tables::addJoin() is working with raw sql so it's always a bit fraught.
Comment #113
alexpottPatches attached:
Still to address from #106 - point 13. and #108.
Comment #114
alexpottThe CR now mentions the having method on the select interface so #108 is addressed.
Comment #115
alexpottMore
allow_square_brackets
tests. Let's work on 9.0.x for now as whatever this change has to go there first.Comment #116
daffie CreditAttribution: daffie commentedThank you @alexpott on working on this issue. The patch looks better. I did some more reviewing:
This is going into 9.0 first, it therefor cannot be removed in 9.0.
Why removing only the double qouotes and not others like backticks?
And what is then the correct way of quoting those? I know what it is, but can we add an example to the comments.
In Drupal 10.0.0 instead of 9.0.0.
Escaped tables, fields and aliases are stored in an array for fast retrieval. Should we not do the same for database names?
Why are with escaped fields storing the value with the identifier quotes and aliases without? Please be consistent.
Can we use the method
identifierQuote()
instead of a hard coded double quote.Why is this change added, it looks like it does not belong in this patch.
Can we add a provider method and add different kinds of allowed table names. Mixing it up shall we say. Should we also do the same for other schema stuff like fields and indexes. Or should we create a followup for that.
If we are touching this test then can we change it to
StubPDP::class
Can we testing with backticks.
I am missing testing for the method
escapeDatabase()
.Comment #117
daffie CreditAttribution: daffie commented@alexpott said in comment #109.12 the following:
Ok. Lets document them like that.
Comment #118
alexpottRe #117 I think we can add @internal to new methods here - but documenting the rest of the DB API with @internal should be a followup created #3118629: Add @internal to public database API methods where appropriate
@daffie thanks for review in #116.
Okay now I'm going to work on #116.7 and the fix suggested above.
Comment #119
daffie CreditAttribution: daffie commented@alexpott: The changes in #118 look good to me.
For 118.9: Mixing it up in the sense of testing with table names that have both lower and upper case characters. We have only testing for lower case table names. Can we add testing for uppercase table names and for table names with both lower and upper case table names.
Also still open #106.13 and #108.
I shall keep reviewing this patch because we need it for #2746541: Migrate D6 and D7 node revision translations to D8.
Comment #120
alexpott#108 has been addressed - the change record now mentions ::having().
Re mixing the table name case I don't think there is any value in that. We're not doing different code paths depending on the case. The point of the test is to prove that any upper case characters in a table name are respected and it does that.
So I've looked into making \Drupal\Core\Database\Connection::identifierQuote() public or adding a constant to the Schema or Connection classes. I've come to the conclusion that both would be wrong moves because:
$saveIdentifier = '"drupal_' . $this->hashBase64($identifierName) . '_' . $tag . '"';
- and I think that's okay. I don't want to see all the instances of this become$saveIdentifier = $this->connnection->identifierQuote() . 'drupal_' . $this->hashBase64($identifierName) . '_' . $tag . $this->connnection->identifierQuote();
or$saveIdentifier = self::identifierQuote . 'drupal_' . $this->hashBase64($identifierName) . '_' . $tag . self::identifierQuote;
.I think the patch makes the right balance between exposing the complexity and doing the simplest thing in the internals of a db driver.
That said I think in code outside the DB driver we need to document clearly what we're copying so I've added more docs here.
Also this isn't necessary for #2746541: Migrate D6 and D7 node revision translations to D8 - it would mean we could remove a
strtolower()
is test code but /shrug. This is important because it helps make our DB API more robust and as when adopt newer versions of databases they will add and remove reserved words so we need to quote identifiers. What we're adding is inline with other projects see Propel and Doctrine (that said Doctrine is using keyword lists and only quotes when identifiers are in the list).Comment #121
alexpottHere are some patches to wor out #116.8 and #106.13
Patch to review is still #120 but doing this here so everyone can see.
Comment #122
alexpott#106.13 was introduced in #18 and I've run all the tests that failed in #14 and they pass without it and they weren't any update tests - so it looks okay.
#116.8 was also introduced in #18. However Drupal\KernelTests\Core\Entity\EntityQueryTest fails.
Looking at the fail in EntityQuery test it's quite a bit of fun. In HEAD (i.e. not this patch) the sql generated is:
The
(0 = '0')
is nonsense. It is due to->condition("id.%delta", 0, '=')
note that later in this test we do->condition("id.%delta", 1, '=')
which generates the following SQL in HEAD(0 = '1')
nice :)With the patch these queries become:
and
respectively.
Specifically this appears to be about testing what happens when \Drupal\Core\Entity\Query\Sql\Tables::addField hits line 230 and does
return 0
which is funky because the interface says:So whilst this is a mess caused by #2384459: Add entity query condition for delta in EFQ this patch is preserving the current behaviour and is doing the right thing.
Comment #124
alexpottFWIW it's not really a mess. It's just very odd stuff to code around. It's all about people using %delta on single value field in the entity query system. What's happening here is that we should only return values where delta is 0. And we do. So all good. And the using of alwaysFalse() makes sense for values other than 0 for a single value field.
Let's add some comments.
Note this code is completely analogous to code already in HEAD in \Drupal\Core\Entity\Query\Sql\Tables::addField()
Note we can't solve this in addField() because we don't have the condition value there.
Comment #125
daffie CreditAttribution: daffie commentedAll my points are addressed. Thank you @alexpott for taking the time to explain everything to me.
The patch looks good.
I am still a bit worried about sites out there that are programmed by people that are still learning Drupal/PHP shall we say. To me the need to fix the problems that are stated in the IS are more important then poorly programmed that might/will fail.
The change record is also in order.
For me the patch is RTBC.
Thanks everybody for working on this issue!
Comment #126
catchThe original issue where this was brought up, is nearly old enough to drink and vote in the UK. https://www.drupal.org/project/drupal/issues/371
As discussed above, there is a risk of disruption to contributed modules with this change, however as core shows, it should only be contrib modules doing complex SQL string manipulation (workspaces is the only module doing this in core, even views isn't touched here), or ones not using the database API properly at all.
Since both of those cases are impossible for us to detect, that makes this a major-only change - although it's technically allowable in the same sense that we might change queries that other modules are trying to alter in minor releases.
s/tabled/table/ - fixed on commit.
Committed 85202b4 and pushed to 9.0.x. Thanks!
Comment #127
daffie CreditAttribution: daffie commented#371: resolve ANSI SQL-92/99/2003 reserved words conflict in query statements is from 2002. Almost the same as 2020. Just 2 numbers reversed. :)
Just be patient. Your issue will be fixed in the end.
Comment #129
andypostIs it going to be backported to 8.9?
CR needs update accordingly
Comment #130
catchI don't think we should backport this to 8.9, due to the potential disruption to query rewriting.
Comment #131
mondrakethis has wrongly assumed that
"
would always be the character for quoting identifiers, which is the case in core, but not necessarily in contrib. Will file a followup.Comment #132
daffie CreditAttribution: daffie commented@mondrake: There are more places in the patch where the double quote is hardcoded.
Comment #133
mondrakeFiled #3119910: Change hardcoded quote identifier in EntityQueryTest to square brackets.
Comment #135
jibranEver since this issue is committed. DER 2.x triggers are broken on D9 see #3129590: Make DER green for D9 branch. Can I please get a patch review over there?
Comment #136
daffie CreditAttribution: daffie commentedCreated #3130579: Make Drupal\Core\Database\Schema work with reserved keywords for naming to fix working with reserved keywords and the class Drupal\Core\Database\Schema.
Comment #137
TR CreditAttribution: TR commentedComment #138
Alexey.Samsonov CreditAttribution: Alexey.Samsonov as a volunteer and at DrupalJedi commentedGot issue with dashes in table name similar this one: https://www.drupal.org/project/drupal/issues/3011987 but for Drupal 9.0.1 in migrate process. No one patch wont applied so I created my own.
Comment #139
effulgentsia CreditAttribution: effulgentsia at Acquia commentedFolks here might be interested in #3218978: MySQL driver allows settings.php to remove ANSI_QUOTES from sql_mode, but doesn't work when it is. I don't know to what extent that issue was affected by this one. It might have already existed, and either made more exposed or not by this one.
Comment #140
Wim LeersCreated #3227361: Fix SqlBase::initializeIterator()'s cross-database joining when using particular DB/table names for #79 through #86.
Comment #141
Chi CreditAttribution: Chi commentedIt there a way to escape brackets in SQL query? Seems like this change imposed severe restrictions on using JSON and array data types in Drupal.