@sap/cds 6.5.0 → 6.6.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 (108) hide show
  1. package/CHANGELOG.md +53 -2
  2. package/README.md +5 -0
  3. package/apis/services.d.ts +5 -0
  4. package/bin/build/buildTaskEngine.js +0 -2
  5. package/bin/build/buildTaskFactory.js +1 -1
  6. package/bin/build/buildTaskHandler.js +1 -1
  7. package/bin/build/provider/buildTaskProviderInternal.js +10 -6
  8. package/bin/build/provider/fiori/index.js +5 -10
  9. package/bin/build/provider/hana/2migration.js +11 -2
  10. package/bin/build/provider/hana/index.js +17 -14
  11. package/bin/build/provider/hana/template/.hdiconfig-hanacloud +137 -0
  12. package/bin/build/provider/mtx-extension/index.js +18 -1
  13. package/bin/build/provider/mtx-sidecar/index.js +1 -1
  14. package/bin/build/util.js +1 -1
  15. package/bin/cds.js +1 -5
  16. package/bin/deploy/to-hana/hana.js +10 -3
  17. package/bin/deploy/to-hana/hdiDeployUtil.js +24 -12
  18. package/bin/serve.js +32 -20
  19. package/lib/auth/jwt-auth.js +4 -4
  20. package/lib/compile/for/lean_drafts.js +55 -6
  21. package/lib/dbs/cds-deploy.js +6 -8
  22. package/lib/env/schemas/cds-rc.json +4 -0
  23. package/lib/index.js +4 -2
  24. package/lib/req/cds-context.js +3 -3
  25. package/lib/srv/bindings.js +1 -2
  26. package/lib/srv/cds-serve.js +2 -1
  27. package/lib/srv/middlewares/trace.js +31 -15
  28. package/lib/srv/protocols/odata-v2-proxy.js +8 -8
  29. package/lib/srv/srv-handlers.js +26 -7
  30. package/lib/srv/srv-methods.js +2 -2
  31. package/lib/srv/srv-models.js +4 -3
  32. package/lib/utils/cds-test.js +7 -5
  33. package/libx/_runtime/auth/strategies/ias-auth.js +1 -1
  34. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +6 -2
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +26 -1
  36. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -0
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +1 -1
  38. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +11 -2
  39. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +8 -8
  40. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +1 -1
  41. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/validator/ValueValidator.js +14 -14
  42. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +7 -8
  43. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ResourceJsonSerializer.js +3 -0
  44. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/UriHelper.js +2 -1
  45. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +3 -2
  46. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +7 -0
  47. package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +0 -3
  48. package/libx/_runtime/cds-services/services/Service.js +8 -19
  49. package/libx/_runtime/cds-services/services/utils/columns.js +7 -4
  50. package/libx/_runtime/cds-services/util/assert.js +7 -1
  51. package/libx/_runtime/common/code-ext/WorkerReq.js +3 -1
  52. package/libx/_runtime/common/code-ext/execute.js +9 -2
  53. package/libx/_runtime/common/code-ext/handlers.js +2 -2
  54. package/libx/_runtime/common/code-ext/worker.js +9 -5
  55. package/libx/_runtime/common/code-ext/workerQueryExecutor.js +5 -2
  56. package/libx/_runtime/common/composition/data.js +5 -2
  57. package/libx/_runtime/common/composition/tree.js +2 -0
  58. package/libx/_runtime/common/generic/auth/restrict.js +1 -1
  59. package/libx/_runtime/common/generic/etag.js +22 -10
  60. package/libx/_runtime/common/generic/input.js +12 -14
  61. package/libx/_runtime/common/utils/cqn2cqn4sql.js +31 -11
  62. package/libx/_runtime/common/utils/path.js +0 -1
  63. package/libx/_runtime/common/utils/search2cqn4sql.js +4 -1
  64. package/libx/_runtime/common/utils/structured.js +1 -0
  65. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +19 -13
  66. package/libx/_runtime/db/data-conversion/post-processing.js +1 -1
  67. package/libx/_runtime/db/expand/expandCQNToJoin.js +5 -3
  68. package/libx/_runtime/db/expand/rawToExpanded.js +3 -2
  69. package/libx/_runtime/db/generic/input.js +2 -2
  70. package/libx/_runtime/db/generic/integrity.js +1 -0
  71. package/libx/_runtime/db/generic/virtual.js +1 -0
  72. package/libx/_runtime/db/query/read.js +3 -2
  73. package/libx/_runtime/fiori/generic/activate.js +3 -1
  74. package/libx/_runtime/fiori/generic/before.js +1 -0
  75. package/libx/_runtime/fiori/generic/edit.js +6 -1
  76. package/libx/_runtime/fiori/generic/new.js +2 -0
  77. package/libx/_runtime/fiori/generic/patch.js +2 -0
  78. package/libx/_runtime/fiori/generic/prepare.js +2 -0
  79. package/libx/_runtime/fiori/generic/read.js +8 -2
  80. package/libx/_runtime/fiori/generic/readOverDraft.js +2 -0
  81. package/libx/_runtime/fiori/lean-draft.js +498 -245
  82. package/libx/_runtime/fiori/utils/delete.js +2 -0
  83. package/libx/_runtime/messaging/Outbox.js +1 -1
  84. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -0
  85. package/libx/_runtime/messaging/enterprise-messaging.js +2 -6
  86. package/libx/_runtime/messaging/file-based.js +1 -2
  87. package/libx/_runtime/messaging/outbox/OutboxRunner.js +1 -1
  88. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  89. package/libx/_runtime/messaging/service.js +0 -1
  90. package/libx/_runtime/remote/Service.js +1 -0
  91. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +19 -3
  92. package/libx/_runtime/sqlite/customBuilder/CustomExpressionBuilder.js +0 -18
  93. package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +0 -18
  94. package/libx/_runtime/sqlite/customBuilder/CustomSelectBuilder.js +0 -24
  95. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -1
  96. package/libx/_runtime/sqlite/customBuilder/index.js +47 -32
  97. package/libx/odata/afterburner.js +17 -5
  98. package/libx/odata/grammar.pegjs +3 -4
  99. package/libx/odata/index.js +5 -1
  100. package/libx/odata/parseToCqn.js +3 -3
  101. package/libx/odata/parser.js +1 -1
  102. package/libx/odata/utils.js +58 -1
  103. package/package.json +1 -1
  104. package/server.js +1 -1
  105. package/libx/_runtime/sqlite/customBuilder/CustomDeleteBuilder.js +0 -17
  106. package/libx/_runtime/sqlite/customBuilder/CustomReferenceBuilder.js +0 -11
  107. package/libx/_runtime/sqlite/customBuilder/CustomUpdateBuilder.js +0 -17
  108. /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
 
@@ -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) process.exit(1)
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.on('subscribe', async (req, next) => {
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.on('unsubscribe', async (req, next) => {
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() {
@@ -50,7 +50,7 @@ class OutboxRunner {
50
50
  const timer = setTimeout(() => {
51
51
  this._setStateProp(SCHEDULED, undefined, { name, tenant })
52
52
  return cb()
53
- }, waitingTime)
53
+ }, waitingTime).unref()
54
54
  this._setStateProp(SCHEDULED, timer, { name, tenant })
55
55
  }
56
56
 
@@ -201,7 +201,7 @@ const processMessages = async (service, tenant, _opts = {}) => {
201
201
  }
202
202
  }, config)
203
203
  spawn.on('done', () => {
204
- if (letAppCrash) process.exit(1)
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
 
@@ -291,4 +291,5 @@ class RemoteService extends cds.Service {
291
291
  }
292
292
  }
293
293
 
294
+ RemoteService.prototype.isExternal = true
294
295
  module.exports = RemoteService
@@ -9,7 +9,7 @@ function sqliteConvertDraftAdminPathExpression(req) {
9
9
  return
10
10
  let hasDraftAdminPathExpression = false
11
11
 
12
- const alias = req.query.SELECT.from.as
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 && alias && col.ref[0] === alias && col.ref[1] === 'DraftAdministrativeData') {
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: alias ? [alias, 'DraftAdministrativeData_DraftUUID'] : ['DraftAdministrativeData_DraftUUID'] },
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
- if (!keys.includes(col_)) updates.push(`${col_}=excluded.${col_}`)
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
- const dependencies = {
2
- get DeleteBuilder() {
3
- const CustomDeleteBuilder = require('./CustomDeleteBuilder')
4
- Object.defineProperty(dependencies, 'DeleteBuilder', { value: CustomDeleteBuilder })
5
- return CustomDeleteBuilder
6
- },
7
- get ExpressionBuilder() {
8
- const CustomExpressionBuilder = require('./CustomExpressionBuilder')
9
- Object.defineProperty(dependencies, 'ExpressionBuilder', { value: CustomExpressionBuilder })
10
- return CustomExpressionBuilder
11
- },
12
- get SelectBuilder() {
13
- const CustomSelectBuilder = require('./CustomSelectBuilder')
14
- Object.defineProperty(dependencies, 'SelectBuilder', { value: CustomSelectBuilder })
15
- return CustomSelectBuilder
16
- },
17
- get ReferenceBuilder() {
18
- const CustomReferenceBuilder = require('./CustomReferenceBuilder')
19
- Object.defineProperty(dependencies, 'ReferenceBuilder', { value: CustomReferenceBuilder })
20
- return CustomReferenceBuilder
21
- },
22
- get UpdateBuilder() {
23
- const CustomUpdateBuilder = require('./CustomUpdateBuilder')
24
- Object.defineProperty(dependencies, 'UpdateBuilder', { value: CustomUpdateBuilder })
25
- return CustomUpdateBuilder
26
- },
27
- get UpsertBuilder() {
28
- const CustomUpsertBuilder = require('./CustomUpsertBuilder')
29
- Object.defineProperty(dependencies, 'UpsertBuilder', { value: CustomUpsertBuilder })
30
- return CustomUpsertBuilder
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
- module.exports = dependencies
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
@@ -329,12 +329,24 @@ function _processSegments(from, model, namespace) {
329
329
  _resolveAliasesInXpr(ref[i].where, current)
330
330
  _processWhere(ref[i].where, current)
331
331
  }
332
- } else if (current._isStructured) {
333
- // > nested property
334
- one = true
335
332
  } else {
336
333
  // > property
334
+ // we do not support navigations from properties yet
337
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
+ }
338
350
  }
339
351
  }
340
352
  }
@@ -455,7 +467,7 @@ function _4service(service) {
455
467
  /*
456
468
  * key vs. path segments (/Books/1/author/books/2/...) and more
457
469
  */
458
- const { one, current, target } = _processSegments(from, model, namespace)
470
+ const { one, current, target } = _processSegments(from, model, namespace, cqn)
459
471
 
460
472
  if (cqn.SELECT.where) {
461
473
  _processWhere(cqn.SELECT.where, root)
@@ -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
- = val:integer? skiptoken:skiptokenChars? {
451
- // REVISIT ignore non-numeric $skiptoken as not supported by CQN
452
- if (skiptoken) return
453
- _setLimitOffset(val)
451
+ = skiptoken:skiptokenChars? {
452
+ skipToken(skiptoken, { SELECT })
454
453
  }
455
454
 
456
455
  skip
@@ -1,4 +1,5 @@
1
- const cds = require('../_runtime/cds'), { decodeURIComponent } = cds.utils
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 {
@@ -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)