Make the conditions in joins in dynamic queries use Condition objects

Created on 2 November 2023, about 1 year ago
Updated 17 September 2024, about 2 months ago

Problem/Motivation

The conditions in joins in dynamic queries can be and are mostly SQL strings. The conditions can also be a Condition object, like they are in Select queries. The problem is that MongoDB does not support SQL strings.

// MongoDB does not support SQL string conditions.
$connection = \Drupal::database();
$query = $connection->select('node', 'n');
$query->join('inner', 'comment', 'c', '[n].[nid] = [c].[nid]');

Proposed resolution

Remove the use of SQL string conditions in joins in dynamic queries. With an exception for the join and relationship plugins of the views module. There is a followup issue for those changes 📌 [PP-1] Make the conditions in joins in dynamic queries use Condition objects in the Views module Postponed .
Create a new method Drupal\Core\Database\Condition::compare() to compare two fields with each other.

// MongoDB supports conditions as Conditions objects
$connection = \Drupal::database();
$query = $connection->select('node', 'n');
$query->join('left', 'comment', 'c', $connection->joinCondition()->compare('n.nid', 'c.nid'));

Remaining tasks

TBD

User interface changes

TBD

API changes

A new method Drupal\Core\Database\Condition::compare() has been added. To compare two fields with each other.

Data model changes

None

Release notes snippet

TBD

📌 Task
Status

RTBC

Version

11.0 🔥

Component
Database 

Last updated 3 days ago

  • Maintained by
  • 🇳🇱Netherlands @daffie
Created by

🇳🇱Netherlands daffie

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @daffie
  • Status changed to Needs review about 1 year ago
  • 🇳🇱Netherlands daffie

    The patch is for D10.3

  • last update about 1 year ago
    30,492 pass
  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 8.1 & MariaDB 10.3.22
    last update about 1 year ago
    30,492 pass
  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 8.1 & pgsql-14.1
    last update about 1 year ago
    30,490 pass, 2 fail
  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 8.1 & sqlite-3.27
    last update about 1 year ago
    30,492 pass
  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 8.1 & pgsql-14.1
    last update about 1 year ago
    30,492 pass
  • Status changed to Needs work about 1 year ago
  • The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

    This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

  • Open in Jenkins → Open on Drupal.org →
    Environment: PHP 8.1 & MariaDB 10.3.22
    last update about 1 year ago
    30,492 pass
  • Status changed to Needs review about 1 year ago
  • 🇮🇳India Nitin shrivastava

    @daffie #2 patch applied succesfully on drupal 11.x

  • 🇳🇱Netherlands daffie

    Hide the patch files.

  • Pipeline finished with Success
    about 1 year ago
    Total: 2469s
    #43809
  • 🇮🇹Italy mondrake 🇮🇹

    Big work, @daffie!

    I put some comments so far; still trying to grasp the entire concept. I see views is impacted heavily, will need someone knowing it better to review it.

  • 🇮🇹Italy mondrake 🇮🇹

    If we are deprecating passing a string as $conditions to Select::addJoin(), I think also ::join(), ::leftJoin() and ::innerJoin() should do the same.

  • 🇳🇱Netherlands daffie

    If we are deprecating passing a string as $conditions to Select::addJoin(), I think also ::join(), ::leftJoin() and ::innerJoin() should do the same.

    The methods ::join(), ::leftJoin() and ::innerJoin() are wrapper methods on top of Select::addJoin(). If you deprecate those on their own, you will get 2 deprecations. One from the wrapper method and one from Select::addJoin().

  • Status changed to Needs work about 1 year ago
  • 🇮🇹Italy mondrake 🇮🇹
    1. How about adding a helper method like this (untested) to the Select class:
        public function joinCondition(array ...$joins): ConditionInterface {
          $condition = $this->connection->condition('AND');
          foreach ($joins as $join) {
            assert(count($join) === 2 || count($join) === 3);
            $condition->compare($join[0], $join[1], $join[2] ?? '=');
          }
          return $condition;
        }
      

      that would IMHO make DX better: instead of

      $query->leftJoin($this->dataTable, 'data', $this->database->condition('AND')->compare("revision.$this->idKey", "data.$this->idKey")->compare("revision.$this->langcodeKey", "data.$this->langcodeKey"));
      

      we would use

      $query->leftJoin($this->dataTable, 'data', $query->joinCondition(
        ["revision.$this->idKey", "data.$this->idKey"],
        ["revision.$this->langcodeKey", "data.$this->langcodeKey"]
      ));
      

      Also, the $operator argument of the compare() method can be made mandatory.

    2. Never mind the field vs column braindump. Still, I find the naming of compare too vague. How about compareFields?
    3. I do not see a problem in getting 2 deprecation messages for #10, and really think we need to deprecate all the related methods.
    4. I think we likely need to split this MR in one adding the feature and one doing the conversions, to make reviewing more palatable. But let's do that later.
  • Status changed to Needs review about 1 year ago
  • 🇳🇱Netherlands daffie

    @mondrake: I like your idea and I have 2 variants I would like to propose.

    We create a helper method that would return the Condition object:

    public function joinCondition(string $conjunction = 'AND') {
      return $this->connection->condition($conjunction);
    }
    

    The query before

    $query->leftJoin($this->dataTable, 'data', $this->database->condition('AND')->compare("revision.$this->idKey", "data.$this->idKey")->compare("revision.$this->langcodeKey", "data.$this->langcodeKey"));
    

    The query after

    $query->leftJoin($this->dataTable, 'data', 
      $query->joinCondition()
        ->compare("revision.$this->idKey", "data.$this->idKey")
        ->compare("revision.$this->langcodeKey", "data.$this->langcodeKey")
    );
    

    We allow the join condition is an array of arrays with each inner array having two values that must be equal. When it is not a simple join than the Condition object must be used.

    The query before

    $query->leftJoin($this->dataTable, 'data', $this->database->condition('AND')->compare("revision.$this->idKey", "data.$this->idKey")->compare("revision.$this->langcodeKey", "data.$this->langcodeKey"));
    

    The query after

    $query->leftJoin($this->dataTable, 'data', [ 
      ["revision.$this->idKey", "data.$this->idKey"],
      ["revision.$this->langcodeKey", "data.$this->langcodeKey"]
    ]);
    

    For 11.2 I personally like compare() more, because it is closer to condition(). But I can live with compareFields().

    For 11.3 If you think that is better, then lets do that.

    @mondrake: Thank you for the reviewing and helping to get this issue to land.

  • 🇮🇹Italy mondrake 🇮🇹

    Hi @daffie

    #12.1 - I like the first option! Keeps things open for uncommon join statements.

    #12.2 - How about asking in Slack others' pov? So we don't go one way or another and then hit the wall later.

    #12.3 - I do think that, but once again how about asking in Slack?

  • Pipeline finished with Success
    about 1 year ago
    Total: 807s
    #44874
  • Pipeline finished with Failed
    about 1 year ago
    Total: 170s
    #44925
  • Pipeline finished with Success
    about 1 year ago
    Total: 636s
    #44934
  • Pipeline finished with Failed
    about 1 year ago
    #44965
  • Pipeline finished with Failed
    about 1 year ago
    Total: 172s
    #45343
  • Pipeline finished with Failed
    about 1 year ago
    Total: 678s
    #45388
  • Pipeline finished with Failed
    about 1 year ago
    Total: 1045s
    #45538
  • Pipeline finished with Success
    about 1 year ago
    #46325
  • Pipeline finished with Failed
    about 1 year ago
    #46363
  • Pipeline finished with Success
    12 months ago
    Total: 878883s
    #46567
  • 🇺🇸United States smustgrave

    Is there a recommended way for testing this one?

  • 🇺🇸United States smustgrave

    Just following up if any recommended way of testing or if this needs any sign off?

  • Pipeline finished with Failed
    10 months ago
    Total: 166s
    #86680
  • Pipeline finished with Failed
    10 months ago
    #86686
  • 🇸🇰Slovakia poker10

    Adding a note here, that in this Slack thread https://drupal.slack.com/archives/C1BMUQ9U6/p1699266961237289, there were concerns raised about adding so much deprecations (especially for contrib modules). Proposed approach was to either separate deprecations into a follow-up issue, or make the first step without deprecations - just the new functionality. It would also make the reviews here easier.

  • Pipeline finished with Failed
    9 months ago
    Total: 168s
    #91074
  • Pipeline finished with Failed
    9 months ago
    Total: 512s
    #91104
  • Pipeline finished with Failed
    9 months ago
    Total: 481s
    #91849
  • Pipeline finished with Success
    9 months ago
    Total: 601s
    #91861
  • 🇳🇱Netherlands daffie

    In the MR are the changes for the join and relationship plugins of the views module removed. I have created 📌 [PP-1] Make the conditions in joins in dynamic queries use Condition objects in the Views module Postponed as a followup to make the changes to the views module.

  • 🇳🇱Netherlands daffie

    The deprecation of the use of string conditions in joins in dynamic queries has been removed from the MR and a followup has been created for the deprecation ( 📌 [PP-2] Deprecate the use of string conditions in joins in dynamic queries Postponed ).

  • 🇺🇸United States smustgrave

    So wasn't 100% sure how to best review this one

    And of course gitlab is freezing up but one nitpicky thing I saw was

    public function compare( could these new functions have a return typehint

    As far as using the code I applied the patch for about an hour. Creating views with multiple relationships, created content and media, used layout builder and block layout. Just trying to make sure I touched as much as I could and nothing appeared to break.

    I checked the CR and it reads well, the examples were extremely helpful so thank you for that!

    For me I'm a +1 for giving this a shot but will leave in review for a bit longer for more eyes.

  • Pipeline finished with Success
    9 months ago
    Total: 793s
    #101243
  • Pipeline finished with Success
    9 months ago
    #101602
  • Status changed to Needs work 9 months ago
  • The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

    This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

  • Pipeline finished with Success
    9 months ago
    Total: 1303s
    #101618
  • Pipeline finished with Success
    9 months ago
    Total: 3740s
    #101653
  • Status changed to Needs review 9 months ago
  • 🇳🇱Netherlands daffie

    @smustgrave: Thank for the review.

    I have added the return type hint to the new method Condition::compare().

  • Status changed to RTBC 9 months ago
  • 🇺🇸United States smustgrave

    Thanks! Think I'm going to mark it so we can have it for 10.3 (not sure the cut off). But also see it's passing all database tests.

  • Status changed to Needs work 8 months ago
  • The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

    This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

  • Pipeline finished with Failed
    2 months ago
    Total: 216s
    #284428
  • Pipeline finished with Canceled
    2 months ago
    Total: 326s
    #284436
  • Pipeline finished with Success
    2 months ago
    Total: 4244s
    #284442
  • Status changed to RTBC about 2 months ago
  • 🇳🇱Netherlands daffie

    Rerolled for 11.x. Back to RTBC.

  • The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

    This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

  • Pipeline finished with Success
    25 days ago
    Total: 642s
    #315895
  • 🇳🇱Netherlands daffie

    Rebased and bacj to RTBC.

  • The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

    This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

  • Pipeline finished with Success
    11 days ago
    Total: 835s
    #328997
  • 🇳🇱Netherlands daffie

    Rebased the PR. Back to RTBC.

  • 🇮🇹Italy mondrake 🇮🇹

    @daffie couple comments inline

  • Pipeline finished with Failed
    10 days ago
    Total: 109s
    #330127
  • Pipeline finished with Failed
    10 days ago
    Total: 536s
    #330137
Production build 0.71.5 2024