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

[8.x] Bitwise #41112

Merged
merged 3 commits into from Feb 18, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/Illuminate/Database/Query/Builder.php
Expand Up @@ -203,6 +203,15 @@ class Builder
'not similar to', 'not ilike', '~~*', '!~~*',
];

/**
* All of the available bitwise operators.
*
* @var string[]
*/
public $bitwiseOperators = [
'&', '|', '^', '<<', '>>', '&~',
];

/**
* Whether to use write pdo for the select.
*
Expand Down Expand Up @@ -754,6 +763,10 @@ public function where($column, $operator = null, $value = null, $boolean = 'and'
}
}

if ($this->isBitwiseOperator($operator)) {
$type = 'Bitwise';
}

// Now that we are working with just a simple query we can put the elements
// in our array and add the query binding to our array of bindings that
// will be bound to each SQL statements when it is finally executed.
Expand Down Expand Up @@ -837,6 +850,18 @@ protected function invalidOperator($operator)
! in_array(strtolower($operator), $this->grammar->getOperators(), true);
}

/**
* Determine if the operator is a bitwise operator.
*
* @param string $operator
* @return bool
*/
protected function isBitwiseOperator($operator)
{
return in_array(strtolower($operator), $this->bitwiseOperators, true) ||
in_array(strtolower($operator), $this->grammar->getBitwiseOperators(), true);
}

/**
* Add an "or where" clause to the query.
*
Expand Down Expand Up @@ -1915,6 +1940,10 @@ public function having($column, $operator = null, $value = null, $boolean = 'and
[$value, $operator] = [$operator, '='];
}

if ($this->isBitwiseOperator($operator)) {
$type = 'Bitwise';
}

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

if (! $value instanceof Expression) {
Expand Down
29 changes: 29 additions & 0 deletions src/Illuminate/Database/Query/Grammars/Grammar.php
Expand Up @@ -18,6 +18,13 @@ class Grammar extends BaseGrammar
*/
protected $operators = [];

/**
* The grammar specific bitwise operators.
*
* @var array
*/
protected $bitwiseOperators = [];

/**
* The components that make up a select clause.
*
Expand Down Expand Up @@ -255,6 +262,18 @@ protected function whereBasic(Builder $query, $where)
return $this->wrap($where['column']).' '.$operator.' '.$value;
}

/**
* Compile a bitwise operator where clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereBitwise(Builder $query, $where)
{
return $this->whereBasic($query, $where);
}

/**
* Compile a "where in" clause.
*
Expand Down Expand Up @@ -1299,4 +1318,14 @@ public function getOperators()
{
return $this->operators;
}

/**
* Get the grammar specific bitwise operators.
*
* @return array
*/
public function getBitwiseOperators()
{
return $this->bitwiseOperators;
}
}
55 changes: 55 additions & 0 deletions src/Illuminate/Database/Query/Grammars/PostgresGrammar.php
Expand Up @@ -21,6 +21,15 @@ class PostgresGrammar extends Grammar
'is distinct from', 'is not distinct from',
];

/**
* The grammar specific bitwise operators.
*
* @var array
*/
protected $bitwiseOperators = [
'~', '&', '|', '#', '<<', '>>', '<<=', '>>=',
];

/**
* {@inheritdoc}
*
Expand All @@ -42,6 +51,22 @@ protected function whereBasic(Builder $query, $where)
return parent::whereBasic($query, $where);
}

/**
* {@inheritdoc}
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereBitwise(Builder $query, $where)
{
$value = $this->parameter($where['value']);

$operator = str_replace('?', '??', $where['operator']);

return '('.$this->wrap($where['column']).' '.$operator.' '.$value.')::bool';
}

/**
* Compile a "where date" clause.
*
Expand Down Expand Up @@ -206,6 +231,36 @@ protected function compileJsonLength($column, $operator, $value)
return 'json_array_length(('.$column.')::json) '.$operator.' '.$value;
}

/**
* {@inheritdoc}
*
* @param array $having
* @return string
*/
protected function compileHaving(array $having)
{
if ($having['type'] === 'Bitwise') {
return $this->compileHavingBitwise($having);
}

return parent::compileHaving($having);
}

/**
* Compile a having clause involving a bitwise operator.
*
* @param array $having
* @return string
*/
protected function compileHavingBitwise($having)
{
$column = $this->wrap($having['column']);

$parameter = $this->parameter($having['value']);

return $having['boolean'].' ('.$column.' '.$having['operator'].' '.$parameter.')::bool';
}

/**
* Compile the lock into SQL.
*
Expand Down
46 changes: 46 additions & 0 deletions src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php
Expand Up @@ -96,6 +96,22 @@ protected function compileFrom(Builder $query, $table)
return $from;
}

/**
* {@inheritdoc}
*
* @param \Illuminate\Database\Query\Builder $query
* @param array $where
* @return string
*/
protected function whereBitwise(Builder $query, $where)
{
$value = $this->parameter($where['value']);

$operator = str_replace('?', '??', $where['operator']);

return '('.$this->wrap($where['column']).' '.$operator.' '.$value.') != 0';
}

/**
* Compile a "where date" clause.
*
Expand Down Expand Up @@ -164,6 +180,36 @@ protected function compileJsonLength($column, $operator, $value)
return '(select count(*) from openjson('.$field.$path.')) '.$operator.' '.$value;
}

/**
* {@inheritdoc}
*
* @param array $having
* @return string
*/
protected function compileHaving(array $having)
{
if ($having['type'] === 'Bitwise') {
return $this->compileHavingBitwise($having);
}

return parent::compileHaving($having);
}

/**
* Compile a having clause involving a bitwise operator.
*
* @param array $having
* @return string
*/
protected function compileHavingBitwise($having)
{
$column = $this->wrap($having['column']);

$parameter = $this->parameter($having['value']);

return $having['boolean'].' ('.$column.' '.$having['operator'].' '.$parameter.') != 0';
}

/**
* Create a full ANSI offset clause for the query.
*
Expand Down
2 changes: 2 additions & 0 deletions tests/Database/DatabaseEloquentModelTest.php
Expand Up @@ -2153,6 +2153,7 @@ protected function addMockConnection($model)
$model->setConnectionResolver($resolver = m::mock(ConnectionResolverInterface::class));
$resolver->shouldReceive('connection')->andReturn($connection = m::mock(Connection::class));
$connection->shouldReceive('getQueryGrammar')->andReturn($grammar = m::mock(Grammar::class));
$grammar->shouldReceive('getBitwiseOperators')->andReturn([]);
$connection->shouldReceive('getPostProcessor')->andReturn($processor = m::mock(Processor::class));
$connection->shouldReceive('query')->andReturnUsing(function () use ($connection, $grammar, $processor) {
return new BaseBuilder($connection, $grammar, $processor);
Expand Down Expand Up @@ -2440,6 +2441,7 @@ public function getConnection()
{
$mock = m::mock(Connection::class);
$mock->shouldReceive('getQueryGrammar')->andReturn($grammar = m::mock(Grammar::class));
$grammar->shouldReceive('getBitwiseOperators')->andReturn([]);
$mock->shouldReceive('getPostProcessor')->andReturn($processor = m::mock(Processor::class));
$mock->shouldReceive('getName')->andReturn('name');
$mock->shouldReceive('query')->andReturnUsing(function () use ($mock, $grammar, $processor) {
Expand Down
35 changes: 35 additions & 0 deletions tests/Database/DatabaseQueryBuilderTest.php
Expand Up @@ -3209,6 +3209,41 @@ public function testMySqlSoundsLikeOperator()
$this->assertEquals(['John Doe'], $builder->getBindings());
}

public function testBitwiseOperators()
{
$builder = $this->getBuilder();
$builder->select('*')->from('users')->where('bar', '&', 1);
$this->assertSame('select * from "users" where "bar" & ?', $builder->toSql());

$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->where('bar', '#', 1);
$this->assertSame('select * from "users" where ("bar" # ?)::bool', $builder->toSql());

$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->where('range', '>>', '[2022-01-08 00:00:00,2022-01-09 00:00:00)');
$this->assertSame('select * from "users" where ("range" >> ?)::bool', $builder->toSql());

$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('users')->where('bar', '&', 1);
$this->assertSame('select * from [users] where ([bar] & ?) != 0', $builder->toSql());

$builder = $this->getBuilder();
$builder->select('*')->from('users')->having('bar', '&', 1);
$this->assertSame('select * from "users" having "bar" & ?', $builder->toSql());

$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->having('bar', '#', 1);
$this->assertSame('select * from "users" having ("bar" # ?)::bool', $builder->toSql());

$builder = $this->getPostgresBuilder();
$builder->select('*')->from('users')->having('range', '>>', '[2022-01-08 00:00:00,2022-01-09 00:00:00)');
$this->assertSame('select * from "users" having ("range" >> ?)::bool', $builder->toSql());

$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('users')->having('bar', '&', 1);
$this->assertSame('select * from [users] having ([bar] & ?) != 0', $builder->toSql());
}

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