Completed
Push — master ( 0f1131...6b0411 )
by Bas
05:36
created

Blueprint::geoIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
c 0
b 0
f 0
rs 10
cc 1
nc 1
nop 2
1
<?php
2
3
namespace LaravelFreelancerNL\Aranguent\Schema;
4
5
use Closure;
6
use Illuminate\Support\Fluent;
7
use Illuminate\Database\Connection;
8
use Illuminate\Support\Traits\Macroable;
9
use Illuminate\Database\Schema\Grammars\Grammar as IlluminateGrammar;
10
11
/**
12
 * Class Blueprint.
13
 *
14
 * The Schema blueprint works differently from the standard Illuminate version:
15
 * 1) ArangoDB is schemaless: we don't need to (and can't) create columns
16
 * 2) ArangoDB doesn't allow DB schema actions within AQL nor within a transaction.
17
 *
18
 * This means that:
19
 * 1) We catch column related methods silently for backwards compatibility and ease of migrating from one DB type to another
20
 * 2) We don't need to compile AQL for transactions within the accompanying schema grammar. (for now)
21
 * 3) We can just execute each command on order. We will gather them first for possible future optimisations.
22
 */
23
class Blueprint
24
{
25
    use Macroable;
0 ignored issues
show
Bug introduced by
The trait Illuminate\Support\Traits\Macroable requires the property $name which is not provided by LaravelFreelancerNL\Aranguent\Schema\Blueprint.
Loading history...
26
27
    /**
28
     * The connection that is used by the blueprint.
29
     *
30
     * @var \Illuminate\Database\Connection
31
     */
32
    protected $connection;
33
34
    /**
35
     * The grammar that is used by the blueprint.
36
     *
37
     * @var \LaravelFreelancerNL\Aranguent\Schema\Grammars\Grammar
38
     */
39
    protected $grammar;
40
41
    /**
42
     * The collection the blueprint describes.
43
     *
44
     * @var string
45
     */
46
    protected $collection;
47
48
    /**
49
     * The handler for collection manipulation.
50
     */
51
    protected $collectionHandler;
52
53
    /**
54
     * The prefix of the collection.
55
     *
56
     * @var string
57
     */
58
    protected $prefix;
59
60
    /**
61
     * The commands that should be run for the collection.
62
     *
63
     * @var \Illuminate\Support\Fluent[]
64
     */
65
    protected $commands = [];
66
67
    /**
68
     * Catching attributes to be able to add fluent indexes.
69
     *
70
     * @var array
71
     */
72
    protected $attributes = [];
73
74
    /**
75
     * Whether to make the collection temporary.
76
     *
77
     * @var bool
78
     */
79
    public $temporary = false;
80
81
    /**
82
     * Detect if _key (and thus proxy _id) should autoincrement.
83
     *
84
     * @var bool
85
     */
86
    protected $autoIncrement = false;
87
88
    /**
89
     * Create a new schema blueprint.
90
     *
91
     * Blueprint constructor.
92
     * @param string $collection
93
     * @param \ArangoDBClient\CollectionHandler $collectionHandler
94
     * @param Closure|null $callback
95
     * @param string $prefix
96
     */
97
    public function __construct($collection, $collectionHandler, Closure $callback = null, $prefix = '')
98
    {
99
        $this->collection = $collection;
100
101
        $this->collectionHandler = $collectionHandler;
102
103
        $this->prefix = $prefix;
104
105
        if (! is_null($callback)) {
106
            $callback($this);
107
        }
108
    }
109
110
    /**
111
     * Execute the blueprint against the database.
112
     *
113
     * @param  \Illuminate\Database\Connection  $connection
114
     * @param  \Illuminate\Database\Schema\Grammars\Grammar  $grammar
115
     * @return void
116
     */
117
    public function build(Connection $connection, IlluminateGrammar $grammar)
118
    {
119
        $this->connection = $connection;
120
        $this->grammar = $connection->getSchemaGrammar();
121
122
        foreach ($this->commands as $command) {
123
            if ($command->handler == 'aql') {
124
                $command = $this->compileAqlCommand($command);
125
            }
126
127
            $this->executeCommand($command);
128
        }
129
    }
130
131
    /**
132
     * Generate the compilation method name and call it if method exists in the Grammar object.
133
     *
134
     * @param $command
135
     * @return mixed
136
     */
137
    public function compileAqlCommand($command)
138
    {
139
        $compileMethod = 'compile'.ucfirst($command->name);
140
        if (method_exists($this->grammar, $compileMethod)) {
141
            return $this->grammar->$compileMethod($this->collection, $command);
142
        }
143
    }
144
145
    /**
146
     * Generate the execution method name and call it if the method exists.
147
     *
148
     * @param $command
149
     */
150
    public function executeCommand($command)
151
    {
152
        $executeNamedMethod = 'execute'.ucfirst($command->name).'Command';
153
        $executeHandlerMethod = 'execute'.ucfirst($command->handler).'Command';
154
        if (method_exists($this, $executeNamedMethod)) {
155
            $this->$executeNamedMethod($command);
156
        } elseif (method_exists($this, $executeHandlerMethod)) {
157
            $this->$executeHandlerMethod($command);
158
        }
159
    }
160
161
    /**
162
     * Execute an AQL statement.
163
     *
164
     * @param $command
165
     */
166
    public function executeAqlCommand($command)
167
    {
168
        $this->connection->statement($command->aqb->query, $command->aqb->binds);
169
    }
170
171
    public function executeCollectionCommand($command)
172
    {
173
        if ($this->connection->pretending()) {
174
            $this->connection->logQuery('/* '.$command->explanation." */\n", []);
175
176
            return;
177
        }
178
179
        if (method_exists($this->collectionHandler, $command->method)) {
180
            $this->collectionHandler->{$command->method}($command->parameters);
181
        }
182
    }
183
184
    /**
185
     * Drop the index by first getting all the indexes on the collection; then selecting the matching one
186
     * by type and attributes.
187
     * @param $command
188
     * @throws \ArangoDBClient\Exception
189
     */
190
    public function executeDropIndexCommand($command)
191
    {
192
        if ($this->connection->pretending()) {
193
            $this->connection->logQuery('/* '.$command->explanation." */\n", []);
194
195
            return;
196
        }
197
        $attributes = $command->attributes;
198
        if (is_string($attributes)) {
199
            $attributes = [$attributes];
200
        }
201
        $indexId = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $indexId is dead and can be removed.
Loading history...
202
        $indexData = $this->collectionHandler->getIndexes($this->collection);
203
        foreach ($indexData['indexes'] as $key => $index) {
204
            if (
205
                $index['type'] === $command->type
206
                && empty(array_diff($index['fields'], $attributes))
207
                && empty(array_diff($attributes, $index['fields']))
208
            ) {
209
                $this->collectionHandler->dropIndex($index['id']);
210
            }
211
        }
212
    }
213
214
    public function executeIndexCommand($command)
215
    {
216
        if ($this->connection->pretending()) {
217
            $this->connection->logQuery('/* '.$command->explanation." */\n", []);
218
219
            return;
220
        }
221
222
        $this->collectionHandler->index($this->collection, $command->type, $command->attributes, $command->unique, $command->indexOptions);
0 ignored issues
show
Deprecated Code introduced by
The function ArangoDBClient\CollectionHandler::index() has been deprecated: use CollectionHandler::createIndex instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

222
        /** @scrutinizer ignore-deprecated */ $this->collectionHandler->index($this->collection, $command->type, $command->attributes, $command->unique, $command->indexOptions);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
223
    }
224
225
    /**
226
     * Solely provides feedback to the developer in pretend mode.
227
     *
228
     * @param $command
229
     * @return null
230
     */
231
    public function executeIgnoreCommand($command)
232
    {
233
        if ($this->connection->pretending()) {
234
            $this->connection->logQuery('/* '.$command->explanation." */\n", []);
235
236
            return;
237
        }
238
    }
239
240
    /**
241
     * Determine if the blueprint has a create command.
242
     *
243
     * @return bool
244
     */
245
    protected function creating()
246
    {
247
        return collect($this->commands)->contains(function ($command) {
248
            return $command->name === 'create';
249
        });
250
    }
251
252
    /**
253
     * Indicate that the collection needs to be created.
254
     *
255
     * @param array $config
256
     * @return \Illuminate\Support\Fluent
257
     */
258
    public function create($config = [])
259
    {
260
        $parameters['config'] = $config;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
261
        $parameters['explanation'] = "Create '{$this->collection}' collection.";
262
        $parameters['handler'] = 'collection';
263
264
        return $this->addCommand('create', $parameters);
265
    }
266
267
    public function executeCreateCommand($command)
268
    {
269
        if ($this->connection->pretending()) {
270
            $this->connection->logQuery('/* '.$command->explanation." */\n", []);
271
272
            return;
273
        }
274
        $config = $command->config;
275
        if ($this->temporary === true) {
276
            $config['isVolatile'] = true;
277
        }
278
        if ($this->autoIncrement === true) {
279
            $config['keyOptions']['autoincrement'] = true;
280
        }
281
        $this->collectionHandler->create($this->collection, $config);
282
    }
283
284
    /**
285
     * Indicate that the collection should be dropped.
286
     *
287
     * @return \Illuminate\Support\Fluent
288
     */
289
    public function drop()
290
    {
291
        $parameters['explanation'] = "Drop the '{$this->collection}' collection.";
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
292
        $parameters['handler'] = 'collection';
293
294
        return $this->addCommand('drop', $parameters);
295
    }
296
297
    /**
298
     * Indicate that the collection should be dropped if it exists.
299
     *
300
     * @return \Illuminate\Support\Fluent
301
     */
302
    public function dropIfExists()
303
    {
304
        $parameters['explanation'] = "Drop the '{$this->collection}' collection.";
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
305
        $parameters['handler'] = 'collection';
306
307
        return $this->addCommand('dropIfExists');
308
    }
309
310
    /**
311
     * Indicate that the given attribute(s) should be dropped.
312
     *
313
     * @param  array|mixed  $attributes
314
     * @return \Illuminate\Support\Fluent
315
     */
316
    public function dropColumn($attributes)
317
    {
318
        $attributes = is_array($attributes) ? $attributes : func_get_args();
319
320
        $parameters['handler'] = 'aql';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
321
        $parameters['attributes'] = $attributes;
322
        $parameters['explanation'] = 'Drop the following attribute(s): '.implode(',', $attributes).'.';
323
324
        return $this->addCommand('dropAttribute', compact('parameters'));
325
    }
326
327
    /**
328
     * Check if any document within the collection has the attribute.
329
     *
330
     * @param string|array $attribute
331
     * @return Fluent
332
     */
333
    public function hasAttribute($attribute)
334
    {
335
        $parameters['handler'] = 'aql';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
336
        $parameters['explanation'] = "Checking if any document within the collection has the '".implode(', ', (array) $attribute)."' attribute(s).";
337
        $parameters['attribute'] = $attribute;
338
339
        return $this->addCommand('hasAttribute', $parameters);
340
    }
341
342
    /**
343
     * Indicate that the given attributes should be renamed.
344
     *
345
     * @param  string  $from
346
     * @param  string  $to
347
     * @return \Illuminate\Support\Fluent
348
     */
349
    public function renameColumn($from, $to)
350
    {
351
        $parameters['handler'] = 'aql';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
352
        $parameters['explanation'] = "Rename the attribute '$from' to '$to'.";
353
        $parameters['from'] = $from;
354
        $parameters['to'] = $to;
355
356
        return $this->addCommand('renameAttribute', $parameters);
357
    }
358
359
    /**
360
     * Indicate that the given index should be dropped.
361
     *
362
     * @param  string|array  $attributes
363
     * @param  string  $type
364
     * @return \Illuminate\Support\Fluent
365
     */
366
    public function dropIndex($attributes, $type)
367
    {
368
        $parameters['attributes'] = $attributes;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.
Loading history...
369
        $parameters['type'] = $type;
370
        $parameters['explanation'] = "Drop the '".$type."' index on [".implode(', ', (array) $attributes).'].';
371
        $parameters['handler'] = 'collection';
372
373
        return $this->addCommand('dropIndex', $parameters);
374
    }
375
376
    /**
377
     * Rename the collection to a given name.
378
     *
379
     * @param  string  $to
380
     * @return \Illuminate\Support\Fluent
381
     */
382
    public function rename($to)
383
    {
384
        return $this->addCommand('rename', compact('to'));
385
    }
386
387
    /**
388
     * Specify an index for the table.
389
     *
390
     * @param  string|array  $columns
391
     * @param  string  $name
392
     * @param  string|null  $algorithm
393
     * @return \Illuminate\Support\Fluent
394
     */
395
    public function index($columns = null, $name = null, $algorithm = null)
396
    {
397
        $type = $this->mapIndexType($algorithm);
398
399
        return $this->indexCommand($type, $columns);
400
    }
401
402
    /**
403
     * Create a hash index for fast exact matching.
404
     * Hash ! has ;).
405
     *
406
     * @param null $attributes
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $attributes is correct as it would always require null to be passed?
Loading history...
407
     * @param array $indexOptions
408
     * @return Fluent
409
     */
410
    public function hashIndex($attributes = null, $indexOptions = [])
411
    {
412
        return $this->indexCommand('hash', $attributes, $indexOptions);
413
    }
414
415
    /**
416
     * @param null|string $attribute
417
     * @param array $indexOptions
418
     * @return Fluent
419
     */
420
    public function fulltextIndex($attribute = null, $indexOptions = [])
421
    {
422
        return $this->indexCommand('fulltext', $attribute, $indexOptions);
423
    }
424
425
    /**
426
     *  Specify a spatial index for the collection.
427
     *
428
     * @param $attributes
429
     * @param array $indexOptions
430
     * @return Fluent
431
     */
432
    public function geoIndex($attributes, $indexOptions = [])
433
    {
434
        return $this->indexCommand('geo', $attributes, $indexOptions);
435
    }
436
437
    /**
438
     * Specify a spatial index for the table.
439
     * Alias for geoIndex().
440
     * @param  string|array  $columns
441
     * @param  string  $name
442
     * @return \Illuminate\Support\Fluent
443
     */
444
    public function spatialIndex($columns, $name = null)
445
    {
446
        return $this->geoIndex($columns);
447
    }
448
449
    public function skiplistIndex($attributes, $indexOptions = [])
450
    {
451
        return $this->indexCommand('skiplist', $attributes, $indexOptions);
452
    }
453
454
    /**
455
     * Specify a unique index for the table.
456
     *
457
     * @param  string|array  $columns
458
     * @param  string  $name
459
     * @param  string|null  $algorithm
460
     * @return \Illuminate\Support\Fluent
461
     */
462
    public function unique($columns = null, $name = null, $algorithm = null)
463
    {
464
        $type = $this->mapIndexType($algorithm);
465
466
        $indexOptions['unique'] = true;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$indexOptions was never initialized. Although not strictly required by PHP, it is generally a good practice to add $indexOptions = array(); before regardless.
Loading history...
467
468
        return $this->indexCommand($type, $columns, $indexOptions);
469
    }
470
471
    /**
472
     * Add a new index command to the blueprint.
473
     *
474
     * @param  string  $type
475
     * @param  string|array  $attributes
476
     * @param  array $indexOptions
477
     * @return \Illuminate\Support\Fluent
478
     */
479
    protected function indexCommand($type = '', $attributes = [], $indexOptions = [])
480
    {
481
        if ($type == '') {
482
            $type = 'skiplist';
483
        }
484
485
        if ($attributes === null) {
0 ignored issues
show
introduced by
The condition $attributes === null is always false.
Loading history...
486
            $attributes = end($this->attributes);
487
        }
488
489
        if (is_string($attributes)) {
490
            $attributes = [$attributes];
491
        }
492
493
        $unique = false;
494
        if (isset($indexOptions['unique'])) {
495
            $unique = $indexOptions['unique'];
496
            unset($indexOptions['unique']);
497
        }
498
499
        return $this->addCommand('index', compact('type', 'attributes', 'unique', 'indexOptions'));
500
    }
501
502
    /**
503
     * Add a new command to the blueprint.
504
     *
505
     * @param  string  $name
506
     * @param  array  $parameters
507
     * @return \Illuminate\Support\Fluent
508
     */
509
    protected function addCommand($name, array $parameters = [])
510
    {
511
        $this->commands[] = $command = $this->createCommand($name, $parameters);
512
513
        return $command;
514
    }
515
516
    /**
517
     * Create a new Fluent command.
518
     *
519
     * @param  string  $name
520
     * @param  array  $parameters
521
     * @return \Illuminate\Support\Fluent
522
     */
523
    protected function createCommand($name, array $parameters = [])
524
    {
525
        return new Fluent(array_merge(compact('name'), $parameters));
526
    }
527
528
    /**
529
     * Get the collection the blueprint describes.
530
     *
531
     * @return string
532
     */
533
    public function getCollection()
534
    {
535
        return $this->collection;
536
    }
537
538
    /**
539
     * Alias for getCollection.
540
     *
541
     * @return string
542
     */
543
    public function getTable()
544
    {
545
        return $this->getCollection();
546
    }
547
548
    /**
549
     * Get the commands on the blueprint.
550
     *
551
     * @return \Illuminate\Support\Fluent[]
552
     */
553
    public function getCommands()
554
    {
555
        return $this->commands;
556
    }
557
558
    /**
559
     * Silently catch unsupported schema methods. Store attributes for backwards compatible fluent index creation.
560
     *
561
     * @param $method
562
     * @param $args
563
     * @return Blueprint
564
     */
565
    public function __call($method, $args)
566
    {
567
        $columnMethods = [
568
            'bigIncrements', 'bigInteger', 'binary', 'boolean', 'char', 'date', 'dateTime', 'dateTimeTz', 'decimal',
569
            'double', 'enum', 'float', 'geometry', 'geometryCollection', 'increments', 'integer', 'ipAddress', 'json',
570
            'jsonb', 'lineString', 'longText', 'macAddress', 'mediumIncrements', 'mediumInteger', 'mediumText',
571
            'morphs', 'uuidMorphs', 'multiLineString', 'multiPoint', 'multiPolygon',
572
            'nullableMorphs', 'nullableUuidMorphs', 'nullableTimestamps', 'point', 'polygon', 'rememberToken',
573
            'set', 'smallIncrements', 'smallInteger', 'softDeletes', 'softDeletesTz', 'string',
574
            'text', 'time', 'timeTz', 'timestamp', 'timestampTz', 'timestamps', 'tinyIncrements', 'tinyInteger',
575
            'unsignedBigInteger', 'unsignedDecimal', 'unsignedInteger', 'unsignedMediumInteger', 'unsignedSmallInteger',
576
            'unsignedTinyInteger', 'uuid', 'year',
577
        ];
578
579
        if (in_array($method, $columnMethods)) {
580
            if (isset($args)) {
581
                $this->attributes[] = $args;
582
            }
583
        }
584
585
        $autoIncrementMethods = ['increments', 'autoIncrement'];
586
        if (in_array($method, $autoIncrementMethods)) {
587
            $this->autoIncrement = true;
588
        }
589
590
        $info['method'] = $method;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$info was never initialized. Although not strictly required by PHP, it is generally a good practice to add $info = array(); before regardless.
Loading history...
591
        $info['explanation'] = "'$method' is ignored; Aranguent Schema Blueprint doesn't support it.";
592
        $this->addCommand('ignore', $info);
593
594
        return $this;
595
    }
596
597
    public function mapIndexType($algorithm)
598
    {
599
        $typeConversion = [
600
            'HASH' => 'hash',
601
            'BTREE' => 'skiplist',
602
            'RTREE' => 'geo',
603
        ];
604
        $algorithm = strtoupper($algorithm);
605
606
        return (isset($typeConversion[$algorithm])) ? $typeConversion[$algorithm] : 'skiplist';
607
    }
608
}
609