Postponed
Project:
Drupal core
Version:
main
Component:
entity system
Priority:
Normal
Category:
Feature request
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
20 May 2015 at 10:45 UTC
Updated:
16 Feb 2026 at 15:24 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #1
erik.erskine commentedHere is a straightforward change that introduces a
pgsql_typein the schema definition forUuidItem. Non-PostgreSQL databases are unaffected.Comment #2
erik.erskine commentedMaking this a child of #2157455: [Meta] Make Drupal 8 work with PostgreSQL or remove support from core before release for visibility.
Comment #3
erik.erskine commentedJust noticed this causes some tests to fail on PG because the test data contains UUID values that are syntactically invalid.
Comment #4
dawehnerInterestedin.
Can you describe what kind of data do we try to insert there? I could imagine things like an empty string maybe?
Comment #5
erik.erskine commentedUpdated issue summary with test failures as a result of applying #1
Comment #6
erik.erskine commented@dawehner it looks like this is because of dummy values in the tests themselves. Sometimes these are a string like
invalid-9a3ecbcd-9b29-4e4d-902c-4416dd2b280b, or the stringuuiditself, or an integer.Comment #7
erik.erskine commented#2403271: Return NULL on EntityRepository::loadEntityByUuid() when the UUID is invalid or it doesn't exist catches malformed UUIDs before they are used in the database query, thereby preventing the test failures from happening if #1 is applied.
Comment #8
erik.erskine commentedtagging as this involves a data model change
Comment #9
bzrudi71 commentedSo let's postpone because of #2403271: Return NULL on EntityRepository::loadEntityByUuid() when the UUID is invalid or it doesn't exist (I left a comment over there)
Comment #12
bzrudi71 commentedA year has gone and we have PostgreSQL testing now. Going to re-upload patch to see what happens...
Comment #13
bzrudi71 commentedComment #15
bzrudi71 commentedComment #16
panchoBack to "postponed" – let's fix #2403271: Return NULL on EntityRepository::loadEntityByUuid() when the UUID is invalid or it doesn't exist first.
Comment #25
tstoecklerMarking as PP-2 (i.e. Postponed on 2 issues) on:
Looked into this a bit, after updating #1805576: Add a 'uuid' database schema type yesterday.
My goal is to build on the patch there, change core's UUID item to use the
uuidschema type and get the resulting patch green. We currently allow arbitrary strings for UUIDs so this is not going to be able to committed, as is, and we will either need a new setting onUuidItemor an entirely new field type for "strict" UUIDs, so the patch I'm initially going for will definitely not be committed in this form. But I want to make sure that we are covering all functionality that currently involves UUIDs. Once we have a green patch we can discuss how to implement this properly.Providing a first version of that attempt here. I've encountered two problems thus far:
LOWER()on UUID columns which can also be seen in some of the test failures here. I "fixed" this by making\Drupal\Core\Entity\Query\Sql\Tablesconsider native UUID columns as case sensitive on PostgreSQL. This is a workaround, I'm not sure what a proper fix would look like.EntityRepository::loadEntityByUuid(), but in runningJsonApiDocumentTopLevelNormalizerTestI found thatJsonApiDocumentTopLevelNormalizer::denormalize()callsEntityStorage::loadByProperties()on a UUID field directly without going through the entity repository. So I implemented another workaround inSqlContentEntityStorage::loadByProperties()directly. This, also, will not fly as is, but is just to get this working for nowLet's see how badly this breaks!
Comment #26
tstoecklerSorry for the 4 test runs that fail on a CS error! I had never checked out the new
commit-code-check.sh, did that now...This one should be better. I am also skipping all update path tests with this patch as they will inherently fail on "UUID field schema needs to be updated" for all entity types, so let's not bother the test runner with that. Other than that I am not aware of any test failures (but am assuming there are a bunch left...)
Not providing a new "for-review" patch as the CS fix is the only difference...
Comment #27
tstoecklertl:dr; JSON API performs direct entity queries with invalid UUID values, so we need to support that. The attached patch attempts to do so ;-)
OK, so the fail in the one above was a bit disheartening as it proved a suspicion/fear I had had before: We need to support people doing direct entity queries on UUID fields with invalid values. It would have been a tough argument to have to no longer allow this and not consider this a BC break for the theoretical use-case of doing. We are spared this argument, though, due to JSON API very practically doing this already. JSON API's collections allow filtering the result-set by any field value which translates into a direct entity query with respective conditions for the filters. And
EntityResourceTest::testGetEmptyCollection()tests exactly the case discussed here: it adds a filter on the UUID field with the value'invalid', expecting an empty collection as a result. Thus, if we want to pursue native UUIDs on PostgreSQL then we need to go one level further than the previous patch and attack the problem at the query level.An aside before I continue with what I've done with the new patch: In investigating the aforementioned and possibles solutions I tried manually running various queries against my local PostgreSQL instance but could not reproduce the errors thrown on those invalid UUID values. This confused me a great deal and I briefly thought this was due to the version difference of PostgreSQL as I have a PostgreSQL 12 locally and the testbot runs 10 by default. This is why I started a test run on PostgreSQL 12 for the latest patch above. After that failed in the same way I realized that the PostgreSQL CLI tool
psql- and by extensiondrush sql:querydoes not show any errors by default unless you explicitly specify the-bor--echo-errorsoption. So anyone trying this out at home will need to do that.I spent a lot of time looking at the code that converts entity queries into SQL queries as that is the place where we need to resolve this issue. Unfortunately that part of the code is very complex and at all easily extendable. Also in the context of an entity query we do not generally have the entire entity schema at hand, so even figuring out which field column is a UUID column is a non-trivial task, without even worrying about how to fix that. After a while, though, I realized two things that lead me to what I think is the solution to this problem:
\Drupal\Core\Entity\Query\Sql\pgsql\Conditionif you, like me, find this astonishing. That means as long as we can hook into the right spot in the process and the figure out what actually to do, we will not have to employ hacky driver checks a la #25, which is great.->condition('field', 'value')intofield LIKE 'value'instead offield = 'value'. This was again very much news to me, but in any case that means we already have a precedent of this process depending on the column schema. Which is exactly what we want to do here.So given the above, I did the following:
\Drupal\Core\Entity\Query\Sql\Tablesin a separate method so it is more easily overridableTablesimplementation that adds tracking of UUID columns on top of the case-sensitivity tracking\Drupal\Core\Entity\Query\Sql\Conditionto allow altering the condition. This ia a bit unfortunate because there is already::translateCondition()but that one does not have access toTablesso it does not help here. I think I still found a way to make the new method fit reasonably well but your mileage may vary.Conditionoverride. In that I check if the column is a UUID column and, if so, and if the value(s) is/contains invalid UUIDs, skips the condition and alters the query result to achieve the expected result. This took me quite a while to get right, but I think I have a version that is both correct and fairly understandableEntityQueryUuidTestthat tests all sorts of entity queries with different sets of conditions and with all possible operators. That passes locally on all three database engines which gave me confidence that the PostgreSQL-specific overrides function in the way they should.Conditionto have a way to mark fields to be casted to text by the database driver. This is needed for comparison operators, i.e. in PostgreSQL you cannot douuid BETWEEN '1a' AND '1f'to find a specific range of UUIDs, instead it needs to beuuid::text BETWEEN '1a' AND '1f'. To support this I added support for specifying this cast (i.e. "::text") as part of the field in a database condition and made the PostgreSQL database driver pick that up and not escape it. If anyone has a better way to achieve this please step forward, I personally think this is the ugliest part of the patch.So I am very much looking forward to the test results on this one. But to summarize, I think this patch gets us a big step in the right direction and comes with fairly extensive test coverage. It no longer depends on #2403271: Return NULL on EntityRepository::loadEntityByUuid() when the UUID is invalid or it doesn't exist and is fairly hack-free (* #25 didn't depend on #2403271: Return NULL on EntityRepository::loadEntityByUuid() when the UUID is invalid or it doesn't exist either but had some ugly hacks). I am not yet turning the "PP-2" into a "PP-1", though, until I get some thumbs up ;-)
Two more notes:
\Drupal\Core\Entity\Query\Sql\Condition::preTranslateCondition()in the patch is broken as it does not account for OR groups. It is not at all tested, though, so I will investigate and (if I am right) open a follow-up for that.NOT BETWEENoperator is not documented as existing, but JSON API has test coverage for it (and it does work), so I included it here. I will open a follow-up to clarify this situation, i.e. probably just amending the docs to mention it "officially".Comment #28
andypostThe function argument already require this interface so not clear why that needed
this assert more confusing, is it expected that 'field' key always exists in condition array?
it will need upgrade and update hook
Comment #29
andypostfixed tags
Comment #30
tstoecklerThanks for the review @andypost!
Re #28:
Tablesimplementation as only that has the UUID tracking, the base class does not have that. That's why the assert makes sense and is needed to get autocompletion for the method call below.Having written 2. above I realized that I hadn't added the scalar type hint to
isUuidField(), so doing that now.This patch should pass the code-style tests. Still getting used to the pre-commit script, I had run it locally, but I guess I had some stuff cached, not sure...
Anyway, this one should hopefully be better.
Comment #31
tstoecklerSo, that was not too bad, but three problems were surfaced by the test fails in #30:
\Drupal\Core\Entity\Query\Sql\pgsql\Tables::trackColumnState(), which caused all the "%delta" errors, fixed that.QueryAggregatefor PostgreSQL that casts UUID fields to::textas needed, in line with the casting done for the various comparison operators already in the previous patch.Let's see, there should definitely be a lot less fails, maybe even none ;-)
Comment #32
tstoecklerAhh sorry, the "...Alias" part should not be there, will fix in the next round...
Comment #33
tstoecklerStill have this on my todo list, will try to make some more progress soon. Here is a work-in-progress interdiff that I have locally so far in case I don't get to it and someone else wants to pick this up. The remaining blocker with this is views-filter support.
Comment #36
tstoecklerFinally got around to opening follow-ups for the issues mentioned in #27:
Not sure I will get around to working on this again soon, but when I do I can now dive in directly without those distractions...
Comment #38
bradjones1Is this more or less a duplicate of #1805576: Add a 'uuid' database schema type, which applies more broadly to all DB drivers?
Comment #39
tstoecklerRe #38: So #1805576: Add a 'uuid' database schema type is about the addition to the Schema API, this issue is about using the new database schema field type provided there for the entity field schema of UUID fields. So this is postponed on that.
Your confusion is understandable, however, because the issue title here is not very accurate and also I don't think this should be assigned to the "postgresql db driver" Component. Since there hasn't been much traction here anyway just going ahead and changing title and component. In case I'm missing something please do feel free to revert. Leaving "PostgreSQL" in the title for searchability, though.
While I'm at it also decreasing the postponed count per #27
Comment #43
murzSeems the PP-1 task got lost in other tasks, so let me necropost here some benefits of implementing this from a duplicated issue #3573735: Improve UUID performance by storing the value as binary format in the database instead of string:
For now, Drupal stores the UUID values in the entity tables just as plain strings in the database:
varchar(128).This type of storage leads to significant performance issues, compared to the binary UUID format:
So, switching to the binary format should significantly improve the performance, here are some estimations:
The same is true for all other database types.
---
So, let's try to finalize and merge this issue?
Comment #44
andypostIf we are attempt to use binary format for UUIDs then it makes sense to use UUID v7 - only one transition
Comment #45
andypostSince PG18 there's native function for it https://www.postgresql.org/docs/current/functions-uuid.html#FUNC_UUID_GE...
Also Symfony since 7.4 also support it https://symfony.com/blog/new-in-symfony-7-4-uid-improvements
PS: the only dark side I see is that it has timestamp inside