Passed
Push — 2.x ( 749fd6...811329 )
by Aleksei
20:07
created

PostgresColumn::createInstance()   F

Complexity

Conditions 25
Paths 385

Size

Total Lines 87
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 650

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 25
eloc 46
nc 385
nop 3
dl 0
loc 87
ccs 0
cts 0
cp 0
crap 650
rs 1.0208
c 1
b 1
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of Cycle ORM package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\Database\Driver\Postgres\Schema;
13
14
use Cycle\Database\Driver\DriverInterface;
15
use Cycle\Database\Exception\SchemaException;
16
use Cycle\Database\Injection\Fragment;
17
use Cycle\Database\Schema\AbstractColumn;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Schema\AbstractColumn was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use Cycle\Database\Schema\Attribute\ColumnAttribute;
19
20
/**
21
 * @method $this timestamptz(int $size = 0)
22
 * @method $this timetz()
23
 * @method $this bitVarying(int $size = 0)
24
 * @method $this bit(int $size = 1)
25
 * @method $this int4range()
26
 * @method $this int8range()
27
 * @method $this numrange()
28
 * @method $this tsrange()
29
 * @method $this tstzrange()
30
 * @method $this daterange()
31
 * @method $this point()
32
 * @method $this line()
33
 * @method $this lseg()
34
 * @method $this box()
35
 * @method $this path()
36
 * @method $this polygon()
37
 * @method $this circle()
38
 * @method $this cidr()
39
 * @method $this inet()
40
 * @method $this macaddr()
41
 * @method $this macaddr8()
42
 * @method $this tsvector()
43
 * @method $this tsquery()
44
 */
45
class PostgresColumn extends AbstractColumn
46
{
47
    private const WITH_TIMEZONE = 'with time zone';
48
    private const WITHOUT_TIMEZONE = 'without time zone';
49
50
    /**
51
     * Default timestamp expression (driver specific).
52
     */
53
    public const DATETIME_NOW = 'now()';
54
    public const DATETIME_PRECISION = 6;
55
56
    /**
57
     * Private state related values.
58
     */
59
    public const EXCLUDE_FROM_COMPARE = [
60
        'userType',
61
        'timezone',
62
        'constrained',
63
        'constrainName',
64
        'attributes',
65
    ];
66
67
    protected const INTERVAL_TYPES = [
68
        'YEAR',
69
        'MONTH',
70
        'DAY',
71
        'HOUR',
72
        'MINUTE',
73
        'SECOND',
74
        'YEAR TO MONTH',
75
        'DAY TO HOUR',
76
        'DAY TO MINUTE',
77
        'DAY TO SECOND',
78
        'HOUR TO MINUTE',
79
        'HOUR TO SECOND',
80
        'MINUTE TO SECOND',
81
    ];
82
83
    protected const INTERVALS_WITH_ALLOWED_PRECISION = [
84
        'SECOND',
85
        'DAY TO SECOND',
86
        'HOUR TO SECOND',
87
        'MINUTE TO SECOND',
88
    ];
89
90
    protected array $aliases = [
91
        'int'            => 'integer',
92
        'smallint'       => 'smallInteger',
93
        'bigint'         => 'bigInteger',
94
        'incremental'    => 'primary',
95
        'bigIncremental' => 'bigPrimary',
96
        'bool'           => 'boolean',
97
        'blob'           => 'binary',
98
        'bitVarying'     => 'bit varying',
99
    ];
100
101
    protected array $mapping = [
102
        //Primary sequences
103
        'smallPrimary' => ['type' => 'smallserial', 'autoIncrement' => true, 'nullable' => false],
104
        'primary'      => ['type' => 'serial', 'autoIncrement' => true, 'nullable' => false],
105
        'bigPrimary'   => ['type' => 'bigserial', 'autoIncrement' => true, 'nullable' => false],
106
107
        //Enum type (mapped via method)
108
        'enum'         => 'enum',
109
110
        //Logical types
111
        'boolean'      => 'boolean',
112
113
        //Integer types (size can always be changed with size method), longInteger has method alias
114
        //bigInteger
115
        'integer'      => 'integer',
116
        'tinyInteger'  => 'smallint',
117
        'smallInteger' => 'smallint',
118
        'bigInteger'   => 'bigint',
119
120
        //String with specified length (mapped via method)
121
        'string'       => 'character varying',
122 8
123
        //Generic types
124 8
        'text'         => 'text',
125
        'tinyText'     => 'text',
126 8
        'longText'     => 'text',
127
128
        //Real types
129
        'double'       => 'double precision',
130 8
        'float'        => 'real',
131
132
        //Decimal type (mapped via method)
133
        'decimal'      => 'numeric',
134
135
        //Date and Time types
136 516
        'datetime'     => 'timestamp',
137
        'date'         => 'date',
138 516
        'time'         => 'time',
139
        'timetz'       => ['type' => 'time', 'withTimezone' => true],
140
        'timestamp'    => 'timestamp',
141 366
        'timestamptz'  => ['type' => 'timestamp', 'withTimezone' => true],
142
        'interval'     => 'interval',
143 366
144
        //Binary types
145
        'binary'       => 'bytea',
146
        'tinyBinary'   => 'bytea',
147
        'longBinary'   => 'bytea',
148
149
        //Bit-string
150 366
        'bit'          => ['type' => 'bit', 'size' => 1],
151
        'bit varying'  => 'bit varying',
152
153 2
        //Ranges
154
        'int4range'    => 'int4range',
155 2
        'int8range'    => 'int8range',
156
        'numrange'     => 'numrange',
157
        'tsrange'      => 'tsrange',
158
        'tstzrange'    => ['type' => 'tstzrange', 'withTimezone' => true],
159
        'daterange'    => 'daterange',
160
161
        //Additional types
162 2
        'json'         => 'text',
163
        'jsonb'        => 'jsonb',
164
        'uuid'         => 'uuid',
165 156
        'point'        => 'point',
166
        'line'         => 'line',
167 156
        'lseg'         => 'lseg',
168
        'box'          => 'box',
169 156
        'path'         => 'path',
170 156
        'polygon'      => 'polygon',
171 156
        'circle'       => 'circle',
172
        'cidr'         => 'cidr',
173
        'inet'         => 'inet',
174 156
        'macaddr'      => 'macaddr',
175
        'macaddr8'     => 'macaddr8',
176
        'tsvector'     => 'tsvector',
177
        'tsquery'      => 'tsquery',
178
    ];
179
180 514
    protected array $reverseMapping = [
181
        'smallPrimary' => ['smallserial'],
182 514
        'primary'      => ['serial'],
183
        'bigPrimary'   => ['bigserial'],
184 514
        'enum'         => ['enum'],
185
        'boolean'      => ['boolean'],
186 506
        'integer'      => ['int', 'integer', 'int4', 'int4range'],
187
        'tinyInteger'  => ['smallint'],
188
        'smallInteger' => ['smallint'],
189
        'bigInteger'   => ['bigint', 'int8', 'int8range'],
190 154
        'string'       => [
191 154
            'character varying',
192 154
            'character',
193
            'char',
194
            'point',
195 154
            'line',
196 154
            'lseg',
197 154
            'box',
198
            'path',
199 154
            'polygon',
200
            'circle',
201
            'cidr',
202
            'inet',
203
            'macaddr',
204
            'macaddr8',
205 40
            'tsvector',
206
            'tsquery',
207 40
        ],
208
        'text'         => ['text'],
209
        'double'       => ['double precision'],
210 40
        'float'        => ['real', 'money'],
211 40
        'decimal'      => ['numeric', 'numrange'],
212
        'date'         => ['date', 'daterange'],
213 40
        'time'         => [['type' => 'time', 'withTimezone' => false]],
214
        'timetz'       => [['type' => 'time', 'withTimezone' => true]],
215
        'timestamp'    => [
216
            ['type' => 'timestamp', 'withTimezone' => false],
217
            ['type' => 'tsrange', 'withTimezone' => false],
218 40
        ],
219 18
        'timestamptz'  => [
220
            ['type' => 'timestamp', 'withTimezone' => true],
221 2
            ['type' => 'tstzrange', 'withTimezone' => true],
222 2
        ],
223 2
        'binary'       => ['bytea'],
224
        'json'         => ['json'],
225
        'jsonb'        => ['jsonb'],
226 2
        'interval'     => ['interval'],
227
        'bit'          => ['bit', 'bit varying'],
228 16
    ];
229
230 16
    /**
231 6
     * Field is auto incremental.
232 12
     */
233
    protected bool $autoIncrement = false;
234
235
    /**
236
     * Indication that column has enum constrain.
237 16
     */
238
    protected bool $constrained = false;
239
240
    /**
241
     * Name of enum constraint associated with field.
242 40
     */
243 6
    protected string $constrainName = '';
244
245
    #[ColumnAttribute(['timestamp', 'time', 'timestamptz', 'timetz', 'tsrange', 'tstzrange'])]
246
    protected bool $withTimezone = false;
247 40
248 10
    #[ColumnAttribute(['interval'])]
249 2
    protected ?string $intervalType = null;
250
251 8
    public function getConstraints(): array
252
    {
253
        $constraints = parent::getConstraints();
254
255
        if ($this->constrained) {
256 40
            $constraints[] = $this->constrainName;
257 4
        }
258
259
        return $constraints;
260 40
    }
261 6
262 6
    /**
263 6
     * @psalm-return non-empty-string
264
     */
265
    public function getAbstractType(): string
266 6
    {
267 6
        return !empty($this->enumValues) ? 'enum' : parent::getAbstractType();
268
    }
269
270 40
    public function smallPrimary(): AbstractColumn
271
    {
272
        if (!empty($this->type) && $this->type !== 'smallserial') {
273
            //Change type of already existed column (we can't use "serial" alias here)
274
            $this->type = 'smallint';
0 ignored issues
show
Bug Best Practice introduced by
The property type does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
275
276
            return $this;
277
        }
278 516
279
        return $this->type('smallPrimary');
280
    }
281
282
    public function primary(): AbstractColumn
283 516
    {
284
        if (!empty($this->type) && $this->type !== 'serial') {
285 516
            //Change type of already existed column (we can't use "serial" alias here)
286 516
            $this->type = 'integer';
0 ignored issues
show
Bug Best Practice introduced by
The property type does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
287 516
288
            return $this;
289
        }
290 516
291 516
        return $this->type('primary');
292 516
    }
293
294 368
    public function bigPrimary(): AbstractColumn
295 368
    {
296
        if (!empty($this->type) && $this->type !== 'bigserial') {
297 368
            //Change type of already existed column (we can't use "serial" alias here)
298
            $this->type = 'bigint';
0 ignored issues
show
Bug Best Practice introduced by
The property type does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
299 368
300
            return $this;
301
        }
302 470
303 264
        return $this->type('bigPrimary');
304
    }
305
306 470
    public function enum(string|array $values): AbstractColumn
307 14
    {
308 14
        $this->enumValues = array_map('strval', \is_array($values) ? $values : \func_get_args());
0 ignored issues
show
Bug Best Practice introduced by
The property enumValues does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
introduced by
The condition is_array($values) is always true.
Loading history...
309
310
        $this->type = 'character varying';
0 ignored issues
show
Bug Best Practice introduced by
The property type does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
311 470
        foreach ($this->enumValues as $value) {
312 2
            $this->size = max((int)$this->size, \strlen($value));
0 ignored issues
show
Bug Best Practice introduced by
The property size does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
313
        }
314
315
        return $this;
316
    }
317
318 2
    public function interval(int $size = 6, ?string $intervalType = null): AbstractColumn
319
    {
320
        if ($intervalType !== null && !\in_array($intervalType, self::INTERVALS_WITH_ALLOWED_PRECISION, true)) {
321 470
            $size = 0;
322
        }
323 264
324
        $this->type = 'interval';
0 ignored issues
show
Bug Best Practice introduced by
The property type does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
325
        $this->size = $size;
0 ignored issues
show
Bug Best Practice introduced by
The property size does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
326 470
        $this->intervalType = $intervalType;
327
328 470
        return $this;
329
    }
330
331 516
    /**
332
     * @psalm-return non-empty-string
333 516
     */
334 506
    public function sqlStatement(DriverInterface $driver): string
335
    {
336
        $statement = [$driver->identifier($this->name), $this->type];
0 ignored issues
show
Bug introduced by
The method identifier() does not exist on Cycle\Database\Driver\DriverInterface. It seems like you code against a sub-type of Cycle\Database\Driver\DriverInterface such as Cycle\Database\Driver\Driver. ( Ignorable by Annotation )

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

336
        $statement = [$driver->/** @scrutinizer ignore-call */ identifier($this->name), $this->type];
Loading history...
337
338 370
        if ($this->intervalType !== null && $this->getAbstractType() === 'interval') {
339 370
            if (!\in_array($this->intervalType, self::INTERVAL_TYPES, true)) {
340
                throw new SchemaException(\sprintf(
341
                    'Invalid interval type value. Valid values for interval type: `%s`.',
342
                    \implode('`, `', self::INTERVAL_TYPES)
343
                ));
344
            }
345
            $statement[] = $this->intervalType;
346
        }
347
348
        if ($this->getAbstractType() === 'enum') {
349
            //Enum specific column options
350
            if (!empty($enumDefinition = $this->quoteEnum($driver))) {
351 154
                $statement[] = $enumDefinition;
352
            }
353
        } elseif (!empty($this->precision)) {
354 154
            $statement[] = "({$this->precision}, {$this->scale})";
355
        } elseif (!empty($this->size) || $this->type === 'timestamp' || $this->type === 'time') {
356
            $statement[] = "({$this->size})";
357
        }
358
359
        if ($this->type === 'timestamp' || $this->type === 'time') {
360 156
            $statement[] = $this->withTimezone ? self::WITH_TIMEZONE : self::WITHOUT_TIMEZONE;
361
        }
362 156
363 156
        $statement[] = $this->nullable ? 'NULL' : 'NOT NULL';
364
365
        if ($this->defaultValue !== null) {
366 156
            $statement[] = "DEFAULT {$this->quoteDefault($driver)}";
367
        }
368
369
        $statement = \implode(' ', $statement);
370
371
        //We have to add constraint for enum type
372 470
        if ($this->getAbstractType() === 'enum') {
373
            $enumValues = [];
374 470
            foreach ($this->enumValues as $value) {
375 402
                $enumValues[] = $driver->quote($value);
376
            }
377
378 214
            $constrain = $driver->identifier($this->enumConstraint());
379
            $column = $driver->identifier($this->getName());
380 188
            $values = \implode(', ', $enumValues);
381 160
382
            return "{$statement} CONSTRAINT {$constrain} CHECK ($column IN ({$values}))";
383
        }
384
385 160
        //Nothing special
386 138
        return $statement;
387
    }
388
389 214
    /**
390 214
     * Generate set of operations need to change column.
391 152
     */
392
    public function alterOperations(DriverInterface $driver, AbstractColumn $initial): array
393 152
    {
394
        $operations = [];
395
396 214
        //To simplify comparation
397
        $currentType = [$this->type, $this->size, $this->precision, $this->scale];
398
        $initialType = [$initial->type, $initial->size, $initial->precision, $initial->scale];
399
400
        $identifier = $driver->identifier($this->getName());
401 264
402
        /*
403
         * This block defines column type and all variations.
404
         */
405
        if ($currentType !== $initialType) {
406 264
            if ($this->getAbstractType() === 'enum') {
407
                //Getting longest value
408
                $enumSize = $this->size;
409 264
                foreach ($this->enumValues as $value) {
410
                    $enumSize = max($enumSize, strlen($value));
411
                }
412 264
413 264
                $operations[] = "ALTER COLUMN {$identifier} TYPE character varying($enumSize)";
414
            } else {
415
                $type = "ALTER COLUMN {$identifier} TYPE {$this->type}";
416
417 264
                if (!empty($this->size)) {
418 156
                    $type .= "($this->size)";
419 156
                } elseif (!empty($this->precision)) {
420 156
                    $type .= "($this->precision, $this->scale)";
421 156
                }
422
423 156
                //Required to perform cross conversion
424
                $operations[] = "{$type} USING {$identifier}::{$this->type}";
425
            }
426 156
        }
427
428 156
        //Dropping enum constrain before any operation
429
        if ($this->constrained && $initial->getAbstractType() === 'enum') {
430 156
            $operations[] = 'DROP CONSTRAINT ' . $driver->identifier($this->enumConstraint());
431 156
        }
432 156
433
        //Default value set and dropping
434
        if ($initial->defaultValue !== $this->defaultValue) {
435 264
            if ($this->defaultValue === null) {
436
                $operations[] = "ALTER COLUMN {$identifier} DROP DEFAULT";
437
            } else {
438
                $operations[] = "ALTER COLUMN {$identifier} SET DEFAULT {$this->quoteDefault($driver)}";
439
            }
440 2
        }
441
442 2
        //Nullable option
443
        if ($initial->nullable !== $this->nullable) {
444 2
            $operations[] = "ALTER COLUMN {$identifier} " . (!$this->nullable ? 'SET' : 'DROP') . ' NOT NULL';
445
        }
446 2
447
        if ($this->getAbstractType() === 'enum') {
448
            $enumValues = [];
449
            foreach ($this->enumValues as $value) {
450
                $enumValues[] = $driver->quote($value);
451
            }
452
453
            $operations[] = "ADD CONSTRAINT {$driver->identifier($this->enumConstraint())} "
454 2
                . "CHECK ({$identifier} IN (" . implode(', ', $enumValues) . '))';
455
        }
456
457
        return $operations;
458
    }
459
460
    /**
461
     * @psalm-param non-empty-string $table Table name.
462
     *
463
     * @param DriverInterface $driver Postgres columns are bit more complex.
464
     */
465
    public static function createInstance(
466
        string $table,
467
        array $schema,
468
        DriverInterface $driver
469
    ): self {
470
        $column = new self($table, $schema['column_name'], $driver->getTimezone());
471
472
        $column->type = match (true) {
0 ignored issues
show
Bug Best Practice introduced by
The property type does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
473
            $schema['typname'] === 'timestamp' || $schema['typname'] === 'timestamptz' => 'timestamp',
474
            $schema['typname'] === 'date' => 'date',
475
            $schema['typname'] === 'time' || $schema['typname'] === 'timetz' => 'time',
476
            default => $schema['data_type']
477
        };
478
479
        $column->defaultValue = $schema['column_default'];
0 ignored issues
show
Bug Best Practice introduced by
The property defaultValue does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
480
        $column->nullable = $schema['is_nullable'] === 'YES';
0 ignored issues
show
Bug Best Practice introduced by
The property nullable does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
481
482
        if (
483
            \is_string($column->defaultValue)
484
            && \in_array($column->type, ['int', 'bigint', 'integer', 'smallint'])
485
            && preg_match('/nextval(.*)/', $column->defaultValue)
486
        ) {
487
            $column->type = match (true) {
488
                $column->type === 'bigint' => 'bigserial',
489
                $column->type === 'smallint' => 'smallserial',
490
                default => 'serial'
491
            };
492
            $column->autoIncrement = true;
493
494
            $column->defaultValue = new Fragment($column->defaultValue);
495
496
            return $column;
497
        }
498
499
        if ($schema['character_maximum_length'] !== null && str_contains($column->type, 'char')) {
500
            $column->size = (int) $schema['character_maximum_length'];
0 ignored issues
show
Bug Best Practice introduced by
The property size does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
501
        }
502
503
        if ($column->type === 'numeric') {
504
            $column->precision = (int) $schema['numeric_precision'];
0 ignored issues
show
Bug Best Practice introduced by
The property precision does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
505
            $column->scale = (int) $schema['numeric_scale'];
0 ignored issues
show
Bug Best Practice introduced by
The property scale does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
506
        }
507
508
        if ($column->type === 'USER-DEFINED' && $schema['typtype'] === 'e') {
509
            $column->type = $schema['typname'];
510
511
            /**
512
             * Attention, this is not default enum type emulated via CHECK.
513
             * This is real Postgres enum type.
514
             */
515
            self::resolveEnum($driver, $column);
516
        }
517
518
        if ($column->type === 'timestamp' || $column->type === 'time' || $column->type === 'interval') {
519
            $column->size = (int) $schema['datetime_precision'];
520
        }
521
522
        if (
523
            $schema['typname'] === 'timestamptz' ||
524
            $schema['typname'] === 'timetz' ||
525
            $schema['typname'] === 'tstzrange'
526
        ) {
527
            $column->withTimezone = true;
528
        }
529
530
        if (!empty($column->size) && str_contains($column->type, 'char')) {
531
            //Potential enum with manually created constraint (check in)
532
            self::resolveConstrains($driver, $schema, $column);
533
        }
534
535
        if ($column->type === 'interval' && \is_string($schema['interval_type'])) {
536
            $column->intervalType = \str_replace(\sprintf('(%s)', $column->size), '', $schema['interval_type']);
537
            if (!in_array($column->intervalType, self::INTERVALS_WITH_ALLOWED_PRECISION, true)) {
538
                $column->size = 0;
539
            }
540
        }
541
542
        if (
543
            ($column->type === 'bit' || $column->type === 'bit varying') &&
544
            isset($schema['character_maximum_length'])
545
        ) {
546
            $column->size = (int) $schema['character_maximum_length'];
547
        }
548
549
        $column->normalizeDefault();
550
551
        return $column;
552
    }
553
554
    public function compare(AbstractColumn $initial): bool
555
    {
556
        if (parent::compare($initial)) {
557
            return true;
558
        }
559
560
        return (bool) (
561
            \in_array($this->getAbstractType(), ['smallPrimary', 'primary', 'bigPrimary'], true)
562
            && $initial->getDefaultValue() != $this->getDefaultValue()
563
        );
564
    }
565
566
    /**
567
     * @psalm-return non-empty-string
568
     */
569
    protected function quoteEnum(DriverInterface $driver): string
570
    {
571
        //Postgres enums are just constrained strings
572
        return '(' . $this->size . ')';
573
    }
574
575
    /**
576
     * Get/generate name for enum constraint.
577
     */
578
    private function enumConstraint(): string
579
    {
580
        if (empty($this->constrainName)) {
581
            $this->constrainName = str_replace('.', '_', $this->table) . '_' . $this->getName() . '_enum_' . uniqid();
582
        }
583
584
        return $this->constrainName;
585
    }
586
587
    /**
588
     * Normalize default value.
589
     */
590
    private function normalizeDefault(): void
591
    {
592
        if (!$this->hasDefaultValue()) {
593
            return;
594
        }
595
596
        if (preg_match('/^\'?(.*?)\'?::(.+)/', $this->defaultValue, $matches)) {
597
            //In database: 'value'::TYPE
598
            $this->defaultValue = $matches[1];
0 ignored issues
show
Bug Best Practice introduced by
The property defaultValue does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
599
        } elseif ($this->type === 'bit') {
600
            $this->defaultValue = bindec(
601
                substr($this->defaultValue, 2, strpos($this->defaultValue, '::') - 3)
602
            );
603
        } elseif ($this->type === 'boolean') {
604
            $this->defaultValue = (strtolower($this->defaultValue) === 'true');
605
        }
606
607
        $type = $this->getType();
608
        if ($type === self::FLOAT || $type === self::INT) {
609
            if (preg_match('/^\(?(.*?)\)?(?!::(.+))?$/', $this->defaultValue, $matches)) {
610
                //Negative numeric values
611
                $this->defaultValue = $matches[1];
612
            }
613
        }
614
    }
615
616
    /**
617
     * Resolving enum constrain and converting it into proper enum values set.
618
     */
619
    private static function resolveConstrains(
620
        DriverInterface $driver,
621
        array $schema,
622
        self $column
623
    ): void {
624
        $query = "SELECT conname, pg_get_constraintdef(oid) as consrc FROM pg_constraint
625
        WHERE conrelid = ? AND contype = 'c' AND conkey = ?";
626
627
        $constraints = $driver->query(
628
            $query,
629
            [
630
                $schema['tableOID'],
631
                '{' . $schema['dtd_identifier'] . '}',
632
            ]
633
        );
634
635
        foreach ($constraints as $constraint) {
636
            if (preg_match('/ARRAY\[([^\]]+)\]/', $constraint['consrc'], $matches)) {
637
                $enumValues = explode(',', $matches[1]);
638
                foreach ($enumValues as &$value) {
639
                    if (preg_match("/^'?(.*?)'?::(.+)/", trim($value, ' ()'), $matches)) {
640
                        //In database: 'value'::TYPE
641
                        $value = $matches[1];
642
                    }
643
644
                    unset($value);
645
                }
646
                unset($value);
647
648
                $column->enumValues = $enumValues;
0 ignored issues
show
Bug Best Practice introduced by
The property enumValues does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
649
                $column->constrainName = $constraint['conname'];
650
                $column->constrained = true;
651
            }
652
        }
653
    }
654
655
    /**
656
     * Resolve native ENUM type if presented.
657
     */
658
    private static function resolveEnum(DriverInterface $driver, self $column): void
659
    {
660
        $range = $driver->query('SELECT enum_range(NULL::' . $column->type . ')')->fetchColumn(0);
661
662
        $column->enumValues = explode(',', substr($range, 1, -1));
0 ignored issues
show
Bug Best Practice introduced by
The property enumValues does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
663
664
        if (!empty($column->defaultValue)) {
665
            //In database: 'value'::enumType
666
            $column->defaultValue = substr(
0 ignored issues
show
Bug Best Practice introduced by
The property defaultValue does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
667
                $column->defaultValue,
668
                1,
669
                strpos($column->defaultValue, $column->type) - 4
670
            );
671
        }
672
    }
673
}
674