@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.
Files changed (136) hide show
  1. package/CHANGELOG.md +72 -2
  2. package/README.md +1 -1
  3. package/apis/connect.d.ts +11 -4
  4. package/apis/core.d.ts +1 -1
  5. package/apis/csn.d.ts +1 -0
  6. package/apis/internal/inference.d.ts +15 -2
  7. package/apis/log.d.ts +10 -0
  8. package/apis/serve.d.ts +4 -9
  9. package/apis/services.d.ts +86 -19
  10. package/bin/build/buildTaskEngine.js +16 -42
  11. package/bin/build/constants.js +4 -2
  12. package/bin/build/provider/buildTaskProviderInternal.js +117 -85
  13. package/bin/build/provider/hana/index.js +6 -1
  14. package/bin/build/provider/mtx-extension/index.js +74 -34
  15. package/bin/build/provider/mtx-sidecar/index.js +3 -3
  16. package/bin/build/provider/nodejs/index.js +2 -2
  17. package/bin/build/util.js +63 -14
  18. package/bin/cds-serve.js +6 -0
  19. package/bin/cds.js +22 -4
  20. package/bin/deploy/to-hana/cfUtil.js +15 -1
  21. package/bin/mtx/in-cds.js +2 -9
  22. package/bin/plugins.js +31 -0
  23. package/bin/serve.js +12 -12
  24. package/lib/compile/etc/_localized.js +1 -1
  25. package/lib/compile/for/lean_drafts.js +23 -6
  26. package/lib/compile/for/nodejs.js +4 -1
  27. package/lib/compile/load.js +4 -2
  28. package/lib/core/index.js +35 -15
  29. package/lib/dbs/cds-deploy.js +129 -133
  30. package/lib/env/cds-env.js +25 -17
  31. package/lib/env/cds-requires.js +10 -40
  32. package/lib/env/compat.js +12 -0
  33. package/lib/env/defaults.js +17 -9
  34. package/lib/env/plugins.js +29 -0
  35. package/lib/env/schemas/cds-rc.json +14 -0
  36. package/lib/index.js +3 -0
  37. package/lib/log/cds-log.js +7 -4
  38. package/lib/ql/CREATE.js +1 -1
  39. package/lib/ql/DELETE.js +1 -1
  40. package/lib/ql/DROP.js +3 -3
  41. package/lib/ql/INSERT.js +1 -1
  42. package/lib/ql/Query.js +14 -6
  43. package/lib/ql/SELECT.js +8 -2
  44. package/lib/ql/UPDATE.js +1 -1
  45. package/lib/ql/Whereable.js +1 -1
  46. package/lib/ql/cds-ql.js +1 -9
  47. package/lib/req/cds-context.js +1 -4
  48. package/lib/req/request.js +63 -2
  49. package/lib/req/response.js +3 -2
  50. package/lib/srv/bindings.js +69 -71
  51. package/lib/srv/cds-connect.js +4 -1
  52. package/lib/srv/cds-serve.js +4 -0
  53. package/lib/srv/middlewares/index.js +37 -6
  54. package/lib/srv/protocols/_legacy.js +1 -1
  55. package/lib/srv/protocols/index.js +1 -1
  56. package/lib/srv/srv-api.js +4 -6
  57. package/lib/srv/srv-dispatch.js +4 -3
  58. package/lib/srv/srv-handlers.js +1 -1
  59. package/lib/srv/srv-methods.js +8 -2
  60. package/lib/utils/cds-test.js +4 -1
  61. package/libx/_runtime/audit/Service.js +8 -9
  62. package/libx/_runtime/audit/generic/personal/index.js +1 -1
  63. package/libx/_runtime/audit/generic/personal/utils.js +1 -1
  64. package/libx/_runtime/audit/utils/v2.js +17 -20
  65. package/libx/_runtime/auth/strategies/mock.js +1 -1
  66. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -0
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -1
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +12 -5
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -2
  72. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +5 -5
  73. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
  74. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +4 -4
  75. package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +2 -2
  76. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -1
  77. package/libx/_runtime/cds-services/services/Service.js +28 -1
  78. package/libx/_runtime/cds-services/util/assert.js +41 -65
  79. package/libx/_runtime/common/code-ext/WorkerPool.js +90 -0
  80. package/libx/_runtime/common/code-ext/WorkerReq.js +0 -4
  81. package/libx/_runtime/common/code-ext/execute.js +28 -18
  82. package/libx/_runtime/common/code-ext/handlers.js +5 -4
  83. package/libx/_runtime/common/code-ext/worker.js +45 -3
  84. package/libx/_runtime/common/code-ext/workerQueryExecutor.js +8 -7
  85. package/libx/_runtime/common/composition/delete.js +1 -1
  86. package/libx/_runtime/common/composition/update.js +3 -5
  87. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  88. package/libx/_runtime/common/generic/auth/readOnly.js +5 -4
  89. package/libx/_runtime/common/generic/auth/restrict.js +7 -2
  90. package/libx/_runtime/common/generic/auth/utils.js +5 -2
  91. package/libx/_runtime/common/generic/crud.js +12 -1
  92. package/libx/_runtime/common/generic/etag.js +11 -3
  93. package/libx/_runtime/common/generic/input.js +8 -6
  94. package/libx/_runtime/common/generic/paging.js +25 -8
  95. package/libx/_runtime/common/generic/put.js +1 -1
  96. package/libx/_runtime/common/generic/sorting.js +0 -1
  97. package/libx/_runtime/common/i18n/messages.properties +1 -0
  98. package/libx/_runtime/common/utils/cqn.js +5 -1
  99. package/libx/_runtime/common/utils/cqn2cqn4sql.js +3 -3
  100. package/libx/_runtime/common/utils/resolveView.js +14 -10
  101. package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -3
  102. package/libx/_runtime/common/utils/templateProcessor.js +15 -17
  103. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +18 -6
  104. package/libx/_runtime/db/Service.js +1 -0
  105. package/libx/_runtime/db/data-conversion/post-processing.js +0 -18
  106. package/libx/_runtime/db/expand/expand-v2.js +2 -2
  107. package/libx/_runtime/db/expand/rawToExpanded.js +6 -6
  108. package/libx/_runtime/db/generic/integrity.js +1 -1
  109. package/libx/_runtime/db/utils/columns.js +5 -5
  110. package/libx/_runtime/fiori/generic/activate.js +3 -3
  111. package/libx/_runtime/fiori/generic/edit.js +1 -1
  112. package/libx/_runtime/fiori/generic/new.js +4 -0
  113. package/libx/_runtime/fiori/lean-draft.js +178 -68
  114. package/libx/_runtime/hana/execute.js +3 -1
  115. package/libx/_runtime/hana/pool.js +10 -2
  116. package/libx/_runtime/hana/search2cqn4sql.js +1 -1
  117. package/libx/_runtime/messaging/common-utils/AMQPClient.js +6 -1
  118. package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
  119. package/libx/_runtime/remote/Service.js +16 -13
  120. package/libx/_runtime/remote/utils/client.js +6 -1
  121. package/libx/_runtime/sqlite/Service.js +5 -59
  122. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
  123. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -2
  124. package/libx/_runtime/sqlite/execute.js +3 -1
  125. package/libx/_runtime/types/api.js +12 -3
  126. package/libx/odata/afterburner.js +38 -2
  127. package/libx/odata/cqn2odata.js +3 -2
  128. package/libx/odata/grammar.pegjs +5 -3
  129. package/libx/odata/parser.js +1 -1
  130. package/libx/odata/utils.js +1 -1
  131. package/libx/rest/RestAdapter.js +1 -1
  132. package/libx/rest/RestRequest.js +1 -0
  133. package/package.json +5 -2
  134. package/libx/_runtime/common/code-ext/workerQuery.js +0 -45
  135. package/libx/_runtime/common/constants/limit.js +0 -12
  136. 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
- return cds.env.requires['cds.xt.DeploymentService']?.['old-instance-manager'] ? null : cds.xt?.serviceManager
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
- if (cds.xt?.serviceManager && !cds.env.requires['cds.xt.DeploymentService']?.['old-instance-manager']) {
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 = { chunks: [Buffer.from(JSON.stringify(message))], type: 'application/json' }
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 (reqOptions.data && reqOptions.method !== 'GET' && reqOptions.method !== 'HEAD') {
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.features.lean_draft && cds.db?.kind !== 'better-sqlite')
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.getDbUrl(tenant)
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
  }
@@ -1,6 +1,7 @@
1
1
  const cds = require('../cds')
2
2
 
3
3
  function sqliteConvertDraftAdminPathExpression(req) {
4
+ if (req.query?.SELECT?.from?.SET) return // not supported, won't happen in draft
4
5
  if (
5
6
  !req.query?.SELECT ||
6
7
  !req.query?._target?.name?.endsWith('.drafts') ||
@@ -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 (query.SELECT.columns.some(c => c.expand && typeof c.expand === 'string' && /^\*{1}[\d|*]+/.test(c.expand))) {
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 {Array<string>} ref
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 {pathSegment[]} [pathSegments]
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<String>} [pathSegments]
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 new Error(`Incorrect call to a view with parameter "${current.name}"`)
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 new Error(`Incorrect call to a view with parameter "${entity.name}"`)
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)
@@ -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 new Error(`Incorrect call to a view with parameter "${target.name}"`)
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 === '*') {
@@ -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 = '*' // by default expand everything
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