Skip to content

Commit

Permalink
Adds sort_order support for deferred binding
Browse files Browse the repository at this point in the history
  • Loading branch information
daftspunk committed Feb 21, 2024
1 parent 5a399b5 commit 58fcf6b
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 53 deletions.
149 changes: 108 additions & 41 deletions src/Database/Relations/BelongsToMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as CollectionBase;
use Illuminate\Database\Eloquent\Relations\BelongsToMany as BelongsToManyBase;
use October\Rain\Support\Facades\DbDongle;

/**
* BelongsToMany
Expand Down Expand Up @@ -55,47 +56,6 @@ public function __construct(
$this->addDefinedConstraints();
}

/**
* performJoin will join the pivot table opportunistically instead of mandatorily
* to support deferred bindings that exist in another table.
*
* This method is based on `performJoin` method logic except it uses a left join.
*
* @param \Illuminate\Database\Eloquent\Builder|null $query
* @return $this
*/
protected function performLeftJoin($query = null)
{
$query = $query ?: $this->query;

$query->leftJoin(
$this->table,
$this->getQualifiedRelatedKeyName(),
'=',
$this->getQualifiedRelatedPivotKeyName()
);

return $this;
}

/**
* shouldSelect gets the select columns for the relation query
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
protected function shouldSelect(array $columns = ['*'])
{
// @deprecated remove this whole method when `countMode` is gone
if ($this->countMode) {
return $this->table.'.'.$this->foreignPivotKey.' as pivot_'.$this->foreignPivotKey;
}

if ($columns === ['*']) {
$columns = [$this->related->getTable().'.*'];
}

return array_merge($columns, $this->aliasedPivotColumns());
}

/**
* save the supplied related model with deferred binding support.
*/
Expand Down Expand Up @@ -467,4 +427,111 @@ public function getOtherKey()
{
return $this->table.'.'.$this->relatedPivotKey;
}

/**
* shouldSelect gets the select columns for the relation query
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
protected function shouldSelect(array $columns = ['*'])
{
// @deprecated remove this whole method when `countMode` is gone
if ($this->countMode) {
return $this->table.'.'.$this->foreignPivotKey.' as pivot_'.$this->foreignPivotKey;
}

if ($columns === ['*']) {
$columns = [$this->related->getTable().'.*'];
}

return array_merge($columns, $this->aliasedPivotColumns());
}

/**
* performJoin will join the pivot table opportunistically instead of mandatorily
* to support deferred bindings that exist in another table.
*
* This method is based on `performJoin` method logic except it uses a left join.
*
* @param \Illuminate\Database\Eloquent\Builder|null $query
* @return $this
*/
protected function performLeftJoin($query = null)
{
$query = $query ?: $this->query;

$query->leftJoin(
$this->table,
$this->getQualifiedRelatedKeyName(),
'=',
$this->getQualifiedRelatedPivotKeyName()
);

return $this;
}

/**
* performSortableColumnJoin includes custom logic to replace the sort order column with
* a unified column
*/
protected function performSortableColumnJoin($query = null, $sessionKey = null)
{
if (
!$this->parent->isClassInstanceOf(\October\Contracts\Database\SortableRelationInterface::class) ||
!$this->parent->isSortableRelation($this->relationName)
) {
return;
}

// Check if sorting by the matched sort_order column
$sortColumn = $this->qualifyPivotColumn(
$this->parent->getRelationSortOrderColumn($this->relationName)
);

$orderDefinitions = $query->getQuery()->orders;

traceLog($orderDefinitions);

if (!is_array($orderDefinitions)) {
return;
}

$sortableIndex = false;
foreach ($orderDefinitions as $index => $order) {
if ($order['column'] === $sortColumn) {
$sortableIndex = $index;
}
}

// Not sorting by the sort column, abort
if ($sortableIndex === false) {
return;
}

// Join the deferred binding table and select the combo column
$tempOrderColumns = 'october_reserved_sort_order';
$combinedOrderColumn = "ifnull(deferred_bindings.sort_order, {$sortColumn}) as {$tempOrderColumns}";
$this->performDeferredLeftJoin($query, $sessionKey);
$this->addSelect(DbDongle::raw($combinedOrderColumn));

// Overwrite the sortable column with the combined one
$query->getQuery()->orders[$sortableIndex]['column'] = $tempOrderColumns;
}

/**
* performDeferredLeftJoin left joins the deferred bindings table
*/
protected function performDeferredLeftJoin($query = null, $sessionKey = null)
{
$query = $query ?: $this->query;

$query->leftJoin('deferred_bindings', function($join) use ($sessionKey) {
$join->on(
$this->getQualifiedRelatedKeyName(), '=', 'deferred_bindings.slave_id')
->where('master_field', $this->relationName)
->where('master_type', get_class($this->parent))
->where('session_key', $sessionKey);
});

return $this;
}
}
36 changes: 26 additions & 10 deletions src/Database/Relations/DeferOneOrMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,28 @@ trait DeferOneOrMany
*/
public function withDeferred($sessionKey = null)
{
$modelQuery = $this->query;
$newQuery = $this->query->getQuery()->newQuery();
$newQuery->from($this->related->getTable());

$newQuery = $modelQuery->getQuery()->newQuery();
// Readd the defined constraints
$this->addDefinedConstraintsToQuery($newQuery);

$newQuery->from($this->related->getTable());
return $this->withDeferredQuery($newQuery, $sessionKey);
}

/**
* withDeferredQuery returns the model query with deferred bindings added
* @param \Illuminate\Database\Query\Builder $newQuery
* @param string|null $sessionKey
* @return \Illuminate\Database\Query\Builder
*/
public function withDeferredQuery($newQuery = null, $sessionKey = null)
{
// Use case here is not wanting addDefinedConstraintsToQuery
if ($newQuery === null) {
$newQuery = $this->query->getQuery()->newQuery();
$newQuery->from($this->related->getTable());
}

// Guess the key from the parent model
if ($sessionKey === null) {
Expand All @@ -32,16 +49,14 @@ public function withDeferred($sessionKey = null)
// Swap the standard inner join for a left join
if ($this instanceof BelongsToManyBase) {
$this->performLeftJoin($newQuery);
$this->performSortableColumnJoin($newQuery, $sessionKey);
}

$newQuery->where(function ($query) use ($sessionKey) {
// Trick the relation to add constraints to this nested query
if ($this->parent->exists) {
$this->query = $query;
$this->addConstraints();

if (!$this instanceof BelongsToManyBase) {
$this->addDefinedConstraintsToQuery($this);
}
}

// Bind (Add)
Expand Down Expand Up @@ -79,14 +94,15 @@ public function withDeferred($sessionKey = null)
]);
});

$modelQuery->setQuery($newQuery);
// Bless this query with the deferred query
$this->query->setQuery($newQuery);

// Apply global scopes
foreach ($this->related->getGlobalScopes() as $identifier => $scope) {
$modelQuery->withGlobalScope($identifier, $scope);
$this->query->withGlobalScope($identifier, $scope);
}

return $this->query = $modelQuery;
return $this->query;
}

/**
Expand Down
15 changes: 13 additions & 2 deletions src/Database/Traits/SortableRelation.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php namespace October\Rain\Database\Traits;

use Db;
use Exception;

/**
Expand Down Expand Up @@ -117,9 +118,19 @@ public function setSortableRelationOrder($relationName, $itemIds, $referencePool

if ($upsert) {
foreach ($upsert as $update) {
$this->$relationName()->updateExistingPivot($update['id'], [
$result = $this->exists ? $this->$relationName()->updateExistingPivot($update['id'], [
$this->getRelationSortOrderColumn($relationName) => $update['sort_order']
]);
]) : 0;

if (!$result && $this->sessionKey) {
Db::table('deferred_bindings')
->where('master_field', $relationName)
->where('master_type', get_class($this))
->where('session_key', $this->sessionKey)
->where('slave_id', $update['id'])
->limit(1)
->update(['sort_order' => $update['sort_order']]);
}
}
}
}
Expand Down

0 comments on commit 58fcf6b

Please sign in to comment.