The Relation module adds a field type "endpoint", which has three columns: entity_type, entity_id and r_index. When querying the entities with this field, I would like to be able to add conditions on these three columns. Core's EntityFieldQuery supports fieldCondition(), but when invoking this method three times, the endpoint data table is joined three times, one time for each condition. I want it to be joined once and those three conditions should apply to that one join.

The code is:

$this->fieldCondition('endpoints', 'entity_type', $entity_type, '=', $this->delta_group);
$this->fieldCondition('endpoints', 'entity_id', $entity_id, NULL, $this->delta_group);
$this->fieldCondition('endpoints', 'r_index', $r_index, '=', $this->delta_group);

The query contains:

FROM field_data_endpoints field_data_endpoints0
INNER JOIN field_data_endpoints field_data_endpoints1 ON field_data_endpoints1.entity_type = field_data_endpoints0.entity_type AND field_data_endpoints1.entity_id = field_data_endpoints0.entity_id
INNER JOIN field_data_endpoints field_data_endpoints2 ON field_data_endpoints2.entity_type = field_data_endpoints0.entity_type AND field_data_endpoints2.entity_id = field_data_endpoints0.entity_id

(field_data_endpoints0.endpoints_entity_type = 'node') AND
(field_data_endpoints1.endpoints_entity_id = '127793') AND
(field_data_endpoints1.delta = field_data_endpoints0.delta) AND
(field_data_endpoints2.endpoints_r_index = '0') AND
(field_data_endpoints2.delta = field_data_endpoints0.delta) AND
(field_data_endpoints0.deleted = '0') AND
(field_data_endpoints0.entity_type = 'relation') AND
(field_data_endpoints0.bundle = 'media')

I want just one field_data_endpoints join with three conditions on endpoints_entity_type, endpoints_entity_id and endpoints_r_index instead of three joins with one condition each.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Jorrit’s picture

Status: Active » Needs review
FileSize
4.53 KB

This patch contains just a test, demonstrating the issue.

Status: Needs review » Needs work

The last submitted patch, multicolumn-1859084-1.patch, failed testing.

Jorrit’s picture

Status: Needs work » Needs review
FileSize
4.59 KB

Eclipse still can't make valid patches ....

Status: Needs review » Needs work

The last submitted patch, multicolumn-1859084-3.patch, failed testing.

Jorrit’s picture

Status: Needs work » Needs review
FileSize
6.46 KB

Fix + patch

mikran’s picture

Version: 7.x-dev » 8.x-dev
Category: support » bug

Fixing category, and also does this bug exist in D8 EFQ?

Jorrit’s picture

The code has been changed much compared to Drupal 7. From the looks of it, the bug doesn't occur anymore. I will try to port the test to confirm this. Does this need to be done before a commit to Drupal 7 is considered?

Jorrit’s picture

Version: 8.x-dev » 7.x-dev

I did not have time to test this in Drupal 8. Is it still possible to have this patch reviewed/committed for Drupal 7?

chx’s picture

> Use the same alias for fields on the same field for the same delta, for instance for two conditions on different columns of the same field.

That's what delta group is for.

Jorrit’s picture

Except that it doesn't work, that is the point of this issue. I am disappointed that you suggest delta group.

As is demonstrated in the initial post of this issue, adding three conditions on the same field with the same delta group results in three separate joins, whereas a much more performant way would be to add one join with three WHERE conditions on that single joined table.

chx’s picture

Status: Needs review » Reviewed & tested by the community

I am so sorry for not reviewing this more carefully. Now I did. I think it's good to go. I am slightly surprised the existing tests didn't catch this.

As for D8, this is all gone and a new system is in place with clean condition groups.

David_Rothstein’s picture

Version: 7.x-dev » 8.x-dev
Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs backport to D7

I think we need to get the tests into Drupal 8 first. (Unless there are equivalent tests already there? But I didn't see any.)

Looking at the code a bit:

     $table_alias = $tablename . $key;
+    // Use the same alias for fields on the same field for the same delta, for instance
+    // for two conditions on different columns of the same field.
+    if (!empty($query->fieldConditions[$key]) && is_int($query->fieldConditions[$key]['delta_group'])) {
+      $table_alias = $tablename . '_delta_' . $query->fieldConditions[$key]['delta_group'];
+    }

1. Is it safe to remove $key from the table alias? (I.e., is it possible that two different fields on the same table will be queried in a way that happens to use the same delta group, and if so, will that break things?)
2. I don't understand the origin of the is_int() check. Is it documented that 'delta_group' has to be an integer? (All the documentation I see says "arbitrary identifier".)

+    $this->assertEqual(1, count($result), t('One result should be returned, got @count', array('@count'
....
+    $this->assertNotNull($query, t('Precondition: the query should be available'));

Minor, but don't use t() in assertion messages. Use format_string() for cases like the first, and just a regular untranslated string for cases like the second.

+/**
+ * Implements hook_query_TAG_alter().
+ *
+ * Saves the query in a global variable to allow tests.
+ */
+function field_sql_storage_query_test_alter($query) {
+  $GLOBALS['test_query'] = $query;
 }

I think it's a little more standard to use a variable_set() here (or state()->set() in Drupal 8).

Jorrit’s picture

  1. Each condition has its own key and as such its own table join. The reason for not using the key is because after this patch, different conditions will have share the same table join in case they have the same delta_group. In that case, delta_group assumes the role that $key had previously: making the table aliases unique..
  2. The delta field is an integer when using the SQL storage engine and delta_group will always be equated to the delta field, so I assumed an is_int() check would work. I can change this to a !is_empty() if you like. never mind, I see that the value doesn't matter.
  3. I'll change that.
  4. $query is an object and doesn't need to be persisted like variable_set() does. Is it really better to use variable_set()? I just need to save the value so testFieldSqlStorageMultipleConditionsDifferentColumns() can fetch it later. I can also use a static getter/setter.

Now I come to think of it: what I did for delta_group may be the same for language_group. I will look into that and into Drupal 8. In any case, thanks for reviewing! I am glad this issue draws a bit of attention.

Jorrit’s picture

Version: 8.x-dev » 7.x-dev
Status: Needs work » Needs review
FileSize
6.68 KB
10.34 KB

I have updated the patch with your comments. I'll change the version again after the QA-bot is done.

chx’s picture

Issue tags: -Needs backport to D7

Dont change the version, EntityFieldQuery is gone completely from D8 and entity_query that replaces it is totally different.

mikran’s picture

Status: Needs review » Reviewed & tested by the community

I'm very happy with the improvements this patch achieves.

adamwhite’s picture

I ran across this issue via #1649398: Performance issues with many relations and the patch in #14 seems to have the performance of my relation-heavy site under control. No problems so far.

burningdog’s picture

I came here via #1970156: MySQL can only use 61 tables in a join: field_data_endpoints on node_save. The patch from #14 does solve the issue (albeit temporarily - as the number of Relations grow so the number of joins will grow until they exceed MySQL's limit again), but when running all tests on all modules which are part of my site, a number of tests fail. Is that to be expected with a core patch like this?

David_Rothstein’s picture

Version: 7.x-dev » 8.x-dev
Status: Reviewed & tested by the community » Needs review
Issue tags: +Needs backport to D7

The parts I've looked at look pretty good to me now, but the tests still need to go in Drupal 8 first - again, unless someone can point to where equivalent tests already exist? (It doesn't matter if the API changed; the tests are just testing the functionality and we need the tests to verify that the bug doesn't exist and won't exist again.)

Couple small issues in the D7 patch (I didn't review the whole thing though), mostly code style:

  1. Why is $query_tables initialized to NULL rather than an empty array?
  2. Replace "Generate" => "Generates" in the code comment.
  3. Replace $fieldkey with $field_key.
  4. Replace $hasgroupconditions with $has_group_conditions.
  5. There should be a blank line before @return (see https://drupal.org/coding-standards/docs).
  6. t() should still be removed from the test assertion messages (especially now that patches have been committed in the interim to clear those out from pretty much everywhere in Drupal core - including, I believe, the field_sql_storage.test file - so we don't want to reintroduce it).
David_Rothstein’s picture

@burningdog, do the same tests fail without this applied, or are there new test failures caused by this patch?

Core tests should in theory always pass but I think it's known that some of them are environment-specific and won't always pass for everyone.

Contrib tests, who knows... it depends entirely on the project.

Jorrit’s picture

FileSize
10.36 KB

I have implemented all suggested changes. Regarding the first comment: the $query_tables initializer could perhaps be removed, because it will always be initialized with the following line:

$query_tables =& $select_query->getTables();

Only after that line has executed, the code at $query_tables[$table_alias] will be run.

I will try to test this with Drupal 8 again. Last time I tried I could not get Drupal 8 to work, but that was a while ago.

Status: Needs review » Needs work

The last submitted patch, multicolumn-1859084-21.patch, failed testing.

Jorrit’s picture

Version: 8.x-dev » 7.x-dev
Status: Needs work » Needs review

It is not easy to port the tests to Drupal 8, the code is entirely different there. For now I change this issue to Drupal 7 to see if the test bot likes the patch.

Jorrit’s picture

Issue tags: -Needs backport to D7

#21: multicolumn-1859084-21.patch queued for re-testing.

Status: Needs review » Needs work
Issue tags: +Needs backport to D7

The last submitted patch, multicolumn-1859084-21.patch, failed testing.

Jorrit’s picture

Status: Needs work » Needs review
FileSize
10.35 KB

Retry.

attiks’s picture

Status: Needs review » Reviewed & tested by the community

THANK YOU, works like a charm

David_Rothstein’s picture

Version: 7.x-dev » 8.x-dev
Assigned: Unassigned » David_Rothstein
Issue summary: View changes
Status: Reviewed & tested by the community » Needs work

I still think it should be possible to test this in Drupal 8 too. Assigning to myself to work on that, probably sometime in the next week or two.

If I'm wrong, mea culpa in advance because it would mean that I unnecessarily delayed this patch from getting in :(

chx’s picture

Version: 8.x-dev » 7.x-dev
Status: Needs work » Reviewed & tested by the community

There's absolutely nothing that is relevant in D8 to this patch: the whole subsystem is just gone.

attiks’s picture

Rerolled for latest D7

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 30: multicolumn-1859084-30.patch, failed testing.

Jorrit’s picture

Status: Needs work » Needs review

30: multicolumn-1859084-30.patch queued for re-testing.

chx’s picture

Status: Needs review » Reviewed & tested by the community

Back to rtbc then

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 30: multicolumn-1859084-30.patch, failed testing.

Jorrit’s picture

Status: Needs work » Needs review

30: multicolumn-1859084-30.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 30: multicolumn-1859084-30.patch, failed testing.

jurcello’s picture

Status: Needs work » Needs review

30: multicolumn-1859084-30.patch queued for re-testing.

dcam’s picture

Status: Needs review » Reviewed & tested by the community

Looks like this is still RTBC.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 30: multicolumn-1859084-30.patch, failed testing.

dcam’s picture

Status: Needs work » Needs review

30: multicolumn-1859084-30.patch queued for re-testing.

chx’s picture

Status: Needs review » Reviewed & tested by the community

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 30: multicolumn-1859084-30.patch, failed testing.

Status: Needs work » Needs review
dcam’s picture

Status: Needs review » Reviewed & tested by the community

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 30: multicolumn-1859084-30.patch, failed testing.

Status: Needs work » Needs review

dcam queued 30: multicolumn-1859084-30.patch for re-testing.

dcam’s picture

Status: Needs review » Reviewed & tested by the community
Fabianx’s picture

RTBC+1

mgifford’s picture

The latest code has been marked RTBC for 4 months now. What's needed for this to get in an upcoming release?

@David_Rothstein let me know if it's useful to put this in a production environment & report back.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 30: multicolumn-1859084-30.patch, failed testing.

Status: Needs work » Needs review
dcam’s picture

Status: Needs review » Reviewed & tested by the community

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 30: multicolumn-1859084-30.patch, failed testing.

Status: Needs work » Needs review

chx queued 30: multicolumn-1859084-30.patch for re-testing.

dcam’s picture

Status: Needs review » Reviewed & tested by the community

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 30: multicolumn-1859084-30.patch, failed testing.

Status: Needs work » Needs review
mgifford’s picture

Status: Needs review » Reviewed & tested by the community
David_Rothstein’s picture

I'm still not sure this can't be tested in Drupal 8, but I never got around to working on it and it missed one release because of that (sorry) so let's just get it in for now.

However, this:

+++ b/modules/field/modules/field_sql_storage/field_sql_storage.test
....
+/**
+ * Implements hook_query_TAG_alter().
+ *
+ * Saves the query in a global variable to allow tests.
+ */
+function field_sql_storage_query_test_alter($query) {
+  $GLOBALS['test_query'] = $query;
 }

We can get away with a global variable, but not with putting this directly in a .test file (and with a non-test-module namespace) :) I didn't notice before that that's where it was.

I cleaned that up in the attached patch and moved it to the field_test module instead. Everything else looked good and seems to work well, so since I only changed the tests I'll commit this if it still passes tests.

It's worth noting (and I will in the commit message) that this patch only fixes the issue when a delta or language group are being used, rather than in the general case. For example, something like this:

  $query = new EntityFieldQuery();
  $query->fieldCondition('body', 'value', 'first excluded value', '!=');
  $query->fieldCondition('body', 'value', 'second_excluded_value', '!=');
  $result = $query->execute();

still results in an unnecessary inner join with this patch applied. So there might be room to fix this further... However, limiting it to queries with a delta or language group is fine for now (and means the impact is much less in case the patch winds up breaking something, which is good also).

chx’s picture

> I'm still not sure this can't be tested in Drupal 8,

I am. The whole subsytem is rewritten from the ground up. There's not even a trace of this code in D8. It's done 100% differently. I wrote both codebases, you can believe me.

David_Rothstein’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: +7.32 release notes

Committed to 7.x - thanks!

I have no doubt that the code is very different in Drupal 8... what I'm not sure of is whether Drupal 8 actually has some kind of similar tests (to ensure that the generated queries don't have an unnecessary number of INNER JOINs)... In any case, I'll mark this "Fixed" for now, but still leave assigned to me to remember to potentially look into that.

  • David_Rothstein committed be54be0 on 7.x
    Issue #1859084 by Jorrit, David_Rothstein, attiks: Improved database...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

David_Rothstein’s picture

Issue tags: -7.32 release notes +7.33 release notes

Drupal 7.32 was a security release, so this is now scheduled to be in 7.33.