Skip to content
Closed
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
51 changes: 51 additions & 0 deletions src/Illuminate/Database/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -1776,6 +1776,57 @@ public function orWhereJsonDoesntContain($column, $value)
return $this->whereJsonDoesntContain($column, $value, 'or');
}

/**
* Add a clause that determines if a JSON path exists to the query.
*
* @param string $column
* @param string $boolean
* @param bool $not
* @return $this
*/
public function whereJsonContainsKey($column, $boolean = 'and', $not = false)
{
$type = 'JsonContainsKey';

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

return $this;
}

/**
* Add an "or" clause that determines if a JSON path exists to the query.
*
* @param string $column
* @return $this
*/
public function orWhereJsonContainsKey($column)
{
return $this->whereJsonContainsKey($column, 'or');
}

/**
* Add a clause that determines if a JSON path does not exist to the query.
*
* @param string $column
* @param string $boolean
* @return $this
*/
public function whereJsonDoesntContainKey($column, $boolean = 'and')
{
return $this->whereJsonContainsKey($column, $boolean, true);
}

/**
* Add an "or" clause that determines if a JSON path does not exist to the query.
*
* @param string $column
* @return $this
*/
public function orWhereJsonDoesntContainKey($column)
{
return $this->whereJsonDoesntContainKey($column, 'or');
}

/**
* Add a "where JSON length" clause to the query.
*
Expand Down
29 changes: 29 additions & 0 deletions src/Illuminate/Database/Query/Grammars/Grammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,35 @@ public function prepareBindingForJsonContains($binding)
return json_encode($binding);
}

/**
* Compile a "where JSON contains key" clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereJsonContainsKey(Builder $query, $where)
{
$not = $where['not'] ? 'not ' : '';

return $not.$this->compileJsonContainsKey(
$where['column']
);
}

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

/**
* Compile a "where JSON length" clause.
*
Expand Down
13 changes: 13 additions & 0 deletions src/Illuminate/Database/Query/Grammars/MySqlGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ protected function compileJsonContains($column, $value)
return 'json_contains('.$field.', '.$value.$path.')';
}

/**
* Compile a "JSON contains key" statement into SQL.
*
* @param string $column
* @return string
*/
protected function compileJsonContainsKey($column)
{
[$field, $path] = $this->wrapJsonFieldAndPath($column);

return 'ifnull(json_contains_path('.$field.', \'one\''.$path.'), 0)';
}

/**
* Compile a "JSON length" statement into SQL.
*
Expand Down
17 changes: 17 additions & 0 deletions src/Illuminate/Database/Query/Grammars/PostgresGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,23 @@ protected function compileJsonContains($column, $value)
return '('.$column.')::jsonb @> '.$value;
}

/**
* Compile a "JSON contains key" statement into SQL.
*
* @param string $column
* @return string
*/
protected function compileJsonContainsKey($column)
{
$parts = explode('->', $column);

$key = "'".str_replace("'", "''", array_pop($parts))."'";

$column = str_replace('->>', '->', $this->wrap(implode('->', $parts)));

return 'coalesce(('.$column.')::jsonb ?? '.$key.', false)';
}

/**
* Compile a "JSON length" statement into SQL.
*
Expand Down
17 changes: 17 additions & 0 deletions src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,23 @@ public function prepareBindingForJsonContains($binding)
return is_bool($binding) ? json_encode($binding) : $binding;
}

/**
* Compile a "JSON contains key" statement into SQL.
*
* @param string $column
* @return string
*/
protected function compileJsonContainsKey($column)
{
$parts = explode('->', $column);

$key = "'".str_replace("'", "''", array_pop($parts))."'";

[$field, $path] = $this->wrapJsonFieldAndPath(implode('->', $parts));

return $key.' in (select [key] from openjson('.$field.$path.'))';
}

/**
* Compile a "JSON length" statement into SQL.
*
Expand Down
94 changes: 94 additions & 0 deletions tests/Database/DatabaseQueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4402,6 +4402,100 @@ public function testWhereJsonDoesntContainSqlServer()
$this->assertEquals([1], $builder->getBindings());
}

public function testWhereJsonContainsKeyMySql()
{
$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->whereJsonContainsKey('users.options->languages');
$this->assertSame('select * from `users` where ifnull(json_contains_path(`users`.`options`, \'one\', \'$."languages"\'), 0)', $builder->toSql());

$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->whereJsonContainsKey('options->language->primary');
$this->assertSame('select * from `users` where ifnull(json_contains_path(`options`, \'one\', \'$."language"."primary"\'), 0)', $builder->toSql());

$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonContainsKey('options->languages');
$this->assertSame('select * from `users` where `id` = ? or ifnull(json_contains_path(`options`, \'one\', \'$."languages"\'), 0)', $builder->toSql());
}

public function testWhereJsonContainsKeyPostgres()
{
$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->whereJsonContainsKey('users.options->languages');
$this->assertSame('select * from "users" where coalesce(("users"."options")::jsonb ?? \'languages\', false)', $builder->toSql());

$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->whereJsonContainsKey('options->language->primary');
$this->assertSame('select * from "users" where coalesce(("options"->\'language\')::jsonb ?? \'primary\', false)', $builder->toSql());

$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonContainsKey('options->languages');
$this->assertSame('select * from "users" where "id" = ? or coalesce(("options")::jsonb ?? \'languages\', false)', $builder->toSql());
}

public function testWhereJsonContainsKeySqlite()
{
$this->expectException(RuntimeException::class);

$builder = $this->getSQLiteBuilder();
$builder->select('*')->from('users')->whereJsonContainsKey('options->languages')->toSql();
}

public function testWhereJsonContainsKeySqlServer()
{
$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('users')->whereJsonContainsKey('users.options->languages');
$this->assertSame('select * from [users] where \'languages\' in (select [key] from openjson([users].[options]))', $builder->toSql());

$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('users')->whereJsonContainsKey('options->language->primary');
$this->assertSame('select * from [users] where \'primary\' in (select [key] from openjson([options], \'$."language"\'))', $builder->toSql());

$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonContainsKey('options->languages');
$this->assertSame('select * from [users] where [id] = ? or \'languages\' in (select [key] from openjson([options]))', $builder->toSql());
}

public function testWhereJsonDoesntContainKeyMySql()
{
$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->whereJsonDoesntContainKey('options->languages');
$this->assertSame('select * from `users` where not ifnull(json_contains_path(`options`, \'one\', \'$."languages"\'), 0)', $builder->toSql());

$builder = $this->getMySqlBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonDoesntContainKey('options->languages');
$this->assertSame('select * from `users` where `id` = ? or not ifnull(json_contains_path(`options`, \'one\', \'$."languages"\'), 0)', $builder->toSql());
}

public function testWhereJsonDoesntContainKeyPostgres()
{
$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->whereJsonDoesntContainKey('options->languages');
$this->assertSame('select * from "users" where not coalesce(("options")::jsonb ?? \'languages\', false)', $builder->toSql());

$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonDoesntContainKey('options->languages');
$this->assertSame('select * from "users" where "id" = ? or not coalesce(("options")::jsonb ?? \'languages\', false)', $builder->toSql());
}

public function testWhereJsonDoesntContainKeySqlite()
{
$this->expectException(RuntimeException::class);

$builder = $this->getSQLiteBuilder();
$builder->select('*')->from('users')->whereJsonDoesntContainKey('options->languages')->toSql();
}

public function testWhereJsonDoesntContainKeySqlServer()
{
$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('users')->whereJsonDoesntContainKey('options->languages');
$this->assertSame('select * from [users] where not \'languages\' in (select [key] from openjson([options]))', $builder->toSql());

$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('users')->where('id', '=', 1)->orWhereJsonDoesntContainKey('options->languages');
$this->assertSame('select * from [users] where [id] = ? or not \'languages\' in (select [key] from openjson([options]))', $builder->toSql());
}

public function testWhereJsonLengthMySql()
{
$builder = $this->getMySqlBuilder();
Expand Down