Skip to content

Commit 045c1bf

Browse files
committed
feat: use any literal with expressions
1 parent 30f3800 commit 045c1bf

File tree

5 files changed

+120
-33
lines changed

5 files changed

+120
-33
lines changed

README.md

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ And you can also create new powerful queries:
3131
```php
3232
// Aggregate multiple statistics with one query for dashboards:
3333
Movie::select([
34-
new CountFilter(new Equal('released', new Number(2021))),
35-
new CountFilter(new Equal('released', new Number(2022))),
36-
new CountFilter(new Equal('genre_id', new Number(12))),
37-
new CountFilter(new Equal('genre_id', new Number(35))),
34+
new CountFilter(new Equal('released', new Value(2021))),
35+
new CountFilter(new Equal('released', new Value(2022))),
36+
new CountFilter(new Equal('genre', new Value('Drama'))),
37+
new CountFilter(new Equal('genre', new Value('Comedy'))),
3838
])->where('streamingservice', 'netflix');
3939
```
4040

@@ -65,25 +65,26 @@ Whenever an expression class needs a `string|Expression` parameter, you can pass
6565

6666
As stated before, an expression is always a column name.
6767
But if you want to e.g. do an equality check, you may want to compare something to a specific value.
68-
That's where you should use the `Number` class.
68+
That's where you should use the `Value` class.
69+
Its values will always be automatically escaped within the query.
6970

7071
```php
71-
use Tpetry\QueryExpressions\Value\Number;
72+
use Tpetry\QueryExpressions\Value\Value;
7273

73-
new Number(44);
74-
new Number(3.1415);
74+
new Value(42);
75+
new Value("Robert'); DROP TABLE students;--");
7576
```
7677

7778
> **Note**
78-
> The `Number` class in isolation is not that usefull.
79+
> The `Value` class in isolation is not that usefull.
7980
> But it will be used more in the next examples.
8081
8182
#### Alias
8283

8384
```php
8485
use Illuminate\Contracts\Database\Query\Expression;
8586
use Tpetry\QueryExpressions\Language\Alias;
86-
use Tpetry\QueryExpressions\Value\Number;
87+
use Tpetry\QueryExpressions\Value\Value;
8788

8889
new Alias(string|Expression $expression, string $name)
8990

@@ -106,7 +107,7 @@ use Illuminate\Contracts\Database\Query\Expression;
106107
use Tpetry\QueryExpressions\Operator\Arithmetic\{
107108
Add, Divide, Modulo, Multiply, Power, Subtract,
108109
};
109-
use Tpetry\QueryExpressions\Operator\Value\Number;
110+
use Tpetry\QueryExpressions\Operator\Value\Value;
110111

111112
new Add(string|Expression $value1, string|Expression $value2);
112113
new Divide(string|Expression $value1, string|Expression $value2);
@@ -117,14 +118,14 @@ new Subtract(string|Expression $value1, string|Expression $value2);
117118

118119
// UPDATE user_quotas SET credits = credits - 15 WHERE id = 1985
119120
$quota->update([
120-
'credits' => new Subtract('credits', new Number(15)),
121+
'credits' => new Subtract('credits', new Value(15)),
121122
]);
122123

123124
// SELECT id, name, (price - discount) * 0.2 AS vat FROM products
124125
Product::select([
125126
'id',
126127
'name',
127-
new Alias(new Multiply(new Subtract('price', 'discount'), new Number(0.2)), 'vat')
128+
new Alias(new Multiply(new Subtract('price', 'discount'), new Value(0.2)), 'vat')
128129
])->get();
129130
```
130131

@@ -135,7 +136,7 @@ use Illuminate\Contracts\Database\Query\Expression;
135136
use Tpetry\QueryExpressions\Operator\Bitwise\{
136137
BitAnd, BitNot, BitOr, BitXor, ShiftLeft, ShiftRight,
137138
};
138-
use Tpetry\QueryExpressions\Operator\Value\Number;
139+
use Tpetry\QueryExpressions\Operator\Value\Value;
139140

140141
new BitAnd(string|Expression $value1, string|Expression $value2);
141142
new BitNot(string|Expression $value);
@@ -145,7 +146,7 @@ new ShiftLeft(string|Expression $value, string|Expression $times);
145146
new ShiftRight(string|Expression $value, string|Expression $times);
146147

147148
// SELECT * FROM users WHERE (acl & 0x8000) = 0x8000
148-
User::where(new BitAnd('acl', 0x8000), 0x8000)
149+
User::where(new BitAnd('acl', new Value(0x8000)), 0x8000)
149150
->get();
150151
```
151152

@@ -159,7 +160,6 @@ use Tpetry\QueryExpressions\Operator\Comparison\{
159160
use Tpetry\QueryExpressions\Operator\Logical\{
160161
CondAnd, CondNot, CondOr, CondXor
161162
};
162-
use Tpetry\QueryExpressions\Operator\Value\Number;
163163

164164
new Between(string|Expression $value, string|Expression $min, string|Expression $max);
165165
new DistinctFrom(string|Expression $value1, string|Expression $value2);
@@ -193,7 +193,7 @@ use Illuminate\Contracts\Database\Query\Expression;
193193
use Tpetry\QueryExpressions\Function\Aggregate\{
194194
Avg, Count, CountFilter, Max, Min, Sum, SumFilter,
195195
};
196-
use Tpetry\QueryExpressions\Operator\Value\Number;
196+
use Tpetry\QueryExpressions\Operator\Value\Value;
197197

198198
new Avg(string|Expression $value);
199199
new Count(string|Expression $value);
@@ -214,15 +214,15 @@ BlogVisit::select([
214214
// SELECT
215215
// COUNT(*) FILTER (WHERE (released = 2021)) AS released_2021,
216216
// COUNT(*) FILTER (WHERE (released = 2022)) AS released_20212,
217-
// COUNT(*) FILTER (WHERE (genre_id = 12)) AS genre_12,
218-
// COUNT(*) FILTER (WHERE (genre_id = 35)) AS genre_35
217+
// COUNT(*) FILTER (WHERE (genre = 'Drama')) AS genre_drama,
218+
// COUNT(*) FILTER (WHERE (genre = 'Comedy')) AS genre_comedy
219219
// FROM movies
220220
// WHERE streamingservice = 'netflix'
221221
Movie::select([
222-
new Alias(new CountFilter(new Equal('released', new Number(2021))), 'released_2021'),
223-
new Alias(new CountFilter(new Equal('released', new Number(2022))), 'released_2022'),
224-
new Alias(new CountFilter(new Equal('genre_id', new Number(12))), 'genre_12'),
225-
new Alias(new CountFilter(new Equal('genre_id', new Number(35))), 'genre_35'),
222+
new Alias(new CountFilter(new Equal('released', new Value(2021))), 'released_2021'),
223+
new Alias(new CountFilter(new Equal('released', new Value(2022))), 'released_2022'),
224+
new Alias(new CountFilter(new Equal('genre', new Value('Drama'))), 'genre_drama'),
225+
new Alias(new CountFilter(new Equal('genre', new Value('Comedy'))), 'genre_comedy'),
226226
])
227227
->where('streamingservice', 'netflix')
228228
->get();

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
],
1919
"require": {
2020
"php": "^8.1",
21-
"illuminate/contracts": "^10.0",
22-
"illuminate/database": "^10.0",
21+
"illuminate/contracts": "^10.13.0",
22+
"illuminate/database": "^10.13.0",
2323
"illuminate/support": "^10.0"
2424
},
2525
"require-dev": {

src/Value/Value.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tpetry\QueryExpressions\Value;
6+
7+
use Illuminate\Contracts\Database\Query\Expression;
8+
use Illuminate\Database\Grammar;
9+
10+
class Value implements Expression
11+
{
12+
public function __construct(
13+
private readonly string|int|float|bool|null $value,
14+
private readonly bool $binary = false,
15+
) {
16+
}
17+
18+
public function getValue(Grammar $grammar): string
19+
{
20+
return $grammar->escape($this->value, $this->binary);
21+
}
22+
}

tests/Pest.php

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22

33
declare(strict_types=1);
44

5-
use Illuminate\Database\Query\Grammars\MySqlGrammar;
6-
use Illuminate\Database\Query\Grammars\PostgresGrammar;
7-
use Illuminate\Database\Query\Grammars\SQLiteGrammar;
8-
use Illuminate\Database\Query\Grammars\SqlServerGrammar;
95
use Illuminate\Support\Facades\DB;
106
use Pest\Expectation;
117
use PHPUnit\Framework\Assert;
@@ -38,25 +34,33 @@
3834
});
3935

4036
expect()->extend('toBeMysql', function (string $expected): Expectation {
41-
Assert::assertSame($expected, $this->value->getValue(new MySqlGrammar()));
37+
if (DB::connection()->getDriverName() === 'mysql') {
38+
Assert::assertSame($expected, $this->value->getValue(DB::connection()->getQueryGrammar()));
39+
}
4240

4341
return $this;
4442
});
4543

4644
expect()->extend('toBePgsql', function (string $expected): Expectation {
47-
Assert::assertSame($expected, $this->value->getValue(new PostgresGrammar()));
45+
if (DB::connection()->getDriverName() === 'pgsql') {
46+
Assert::assertSame($expected, $this->value->getValue(DB::connection()->getQueryGrammar()));
47+
}
4848

4949
return $this;
5050
});
5151

5252
expect()->extend('toBeSqlite', function (string $expected): Expectation {
53-
Assert::assertSame($expected, $this->value->getValue(new SQLiteGrammar()));
53+
if (DB::connection()->getDriverName() === 'sqlite') {
54+
Assert::assertSame($expected, $this->value->getValue(DB::connection()->getQueryGrammar()));
55+
}
5456

5557
return $this;
5658
});
5759

5860
expect()->extend('toBeSqlsrv', function (string $expected): Expectation {
59-
Assert::assertSame($expected, $this->value->getValue(new SqlServerGrammar()));
61+
if (DB::connection()->getDriverName() === 'sqlsrv') {
62+
Assert::assertSame($expected, $this->value->getValue(DB::connection()->getQueryGrammar()));
63+
}
6064

6165
return $this;
6266
});

tests/Value/ValueTest.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Tpetry\QueryExpressions\Value\Value;
6+
7+
it('can output an integer')
8+
->expect(new Value(10))
9+
->toBeExecutable()
10+
->toBeMysql('10')
11+
->toBePgsql('10')
12+
->toBeSqlite('10')
13+
->toBeSqlsrv('10');
14+
15+
it('can output a float')
16+
->expect(new Value(3.141592))
17+
->toBeExecutable()
18+
->toBeMysql('3.141592')
19+
->toBePgsql('3.141592')
20+
->toBeSqlite('3.141592')
21+
->toBeSqlsrv('3.141592');
22+
23+
it('can output a boolean (true)')
24+
->expect(new Value(true))
25+
->toBeExecutable()
26+
->toBeMysql('1')
27+
->toBePgsql('true')
28+
->toBeSqlite('1')
29+
->toBeSqlsrv('1');
30+
31+
it('can output a boolean (false)')
32+
->expect(new Value(false))
33+
->toBeExecutable()
34+
->toBeMysql('0')
35+
->toBePgsql('false')
36+
->toBeSqlite('0')
37+
->toBeSqlsrv('0');
38+
39+
it('can output a string')
40+
->expect(new Value("Robert'); DROP TABLE students;--"))
41+
->toBeExecutable()
42+
->toBeMysql("'Robert\'); DROP TABLE students;--'")
43+
->toBePgsql("'Robert''); DROP TABLE students;--'")
44+
->toBeSqlite("'Robert''); DROP TABLE students;--'")
45+
->toBeSqlsrv("'Robert''); DROP TABLE students;--'");
46+
47+
it('can output binary')
48+
->expect(new Value(hex2bin('dead00beef'), true))
49+
->toBeExecutable()
50+
->toBeMysql("x'dead00beef'")
51+
->toBePgsql("'\\xdead00beef'::bytea")
52+
->toBeSqlite("x'dead00beef'")
53+
->toBeSqlsrv('0xdead00beef');
54+
55+
it('can output null')
56+
->expect(new Value(null))
57+
->toBeExecutable()
58+
->toBeMysql('null')
59+
->toBePgsql('null')
60+
->toBeSqlite('null')
61+
->toBeSqlsrv('null');

0 commit comments

Comments
 (0)