Skip to content

Commit b6db32f

Browse files
committed
feat: progress on database introspection and syncing
1 parent 55ea05f commit b6db32f

File tree

8 files changed

+345
-101
lines changed

8 files changed

+345
-101
lines changed

packages/cli/src/actions/db.ts

Lines changed: 135 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { Model, Enum, DataModel } from '@zenstackhq/language/ast';
1+
import { Model, Enum, DataModel, DataField } from '@zenstackhq/language/ast';
22
import { ZModelCodeGenerator } from '@zenstackhq/sdk';
33
import fs from 'node:fs';
44
import path from 'node:path';
55
import { execPrisma } from '../utils/exec-utils';
66
import { generateTempPrismaSchema, getSchemaFile, handleSubProcessError, requireDataSourceUrl, loadSchemaDocumentWithServices } from './action-utils';
77
import { syncEnums, syncRelation, syncTable, type Relation } from './pull';
88
import { providers } from './pull/provider';
9-
import { getDatasource, getDbName } from './pull/utils';
9+
import { getDatasource, getDbName, getRelationFkName } from './pull/utils';
1010
import { config } from '@dotenvx/dotenvx';
1111

1212
type PushOptions = {
@@ -20,7 +20,7 @@ export type PullOptions = {
2020
out?: string;
2121
naming?: 'pascal' | 'camel' | 'snake' | 'kebab' | 'none';
2222
alwaysMap?: boolean;
23-
excludeSchemas: string[];
23+
excludeSchemas?: string[];
2424
};
2525

2626
/**
@@ -91,8 +91,8 @@ async function runPull(options: PullOptions) {
9191
}
9292

9393
const { enums: allEnums, tables: allTables } = await provider.introspect(datasource.url);
94-
const enums = allEnums.filter((e) => !options.excludeSchemas.includes(e.schema_name));
95-
const tables = allTables.filter((t) => !options.excludeSchemas.includes(t.schema));
94+
const enums = allEnums.filter((e) => !options.excludeSchemas?.includes(e.schema_name));
95+
const tables = allTables.filter((t) => !options.excludeSchemas?.includes(t.schema));
9696

9797
const newModel: Model = {
9898
$type: 'Model',
@@ -112,79 +112,178 @@ async function runPull(options: PullOptions) {
112112
}
113113

114114
for (const relation of resolvedRelations) {
115-
syncRelation({ model: newModel, relation, services, options });
115+
const simmilarRelations = resolvedRelations.filter((rr) => {
116+
return (
117+
(rr.schema === relation.schema &&
118+
rr.table === relation.table &&
119+
rr.references.schema === relation.references.schema &&
120+
rr.references.table === relation.references.table) ||
121+
(rr.schema === relation.references.schema &&
122+
rr.column === relation.references.column &&
123+
rr.references.schema === relation.schema &&
124+
rr.references.table === relation.table)
125+
);
126+
}).length;
127+
const selfRelation =
128+
relation.references.schema === relation.schema && relation.references.table === relation.table;
129+
syncRelation({
130+
model: newModel,
131+
relation,
132+
services,
133+
options,
134+
selfRelation,
135+
simmilarRelations,
136+
});
116137
}
117138

118139
const cwd = new URL(`file://${process.cwd()}`).pathname;
119140
const docs = services.shared.workspace.LangiumDocuments.all
120141
.filter(({ uri }) => uri.path.toLowerCase().startsWith(cwd.toLowerCase()))
121142
.toArray();
122143
const docsSet = new Set(docs.map((d) => d.uri.toString()));
123-
console.log(docsSet);
144+
145+
services.shared.workspace.IndexManager.allElements('DataModel', docsSet)
146+
.filter(
147+
(declaration) =>
148+
!newModel.declarations.find((d) => getDbName(d) === getDbName(declaration.node as any)),
149+
)
150+
.forEach((decl) => {
151+
const model = decl.node!.$container as Model;
152+
const index = model.declarations.findIndex((d) => d === decl.node);
153+
model.declarations.splice(index, 1);
154+
console.log(`Delete model ${decl.name}`);
155+
});
156+
services.shared.workspace.IndexManager.allElements('Enum', docsSet)
157+
.filter(
158+
(declaration) =>
159+
!newModel.declarations.find((d) => getDbName(d) === getDbName(declaration.node as any)),
160+
)
161+
.forEach((decl) => {
162+
const model = decl.node!.$container as Model;
163+
const index = model.declarations.findIndex((d) => d === decl.node);
164+
model.declarations.splice(index, 1);
165+
console.log(`Delete enum ${decl.name}`);
166+
});
167+
124168
newModel.declarations
125169
.filter((d) => [DataModel, Enum].includes(d.$type))
126170
.forEach((_declaration) => {
127171
const declaration = _declaration as DataModel | Enum;
128-
const declarations = services.shared.workspace.IndexManager.allElements(declaration.$type, docsSet);
172+
const declarations = services.shared.workspace.IndexManager.allElements(
173+
declaration.$type,
174+
docsSet,
175+
).toArray();
129176
const originalModel = declarations.find((d) => getDbName(d.node as any) === getDbName(declaration))
130177
?.node as DataModel | Enum | undefined;
131178
if (!originalModel) {
132179
model.declarations.push(declaration);
133180
(declaration as any).$container = model;
181+
declaration.fields.forEach((f) => {
182+
if (f.$type === 'DataField' && f.type.reference?.ref) {
183+
const ref = declarations.find(
184+
(d) => getDbName(d.node as any) === getDbName(f.type.reference!.ref as any),
185+
)?.node;
186+
if (ref) (f.type.reference.ref as any) = ref;
187+
}
188+
});
134189
return;
135190
}
136191

137192
declaration.fields.forEach((f) => {
138-
const originalField = originalModel.fields.find((d) => getDbName(d) === getDbName(f));
193+
const originalField = originalModel.fields.find(
194+
(d) =>
195+
getDbName(d) === getDbName(f) ||
196+
(getRelationFkName(d as any) === getRelationFkName(f as any) &&
197+
!!getRelationFkName(d as any) &&
198+
!!getRelationFkName(f as any)),
199+
);
139200

140201
if (!originalField) {
141-
console.log(`Added field ${f.name} to ${originalModel.name}`);
202+
//console.log(`Added field ${f.name} to ${originalModel.name}`);
142203
(f as any).$container = originalModel;
143204
originalModel.fields.push(f as any);
205+
if (f.$type === 'DataField' && f.type.reference?.ref) {
206+
const ref = declarations.find(
207+
(d) => getDbName(d.node as any) === getDbName(f.type.reference!.ref as any),
208+
)?.node as DataModel | undefined;
209+
if (ref) {
210+
(f.type.reference.$refText as any) = ref.name;
211+
(f.type.reference.ref as any) = ref;
212+
}
213+
}
144214
return;
145215
}
146-
//TODO: update field
216+
217+
if (originalField.$type === 'DataField') {
218+
const field = f as DataField;
219+
originalField.type = field.type;
220+
if (field.type.reference) {
221+
const ref = declarations.find(
222+
(d) => getDbName(d.node as any) === getDbName(field.type.reference!.ref as any),
223+
)?.node as DataModel | undefined;
224+
if (ref) {
225+
(field.type.reference.$refText as any) = ref.name;
226+
(field.type.reference.ref as any) = ref;
227+
}
228+
}
229+
230+
(originalField.type.$container as any) = originalField;
231+
}
232+
233+
f.attributes.forEach((attr) => {
234+
const originalAttribute = originalField.attributes.find(
235+
(d) => d.decl.$refText === attr.decl.$refText,
236+
);
237+
238+
if (!originalAttribute) {
239+
//console.log(`Added Attribute ${attr.decl.$refText} to ${f.name}`);
240+
(f as any).$container = originalField;
241+
originalField.attributes.push(attr as any);
242+
return;
243+
}
244+
245+
originalAttribute.args = attr.args;
246+
attr.args.forEach((a) => {
247+
(a.$container as any) = originalAttribute;
248+
});
249+
});
250+
251+
originalField.attributes
252+
.filter((attr) => !f.attributes.find((d) => d.decl.$refText === attr.decl.$refText))
253+
.forEach((attr) => {
254+
const field = attr.$container;
255+
const index = field.attributes.findIndex((d) => d === attr);
256+
field.attributes.splice(index, 1);
257+
//console.log(`Delete attribute from field:${field.name} ${attr.decl.$refText}`);
258+
});
147259
});
148260
originalModel.fields
149-
.filter((f) => !declaration.fields.find((d) => getDbName(d) === getDbName(f)))
261+
.filter(
262+
(f) =>
263+
!declaration.fields.find(
264+
(d) =>
265+
getDbName(d) === getDbName(f) ||
266+
(getRelationFkName(d as any) === getRelationFkName(f as any) &&
267+
!!getRelationFkName(d as any) &&
268+
!!getRelationFkName(f as any)),
269+
),
270+
)
150271
.forEach((f) => {
151272
const model = f.$container;
152273
const index = model.fields.findIndex((d) => d === f);
153274
model.fields.splice(index, 1);
154-
console.log(`Delete field ${f.name}`);
275+
//console.log(`Delete field ${f.name}`);
155276
});
156277
});
157278

158-
services.shared.workspace.IndexManager.allElements('DataModel', docsSet)
159-
.filter(
160-
(declaration) =>
161-
!newModel.declarations.find((d) => getDbName(d) === getDbName(declaration.node as any)),
162-
)
163-
.forEach((decl) => {
164-
const model = decl.node!.$container as Model;
165-
const index = model.declarations.findIndex((d) => d === decl.node);
166-
model.declarations.splice(index, 1);
167-
console.log(`Delete model ${decl.name}`);
168-
});
169-
services.shared.workspace.IndexManager.allElements('Enum', docsSet)
170-
.filter(
171-
(declaration) =>
172-
!newModel.declarations.find((d) => getDbName(d) === getDbName(declaration.node as any)),
173-
)
174-
.forEach((decl) => {
175-
const model = decl.node!.$container as Model;
176-
const index = model.declarations.findIndex((d) => d === decl.node);
177-
model.declarations.splice(index, 1);
178-
console.log(`Delete enum ${decl.name}`);
179-
});
180-
181279
if (options.out && !fs.lstatSync(options.out).isFile()) {
182280
throw new Error(`Output path ${options.out} is not a file`);
183281
}
184282

185283
const generator = new ZModelCodeGenerator({
186284
//TODO: make configurable
187285
quote: 'double',
286+
indent: 2,
188287
});
189288

190289
if (options.out) {

0 commit comments

Comments
 (0)