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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/bin/universalTsInit.js +59 -0
  4. package/docs/deletion.md +186 -0
  5. package/docs/find.md +265 -0
  6. package/docs/getting-started-javascript.md +112 -0
  7. package/docs/getting-started-typescript.md +159 -0
  8. package/docs/in-depth-class-definitions.md +193 -0
  9. package/docs/jsdoc-ux-tips.md +17 -0
  10. package/docs/managing-the-database.md +37 -0
  11. package/docs/saving-to-database.md +37 -0
  12. package/index.d.ts +7 -0
  13. package/index.js +11 -0
  14. package/jsconfig.json +29 -0
  15. package/package.json +40 -0
  16. package/src/ORM/DbManager.js +76 -0
  17. package/src/ORM/ORM.js +181 -0
  18. package/src/ORM/bootOrm.js +929 -0
  19. package/src/changeLogger/changeLogger.js +55 -0
  20. package/src/changeLogger/save.js +237 -0
  21. package/src/changeLogger/sqlClients/postgres.js +97 -0
  22. package/src/changeLogger/sqlClients/sqlite.js +120 -0
  23. package/src/entity/delete/delete.js +20 -0
  24. package/src/entity/delete/getDependents.js +46 -0
  25. package/src/entity/entity.d.ts +46 -0
  26. package/src/entity/entity.js +228 -0
  27. package/src/entity/find/find.js +157 -0
  28. package/src/entity/find/joins.js +52 -0
  29. package/src/entity/find/queryBuilder.js +95 -0
  30. package/src/entity/find/relations.js +23 -0
  31. package/src/entity/find/scopeProxies.js +60 -0
  32. package/src/entity/find/sqlClients/postgresFuncs.js +171 -0
  33. package/src/entity/find/sqlClients/sqliteFuncs.js +211 -0
  34. package/src/entity/find/where/relationalWhere.js +142 -0
  35. package/src/entity/find/where/where.js +244 -0
  36. package/src/entity/find/where/whereArgsFunctions.js +49 -0
  37. package/src/misc/classes.js +63 -0
  38. package/src/misc/constants.js +42 -0
  39. package/src/misc/miscFunctions.js +167 -0
  40. package/src/misc/ormStore.js +18 -0
  41. package/src/misc/types.d.ts +560 -0
  42. package/src/proxies/instanceProxy.js +544 -0
  43. package/src/proxies/nonRelationalArrayProxy.js +76 -0
  44. package/src/proxies/objectProxy.js +50 -0
  45. package/src/proxies/relationalArrayProxy.js +130 -0
  46. package/src/webpack/masquerade-loader.js +14 -0
  47. package/src/webpack/plugin.js +43 -0
  48. package/src/webpack/store.js +2 -0
  49. package/src/webpack/webpack.config.js +41 -0
  50. package/testing/dev-doc.md +7 -0
  51. package/testing/generationFuncs.js +21 -0
  52. package/testing/miscFunctions.js +97 -0
  53. package/testing/postgres.test.js +254 -0
  54. package/testing/sqlite.test.js +257 -0
  55. 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
+ }