@sap/cds 6.6.2 → 6.7.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 +72 -2
- package/README.md +1 -1
- package/apis/connect.d.ts +11 -4
- package/apis/core.d.ts +1 -1
- package/apis/csn.d.ts +1 -0
- package/apis/internal/inference.d.ts +15 -2
- package/apis/log.d.ts +10 -0
- package/apis/serve.d.ts +4 -9
- package/apis/services.d.ts +86 -19
- package/bin/build/buildTaskEngine.js +16 -42
- package/bin/build/constants.js +4 -2
- package/bin/build/provider/buildTaskProviderInternal.js +117 -85
- package/bin/build/provider/hana/index.js +6 -1
- package/bin/build/provider/mtx-extension/index.js +74 -34
- package/bin/build/provider/mtx-sidecar/index.js +3 -3
- package/bin/build/provider/nodejs/index.js +2 -2
- package/bin/build/util.js +63 -14
- package/bin/cds-serve.js +6 -0
- package/bin/cds.js +22 -4
- package/bin/deploy/to-hana/cfUtil.js +15 -1
- package/bin/mtx/in-cds.js +2 -9
- package/bin/plugins.js +31 -0
- package/bin/serve.js +12 -12
- package/lib/compile/etc/_localized.js +1 -1
- package/lib/compile/for/lean_drafts.js +23 -6
- package/lib/compile/for/nodejs.js +4 -1
- package/lib/compile/load.js +4 -2
- package/lib/core/index.js +35 -15
- package/lib/dbs/cds-deploy.js +129 -133
- package/lib/env/cds-env.js +25 -17
- package/lib/env/cds-requires.js +10 -40
- package/lib/env/compat.js +12 -0
- package/lib/env/defaults.js +17 -9
- package/lib/env/plugins.js +29 -0
- package/lib/env/schemas/cds-rc.json +14 -0
- package/lib/index.js +3 -0
- package/lib/log/cds-log.js +7 -4
- package/lib/ql/CREATE.js +1 -1
- package/lib/ql/DELETE.js +1 -1
- package/lib/ql/DROP.js +3 -3
- package/lib/ql/INSERT.js +1 -1
- package/lib/ql/Query.js +14 -6
- package/lib/ql/SELECT.js +8 -2
- package/lib/ql/UPDATE.js +1 -1
- package/lib/ql/Whereable.js +1 -1
- package/lib/ql/cds-ql.js +1 -9
- package/lib/req/cds-context.js +1 -4
- package/lib/req/request.js +63 -2
- package/lib/req/response.js +3 -2
- package/lib/srv/bindings.js +69 -71
- package/lib/srv/cds-connect.js +4 -1
- package/lib/srv/cds-serve.js +4 -0
- package/lib/srv/middlewares/index.js +37 -6
- package/lib/srv/protocols/_legacy.js +1 -1
- package/lib/srv/protocols/index.js +1 -1
- package/lib/srv/srv-api.js +4 -6
- package/lib/srv/srv-dispatch.js +4 -3
- package/lib/srv/srv-handlers.js +1 -1
- package/lib/srv/srv-methods.js +8 -2
- package/lib/utils/cds-test.js +4 -1
- package/libx/_runtime/audit/Service.js +8 -9
- package/libx/_runtime/audit/generic/personal/index.js +1 -1
- package/libx/_runtime/audit/generic/personal/utils.js +1 -1
- package/libx/_runtime/audit/utils/v2.js +17 -20
- package/libx/_runtime/auth/strategies/mock.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +5 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +4 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -1
- package/libx/_runtime/cds-services/services/Service.js +28 -1
- package/libx/_runtime/cds-services/util/assert.js +41 -65
- package/libx/_runtime/common/code-ext/WorkerPool.js +90 -0
- package/libx/_runtime/common/code-ext/WorkerReq.js +0 -4
- package/libx/_runtime/common/code-ext/execute.js +28 -18
- package/libx/_runtime/common/code-ext/handlers.js +5 -4
- package/libx/_runtime/common/code-ext/worker.js +45 -3
- package/libx/_runtime/common/code-ext/workerQueryExecutor.js +8 -7
- package/libx/_runtime/common/composition/delete.js +1 -1
- package/libx/_runtime/common/composition/update.js +3 -5
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/readOnly.js +5 -4
- package/libx/_runtime/common/generic/auth/restrict.js +7 -2
- package/libx/_runtime/common/generic/auth/utils.js +5 -2
- package/libx/_runtime/common/generic/crud.js +12 -1
- package/libx/_runtime/common/generic/etag.js +11 -3
- package/libx/_runtime/common/generic/input.js +8 -6
- package/libx/_runtime/common/generic/paging.js +25 -8
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/sorting.js +0 -1
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/common/utils/cqn.js +5 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +3 -3
- package/libx/_runtime/common/utils/resolveView.js +14 -10
- package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -3
- package/libx/_runtime/common/utils/templateProcessor.js +15 -17
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +18 -6
- package/libx/_runtime/db/Service.js +1 -0
- package/libx/_runtime/db/data-conversion/post-processing.js +0 -18
- package/libx/_runtime/db/expand/expand-v2.js +2 -2
- package/libx/_runtime/db/expand/rawToExpanded.js +6 -6
- package/libx/_runtime/db/generic/integrity.js +1 -1
- package/libx/_runtime/db/utils/columns.js +5 -5
- package/libx/_runtime/fiori/generic/activate.js +3 -3
- package/libx/_runtime/fiori/generic/edit.js +1 -1
- package/libx/_runtime/fiori/generic/new.js +4 -0
- package/libx/_runtime/fiori/lean-draft.js +178 -68
- package/libx/_runtime/hana/execute.js +3 -1
- package/libx/_runtime/hana/pool.js +10 -2
- package/libx/_runtime/hana/search2cqn4sql.js +1 -1
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +6 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/remote/Service.js +16 -13
- package/libx/_runtime/remote/utils/client.js +6 -1
- package/libx/_runtime/sqlite/Service.js +5 -59
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -2
- package/libx/_runtime/sqlite/execute.js +3 -1
- package/libx/_runtime/types/api.js +12 -3
- package/libx/odata/afterburner.js +38 -2
- package/libx/odata/cqn2odata.js +3 -2
- package/libx/odata/grammar.pegjs +5 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +1 -1
- package/libx/rest/RestAdapter.js +1 -1
- package/libx/rest/RestRequest.js +1 -0
- package/package.json +5 -2
- package/libx/_runtime/common/code-ext/workerQuery.js +0 -45
- package/libx/_runtime/common/constants/limit.js +0 -12
- package/libx/_runtime/common/utils/page.js +0 -39
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// REVISIT: Remove @sap/instance-manager compat with CDS 7
|
|
2
|
+
|
|
1
3
|
const cds = require('../cds')
|
|
2
4
|
const LOG = cds.log('pool|db')
|
|
3
5
|
|
|
@@ -15,7 +17,10 @@ function multiTenantServiceManager() {
|
|
|
15
17
|
if (e.code === 'MODULE_NOT_FOUND') return null
|
|
16
18
|
else throw e
|
|
17
19
|
}
|
|
18
|
-
|
|
20
|
+
const oldIm =
|
|
21
|
+
cds.requires.multitenancy?.['old-instance-manager'] ??
|
|
22
|
+
cds.env.requires?.['cds.xt.DeploymentService']?.['old-instance-manager']
|
|
23
|
+
return oldIm ? null : cds.xt?.serviceManager
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
function multiTenantInstanceManager(config = cds.env.requires.db) {
|
|
@@ -78,7 +83,10 @@ async function credentials4(tenant, db) {
|
|
|
78
83
|
: singleTenantInstanceManager(opts)
|
|
79
84
|
}
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
const oldIm =
|
|
87
|
+
cds.requires.multitenancy?.['old-instance-manager'] ??
|
|
88
|
+
cds.env.requires?.['cds.xt.DeploymentService']?.['old-instance-manager']
|
|
89
|
+
if (cds.xt?.serviceManager && !oldIm) {
|
|
82
90
|
return (await db._instance_manager.get(tenant, { disableCache: true })).credentials
|
|
83
91
|
}
|
|
84
92
|
|
|
@@ -6,7 +6,7 @@ const { addAliasToExpression } = require('../db/utils/generateAliases')
|
|
|
6
6
|
const targetAlias = 'Target'
|
|
7
7
|
const textsAlias = 'Texts'
|
|
8
8
|
const _generateKeysWhereCondition = (entity, alias1, alias2) => {
|
|
9
|
-
const keys = Object.keys(entity.keys).filter(k => !entity.keys[k].isAssociation)
|
|
9
|
+
const keys = Object.keys(entity.keys).filter(k => !entity.keys[k].isAssociation && !entity.keys[k].virtual)
|
|
10
10
|
const where = []
|
|
11
11
|
keys.forEach(key => {
|
|
12
12
|
if (where.length > 0) where.push('and')
|
|
@@ -31,7 +31,12 @@ const emit = ({ data, event: topic, headers = {} }, stream, prefix, LOG) =>
|
|
|
31
31
|
new Promise((resolve, reject) => {
|
|
32
32
|
LOG._info && LOG.info('Emit', { topic })
|
|
33
33
|
const message = { ...headers, data }
|
|
34
|
-
const payload = {
|
|
34
|
+
const payload = {
|
|
35
|
+
chunks: [Buffer.from(JSON.stringify(message))],
|
|
36
|
+
type: ['id', 'source', 'specversion', 'type'].every(el => el in headers)
|
|
37
|
+
? 'application/cloudevents+json'
|
|
38
|
+
: 'application/json'
|
|
39
|
+
}
|
|
35
40
|
const msg = {
|
|
36
41
|
done: resolve,
|
|
37
42
|
failed: e => {
|
|
@@ -82,6 +82,7 @@ class EnterpriseMessaging extends AMQPWebhookMessaging {
|
|
|
82
82
|
|
|
83
83
|
// New mtx based on @sap/cds-mtxs
|
|
84
84
|
async addMTXSHandlers() {
|
|
85
|
+
// REVISIT: Is that tested with MTX services in sidecar?
|
|
85
86
|
const deploymentSrv = await cds.connect.to('cds.xt.DeploymentService')
|
|
86
87
|
const provisioningSrv = await cds.connect.to('cds.xt.SaasProvisioningService')
|
|
87
88
|
deploymentSrv.impl(() => {
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
2
|
const LOG = cds.log('remote')
|
|
3
3
|
|
|
4
|
-
// REVISIT: use cds.log's logger in cloud sdk
|
|
5
|
-
|
|
6
|
-
// disable sdk logger if not in debug mode
|
|
7
|
-
if (!LOG._debug) {
|
|
8
|
-
try {
|
|
9
|
-
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
10
|
-
const sdkUtils = require('@sap-cloud-sdk/util')
|
|
11
|
-
sdkUtils.setGlobalLogLevel('error')
|
|
12
|
-
} catch (err) {
|
|
13
|
-
/* might fail in cds repl due to winston's exception handler, see cap/issues#10134 */
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
4
|
const { resolveView, getTransition, restoreLink, findQueryTarget } = require('../common/utils/resolveView')
|
|
18
5
|
const { postProcess } = require('../common/utils/postProcessing')
|
|
19
6
|
const { getKind, run, getDestination, getAdditionalOptions, getReqOptions } = require('./utils/client')
|
|
@@ -165,6 +152,7 @@ const resolvedTargetOfQuery = q => {
|
|
|
165
152
|
return transitions.length && [transitions.length - 1].target
|
|
166
153
|
}
|
|
167
154
|
let logged
|
|
155
|
+
let sdkLoggerDisabled
|
|
168
156
|
class RemoteService extends cds.Service {
|
|
169
157
|
init() {
|
|
170
158
|
if (!this.options.credentials) {
|
|
@@ -186,6 +174,21 @@ class RemoteService extends cds.Service {
|
|
|
186
174
|
'Configuration option "cds.env.features.fetch_csrf" is deprecated.\n Please use "csrf"/"csrfInBatch" as described in https://cap.cloud.sap/docs/node.js/remote-services'
|
|
187
175
|
)
|
|
188
176
|
}
|
|
177
|
+
|
|
178
|
+
// REVISIT: use cds.log's logger in cloud sdk
|
|
179
|
+
|
|
180
|
+
// disable sdk logger if not in debug mode
|
|
181
|
+
if (!LOG._debug && !sdkLoggerDisabled) {
|
|
182
|
+
try {
|
|
183
|
+
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
184
|
+
const sdkUtils = require('@sap-cloud-sdk/util')
|
|
185
|
+
sdkUtils.setGlobalLogLevel('error')
|
|
186
|
+
// disable sdk logger once
|
|
187
|
+
sdkLoggerDisabled = true
|
|
188
|
+
} catch (err) {
|
|
189
|
+
/* might fail in cds repl due to winston's exception handler, see cap/issues#10134 */
|
|
190
|
+
}
|
|
191
|
+
}
|
|
189
192
|
// REVISIT: remove cds.env.features.fetch_csrf in next major ^7
|
|
190
193
|
this.csrf = cds.env.features.fetch_csrf || this.options.csrf
|
|
191
194
|
this.csrfInBatch = this.options.csrfInBatch
|
|
@@ -483,7 +483,12 @@ const getReqOptions = (req, query, service) => {
|
|
|
483
483
|
for (const k in originalHeaders) if (k.match(/^dwc-/)) reqOptions.headers[k] = originalHeaders[k]
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
-
if (
|
|
486
|
+
if (
|
|
487
|
+
reqOptions.data &&
|
|
488
|
+
reqOptions.method !== 'GET' &&
|
|
489
|
+
reqOptions.method !== 'HEAD' &&
|
|
490
|
+
!(reqOptions.data instanceof require('stream').Readable)
|
|
491
|
+
) {
|
|
487
492
|
if (typeof reqOptions.data === 'object' && !Buffer.isBuffer(reqOptions.data)) {
|
|
488
493
|
reqOptions.headers['content-type'] = 'application/json'
|
|
489
494
|
reqOptions.headers['content-length'] = Buffer.byteLength(JSON.stringify(reqOptions.data))
|
|
@@ -75,8 +75,7 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
75
75
|
this.before(['CREATE', 'UPDATE'], '*', this._input) // > has to run before rewrite
|
|
76
76
|
this.before(['CREATE', 'READ', 'UPDATE', 'DELETE', 'UPSERT'], '*', this._rewrite)
|
|
77
77
|
|
|
78
|
-
if (cds.env.
|
|
79
|
-
this.before('READ', '*', convertDraftAdminPathExpression)
|
|
78
|
+
if (cds.env.fiori.lean_draft && !cds.db?.cqn2sql) this.before('READ', '*', convertDraftAdminPathExpression)
|
|
80
79
|
this.before('READ', '*', convertAssocToOneManaged)
|
|
81
80
|
this.before('READ', '*', localized) // > has to run after rewrite
|
|
82
81
|
this.before('READ', '*', this._virtual)
|
|
@@ -103,6 +102,9 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
103
102
|
}
|
|
104
103
|
|
|
105
104
|
getDbUrl(tenant) {
|
|
105
|
+
return this.url4(tenant)
|
|
106
|
+
}
|
|
107
|
+
url4(tenant) {
|
|
106
108
|
const credentials = this.options.credentials || this.options || {}
|
|
107
109
|
let dbUrl = credentials.database || credentials.url || credentials.host || ':memory:'
|
|
108
110
|
|
|
@@ -123,7 +125,7 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
123
125
|
const tenant = isMultitenant && arg && (typeof arg === 'string' ? arg : arg.tenant || (arg.user && arg.user.tenant))
|
|
124
126
|
let dbc = this.dbcs.get(tenant)
|
|
125
127
|
if (!dbc) {
|
|
126
|
-
const dbUrl = this.
|
|
128
|
+
const dbUrl = this.url4(tenant)
|
|
127
129
|
|
|
128
130
|
dbc = await _new(dbUrl)
|
|
129
131
|
|
|
@@ -152,62 +154,6 @@ module.exports = class SQLiteDatabase extends DatabaseService {
|
|
|
152
154
|
else dbc._busy = false
|
|
153
155
|
}
|
|
154
156
|
|
|
155
|
-
/*
|
|
156
|
-
* deploy
|
|
157
|
-
*/
|
|
158
|
-
// REVISIT: make tenant aware
|
|
159
|
-
async deploy(model, options = {}) {
|
|
160
|
-
let createEntities = cds.compile.to.sql(model, options)
|
|
161
|
-
if (createEntities.length === 0) return // > nothing to deploy
|
|
162
|
-
|
|
163
|
-
let dropViews = []
|
|
164
|
-
let dropTables = []
|
|
165
|
-
for (const each of createEntities) {
|
|
166
|
-
const [, table, entity] = each.match(/^CREATE (?:(TABLE)|VIEW)\s+"?([^\s"(]+)"?/im) || []
|
|
167
|
-
if (table) dropTables.push({ DROP: { entity } })
|
|
168
|
-
else dropViews.push({ DROP: { view: entity } })
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// H2 is picky on the order
|
|
172
|
-
dropTables.reverse()
|
|
173
|
-
dropViews.reverse()
|
|
174
|
-
|
|
175
|
-
if (options.dry) {
|
|
176
|
-
// do not use cds.log() here!
|
|
177
|
-
const log = console.log // eslint-disable-line no-console
|
|
178
|
-
for (const {
|
|
179
|
-
DROP: { view }
|
|
180
|
-
} of dropViews) {
|
|
181
|
-
log('DROP VIEW IF EXISTS ' + view + ';')
|
|
182
|
-
}
|
|
183
|
-
log()
|
|
184
|
-
for (const {
|
|
185
|
-
DROP: { entity }
|
|
186
|
-
} of dropTables) {
|
|
187
|
-
log('DROP TABLE IF EXISTS ' + entity + ';')
|
|
188
|
-
}
|
|
189
|
-
log()
|
|
190
|
-
for (const each of createEntities) log(each + '\n')
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
await this.run(async tx => {
|
|
195
|
-
// This starts a new transaction if called from CLI, while joining
|
|
196
|
-
// existing root tx, e.g. when called from DeploymenrService
|
|
197
|
-
const [ext] = await tx.run(`SELECT 1 from sqlite_master where name='cds_xt_Extensions'`)
|
|
198
|
-
if (ext) {
|
|
199
|
-
// Poor man's schema evolution for MTX upgrade operations
|
|
200
|
-
createEntities = createEntities.filter(ct => !ct.match(/^CREATE TABLE cds_xt_Extensions/im))
|
|
201
|
-
dropTables = dropTables.filter(dt => dt.DROP.entity !== 'cds_xt_Extensions')
|
|
202
|
-
}
|
|
203
|
-
await tx.run(dropViews)
|
|
204
|
-
await tx.run(dropTables)
|
|
205
|
-
await tx.run(createEntities)
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
return true
|
|
209
|
-
}
|
|
210
|
-
|
|
211
157
|
async disconnect(tenant) {
|
|
212
158
|
this.dbcs.delete(tenant)
|
|
213
159
|
}
|
|
@@ -31,8 +31,8 @@ class CustomUpsertBuilder extends InsertBuilder {
|
|
|
31
31
|
if (!keys.includes(col_)) updates.push(`${sqlColumn}=excluded.${sqlColumn}`)
|
|
32
32
|
})
|
|
33
33
|
const conflict = updates.length
|
|
34
|
-
? ` ON CONFLICT(${keys}) DO UPDATE SET ` + updates.join(', ')
|
|
35
|
-
: ` ON CONFLICT(${keys}) DO NOTHING`
|
|
34
|
+
? ` ON CONFLICT (${keys}) DO UPDATE SET ` + updates.join(', ')
|
|
35
|
+
: ` ON CONFLICT (${keys}) DO NOTHING`
|
|
36
36
|
|
|
37
37
|
this._outputObj.sql = this._outputObj.sql + conflict
|
|
38
38
|
return this._outputObj
|
|
@@ -109,7 +109,9 @@ function _processExpand(model, dbc, cqn, user, locale, txTimestamp) {
|
|
|
109
109
|
function executeSelectCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
110
110
|
if (hasExpand(query)) {
|
|
111
111
|
// expand: '**' or '*3' is handled by new impl
|
|
112
|
-
if (
|
|
112
|
+
if (
|
|
113
|
+
query.SELECT.columns.some(c => c.expand && typeof c.expand[0] === 'string' && /^\*{1}[\d|*]+/.test(c.expand[0]))
|
|
114
|
+
) {
|
|
113
115
|
return expandV2(model, dbc, query, user, locale, txTimestamp, executeSelectCQN)
|
|
114
116
|
}
|
|
115
117
|
return _processExpand(model, dbc, query, user, locale, txTimestamp)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {object} ColumnRef
|
|
5
|
-
* @property {
|
|
5
|
+
* @property {string[]} ref
|
|
6
6
|
* @property {function} func
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* @property {*} value
|
|
19
19
|
* @property {Array} errors
|
|
20
20
|
* @property {string} [key]
|
|
21
|
-
* @property {
|
|
21
|
+
* @property {pathSegmentInfo[]} [pathSegmentsInfo]
|
|
22
22
|
* @property {string} event
|
|
23
23
|
*/
|
|
24
24
|
|
|
@@ -64,6 +64,15 @@
|
|
|
64
64
|
* @property {TemplateProcessorPathOptions} [pathOptions=null]
|
|
65
65
|
*/
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* @typedef {object} pathSegmentInfo
|
|
69
|
+
* @property {string} key
|
|
70
|
+
* @property {string[]} keyNames
|
|
71
|
+
* @property {object} row
|
|
72
|
+
* @property {object} elements
|
|
73
|
+
* @property {string[]} draftKeys
|
|
74
|
+
*/
|
|
75
|
+
|
|
67
76
|
/**
|
|
68
77
|
* @typedef {object} templateElementInfo
|
|
69
78
|
* @property {object} row
|
|
@@ -72,7 +81,7 @@
|
|
|
72
81
|
* @property {boolean} plain
|
|
73
82
|
* @property {entity} target
|
|
74
83
|
* @property {boolean} isRoot
|
|
75
|
-
* @property {Array<
|
|
84
|
+
* @property {string[] | Array<pathSegmentInfo>} [pathSegmentsInfo]
|
|
76
85
|
*/
|
|
77
86
|
|
|
78
87
|
// Search
|
|
@@ -266,7 +266,7 @@ function _processSegments(from, model, namespace, cqn) {
|
|
|
266
266
|
ref[i].where = undefined
|
|
267
267
|
if (ref[i + 1] !== 'Set') {
|
|
268
268
|
// /Set is missing
|
|
269
|
-
throw
|
|
269
|
+
throw cds.error(`Invalid call to "${current.name}". You need to navigate to Set`, { status: 400, code: 400 })
|
|
270
270
|
}
|
|
271
271
|
ref[++i] = null
|
|
272
272
|
} else if (current.kind === 'entity') {
|
|
@@ -371,11 +371,46 @@ function _processSegments(from, model, namespace, cqn) {
|
|
|
371
371
|
|
|
372
372
|
const AGGREGATION_DEFAULT = '@Aggregation.default'
|
|
373
373
|
|
|
374
|
+
function _addKeys(columns, target) {
|
|
375
|
+
let hasAggregatedColumn = false,
|
|
376
|
+
hasStarColumn = false
|
|
377
|
+
for (let k = 0; k < columns.length; k++) {
|
|
378
|
+
if (columns[k] === '*') hasStarColumn = true
|
|
379
|
+
else if (columns[k].func || columns[k].func === null) hasAggregatedColumn = true
|
|
380
|
+
// Add keys to (sub-)expands
|
|
381
|
+
else if (columns[k].expand && columns[k].expand[0] !== '*')
|
|
382
|
+
_addKeys(columns[k].expand, target.elements[columns[k].ref]._target)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Don't add keys to queries with calculated properties, especially aggregations
|
|
386
|
+
// REVISIT Clarify if keys should be added for queries containing non-aggregating func columns
|
|
387
|
+
if (hasAggregatedColumn) return
|
|
388
|
+
|
|
389
|
+
if (hasStarColumn) return
|
|
390
|
+
|
|
391
|
+
const keys = _keysOf(target)
|
|
392
|
+
|
|
393
|
+
for (const key of keys) {
|
|
394
|
+
if (!columns.some(c => (typeof c === 'string' ? c === key : c.ref?.[0] === key))) columns.push({ ref: [key] })
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
374
398
|
function _processColumns(cqn, target) {
|
|
375
399
|
if (cqn.SELECT.from.SELECT) _processColumns(cqn.SELECT.from, target)
|
|
376
400
|
|
|
377
401
|
const columns = cqn.SELECT.columns
|
|
378
402
|
|
|
403
|
+
// REVISIT Keys should be added only in case of odata, not e.g. rest.
|
|
404
|
+
// Currently odata is detected via odata_new_parser flag -> find a better indicator.
|
|
405
|
+
if (columns && !cqn.SELECT.groupBy && cds.env.features.odata_new_parser) {
|
|
406
|
+
let entity
|
|
407
|
+
if (target.kind === 'entity') entity = target
|
|
408
|
+
else if (target.kind === 'action' && target.returns?.kind === 'entity') entity = target.returns
|
|
409
|
+
if (!entity) return
|
|
410
|
+
|
|
411
|
+
_addKeys(columns, entity)
|
|
412
|
+
}
|
|
413
|
+
|
|
379
414
|
if (!Array.isArray(columns)) return
|
|
380
415
|
|
|
381
416
|
let aggrProp, aggrElem
|
|
@@ -409,7 +444,7 @@ const _checkAllKeysProvided = (params, entity) => {
|
|
|
409
444
|
if (isView) {
|
|
410
445
|
// view with params
|
|
411
446
|
if (params === undefined) {
|
|
412
|
-
throw
|
|
447
|
+
throw cds.error(`Invalid call to "${entity.name}". You need to navigate to Set`, { status: 400, code: 400 })
|
|
413
448
|
} else if (Object.keys(params).length === 0) {
|
|
414
449
|
throw new Error('KEY_EXPECTED')
|
|
415
450
|
}
|
|
@@ -441,6 +476,7 @@ const _checkAllKeysProvided = (params, entity) => {
|
|
|
441
476
|
|
|
442
477
|
function _4service(service) {
|
|
443
478
|
const { namespace, model } = service
|
|
479
|
+
if (!model) return cqn => cqn
|
|
444
480
|
|
|
445
481
|
return cqn => {
|
|
446
482
|
const from = resolveFromSelect(cqn)
|
package/libx/odata/cqn2odata.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { formatVal } = require('./utils')
|
|
2
|
+
const cds = require('../_runtime/cds')
|
|
2
3
|
|
|
3
4
|
const OPERATORS = {
|
|
4
5
|
'=': 'eq',
|
|
@@ -253,7 +254,7 @@ function _getQueryTarget(entity, propOrEntity, model) {
|
|
|
253
254
|
|
|
254
255
|
const _params = (args, kind, target) => {
|
|
255
256
|
if (!args) {
|
|
256
|
-
throw
|
|
257
|
+
throw cds.error(`Invalid call to "${target.name}". You need to navigate to Set`, { status: 400, code: 400 })
|
|
257
258
|
}
|
|
258
259
|
const params = Object.keys(args)
|
|
259
260
|
if (params.length !== Object.keys(target.params).length) {
|
|
@@ -345,7 +346,7 @@ const _parseColumns = columns => {
|
|
|
345
346
|
} else {
|
|
346
347
|
select.push(refName)
|
|
347
348
|
}
|
|
348
|
-
} else if (hasValidProps(column, 'expand') && column.expand === '*') {
|
|
349
|
+
} else if (hasValidProps(column, 'expand') && column.expand[0] === '*') {
|
|
349
350
|
expand.push('*')
|
|
350
351
|
}
|
|
351
352
|
if (column === '*') {
|
package/libx/odata/grammar.pegjs
CHANGED
|
@@ -408,7 +408,6 @@
|
|
|
408
408
|
throw err;
|
|
409
409
|
}
|
|
410
410
|
if (SELECT.columns) {
|
|
411
|
-
if (SELECT.expand === '*') SELECT.expand = []
|
|
412
411
|
for (const col of SELECT.columns) {
|
|
413
412
|
if (!SELECT.expand.find(_compareRefs(col))) SELECT.expand.push(col)
|
|
414
413
|
}
|
|
@@ -423,10 +422,13 @@
|
|
|
423
422
|
= (OPEN {
|
|
424
423
|
stack.push (SELECT)
|
|
425
424
|
SELECT = SELECT.expand[SELECT.expand.length-1]
|
|
426
|
-
SELECT.expand =
|
|
425
|
+
SELECT.expand = []
|
|
427
426
|
})
|
|
428
427
|
expandQueryOptions?
|
|
429
428
|
(CLOSE {
|
|
429
|
+
if (!SELECT.expand.length) {
|
|
430
|
+
SELECT.expand.push('*') // by default expand everything
|
|
431
|
+
}
|
|
430
432
|
SELECT = stack.pop()
|
|
431
433
|
})
|
|
432
434
|
|
|
@@ -436,7 +438,7 @@
|
|
|
436
438
|
= (
|
|
437
439
|
c:('*' / ref) {
|
|
438
440
|
const col = c === '*' ? {} : c
|
|
439
|
-
col.expand = '*'
|
|
441
|
+
col.expand = ['*']
|
|
440
442
|
if (!Array.isArray(SELECT.expand)) SELECT.expand = []
|
|
441
443
|
if (!SELECT.expand.find(_compareRefs(col))) SELECT.expand.push(col)
|
|
442
444
|
return col
|