Closed (fixed)
Project:
Drupal core
Version:
8.7.x-dev
Component:
postgresql db driver
Priority:
Critical
Category:
Bug report
Assigned:
Unassigned
Reporter:
Created:
16 Jan 2019 at 14:36 UTC
Updated:
13 Mar 2019 at 18:04 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
amateescu commentedThis should do it.
Opened as a major bug because it blocks #2984782: Add an API for converting entity type schemas to (non-)revisionable/(non-)translatable, with or without data, which is critical.
Comment #3
plachAnything blocking a critical is critical in my book :)
Comment #4
jibranDo we need a change notice here?
Comment #5
amateescu commentedI don't think so, it's just a bug fix not a behavior change or addition.
Comment #6
jibranThanks, for clearing that up. The patch is green and #2984782-56: Add an API for converting entity type schemas to (non-)revisionable/(non-)translatable, with or without data is also green. The changes look good and are well documented so setting it to RTBC.
Comment #7
plachWow, impressive detective work :)
I have a few doubts, but I'm no PgSql expert so they might be moot. Anyway, better safe than sorry:
Does this mean that
$this->prefixNonTable($table, $field, 'seq');generates a sequence name that matches PostgreSQL's default behavior, unless its length is greater than 63, in which case the actual name is unpredictable? If this is not the case, then I don't understand how this code was working before the change :)Assuming a
pg_prefix,$this->prefixNonTable('new_name', 'field', 'seq')returnspg_new_name_field_seq, however the new code would returnpg_new_name__field__seq. To minimize the risk of introducing a BC break, I think we should change this line to:Since we are at this, what about using 64 for MySql? That way we actually test limits for all db types we support. We could introduce an helper method to avoid repeating this logic all over the place.
Comment #8
xjmComment #9
amateescu commented@plach, thanks, it quite fun to figure out all this :)
Re #7:
Yup, that's exactly what it means.
pg_get_serial_sequence(), which will be correct regardless whether the sequence is using the default name generated by PostgreSQL, a name generated before this patch (withprefixNonTable()) or a name generated after this patch (withensureIdentifiersLength())Comment #10
plachI agree that the current code does not break renaming tables for existing sites and is BC with the previous names, but what if some custom code is querying those values for any reason? I know it's far fetched, but given that the solution for that is just tweaking one line, I think it would be worth it, unless the tweak I suggested has some drawbacks I'm missing.
Comment #11
amateescu commentedUnfortunately, your suggestion doesn't work because
\Drupal\Core\Database\Driver\pgsql\Schema::ensureIdentifiersLength()calls$this->getPrefixInfo()on the first argument it receives, which means it has to be a table name otherwise the call togetPrefixInfo()will not return the expected result..Comment #12
plachMmmh, I tested it both manually and automatically and it appeared to work. However, upon a closer look, I guess it might fail if a per-table prefix has been set, so scratch that :)
Comment #13
mradcliffeI added the additional test runs a few days ago to compare against any performance regressions on the test bots, but I never got back to looking at the results. These look fine.
The internals of pg_get_serial_sequence look good and I don't see anything costly like getting from information_schema, which is slow.
For that aspect, +1 to the patch.
In the past, not related to Drupal, I've used currval, nextval and setval functions, which allow me to specify the sequence name. I think in a custom application that's not a CMS that would be fine, but I think that if we're working with a dynamic system, then using pg_get_serial_sequence inside one of those functions would be the best.
The Database API handbook doesn't reference anything here as a guide for developers other than ("You should almost never be making database calls directly unless you are developing core APIs."). However since our DBAL isn't fully-featured, if someone were to be concatenating values to query a sequence dynamically for any reason, then they would be caught off-guard and their code would break.
Maybe a change record that demonstrates usage change would help?
If you're doing something like this,
Then do something like this instead
That might not be accurate usage of table prefixes, and I'm having a hard time coming up with a good Drupal example. The above doesn't really make sense in actual usage because I'd just get nid from the new Node entity? I think I'd need to be doing something without entities, directly querying database on a Drupal web application that was querying and inserting data into a shared database?
Comment #14
amateescu commented@mradcliffe, thanks for reviewing! I think a CR is a great idea for this issue. Here it is: https://www.drupal.org/node/3027618
Comment #15
mradcliffeI was reviewing the CR and how table prefixes work when I came across
makeSequenceName method in Connection. This is used by pgsql driver only (in core, but maybe a contrib driver also uses it?).
I think that may need to change as well..?
I'm setting to Needs review to answer that.
Comment #16
mradcliffeWow, that method is fairly old. nextId() is now only called by user module in core from what I can tell. There's a mention in the entity storage API. The change to nextId() would then be an actual example for the change record.
We might remove the makeSequenceName usage in nextId() and replace with getting via pg_get_serial_sequence(), and then should we deprecate makeSequenceName in favor of Schema::prefixNonTable() (or the aforementioned change record) since it would then be unused?
If makeSequenceName is in the API, then are there more BC concerns?
More questions, sorry. :|
Edit: grammar
Comment #17
amateescu commentedVery good questions though :)
I think the crux of the problem boils down to automatic vs. manually generated sequences. I did a bit of research and at least SQL Server and Oracle also have the concept of sequences, but the difference with Postgres is that they don't create them automatically on table creation.
With that in mind, I think
makeSequenceName()does its job perfectly well, because it is about manual generation of a sequence name. And its usage in\Drupal\Core\Database\Driver\pgsql\Connection::nextId()is legitimate because we know that the resulting name will not exceed 63 chars. If anything, I thinkmakeSequenceName()needs to be used more, not less, and I even found another place where it should be used. If folks think that's not in scope of this issue, I don't mind moving that one-line change to a followup.IMO, what we should do instead is make the developers aware of manual vs. auto-created sequences in the doxygen block of
makeSequenceName(), and point out that they should be using database-specific ways of getting the name of an auto-created existing sequence.Also, as suggested by @plach a few days ago when we were discussing this issue, let's add a way for
ensureIdentifiersLength()to use a custom separator, which will make it return a "standard" sequence name if it doesn't exceed the 63 char limit.Comment #18
plach::makeSequenceName()does not handle the PG identifier limit, shouldn't we override it and call::ensureIdentifiersLength()internally? This way both places would be correctly dealing with long identifiers.I just noticed that the identifier name is enclosed in double quotes, so those will end up in the sequence name (see the attached screenshots). Shouldn't we
trim()them?Comment #19
plach(posting the same screenshot twice doesn't help)
Comment #20
amateescu commented@plach:
makeSequenceName()is to used as a manual way of generating a sequence name, and the developer will receive feedback immediately from the database (i.e. an error) that the sequence name is too long, so they will be able to correct the problem. However, in the automatic case (table renames), Drupal handles the sequence auto-renaming so the user has no way to intervene in the process.Comment #21
plach@#20:
1: 👍
2: Ha, tricky :) Confirmed this behavior by creating a
Testtable (see attached screen).Comment #22
plachIt seems indeed that if we want to keep uppercase characters we need to leave double quotes around, however those seem to be actually part of the sequence name, or at least there's no way to refer to the sequence name without them:
Comment #23
amateescu commentedThat's the problem :) You can read more about that here: https://www.postgresql.org/docs/9.1/sql-syntax-lexical.html
For your specific example,
SELECT pg_get_serial_sequence('Test', 'id');treatsTestastest, which doesn't exist, so you need to quote it in order to prevent the automatic conversion to lowercase.Comment #24
mradcliffeAh, thanks, @amateescu. This seems odd. The two usages of nextId are UserStorage and batches in form.inc? And in the pgsql driver it's always going to use the "sequences_value_seq" sequence for.. both? So user ids are tied to batch ids? Uh, hrm, maybe we should change that in a follow-up. That's probably going to require some extensive changes to either oul DBAL or UserStorage (and drupal 7).
I found another Major issue with changeField, but it's not related here. I don't see where we're relating a new sequence to the table field. It should use the OWNED BY statement, but it's not. This means anyone who's changed a field to be serial has floating sequences. This probably doesn't matter, but pg_get_serial_sequence will not find it.
That seems like database update territory especially if we publish the CR about using pg_get_serial_sequence.
I also found that the sequences automagically created with CREATE TABLE and sequences created with CREATE SEQUENCE are not truncated in the same way. The former will truncate the table name adding the trailing _, but the latter will truncate entire thing. But that shouldn't matter if you always (and can) use pg_get_serial_sequence.
So I think that the change record should reflect a better example on manually created sequences or for non-entity tables.
I feel like the issue summary could be updated after the discussion in the last week to provide some background information about driver things and answers to some of the questions.
Comment #25
amateescu commentedWow, good find with the
changeField()issue. I can confirm it and we should definitely open a new issue for that.I updated the CR to mention that using
pg_get_serial_sequence()is also the recommended best practice for manually created sequences. Note that the example in the CR does not mention on purpose anything about entity tables, so it's pretty generic already.Also updated the IS to be more explicit about the proposed resolution, but please feel free to improve it further if you think there's any additional info needed :)
Comment #26
mradcliffeThank you, @amateescu.
I created follow-up issues for pgsql driver. I think it's safe to postpone the CREATE SEQUENCE one after this because there are no core updates that change fields into serial columns.
- #3028706: New serial columns do not own sequences
- #3028709: Change behavior of PostgreSQL to not share sequence for batch and users
Let's flip it back to RTBC for #17.
Comment #27
plachThanks for the very thorough reviews, @mradcliffe!
Comment #28
plachCommitted 1b46f2f and pushed to 8.7.x. Thanks!
Since this was only marked critical because it is blocking a critical issue and it might require to follow the change record to be fully compliant with the new logic, I think it's ok to just commit this to 8.7.x. I will ping a release manager to confirm this.
Comment #31
kattekrab commentedComment #32
xjmI think the title of the CR is sufficient information for the release note here, so I've used that. This is potentially disruptive so tagging for the release notes directly.