masquerade-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/LICENSE +21 -0
- package/README.md +149 -0
- package/bin/universalTsInit.js +59 -0
- package/docs/deletion.md +186 -0
- package/docs/find.md +265 -0
- package/docs/getting-started-javascript.md +112 -0
- package/docs/getting-started-typescript.md +159 -0
- package/docs/in-depth-class-definitions.md +193 -0
- package/docs/jsdoc-ux-tips.md +17 -0
- package/docs/managing-the-database.md +37 -0
- package/docs/saving-to-database.md +37 -0
- package/index.d.ts +7 -0
- package/index.js +11 -0
- package/jsconfig.json +29 -0
- package/package.json +40 -0
- package/src/ORM/DbManager.js +76 -0
- package/src/ORM/ORM.js +181 -0
- package/src/ORM/bootOrm.js +929 -0
- package/src/changeLogger/changeLogger.js +55 -0
- package/src/changeLogger/save.js +237 -0
- package/src/changeLogger/sqlClients/postgres.js +97 -0
- package/src/changeLogger/sqlClients/sqlite.js +120 -0
- package/src/entity/delete/delete.js +20 -0
- package/src/entity/delete/getDependents.js +46 -0
- package/src/entity/entity.d.ts +46 -0
- package/src/entity/entity.js +228 -0
- package/src/entity/find/find.js +157 -0
- package/src/entity/find/joins.js +52 -0
- package/src/entity/find/queryBuilder.js +95 -0
- package/src/entity/find/relations.js +23 -0
- package/src/entity/find/scopeProxies.js +60 -0
- package/src/entity/find/sqlClients/postgresFuncs.js +171 -0
- package/src/entity/find/sqlClients/sqliteFuncs.js +211 -0
- package/src/entity/find/where/relationalWhere.js +142 -0
- package/src/entity/find/where/where.js +244 -0
- package/src/entity/find/where/whereArgsFunctions.js +49 -0
- package/src/misc/classes.js +63 -0
- package/src/misc/constants.js +42 -0
- package/src/misc/miscFunctions.js +167 -0
- package/src/misc/ormStore.js +18 -0
- package/src/misc/types.d.ts +560 -0
- package/src/proxies/instanceProxy.js +544 -0
- package/src/proxies/nonRelationalArrayProxy.js +76 -0
- package/src/proxies/objectProxy.js +50 -0
- package/src/proxies/relationalArrayProxy.js +130 -0
- package/src/webpack/masquerade-loader.js +14 -0
- package/src/webpack/plugin.js +43 -0
- package/src/webpack/store.js +2 -0
- package/src/webpack/webpack.config.js +41 -0
- package/testing/dev-doc.md +7 -0
- package/testing/generationFuncs.js +21 -0
- package/testing/miscFunctions.js +97 -0
- package/testing/postgres.test.js +254 -0
- package/testing/sqlite.test.js +257 -0
- package/testing/testing-classes.js +102 -0
|
@@ -0,0 +1,929 @@
|
|
|
1
|
+
|
|
2
|
+
import { createSourceFile, SyntaxKind, ScriptKind, ScriptTarget } from "typescript"
|
|
3
|
+
import { js2SqlTyping, nonSnake2Snake, snake2Pascal, array2String, coloredBackgroundConsoleLog } from "../misc/miscFunctions.js"
|
|
4
|
+
import { dependenciesSymb, floatColumnTypes, referencesSymb } from "../misc/constants.js"
|
|
5
|
+
import { uuidv7 } from "uuidv7"
|
|
6
|
+
import { DbManagerStore } from "./DbManager.js"
|
|
7
|
+
import { OrmStore } from "../misc/ormStore.js"
|
|
8
|
+
/**@typedef {import('../misc/types.js').TABLE} TABLE */
|
|
9
|
+
/**@typedef {import('../misc/types.js').DbPrimaryKey} DbPrimaryKey */
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export async function compareAgainstDb(tablesDict) {
|
|
13
|
+
const { dbConnection, sqlClient } = OrmStore.store
|
|
14
|
+
let queryFunc
|
|
15
|
+
let strFunc
|
|
16
|
+
let dbTableNames
|
|
17
|
+
if (sqlClient === `postgresql`) {
|
|
18
|
+
queryFunc = dbConnection.query.bind(dbConnection)
|
|
19
|
+
strFunc = (tableName) => `SELECT column_name
|
|
20
|
+
FROM information_schema.columns
|
|
21
|
+
WHERE table_schema = 'public'
|
|
22
|
+
AND table_name = '${tableName}';`
|
|
23
|
+
|
|
24
|
+
dbTableNames = (await queryFunc(`SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'public' ORDER BY tablename;`))
|
|
25
|
+
.rows.map(rowObj => rowObj.tablename)
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
queryFunc = (sql) => {
|
|
29
|
+
const stmt = dbConnection.prepare(sql)
|
|
30
|
+
const rows = stmt.all()
|
|
31
|
+
return rows.map(r => r.name)
|
|
32
|
+
}
|
|
33
|
+
strFunc = (tableName) => `SELECT name FROM pragma_table_info('${tableName}');`
|
|
34
|
+
dbTableNames = dbConnection.prepare(`PRAGMA table_list;`).all()
|
|
35
|
+
.filter(tableObj => tableObj.schema === `main` && tableObj.name !== `sqlite_schema`)
|
|
36
|
+
.map(tableObj => tableObj.name)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const [tableName, tableObj] of Object.entries(tablesDict)) {
|
|
40
|
+
const tableColumns = sqlClient === `postgresql`
|
|
41
|
+
? (await queryFunc(strFunc(tableName))).rows.map(rowObj => snake2Pascal(rowObj.column_name, true))
|
|
42
|
+
: await queryFunc(strFunc(tableName)).map(tableName => snake2Pascal(tableName, true))
|
|
43
|
+
|
|
44
|
+
const index = dbTableNames.findIndex(dbTableName => dbTableName === tableName)
|
|
45
|
+
if (index === -1) {
|
|
46
|
+
tableObj.alreadyExists = false
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
tableObj.alreadyExists = true
|
|
51
|
+
dbTableNames.splice(index, 1)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const classColumns = Object.keys(tableObj.columns)
|
|
55
|
+
const unusedColumns = tableColumns.filter(columnName => !classColumns.includes(columnName))
|
|
56
|
+
const newColumns = classColumns.filter(columnName => !tableColumns.includes(columnName))
|
|
57
|
+
|
|
58
|
+
if (newColumns.length) { // newColumns includes not only new columns, but also properties corresponding to junction tables, that may or may not exist on db.
|
|
59
|
+
for (const column of newColumns) {
|
|
60
|
+
const columnObj = tableObj.columns[column]
|
|
61
|
+
if (columnObj.relational) {
|
|
62
|
+
const junctionTableName = `${tableName}___${nonSnake2Snake(columnObj.name)}_jt`
|
|
63
|
+
const index = dbTableNames.findIndex(dbTableName => dbTableName === junctionTableName)
|
|
64
|
+
if (index !== -1) {
|
|
65
|
+
dbTableNames.splice(index, 1)
|
|
66
|
+
delete tableObj.columns[column]
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const newColumnsDict = tableObj.newColumns ??= {}
|
|
71
|
+
newColumnsDict[column] = columnObj
|
|
72
|
+
delete tableObj.columns[column]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (unusedColumns.length) {
|
|
77
|
+
const className = snake2Pascal(tableName, true)
|
|
78
|
+
const dropColumnsDict = DbManagerStore.dropColumnsDict
|
|
79
|
+
dropColumnsDict[className] = unusedColumns
|
|
80
|
+
const loggedArr = array2String(unusedColumns.map(columnName => nonSnake2Snake(columnName)))
|
|
81
|
+
coloredBackgroundConsoleLog(`Warning: Unused columns found in table '${tableName}' (${className}): ${loggedArr}. Consider removing them manually or using DbManager\`s 'dropUnusedColumns' method.\n`, "warning")
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const unusedJunctions = dbTableNames.filter(tableName => tableName.includes(`___`) && tableName.endsWith(`_jt`))
|
|
85
|
+
const unusedTables = dbTableNames.filter(tableName => !unusedJunctions.includes(tableName))
|
|
86
|
+
if (unusedTables.length) {
|
|
87
|
+
coloredBackgroundConsoleLog(`Warning: The following entity tables are unused: ${array2String(unusedTables)}. Consider removing them manually or using DbManager\`s 'dropUnusedTables' method.\n`, "warning")
|
|
88
|
+
DbManagerStore.deleteTables = unusedTables
|
|
89
|
+
}
|
|
90
|
+
if (unusedJunctions.length) {
|
|
91
|
+
coloredBackgroundConsoleLog(`Warning: The following junction tables are unused: ${array2String(unusedJunctions)}. Consider removing them manually or using DbManager\`s 'dropUnusedJunctions' method.\n`, "warning")
|
|
92
|
+
DbManagerStore.deleteJunctions = unusedJunctions
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
export async function getInitIdValues(tablesDict) {
|
|
98
|
+
const ormStore = OrmStore.store
|
|
99
|
+
const idLogger = ormStore.idLogger = {}
|
|
100
|
+
const { dbConnection, sqlClient } = ormStore
|
|
101
|
+
let queryFunc
|
|
102
|
+
if (sqlClient === `postgresql`) queryFunc = dbConnection.query.bind(dbConnection)
|
|
103
|
+
else queryFunc = (query) => dbConnection.prepare(query).all()
|
|
104
|
+
|
|
105
|
+
const queryFuncWithTryCatch = async (query) => {
|
|
106
|
+
try {
|
|
107
|
+
return await queryFunc(query)
|
|
108
|
+
}
|
|
109
|
+
catch (e) { }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const [tableName, tableObj] of Object.entries(tablesDict)) {
|
|
113
|
+
const idType = tableObj.columns.id.type
|
|
114
|
+
if (idType === `string`) {
|
|
115
|
+
idLogger[snake2Pascal(tableName)] = uuidv7
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const queryStr = `SELECT id FROM ${tableName} ORDER BY id DESC LIMIT 1;`
|
|
120
|
+
const res = await queryFuncWithTryCatch(queryStr)
|
|
121
|
+
if (!res) {
|
|
122
|
+
const idVal = idType === `number` ? 0 : 0n
|
|
123
|
+
idLogger[snake2Pascal(tableName)] = idVal
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
let id = sqlClient === `postgresql` ? res.rows[0]?.id : res[0]?.id
|
|
127
|
+
if (id) {
|
|
128
|
+
id = idType === `number` ? parseInt(id, 10) : BigInt(id)
|
|
129
|
+
idLogger[snake2Pascal(tableName)] = id
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const idVal = idType === `number` ? 0 : 0n
|
|
133
|
+
idLogger[snake2Pascal(tableName)] = idVal
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function nodeArr2ClassDict(nodeArr) {
|
|
139
|
+
const classObjArr = filterNodesAndConvert2ClassObjects(nodeArr)
|
|
140
|
+
let classArr = []
|
|
141
|
+
const entityArr = classObjArr.map(classObj => snake2Pascal(classObj.name))
|
|
142
|
+
for (const classObj of classObjArr) {
|
|
143
|
+
fillClassObjColumns(classObj, entityArr)
|
|
144
|
+
delete classObj.node
|
|
145
|
+
classArr.push(classObj)
|
|
146
|
+
}
|
|
147
|
+
// classArr.push(returnEntityClassObj())
|
|
148
|
+
classArr = classArr.map(classObj => [classObj.name, classObj])
|
|
149
|
+
const classDict = Object.fromEntries(classArr)
|
|
150
|
+
return classDict
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function returnEntityClassObj() {
|
|
154
|
+
let { idTypeDefault } = OrmStore.store
|
|
155
|
+
if (idTypeDefault === "UUID") idTypeDefault = "string"
|
|
156
|
+
else if (idTypeDefault === "INT") idTypeDefault = "number"
|
|
157
|
+
else if (idTypeDefault === "BIGINT") idTypeDefault = "bigint"
|
|
158
|
+
else throw new Error(`\n'${idTypeDefault}' is not a valid primary type.`)
|
|
159
|
+
|
|
160
|
+
const idColumn = { name: "id", type: idTypeDefault }
|
|
161
|
+
const updatedAtColumn = { name: "updatedAt", type: "Date" }
|
|
162
|
+
return { name: "entity", abstract: true, columns: [idColumn, updatedAtColumn] }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function filterNodesAndConvert2ClassObjects(nodeArr) {
|
|
166
|
+
const classObjArr = []
|
|
167
|
+
let currentUnfilteredNodes = nodeArr
|
|
168
|
+
let newUnfilteredNodes = []
|
|
169
|
+
let currentValidParents = ['entity']
|
|
170
|
+
let newValidParents = []
|
|
171
|
+
let iteration = 0
|
|
172
|
+
while (currentValidParents.length) {
|
|
173
|
+
for (const node of currentUnfilteredNodes) {
|
|
174
|
+
const parent = nonSnake2Snake(node.heritageClauses[0].types[0].expression.escapedText)
|
|
175
|
+
if (currentValidParents.includes(parent) || newValidParents.includes(parent)) {
|
|
176
|
+
const name = nonSnake2Snake(node.name.escapedText)
|
|
177
|
+
newValidParents.push(name)
|
|
178
|
+
const classObj = { name, parent, node, columns: [], abstract: false }
|
|
179
|
+
if (node.modifiers) {
|
|
180
|
+
for (const modifier of node.modifiers)
|
|
181
|
+
if (modifier.kind == SyntaxKind.AbstractKeyword) classObj.abstract = true
|
|
182
|
+
}
|
|
183
|
+
classObjArr.push(classObj)
|
|
184
|
+
}
|
|
185
|
+
else newUnfilteredNodes.push(node)
|
|
186
|
+
}
|
|
187
|
+
currentValidParents = newValidParents
|
|
188
|
+
newValidParents = []
|
|
189
|
+
currentUnfilteredNodes = newUnfilteredNodes
|
|
190
|
+
newUnfilteredNodes = []
|
|
191
|
+
iteration++
|
|
192
|
+
}
|
|
193
|
+
return classObjArr
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function handleSpecialClassSettingsObj(nodeInitializer) {
|
|
197
|
+
const specialSettingsObj = {}
|
|
198
|
+
const keysNodes = nodeInitializer.properties ?? nodeInitializer.elements
|
|
199
|
+
for (const keyNode of keysNodes) {
|
|
200
|
+
const key = keyNode.name.escapedText
|
|
201
|
+
const value = keyNode.initializer.text ?? handleSpecialClassSettingsObj(keyNode.initializer)
|
|
202
|
+
specialSettingsObj[key] = value
|
|
203
|
+
}
|
|
204
|
+
return specialSettingsObj
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function parseTypeObjContext(/**@type {object | string}*/ typeObjOrString, columnObj, entityNamesArr, nonRelationalTypesArr, className, /**@type {string | undefined}*/ tagName, /**@type {string[] | undefined}*/ typeScriptInvalidTypeArr = undefined) {
|
|
208
|
+
/**@type {string}*/ let typeName = typeof typeObjOrString === `string` ? typeObjOrString.trim() : typeObjOrString.getText().trim()
|
|
209
|
+
if (typeName.endsWith(`[]`)) {
|
|
210
|
+
typeName = typeName.slice(0, -2)
|
|
211
|
+
columnObj.isArray = true
|
|
212
|
+
|
|
213
|
+
if (typeName.startsWith(`(`) && typeName.endsWith(`)`)) {
|
|
214
|
+
typeName = typeName.slice(1, -1)
|
|
215
|
+
const separatedTypes = typeName.split(`|`)
|
|
216
|
+
for (let type of separatedTypes) {
|
|
217
|
+
type = type.trim()
|
|
218
|
+
if (type === `undefined`) continue
|
|
219
|
+
else mapType2ValidMainType(type, columnObj, className, nonRelationalTypesArr, entityNamesArr, tagName, typeScriptInvalidTypeArr)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else mapType2ValidMainType(typeName, columnObj, className, nonRelationalTypesArr, entityNamesArr, tagName, typeScriptInvalidTypeArr)
|
|
223
|
+
}
|
|
224
|
+
else if (typeName === `undefined`) columnObj.nullable = true
|
|
225
|
+
else if (typeName === `Unique`) columnObj.unique = true
|
|
226
|
+
else mapType2ValidMainType(typeName, columnObj, className, nonRelationalTypesArr, entityNamesArr, tagName, typeScriptInvalidTypeArr)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function mapType2ValidMainType(typeName, columnObj, className, nonRelationalTypesArr, entityNamesArr, tagName, typeScriptInvalidTypeArr) {
|
|
230
|
+
if (nonRelationalTypesArr.includes(typeName)) assignColumnType(typeName, columnObj, className)
|
|
231
|
+
else if (entityNamesArr.includes(typeName)) {
|
|
232
|
+
columnObj.relational = true
|
|
233
|
+
assignColumnType(typeName, columnObj, className)
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
if (tagName === `satisfies`) assignColumnType(`object`, columnObj, className)
|
|
237
|
+
else {
|
|
238
|
+
if (typeScriptInvalidTypeArr) {
|
|
239
|
+
if (typeName.includes('object')) assignColumnType('object', columnObj, className)
|
|
240
|
+
typeScriptInvalidTypeArr.push(typeName)
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
console.error(`\nInvalid typing error on property '${columnObj.name}' of class ${snake2Pascal(className)} - ${typeName} is not a valid main type.\n`
|
|
244
|
+
+ `Valid main types are ${array2String([...nonRelationalTypesArr, ...entityNamesArr])}.`)
|
|
245
|
+
process.exit(1)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function assignColumnType(type, columnObj, className) {
|
|
252
|
+
if (columnObj.type) {
|
|
253
|
+
console.error(`\nInvalid typing error on property '${columnObj.name}' of class ${snake2Pascal(className)} - cannot have two main types of ${type} and ${columnObj.type}.`)
|
|
254
|
+
process.exit(1)
|
|
255
|
+
}
|
|
256
|
+
columnObj.type = type
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function fillClassObjColumns(classObj, entityNamesArr) {
|
|
260
|
+
const classNodesArr = classObj.node.members.length ? classObj.node.members : []
|
|
261
|
+
const nonRelationalTypesArr = [`number`, `integer`, `string`, `boolean`, `object`, `Date`, `bigint`]
|
|
262
|
+
|
|
263
|
+
for (const node of classNodesArr) {
|
|
264
|
+
if (node.kind == SyntaxKind.Constructor && node.jsDoc) {
|
|
265
|
+
node.jsDoc[0].tags[0].tagName.escapedText === "abstract" && (classObj.abstract = true)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (node.kind == SyntaxKind.PropertyDeclaration) {
|
|
269
|
+
let propertyName
|
|
270
|
+
/**@type {any}*/ let columnObj
|
|
271
|
+
let isStatic = false
|
|
272
|
+
|
|
273
|
+
if (node.modifiers) {
|
|
274
|
+
for (const modifier of node.modifiers) {
|
|
275
|
+
if (modifier.kind === SyntaxKind.StaticKeyword) isStatic = true
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if ((node.name.expression && node.name.expression.escapedText === "ormClassSettings_") || (node.name.escapedText === "ormClassSettings_")) {
|
|
280
|
+
propertyName = "ormClassSettings_"
|
|
281
|
+
|
|
282
|
+
if (!isStatic) {
|
|
283
|
+
console.error(`\nProperty '${propertyName}' of class ${snake2Pascal(classObj.name)} needs to be a static property.`)
|
|
284
|
+
process.exit(1)
|
|
285
|
+
}
|
|
286
|
+
columnObj = {}
|
|
287
|
+
const value = node.initializer ?? {}
|
|
288
|
+
//TODO special class settings
|
|
289
|
+
if (value.kind == SyntaxKind.ObjectLiteralExpression) columnObj = handleSpecialClassSettingsObj(value)
|
|
290
|
+
classObj.specialSettings = columnObj
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
if (isStatic) continue
|
|
294
|
+
|
|
295
|
+
const typeScriptInvalidTypesArr = []
|
|
296
|
+
propertyName = node.name.escapedText
|
|
297
|
+
columnObj = { name: propertyName }
|
|
298
|
+
|
|
299
|
+
if (node.jsDoc) {
|
|
300
|
+
const tagObj = node.jsDoc[0].tags[0]
|
|
301
|
+
const tagName = tagObj.tagName ? tagObj.tagName.escapedText : undefined
|
|
302
|
+
const typeObj = tagObj.typeExpression.type
|
|
303
|
+
if (typeObj.types) {
|
|
304
|
+
if (typeObj.types.length > 3) {
|
|
305
|
+
console.error(`\nInvalid typing error on property '${propertyName}' of class ${snake2Pascal(classObj.name)} - a property cannot have more than three types (one main type + undefined + Unique).`)
|
|
306
|
+
process.exit(1)
|
|
307
|
+
}
|
|
308
|
+
const typesObj = typeObj.types
|
|
309
|
+
for (const typeObj of typesObj) parseTypeObjContext(typeObj, columnObj, entityNamesArr, nonRelationalTypesArr, classObj.name, tagName)
|
|
310
|
+
}
|
|
311
|
+
else parseTypeObjContext(typeObj, columnObj, entityNamesArr, nonRelationalTypesArr, classObj.name, tagName)
|
|
312
|
+
}
|
|
313
|
+
else if (node.type) {
|
|
314
|
+
const typeObj = node.type
|
|
315
|
+
if (typeObj.types) {
|
|
316
|
+
if (typeObj.types.length > 3) {
|
|
317
|
+
console.error(`\nInvalid typing error on property '${propertyName}' of class ${snake2Pascal(classObj.name)} - a property cannot have more than three types (main type + undefined + Unique).`)
|
|
318
|
+
process.exit(1)
|
|
319
|
+
}
|
|
320
|
+
const typesObj = typeObj.types
|
|
321
|
+
for (const typeObj of typesObj) parseTypeObjContext(typeObj, columnObj, entityNamesArr, nonRelationalTypesArr, classObj.name, undefined, typeScriptInvalidTypesArr)
|
|
322
|
+
}
|
|
323
|
+
else parseTypeObjContext(typeObj, columnObj, entityNamesArr, nonRelationalTypesArr, classObj.name, undefined, typeScriptInvalidTypesArr)
|
|
324
|
+
}
|
|
325
|
+
else if (node.initializer.kind === SyntaxKind.SatisfiesExpression) {
|
|
326
|
+
const typeObj = node.initializer
|
|
327
|
+
const satisfiesText = typeObj.getText()
|
|
328
|
+
const typeText = satisfiesText.split(`satisfies`)[1]
|
|
329
|
+
parseTypeObjContext(typeText, columnObj, entityNamesArr, nonRelationalTypesArr, classObj.name, `satisfies`, typeScriptInvalidTypesArr)
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
console.error(`\nInvalid typing error on property '${propertyName}' of class ${snake2Pascal(classObj.name)} - property has no typing.`)
|
|
333
|
+
process.exit(1)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (node.questionToken) columnObj.nullable = true
|
|
337
|
+
|
|
338
|
+
if (!columnObj.type) noMainTypeOnColumnObjErr(propertyName, classObj, typeScriptInvalidTypesArr, [...nonRelationalTypesArr, ...entityNamesArr])
|
|
339
|
+
else classObj.columns.push(columnObj)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function noMainTypeOnColumnObjErr(propertyName, classObj, typeScriptInvalidTypesArr, validMainTypesArr) {
|
|
346
|
+
console.error(`\nInvalid typing error on property '${propertyName}' of class ${snake2Pascal(classObj.name)} - property has no main type.`)
|
|
347
|
+
if (typeScriptInvalidTypesArr.length) console.error(`\nThe types ${array2String(typeScriptInvalidTypesArr)} may need to be paired with a main type from the following ${array2String(validMainTypesArr)} .`)
|
|
348
|
+
process.exit(1)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function entities2NodeArr(/**@type {{ [key: string]: function }}*/ entityObject) {
|
|
352
|
+
if (!Object.keys(entityObject).length) return {}
|
|
353
|
+
const nodeArr = []
|
|
354
|
+
const nodes = Object.values(entityObject).map(entity => createSourceFile('', entity.toString(), ScriptTarget.Latest, true, ScriptKind.TSX).statements[0])
|
|
355
|
+
for (const node of nodes) {
|
|
356
|
+
//@ts-ignore
|
|
357
|
+
if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && node.heritageClauses)
|
|
358
|
+
nodeArr.push(node)
|
|
359
|
+
}
|
|
360
|
+
return nodeArr
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function addChildrenToClasses(classDict) {
|
|
364
|
+
const childrenArrDict = {}
|
|
365
|
+
for (const classObj of Object.values(classDict)) {
|
|
366
|
+
if (classObj.parent) {
|
|
367
|
+
childrenArrDict[classObj.parent] ??= []
|
|
368
|
+
childrenArrDict[classObj.parent].push(classObj.name)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
for (const [className, childrenArr] of Object.entries(childrenArrDict))
|
|
372
|
+
classDict[className].children = childrenArr
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function recursiveBranch(classObj, branchArr, classDict, previousClassObj = undefined, branchLength = 0) {
|
|
376
|
+
// takes leaves and turns them into branches
|
|
377
|
+
// previousClassObj is actually the original leaf
|
|
378
|
+
branchLength++
|
|
379
|
+
if (previousClassObj) {
|
|
380
|
+
/**@type {any}*/ let lastParent = previousClassObj
|
|
381
|
+
for (let i = 0; i < branchLength - 1; i++) lastParent = lastParent.parent
|
|
382
|
+
|
|
383
|
+
if (lastParent.parent) {
|
|
384
|
+
const newParentObj = classDict[lastParent.parent]
|
|
385
|
+
lastParent.parent = newParentObj
|
|
386
|
+
recursiveBranch(newParentObj, branchArr, classDict, previousClassObj, branchLength)
|
|
387
|
+
}
|
|
388
|
+
else branchArr.push(previousClassObj)
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
if (classObj.parent) {
|
|
392
|
+
const parent = classDict[classObj.parent]
|
|
393
|
+
classObj.parent = parent
|
|
394
|
+
recursiveBranch(parent, branchArr, classDict, classObj, branchLength)
|
|
395
|
+
}
|
|
396
|
+
else branchArr.push(classObj)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function createBranches(classDict) {
|
|
401
|
+
const branches = []
|
|
402
|
+
|
|
403
|
+
for (const classObj of Object.values(classDict)) {
|
|
404
|
+
if (!classObj.children) {
|
|
405
|
+
if (classObj.abstract) throw new Error(`\nInvalid class declaration - '${snake2Pascal(classObj.name)}' cannot be abstract since abstract classes must have children.`)
|
|
406
|
+
recursiveBranch(classObj, branches, classDict)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return branches
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function inheritColumns(inheritingClass) {
|
|
413
|
+
let iterativeClassObj = inheritingClass
|
|
414
|
+
let parent
|
|
415
|
+
|
|
416
|
+
while (iterativeClassObj.parent) {
|
|
417
|
+
parent = iterativeClassObj.parent
|
|
418
|
+
if (!parent.abstract) {
|
|
419
|
+
const { columns } = returnEntityClassObj()
|
|
420
|
+
const [id, updatedAt] = columns
|
|
421
|
+
inheritingClass.columns.unshift(id)
|
|
422
|
+
return parent
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
inheritingClass.columns.push(...parent.columns)
|
|
426
|
+
if (iterativeClassObj.specialSettings && iterativeClassObj.specialSettings.idType) {
|
|
427
|
+
const index = inheritingClass.columns.find(columnObj => columnObj.name === 'id')
|
|
428
|
+
inheritingClass.columns[index] = idType2IdColumnObj(iterativeClassObj.specialSettings.idType)
|
|
429
|
+
}
|
|
430
|
+
iterativeClassObj = parent
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export function linkClassMap(classMapsObj, dependenciesObj, referencesObj) {
|
|
435
|
+
|
|
436
|
+
for (const classMap of Object.values(classMapsObj)) {
|
|
437
|
+
const className = classMap.className
|
|
438
|
+
|
|
439
|
+
if (classMap.parent) {
|
|
440
|
+
const parentName = classMap.parent
|
|
441
|
+
if (parentName === `Entity`) delete classMap.parent
|
|
442
|
+
else classMap.parent = classMapsObj[parentName]
|
|
443
|
+
}
|
|
444
|
+
if (dependenciesObj[className]) classMap[dependenciesSymb] = dependenciesObj[className]
|
|
445
|
+
if (referencesObj[className]) classMap[referencesSymb] = referencesObj[className]
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
for (const classMap of Object.values(classMapsObj)) {
|
|
449
|
+
if (!Object.keys(classMap.junctions).length) delete classMap.junctions
|
|
450
|
+
else {
|
|
451
|
+
for (const [propertyName, junctionObj] of Object.entries(classMap.junctions)) {
|
|
452
|
+
const className = junctionObj.className
|
|
453
|
+
const { isArray, optional } = junctionObj
|
|
454
|
+
classMap.junctions[propertyName] = { ...classMapsObj[className] }
|
|
455
|
+
if (isArray) classMap.junctions[propertyName].isArray = true
|
|
456
|
+
if (optional) classMap.junctions[propertyName].optional = true
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function createClassMap(tableObj) {
|
|
463
|
+
const tableNames = Object.keys(tableObj)
|
|
464
|
+
const ormMapsObj = {}
|
|
465
|
+
const dependenciesObj = {}
|
|
466
|
+
const referencesObj = {}
|
|
467
|
+
|
|
468
|
+
for (const name of tableNames) {
|
|
469
|
+
const table = tableObj[name]
|
|
470
|
+
const junctionMap = {}
|
|
471
|
+
const map = {
|
|
472
|
+
columns: {},
|
|
473
|
+
junctions: junctionMap,
|
|
474
|
+
className: snake2Pascal(table.name)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
ormMapsObj[map.className] = map
|
|
478
|
+
|
|
479
|
+
if (table.parent) map.parent = snake2Pascal(table.parent.name)
|
|
480
|
+
|
|
481
|
+
for (const columnObj of Object.values(table.columns)) {
|
|
482
|
+
const columnName = columnObj.name
|
|
483
|
+
if (columnName === "id") {
|
|
484
|
+
map.columns.id = { type: columnObj.type }
|
|
485
|
+
continue
|
|
486
|
+
}
|
|
487
|
+
const { type, isArray, nullable } = columnObj
|
|
488
|
+
|
|
489
|
+
if (columnObj.relational) {
|
|
490
|
+
junctionMap[columnName] = {
|
|
491
|
+
className: type,
|
|
492
|
+
isArray: isArray,
|
|
493
|
+
optional: nullable
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (!isArray && !nullable) {
|
|
497
|
+
const targetTable = dependenciesObj[type] ??= {}
|
|
498
|
+
const dependantKeyArr = targetTable[map.className] ??= []
|
|
499
|
+
dependantKeyArr.push(columnName)
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
const targetTable = referencesObj[type] ??= {}
|
|
503
|
+
const dependantKeyArr = targetTable[map.className] ??= []
|
|
504
|
+
dependantKeyArr.push(columnName)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
/**@type {any}*/ const { name, nullable, ...mapColumnObj } = columnObj
|
|
509
|
+
if (nullable) mapColumnObj.optional = true
|
|
510
|
+
if (mapColumnObj.type === `integer`) mapColumnObj.type = `number`
|
|
511
|
+
map.columns[columnName] = mapColumnObj
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
linkClassMap(ormMapsObj, dependenciesObj, referencesObj)
|
|
516
|
+
OrmStore.store.classWikiDict = ormMapsObj
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export function createTableObject(branchArr) {
|
|
520
|
+
let tableObj = {}
|
|
521
|
+
for (const branch of branchArr) {
|
|
522
|
+
if (branch.parent) {
|
|
523
|
+
const newBranch = inheritColumns(branch)
|
|
524
|
+
if (newBranch) branchArr.push(newBranch)
|
|
525
|
+
tableObj[branch.name] = branch
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return tableObj
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function createJunctionColumnContext(tablesDict) {
|
|
532
|
+
for (const tableObj of Object.values(tablesDict)) {
|
|
533
|
+
const columns = tableObj.columns
|
|
534
|
+
for (const columnObj of columns) {
|
|
535
|
+
const isArray = columnObj.isArray
|
|
536
|
+
if (columnObj.relational) {
|
|
537
|
+
const snakedColumnType = nonSnake2Snake(columnObj.type)
|
|
538
|
+
const joinedTable = tablesDict[snakedColumnType]
|
|
539
|
+
columnObj.thisTableIdUnique = !isArray
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
columnArr2ColumnDict(tablesDict)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
function formatRelationalColumnObj(tableObj, columnObj, tablesDict, sqlClient) {
|
|
548
|
+
const newJunctionTable = {
|
|
549
|
+
name: `${tableObj.name}___${nonSnake2Snake(columnObj.name)}_jt`,
|
|
550
|
+
columns: {}
|
|
551
|
+
}
|
|
552
|
+
const joiningIdTypeInDb = js2SqlTyping(sqlClient, tableObj.columns.id.type, true)
|
|
553
|
+
let newJunctionTableColumn = {
|
|
554
|
+
type: joiningIdTypeInDb,
|
|
555
|
+
unique: columnObj.thisTableIdUnique,
|
|
556
|
+
refTable: tableObj.name
|
|
557
|
+
}
|
|
558
|
+
newJunctionTable.columns.joining = newJunctionTableColumn
|
|
559
|
+
|
|
560
|
+
const joinedTableName = columnObj.type
|
|
561
|
+
const joinedTable = tablesDict[joinedTableName] || tablesDict[nonSnake2Snake(joinedTableName)]
|
|
562
|
+
const joinedIdTypeInDb = js2SqlTyping(sqlClient, joinedTable.columns.id.type, true)
|
|
563
|
+
|
|
564
|
+
newJunctionTableColumn = {
|
|
565
|
+
type: joinedIdTypeInDb,
|
|
566
|
+
unique: false,
|
|
567
|
+
refTable: nonSnake2Snake(joinedTableName)
|
|
568
|
+
}
|
|
569
|
+
newJunctionTable.columns.joined = newJunctionTableColumn
|
|
570
|
+
return newJunctionTable
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
export function formatForCreation(tablesDict) {
|
|
575
|
+
/**@type {TABLE[]}*/ const formattedTables = []
|
|
576
|
+
const alterTableArr = []
|
|
577
|
+
const sqlClient = OrmStore.store.sqlClient
|
|
578
|
+
|
|
579
|
+
for (const tableObj of Object.values(tablesDict)) {
|
|
580
|
+
if (tableObj.alreadyExists) {
|
|
581
|
+
if (!tableObj.newColumns) continue
|
|
582
|
+
alterTableArr.push(tableObj)
|
|
583
|
+
continue
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**@type {TABLE}*/ const formattedTable = {
|
|
587
|
+
name: tableObj.name,
|
|
588
|
+
columns: {},
|
|
589
|
+
junctions: []
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (tableObj.parent && tableObj.parent.name !== 'entity') {
|
|
593
|
+
let currentParent = tableObj.parent
|
|
594
|
+
while (currentParent.abstract && currentParent.parent) currentParent = currentParent.parent
|
|
595
|
+
if (currentParent.name !== 'entity') formattedTable.parent = tableObj.parent.name
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
for (const columnObj of Object.values(tableObj.columns)) {
|
|
599
|
+
if (columnObj.relational) formattedTable.junctions?.push(formatRelationalColumnObj(tableObj, columnObj, tablesDict, sqlClient))
|
|
600
|
+
else {
|
|
601
|
+
const { name, ...rest } = columnObj
|
|
602
|
+
formattedTable.columns[`${name}`] = rest
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
formattedTables.push(formattedTable)
|
|
606
|
+
}
|
|
607
|
+
return [formattedTables, alterTableArr]
|
|
608
|
+
}
|
|
609
|
+
export function produceTableCreationQuery(/**@type {TABLE}*/ table, /**@type {boolean}*/ isJunction = false) {
|
|
610
|
+
let query = `CREATE TABLE ${table.name} (\n`
|
|
611
|
+
|
|
612
|
+
if (!isJunction) {
|
|
613
|
+
const primaryKeyType = table.columns.id.type
|
|
614
|
+
query += ` id ${primaryKeyType} PRIMARY KEY,\n`
|
|
615
|
+
|
|
616
|
+
const columnEntries = Object.entries(table.columns).filter(column => column[0] !== "id")
|
|
617
|
+
query += columnEntries2QueryStr(columnEntries)
|
|
618
|
+
|
|
619
|
+
if (table.parent) query += `FOREIGN KEY (id) REFERENCES ${table.parent}(id) ON DELETE CASCADE \n);`
|
|
620
|
+
else query = query.slice(0, -3) + `);`
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
const columns = Object.entries(table.columns)
|
|
624
|
+
|
|
625
|
+
const baseColumn = {
|
|
626
|
+
type: columns[0][1].type,
|
|
627
|
+
unique: columns[0][1].unique,
|
|
628
|
+
ref: columns[0][1][`refTable`]
|
|
629
|
+
}
|
|
630
|
+
const referencedColumn = {
|
|
631
|
+
type: columns[1][1].type,
|
|
632
|
+
unique: columns[1][1].unique,
|
|
633
|
+
ref: columns[1][1][`refTable`]
|
|
634
|
+
}
|
|
635
|
+
query += ` joining_id ${baseColumn.type}`
|
|
636
|
+
|
|
637
|
+
if (baseColumn.unique) {
|
|
638
|
+
query += ` PRIMARY KEY`
|
|
639
|
+
query += ` REFERENCES ${baseColumn.ref}(id) ON DELETE CASCADE, \n`
|
|
640
|
+
query += `joined_id ${referencedColumn.type} NOT NULL REFERENCES ${referencedColumn.ref}(id) ON DELETE CASCADE`
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
query += ` NOT NULL REFERENCES ${baseColumn.ref}(id) ON DELETE CASCADE, \n`
|
|
644
|
+
query += `joined_id ${referencedColumn.type} NOT NULL REFERENCES ${referencedColumn.ref}(id) ON DELETE CASCADE, \n`
|
|
645
|
+
query += `PRIMARY KEY (joining_id, joined_id)`
|
|
646
|
+
}
|
|
647
|
+
query += `\n);`
|
|
648
|
+
}
|
|
649
|
+
return query
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
export function columnEntries2QueryStr(columnEntries) {
|
|
653
|
+
let queryStr = ``
|
|
654
|
+
const client = OrmStore.store.sqlClient
|
|
655
|
+
|
|
656
|
+
if (client === "postgresql") {
|
|
657
|
+
for (const column of columnEntries) {
|
|
658
|
+
const columnName = nonSnake2Snake(column[0])
|
|
659
|
+
|
|
660
|
+
const { nullable, unique, type, defaultValue } = column[1]
|
|
661
|
+
queryStr += ` ${columnName} ${type}`
|
|
662
|
+
queryStr += nullable ? `` : ` NOT NULL`
|
|
663
|
+
queryStr += unique ? ` UNIQUE` : ``
|
|
664
|
+
|
|
665
|
+
//TODO BELOW, DEFAULT VALUE IS PROBABLY REDUNDANT? even though it might not be...lets you
|
|
666
|
+
// set values without using args with default values in constructor, which can limit freedom because of args order
|
|
667
|
+
// if we keep this, need to account for this during row creation save
|
|
668
|
+
// query +=
|
|
669
|
+
// defaultValue !== undefined
|
|
670
|
+
// ? ` DEFAULT ${typeof defaultValue === `object`
|
|
671
|
+
// ? `'${JSON.stringify(defaultValue)}'`
|
|
672
|
+
// : defaultValue
|
|
673
|
+
// }`
|
|
674
|
+
// : ``
|
|
675
|
+
|
|
676
|
+
queryStr += `, \n`
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
for (const column of columnEntries) {
|
|
681
|
+
const columnName = nonSnake2Snake(column[0])
|
|
682
|
+
const { nullable, unique, type, defaultValue } = column[1]
|
|
683
|
+
|
|
684
|
+
if (type.endsWith('[]')) queryStr += ` ${columnName} TEXT`
|
|
685
|
+
else queryStr += ` ${columnName} ${type}`
|
|
686
|
+
|
|
687
|
+
queryStr += nullable ? `` : ` NOT NULL`
|
|
688
|
+
queryStr += unique ? ` UNIQUE` : ``
|
|
689
|
+
queryStr += `, \n`
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return queryStr
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
export function sqlTypeTableObj(tableObj, sqlClient) {
|
|
696
|
+
//if (tableObj.parent === `entity`) delete tableObj.parent
|
|
697
|
+
const columnObjectsArr = Object.values(tableObj.columns)
|
|
698
|
+
|
|
699
|
+
if (sqlClient === `postgresql`) {
|
|
700
|
+
for (const columnObj of columnObjectsArr) {
|
|
701
|
+
if (columnObj.isArray) {
|
|
702
|
+
if (columnObj.type === `object`) columnObj.type = `JSONB`
|
|
703
|
+
else columnObj.type = js2SqlTyping(sqlClient, columnObj.type) + `[]`
|
|
704
|
+
}
|
|
705
|
+
else columnObj.type = js2SqlTyping(sqlClient, columnObj.type)
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
for (const columnObj of columnObjectsArr) {
|
|
710
|
+
if (columnObj.isArray) columnObj.type = `TEXT`
|
|
711
|
+
else columnObj.type = js2SqlTyping(sqlClient, columnObj.type)
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
export function generateTableCreationQueryObject(formattedTables) {
|
|
717
|
+
const sqlClient = OrmStore.store.sqlClient
|
|
718
|
+
for (const tableObj of Object.values(formattedTables)) {
|
|
719
|
+
sqlTypeTableObj(tableObj, sqlClient)
|
|
720
|
+
if (floatColumnTypes.includes(tableObj.columns.id.type)) tableObj.columns.id.type = 'INTEGER'
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const rootTables = formattedTables.filter(table => !table.parent)
|
|
724
|
+
const childrenTables = formattedTables.filter(table => table.parent)
|
|
725
|
+
|
|
726
|
+
const tableQueryObject = {}
|
|
727
|
+
const junctionQueriesArr = []
|
|
728
|
+
|
|
729
|
+
for (const rootTable of rootTables) produceQueryObj(rootTable, tableQueryObject, childrenTables, junctionQueriesArr)
|
|
730
|
+
|
|
731
|
+
return { tableQueryObject, junctionQueriesArr }
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
export async function sendQueryFromQueryObj(queryObj, queryFunc) {
|
|
735
|
+
const { query, children } = queryObj
|
|
736
|
+
await queryFunc(query)
|
|
737
|
+
|
|
738
|
+
if (children) {
|
|
739
|
+
const childrenQueryObjects = Object.values(children)
|
|
740
|
+
for (const queryObject of childrenQueryObjects) await sendQueryFromQueryObj(queryObject, queryFunc)
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export async function sendTableCreationQueries(tableCreationObj) {
|
|
745
|
+
const { dbConnection, sqlClient } = OrmStore.store
|
|
746
|
+
let queryFunc
|
|
747
|
+
if (sqlClient === "postgresql") queryFunc = dbConnection.query.bind(dbConnection)
|
|
748
|
+
else queryFunc = (query) => dbConnection.exec(query)
|
|
749
|
+
|
|
750
|
+
const queryFuncWithTryCatch = async (query) => {
|
|
751
|
+
try {
|
|
752
|
+
await queryFunc(query)
|
|
753
|
+
}
|
|
754
|
+
catch (e) {
|
|
755
|
+
coloredBackgroundConsoleLog(`Error while creating tables. ${e}\n`, `failure`)
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const tableQueryObjectsEntries = Object.entries(tableCreationObj.tableQueryObject)
|
|
760
|
+
coloredBackgroundConsoleLog(`Updating database schema...\n`, `success`)
|
|
761
|
+
for (const [tableName, queryObj] of tableQueryObjectsEntries) await sendQueryFromQueryObj(queryObj, queryFuncWithTryCatch)
|
|
762
|
+
for (const query of tableCreationObj.junctionQueriesArr) await queryFuncWithTryCatch(query)
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
export function produceQueryObj(rootTable, tableQueryObject, childrenTables, junctionQueriesArr) {
|
|
767
|
+
const query = produceTableCreationQuery(rootTable)
|
|
768
|
+
|
|
769
|
+
const junctions = rootTable.junctions
|
|
770
|
+
for (const junctionTable of junctions) junctionQueriesArr.push(produceTableCreationQuery(junctionTable, true))
|
|
771
|
+
|
|
772
|
+
const rootTableQueryObj = tableQueryObject[rootTable.name] = { query }
|
|
773
|
+
const children = childrenTables.filter(table => table.parent === rootTable.name)
|
|
774
|
+
childrenTables = childrenTables.filter(table => table.parent !== rootTable.name)
|
|
775
|
+
if (children.length) {
|
|
776
|
+
const tableQueryObject = rootTableQueryObj[`children`] = {}
|
|
777
|
+
for (const childTable of children) produceQueryObj(childTable, tableQueryObject, childrenTables, junctionQueriesArr)
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
export function handleSpecialSettingId(tablesDict) {
|
|
783
|
+
for (const classObj of Object.values(tablesDict)) {
|
|
784
|
+
if (classObj.parent === 'entity' && classObj.specialSettings && classObj.specialSettings.idType) {
|
|
785
|
+
const settingsObj = classObj.specialSettings
|
|
786
|
+
const specialIdType = settingsObj.idType
|
|
787
|
+
let idObj = idType2IdColumnObj(specialIdType)
|
|
788
|
+
classObj.columns.id = idObj
|
|
789
|
+
// if (classObj.children) changeIdColumnOnChildren(idObj, tablesDict, classObj.children)
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// export function changeIdColumnOnChildren(idObj, classesObj, childrenNameArr) {
|
|
795
|
+
// const nextChildrenNameArr = []
|
|
796
|
+
// for (const childName of childrenNameArr) {
|
|
797
|
+
// const childObj = classesObj[childName]
|
|
798
|
+
// childObj.columns.id = idObj
|
|
799
|
+
// if (childObj.children) nextChildrenNameArr.push(...childObj.children)
|
|
800
|
+
// }
|
|
801
|
+
// if (nextChildrenNameArr.length) changeIdColumnOnChildren(idObj, classesObj, nextChildrenNameArr)
|
|
802
|
+
// }
|
|
803
|
+
|
|
804
|
+
export function idType2IdColumnObj(idType) {
|
|
805
|
+
let idColumnObj
|
|
806
|
+
if (idType === "UUID") {
|
|
807
|
+
idColumnObj = { name: "id", type: "string" }
|
|
808
|
+
}
|
|
809
|
+
else if (idType === "INT") {
|
|
810
|
+
idColumnObj = { name: "id", type: "number" }
|
|
811
|
+
}
|
|
812
|
+
else if (idType === "BIGINT") {
|
|
813
|
+
idColumnObj = { name: "id", type: "bigint" }
|
|
814
|
+
}
|
|
815
|
+
else throw new Error(`\n'${idType}' is not a valid primary key type.`)
|
|
816
|
+
return idColumnObj
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
export function columnArr2ColumnDict(tablesObj) {
|
|
820
|
+
for (const tableObj of Object.values(tablesObj)) {
|
|
821
|
+
const columnsDictObj = {}
|
|
822
|
+
for (const columnObj of tableObj.columns) {
|
|
823
|
+
columnsDictObj[columnObj.name] = columnObj
|
|
824
|
+
}
|
|
825
|
+
tableObj.columns = columnsDictObj
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// export function filterTableArray(tableArray) {
|
|
830
|
+
// let filteredArr = []
|
|
831
|
+
// for (let table of tableArray) {
|
|
832
|
+
// let ancestorOfEntity = false
|
|
833
|
+
// let currentTable = table
|
|
834
|
+
// while (currentTable.parent) {
|
|
835
|
+
// if (currentTable.parent.name === 'entity') ancestorOfEntity = true
|
|
836
|
+
// currentTable = currentTable.parent
|
|
837
|
+
// }
|
|
838
|
+
// if (ancestorOfEntity) filteredArr.push(table)
|
|
839
|
+
// }
|
|
840
|
+
// return filteredArr
|
|
841
|
+
// }
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
export async function alterTables(tables2alterArr) {
|
|
845
|
+
if (!tables2alterArr.length) return
|
|
846
|
+
|
|
847
|
+
const { sqlClient, dbConnection, classWikiDict } = OrmStore.store
|
|
848
|
+
const queryFunc = sqlClient === `postgresql`
|
|
849
|
+
? async (alterStatements, junctionStatements) => {
|
|
850
|
+
try {
|
|
851
|
+
if (alterStatements.length) await dbConnection.query(alterStatements.join(`, `))
|
|
852
|
+
if (junctionStatements.length) {
|
|
853
|
+
await dbConnection.query('BEGIN;')
|
|
854
|
+
for (const statement of junctionStatements) await dbConnection.query(statement)
|
|
855
|
+
await dbConnection.query('COMMIT;')
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
catch (e) {
|
|
859
|
+
if (junctionStatements.length) await dbConnection.query('ROLLBACK;')
|
|
860
|
+
coloredBackgroundConsoleLog(e, "failure")
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
: (alterStatements, junctionStatements) => {
|
|
864
|
+
const statements = [...alterStatements, ...junctionStatements]
|
|
865
|
+
try {
|
|
866
|
+
dbConnection.exec('BEGIN;')
|
|
867
|
+
for (const statement of statements) {
|
|
868
|
+
dbConnection.exec(statement)
|
|
869
|
+
}
|
|
870
|
+
dbConnection.exec('COMMIT;')
|
|
871
|
+
}
|
|
872
|
+
catch (e) {
|
|
873
|
+
dbConnection.exec('ROLLBACK;')
|
|
874
|
+
coloredBackgroundConsoleLog(e, "failure")
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
let alterTableStr
|
|
879
|
+
const alterStatements = []
|
|
880
|
+
const newJunctionStatements = []
|
|
881
|
+
|
|
882
|
+
for (const tableObj of tables2alterArr) {
|
|
883
|
+
alterTableStr = `ALTER TABLE ${tableObj.name} `
|
|
884
|
+
const idColumn = tableObj.columns.id
|
|
885
|
+
tableObj.columns = structuredClone(tableObj.newColumns)
|
|
886
|
+
sqlTypeTableObj(tableObj, sqlClient)
|
|
887
|
+
|
|
888
|
+
tableObj.columns.id = idColumn
|
|
889
|
+
|
|
890
|
+
for (const [columnName, columnObj] of Object.entries(tableObj.newColumns)) {
|
|
891
|
+
const { type, nullable, isArray, relational } = columnObj
|
|
892
|
+
let statement
|
|
893
|
+
if (relational) {
|
|
894
|
+
const junctionTableObj = formatRelationalColumnObj(tableObj, columnObj, classWikiDict, sqlClient)
|
|
895
|
+
statement = produceTableCreationQuery(junctionTableObj, true)
|
|
896
|
+
newJunctionStatements.push(statement)
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
statement = `ADD COLUMN ${nonSnake2Snake(columnName)} ${tableObj.columns[columnName].type} `
|
|
900
|
+
if (!nullable) statement += `NOT NULL DEFAULT ${type2DefaultValue(type, isArray, sqlClient)}`
|
|
901
|
+
alterStatements.push(alterTableStr + statement)
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
await queryFunc(alterStatements, newJunctionStatements)
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function type2DefaultValue(type, isArray, sqlClient) {
|
|
910
|
+
//need to get correct types from tableObj.newColumns
|
|
911
|
+
if (sqlClient === `postgresql`) {
|
|
912
|
+
if (isArray) {
|
|
913
|
+
const typeCast = js2SqlTyping(sqlClient, type)
|
|
914
|
+
return `ARRAY[]::${typeCast}[]`
|
|
915
|
+
}
|
|
916
|
+
else if (type === `string`) return `''`
|
|
917
|
+
else if (type === `boolean`) return false
|
|
918
|
+
else if (type === `number` || type === `integer`) return 0
|
|
919
|
+
else if (type === `bigint`) return `0`
|
|
920
|
+
else if (type === `object`) return `'{}'::JSONB`
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (isArray) return `'[]'`
|
|
924
|
+
else if (type === `string`) return `''`
|
|
925
|
+
else if (type === `boolean`) return 0
|
|
926
|
+
else if (type === `number` || type === `integer`) return 0
|
|
927
|
+
else if (type === `bigint`) return `0`
|
|
928
|
+
else if (type === `object`) return `'{}'`
|
|
929
|
+
}
|