@prisma-next/family-sql 0.5.0-dev.27 → 0.5.0-dev.28
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/control.d.mts +1 -1
- package/dist/control.mjs +1012 -0
- package/dist/control.mjs.map +1 -1
- package/dist/migration.d.mts +1 -1
- package/dist/schema-verify.d.mts +1 -1
- package/dist/{types-BtFk2T25.d.mts → types-sZihdnGx.d.mts} +6 -3
- package/dist/types-sZihdnGx.d.mts.map +1 -0
- package/package.json +18 -17
- package/src/core/control-instance.ts +21 -0
- package/src/core/operation-preview.ts +62 -0
- package/src/core/psl-contract-infer/default-mapping.ts +56 -0
- package/src/core/psl-contract-infer/name-transforms.ts +178 -0
- package/src/core/psl-contract-infer/postgres-default-mapping.ts +16 -0
- package/src/core/psl-contract-infer/postgres-type-map.ts +165 -0
- package/src/core/psl-contract-infer/printer-config.ts +55 -0
- package/src/core/psl-contract-infer/raw-default-parser.ts +91 -0
- package/src/core/psl-contract-infer/relation-inference.ts +196 -0
- package/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts +832 -0
- package/dist/types-BtFk2T25.d.mts.map +0 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { SqlForeignKeyIR, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
|
|
2
|
+
import { deriveBackRelationFieldName, deriveRelationFieldName, pluralize } from './name-transforms';
|
|
3
|
+
import type { RelationField } from './printer-config';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_ON_DELETE = 'noAction';
|
|
6
|
+
const DEFAULT_ON_UPDATE = 'noAction';
|
|
7
|
+
|
|
8
|
+
const REFERENTIAL_ACTION_PSL: Record<string, string> = {
|
|
9
|
+
noAction: 'NoAction',
|
|
10
|
+
restrict: 'Restrict',
|
|
11
|
+
cascade: 'Cascade',
|
|
12
|
+
setNull: 'SetNull',
|
|
13
|
+
setDefault: 'SetDefault',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type InferredRelations = {
|
|
17
|
+
readonly relationsByTable: ReadonlyMap<string, readonly RelationField[]>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function inferRelations(
|
|
21
|
+
tables: Record<string, SqlTableIR>,
|
|
22
|
+
modelNameMap: ReadonlyMap<string, string>,
|
|
23
|
+
): InferredRelations {
|
|
24
|
+
const relationsByTable = new Map<string, RelationField[]>();
|
|
25
|
+
|
|
26
|
+
const fkCountByPair = new Map<string, number>();
|
|
27
|
+
for (const table of Object.values(tables)) {
|
|
28
|
+
for (const fk of table.foreignKeys) {
|
|
29
|
+
const pairKey = `${table.name}→${fk.referencedTable}`;
|
|
30
|
+
fkCountByPair.set(pairKey, (fkCountByPair.get(pairKey) ?? 0) + 1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const usedFieldNames = new Map<string, Set<string>>();
|
|
35
|
+
for (const table of Object.values(tables)) {
|
|
36
|
+
const names = new Set<string>();
|
|
37
|
+
for (const col of Object.values(table.columns)) {
|
|
38
|
+
names.add(col.name);
|
|
39
|
+
}
|
|
40
|
+
usedFieldNames.set(table.name, names);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const table of Object.values(tables)) {
|
|
44
|
+
for (const fk of table.foreignKeys) {
|
|
45
|
+
const childTableName = table.name;
|
|
46
|
+
const parentTableName = fk.referencedTable;
|
|
47
|
+
const childUsed = usedFieldNames.get(childTableName) as Set<string>;
|
|
48
|
+
const childModelName = modelNameMap.get(childTableName) ?? childTableName;
|
|
49
|
+
const parentModelName = modelNameMap.get(parentTableName) ?? parentTableName;
|
|
50
|
+
const pairKey = `${childTableName}→${parentTableName}`;
|
|
51
|
+
const isSelfRelation = childTableName === parentTableName;
|
|
52
|
+
const needsRelationName = (fkCountByPair.get(pairKey) as number) > 1 || isSelfRelation;
|
|
53
|
+
|
|
54
|
+
const isOneToOne = detectOneToOne(fk, table);
|
|
55
|
+
|
|
56
|
+
const childRelFieldName = resolveUniqueFieldName(
|
|
57
|
+
deriveRelationFieldName(fk.columns, parentTableName),
|
|
58
|
+
childUsed,
|
|
59
|
+
parentModelName,
|
|
60
|
+
);
|
|
61
|
+
const relationName = needsRelationName
|
|
62
|
+
? deriveRelationName(fk, childRelFieldName, parentModelName, isSelfRelation)
|
|
63
|
+
: undefined;
|
|
64
|
+
const childOptional = fk.columns.some(
|
|
65
|
+
(columnName) => table.columns[columnName]?.nullable ?? false,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const childRelField = buildChildRelationField(
|
|
69
|
+
childRelFieldName,
|
|
70
|
+
parentModelName,
|
|
71
|
+
fk,
|
|
72
|
+
childOptional,
|
|
73
|
+
relationName,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
addRelationField(relationsByTable, childTableName, childRelField);
|
|
77
|
+
childUsed.add(childRelFieldName);
|
|
78
|
+
|
|
79
|
+
const parentUsed = usedFieldNames.get(parentTableName) ?? new Set();
|
|
80
|
+
usedFieldNames.set(parentTableName, parentUsed);
|
|
81
|
+
|
|
82
|
+
const backRelFieldName = resolveUniqueFieldName(
|
|
83
|
+
deriveBackRelationFieldName(childModelName, isOneToOne),
|
|
84
|
+
parentUsed,
|
|
85
|
+
childModelName,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const backRelField: RelationField = {
|
|
89
|
+
fieldName: backRelFieldName,
|
|
90
|
+
typeName: childModelName,
|
|
91
|
+
optional: isOneToOne,
|
|
92
|
+
list: !isOneToOne,
|
|
93
|
+
relationName,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
addRelationField(relationsByTable, parentTableName, backRelField);
|
|
97
|
+
parentUsed.add(backRelFieldName);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { relationsByTable };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function detectOneToOne(fk: SqlForeignKeyIR, table: SqlTableIR): boolean {
|
|
105
|
+
const fkCols = [...fk.columns].sort();
|
|
106
|
+
|
|
107
|
+
if (table.primaryKey) {
|
|
108
|
+
const pkCols = [...table.primaryKey.columns].sort();
|
|
109
|
+
if (pkCols.length === fkCols.length && pkCols.every((c, i) => c === fkCols[i])) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const unique of table.uniques) {
|
|
115
|
+
const uniqueCols = [...unique.columns].sort();
|
|
116
|
+
if (uniqueCols.length === fkCols.length && uniqueCols.every((c, i) => c === fkCols[i])) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function deriveRelationName(
|
|
125
|
+
fk: SqlForeignKeyIR,
|
|
126
|
+
childRelationFieldName: string,
|
|
127
|
+
parentModelName: string,
|
|
128
|
+
isSelfRelation: boolean,
|
|
129
|
+
): string {
|
|
130
|
+
if (fk.name) {
|
|
131
|
+
return fk.name;
|
|
132
|
+
}
|
|
133
|
+
if (isSelfRelation) {
|
|
134
|
+
return `${childRelationFieldName.charAt(0).toUpperCase() + childRelationFieldName.slice(1)}${pluralize(parentModelName)}`;
|
|
135
|
+
}
|
|
136
|
+
return fk.columns.join('_');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildChildRelationField(
|
|
140
|
+
fieldName: string,
|
|
141
|
+
parentModelName: string,
|
|
142
|
+
fk: SqlForeignKeyIR,
|
|
143
|
+
optional: boolean,
|
|
144
|
+
relationName?: string,
|
|
145
|
+
): RelationField {
|
|
146
|
+
const onDelete = fk.onDelete && fk.onDelete !== DEFAULT_ON_DELETE ? fk.onDelete : undefined;
|
|
147
|
+
const onUpdate = fk.onUpdate && fk.onUpdate !== DEFAULT_ON_UPDATE ? fk.onUpdate : undefined;
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
fieldName,
|
|
151
|
+
typeName: parentModelName,
|
|
152
|
+
referencedTableName: fk.referencedTable,
|
|
153
|
+
optional,
|
|
154
|
+
list: false,
|
|
155
|
+
relationName,
|
|
156
|
+
fkName: fk.name,
|
|
157
|
+
fields: fk.columns,
|
|
158
|
+
references: fk.referencedColumns,
|
|
159
|
+
onDelete: onDelete ? REFERENTIAL_ACTION_PSL[onDelete] : undefined,
|
|
160
|
+
onUpdate: onUpdate ? REFERENTIAL_ACTION_PSL[onUpdate] : undefined,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function resolveUniqueFieldName(
|
|
165
|
+
desired: string,
|
|
166
|
+
usedNames: ReadonlySet<string>,
|
|
167
|
+
fallbackSuffix: string,
|
|
168
|
+
): string {
|
|
169
|
+
if (!usedNames.has(desired)) {
|
|
170
|
+
return desired;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const withSuffix = `${desired}${fallbackSuffix}`;
|
|
174
|
+
if (!usedNames.has(withSuffix)) {
|
|
175
|
+
return withSuffix;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let counter = 2;
|
|
179
|
+
while (usedNames.has(`${desired}${counter}`)) {
|
|
180
|
+
counter++;
|
|
181
|
+
}
|
|
182
|
+
return `${desired}${counter}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function addRelationField(
|
|
186
|
+
map: Map<string, RelationField[]>,
|
|
187
|
+
tableName: string,
|
|
188
|
+
field: RelationField,
|
|
189
|
+
): void {
|
|
190
|
+
const existing = map.get(tableName);
|
|
191
|
+
if (existing) {
|
|
192
|
+
existing.push(field);
|
|
193
|
+
} else {
|
|
194
|
+
map.set(tableName, [field]);
|
|
195
|
+
}
|
|
196
|
+
}
|