Problem/Motivation

Currently we have in the SelectInterface the following methods: join(), innerJoin(), leftJoin() and rightJoin(). They are non-fluent and that makes creating queries with them less beautiful and less readable. So, from:

  $query = db_select('node', 'n')
    ->fields('n', array('nid'))
    ->condition('n.language', $langcode);
  $query->join('field_data_field_home_code', 'hc', 'hc.entity_id = n.nid);
  $nids = $query
    ->condition('hc.field_home_code_value', $house_code)
    ->execute()
    ->fetchCol();

To:

  $nids = db_select('node', 'n')
    ->fields('n', array('nid'))
    ->condition('n.language', $langcode);
    ->join('field_data_field_home_code', 'hc', 'hc.entity_id = n.nid);
    ->condition('hc.field_home_code_value', $house_code)
    ->execute()
    ->fetchCol();

Proposed resolution

  1. The two new methods need to be added to the Drupal\Core\Database\Query\SelectInterface (leftInnerJoin() and leftOuterJoin()). The return value will become $this.
  2. Implement the two new methods in the class Drupal\Core\Database\Query\Select. Use the method addJoin to create their functionality and the return value will be $this.
  3. The two new methods need testing. Use Drupal\KernelTests\Core\Database\SelectComplexTest::testDefaultJoin() and Drupal\KernelTests\Core\Database\SelectComplexTest::testLeftOuterJoin() as examples and create two new tests (testLeftInnerJoinFluent() and testLeftOuterJoinFluent()).
  4. Deprecate the methods join(), innerJoin() and leftJoin() on the Drupal\Core\Database\Query\SelectInterface. The method rightJoin() does not need to be deprecated, because it is already deprecated.
  5. Remove the usages of the methods join(), innerJoin() and leftJoin() in the rest of Drupal core. If the join alias is needed then use the method addJoin(). If he join alias is not needed use the method leftInnerJoin() or leftOuterJoin().

Remaining tasks

  1. Write patch
  2. Review patch
  3. Create a change record

User interface changes

None.

API changes

Yes. Two methods are added to Drupal\Core\Database\Query\SelectInterface (leftInnerJoin() and leftOuterJoin()). Three methods are deprecated on Drupal\Core\Database\Query\SelectInterface (join(), innerJoin(), leftJoin()).

Data model changes

None.

CommentFileSizeAuthor
fluent_api.patch1.78 KBchx

Comments

Crell’s picture

Status: Needs review » Closed (works as designed)

chx, we've been over this many times. The ability to return the assigned alias is mandatory. Full stop. The fields() method was added specifically to provide a fluent alternative to addField() so that addField() could still return the alias. This patch would break functionality.

If you want to make the select builder more fluent (making every method fluent is simply not on the table), suggest alternates for addExpression() or make the tagging and altering methods fluent, in a new issue.

chx’s picture

Title: Make the select builder fluent » Make db_select methods consistent
Status: Closed (works as designed) » Needs review

If ths is by design then this is the wrong design. The API should be consistent. There is nothing wrong with a reference argument.

damien tournoud’s picture

I agree with chx, addField() and addExpression() are of the few methods that don't return $this.

chx’s picture

It's of particular interest that in core none of the addField calls use the returned alias.

Crell’s picture

I am deliberately being forward-looking here to complex alter statements and hopefully allowing Views to leverage the core query builder. Views asks for an automatic alias for virtually everything.

Input/output parameters are a nightmare to work with. They also break the entire concept of fluency because you have to initialize them first in a separate statement, pass them in, and then check them afterward. That's ridiculous, and far more work than it's worth.

There is absolutely zero reason to make addField() return $this. fields() was already added specifically to fill that need as well the ability to add multiple fields at once. I spent 2 hours talking to webchick about this already when we came to that conclusion. We've already solved this problem.

Making every single method on SelectQuery fluent is nonsensical, and a pointless religious adherence to fluency. I like fluent APIs, really, but to say that every method must be fluent is just silly.

Should addTag() in QueryAlterableInterface be fluent? Yeah, probably, since it doesn't return anything now. Let's do. What about hasTag()? Shall we pass in a variable that gets set to boolean TRUE or FALSE by reference just so that's fluent as well? Of course not, that would be moronic. As many methods should be fluent as possible while still having a clean API, and no more.

The correct solution, which is what we've already started doing with fields(), is to offer multiple mechanisms. That's nice and cheap and gives us the most flexibility. One thing I've considered is making join() fluent and requiring the use of innerJoin(), leftJoin(), and outerJoin() if you want the alias returned. That would fit with the way that fields() vs. addFields() now work. We can probably do something similar for addExpression().

Status: Needs review » Needs work

The last submitted patch failed testing.

dave reid’s picture

Is this the reason we can't do the following (unlike db_insert, db_update, db_merge, and db_delete)?

$result = db_select('mytable', 'm')
  ->addField('m', 'field1')
  ->condition('field1', 1)
  ->execute();

And we have to do this instead:

$query = db_select('mytable', 'm');
$query->addField('m', 'field1');
$query->condition('field1', 1);
$result = $query->execute();
Crell’s picture

You can do this already:

$result = db_select('mytable', 'm')
  ->fields('m', array('field1'))
  ->condition('field1', 1)
  ->execute();

fields() is for adding multiple fields at once when you don't need the alias, and is therefore chainable. addField() is for adding one field at a time when you do care about the alias, and is therefore not chainable. The condition() method has always been chainable.

There is no problem here.

Crell’s picture

Title: Make db_select methods consistent » Add fluent version of join()
Version: 7.x-dev » 8.x-dev

I guess we'll get back to this later.

fietserwin’s picture

Join absolutely should be fluent:

Ugly:

function mymodule_get_by_code($house_code, $langcode) {
  $query = db_select('node', 'n')
    ->fields('n', array('nid'))
    ->condition('n.language', $langcode);
  $query
    ->join('field_data_field_home_code', 'hc', 'hc.entity_id = n.nid and hc.language = n.language');
  $nids = $query
    ->condition('hc.field_home_code_value', $house_code)
    ->execute()
    ->fetchCol();
  return node_load(reset($nids));

Better:

  $nids = db_select('node', 'n')
    ->fields('n', array('nid'))
    ->condition('n.language', $langcode);
    ->join('field_data_field_home_code', 'hc', 'hc.entity_id = n.nid and hc.language = n.language');
    ->condition('hc.field_home_code_value', $house_code)
    ->execute()
    ->fetchCol();
  return node_load(reset($nids));

I can understand the reason that views wants auto-aliases, but they are about the only ones. Furthermore %alias can be used within the join itself. Use of a created alias further on can be done via optional reference arguments. However, the idea of only making join fluent has also its merits. But I think that it would be better to reserve addJoin for that and thus make join, innerJoin, leftJoin and rightJoin all fluent.

Summarizing: a +1 for this request with the following details:
- join, innerJoin, leftjoin and rightJoin become fluent without reference arguments.
- addJoin remains non-fluent and returns the (created) alias.

xjm’s picture

Adding an additional method as was done for fields() and addField() seems like a good idea if we need to have non-fluent variants. #10 sounds good as well.

We'd also want to clearly document which methods fall into which category. Would standard pattern for naming help, or be too much API shuffling for too little gain?

I also opened #1336726: Document which database methods can be chained (make sure it's correct).

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

daffie’s picture

Title: Add fluent version of join() » Make join(), innerJoin(), leftJoin() and rightJoin() fluent and leave addJoin() non-fluent.
Version: 8.1.x-dev » 9.x-dev
Issue summary: View changes
Issue tags: +API change

Updating the IS and moving this to D9.

catch’s picture

Version: 9.x-dev » 8.3.x-dev

We could add new, fluent, methods in 8.x, moving back for now.

daffie’s picture

Title: Make join(), innerJoin(), leftJoin() and rightJoin() fluent and leave addJoin() non-fluent. » Create SelectInterface::leftInnerJoin() and SelectInterface::leftOuterJoin() and make them fluent.
Issue summary: View changes
Issue tags: -API change +API addition, +Needs change record

Updated the IS with what needs to be done.

daffie’s picture

Issue summary: View changes
Issue tags: +Novice

I will do the reviewing for this.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

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

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

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.

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.

mariacha1’s picture

Issue tags: -Novice

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.