@sap/cds 6.4.1 → 6.6.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 +79 -6
- package/README.md +5 -0
- package/apis/cqn.d.ts +14 -3
- package/apis/ql.d.ts +8 -8
- package/apis/services.d.ts +37 -65
- package/apis/test.d.ts +7 -0
- package/bin/build/buildTaskEngine.js +9 -14
- package/bin/build/buildTaskFactory.js +1 -1
- package/bin/build/buildTaskHandler.js +3 -14
- package/bin/build/index.js +8 -2
- package/bin/build/provider/buildTaskProviderInternal.js +18 -13
- package/bin/build/provider/fiori/index.js +5 -10
- package/bin/build/provider/hana/2migration.js +11 -2
- package/bin/build/provider/hana/index.js +17 -14
- package/bin/build/provider/hana/template/.hdiconfig-hanacloud +137 -0
- package/bin/build/provider/hana/template/package.json +3 -0
- package/bin/build/provider/mtx/resourcesTarBuilder.js +12 -3
- package/bin/build/provider/mtx-extension/index.js +57 -37
- package/bin/build/provider/mtx-sidecar/index.js +1 -1
- package/bin/build/util.js +18 -1
- package/bin/cds.js +1 -5
- package/bin/deploy/to-hana/hana.js +10 -3
- package/bin/serve.js +36 -20
- package/common.cds +7 -0
- package/lib/auth/jwt-auth.js +8 -7
- package/lib/compile/for/lean_drafts.js +55 -6
- package/lib/compile/minify.js +3 -3
- package/lib/dbs/cds-deploy.js +18 -17
- package/lib/env/cds-requires.js +1 -1
- package/lib/env/defaults.js +5 -1
- package/lib/env/schemas/cds-rc.json +74 -3
- package/lib/index.js +4 -2
- package/lib/lazy.js +6 -8
- package/lib/log/cds-error.js +2 -2
- package/lib/ql/Whereable.js +22 -11
- package/lib/ql/cds-ql.js +1 -1
- package/lib/req/cds-context.js +3 -3
- package/lib/req/response.js +8 -3
- package/lib/req/user.js +12 -2
- package/lib/srv/bindings.js +1 -2
- package/lib/srv/cds-serve.js +2 -1
- package/lib/srv/middlewares/trace.js +31 -15
- package/lib/srv/protocols/odata-v2-proxy.js +8 -8
- package/lib/srv/srv-handlers.js +26 -7
- package/lib/srv/srv-methods.js +2 -2
- package/lib/srv/srv-models.js +8 -3
- package/lib/utils/cds-test.js +7 -5
- package/lib/utils/cds-utils.js +3 -1
- package/lib/utils/tar.js +6 -3
- package/libx/_runtime/auth/strategies/JWT.js +1 -0
- package/libx/_runtime/auth/strategies/ias-auth.js +3 -2
- package/libx/_runtime/auth/strategies/mock.js +12 -1
- package/libx/_runtime/auth/strategies/xssecUtils.js +7 -8
- package/libx/_runtime/auth/strategies/xsuaa.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +8 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +11 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +8 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +14 -14
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +1 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ResourceJsonSerializer.js +3 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/UriHelper.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +3 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +7 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +0 -3
- package/libx/_runtime/cds-services/services/Service.js +11 -19
- package/libx/_runtime/cds-services/services/utils/columns.js +42 -40
- package/libx/_runtime/cds-services/util/assert.js +7 -1
- package/libx/_runtime/common/code-ext/WorkerReq.js +81 -0
- package/libx/_runtime/common/code-ext/config.js +13 -0
- package/libx/_runtime/common/code-ext/execute.js +113 -0
- package/libx/_runtime/common/code-ext/handlers.js +49 -0
- package/libx/_runtime/common/code-ext/worker.js +40 -0
- package/libx/_runtime/common/code-ext/workerQuery.js +45 -0
- package/libx/_runtime/common/code-ext/workerQueryExecutor.js +36 -0
- package/libx/_runtime/common/composition/data.js +5 -2
- package/libx/_runtime/common/composition/tree.js +2 -0
- package/libx/_runtime/common/generic/auth/restrict.js +1 -1
- package/libx/_runtime/common/generic/crud.js +4 -0
- package/libx/_runtime/common/generic/etag.js +3 -1
- package/libx/_runtime/common/generic/input.js +12 -14
- package/libx/_runtime/common/i18n/index.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +47 -22
- package/libx/_runtime/common/utils/path.js +5 -26
- package/libx/_runtime/common/utils/search2cqn4sql.js +16 -9
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +19 -13
- package/libx/_runtime/db/data-conversion/post-processing.js +1 -1
- package/libx/_runtime/db/expand/expandCQNToJoin.js +7 -4
- package/libx/_runtime/db/expand/rawToExpanded.js +3 -2
- package/libx/_runtime/db/generic/input.js +2 -2
- package/libx/_runtime/db/generic/integrity.js +1 -0
- package/libx/_runtime/db/generic/virtual.js +1 -0
- package/libx/_runtime/db/query/read.js +3 -2
- package/libx/_runtime/db/utils/localized.js +1 -1
- package/libx/_runtime/fiori/generic/activate.js +7 -1
- package/libx/_runtime/fiori/generic/before.js +9 -1
- package/libx/_runtime/fiori/generic/edit.js +8 -1
- package/libx/_runtime/fiori/generic/new.js +2 -0
- package/libx/_runtime/fiori/generic/patch.js +2 -0
- package/libx/_runtime/fiori/generic/prepare.js +2 -0
- package/libx/_runtime/fiori/generic/read.js +16 -5
- package/libx/_runtime/fiori/generic/readOverDraft.js +2 -0
- package/libx/_runtime/fiori/lean-draft.js +505 -241
- package/libx/_runtime/fiori/utils/delete.js +2 -0
- package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -5
- package/libx/_runtime/hana/pool.js +1 -1
- package/libx/_runtime/hana/search2cqn4sql.js +51 -51
- package/libx/_runtime/messaging/Outbox.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -0
- package/libx/_runtime/messaging/enterprise-messaging.js +2 -6
- package/libx/_runtime/messaging/file-based.js +1 -2
- package/libx/_runtime/messaging/outbox/OutboxRunner.js +1 -1
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/messaging/service.js +0 -1
- package/libx/_runtime/remote/Service.js +1 -0
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +19 -3
- package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +0 -18
- package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +0 -18
- package/libx/_runtime/sqlite/customBuilder/CustomSelectBuilder.js +0 -24
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -1
- package/libx/_runtime/sqlite/customBuilder/index.js +47 -32
- package/libx/odata/afterburner.js +23 -8
- package/libx/odata/cqn2odata.js +1 -1
- package/libx/odata/grammar.pegjs +3 -4
- package/libx/odata/index.js +5 -1
- package/libx/odata/parseToCqn.js +3 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +58 -1
- package/libx/rest/middleware/parse.js +26 -4
- package/package.json +1 -1
- package/server.js +1 -1
- package/libx/_runtime/sqlite/customBuilder/CustomDeleteBuilder.js +0 -17
- package/libx/_runtime/sqlite/customBuilder/CustomReferenceBuilder.js +0 -11
- package/libx/_runtime/sqlite/customBuilder/CustomUpdateBuilder.js +0 -17
- /package/bin/build/provider/hana/template/{.hdiconfig → .hdiconfig-haas} +0 -0
|
@@ -34,6 +34,8 @@ const _validate = (activeResult, draftResult, req, IsActiveEntity) => {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const deleteDraft = async (req, srv, includingActive = false) => {
|
|
37
|
+
if (!cds.db) req.reject('NO_DATABASE_CONNECTION')
|
|
38
|
+
|
|
37
39
|
const dbtx = cds.tx(req)
|
|
38
40
|
const definitions = srv.model.definitions
|
|
39
41
|
|
|
@@ -4,6 +4,11 @@ const LOG = cds.log('hana|db|sql')
|
|
|
4
4
|
const SelectBuilder = require('../../db/sql-builder').SelectBuilder
|
|
5
5
|
|
|
6
6
|
class CustomSelectBuilder extends SelectBuilder {
|
|
7
|
+
constructor(obj, options, csn) {
|
|
8
|
+
super(obj, options, csn)
|
|
9
|
+
// $searchUsingContains property is set in the sub SELECT of the query, optimized search case
|
|
10
|
+
if (this._obj?._$searchUsingContains) this._options.$searchUsingContains = this._obj._$searchUsingContains
|
|
11
|
+
}
|
|
7
12
|
get FunctionBuilder() {
|
|
8
13
|
const FunctionBuilder = require('./CustomFunctionBuilder')
|
|
9
14
|
Object.defineProperty(this, 'FunctionBuilder', { value: FunctionBuilder })
|
|
@@ -28,11 +33,6 @@ class CustomSelectBuilder extends SelectBuilder {
|
|
|
28
33
|
return SelectBuilder
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
getDefaultOptions() {
|
|
32
|
-
const options = { $searchUsingContains: !!this._obj.SELECT._$searchUsingContains }
|
|
33
|
-
return { ...super.getDefaultOptions(), ...options }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
36
|
_val(obj) {
|
|
37
37
|
if (typeof obj.val === 'boolean') return { sql: obj.val ? 'true' : 'false', values: [] }
|
|
38
38
|
return super._val(obj)
|
|
@@ -79,7 +79,7 @@ async function credentials4(tenant, db) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
if (cds.xt?.serviceManager && !cds.env.requires['cds.xt.DeploymentService']?.['old-instance-manager']) {
|
|
82
|
-
return (await db._instance_manager.get(tenant)).credentials
|
|
82
|
+
return (await db._instance_manager.get(tenant, { disableCache: true })).credentials
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
return new Promise((resolve, reject) => {
|
|
@@ -1,8 +1,25 @@
|
|
|
1
|
+
const cds = require('../cds')
|
|
1
2
|
const { computeColumnsToBeSearched } = require('../cds-services/services/utils/columns')
|
|
2
3
|
const searchToLike = require('../common/utils/searchToLike')
|
|
3
4
|
const { isContainsPredicateSupported, search2Contains } = require('./search2Contains')
|
|
4
5
|
const { addAliasToExpression } = require('../db/utils/generateAliases')
|
|
5
|
-
|
|
6
|
+
const targetAlias = 'Target'
|
|
7
|
+
const textsAlias = 'Texts'
|
|
8
|
+
const _generateKeysWhereCondition = (entity, alias1, alias2) => {
|
|
9
|
+
const keys = Object.keys(entity.keys).filter(k => !entity.keys[k].isAssociation)
|
|
10
|
+
const where = []
|
|
11
|
+
keys.forEach(key => {
|
|
12
|
+
if (where.length > 0) where.push('and')
|
|
13
|
+
where.push({ ref: [alias1, key] }, '=', { ref: [alias2, key] })
|
|
14
|
+
})
|
|
15
|
+
return where
|
|
16
|
+
}
|
|
17
|
+
const _generateContainsColumns = (columns, entity) => {
|
|
18
|
+
const columnsTarget = addAliasToExpression(columns, targetAlias)
|
|
19
|
+
const columns2SearchText = columns.filter(col => col.ref && entity.elements[col.ref[col.ref.length - 1]].localized)
|
|
20
|
+
const columnsText = addAliasToExpression(columns2SearchText, textsAlias)
|
|
21
|
+
return [...columnsTarget, ...columnsText]
|
|
22
|
+
}
|
|
6
23
|
/**
|
|
7
24
|
* Computes a CQN expression for a search query.
|
|
8
25
|
*
|
|
@@ -24,67 +41,50 @@ const search2cqn4sql = (query, entity, options) => {
|
|
|
24
41
|
const cqnSearchPhrase = query.SELECT.search
|
|
25
42
|
if (!cqnSearchPhrase) return query
|
|
26
43
|
|
|
27
|
-
let { columns: columns2Search = computeColumnsToBeSearched(query, entity), locale } = options
|
|
28
44
|
const localizedAssociation = entity.associations?.localized
|
|
29
|
-
|
|
30
|
-
// Resolve localized data at runtime if the localized association is defined for the target entity.
|
|
31
|
-
// Notice that if the localized association is defined, there should be at least one localized element.
|
|
32
45
|
if (localizedAssociation) {
|
|
33
|
-
|
|
46
|
+
let { columns: columns2Search = computeColumnsToBeSearched(query, entity), locale } = options
|
|
47
|
+
const viewAlias = query.SELECT.from.as ? query.SELECT.from.as : 'LocalizedView'
|
|
48
|
+
if (!query.SELECT.from.as) {
|
|
49
|
+
_addAliasToQuery(query, viewAlias)
|
|
50
|
+
}
|
|
34
51
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
onCondition
|
|
52
|
+
const subQuery = cds.ql.SELECT.from(entity.name).columns(1)
|
|
53
|
+
subQuery.SELECT.from.as = targetAlias
|
|
54
|
+
const onCondition = _generateKeysWhereCondition(entity, targetAlias, textsAlias)
|
|
55
|
+
onCondition.push('and', { ref: [textsAlias, 'locale'] }, '=', { val: locale || "SESSION_CONTEXT('LOCALE')" })
|
|
38
56
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
57
|
+
// left outer join the target table with the _texts table (the _texts table contains the translated texts)
|
|
58
|
+
subQuery.leftJoin(localizedAssociation.target, textsAlias).on(onCondition)
|
|
59
|
+
// add condition for equal keys of target table and localized view
|
|
60
|
+
subQuery.where(_generateKeysWhereCondition(entity, targetAlias, viewAlias))
|
|
61
|
+
const containsColumns = _generateContainsColumns(columns2Search, entity)
|
|
62
|
+
let expression
|
|
63
|
+
if (isContainsPredicateSupported(query, entity, columns2Search)) {
|
|
64
|
+
// generate CQN expression with `CONTAINS` predicate for the columns from the target and text table
|
|
65
|
+
expression = search2Contains(cqnSearchPhrase, containsColumns)
|
|
66
|
+
Object.defineProperty(subQuery, '_$searchUsingContains', { value: true, enumerable: true })
|
|
67
|
+
} else {
|
|
68
|
+
expression = searchToLike(cqnSearchPhrase, containsColumns)
|
|
69
|
+
}
|
|
42
70
|
|
|
43
|
-
|
|
44
|
-
columns2Search = _addAliasToQuery(query, entity, columns2Search)
|
|
71
|
+
subQuery.where(expression)
|
|
45
72
|
|
|
46
|
-
// suppress the localize handler from redirecting the
|
|
47
|
-
Object.defineProperty(
|
|
48
|
-
|
|
73
|
+
// suppress the localize handler from redirecting the subQuery's target to the localized view
|
|
74
|
+
Object.defineProperty(subQuery, '_suppressLocalization', { value: true })
|
|
75
|
+
query.where('exists', subQuery)
|
|
49
76
|
|
|
50
|
-
|
|
51
|
-
let expression
|
|
52
|
-
|
|
53
|
-
if (useContains) {
|
|
54
|
-
expression = search2Contains(cqnSearchPhrase, columns2Search)
|
|
55
|
-
Object.defineProperty(query.SELECT, '_$searchUsingContains', { value: true, enumerable: true })
|
|
56
|
-
} else {
|
|
57
|
-
// No CONTAINS optimization possible. The search implementation for localized
|
|
58
|
-
// texts falls back to the LIKE predicate.
|
|
59
|
-
expression = searchToLike(cqnSearchPhrase, columns2Search)
|
|
77
|
+
return query
|
|
60
78
|
}
|
|
61
|
-
|
|
62
|
-
// REVISIT: find out here if where or having must be used
|
|
63
|
-
query._aggregated || /* if new parser */ query.SELECT.groupBy ? query.having(expression) : query.where(expression)
|
|
64
|
-
return query
|
|
65
79
|
}
|
|
66
80
|
|
|
67
|
-
|
|
68
|
-
// therefore add the table/entity name (as a preceding element) to the columns ref
|
|
69
|
-
// to prevent a SQL ambiguity error.
|
|
70
|
-
const _addAliasToQuery = (query, entity, columnsToBeSearched) => {
|
|
81
|
+
const _addAliasToQuery = (query, alias) => {
|
|
71
82
|
const SELECT = query.SELECT
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const localizedElement = !!elements[columnName].localized
|
|
78
|
-
const targetEntityName = localizedElement ? localizedEntityName : entityName
|
|
79
|
-
return targetEntityName
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
SELECT.columns = addAliasToExpression(SELECT.columns, getEntityName)
|
|
83
|
-
columnsToBeSearched = addAliasToExpression(columnsToBeSearched, getEntityName)
|
|
84
|
-
SELECT.groupBy = addAliasToExpression(SELECT.groupBy, getEntityName)
|
|
85
|
-
SELECT.orderBy = addAliasToExpression(SELECT.orderBy, getEntityName)
|
|
86
|
-
SELECT.where = addAliasToExpression(SELECT.where, getEntityName)
|
|
87
|
-
return columnsToBeSearched
|
|
83
|
+
SELECT.from.as = alias
|
|
84
|
+
SELECT.columns = addAliasToExpression(SELECT.columns, alias)
|
|
85
|
+
SELECT.groupBy = addAliasToExpression(SELECT.groupBy, alias)
|
|
86
|
+
SELECT.orderBy = addAliasToExpression(SELECT.orderBy, alias)
|
|
87
|
+
SELECT.where = addAliasToExpression(SELECT.where, alias)
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
module.exports = search2cqn4sql
|
|
@@ -40,7 +40,7 @@ class OutboxService extends cds.Service {
|
|
|
40
40
|
} catch (e) {
|
|
41
41
|
LOG.error('Emit failed', { event: msg.event, cause: e })
|
|
42
42
|
// opts.crashOnError is not official!!!
|
|
43
|
-
if (isUnrecoverable(this, e) && outboxOpts.crashOnError !== false)
|
|
43
|
+
if (isUnrecoverable(this, e) && outboxOpts.crashOnError !== false) cds.exit(1)
|
|
44
44
|
}
|
|
45
45
|
})
|
|
46
46
|
return
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const cds = require('../../cds')
|
|
2
2
|
const _transform = o => ({ subdomain: o.subscribedSubdomain, tenant: o.subscribedTenantId })
|
|
3
3
|
|
|
4
|
+
// REVISIT: Looks ugly -> can we simplify that?
|
|
4
5
|
const getTenantInfo = async tenant => {
|
|
5
6
|
const provisioningServiceName = cds.mtx ? 'ProvisioningService' : 'cds.xt.SaasProvisioningService'
|
|
6
7
|
const primaryKey = cds.mtx ? 'ID' : 'subscribedTenantId'
|
|
@@ -85,8 +85,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
85
85
|
const deploymentSrv = await cds.connect.to('cds.xt.DeploymentService')
|
|
86
86
|
const provisioningSrv = await cds.connect.to('cds.xt.SaasProvisioningService')
|
|
87
87
|
deploymentSrv.impl(() => {
|
|
88
|
-
deploymentSrv.
|
|
89
|
-
const res = await next()
|
|
88
|
+
deploymentSrv.after('subscribe', async (res, req) => {
|
|
90
89
|
const { tenant } = req.data
|
|
91
90
|
let subdomain
|
|
92
91
|
try {
|
|
@@ -98,9 +97,8 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
98
97
|
}
|
|
99
98
|
const management = await this.getManagement(subdomain).waitUntilReady()
|
|
100
99
|
await management.deploy()
|
|
101
|
-
return res
|
|
102
100
|
})
|
|
103
|
-
deploymentSrv.
|
|
101
|
+
deploymentSrv.before('unsubscribe', async req => {
|
|
104
102
|
const { tenant } = req.data
|
|
105
103
|
let subdomain
|
|
106
104
|
try {
|
|
@@ -110,14 +108,12 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
110
108
|
this.LOG.error("'unsubscribe' is not yet implemented for @sap/cds-mtxs")
|
|
111
109
|
throw e
|
|
112
110
|
}
|
|
113
|
-
const res = await next()
|
|
114
111
|
try {
|
|
115
112
|
const management = await this.getManagement(subdomain).waitUntilReady()
|
|
116
113
|
await management.undeploy()
|
|
117
114
|
} catch (error) {
|
|
118
115
|
this.LOG.error('Failed to delete messaging artifacts for subdomain', subdomain, '(', error, ')')
|
|
119
116
|
}
|
|
120
|
-
return res
|
|
121
117
|
})
|
|
122
118
|
})
|
|
123
119
|
provisioningSrv.impl(() => {
|
|
@@ -71,8 +71,7 @@ class FileBasedMessaging extends MessagingService {
|
|
|
71
71
|
unlock(this.file)
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
this.watching = setInterval(watcher, this.options.interval || 500)
|
|
75
|
-
cds.on('shutdown', () => this.disconnect())
|
|
74
|
+
this.watching = setInterval(watcher, this.options.interval || 500).unref()
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
disconnect() {
|
|
@@ -201,7 +201,7 @@ const processMessages = async (service, tenant, _opts = {}) => {
|
|
|
201
201
|
}
|
|
202
202
|
}, config)
|
|
203
203
|
spawn.on('done', () => {
|
|
204
|
-
if (letAppCrash)
|
|
204
|
+
if (letAppCrash) cds.exit(1)
|
|
205
205
|
outboxRunner.end({ name, tenant }, () => processMessages(service, tenant, opts))
|
|
206
206
|
})
|
|
207
207
|
})
|
|
@@ -4,7 +4,6 @@ const OutboxService = require('./Outbox')
|
|
|
4
4
|
const ExtendedModels = require('../../../lib/srv/srv-models')
|
|
5
5
|
|
|
6
6
|
const appId = require('./common-utils/appId')
|
|
7
|
-
const { context } = require('../../../lib/core/classes')
|
|
8
7
|
|
|
9
8
|
const _topic = declared => declared['@topic'] || declared.name
|
|
10
9
|
|
|
@@ -9,7 +9,7 @@ function sqliteConvertDraftAdminPathExpression(req) {
|
|
|
9
9
|
return
|
|
10
10
|
let hasDraftAdminPathExpression = false
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const tableId = req.query.SELECT.from.as || req.query.SELECT.from.ref[0]
|
|
13
13
|
|
|
14
14
|
const _modifyCols = cols => {
|
|
15
15
|
return cols.map(col => {
|
|
@@ -19,7 +19,7 @@ function sqliteConvertDraftAdminPathExpression(req) {
|
|
|
19
19
|
newCol.ref = [...col.ref]
|
|
20
20
|
newCol.ref[0] = 'filterAdmin'
|
|
21
21
|
return newCol
|
|
22
|
-
} else if (col.ref?.length > 1 &&
|
|
22
|
+
} else if (col.ref?.length > 1 && tableId && col.ref[0] === tableId && col.ref[1] === 'DraftAdministrativeData') {
|
|
23
23
|
hasDraftAdminPathExpression = true
|
|
24
24
|
const newCol = { ...col }
|
|
25
25
|
newCol.ref = [...col.ref]
|
|
@@ -40,15 +40,31 @@ function sqliteConvertDraftAdminPathExpression(req) {
|
|
|
40
40
|
|
|
41
41
|
if (clone.SELECT.columns) clone.SELECT.columns = _modifyCols(req.query.SELECT.columns)
|
|
42
42
|
if (clone.SELECT.where) clone.SELECT.where = _modifyCols(req.query.SELECT.where)
|
|
43
|
+
if (clone.SELECT.orderBy) clone.SELECT.orderBy = _modifyCols(req.query.SELECT.orderBy)
|
|
44
|
+
if (clone.SELECT.groupBy) clone.SELECT.groupBy = _modifyCols(req.query.SELECT.groupBy)
|
|
45
|
+
|
|
46
|
+
const _addTableId = c => {
|
|
47
|
+
if (!c.ref || c.ref[0] === tableId || c.ref[0] === 'filterAdmin') return c
|
|
48
|
+
const newC = { ...c }
|
|
49
|
+
newC.ref = [...c.ref]
|
|
50
|
+
newC.ref.unshift(tableId)
|
|
51
|
+
return newC
|
|
52
|
+
}
|
|
43
53
|
|
|
44
54
|
if (hasDraftAdminPathExpression) {
|
|
45
55
|
clone
|
|
46
56
|
.join('DRAFT_DraftAdministrativeData', 'filterAdmin')
|
|
47
57
|
.on([
|
|
48
|
-
{ ref:
|
|
58
|
+
{ ref: tableId ? [tableId, 'DraftAdministrativeData_DraftUUID'] : ['DraftAdministrativeData_DraftUUID'] },
|
|
49
59
|
'=',
|
|
50
60
|
{ ref: ['filterAdmin', 'DraftUUID'] }
|
|
51
61
|
])
|
|
62
|
+
if (tableId) {
|
|
63
|
+
if (clone.SELECT.columns) clone.SELECT.columns = clone.SELECT.columns.map(_addTableId)
|
|
64
|
+
if (clone.SELECT.where) clone.SELECT.where = clone.SELECT.where.map(_addTableId)
|
|
65
|
+
if (clone.SELECT.orderBy) clone.SELECT.orderBy = clone.SELECT.orderBy.map(_addTableId)
|
|
66
|
+
if (clone.SELECT.groupBy) clone.SELECT.groupBy = clone.SELECT.groupBy.map(_addTableId)
|
|
67
|
+
}
|
|
52
68
|
req.query = clone
|
|
53
69
|
}
|
|
54
70
|
}
|
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
const ExpressionBuilder = require('../../db/sql-builder').ExpressionBuilder
|
|
2
2
|
|
|
3
3
|
class CustomExpressionBuilder extends ExpressionBuilder {
|
|
4
|
-
get ReferenceBuilder() {
|
|
5
|
-
const ReferenceBuilder = require('./CustomReferenceBuilder')
|
|
6
|
-
Object.defineProperty(this, 'ReferenceBuilder', { value: ReferenceBuilder })
|
|
7
|
-
return ReferenceBuilder
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
get SelectBuilder() {
|
|
11
|
-
const SelectBuilder = require('./CustomSelectBuilder')
|
|
12
|
-
Object.defineProperty(this, 'SelectBuilder', { value: SelectBuilder })
|
|
13
|
-
return SelectBuilder
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
get FunctionBuilder() {
|
|
17
|
-
const FunctionBuilder = require('./CustomFunctionBuilder')
|
|
18
|
-
Object.defineProperty(this, 'FunctionBuilder', { value: FunctionBuilder })
|
|
19
|
-
return FunctionBuilder
|
|
20
|
-
}
|
|
21
|
-
|
|
22
4
|
_addListToOutputObj(list) {
|
|
23
5
|
this._outputObj.sql.push('(')
|
|
24
6
|
|
|
@@ -14,24 +14,6 @@ const STANDAD_FUNCTIONS_MAP = ['locate', 'substring', 'to_date', 'to_time'].redu
|
|
|
14
14
|
}, {})
|
|
15
15
|
|
|
16
16
|
class CustomFunctionBuilder extends FunctionBuilder {
|
|
17
|
-
get ExpressionBuilder() {
|
|
18
|
-
const ExpressionBuilder = require('./CustomExpressionBuilder')
|
|
19
|
-
Object.defineProperty(this, 'ExpressionBuilder', { value: ExpressionBuilder })
|
|
20
|
-
return ExpressionBuilder
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
get ReferenceBuilder() {
|
|
24
|
-
const ReferenceBuilder = require('./CustomReferenceBuilder')
|
|
25
|
-
Object.defineProperty(this, 'ReferenceBuilder', { value: ReferenceBuilder })
|
|
26
|
-
return ReferenceBuilder
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
get SelectBuilder() {
|
|
30
|
-
const SelectBuilder = require('./CustomSelectBuilder')
|
|
31
|
-
Object.defineProperty(this, 'SelectBuilder', { value: SelectBuilder })
|
|
32
|
-
return SelectBuilder
|
|
33
|
-
}
|
|
34
|
-
|
|
35
17
|
_handleFunction() {
|
|
36
18
|
const functionName = this._functionName()
|
|
37
19
|
const args = this._functionArgs()
|
|
@@ -1,30 +1,6 @@
|
|
|
1
1
|
const SelectBuilder = require('../../db/sql-builder').SelectBuilder
|
|
2
2
|
|
|
3
3
|
class CustomSelectBuilder extends SelectBuilder {
|
|
4
|
-
get ReferenceBuilder() {
|
|
5
|
-
const ReferenceBuilder = require('./CustomReferenceBuilder')
|
|
6
|
-
Object.defineProperty(this, 'ReferenceBuilder', { value: ReferenceBuilder })
|
|
7
|
-
return ReferenceBuilder
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
get ExpressionBuilder() {
|
|
11
|
-
const ExpressionBuilder = require('./CustomExpressionBuilder')
|
|
12
|
-
Object.defineProperty(this, 'ExpressionBuilder', { value: ExpressionBuilder })
|
|
13
|
-
return ExpressionBuilder
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
get FunctionBuilder() {
|
|
17
|
-
const FunctionBuilder = require('./CustomFunctionBuilder')
|
|
18
|
-
Object.defineProperty(this, 'FunctionBuilder', { value: FunctionBuilder })
|
|
19
|
-
return FunctionBuilder
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
get SelectBuilder() {
|
|
23
|
-
const SelectBuilder = require('./CustomSelectBuilder')
|
|
24
|
-
Object.defineProperty(this, 'SelectBuilder', { value: SelectBuilder })
|
|
25
|
-
return SelectBuilder
|
|
26
|
-
}
|
|
27
|
-
|
|
28
4
|
getCollate() {
|
|
29
5
|
return 'COLLATE ' + this.getCollatingSequence()
|
|
30
6
|
}
|
|
@@ -27,7 +27,8 @@ class CustomUpsertBuilder extends InsertBuilder {
|
|
|
27
27
|
|
|
28
28
|
columns.forEach(col => {
|
|
29
29
|
const col_ = col.replace(/\./g, '_')
|
|
30
|
-
|
|
30
|
+
const sqlColumn = this._quoteElement(col_)
|
|
31
|
+
if (!keys.includes(col_)) updates.push(`${sqlColumn}=excluded.${sqlColumn}`)
|
|
31
32
|
})
|
|
32
33
|
const conflict = updates.length
|
|
33
34
|
? ` ON CONFLICT(${keys}) DO UPDATE SET ` + updates.join(', ')
|
|
@@ -1,34 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
class ReferenceBuilder extends require('../../db/sql-builder').ReferenceBuilder {}
|
|
2
|
+
const ExpressionBuilder = require('./CustomExpressionBuilder')
|
|
3
|
+
const FunctionBuilder = require('./CustomFunctionBuilder')
|
|
4
|
+
const SelectBuilder = require('./CustomSelectBuilder')
|
|
5
|
+
const UpsertBuilder = require('./CustomUpsertBuilder')
|
|
6
|
+
class UpdateBuilder extends require('../../db/sql-builder').UpdateBuilder {}
|
|
7
|
+
class DeleteBuilder extends require('../../db/sql-builder').DeleteBuilder {}
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
ReferenceBuilder: extend(ReferenceBuilder).with({
|
|
11
|
+
FunctionBuilder
|
|
12
|
+
}),
|
|
13
|
+
ExpressionBuilder: extend(ExpressionBuilder).with({
|
|
14
|
+
ReferenceBuilder,
|
|
15
|
+
SelectBuilder,
|
|
16
|
+
FunctionBuilder
|
|
17
|
+
}),
|
|
18
|
+
FunctionBuilder: extend(FunctionBuilder).with({
|
|
19
|
+
ExpressionBuilder,
|
|
20
|
+
ReferenceBuilder,
|
|
21
|
+
SelectBuilder
|
|
22
|
+
}),
|
|
23
|
+
SelectBuilder: extend(SelectBuilder).with({
|
|
24
|
+
ExpressionBuilder,
|
|
25
|
+
ReferenceBuilder,
|
|
26
|
+
FunctionBuilder,
|
|
27
|
+
SelectBuilder
|
|
28
|
+
}),
|
|
29
|
+
UpsertBuilder,
|
|
30
|
+
UpdateBuilder: extend(UpdateBuilder).with({
|
|
31
|
+
ReferenceBuilder,
|
|
32
|
+
ExpressionBuilder
|
|
33
|
+
}),
|
|
34
|
+
DeleteBuilder: extend(DeleteBuilder).with({
|
|
35
|
+
ReferenceBuilder,
|
|
36
|
+
ExpressionBuilder
|
|
37
|
+
})
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
function extend(clazz) {
|
|
41
|
+
return {
|
|
42
|
+
with(properties) {
|
|
43
|
+
for (let p in properties) {
|
|
44
|
+
Object.defineProperty(clazz.prototype, p, { value: properties[p] })
|
|
45
|
+
}
|
|
46
|
+
return clazz
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -189,7 +189,7 @@ function _convertVal(element, value) {
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
function _processSegments(from, model, namespace) {
|
|
192
|
+
function _processSegments(from, model, namespace, cqn) {
|
|
193
193
|
const { ref } = from
|
|
194
194
|
|
|
195
195
|
let current = model
|
|
@@ -284,11 +284,14 @@ function _processSegments(from, model, namespace) {
|
|
|
284
284
|
incompleteKeys = ref[i].where ? false : i === ref.length - 1 || one ? false : true
|
|
285
285
|
|
|
286
286
|
if (incompleteKeys && action) {
|
|
287
|
-
if (
|
|
287
|
+
if (
|
|
288
|
+
action['@cds.odata.bindingparameter.collection'] ||
|
|
289
|
+
(action.params && Object.values(action.params).some(e => e?.items?.type === '$self'))
|
|
290
|
+
) {
|
|
288
291
|
incompleteKeys = false
|
|
289
292
|
} else {
|
|
290
|
-
const msg = `
|
|
291
|
-
throw Object.assign(new Error(msg), { statusCode:
|
|
293
|
+
const msg = `"${action.name}" must be called on a single instance of "${current.name}".`
|
|
294
|
+
throw Object.assign(new Error(msg), { statusCode: 400 })
|
|
292
295
|
}
|
|
293
296
|
}
|
|
294
297
|
|
|
@@ -326,12 +329,24 @@ function _processSegments(from, model, namespace) {
|
|
|
326
329
|
_resolveAliasesInXpr(ref[i].where, current)
|
|
327
330
|
_processWhere(ref[i].where, current)
|
|
328
331
|
}
|
|
329
|
-
} else if (current._isStructured) {
|
|
330
|
-
// > nested property
|
|
331
|
-
one = true
|
|
332
332
|
} else {
|
|
333
333
|
// > property
|
|
334
|
+
// we do not support navigations from properties yet
|
|
334
335
|
one = true
|
|
336
|
+
// if the last segment is a property, it must be removed and pushed to columns
|
|
337
|
+
target = target || _getDefinition(model, ref[0].id, namespace)
|
|
338
|
+
if (Object.keys(target.elements).includes(current.name)) {
|
|
339
|
+
if (!cqn.SELECT.columns) cqn.SELECT.columns = []
|
|
340
|
+
cqn.SELECT.columns.push({ ref: ref.slice(i) })
|
|
341
|
+
// we need the keys to generate the correct @odata.context
|
|
342
|
+
for (const key in target.keys || {}) {
|
|
343
|
+
if (key !== 'IsActiveEntity' && !cqn.SELECT.columns.some(c => c.ref?.[0] === key))
|
|
344
|
+
cqn.SELECT.columns.push({ ref: [key] })
|
|
345
|
+
}
|
|
346
|
+
Object.defineProperty(cqn, '_propertyAccess', { value: current.name, enumerable: false })
|
|
347
|
+
from.ref.splice(i)
|
|
348
|
+
break
|
|
349
|
+
}
|
|
335
350
|
}
|
|
336
351
|
}
|
|
337
352
|
}
|
|
@@ -452,7 +467,7 @@ function _4service(service) {
|
|
|
452
467
|
/*
|
|
453
468
|
* key vs. path segments (/Books/1/author/books/2/...) and more
|
|
454
469
|
*/
|
|
455
|
-
const { one, current, target } = _processSegments(from, model, namespace)
|
|
470
|
+
const { one, current, target } = _processSegments(from, model, namespace, cqn)
|
|
456
471
|
|
|
457
472
|
if (cqn.SELECT.where) {
|
|
458
473
|
_processWhere(cqn.SELECT.where, root)
|
package/libx/odata/cqn2odata.js
CHANGED
package/libx/odata/grammar.pegjs
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
const n = Number(str)
|
|
41
41
|
return Number.isSafeInteger(n) ? n : str
|
|
42
42
|
}
|
|
43
|
+
const skipToken = options.skipToken
|
|
43
44
|
const standardBase64 =
|
|
44
45
|
options.standardBase64 ||
|
|
45
46
|
function (str) {
|
|
@@ -447,10 +448,8 @@
|
|
|
447
448
|
= val:integer { return val }
|
|
448
449
|
|
|
449
450
|
skiptoken
|
|
450
|
-
=
|
|
451
|
-
|
|
452
|
-
if (skiptoken) return
|
|
453
|
-
_setLimitOffset(val)
|
|
451
|
+
= skiptoken:skiptokenChars? {
|
|
452
|
+
skipToken(skiptoken, { SELECT })
|
|
454
453
|
}
|
|
455
454
|
|
|
456
455
|
skip
|
package/libx/odata/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const cds = require('../_runtime/cds'),
|
|
1
|
+
const cds = require('../_runtime/cds'),
|
|
2
|
+
{ decodeURIComponent } = cds.utils
|
|
2
3
|
const { SELECT } = cds.ql
|
|
3
4
|
|
|
4
5
|
const odata2cqn = require('./parser').parse
|
|
@@ -51,6 +52,8 @@ const enhanceCqn = (cqn, options) => {
|
|
|
51
52
|
// REVISIT: _target vs __target, i.e., pseudo csn vs actual csn
|
|
52
53
|
// DO NOT USE __target outside of libx/rest!!!
|
|
53
54
|
if (cqn.__target) query.__target = cqn.__target
|
|
55
|
+
if (cqn._propertyAccess)
|
|
56
|
+
Object.defineProperty(query, '_propertyAccess', { value: cqn._propertyAccess, enumerable: false })
|
|
54
57
|
return query
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -69,6 +72,7 @@ module.exports = {
|
|
|
69
72
|
options = options === 'strict' ? { strict } : options.strict ? { ...options, strict } : options
|
|
70
73
|
if (options.service) Object.assign(options, { minimal: true, afterburner: afterburner.for(options.service) })
|
|
71
74
|
options.safeNumber = safeNumber
|
|
75
|
+
options.skipToken = require('./utils').skipToken
|
|
72
76
|
|
|
73
77
|
let cqn
|
|
74
78
|
try {
|
package/libx/odata/parseToCqn.js
CHANGED
|
@@ -24,10 +24,10 @@ module.exports = (component, service, target, data, odataReq, upsert) => {
|
|
|
24
24
|
if (!one) cds.error('DELETE not allowed on collection', { code: 400 })
|
|
25
25
|
|
|
26
26
|
// eslint-disable-next-line no-case-declarations
|
|
27
|
-
const last = _target.ref && _target.ref[_target.ref.length - 1]
|
|
28
|
-
if (target.elements[last]) {
|
|
27
|
+
const last = query._propertyAccess || (_target.ref && _target.ref[_target.ref.length - 1])
|
|
28
|
+
if (target.elements[last] || target.elements[query._propertyAccess]) {
|
|
29
29
|
// delete simple property
|
|
30
|
-
const ref = { ref: _target.ref.slice(0, -1) }
|
|
30
|
+
const ref = { ref: query._propertyAccess ? _target.ref : _target.ref.slice(0, -1) }
|
|
31
31
|
return UPDATE(ref).data({ [last]: null })
|
|
32
32
|
} else {
|
|
33
33
|
return DELETE.from(_target)
|