doctrine / doctrine/phpcr-odm

Query builder seems unable to produce a fully working left join

Open
#658 23 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.