Skip to content

Commit a44f713

Browse files
d060338d060338
d060338
authored and
d060338
committed
use dataloader to collect calls to DB fixes cap-js#30
1 parent 457a2a0 commit a44f713

File tree

3 files changed

+49
-34
lines changed

3 files changed

+49
-34
lines changed

lib/change-log.js

+10-9
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,12 @@ const _getChildChangeObjId = async function (
167167
const _formatCompositionContext = async function (changes, reqData) {
168168
const childNodeChanges = []
169169

170-
for (const change of changes) {
170+
await Promise.all(changes.map(async (change) => {
171171
if (typeof change.valueChangedTo === "object") {
172172
if (!Array.isArray(change.valueChangedTo)) {
173173
change.valueChangedTo = [change.valueChangedTo]
174174
}
175-
for (const childNodeChange of change.valueChangedTo) {
175+
await Promise.all(change.valueChangedTo.map(async (childNodeChange) => {
176176
const curChange = Object.assign({}, change)
177177
const path = childNodeChange._path.split('/')
178178
const curNodePathVal = path.pop()
@@ -184,10 +184,10 @@ const _formatCompositionContext = async function (changes, reqData) {
184184
reqData
185185
)
186186
_formatCompositionValue(curChange, objId, childNodeChange, childNodeChanges)
187-
}
187+
}))
188188
change.valueChangedTo = undefined
189189
}
190-
}
190+
}))
191191
changes.push(...childNodeChanges)
192192
}
193193

@@ -248,7 +248,7 @@ const _getObjectIdByPath = async function (
248248

249249
const _formatObjectID = async function (changes, reqData) {
250250
const objectIdCache = new Map()
251-
for (const change of changes) {
251+
await Promise.all(changes.map(async (change) => {
252252
const path = change.serviceEntityPath.split('/')
253253
const curNodePathVal = path.pop()
254254
const parentNodePathVal = path.pop()
@@ -276,7 +276,7 @@ const _formatObjectID = async function (changes, reqData) {
276276
change.entityID = curNodeObjId
277277
change.parentEntityID = parentNodeObjId
278278
change.parentKey = getUUIDFromPathVal(parentNodePathVal)
279-
}
279+
}))
280280
}
281281

282282
const _isCompositionContextPath = function (aPath, hasComp) {
@@ -290,6 +290,7 @@ const _isCompositionContextPath = function (aPath, hasComp) {
290290
}
291291

292292
const _formatChangeLog = async function (changes, req) {
293+
cds.context.dataloaders = {}
293294
await _formatObjectID(changes, req.data)
294295
await _formatAssociationContext(changes, req.data)
295296
await _formatCompositionContext(changes, req.data)
@@ -317,9 +318,9 @@ function _trackedChanges4 (srv, target, diff) {
317318
if (from === to) return
318319

319320
/**
320-
*
321-
* For the Inline entity such as Items,
322-
* further filtering is required on the keys
321+
*
322+
* For the Inline entity such as Items,
323+
* further filtering is required on the keys
323324
* within the 'association' and 'foreign key' to ultimately retain the keys of the entity itself.
324325
* entity Order : cuid {
325326
* title : String;

lib/entity-helper.js

+36-25
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const cds = require("@sap/cds")
2+
const DataLoader = require("dataloader");
23
const LOG = cds.log("change-log")
34

45

@@ -22,18 +23,28 @@ const getEntityByContextPath = function (aPath, hasComp = false) {
2223

2324
const getObjIdElementNamesInArray = function (elements) {
2425
if (Array.isArray(elements)) return elements.map(e => {
25-
const splitted = (e["="]||e).split('.')
26+
const splitted = (e["="] || e).split('.')
2627
splitted.shift()
2728
return splitted.join('.')
2829
})
2930
else return []
3031
}
3132

32-
const getCurObjFromDbQuery = async function (entityName, queryVal, /**optional*/ queryKey='ID') {
33+
const getCurObjFromDbQuery = async function (entityName, queryVal, /**optional*/ queryKey = 'ID') {
34+
if (!(entityName in cds.context.dataloaders)) {
35+
cds.context.dataloaders[entityName] = new DataLoader(async (keys) => {
36+
// REVISIT: This always reads all elements -> should read required ones only!
37+
const results = await SELECT.from(entityName).where(queryKey, 'in', keys)
38+
const resultsByKey = results.reduce((acc, instance) => {
39+
const key = instance[queryKey]
40+
acc[key] = instance
41+
return acc
42+
}, {})
43+
return keys.map(key => resultsByKey[key] || {})
44+
})
45+
}
3346
if (!queryVal) return {}
34-
// REVISIT: This always reads all elements -> should read required ones only!
35-
const obj = await SELECT.one.from(entityName).where({[queryKey]: queryVal})
36-
return obj || {}
47+
return cds.context.dataloaders[entityName].load(queryVal)
3748
}
3849

3950
const getCurObjFromReqData = function (reqData, nodePathVal, pathVal) {
@@ -71,21 +82,22 @@ const getCurObjFromReqData = function (reqData, nodePathVal, pathVal) {
7182
}
7283

7384

74-
async function getObjectId (reqData, entityName, fields, curObj) {
75-
let all = [], { curObjFromReqData: req_data={}, curObjFromDbQuery: db_data={} } = curObj
85+
async function getObjectId(reqData, entityName, fields, curObj) {
86+
let all = [], {curObjFromReqData: req_data = {}, curObjFromDbQuery: db_data = {}} = curObj
7687
let entity = cds.model.definitions[entityName]
7788
if (!fields?.length) fields = entity["@changelog"]?.map?.(k => k['='] || k) || []
7889
for (let field of fields) {
7990
let path = field.split('.')
8091
if (path.length > 1) {
8192
let current = entity, _db_data = db_data
8293
while (path.length > 1) {
83-
let assoc = current.elements[path[0]]; if (!assoc?.isAssociation) break
94+
let assoc = current.elements[path[0]];
95+
if (!assoc?.isAssociation) break
8496
let foreignKey = assoc.keys?.[0]?.$generatedFieldName
8597
let IDval =
86-
req_data[foreignKey] && current.name === entityName
87-
? req_data[foreignKey]
88-
: _db_data[foreignKey]
98+
req_data[foreignKey] && current.name === entityName
99+
? req_data[foreignKey]
100+
: _db_data[foreignKey]
89101
if (!IDval) {
90102
_db_data = {};
91103
} else try {
@@ -125,19 +137,18 @@ async function getObjectId (reqData, entityName, fields, curObj) {
125137
}
126138

127139

128-
129140
const getDBEntity = (entity) => {
130141
if (typeof entity === 'string') entity = cds.model.definitions[entity]
131142
let proto = Reflect.getPrototypeOf(entity)
132143
if (proto instanceof cds.entity) return proto
133144
}
134145

135146
const getValueEntityType = function (entityName, fields) {
136-
const types=[], entity = cds.model.definitions[entityName]
147+
const types = [], entity = cds.model.definitions[entityName]
137148
for (let field of fields) {
138149
let current = entity, path = field.split('.')
139150
if (path.length > 1) {
140-
for (;;) {
151+
for (; ;) {
141152
let target = current.elements[path[0]]?._target
142153
if (target) current = target; else break
143154
path.shift()
@@ -167,20 +178,20 @@ const hasComposition = function (parentEntity, subEntity) {
167178
}
168179

169180
const _getCompositionObjFromReq = function (obj, targetID) {
170-
if (obj?.ID === targetID) {
171-
return obj;
172-
}
181+
if (obj?.ID === targetID) {
182+
return obj;
183+
}
173184

174-
for (const key in obj) {
175-
if (typeof obj[key] === "object" && obj[key] !== null) {
176-
const result = _getCompositionObjFromReq(obj[key], targetID);
177-
if (result) {
178-
return result;
179-
}
180-
}
185+
for (const key in obj) {
186+
if (typeof obj[key] === "object" && obj[key] !== null) {
187+
const result = _getCompositionObjFromReq(obj[key], targetID);
188+
if (result) {
189+
return result;
190+
}
181191
}
192+
}
182193

183-
return null;
194+
return null;
184195
};
185196

186197
module.exports = {

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,8 @@
3737
"model": "@cap-js/change-tracking"
3838
}
3939
}
40+
},
41+
"dependencies": {
42+
"dataloader": "^2.2.2"
4043
}
4144
}

0 commit comments

Comments
 (0)