Skip to content

Commit b18361a

Browse files
author
Deploy
committed
feat!: expanded key generator selection
1 parent 2a9956e commit b18361a

File tree

8 files changed

+263
-88
lines changed

8 files changed

+263
-88
lines changed

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
"require": {
2525
"php": "^8.2",
2626
"ext-json": "*",
27-
"composer/composer": "^2.7.0",
28-
"laravel-freelancer-nl/arangodb-php-client": "^2.7.0",
27+
"composer/composer": "^2.8.0",
28+
"laravel-freelancer-nl/arangodb-php-client": "^2.8.0",
2929
"laravel-freelancer-nl/fluentaql": "^2.0",
3030
"laravel/framework": "^11.0",
3131
"spatie/laravel-data": "^4.4.0",

config/arangodb.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,22 @@
55
return [
66
'datetime_format' => 'Y-m-d\TH:i:s.vp',
77
'schema' => [
8+
/*
9+
* @see https://docs.arangodb.com/stable/develop/http-api/collections/#create-a-collection_body_keyOptions_allowUserKeys
10+
*/
811
'keyOptions' => [
912
'allowUserKeys' => true,
1013
'type' => 'traditional',
1114
],
15+
'key_handling' => [
16+
'prioritize_configured_key_type' => false,
17+
'use_traditional_over_autoincrement' => true,
18+
],
19+
// Key type prioritization takes place in the following order:
20+
// 1: table config within the migration file (this always takes priority)
21+
// 2: The id column methods such as id() and ...Increments() methods in the migration file
22+
// 3: The configured key type above.
23+
// The order of 2 and 3 can be swapped; in which case the configured key takes priority over column methods.
24+
// These settings are merged, individual keyOptions can be overridden in this way.
1225
],
1326
];

docs/key-options.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Key generator options
2+
Tables in ArangoDB can be created using one of the available key generators. You can read up about them
3+
[here](https://docs.arangodb.com/stable/concepts/data-structure/documents/#document-keys)
4+
and [here](https://docs.arangodb.com/stable/develop/http-api/collections/#create-a-collection_body_keyOptions).
5+
6+
The following assumes you have knowledge about ArangoDB keys as can be obtained through the above links.
7+
8+
## Column defined key generators
9+
Laravel has several column methods which can be used to set a primary key. If the given field is equal to:
10+
'id', '_key' or '_id', the key generator will be set according to the mapping below.
11+
12+
If these column methods are not found, or not called on these fields, the configured default generator is used.
13+
You can also ignore the column methods by setting the config value
14+
'arangodb.schema.key_handling.prioritize_configured_key_type' to true.
15+
16+
By default, we map the key methods to the following ArangoDB key generators:
17+
18+
| Laravel column method | ArangoDB key generator |
19+
|:----------------------|:-----------------------|
20+
| autoIncrement() | traditional |
21+
| id() | traditional |
22+
| increments('id') | traditional |
23+
| smallIncrements | traditional |
24+
| bigIncrements | traditional |
25+
| mediumIncrements | traditional |
26+
| uuid(id) | uuid |
27+
| ulid(id) | _n/a_ |
28+
29+
## Traditional vs autoincrement key generators
30+
Even though ArangoDB has an autoincrement key generator we don't use it by default as it is not cluster safe.
31+
The traditional key generator is similar to autoincrement: it is cluster safe although there may be gaps between
32+
the _key increases.
33+
34+
If you want the column methods to set the generator to autoincrement you can override the default behaviour by setting
35+
the config value 'arangodb.schema.key_handling.use_traditional_over_autoincrement' to false.
36+
In which case any given offset in the 'from' method is also used.
37+
38+
## ulid
39+
There is no ulid key generator in ArangoDB. The 'padded' generator may be used if you want
40+
a lexigraphical sort order. You can do so by setting it in the config as the default key, and using configured keys only.
41+
Or by setting it within the migration in the table options.
42+
43+
## Table option key generators
44+
You can set the key options for the table in the migration. This overrides both the default key options and the one defined by column methods.
45+
46+
```
47+
Schema::create('taggables', function (Blueprint $collection) {
48+
//
49+
}, [
50+
'keyOptions' => [
51+
'type' => 'padded',
52+
],
53+
]);
54+
```
55+

src/Connection.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ class Connection extends IlluminateConnection
2727
use ManagesTransactions;
2828
use RunsQueries;
2929

30-
protected ?ArangoClient $arangoClient = null;
30+
/**
31+
* @var ArangoClient|null
32+
*/
33+
protected $arangoClient;
3134

3235
/**
3336
* The ArangoDB driver name.

src/Schema/Blueprint.php

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -276,36 +276,22 @@ public function __call($method, $args = [])
276276
'unsignedTinyInteger', 'uuid', 'year',
277277
];
278278

279-
$keyMethods = ['bigIncrements', 'increments', 'mediumIncrements', ];
280-
281279
if (in_array($method, $columnMethods)) {
282280
if (isset($args[0]) && is_string($args[0])) {
283281
$this->columns[] = $args[0];
284282
}
285283
}
286284

287-
$autoIncrementMethods = ['increments', 'autoIncrement'];
288-
if (in_array($method, $autoIncrementMethods)) {
289-
$this->setKeyGenerator('autoincrement');
290-
}
291-
292-
if ($method === 'uuid') {
293-
$this->setKeyGenerator('uuid');
285+
$keyMethods = ['autoIncrement', 'bigIncrements', 'increments', 'mediumIncrements', 'tinyIncrements', 'uuid'];
286+
if (in_array($method, $keyMethods)) {
287+
$this->handleKeyCommands($method, $args);
294288
}
295289

296290
$this->ignoreMethod($method);
297291

298292
return $this;
299293
}
300294

301-
protected function setKeyGenerator(string $generator = 'traditional'): void
302-
{
303-
$column = end($this->columns);
304-
if ($column === '_key' || $column === 'id') {
305-
$this->keyGenerator = $generator;
306-
}
307-
}
308-
309295
protected function ignoreMethod(string $method)
310296
{
311297
$info = [];
@@ -320,4 +306,26 @@ public function renameIdField(mixed $fields)
320306
return $value === 'id' ? '_key' : $value;
321307
}, $fields);
322308
}
309+
310+
/**
311+
* @param mixed[] $options
312+
* @return mixed[]
313+
*/
314+
protected function setKeyOptions($tableOptions)
315+
{
316+
$configuredKeyOptions = config('arangodb.schema.keyOptions');
317+
318+
$columnOptions = [];
319+
$columnOptions['type'] = $this->keyGenerator;
320+
321+
$mergedKeyOptions = (config('arangodb.schema.key_handling.prioritize_configured_key_type'))
322+
? array_merge($columnOptions, $configuredKeyOptions, $tableOptions)
323+
: array_merge($configuredKeyOptions, $columnOptions, $tableOptions);
324+
325+
if ($mergedKeyOptions['type'] === 'autoincrement' && $this->incrementOffset !== 0) {
326+
$mergedKeyOptions['offset'] = $this->incrementOffset;
327+
}
328+
329+
return $mergedKeyOptions;
330+
}
323331
}

src/Schema/Concerns/TableCommands.php

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,55 @@ trait TableCommands
1717
public function create($options = [])
1818
{
1919
$parameters = [];
20-
$parameters['options'] = array_merge(
21-
[
22-
'keyOptions' => config('arangodb.schema.keyOptions'),
23-
],
24-
$options,
25-
);
20+
$parameters['options'] = $options;
2621
$parameters['explanation'] = "Create '{$this->table}' table.";
2722
$parameters['handler'] = 'table';
2823

2924
return $this->addCommand('create', $parameters);
3025
}
3126

32-
public function executeCreateCommand($command)
27+
/**
28+
* @param string $command
29+
* @param mixed[] $args
30+
* @return void
31+
*/
32+
public function handleKeyCommands($command, $args)
3333
{
34-
if ($this->connection->pretending()) {
35-
$this->connection->logQuery('/* ' . $command->explanation . " */\n", []);
34+
$acceptedKeyFields = ['id', '_id', '_key'];
35+
36+
$columns = ($command === 'autoIncrement') ? end($this->columns) : $args;
37+
$columns = (is_array($columns)) ? $columns : [$columns];
3638

39+
if (count($columns) !== 1 || ! in_array($columns[0], $acceptedKeyFields)) {
3740
return;
3841
}
39-
$options = $command->options;
4042

41-
if ($this->keyGenerator !== 'traditional') {
42-
$options['keyOptions']['type'] = $this->keyGenerator;
43+
if ($command === 'uuid') {
44+
$this->keyGenerator = 'uuid';
45+
46+
return;
4347
}
4448

45-
if ($this->keyGenerator === 'autoincrement' && $this->incrementOffset !== 0) {
46-
$options['keyOptions']['offset'] = $this->incrementOffset;
49+
if (config('arangodb.schema.key_handling.use_traditional_over_autoincrement') === false) {
50+
$this->keyGenerator = 'autoincrement';
51+
52+
return;
4753
}
4854

55+
$this->keyGenerator = 'traditional';
56+
}
57+
58+
public function executeCreateCommand($command)
59+
{
60+
if ($this->connection->pretending()) {
61+
$this->connection->logQuery('/* ' . $command->explanation . " */\n", []);
62+
63+
return;
64+
}
65+
66+
$options = $command->options;
67+
$options['keyOptions'] = $this->setKeyOptions($options['keyOptions'] ?? []);
68+
4969
if (!$this->schemaManager->hasCollection($this->table)) {
5070
$this->schemaManager->createCollection($this->table, $options);
5171
}

tests/Schema/TableKeyTest.php

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use LaravelFreelancerNL\Aranguent\Schema\Blueprint;
6+
7+
test('creating table with default key generator', function () {
8+
$schema = DB::connection()->getSchemaBuilder();
9+
10+
$schema->create('white_walkers', function (Blueprint $table) use (& $creating) {});
11+
12+
$schemaManager = $this->connection->getArangoClient()->schema();
13+
14+
$collectionProperties = $schemaManager->getCollectionProperties('white_walkers');
15+
16+
expect($collectionProperties->keyOptions->type)->toBe('traditional');
17+
18+
Schema::drop('white_walkers');
19+
});
20+
21+
test('creating table with different default key generator', function () {
22+
Config::set('arangodb.schema.keyOptions.type', 'padded');
23+
$schema = DB::connection()->getSchemaBuilder();
24+
25+
$schema->create('white_walkers', function (Blueprint $table) use (& $creating) {});
26+
27+
$schemaManager = $this->connection->getArangoClient()->schema();
28+
29+
$collectionProperties = $schemaManager->getCollectionProperties('white_walkers');
30+
31+
expect($collectionProperties->keyOptions->type)->toBe('padded');
32+
33+
Schema::drop('white_walkers');
34+
});
35+
36+
test('creating table with autoincrement key generator', function () {
37+
Config::set('arangodb.schema.key_handling.use_traditional_over_autoincrement', false);
38+
39+
$schema = DB::connection()->getSchemaBuilder();
40+
41+
$schema->create('white_walkers', function (Blueprint $table) use (& $creating) {
42+
$table->increments('id');
43+
});
44+
45+
$schemaManager = $this->connection->getArangoClient()->schema();
46+
47+
$collectionProperties = $schemaManager->getCollectionProperties('white_walkers');
48+
49+
expect($collectionProperties->keyOptions->type)->toBe('autoincrement');
50+
51+
Schema::drop('white_walkers');
52+
Config::set('arangodb.schema.key_handling.use_traditional_over_autoincrement', true);
53+
});
54+
55+
test('creating table with autoIncrement offset', function () {
56+
Config::set('arangodb.schema.key_handling.use_traditional_over_autoincrement', false);
57+
58+
$schema = DB::connection()->getSchemaBuilder();
59+
60+
$schema->create('white_walkers', function (Blueprint $table) use (& $creating) {
61+
$table->string('id')->autoIncrement()->from(5);
62+
});
63+
64+
$schemaManager = $this->connection->getArangoClient()->schema();
65+
66+
$collectionProperties = $schemaManager->getCollectionProperties('white_walkers');
67+
68+
expect($collectionProperties->keyOptions->type)->toBe('autoincrement');
69+
expect($collectionProperties->keyOptions->offset)->toBe(5);
70+
71+
Schema::drop('white_walkers');
72+
});
73+
74+
test('create table with uuid key generator', function () {
75+
$schema = DB::connection()->getSchemaBuilder();
76+
77+
$schema->create('white_walkers', function (Blueprint $table) use (& $creating) {
78+
$table->uuid('id');
79+
});
80+
81+
$schemaManager = $this->connection->getArangoClient()->schema();
82+
83+
$collectionProperties = $schemaManager->getCollectionProperties('white_walkers');
84+
85+
expect($collectionProperties->keyOptions->type)->toBe('uuid');
86+
87+
Schema::drop('white_walkers');
88+
});
89+
90+
test('table options override column key generator', function () {
91+
$schema = DB::connection()->getSchemaBuilder();
92+
93+
$schema->create('white_walkers', function (Blueprint $table) use (& $creating) {
94+
$table->uuid('id');
95+
}, [
96+
'keyOptions' => [
97+
'type' => 'padded',
98+
],
99+
]);
100+
101+
$schemaManager = $this->connection->getArangoClient()->schema();
102+
103+
$collectionProperties = $schemaManager->getCollectionProperties('white_walkers');
104+
105+
expect($collectionProperties->keyOptions->type)->toBe('padded');
106+
107+
Schema::drop('white_walkers');
108+
});
109+
110+
test('table options override default key generator', function () {
111+
$schema = DB::connection()->getSchemaBuilder();
112+
113+
$schema->create('white_walkers', function (Blueprint $table) use (& $creating) {}, [
114+
'keyOptions' => [
115+
'type' => 'padded',
116+
'allowUserKeys' => false,
117+
],
118+
]);
119+
120+
$schemaManager = $this->connection->getArangoClient()->schema();
121+
122+
$collectionProperties = $schemaManager->getCollectionProperties('white_walkers');
123+
124+
expect($collectionProperties->keyOptions->type)->toBe('padded');
125+
expect($collectionProperties->keyOptions->allowUserKeys)->toBeFalse();
126+
127+
Schema::drop('white_walkers');
128+
});

0 commit comments

Comments
 (0)