Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.7] Add Builder::whereJsonLength() #25047

Merged
merged 3 commits into from
Sep 6, 2018
Merged
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
43 changes: 43 additions & 0 deletions src/Illuminate/Database/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -1492,6 +1492,49 @@ public function orWhereJsonDoesntContain($column, $value)
return $this->whereJsonDoesntContain($column, $value, 'or');
}

/**
* Add a "where JSON length" clause to the query.
*
* @param string $column
* @param mixed $operator
* @param mixed $value
* @param string $boolean
* @return $this
*/
public function whereJsonLength($column, $operator, $value = null, $boolean = 'and')
{
$type = 'JsonLength';

list($value, $operator) = $this->prepareValueAndOperator(
$value, $operator, func_num_args() === 2
);

$this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');

if (! $value instanceof Expression) {
$this->addBinding($value);
}

return $this;
}

/**
* Add a "or where JSON length" clause to the query.
*
* @param string $column
* @param mixed $operator
* @param mixed $value
* @return $this
*/
public function orWhereJsonLength($column, $operator, $value = null)
{
list($value, $operator) = $this->prepareValueAndOperator(
$value, $operator, func_num_args() === 2
);

return $this->whereJsonLength($column, $operator, $value, 'or');
}

/**
* Handles dynamic "where" clauses to the query.
*
Expand Down
45 changes: 45 additions & 0 deletions src/Illuminate/Database/Query/Grammars/Grammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,34 @@ public function prepareBindingForJsonContains($binding)
return json_encode($binding);
}

/**
* Compile a "where JSON length" clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereJsonLength(Builder $query, $where)
{
return $this->compileJsonLength(
$where['column'], $where['operator'], $this->parameter($where['value'])
);
}

/**
* Compile a "JSON length" statement into SQL.
*
* @param string $column
* @param string $operator
* @param string $value
* @return string
* @throws \RuntimeException
*/
protected function compileJsonLength($column, $operator, $value)
{
throw new RuntimeException('This database engine does not support JSON length operations.');
}

/**
* Compile the "group by" portions of the query.
*
Expand Down Expand Up @@ -932,6 +960,23 @@ protected function wrapJsonSelector($value)
throw new RuntimeException('This database engine does not support JSON operations.');
}

/**
* Split the given JSON selector into the field and the optional path and wrap them separately.
*
* @param string $column
* @return array
*/
protected function wrapJsonFieldAndPath($column)
{
$parts = explode('->', $column, 2);

$field = $this->wrap($parts[0]);

$path = count($parts) > 1 ? ', '.$this->wrapJsonPath($parts[1]) : '';

return [$field, $path];
}

/**
* Wrap the given JSON path.
*
Expand Down
15 changes: 15 additions & 0 deletions src/Illuminate/Database/Query/Grammars/MySqlGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ protected function compileJsonContains($column, $value)
return 'json_contains('.$this->wrap($column).', '.$value.')';
}

/**
* Compile a "JSON length" statement into SQL.
*
* @param string $column
* @param string $operator
* @param string $value
* @return string
*/
protected function compileJsonLength($column, $operator, $value)
{
list($field, $path) = $this->wrapJsonFieldAndPath($column);
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe will be better to use [$field, $path] instead list($field, $path) ?


return 'json_length('.$field.$path.') '.$operator.' '.$value;
}

/**
* Compile a single union statement.
*
Expand Down
15 changes: 15 additions & 0 deletions src/Illuminate/Database/Query/Grammars/PostgresGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ protected function compileJsonContains($column, $value)
return '('.$column.')::jsonb @> '.$value;
}

/**
* Compile a "JSON length" statement into SQL.
*
* @param string $column
* @param string $operator
* @param string $value
* @return string
*/
protected function compileJsonLength($column, $operator, $value)
{
$column = str_replace('->>', '->', $this->wrap($column));

return 'json_array_length(('.$column.')::json) '.$operator.' '.$value;
}

/**
* Compile the lock into SQL.
*
Expand Down
15 changes: 15 additions & 0 deletions src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,21 @@ protected function dateBasedWhere($type, Builder $query, $where)
return "strftime('{$type}', {$this->wrap($where['column'])}) {$where['operator']} cast({$value} as text)";
}

/**
* Compile a "JSON length" statement into SQL.
*
* @param string $column
* @param string $operator
* @param string $value
* @return string
*/
protected function compileJsonLength($column, $operator, $value)
{
list($field, $path) = $this->wrapJsonFieldAndPath($column);

return 'json_array_length('.$field.$path.') '.$operator.' '.$value;
}

/**
* Compile an insert statement into SQL.
*
Expand Down
21 changes: 16 additions & 5 deletions src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,7 @@ protected function whereTime(Builder $query, $where)
*/
protected function compileJsonContains($column, $value)
{
$parts = explode('->', $column, 2);

$field = $this->wrap($parts[0]);

$path = count($parts) > 1 ? ', '.$this->wrapJsonPath($parts[1]) : '';
list($field, $path) = $this->wrapJsonFieldAndPath($column);

return $value.' in (select [value] from openjson('.$field.$path.'))';
}
Expand All @@ -146,6 +142,21 @@ public function prepareBindingForJsonContains($binding)
return is_bool($binding) ? json_encode($binding) : $binding;
}

/**
* Compile a "JSON length" statement into SQL.
*
* @param string $column
* @param string $operator
* @param string $value
* @return string
*/
protected function compileJsonLength($column, $operator, $value)
{
list($field, $path) = $this->wrapJsonFieldAndPath($column);

return '(select count(*) from openjson('.$field.$path.')) '.$operator.' '.$value;
}

/**
* Create a full ANSI offset clause for the query.
*
Expand Down
92 changes: 92 additions & 0 deletions tests/Database/DatabaseQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2791,6 +2791,98 @@ public function testWhereJsonDoesntContainSqlServer()
$this->assertEquals([1], $builder->getBindings());
}

public function testWhereJsonLengthMySql()
{
$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->whereJsonLength('options', 0);
$this->assertEquals('select * from `users` where json_length(`options`) = ?', $builder->toSql());
$this->assertEquals([0], $builder->getBindings());

$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->whereJsonLength('users.options->languages', '>', 0);
$this->assertEquals('select * from `users` where json_length(`users`.`options`, \'$."languages"\') > ?', $builder->toSql());
$this->assertEquals([0], $builder->getBindings());

$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonLength('options->languages', new Raw('0'));
$this->assertEquals('select * from `users` where `id` = ? or json_length(`options`, \'$."languages"\') = 0', $builder->toSql());
$this->assertEquals([1], $builder->getBindings());

$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonLength('options->languages', '>', new Raw('0'));
$this->assertEquals('select * from `users` where `id` = ? or json_length(`options`, \'$."languages"\') > 0', $builder->toSql());
$this->assertEquals([1], $builder->getBindings());
}

public function testWhereJsonLengthPostgres()
{
$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->whereJsonLength('options', 0);
$this->assertEquals('select * from "users" where json_array_length(("options")::json) = ?', $builder->toSql());
$this->assertEquals([0], $builder->getBindings());

$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->whereJsonLength('users.options->languages', '>', 0);
$this->assertEquals('select * from "users" where json_array_length(("users"."options"->\'languages\')::json) > ?', $builder->toSql());
$this->assertEquals([0], $builder->getBindings());

$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonLength('options->languages', new Raw('0'));
$this->assertEquals('select * from "users" where "id" = ? or json_array_length(("options"->\'languages\')::json) = 0', $builder->toSql());
$this->assertEquals([1], $builder->getBindings());

$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonLength('options->languages', '>', new Raw('0'));
$this->assertEquals('select * from "users" where "id" = ? or json_array_length(("options"->\'languages\')::json) > 0', $builder->toSql());
$this->assertEquals([1], $builder->getBindings());
}

public function testWhereJsonLengthSqlite()
{
$builder = $this->getSQLiteBuilder();
$builder->select('*')->from('users')->whereJsonLength('options', 0);
$this->assertEquals('select * from "users" where json_array_length("options") = ?', $builder->toSql());
$this->assertEquals([0], $builder->getBindings());

$builder = $this->getSQLiteBuilder();
$builder->select('*')->from('users')->whereJsonLength('users.options->languages', '>', 0);
$this->assertEquals('select * from "users" where json_array_length("users"."options", \'$."languages"\') > ?', $builder->toSql());
$this->assertEquals([0], $builder->getBindings());

$builder = $this->getSQLiteBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonLength('options->languages', new Raw('0'));
$this->assertEquals('select * from "users" where "id" = ? or json_array_length("options", \'$."languages"\') = 0', $builder->toSql());
$this->assertEquals([1], $builder->getBindings());

$builder = $this->getSQLiteBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonLength('options->languages', '>', new Raw('0'));
$this->assertEquals('select * from "users" where "id" = ? or json_array_length("options", \'$."languages"\') > 0', $builder->toSql());
$this->assertEquals([1], $builder->getBindings());
}

public function testWhereJsonLengthSqlServer()
{
$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('users')->whereJsonLength('options', 0);
$this->assertEquals('select * from [users] where (select count(*) from openjson([options])) = ?', $builder->toSql());
$this->assertEquals([0], $builder->getBindings());

$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('users')->whereJsonLength('users.options->languages', '>', 0);
$this->assertEquals('select * from [users] where (select count(*) from openjson([users].[options], \'$."languages"\')) > ?', $builder->toSql());
$this->assertEquals([0], $builder->getBindings());

$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonLength('options->languages', new Raw('0'));
$this->assertEquals('select * from [users] where [id] = ? or (select count(*) from openjson([options], \'$."languages"\')) = 0', $builder->toSql());
$this->assertEquals([1], $builder->getBindings());

$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonLength('options->languages', '>', new Raw('0'));
$this->assertEquals('select * from [users] where [id] = ? or (select count(*) from openjson([options], \'$."languages"\')) > 0', $builder->toSql());
$this->assertEquals([1], $builder->getBindings());
}

public function testFromSub()
{
$builder = $this->getBuilder();
Expand Down