packages/sql-fixture/src/Platform/PostgreSql/Schema/CatalogColumn.php

1<?php
2
3declare(strict_types=1);
4
5namespace SqlFixture\Platform\PostgreSql\Schema;
6
7/**
8 * Interprets PostgreSQL catalog type and default metadata.
9 *
10 * @visibility root
11 */
12final class CatalogColumn
13{
14    /**
15     * @param array{data_type: string, character_maximum_length: ?string, numeric_precision: ?string, numeric_scale: ?string, udt_name: string} $col
16     */
17    public function mapDataType(array $col): string
18    {
19        $type = strtoupper($col['data_type']);
20
21        if ($type === 'CHARACTER VARYING' && $col['character_maximum_length'] !== null) {
22            return 'VARCHAR(' . $col['character_maximum_length'] . ')';
23        }
24
25        if ($type === 'CHARACTER' && $col['character_maximum_length'] !== null) {
26            return 'CHAR(' . $col['character_maximum_length'] . ')';
27        }
28
29        if ($type === 'NUMERIC' && $col['numeric_precision'] !== null) {
30            $precision = $col['numeric_precision'];
31            if ($col['numeric_scale'] !== null && $col['numeric_scale'] !== '0') {
32                return "NUMERIC({$precision}, {$col['numeric_scale']})";
33            }
34            return "NUMERIC({$precision})";
35        }
36
37        if ($type === 'ARRAY') {
38            return $this->elementType($col['udt_name']) . '[]';
39        }
40
41        if ($type === 'USER-DEFINED') {
42            return strtoupper($col['udt_name']);
43        }
44
45        return $type;
46    }
47
48    /**
49     * Names the declared type of an array element from the catalog type of the array, such as `_int4`.
50     *
51     * @param string $udtName Catalog type name of the array, with its leading underscore
52     *
53     * @return string The type name a CREATE TABLE declares, such as `INTEGER`
54     */
55    public function elementType(string $udtName): string
56    {
57        $element = strtolower(ltrim($udtName, '_'));
58
59        return match ($element) {
60            'int2' => 'SMALLINT',
61            'int4' => 'INTEGER',
62            'int8' => 'BIGINT',
63            'float4' => 'REAL',
64            'float8' => 'DOUBLE PRECISION',
65            'bool' => 'BOOLEAN',
66            'varchar' => 'VARCHAR',
67            'bpchar' => 'CHAR',
68            default => strtoupper($element),
69        };
70    }
71
72    /**
73     * @param array{data_type: string, udt_name: string, character_maximum_length: ?string, numeric_precision: ?string, numeric_scale: ?string} $row
74     */
75    public function resolveType(array $row): string
76    {
77        $type = strtoupper($row['data_type']);
78
79        if ($type === 'ARRAY') {
80            return $this->elementType($row['udt_name']) . '_ARRAY';
81        }
82
83        if ($type === 'USER-DEFINED') {
84            return strtoupper($row['udt_name']);
85        }
86
87        return $type;
88    }
89
90    /**
91     * Parses default.
92     */
93    public function parseDefault(?string $value): int|float|bool|string|null
94    {
95        if ($value === null) {
96            return null;
97        }
98
99        if (strtoupper($value) === 'NULL' || strtoupper($value) === 'NULL::') {
100            return null;
101        }
102
103        if (str_contains($value, 'nextval(')) {
104            return null;
105        }
106
107        if (preg_match("/^'(.*)'::.*$/s", $value, $matches) === 1) {
108            return $matches[1];
109        }
110
111        if (preg_match("/^'(.*)'$/s", $value, $matches) === 1) {
112            return $matches[1];
113        }
114
115        if (strtolower($value) === 'true') {
116            return true;
117        }
118        if (strtolower($value) === 'false') {
119            return false;
120        }
121
122        if (is_numeric($value)) {
123            if (str_contains($value, '.')) {
124                return (float) $value;
125            }
126            return (int) $value;
127        }
128
129        return $value;
130    }
131}
132