@sap/cds 7.6.4 → 7.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 (97) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/_i18n/i18n.properties +3 -0
  3. package/app/index.js +14 -8
  4. package/bin/serve.js +51 -19
  5. package/common.cds +16 -0
  6. package/lib/auth/ias-auth.js +2 -2
  7. package/lib/auth/index.js +1 -1
  8. package/lib/auth/jwt-auth.js +1 -1
  9. package/lib/compile/cdsc.js +23 -11
  10. package/lib/compile/for/nodejs.js +2 -2
  11. package/lib/compile/for/odata.js +4 -0
  12. package/lib/compile/load.js +7 -2
  13. package/lib/compile/to/sql.js +3 -0
  14. package/lib/dbs/cds-deploy.js +197 -220
  15. package/lib/env/defaults.js +2 -1
  16. package/lib/index.js +8 -2
  17. package/lib/linked/types.js +1 -0
  18. package/lib/log/format/json.js +4 -1
  19. package/lib/plugins.js +2 -2
  20. package/lib/ql/SELECT.js +8 -8
  21. package/lib/req/context.js +22 -13
  22. package/lib/req/request.js +10 -4
  23. package/lib/srv/cds-connect.js +9 -3
  24. package/lib/srv/cds-serve.js +5 -3
  25. package/lib/srv/middlewares/ctx-model.js +1 -1
  26. package/lib/srv/protocols/odata-v4.js +38 -9
  27. package/lib/srv/srv-api.js +98 -140
  28. package/lib/srv/srv-models.js +2 -2
  29. package/lib/srv/srv-tx.js +1 -0
  30. package/lib/utils/cds-utils.js +32 -23
  31. package/lib/utils/data.js +1 -1
  32. package/lib/utils/tar.js +1 -1
  33. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
  34. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -2
  35. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +18 -1
  36. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -1
  37. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +7 -3
  38. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
  39. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/index.js +5 -0
  40. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +71 -25
  41. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +10 -2
  42. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +6 -1
  43. package/libx/_runtime/cds-services/util/assert.js +50 -240
  44. package/libx/_runtime/cds.js +5 -0
  45. package/libx/_runtime/common/aspects/any.js +53 -45
  46. package/libx/_runtime/common/generic/input.js +14 -10
  47. package/libx/_runtime/common/generic/paging.js +1 -1
  48. package/libx/_runtime/common/utils/cqn.js +1 -1
  49. package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
  50. package/libx/_runtime/common/utils/keys.js +1 -1
  51. package/libx/_runtime/common/utils/quotingStyles.js +1 -1
  52. package/libx/_runtime/common/utils/resolveStructured.js +4 -1
  53. package/libx/_runtime/common/utils/rewriteAsterisks.js +5 -12
  54. package/libx/_runtime/common/utils/stream.js +2 -16
  55. package/libx/_runtime/common/utils/streamProp.js +16 -6
  56. package/libx/_runtime/common/utils/ucsn.js +1 -0
  57. package/libx/_runtime/db/expand/expandCQNToJoin.js +1 -1
  58. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  59. package/libx/_runtime/db/utils/columns.js +6 -1
  60. package/libx/_runtime/fiori/generic/activate.js +11 -3
  61. package/libx/_runtime/fiori/generic/edit.js +8 -2
  62. package/libx/_runtime/fiori/lean-draft.js +94 -30
  63. package/libx/_runtime/hana/execute.js +2 -5
  64. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +12 -22
  65. package/libx/_runtime/messaging/service.js +6 -2
  66. package/libx/common/assert/index.js +232 -0
  67. package/libx/common/assert/type.js +109 -0
  68. package/libx/common/assert/utils.js +125 -0
  69. package/libx/common/assert/validation.js +109 -0
  70. package/libx/odata/index.js +5 -5
  71. package/libx/odata/middleware/create.js +83 -0
  72. package/libx/odata/middleware/delete.js +38 -0
  73. package/libx/odata/middleware/error.js +8 -0
  74. package/libx/odata/{metadata.js → middleware/metadata.js} +8 -6
  75. package/libx/odata/middleware/operation.js +78 -0
  76. package/libx/odata/middleware/parse.js +11 -0
  77. package/libx/odata/{read.js → middleware/read.js} +42 -20
  78. package/libx/odata/{service-document.js → middleware/service-document.js} +2 -1
  79. package/libx/odata/middleware/stream.js +237 -0
  80. package/libx/odata/middleware/update.js +165 -0
  81. package/libx/odata/{afterburner.js → parse/afterburner.js} +79 -29
  82. package/libx/odata/{cqn2odata.js → parse/cqn2odata.js} +5 -3
  83. package/libx/odata/{parseToCqn.js → parse/parseToCqn.js} +3 -6
  84. package/libx/odata/{utils.js → utils/index.js} +95 -9
  85. package/libx/outbox/index.js +2 -1
  86. package/libx/rest/RestAdapter.js +0 -1
  87. package/libx/rest/middleware/operation.js +6 -4
  88. package/libx/rest/middleware/parse.js +20 -2
  89. package/package.json +1 -1
  90. package/server.js +43 -71
  91. package/libx/odata/create.js +0 -44
  92. package/libx/odata/delete.js +0 -25
  93. package/libx/odata/error.js +0 -12
  94. package/libx/odata/update.js +0 -110
  95. /package/libx/odata/{grammar.peggy → parse/grammar.peggy} +0 -0
  96. /package/libx/odata/{parser.js → parse/parser.js} +0 -0
  97. /package/libx/odata/{result.js → utils/result.js} +0 -0
@@ -5,20 +5,9 @@ const templatePathSerializer = require('../../common/utils/templateProcessorPath
5
5
  // REVISIT: replace with cds.Request
6
6
  const getEntry = require('../../common/error/entry')
7
7
 
8
- const ISO_DATE_PART1 =
9
- '[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)'
10
- const ISO_DATE_PART2 = '(?:[1-9]\\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29'
11
- const ISO_DATE = `(?:${ISO_DATE_PART1}|${ISO_DATE_PART2})`
12
- const ISO_TIME_NO_MILLIS = '(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d'
13
- const ISO_TIME = `${ISO_TIME_NO_MILLIS}(?:\\.\\d{1,9})?`
14
- const ISO_DATE_TIME = `${ISO_DATE}T${ISO_TIME_NO_MILLIS}(?:Z|[+-][01]\\d:?[0-5]\\d)`
15
- const ISO_TIMESTAMP = `${ISO_DATE}T${ISO_TIME}(?:Z|[+-][01]\\d:?[0-5]\\d)`
16
-
17
- const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
18
- const ISO_DATE_REGEX = new RegExp(`^${ISO_DATE}$`, 'i')
19
- const ISO_TIME_REGEX = new RegExp(`^${ISO_TIME_NO_MILLIS}$`, 'i')
20
- const ISO_DATE_TIME_REGEX = new RegExp(`^${ISO_DATE_TIME}$`, 'i')
21
- const ISO_TIMESTAMP_REGEX = new RegExp(`^${ISO_TIMESTAMP}$`, 'i')
8
+ const typeCheckers = require('../../../common/assert/type')
9
+ const { 'cds.Decimal': checkDecimal } = typeCheckers
10
+ const { checkMandatory, checkEnum, checkRange, checkFormat } = require('../../../common/assert/validation')
22
11
 
23
12
  const ASSERT_VALID_ELEMENT = 'ASSERT_VALID_ELEMENT'
24
13
  const ASSERT_RANGE = 'ASSERT_RANGE'
@@ -80,93 +69,6 @@ const assertError = (code, element, value, key, path) => {
80
69
  return assertError
81
70
  }
82
71
 
83
- const _checkString = value => typeof value === 'string'
84
-
85
- const _checkNumber = value => typeof value === 'number'
86
-
87
- const _checkDecimal = (value, element) => {
88
- const [left, right] = String(value).split('.')
89
- return (
90
- _checkNumber(value) &&
91
- (!element.precision || left.length <= element.precision - (element.scale || 0)) &&
92
- (!element.scale || ((right || '').length <= element.scale && parseFloat(right) !== 0))
93
- )
94
- }
95
-
96
- const _checkInteger = value => _checkNumber(value) && parseInt(value, 10) === value
97
-
98
- const _checkBoolean = value => typeof value === 'boolean'
99
-
100
- // REVISIT: Extension parameter in push is an object with buffer data
101
- const _checkBuffer = value => Buffer.isBuffer(value) || value.type === 'Buffer'
102
-
103
- const _checkUUID = value => {
104
- return _checkString(value) && UUID_REGEX.test(value)
105
- }
106
-
107
- const _checkISODate = value => (_checkString(value) && ISO_DATE_REGEX.test(value)) || value instanceof Date
108
-
109
- const _checkISOTime = value => _checkString(value) && ISO_TIME_REGEX.test(value)
110
-
111
- const _checkISODateTime = value => (_checkString(value) && ISO_DATE_TIME_REGEX.test(value)) || value instanceof Date
112
-
113
- const _checkISOTimestamp = value => (_checkString(value) && ISO_TIMESTAMP_REGEX.test(value)) || value instanceof Date
114
-
115
- const _checkDateValue = (val, r1, r2) => {
116
- const dateVal = new Date(val)
117
- return (dateVal - new Date(r1)) * (dateVal - new Date(r2)) <= 0
118
- }
119
-
120
- const _toDate = val => `2000-01-01T${val}Z`
121
-
122
- const _checkInRange = (val, range, type) => {
123
- switch (type) {
124
- case 'cds.Date':
125
- return _checkISODate(val) && _checkDateValue(val, range[0], range[1])
126
- case 'cds.DateTime':
127
- return _checkISODateTime(val) && _checkDateValue(val, range[0], range[1])
128
- case 'cds.Timestamp':
129
- return _checkISOTimestamp(val) && _checkDateValue(val, range[0], range[1])
130
- case 'cds.Time':
131
- return _checkISOTime(val) && _checkDateValue(_toDate(val), _toDate(range[0]), _toDate(range[1]))
132
- default:
133
- return (val - range[0]) * (val - range[1]) <= 0
134
- }
135
- }
136
-
137
- const _resolveCDSType = element => {
138
- if (element.type.startsWith('cds.')) return element.type
139
- if (!element.type) return
140
-
141
- return _resolveCDSType(element.__proto__)
142
- }
143
-
144
- // process.env.CDS_ASSERT_FORMAT_FLAGS not official!
145
- const _checkRegExpFormat = (val, format) =>
146
- _checkString(val) && val.match(new RegExp(format, process.env.CDS_ASSERT_FORMAT_FLAGS || 'u'))
147
-
148
- const CDS_TYPE_CHECKS = {
149
- 'cds.UUID': _checkUUID,
150
- 'cds.Boolean': _checkBoolean,
151
- 'cds.Integer': _checkInteger,
152
- 'cds.UInt8': _checkInteger,
153
- 'cds.Int16': _checkInteger,
154
- 'cds.Int32': _checkInteger,
155
- 'cds.Integer64': _checkInteger,
156
- 'cds.Int64': _checkInteger,
157
- 'cds.Decimal': _checkDecimal,
158
- 'cds.DecimalFloat': _checkNumber,
159
- 'cds.Double': _checkNumber,
160
- 'cds.Date': _checkISODate,
161
- 'cds.Time': _checkISOTime,
162
- 'cds.DateTime': _checkISODateTime,
163
- 'cds.Timestamp': _checkISOTimestamp,
164
- 'cds.String': _checkString,
165
- 'cds.Binary': _checkBuffer,
166
- 'cds.LargeString': _checkString,
167
- 'cds.LargeBinary': _checkBuffer
168
- }
169
-
170
72
  // Limitation: depth 1
171
73
  const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
172
74
  let found = false
@@ -174,12 +76,12 @@ const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
174
76
  for (const objKey in elements) {
175
77
  if (objKey.startsWith(`${key}_`)) {
176
78
  const element = elements[objKey]
177
- const check = CDS_TYPE_CHECKS[element._type]
79
+ const typeChecker = typeCheckers[element._type]
178
80
  found = true
179
81
 
180
82
  const nestedData = value[objKey.substring(key.length + 1)]
181
83
  // check existence of nestedData to not stumble across not-provided, yet-modelled type parts with depth > 1
182
- if (nestedData && !check(nestedData)) {
84
+ if (nestedData && !typeChecker(nestedData)) {
183
85
  return false
184
86
  }
185
87
  }
@@ -188,6 +90,7 @@ const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
188
90
  return found || ignoreNonModelledData
189
91
  }
190
92
 
93
+ // TODO: what fails if no-op?!
191
94
  const checkStaticElementByKey = (definition, key, value, result = [], ignoreNonModelledData = true) => {
192
95
  const elementsOrParameters = definition.elements || definition.params
193
96
  if (!elementsOrParameters) return result
@@ -201,15 +104,15 @@ const checkStaticElementByKey = (definition, key, value, result = [], ignoreNonM
201
104
  return result
202
105
  }
203
106
 
204
- let check
107
+ let typeChecker
205
108
  if (elementOrParameter.isUUID && definition.name === 'ProvisioningService.tenant') {
206
109
  // > old SCP accounts don't have UUID ids
207
- check = CDS_TYPE_CHECKS['cds.String']
110
+ typeChecker = typeCheckers['cds.String']
208
111
  } else {
209
- check = CDS_TYPE_CHECKS[elementOrParameter._type]
112
+ typeChecker = typeCheckers[elementOrParameter._type]
210
113
  }
211
114
 
212
- if (check && !check(value, elementOrParameter)) {
115
+ if (typeChecker && !typeChecker(value, elementOrParameter)) {
213
116
  // code, entity, element, value
214
117
  const args = [typeof value === 'string' ? '"' + value + '"' : value, elementOrParameter._type]
215
118
  result.push(assertError({ code: ASSERT_DATA_TYPE, args }, elementOrParameter, value, key))
@@ -218,131 +121,62 @@ const checkStaticElementByKey = (definition, key, value, result = [], ignoreNonM
218
121
  return result
219
122
  }
220
123
 
221
- const _isNotFilled = value =>
222
- value === null || value === undefined || (typeof value === 'string' && value.trim() === '')
223
-
224
- const _checkMandatoryElement = (element, value, errors, key, pathSegmentsInfo) => {
225
- if (element.parent?.query?.SELECT?.columns?.find(col => _isNavigationColumn(col, element.name))) return
226
- if (element._isMandatory && !element.default && _isNotFilled(value)) {
227
- errors.push(assertError(ASSERT_NOT_NULL, element, value, key, pathSegmentsInfo))
228
- }
229
- }
230
-
231
- const _isNavigationColumn = (column, searched) =>
232
- column.ref?.length > 1 && (column.as === searched || column.ref[column.ref.length - 1] === searched)
233
-
234
- const _getEnumElement = element =>
235
- (element['@assert.range'] && element.enum) || element['@assert.enum'] ? element.enum : undefined
236
-
237
- const _checkEnumElement = (element, value, errors, key, pathSegmentsInfo) => {
238
- const enumElements = _getEnumElement(element)
239
- const enumValues = enumElements && _enumValues(enumElements)
124
+ /**
125
+ * @param {import('../../types/api').InputConstraints} constraints
126
+ */
127
+ const checkInputConstraints = ({ element, value, errors, key, pathSegmentsInfo }) => {
128
+ if (!element) return errors
240
129
 
241
- if (enumElements && !enumValues.includes(value)) {
242
- const args =
243
- typeof value === 'string'
244
- ? ['"' + value + '"', enumValues.map(ele => '"' + ele + '"').join(', ')]
245
- : [value, enumValues.join(', ')]
130
+ let path
131
+ if (pathSegmentsInfo?.length) path = templatePathSerializer(element.name || key, pathSegmentsInfo)
246
132
 
247
- errors.push(assertError({ code: ASSERT_ENUM, args }, element, value, key, pathSegmentsInfo))
133
+ // not nice, but best option for keeping new cds.assert() clean
134
+ if (element._isMandatory) {
135
+ const mandatoryErrors = []
136
+ checkMandatory(value, element, mandatoryErrors, [], key)
137
+ if (mandatoryErrors.length) {
138
+ errors.push(...mandatoryErrors.map(() => assertError({ code: ASSERT_NOT_NULL }, element, value, key, path)))
139
+ }
248
140
  }
249
- }
250
141
 
251
- const _checkRangeElement = (element, value, errors, key, pathSegmentsInfo) => {
252
- const rangeElements = element['@assert.range'] && !_getEnumElement(element) ? element['@assert.range'] : undefined
253
- if (rangeElements && !_checkInRange(value, rangeElements, _resolveCDSType(element))) {
254
- const args = [value, ...element['@assert.range']]
255
- errors.push(assertError({ code: ASSERT_RANGE, args }, element, value, key, pathSegmentsInfo))
256
- }
257
- }
142
+ if (value == null) return errors
258
143
 
259
- const _checkFormatElement = (element, value, errors, key, pathSegments) => {
260
- const formatElements = element['@assert.format']
261
- if (formatElements && !_checkRegExpFormat(value, formatElements)) {
262
- errors.push(assertError({ code: ASSERT_FORMAT, args: [value, formatElements] }, element, value, key, pathSegments))
144
+ // not nice, but best option for keeping new cds.assert() clean
145
+ const enumErrors = []
146
+ checkEnum(value, element, enumErrors, [], key)
147
+ if (enumErrors.length) {
148
+ errors.push(...enumErrors.map(e => assertError({ code: ASSERT_ENUM, args: e.args }, element, value, key, path)))
263
149
  }
264
- }
265
150
 
266
- const getNormalizedDecimal = value => {
267
- let val = `${value}`
268
- const cgs = val.match(/^(\d*\.*\d*)e([+|-]*)(\d*)$/)
269
- if (cgs) {
270
- let [l, r = ''] = cgs[1].split('.')
271
- const dir = cgs[2] || '+'
272
- const exp = Number(cgs[3])
273
- if (dir === '+') {
274
- // move decimal point to the right
275
- r = r.padEnd(exp, '0')
276
- l += r.substring(0, exp)
277
- r = r.slice(exp)
278
- val = `${l}${r ? '.' + r : ''}`
279
- } else {
280
- // move decimal point to the left
281
- l = l.padStart(exp, '0')
282
- r = l.substring(0, exp) + r
283
- l = l.slice(exp)
284
- val = `${l ? l : '0'}.${r}`
285
- }
151
+ // not nice, but best option for keeping new cds.assert() clean
152
+ const rangeErrors = []
153
+ checkRange(value, element, rangeErrors, [], key)
154
+ if (rangeErrors.length) {
155
+ errors.push(...rangeErrors.map(e => assertError({ code: ASSERT_RANGE, args: e.args }, element, value, key, path)))
286
156
  }
287
- return val
288
- }
289
157
 
290
- const _checkDecimalElement = (element, value, errors, key, path) => {
291
- const { precision, scale } = element
292
- let val = getNormalizedDecimal(value)
293
- if (precision != null && scale != null) {
294
- let isValid = true
295
- if (!val.match(/\./)) val += '.0'
296
-
297
- if (precision === scale) {
298
- if (!val.match(new RegExp(`^-?0\\.\\d{0,${scale}}$`, 'g'))) isValid = false
299
- } else if (scale === 0) {
300
- if (!val.match(new RegExp(`^-?\\d{1,${precision - scale}}\\.0{0,1}$`, 'g'))) isValid = false
301
- } else if (!val.match(new RegExp(`^-?\\d{1,${precision - scale}}\\.\\d{0,${scale}}$`, 'g'))) {
302
- isValid = false
303
- }
158
+ // not nice, but best option for keeping new cds.assert() clean
159
+ const formatErrors = []
160
+ checkFormat(value, element, formatErrors, [], key)
161
+ if (formatErrors.length) {
162
+ errors.push(...formatErrors.map(e => assertError({ code: ASSERT_FORMAT, args: e.args }, element, value, key, path)))
163
+ }
304
164
 
305
- if (!isValid)
165
+ if (element.type === 'cds.Decimal') {
166
+ // not nice, but best option for keeping new cds.assert() clean
167
+ const decimalErrors = []
168
+ checkDecimal(value, element, decimalErrors, [], key)
169
+ if (decimalErrors.length) {
306
170
  errors.push(
307
- assertError(
308
- { code: ASSERT_DATA_TYPE, args: [value, `Decimal(${precision},${scale})`] },
309
- element,
310
- value,
311
- key,
312
- path
313
- )
314
- )
315
- } else if (precision != null) {
316
- if (!val.match(new RegExp(`^-?\\d{1,${precision}}$`, 'g'))) {
317
- errors.push(
318
- assertError({ code: ASSERT_DATA_TYPE, args: [value, `Decimal(${precision})`] }, element, value, key, path)
171
+ ...decimalErrors.map(e => assertError({ code: ASSERT_DATA_TYPE, args: e.args }, element, value, key, path))
319
172
  )
320
173
  }
321
174
  }
322
- }
323
-
324
- /**
325
- * @param {import('../../types/api').InputConstraints} constraints
326
- */
327
- const checkInputConstraints = ({ element, value, errors, key, pathSegmentsInfo }) => {
328
- if (!element) return errors
329
-
330
- let path
331
-
332
- if (pathSegmentsInfo?.length) path = templatePathSerializer(element.name || key, pathSegmentsInfo)
333
- _checkMandatoryElement(element, value, errors, key, path)
334
-
335
- if (value == null) return errors
336
-
337
- _checkEnumElement(element, value, errors, key, path)
338
- _checkRangeElement(element, value, errors, key, path)
339
- _checkFormatElement(element, value, errors, key, path)
340
-
341
- if (element.type === 'cds.Decimal') _checkDecimalElement(element, value, errors, key, path)
342
175
 
343
176
  return errors
344
177
  }
345
178
 
179
+ // TODO: what fails if no-op?!
346
180
  const checkStatic = (definition, data, ignoreNonModelledData = false) => {
347
181
  if (!Array.isArray(data)) data = [data]
348
182
 
@@ -355,24 +189,6 @@ const checkStatic = (definition, data, ignoreNonModelledData = false) => {
355
189
  }, [])
356
190
  }
357
191
 
358
- const checkKeys = (entity, data) => {
359
- if (!Array.isArray(data)) {
360
- return checkKeys(entity, [data])
361
- }
362
-
363
- const entityKeys = Object.keys(entity.keys)
364
- return data.reduce((result, row) => {
365
- for (const key of entityKeys) {
366
- if (row[key] === undefined && !entity.elements[key].isAssociation)
367
- result.push(assertError(ASSERT_NOT_NULL, entity.elements[key]))
368
- }
369
-
370
- return result
371
- }, [])
372
- }
373
-
374
- const assertNotNullError = element => assertError(ASSERT_NOT_NULL, element)
375
-
376
192
  /**
377
193
  * Check whether the target entity referenced by the association (the reference's target) exists and assert an error if
378
194
  * the the reference's target doesn't exist.
@@ -428,14 +244,8 @@ const assertTargets = async (assertMap, errors) => {
428
244
  }
429
245
 
430
246
  module.exports = {
431
- CDS_TYPE_CHECKS,
432
- checkComplexType,
433
247
  checkStatic,
434
- checkInputConstraints,
435
- checkKeys,
436
- assertError,
437
248
  checkStaticElementByKey,
438
- assertNotNullError,
439
- assertTargets,
440
- getNormalizedDecimal
249
+ checkInputConstraints,
250
+ assertTargets
441
251
  }
@@ -10,6 +10,11 @@ cds.extend(any).with(require('./common/aspects/any'))
10
10
  cds.extend(Association).with(require('./common/aspects/Association'))
11
11
  cds.extend(entity).with(require('./common/aspects/entity'))
12
12
 
13
+ /*
14
+ * cds.assert(data, definition, options?)
15
+ */
16
+ Object.defineProperty(cds, 'assert', { get: () => require('../common/assert') })
17
+
13
18
  /**
14
19
  * Logs a deprecation warning once per process
15
20
  *
@@ -1,31 +1,34 @@
1
1
  const { foreignKey4 } = require('../../common/utils/foreignKeyPropagations')
2
2
 
3
- // NOTE: Please only add things which are relevant to _any_ type,
4
- // use specialized types otherwise (entity, Association, ...).
5
- module.exports = class {
6
- get _isStructured() {
7
- return this.own('__isStructured', () => !!this.elements && this.kind !== 'entity')
8
- }
9
-
10
- get _isMandatory() {
11
- return this.own('__isMandatory', () => !this.isAssociation && isMandatory(this))
12
- }
13
-
14
- get _isReadOnly() {
15
- return this.own('__isReadOnly', () => !this.key && isReadOnly(this))
16
- }
3
+ const _getCommonFieldControl = e => {
4
+ const cfr = e['@Common.FieldControl']
5
+ return cfr && cfr['#']
6
+ }
17
7
 
18
- // REVISIT: Where to put?
19
- get _relations() {
20
- return this.own('__relations', () => getRelations(this))
21
- }
8
+ const _isMandatory = e => {
9
+ return (
10
+ e['@assert.mandatory'] !== false &&
11
+ (e['@mandatory'] ||
12
+ e['@Common.FieldControl.Mandatory'] ||
13
+ e['@FieldControl.Mandatory'] ||
14
+ _getCommonFieldControl(e) === 'Mandatory')
15
+ )
16
+ }
22
17
 
23
- get _foreignKey4() {
24
- return this.own('__foreignKey4', () => foreignKey4(this))
25
- }
18
+ const _isReadOnly = e => {
19
+ return (
20
+ e['@readonly'] ||
21
+ e['@cds.on.update'] ||
22
+ e['@cds.on.insert'] ||
23
+ e['@Core.Computed'] ||
24
+ e['@Common.FieldControl.ReadOnly'] ||
25
+ e['@FieldControl.ReadOnly'] ||
26
+ _getCommonFieldControl(e) === 'ReadOnly'
27
+ )
26
28
  }
27
29
 
28
30
  const Relation = require('./relation')
31
+
29
32
  const _exposeRelation = relation => Object.defineProperty({}, '_', { get: () => relation })
30
33
 
31
34
  const _relationHandler = relation => ({
@@ -52,34 +55,39 @@ const _relationHandler = relation => ({
52
55
  }
53
56
  })
54
57
 
55
- const getRelations = e => {
58
+ const _getRelations = e => {
56
59
  const newRelation = Relation.to(e)
57
60
  return new Proxy(_exposeRelation(newRelation), _relationHandler(newRelation))
58
61
  }
59
62
 
60
- const CommonFieldControl = e => {
61
- const cfr = e['@Common.FieldControl']
62
- return cfr && cfr['#']
63
- }
63
+ // NOTE: Please only add things which are relevant to _any_ type,
64
+ // use specialized types otherwise (entity, Association, ...).
65
+ module.exports = class {
66
+ get _isStructured() {
67
+ return this.own('__isStructured', () => !!this.elements && this.kind !== 'entity')
68
+ }
64
69
 
65
- const isMandatory = e => {
66
- return (
67
- e['@assert.mandatory'] !== false &&
68
- (e['@mandatory'] ||
69
- e['@Common.FieldControl.Mandatory'] ||
70
- e['@FieldControl.Mandatory'] ||
71
- CommonFieldControl(e) === 'Mandatory')
72
- )
73
- }
70
+ get _isMandatory() {
71
+ return this.own('__isMandatory', () => !this.isAssociation && _isMandatory(this))
72
+ }
74
73
 
75
- const isReadOnly = e => {
76
- return (
77
- e['@readonly'] ||
78
- e['@cds.on.update'] ||
79
- e['@cds.on.insert'] ||
80
- e['@Core.Computed'] ||
81
- e['@Common.FieldControl.ReadOnly'] ||
82
- e['@FieldControl.ReadOnly'] ||
83
- CommonFieldControl(e) === 'ReadOnly'
84
- )
74
+ get _isReadOnly() {
75
+ return this.own('__isReadOnly', () => !this.key && _isReadOnly(this))
76
+ }
77
+
78
+ get _mandatories() {
79
+ return this.own(
80
+ '__mandatories',
81
+ () => this.elements && Object.entries(this.elements).filter(([_, v]) => v._isMandatory)
82
+ )
83
+ }
84
+
85
+ // REVISIT: Where to put?
86
+ get _relations() {
87
+ return this.own('__relations', () => _getRelations(this))
88
+ }
89
+
90
+ get _foreignKey4() {
91
+ return this.own('__foreignKey4', () => foreignKey4(this))
92
+ }
85
93
  }
@@ -179,19 +179,23 @@ const _pick = element => {
179
179
  categories.push({ category: 'propagateForeignKeys' })
180
180
  }
181
181
 
182
- if (
183
- element['@assert.range'] ||
184
- element['@assert.enum'] ||
185
- element['@assert.format'] ||
186
- element.type === 'cds.Decimal'
187
- ) {
188
- categories.push('assert')
189
- }
182
+ if (!cds.env.features.cds_assert) {
183
+ if (
184
+ element['@assert.range'] ||
185
+ element['@assert.enum'] ||
186
+ element['@assert.format'] ||
187
+ element.type === 'cds.Decimal'
188
+ ) {
189
+ categories.push('assert')
190
+ }
190
191
 
191
- if (element._isMandatory) {
192
- categories.push('mandatory')
192
+ if (element._isMandatory) {
193
+ categories.push('mandatory')
194
+ }
193
195
  }
194
196
 
197
+ // TODO: can we move more checks to cds.assert()?
198
+
195
199
  if (element._isReadOnly) {
196
200
  // > _isReadOnly includes @cds.on.insert and @cds.on.update
197
201
  categories.push('readonly')
@@ -21,7 +21,7 @@ const getPageSize = def => {
21
21
  DEFAULT
22
22
  if (!max) max = Number.MAX_SAFE_INTEGER
23
23
  if (!_default || _default > max) _default = max
24
- return (def[_cached] = { default: _default, max })
24
+ return def.set(_cached, { default: _default, max })
25
25
  }
26
26
 
27
27
  const commonGenericPaging = function (req) {
@@ -37,7 +37,7 @@ function where2obj(where, target = null, data = {}) {
37
37
  // optional validation if target is passed
38
38
  if (target) {
39
39
  const colEl = target.elements[colName]
40
- if (!colEl || !(colEl.key || colEl.__foreignKey4)) continue
40
+ if (!colEl || !colEl.key) continue
41
41
  }
42
42
  const opWhere = where[i + 1]
43
43
  const valWhere = where[i + 2]
@@ -12,7 +12,7 @@ const getError = require('../../common/error')
12
12
  const { rewriteAsterisks } = require('./rewriteAsterisks')
13
13
  const { getEntityFromPath } = require('../../common/utils/path')
14
14
  const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
15
- const { addRefToWhereIfNecessary } = require('../../../odata/afterburner')
15
+ const { addRefToWhereIfNecessary } = require('../../../odata/parse/afterburner')
16
16
  const { addAliasToExpression, PARENT_ALIAS, FOREIGN_ALIAS } = require('../../db/utils/generateAliases')
17
17
  const { getColumns } = require('../../cds-services/services/utils/columns')
18
18
 
@@ -57,7 +57,7 @@ function _buildWhereForNavigations(ref, newWhere, model, target) {
57
57
 
58
58
  if (!navigationElement || !navigationElement.on) return
59
59
 
60
- const onCond = getOnCond(navigationElement, [], { select: 'source', join: 'target' })
60
+ const onCond = getOnCond(navigationElement, [navigationElement.name], { select: 'source', join: 'target' })
61
61
  const nextKeys = _getOnCondElements(onCond[0].xpr)
62
62
 
63
63
  // only add where once in _modifyWhereWithNavigations
@@ -1,6 +1,6 @@
1
1
  const cds = require('../../cds')
2
2
 
3
- const { smartId } = require('@sap/cds-compiler/lib/sql-identifier')
3
+ const { smartId } = cds.compiler.to.sql
4
4
 
5
5
  let _dialect
6
6
 
@@ -13,17 +13,20 @@ const _flattenProps = (element, structProperties, asRef, withKey, prefix) => {
13
13
  if (structProperties.length && element.is2one && !element.on) {
14
14
  const resolved = [...prefix, ...structProperties]
15
15
  if (withKey) {
16
+ // TODO: should be "element._alias || element.name", but how to get alias from edm?
17
+ // cf. removed alias2ref (https://github.tools.sap/cap/cds/pull/3583)
16
18
  return [{ key: element.name, resolved }]
17
19
  }
18
20
  const flattenedName = resolved.join('_')
19
21
  return asRef ? [{ ref: [flattenedName] }] : [flattenedName]
20
22
  }
21
-
22
23
  return []
23
24
  }
24
25
 
25
26
  const resolved = [...prefix, element.name]
26
27
  if (withKey) {
28
+ // TODO: should be "element._alias || element.name", but how to get alias from edm?
29
+ // cf. removed alias2ref (https://github.tools.sap/cap/cds/pull/3583)
27
30
  return [{ key: element.name, resolved }]
28
31
  }
29
32
  const flattenedName = resolved.join('_')
@@ -68,22 +68,15 @@ const rewriteExpandAsterisk = (columns, target) => {
68
68
  }
69
69
  }
70
70
 
71
- const _isLargeBinary = (col, target) => target.elements[col.ref[col.ref.length - 1]]?.type === 'cds.LargeBinary'
72
-
73
71
  const _rewriteAsterisk = (columns, target, _4db, isRoot) => {
74
72
  const asteriskColumnIndex = columns.findIndex(col => isAsteriskColumn(col))
75
73
  if (asteriskColumnIndex > -1) {
76
74
  columns.splice(
77
75
  asteriskColumnIndex,
78
76
  1,
79
- ...getColumns(target, { _4db })
77
+ ...getColumns(target, { _4db, omitStream: !cds.env.features.stream_compat })
80
78
  .map(c => ({ ref: [c.name] }))
81
- .filter(
82
- c =>
83
- !columns.find(isDuplicate(c)) &&
84
- (!_4db || cds.env.features.stream_compat || !_isLargeBinary(c, target)) &&
85
- (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID')
86
- )
79
+ .filter(c => !columns.find(isDuplicate(c)) && (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID'))
87
80
  )
88
81
  }
89
82
  }
@@ -150,9 +143,9 @@ const rewriteAsterisks = (query, model, options) => {
150
143
  const target = _targetOfQueryIfNotDraft(query, model)
151
144
  if (!target) return
152
145
 
153
- query.SELECT.columns = getColumns(target, { _4db })
154
- .map(col => ({ ref: [col.name] }))
155
- .filter(col => !_4db || cds.env.features.stream_compat || !_isLargeBinary(col, target))
146
+ query.SELECT.columns = getColumns(target, { _4db, omitStream: !cds.env.features.stream_compat }).map(col => ({
147
+ ref: [col.name]
148
+ }))
156
149
  if (_4db && target._isDraftEnabled && !cds.env.fiori.lean_draft)
157
150
  query.SELECT.columns.push(..._cqlDraftColumns(target))
158
151
  }