Skip to content

Commit

Permalink
[FRAM-87] Replace a where clause (#67)
Browse files Browse the repository at this point in the history
* feat: Replace a where clause (#FRAM-87)

* fix: Where replace with null value

* chore: Update changelog for #FRAM-87
  • Loading branch information
vincent4vx committed Mar 17, 2023
1 parent 3046c22 commit ce15f10
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Expand Up @@ -19,6 +19,7 @@ v2.1.0
* Change: `Clause::buildClause()`, and all related methods like `Wherable::where()` now handle iterable expression parameters.
This change allows to use the `Criteria` as parameter of the where clause without the need to call `Criteria::all()`
* Feat: Add `Bdf\Prime\Query\Expression\Operator` to wrap the operator and the value as expression. So it can be used in on criteria setters
* Feat: Add `Clause::replaceClause()` and `Wherable::replaceWhere()` to replace a single filter value. Method `Wherable::replaceWhere()` is commented for now to avoid BC break.

v2.0.6
------
Expand Down
36 changes: 36 additions & 0 deletions src/Query/Clause.php
Expand Up @@ -172,6 +172,42 @@ public function buildClause(string $statement, $expression, $operator = null, $v
return $this;
}

/**
* {@inheritdoc}
*/
public function replaceClause(string $statement, string $expression, $operator = null, $value = null)
{
if ($value === null && (!is_string($operator) || !isset($this->operators[$operator]))) {
$value = $operator;
$operator = '=';
}

$found = false;

foreach ($this->statements[$statement] ?? [] as $key => $clause) {
if (
isset($clause['column'], $clause['operator'])
&& $clause['column'] === $expression
&& $clause['operator'] === $operator
) {
$this->statements[$statement][$key]['value'] = $value;
$found = true;
break;
}
}

if (!$found) {
$this->statements[$statement][] = [
'column' => $expression,
'operator' => $operator,
'value' => $value,
'glue' => CompositeExpression::TYPE_AND,
];
}

return $this;
}

/**
* {@inheritdoc}
*/
Expand Down
23 changes: 23 additions & 0 deletions src/Query/ClauseInterface.php
Expand Up @@ -91,6 +91,29 @@ public function addStatement(string $name, $values): void;
*/
public function buildClause(string $statement, $expression, $operator = null, $value = null, string $type = CompositeExpression::TYPE_AND);

/**
* Replace a clause value
*
* If the clause does not exist, it will be added
* Only the first statement matching the expression and the operator will be replaced
*
* Note: unlike the buildClause method, this method does not support the array syntax nor nested statements
*
* <code>
* $query->from('users', 'u')->where('u.id', '=', 1); // SELECT * FROM users u WHERE u.id = 1
* $query->replaceClause('where', 'u.id', '=', 2); // Replace the clause : SELECT * FROM users u WHERE u.id = 2
* $query->replaceClause('where', 'u.id', '<', 1000); // Operator is different, so the clause is added : SELECT * FROM users u WHERE u.id = 2 AND u.id < 1000
* </code>
*
* @param string $statement Statement name to modify. e.g. where, having, on...
* @param string $expression Column name or expression
* @param string|mixed|null $operator Comparison operator. If the $value parameter is omitted, this parameter is used as the value, and the operator is set to '='.
* @param mixed $value The value to compare to.
*
* @return $this
*/
public function replaceClause(string $statement, string $expression, $operator = null, $value = null);

/**
* Add a raw expression in statement
*
Expand Down
28 changes: 27 additions & 1 deletion src/Query/Contract/Whereable.php
Expand Up @@ -6,12 +6,13 @@

/**
* Interface for where() method
*
* @method $this whereReplace(string $column, $operator = null, $value = null) Add or replace single where criterion.
*/
interface Whereable
{
/**
* Specifies one or more restrictions to the query result.
* Replaces any previously specified restrictions, if any.
*
* <code>
* $query
Expand All @@ -37,6 +38,31 @@ interface Whereable
*/
public function where($column, $operator = null, $value = null);

/**
* Add or replace single where criterion
* The criterion value will be replaced on the first occurrence matching with the column and the operator
*
* Note: Unlike the where() method, this method does not support the array syntax nor nested statements
*
* <code>
* $query
* ->select('u.name')
* ->from('users', 'u')
* ->whereReplace('u.id', '1') // Filter not existing, will be added
* ;
*
* $query->whereReplace('u.id', '2'); // Will replace the previous where
* $query->whereReplace('u.id', '<', '1000'); // Operator is different, so the clause is added
* </code>
*
* @param string $column The column name to filter
* @param string|mixed|null $operator The comparison operator, or the value is you want to use "=" operator
* @param mixed $value
*
* @return $this This Query instance.
*/
//public function whereReplace(string $column, $operator = null, $value = null); // @todo uncomment on prime 3.0

/**
* Adds one or more restrictions to the query results, forming a logical
* disjunction with any previously specified restrictions.
Expand Down
19 changes: 19 additions & 0 deletions src/Query/Extension/SimpleWhereTrait.php
Expand Up @@ -35,6 +35,18 @@ public function where($column, $operator = null, $value = null)
return $this;
}

/**
* {@inheritdoc}
*
* @see Whereable::whereReplace()
*/
public function whereReplace(string $column, $operator = null, $value = null)
{
$this->compilerState->invalidate('where');
$this->replaceClause('where', $column, $operator, $value);
return $this;
}

/**
* {@inheritdoc}
*
Expand Down Expand Up @@ -138,6 +150,13 @@ public function nested(callable $callback, string $type = CompositeExpression::T
*/
abstract public function buildClause(string $statement, $expression, $operator = null, $value = null, string $type = CompositeExpression::TYPE_AND);

/**
* {@inheritdoc}
*
* @see Clause::replaceClause()
*/
abstract public function replaceClause(string $statement, string $expression, $operator = null, $value = null);

/**
* {@inheritdoc}
*
Expand Down
26 changes: 7 additions & 19 deletions src/Query/Pagination/WalkStrategy/KeyWalkStrategy.php
Expand Up @@ -13,6 +13,8 @@
use Bdf\Prime\Query\ReadCommandInterface;
use InvalidArgumentException;

use function method_exists;

/**
* Walk strategy using a primary key (or any unique key) as cursor
* This strategy supports deleting entities during the walk, but the entity must contains a single primary key, and the query must be ordered by this key
Expand Down Expand Up @@ -78,25 +80,11 @@ public function next(WalkCursor $cursor): WalkCursor
$column = $this->key->name();
$operator = $query->getOrders()[$column] === Orderable::ORDER_ASC ? '>' : '<';

// Quick fix for FRAM-86 : reset where clause
$set = false;

if ($query instanceof CompilableClause && !empty($query->statements['where'])) {
foreach ($query->statements['where'] as $key => $statement) {
if (
isset($statement['column'], $statement['operator'], $statement['value'])
&& $statement['column'] === $column
&& $statement['operator'] === $operator
) {
$query->statements['where'][$key]['value'] = $cursor->cursor;
$set = true;
$query->state()->invalidate('where');
break;
}
}
}

if (!$set) {
// #FRAM-86 : reset where clause
// @todo remove method_exists check on prime 3.0
if (method_exists($query, 'whereReplace')) {
$query->whereReplace($column, $operator, $cursor->cursor);
} else {
$query->where($column, $operator, $cursor->cursor);
}
}
Expand Down
18 changes: 18 additions & 0 deletions tests/Query/QueryTest.php
Expand Up @@ -1610,4 +1610,22 @@ public function test_count_alias()
$query->toSql()
);
}

public function test_whereReplace()
{
$query = $this->query()->whereReplace('id', 1);

$this->assertEquals('SELECT * FROM test_ WHERE id = 1', $query->toRawSql());
$this->assertEquals('SELECT * FROM test_ WHERE id = 3', $query->whereReplace('id', 3)->toRawSql());
$this->assertEquals('SELECT * FROM test_ WHERE id = 3 AND id < 42', $query->whereReplace('id', '<', 42)->toRawSql());
$this->assertEquals('SELECT * FROM test_ WHERE id = 3 AND id < 42 AND raw clause', $query->whereRaw('raw clause')->toRawSql());
}

public function test_whereReplace_null()
{
$query = $this->query()->whereReplace('id', null);

$this->assertEquals('SELECT * FROM test_ WHERE id IS NULL', $query->toRawSql());
$this->assertEquals('SELECT * FROM test_ WHERE id = 42', $query->whereReplace('id', 42)->toRawSql());
}
}

0 comments on commit ce15f10

Please sign in to comment.