@sap/cds 6.3.2 → 6.4.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 (128) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/apis/cds.d.ts +3 -1
  3. package/apis/core.d.ts +118 -90
  4. package/apis/cqn.d.ts +11 -2
  5. package/apis/internal/inference.d.ts +7 -2
  6. package/apis/ql.d.ts +49 -11
  7. package/apis/serve.d.ts +8 -1
  8. package/apis/services.d.ts +311 -305
  9. package/bin/build/buildTaskEngine.js +28 -36
  10. package/bin/build/buildTaskFactory.js +32 -81
  11. package/bin/build/buildTaskHandler.js +3 -2
  12. package/bin/build/buildTaskProvider.js +2 -2
  13. package/bin/build/buildTaskProviderFactory.js +5 -14
  14. package/bin/build/constants.js +0 -1
  15. package/bin/build/provider/buildTaskHandlerEdmx.js +7 -6
  16. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +6 -5
  17. package/bin/build/provider/buildTaskHandlerInternal.js +9 -30
  18. package/bin/build/provider/buildTaskProviderInternal.js +70 -58
  19. package/bin/build/provider/fiori/index.js +6 -5
  20. package/bin/build/provider/hana/2migration.js +20 -3
  21. package/bin/build/provider/hana/2tabledata.js +1 -0
  22. package/bin/build/provider/hana/index.js +40 -17
  23. package/bin/build/provider/java/index.js +10 -10
  24. package/bin/build/provider/mtx/index.js +25 -16
  25. package/bin/build/provider/mtx/resourcesTarBuilder.js +22 -27
  26. package/bin/build/provider/mtx-extension/index.js +3 -2
  27. package/bin/build/provider/mtx-sidecar/index.js +16 -15
  28. package/bin/build/provider/nodejs/index.js +14 -56
  29. package/bin/build/util.js +56 -16
  30. package/bin/deploy/to-hana/cfUtil.js +2 -0
  31. package/bin/deploy/to-hana/gitUtil.js +1 -1
  32. package/bin/deploy/to-hana/hana.js +45 -38
  33. package/bin/deploy/to-hana/hdiDeployUtil.js +17 -12
  34. package/bin/deploy/to-hana/mtaUtil.js +13 -14
  35. package/bin/mtx/in-cds.js +3 -1
  36. package/bin/serve.js +1 -1
  37. package/bin/version.js +2 -1
  38. package/lib/auth/index.js +17 -15
  39. package/lib/compile/cds-compile.js +1 -0
  40. package/lib/compile/cdsc.js +1 -0
  41. package/lib/compile/etc/_localized.js +2 -2
  42. package/lib/compile/for/lean_drafts.js +83 -0
  43. package/lib/compile/for/nodejs.js +1 -0
  44. package/lib/compile/minify.js +2 -1
  45. package/lib/compile/to/gql.js +1 -1
  46. package/lib/compile/to/sql.js +11 -1
  47. package/lib/core/entities.js +1 -1
  48. package/lib/core/index.js +9 -9
  49. package/lib/core/infer.js +1 -0
  50. package/lib/dbs/cds-deploy.js +97 -41
  51. package/lib/env/cds-env.js +9 -10
  52. package/lib/env/cds-requires.js +8 -2
  53. package/lib/env/defaults.js +0 -4
  54. package/lib/env/schemas/cds-rc.json +38 -0
  55. package/lib/ql/SELECT.js +10 -4
  56. package/lib/srv/bindings.js +1 -1
  57. package/lib/srv/factory.js +1 -1
  58. package/lib/srv/middlewares/cds-context.js +0 -2
  59. package/lib/srv/middlewares/ctx-auth.js +11 -0
  60. package/lib/srv/middlewares/ctx-model.js +22 -20
  61. package/lib/srv/middlewares/index.js +7 -9
  62. package/lib/srv/protocols/_legacy.js +4 -0
  63. package/lib/srv/protocols/graphql.js +2 -2
  64. package/lib/srv/protocols/index.js +7 -3
  65. package/lib/srv/srv-api.js +1 -0
  66. package/lib/srv/srv-methods.js +1 -1
  67. package/lib/utils/cds-utils.js +11 -0
  68. package/lib/utils/data.js +2 -2
  69. package/lib/utils/inflect.js +13 -12
  70. package/lib/utils/tar.js +43 -13
  71. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -2
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
  73. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
  74. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -1
  75. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -15
  76. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -1
  77. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
  78. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/UriSyntaxError.js +1 -1
  79. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +6 -1
  80. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/utils/BufferedWriter.js +1 -1
  81. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +0 -12
  82. package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +1 -7
  83. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +4 -0
  84. package/libx/_runtime/cds-services/services/Service.js +23 -1
  85. package/libx/_runtime/cds-services/util/assert.js +0 -41
  86. package/libx/_runtime/common/composition/data.js +5 -1
  87. package/libx/_runtime/common/generic/auth/utils.js +3 -3
  88. package/libx/_runtime/common/generic/crud.js +1 -1
  89. package/libx/_runtime/common/generic/input.js +4 -24
  90. package/libx/_runtime/common/generic/paging.js +10 -9
  91. package/libx/_runtime/common/utils/cqn2cqn4sql.js +31 -0
  92. package/libx/_runtime/common/utils/csn.js +21 -15
  93. package/libx/_runtime/common/utils/draft.js +2 -1
  94. package/libx/_runtime/common/utils/resolveView.js +27 -4
  95. package/libx/_runtime/common/utils/rewriteAsterisks.js +3 -1
  96. package/libx/_runtime/common/utils/rowUUIDGenerator.js +21 -0
  97. package/libx/_runtime/common/utils/templateProcessor.js +12 -15
  98. package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +23 -0
  99. package/libx/_runtime/db/expand/expandCQNToJoin.js +29 -12
  100. package/libx/_runtime/db/generic/input.js +7 -13
  101. package/libx/_runtime/db/sql-builder/InsertBuilder.js +5 -1
  102. package/libx/_runtime/db/sql-builder/UpsertBuilder.js +24 -0
  103. package/libx/_runtime/db/sql-builder/annotations.js +6 -3
  104. package/libx/_runtime/db/sql-builder/index.js +2 -0
  105. package/libx/_runtime/db/sql-builder/sqlFactory.js +9 -0
  106. package/libx/_runtime/db/utils/columns.js +4 -2
  107. package/libx/_runtime/fiori/generic/read.js +1 -12
  108. package/libx/_runtime/fiori/lean-draft.js +657 -0
  109. package/libx/_runtime/fiori/utils/handler.js +1 -1
  110. package/libx/_runtime/hana/Service.js +1 -1
  111. package/libx/_runtime/hana/execute.js +5 -5
  112. package/libx/_runtime/hana/pool.js +16 -1
  113. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -1
  114. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -1
  115. package/libx/_runtime/messaging/enterprise-messaging.js +2 -3
  116. package/libx/_runtime/messaging/outbox/utils.js +109 -70
  117. package/libx/_runtime/messaging/service.js +16 -7
  118. package/libx/_runtime/remote/Service.js +15 -2
  119. package/libx/_runtime/remote/utils/client.js +41 -11
  120. package/libx/_runtime/sqlite/Service.js +4 -1
  121. package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +56 -0
  122. package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +41 -0
  123. package/libx/_runtime/sqlite/customBuilder/index.js +5 -0
  124. package/libx/_runtime/sqlite/execute.js +1 -1
  125. package/libx/_runtime/types/api.js +2 -2
  126. package/libx/rest/RestAdapter.js +15 -13
  127. package/package.json +1 -1
  128. package/server.js +2 -19
@@ -319,7 +319,7 @@ const _rewriteQueryPath = (path, transitions) => {
319
319
  const _newUpdate = (query, transitions, service) => {
320
320
  const targetTransition = transitions[transitions.length - 1]
321
321
  const targetName = targetTransition.target.name
322
- const newUpdate = { ...query.UPDATE }
322
+ const newUpdate = Object.create(query.UPDATE)
323
323
  newUpdate.entity = newUpdate.entity.ref
324
324
  ? {
325
325
  ...newUpdate.entity,
@@ -345,7 +345,7 @@ const _newUpdate = (query, transitions, service) => {
345
345
 
346
346
  const _newSelect = (query, transitions, service) => {
347
347
  const targetTransition = transitions[transitions.length - 1]
348
- const newSelect = { ...query.SELECT }
348
+ const newSelect = Object.create(query.SELECT)
349
349
  newSelect.from = {
350
350
  ...newSelect.from,
351
351
  ref: _rewriteQueryPath(query.SELECT.from, transitions)
@@ -379,7 +379,7 @@ const _newSelect = (query, transitions, service) => {
379
379
  const _newInsert = (query, transitions, service) => {
380
380
  const targetTransition = transitions[transitions.length - 1]
381
381
  const targetName = targetTransition.target.name
382
- const newInsert = { ...query.INSERT }
382
+ const newInsert = Object.create(query.INSERT)
383
383
  newInsert.into = newInsert.into.ref
384
384
  ? {
385
385
  ...newInsert.into,
@@ -395,10 +395,29 @@ const _newInsert = (query, transitions, service) => {
395
395
  return newInsert
396
396
  }
397
397
 
398
+ const _newUpsert = (query, transitions, service) => {
399
+ const targetTransition = transitions[transitions.length - 1]
400
+ const targetName = targetTransition.target.name
401
+ const newUpsert = Object.create(query.UPSERT)
402
+ newUpsert.into = newUpsert.into.ref
403
+ ? {
404
+ ...newUpsert.into,
405
+ ref: _rewriteQueryPath(query.UPSERT.into, transitions)
406
+ }
407
+ : targetName
408
+ if (newUpsert.columns) newUpsert.columns = _newInsertColumns(newUpsert.columns, targetTransition)
409
+ if (newUpsert.entries) newUpsert.entries = _newEntries(newUpsert.entries, targetTransition, service)
410
+ Object.defineProperty(newUpsert, '_transitions', {
411
+ enumerable: false,
412
+ value: transitions
413
+ })
414
+ return newUpsert
415
+ }
416
+
398
417
  const _newDelete = (query, transitions) => {
399
418
  const targetTransition = transitions[transitions.length - 1]
400
419
  const targetName = targetTransition.target.name
401
- const newDelete = { ...query.DELETE }
420
+ const newDelete = Object.create(query.DELETE)
402
421
  newDelete.from = newDelete.from.ref
403
422
  ? {
404
423
  ...newDelete.from,
@@ -600,6 +619,7 @@ const _newQuery = (query, event, model, service) => {
600
619
  const [_prop, _func] = {
601
620
  SELECT: ['from', _newSelect],
602
621
  INSERT: ['into', _newInsert],
622
+ UPSERT: ['into', _newUpsert],
603
623
  UPDATE: ['entity', _newUpdate],
604
624
  DELETE: ['from', _newDelete]
605
625
  }[event]
@@ -619,6 +639,7 @@ const resolveView = (query, model, service) => {
619
639
  if (query.cmd) _event = query.cmd
620
640
  else if (query.SELECT) _event = 'SELECT'
621
641
  else if (query.INSERT) _event = 'INSERT'
642
+ else if (query.UPSERT) _event = 'UPSERT'
622
643
  else if (query.UPDATE) _event = 'UPDATE'
623
644
  else if (query.DELETE) _event = 'DELETE'
624
645
 
@@ -658,6 +679,8 @@ const findQueryTarget = q => {
658
679
  ? q.INSERT._transitions[q.INSERT._transitions.length - 1].target
659
680
  : q.UPDATE
660
681
  ? q.UPDATE._transitions[q.UPDATE._transitions.length - 1].target
682
+ : q.UPSERT
683
+ ? q.UPSERT._transitions[q.UPSERT._transitions.length - 1].target
661
684
  : q.DELETE
662
685
  ? q.DELETE._transitions[q.DELETE._transitions.length - 1].target
663
686
  : undefined
@@ -2,6 +2,7 @@ const { getNavigationIfStruct } = require('./structured')
2
2
  const getColumns = require('../../db/utils/columns')
3
3
  const { ensureNoDraftsSuffix, getDraftColumnsCQNForActive } = require('./draft')
4
4
  const { getEntityNameFromCQN } = require('./entityFromCqn')
5
+ const cds = require('../../cds')
5
6
 
6
7
  const isAsteriskColumn = col => col === '*' || (col.ref && col.ref[0] === '*' && !col.expand)
7
8
 
@@ -118,7 +119,8 @@ const rewriteAsterisks = (query, model, options) => {
118
119
  if (!target) return
119
120
 
120
121
  query.SELECT.columns = getColumns(target, { _4db }).map(col => ({ ref: [col.name] }))
121
- if (_4db && target._isDraftEnabled) query.SELECT.columns.push(..._cqlDraftColumns(target))
122
+ if (_4db && target._isDraftEnabled && !cds.env.features.lean_draft)
123
+ query.SELECT.columns.push(..._cqlDraftColumns(target))
122
124
  }
123
125
  }
124
126
 
@@ -0,0 +1,21 @@
1
+ const cds = require('../../cds')
2
+
3
+ const getRowUUIDGeneratorFn = eventName => {
4
+ if (eventName === 'UPDATE') return
5
+ return (keyNames, row, template) => {
6
+ for (const keyName of keyNames) {
7
+ if (Object.prototype.hasOwnProperty.call(row, keyName)) {
8
+ continue
9
+ }
10
+
11
+ const elementInfo = template.elements.get(keyName)
12
+ const plain = elementInfo && elementInfo.picked && elementInfo.picked.plain
13
+ if (!plain || !plain.categories) continue
14
+ if (plain.categories.includes('uuid')) {
15
+ row[keyName] = cds.utils.uuid()
16
+ }
17
+ }
18
+ }
19
+ }
20
+
21
+ module.exports = getRowUUIDGeneratorFn
@@ -1,16 +1,12 @@
1
1
  const DELIMITER = require('./templateDelimiter')
2
-
3
- const _formatRowContext = (tKey, keyNames, row) => {
4
- const keyValuePairs = keyNames.map(key => `${key}=${row[key]}`)
5
- const keyValuePairsSerialized = keyValuePairs.join(',')
6
- return `${tKey}(${keyValuePairsSerialized})`
7
- }
2
+ const pathSerializer = require('./templateProcessorPathSerializer')
8
3
 
9
4
  const _processElement = (processFn, row, key, target, picked = {}, isRoot, pathSegments) => {
10
5
  const element = (target.elements || target.params)[key]
11
6
  const { plain } = picked
12
7
 
13
8
  if (!plain) return
9
+
14
10
  /**
15
11
  * @type import('../../types/api').templateElementInfo
16
12
  */
@@ -19,6 +15,7 @@ const _processElement = (processFn, row, key, target, picked = {}, isRoot, pathS
19
15
  elementInfo.pathSegments = pathSegments.slice(0)
20
16
  elementInfo.pathSegments.push(...target._flat2struct[key])
21
17
  }
18
+
22
19
  processFn(elementInfo)
23
20
  }
24
21
 
@@ -36,34 +33,34 @@ const _processRow = (processFn, row, template, tKey, tValue, isRoot, pathOptions
36
33
 
37
34
  const _getTargetKeyNames = target => {
38
35
  const keyNames = []
36
+
39
37
  for (const keyName in target.keys) {
40
38
  if (target.keys[keyName].__isAssociationStrict) continue
41
39
  keyNames.push(keyName)
42
40
  }
41
+
43
42
  return keyNames
44
43
  }
45
44
 
46
45
  const _processComplex = (processFn, row, template, key, pathOptions) => {
47
- const value = row && row[key]
48
- const rows = Array.isArray(value) ? value : [value]
46
+ const subRow = row?.[key]
47
+ const rows = Array.isArray(subRow) ? subRow : [subRow]
49
48
  if (rows.length === 0) return
50
- const keyNames = _getTargetKeyNames(template.target)
49
+ const keyNames = pathOptions.includeKeyValues && _getTargetKeyNames(template.target)
51
50
 
52
51
  for (let idx = 0; idx < rows.length; idx++) {
53
52
  const row = rows[idx]
54
53
  if (row == null) continue
55
54
  const args = { processFn, row, template, isRoot: false, pathOptions }
56
55
 
57
- let rowContext
56
+ let pathSegment
58
57
  if (pathOptions.includeKeyValues) {
59
- if (pathOptions.rowKeysGenerator) pathOptions.rowKeysGenerator(keyNames, row, template)
60
- rowContext = _formatRowContext(key, keyNames, Object.assign({}, row, pathOptions.extraKeys))
58
+ if (pathOptions.rowUUIDGenerator) pathOptions.rowUUIDGenerator(keyNames, row, template)
59
+ pathSegment = pathSerializer(key, keyNames, row, template.target.elements, pathOptions.draftKeys)
61
60
  }
62
61
 
63
- if (pathOptions.pathSegments) pathOptions.pathSegments.push(rowContext || key)
64
-
62
+ if (pathOptions.pathSegments) pathOptions.pathSegments.push(pathSegment || key)
65
63
  templateProcessor(args)
66
-
67
64
  if (pathOptions.pathSegments) pathOptions.pathSegments.pop()
68
65
  }
69
66
  }
@@ -0,0 +1,23 @@
1
+ const templatePathSerializer = (tKey, keyNames, row, elements, draftKeys) => {
2
+ const keyValuePairs = keyNames.map(key => {
3
+ let quote
4
+
5
+ switch (elements[key].type) {
6
+ case 'cds.String':
7
+ quote = "'"
8
+ break
9
+
10
+ default:
11
+ quote = ''
12
+ break
13
+ }
14
+
15
+ const keyValue = row[key] ?? draftKeys?.[key]
16
+ return `${key}=${quote}${keyValue}${quote}`
17
+ })
18
+
19
+ const keyValuePairsSerialized = keyValuePairs.join(',')
20
+ return `${tKey}(${keyValuePairsSerialized})`
21
+ }
22
+
23
+ module.exports = templatePathSerializer
@@ -138,6 +138,7 @@ class JoinCQNFromExpanded {
138
138
  this._addImplicitOrderBy(readToOneCQN, entity, tableAlias)
139
139
  const givenColumns = readToOneCQN.columns
140
140
  readToOneCQN.columns = []
141
+ if (entity['@cds.localized'] === false) defaultLanguage = true
141
142
  this._expandedToFlat({ entity, givenColumns, readToOneCQN, tableAlias, toManyTree, defaultLanguage })
142
143
  } else {
143
144
  const table = unionTable || this._getRef(SELECT).table
@@ -150,6 +151,7 @@ class JoinCQNFromExpanded {
150
151
 
151
152
  const givenColumns = readToOneCQN.columns
152
153
  readToOneCQN.columns = []
154
+ if (entity['@cds.localized'] === false) defaultLanguage = true
153
155
  this._expandedToFlat({ entity, givenColumns, readToOneCQN, tableAlias, toManyTree, defaultLanguage })
154
156
  }
155
157
 
@@ -491,6 +493,7 @@ class JoinCQNFromExpanded {
491
493
  * @param {boolean} arg.defaultLanguage - Use default language for localized fields
492
494
  * @private
493
495
  */
496
+ // eslint-disable-next-line complexity
494
497
  _expandedToFlat({ entity, givenColumns, readToOneCQN, tableAlias, toManyTree, defaultLanguage }) {
495
498
  const toManyColumns = []
496
499
  const mappings = this._getMappingObject(toManyTree)
@@ -518,7 +521,8 @@ class JoinCQNFromExpanded {
518
521
  entity._isDraftEnabled &&
519
522
  navTarget._isAssociationStrict &&
520
523
  !navTarget['@odata.draft.enclosed'] &&
521
- navTarget.name !== 'DraftAdministrativeData'
524
+ navTarget.name !== 'DraftAdministrativeData' &&
525
+ !entity.elements[navProp]._isCompositionBacklink
522
526
  ) {
523
527
  mappings[navProp] = { [TO_ACTIVE]: true }
524
528
  }
@@ -660,7 +664,10 @@ class JoinCQNFromExpanded {
660
664
  const activeTableRequired =
661
665
  readToOneCQN[IS_UNION_DRAFT] || // > REVISIT: blocks expanding comp2one
662
666
  readToOneCQN[IS_ACTIVE] ||
663
- (element && element._isAssociationStrict && !element['@odata.draft.enclosed']) ||
667
+ (element &&
668
+ element._isAssociationStrict &&
669
+ !element['@odata.draft.enclosed'] &&
670
+ !element._isCompositionBacklink) ||
664
671
  !this._csn.definitions[target]._isDraftEnabled
665
672
 
666
673
  const colTarget = target && ensureUnlocalized(target)
@@ -688,7 +695,7 @@ class JoinCQNFromExpanded {
688
695
 
689
696
  const expandedEntity = this._getEntityForTable(target)
690
697
  if (readToOneCQN[IS_UNION_DRAFT] && expandedEntity.drafts) {
691
- const cols = column.expand.filter(c => !c.expand && !(c.ref[0] in DRAFT_COLUMNS_MAP)).map(c => c.ref[0])
698
+ const cols = column.expand.filter(c => c.ref && !c.expand && !(c.ref[0] in DRAFT_COLUMNS_MAP)).map(c => c.ref[0])
692
699
  const ks = Object.keys(expandedEntity.keys).filter(
693
700
  c => !expandedEntity.keys[c].isAssociation && !(c in DRAFT_COLUMNS_MAP)
694
701
  )
@@ -968,6 +975,23 @@ class JoinCQNFromExpanded {
968
975
  return column
969
976
  }
970
977
 
978
+ if (Array.isArray(column.xpr)) {
979
+ return this._buildNewAliasColumn(
980
+ {
981
+ ...column,
982
+ xpr: column.xpr.map(x => {
983
+ if (x.ref) {
984
+ const res = this._buildNewAliasColumn(x, entity, tableAlias, mappings)
985
+ delete res.as
986
+ return res
987
+ } else return x
988
+ })
989
+ },
990
+ entity,
991
+ tableAlias,
992
+ mappings
993
+ )
994
+ }
971
995
  return this._buildNewAliasColumn(column, entity, tableAlias, mappings)
972
996
  }
973
997
 
@@ -1146,7 +1170,8 @@ class JoinCQNFromExpanded {
1146
1170
  readToOneCQN[IS_ACTIVE] ||
1147
1171
  (element._isAssociationStrict && !element['@odata.draft.enclosed']) ||
1148
1172
  !this._csn.definitions[colTarget]._isDraftEnabled
1149
- const ref = this._getJoinRef(entity.elements, colRef[0], expandActive, defaultLanguageThis)
1173
+
1174
+ const ref = this._refFromRefByExpand(column.ref[0], colTarget, defaultLanguageThis, expandActive)
1150
1175
  const tableAlias = this._createAlias(toManyTree.concat(colRef).join(':'))
1151
1176
  const on = entity._relations[colRef[0]].join(tableAlias, 'filterExpand')
1152
1177
  const filterExpand = this._getFilterExpandCQN(readToOneCQN, on, parentAlias, entity.keys)
@@ -1239,14 +1264,6 @@ class JoinCQNFromExpanded {
1239
1264
  return cqn
1240
1265
  }
1241
1266
 
1242
- _getJoinRef(elements, column, isActive, defaultLanguage) {
1243
- const assoc = elements[column]
1244
- if (typeof isActive !== 'boolean' || isActive || !assoc.isComposition) {
1245
- return defaultLanguage ? ensureUnlocalized(assoc.target) : assoc.target
1246
- }
1247
- return assoc.target + '_drafts'
1248
- }
1249
-
1250
1267
  _isPathExpressionToOne(ref, entity) {
1251
1268
  const ref0 = ref[0]
1252
1269
  const el = entity.elements[ref0]
@@ -18,8 +18,6 @@ const propagateForeignKeys = require('../../common/utils/propagateForeignKeys')
18
18
  const getTemplate = require('../../common/utils/template')
19
19
  const templateProcessor = require('../../common/utils/templateProcessor')
20
20
 
21
- const { checkIfAssocDeep } = require('../../cds-services/util/assert')
22
-
23
21
  const { DRAFT_COLUMNS_MAP } = require('../../common/constants/draft')
24
22
 
25
23
  const _isManaged = (category, event) =>
@@ -85,7 +83,12 @@ const _processCategory = (req, category, { row, key, element }) => {
85
83
  if (k.isAssociation || k.name === 'IsActiveEntity') continue
86
84
 
87
85
  if (!k.isUUID && !(k.name in val)) {
88
- req.error(400, 'ASSERT_NOT_NULL', key + '[' + k + ']', [key + '[' + k + ']'])
86
+ req.error({
87
+ code: 'MUST_NOT_BE_NULL',
88
+ message: 'Value is required',
89
+ target: key + '[' + k + ']',
90
+ args: [key + '[' + k + ']']
91
+ })
89
92
  return
90
93
  }
91
94
  }
@@ -93,7 +96,7 @@ const _processCategory = (req, category, { row, key, element }) => {
93
96
 
94
97
  // not null without default (for better error message)
95
98
  if (category === '!default' && val == null && req.event === 'CREATE') {
96
- req.error(400, 'ASSERT_NOT_NULL', key, [key])
99
+ req.error({ code: 'MUST_NOT_BE_NULL', message: 'Value is required', target: key, args: [key] })
97
100
  return
98
101
  }
99
102
 
@@ -101,11 +104,6 @@ const _processCategory = (req, category, { row, key, element }) => {
101
104
  if (category === 'uuid' && !val && req.event === 'CREATE') {
102
105
  row[key] = cds.utils.uuid()
103
106
  }
104
-
105
- // check for forbidden deep operations for association
106
- if (category === 'associationEffective' && (req.event === 'CREATE' || req.event === 'UPDATE')) {
107
- checkIfAssocDeep(element, val, req)
108
- }
109
107
  }
110
108
 
111
109
  const _processorFn = req => elementInfo => {
@@ -152,10 +150,6 @@ const _pickCRUD = element => {
152
150
  categories.push({ category: '@cds.on.update', args: element['@cds.on.update'] })
153
151
  }
154
152
 
155
- if (element._isAssociationStrict && !element._target._hasPersistenceSkip) {
156
- categories.push('associationEffective')
157
- }
158
-
159
153
  // REVISIT: element._foreignKeys.length seems to be a very broad check
160
154
  if (element.isAssociation && element._foreignKeys.length) {
161
155
  categories.push({ category: 'propagateForeignKeys' })
@@ -36,6 +36,10 @@ class InsertBuilder extends BaseBuilder {
36
36
  this._csn = csn
37
37
  }
38
38
 
39
+ annotatedColumns(entityName, csn) {
40
+ return getAnnotatedColumns(entityName, csn)
41
+ }
42
+
39
43
  /**
40
44
  * Builds an Object based on the properties of the CQN object.
41
45
  *
@@ -77,7 +81,7 @@ class InsertBuilder extends BaseBuilder {
77
81
  this._findUuidKeys(entityName)
78
82
 
79
83
  this._columnIndexesToDelete = []
80
- const annotatedColumns = getAnnotatedColumns(entityName, this._csn)
84
+ const annotatedColumns = this.annotatedColumns(entityName, this._csn)
81
85
 
82
86
  if (this._obj.INSERT.columns) {
83
87
  this._removeAlreadyExistingInsertAnnotatedColumnsFromMap(annotatedColumns)
@@ -0,0 +1,24 @@
1
+ const InsertBuilder = require('./InsertBuilder')
2
+ const getAnnotatedColumns = require('./annotations')
3
+
4
+ class UpsertBuilder extends InsertBuilder {
5
+ constructor(obj, options, csn) {
6
+ super(obj, options, csn)
7
+ }
8
+
9
+ annotatedColumns(entityName, csn) {
10
+ const { updateAnnotatedColumns } = getAnnotatedColumns(entityName, csn)
11
+ return { insertAnnotatedColumns: updateAnnotatedColumns }
12
+ }
13
+
14
+ // REVISIT: We need to copy over the implementation for annotation handling
15
+ build() {
16
+ this._obj = { INSERT: this._obj.UPSERT }
17
+ super.build()
18
+ this._outputObj.sql = this._outputObj.sql.replace('INSERT INTO', 'UPSERT')
19
+ this._outputObj.sql += ' WITH PRIMARY KEY'
20
+ return this._outputObj
21
+ }
22
+ }
23
+
24
+ module.exports = UpsertBuilder
@@ -16,7 +16,10 @@ const _getAnnotationNames = column => {
16
16
  const getAnnotatedColumns = (entityName, csn) => {
17
17
  const entityNameWithoutSuffix = ensureNoDraftsSuffix(entityName)
18
18
  if (!csn || !csn.definitions[entityNameWithoutSuffix]) {
19
- return undefined
19
+ return {
20
+ insertAnnotatedColumns: new Map(),
21
+ updateAnnotatedColumns: new Map()
22
+ }
20
23
  }
21
24
  const columns = getColumns(csn.definitions[entityNameWithoutSuffix])
22
25
  const insertAnnotatedColumns = new Map()
@@ -39,8 +42,8 @@ const getAnnotatedColumns = (entityName, csn) => {
39
42
  }
40
43
 
41
44
  return {
42
- insertAnnotatedColumns: insertAnnotatedColumns,
43
- updateAnnotatedColumns: updateAnnotatedColumns
45
+ insertAnnotatedColumns,
46
+ updateAnnotatedColumns
44
47
  }
45
48
  }
46
49
 
@@ -3,6 +3,7 @@ const DropBuilder = require('./DropBuilder')
3
3
  const SelectBuilder = require('./SelectBuilder')
4
4
  const InsertBuilder = require('./InsertBuilder')
5
5
  const UpdateBuilder = require('./UpdateBuilder')
6
+ const UpsertBuilder = require('./UpsertBuilder')
6
7
  const DeleteBuilder = require('./DeleteBuilder')
7
8
  const ExpressionBuilder = require('./ExpressionBuilder')
8
9
  const ReferenceBuilder = require('./ReferenceBuilder')
@@ -18,6 +19,7 @@ module.exports = {
18
19
  SelectBuilder,
19
20
  InsertBuilder,
20
21
  UpdateBuilder,
22
+ UpsertBuilder,
21
23
  DeleteBuilder,
22
24
  ExpressionBuilder,
23
25
  ReferenceBuilder,
@@ -2,6 +2,7 @@ const DeleteBuilder = require('./DeleteBuilder')
2
2
  const InsertBuilder = require('./InsertBuilder')
3
3
  const SelectBuilder = require('./SelectBuilder')
4
4
  const UpdateBuilder = require('./UpdateBuilder')
5
+ const UpsertBuilder = require('./UpsertBuilder')
5
6
  const CreateBuilder = require('./CreateBuilder')
6
7
  const DropBuilder = require('./DropBuilder')
7
8
 
@@ -16,6 +17,10 @@ const _getCustomBuilderIfExists = (options, type) => {
16
17
  return options.customBuilder.UpdateBuilder
17
18
  }
18
19
 
20
+ case 'UPSERT': {
21
+ return options.customBuilder.UpsertBuilder
22
+ }
23
+
19
24
  case 'DELETE': {
20
25
  return options.customBuilder.DeleteBuilder
21
26
  }
@@ -77,6 +82,10 @@ const build = (cqn, options, csn) => {
77
82
  return build(_getCustomBuilderIfExists(options, 'UPDATE') || UpdateBuilder)
78
83
  }
79
84
 
85
+ if (cqn.UPSERT) {
86
+ return build(_getCustomBuilderIfExists(options, 'UPSERT') || UpsertBuilder)
87
+ }
88
+
80
89
  if (cqn.DELETE) {
81
90
  return build(_getCustomBuilderIfExists(options, 'DELETE') || DeleteBuilder)
82
91
  }
@@ -16,12 +16,14 @@ const getColumns = (entity, { _4db, onlyKeys } = { _4db: true, onlyKeys: false }
16
16
  if (!(entity && entity.elements)) return []
17
17
  const columnNames = []
18
18
  // REVISIT!!!
19
- const elements = Object.getPrototypeOf(entity.elements) || entity.elements
19
+ const elements = cds.env.features.lean_draft
20
+ ? entity.elements
21
+ : Object.getPrototypeOf(entity.elements) || entity.elements
20
22
  for (const elementName in elements) {
21
23
  const element = elements[elementName]
22
24
  if (onlyKeys && !element.key) continue
23
25
  if (element.isAssociation) continue
24
- if (_4db && entity._isDraftEnabled && elementName in DRAFT_COLUMNS_MAP) continue
26
+ if (!cds.env.features.lean_draft && _4db && entity._isDraftEnabled && elementName in DRAFT_COLUMNS_MAP) continue
25
27
  if ((cds.env.effective.odata.structs || cds.env.features.ucsn_struct_conversion) && element.elements) {
26
28
  columnNames.push(...resolveStructured({ element, structProperties: [] }, false))
27
29
  continue
@@ -714,16 +714,6 @@ const _getSiblingScenario = (req, columns, model, siblingIndex, nav) => {
714
714
  const _getSiblingQueryFromWhere = (query, queryIndex, parentQuery) => {
715
715
  if (query.SELECT && query.SELECT.where && queryIndex > 0) {
716
716
  for (let i = 0; i < query.SELECT.where.length; i++) {
717
- if (query.SELECT.where[i].xpr && queryIndex > 0) {
718
- const sibilingQueryFromWhere = _getSiblingQueryFromWhere(
719
- { SELECT: { where: query.SELECT.where[i].xpr } },
720
- queryIndex - 1,
721
- query
722
- )
723
-
724
- if (sibilingQueryFromWhere) return sibilingQueryFromWhere
725
- }
726
-
727
717
  if (query.SELECT.where[i] === 'exists' && queryIndex > 0) {
728
718
  return _getSiblingQueryFromWhere(query.SELECT.where[i + 1], queryIndex - 1, query)
729
719
  }
@@ -757,7 +747,6 @@ const _replaceWhereExists = (query, _siblingIndex, siblingCQN) => {
757
747
 
758
748
  const indexExists = query.SELECT.where.indexOf('exists')
759
749
  if (indexExists > -1) {
760
- if (_siblingIndex > 0) return _replaceWhereExists(query.SELECT.where[indexExists + 1], _siblingIndex - 1)
761
750
  query.SELECT.where.splice(indexExists + 1, 1, siblingCQN)
762
751
  }
763
752
  }
@@ -1019,7 +1008,7 @@ const _generateCQN = (req, columns, from, model) => {
1019
1008
  let siblingIndex = nav.indexOf('SiblingEntity')
1020
1009
 
1021
1010
  // it can also be a property access (new parser), then we must shift it
1022
- if (siblingIndex === 1 && req.target.elements[nav[0]]) {
1011
+ if (siblingIndex > 0 && req.target.elements[nav[0]] && !req.target.elements[nav[0]].isAssociation) {
1023
1012
  nav.shift()
1024
1013
  siblingIndex = siblingIndex - 1
1025
1014
  }