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,228 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import { ChangeLogger } from "../changeLogger/changeLogger.js"
|
|
4
|
+
import { dependenciesSymb, referencesSymb } from "../misc/constants.js"
|
|
5
|
+
import { nonSnake2Snake } from "../misc/miscFunctions.js"
|
|
6
|
+
import { OrmStore } from "../misc/ormStore.js"
|
|
7
|
+
import { insertProxyIntoEntityMap, proxifyEntityInstanceObj } from "../proxies/instanceProxy.js"
|
|
8
|
+
import { throwDeletionErr, throwImproperDecouplingErr, validateDependentDataDecoupling } from "./delete/delete.js"
|
|
9
|
+
import { insertDependentsData, internalFind } from "./delete/getDependents.js"
|
|
10
|
+
import { aliasedFindWiki2QueryRes, parseFindWiki, destructureAndValidateArg } from "./find/find.js"
|
|
11
|
+
import { deproxifyScopeProxy, classWiki2ScopeProxy } from "./find/scopeProxies.js"
|
|
12
|
+
import { postgresCreateProxyArray } from "./find/sqlClients/postgresFuncs.js"
|
|
13
|
+
import { sqliteCreateProxyArray } from "./find/sqlClients/sqliteFuncs.js"
|
|
14
|
+
import { mergeRelationalWhereScope } from "./find/where/relationalWhere.js"
|
|
15
|
+
import { mergeWhereScope } from "./find/where/where.js"
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @template T
|
|
19
|
+
* @typedef {import("../misc/types").FindObj<T>} FindObj
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export class Entity {
|
|
23
|
+
//@ts-ignore
|
|
24
|
+
/**@type {string | number}*/ id
|
|
25
|
+
/**@type {Date}*/ updatedAt
|
|
26
|
+
|
|
27
|
+
/** @abstract */
|
|
28
|
+
constructor() {
|
|
29
|
+
const className = this.constructor.name
|
|
30
|
+
|
|
31
|
+
const { classWikiDict, idLogger, entityMapsObj } = OrmStore.store
|
|
32
|
+
if (!classWikiDict) throw new Error("ORM is not initialized. Please call the appropriate ORM boot method before use.")
|
|
33
|
+
else if (!classWikiDict[className]) throw new Error(`Cannot create an instance of class '${className}' since it is an abstract class.`)
|
|
34
|
+
|
|
35
|
+
let idVal
|
|
36
|
+
if (typeof idLogger[className] === `function`) idVal = idLogger[className]()
|
|
37
|
+
else idVal = ++idLogger[className]
|
|
38
|
+
|
|
39
|
+
Object.defineProperty(this, 'id', {
|
|
40
|
+
value: idVal,
|
|
41
|
+
writable: false,
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: false
|
|
44
|
+
})
|
|
45
|
+
this.updatedAt = new Date()
|
|
46
|
+
|
|
47
|
+
const proxy = proxifyEntityInstanceObj(this)
|
|
48
|
+
const entityMap = entityMapsObj[className] ??= new Map()
|
|
49
|
+
insertProxyIntoEntityMap(proxy, entityMap)
|
|
50
|
+
ChangeLogger.flushChanges()
|
|
51
|
+
return proxy
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Finds instances in the database that match the given argument.
|
|
56
|
+
* Relations do not get filtered by any where condition, only the root instances get filtered.
|
|
57
|
+
* (RootClass.find(arg)) => only RootClass instances matching the arg's where conditions get returned.
|
|
58
|
+
*
|
|
59
|
+
* @template T
|
|
60
|
+
* @this {{ new(...args: any[]): T }}
|
|
61
|
+
* @param {FindObj<T>} findObj
|
|
62
|
+
* @returns {Promise<T[]>}
|
|
63
|
+
*/
|
|
64
|
+
static async find(findObj) {
|
|
65
|
+
const { dbConnection, sqlClient, entities, classWikiDict } = OrmStore.store
|
|
66
|
+
if (!dbConnection) throw new Error("ORM is not initialized. Please call the appropriate ORM boot method before use.")
|
|
67
|
+
if (ChangeLogger.scheduledFlush) await ChangeLogger.save()
|
|
68
|
+
|
|
69
|
+
let classWiki = classWikiDict[this.name]
|
|
70
|
+
if (!classWiki) throw new Error(`The class '${this.name}' has not been included in the ORM boot method.`)
|
|
71
|
+
const [relationsArg, whereArg, relationalWhereArg] = destructureAndValidateArg(findObj)
|
|
72
|
+
let findWiki
|
|
73
|
+
const baseProxyMap = classWiki2ScopeProxy(classWiki)
|
|
74
|
+
if (whereArg) mergeWhereScope(baseProxyMap, whereArg)
|
|
75
|
+
if (relationalWhereArg) findWiki = mergeRelationalWhereScope(baseProxyMap, relationalWhereArg)
|
|
76
|
+
findWiki = deproxifyScopeProxy(baseProxyMap)
|
|
77
|
+
|
|
78
|
+
const [aliasedFindMap, joinStatements, whereObj] = parseFindWiki(findWiki)
|
|
79
|
+
const [queryResult, relationsScopeObj] = await aliasedFindWiki2QueryRes(aliasedFindMap, joinStatements, whereObj, relationsArg, classWiki, dbConnection)
|
|
80
|
+
const instanceArr = sqlClient === "postgresql"
|
|
81
|
+
? postgresCreateProxyArray(queryResult, relationsScopeObj, entities, relationsArg)
|
|
82
|
+
: sqliteCreateProxyArray(queryResult, relationsScopeObj, entities, relationsArg)
|
|
83
|
+
|
|
84
|
+
return instanceArr
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// async save() {
|
|
88
|
+
// const { dbConnection, classWikiDict } = OrmStore.store
|
|
89
|
+
// if (!dbConnection) throw new Error("ORM is not initialized. Please call the appropriate ORM boot method before use.")
|
|
90
|
+
// const className = this.constructor.name
|
|
91
|
+
// let classWiki = classWikiDict[className]
|
|
92
|
+
|
|
93
|
+
// const branchesCteArray = []
|
|
94
|
+
// const createdInstacesLogger = []
|
|
95
|
+
|
|
96
|
+
// newRootInsertionCte(this, classWiki, branchesCteArray, createdInstacesLogger)
|
|
97
|
+
// let queryObj = parseInsertionQueryObj(branchesCteArray)
|
|
98
|
+
|
|
99
|
+
// try {
|
|
100
|
+
// //@ts-ignore
|
|
101
|
+
// await pool.query(queryObj.queryStr, queryObj.values)
|
|
102
|
+
// }
|
|
103
|
+
// catch (e) {
|
|
104
|
+
// console.warn(e)
|
|
105
|
+
// }
|
|
106
|
+
// }
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Hard deletes the instance from the database. May require a pre-deletion step - the 'getDependents' method.
|
|
110
|
+
*/
|
|
111
|
+
async delete() {
|
|
112
|
+
const { dbConnection, classWikiDict, dependentsMapsObj, dbChangesObj } = OrmStore.store
|
|
113
|
+
if (!dbConnection) throw new Error("ORM is not initialized. Please call the appropriate ORM boot method before use.")
|
|
114
|
+
|
|
115
|
+
const id4Deletion = this.id
|
|
116
|
+
const className = this.constructor.name
|
|
117
|
+
let classWiki = classWikiDict[className]
|
|
118
|
+
const dependencyContext = classWiki[dependenciesSymb]
|
|
119
|
+
if (dependencyContext) {
|
|
120
|
+
let dependentsData
|
|
121
|
+
if (!dependentsMapsObj[className]) throwDeletionErr(className, id4Deletion)
|
|
122
|
+
else dependentsData = dependentsMapsObj[className].get(id4Deletion)
|
|
123
|
+
|
|
124
|
+
dependentsData = dependentsData.deref()
|
|
125
|
+
const isValid = validateDependentDataDecoupling(dependentsData, id4Deletion)
|
|
126
|
+
if (!isValid) throwImproperDecouplingErr(className, id4Deletion)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
//@ts-ignore
|
|
130
|
+
const emitter = this.eEmitter_
|
|
131
|
+
emitter.dispatchEvent(
|
|
132
|
+
new CustomEvent("delete", {
|
|
133
|
+
detail: {
|
|
134
|
+
id: id4Deletion
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
let targetTableName
|
|
140
|
+
|
|
141
|
+
if (classWiki.parent) {
|
|
142
|
+
let currentWiki = classWiki
|
|
143
|
+
while (currentWiki.parent) {
|
|
144
|
+
targetTableName = currentWiki.parent.className
|
|
145
|
+
currentWiki = currentWiki.parent
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else targetTableName = className
|
|
149
|
+
|
|
150
|
+
dbChangesObj.deletedInstancesArr ??= []
|
|
151
|
+
dbChangesObj.deletedInstancesArr.push([nonSnake2Snake(targetTableName), id4Deletion])
|
|
152
|
+
if (dbChangesObj[className] && dbChangesObj[className][id4Deletion]) delete dbChangesObj[className][id4Deletion]
|
|
153
|
+
|
|
154
|
+
//@ts-ignore
|
|
155
|
+
this.source_._isDeleted_ = true
|
|
156
|
+
ChangeLogger.flushChanges()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A pre-deletion step that is required in certain cases.
|
|
161
|
+
* Finds all instances that have a one-to-one relationship with the calling instance
|
|
162
|
+
* where the related property cannot be set to `undefined`.
|
|
163
|
+
* These relationships must be reassigned before the calling instance
|
|
164
|
+
* can be safely deleted.
|
|
165
|
+
*/
|
|
166
|
+
async getDependents() {
|
|
167
|
+
if (!this.id) return undefined
|
|
168
|
+
const returnedObj = {}
|
|
169
|
+
const className = this.constructor.name
|
|
170
|
+
const dependedOnId = this.id
|
|
171
|
+
const { classWikiDict, dependentsMapsObj } = OrmStore.store
|
|
172
|
+
|
|
173
|
+
const classWiki = classWikiDict[className]
|
|
174
|
+
const dependencyContext = classWiki[dependenciesSymb]
|
|
175
|
+
if (!dependencyContext) return undefined
|
|
176
|
+
|
|
177
|
+
for (const [className, relationalProps] of Object.entries(dependencyContext)) {
|
|
178
|
+
const dependentMap = classWikiDict[className]
|
|
179
|
+
returnedObj[className] = [await internalFind(dependentMap, relationalProps, dependedOnId), relationalProps]
|
|
180
|
+
}
|
|
181
|
+
insertDependentsData(className, dependedOnId, returnedObj, dependentsMapsObj)
|
|
182
|
+
return returnedObj
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Finds all instances that have a relation with the calling instance,
|
|
187
|
+
* This method is a superset of the getDependents method, and is not meant as a pre-deletion step, but as a utility.
|
|
188
|
+
*/
|
|
189
|
+
async getReferencers() {
|
|
190
|
+
if (!this.id) return undefined
|
|
191
|
+
const returnedObj = {}
|
|
192
|
+
const className = this.constructor.name
|
|
193
|
+
const referencedId = this.id
|
|
194
|
+
const { classWikiDict, dependentsMapsObj } = OrmStore.store
|
|
195
|
+
const classWiki = classWikiDict[className]
|
|
196
|
+
|
|
197
|
+
const referencesContext = classWiki[referencesSymb]
|
|
198
|
+
|
|
199
|
+
if (referencesContext) {
|
|
200
|
+
for (const [className, relationalProps] of Object.entries(referencesContext ?? {})) {
|
|
201
|
+
const referencesMap = classWikiDict[className]
|
|
202
|
+
returnedObj[className] = [await internalFind(referencesMap, relationalProps, referencedId), relationalProps]
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const dependencyContext = classWiki[dependenciesSymb]
|
|
207
|
+
if (!dependencyContext) {
|
|
208
|
+
if (!referencesContext) return undefined
|
|
209
|
+
return returnedObj
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const dependentsDataObj = {}
|
|
213
|
+
for (const [className, relationalProps] of Object.entries(dependencyContext)) {
|
|
214
|
+
const dependentMap = classWikiDict[className]
|
|
215
|
+
const dependentInstanceArr = await internalFind(dependentMap, relationalProps, referencedId)
|
|
216
|
+
dependentsDataObj[className] = [dependentInstanceArr, relationalProps]
|
|
217
|
+
if (!returnedObj[className]) returnedObj[className] = [dependentInstanceArr, relationalProps]
|
|
218
|
+
else {
|
|
219
|
+
returnedObj[className][0].push(...dependentInstanceArr)
|
|
220
|
+
let uniqueDependentInstances = [...new Set(returnedObj[className][0])]
|
|
221
|
+
returnedObj[className][0] = [...uniqueDependentInstances]
|
|
222
|
+
returnedObj[className][1].push(...relationalProps)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
insertDependentsData(className, referencedId, dependentsDataObj, dependentsMapsObj)
|
|
226
|
+
return returnedObj
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
|
|
2
|
+
import { coloredBackgroundConsoleLog, getType, jsValue2SqliteValue, postgres2sqliteQueryStr } from "../../misc/miscFunctions.js"
|
|
3
|
+
import { rowObj2InstanceProxy } from "../../proxies/instanceProxy.js"
|
|
4
|
+
import { createRelationalArrayProxy } from "../../proxies/relationalArrayProxy.js"
|
|
5
|
+
import { generateQueryStrWithCTEs } from "./queryBuilder.js"
|
|
6
|
+
import { junctionJoin, parentJoin } from "./joins.js"
|
|
7
|
+
import { relationalWhereFuncs2Statements } from "./where/relationalWhere.js"
|
|
8
|
+
import { whereValues2Statements } from "./where/where.js"
|
|
9
|
+
import { OrmStore } from "../../misc/ormStore.js"
|
|
10
|
+
|
|
11
|
+
export const proxyType = Symbol('proxyType')
|
|
12
|
+
|
|
13
|
+
export function destructureAndValidateArg(findObj) {
|
|
14
|
+
let { relations: eagerLoad, where, relationalWhere } = findObj
|
|
15
|
+
|
|
16
|
+
if (eagerLoad) {
|
|
17
|
+
const type = getType(eagerLoad)
|
|
18
|
+
if (type !== "object") throw new Error(`\nInvalid value in the 'eagerLoad' field of the 'find' function's argument. Expected an object.`)
|
|
19
|
+
else if (Object.keys(eagerLoad).length === 0) eagerLoad = undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (where) {
|
|
23
|
+
const type = getType(where)
|
|
24
|
+
if (type !== "object") throw new Error(`\nInvalid value in the 'where' field of the 'find' function's argument. Expected an object.`)
|
|
25
|
+
else if (Object.keys(where).length === 0) where = undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (relationalWhere) {
|
|
29
|
+
const type = getType(relationalWhere)
|
|
30
|
+
if (type !== "function") throw new Error(`\nInvalid value in the 'relationalWhere' field of the 'find' function's argument. Expected a function.`)
|
|
31
|
+
}
|
|
32
|
+
return [eagerLoad, where, relationalWhere]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function removeRelationFromUnusedRelations(classMap, key) {
|
|
36
|
+
const filteredUnusedRelations = Object.fromEntries(
|
|
37
|
+
Object.entries(classMap.uncalledJunctions_).filter(([prop, val]) => prop !== key)
|
|
38
|
+
)
|
|
39
|
+
classMap.uncalledJunctions_ = filteredUnusedRelations
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function parseFindWiki(findWiki, aliasBase = 'a', aliasArr = [], joinStatements = [], whereObj = { statements: [], params: [] }, selectArr = []) {
|
|
43
|
+
let returnedWiki = findWiki
|
|
44
|
+
if (aliasBase === 'a') {
|
|
45
|
+
returnedWiki = {}
|
|
46
|
+
for (const key of Object.keys(findWiki)) returnedWiki[key.slice(0, -1)] = findWiki[key]
|
|
47
|
+
}
|
|
48
|
+
const alias = returnedWiki.alias = `${aliasBase}${aliasArr.length + 1}`
|
|
49
|
+
aliasArr.push(alias)
|
|
50
|
+
|
|
51
|
+
let parent = returnedWiki.parent
|
|
52
|
+
if (parent) {
|
|
53
|
+
const parentAlias = `${aliasBase}${aliasArr.length + 1}`
|
|
54
|
+
if (aliasBase === 'a') joinStatements.push(parentJoin(parent, parentAlias, returnedWiki))
|
|
55
|
+
returnedWiki.parent = parseFindWiki(parent, aliasBase, aliasArr, joinStatements, whereObj, selectArr)[0]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (returnedWiki.junctions) {
|
|
59
|
+
for (const key of Object.keys(returnedWiki.junctions)) {
|
|
60
|
+
const joinedTable = returnedWiki.junctions[key]
|
|
61
|
+
const joinedTableAlias = `${aliasBase}${aliasArr.length + 1}`
|
|
62
|
+
if (aliasBase === 'a') joinStatements.push(junctionJoin(joinedTable, joinedTableAlias, returnedWiki, key))
|
|
63
|
+
returnedWiki.junctions[key] = parseFindWiki(joinedTable, aliasBase, aliasArr, joinStatements, whereObj, selectArr)[0]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (returnedWiki.relationalWhere) relationalWhereFuncs2Statements(returnedWiki, whereObj)
|
|
67
|
+
if (returnedWiki.where) whereValues2Statements(returnedWiki, whereObj)
|
|
68
|
+
return [returnedWiki, joinStatements, whereObj, selectArr]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function aliasedFindWiki2QueryRes(aliasedFindWiki, joinStatements, whereObj, eagerLoadObj, classWiki, dbConnection, forInternalFind = false) {
|
|
72
|
+
const sqlClient = OrmStore.store.sqlClient
|
|
73
|
+
let [queryString, relationsScope] = generateQueryStrWithCTEs(aliasedFindWiki, joinStatements, whereObj, eagerLoadObj, classWiki, sqlClient)
|
|
74
|
+
//@ts-ignore
|
|
75
|
+
if (forInternalFind) queryString = queryString.replace(/\bAND\b/g, `OR`)
|
|
76
|
+
try {
|
|
77
|
+
let queryResult
|
|
78
|
+
if (sqlClient === "postgresql") queryResult = (await dbConnection.query(queryString, whereObj.params)).rows
|
|
79
|
+
else {
|
|
80
|
+
const formattedParams = []
|
|
81
|
+
for (const param of whereObj.params) formattedParams.push(jsValue2SqliteValue(param))
|
|
82
|
+
queryResult = dbConnection.prepare(postgres2sqliteQueryStr(queryString)).all(...formattedParams)
|
|
83
|
+
}
|
|
84
|
+
return [queryResult, relationsScope]
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
coloredBackgroundConsoleLog(`Find failed. ${e}\n`, `failure`)
|
|
88
|
+
return []
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function findColumnObjOnWiki(propertyName, scopedWiki) {
|
|
93
|
+
let currentWiki = scopedWiki
|
|
94
|
+
let columnObj
|
|
95
|
+
while (!columnObj) {
|
|
96
|
+
columnObj = currentWiki.columns[propertyName]
|
|
97
|
+
currentWiki = currentWiki.parent
|
|
98
|
+
}
|
|
99
|
+
return columnObj
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function junctionProp2Wiki(scopedWiki, propertyName) {
|
|
103
|
+
let mapWithJunction
|
|
104
|
+
if (scopedWiki.junctions[propertyName]) mapWithJunction = scopedWiki.junctions[propertyName]
|
|
105
|
+
else {
|
|
106
|
+
let currentParent = scopedWiki.parent
|
|
107
|
+
while (!currentParent.junctions[propertyName]) currentParent = currentParent.parent
|
|
108
|
+
mapWithJunction = currentParent.junctions[propertyName]
|
|
109
|
+
}
|
|
110
|
+
return mapWithJunction
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function fillCalledRelationsOnInstance(instance, resultObj, scopedWiki, relationalProperties2Add, Entities) {
|
|
114
|
+
for (const propertyName of relationalProperties2Add) {
|
|
115
|
+
const scopedMap4Junction = junctionProp2Wiki(scopedWiki, propertyName)
|
|
116
|
+
if (scopedMap4Junction.isArray) {
|
|
117
|
+
if (!resultObj[propertyName].length) {
|
|
118
|
+
instance[propertyName] = createRelationalArrayProxy(instance, propertyName)
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
instance[propertyName] = []
|
|
123
|
+
for (let i = 0; i < resultObj[propertyName].length; i++) {
|
|
124
|
+
const instanceProxy = rowObj2InstanceProxy(resultObj[propertyName][i], scopedMap4Junction, Entities)
|
|
125
|
+
instance[propertyName][i] = instanceProxy
|
|
126
|
+
}
|
|
127
|
+
instance[propertyName] = createRelationalArrayProxy(instance, propertyName, instance[propertyName])
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
instance[propertyName] = undefined
|
|
131
|
+
if (!resultObj[propertyName]) continue
|
|
132
|
+
const instanceProxy = rowObj2InstanceProxy(resultObj[propertyName], scopedMap4Junction, Entities)
|
|
133
|
+
instance[propertyName] = instanceProxy
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function getRelationalPropNames(classWiki, only1To1Relations = false) {
|
|
139
|
+
let relationalPropsEntries = Object.entries(classWiki.junctions ?? {})
|
|
140
|
+
let currentWiki = classWiki
|
|
141
|
+
while (currentWiki.parent) {
|
|
142
|
+
let parentRelationalPropEntries = Object.entries(currentWiki.parent.junctions ?? {})
|
|
143
|
+
relationalPropsEntries.push(...parentRelationalPropEntries)
|
|
144
|
+
currentWiki = currentWiki.parent
|
|
145
|
+
}
|
|
146
|
+
if (only1To1Relations) relationalPropsEntries = relationalPropsEntries.filter(([propName, junctionObj]) => !junctionObj.isArray)
|
|
147
|
+
return relationalPropsEntries.map(([propName, junctionObj]) => propName)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function traverseResultObjRelationalScope(instanceArrOrInstanceObj, scopedWiki, entitiesFuncsArr) {
|
|
151
|
+
if (!instanceArrOrInstanceObj) return
|
|
152
|
+
else if (Array.isArray(instanceArrOrInstanceObj)) {
|
|
153
|
+
if (!instanceArrOrInstanceObj.length) return
|
|
154
|
+
for (const instance of instanceArrOrInstanceObj) rowObj2InstanceProxy(instance, scopedWiki, entitiesFuncsArr)
|
|
155
|
+
}
|
|
156
|
+
else rowObj2InstanceProxy(instanceArrOrInstanceObj, scopedWiki, entitiesFuncsArr)
|
|
157
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { nonSnake2Snake } from "../../misc/miscFunctions.js"
|
|
2
|
+
|
|
3
|
+
export function parentJoin(parentObj, parentAlias, childObj) {
|
|
4
|
+
const childAlias = childObj.alias
|
|
5
|
+
const parentName = nonSnake2Snake(parentObj.className_ ?? parentObj.className)
|
|
6
|
+
return `\nLEFT JOIN ${parentName} ${parentAlias} ON ${parentAlias}.id = ${childAlias}.id`
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function junctionJoin(joinedTable, joinedTableAlias, baseTable, junctionPropertyName) {
|
|
10
|
+
const baseTableName = nonSnake2Snake(baseTable.className)
|
|
11
|
+
let baseTableAlias = baseTable.alias
|
|
12
|
+
const joinedTableName = nonSnake2Snake(joinedTable.className_ ?? joinedTable.className)
|
|
13
|
+
|
|
14
|
+
const junctionName = `${baseTableName}___${nonSnake2Snake(junctionPropertyName)}_jt`
|
|
15
|
+
const junctionAlias = `jt_${baseTableAlias}_${joinedTableAlias}`
|
|
16
|
+
|
|
17
|
+
let queryStr = `\nLEFT JOIN ${junctionName} ${junctionAlias} ON ${baseTableAlias}.id = ${junctionAlias}.joining_id `
|
|
18
|
+
queryStr += ` \nLEFT JOIN ${joinedTableName} ${joinedTableAlias} ON ${junctionAlias}.joined_id = ${joinedTableAlias}.id \n`
|
|
19
|
+
|
|
20
|
+
return queryStr
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function junctionJoinCte(joinedTable, baseTable, junctionPropertyName, /**@type {string}*/ sqlClient) {
|
|
24
|
+
const baseTableName = nonSnake2Snake(baseTable.className)
|
|
25
|
+
let baseTableAlias = baseTable.alias
|
|
26
|
+
//const joinedTableName = nonSnake2Snake(joinedTable.className)
|
|
27
|
+
const joinedTableAlias = joinedTable.alias
|
|
28
|
+
|
|
29
|
+
const junctionName = `${baseTableName}___${nonSnake2Snake(junctionPropertyName)}_jt`
|
|
30
|
+
const junctionAlias = `jt_${baseTableAlias}_${joinedTableAlias}`
|
|
31
|
+
|
|
32
|
+
let queryStr = `\nLEFT JOIN ${junctionName} ${junctionAlias} ON ${baseTableAlias}.id = ${junctionAlias}.joining_id `
|
|
33
|
+
if (sqlClient === 'postgresql') queryStr += ` \nLEFT JOIN ${joinedTableAlias}_cte ${joinedTableAlias} ON ${junctionAlias}.joined_id = ${joinedTableAlias}.id \n`
|
|
34
|
+
else queryStr += ` \nLEFT JOIN ${joinedTableAlias}_cte ${joinedTableAlias} ON ${junctionAlias}.joined_id = ${joinedTableAlias}.${joinedTableAlias}_id \n`
|
|
35
|
+
return queryStr
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
export function junctionJoinSelectedCte(joinedTable, baseTable, junctionPropertyName, sqlClient) {
|
|
40
|
+
const baseTableName = nonSnake2Snake(baseTable.className)
|
|
41
|
+
let baseTableAlias = baseTable.alias
|
|
42
|
+
const joinedTableAlias = joinedTable.alias
|
|
43
|
+
|
|
44
|
+
const junctionName = `${baseTableName}___${nonSnake2Snake(junctionPropertyName)}_jt`
|
|
45
|
+
const junctionAlias = `jt_${baseTableAlias}_${joinedTableAlias}`
|
|
46
|
+
|
|
47
|
+
let queryStr = `\nLEFT JOIN ${junctionName} ${junctionAlias} ON ${baseTableAlias}.${baseTableAlias}_id = ${junctionAlias}.joining_id `
|
|
48
|
+
|
|
49
|
+
if (sqlClient === 'postgresql') queryStr += ` \nLEFT JOIN ${joinedTableAlias}_cte ${joinedTableAlias} ON ${junctionAlias}.joined_id = ${joinedTableAlias}.id \n`
|
|
50
|
+
else queryStr += ` \nLEFT JOIN ${joinedTableAlias}_cte ${joinedTableAlias} ON ${junctionAlias}.joined_id = ${joinedTableAlias}.${joinedTableAlias}_id \n`
|
|
51
|
+
return queryStr
|
|
52
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { nonSnake2Snake } from "../../misc/miscFunctions.js"
|
|
2
|
+
import { parseFindWiki } from "./find.js"
|
|
3
|
+
import { mergeRelationsScope } from "./relations.js"
|
|
4
|
+
import { deproxifyScopeProxy, classWiki2ScopeProxy } from "./scopeProxies.js"
|
|
5
|
+
import { parentJoin } from "./joins.js"
|
|
6
|
+
import { eagerLoadCTEsPostgres } from "./sqlClients/postgresFuncs.js"
|
|
7
|
+
import { eagerLoadCTEsSqlite } from "./sqlClients/sqliteFuncs.js"
|
|
8
|
+
|
|
9
|
+
export function generateQueryStrWithCTEs(findWiki, joinStatements, whereObj, eagerLoad, classWiki, sqlClient) {
|
|
10
|
+
let flatFilteredCte = generateFlatFilteredCte(findWiki, joinStatements, whereObj.statements)
|
|
11
|
+
let relationsWiki = classWiki2ScopeProxy(classWiki)
|
|
12
|
+
let eagerLoadCteArr
|
|
13
|
+
|
|
14
|
+
if (eagerLoad) {
|
|
15
|
+
mergeRelationsScope(relationsWiki, eagerLoad)
|
|
16
|
+
relationsWiki = deproxifyScopeProxy(relationsWiki, true)
|
|
17
|
+
const columnObj = generateColumnObj(relationsWiki)
|
|
18
|
+
let rootCte = generateRootCte(relationsWiki, columnObj)
|
|
19
|
+
const [aliasedFindMap] = parseFindWiki(relationsWiki, 'b')
|
|
20
|
+
eagerLoadCteArr = generateEagerLoadCTEsArr(aliasedFindMap, columnObj, sqlClient)
|
|
21
|
+
let queryStr = flatFilteredCte + `, ` + rootCte + `, ` + eagerLoadCteArr.join(`, `)
|
|
22
|
+
if (sqlClient === `postgresql`) queryStr += ` SELECT json FROM selected_cte`
|
|
23
|
+
else queryStr += ` SELECT * FROM selected_cte`
|
|
24
|
+
|
|
25
|
+
return [queryStr, relationsWiki]
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
relationsWiki = deproxifyScopeProxy(relationsWiki, true)
|
|
29
|
+
relationsWiki.alias = `b1`
|
|
30
|
+
const columnObj = generateColumnObj(findWiki, false)
|
|
31
|
+
let rootCte = generateRootCte(findWiki, columnObj)
|
|
32
|
+
let queryStr = flatFilteredCte + `, ` + rootCte
|
|
33
|
+
return [queryStr + ` SELECT * FROM root_cte`, relationsWiki]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function generateFlatFilteredCte(findWiki, joinStatements, whereStatements, queryStr = ``) {
|
|
38
|
+
queryStr += `SELECT DISTINCT ${findWiki.alias}.id FROM ${nonSnake2Snake(findWiki.className)} a1 `
|
|
39
|
+
if (joinStatements.length) queryStr += joinStatements.join(` `) + ` `
|
|
40
|
+
if (whereStatements.length) queryStr += `WHERE (` + whereStatements.join(`) AND (`) + `) `
|
|
41
|
+
return `WITH root_ids AS (${queryStr})`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function generateRootCte(findWiki, columnObj, aliasBase = 'b') {
|
|
45
|
+
let snakeCasedColumnNames2dArr = [Object.values(columnObj[findWiki.className])]
|
|
46
|
+
let joinStatements = []
|
|
47
|
+
if (findWiki.parent) {
|
|
48
|
+
let currentWiki = findWiki
|
|
49
|
+
let i = 1
|
|
50
|
+
while (currentWiki.parent) {
|
|
51
|
+
currentWiki.alias = `${aliasBase}${i}`
|
|
52
|
+
const parentName = currentWiki.parent.className
|
|
53
|
+
|
|
54
|
+
let parentColumnsArr = Object.values(columnObj[parentName])
|
|
55
|
+
const index = parentColumnsArr.indexOf(`id`)
|
|
56
|
+
parentColumnsArr.splice(index, 1)
|
|
57
|
+
snakeCasedColumnNames2dArr.push(parentColumnsArr)
|
|
58
|
+
|
|
59
|
+
joinStatements.push(parentJoin(currentWiki.parent, `${aliasBase}${++i}`, currentWiki))
|
|
60
|
+
currentWiki = currentWiki.parent
|
|
61
|
+
}
|
|
62
|
+
currentWiki.alias = `${aliasBase}${i}`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let columnNamingStr = ``
|
|
66
|
+
for (const [index, arr] of snakeCasedColumnNames2dArr.entries())
|
|
67
|
+
columnNamingStr += arr.map(name => `b${index + 1}.${name} AS b1_${name}`).join(`, `) + `, `
|
|
68
|
+
|
|
69
|
+
let cteStr = `root_cte AS (SELECT ${columnNamingStr.slice(0, -2)} FROM root_ids r JOIN ${nonSnake2Snake(findWiki.className)} ${aliasBase}1 ON ${aliasBase}1.id = r.id`
|
|
70
|
+
if (findWiki.parent) return cteStr + ` ` + joinStatements.join(` `) + `)`
|
|
71
|
+
else return cteStr + `)`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
function generateEagerLoadCTEsArr(findWiki, columnObj, sqlClient) {
|
|
76
|
+
if (sqlClient === "postgresql") return eagerLoadCTEsPostgres(findWiki, [], true)
|
|
77
|
+
else return eagerLoadCTEsSqlite(findWiki, columnObj)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
function generateColumnObj(findWiki, relationalRecusrion = true, columnObj = {}) {
|
|
82
|
+
const className = findWiki.className
|
|
83
|
+
if (columnObj[className]) return
|
|
84
|
+
|
|
85
|
+
const classColumnObj = columnObj[className] = {}
|
|
86
|
+
const columnNames = Object.keys(findWiki.columns)
|
|
87
|
+
for (const columnName of columnNames) classColumnObj[columnName] = nonSnake2Snake(columnName)
|
|
88
|
+
|
|
89
|
+
if (findWiki.parent) generateColumnObj(findWiki.parent, relationalRecusrion, columnObj)
|
|
90
|
+
const relations = findWiki.junctions
|
|
91
|
+
if (relations && relationalRecusrion) {
|
|
92
|
+
for (const key of Object.keys(relations)) generateColumnObj(relations[key], relationalRecusrion, columnObj)
|
|
93
|
+
}
|
|
94
|
+
return columnObj
|
|
95
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { removeRelationFromUnusedRelations } from "./find.js"
|
|
2
|
+
import { classWiki2ScopeProxy } from "./scopeProxies.js"
|
|
3
|
+
|
|
4
|
+
export function mergeRelationsScope(scopeProxy, relationsObj) {
|
|
5
|
+
if (relationsObj === true) return
|
|
6
|
+
else if (!(relationsObj instanceof Object) || Array.isArray(relationsObj))
|
|
7
|
+
throw new Error
|
|
8
|
+
(
|
|
9
|
+
`\nThe 'relations' field of the find function's argument must be an object with values of either boolean trues or objects.`
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
const relationEntries = Object.entries(relationsObj)
|
|
13
|
+
for (const [key, objOrTrue] of relationEntries) {
|
|
14
|
+
const [value, classMap, keyCategory] = scopeProxy[key]
|
|
15
|
+
if (keyCategory !== "uncalledJunctions_") throw new Error(`\n'${key}' is not a valid relational property of class ${scopeProxy.className_}.`)
|
|
16
|
+
else {
|
|
17
|
+
removeRelationFromUnusedRelations(classMap, key)
|
|
18
|
+
classMap.junctions_ ??= {}
|
|
19
|
+
classMap.junctions_[key] = classWiki2ScopeProxy(value)
|
|
20
|
+
if (objOrTrue) mergeRelationsScope(classMap.junctions_[key], objOrTrue)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { proxyType } from "./find.js"
|
|
2
|
+
|
|
3
|
+
export function classWiki2ScopeObj(classWiki) {
|
|
4
|
+
const scopeProxy = {
|
|
5
|
+
className_: classWiki.className ?? classWiki.className_,
|
|
6
|
+
columns_: classWiki.columns ?? classWiki.columns_,
|
|
7
|
+
uncalledJunctions_: classWiki.junctions ?? classWiki.junctions_,
|
|
8
|
+
}
|
|
9
|
+
if (classWiki.isArray || classWiki.isArray_) scopeProxy.isArray_ = true
|
|
10
|
+
return scopeProxy
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function deproxifyScopeProxy(scopeProxy, fixPropertyNames = false) {
|
|
14
|
+
scopeProxy = scopeProxy.raw_
|
|
15
|
+
if (scopeProxy.parent_) scopeProxy.parent_ = deproxifyScopeProxy(scopeProxy.parent_, fixPropertyNames)
|
|
16
|
+
|
|
17
|
+
if (scopeProxy.junctions_) {
|
|
18
|
+
for (const key of Object.keys(scopeProxy.junctions_))
|
|
19
|
+
scopeProxy.junctions_[key] = deproxifyScopeProxy(scopeProxy.junctions_[key], fixPropertyNames)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (fixPropertyNames) {
|
|
23
|
+
const fixedMap = {}
|
|
24
|
+
for (const property of Object.keys(scopeProxy)) fixedMap[property.slice(0, -1)] = scopeProxy[property]
|
|
25
|
+
return fixedMap
|
|
26
|
+
}
|
|
27
|
+
return scopeProxy
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function classWiki2ScopeProxy(classWiki) {
|
|
31
|
+
const scopeObj = classWiki2ScopeObj(classWiki)
|
|
32
|
+
if (classWiki.parent) scopeObj.parent_ = classWiki2ScopeProxy(classWiki.parent)
|
|
33
|
+
|
|
34
|
+
const proxy = new Proxy(scopeObj, {
|
|
35
|
+
get: (target, key, reciever) => {
|
|
36
|
+
if (
|
|
37
|
+
key === "className_"
|
|
38
|
+
|| key === "parent_"
|
|
39
|
+
|| key === "columns_"
|
|
40
|
+
|| key === "uncalledJunctions_"
|
|
41
|
+
|| key === "junctions_"
|
|
42
|
+
|| key === "where_"
|
|
43
|
+
|| key === "relationalWhere_"
|
|
44
|
+
|| key === "isArray_"
|
|
45
|
+
) return target[key]
|
|
46
|
+
else if (key === proxyType) return 'categorizingProxy'
|
|
47
|
+
else if (key === "raw_") return target
|
|
48
|
+
else return findPropOnScopeProxy(target, key, classWiki.className)
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
return proxy
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function findPropOnScopeProxy(scopeProxy, key, rootClassName) {
|
|
55
|
+
if (scopeProxy.columns_[key]) return [scopeProxy.columns_[key], scopeProxy, "columns_"]
|
|
56
|
+
else if (scopeProxy.uncalledJunctions_ && scopeProxy.uncalledJunctions_[key]) return [scopeProxy.uncalledJunctions_[key], scopeProxy, "uncalledJunctions_"]
|
|
57
|
+
else if (scopeProxy.junctions_ && scopeProxy.junctions_[key]) return [scopeProxy.junctions_[key], scopeProxy, "junctions_"]
|
|
58
|
+
else if (scopeProxy.parent_) return findPropOnScopeProxy(scopeProxy.parent_, key, rootClassName)
|
|
59
|
+
else throw new Error(`\n'${key}' is not a valid property of class ${rootClassName}. Please fix the find function's argument. \nhint: use intellisense by pressing CNTRL + space to see all viable options.`)
|
|
60
|
+
}
|