Query builder seems unable to produce a fully working left join
- Dominant language
- PHP
- Stars
- 181
- Forks
- 102
- PR merge metrics
- No merged PRs in 30d
Description
I am attempting to use a `LEFT JOIN` behavior on child documents, but it seems to be impossible based on what I've seen.
I have the following situation:
- Tree of documents of the class `Product`
- Documents _may_ have one or more children of the class `ProductDetail`
- Attempting to design a search query which includes data from these details
Take the following query builder as an example:
``` php
$searchTerm = '12345';
$query = $queryBuilder
->fromDocument('Path\To\Product', 'p')
->addJoinLeftOuter()
->right()->document('Path\To\ProductDetail', 'pd')->end()
->condition()->child('pd', 'p')->end()
->end();
->andWhere()
->orX()
->like()->localName('p')->literal('%' . $searchTerm . '%')->end();
->andX()
->eq()->field('pd.type')->literal('title')->end()
->fullTextSearch('pd.content', $searchTerm)
->end()
->end()
->end()
->getQuery();
```
Now, PHPCR-ODM produces a JCR-SQL2 query like:
``` sql
SELECT *
FROM [nt:unstructured] AS p
LEFT OUTER JOIN [nt:unstructured] AS pd ON ISCHILDNODE(pd, p)
WHERE (((LOCALNAME(p) LIKE '%12345%' OR (pd.type = 'title' AND CONTAINS(pd.content, '12345')))
AND (p.[phpcr:class] = 'Path\To\Product' OR p.[phpcr:classparents] = 'Path\To\Product'))
AND (pd.[phpcr:class] = 'Path\To\ProductDetail' OR pd.[phpcr:classparents] = 'Path\To\ProductDetail'))
```
This all works as expected, _if and only if_ the `Product` nodes contain at least one `ProductDetail` child -- in other words, it seems to behave as an `INNER JOIN`. Any `Product` node that has no such child will not be included in the results of the query, because of two things:
1. The query is specifying `SELECT *` rather than `SELECT p.*`, which is requiring a result for the `pd` alias
2. The query is appending an `AND` clause at the end, which will only match if the alias `pd` is present and of the appropriate class
The query that I actually want is this:
``` sql
SELECT p.*
FROM [nt:unstructured] AS p
LEFT OUTER JOIN [nt:unstructured] AS pd ON ISCHILDNODE(pd, p)
WHERE (((LOCALNAME(p) LIKE '%12345%' OR (pd.type = 'title' AND CONTAINS(pd.content, '12345')))
AND (p.[phpcr:class] = 'Path\To\Product' OR p.[phpcr:classparents] = 'Path\To\Product')))
```
This works as one would expect a `LEFT JOIN` to, delivering results whose node names match "12345" regardless of whether or not they contain one or more `ProductDetail` children.
What I cannot figure out is if it is possible to get to my desired query; is it? This _seems_ to be a bug, but perhaps I am misunderstanding the intent here.
If this is intended, is there a workaround to achieve what I am trying to do? I realize I could just pass the JCR-SQL2 query to the session (my environment uses Jackrabbit) and then manually invoke hydration, but that seems a bit ugly. :)
Contributor guide
Research direction
No source file or test is named. Start by tracing the query-builder join, selection, and class-constraint generation shown in the issue, then compare the generated JCR-SQL2 with the desired SELECT p.* form. Done means products without ProductDetail children remain results while matching detail fields still work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php, sql
- Domain
- database
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100