Skip to content

Commit 95b732f

Browse files
taylorotwellMarc-Etienne Barrut
andauthored
Bitwise (#41112)
* Bitwise operators support * formatting Co-authored-by: Marc-Etienne Barrut <[email protected]>
1 parent 60f9da3 commit 95b732f

File tree

6 files changed

+196
-0
lines changed

6 files changed

+196
-0
lines changed

src/Illuminate/Database/Query/Builder.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,15 @@ class Builder
203203
'not similar to', 'not ilike', '~~*', '!~~*',
204204
];
205205

206+
/**
207+
* All of the available bitwise operators.
208+
*
209+
* @var string[]
210+
*/
211+
public $bitwiseOperators = [
212+
'&', '|', '^', '<<', '>>', '&~',
213+
];
214+
206215
/**
207216
* Whether to use write pdo for the select.
208217
*
@@ -754,6 +763,10 @@ public function where($column, $operator = null, $value = null, $boolean = 'and'
754763
}
755764
}
756765

766+
if ($this->isBitwiseOperator($operator)) {
767+
$type = 'Bitwise';
768+
}
769+
757770
// Now that we are working with just a simple query we can put the elements
758771
// in our array and add the query binding to our array of bindings that
759772
// will be bound to each SQL statements when it is finally executed.
@@ -837,6 +850,18 @@ protected function invalidOperator($operator)
837850
! in_array(strtolower($operator), $this->grammar->getOperators(), true);
838851
}
839852

853+
/**
854+
* Determine if the operator is a bitwise operator.
855+
*
856+
* @param string $operator
857+
* @return bool
858+
*/
859+
protected function isBitwiseOperator($operator)
860+
{
861+
return in_array(strtolower($operator), $this->bitwiseOperators, true) ||
862+
in_array(strtolower($operator), $this->grammar->getBitwiseOperators(), true);
863+
}
864+
840865
/**
841866
* Add an "or where" clause to the query.
842867
*
@@ -1915,6 +1940,10 @@ public function having($column, $operator = null, $value = null, $boolean = 'and
19151940
[$value, $operator] = [$operator, '='];
19161941
}
19171942

1943+
if ($this->isBitwiseOperator($operator)) {
1944+
$type = 'Bitwise';
1945+
}
1946+
19181947
$this->havings[] = compact('type', 'column', 'operator', 'value', 'boolean');
19191948

19201949
if (! $value instanceof Expression) {

src/Illuminate/Database/Query/Grammars/Grammar.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ class Grammar extends BaseGrammar
1818
*/
1919
protected $operators = [];
2020

21+
/**
22+
* The grammar specific bitwise operators.
23+
*
24+
* @var array
25+
*/
26+
protected $bitwiseOperators = [];
27+
2128
/**
2229
* The components that make up a select clause.
2330
*
@@ -255,6 +262,18 @@ protected function whereBasic(Builder $query, $where)
255262
return $this->wrap($where['column']).' '.$operator.' '.$value;
256263
}
257264

265+
/**
266+
* Compile a bitwise operator where clause.
267+
*
268+
* @param \Illuminate\Database\Query\Builder $query
269+
* @param array $where
270+
* @return string
271+
*/
272+
protected function whereBitwise(Builder $query, $where)
273+
{
274+
return $this->whereBasic($query, $where);
275+
}
276+
258277
/**
259278
* Compile a "where in" clause.
260279
*
@@ -1299,4 +1318,14 @@ public function getOperators()
12991318
{
13001319
return $this->operators;
13011320
}
1321+
1322+
/**
1323+
* Get the grammar specific bitwise operators.
1324+
*
1325+
* @return array
1326+
*/
1327+
public function getBitwiseOperators()
1328+
{
1329+
return $this->bitwiseOperators;
1330+
}
13021331
}

src/Illuminate/Database/Query/Grammars/PostgresGrammar.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ class PostgresGrammar extends Grammar
2121
'is distinct from', 'is not distinct from',
2222
];
2323

24+
/**
25+
* The grammar specific bitwise operators.
26+
*
27+
* @var array
28+
*/
29+
protected $bitwiseOperators = [
30+
'~', '&', '|', '#', '<<', '>>', '<<=', '>>=',
31+
];
32+
2433
/**
2534
* {@inheritdoc}
2635
*
@@ -42,6 +51,22 @@ protected function whereBasic(Builder $query, $where)
4251
return parent::whereBasic($query, $where);
4352
}
4453

54+
/**
55+
* {@inheritdoc}
56+
*
57+
* @param \Illuminate\Database\Query\Builder $query
58+
* @param array $where
59+
* @return string
60+
*/
61+
protected function whereBitwise(Builder $query, $where)
62+
{
63+
$value = $this->parameter($where['value']);
64+
65+
$operator = str_replace('?', '??', $where['operator']);
66+
67+
return '('.$this->wrap($where['column']).' '.$operator.' '.$value.')::bool';
68+
}
69+
4570
/**
4671
* Compile a "where date" clause.
4772
*
@@ -206,6 +231,36 @@ protected function compileJsonLength($column, $operator, $value)
206231
return 'json_array_length(('.$column.')::json) '.$operator.' '.$value;
207232
}
208233

234+
/**
235+
* {@inheritdoc}
236+
*
237+
* @param array $having
238+
* @return string
239+
*/
240+
protected function compileHaving(array $having)
241+
{
242+
if ($having['type'] === 'Bitwise') {
243+
return $this->compileHavingBitwise($having);
244+
}
245+
246+
return parent::compileHaving($having);
247+
}
248+
249+
/**
250+
* Compile a having clause involving a bitwise operator.
251+
*
252+
* @param array $having
253+
* @return string
254+
*/
255+
protected function compileHavingBitwise($having)
256+
{
257+
$column = $this->wrap($having['column']);
258+
259+
$parameter = $this->parameter($having['value']);
260+
261+
return $having['boolean'].' ('.$column.' '.$having['operator'].' '.$parameter.')::bool';
262+
}
263+
209264
/**
210265
* Compile the lock into SQL.
211266
*

src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,22 @@ protected function compileFrom(Builder $query, $table)
9696
return $from;
9797
}
9898

99+
/**
100+
* {@inheritdoc}
101+
*
102+
* @param \Illuminate\Database\Query\Builder $query
103+
* @param array $where
104+
* @return string
105+
*/
106+
protected function whereBitwise(Builder $query, $where)
107+
{
108+
$value = $this->parameter($where['value']);
109+
110+
$operator = str_replace('?', '??', $where['operator']);
111+
112+
return '('.$this->wrap($where['column']).' '.$operator.' '.$value.') != 0';
113+
}
114+
99115
/**
100116
* Compile a "where date" clause.
101117
*
@@ -164,6 +180,36 @@ protected function compileJsonLength($column, $operator, $value)
164180
return '(select count(*) from openjson('.$field.$path.')) '.$operator.' '.$value;
165181
}
166182

183+
/**
184+
* {@inheritdoc}
185+
*
186+
* @param array $having
187+
* @return string
188+
*/
189+
protected function compileHaving(array $having)
190+
{
191+
if ($having['type'] === 'Bitwise') {
192+
return $this->compileHavingBitwise($having);
193+
}
194+
195+
return parent::compileHaving($having);
196+
}
197+
198+
/**
199+
* Compile a having clause involving a bitwise operator.
200+
*
201+
* @param array $having
202+
* @return string
203+
*/
204+
protected function compileHavingBitwise($having)
205+
{
206+
$column = $this->wrap($having['column']);
207+
208+
$parameter = $this->parameter($having['value']);
209+
210+
return $having['boolean'].' ('.$column.' '.$having['operator'].' '.$parameter.') != 0';
211+
}
212+
167213
/**
168214
* Create a full ANSI offset clause for the query.
169215
*

tests/Database/DatabaseEloquentModelTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2153,6 +2153,7 @@ protected function addMockConnection($model)
21532153
$model->setConnectionResolver($resolver = m::mock(ConnectionResolverInterface::class));
21542154
$resolver->shouldReceive('connection')->andReturn($connection = m::mock(Connection::class));
21552155
$connection->shouldReceive('getQueryGrammar')->andReturn($grammar = m::mock(Grammar::class));
2156+
$grammar->shouldReceive('getBitwiseOperators')->andReturn([]);
21562157
$connection->shouldReceive('getPostProcessor')->andReturn($processor = m::mock(Processor::class));
21572158
$connection->shouldReceive('query')->andReturnUsing(function () use ($connection, $grammar, $processor) {
21582159
return new BaseBuilder($connection, $grammar, $processor);
@@ -2440,6 +2441,7 @@ public function getConnection()
24402441
{
24412442
$mock = m::mock(Connection::class);
24422443
$mock->shouldReceive('getQueryGrammar')->andReturn($grammar = m::mock(Grammar::class));
2444+
$grammar->shouldReceive('getBitwiseOperators')->andReturn([]);
24432445
$mock->shouldReceive('getPostProcessor')->andReturn($processor = m::mock(Processor::class));
24442446
$mock->shouldReceive('getName')->andReturn('name');
24452447
$mock->shouldReceive('query')->andReturnUsing(function () use ($mock, $grammar, $processor) {

tests/Database/DatabaseQueryBuilderTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3209,6 +3209,41 @@ public function testMySqlSoundsLikeOperator()
32093209
$this->assertEquals(['John Doe'], $builder->getBindings());
32103210
}
32113211

3212+
public function testBitwiseOperators()
3213+
{
3214+
$builder = $this->getBuilder();
3215+
$builder->select('*')->from('users')->where('bar', '&', 1);
3216+
$this->assertSame('select * from "users" where "bar" & ?', $builder->toSql());
3217+
3218+
$builder = $this->getPostgresBuilder();
3219+
$builder->select('*')->from('users')->where('bar', '#', 1);
3220+
$this->assertSame('select * from "users" where ("bar" # ?)::bool', $builder->toSql());
3221+
3222+
$builder = $this->getPostgresBuilder();
3223+
$builder->select('*')->from('users')->where('range', '>>', '[2022-01-08 00:00:00,2022-01-09 00:00:00)');
3224+
$this->assertSame('select * from "users" where ("range" >> ?)::bool', $builder->toSql());
3225+
3226+
$builder = $this->getSqlServerBuilder();
3227+
$builder->select('*')->from('users')->where('bar', '&', 1);
3228+
$this->assertSame('select * from [users] where ([bar] & ?) != 0', $builder->toSql());
3229+
3230+
$builder = $this->getBuilder();
3231+
$builder->select('*')->from('users')->having('bar', '&', 1);
3232+
$this->assertSame('select * from "users" having "bar" & ?', $builder->toSql());
3233+
3234+
$builder = $this->getPostgresBuilder();
3235+
$builder->select('*')->from('users')->having('bar', '#', 1);
3236+
$this->assertSame('select * from "users" having ("bar" # ?)::bool', $builder->toSql());
3237+
3238+
$builder = $this->getPostgresBuilder();
3239+
$builder->select('*')->from('users')->having('range', '>>', '[2022-01-08 00:00:00,2022-01-09 00:00:00)');
3240+
$this->assertSame('select * from "users" having ("range" >> ?)::bool', $builder->toSql());
3241+
3242+
$builder = $this->getSqlServerBuilder();
3243+
$builder->select('*')->from('users')->having('bar', '&', 1);
3244+
$this->assertSame('select * from [users] having ([bar] & ?) != 0', $builder->toSql());
3245+
}
3246+
32123247
public function testMergeWheresCanMergeWheresAndBindings()
32133248
{
32143249
$builder = $this->getBuilder();

0 commit comments

Comments
 (0)