@sap/cds 5.4.3 → 5.5.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/CHANGELOG.md +239 -2
- package/apis/ql.d.ts +17 -15
- package/app/index.js +1 -1
- package/bin/build/buildTaskEngine.js +26 -42
- package/bin/build/buildTaskFactory.js +6 -10
- package/bin/build/buildTaskHandler.js +2 -4
- package/bin/build/buildTaskProvider.js +3 -1
- package/bin/build/buildTaskProviderFactory.js +9 -15
- package/bin/build/constants.js +15 -3
- package/bin/build/index.js +5 -4
- package/bin/build/mtaUtil.js +8 -11
- package/bin/build/provider/buildTaskHandlerEdmx.js +63 -6
- package/bin/build/provider/buildTaskHandlerInternal.js +2 -34
- package/bin/build/provider/buildTaskProviderInternal.js +16 -42
- package/bin/build/provider/fiori/index.js +13 -24
- package/bin/build/provider/hana/2migration.js +17 -15
- package/bin/build/provider/hana/2tabledata.js +52 -48
- package/bin/build/provider/hana/index.js +27 -25
- package/bin/build/provider/hana/migrationtable.js +91 -67
- package/bin/build/provider/java-cf/index.js +14 -24
- package/bin/build/provider/mtx/index.js +12 -14
- package/bin/build/provider/node-cf/index.js +18 -32
- package/bin/cds.js +5 -5
- package/bin/serve.js +29 -23
- package/bin/version.js +0 -1
- package/lib/compile/etc/_localized.js +4 -9
- package/lib/compile/for/sql.js +5 -2
- package/lib/compile/parse.js +25 -17
- package/lib/compile/to/srvinfo.js +2 -1
- package/lib/connect/bindings.js +2 -1
- package/lib/connect/index.js +48 -49
- package/lib/core/classes.js +1 -1
- package/lib/core/reflect.js +10 -2
- package/lib/deploy.js +26 -23
- package/lib/env/defaults.js +13 -6
- package/lib/env/index.js +73 -78
- package/lib/env/requires.js +38 -19
- package/lib/index.js +9 -10
- package/lib/lazy.js +2 -2
- package/lib/log/index.js +33 -45
- package/lib/log/service/index.js +2 -2
- package/lib/ql/CREATE.js +14 -9
- package/lib/ql/DELETE.js +6 -5
- package/lib/ql/DROP.js +12 -9
- package/lib/ql/INSERT.js +40 -16
- package/lib/ql/Query.js +67 -40
- package/lib/ql/SELECT.js +162 -127
- package/lib/ql/UPDATE.js +74 -42
- package/lib/ql/Whereable.js +77 -87
- package/lib/ql/index.js +36 -24
- package/lib/ql/parse.js +35 -0
- package/lib/req/context.js +44 -8
- package/lib/req/locale.js +7 -7
- package/lib/serve/Service-api.js +21 -14
- package/lib/serve/Service-dispatch.js +28 -12
- package/lib/serve/Transaction.js +22 -10
- package/lib/serve/index.js +16 -11
- package/lib/utils/axios.js +23 -16
- package/lib/utils/data.js +35 -0
- package/lib/utils/tests.js +27 -18
- package/libx/_runtime/audit/generic/personal/access.js +81 -0
- package/libx/_runtime/audit/generic/personal/constants.js +4 -0
- package/libx/_runtime/audit/generic/personal/index.js +50 -0
- package/libx/_runtime/audit/generic/personal/modification.js +138 -0
- package/libx/_runtime/audit/generic/personal/utils.js +186 -0
- package/libx/_runtime/audit/utils/v2.js +10 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +5 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +5 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +2 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +59 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +11 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +6 -10
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +3 -46
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/createToCQN.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +4 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +16 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/EdmEntityType.js +6 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/format/RepresentationKind.js +4 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/core/OdataRequest.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/SerializerFactory.js +15 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/OperationValidator.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +8 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +6 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/omitValues.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +7 -7
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +14 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +13 -13
- package/libx/_runtime/cds-services/adapter/rest/handlers/create.js +0 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/operation.js +2 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +2 -2
- package/libx/_runtime/cds-services/adapter/rest/rest-to-cqn/index.js +2 -4
- package/libx/_runtime/cds-services/adapter/rest/utils/result.js +4 -2
- package/libx/_runtime/cds-services/services/Service.js +40 -5
- package/libx/_runtime/cds-services/services/utils/columns.js +13 -7
- package/libx/_runtime/cds-services/services/utils/compareJson.js +88 -4
- package/libx/_runtime/cds-services/services/utils/differ.js +24 -6
- package/libx/_runtime/cds-services/services/utils/handlerUtils.js +2 -2
- package/libx/_runtime/common/composition/data.js +66 -63
- package/libx/_runtime/common/composition/delete.js +97 -71
- package/libx/_runtime/common/composition/index.js +2 -1
- package/libx/_runtime/common/composition/insert.js +34 -11
- package/libx/_runtime/common/composition/tree.js +119 -92
- package/libx/_runtime/common/composition/update.js +12 -1
- package/libx/_runtime/common/composition/utils.js +1 -3
- package/libx/_runtime/common/constants/draft.js +12 -1
- package/libx/_runtime/common/generic/auth.js +53 -31
- package/libx/_runtime/common/generic/crud.js +14 -13
- package/libx/_runtime/common/generic/input.js +23 -26
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/sorting.js +16 -16
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/i18n/messages.properties +4 -0
- package/libx/_runtime/common/utils/backlinks.js +12 -5
- package/libx/_runtime/common/utils/cqn.js +6 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +123 -108
- package/libx/_runtime/common/utils/csn.js +56 -4
- package/libx/_runtime/common/utils/data.js +0 -37
- package/libx/_runtime/common/utils/enrichWithKeysFromWhere.js +1 -1
- package/libx/_runtime/common/utils/entityFromCqn.js +7 -24
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +39 -7
- package/libx/_runtime/common/utils/generateOnCond.js +11 -12
- package/libx/_runtime/common/utils/onlyKeysRemain.js +10 -0
- package/libx/_runtime/common/utils/path.js +35 -0
- package/libx/_runtime/common/utils/postProcessing.js +86 -0
- package/libx/_runtime/common/utils/quotingStyles.js +37 -26
- package/libx/_runtime/common/utils/resolveView.js +227 -173
- package/libx/_runtime/common/utils/rewriteAsterisk.js +46 -26
- package/libx/_runtime/common/utils/structured.js +13 -13
- package/libx/_runtime/common/utils/template.js +10 -5
- package/libx/_runtime/common/utils/templateDelimiter.js +1 -0
- package/libx/_runtime/common/utils/templateProcessor.js +28 -72
- package/libx/_runtime/common/utils/union.js +31 -0
- package/libx/_runtime/common/utils/unionCqnTemplate.js +184 -0
- package/libx/_runtime/db/Service.js +1 -1
- package/libx/_runtime/db/data-conversion/timestamp.js +2 -9
- package/libx/_runtime/db/expand/expandCQNToJoin.js +204 -297
- package/libx/_runtime/db/expand/index.js +3 -3
- package/libx/_runtime/db/expand/rawToExpanded.js +36 -7
- package/libx/_runtime/db/generic/index.js +1 -1
- package/libx/_runtime/db/generic/input.js +5 -7
- package/libx/_runtime/db/generic/integrity.js +1 -1
- package/libx/_runtime/db/generic/rewrite.js +2 -10
- package/libx/_runtime/db/generic/update.js +13 -5
- package/libx/_runtime/db/generic/virtual.js +22 -58
- package/libx/_runtime/db/query/delete.js +7 -4
- package/libx/_runtime/db/query/insert.js +6 -4
- package/libx/_runtime/db/query/read.js +21 -8
- package/libx/_runtime/db/query/run.js +4 -1
- package/libx/_runtime/db/query/update.js +5 -4
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +35 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +17 -2
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +6 -5
- package/libx/_runtime/db/sql-builder/ReferenceBuilder.js +10 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +35 -24
- package/libx/_runtime/db/sql-builder/UpdateBuilder.js +14 -4
- package/libx/_runtime/db/sql-builder/arrayed.js +4 -0
- package/libx/_runtime/db/utils/deep.js +8 -0
- package/libx/_runtime/db/utils/generateAliases.js +2 -1
- package/libx/_runtime/fiori/generic/activate.js +19 -15
- package/libx/_runtime/fiori/generic/before.js +3 -11
- package/libx/_runtime/fiori/generic/cancel.js +1 -1
- package/libx/_runtime/fiori/generic/delete.js +3 -1
- package/libx/_runtime/fiori/generic/edit.js +12 -2
- package/libx/_runtime/fiori/generic/new.js +5 -5
- package/libx/_runtime/fiori/generic/patch.js +0 -18
- package/libx/_runtime/fiori/generic/read.js +261 -205
- package/libx/_runtime/fiori/utils/delete.js +36 -7
- package/libx/_runtime/fiori/utils/handler.js +43 -44
- package/libx/_runtime/fiori/utils/where.js +30 -15
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +4 -6
- package/libx/_runtime/hana/execute.js +3 -3
- package/libx/_runtime/hana/localized.js +4 -4
- package/libx/_runtime/hana/pool.js +29 -14
- package/libx/_runtime/hana/search2cqn4sql.js +2 -1
- package/libx/_runtime/hana/searchToContains.js +18 -14
- package/libx/_runtime/index.js +0 -5
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +13 -5
- package/libx/_runtime/messaging/common-utils/naming-conventions.js +4 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +31 -19
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -2
- package/libx/_runtime/messaging/enterprise-messaging.js +6 -4
- package/libx/_runtime/messaging/service.js +7 -6
- package/libx/_runtime/odata/cqn2odata.js +110 -43
- package/libx/_runtime/odata/index.js +26 -48
- package/libx/_runtime/odata/odata2cqn.js +1 -6154
- package/libx/_runtime/odata/odata2cqn.pegjs +559 -0
- package/libx/_runtime/odata/readToCqn.js +94 -64
- package/libx/_runtime/remote/Service.js +74 -21
- package/libx/_runtime/remote/cqn2odata/index.js +1 -5
- package/libx/_runtime/remote/utils/client.js +24 -101
- package/libx/_runtime/remote/utils/dataConversion.js +27 -12
- package/libx/_runtime/sqlite/Service.js +3 -5
- package/libx/_runtime/sqlite/execute.js +33 -27
- package/libx/_runtime/sqlite/localized.js +12 -7
- package/libx/_runtime/types/api.js +10 -0
- package/package.json +2 -2
- package/server.js +16 -2
- package/lib/ql/grammar.pegjs +0 -208
- package/lib/ql/parser.js +0 -1
- package/lib/ql/rt/DELETE.js +0 -29
- package/lib/ql/rt/INSERT.js +0 -23
- package/lib/ql/rt/Query.js +0 -84
- package/lib/ql/rt/SELECT.js +0 -174
- package/lib/ql/rt/UPDATE.js +0 -119
- package/lib/ql/rt/_helpers.js +0 -91
- package/lib/ql/rt/index.js +0 -32
- package/libx/_runtime/audit/generic/personal.js +0 -260
- package/libx/_runtime/cds-services/statements/BaseStatement.js +0 -72
- package/libx/_runtime/cds-services/statements/Create.js +0 -57
- package/libx/_runtime/cds-services/statements/Delete.js +0 -33
- package/libx/_runtime/cds-services/statements/Drop.js +0 -42
- package/libx/_runtime/cds-services/statements/Insert.js +0 -201
- package/libx/_runtime/cds-services/statements/Select.js +0 -826
- package/libx/_runtime/cds-services/statements/Update.js +0 -181
- package/libx/_runtime/cds-services/statements/Where.js +0 -726
- package/libx/_runtime/cds-services/statements/index.js +0 -25
- package/libx/_runtime/common/generic/resolve-mock.js +0 -9
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
|
|
1
3
|
const { getCompositionTree } = require('./tree')
|
|
2
4
|
const { getDeepInsertCQNs } = require('./insert')
|
|
3
5
|
const { getDeepDeleteCQNs } = require('./delete')
|
|
@@ -13,6 +15,8 @@ const getError = require('../../common/error')
|
|
|
13
15
|
*/
|
|
14
16
|
|
|
15
17
|
const _serializedKey = (entity, data) => {
|
|
18
|
+
if (data === null) return 'null'
|
|
19
|
+
|
|
16
20
|
return JSON.stringify(
|
|
17
21
|
ctUtils
|
|
18
22
|
.keyElements(entity)
|
|
@@ -72,6 +76,10 @@ const _diffData = (newData, oldData, entity, newEntry, oldEntry, definitions) =>
|
|
|
72
76
|
const oldVal = ctUtils.val(oldData[key])
|
|
73
77
|
|
|
74
78
|
if (newVal !== undefined && newVal !== oldVal) {
|
|
79
|
+
if (entity.elements[key]._isStructured && Object.keys(newData[key]).length === 0) {
|
|
80
|
+
// empty structured -> skip
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
75
83
|
result[key] = newData[key]
|
|
76
84
|
continue
|
|
77
85
|
}
|
|
@@ -107,6 +115,8 @@ function _addSubDeepUpdateCQNForUpdateInsert({
|
|
|
107
115
|
const selectDataByKey = _dataByKey(entity, selectData)
|
|
108
116
|
const deepUpdateData = []
|
|
109
117
|
for (const entry of data) {
|
|
118
|
+
if (entry === null) continue
|
|
119
|
+
|
|
110
120
|
const key = ctUtils.key(entity, entry)
|
|
111
121
|
const selectEntry = selectDataByKey.get(_serializedKey(entity, entry))
|
|
112
122
|
_fillLinkFromStructuredData(entity, entry)
|
|
@@ -288,7 +298,8 @@ const getDeepUpdateCQNs = (definitions, cqn, selectData) => {
|
|
|
288
298
|
definitions,
|
|
289
299
|
rootEntityName: entityName,
|
|
290
300
|
checkRoot: false,
|
|
291
|
-
resolveViews: !draft
|
|
301
|
+
resolveViews: !draft,
|
|
302
|
+
service: cds.db
|
|
292
303
|
})
|
|
293
304
|
|
|
294
305
|
const subCQNs = _addSubDeepUpdateCQN({ definitions, compositionTree, data: [entry], selectData, cqns: [], draft })
|
|
@@ -9,9 +9,7 @@ const addDraftSuffix = (draft, name) => {
|
|
|
9
9
|
const whereKey = key => {
|
|
10
10
|
const where = []
|
|
11
11
|
Object.keys(key).forEach(keyPart => {
|
|
12
|
-
if (where.length > 0)
|
|
13
|
-
where.push('and')
|
|
14
|
-
}
|
|
12
|
+
if (where.length > 0) where.push('and')
|
|
15
13
|
where.push({ ref: [keyPart] }, '=', { val: key[keyPart] })
|
|
16
14
|
})
|
|
17
15
|
return where
|
|
@@ -30,5 +30,16 @@ module.exports = {
|
|
|
30
30
|
{ ref: ['HasActiveEntity'], cast: { type: 'cds.Boolean' } },
|
|
31
31
|
{ ref: ['HasDraftEntity'], cast: { type: 'cds.Boolean' } },
|
|
32
32
|
{ ref: ['DraftAdministrativeData_DraftUUID'] }
|
|
33
|
-
]
|
|
33
|
+
],
|
|
34
|
+
SCENARIO: {
|
|
35
|
+
ACTIVE: 'ACTIVE',
|
|
36
|
+
ACTIVE_WITHOUT_DRAFT: 'ACTIVE_WITHOUT_DRAFT',
|
|
37
|
+
ALL_ACTIVE: 'ALL_ACTIVE',
|
|
38
|
+
ALL_INACTIVE: 'ALL_INACTIVE',
|
|
39
|
+
DRAFT_ADMIN: 'DRAFT_ADMIN',
|
|
40
|
+
DRAFT_IN_PROCESS: 'DRAFT_IN_PROCESS',
|
|
41
|
+
DRAFT_WHICH_OWNER: 'DRAFT_WHICH_OWNER',
|
|
42
|
+
SIBLING_ENTITY: 'SIBLING_ENTITY',
|
|
43
|
+
UNION: 'UNION'
|
|
44
|
+
}
|
|
34
45
|
}
|
|
@@ -26,9 +26,6 @@ const RESTRICTIONS = {
|
|
|
26
26
|
DELETABLE: 'DeleteRestrictions.Deletable'
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// REVISIT: remove everywhere
|
|
30
|
-
const SYMBOL_FROM_ANNOTATION = Symbol.for('sap.cds.FROM_ANNOTATION')
|
|
31
|
-
|
|
32
29
|
const _reject = req => {
|
|
33
30
|
// unauthorized or forbidden?
|
|
34
31
|
if (req.user._is_anonymous) {
|
|
@@ -46,10 +43,12 @@ const _reject = req => {
|
|
|
46
43
|
|
|
47
44
|
const _getCurrentSubClause = (next, restrict) => {
|
|
48
45
|
const escaped = next[0].replace(/\$/g, '\\$').replace(/\./g, '\\.')
|
|
49
|
-
const
|
|
50
|
-
const
|
|
46
|
+
const re1 = new RegExp(`([\\w\\.']*)\\s*=\\s*(${escaped})|(${escaped})\\s*=\\s*([\\w\\.']*)`)
|
|
47
|
+
const re2 = new RegExp(`([\\w\\.']*)\\s*in\\s*(${escaped})|(${escaped})\\s*in\\s*([\\w\\.']*)`)
|
|
48
|
+
const clause = restrict.where.match(re1) || restrict.where.match(re2)
|
|
51
49
|
if (!clause) {
|
|
52
|
-
|
|
50
|
+
// NOTE: arrayed attr with "=" as operator is some kind of legacy case
|
|
51
|
+
throw new Error('user attribute array must be used with operator "=" or "in"')
|
|
53
52
|
}
|
|
54
53
|
return clause
|
|
55
54
|
}
|
|
@@ -57,7 +56,18 @@ const _getCurrentSubClause = (next, restrict) => {
|
|
|
57
56
|
const _processUserAttr = (next, restrict, user, attr) => {
|
|
58
57
|
const clause = _getCurrentSubClause(next, restrict)
|
|
59
58
|
const valOrRef = clause[1] || clause[4]
|
|
60
|
-
if (
|
|
59
|
+
if (clause[0].match(/ in /)) {
|
|
60
|
+
if (!user[attr] || user[attr].length === 0) {
|
|
61
|
+
restrict.where = restrict.where.replace(clause[0], '1 = 2')
|
|
62
|
+
} else if (user[attr].length === 1) {
|
|
63
|
+
restrict.where = restrict.where.replace(clause[0], `${valOrRef} = '${user[attr][0]}'`)
|
|
64
|
+
} else {
|
|
65
|
+
restrict.where = restrict.where.replace(
|
|
66
|
+
clause[0],
|
|
67
|
+
`${valOrRef} in (${user[attr].map(ele => `'${ele}'`).join(', ')})`
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
} else if (valOrRef.startsWith("'") && user[attr].includes(valOrRef.split("'")[1])) {
|
|
61
71
|
restrict.where = restrict.where.replace(clause[0], `${valOrRef} = ${valOrRef}`)
|
|
62
72
|
} else {
|
|
63
73
|
restrict.where = restrict.where.replace(
|
|
@@ -164,16 +174,10 @@ const _evalStatic = (op, vals) => {
|
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
176
|
|
|
167
|
-
const _addSymbol = element => {
|
|
168
|
-
element = typeof element === 'string' ? new String(element) : element
|
|
169
|
-
element[SYMBOL_FROM_ANNOTATION] = true
|
|
170
|
-
return element
|
|
171
|
-
}
|
|
172
|
-
|
|
173
177
|
const _getMergedWhere = restricts => {
|
|
174
178
|
const xprs = []
|
|
175
179
|
restricts.forEach(ele => {
|
|
176
|
-
xprs.push('(', ...ele._xpr
|
|
180
|
+
xprs.push('(', ...ele._xpr, ')', 'or')
|
|
177
181
|
})
|
|
178
182
|
xprs.pop()
|
|
179
183
|
return xprs
|
|
@@ -229,16 +233,9 @@ const _ensureTableAlias = (ref, aliases, targetFrom, model, hasExpand) => {
|
|
|
229
233
|
} else {
|
|
230
234
|
_adaptTableName(ref, nameObj.refIndex, nameObj.name)
|
|
231
235
|
}
|
|
232
|
-
|
|
233
|
-
if (hasExpand && nameObj.aliasIndex === 0) {
|
|
234
|
-
_addSymbol(ref)
|
|
235
|
-
}
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
const _enhanceAnnotationSubSelect = (select, model, targetName, targetFrom, hasExpand) => {
|
|
239
|
-
if (select.from && select.from.ref) {
|
|
240
|
-
_addSymbol(select.from.ref)
|
|
241
|
-
}
|
|
242
239
|
if (select.where) {
|
|
243
240
|
for (const v of select.where) {
|
|
244
241
|
if (v.ref && select.from.ref) {
|
|
@@ -368,8 +365,10 @@ const _getRestrictionForTarget = (resolvedApplicables, target) => {
|
|
|
368
365
|
}
|
|
369
366
|
|
|
370
367
|
const _addRestrictionsToRead = async (req, model, resolvedApplicables) => {
|
|
371
|
-
|
|
372
|
-
|
|
368
|
+
if (req.target._isDraftEnabled) {
|
|
369
|
+
req.query._draftRestrictions = resolvedApplicables.map(ra => ra._xpr)
|
|
370
|
+
return
|
|
371
|
+
}
|
|
373
372
|
|
|
374
373
|
if (typeof req.query.SELECT.from === 'object')
|
|
375
374
|
req.query.SELECT.from.ref = _addWheresToRef(req.query.SELECT.from.ref, model, resolvedApplicables)
|
|
@@ -377,6 +376,7 @@ const _addRestrictionsToRead = async (req, model, resolvedApplicables) => {
|
|
|
377
376
|
const restrictionForTarget = _getRestrictionForTarget(resolvedApplicables, req.target)
|
|
378
377
|
if (restrictionForTarget) {
|
|
379
378
|
req.query.where(restrictionForTarget)
|
|
379
|
+
// REVISIT: remove with cds^6
|
|
380
380
|
_enhanceAnnotationWhere(req.query, restrictionForTarget, model)
|
|
381
381
|
}
|
|
382
382
|
}
|
|
@@ -418,7 +418,7 @@ const _getRestrictedCount = async (req, model, resolvedApplicables) => {
|
|
|
418
418
|
const restrictionForTarget = _getRestrictionForTarget(resolvedApplicables, req.target)
|
|
419
419
|
if (restrictionForTarget) selectRestricted.where(restrictionForTarget)
|
|
420
420
|
|
|
421
|
-
const { n } = await dbtx.run(selectRestricted)
|
|
421
|
+
const { n } = await dbtx.run(cqn2cqn4sql(selectRestricted, model, { suppressSearch: true }))
|
|
422
422
|
return n
|
|
423
423
|
}
|
|
424
424
|
|
|
@@ -539,27 +539,49 @@ const _addNormalizedRestrictPerGrant = (grant, where, restrict, restricts, defin
|
|
|
539
539
|
}
|
|
540
540
|
}
|
|
541
541
|
|
|
542
|
-
const _addNormalizedRestrict = (restrict, restricts, definition) => {
|
|
543
|
-
|
|
542
|
+
const _addNormalizedRestrict = (restrict, restricts, definition, definitions) => {
|
|
543
|
+
let where = restrict.where
|
|
544
544
|
? restrict.where.replace(/\$user/g, '$user.id').replace(/\$user\.id\./g, '$user.')
|
|
545
545
|
: undefined
|
|
546
|
+
|
|
547
|
+
// NOTE: "exists toMany.toOne[prop = $user]" -> "exists toMany[exists toOne[prop = $user]]"
|
|
548
|
+
try {
|
|
549
|
+
if (where) {
|
|
550
|
+
// operate on a copy
|
|
551
|
+
let _where = where
|
|
552
|
+
const paths = where.match(/ (\w*)\.(\w*)/g) || []
|
|
553
|
+
for (let i = 0; i < paths.length; i++) {
|
|
554
|
+
const parts = paths[i].trim().split('.')
|
|
555
|
+
let current = definition
|
|
556
|
+
while (parts.length) {
|
|
557
|
+
current = current.elements[parts.shift()]
|
|
558
|
+
if (current.is2many) _where = _where.replace(current.name + '.', current.name + '[exists ') + ']'
|
|
559
|
+
if (current.target) current = definitions[current.target]
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
where = _where
|
|
563
|
+
}
|
|
564
|
+
} catch (e) {
|
|
565
|
+
// ignore
|
|
566
|
+
}
|
|
567
|
+
|
|
546
568
|
restrict.grant = Array.isArray(restrict.grant) ? restrict.grant : [restrict.grant || '*']
|
|
547
569
|
restrict.grant.forEach(grant => _addNormalizedRestrictPerGrant(grant, where, restrict, restricts, definition))
|
|
548
570
|
}
|
|
549
571
|
|
|
550
|
-
const _getNormalizedRestricts = definition => {
|
|
572
|
+
const _getNormalizedRestricts = (definition, definitions) => {
|
|
551
573
|
const restricts = []
|
|
552
574
|
|
|
553
575
|
// own
|
|
554
576
|
definition['@restrict'] &&
|
|
555
|
-
definition['@restrict'].forEach(restrict => _addNormalizedRestrict(restrict, restricts, definition))
|
|
577
|
+
definition['@restrict'].forEach(restrict => _addNormalizedRestrict(restrict, restricts, definition, definitions))
|
|
556
578
|
|
|
557
579
|
// bounds
|
|
558
580
|
if (definition.actions && Object.keys(definition.actions).some(k => definition.actions[k]['@restrict'])) {
|
|
559
581
|
for (const k in definition.actions) {
|
|
560
582
|
const action = definition.actions[k]
|
|
561
583
|
if (action['@restrict']) {
|
|
562
|
-
restricts.push(..._getNormalizedRestricts(action))
|
|
584
|
+
restricts.push(..._getNormalizedRestricts(action, definitions))
|
|
563
585
|
} else if (!definition['@restrict']) {
|
|
564
586
|
// > no entity-level restrictions => unrestricted action
|
|
565
587
|
restricts.push({ grant: action.name, to: ['any'], target: action.parent })
|
|
@@ -635,7 +657,7 @@ const _registerEntityRequiresHandlers = (entity, srv, { dependentEntity, interme
|
|
|
635
657
|
|
|
636
658
|
const _registerEntityRestrictHandlers = (entity, srv, { dependentEntity, intermediateEntities } = {}) => {
|
|
637
659
|
if (entity['@restrict'] || entity.actions) {
|
|
638
|
-
const restricts = _getNormalizedRestricts(entity)
|
|
660
|
+
const restricts = _getNormalizedRestricts(entity, srv.model.definitions)
|
|
639
661
|
if (restricts.length > 0) {
|
|
640
662
|
if (dependentEntity)
|
|
641
663
|
srv.before(
|
|
@@ -657,7 +679,7 @@ const _registerOperationRequiresHandlers = (operation, srv) => {
|
|
|
657
679
|
|
|
658
680
|
const _registerOperationRestrictHandlers = (operation, srv) => {
|
|
659
681
|
if (operation['@restrict']) {
|
|
660
|
-
const restricts = _getNormalizedRestricts(operation)
|
|
682
|
+
const restricts = _getNormalizedRestricts(operation, srv.model.definitions)
|
|
661
683
|
if (restricts.length > 0) {
|
|
662
684
|
srv.before(_getLocalName(operation), _getRestrictsHandler(restricts, operation, srv.model))
|
|
663
685
|
}
|
|
@@ -5,16 +5,7 @@ const getTemplate = require('../utils/template')
|
|
|
5
5
|
const templateProcessor = require('../utils/templateProcessor')
|
|
6
6
|
const replaceManagedData = require('../utils/dollar')
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
// if custom handlers uses only expression like {col: {'+=': 1}},
|
|
10
|
-
// req.data will only contain the keys
|
|
11
|
-
if (req.query.UPDATE.with && Object.keys(req.query.UPDATE.with).length > 0) {
|
|
12
|
-
return false
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const keys = Object.keys(req.target.keys)
|
|
16
|
-
return !Object.keys(req.data).some(k => !keys.includes(k))
|
|
17
|
-
}
|
|
8
|
+
const onlyKeysRemain = require('../utils/onlyKeysRemain')
|
|
18
9
|
|
|
19
10
|
const _targetEntityDoesNotExist = async req => {
|
|
20
11
|
const { query } = req
|
|
@@ -39,7 +30,7 @@ const _processorFn = req => {
|
|
|
39
30
|
const { event, user, timestamp } = req
|
|
40
31
|
const ts = new Date(timestamp).toISOString()
|
|
41
32
|
|
|
42
|
-
return (row, key,
|
|
33
|
+
return ({ row, key, plain }) => {
|
|
43
34
|
const categories = plain.categories
|
|
44
35
|
|
|
45
36
|
for (const category of categories) {
|
|
@@ -89,8 +80,8 @@ module.exports = function () {
|
|
|
89
80
|
|
|
90
81
|
let result
|
|
91
82
|
|
|
92
|
-
// no changes, no op (otherwise, @cds.on.update gets new values), but we need to check
|
|
93
|
-
if (req.event === 'UPDATE' &&
|
|
83
|
+
// no changes, no op (otherwise, @cds.on.update gets new values), but we need to check existence
|
|
84
|
+
if (req.event === 'UPDATE' && onlyKeysRemain(req)) {
|
|
94
85
|
if (await _targetEntityDoesNotExist(req)) {
|
|
95
86
|
req.reject(404)
|
|
96
87
|
}
|
|
@@ -98,6 +89,16 @@ module.exports = function () {
|
|
|
98
89
|
result = req.data
|
|
99
90
|
}
|
|
100
91
|
|
|
92
|
+
if (req.event === 'DELETE' && req.target._isSingleton) {
|
|
93
|
+
if (!req.target['@odata.singleton.nullable']) {
|
|
94
|
+
req.reject(400, 'SINGLETON_NOT_NULLABLE')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const singleton = await cds.tx(req).run(SELECT.one(req.target))
|
|
98
|
+
if (!singleton) req.reject(404)
|
|
99
|
+
req.query.where(singleton)
|
|
100
|
+
}
|
|
101
|
+
|
|
101
102
|
if (!result) {
|
|
102
103
|
result = await cds.tx(req).run(req.query, req.data)
|
|
103
104
|
}
|
|
@@ -16,9 +16,8 @@ const templateProcessor = require('../utils/templateProcessor')
|
|
|
16
16
|
const { getDataFromCQN, setDataFromCQN } = require('../utils/data')
|
|
17
17
|
const { isMandatory, isReadOnly } = require('../aspects/utils')
|
|
18
18
|
|
|
19
|
-
const shouldSuppressErrorPropagation = ({ event, value
|
|
19
|
+
const shouldSuppressErrorPropagation = ({ event, value }) => {
|
|
20
20
|
return (
|
|
21
|
-
suppress ||
|
|
22
21
|
event === 'NEW' ||
|
|
23
22
|
event === 'PATCH' ||
|
|
24
23
|
(event === 'UPDATE' && value.val === undefined) ||
|
|
@@ -35,7 +34,7 @@ const getSimpleCategory = category => {
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
const rowKeysGenerator = eventName => {
|
|
38
|
-
return (
|
|
37
|
+
return (keyNames, row, template) => {
|
|
39
38
|
if (eventName === 'UPDATE') return
|
|
40
39
|
|
|
41
40
|
for (const keyName of keyNames) {
|
|
@@ -96,7 +95,7 @@ const _processCategory = ({ row, key, category, isRoot, event, value, req, eleme
|
|
|
96
95
|
const processorFn = (errors, req) => {
|
|
97
96
|
const { event } = req
|
|
98
97
|
|
|
99
|
-
return (row, key, element, plain, isRoot, pathSegments
|
|
98
|
+
return ({ row, key, element, plain, isRoot, pathSegments }) => {
|
|
100
99
|
const categories = plain.categories
|
|
101
100
|
// ugly pointer passing for sonar
|
|
102
101
|
const value = { mandatory: false, val: row && row[key] }
|
|
@@ -105,7 +104,7 @@ const processorFn = (errors, req) => {
|
|
|
105
104
|
_processCategory({ row, key, category, isRoot, event, value, req, element })
|
|
106
105
|
}
|
|
107
106
|
|
|
108
|
-
if (shouldSuppressErrorPropagation({ event, value
|
|
107
|
+
if (shouldSuppressErrorPropagation({ event, value })) {
|
|
109
108
|
return
|
|
110
109
|
}
|
|
111
110
|
|
|
@@ -211,34 +210,30 @@ function _handler(req) {
|
|
|
211
210
|
_callError(req, errors)
|
|
212
211
|
}
|
|
213
212
|
|
|
214
|
-
const processorFnForActionsFunctions =
|
|
215
|
-
|
|
213
|
+
const processorFnForActionsFunctions =
|
|
214
|
+
(errors, opName) =>
|
|
215
|
+
({ row, key, element }) => {
|
|
216
|
+
const value = row && row[key]
|
|
216
217
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const KINDS_TO_VALIDATE = {
|
|
222
|
-
entity: 1,
|
|
223
|
-
type: 1
|
|
224
|
-
}
|
|
218
|
+
// REVISIT: Convert checkInputConstraints to template mechanism
|
|
219
|
+
checkInputConstraints({ element, value, errors, key: opName })
|
|
220
|
+
}
|
|
225
221
|
|
|
226
222
|
const _processActionFunctionRow = (row, param, key, errors, event, service) => {
|
|
227
223
|
const values = Array.isArray(row[key]) ? row[key] : [row[key]]
|
|
228
|
-
|
|
229
224
|
// unstructured
|
|
230
225
|
for (const value of values) {
|
|
231
226
|
checkInputConstraints({ element: param, value, errors, key })
|
|
232
227
|
}
|
|
233
228
|
|
|
234
229
|
// structured
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
230
|
+
const template = getTemplate('app-input-operation', service, param, {
|
|
231
|
+
pick: _pick
|
|
232
|
+
})
|
|
233
|
+
if (template && template.elements.size) {
|
|
234
|
+
for (const value of values) {
|
|
235
|
+
const args = { processFn: processorFnForActionsFunctions(errors, key), row: value, template }
|
|
236
|
+
templateProcessor(args)
|
|
242
237
|
}
|
|
243
238
|
}
|
|
244
239
|
}
|
|
@@ -246,7 +241,8 @@ const _processActionFunctionRow = (row, param, key, errors, event, service) => {
|
|
|
246
241
|
const _processActionFunction = (row, eventParams, errors, event, service) => {
|
|
247
242
|
for (const key in eventParams) {
|
|
248
243
|
let param = eventParams[key]
|
|
249
|
-
|
|
244
|
+
const _type = param.type
|
|
245
|
+
if (!_type && param.items) param = param.items
|
|
250
246
|
_processActionFunctionRow(row, param, key, errors, event, service)
|
|
251
247
|
}
|
|
252
248
|
}
|
|
@@ -280,8 +276,9 @@ function _actionFunctionHandler(req) {
|
|
|
280
276
|
// REVISIT: find better solution, maybe compiler?
|
|
281
277
|
// resolve enums like format, range, etc.
|
|
282
278
|
for (const param of Object.values(eventParams)) {
|
|
283
|
-
|
|
284
|
-
|
|
279
|
+
const _type = param.type && this.model && this.model.definitions[param.type]
|
|
280
|
+
if (_type) {
|
|
281
|
+
param.enum = _type.enum
|
|
285
282
|
}
|
|
286
283
|
}
|
|
287
284
|
|
|
@@ -25,7 +25,7 @@ const _fillStructure = (row, parts, element, category, args) => {
|
|
|
25
25
|
const processorFn = req => {
|
|
26
26
|
const REST = req.constructor.name === 'RestRequest'
|
|
27
27
|
|
|
28
|
-
return (row, key, element, plain) => {
|
|
28
|
+
return ({ row, key, element, plain }) => {
|
|
29
29
|
if (!row || row[key] !== undefined) return
|
|
30
30
|
|
|
31
31
|
const { category, args } = plain
|
|
@@ -5,7 +5,6 @@ const DRAFT_COLUMNS = ['IsActiveEntity', 'HasDraftEntity', 'HasActiveEntity']
|
|
|
5
5
|
|
|
6
6
|
const _getStaticOrders = req => {
|
|
7
7
|
const { target: entity, query } = req
|
|
8
|
-
|
|
9
8
|
const defaultOrders = entity['@cds.default.order'] || entity['@odata.default.order'] || []
|
|
10
9
|
|
|
11
10
|
if (!cds._deprecationWarningForDefaultSort && defaultOrders.length > 0) {
|
|
@@ -60,25 +59,26 @@ const _handler = function (req) {
|
|
|
60
59
|
|
|
61
60
|
// remove defaultOrder if not part of group by
|
|
62
61
|
if (select.groupBy && select.groupBy.length > 0) {
|
|
63
|
-
staticOrders = staticOrders.filter(d =>
|
|
64
|
-
return select.groupBy.find(e => e.ref[0] === d.by['='])
|
|
65
|
-
})
|
|
62
|
+
staticOrders = staticOrders.filter(d => select.groupBy.find(e => e.ref[0] === d.by['=']))
|
|
66
63
|
}
|
|
67
64
|
|
|
65
|
+
if (!select.orderBy && staticOrders.length === 0) return
|
|
68
66
|
select.orderBy = select.orderBy || []
|
|
67
|
+
|
|
69
68
|
for (const defaultOrder of staticOrders) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
69
|
+
const some = select.orderBy.some(orderBy => {
|
|
70
|
+
const managedKey = orderBy.ref && orderBy.ref.length > 1 && orderBy.ref.join('_')
|
|
71
|
+
const element = managedKey && req.target.elements[managedKey]
|
|
72
|
+
const isManagedKey = element && element.key && !element.is2one
|
|
73
|
+
|
|
74
|
+
// don't add duplicates
|
|
75
|
+
return (
|
|
76
|
+
(orderBy.ref && orderBy.ref.length === 1 && orderBy.ref[0] === defaultOrder.by['=']) ||
|
|
77
|
+
(isManagedKey && managedKey === defaultOrder.by['='])
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
if (!some) {
|
|
82
82
|
const orderByItem = { ref: [defaultOrder.by['=']], sort: defaultOrder.desc ? 'desc' : 'asc' }
|
|
83
83
|
select.orderBy.push(orderByItem)
|
|
84
84
|
}
|
|
@@ -98,7 +98,7 @@ module.exports = (key, locale = '', args = {}) => {
|
|
|
98
98
|
|
|
99
99
|
// for locale OR app default OR cds default
|
|
100
100
|
let text = i18ns[locale][key] || i18ns[''][key] || i18ns.default[key]
|
|
101
|
-
|
|
101
|
+
if (!text) return
|
|
102
102
|
// best effort replacement
|
|
103
103
|
try {
|
|
104
104
|
const matches = text.match(/\{[\w][\w]*\}/g) || []
|
|
@@ -71,6 +71,7 @@ ENTITY_IS_READ_ONLY=Entity "{0}" is read-only
|
|
|
71
71
|
ENTITY_IS_NOT_CRUD=Entity "{0}" is not {1}
|
|
72
72
|
ENTITY_IS_NOT_CRUD_VIA_NAVIGATION=Entity "{0}" is not {1} via association "{2}"
|
|
73
73
|
ENTITY_IS_AUTOEXPOSED=Entity "{0}" is not explicitely exposed as part of the service
|
|
74
|
+
EXPAND_IS_RESTRICTED=Navigation property "{0}" is not allowed for expand operation
|
|
74
75
|
|
|
75
76
|
# rest protocol adapter
|
|
76
77
|
INVALID_RESOURCE="{0}" is not a valid resource
|
|
@@ -87,3 +88,6 @@ CRUD_VIA_NAVIGATION_NOT_SUPPORTED=CRUD via navigations is not yet supported
|
|
|
87
88
|
# draft
|
|
88
89
|
DRAFT_ALREADY_EXISTS=A draft for this entity already exists
|
|
89
90
|
DRAFT_LOCKED_BY_ANOTHER_USER=The entity is locked by another user
|
|
91
|
+
|
|
92
|
+
# singleton
|
|
93
|
+
SINGLETON_NOT_NULLABLE=The singleton entity is not nullable
|
|
@@ -33,15 +33,22 @@ const _getBacklinkNameFromOnCond = element => {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const isBacklink = (element, parent, checkContained) => {
|
|
36
|
+
const isBacklink = (element, parent, checkContained, backLinkName) => {
|
|
37
37
|
if (!element._isAssociationStrict) return false
|
|
38
38
|
if (!parent || !(element.keys || element.on)) return false
|
|
39
39
|
if (element.target !== parent.name) return false
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const _isBackLink = parentElement =>
|
|
42
|
+
(!checkContained || parentElement._isContained) && _getBacklinkNameFromOnCond(parentElement) === element.name
|
|
43
|
+
|
|
44
|
+
if (backLinkName) {
|
|
45
|
+
const parentElement = parent.elements[backLinkName]
|
|
46
|
+
return parentElement.isAssociation && _isBackLink(parentElement)
|
|
47
|
+
}
|
|
48
|
+
for (const parentElementName in parent.elements) {
|
|
49
|
+
const parentElement = parent.elements[parentElementName]
|
|
50
|
+
if (!parentElement.isAssociation) continue
|
|
51
|
+
if (_isBackLink(parentElement)) return true
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
return false
|
|
@@ -12,6 +12,11 @@ const getEntityNameFromDeleteCQN = cqn => {
|
|
|
12
12
|
return from
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
const getEntityNameFromUpdateCQN = cqn => {
|
|
16
|
+
return (cqn.UPDATE.entity.ref && cqn.UPDATE.entity.ref[0]) || cqn.UPDATE.entity.name || cqn.UPDATE.entity
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
module.exports = {
|
|
16
|
-
getEntityNameFromDeleteCQN
|
|
20
|
+
getEntityNameFromDeleteCQN,
|
|
21
|
+
getEntityNameFromUpdateCQN
|
|
17
22
|
}
|