@sap/cds 9.4.5 → 9.5.1
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/CHANGELOG.md +71 -1
- package/_i18n/messages_en_US_saptrc.properties +1 -1
- package/common.cds +5 -2
- package/lib/compile/cds-compile.js +1 -0
- package/lib/compile/for/assert.js +64 -0
- package/lib/compile/for/flows.js +194 -58
- package/lib/compile/for/lean_drafts.js +75 -7
- package/lib/compile/parse.js +1 -1
- package/lib/compile/to/csn.js +6 -2
- package/lib/compile/to/edm.js +1 -1
- package/lib/compile/to/yaml.js +8 -1
- package/lib/dbs/cds-deploy.js +2 -2
- package/lib/env/cds-env.js +14 -4
- package/lib/env/defaults.js +6 -1
- package/lib/i18n/localize.js +1 -1
- package/lib/index.js +7 -7
- package/lib/req/event.js +4 -0
- package/lib/req/validate.js +3 -0
- package/lib/srv/cds.Service.js +2 -1
- package/lib/srv/middlewares/auth/ias-auth.js +5 -7
- package/lib/srv/middlewares/auth/index.js +1 -1
- package/lib/srv/protocols/index.js +7 -6
- package/lib/srv/srv-handlers.js +7 -0
- package/libx/_runtime/common/Service.js +5 -1
- package/libx/_runtime/common/constants/events.js +1 -0
- package/libx/_runtime/common/generic/assert.js +220 -0
- package/libx/_runtime/common/generic/flows.js +168 -108
- package/libx/_runtime/common/utils/cqn.js +0 -24
- package/libx/_runtime/common/utils/normalizeTimestamp.js +2 -2
- package/libx/_runtime/common/utils/resolveView.js +8 -2
- package/libx/_runtime/common/utils/templateProcessor.js +10 -1
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +21 -9
- package/libx/_runtime/fiori/lean-draft.js +511 -379
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +39 -35
- package/libx/_runtime/messaging/enterprise-messaging.js +2 -2
- package/libx/_runtime/remote/Service.js +4 -5
- package/libx/_runtime/ucl/Service.js +111 -15
- package/libx/common/utils/streaming.js +1 -1
- package/libx/odata/middleware/batch.js +8 -6
- package/libx/odata/middleware/create.js +2 -2
- package/libx/odata/middleware/delete.js +2 -2
- package/libx/odata/middleware/metadata.js +18 -11
- package/libx/odata/middleware/read.js +2 -2
- package/libx/odata/middleware/service-document.js +1 -1
- package/libx/odata/middleware/update.js +1 -1
- package/libx/odata/parse/afterburner.js +24 -25
- package/libx/odata/parse/cqn2odata.js +2 -6
- package/libx/odata/parse/grammar.peggy +90 -12
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +2 -2
- package/libx/odata/utils/readAfterWrite.js +2 -0
- package/libx/queue/TaskRunner.js +26 -1
- package/libx/queue/index.js +11 -1
- package/package.json +1 -1
- package/srv/ucl-service.cds +2 -0
|
@@ -5,12 +5,10 @@ const { getFrom } = require('../../../../lib/compile/for/flows')
|
|
|
5
5
|
const FLOW_STATUS = '@flow.status'
|
|
6
6
|
const FROM = '@from'
|
|
7
7
|
const TO = '@to'
|
|
8
|
-
// backwards compat
|
|
9
|
-
const FLOW_FROM = '@flow.from'
|
|
10
|
-
const FLOW_TO = '@flow.to'
|
|
11
|
-
|
|
12
8
|
const FLOW_PREVIOUS = '$flow.previous'
|
|
13
9
|
|
|
10
|
+
const $transitions_ = Symbol.for('transitions_')
|
|
11
|
+
|
|
14
12
|
function buildAllowedCondition(action, statusElementName, statusEnum) {
|
|
15
13
|
const fromList = getFrom(action)
|
|
16
14
|
const conditions = fromList.map(from => {
|
|
@@ -20,154 +18,216 @@ function buildAllowedCondition(action, statusElementName, statusEnum) {
|
|
|
20
18
|
return `(${conditions.join(' OR ')})`
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
async function isCurrentStatusInFrom(
|
|
21
|
+
async function isCurrentStatusInFrom(subject, action, statusElementName, statusEnum) {
|
|
24
22
|
const cond = buildAllowedCondition(action, statusElementName, statusEnum)
|
|
25
23
|
const parsedXpr = cds.parse.expr(cond)
|
|
26
|
-
const dbEntity = await SELECT.one.from(
|
|
24
|
+
const dbEntity = await SELECT.one.from(subject).where(parsedXpr)
|
|
27
25
|
return dbEntity !== undefined
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
async function checkStatus(
|
|
31
|
-
const allowed = await isCurrentStatusInFrom(
|
|
28
|
+
async function checkStatus(subject, action, statusElementName, statusEnum) {
|
|
29
|
+
const allowed = await isCurrentStatusInFrom(subject, action, statusElementName, statusEnum)
|
|
32
30
|
if (!allowed) {
|
|
33
31
|
const from = getFrom(action)
|
|
34
32
|
const fromValues = JSON.stringify(from.flatMap(el => Object.values(el)))
|
|
35
33
|
cds.error({
|
|
36
|
-
|
|
34
|
+
status: 409,
|
|
37
35
|
message: from.length > 1 ? 'INVALID_FLOW_TRANSITION_MULTI' : 'INVALID_FLOW_TRANSITION_SINGLE',
|
|
38
36
|
args: [action.name, statusElementName, fromValues]
|
|
39
37
|
})
|
|
40
38
|
}
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
// REVISIT: what about renamed keys?
|
|
42
|
+
const buildUpKeys = async (entity, data, subject) => {
|
|
43
|
+
const parentKeys = Object.keys(entity.keys).filter(k => k !== 'IsActiveEntity')
|
|
44
|
+
// REVISIT: when do we not hava all keys?
|
|
45
|
+
const keyValues =
|
|
46
|
+
data && parentKeys.every(key => key in data) ? data : await SELECT.one.from(subject).columns(parentKeys)
|
|
44
47
|
const upKeys = {}
|
|
45
|
-
for (
|
|
46
|
-
upKeys[`up__${
|
|
48
|
+
for (let i = 0; i < parentKeys.length; i++) {
|
|
49
|
+
upKeys[`up__${parentKeys[i]}`] = keyValues[parentKeys[i]]
|
|
47
50
|
}
|
|
48
51
|
return upKeys
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
const resolveTo = (action, statusEnum) => {
|
|
55
|
+
let to = action[TO]
|
|
56
|
+
to = to['#'] ? (statusEnum[to['#']].val ?? statusEnum[to['#']]['$path'].at(-1)) : to
|
|
57
|
+
return to
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const handleTransition = async (entity, data, subject, to) => {
|
|
61
|
+
const isPrevious = to['='] === FLOW_PREVIOUS
|
|
62
|
+
if (isPrevious) {
|
|
63
|
+
const upKeys = await buildUpKeys(entity, data, subject)
|
|
64
|
+
const previous = await SELECT.one
|
|
65
|
+
.from(entity[$transitions_].target)
|
|
66
|
+
.where({ ...upKeys })
|
|
67
|
+
.orderBy('timestamp desc')
|
|
68
|
+
.limit(1, 1)
|
|
69
|
+
if (!previous)
|
|
70
|
+
cds.error({ status: 409, message: 'No change has been made yet, cannot transition to previous status.' })
|
|
71
|
+
to = previous.status
|
|
72
|
+
}
|
|
73
|
+
return to
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const getStatusInfo = statusElement => {
|
|
77
|
+
let statusEnum, statusElementName
|
|
78
|
+
if (statusElement.enum) {
|
|
79
|
+
statusEnum = statusElement.enum
|
|
80
|
+
statusElementName = statusElement.name
|
|
81
|
+
} else if (statusElement?._target?.elements['code']) {
|
|
82
|
+
statusEnum = statusElement._target.elements['code'].enum
|
|
83
|
+
statusElementName = statusElement.name + '_code'
|
|
56
84
|
} else {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
85
|
+
cds.error({
|
|
86
|
+
status: 409,
|
|
87
|
+
message: `Status element in ${statusElement.parent.name} must be an enum or target an entity with an enum named "code"`
|
|
60
88
|
})
|
|
61
89
|
}
|
|
90
|
+
return { statusEnum, statusElementName }
|
|
62
91
|
}
|
|
63
92
|
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
const from_factory = (entity, action, { statusElementName, statusEnum }) => {
|
|
94
|
+
async function handle_flow_from(req) {
|
|
95
|
+
const subject = cds.clone(req.subject)
|
|
96
|
+
if (entity.name.endsWith('.drafts')) subject.ref[0].id = entity.name
|
|
97
|
+
|
|
98
|
+
await checkStatus(subject, action, statusElementName, statusEnum)
|
|
99
|
+
}
|
|
100
|
+
handle_flow_from._initial = true
|
|
101
|
+
return handle_flow_from
|
|
68
102
|
}
|
|
69
103
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.
|
|
75
|
-
.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if
|
|
79
|
-
|
|
80
|
-
|
|
104
|
+
const to___factory = (entity, action, { statusElementName, statusEnum }) => {
|
|
105
|
+
return async function handle_flow_to(req, next) {
|
|
106
|
+
const res = await next()
|
|
107
|
+
|
|
108
|
+
let subject = cds.clone(req.subject)
|
|
109
|
+
if (entity.name.endsWith('.drafts')) subject.ref[0].id = entity.name
|
|
110
|
+
|
|
111
|
+
// REVISIT: this only happens on CREATE, where req.subject is a collection
|
|
112
|
+
// -> could be avoided if setting the status would be done via req.data
|
|
113
|
+
if (!subject.ref[0].id) {
|
|
114
|
+
const keys = Object.keys(entity.keys).reduce((acc, cur) => {
|
|
115
|
+
acc[cur] = res[cur]
|
|
116
|
+
return acc
|
|
117
|
+
}, {})
|
|
118
|
+
subject = SELECT.from(entity.name, keys).SELECT.from
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let to = resolveTo(action, statusEnum)
|
|
122
|
+
|
|
123
|
+
if (Object.prototype.hasOwnProperty.call(entity, $transitions_)) {
|
|
124
|
+
to = await handleTransition(entity, req.data, subject, to)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await UPDATE(subject).with({ [statusElementName]: to })
|
|
128
|
+
|
|
129
|
+
// REVISIT: for stack, we now need to delete the last to transitions
|
|
130
|
+
if (cds.env.features.flows_history_stack && resolveTo(action, statusEnum)['='] === FLOW_PREVIOUS) {
|
|
131
|
+
const upKeys = await buildUpKeys(entity, req.data, req.subject)
|
|
132
|
+
const timestamps = SELECT('timestamp')
|
|
133
|
+
.from(entity[$transitions_].target)
|
|
134
|
+
.where({ ...upKeys })
|
|
135
|
+
.orderBy('timestamp desc')
|
|
136
|
+
.limit(2)
|
|
137
|
+
await DELETE.from(entity[$transitions_].target)
|
|
138
|
+
.where({ ...upKeys })
|
|
139
|
+
.where(`timestamp in`, timestamps)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return res
|
|
81
143
|
}
|
|
82
|
-
await service.run(UPDATE(req.subject).with({ [statusElementName]: toKey }))
|
|
83
|
-
await updateFlowHistory(req, toKey, upKeys, changes, isPrevious)
|
|
84
144
|
}
|
|
85
145
|
|
|
86
146
|
/**
|
|
87
147
|
* handler registration
|
|
88
148
|
*/
|
|
89
149
|
module.exports = cds.service.impl(function () {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
150
|
+
const b4 = []
|
|
151
|
+
const on = []
|
|
152
|
+
const after = []
|
|
92
153
|
|
|
93
154
|
for (const entity of this.entities) {
|
|
94
155
|
if (!entity.actions || !entity.elements) continue
|
|
95
156
|
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
157
|
+
const statusElement = Object.values(entity.elements).find(el => el[FLOW_STATUS])
|
|
158
|
+
if (!statusElement) continue
|
|
159
|
+
|
|
160
|
+
const statusInfo = getStatusInfo(statusElement)
|
|
161
|
+
|
|
162
|
+
// determine and cache target for transitions recording, if any
|
|
163
|
+
let base = entity
|
|
164
|
+
while (base.__proto__.kind === 'entity') base = base.__proto__
|
|
165
|
+
if (base.compositions?.transitions_) {
|
|
166
|
+
entity[$transitions_] = base.compositions.transitions_
|
|
167
|
+
// track changes on db level
|
|
168
|
+
cds.connect.to('db').then(db => {
|
|
169
|
+
db.after(['CREATE', 'UPDATE', 'UPSERT'], entity, async (res, req) => {
|
|
170
|
+
if ((res.affectedRows ?? res) !== 1) return
|
|
171
|
+
if (!(statusInfo.statusElementName in req.data)) return
|
|
172
|
+
const status = req.data[statusInfo.statusElementName]
|
|
173
|
+
const upKeys = await buildUpKeys(entity, req.data, req.subject)
|
|
174
|
+
const last = await SELECT.one.from(entity[$transitions_].target).orderBy('timestamp desc').where(upKeys)
|
|
175
|
+
if (last?.status !== status) await UPSERT.into(entity[$transitions_].target).entries({ ...upKeys, status })
|
|
176
|
+
})
|
|
109
177
|
})
|
|
110
178
|
}
|
|
111
179
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
180
|
+
// register handlers
|
|
181
|
+
for (const action of entity.actions) {
|
|
182
|
+
const to__ = action[TO]
|
|
183
|
+
const from = action[FROM]
|
|
184
|
+
|
|
185
|
+
// REVISIT: for CRUD and Draft, we could set status in before handlers (on db level) to save roundtrips
|
|
186
|
+
switch (action.name) {
|
|
187
|
+
// CRUD
|
|
188
|
+
case 'CREATE':
|
|
189
|
+
if (to__) on.push([action.name, entity, to___factory(entity, action, statusInfo)])
|
|
190
|
+
break
|
|
191
|
+
case 'READ':
|
|
192
|
+
// nothing to do
|
|
193
|
+
break
|
|
194
|
+
case 'UPDATE':
|
|
195
|
+
if (from) b4.push([action.name, entity, from_factory(entity, action, statusInfo)])
|
|
196
|
+
if (to__) on.push([action.name, entity, to___factory(entity, action, statusInfo)])
|
|
197
|
+
break
|
|
198
|
+
case 'DELETE':
|
|
199
|
+
if (from) b4.push([action.name, entity, from_factory(entity, action, statusInfo)])
|
|
200
|
+
break
|
|
201
|
+
// Draft
|
|
202
|
+
case 'NEW':
|
|
203
|
+
if (to__) on.push(['CREATE', entity.drafts, to___factory(entity.drafts, action, statusInfo)])
|
|
204
|
+
break
|
|
205
|
+
case 'PATCH':
|
|
206
|
+
if (from) b4.push(['UPDATE', entity.drafts, from_factory(entity.drafts, action, statusInfo)])
|
|
207
|
+
if (to__) on.push(['UPDATE', entity.drafts, to___factory(entity.drafts, action, statusInfo)])
|
|
208
|
+
break
|
|
209
|
+
case 'SAVE':
|
|
210
|
+
if (from) b4.push([action.name, entity.drafts, from_factory(entity.drafts, action, statusInfo)])
|
|
211
|
+
if (to__) on.push([action.name, entity.drafts, to___factory(entity, action, statusInfo)])
|
|
212
|
+
break
|
|
213
|
+
case 'EDIT':
|
|
214
|
+
if (from) b4.push([action.name, entity, from_factory(entity, action, statusInfo)])
|
|
215
|
+
if (to__) on.push([action.name, entity, to___factory(entity.drafts, action, statusInfo)])
|
|
216
|
+
break
|
|
217
|
+
case 'DISCARD':
|
|
218
|
+
if (from) b4.push([action.name, entity.drafts, from_factory(entity.drafts, action, statusInfo)])
|
|
219
|
+
break
|
|
220
|
+
// custom actions
|
|
221
|
+
default:
|
|
222
|
+
if (from) b4.push([action.name, entity, from_factory(entity, action, statusInfo)])
|
|
223
|
+
if (to__) on.push([action.name, entity, to___factory(entity, action, statusInfo)])
|
|
224
|
+
}
|
|
124
225
|
}
|
|
125
|
-
|
|
126
|
-
entry.push({ events: fromActions, entity, statusElementName, statusEnum })
|
|
127
|
-
exit.push({ events: toActions, entity, statusElementName, statusEnum })
|
|
128
226
|
}
|
|
129
227
|
|
|
130
228
|
this.prepend(function () {
|
|
131
|
-
for (const each of
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
each.entity,
|
|
135
|
-
Object.assign(
|
|
136
|
-
async function handle_entry_state(req) {
|
|
137
|
-
const action = req.target.actions[req.event]
|
|
138
|
-
await checkStatus(req, action, each.statusElementName, each.statusEnum)
|
|
139
|
-
},
|
|
140
|
-
{ _initial: true }
|
|
141
|
-
)
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
for (const each of exit) {
|
|
146
|
-
async function handle_after_create(res, req) {
|
|
147
|
-
const parentKeys = Object.keys(req.target.keys)
|
|
148
|
-
const entry = {}
|
|
149
|
-
for (let i = 0; i < parentKeys.length; i++) {
|
|
150
|
-
entry[`up__${parentKeys[i]}`] = req.data[parentKeys[i]]
|
|
151
|
-
}
|
|
152
|
-
await INSERT.into(req.target.compositions['transitions_'].target).entries({
|
|
153
|
-
...entry,
|
|
154
|
-
status: res[each.statusElementName]
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
if ('transitions_' in (each.entity.compositions ?? {})) this.after('CREATE', each.entity, handle_after_create)
|
|
158
|
-
|
|
159
|
-
async function handle_exit_state(req, next) {
|
|
160
|
-
const res = await next()
|
|
161
|
-
const action = req.target.actions[req.event]
|
|
162
|
-
let toKey = buildToKey(action, each.statusEnum)
|
|
163
|
-
if ('transitions_' in (req.target.compositions ?? {})) {
|
|
164
|
-
await handleStatusTransitionWithHistory(req, each.statusElementName, toKey, this)
|
|
165
|
-
} else {
|
|
166
|
-
await this.run(UPDATE(req.subject).with({ [each.statusElementName]: toKey }))
|
|
167
|
-
}
|
|
168
|
-
return res
|
|
169
|
-
}
|
|
170
|
-
this.on(each.events, each.entity, handle_exit_state)
|
|
171
|
-
}
|
|
229
|
+
for (const each of b4) this.before(...each)
|
|
230
|
+
for (const each of on) this.on(...each)
|
|
231
|
+
for (const each of after) this.after(...each)
|
|
172
232
|
})
|
|
173
233
|
})
|
|
@@ -2,28 +2,6 @@ const cds = require('../../cds')
|
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
const { setEntityContained } = require('./csn')
|
|
4
4
|
|
|
5
|
-
const getEntityNameFromDeleteCQN = cqn => {
|
|
6
|
-
let from
|
|
7
|
-
if (cqn && cqn.DELETE && cqn.DELETE.from) {
|
|
8
|
-
if (typeof cqn.DELETE.from === 'string') {
|
|
9
|
-
from = cqn.DELETE.from
|
|
10
|
-
} else if (cqn.DELETE.from.name) {
|
|
11
|
-
from = cqn.DELETE.from.name
|
|
12
|
-
} else if (cqn.DELETE.from.ref && cqn.DELETE.from.ref.length === 1) {
|
|
13
|
-
from = cqn.DELETE.from.ref[0]
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return from
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const getEntityNameFromUpdateCQN = cqn => {
|
|
20
|
-
return (
|
|
21
|
-
(cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0] && (cqn.UPDATE.entity.ref[0].id || cqn.UPDATE.entity.ref[0])) ||
|
|
22
|
-
cqn.UPDATE.entity.name ||
|
|
23
|
-
cqn.UPDATE.entity
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
5
|
// scope: simple wheres à la "[{ ref: ['foo'] }, '=', { val: 'bar' }, 'and', ... ]"
|
|
28
6
|
function where2obj(where, target = null, data = {}) {
|
|
29
7
|
for (let i = 0; i < where.length; ) {
|
|
@@ -89,8 +67,6 @@ const resolveFromSelect = query => {
|
|
|
89
67
|
}
|
|
90
68
|
|
|
91
69
|
module.exports = {
|
|
92
|
-
getEntityNameFromDeleteCQN,
|
|
93
|
-
getEntityNameFromUpdateCQN,
|
|
94
70
|
where2obj,
|
|
95
71
|
targetFromPath,
|
|
96
72
|
resolveFromSelect
|
|
@@ -11,7 +11,7 @@ module.exports = value => {
|
|
|
11
11
|
if (typeof value === 'number') value = new Date(value).toISOString()
|
|
12
12
|
if (typeof value !== 'string') {
|
|
13
13
|
const msg = `Value "${value}" is not a valid Timestamp`
|
|
14
|
-
|
|
14
|
+
cds.error({ status: 400, message: msg })
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const decimalPointIndex = _lengthIfNotFoundIndex(value.lastIndexOf('.'), value.length)
|
|
@@ -22,7 +22,7 @@ module.exports = value => {
|
|
|
22
22
|
let dt = new Date(value.slice(0, dateEndIndex) + tz)
|
|
23
23
|
if (isNaN(dt)) {
|
|
24
24
|
const msg = `Value "${value}" is not a valid Timestamp`
|
|
25
|
-
|
|
25
|
+
cds.error({ status: 400, message: msg })
|
|
26
26
|
}
|
|
27
27
|
const dateNoMillisNoTZ = dt.toISOString().slice(0, 19)
|
|
28
28
|
const normalizedFractionalDigits = value
|
|
@@ -473,8 +473,14 @@ const _newDelete = (query, transitions, options) => {
|
|
|
473
473
|
: targetName
|
|
474
474
|
|
|
475
475
|
if (newDelete.where) {
|
|
476
|
-
|
|
477
|
-
|
|
476
|
+
newDelete.where = _newWhere(
|
|
477
|
+
newDelete.where,
|
|
478
|
+
targetTransition,
|
|
479
|
+
query.DELETE.from.ref[0],
|
|
480
|
+
query.DELETE.from.as,
|
|
481
|
+
undefined,
|
|
482
|
+
options
|
|
483
|
+
)
|
|
478
484
|
}
|
|
479
485
|
|
|
480
486
|
return newDelete
|
|
@@ -45,14 +45,23 @@ const _processComplex = (processFn, row, template, key, pathOptions) => {
|
|
|
45
45
|
if (rows.length === 0) return
|
|
46
46
|
const keyNames = pathOptions.includeKeyValues && _getTargetKeyNames(template.target)
|
|
47
47
|
|
|
48
|
+
let rowIndex = -1
|
|
48
49
|
for (const row of rows) {
|
|
50
|
+
rowIndex++
|
|
49
51
|
if (row == null) continue
|
|
50
52
|
const args = { processFn, data: row, template, isRoot: false, pathOptions }
|
|
51
53
|
|
|
52
54
|
let pathSegmentInfo
|
|
53
55
|
if (pathOptions.includeKeyValues) {
|
|
54
56
|
pathOptions.rowUUIDGenerator?.(keyNames, row, template)
|
|
55
|
-
pathSegmentInfo = {
|
|
57
|
+
pathSegmentInfo = {
|
|
58
|
+
key,
|
|
59
|
+
keyNames,
|
|
60
|
+
row,
|
|
61
|
+
rowIndex,
|
|
62
|
+
elements: template.target.elements,
|
|
63
|
+
draftKeys: pathOptions.draftKeys
|
|
64
|
+
}
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
if (pathOptions.pathSegmentsInfo) pathOptions.pathSegmentsInfo.push(pathSegmentInfo || key)
|
|
@@ -1,24 +1,30 @@
|
|
|
1
|
-
const
|
|
1
|
+
const segmentKeySerializer = pathSegmentInfo => {
|
|
2
2
|
const { key: tKey, row, elements, draftKeys } = pathSegmentInfo
|
|
3
3
|
let keyNames = pathSegmentInfo.keyNames
|
|
4
4
|
|
|
5
5
|
const keyValuePairs = keyNames
|
|
6
6
|
.map(key => {
|
|
7
|
-
|
|
7
|
+
const keyValue = row[key] ?? draftKeys?.[key]
|
|
8
|
+
if (keyValue == null) return
|
|
9
|
+
|
|
10
|
+
let formattedValue
|
|
8
11
|
|
|
9
12
|
switch (elements[key].type) {
|
|
10
13
|
case 'cds.String':
|
|
11
|
-
|
|
14
|
+
formattedValue = `'${keyValue}'`
|
|
15
|
+
break
|
|
16
|
+
|
|
17
|
+
case 'cds.Binary':
|
|
18
|
+
if (Buffer.isBuffer(keyValue)) formattedValue = keyValue.toString('base64')
|
|
19
|
+
formattedValue = `binary'${formattedValue}'`
|
|
12
20
|
break
|
|
13
21
|
|
|
14
22
|
default:
|
|
15
|
-
|
|
23
|
+
formattedValue = keyValue
|
|
16
24
|
break
|
|
17
25
|
}
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
if (keyValue == null) return
|
|
21
|
-
return `${key}=${quote}${keyValue}${quote}`
|
|
27
|
+
return `${key}=${formattedValue}`
|
|
22
28
|
})
|
|
23
29
|
.filter(c => c)
|
|
24
30
|
|
|
@@ -27,10 +33,16 @@ const segmentSerializer = pathSegmentInfo => {
|
|
|
27
33
|
return pathSegment
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
const
|
|
36
|
+
const segmentIndexSerializer = pathSegmentInfo => {
|
|
37
|
+
const { key: tKey, rowIndex } = pathSegmentInfo
|
|
38
|
+
return `${tKey}[${rowIndex}]`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const templatePathSerializer = (elementName, pathSegmentsInfo, serializeWithIndices = false) => {
|
|
31
42
|
const pathSegments = pathSegmentsInfo.map(pathSegmentInfo => {
|
|
32
43
|
if (typeof pathSegmentInfo === 'string') return pathSegmentInfo
|
|
33
|
-
return
|
|
44
|
+
if (serializeWithIndices) return segmentIndexSerializer(pathSegmentInfo)
|
|
45
|
+
return segmentKeySerializer(pathSegmentInfo)
|
|
34
46
|
})
|
|
35
47
|
const path = `${pathSegments.join('/')}${pathSegments.length ? '/' : ''}${elementName}`
|
|
36
48
|
return path
|