Problem/Motivation
Using entityQuery() with node as a base table in combination with node access grants and revisions results in a fatal error. Coming from #2781423: Skip access checking during update., where this was thought to be a Paragraphs or View unpublished issue, but it appears to be a core bug.
Steps to reproduce
- On a fresh D8 (Standard profile), enable a Node Access module (I used View Unpublished for this scenario).
- Add an unpublished article
- Rebuild node access permissions
- As non-admin, run the following code:
$entity_ids = \Drupal::entityQuery('node') ->condition('comment', NULL, 'IS NOT NULL') ->allRevisions() ->execute();
Executing this query will result in the exception below being thrown.
This is thought to be a core issue rather than View Unpublished since the hardcoded 'nid' column is added through node_query_node_access_alter() and NodeGrantDatabaseStorage::alterQuery()
Exception
The website encountered an unexpected error. Please try again later.
Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'node_revision__comment.nid' in 'where clause': SELECT base_table.vid AS vid, base_table.nid AS nid FROM {node_revision} base_table INNER JOIN {node_revision__comment} node_revision__comment ON node_revision__comment.revision_id = base_table.vid WHERE (node_revision__comment.comment_status IS NOT NULL) AND (EXISTS (SELECT na.nid AS nid FROM {node_access} na WHERE ((gid IN (:db_condition_placeholder_0)) AND (realm = :db_condition_placeholder_1)) AND (na.grant_view >= :db_condition_placeholder_2) AND ((node_revision__comment.nid = na.nid)))); Array ( [:db_condition_placeholder_0] => 0 [:db_condition_placeholder_1] => all [:db_condition_placeholder_2] => 1 ) in eval() (line 4 of modules/devel/src/Form/ExecutePHP.php(55) : eval()'d code).
Drupal\Core\Database\Statement->execute(Array, Array) (Line: 624)
Drupal\Core\Database\Connection->query('SELECT base_table.vid AS vid, base_table.nid AS nid
FROM
{node_revision} base_table
INNER JOIN {node_revision__comment} node_revision__comment ON node_revision__comment.revision_id = base_table.vid
WHERE (node_revision__comment.comment_status IS NOT NULL) AND (EXISTS (SELECT na.nid AS nid
FROM
{node_access} na
WHERE ((gid IN (:db_condition_placeholder_0)) AND (realm = :db_condition_placeholder_1)) AND (na.grant_view >= :db_condition_placeholder_2) AND ((node_revision__comment.nid = na.nid))))', Array, Array) (Line: 87)
Drupal\Core\Database\Driver\mysql\Connection->query('SELECT base_table.vid AS vid, base_table.nid AS nid
FROM
{node_revision} base_table
INNER JOIN {node_revision__comment} node_revision__comment ON node_revision__comment.revision_id = base_table.vid
WHERE (node_revision__comment.comment_status IS NOT NULL) AND (EXISTS (SELECT na.nid AS nid
FROM
{node_access} na
WHERE ((gid IN (:db_condition_placeholder_0)) AND (realm = :db_condition_placeholder_1)) AND (na.grant_view >= :db_condition_placeholder_2) AND ((node_revision__comment.nid = na.nid))))', Array, Array) (Line: 493)
Drupal\Core\Database\Query\Select->execute() (Line: 250)
Drupal\Core\Entity\Query\Sql\Query->result() (Line: 77)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 4)
eval() (Line: 55)
Drupal\devel\Form\ExecutePHP->submitForm(Array, Object)
call_user_func_array(Array, Array) (Line: 111)
Drupal\Core\Form\FormSubmitter->executeSubmitHandlers(Array, Object) (Line: 51)
Drupal\Core\Form\FormSubmitter->doSubmitForm(Array, Object) (Line: 585)
Drupal\Core\Form\FormBuilder->processForm('devel_execute_form', Array, Object) (Line: 314)
Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 74)
Drupal\Core\Controller\FormController->getContentResult(Object, Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 574)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}()
call_user_func_array(Object, Array) (Line: 139)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 62)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 99)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 78)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 50)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 654)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
The website encountered an unexpected error. Please try again later.
Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'node_revision__comment.nid' in 'where clause': SELECT base_table.vid AS vid, base_table.nid AS nid FROM {node_revision} base_table INNER JOIN {node_revision__comment} node_revision__comment ON node_revision__comment.revision_id = base_table.vid WHERE (node_revision__comment.comment_status IS NOT NULL) AND (EXISTS (SELECT na.nid AS nid FROM {node_access} na WHERE ((gid IN (:db_condition_placeholder_0)) AND (realm = :db_condition_placeholder_1)) AND (na.grant_view >= :db_condition_placeholder_2) AND ((node_revision__comment.nid = na.nid)))); Array ( [:db_condition_placeholder_0] => 0 [:db_condition_placeholder_1] => all [:db_condition_placeholder_2] => 1 ) in eval() (line 4 of modules/devel/src/Form/ExecutePHP.php(55) : eval()'d code).
Drupal\Core\Database\Statement->execute(Array, Array) (Line: 624)
Drupal\Core\Database\Connection->query('SELECT base_table.vid AS vid, base_table.nid AS nid
FROM
{node_revision} base_table
INNER JOIN {node_revision__comment} node_revision__comment ON node_revision__comment.revision_id = base_table.vid
WHERE (node_revision__comment.comment_status IS NOT NULL) AND (EXISTS (SELECT na.nid AS nid
FROM
{node_access} na
WHERE ((gid IN (:db_condition_placeholder_0)) AND (realm = :db_condition_placeholder_1)) AND (na.grant_view >= :db_condition_placeholder_2) AND ((node_revision__comment.nid = na.nid))))', Array, Array) (Line: 87)
Drupal\Core\Database\Driver\mysql\Connection->query('SELECT base_table.vid AS vid, base_table.nid AS nid
FROM
{node_revision} base_table
INNER JOIN {node_revision__comment} node_revision__comment ON node_revision__comment.revision_id = base_table.vid
WHERE (node_revision__comment.comment_status IS NOT NULL) AND (EXISTS (SELECT na.nid AS nid
FROM
{node_access} na
WHERE ((gid IN (:db_condition_placeholder_0)) AND (realm = :db_condition_placeholder_1)) AND (na.grant_view >= :db_condition_placeholder_2) AND ((node_revision__comment.nid = na.nid))))', Array, Array) (Line: 493)
Drupal\Core\Database\Query\Select->execute() (Line: 250)
Drupal\Core\Entity\Query\Sql\Query->result() (Line: 77)
Drupal\Core\Entity\Query\Sql\Query->execute() (Line: 4)
eval() (Line: 55)
Drupal\devel\Form\ExecutePHP->submitForm(Array, Object)
call_user_func_array(Array, Array) (Line: 111)
Drupal\Core\Form\FormSubmitter->executeSubmitHandlers(Array, Object) (Line: 51)
Drupal\Core\Form\FormSubmitter->doSubmitForm(Array, Object) (Line: 585)
Drupal\Core\Form\FormBuilder->processForm('devel_execute_form', Array, Object) (Line: 314)
Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 74)
Drupal\Core\Controller\FormController->getContentResult(Object, Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 574)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}()
call_user_func_array(Object, Array) (Line: 139)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 62)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 99)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 78)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 50)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 654)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
| Comment | File | Size | Author |
|---|---|---|---|
| #31 | node-grant-database-storage-fix-entity-id.patch | 740 bytes | egruel |
| #25 | 2823957-nr-bot.txt | 2.15 KB | needs-review-queue-bot |
| #13 | 2823957-13.patch | 2.16 KB | berdir |
| #6 | 2823957-6-FAIL.patch | 1.34 KB | phenaproxima |
Comments
Comment #2
mr.baileysComment #6
phenaproximaHere's a fail patch proving the problem exists. It only happens, for me, when you combine an allRevisions() query with a field that has a dedicated table. It tries to join the field table on the 'nid' column, which field tables don't have.
Comment #7
timmillwoodComment #8
phenaproximaAlso...8.6.x please!
Comment #11
szato commentedSame error with paragraphs (entity_reference_revisions), on revision delete:
Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'node_revision__[ENTITY_REFERENCE_REVISION_FIELD].nid' in 'where clause': SELECT base_table.vid AS vid, base_table.nid AS nid FROM {node_revision} base_table INNER JOIN {node_revision__[ENTITY_REFERENCE_REVISION_FIELD]} node_revision__[ENTITY_REFERENCE_REVISION_FIELD] ON node_revision__[ENTITY_REFERENCE_REVISION_FIELD].revision_id = base_table.vid WHERE (node_revision__[ENTITY_REFERENCE_REVISION_FIELD].[ENTITY_REFERENCE_REVISION_FIELD]_target_revision_id = :db_condition_placeholder_0) AND (EXISTS (SELECT na.nid AS nid FROM {node_access} na WHERE (((gid IN (:db_condition_placeholder_1)) AND (realm = :db_condition_placeholder_2)) OR ((gid IN (:db_condition_placeholder_3)) AND (realm = :db_condition_placeholder_4)) OR ((gid IN (:db_condition_placeholder_5)) AND (realm = :db_condition_placeholder_6)) OR ((gid IN (:db_condition_placeholder_7)) AND (realm = :db_condition_placeholder_8))) AND (na.grant_view >= :db_condition_placeholder_9) AND (na.fallback = :db_condition_placeholder_10) AND ((node_revision__[ENTITY_REFERENCE_REVISION_FIELD].nid = na.nid)))); Array ( [:db_condition_placeholder_0] => 334030 [:db_condition_placeholder_1] => 0 [:db_condition_placeholder_2] => all [:db_condition_placeholder_3] => 286 [:db_condition_placeholder_4] => view_unpublished_author [:db_condition_placeholder_5] => 1 [:db_condition_placeholder_6] => view_unpublished_published_content [:db_condition_placeholder_7] => 1 [:db_condition_placeholder_8] => view_unpublished_content [:db_condition_placeholder_9] => 1 [:db_condition_placeholder_10] => 1 ) in Drupal\entity_reference_revisions\Plugin\Field\FieldType\EntityReferenceRevisionsItem->deleteRevision() (line 341 of /home/devel/www/***/web/modules/contrib/entity_reference_revisions/src/Plugin/Field/FieldType/EntityReferenceRevisionsItem.php).Comment #12
szato commentedComment #13
berdirThe idea was good, but does it really hurt if we hardcode the 4 table names we really want to see there? We already hardcode two of them below. we could call getBaseTable(), getDataTable(), ... I guess...
The test is a start, but IMHO it should be in a kernel test as we don't need the UI for this. We also don't actually need a cardinality unlimited field, all configurable fields have dedicated tables at the moment. And we should have actual data, so we can assert that the query doesn't just run, it also returns the expected results. Possibly combined with an actual limit applied through node grants?
Comment #14
phenaproximaAs you pointed out, we can make this a bit more flexible by loading the node entity type definition and calling getBaseTable(), getDataTable(), etc., rather than hard-coding. It's probably a better idea to use the abstraction layer.
"At the moment"? What if this changes later? Would it be more future-proof to try with fields of varying cardinality?
I put it into an existing test so that we could take advantage of existing node access-related test code (the node_access_test module in particular). It'd take a lot more effort and set-up code to stand up a kernel test that will reproduce this bug, and browser tests aren't that slow anymore since they're not installing a gigantic profile with a kazillion modules.
Yet another reason to keep the test in the functional space, to take advantage of BrowserTestBase's "realness".
Comment #15
berdirAre you sure any of that code is actually needed, it might be enough to just have a node query access module enabled that adds grants?
Functional tests are still easily 5x slower than kernel tests and it is worth to keep tests kernel tests if they do not need a UI.
> Yet another reason to keep the test in the functional space, to take advantage of BrowserTestBase's "realness".
Don't agree with that. Nothing prevents us from adding data in a kernel test. The primary entity query test coverage (\Drupal\KernelTests\Core\Entity\EntityQuery*Test) are kernel tests.
Comment #25
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #27
shishircoolin commentedThe "Node Access module" is not fully compatible with Drupal 10, let alone Drupal 11.x. I'm unable to reproduce this issue for now.
Comment #28
acbramley commentedNot much action here in a while, is this still reproducible?
Comment #29
smustgrave commentedWanted to bump 1 more time if anyone still experiencing this.
Comment #31
egruel commentedI encountered this issue on Drupal 11.3.2 when combining custom hook_node_grants() implementations with entity reference revision fields (paragraphs).
Steps to reproduce:
1. Implement custom hook_node_grants() and hook_node_access_records()
2. Create content with entity reference revision fields (e.g., paragraph fields)
3. Visit a page containing this content as an anonymous user
Error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'node_revision__field_FIELDNAME.nid' in 'where clause'
Root cause:
In NodeGrantDatabaseStorage::alterQuery() (line 193), the code hardcodes $field = 'nid' for all tables. However, field revision tables (node_revision__field_*) and field data tables (node__field_*) use entity_id instead of nid
as their primary key column.
Proposed solution:
The attached patch detects the table type and uses the appropriate column name:
- entity_id for field tables (node_revision__field_* and node__field_*)
- nid for base node tables
This issue affects sites using custom node access grants with entity reference revision fields in Drupal 11.3+.
Comment #32
smustgrave commentedComment #33
ghost of drupal past#13 looks much better to me at least but I think calling the four methods from the table mapping instead of hardwiring them would be best.