@cheetah.js/orm 0.1.0
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/README.md +228 -0
- package/build.ts +14 -0
- package/cheetah.config.ts +14 -0
- package/dist/SqlBuilder.d.ts +57 -0
- package/dist/SqlBuilder.js +436 -0
- package/dist/SqlBuilder.js.map +1 -0
- package/dist/bun/index.d.ts +13 -0
- package/dist/bun/index.js +224319 -0
- package/dist/bun/index.js.map +306 -0
- package/dist/cheetah.d.ts +1 -0
- package/dist/cheetah.js +24 -0
- package/dist/cheetah.js.map +1 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +5 -0
- package/dist/constants.js.map +1 -0
- package/dist/decorators/entity.decorator.d.ts +3 -0
- package/dist/decorators/entity.decorator.js +10 -0
- package/dist/decorators/entity.decorator.js.map +1 -0
- package/dist/decorators/index.decorator.d.ts +3 -0
- package/dist/decorators/index.decorator.js +17 -0
- package/dist/decorators/index.decorator.js.map +1 -0
- package/dist/decorators/one-many.decorator.d.ts +3 -0
- package/dist/decorators/one-many.decorator.js +17 -0
- package/dist/decorators/one-many.decorator.js.map +1 -0
- package/dist/decorators/primary-key.decorator.d.ts +2 -0
- package/dist/decorators/primary-key.decorator.js +6 -0
- package/dist/decorators/primary-key.decorator.js.map +1 -0
- package/dist/decorators/property.decorator.d.ts +15 -0
- package/dist/decorators/property.decorator.js +25 -0
- package/dist/decorators/property.decorator.js.map +1 -0
- package/dist/domain/base-entity.d.ts +42 -0
- package/dist/domain/base-entity.js +106 -0
- package/dist/domain/base-entity.js.map +1 -0
- package/dist/domain/collection.d.ts +6 -0
- package/dist/domain/collection.js +11 -0
- package/dist/domain/collection.js.map +1 -0
- package/dist/domain/entities.d.ts +40 -0
- package/dist/domain/entities.js +137 -0
- package/dist/domain/entities.js.map +1 -0
- package/dist/domain/reference.d.ts +4 -0
- package/dist/domain/reference.js +7 -0
- package/dist/domain/reference.js.map +1 -0
- package/dist/driver/driver.interface.d.ts +270 -0
- package/dist/driver/driver.interface.js +2 -0
- package/dist/driver/driver.interface.js.map +1 -0
- package/dist/driver/pg-driver.d.ts +43 -0
- package/dist/driver/pg-driver.js +255 -0
- package/dist/driver/pg-driver.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/migration/diff-calculator.d.ts +17 -0
- package/dist/migration/diff-calculator.js +230 -0
- package/dist/migration/diff-calculator.js.map +1 -0
- package/dist/migration/migrator.d.ts +18 -0
- package/dist/migration/migrator.js +233 -0
- package/dist/migration/migrator.js.map +1 -0
- package/dist/orm.d.ts +14 -0
- package/dist/orm.js +23 -0
- package/dist/orm.js.map +1 -0
- package/dist/orm.service.d.ts +8 -0
- package/dist/orm.service.js +115 -0
- package/dist/orm.service.js.map +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +16 -0
- package/dist/utils.js.map +1 -0
- package/package.json +50 -0
- package/src/SqlBuilder.ts +542 -0
- package/src/cheetah.ts +28 -0
- package/src/constants.ts +4 -0
- package/src/decorators/entity.decorator.ts +10 -0
- package/src/decorators/index.decorator.ts +18 -0
- package/src/decorators/one-many.decorator.ts +19 -0
- package/src/decorators/primary-key.decorator.ts +6 -0
- package/src/decorators/property.decorator.ts +41 -0
- package/src/domain/base-entity.ts +149 -0
- package/src/domain/collection.ts +10 -0
- package/src/domain/entities.ts +159 -0
- package/src/domain/reference.ts +5 -0
- package/src/driver/driver.interface.ts +331 -0
- package/src/driver/pg-driver.ts +308 -0
- package/src/index.ts +17 -0
- package/src/migration/diff-calculator.ts +258 -0
- package/src/migration/migrator.ts +278 -0
- package/src/orm.service.ts +115 -0
- package/src/orm.ts +30 -0
- package/src/utils.ts +18 -0
- package/test/domain/base-entity.spec.ts +463 -0
- package/test/migration/.sql +5 -0
- package/test/migration/cheetah.config.ts +13 -0
- package/test/migration/migator.spec.ts +251 -0
- package/test/migration/test.sql +5 -0
- package/test/node-database.ts +32 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ColDiff,
|
|
3
|
+
ConnectionSettings,
|
|
4
|
+
DriverInterface, ForeignKeyInfo,
|
|
5
|
+
SnapshotConstraintInfo,
|
|
6
|
+
SnapshotIndexInfo,
|
|
7
|
+
SnapshotTable,
|
|
8
|
+
Statement,
|
|
9
|
+
} from './driver.interface';
|
|
10
|
+
import { Client } from 'pg'
|
|
11
|
+
|
|
12
|
+
export class PgDriver implements DriverInterface {
|
|
13
|
+
connectionString: string;
|
|
14
|
+
|
|
15
|
+
private client: Client;
|
|
16
|
+
|
|
17
|
+
constructor(options: ConnectionSettings) {
|
|
18
|
+
if (options.connectionString) {
|
|
19
|
+
this.connectionString = options.connectionString;
|
|
20
|
+
} else {
|
|
21
|
+
const {host, port, username, password, database} = options;
|
|
22
|
+
this.connectionString = `postgres://${username}:${password}@${host}:${port}/${database}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.client = new Client({
|
|
26
|
+
connectionString: this.connectionString,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getCreateTableInstruction(schema: string | undefined, tableName: string, creates: ColDiff[]) {
|
|
31
|
+
return `CREATE TABLE "${schema}"."${tableName}" (${creates.map(colDiff => {
|
|
32
|
+
let sql = `"${colDiff.colName}" ${colDiff.colType}${(colDiff.colLength ? `(${colDiff.colLength})` : '')}`;
|
|
33
|
+
|
|
34
|
+
if (!colDiff.colChanges?.nullable) {
|
|
35
|
+
sql += ' NOT NULL';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (colDiff.colChanges?.primary) {
|
|
39
|
+
sql += ' PRIMARY KEY';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (colDiff.colChanges?.unique) {
|
|
43
|
+
sql += ' UNIQUE';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (colDiff.colChanges?.default) {
|
|
47
|
+
sql += ` DEFAULT ${colDiff.colChanges.default}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return sql;
|
|
51
|
+
})});`;
|
|
52
|
+
}
|
|
53
|
+
getAlterTableFkInstruction(schema: string | undefined, tableName: string, colDiff: ColDiff, fk: ForeignKeyInfo) {
|
|
54
|
+
return `ALTER TABLE "${schema}"."${tableName}" ADD CONSTRAINT "${tableName}_${colDiff.colName}_fk" FOREIGN KEY ("${colDiff.colName}") REFERENCES "${fk.referencedTableName}" ("${fk.referencedColumnName}");`;
|
|
55
|
+
}
|
|
56
|
+
getCreateIndex(index: { name: string; properties: string[] }, schema: string | undefined, tableName: string) {
|
|
57
|
+
return `CREATE INDEX "${index.name}" ON "${schema}"."${tableName}" (${index.properties.map(prop => `"${prop}"`).join(', ')});`;
|
|
58
|
+
}
|
|
59
|
+
getAddColumn(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff, colDiffInstructions: string[]): void{
|
|
60
|
+
let sql = `ALTER TABLE "${schema}"."${tableName}" ADD COLUMN "${colName}" ${colDiff.colType}${(colDiff.colLength ? `(${colDiff.colLength})` : '')}`
|
|
61
|
+
|
|
62
|
+
if (!colDiff.colChanges?.nullable) {
|
|
63
|
+
sql += ' NOT NULL';
|
|
64
|
+
}
|
|
65
|
+
if (colDiff.colChanges?.primary) {
|
|
66
|
+
sql += ' PRIMARY KEY';
|
|
67
|
+
}
|
|
68
|
+
if (colDiff.colChanges?.unique) {
|
|
69
|
+
sql += ' UNIQUE';
|
|
70
|
+
}
|
|
71
|
+
if (colDiff.colChanges?.default) {
|
|
72
|
+
sql += ` DEFAULT ${colDiff.colChanges.default}`;
|
|
73
|
+
}
|
|
74
|
+
colDiffInstructions.push(sql.concat(';'));
|
|
75
|
+
|
|
76
|
+
if (colDiff.colChanges?.foreignKeys) {
|
|
77
|
+
colDiff.colChanges.foreignKeys.forEach(fk => {
|
|
78
|
+
colDiffInstructions.push(`ALTER TABLE "${schema}"."${tableName}" ADD CONSTRAINT "${tableName}_${colName}_fk" FOREIGN KEY ("${colName}") REFERENCES "${fk.referencedTableName}" ("${fk.referencedColumnName}");`);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
getDropColumn(colDiffInstructions: string[], schema: string | undefined, tableName: string, colName: string) {
|
|
83
|
+
colDiffInstructions.push(`ALTER TABLE "${schema}"."${tableName}" DROP COLUMN IF EXISTS "${colName}";`);
|
|
84
|
+
}
|
|
85
|
+
getDropIndex(index: { name: string; properties?: string[] }, schema: string | undefined, tableName: string) {
|
|
86
|
+
return `DROP INDEX "${index.name}" ON "${schema}"."${tableName}";`;
|
|
87
|
+
}
|
|
88
|
+
getAlterTableType(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string {
|
|
89
|
+
return `ALTER TABLE "${schema}"."${tableName}" ALTER COLUMN "${colName}" TYPE ${colDiff.colType}${(colDiff.colLength ? `(${colDiff.colLength})` : '')};`;
|
|
90
|
+
}
|
|
91
|
+
getAlterTableDefaultInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string {
|
|
92
|
+
return `ALTER TABLE "${schema}"."${tableName}" ALTER COLUMN "${colName}" SET DEFAULT ${colDiff.colChanges!.default};`;
|
|
93
|
+
}
|
|
94
|
+
getAlterTablePrimaryKeyInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string {
|
|
95
|
+
return `ALTER TABLE "${schema}"."${tableName}" ADD PRIMARY KEY ("${colName}");`
|
|
96
|
+
}
|
|
97
|
+
getDropConstraint(param: { name: string }, schema: string | undefined, tableName: string): string {
|
|
98
|
+
return `ALTER TABLE "${schema}"."${tableName}" DROP CONSTRAINT "${param.name}";`;
|
|
99
|
+
}
|
|
100
|
+
getAddUniqueConstraint(schema: string | undefined, tableName: string, colName: string): string {
|
|
101
|
+
return `ALTER TABLE "${schema}"."${tableName}" ADD UNIQUE ("${colName}");`
|
|
102
|
+
}
|
|
103
|
+
getAlterTableDropNullInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string {
|
|
104
|
+
return `ALTER TABLE "${schema}"."${tableName}" ALTER COLUMN "${colName}" DROP NOT NULL;`
|
|
105
|
+
}
|
|
106
|
+
getAlterTableDropNotNullInstruction(schema: string | undefined, tableName: string, colName: string, colDiff: ColDiff): string {
|
|
107
|
+
return `ALTER TABLE "${schema}"."${tableName}" ALTER COLUMN "${colName}" SET NOT NULL;`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async startTransaction(): Promise<void> {
|
|
111
|
+
await this.client.query('BEGIN;');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async commitTransaction(): Promise<void> {
|
|
115
|
+
await this.client.query('COMMIT;');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async rollbackTransaction(): Promise<void> {
|
|
119
|
+
await this.client.query('ROLLBACK;');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async executeStatement(statement: Statement<any>): Promise<{ query: any, startTime: number, sql: string }>{
|
|
123
|
+
let {statement: statementType, table, columns, where, limit, alias} = statement;
|
|
124
|
+
let sql = '';
|
|
125
|
+
|
|
126
|
+
switch (statementType) {
|
|
127
|
+
case 'select':
|
|
128
|
+
sql = `SELECT ${columns ? columns.join(', ') : '*'} FROM ${table} ${alias}`;
|
|
129
|
+
break;
|
|
130
|
+
case 'insert': // TODO: Tratar corretamente os valores string, number
|
|
131
|
+
const fields = Object.keys(statement.values).map(v => `"${v}"`).join(', ');
|
|
132
|
+
const values = Object.values(statement.values).map(value => this.toDatabaseValue(value)).join(', ');
|
|
133
|
+
|
|
134
|
+
sql = `INSERT INTO ${table} (${fields}) VALUES (${values}) RETURNING ${statement.columns!.join(', ').replaceAll(`${alias}.`, '')}`;
|
|
135
|
+
break;
|
|
136
|
+
case 'update':
|
|
137
|
+
sql = `UPDATE ${table} as ${alias} SET ${Object.entries(statement.values).map(([key, value]) => `${key} = '${value}'`).join(', ')}`;
|
|
138
|
+
break;
|
|
139
|
+
case 'delete':
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (statement.join) {
|
|
144
|
+
statement.join.forEach(join => {
|
|
145
|
+
sql += ` ${join.type} JOIN ${join.joinSchema}.${join.joinTable} ${join.joinAlias} ON ${join.on}`;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (statementType !== 'insert') {
|
|
150
|
+
if (where) {
|
|
151
|
+
sql += ` WHERE ${where}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (statement.orderBy) {
|
|
155
|
+
sql += ` ORDER BY ${statement.orderBy.join(', ')}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (statement.offset) {
|
|
159
|
+
sql += ` OFFSET ${statement.offset}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (limit) {
|
|
163
|
+
sql += ` LIMIT ${statement.limit}`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const startTime = Date.now();
|
|
168
|
+
return {
|
|
169
|
+
query: await this.client.query(sql),
|
|
170
|
+
startTime,
|
|
171
|
+
sql
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
connect(): Promise<void> {
|
|
177
|
+
return this.client.connect()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
disconnect(): Promise<void> {
|
|
181
|
+
return this.client.end()
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
executeSql(s: string) {
|
|
185
|
+
return this.client.query(s)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async snapshot(tableName: string, options: any): Promise<SnapshotTable | undefined> {
|
|
189
|
+
const schema = (options && options.schema) || 'public';
|
|
190
|
+
const sql = `SELECT * FROM information_schema.columns WHERE table_name = '${tableName}' AND table_schema = '${schema}'`;
|
|
191
|
+
const result = await this.client.query(sql);
|
|
192
|
+
|
|
193
|
+
if (!result.rows || result.rows.length === 0) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const indexes = await this.index(tableName, options) || [];
|
|
198
|
+
const constraints = await this.constraints(tableName, options) || [];
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
tableName,
|
|
202
|
+
schema,
|
|
203
|
+
indexes,
|
|
204
|
+
columns: result.rows.map(row => {
|
|
205
|
+
// console.log(this.getForeignKeys(constraints, row), row.column_name)
|
|
206
|
+
return {
|
|
207
|
+
default: row.column_default,
|
|
208
|
+
length: row.character_maximum_length || row.numeric_precision || row.datetime_precision,
|
|
209
|
+
name: row.column_name,
|
|
210
|
+
nullable: row.is_nullable === 'YES',
|
|
211
|
+
primary: constraints.some(c => c.type === 'PRIMARY KEY' && c.consDef.includes(row.column_name)),
|
|
212
|
+
unique: constraints.some(c => (c.type === 'UNIQUE' || c.type === 'PRIMARY KEY') && c.consDef.includes(row.column_name)),
|
|
213
|
+
type: row.data_type,
|
|
214
|
+
foreignKeys: this.getForeignKeys(constraints, row)
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private getForeignKeys(constraints: SnapshotConstraintInfo[], row: any) {
|
|
221
|
+
const name = row.column_name
|
|
222
|
+
return constraints.filter(c => c.type === 'FOREIGN KEY' && c.consDef.match(new RegExp(`FOREIGN KEY \\("${row.column_name}"\\)`))).map(c => {
|
|
223
|
+
const filter = c.consDef.match(/REFERENCES\s+"([^"]+)"\s*\(([^)]+)\)/);
|
|
224
|
+
|
|
225
|
+
if (!filter) throw new Error('Invalid constraint definition');
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
referencedColumnName: filter[2].split(',')[0].trim(),
|
|
229
|
+
referencedTableName: filter[1],
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async index(tableName: string, options: any): Promise<SnapshotIndexInfo[] | undefined> {
|
|
235
|
+
const schema = (options && options.schema) || 'public';
|
|
236
|
+
const result = await this.client.query(
|
|
237
|
+
`SELECT indexname AS index_name, indexdef AS column_name, tablename AS table_name
|
|
238
|
+
FROM pg_indexes
|
|
239
|
+
WHERE tablename = '${tableName}' AND schemaname = '${schema}'`
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
return result.rows.map(row => {
|
|
243
|
+
return {
|
|
244
|
+
table: tableName,
|
|
245
|
+
indexName: row.index_name,
|
|
246
|
+
columnName: row.column_name
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async constraints(tableName: string, options: any): Promise<SnapshotConstraintInfo[] | undefined> {
|
|
252
|
+
const schema = (options && options.schema) || 'public';
|
|
253
|
+
const result = await this.client.query(
|
|
254
|
+
`SELECT
|
|
255
|
+
conname AS index_name,
|
|
256
|
+
pg_get_constraintdef(pg_constraint.oid) as consdef,
|
|
257
|
+
CASE contype
|
|
258
|
+
WHEN 'c' THEN 'CHECK'
|
|
259
|
+
WHEN 'f' THEN 'FOREIGN KEY'
|
|
260
|
+
WHEN 'p' THEN 'PRIMARY KEY'
|
|
261
|
+
WHEN 'u' THEN 'UNIQUE'
|
|
262
|
+
WHEN 't' THEN 'TRIGGER'
|
|
263
|
+
WHEN 'x' THEN 'EXCLUSION'
|
|
264
|
+
ELSE 'UNKNOWN'
|
|
265
|
+
END AS constraint_type
|
|
266
|
+
FROM pg_constraint
|
|
267
|
+
where conrelid = (
|
|
268
|
+
SELECT oid
|
|
269
|
+
FROM pg_class
|
|
270
|
+
WHERE relname = '${tableName}'
|
|
271
|
+
AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = '${schema}')
|
|
272
|
+
)
|
|
273
|
+
AND conkey @> ARRAY(
|
|
274
|
+
SELECT attnum
|
|
275
|
+
FROM pg_attribute
|
|
276
|
+
WHERE attrelid = conrelid
|
|
277
|
+
AND attname = '${schema}'
|
|
278
|
+
)`
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
return result.rows.map(row => {
|
|
282
|
+
return {
|
|
283
|
+
indexName: row.index_name,
|
|
284
|
+
type: row.constraint_type,
|
|
285
|
+
consDef: row.consdef
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private toDatabaseValue(value: unknown) {
|
|
291
|
+
if (value instanceof Date) {
|
|
292
|
+
return `'${value.toISOString()}'`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
switch (typeof value) {
|
|
296
|
+
case 'string':
|
|
297
|
+
return `'${value}'`;
|
|
298
|
+
case 'number':
|
|
299
|
+
return value;
|
|
300
|
+
case 'boolean':
|
|
301
|
+
return value;
|
|
302
|
+
case 'object':
|
|
303
|
+
return `'${JSON.stringify(value)}'`;
|
|
304
|
+
default:
|
|
305
|
+
return `'${value}'`;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Cheetah } from '@cheetah.js/core';
|
|
2
|
+
import { OrmService } from '@cheetah.js/orm/orm.service';
|
|
3
|
+
import { EntityStorage } from '@cheetah.js/orm/domain/entities';
|
|
4
|
+
|
|
5
|
+
export * from './decorators/entity.decorator';
|
|
6
|
+
export * from './decorators/property.decorator';
|
|
7
|
+
export * from './decorators/primary-key.decorator';
|
|
8
|
+
export * from './decorators/one-many.decorator';
|
|
9
|
+
export * from './decorators/index.decorator';
|
|
10
|
+
export * from './orm'
|
|
11
|
+
export * from './orm.service'
|
|
12
|
+
export * from './domain/base-entity'
|
|
13
|
+
export * from './driver/pg-driver'
|
|
14
|
+
export * from './utils'
|
|
15
|
+
export * from './driver/driver.interface'
|
|
16
|
+
|
|
17
|
+
export const CheetahOrm = new Cheetah({exports: [OrmService, EntityStorage]})
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { EntityStorage } from '../domain/entities';
|
|
2
|
+
import { ColDiff, ColumnsInfo, SnapshotTable, TableDiff } from '@cheetah.js/orm/driver/driver.interface';
|
|
3
|
+
|
|
4
|
+
export class DiffCalculator {
|
|
5
|
+
private entities: EntityStorage;
|
|
6
|
+
|
|
7
|
+
constructor(entities: EntityStorage) {
|
|
8
|
+
this.entities = entities;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
diff(snapshotBd: SnapshotTable[], snapshotEntities: SnapshotTable[]): TableDiff[] {
|
|
12
|
+
let diffs: TableDiff[] = [];
|
|
13
|
+
// Cria um mapa (dicionário) para facilitar o acesso por nome da tabela
|
|
14
|
+
const bdTablesMap = new Map(snapshotBd.map(table => [table.tableName, table]));
|
|
15
|
+
const entityTablesMap = new Map(snapshotEntities.map(table => [table.tableName, table]));
|
|
16
|
+
|
|
17
|
+
// Junta todos os nomes de tabelas
|
|
18
|
+
const allTableNames = new Set([...bdTablesMap.keys(), ...entityTablesMap.keys()]);
|
|
19
|
+
|
|
20
|
+
allTableNames.forEach(tableName => {
|
|
21
|
+
const bdTable = bdTablesMap.get(tableName);
|
|
22
|
+
const entityTable = entityTablesMap.get(tableName);
|
|
23
|
+
|
|
24
|
+
if (!entityTable) {
|
|
25
|
+
// Se a tabela só está no banco de dados, precisamos deletá-la
|
|
26
|
+
diffs.push({
|
|
27
|
+
tableName,
|
|
28
|
+
colDiffs: [{actionType: 'DELETE', colName: '*'}], // Indica que todas as colunas devem ser deletadas (ou seja, a tabela inteira)
|
|
29
|
+
});
|
|
30
|
+
} else if (!bdTable) {
|
|
31
|
+
const colDiffs: ColDiff[] = entityTable.columns.flatMap(c => {
|
|
32
|
+
return this.createNewColumn(c, [])
|
|
33
|
+
});
|
|
34
|
+
this.checkIndexes(bdTable, entityTable, colDiffs)
|
|
35
|
+
// Se a tabela só está nas entidades, precisamos criá-la
|
|
36
|
+
diffs.push({
|
|
37
|
+
tableName,
|
|
38
|
+
newTable: true,
|
|
39
|
+
schema: entityTable.schema ?? 'public',
|
|
40
|
+
colDiffs,// Indica que todas as colunas devem ser criadas
|
|
41
|
+
});
|
|
42
|
+
} else {
|
|
43
|
+
const colDiffs: ColDiff[] = [];
|
|
44
|
+
// Se a tabela está em ambos, precisamos comparar as colunas
|
|
45
|
+
const bdColumnsMap = new Map(bdTable.columns.map(col => [col.name, col]));
|
|
46
|
+
const entityColumnsMap = new Map(entityTable.columns.map(col => [col.name, col]));
|
|
47
|
+
const allColumnNames = new Set([...bdColumnsMap.keys(), ...entityColumnsMap.keys()]);
|
|
48
|
+
|
|
49
|
+
this.checkIndexes(bdTable, entityTable, colDiffs)
|
|
50
|
+
|
|
51
|
+
allColumnNames.forEach(colName => {
|
|
52
|
+
const bdCol = bdColumnsMap.get(colName);
|
|
53
|
+
const entityCol = entityColumnsMap.get(colName);
|
|
54
|
+
|
|
55
|
+
if (!entityCol) {
|
|
56
|
+
colDiffs.push({
|
|
57
|
+
actionType: 'DELETE',
|
|
58
|
+
colName: bdCol!.name,
|
|
59
|
+
});
|
|
60
|
+
} else if (!bdCol) {
|
|
61
|
+
this.createNewColumn(entityCol, colDiffs);
|
|
62
|
+
} else this.diffColumnSql(bdCol, entityCol, colDiffs);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (colDiffs.length > 0) {
|
|
66
|
+
diffs.push({
|
|
67
|
+
tableName: tableName,
|
|
68
|
+
schema: entityTable.schema ?? 'public',
|
|
69
|
+
colDiffs,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return diffs;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private checkIndexes(bdTable: SnapshotTable | undefined, entityTable: SnapshotTable | undefined, colDiffs: ColDiff[]) {
|
|
79
|
+
if ((bdTable && bdTable.indexes) || (entityTable && entityTable.indexes)) {
|
|
80
|
+
if (!bdTable || !bdTable.indexes){
|
|
81
|
+
colDiffs.push({
|
|
82
|
+
actionType: 'INDEX',
|
|
83
|
+
colName: '*',
|
|
84
|
+
indexTables: entityTable!.indexes.map(index => ({name: index.indexName, properties: index.columnName.split(',')})),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!entityTable || !entityTable.indexes) {
|
|
89
|
+
colDiffs.push({
|
|
90
|
+
actionType: 'INDEX',
|
|
91
|
+
colName: '*',
|
|
92
|
+
indexTables: bdTable!.indexes.map(index => ({ name: index.indexName })),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if ((bdTable && bdTable.indexes) && (entityTable && entityTable.indexes)) {
|
|
98
|
+
const bdIndexesMap = new Map(bdTable.indexes.map(index => [index.indexName, index]));
|
|
99
|
+
const entityIndexesMap = new Map(entityTable.indexes.map(index => [index.indexName, index]));
|
|
100
|
+
const allIndexes = new Set([...bdIndexesMap.keys(), ...entityIndexesMap.keys()]);
|
|
101
|
+
allIndexes.forEach(indexName => {
|
|
102
|
+
const bdIndex = bdIndexesMap.get(indexName);
|
|
103
|
+
const entityIndex = entityIndexesMap.get(indexName);
|
|
104
|
+
if (!entityIndex) {
|
|
105
|
+
colDiffs.push({
|
|
106
|
+
actionType: 'INDEX',
|
|
107
|
+
colName: bdIndex!.columnName,
|
|
108
|
+
indexTables: [{name: indexName}],
|
|
109
|
+
});
|
|
110
|
+
} else if (!bdIndex) {
|
|
111
|
+
colDiffs.push({
|
|
112
|
+
actionType: 'INDEX',
|
|
113
|
+
colName: entityIndex.columnName,
|
|
114
|
+
indexTables: [{name: indexName, properties: entityIndex.columnName.split(',')}],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private createNewColumn(entityCol: ColumnsInfo, colDiffs: ColDiff[]): ColDiff[] {
|
|
122
|
+
|
|
123
|
+
colDiffs.push({
|
|
124
|
+
actionType: 'CREATE',
|
|
125
|
+
colName: entityCol.name,
|
|
126
|
+
colType: this.convertEntityTypeToSqlType(entityCol.type),
|
|
127
|
+
colLength: entityCol.length,
|
|
128
|
+
colChanges: {
|
|
129
|
+
default: entityCol.default,
|
|
130
|
+
primary: entityCol.primary,
|
|
131
|
+
unique: entityCol.unique,
|
|
132
|
+
nullable: entityCol.nullable,
|
|
133
|
+
foreignKeys: entityCol.foreignKeys ?? [],
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return colDiffs;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private diffColumnType(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]): void {
|
|
141
|
+
if (bdCol.type !== this.convertEntityTypeToSqlType(entityCol.type) || bdCol.length !== entityCol.length) {
|
|
142
|
+
colDiffs.push({
|
|
143
|
+
actionType: 'ALTER',
|
|
144
|
+
colName: entityCol.name,
|
|
145
|
+
colType: this.convertEntityTypeToSqlType(entityCol.type),
|
|
146
|
+
colLength: entityCol.length,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private diffColumnDefault(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]): void {
|
|
152
|
+
if (bdCol.default !== entityCol.default) {
|
|
153
|
+
colDiffs.push({
|
|
154
|
+
actionType: 'ALTER',
|
|
155
|
+
colName: entityCol.name,
|
|
156
|
+
colChanges: {default: entityCol.default},
|
|
157
|
+
colLength: entityCol.length,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private diffColumnPrimary(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]): void {
|
|
163
|
+
if (bdCol.primary !== entityCol.primary) {
|
|
164
|
+
colDiffs.push({
|
|
165
|
+
actionType: 'ALTER',
|
|
166
|
+
colName: entityCol.name,
|
|
167
|
+
colChanges: {primary: entityCol.primary},
|
|
168
|
+
colLength: entityCol.length,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private diffColumnUnique(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]): void {
|
|
174
|
+
if (bdCol.unique !== entityCol.unique) {
|
|
175
|
+
|
|
176
|
+
if (bdCol.unique === false && entityCol.unique === undefined) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
colDiffs.push({
|
|
181
|
+
actionType: 'ALTER',
|
|
182
|
+
colName: entityCol.name,
|
|
183
|
+
colChanges: {unique: entityCol.unique || false},
|
|
184
|
+
colLength: entityCol.length,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private diffForeignKey(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]): void {
|
|
190
|
+
if (bdCol.foreignKeys || entityCol.foreignKeys) {
|
|
191
|
+
const bdFKMap = new Map((bdCol.foreignKeys || []).map(fk => [`${fk.referencedTableName}.${fk.referencedColumnName}`, fk]));
|
|
192
|
+
const entityFKMap = new Map((entityCol.foreignKeys || []).map(fk => [`${fk.referencedTableName}.${fk.referencedColumnName}`, fk]));
|
|
193
|
+
|
|
194
|
+
const allFKs = new Set([...bdFKMap.keys(), ...entityFKMap.keys()]);
|
|
195
|
+
|
|
196
|
+
allFKs.forEach(fkName => {
|
|
197
|
+
const bdFK = bdFKMap.get(fkName);
|
|
198
|
+
const entityFK = entityFKMap.get(fkName);
|
|
199
|
+
|
|
200
|
+
if (!entityFK) {
|
|
201
|
+
colDiffs.push({
|
|
202
|
+
actionType: 'ALTER',
|
|
203
|
+
colName: bdCol.name,
|
|
204
|
+
colChanges: {
|
|
205
|
+
foreignKeys: bdCol.foreignKeys?.filter((fk: any) => fk !== bdFK),
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// else if (!bdFK) {
|
|
210
|
+
// console.log(bdFK, 'lu')
|
|
211
|
+
// colDiffs.push({
|
|
212
|
+
// actionType: 'ALTER',
|
|
213
|
+
// colName: entityCol.name,
|
|
214
|
+
// colChanges: {
|
|
215
|
+
// foreignKeys: [...(bdCol.foreignKeys || []), entityFK],
|
|
216
|
+
// },
|
|
217
|
+
// });
|
|
218
|
+
// }
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private diffColumnSql(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]) {
|
|
225
|
+
this.diffForeignKey(bdCol, entityCol, colDiffs);
|
|
226
|
+
this.diffColumnType(bdCol, entityCol, colDiffs);
|
|
227
|
+
this.diffColumnDefault(bdCol, entityCol, colDiffs);
|
|
228
|
+
this.diffColumnPrimary(bdCol, entityCol, colDiffs);
|
|
229
|
+
this.diffColumnUnique(bdCol, entityCol, colDiffs);
|
|
230
|
+
this.diffColumnNullable(bdCol, entityCol, colDiffs);
|
|
231
|
+
|
|
232
|
+
return colDiffs;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private diffColumnNullable(bdCol: ColumnsInfo, entityCol: ColumnsInfo, colDiffs: ColDiff[]) {
|
|
236
|
+
if (bdCol.nullable !== entityCol.nullable) {
|
|
237
|
+
colDiffs.push({
|
|
238
|
+
actionType: 'ALTER',
|
|
239
|
+
colName: entityCol.name,
|
|
240
|
+
colChanges: {nullable: entityCol.nullable},
|
|
241
|
+
colLength: entityCol.length,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// TODO: Precisa ser de acordo com o driver
|
|
247
|
+
private convertEntityTypeToSqlType(entityType: string): string {
|
|
248
|
+
switch (entityType) {
|
|
249
|
+
case "Number":
|
|
250
|
+
return "numeric";
|
|
251
|
+
case "String":
|
|
252
|
+
return "character varying";
|
|
253
|
+
default:
|
|
254
|
+
return "character varying"
|
|
255
|
+
//... mais casos aqui ...
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|