@prisma-next/family-sql 0.14.0-dev.4 → 0.14.0-dev.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{authoring-type-constructors-CjFfO6LM.mjs → authoring-type-constructors-CXd-8ydc.mjs} +7 -30
- package/dist/authoring-type-constructors-CXd-8ydc.mjs.map +1 -0
- package/dist/contract-to-schema-ir-S-evq8E6.mjs +264 -0
- package/dist/contract-to-schema-ir-S-evq8E6.mjs.map +1 -0
- package/dist/{control-adapter-Cmw9LvEP.d.mts → control-adapter-DHYFuOBy.d.mts} +12 -7
- package/dist/{control-adapter-Cmw9LvEP.d.mts.map → control-adapter-DHYFuOBy.d.mts.map} +1 -1
- package/dist/control-adapter.d.mts +1 -1
- package/dist/control.d.mts +18 -3
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +81 -943
- package/dist/control.mjs.map +1 -1
- package/dist/{schema-verify.d.mts → diff.d.mts} +5 -5
- package/dist/diff.d.mts.map +1 -0
- package/dist/{verify-sql-schema-xT4udQLQ.mjs → diff.mjs} +169 -253
- package/dist/diff.mjs.map +1 -0
- package/dist/ir.d.mts +9 -6
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +1 -1
- package/dist/migration.d.mts +11 -2
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +7 -0
- package/dist/migration.mjs.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/psl-infer.d.mts +80 -0
- package/dist/psl-infer.d.mts.map +1 -0
- package/dist/psl-infer.mjs +334 -0
- package/dist/psl-infer.mjs.map +1 -0
- package/dist/{sql-contract-serializer-BR2vC7Z-.mjs → sql-contract-serializer-CYtXg_zs.mjs} +17 -20
- package/dist/sql-contract-serializer-CYtXg_zs.mjs.map +1 -0
- package/dist/{verify-sql-schema-thU-jKpf.d.mts → sql-schema-diff-6z36dZt6.d.mts} +44 -5
- package/dist/sql-schema-diff-6z36dZt6.d.mts.map +1 -0
- package/dist/{types-kgstZ_Zd.d.mts → types-CkOIJXxU.d.mts} +84 -17
- package/dist/types-CkOIJXxU.d.mts.map +1 -0
- package/package.json +23 -23
- package/src/core/authoring-entity-types.ts +12 -37
- package/src/core/control-adapter.ts +10 -5
- package/src/core/control-instance.ts +174 -41
- package/src/core/{schema-verify/verify-sql-schema.ts → diff/sql-schema-diff.ts} +208 -16
- package/src/core/{schema-verify → diff}/verify-helpers.ts +12 -0
- package/src/core/ir/sql-contract-serializer-base.ts +23 -55
- package/src/core/ir/sql-contract-serializer.ts +5 -7
- package/src/core/migrations/contract-to-schema-ir.ts +49 -12
- package/src/core/migrations/field-event-planner.ts +2 -2
- package/src/core/migrations/schema-differ.ts +40 -0
- package/src/core/migrations/types.ts +43 -2
- package/src/core/psl-contract-infer/printer-config.ts +0 -6
- package/src/core/sql-migration.ts +12 -1
- package/src/exports/control.ts +1 -0
- package/src/exports/diff.ts +18 -0
- package/src/exports/psl-infer.ts +36 -0
- package/dist/authoring-type-constructors-CjFfO6LM.mjs.map +0 -1
- package/dist/schema-verify.d.mts.map +0 -1
- package/dist/schema-verify.mjs +0 -2
- package/dist/sql-contract-serializer-BR2vC7Z-.mjs.map +0 -1
- package/dist/test-utils.d.mts +0 -2
- package/dist/test-utils.mjs +0 -2
- package/dist/types-kgstZ_Zd.d.mts.map +0 -1
- package/dist/verify-sql-schema-thU-jKpf.d.mts.map +0 -1
- package/dist/verify-sql-schema-xT4udQLQ.mjs.map +0 -1
- package/src/core/psl-contract-infer/postgres-default-mapping.ts +0 -16
- package/src/core/psl-contract-infer/postgres-type-map.ts +0 -157
- package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +0 -795
- package/src/exports/schema-verify.ts +0 -18
- package/src/exports/test-utils.ts +0 -9
- /package/src/core/{schema-verify → diff}/control-verify-emit.ts +0 -0
- /package/src/core/{schema-verify → diff}/verifier-disposition.ts +0 -0
package/dist/control.mjs
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { i as sqlFamilyPslBlockDescriptors, n as sqlFamilyAuthoringFieldPresets, r as sqlFamilyEntityTypes, t as sqlFamilyAuthoringTypes } from "./authoring-type-constructors-
|
|
2
|
-
import { t as SqlContractSerializer } from "./sql-contract-serializer-
|
|
3
|
-
import { a as contractToSchemaIR, c as extractCodecControlHooks, o as detectDestructiveChanges, s as resolveValueSetValues, t as verifySqlSchema } from "./verify-sql-schema-xT4udQLQ.mjs";
|
|
1
|
+
import { i as sqlFamilyPslBlockDescriptors, n as sqlFamilyAuthoringFieldPresets, r as sqlFamilyEntityTypes, t as sqlFamilyAuthoringTypes } from "./authoring-type-constructors-CXd-8ydc.mjs";
|
|
2
|
+
import { t as SqlContractSerializer } from "./sql-contract-serializer-CYtXg_zs.mjs";
|
|
4
3
|
import { t as collectSupportedCodecTypeIds } from "./verify-C-G0obRm.mjs";
|
|
4
|
+
import { a as extractCodecControlHooks, i as resolveValueSetValues, n as contractToSchemaIR, r as detectDestructiveChanges, t as contractNamespaceToSchemaIR } from "./contract-to-schema-ir-S-evq8E6.mjs";
|
|
5
5
|
import { n as temporalAuthoringPresets, r as timestampNowControlDescriptor } from "./timestamp-now-generator-CloimujU.mjs";
|
|
6
6
|
import { sqlEmission } from "@prisma-next/sql-contract-emitter";
|
|
7
|
-
import {
|
|
7
|
+
import { blindCast } from "@prisma-next/utils/casts";
|
|
8
|
+
import { effectiveControlPolicy } from "@prisma-next/contract/types";
|
|
9
|
+
import { APP_SPACE_ID, SchemaTreeNode, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, assembleAuthoringContributions, dispositionForCategory } from "@prisma-next/framework-components/control";
|
|
10
|
+
import { isPlainRecord } from "@prisma-next/framework-components/ir";
|
|
8
11
|
import { assertDescriptorSelfConsistency } from "@prisma-next/migration-tools/spaces";
|
|
9
12
|
import { sqlContractCanonicalizationHooks } from "@prisma-next/sql-contract/canonicalization-hooks";
|
|
10
13
|
import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming";
|
|
11
14
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
12
15
|
import { StorageTable } from "@prisma-next/sql-contract/types";
|
|
13
|
-
import { UNSPECIFIED_PSL_NAMESPACE_ID, makePslNamespace, makePslNamespaceEntries } from "@prisma-next/framework-components/psl-ast";
|
|
14
|
-
import { effectiveControlPolicy } from "@prisma-next/contract/types";
|
|
15
16
|
import { notOk, ok } from "@prisma-next/utils/result";
|
|
16
17
|
//#region src/core/operation-preview.ts
|
|
17
18
|
function isDdlStatement(sqlStatement) {
|
|
@@ -46,921 +47,6 @@ function sqlOperationsToPreview(operations) {
|
|
|
46
47
|
})) };
|
|
47
48
|
}
|
|
48
49
|
//#endregion
|
|
49
|
-
//#region src/core/psl-contract-infer/default-mapping.ts
|
|
50
|
-
const DEFAULT_FUNCTION_ATTRIBUTES = {
|
|
51
|
-
"autoincrement()": "@default(autoincrement())",
|
|
52
|
-
"now()": "@default(now())"
|
|
53
|
-
};
|
|
54
|
-
function mapDefault(columnDefault, options) {
|
|
55
|
-
switch (columnDefault.kind) {
|
|
56
|
-
case "literal": return { attribute: `@default(${formatLiteralValue(columnDefault.value)})` };
|
|
57
|
-
case "function": {
|
|
58
|
-
const attribute = options?.functionAttributes?.[columnDefault.expression] ?? DEFAULT_FUNCTION_ATTRIBUTES[columnDefault.expression] ?? options?.fallbackFunctionAttribute?.(columnDefault.expression);
|
|
59
|
-
return attribute ? { attribute } : { comment: `// Raw default: ${columnDefault.expression.replace(/[\r\n]+/g, " ")}` };
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function formatLiteralValue(value) {
|
|
64
|
-
if (value === null) return "null";
|
|
65
|
-
switch (typeof value) {
|
|
66
|
-
case "boolean":
|
|
67
|
-
case "number": return String(value);
|
|
68
|
-
case "string": return quoteString(value);
|
|
69
|
-
default: return quoteString(JSON.stringify(value));
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function quoteString(str) {
|
|
73
|
-
return `"${escapeString(str)}"`;
|
|
74
|
-
}
|
|
75
|
-
function escapeString(str) {
|
|
76
|
-
return JSON.stringify(str).slice(1, -1);
|
|
77
|
-
}
|
|
78
|
-
//#endregion
|
|
79
|
-
//#region src/core/psl-contract-infer/name-transforms.ts
|
|
80
|
-
const PSL_RESERVED_WORDS = new Set([
|
|
81
|
-
"model",
|
|
82
|
-
"enum",
|
|
83
|
-
"types",
|
|
84
|
-
"type",
|
|
85
|
-
"generator",
|
|
86
|
-
"datasource"
|
|
87
|
-
]);
|
|
88
|
-
const IDENTIFIER_PART_PATTERN = /[A-Za-z0-9]+/g;
|
|
89
|
-
function hasSeparators(input) {
|
|
90
|
-
return /[^A-Za-z0-9]/.test(input);
|
|
91
|
-
}
|
|
92
|
-
function extractIdentifierParts(input) {
|
|
93
|
-
return input.match(IDENTIFIER_PART_PATTERN) ?? [];
|
|
94
|
-
}
|
|
95
|
-
function createSyntheticIdentifier(input) {
|
|
96
|
-
let hash = 2166136261;
|
|
97
|
-
for (const char of input) {
|
|
98
|
-
hash ^= char.codePointAt(0) ?? 0;
|
|
99
|
-
hash = Math.imul(hash, 16777619);
|
|
100
|
-
}
|
|
101
|
-
return `x${(hash >>> 0).toString(16)}`;
|
|
102
|
-
}
|
|
103
|
-
function sanitizeIdentifierCharacters(input) {
|
|
104
|
-
const sanitized = input.replace(/[^\w]/g, "");
|
|
105
|
-
return sanitized.length > 0 ? sanitized : createSyntheticIdentifier(input);
|
|
106
|
-
}
|
|
107
|
-
function capitalize(word) {
|
|
108
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
109
|
-
}
|
|
110
|
-
function snakeToPascalCase(input) {
|
|
111
|
-
const parts = extractIdentifierParts(input);
|
|
112
|
-
if (parts.length === 0) return capitalize(sanitizeIdentifierCharacters(input));
|
|
113
|
-
return parts.map(capitalize).join("");
|
|
114
|
-
}
|
|
115
|
-
function snakeToCamelCase(input) {
|
|
116
|
-
const parts = extractIdentifierParts(input);
|
|
117
|
-
if (parts.length === 0) return sanitizeIdentifierCharacters(input);
|
|
118
|
-
const [firstPart = input, ...rest] = parts;
|
|
119
|
-
return firstPart.charAt(0).toLowerCase() + firstPart.slice(1) + rest.map(capitalize).join("");
|
|
120
|
-
}
|
|
121
|
-
function needsEscaping(name) {
|
|
122
|
-
return PSL_RESERVED_WORDS.has(name.toLowerCase()) || /^\d/.test(name);
|
|
123
|
-
}
|
|
124
|
-
function escapeName(name) {
|
|
125
|
-
return `_${name}`;
|
|
126
|
-
}
|
|
127
|
-
function escapeIfNeeded(name) {
|
|
128
|
-
return needsEscaping(name) ? escapeName(name) : name;
|
|
129
|
-
}
|
|
130
|
-
function toModelName(tableName) {
|
|
131
|
-
let name;
|
|
132
|
-
if (hasSeparators(tableName)) name = snakeToPascalCase(tableName);
|
|
133
|
-
else name = tableName.charAt(0).toUpperCase() + tableName.slice(1);
|
|
134
|
-
if (needsEscaping(name)) return {
|
|
135
|
-
name: escapeName(name),
|
|
136
|
-
map: tableName
|
|
137
|
-
};
|
|
138
|
-
if (name !== tableName) return {
|
|
139
|
-
name,
|
|
140
|
-
map: tableName
|
|
141
|
-
};
|
|
142
|
-
return { name };
|
|
143
|
-
}
|
|
144
|
-
function toFieldName(columnName) {
|
|
145
|
-
let name;
|
|
146
|
-
if (hasSeparators(columnName)) name = snakeToCamelCase(columnName);
|
|
147
|
-
else name = columnName.charAt(0).toLowerCase() + columnName.slice(1);
|
|
148
|
-
if (needsEscaping(name)) return {
|
|
149
|
-
name: escapeName(name),
|
|
150
|
-
map: columnName
|
|
151
|
-
};
|
|
152
|
-
if (name !== columnName) return {
|
|
153
|
-
name,
|
|
154
|
-
map: columnName
|
|
155
|
-
};
|
|
156
|
-
return { name };
|
|
157
|
-
}
|
|
158
|
-
function pluralize(word) {
|
|
159
|
-
if (word.endsWith("s") || word.endsWith("x") || word.endsWith("z") || word.endsWith("ch") || word.endsWith("sh")) return `${word}es`;
|
|
160
|
-
if (word.endsWith("y") && !/[aeiou]y$/i.test(word)) return `${word.slice(0, -1)}ies`;
|
|
161
|
-
return `${word}s`;
|
|
162
|
-
}
|
|
163
|
-
function deriveRelationFieldName(fkColumns, referencedTableName) {
|
|
164
|
-
if (fkColumns.length === 1) {
|
|
165
|
-
const [col = referencedTableName] = fkColumns;
|
|
166
|
-
const stripped = col.replace(/_id$/i, "").replace(/Id$/, "");
|
|
167
|
-
if (stripped.length > 0 && stripped !== col) return escapeIfNeeded(snakeToCamelCase(stripped));
|
|
168
|
-
return escapeIfNeeded(snakeToCamelCase(referencedTableName));
|
|
169
|
-
}
|
|
170
|
-
return escapeIfNeeded(snakeToCamelCase(referencedTableName));
|
|
171
|
-
}
|
|
172
|
-
function deriveBackRelationFieldName(childModelName, isOneToOne) {
|
|
173
|
-
const base = childModelName.charAt(0).toLowerCase() + childModelName.slice(1);
|
|
174
|
-
return isOneToOne ? base : pluralize(base);
|
|
175
|
-
}
|
|
176
|
-
function toNamedTypeName(columnName) {
|
|
177
|
-
let name;
|
|
178
|
-
if (hasSeparators(columnName)) name = snakeToPascalCase(columnName);
|
|
179
|
-
else name = columnName.charAt(0).toUpperCase() + columnName.slice(1);
|
|
180
|
-
return escapeIfNeeded(name);
|
|
181
|
-
}
|
|
182
|
-
//#endregion
|
|
183
|
-
//#region src/core/psl-contract-infer/postgres-default-mapping.ts
|
|
184
|
-
const POSTGRES_FUNCTION_ATTRIBUTES = { "gen_random_uuid()": "@default(dbgenerated(\"gen_random_uuid()\"))" };
|
|
185
|
-
function formatDbGeneratedAttribute(expression) {
|
|
186
|
-
return `@default(dbgenerated(${JSON.stringify(expression)}))`;
|
|
187
|
-
}
|
|
188
|
-
function createPostgresDefaultMapping() {
|
|
189
|
-
return {
|
|
190
|
-
functionAttributes: POSTGRES_FUNCTION_ATTRIBUTES,
|
|
191
|
-
fallbackFunctionAttribute: formatDbGeneratedAttribute
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
//#endregion
|
|
195
|
-
//#region src/core/psl-contract-infer/postgres-type-map.ts
|
|
196
|
-
const POSTGRES_TO_PSL = {
|
|
197
|
-
text: "String",
|
|
198
|
-
bool: "Boolean",
|
|
199
|
-
boolean: "Boolean",
|
|
200
|
-
int4: "Int",
|
|
201
|
-
integer: "Int",
|
|
202
|
-
int8: "BigInt",
|
|
203
|
-
bigint: "BigInt",
|
|
204
|
-
float8: "Float",
|
|
205
|
-
"double precision": "Float",
|
|
206
|
-
numeric: "Decimal",
|
|
207
|
-
decimal: "Decimal",
|
|
208
|
-
timestamptz: "DateTime",
|
|
209
|
-
"timestamp with time zone": "DateTime",
|
|
210
|
-
jsonb: "Json",
|
|
211
|
-
bytea: "Bytes"
|
|
212
|
-
};
|
|
213
|
-
const PRESERVED_NATIVE_TYPES = {
|
|
214
|
-
"character varying": {
|
|
215
|
-
pslType: "String",
|
|
216
|
-
attributeName: "db.VarChar"
|
|
217
|
-
},
|
|
218
|
-
character: {
|
|
219
|
-
pslType: "String",
|
|
220
|
-
attributeName: "db.Char"
|
|
221
|
-
},
|
|
222
|
-
char: {
|
|
223
|
-
pslType: "String",
|
|
224
|
-
attributeName: "db.Char"
|
|
225
|
-
},
|
|
226
|
-
varchar: {
|
|
227
|
-
pslType: "String",
|
|
228
|
-
attributeName: "db.VarChar"
|
|
229
|
-
},
|
|
230
|
-
uuid: {
|
|
231
|
-
pslType: "String",
|
|
232
|
-
attributeName: "db.Uuid"
|
|
233
|
-
},
|
|
234
|
-
int2: {
|
|
235
|
-
pslType: "Int",
|
|
236
|
-
attributeName: "db.SmallInt"
|
|
237
|
-
},
|
|
238
|
-
smallint: {
|
|
239
|
-
pslType: "Int",
|
|
240
|
-
attributeName: "db.SmallInt"
|
|
241
|
-
},
|
|
242
|
-
float4: {
|
|
243
|
-
pslType: "Float",
|
|
244
|
-
attributeName: "db.Real"
|
|
245
|
-
},
|
|
246
|
-
real: {
|
|
247
|
-
pslType: "Float",
|
|
248
|
-
attributeName: "db.Real"
|
|
249
|
-
},
|
|
250
|
-
timestamp: {
|
|
251
|
-
pslType: "DateTime",
|
|
252
|
-
attributeName: "db.Timestamp"
|
|
253
|
-
},
|
|
254
|
-
"timestamp without time zone": {
|
|
255
|
-
pslType: "DateTime",
|
|
256
|
-
attributeName: "db.Timestamp"
|
|
257
|
-
},
|
|
258
|
-
date: {
|
|
259
|
-
pslType: "DateTime",
|
|
260
|
-
attributeName: "db.Date"
|
|
261
|
-
},
|
|
262
|
-
time: {
|
|
263
|
-
pslType: "DateTime",
|
|
264
|
-
attributeName: "db.Time"
|
|
265
|
-
},
|
|
266
|
-
"time without time zone": {
|
|
267
|
-
pslType: "DateTime",
|
|
268
|
-
attributeName: "db.Time"
|
|
269
|
-
},
|
|
270
|
-
timetz: {
|
|
271
|
-
pslType: "DateTime",
|
|
272
|
-
attributeName: "db.Timetz"
|
|
273
|
-
},
|
|
274
|
-
"time with time zone": {
|
|
275
|
-
pslType: "DateTime",
|
|
276
|
-
attributeName: "db.Timetz"
|
|
277
|
-
},
|
|
278
|
-
json: {
|
|
279
|
-
pslType: "Json",
|
|
280
|
-
attributeName: "db.Json"
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
const PARAMETERIZED_NATIVE_TYPES = {
|
|
284
|
-
"character varying": {
|
|
285
|
-
pslType: "String",
|
|
286
|
-
attributeName: "db.VarChar"
|
|
287
|
-
},
|
|
288
|
-
character: {
|
|
289
|
-
pslType: "String",
|
|
290
|
-
attributeName: "db.Char"
|
|
291
|
-
},
|
|
292
|
-
char: {
|
|
293
|
-
pslType: "String",
|
|
294
|
-
attributeName: "db.Char"
|
|
295
|
-
},
|
|
296
|
-
varchar: {
|
|
297
|
-
pslType: "String",
|
|
298
|
-
attributeName: "db.VarChar"
|
|
299
|
-
},
|
|
300
|
-
numeric: {
|
|
301
|
-
pslType: "Decimal",
|
|
302
|
-
attributeName: "db.Numeric"
|
|
303
|
-
},
|
|
304
|
-
timestamp: {
|
|
305
|
-
pslType: "DateTime",
|
|
306
|
-
attributeName: "db.Timestamp"
|
|
307
|
-
},
|
|
308
|
-
timestamptz: {
|
|
309
|
-
pslType: "DateTime",
|
|
310
|
-
attributeName: "db.Timestamptz"
|
|
311
|
-
},
|
|
312
|
-
time: {
|
|
313
|
-
pslType: "DateTime",
|
|
314
|
-
attributeName: "db.Time"
|
|
315
|
-
},
|
|
316
|
-
timetz: {
|
|
317
|
-
pslType: "DateTime",
|
|
318
|
-
attributeName: "db.Timetz"
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
const PARAMETERIZED_TYPE_PATTERN = /^(.+?)\((.+)\)$/;
|
|
322
|
-
function getOwnMappingValue(map, key) {
|
|
323
|
-
return Object.hasOwn(map, key) ? map[key] : void 0;
|
|
324
|
-
}
|
|
325
|
-
function getOwnRecordValue(map, key) {
|
|
326
|
-
return Object.hasOwn(map, key) ? map[key] : void 0;
|
|
327
|
-
}
|
|
328
|
-
function createNativeTypeAttribute(name, args) {
|
|
329
|
-
return args && args.length > 0 ? {
|
|
330
|
-
name,
|
|
331
|
-
args
|
|
332
|
-
} : { name };
|
|
333
|
-
}
|
|
334
|
-
function splitTypeParameterList(params) {
|
|
335
|
-
return params.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
|
|
336
|
-
}
|
|
337
|
-
function createPostgresTypeMap(enumTypeNames) {
|
|
338
|
-
return { resolve(nativeType) {
|
|
339
|
-
if (enumTypeNames?.has(nativeType)) return {
|
|
340
|
-
pslType: nativeType,
|
|
341
|
-
nativeType
|
|
342
|
-
};
|
|
343
|
-
const paramMatch = nativeType.match(PARAMETERIZED_TYPE_PATTERN);
|
|
344
|
-
if (paramMatch) {
|
|
345
|
-
const [, baseType = nativeType, params = ""] = paramMatch;
|
|
346
|
-
const template = getOwnRecordValue(PARAMETERIZED_NATIVE_TYPES, baseType);
|
|
347
|
-
if (template) return {
|
|
348
|
-
pslType: template.pslType,
|
|
349
|
-
nativeType,
|
|
350
|
-
typeParams: {
|
|
351
|
-
baseType,
|
|
352
|
-
params
|
|
353
|
-
},
|
|
354
|
-
nativeTypeAttribute: createNativeTypeAttribute(template.attributeName, splitTypeParameterList(params))
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
const preservedType = getOwnRecordValue(PRESERVED_NATIVE_TYPES, nativeType);
|
|
358
|
-
if (preservedType) return {
|
|
359
|
-
pslType: preservedType.pslType,
|
|
360
|
-
nativeType,
|
|
361
|
-
nativeTypeAttribute: createNativeTypeAttribute(preservedType.attributeName)
|
|
362
|
-
};
|
|
363
|
-
const pslType = getOwnMappingValue(POSTGRES_TO_PSL, nativeType);
|
|
364
|
-
if (pslType) return {
|
|
365
|
-
pslType,
|
|
366
|
-
nativeType
|
|
367
|
-
};
|
|
368
|
-
return {
|
|
369
|
-
unsupported: true,
|
|
370
|
-
nativeType
|
|
371
|
-
};
|
|
372
|
-
} };
|
|
373
|
-
}
|
|
374
|
-
function extractEnumInfo(annotations) {
|
|
375
|
-
const nativeEnumTypeNames = (annotations?.["pg"])?.["nativeEnumTypeNames"];
|
|
376
|
-
const typeNames = /* @__PURE__ */ new Set();
|
|
377
|
-
const definitions = /* @__PURE__ */ new Map();
|
|
378
|
-
if (Array.isArray(nativeEnumTypeNames)) {
|
|
379
|
-
for (const name of nativeEnumTypeNames) if (typeof name === "string") typeNames.add(name);
|
|
380
|
-
}
|
|
381
|
-
return {
|
|
382
|
-
typeNames,
|
|
383
|
-
definitions
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
//#endregion
|
|
387
|
-
//#region src/core/psl-contract-infer/raw-default-parser.ts
|
|
388
|
-
const NEXTVAL_PATTERN = /^nextval\s*\(/i;
|
|
389
|
-
const NOW_FUNCTION_PATTERN = /^(now\s*\(\s*\)|CURRENT_TIMESTAMP)$/i;
|
|
390
|
-
const CLOCK_TIMESTAMP_PATTERN = /^clock_timestamp\s*\(\s*\)$/i;
|
|
391
|
-
const TIMESTAMP_CAST_SUFFIX = /::timestamp(?:tz|\s+(?:with|without)\s+time\s+zone)?$/i;
|
|
392
|
-
const TEXT_CAST_SUFFIX = /::text$/i;
|
|
393
|
-
const NOW_LITERAL_PATTERN = /^'now'$/i;
|
|
394
|
-
const UUID_PATTERN = /^gen_random_uuid\s*\(\s*\)$/i;
|
|
395
|
-
const UUID_OSSP_PATTERN = /^uuid_generate_v4\s*\(\s*\)$/i;
|
|
396
|
-
const NULL_PATTERN = /^NULL(?:::.+)?$/i;
|
|
397
|
-
const TRUE_PATTERN = /^true$/i;
|
|
398
|
-
const FALSE_PATTERN = /^false$/i;
|
|
399
|
-
const NUMERIC_PATTERN = /^-?\d+(\.\d+)?$/;
|
|
400
|
-
const JSON_CAST_SUFFIX = /::jsonb?$/i;
|
|
401
|
-
const STRING_LITERAL_PATTERN = /^'((?:[^']|'')*)'(?:::(?:"[^"]+"|[\w\s]+)(?:\(\d+\))?)?$/;
|
|
402
|
-
function canonicalizeTimestampDefault(expr) {
|
|
403
|
-
if (NOW_FUNCTION_PATTERN.test(expr)) return "now()";
|
|
404
|
-
if (CLOCK_TIMESTAMP_PATTERN.test(expr)) return "clock_timestamp()";
|
|
405
|
-
if (!TIMESTAMP_CAST_SUFFIX.test(expr)) return void 0;
|
|
406
|
-
let inner = expr.replace(TIMESTAMP_CAST_SUFFIX, "").trim();
|
|
407
|
-
if (inner.startsWith("(") && inner.endsWith(")")) inner = inner.slice(1, -1).trim();
|
|
408
|
-
if (NOW_FUNCTION_PATTERN.test(inner)) return "now()";
|
|
409
|
-
if (CLOCK_TIMESTAMP_PATTERN.test(inner)) return "clock_timestamp()";
|
|
410
|
-
inner = inner.replace(TEXT_CAST_SUFFIX, "").trim();
|
|
411
|
-
if (NOW_LITERAL_PATTERN.test(inner)) return "now()";
|
|
412
|
-
}
|
|
413
|
-
function parseRawDefault(rawDefault, nativeType) {
|
|
414
|
-
const trimmed = rawDefault.trim();
|
|
415
|
-
const normalizedType = nativeType?.toLowerCase();
|
|
416
|
-
if (NEXTVAL_PATTERN.test(trimmed)) return {
|
|
417
|
-
kind: "function",
|
|
418
|
-
expression: "autoincrement()"
|
|
419
|
-
};
|
|
420
|
-
const canonicalTimestamp = canonicalizeTimestampDefault(trimmed);
|
|
421
|
-
if (canonicalTimestamp) return {
|
|
422
|
-
kind: "function",
|
|
423
|
-
expression: canonicalTimestamp
|
|
424
|
-
};
|
|
425
|
-
if (UUID_PATTERN.test(trimmed) || UUID_OSSP_PATTERN.test(trimmed)) return {
|
|
426
|
-
kind: "function",
|
|
427
|
-
expression: "gen_random_uuid()"
|
|
428
|
-
};
|
|
429
|
-
if (NULL_PATTERN.test(trimmed)) return {
|
|
430
|
-
kind: "literal",
|
|
431
|
-
value: null
|
|
432
|
-
};
|
|
433
|
-
if (TRUE_PATTERN.test(trimmed)) return {
|
|
434
|
-
kind: "literal",
|
|
435
|
-
value: true
|
|
436
|
-
};
|
|
437
|
-
if (FALSE_PATTERN.test(trimmed)) return {
|
|
438
|
-
kind: "literal",
|
|
439
|
-
value: false
|
|
440
|
-
};
|
|
441
|
-
if (NUMERIC_PATTERN.test(trimmed)) return {
|
|
442
|
-
kind: "literal",
|
|
443
|
-
value: Number(trimmed)
|
|
444
|
-
};
|
|
445
|
-
const stringMatch = trimmed.match(STRING_LITERAL_PATTERN);
|
|
446
|
-
if (stringMatch?.[1] !== void 0) {
|
|
447
|
-
const unescaped = stringMatch[1].replace(/''/g, "'");
|
|
448
|
-
if (normalizedType === "json" || normalizedType === "jsonb") {
|
|
449
|
-
if (JSON_CAST_SUFFIX.test(trimmed)) return {
|
|
450
|
-
kind: "function",
|
|
451
|
-
expression: trimmed
|
|
452
|
-
};
|
|
453
|
-
try {
|
|
454
|
-
return {
|
|
455
|
-
kind: "literal",
|
|
456
|
-
value: JSON.parse(unescaped)
|
|
457
|
-
};
|
|
458
|
-
} catch {}
|
|
459
|
-
}
|
|
460
|
-
return {
|
|
461
|
-
kind: "literal",
|
|
462
|
-
value: unescaped
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
return {
|
|
466
|
-
kind: "function",
|
|
467
|
-
expression: trimmed
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
//#endregion
|
|
471
|
-
//#region src/core/psl-contract-infer/relation-inference.ts
|
|
472
|
-
const DEFAULT_ON_DELETE = "noAction";
|
|
473
|
-
const DEFAULT_ON_UPDATE = "noAction";
|
|
474
|
-
const REFERENTIAL_ACTION_PSL = {
|
|
475
|
-
noAction: "NoAction",
|
|
476
|
-
restrict: "Restrict",
|
|
477
|
-
cascade: "Cascade",
|
|
478
|
-
setNull: "SetNull",
|
|
479
|
-
setDefault: "SetDefault"
|
|
480
|
-
};
|
|
481
|
-
function inferRelations(tables, modelNameMap) {
|
|
482
|
-
const relationsByTable = /* @__PURE__ */ new Map();
|
|
483
|
-
const fkCountByPair = /* @__PURE__ */ new Map();
|
|
484
|
-
for (const table of Object.values(tables)) for (const fk of table.foreignKeys) {
|
|
485
|
-
const pairKey = `${table.name}→${fk.referencedTable}`;
|
|
486
|
-
fkCountByPair.set(pairKey, (fkCountByPair.get(pairKey) ?? 0) + 1);
|
|
487
|
-
}
|
|
488
|
-
const usedFieldNames = /* @__PURE__ */ new Map();
|
|
489
|
-
for (const table of Object.values(tables)) {
|
|
490
|
-
const names = /* @__PURE__ */ new Set();
|
|
491
|
-
for (const col of Object.values(table.columns)) names.add(col.name);
|
|
492
|
-
usedFieldNames.set(table.name, names);
|
|
493
|
-
}
|
|
494
|
-
for (const table of Object.values(tables)) for (const fk of table.foreignKeys) {
|
|
495
|
-
const childTableName = table.name;
|
|
496
|
-
const parentTableName = fk.referencedTable;
|
|
497
|
-
const childUsed = usedFieldNames.get(childTableName);
|
|
498
|
-
const childModelName = modelNameMap.get(childTableName) ?? childTableName;
|
|
499
|
-
const parentModelName = modelNameMap.get(parentTableName) ?? parentTableName;
|
|
500
|
-
const pairKey = `${childTableName}→${parentTableName}`;
|
|
501
|
-
const isSelfRelation = childTableName === parentTableName;
|
|
502
|
-
const needsRelationName = fkCountByPair.get(pairKey) > 1 || isSelfRelation;
|
|
503
|
-
const isOneToOne = detectOneToOne(fk, table);
|
|
504
|
-
const childRelFieldName = resolveUniqueFieldName(deriveRelationFieldName(fk.columns, parentTableName), childUsed, parentModelName);
|
|
505
|
-
const relationName = needsRelationName ? deriveRelationName(fk, childRelFieldName, parentModelName, isSelfRelation) : void 0;
|
|
506
|
-
addRelationField(relationsByTable, childTableName, buildChildRelationField(childRelFieldName, parentModelName, fk, fk.columns.some((columnName) => table.columns[columnName]?.nullable ?? false), relationName));
|
|
507
|
-
childUsed.add(childRelFieldName);
|
|
508
|
-
const parentUsed = usedFieldNames.get(parentTableName) ?? /* @__PURE__ */ new Set();
|
|
509
|
-
usedFieldNames.set(parentTableName, parentUsed);
|
|
510
|
-
const backRelFieldName = resolveUniqueFieldName(deriveBackRelationFieldName(childModelName, isOneToOne), parentUsed, childModelName);
|
|
511
|
-
addRelationField(relationsByTable, parentTableName, {
|
|
512
|
-
fieldName: backRelFieldName,
|
|
513
|
-
typeName: childModelName,
|
|
514
|
-
optional: isOneToOne,
|
|
515
|
-
list: !isOneToOne,
|
|
516
|
-
relationName
|
|
517
|
-
});
|
|
518
|
-
parentUsed.add(backRelFieldName);
|
|
519
|
-
}
|
|
520
|
-
return { relationsByTable };
|
|
521
|
-
}
|
|
522
|
-
function detectOneToOne(fk, table) {
|
|
523
|
-
const fkCols = [...fk.columns].sort();
|
|
524
|
-
if (table.primaryKey) {
|
|
525
|
-
const pkCols = [...table.primaryKey.columns].sort();
|
|
526
|
-
if (pkCols.length === fkCols.length && pkCols.every((c, i) => c === fkCols[i])) return true;
|
|
527
|
-
}
|
|
528
|
-
for (const unique of table.uniques) {
|
|
529
|
-
const uniqueCols = [...unique.columns].sort();
|
|
530
|
-
if (uniqueCols.length === fkCols.length && uniqueCols.every((c, i) => c === fkCols[i])) return true;
|
|
531
|
-
}
|
|
532
|
-
return false;
|
|
533
|
-
}
|
|
534
|
-
function deriveRelationName(fk, childRelationFieldName, parentModelName, isSelfRelation) {
|
|
535
|
-
if (fk.name) return fk.name;
|
|
536
|
-
if (isSelfRelation) return `${childRelationFieldName.charAt(0).toUpperCase() + childRelationFieldName.slice(1)}${pluralize(parentModelName)}`;
|
|
537
|
-
return fk.columns.join("_");
|
|
538
|
-
}
|
|
539
|
-
function buildChildRelationField(fieldName, parentModelName, fk, optional, relationName) {
|
|
540
|
-
const onDelete = fk.onDelete && fk.onDelete !== DEFAULT_ON_DELETE ? fk.onDelete : void 0;
|
|
541
|
-
const onUpdate = fk.onUpdate && fk.onUpdate !== DEFAULT_ON_UPDATE ? fk.onUpdate : void 0;
|
|
542
|
-
return {
|
|
543
|
-
fieldName,
|
|
544
|
-
typeName: parentModelName,
|
|
545
|
-
referencedTableName: fk.referencedTable,
|
|
546
|
-
optional,
|
|
547
|
-
list: false,
|
|
548
|
-
relationName,
|
|
549
|
-
fkName: fk.name,
|
|
550
|
-
fields: fk.columns,
|
|
551
|
-
references: fk.referencedColumns,
|
|
552
|
-
onDelete: onDelete ? REFERENTIAL_ACTION_PSL[onDelete] : void 0,
|
|
553
|
-
onUpdate: onUpdate ? REFERENTIAL_ACTION_PSL[onUpdate] : void 0
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
function resolveUniqueFieldName(desired, usedNames, fallbackSuffix) {
|
|
557
|
-
if (!usedNames.has(desired)) return desired;
|
|
558
|
-
const withSuffix = `${desired}${fallbackSuffix}`;
|
|
559
|
-
if (!usedNames.has(withSuffix)) return withSuffix;
|
|
560
|
-
let counter = 2;
|
|
561
|
-
while (usedNames.has(`${desired}${counter}`)) counter++;
|
|
562
|
-
return `${desired}${counter}`;
|
|
563
|
-
}
|
|
564
|
-
function addRelationField(map, tableName, field) {
|
|
565
|
-
const existing = map.get(tableName);
|
|
566
|
-
if (existing) existing.push(field);
|
|
567
|
-
else map.set(tableName, [field]);
|
|
568
|
-
}
|
|
569
|
-
//#endregion
|
|
570
|
-
//#region src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts
|
|
571
|
-
const SYNTHETIC_SPAN = {
|
|
572
|
-
start: {
|
|
573
|
-
offset: 0,
|
|
574
|
-
line: 1,
|
|
575
|
-
column: 1
|
|
576
|
-
},
|
|
577
|
-
end: {
|
|
578
|
-
offset: 0,
|
|
579
|
-
line: 1,
|
|
580
|
-
column: 1
|
|
581
|
-
}
|
|
582
|
-
};
|
|
583
|
-
const PSL_SCALAR_TYPE_NAMES = new Set([
|
|
584
|
-
"String",
|
|
585
|
-
"Boolean",
|
|
586
|
-
"Int",
|
|
587
|
-
"BigInt",
|
|
588
|
-
"Float",
|
|
589
|
-
"Decimal",
|
|
590
|
-
"DateTime",
|
|
591
|
-
"Json",
|
|
592
|
-
"Bytes"
|
|
593
|
-
]);
|
|
594
|
-
/**
|
|
595
|
-
* Converts a SQL schema IR into a PSL AST suitable for `printPsl`.
|
|
596
|
-
*
|
|
597
|
-
* This function owns all SQL-specific concerns: native type mapping (Postgres),
|
|
598
|
-
* relation inference from foreign keys, enum extraction, and raw default parsing.
|
|
599
|
-
* The output is a fully-formed `PslDocumentAst` with synthetic spans.
|
|
600
|
-
*/
|
|
601
|
-
function sqlSchemaIrToPslAst(schemaIR) {
|
|
602
|
-
const enumInfo = extractEnumInfo(schemaIR.annotations);
|
|
603
|
-
if (enumInfo.typeNames.size > 0) {
|
|
604
|
-
const names = [...enumInfo.typeNames].join(", ");
|
|
605
|
-
throw new Error(`contract infer: the database contains native Postgres enum type(s): ${names}. Native Postgres enums (CREATE TYPE … AS ENUM) are not adoptable by contract infer. Drop the native type and replace each column with a text column carrying a CHECK constraint, then re-run contract infer. The domain enum (enum Name { @@type("pg/text@1") … }) authoring surface generates the required check automatically.`);
|
|
606
|
-
}
|
|
607
|
-
return buildPslDocumentAst(schemaIR, {
|
|
608
|
-
typeMap: createPostgresTypeMap(/* @__PURE__ */ new Set()),
|
|
609
|
-
defaultMapping: createPostgresDefaultMapping(),
|
|
610
|
-
parseRawDefault
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
function buildPslDocumentAst(schemaIR, options) {
|
|
614
|
-
const { typeMap, defaultMapping, parseRawDefault: rawDefaultParser } = options;
|
|
615
|
-
const modelNames = buildTopLevelNameMap(Object.keys(schemaIR.tables), toModelName, "model", "table");
|
|
616
|
-
const modelNameMap = new Map([...modelNames].map(([tableName, result]) => [tableName, result.name]));
|
|
617
|
-
const reservedNamedTypeNames = createReservedNamedTypeNames(modelNames);
|
|
618
|
-
const fieldNamesByTable = buildFieldNamesByTable(schemaIR.tables);
|
|
619
|
-
const { relationsByTable } = inferRelations(schemaIR.tables, modelNameMap);
|
|
620
|
-
const namedTypes = seedNamedTypeRegistry(schemaIR, typeMap, /* @__PURE__ */ new Map(), reservedNamedTypeNames);
|
|
621
|
-
const models = [];
|
|
622
|
-
for (const table of Object.values(schemaIR.tables)) models.push(buildModel(table, typeMap, /* @__PURE__ */ new Map(), fieldNamesByTable, namedTypes, defaultMapping, rawDefaultParser, relationsByTable.get(table.name) ?? []));
|
|
623
|
-
const sortedModels = topologicalSort(models, schemaIR.tables, modelNameMap);
|
|
624
|
-
const namedTypeEntries = [...namedTypes.entriesByKey.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
625
|
-
const types = namedTypeEntries.length > 0 ? {
|
|
626
|
-
kind: "types",
|
|
627
|
-
declarations: namedTypeEntries.map(buildNamedTypeDeclaration),
|
|
628
|
-
span: SYNTHETIC_SPAN
|
|
629
|
-
} : void 0;
|
|
630
|
-
return {
|
|
631
|
-
kind: "document",
|
|
632
|
-
sourceId: "<sql-schema-ir>",
|
|
633
|
-
namespaces: [makePslNamespace({
|
|
634
|
-
kind: "namespace",
|
|
635
|
-
name: UNSPECIFIED_PSL_NAMESPACE_ID,
|
|
636
|
-
entries: makePslNamespaceEntries(sortedModels, [], []),
|
|
637
|
-
span: SYNTHETIC_SPAN
|
|
638
|
-
})],
|
|
639
|
-
...types ? { types } : {},
|
|
640
|
-
span: SYNTHETIC_SPAN
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
function buildModel(table, typeMap, enumNameMap, fieldNamesByTable, namedTypes, defaultMapping, rawDefaultParser, relationFields) {
|
|
644
|
-
const { name: modelName, map: mapName } = toModelName(table.name);
|
|
645
|
-
const fieldNameMap = fieldNamesByTable.get(table.name);
|
|
646
|
-
const pkColumns = new Set(table.primaryKey?.columns ?? []);
|
|
647
|
-
const isSinglePk = pkColumns.size === 1;
|
|
648
|
-
const singlePkConstraintName = isSinglePk ? table.primaryKey?.name : void 0;
|
|
649
|
-
const uniqueColumns = /* @__PURE__ */ new Map();
|
|
650
|
-
for (const unique of table.uniques) if (unique.columns.length === 1) {
|
|
651
|
-
const [columnName = ""] = unique.columns;
|
|
652
|
-
const existingConstraintName = uniqueColumns.get(columnName);
|
|
653
|
-
if (!uniqueColumns.has(columnName) || existingConstraintName === void 0 && unique.name) uniqueColumns.set(columnName, unique.name);
|
|
654
|
-
}
|
|
655
|
-
const fields = [];
|
|
656
|
-
for (const column of Object.values(table.columns)) fields.push(buildScalarField(column, table, typeMap, enumNameMap, fieldNameMap, namedTypes, defaultMapping, rawDefaultParser, pkColumns, isSinglePk, singlePkConstraintName, uniqueColumns));
|
|
657
|
-
const usedFieldNames = new Set(fields.map((field) => field.name));
|
|
658
|
-
for (const rel of relationFields) fields.push(buildRelationField(rel, table.name, fieldNamesByTable, usedFieldNames));
|
|
659
|
-
const modelAttributes = [];
|
|
660
|
-
if (table.primaryKey && table.primaryKey.columns.length > 1) {
|
|
661
|
-
const pkFieldNames = table.primaryKey.columns.map((columnName) => resolveColumnFieldName(fieldNamesByTable, table.name, columnName));
|
|
662
|
-
modelAttributes.push(buildModelConstraintAttribute("id", pkFieldNames, table.primaryKey.name));
|
|
663
|
-
}
|
|
664
|
-
for (const unique of table.uniques) if (unique.columns.length > 1) {
|
|
665
|
-
const uniqueFieldNames = unique.columns.map((columnName) => resolveColumnFieldName(fieldNamesByTable, table.name, columnName));
|
|
666
|
-
modelAttributes.push(buildModelConstraintAttribute("unique", uniqueFieldNames, unique.name));
|
|
667
|
-
}
|
|
668
|
-
for (const index of table.indexes) if (!index.unique) {
|
|
669
|
-
const indexFieldNames = index.columns.map((columnName) => resolveColumnFieldName(fieldNamesByTable, table.name, columnName));
|
|
670
|
-
modelAttributes.push(buildModelConstraintAttribute("index", indexFieldNames, index.name));
|
|
671
|
-
}
|
|
672
|
-
if (mapName) modelAttributes.push(buildMapAttribute("model", mapName));
|
|
673
|
-
const comment = table.primaryKey ? void 0 : "// WARNING: This table has no primary key in the database";
|
|
674
|
-
return {
|
|
675
|
-
kind: "model",
|
|
676
|
-
name: modelName,
|
|
677
|
-
fields,
|
|
678
|
-
attributes: modelAttributes,
|
|
679
|
-
span: SYNTHETIC_SPAN,
|
|
680
|
-
...comment !== void 0 ? { comment } : {}
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
function buildScalarField(column, table, typeMap, enumNameMap, fieldNameMap, namedTypes, defaultMapping, rawDefaultParser, pkColumns, isSinglePk, singlePkConstraintName, uniqueColumns) {
|
|
684
|
-
const resolvedField = fieldNameMap?.get(column.name);
|
|
685
|
-
const fieldName = resolvedField?.fieldName ?? toFieldName(column.name).name;
|
|
686
|
-
const fieldMap = resolvedField?.fieldMap;
|
|
687
|
-
const resolution = typeMap.resolve(column.nativeType, table.annotations);
|
|
688
|
-
if ("unsupported" in resolution) {
|
|
689
|
-
const attrs = [];
|
|
690
|
-
if (fieldMap !== void 0) attrs.push(buildMapAttribute("field", fieldMap));
|
|
691
|
-
return {
|
|
692
|
-
kind: "field",
|
|
693
|
-
name: fieldName,
|
|
694
|
-
typeName: `Unsupported("${escapePslString(resolution.nativeType)}")`,
|
|
695
|
-
optional: column.nullable,
|
|
696
|
-
list: false,
|
|
697
|
-
attributes: attrs,
|
|
698
|
-
span: SYNTHETIC_SPAN
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
let typeName = resolution.pslType;
|
|
702
|
-
const enumPslName = enumNameMap.get(column.nativeType);
|
|
703
|
-
if (enumPslName) typeName = enumPslName;
|
|
704
|
-
if (resolution.nativeTypeAttribute && !enumPslName) typeName = resolveNamedTypeName(namedTypes, resolution);
|
|
705
|
-
const attributes = [];
|
|
706
|
-
const isId = isSinglePk && pkColumns.has(column.name);
|
|
707
|
-
if (isId) attributes.push(buildSimpleConstraintFieldAttribute("id", singlePkConstraintName));
|
|
708
|
-
if (column.default !== void 0) {
|
|
709
|
-
const parsed = parseColumnDefault(column.default, column.nativeType, rawDefaultParser);
|
|
710
|
-
if (parsed) {
|
|
711
|
-
const result = mapDefault(parsed, defaultMapping);
|
|
712
|
-
if ("attribute" in result) attributes.push(parseDefaultAttributeString(result.attribute));
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
if (uniqueColumns.has(column.name) && !isId) {
|
|
716
|
-
const uniqueConstraintName = uniqueColumns.get(column.name);
|
|
717
|
-
attributes.push(buildSimpleConstraintFieldAttribute("unique", uniqueConstraintName));
|
|
718
|
-
}
|
|
719
|
-
if (fieldMap !== void 0) attributes.push(buildMapAttribute("field", fieldMap));
|
|
720
|
-
return {
|
|
721
|
-
kind: "field",
|
|
722
|
-
name: fieldName,
|
|
723
|
-
typeName,
|
|
724
|
-
optional: column.nullable,
|
|
725
|
-
list: false,
|
|
726
|
-
attributes,
|
|
727
|
-
span: SYNTHETIC_SPAN
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
function buildRelationField(rel, hostTableName, fieldNamesByTable, usedFieldNames) {
|
|
731
|
-
const fieldName = createUniqueFieldName(rel.fieldName, usedFieldNames);
|
|
732
|
-
usedFieldNames.add(fieldName);
|
|
733
|
-
const args = [];
|
|
734
|
-
if (rel.fields && rel.references) {
|
|
735
|
-
if (rel.relationName) args.push(namedArg("name", `"${escapePslString(rel.relationName)}"`));
|
|
736
|
-
args.push(namedArg("fields", `[${rel.fields.map((columnName) => resolveColumnFieldName(fieldNamesByTable, hostTableName, columnName)).join(", ")}]`));
|
|
737
|
-
args.push(namedArg("references", `[${rel.references.map((columnName) => resolveColumnFieldName(fieldNamesByTable, rel.referencedTableName ?? "", columnName)).join(", ")}]`));
|
|
738
|
-
if (rel.onDelete) args.push(namedArg("onDelete", rel.onDelete));
|
|
739
|
-
if (rel.onUpdate) args.push(namedArg("onUpdate", rel.onUpdate));
|
|
740
|
-
if (rel.fkName) args.push(namedArg("map", `"${escapePslString(rel.fkName)}"`));
|
|
741
|
-
} else if (rel.relationName) args.push(namedArg("name", `"${escapePslString(rel.relationName)}"`));
|
|
742
|
-
const attrs = args.length > 0 ? [buildAttribute("field", "relation", args)] : [];
|
|
743
|
-
return {
|
|
744
|
-
kind: "field",
|
|
745
|
-
name: fieldName,
|
|
746
|
-
typeName: rel.typeName,
|
|
747
|
-
optional: rel.optional,
|
|
748
|
-
list: rel.list,
|
|
749
|
-
attributes: attrs,
|
|
750
|
-
span: SYNTHETIC_SPAN
|
|
751
|
-
};
|
|
752
|
-
}
|
|
753
|
-
function buildModelConstraintAttribute(name, fields, constraintName) {
|
|
754
|
-
const args = [positionalArg(`[${fields.join(", ")}]`)];
|
|
755
|
-
if (constraintName !== void 0) args.push(namedArg("map", `"${escapePslString(constraintName)}"`));
|
|
756
|
-
return buildAttribute("model", name, args);
|
|
757
|
-
}
|
|
758
|
-
function buildSimpleConstraintFieldAttribute(name, constraintName) {
|
|
759
|
-
if (constraintName === void 0) return buildAttribute("field", name, []);
|
|
760
|
-
return buildAttribute("field", name, [namedArg("map", `"${escapePslString(constraintName)}"`)]);
|
|
761
|
-
}
|
|
762
|
-
function parseDefaultAttributeString(attributeText) {
|
|
763
|
-
return buildAttribute("field", "default", [positionalArg(attributeText.replace(/^@default\(/, "").replace(/\)$/, ""))]);
|
|
764
|
-
}
|
|
765
|
-
function buildMapAttribute(target, mapName) {
|
|
766
|
-
return buildAttribute(target, "map", [positionalArg(`"${escapePslString(mapName)}"`)]);
|
|
767
|
-
}
|
|
768
|
-
function buildAttribute(target, name, args) {
|
|
769
|
-
return {
|
|
770
|
-
kind: "attribute",
|
|
771
|
-
target,
|
|
772
|
-
name,
|
|
773
|
-
args,
|
|
774
|
-
span: SYNTHETIC_SPAN
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
function positionalArg(value) {
|
|
778
|
-
return {
|
|
779
|
-
kind: "positional",
|
|
780
|
-
value,
|
|
781
|
-
span: SYNTHETIC_SPAN
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
function namedArg(name, value) {
|
|
785
|
-
return {
|
|
786
|
-
kind: "named",
|
|
787
|
-
name,
|
|
788
|
-
value,
|
|
789
|
-
span: SYNTHETIC_SPAN
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
function buildNamedTypeDeclaration(entry) {
|
|
793
|
-
const attribute = buildAttribute("namedType", entry.nativeTypeAttribute.name, (entry.nativeTypeAttribute.args ?? []).map(positionalArg));
|
|
794
|
-
return {
|
|
795
|
-
kind: "namedType",
|
|
796
|
-
name: entry.name,
|
|
797
|
-
baseType: entry.baseType,
|
|
798
|
-
attributes: [attribute],
|
|
799
|
-
span: SYNTHETIC_SPAN
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
function escapePslString(value) {
|
|
803
|
-
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* Resolves a `SqlColumnIR.default` value into a normalized {@link ColumnDefault}.
|
|
807
|
-
*
|
|
808
|
-
* `SqlSchemaIR` types the column default as `string` (a raw database default
|
|
809
|
-
* expression). Some legacy fixtures and tests still pass already-normalized
|
|
810
|
-
* `ColumnDefault` objects in the same slot, so we accept either shape
|
|
811
|
-
* defensively at runtime.
|
|
812
|
-
*/
|
|
813
|
-
function parseColumnDefault(value, nativeType, rawDefaultParser) {
|
|
814
|
-
if (typeof value === "string") return rawDefaultParser ? rawDefaultParser(value, nativeType) : void 0;
|
|
815
|
-
if (value !== null && typeof value === "object" && "kind" in value) return value;
|
|
816
|
-
}
|
|
817
|
-
function buildFieldNamesByTable(tables) {
|
|
818
|
-
const fieldNamesByTable = /* @__PURE__ */ new Map();
|
|
819
|
-
for (const table of Object.values(tables)) {
|
|
820
|
-
const assignmentOrder = [...Object.values(table.columns).map((column, index) => {
|
|
821
|
-
const { name, map } = toFieldName(column.name);
|
|
822
|
-
return {
|
|
823
|
-
columnName: column.name,
|
|
824
|
-
desiredFieldName: name,
|
|
825
|
-
fieldMap: map,
|
|
826
|
-
index
|
|
827
|
-
};
|
|
828
|
-
})].sort((left, right) => {
|
|
829
|
-
const mapComparison = Number(left.fieldMap !== void 0) - Number(right.fieldMap !== void 0);
|
|
830
|
-
if (mapComparison !== 0) return mapComparison;
|
|
831
|
-
return left.index - right.index;
|
|
832
|
-
});
|
|
833
|
-
const usedFieldNames = /* @__PURE__ */ new Set();
|
|
834
|
-
const tableFieldNames = /* @__PURE__ */ new Map();
|
|
835
|
-
for (const column of assignmentOrder) {
|
|
836
|
-
const fieldName = createUniqueFieldName(column.desiredFieldName, usedFieldNames);
|
|
837
|
-
usedFieldNames.add(fieldName);
|
|
838
|
-
tableFieldNames.set(column.columnName, {
|
|
839
|
-
fieldName,
|
|
840
|
-
fieldMap: column.fieldMap
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
fieldNamesByTable.set(table.name, tableFieldNames);
|
|
844
|
-
}
|
|
845
|
-
return fieldNamesByTable;
|
|
846
|
-
}
|
|
847
|
-
function resolveColumnFieldName(fieldNamesByTable, tableName, columnName) {
|
|
848
|
-
return fieldNamesByTable.get(tableName)?.get(columnName)?.fieldName ?? toFieldName(columnName).name;
|
|
849
|
-
}
|
|
850
|
-
function createUniqueFieldName(desiredName, usedFieldNames) {
|
|
851
|
-
if (!usedFieldNames.has(desiredName)) return desiredName;
|
|
852
|
-
let counter = 2;
|
|
853
|
-
while (usedFieldNames.has(`${desiredName}${counter}`)) counter++;
|
|
854
|
-
return `${desiredName}${counter}`;
|
|
855
|
-
}
|
|
856
|
-
function buildTopLevelNameMap(sources, normalize, kind, sourceKind) {
|
|
857
|
-
const results = /* @__PURE__ */ new Map();
|
|
858
|
-
const normalizedToSources = /* @__PURE__ */ new Map();
|
|
859
|
-
for (const source of sources) {
|
|
860
|
-
const normalized = normalize(source);
|
|
861
|
-
results.set(source, normalized);
|
|
862
|
-
normalizedToSources.set(normalized.name, [...normalizedToSources.get(normalized.name) ?? [], source]);
|
|
863
|
-
}
|
|
864
|
-
const duplicates = [...normalizedToSources.entries()].filter(([, conflictingSources]) => conflictingSources.length > 1);
|
|
865
|
-
if (duplicates.length > 0) {
|
|
866
|
-
const details = duplicates.map(([normalizedName, conflictingSources]) => `- ${kind} "${normalizedName}" from ${sourceKind}s ${conflictingSources.map((source) => `"${source}"`).join(", ")}`);
|
|
867
|
-
throw new Error(`PSL ${kind} name collisions detected:\n${details.join("\n")}`);
|
|
868
|
-
}
|
|
869
|
-
return results;
|
|
870
|
-
}
|
|
871
|
-
function createReservedNamedTypeNames(modelNames) {
|
|
872
|
-
const reservedNames = new Set(PSL_SCALAR_TYPE_NAMES);
|
|
873
|
-
for (const result of modelNames.values()) reservedNames.add(result.name);
|
|
874
|
-
return reservedNames;
|
|
875
|
-
}
|
|
876
|
-
function seedNamedTypeRegistry(schemaIR, typeMap, enumNameMap, reservedNames) {
|
|
877
|
-
const seeds = /* @__PURE__ */ new Map();
|
|
878
|
-
for (const tableName of Object.keys(schemaIR.tables).sort()) {
|
|
879
|
-
const table = schemaIR.tables[tableName];
|
|
880
|
-
if (!table) continue;
|
|
881
|
-
for (const columnName of Object.keys(table.columns).sort()) {
|
|
882
|
-
const column = table.columns[columnName];
|
|
883
|
-
if (!column) continue;
|
|
884
|
-
const resolution = typeMap.resolve(column.nativeType, table.annotations);
|
|
885
|
-
if ("unsupported" in resolution || enumNameMap.has(column.nativeType) || !resolution.nativeTypeAttribute) continue;
|
|
886
|
-
const signatureKey = createNamedTypeSignatureKey(resolution);
|
|
887
|
-
if (!seeds.has(signatureKey)) seeds.set(signatureKey, {
|
|
888
|
-
baseType: resolution.pslType,
|
|
889
|
-
desiredName: toNamedTypeName(column.name),
|
|
890
|
-
nativeTypeAttribute: resolution.nativeTypeAttribute
|
|
891
|
-
});
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
const registry = {
|
|
895
|
-
entriesByKey: /* @__PURE__ */ new Map(),
|
|
896
|
-
usedNames: new Set(reservedNames)
|
|
897
|
-
};
|
|
898
|
-
const sortedSeeds = [...seeds.entries()].sort((left, right) => {
|
|
899
|
-
const desiredNameComparison = left[1].desiredName.localeCompare(right[1].desiredName);
|
|
900
|
-
if (desiredNameComparison !== 0) return desiredNameComparison;
|
|
901
|
-
return left[0].localeCompare(right[0]);
|
|
902
|
-
});
|
|
903
|
-
for (const [signatureKey, seed] of sortedSeeds) {
|
|
904
|
-
const name = createUniqueFieldName(seed.desiredName, registry.usedNames);
|
|
905
|
-
registry.entriesByKey.set(signatureKey, {
|
|
906
|
-
name,
|
|
907
|
-
baseType: seed.baseType,
|
|
908
|
-
nativeTypeAttribute: seed.nativeTypeAttribute
|
|
909
|
-
});
|
|
910
|
-
registry.usedNames.add(name);
|
|
911
|
-
}
|
|
912
|
-
return registry;
|
|
913
|
-
}
|
|
914
|
-
function resolveNamedTypeName(registry, resolution) {
|
|
915
|
-
const key = createNamedTypeSignatureKey(resolution);
|
|
916
|
-
const existing = registry.entriesByKey.get(key);
|
|
917
|
-
if (existing) return existing.name;
|
|
918
|
-
throw new Error(`Named type registry was not seeded for native type "${resolution.nativeType}"`);
|
|
919
|
-
}
|
|
920
|
-
function createNamedTypeSignatureKey(resolution) {
|
|
921
|
-
return JSON.stringify({
|
|
922
|
-
baseType: resolution.pslType,
|
|
923
|
-
nativeTypeAttribute: resolution.nativeTypeAttribute ? {
|
|
924
|
-
name: resolution.nativeTypeAttribute.name,
|
|
925
|
-
args: resolution.nativeTypeAttribute.args ?? null
|
|
926
|
-
} : null
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
function topologicalSort(models, tables, modelNameMap) {
|
|
930
|
-
const modelByName = /* @__PURE__ */ new Map();
|
|
931
|
-
for (const model of models) modelByName.set(model.name, model);
|
|
932
|
-
const deps = /* @__PURE__ */ new Map();
|
|
933
|
-
const tableToModel = /* @__PURE__ */ new Map();
|
|
934
|
-
for (const tableName of Object.keys(tables)) {
|
|
935
|
-
const modelName = modelNameMap.get(tableName);
|
|
936
|
-
tableToModel.set(tableName, modelName);
|
|
937
|
-
deps.set(modelName, /* @__PURE__ */ new Set());
|
|
938
|
-
}
|
|
939
|
-
for (const [tableName, table] of Object.entries(tables)) {
|
|
940
|
-
const modelName = tableToModel.get(tableName);
|
|
941
|
-
for (const fk of table.foreignKeys) {
|
|
942
|
-
const refModelName = tableToModel.get(fk.referencedTable);
|
|
943
|
-
if (refModelName && refModelName !== modelName) deps.get(modelName).add(refModelName);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
const result = [];
|
|
947
|
-
const visited = /* @__PURE__ */ new Set();
|
|
948
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
949
|
-
const sortedNames = [...deps.keys()].sort();
|
|
950
|
-
function visit(name) {
|
|
951
|
-
if (visited.has(name)) return;
|
|
952
|
-
if (visiting.has(name)) return;
|
|
953
|
-
visiting.add(name);
|
|
954
|
-
const sortedDeps = [...deps.get(name)].sort();
|
|
955
|
-
for (const dep of sortedDeps) visit(dep);
|
|
956
|
-
visiting.delete(name);
|
|
957
|
-
visited.add(name);
|
|
958
|
-
result.push(modelByName.get(name));
|
|
959
|
-
}
|
|
960
|
-
for (const name of sortedNames) visit(name);
|
|
961
|
-
return result;
|
|
962
|
-
}
|
|
963
|
-
//#endregion
|
|
964
50
|
//#region src/core/control-instance.ts
|
|
965
51
|
function extractCodecTypeIdsFromContract(contract) {
|
|
966
52
|
const typeIds = /* @__PURE__ */ new Set();
|
|
@@ -1123,8 +209,13 @@ function createSqlFamilyInstance(stack) {
|
|
|
1123
209
|
let controlAdapter;
|
|
1124
210
|
const getControlAdapter = () => controlAdapter ??= adapter.create(stack);
|
|
1125
211
|
const targetSerializer = target.contractSerializer;
|
|
1126
|
-
const
|
|
1127
|
-
|
|
212
|
+
const targetInferPslContract = blindCast(target).inferPslContract;
|
|
213
|
+
const verifyDatabaseSchema = blindCast(target).verifyDatabaseSchema;
|
|
214
|
+
if (!verifyDatabaseSchema) throw new Error(`SQL target "${target.targetId}" is missing the required verifyDatabaseSchema descriptor operation`);
|
|
215
|
+
const deserializeWithTargetSerializer = (contractOrJson) => {
|
|
216
|
+
const serializer = targetSerializer ?? new SqlContractSerializer();
|
|
217
|
+
const json = targetSerializer !== void 0 && !isPlainRecord(contractOrJson) ? targetSerializer.serializeContract(blindCast(contractOrJson)) : contractOrJson;
|
|
218
|
+
return serializer.deserializeContract(json);
|
|
1128
219
|
};
|
|
1129
220
|
return {
|
|
1130
221
|
familyId: "sql",
|
|
@@ -1227,16 +318,40 @@ function createSqlFamilyInstance(stack) {
|
|
|
1227
318
|
},
|
|
1228
319
|
verifySchema(options) {
|
|
1229
320
|
const contract = deserializeWithTargetSerializer(options.contract);
|
|
1230
|
-
const
|
|
1231
|
-
return verifySqlSchema({
|
|
321
|
+
const sqlResult = verifyDatabaseSchema({
|
|
1232
322
|
contract,
|
|
1233
323
|
schema: options.schema,
|
|
1234
324
|
strict: options.strict,
|
|
1235
325
|
typeMetadataRegistry,
|
|
1236
|
-
frameworkComponents: options.frameworkComponents
|
|
1237
|
-
...ifDefined("normalizeDefault", controlAdapter.normalizeDefault),
|
|
1238
|
-
...ifDefined("normalizeNativeType", controlAdapter.normalizeNativeType)
|
|
326
|
+
frameworkComponents: options.frameworkComponents
|
|
1239
327
|
});
|
|
328
|
+
const schemaDiffIssues = filterSchemaDiffIssues(sqlResult.schema.schemaDiffIssues, contract.defaultControlPolicy);
|
|
329
|
+
const relationalFails = sqlResult.schema.counts.fail;
|
|
330
|
+
if (schemaDiffIssues.length === 0) {
|
|
331
|
+
if (schemaDiffIssues === sqlResult.schema.schemaDiffIssues) return sqlResult;
|
|
332
|
+
return {
|
|
333
|
+
...sqlResult,
|
|
334
|
+
schema: {
|
|
335
|
+
...sqlResult.schema,
|
|
336
|
+
schemaDiffIssues
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
const totalFails = relationalFails + schemaDiffIssues.length;
|
|
341
|
+
return {
|
|
342
|
+
...sqlResult,
|
|
343
|
+
ok: false,
|
|
344
|
+
code: sqlResult.code ?? "PN-RUN-3010",
|
|
345
|
+
summary: `Database schema does not satisfy contract (${totalFails} failure${totalFails === 1 ? "" : "s"})`,
|
|
346
|
+
schema: {
|
|
347
|
+
...sqlResult.schema,
|
|
348
|
+
schemaDiffIssues,
|
|
349
|
+
counts: {
|
|
350
|
+
...sqlResult.schema.counts,
|
|
351
|
+
fail: totalFails
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
};
|
|
1240
355
|
},
|
|
1241
356
|
async sign(options) {
|
|
1242
357
|
const { driver, contract: contractInput, contractPath, configPath } = options;
|
|
@@ -1326,7 +441,8 @@ function createSqlFamilyInstance(stack) {
|
|
|
1326
441
|
return getControlAdapter().introspect(options.driver, options.contract);
|
|
1327
442
|
},
|
|
1328
443
|
inferPslContract(schemaIR) {
|
|
1329
|
-
|
|
444
|
+
if (!targetInferPslContract) throw new Error(`Target "${target.targetId}" does not support contract infer (no inferPslContract on its descriptor).`);
|
|
445
|
+
return targetInferPslContract(schemaIR);
|
|
1330
446
|
},
|
|
1331
447
|
lowerAst(ast, context) {
|
|
1332
448
|
return getControlAdapter().lowerToExecuteRequest(ast, context);
|
|
@@ -1334,21 +450,25 @@ function createSqlFamilyInstance(stack) {
|
|
|
1334
450
|
bootstrapControlTableQueries() {
|
|
1335
451
|
return getControlAdapter().bootstrapControlTableQueries();
|
|
1336
452
|
},
|
|
1337
|
-
bootstrapSignMarkerQueries() {
|
|
1338
|
-
return getControlAdapter().bootstrapSignMarkerQueries();
|
|
1339
|
-
},
|
|
1340
453
|
toOperationPreview(operations) {
|
|
1341
454
|
return sqlOperationsToPreview(operations);
|
|
1342
455
|
},
|
|
1343
456
|
toSchemaView(schema) {
|
|
1344
|
-
const
|
|
457
|
+
const root = blindCast(schema);
|
|
458
|
+
const namespaceEntries = root.namespaces !== void 0 ? Object.entries(root.namespaces).map(([namespaceKey, namespace]) => [namespaceKey, namespace.tables]) : [[void 0, root.tables ?? {}]];
|
|
459
|
+
const qualify = namespaceEntries.length > 1;
|
|
460
|
+
const tableNodes = namespaceEntries.flatMap(([namespaceKey, tables]) => Object.entries(tables).map(([tableName, table]) => [
|
|
461
|
+
qualify && namespaceKey !== void 0 ? `${namespaceKey}.${tableName}` : tableName,
|
|
462
|
+
tableName,
|
|
463
|
+
table
|
|
464
|
+
])).map(([displayName, tableName, table]) => {
|
|
1345
465
|
const children = [];
|
|
1346
466
|
const columnNodes = [];
|
|
1347
467
|
for (const [columnName, column] of Object.entries(table.columns)) {
|
|
1348
468
|
const label = `${columnName}: ${column.nativeType} (${column.nullable ? "nullable" : "not nullable"})`;
|
|
1349
469
|
columnNodes.push(new SchemaTreeNode({
|
|
1350
470
|
kind: "field",
|
|
1351
|
-
id: `column-${
|
|
471
|
+
id: `column-${displayName}-${columnName}`,
|
|
1352
472
|
label,
|
|
1353
473
|
meta: {
|
|
1354
474
|
nativeType: column.nativeType,
|
|
@@ -1359,7 +479,7 @@ function createSqlFamilyInstance(stack) {
|
|
|
1359
479
|
}
|
|
1360
480
|
if (columnNodes.length > 0) children.push(new SchemaTreeNode({
|
|
1361
481
|
kind: "collection",
|
|
1362
|
-
id: `columns-${
|
|
482
|
+
id: `columns-${displayName}`,
|
|
1363
483
|
label: "columns",
|
|
1364
484
|
children: columnNodes
|
|
1365
485
|
}));
|
|
@@ -1367,7 +487,7 @@ function createSqlFamilyInstance(stack) {
|
|
|
1367
487
|
const pkColumns = table.primaryKey.columns.join(", ");
|
|
1368
488
|
children.push(new SchemaTreeNode({
|
|
1369
489
|
kind: "index",
|
|
1370
|
-
id: `primary-key-${
|
|
490
|
+
id: `primary-key-${displayName}`,
|
|
1371
491
|
label: `primary key: ${pkColumns}`,
|
|
1372
492
|
meta: {
|
|
1373
493
|
columns: table.primaryKey.columns,
|
|
@@ -1380,7 +500,7 @@ function createSqlFamilyInstance(stack) {
|
|
|
1380
500
|
const label = `unique ${name}`;
|
|
1381
501
|
children.push(new SchemaTreeNode({
|
|
1382
502
|
kind: "index",
|
|
1383
|
-
id: `unique-${
|
|
503
|
+
id: `unique-${displayName}-${name}`,
|
|
1384
504
|
label,
|
|
1385
505
|
meta: {
|
|
1386
506
|
columns: unique.columns,
|
|
@@ -1393,7 +513,7 @@ function createSqlFamilyInstance(stack) {
|
|
|
1393
513
|
const label = index.unique ? `unique index ${name}` : `index ${name}`;
|
|
1394
514
|
children.push(new SchemaTreeNode({
|
|
1395
515
|
kind: "index",
|
|
1396
|
-
id: `index-${
|
|
516
|
+
id: `index-${displayName}-${name}`,
|
|
1397
517
|
label,
|
|
1398
518
|
meta: {
|
|
1399
519
|
columns: index.columns,
|
|
@@ -1414,8 +534,8 @@ function createSqlFamilyInstance(stack) {
|
|
|
1414
534
|
}));
|
|
1415
535
|
return new SchemaTreeNode({
|
|
1416
536
|
kind: "entity",
|
|
1417
|
-
id: `table-${
|
|
1418
|
-
label: `table ${
|
|
537
|
+
id: `table-${displayName}`,
|
|
538
|
+
label: `table ${displayName}`,
|
|
1419
539
|
...Object.keys(tableMeta).length > 0 ? { meta: tableMeta } : {},
|
|
1420
540
|
...children.length > 0 ? { children } : {}
|
|
1421
541
|
});
|
|
@@ -1429,6 +549,24 @@ function createSqlFamilyInstance(stack) {
|
|
|
1429
549
|
}
|
|
1430
550
|
};
|
|
1431
551
|
}
|
|
552
|
+
/**
|
|
553
|
+
* Filters the structural schema-diff issues (from `diffDatabaseSchema`) through
|
|
554
|
+
* the contract's `defaultControlPolicy`. Issues whose outcome maps to a suppressed
|
|
555
|
+
* category under the effective policy are removed. This mirrors the control-policy
|
|
556
|
+
* filtering applied by `verifySqlSchema` for table/column-level findings.
|
|
557
|
+
*
|
|
558
|
+
* Outcome → category mapping:
|
|
559
|
+
* - `'extra'` → `'extraAuxiliary'` (an extra auxiliary entity, e.g. an RLS policy)
|
|
560
|
+
* - `'missing'` → `'declaredMissing'`
|
|
561
|
+
* - `'mismatch'` → `'declaredIncompatible'`
|
|
562
|
+
*/
|
|
563
|
+
function filterSchemaDiffIssues(issues, defaultControlPolicy) {
|
|
564
|
+
if (issues.length === 0) return issues;
|
|
565
|
+
const policy = effectiveControlPolicy(void 0, defaultControlPolicy);
|
|
566
|
+
return issues.filter((issue) => {
|
|
567
|
+
return dispositionForCategory(policy, issue.outcome === "extra" ? "extraAuxiliary" : issue.outcome === "missing" ? "declaredMissing" : "declaredIncompatible") !== "suppress";
|
|
568
|
+
});
|
|
569
|
+
}
|
|
1432
570
|
//#endregion
|
|
1433
571
|
//#region src/core/control-descriptor.ts
|
|
1434
572
|
var SqlFamilyDescriptor = class {
|
|
@@ -1635,8 +773,8 @@ function planFieldEventOperations(options) {
|
|
|
1635
773
|
for (const tableName of tableNames) {
|
|
1636
774
|
const priorTableRaw = priorTables?.[tableName];
|
|
1637
775
|
const newTableRaw = newTables?.[tableName];
|
|
1638
|
-
const priorTable = priorTableRaw
|
|
1639
|
-
const newTable = newTableRaw
|
|
776
|
+
const priorTable = StorageTable.is(priorTableRaw) ? priorTableRaw : void 0;
|
|
777
|
+
const newTable = StorageTable.is(newTableRaw) ? newTableRaw : void 0;
|
|
1640
778
|
const fieldNames = unionSorted(priorTable ? Object.keys(priorTable.columns) : [], newTable ? Object.keys(newTable.columns) : []);
|
|
1641
779
|
for (const fieldName of fieldNames) {
|
|
1642
780
|
const priorField = priorTable?.columns[fieldName];
|
|
@@ -1853,6 +991,6 @@ const INIT_ADDITIVE_POLICY = Object.freeze({ allowedOperationClasses: Object.fre
|
|
|
1853
991
|
//#region src/exports/control.ts
|
|
1854
992
|
var control_default = new SqlFamilyDescriptor();
|
|
1855
993
|
//#endregion
|
|
1856
|
-
export { INIT_ADDITIVE_POLICY, assembleAuthoringContributions, contractToSchemaIR, controlPolicyForCall, createMigrationPlan, control_default as default, detectDestructiveChanges, extractCodecControlHooks, partitionCallsByControlPolicy, partitionIssuesByControlPolicy, planFieldEventOperations, plannerFailure, plannerSuccess, resolveValueSetValues, runnerFailure, runnerSuccess, temporalAuthoringPresets, timestampNowControlDescriptor };
|
|
994
|
+
export { INIT_ADDITIVE_POLICY, assembleAuthoringContributions, contractNamespaceToSchemaIR, contractToSchemaIR, controlPolicyForCall, createMigrationPlan, control_default as default, detectDestructiveChanges, extractCodecControlHooks, partitionCallsByControlPolicy, partitionIssuesByControlPolicy, planFieldEventOperations, plannerFailure, plannerSuccess, resolveValueSetValues, runnerFailure, runnerSuccess, temporalAuthoringPresets, timestampNowControlDescriptor };
|
|
1857
995
|
|
|
1858
996
|
//# sourceMappingURL=control.mjs.map
|