Skip to content

Commit dddaf0e

Browse files
committed
feat: uuid4 generation
1 parent 96bedac commit dddaf0e

File tree

5 files changed

+135
-3
lines changed

5 files changed

+135
-3
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
name: MariaDB 10.10
4343
uses: ./.github/workflows/tests-maria.yml
4444
with:
45-
version: 10.10
45+
version: "10.10"
4646

4747
mysql5_7:
4848
name: MySQL 5.7

README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ new CondXor(string|Expression $value1, string|Expression $value2);
186186
187187
### Functions
188188

189-
### Aggregates
189+
#### Aggregates
190190

191191
```php
192192
use Illuminate\Contracts\Database\Query\Expression;
@@ -228,7 +228,7 @@ Movie::select([
228228
->get();
229229
```
230230

231-
### Conditional
231+
#### Conditional
232232

233233
```php
234234
use Tpetry\QueryExpressions\Function\Conditional\{
@@ -247,6 +247,21 @@ BlogArticle::select([
247247
->get();
248248
```
249249

250+
#### String
251+
```php
252+
use Tpetry\QueryExpressions\Function\String\Uuid4;
253+
254+
new Uuid4();
255+
256+
Schema::table('users', function (Blueprint $table): void {
257+
$table->uuid()->default(new Uuid4())->unique();
258+
});
259+
```
260+
261+
> **Warning**
262+
> The `Uuid4` expression is not available for all database versions.
263+
> With PostgreSQL you need at least v13 and with MariaDB at least v10.10.
264+
250265
## Changelog
251266

252267
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

src/Function/String/Uuid4.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tpetry\QueryExpressions\Function\String;
6+
7+
use Illuminate\Contracts\Database\Query\Expression;
8+
use Illuminate\Database\Grammar;
9+
use Tpetry\QueryExpressions\Concerns\IdentifiesDriver;
10+
11+
class Uuid4 implements Expression
12+
{
13+
use IdentifiesDriver;
14+
15+
public function getValue(Grammar $grammar)
16+
{
17+
// MySQL: The expression needs to be enclosed by parentheses to be used as a default value in create table statements.
18+
// SQLite: The expression needs to be enclosed by parentheses to be used as a default value in create table statements.
19+
// SQLite: First character in 4th group is hardcoded to 8 because the required 8, 9, A, or B can't be randomly generated.
20+
return match ($this->identify($grammar)) {
21+
'mysql' => "(lower(concat(hex(random_bytes(4)),'-',hex(random_bytes(2)),'-4',substr(hex(random_bytes(2)), -3),'-',hex((ascii(random_bytes(1))>>6)+8),substr(hex(random_bytes(2)),-3),'-',hex(random_bytes(6)))))",
22+
'pgsql' => 'gen_random_uuid()',
23+
'sqlite' => "(lower(hex(randomblob(4))||'-'||hex(randomblob(2))||'-4'||substr(hex(randomblob(2)), -3)||'-8'||substr(hex(randomblob(2)),-3)||'-'||hex(randomblob(6))))",
24+
'sqlsrv' => 'lower(newid())',
25+
};
26+
}
27+
}

tests/Function/String/Uuid4Test.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Tpetry\QueryExpressions\Function\String\Uuid4;
6+
7+
it('can generate UUIDv4 values')
8+
->skipOnMariaBefore('10.10')
9+
->skipOnPgsqlBefore('13')
10+
->expect(new Uuid4())
11+
->toBeExecutable()
12+
->toBeMysql("(lower(concat(hex(random_bytes(4)),'-',hex(random_bytes(2)),'-4',substr(hex(random_bytes(2)), -3),'-',hex((ascii(random_bytes(1))>>6)+8),substr(hex(random_bytes(2)),-3),'-',hex(random_bytes(6)))))")
13+
->toBePgsql('gen_random_uuid()')
14+
->toBeSqlite("(lower(hex(randomblob(4))||'-'||hex(randomblob(2))||'-4'||substr(hex(randomblob(2)), -3)||'-8'||substr(hex(randomblob(2)),-3)||'-'||hex(randomblob(6))))")
15+
->toBeSqlsrv('lower(newid())');

tests/Pest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,78 @@
6060

6161
return $this;
6262
});
63+
64+
function skipOnMariaBefore(string $version)
65+
{
66+
/** @var \Illuminate\Database\Connection $connection */
67+
$connection = DB::connection();
68+
69+
if ($connection->getDriverName() !== 'mysql') {
70+
return;
71+
}
72+
73+
$actual = $connection->scalar('select version()');
74+
if (str_contains($actual, 'MariaDB') && version_compare($actual, $version, '<')) {
75+
test()->markTestSkipped("The MariaDB version must be at least {$version}.");
76+
}
77+
}
78+
79+
function skipOnMysqlBefore(string $version): void
80+
{
81+
/** @var \Illuminate\Database\Connection $connection */
82+
$connection = DB::connection();
83+
84+
if ($connection->getDriverName() !== 'mysql') {
85+
return;
86+
}
87+
88+
$actual = $connection->scalar('select version()');
89+
if (! str_contains($actual, 'MariaDB') && version_compare($actual, $version, '<')) {
90+
test()->markTestSkipped("The MariaDB version must be at least {$version}.");
91+
}
92+
}
93+
94+
function skipOnPgsqlBefore(string $version): void
95+
{
96+
/** @var \Illuminate\Database\Connection $connection */
97+
$connection = DB::connection();
98+
99+
if ($connection->getDriverName() !== 'pgsql') {
100+
return;
101+
}
102+
103+
$actual = $connection->scalar('show server_version');
104+
if (version_compare($actual, $version, '<')) {
105+
test()->markTestSkipped("The PostgreSQL version must be at least {$version}.");
106+
}
107+
}
108+
109+
function skipOnSqliteBefore(string $version): void
110+
{
111+
/** @var \Illuminate\Database\Connection $connection */
112+
$connection = DB::connection();
113+
114+
if ($connection->getDriverName() !== 'sqlite') {
115+
return;
116+
}
117+
118+
$actual = $connection->scalar('select sqlite_version()');
119+
if (version_compare($actual, $version, '<')) {
120+
test()->markTestSkipped("The SQLite version must be at least {$version}.");
121+
}
122+
}
123+
124+
function skipOnSqlsrvBefore(string $version): void
125+
{
126+
/** @var \Illuminate\Database\Connection $connection */
127+
$connection = DB::connection();
128+
129+
if ($connection->getDriverName() !== 'sqlsrv') {
130+
return;
131+
}
132+
133+
$actual = $connection->scalar("select serverproperty('productversion')");
134+
if (version_compare($actual, $version, '<')) {
135+
test()->markTestSkipped("The SQL Server version must be at least {$version}.");
136+
}
137+
}

0 commit comments

Comments
 (0)