Passed
Pull Request — 2.x (#93)
by Maxim
19:27
created

PostgresColumn::sqlStatement()   F

Complexity

Conditions 17
Paths 361

Size

Total Lines 53
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 17

Importance

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

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