Skip to content

Commit cda6324

Browse files
committed
feat: progress on database introspection and syncing
1 parent 1b1cd26 commit cda6324

File tree

8 files changed

+346
-102
lines changed

8 files changed

+346
-102
lines changed

packages/cli/src/actions/db.ts

Lines changed: 135 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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';
@@ -11,7 +11,7 @@ import {
1111
} from './action-utils';
1212
import { syncEnums, syncRelation, syncTable, type Relation } from './pull';
1313
import { providers } from './pull/provider';
14-
import { getDatasource, getDbName } from './pull/utils';
14+
import { getDatasource, getDbName, getRelationFkName } from './pull/utils';
1515
import { config } from '@dotenvx/dotenvx';
1616

1717
type PushOptions = {
@@ -25,7 +25,7 @@ export type PullOptions = {
2525
out?: string;
2626
naming?: 'pascal' | 'camel' | 'snake' | 'kebab' | 'none';
2727
alwaysMap?: boolean;
28-
excludeSchemas: string[];
28+
excludeSchemas?: string[];
2929
};
3030

3131
/**
@@ -92,8 +92,8 @@ async function runPull(options: PullOptions) {
9292
}
9393

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

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

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

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

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

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

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

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

191290
if (options.out) {

0 commit comments

Comments
 (0)