packages/ztd-query-core/src/Schema/TableDefinition.php

1<?php
2
3declare(strict_types=1);
4
5namespace ZtdQuery\Schema;
6
7use ZtdQuery\Schema\Key\CandidateKeySet;
8use ZtdQuery\Schema\Key\ForeignKeyDefinition;
9use ZtdQuery\Schema\Key\IdentityGenerationStrategy;
10use ZtdQuery\Schema\Key\PartialUniqueIndex;
11use ZtdQuery\Schema\Partition\TablePartitioning;
12use ZtdQuery\Schema\Partition\TablePartitionKey;
13use ZtdQuery\Schema\Partition\TablePartitionRelation;
14
15/**
16 * Structured representation of a table's schema metadata.
17 *
18 * Values remain opaque to the neutral core until a platform interprets them.
19 * This also preserves driver values such as LOB streams and caller literals.
20 *
21 * @phpstan-type RowValue mixed
22 * @phpstan-type Row array<string, RowValue>
23 */
24final class TableDefinition
25{
26    /**
27     * @var array<string, ColumnDeclaration>
28     */
29    public readonly array $typedColumns;
30
31    /**
32     * @var array<string, string>
33     */
34    public readonly array $columnDefaults;
35
36    /**
37     * @var array<string, IdentityGenerationStrategy>
38     */
39    public readonly array $identityStrategies;
40
41    /**
42     * @var array<string, string>
43     */
44    public readonly array $generatedExpressions;
45
46    /**
47     * @var array<string, ForeignKeyDefinition>
48     */
49    public readonly array $foreignKeys;
50
51    /**
52     * @var TablePartitioning|null How the table is divided, or null when it is not
53     */
54    public readonly ?TablePartitioning $partitioning;
55
56    /**
57     * @var TablePartitionKey|null What the division is by, or null when there is none
58     */
59    public readonly ?TablePartitionKey $partitionKey;
60
61    /**
62     * @var TablePartitionRelation|null The table this one is a partition of, or null when it is not one
63     */
64    public readonly ?TablePartitionRelation $partitionRelation;
65
66    /**
67     * @var array<string, PartialUniqueIndex>
68     */
69    public readonly array $partialUniqueIndexes;
70
71    /**
72     * @param list<string> $columns Column names in declaration order.
73     * @param array<string, string> $columnTypes Column name => MySQL type string.
74     * @param list<string> $primaryKeys Primary key column names.
75     * @param list<string> $notNullColumns Columns with NOT NULL constraint.
76     * @param array<string, list<string>> $uniqueConstraints Key name => column list.
77     * @param array<string, ColumnDeclaration> $typedColumns Column name => structured ColumnDeclaration.
78     * @param array<string, string> $columnDefaults Column name => SQL default expression.
79     * @param array<string, IdentityGenerationStrategy> $identityStrategies Column name => shadow generation strategy.
80     * @param array<string, string> $generatedExpressions Column name => database generated expression.
81     * @param array<string, ForeignKeyDefinition> $foreignKeys Constraint name => foreign-key definition.
82     * @param TablePartitioning|null $partitioning Named partition selection predicates.
83     * @param TablePartitionKey|null $partitionKey Declarative partition key metadata.
84     * @param TablePartitionRelation|null $partitionRelation Parent partition relationship.
85     * @param array<string, PartialUniqueIndex> $partialUniqueIndexes Partial unique indexes keyed by name.
86     */
87    public function __construct(
88        public readonly array $columns,
89        public readonly array $columnTypes,
90        public readonly array $primaryKeys,
91        public readonly array $notNullColumns,
92        public readonly array $uniqueConstraints,
93        array $typedColumns = [],
94        array $columnDefaults = [],
95        array $identityStrategies = [],
96        array $generatedExpressions = [],
97        array $foreignKeys = [],
98        ?TablePartitioning $partitioning = null,
99        ?TablePartitionKey $partitionKey = null,
100        ?TablePartitionRelation $partitionRelation = null,
101        array $partialUniqueIndexes = [],
102    ) {
103        $this->typedColumns = $typedColumns;
104        $this->columnDefaults = $columnDefaults;
105        $this->identityStrategies = $identityStrategies;
106        $this->generatedExpressions = $generatedExpressions;
107        $this->foreignKeys = $foreignKeys;
108        $this->partitioning = $partitioning;
109        $this->partitionKey = $partitionKey;
110        $this->partitionRelation = $partitionRelation;
111        $this->partialUniqueIndexes = $partialUniqueIndexes;
112    }
113
114    /**
115     * Candidate keys.
116     *
117     * @return CandidateKeySet
118     */
119    public function candidateKeys(): CandidateKeySet
120    {
121        return CandidateKeySet::fromSchema($this->primaryKeys, $this->uniqueConstraints);
122    }
123
124    /**
125     * With partitioning.
126     *
127     * @param ?TablePartitioning $partitioning
128     * @return self
129     */
130    public function withPartitioning(?TablePartitioning $partitioning): self
131    {
132        return new self(
133            $this->columns,
134            $this->columnTypes,
135            $this->primaryKeys,
136            $this->notNullColumns,
137            $this->uniqueConstraints,
138            $this->typedColumns,
139            $this->columnDefaults,
140            $this->identityStrategies,
141            $this->generatedExpressions,
142            $this->foreignKeys,
143            $partitioning,
144            $this->partitionKey,
145            $this->partitionRelation,
146            $this->partialUniqueIndexes,
147        );
148    }
149
150    /**
151     * With partition key.
152     *
153     * @param ?TablePartitionKey $partitionKey
154     * @return self
155     */
156    public function withPartitionKey(?TablePartitionKey $partitionKey): self
157    {
158        return new self(
159            $this->columns,
160            $this->columnTypes,
161            $this->primaryKeys,
162            $this->notNullColumns,
163            $this->uniqueConstraints,
164            $this->typedColumns,
165            $this->columnDefaults,
166            $this->identityStrategies,
167            $this->generatedExpressions,
168            $this->foreignKeys,
169            $this->partitioning,
170            $partitionKey,
171            $this->partitionRelation,
172            $this->partialUniqueIndexes,
173        );
174    }
175
176    /**
177     * With partition relation.
178     *
179     * @param ?TablePartitionRelation $partitionRelation
180     * @return self
181     */
182    public function withPartitionRelation(?TablePartitionRelation $partitionRelation): self
183    {
184        return new self(
185            $this->columns,
186            $this->columnTypes,
187            $this->primaryKeys,
188            $this->notNullColumns,
189            $this->uniqueConstraints,
190            $this->typedColumns,
191            $this->columnDefaults,
192            $this->identityStrategies,
193            $this->generatedExpressions,
194            $this->foreignKeys,
195            $this->partitioning,
196            $this->partitionKey,
197            $partitionRelation,
198            $this->partialUniqueIndexes,
199        );
200    }
201
202    /**
203     * With partial unique index.
204     *
205     * @param PartialUniqueIndex $index
206     * @return self
207     */
208    public function withPartialUniqueIndex(PartialUniqueIndex $index): self
209    {
210        $indexes = $this->partialUniqueIndexes;
211        $indexes[$index->name] = $index;
212
213        return new self(
214            $this->columns,
215            $this->columnTypes,
216            $this->primaryKeys,
217            $this->notNullColumns,
218            $this->uniqueConstraints,
219            $this->typedColumns,
220            $this->columnDefaults,
221            $this->identityStrategies,
222            $this->generatedExpressions,
223            $this->foreignKeys,
224            $this->partitioning,
225            $this->partitionKey,
226            $this->partitionRelation,
227            $indexes,
228        );
229    }
230}
231