Problem/Motivation

The hook_query_alter() is all called for every tagged query. It is only called on tagged queries due to performance concerns - see #619580: Only invoke query_alter on tagged queries. However, if the only queries it is called on are tagged queries and tagged queries have their own alter hook what's the point of the generic alter hook that does not fire on untagged queries? Let's do less and only trigger hook_query_TAG_alter.

For an overview of contrib modules that implement the hook_query_alter: http://grep.xnddx.ru/search?text=hook_query_alter&filename=

Proposed resolution

Deprecate hook_query_alter() because not all queries are alterable and all that are are tagged and have a specific hook_query_TAG_alter fired.

Remaining tasks

User interface changes

None

API changes

hook_query_alter() deprecated

Data model changes

None

Release notes snippet

@todo

Comments

daffie created an issue. See original summary.

daffie’s picture

Status: Active » Needs review
StatusFileSize
new2.67 KB

Patch for deprecating hook_query_alter

daffie’s picture

Title: Depreating hook_query_alter » Deprecating hook_query_alter
alexpott’s picture

+++ b/core/lib/Drupal/Core/Database/Query/Select.php
@@ -477,7 +477,9 @@ public function preExecute(SelectInterface $query = NULL) {
+      $module_handler->alter($hooks, $query);
+      $module_handler->alterDeprecated('This hook is deprecated in Drupal 9.1.0 and will be removed before Drupal 10.0.0. Convert your calls to hook_query_TAG_alter. See: https://www.drupal.org/node/', 'query', $query);

We need to reverse the order here because the hook_query_alter() was fired first. To preserve BC in D9 properly. Also $hooks = ['query']; should be $hooks = []; because with this code it's being fired once by the call to ->alter() and once by the call to ->alterDeprecated().

alexpott’s picture

So git archaeology about the amusing if (isset($this->alterTags)) { mess.

We added the hook_query_TAG_alter in #320591: Tag specific alter hook at this point the generic alter was outside the if. And then later we did #619580: Only invoke query_alter on tagged queries which means we only alter tagged queries.

daffie’s picture

Issue tags: +Needs change record
StatusFileSize
new7.37 KB

Did what has been suggested by @alexpott. Now see how many deprecation warnings we get.
A link to the change record also needs to be added to the patch.

daffie’s picture

@alexpott: How would you like to have the stuff from comment #5 fixed? If you want to have it fixed, of course.

The last submitted patch, 2: 3127091-2.patch, failed testing. View results

daffie’s picture

StatusFileSize
new7.38 KB
new433 bytes

Minor fix.

daffie’s picture

The testbot is reporting 3646 tests are failing with the deprecation message. Should we add for every test @group legacy. And when we do that then when Drupal 10.0 arrives we have to remove all those @group legacy again. Is this what we want or should we solve this problem in an other way?
Every query that adds a tag like: 'node_access', 'term_access', 'taxonomy_term_access' and 'views' has this problem.

avpaderno’s picture

Since hook_query_alter() is called for queries that have at least a tag, why isn't the documentation for that hook changed to make that explicit?
hook_query_alter() is never an exact equivalent to hook_query_TAG_alter(), as the latter doesn't allow to alter any tagged query basing on its metadata.

It's true there are modules that implement hook_query_alter() to just handle queries with a very specific tag, but it also true that Drupal core doesn't use a specific tag to say the query contains a specific metadata. With hook_query_TAG_alter(), the modules are required to know a tag name, even if, for example, they are just interested in those queries for which $query->getMetadata('account') returns a value.

alexpott’s picture

StatusFileSize
new603 bytes
new7.37 KB

@daffie no we definite should not mark all tests @group legacy. That would imply we've not removed all usages of hook_query_alter() from core. That's not the case. The test module is #9 is broken - that's why things are failing everywhere.

We'll also need to replace taxonomy_test_query_alter() with the hook_query_TAG_alter() equivalent.

We also need to:

  • update docs in \Drupal\Core\Database\Query\PagerSelectExtender::execute()
  • update class docs of \Drupal\search\SearchQuery()
  • update docs in \Drupal\Core\Database\Query\SelectExtender::execute
  • Think about the help text in \Drupal\views\Plugin\views\query\Sql::buildOptionsForm()
  • Think docs that are like "This is the hook_query_alter() for queries tagged with"
alexpott’s picture

Status: Needs review » Needs work
+++ b/core/tests/Drupal/KernelTests/Core/Database/AlterTest.php
@@ -5,8 +5,12 @@
 /**
  * Tests the hook_query_alter capabilities of the Select builder.
  *
+ * The group legacy is added, only because the deprecated hook_query_alter is
+ * called when a tag has been added to the query.
+ *
+ * @group legacy
  * @group Database
- * @see database_test_query_alter()
+ * @see database_test_query_TAG_alter()
  */
 class AlterTest extends DatabaseTestBase {

This test does not need to be marked @legacy. It's not relying on a hook_query_alter(). The comment is added here is incorrect. Merely calling $module_handler->alterDeprecated() does not trigger a deprecation. There has to be at least one implementation for the deprecation to be triggered.

avpaderno’s picture

Also I could not find any bug report where somebody complains that hook_query_alter does not work as promised.

There are developers who know that, though. (See tests/src/Functional/DbeeQueryUsersTest.php, for example.)

// The hook_query_alter function only works on dynamic tagged queries.
$query->addTag('test');
if (!$dbee) {
  $query->addTag('dbee_disabled');
}
$query->orderBy('uid');
$result = $query->execute();
daffie’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record
StatusFileSize
new9.28 KB
new14.5 KB

@alexpott: Thank you for your help on this issue.

@kiamlaluno: I am not suprised that other developer know that the hook_query_alter is not run on every select query. If they thought it was a bug that needed to be fixed, then they would have created a bug report for it. I also agree with you that we should update the documentation that the hook_query_alter is only being run when the query is tagged.

Changes in this patch:

  1. Updated the taxonomy_test. I could just remove the taxonomy_test_query_alter() implementation, because there are already several taxonomy_test_query_TAG_alter() hooks in use.
  2. Updated the documentation in several places.
  3. Added a change record and update the patch with links to the CR.
  4. Updated the Drupal\KernelTests\Core\Database\AlterTest.
alexpott’s picture

Issue summary: View changes
StatusFileSize
new2.98 KB
new15.06 KB
+++ b/core/lib/Drupal/Core/Database/Query/Select.php
@@ -473,11 +473,13 @@ public function preExecute(SelectInterface $query = NULL) {
+      $module_handler->alterDeprecated('This hook is deprecated in Drupal 9.1.0 and will be removed before Drupal 10.0.0. Convert your calls to hook_query_TAG_alter. See: https://www.drupal.org/node/3127431', 'query', $query);
+      $module_handler->alter($hooks, $query);

I've been looking at this again and reading what was committed on #765860: drupal_alter() fails to order modules correctly in some cases I think unfortunately this is in the realms of the super super complex. I think in order to do this in a 100% bc compatible manner with HEAD we're going to have to copy large parts of \Drupal\Core\Extension\ModuleHandler::alter() to here :( Or somehow how add support for only deprecating a single hook out of an array of hooks in \Drupal\Core\Extension\ModuleHandler::alterDeprecated() which doesn't look good.

AH there is another way. We can call the old alter() as before but check implementations here also given the amount of time a select can occur I think we should limit the deprecation to being triggered once per execution - especially as this is low level and extremely performance sensitive code.

I've also tweaked the format of the deprecation message to match the standard - unfortunately the alterDeprecated() has not been updated yet.

I've also fixed the issue summary to encompass the learnings and concisely state the problem and the resolution.

catch’s picture

I'm not sure the hook only being called on tagged queries is sufficient reason to remove it, for example cps_query_alter() in Drupal 7 uses it to support a dynamic amount of entity types. The query tags are predictable, but there would have been no way for CPS to specify each entity type's query tag individually in a different hook implementation, including an unknown number of contrib and custom ones.

https://git.drupalcode.org/project/cps/-/blob/7.x-1.x/includes/query.inc

CPS itself won't see a Drupal 8 port because workspaces module in core is covering the same use case (and without relying on hook_query_alter()), but the reason for implementing hook_query_alter() is the same if you had a similar situation.

alexpott’s picture

@daffie correct me if I'm wrong but the motivation behind this issue was to try to bring ->select() performance closer to ->query() performance right? So we can deprecate query()? Given that we've discovered the alters only get fired for tagged queries - is this really worth it? Given the use-case described in #17?

johnwebdev’s picture

no way for CPS to specify each entity type's query tag individually in a different hook implementation, including an unknown number of contrib and custom ones.

Doesn't entity types share a query tag entity_query that can be used for all entity types?

avpaderno’s picture

@johndevman That module is changing all the queries having a $entity_type . '_load_multiple' tag, not all the entity queries.

avpaderno’s picture

That tag is still used in Drupal 9: The SqlContentEntityStorage.php file contains this line.

$query->addTag($this->entityTypeId . '_load_multiple');
daffie’s picture

Let me ask it in another way. Lets look at the situation where we did not have a hook_query_alter() and somebody created an feature request for adding such a hook to Drupal 9. What would the answer be. My guess would be: no. No, because we already have hook_query_TAG_alter() and you should be using that. We do not want be calling an extra hook on every "tagged" query, it like doing someting double. Also we have backend overridable services and a plugin system, that are great alternatives for using hook_query_alter(). If you do not want to add a new hook_query_alter(), then we should not want to keep it either. Those arguments for why we would not add a new hook_query_alter() are the same arguments why we should remove hook_query_alter.

@catch: You come up with a Drupal 7 module that cannot be done without using hook_query_alter() for which there is no Drupal 8 version of the module, because there is now a better solution in Drupal 8 & 9 that does not use hook_query_alter(). I think in the time of Drupal 7 and before, there was a real need for hook_query_alter(). Now with Drupal 8 & 9, we have backend overridable services, the plugin system and we still have hook_query_TAG_alter(). I have this feeling like when you use hook_query_alter() in a Drupal 9 module, you are doing something wrong. Some kind of code smell.

@daffie correct me if I'm wrong but the motivation behind this issue was to try to bring ->select() performance closer to ->query() performance right? So we can deprecate query()?

I am very happy to see that most queries do not have the burden of the hook_query_alter(). That said there are still a great number of "tagged" queries that have the hook_query_TAG_alter() and hook_query_alter() being called. Why do do all those queries have to have the extra hook_query_alter() called. I do not see why that is necessary. Only calling hook_query_TAG_alter() should be enough.

The final decision whether or not to deprecate hook_query_alter() is up to the core framework managers. If they want to keep it, then fine lets keep it then.

After the decision is made to remove the hook_query_alter we must look how to best do it. If the patch from comment #16 is not good enough then to we just have to formally make the decision that hook_query_alter() is deprecated in Drupal 10.0 and communicate that as much as possible to the community. After that we just remove the hook_query_alter() in Drupal 10.0.

catch’s picture

@daffie so the reason there is no Drupal 9 need for the module is because there was a multi-year project to refactor core entity storage, update every entity type to it, etc. etc. as well as iirc adding a new hook explicitly to prevent needing to query rewrite. But it's the same basic thing as occasionally needing to implement hook_form_alter(), even if a lot narrower.

If the multiple load query had also added a tag like entity_load_multiple alongside ENTITY_TYPE_load_multiple, CPS could have used that (similar to hook_BASE_FORM_ID_form_alter()).

alexpott’s picture

Note that domain_entity (part of the domain module) which does have a supported Drupal 8 version and probably will have a D9 version too also uses hook_query_alter in exactly the same way as CPS does in Drupal 7.

daffie’s picture

Note that domain_entity (part of the domain module) which does have a supported Drupal 8 version and probably will have a D9 version too also uses hook_query_alter in exactly the same way as CPS does in Drupal 7.

I have looked at how https://www.drupal.org/project/domain_entity uses the hook_query_alter(). It be be every easily rewritten to a hook_query_TAG_alter() with the tag being "entity_query". See: https://git.drupalcode.org/project/domain_entity/-/blob/8.x-1.x/domain_e.... By the way the module has only an alpha release and that release is now more then 2.5 years old. After the release of Drupal 8.0 more then 4 years ago, we do not have any example (core or contributed) why we should keep hook_query_alter(). It does however run on every "tagged" query on every Drupal 8 site out there.

avpaderno’s picture

I read all the comments in this issue, but I still have to find an explanation of why this hook should be removed.
It doesn't seem it's because it increases performance, as that is already done by invoking the hook only when the query has a tag, At this point, avoiding one or more if() in code doesn't make the code more performant.

hook_query_TAG_alter() cannot replace hook_query_alter() because the latter allows to alter every tagged query for which I don't need to know exactly the used tags. I could also alter every tagged query containing a specific metadata; in this case, the tag name is irrelevant, and the hook could not be hook_query_TAG_alter(), which requires to put a tag name in the hook name.
While Drupal core could use an entity_load_multiple tag for queries loading an entity, what can Drupal core do to make clear a query uses specific metadata? Should it add a tag like account for those queries for which $query->getMetaData('account') returns a value, and use a similar tag for everything added as metadata?

andypost’s picture

I wonder that you count only contrib mofuyles for the hook, I see at least 3 supported client projects which using this hook to provide custom access instead of writing hooks for every possible tag and metadata keys.

I will check deeper suggested replacement but jook removal is no-go in 9.x, domain entity has 2-3 variations in contrib and all of them used and supported.

It means it could be used as example to prevent security issues with hook removal

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new146 bytes

The 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.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.