Problem/Motivation

From #1986606: Convert the comments administration screen to a view. If you create a view, add a relationship to the view without "require this relationship" checked, and then add a handler that uses a table with the join declared as INNER in hook_views_data(), the inner join is respected and strips the rows that don't match in the base table. Since the joined table is logically part of the optional relationship, this is unexpected behavior.

#1986606-125: Convert the comments administration screen to a view shows an example of this but it probably won't be reproducible later if we fix that as proposed in the issue now.

Proposed resolution

Add a new relationship handler which allows "implicit joins" to be configured. This allows users to mark implicit relationships as "not required", thereby forcing them to LEFT instead of INNER joins. Existing views are not affected.

Remaining tasks

Check whether the problem still exists. Comment #30 suggests a way to create a view in order to test the problem. (This is a Novice task.)

User interface changes

None.

API changes

Added ViewsHandlerInterface:hidden() to determine whether a handle should be visible in the UI. Used to hide "implicit join relationship handlers" from the "Relationship" dropdowns on other handlers.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dawehner’s picture

Thank you to create an issue!

Relationships are optional by default. The question is whether we should fall back to the defined join type by default and provide an explicit "force optional"
or whether optional means optional all the time. The problem with the second behavior is that LEFT joins are often slower ...

jibran’s picture

bforchhammer’s picture

I just ran into this problem as well...

My temporary fix is to just unset the join type for the node_field_data table. Could we just do this in core? Is there any particular reason why the field-data joins should be inner joins instead of left joins?

function MODULE_views_data_alter(array &$data) {
  unset($data['node_field_data']['table']['join']['node']['type']);
}
bforchhammer’s picture

Status: Active » Needs review
FileSize
1.13 KB

Removed inner joins from all field-data tables.

Status: Needs review » Needs work

The last submitted patch, 4: field-data-left-joins-2273849-4.patch, failed testing.

jibran’s picture

Is there any particular reason why the field-data joins should be inner joins instead of left joins?

yeah it is required for translation of the entities.

bforchhammer’s picture

Related issues: +#1969208: Query node access alter filtering out accesible nodes

Just a side-note: if we manage to fix this problem and allow optional relationships in some way, there is also a bug in the node-access system which can cause nodes to be filtered out similar to what the INNER JOIN currently does. Might be important to know for testing ;-) See #1969208: Query node access alter filtering out accesible nodes.

bforchhammer’s picture

Assigned: Unassigned » bforchhammer
idebr’s picture

idebr’s picture

bforchhammer’s picture

Status: Needs work » Needs review
FileSize
4.97 KB

Attached patch adds a relationship handler which allows to configure the automatically added "implicit joins". This shouldn't break anything, but allows users to mark implicit relationships as "not required", thereby forcing them to LEFT instead of INNER joins.

benjifisher’s picture

I am testing the patch in #11. (My original goal is to work on #1969208: Query node access alter filtering out accesible nodes.) But I do not see where to configure the relationship. :-(

bforchhammer’s picture

Status: Needs review » Needs work
Issue tags: +SprintWeekend2015

The idea is that you add a new relationship, e.g. "Comment: Comment data table (implicit)" when working on comments...

Unfortunately the patch does not work yet for data tables of relationships (e.g. the data table for "parent comments"). I'm trying to figure that out now... also trying to add tests ;-)

dawehner’s picture

Seems we need some kind of additional test coverage, good catch!

bforchhammer’s picture

Status: Needs work » Needs review
FileSize
8.24 KB

Now it should work. Tests are still missing. Let's see if I broke anything. Go, testbot, go! :)

Status: Needs review » Needs work

The last submitted patch, 15: convert_inner_joins_to-2273849-15.patch, failed testing.

bforchhammer’s picture

Status: Needs work » Needs review
FileSize
11.97 KB

New day, let's try again.

Status: Needs review » Needs work

The last submitted patch, 17: convert_inner_joins_to-2273849-17.patch, failed testing.

bforchhammer’s picture

Assigned: bforchhammer » Unassigned
Status: Needs work » Needs review
FileSize
23.08 KB

Test failures should be fixed now. I have also added a test based on listing comments and parent comments in a view...

bforchhammer’s picture

FileSize
23.07 KB

Minor code style fixes.

bforchhammer’s picture

Issue tags: +SWB2015

The last submitted patch, 19: convert_inner_joins_to-2273849-19.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 20: convert_inner_joins_to-2273849-20.patch, failed testing.

bforchhammer’s picture

Status: Needs work » Needs review
FileSize
23.13 KB

And again... ;)

bforchhammer’s picture

Issue summary: View changes
jibran’s picture

Interdiffs my dear friend @bforchhammer are reviewers friend.

Status: Needs review » Needs work

The last submitted patch, 24: convert_inner_joins_to-2273849-24.patch, failed testing.

bforchhammer’s picture

FileSize
18.94 KB

Hm, that test passes locally. Random testbot failure?

@jibran sorry, here's an interdiff for the changes since yesterday's patch. Does that help? ;-)

bforchhammer’s picture

Status: Needs work » Needs review
FileSize
23.3 KB
608 bytes

I guess those use statements should not have been removed... (not having them causes Twig_Error_Runtime exceptions for anonymous users).

benjifisher’s picture

Status: Needs review » Needs work
FileSize
996 bytes

As I said earlier, I came to this issue from #1969208: Query node access alter filtering out accesible nodes. I have been experimenting with the exported view that @TechNikh attached to #35 in that issue.

I have decided that

  1. The UI for the patch in #29 (this issue) needs work.
  2. This is the wrong approach anyway.

I think I am qualified to make the claim in #1. I am less sure about the one in #2, but if I am right about that, then my opinion on #1 is irrelevant.

This is how the patch from #29 works. I have a view with a relationship (an entity reference). I de-select "Require this relationship" but I still get an inner join. I have to add a new relationship of type Content: Content data table (implicit) "using" the relationship I have already added, and again de-select the "Require this relationship" option (which is enabled by default). If I had not read the comments on this issue (specifically #13) then I would not have guessed to do that.

Here is why I think the approach is wrong, and what is the right approach. After adding a relationship and selecting "Require this relationship" the query looks something like this, according to the Views preview:

SELECT ...
FROM
{node} node
INNER JOIN {node_field_data} ...
LEFT JOIN {node__field_related_pages} ...
INNER JOIN {node} ...
INNER JOIN {node_field_data} ...
WHERE ...
LIMIT ...

If I de-select "Require this relationship", then INNER JOIN {node} changes to LEFT JOIN {node}. If I apply the patch from #29 and de-select "Require this relationship" in the (implicit) relationship, then the second INNER JOIN {node_field_data} becomes LEFT JOIN {node_field_data}. If I understand @jibran's comment in #6 (and I am way out of my depth here) then this will break entity translation.

It seems to me that we always want to use an inner join on node_field_data, but we may then choose an inner or left join on that compound table. (Probably not the right term.) That is, in summary form,

SELECT ...
FROM
({node} node
  INNER JOIN {node_field_data} ...)
LEFT JOIN {node__field_related_pages} ...
INNER JOIN ({node} ...
  INNER JOIN {node_field_data} ...)
WHERE ...
LIMIT ...

The two indented joins should be hard-coded as INNER, but the other joins are INNER or LEFT depending on whether "Require this relationship" has been selected.

In case my summary leaves too much to the imagination, I have attached foo.sql.txt. Once the node__field_related_pages (entity reference) field has been added, along with a few test nodes, I get the expected results from drush sqlc < foo.sql.txt.

lokapujya’s picture

Status: Needs work » Needs review
FileSize
2.21 KB

Here is an attempt to demonstrate the issue in a test. The existing view already creates 3 entities, 2 of which have an entity reference to another entity. When adding a relationship and filter, I'm expecting all 3 entities to show up (since the relationship is not required.) This is a work in progress from the code sprint, but I'll post it incase it helps.

Edit: This is not the right test. Here the joins are changed to left join; in this test case, it's the filter that is causing this test to fail.

Status: Needs review » Needs work

The last submitted patch, 31: 2273849-31-test-only.patch, failed testing.

lokapujya’s picture

@29 While testing, seems like it's required to add the implicit join first, before creating the node join.
@30 This is going to be hard, because AFAIK, Drupal API doesn't, yet, support the concept of parentheses around joins. I'm not even sure if it can be done in a relationship handler; I think it might be deeper in the database code.

benjifisher’s picture

@lokapujya:

Do you agree that my suggestion in #30, if we can get there, is the query we want?

Is this related to #2337509: Remove "@todo In theory we should use the data table as base table, as this would" from EntityViewsData? Maybe, instead of making complex joins (in the sense that some tables are joined and "parenthesized", and then the compound tables are joined with others) we could get the same results more simply, with fewer tables involved.

More directly: in the example above, do we need the node table at all, or is node_field_data all we need? The attached SQL file works just as well as the one I attached in #30.

I will add #2337509: Remove "@todo In theory we should use the data table as base table, as this would" from EntityViewsData as a related issue. If it is not actually related, then someone can remove it.

lokapujya’s picture

"Do you agree that my suggestion in #30, if we can get there, is the query we want?" That would work, but we might not need to go there. The issue becomes simpler if we don't have to deal with nested joins. I need to see more test cases to make sure we have all scenarios covered.

lokapujya’s picture

Maybe, we move forward with #29. It's the best solution we currently have. We can Create some tests for it. When we come across a test case that doesn't work, we can update the solution. Even if the solution in #29 has to be changed, the tests will still have some value.

I tried to write a test using entities in #31, but I couldn't import the view and test entities successfully into Drupal to modify. Maybe, I could create a test using a view with nodes, but then the simpletest will have to create the nodes and fields that are used by the view. Any ideas?

benjifisher’s picture

lokapujya’s picture

Now that #2429447: Use data table as views base table, if available. landed, let's see if it simplifies this issue.

benjifisher’s picture

Issue summary: View changes
Issue tags: +Novice

I am adding the Novice tag for the task of testing whether this issue has gone away thanks to #2429447: Use data table as views base table, if available.. I will update the issue summary.

deepakaryan1988’s picture

Issue tags: -SprintWeekend2015, -SWB2015

Removing sprint weekend tag!!
As suggested by @YesCT

deepakaryan1988’s picture

Issue tags: +SprintWeekend2015

Sorry, these issues were actually worked on during the 2015 Global Sprint
Weekend https://groups.drupal.org/node/447258

The last submitted patch, 29: convert_inner_joins_to-2273849-29.patch, failed testing.

lokapujya’s picture

Seems like D8 now uses a left join.

SELECT node_field_data.nid AS nid, node_field_data_node__field_related_pages.nid AS node_field_data_node__field_related_pages_nid
FROM 
{node_field_data} node_field_data
LEFT JOIN {node__field_related_pages} node__field_related_pages ON node_field_data.nid = node__field_related_pages.entity_id AND (node__field_related_pages.deleted = '0' AND node__field_related_pages.langcode = node_field_data.langcode)
LEFT JOIN {node_field_data} node_field_data_node__field_related_pages ON node__field_related_pages.field_related_pages_target_id = node_field_data_node__field_related_pages.nid
lokapujya’s picture

Status: Needs work » Closed (cannot reproduce)
Issue tags: -Novice
idebr’s picture

Then what about the backport to Views 7.x-3.x?

lokapujya’s picture

Project: Drupal core » Views (for Drupal 7)
Version: 8.0.x-dev » 8.x-3.x-dev
Component: views.module » Code
Status: Closed (cannot reproduce) » Needs work

Well, it would be nice to track down how it got fixed in D8 so that maybe D7 views fix can be similar.

lokapujya’s picture

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