diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 2d2d96ff76bc..908d97d129aa 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -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. * @@ -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. @@ -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. * @@ -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) { diff --git a/src/Illuminate/Database/Query/Grammars/Grammar.php b/src/Illuminate/Database/Query/Grammars/Grammar.php index 77472f088e02..0dbdb1e0535e 100755 --- a/src/Illuminate/Database/Query/Grammars/Grammar.php +++ b/src/Illuminate/Database/Query/Grammars/Grammar.php @@ -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. * @@ -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. * @@ -1299,4 +1318,14 @@ public function getOperators() { return $this->operators; } + + /** + * Get the grammar specific bitwise operators. + * + * @return array + */ + public function getBitwiseOperators() + { + return $this->bitwiseOperators; + } } diff --git a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php index 47f498e54708..1b49bf10e9cb 100755 --- a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php @@ -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} * @@ -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. * @@ -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. * diff --git a/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php index 3fce201bd28c..417b63e4a324 100755 --- a/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php @@ -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. * @@ -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. * diff --git a/tests/Database/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index 76fa651e855c..5597accae834 100755 --- a/tests/Database/DatabaseEloquentModelTest.php +++ b/tests/Database/DatabaseEloquentModelTest.php @@ -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); @@ -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) { diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index d27a38a10276..ef2c67a3b588 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -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();