Skip to content
This repository has been archived by the owner on Feb 4, 2023. It is now read-only.

Support for join conditions (using 'WITH') #820

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Datatable/Column/AbstractColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,14 @@ abstract class AbstractColumn implements ColumnInterface
*/
protected $joinType;

/**
* Join conditions, if the column represents an association.
* Default: null
*
* @var null|string
*/
protected $joinConditions;

/**
* The data type of the column.
* Is set automatically in ColumnBuilder when 'null'.
Expand Down Expand Up @@ -321,6 +329,7 @@ public function configureOptions(OptionsResolver $resolver)
'width' => null,
'add_if' => null,
'join_type' => 'leftJoin',
'join_conditions' => null,
'type_of_field' => null,
'responsive_priority' => null,
));
Expand All @@ -341,6 +350,7 @@ public function configureOptions(OptionsResolver $resolver)
$resolver->setAllowedTypes('width', array('null', 'string'));
$resolver->setAllowedTypes('add_if', array('null', 'Closure'));
$resolver->setAllowedTypes('join_type', 'string');
$resolver->setAllowedTypes('join_conditions', array('null', 'string'));
$resolver->setAllowedTypes('type_of_field', array('null', 'string'));
$resolver->setAllowedTypes('responsive_priority', array('null', 'int'));

Expand Down Expand Up @@ -817,6 +827,30 @@ public function setJoinType($joinType)
return $this;
}

/**
* Get join conditions.
*
* @return string
*/
public function getJoinConditions()
{
return $this->joinConditions;
}

/**
* Set join conditions.
*
* @param string $joinConditions
*
* @return $this
*/
public function setJoinConditions($joinConditions = null)
{
$this->joinConditions = $joinConditions;

return $this;
}

/**
* Get type of field.
*
Expand Down
2 changes: 2 additions & 0 deletions Resources/doc/columns.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ With 'null' initialized options uses the default value of the DataTables plugin.
| width | null or string | null | | Column width assignment. |
| add_if | null or Closure | null | | Add column only if conditions are TRUE. |
| join_type | string | 'leftJoin' | | Join type (default: 'leftJoin'), if the column represents an association. |
| join_conditions | null or string | null | | Join conditions (using WITH), if the column represents an association. |
| type_of_field | null or string | null (autodetect) | | Set the data type itself for ordering (example: integer instead string). |
| responsive_priority | null or int | null | | Set column's visibility priority. Requires the Responsive extension. |
| filter | array | TextFilter | | A Filter instance for individual filtering. |
Expand Down Expand Up @@ -117,6 +118,7 @@ $this->columnBuilder
'data' => 'comments[,].title',
'searchable' => true,
'orderable' => true,
'join_conditions' => 'comments.reviewed = 1',
))
;
```
Expand Down
21 changes: 17 additions & 4 deletions Response/DatatableQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ private function initColumnArrays()
$this->addSearchColumn($column, null, $searchDql);
} elseif (true === $this->accessor->getValue($column, 'selectColumn')) {
$parts = explode('.', $dql);
$withExpr = $this->accessor->getValue($column, 'joinConditions');

while (count($parts) > 1) {
$previousPart = $currentPart;
Expand All @@ -263,8 +264,14 @@ private function initColumnArrays()
$currentPart = array_shift($parts);
$currentAlias = ($previousPart === $this->entityShortName ? '' : $previousPart.'_').$currentPart;

if (!array_key_exists($previousAlias.'.'.$currentPart, $this->joins)) {
$this->addJoin($previousAlias.'.'.$currentPart, $currentAlias, $this->accessor->getValue($column, 'joinType'));
$columnTableName = $previousAlias.'.'.$currentPart;
if (!array_key_exists($columnTableName, $this->joins)) {
$this->addJoin($columnTableName, $currentAlias, $this->accessor->getValue($column, 'joinType'));
}
// for the last dql part join using WITH, if expression is given for column
if (count($parts) === 1 && array_key_exists($columnTableName, $this->joins) && null !== $withExpr) {
$with = str_replace($currentPart . '.', $currentAlias . '.', $withExpr);
$this->addJoin($columnTableName, $currentAlias, $this->accessor->getValue($column, 'joinType'), $with);
Copy link
Collaborator

@Seb33300 Seb33300 Sep 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have a "complex" join like table1.table2.table3.field, maybe we want to set a different join condition on each join.
How do you handle it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's possible by adding a column of each joined table to the column builder and defining the respective join condition.

$this->columnBuilder
    ->add('table1.table2.id', Column\Column::class, array(
        'visible' => false,
        'join_conditions' => 'table2.enabled = 1'
    ));

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you tried it?
Is the table2.enabled = 1 not applied on the table1.table2 join?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, its applied on the table1.table2 join. If you then add a join to table3 you have different join conditions on each join. Or am I misunderstanding the question?

$this->columnBuilder
    ->add('table1.id', Column\Column::class, array()) // see comment below
    ->add('table1.table2.id', Column\Column::class, array(
        'join_conditions' => 'table2.enabled = 1'
    ))
    ->add('table1.table2.table3.id', Column\Column::class, array()) // see comment below
    ->add('table1.table2.table3.field', Column\Column::class, array(
        'join_conditions' => 'table3.otherField = 0'
    ));

During my tests I noticed that the joins only worked if the ids of the joined tables were explicitly included as columns in the datatable. Otherwise doctrine will throw an exception Cannot select distinct identifiers from query with LIMIT and ORDER BY on a column from a fetch joined to-many association. Use output walkers. But this also happens without the changes I made in this PR and the usage of join_conditions.

}

$metadata = $this->setIdentifierFromAssociation($currentAlias, $currentPart, $metadata);
Expand Down Expand Up @@ -395,7 +402,11 @@ private function setSelectFrom(QueryBuilder $qb)
private function setJoins(QueryBuilder $qb)
{
foreach ($this->joins as $key => $value) {
$qb->{$value['type']}($key, $value['alias']);
if (\array_key_exists('with', $value) && null !== $value['with']) {
$qb->{$value['type']}($key, $value['alias'], Query\Expr\Join::WITH, $value['with']);
} else {
$qb->{$value['type']}($key, $value['alias']);
}
}

return $this;
Expand Down Expand Up @@ -723,14 +734,16 @@ private function addSearchOrderColumn($column, $columnTableName, $data)
* @param string $columnTableName
* @param string $alias
* @param string $type
* @param string $with
*
* @return $this
*/
private function addJoin($columnTableName, $alias, $type)
private function addJoin($columnTableName, $alias, $type, $with = null)
{
$this->joins[$columnTableName] = array(
'alias' => $alias,
'type' => $type,
'with' => $with,
);

return $this;
Expand Down