@sap/cds-compiler 6.1.0 → 6.3.0

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 (90) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/bin/cdsc.js +17 -6
  3. package/bin/cdsse.js +1 -1
  4. package/bin/cdsv2m.js +1 -1
  5. package/lib/api/main.js +29 -7
  6. package/lib/api/options.js +1 -1
  7. package/lib/base/builtins.js +9 -0
  8. package/lib/base/keywords.js +1 -1
  9. package/lib/base/message-registry.js +41 -10
  10. package/lib/base/messages.js +13 -6
  11. package/lib/base/model.js +1 -1
  12. package/lib/base/optionProcessorHelper.js +7 -2
  13. package/lib/checks/assocOutsideService.js +17 -30
  14. package/lib/checks/checkForTypes.js +0 -18
  15. package/lib/checks/checkPathsInStoredCalcElement.js +2 -1
  16. package/lib/checks/featureFlags.js +4 -1
  17. package/lib/checks/onConditions.js +2 -2
  18. package/lib/checks/queryNoDbArtifacts.js +16 -15
  19. package/lib/checks/types.js +1 -1
  20. package/lib/checks/utils.js +30 -6
  21. package/lib/checks/validator.js +4 -5
  22. package/lib/compiler/assert-consistency.js +3 -1
  23. package/lib/compiler/base.js +1 -1
  24. package/lib/compiler/builtins.js +1 -1
  25. package/lib/compiler/checks.js +85 -39
  26. package/lib/compiler/define.js +24 -5
  27. package/lib/compiler/extend.js +1 -1
  28. package/lib/compiler/finalize-parse-cdl.js +9 -1
  29. package/lib/compiler/generate.js +4 -4
  30. package/lib/compiler/index.js +88 -6
  31. package/lib/compiler/lsp-api.js +2 -0
  32. package/lib/compiler/populate.js +8 -8
  33. package/lib/compiler/propagator.js +1 -1
  34. package/lib/compiler/resolve.js +22 -21
  35. package/lib/compiler/shared.js +6 -6
  36. package/lib/compiler/tweak-assocs.js +53 -31
  37. package/lib/compiler/utils.js +9 -16
  38. package/lib/compiler/xpr-rewrite.js +2 -2
  39. package/lib/gen/BaseParser.js +35 -29
  40. package/lib/gen/CdlGrammar.checksum +1 -1
  41. package/lib/gen/CdlParser.js +1424 -1430
  42. package/lib/gen/Dictionary.json +1 -2
  43. package/lib/gen/cdlKeywords.json +26 -0
  44. package/lib/inspect/inspectPropagation.js +1 -1
  45. package/lib/json/from-csn.js +2 -2
  46. package/lib/json/to-csn.js +1 -1
  47. package/lib/language/multiLineStringParser.js +1 -1
  48. package/lib/model/cloneCsn.js +1 -0
  49. package/lib/model/csnRefs.js +9 -4
  50. package/lib/model/csnUtils.js +67 -2
  51. package/lib/optionProcessor.js +9 -9
  52. package/lib/parsers/AstBuildingParser.js +28 -26
  53. package/lib/parsers/identifiers.js +2 -30
  54. package/lib/render/toCdl.js +73 -13
  55. package/lib/render/toSql.js +127 -108
  56. package/lib/render/utils/common.js +4 -2
  57. package/lib/render/utils/sql.js +67 -0
  58. package/lib/transform/addTenantFields.js +4 -4
  59. package/lib/transform/db/assertUnique.js +2 -1
  60. package/lib/transform/db/associations.js +37 -1
  61. package/lib/transform/db/assocsToQueries/transformExists.js +21 -32
  62. package/lib/transform/db/assocsToQueries/utils.js +1 -1
  63. package/lib/transform/db/cdsPersistence.js +1 -1
  64. package/lib/transform/db/expansion.js +37 -36
  65. package/lib/transform/db/killAnnotations.js +1 -0
  66. package/lib/transform/db/processSqlServices.js +20 -2
  67. package/lib/transform/draft/db.js +20 -20
  68. package/lib/transform/draft/odata.js +38 -40
  69. package/lib/transform/effective/associations.js +1 -1
  70. package/lib/transform/effective/flattening.js +40 -47
  71. package/lib/transform/effective/main.js +6 -4
  72. package/lib/transform/forOdata.js +201 -92
  73. package/lib/transform/forRelationalDB.js +151 -142
  74. package/lib/transform/localized.js +116 -109
  75. package/lib/transform/odata/adaptAnnotationRefs.js +21 -16
  76. package/lib/transform/odata/createForeignKeys.js +73 -70
  77. package/lib/transform/odata/flattening.js +216 -200
  78. package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +47 -45
  79. package/lib/transform/odata/toFinalBaseType.js +40 -39
  80. package/lib/transform/odata/typesExposure.js +151 -133
  81. package/lib/transform/odata/utils.js +7 -6
  82. package/lib/transform/parseExpr.js +165 -162
  83. package/lib/transform/transformUtils.js +184 -551
  84. package/lib/transform/translateAssocsToJoins.js +511 -596
  85. package/lib/transform/tupleExpansion.js +495 -0
  86. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  87. package/lib/utils/moduleResolve.js +1 -1
  88. package/package.json +2 -2
  89. package/lib/base/cleanSymbols.js +0 -17
  90. package/lib/checks/nonexpandableStructured.js +0 -39
@@ -4,26 +4,25 @@
4
4
  // different backends.
5
5
  // The sibling of model/transform/TransformUtil.js which works with compacted new CSN.
6
6
 
7
- const { setProp, isBetaEnabled } = require('../base/model');
7
+ const { setProp } = require('../base/model');
8
8
 
9
- const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand, isDeepEqual } = require('../model/csnUtils');
9
+ const { copyAnnotations, applyTransformations, isDeepEqual } = require('../model/csnUtils');
10
10
  const { getUtils } = require('../model/csnUtils');
11
11
  const { typeParameters } = require('../compiler/builtins');
12
12
  const { isBuiltinType } = require('../base/builtins');
13
- const { ModelError, CompilerAssertion} = require('../base/error');
13
+ const { ModelError, CompilerAssertion } = require('../base/error');
14
14
  const { forEach } = require('../utils/objectUtils');
15
15
  const { cloneCsnNonDict, cloneCsnDict } = require('../model/cloneCsn');
16
16
  const { addTenantFieldToArt } = require('./addTenantFields');
17
+ const { tupleExpansion } = require('./tupleExpansion');
17
18
 
18
- const RestrictedOperators = ['<', '>', '>=', '<='];
19
- const RelationalOperators = ['=', '<>', '==', '!=', 'is' /*, 'like'*/,...RestrictedOperators];
20
19
  // Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).
21
20
  // Use 'pathDelimiter' for flattened names (e.g. of struct elements or foreign key elements).
22
21
  // 'model' is compacted new style CSN
23
22
  // TODO: Error and warnings handling with compacted CSN? - currently just throw new ModelError for everything
24
23
  // TODO: check the situation with assocs with values. In compacted CSN such elements have only "@Core.Computed": true
25
24
  function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
26
- const { message, error, warning, info } = msgFunctions;
25
+ const { error, warning } = msgFunctions;
27
26
  const csnUtils = getUtils(model);
28
27
  const {
29
28
  getCsnDef,
@@ -32,11 +31,10 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
32
31
  isStructured,
33
32
  effectiveType,
34
33
  } = csnUtils;
34
+ const tuples = tupleExpansion(model, csnUtils, msgFunctions, options);
35
35
 
36
36
  return {
37
37
  csnUtils,
38
- resolvePath,
39
- flattenPath,
40
38
  addDefaultTypeFacets,
41
39
  flattenStructuredElement,
42
40
  flattenStructStepsInRef,
@@ -60,7 +58,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
60
58
  renameAnnotation,
61
59
  setAnnotation,
62
60
  resetAnnotation,
63
- expandStructsInExpression,
61
+ expandStructsInExpression: tuples.expandStructsInExpression,
62
+ flattenPath: tuples.flattenPath,
64
63
  };
65
64
 
66
65
  /**
@@ -70,7 +69,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
70
69
  *
71
70
  * @param {CSN.Element} element
72
71
  * @param {null|object} [internalDefaultLengths] Either null (no implicit default) or an object `{ 'cds.String': N, 'cds.Binary': N }`.
73
- **/
72
+ * */
74
73
  function addDefaultTypeFacets(element, internalDefaultLengths = null) {
75
74
  if (!element || !element.type)
76
75
  return;
@@ -89,7 +88,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
89
88
  element.length = options.defaultBinaryLength;
90
89
  setProp(element, '$default', true);
91
90
  }
92
- else if(internalDefaultLengths !== null) {
91
+ else if (internalDefaultLengths !== null) {
93
92
  element.length = internalDefaultLengths[element.type];
94
93
  }
95
94
  }
@@ -128,8 +127,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
128
127
  // type: 'cds.String',
129
128
  // length: 42 },
130
129
  // }
131
- function flattenStructuredElement(elem, elemName, parentElementPath=[], pathInCsn=[]) {
132
- const elementPath=parentElementPath.concat(elemName); // elementPath contains only element names without the csn structure node names
130
+ function flattenStructuredElement(elem, elemName, parentElementPath = [], pathInCsn = []) {
131
+ const elementPath = parentElementPath.concat(elemName); // elementPath contains only element names without the csn structure node names
133
132
  // in case the element is of user defined type => take the definition of the type
134
133
  // for type of 'x' -> elem.type is an object, not a string -> use directly
135
134
  let elemType;
@@ -144,14 +143,14 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
144
143
  const result = Object.create(null);
145
144
  const addGeneratedFlattenedElement = (e, eName) => {
146
145
  if (result[eName])
147
- error('name-duplicate-element', pathInCsn, { '#': 'flatten-element-gen', name: eName })
146
+ error('name-duplicate-element', pathInCsn, { '#': 'flatten-element-gen', name: eName });
148
147
  else
149
148
  result[eName] = e;
150
- }
149
+ };
151
150
  forEach(struct, (childName, childElem) => {
152
151
  if (isStructured(childElem)) {
153
152
  // Descend recursively into structured children
154
- const grandChildElems = flattenStructuredElement(childElem, childName, elementPath, pathInCsn.concat('elements',childName));
153
+ const grandChildElems = flattenStructuredElement(childElem, childName, elementPath, pathInCsn.concat('elements', childName));
155
154
  for (const grandChildName in grandChildElems) {
156
155
  const flatElemName = elemName + pathDelimiter + grandChildName;
157
156
  const flatElem = grandChildElems[grandChildName];
@@ -163,7 +162,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
163
162
  // }
164
163
  // Preserve the generated element name as it would have been with 'hdbcds' names
165
164
  }
166
- } else {
165
+ }
166
+ else {
167
167
  // Primitive child - clone it and restore its cross references
168
168
  const flatElemName = elemName + pathDelimiter + childName;
169
169
  const flatElem = cloneCsnNonDict(childElem, options);
@@ -184,29 +184,28 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
184
184
  // When flattening structured elements for OData don't propagate the odata.Type annotations
185
185
  // as these would falsify the flattened elements. Type facets must be aligned with
186
186
  // EdmTypeFacetMap defined in edm.js
187
- const excludes = options.toOdata ?
188
- {
189
- '@odata.Type': 1,
190
- '@odata.Scale': 1,
191
- '@odata.Precision': 1,
192
- '@odata.MaxLength': 1,
193
- '@odata.SRID': 1,
194
- '@odata.FixedLength': 1,
195
- '@odata.Collation': 1,
196
- '@odata.Unicode': 1,
197
- } : {};
187
+ const excludes = options.toOdata
188
+ ? {
189
+ '@odata.Type': 1,
190
+ '@odata.Scale': 1,
191
+ '@odata.Precision': 1,
192
+ '@odata.MaxLength': 1,
193
+ '@odata.SRID': 1,
194
+ '@odata.FixedLength': 1,
195
+ '@odata.Collation': 1,
196
+ '@odata.Unicode': 1,
197
+ } : {};
198
198
 
199
199
  copyAnnotations(elem, flatElem, false, excludes);
200
200
 
201
201
  // Copy selected type properties
202
- const props = ['key', 'virtual', 'masked', 'viaAll'];
202
+ const props = [ 'key', 'virtual', 'masked', 'viaAll' ];
203
203
  // 'localized' is needed for OData
204
- if(options.toOdata)
204
+ if (options.toOdata)
205
205
  props.push('localized');
206
206
  for (const p of props) {
207
- if (elem[p]) {
207
+ if (elem[p])
208
208
  flatElem[p] = elem[p];
209
- }
210
209
  }
211
210
  });
212
211
  return result;
@@ -232,17 +231,17 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
232
231
  * @todo: Refactor to take config object instead of N boolean arguments.
233
232
  * @returns [string[], bool]
234
233
  */
235
- function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), suspend=false, suspendPos=0, revokeAtSuspendPos=false, flattenParameters = false) {
234
+ function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes = new WeakMap(), suspend = false, suspendPos = 0, revokeAtSuspendPos = false, flattenParameters = false) {
236
235
  // A path is absolute if it starts with $self or a parameter. Then we must not flatten the first path step.
237
236
  const pathIsAbsolute = scope === '$self' || (!flattenParameters && scope === 'param');
238
237
  // Refs of length 1 cannot contain steps - no need to check
239
- if (ref.length < 2 || (pathIsAbsolute && ref.length === 2)) {
238
+ if (ref.length < 2 || (pathIsAbsolute && ref.length === 2))
240
239
  return [ ref, false ];
241
- }
242
240
 
243
- const result = pathIsAbsolute ? [ref[0]] : [];
244
- //let stack = []; // IDs of path steps not yet processed or part of a struct traversal
245
- if(!links && !scope) { // calculate JIT if not supplied
241
+
242
+ const result = pathIsAbsolute ? [ ref[0] ] : [];
243
+ // let stack = []; // IDs of path steps not yet processed or part of a struct traversal
244
+ if (!links && !scope) { // calculate JIT if not supplied
246
245
  const res = inspectRef(path);
247
246
  links = res.links;
248
247
  scope = res.scope;
@@ -254,23 +253,21 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
254
253
  let i = pathIsAbsolute ? 1 : 0;
255
254
 
256
255
  // read property from resolved path link
257
- const art = (propName) =>
258
- (links[i].art?.[propName] ||
256
+ const art = propName => (links[i].art?.[propName] ||
259
257
  effectiveType(links[i].art)[propName] ||
260
- (resolvedLinkTypes.get(links[i])||{})[propName]);
258
+ (resolvedLinkTypes.get(links[i]) || {})[propName]);
261
259
 
262
- let refChanged = false
263
- let flattenStep = false;
260
+ let refChanged = false;
261
+ let flattenStep = false;
264
262
  suspend = !!art('items') || (suspend && i <= suspendPos);
265
- for(; i < links.length; i++) {
266
-
263
+ for (; i < links.length; i++) {
267
264
  if (flattenStep && !suspend) {
268
265
  result[result.length - 1] += pathDelimiter + (ref[i].id ? ref[i].id : ref[i]);
269
266
  // if we had a filter or args, we had an assoc so this step is done
270
267
  // we then keep along the filter/args by updating the id of the current ref
271
- if(ref[i].id) {
272
- ref[i].id = result[result.length-1];
273
- result[result.length-1] = ref[i];
268
+ if (ref[i].id) {
269
+ ref[i].id = result[result.length - 1];
270
+ result[result.length - 1] = ref[i];
274
271
  }
275
272
  refChanged = true;
276
273
  // suspend flattening if the next path step has some 'items'
@@ -281,7 +278,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
281
278
  suspend ||= !!art('items');
282
279
  }
283
280
  // revoke items suspension for next assoc step
284
- if(suspend && art('target') || (revokeAtSuspendPos && i === suspendPos))
281
+ if (suspend && art('target') || (revokeAtSuspendPos && i === suspendPos))
285
282
  suspend = false;
286
283
 
287
284
  flattenStep = !links[i].art?.kind &&
@@ -302,9 +299,9 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
302
299
  */
303
300
  function toFinalBaseType(nodeWithType, resolved = new WeakMap(), keepLocalized = false) {
304
301
  const type = nodeWithType?.type;
305
- if (!type || nodeWithType.elements || nodeWithType.items || resolved.has(nodeWithType)) {
302
+ if (!type || nodeWithType.elements || nodeWithType.items || resolved.has(nodeWithType))
306
303
  return;
307
- }
304
+
308
305
  // The caller may use `{ art }` syntax for `{ ref }` objects, but we only use
309
306
  // it to indicate that an artifact has been processed.
310
307
  resolved.set(nodeWithType, nodeWithType);
@@ -313,8 +310,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
313
310
  if (typeof type === 'string' && isBuiltinType(type))
314
311
  return;
315
312
 
316
- const typeRef = getFinalTypeInfo(type, (t) => resolved.get(t)?.art || csnUtils.artifactRef(t));
317
- if(!typeRef)
313
+ const typeRef = getFinalTypeInfo(type, t => resolved.get(t)?.art || csnUtils.artifactRef(t));
314
+ if (!typeRef)
318
315
  return;
319
316
 
320
317
  if (typeRef.elements || typeRef.items) {
@@ -335,9 +332,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
335
332
  // Copy type and type arguments (+ localized)
336
333
 
337
334
  for (const param of typeParameters.list) {
338
- if (nodeWithType[param] === undefined && typeRef[param] !== undefined &&!typeRef.$default) {
335
+ if (nodeWithType[param] === undefined && typeRef[param] !== undefined && !typeRef.$default)
339
336
  nodeWithType[param] = typeRef[param];
340
- }
341
337
  }
342
338
  if (keepLocalized && nodeWithType.localized === undefined && typeRef.localized !== undefined)
343
339
  nodeWithType.localized = typeRef.localized;
@@ -349,39 +345,38 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
349
345
  // Add the created projection to the model and complain if artifact already exists.
350
346
  // Used by Draft generation
351
347
  function createExposingProjection(art, artName, projectionId, service) {
352
- const projectionAbsoluteName = `${service}.${projectionId}`;
348
+ const projectionAbsoluteName = `${ service }.${ projectionId }`;
353
349
  // Create elements matching the artifact's elements
354
350
  const elements = Object.create(null);
355
- art.elements && Object.entries(art.elements).forEach(([elemName, artElem]) => {
356
- const elem = Object.assign({}, artElem);
351
+ Object.entries(art.elements || {}).forEach(([ elemName, artElem ]) => {
357
352
  // Transfer xrefs, that are redirected to the projection
358
353
  // TODO: shall we remove the transferred elements from the original?
359
354
  // if (artElem._xref) {
360
355
  // setProp(elem, '_xref', artElem._xref.filter(xref => xref.user && xref.user._main && xref.user._main._service == service));
361
356
  // }
362
357
  // FIXME: Remove once the compactor no longer renders 'origin'
363
- elements[elemName] = elem;
358
+ elements[elemName] = Object.assign({}, artElem);
364
359
  });
365
360
 
366
361
  const query = {
367
- 'SELECT': {
368
- 'from': {
369
- 'ref': [
370
- artName
371
- ]
372
- }
373
- }
362
+ SELECT: {
363
+ from: {
364
+ ref: [
365
+ artName,
366
+ ],
367
+ },
368
+ },
374
369
  };
375
370
  // Assemble the projection itself and add it into the model
376
371
  const projection = {
377
- 'kind': 'entity',
372
+ kind: 'entity',
378
373
  projection: query.SELECT, // it is important that projection and query refer to the same object!
379
- elements
374
+ elements,
380
375
  };
381
376
  // copy annotations from art to projection
382
- for (const a of Object.keys(art).filter(x => x.startsWith('@'))) {
377
+ for (const a of Object.keys(art).filter(x => x.startsWith('@')))
383
378
  projection[a] = art[a];
384
- }
379
+
385
380
  model.definitions[projectionAbsoluteName] = projection;
386
381
  return projection;
387
382
  }
@@ -396,18 +391,17 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
396
391
  * @param {boolean} [hanaMode=false] Turn UUID into String(36)
397
392
  * @returns {CSN.Artifact}
398
393
  */
399
- function createAndAddDraftAdminDataProjection(service, hanaMode=false) {
394
+ function createAndAddDraftAdminDataProjection(service, hanaMode = false) {
400
395
  // Make sure we have a DRAFT.DraftAdministrativeData entity
401
396
  let draftAdminDataEntity = model.definitions['DRAFT.DraftAdministrativeData'];
402
397
  if (!draftAdminDataEntity) {
403
398
  draftAdminDataEntity = createAndAddDraftAdminDataEntity();
404
399
  model.definitions['DRAFT.DraftAdministrativeData'] = draftAdminDataEntity;
405
- if ((isBetaEnabled(options, 'draftMessages') || options.draftMessages)
406
- && options.transformation === 'odata'
407
- && !model.definitions['DRAFT.DraftAdministrativeData_DraftMessage']) {
400
+ if (options.draftMessages && options.transformation === 'odata' &&
401
+ !model.definitions['DRAFT.DraftAdministrativeData_DraftMessage'])
408
402
  model.definitions['DRAFT.DraftAdministrativeData_DraftMessage'] = createDraftAdminDataMessagesType();
409
- }
410
- if(options.tenantDiscriminator && options.transformation !== 'odata')
403
+
404
+ if (options.tenantDiscriminator && options.transformation !== 'odata')
411
405
  addTenantFieldToArt(model.definitions['DRAFT.DraftAdministrativeData'], options);
412
406
  }
413
407
 
@@ -424,11 +418,11 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
424
418
  kind: 'entity',
425
419
  elements: Object.create(null),
426
420
  '@Common.Label': '{i18n>Draft_DraftAdministrativeData}',
427
- }
421
+ };
428
422
 
429
423
  // key DraftUUID : UUID
430
424
  const draftUuid = createScalarElement('DraftUUID', hanaMode ? 'cds.String' : 'cds.UUID', true);
431
- if(hanaMode)
425
+ if (hanaMode)
432
426
  draftUuid.DraftUUID.length = 36;
433
427
 
434
428
  draftUuid.DraftUUID['@UI.Hidden'] = true;
@@ -442,10 +436,18 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
442
436
 
443
437
  // CreatedByUser : String(256);
444
438
  const createdByUser = createScalarElement('CreatedByUser', 'cds.String');
445
- createdByUser['CreatedByUser'].length = 256;
439
+ createdByUser.CreatedByUser.length = 256;
446
440
  createdByUser.CreatedByUser['@Common.Label'] = '{i18n>Draft_CreatedByUser}';
447
441
  addElement(createdByUser, artifact, artifactName);
448
442
 
443
+ if ((options.draftUserDescription && options.transformation === 'odata') || options.transformation !== 'odata') {
444
+ // CreatedByUserDescription : String(256);
445
+ const createdByUserDescription = createScalarElement('CreatedByUserDescription', 'cds.String');
446
+ createdByUserDescription.CreatedByUserDescription.length = 256;
447
+ createdByUserDescription.CreatedByUserDescription['@Common.Label'] = '{i18n>Draft_CreatedByUserDescription}';
448
+ addElement(createdByUserDescription, artifact, artifactName);
449
+ }
450
+
449
451
  // DraftIsCreatedByMe : Boolean;
450
452
  const draftIsCreatedByMe = createScalarElement('DraftIsCreatedByMe', 'cds.Boolean');
451
453
  draftIsCreatedByMe.DraftIsCreatedByMe['@UI.Hidden'] = true;
@@ -459,31 +461,47 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
459
461
 
460
462
  // LastChangedByUser : String(256);
461
463
  const lastChangedByUser = createScalarElement('LastChangedByUser', 'cds.String');
462
- lastChangedByUser['LastChangedByUser'].length = 256;
464
+ lastChangedByUser.LastChangedByUser.length = 256;
463
465
  lastChangedByUser.LastChangedByUser['@Common.Label'] = '{i18n>Draft_LastChangedByUser}';
464
466
  addElement(lastChangedByUser, artifact, artifactName);
465
467
 
468
+ if (options.transformation !== 'odata' || options.draftUserDescription) {
469
+ // LastChangedByUserDescription : String(256);
470
+ const lastChangedByUserDescription = createScalarElement('LastChangedByUserDescription', 'cds.String');
471
+ lastChangedByUserDescription.LastChangedByUserDescription.length = 256;
472
+ lastChangedByUserDescription.LastChangedByUserDescription['@Common.Label'] = '{i18n>Draft_LastChangedByUserDescription}';
473
+ addElement(lastChangedByUserDescription, artifact, artifactName);
474
+ }
475
+
466
476
  // InProcessByUser : String(256);
467
477
  const inProcessByUser = createScalarElement('InProcessByUser', 'cds.String');
468
- inProcessByUser['InProcessByUser'].length = 256;
478
+ inProcessByUser.InProcessByUser.length = 256;
469
479
  inProcessByUser.InProcessByUser['@Common.Label'] = '{i18n>Draft_InProcessByUser}';
470
480
  addElement(inProcessByUser, artifact, artifactName);
471
481
 
482
+ if (options.transformation !== 'odata' || options.draftUserDescription) {
483
+ // InProcessByUserDescription : String(256);
484
+ const inProcessByUserDescription = createScalarElement('InProcessByUserDescription', 'cds.String');
485
+ inProcessByUserDescription.InProcessByUserDescription.length = 256;
486
+ inProcessByUserDescription.InProcessByUserDescription['@Common.Label'] = '{i18n>Draft_InProcessByUserDescription}';
487
+ addElement(inProcessByUserDescription, artifact, artifactName);
488
+ }
489
+
472
490
  // DraftIsProcessedByMe : Boolean;
473
491
  const draftIsProcessedByMe = createScalarElement('DraftIsProcessedByMe', 'cds.Boolean');
474
492
  draftIsProcessedByMe.DraftIsProcessedByMe['@UI.Hidden'] = true;
475
493
  draftIsProcessedByMe.DraftIsProcessedByMe['@Common.Label'] = '{i18n>Draft_DraftIsProcessedByMe}';
476
494
  addElement(draftIsProcessedByMe, artifact, artifactName);
477
495
 
478
- if (isBetaEnabled(options, 'draftMessages') || options.draftMessages) {
496
+ if (options.transformation !== 'odata' || options.draftMessages) {
479
497
  const messages = { DraftMessages: { } };
480
- if (options.transformation === 'odata') {
498
+ if (options.transformation === 'odata')
481
499
  messages.DraftMessages = { items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } };
482
- } else {
500
+ else
483
501
  messages.DraftMessages = { type: 'cds.LargeString' };
484
- }
502
+
485
503
  messages.DraftMessages['@cds.api.ignore'] = true;
486
- addElement(messages , artifact, artifactName);
504
+ addElement(messages, artifact, artifactName);
487
505
  }
488
506
 
489
507
  return artifact;
@@ -495,13 +513,13 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
495
513
  function createDraftAdminDataMessagesType() {
496
514
  const messagesType = {
497
515
  kind: 'type',
498
- elements: Object.create(null)
499
- }
516
+ elements: Object.create(null),
517
+ };
500
518
 
501
519
  addElement(createScalarElement('code', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
502
520
  addElement(createScalarElement('message', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
503
521
  addElement(createScalarElement('target', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
504
- addElement({ 'additionalTargets': createScalarElement('items', 'cds.String') }, messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
522
+ addElement({ additionalTargets: createScalarElement('items', 'cds.String') }, messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
505
523
  addElement(createScalarElement('transition', 'cds.Boolean'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
506
524
  addElement(createScalarElement('numericSeverity', 'cds.UInt8'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
507
525
  addElement(createScalarElement('longtextUrl', 'cds.String'), messagesType, 'DRAFT.DraftAdministrativeData_DraftMessage');
@@ -523,26 +541,26 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
523
541
  // example result: { foo: { type: 'cds.Integer', key: true, default: { val: 6 }, notNull: true } }
524
542
  // ^^^ ^^^^^^^^^ ^^^^ ^^ ^^
525
543
  // elemName typeName isKey defaultVal notNull
526
- function createScalarElement(elemName, typeName, isKey = false, defaultVal = undefined, notNull=false) {
527
- if (!isBuiltinType(typeName) && !model.definitions[typeName]) {
528
- throw new ModelError('Expecting valid type name: ' + typeName);
529
- }
544
+ function createScalarElement(elemName, typeName, isKey = false, defaultVal = undefined, notNull = false) {
545
+ if (!isBuiltinType(typeName) && !model.definitions[typeName])
546
+ throw new ModelError(`Expecting valid type name: ${ typeName }`);
547
+
530
548
  const result = {
531
549
  [elemName]: {
532
- type: typeName
533
- }
550
+ type: typeName,
551
+ },
534
552
  };
535
- if (isKey) {
553
+ if (isKey)
536
554
  result[elemName].key = true;
537
- }
555
+
538
556
  if (defaultVal !== undefined) {
539
557
  result[elemName].default = {
540
558
  val: defaultVal,
541
- }
559
+ };
542
560
  }
543
- if(notNull) {
561
+ if (notNull)
544
562
  result[elemName].notNull = true;
545
- }
563
+
546
564
  return result;
547
565
  }
548
566
 
@@ -562,7 +580,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
562
580
  if (isManaged) {
563
581
  assoc.keys = [];
564
582
  const targetArt = getCsnDef(target);
565
- targetArt.elements && Object.entries(targetArt.elements).forEach(([keyElemName, keyElem]) => {
583
+ Object.entries(targetArt.elements || {}).forEach(([ keyElemName, keyElem ]) => {
566
584
  if (keyElem.key) {
567
585
  const foreignKey = createForeignKey(keyElemName, keyElem);
568
586
  addForeignKey(foreignKey, assoc);
@@ -579,29 +597,28 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
579
597
  // assoc foreignElem op elem
580
598
  function createAssociationPathComparison(assoc, foreignElem, op, elem) {
581
599
  return [
582
- { ref: [assoc, foreignElem] }, op, { ref: [elem] }
583
- ]
600
+ { ref: [ assoc, foreignElem ] }, op, { ref: [ elem ] },
601
+ ];
584
602
  }
585
603
 
586
604
  // Create an artificial foreign key 'keyElemName' for key element 'keyElem'. Note that this
587
605
  // only creates a foreign key, not the generated foreign key element.
588
606
  // TODO: check the usage of this function's param 'keyElem' ?
589
607
  function createForeignKey(keyElemName, keyElem = undefined) { /* eslint-disable-line no-unused-vars */
590
-
591
608
  return {
592
- ref: [keyElemName]
609
+ ref: [ keyElemName ],
593
610
  // TODO: do we need these two?
594
611
  // calculated: true,
595
612
  // $inferred: 'keys',
596
- }
613
+ };
597
614
  }
598
615
 
599
616
  // Add foreign key 'foreignKey' to association element 'elem'.
600
617
  function addForeignKey(foreignKey, elem) {
601
618
  // Sanity checks
602
- if (!elem.target || !elem.keys) {
619
+ if (!elem.target || !elem.keys)
603
620
  throw new ModelError('Expecting managed association element with foreign keys');
604
- }
621
+
605
622
 
606
623
  // Add the foreign key
607
624
  elem.keys.push(foreignKey);
@@ -618,16 +635,16 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
618
635
  */
619
636
  function addElement(elem, artifact, artifactName) {
620
637
  // Sanity check
621
- if (!artifact.elements) {
622
- throw new ModelError('Expecting artifact with elements: ' + JSON.stringify(artifact));
623
- }
638
+ if (!artifact.elements)
639
+ throw new ModelError(`Expecting artifact with elements: ${ JSON.stringify(artifact) }`);
640
+
624
641
  const elemName = Object.keys(elem)[0];
625
642
  // Element must not exist
626
643
  if (artifact.elements[elemName]) {
627
644
  let path = null;
628
- if (artifactName) {
629
- path = ['definitions', artifactName, 'elements', elemName];
630
- }
645
+ if (artifactName)
646
+ path = [ 'definitions', artifactName, 'elements', elemName ];
647
+
631
648
  error(null, path, { name: elemName }, 'Generated element $(NAME) conflicts with existing element');
632
649
  return;
633
650
  }
@@ -649,18 +666,18 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
649
666
  * @param {string} elementName
650
667
  */
651
668
  function copyAndAddElement(elem, artifact, artifactName, elementName) {
652
- if (!artifact.elements) {
669
+ if (!artifact.elements)
653
670
  throw new ModelError('Expected structured artifact');
654
- }
671
+
655
672
  // Must not already have such an element
656
673
  if (artifact.elements[elementName]) {
657
- const path = ['definitions', artifactName, 'elements', elementName];
674
+ const path = [ 'definitions', artifactName, 'elements', elementName ];
658
675
  error(null, path, { name: elementName }, 'Generated element $(NAME) conflicts with existing element');
659
676
  }
660
677
 
661
678
  const result = Object.create(null);
662
679
  result[elementName] = {};
663
- elem && Object.entries(elem).forEach(([prop, value]) => {
680
+ Object.entries(elem || {}).forEach(([ prop, value ]) => {
664
681
  result[elementName][prop] = value;
665
682
  });
666
683
  Object.assign(artifact.elements, result);
@@ -673,15 +690,15 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
673
690
  // Assemble the action
674
691
  const result = {
675
692
  [actionName]: {
676
- kind: 'action'
677
- }
693
+ kind: 'action',
694
+ },
678
695
  };
679
696
 
680
697
  const action = result[actionName];
681
698
 
682
699
  if (returnTypeName) {
683
700
  if (!isBuiltinType(returnTypeName) && !model.definitions[returnTypeName])
684
- throw new ModelError('Expecting valid return type name: ' + returnTypeName);
701
+ throw new ModelError(`Expecting valid return type name: ${ returnTypeName }`);
685
702
  action.returns = { type: returnTypeName };
686
703
  // TODO: What about annotation propagation from return type to `returns`?
687
704
  }
@@ -689,12 +706,12 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
689
706
  // Add parameter if provided
690
707
  if (paramName && paramTypeName) {
691
708
  if (!isBuiltinType(paramTypeName) && !model.definitions[paramTypeName])
692
- throw new ModelError('Expecting valid parameter type name: ' + paramTypeName);
709
+ throw new ModelError(`Expecting valid parameter type name: ${ paramTypeName }`);
693
710
 
694
711
  action.params = Object.create(null);
695
712
  action.params[paramName] = {
696
- type: paramTypeName
697
- }
713
+ type: paramTypeName,
714
+ };
698
715
  }
699
716
 
700
717
  return result;
@@ -706,11 +723,11 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
706
723
  * @param {object} action Action that shall be added to the given artifact.
707
724
  * In form of `{ myAction: { kind: 'action', returns ... } }`
708
725
  * @param {CSN.Artifact} artifact Artifact in the form of `{ kind: 'entity', elements: ... }`
709
- **/
726
+ * */
710
727
  function assignAction(action, artifact) {
711
- if (!artifact.actions) {
728
+ if (!artifact.actions)
712
729
  artifact.actions = Object.create(null);
713
- }
730
+
714
731
 
715
732
  const actionName = Object.keys(action)[0];
716
733
  // Element must not exist
@@ -729,13 +746,18 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
729
746
  * second field if it has @cds.valid.to. Default value is [] for each field.
730
747
  */
731
748
  function extractValidFromToKeyElement(element, path) {
732
- const validFroms = [], validTos = [], validKeys = [];
749
+ const validFroms = [];
733
750
  if (element['@cds.valid.from'])
734
- validFroms.push({ element, path: [...path] });
751
+ validFroms.push({ element, path: [ ...path ] });
752
+
753
+ const validTos = [];
735
754
  if (element['@cds.valid.to'])
736
- validTos.push({ element, path: [...path] });
755
+ validTos.push({ element, path: [ ...path ] });
756
+
757
+ const validKeys = [];
737
758
  if (element['@cds.valid.key'])
738
- validKeys.push({ element, path: [...path] });
759
+ validKeys.push({ element, path: [ ...path ] });
760
+
739
761
  return [ validFroms, validTos, validKeys ];
740
762
  }
741
763
 
@@ -778,12 +800,11 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
778
800
  */
779
801
  function checkMultipleAssignments(array, annoName, artifact, artifactName, err = true) {
780
802
  if (array.length > 1) {
781
- const loc = ['definitions', artifactName];
782
- if (err === true) {
803
+ const loc = [ 'definitions', artifactName ];
804
+ if (err === true)
783
805
  error(null, loc, { anno: annoName }, 'Annotation $(ANNO) must be assigned only once');
784
- } else {
785
- warning(null, loc, { anno: annoName },'Annotation $(ANNO) must be assigned only once');
786
- }
806
+ else
807
+ warning(null, loc, { anno: annoName }, 'Annotation $(ANNO) must be assigned only once');
787
808
  }
788
809
  }
789
810
 
@@ -796,7 +817,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
796
817
  */
797
818
  function recurseElements(artifact, path, callback) {
798
819
  callback(artifact, path);
799
- const elements = artifact.elements;
820
+ const { elements } = artifact;
800
821
  if (elements) {
801
822
  path.push('elements', null);
802
823
  forEach(elements, (name, obj) => {
@@ -812,16 +833,16 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
812
833
  function renameAnnotation(node, fromName, toName) {
813
834
  const annotation = node && node[fromName];
814
835
  // Sanity checks
815
- if (!fromName.startsWith('@')) {
816
- throw new CompilerAssertion('Annotation name should start with "@": ' + fromName);
817
- }
818
- if (!toName.startsWith('@')) {
819
- throw new CompilerAssertion('Annotation name should start with "@": ' + toName);
820
- }
821
- if (annotation === undefined) {
822
- throw new CompilerAssertion('Annotation ' + fromName + ' not found in ' + JSON.stringify(node));
823
- }
824
- if(node[toName] == null) {
836
+ if (!fromName.startsWith('@'))
837
+ throw new CompilerAssertion(`Annotation name should start with "@": ${ fromName }`);
838
+
839
+ if (!toName.startsWith('@'))
840
+ throw new CompilerAssertion(`Annotation name should start with "@": ${ toName }`);
841
+
842
+ if (annotation === undefined)
843
+ throw new CompilerAssertion(`Annotation ${ fromName } not found in ${ JSON.stringify(node) }`);
844
+
845
+ if (node[toName] == null) {
825
846
  delete node[fromName];
826
847
  node[toName] = annotation;
827
848
  }
@@ -837,12 +858,12 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
837
858
  * @returns {void}
838
859
  */
839
860
  function setAnnotation(node, name, value) {
840
- if (!name.startsWith('@')) {
841
- throw new CompilerAssertion('Annotation name should start with "@": ' + name);
842
- }
843
- if (value === undefined) {
861
+ if (!name.startsWith('@'))
862
+ throw new CompilerAssertion(`Annotation name should start with "@": ${ name }`);
863
+
864
+ if (value === undefined)
844
865
  throw new CompilerAssertion('Annotation value must not be undefined');
845
- }
866
+
846
867
  node[name] ??= value;
847
868
  }
848
869
 
@@ -859,408 +880,22 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
859
880
  * @returns {boolean} wasOverwritten true when the annotation was overwritten
860
881
  */
861
882
  function resetAnnotation(node, name, value, info, path) {
862
- if (!name.startsWith('@')) {
863
- throw new CompilerAssertion('Annotation name should start with "@": ' + name);
864
- }
865
- if (value === undefined) {
883
+ if (!name.startsWith('@'))
884
+ throw new CompilerAssertion(`Annotation name should start with "@": ${ name }`);
885
+
886
+ if (value === undefined)
866
887
  throw new CompilerAssertion('Annotation value must not be undefined');
867
- }
888
+
868
889
 
869
890
  const wasOverwritten = node[name] !== undefined && node[name] !== null && node[name] !== value;
870
891
  const oldValue = node[name];
871
892
  node[name] = value;
872
- if(wasOverwritten)
893
+ if (wasOverwritten) {
873
894
  info(null, path, { anno: name, prop: value, otherprop: oldValue },
874
- 'Value $(OTHERPROP) of annotation $(ANNO) is overwritten with new value $(PROP)');
875
- return wasOverwritten;
876
- }
877
-
878
- /*
879
- Resolve the type of an artifact
880
- If art is undefined, stop
881
- If art has elements or items.elements, stop
882
- If art has a type and the type is scalar, stop
883
- If art has a named type or a type ref, resolve it
884
- */
885
- function resolveType(art) {
886
- while(art &&
887
- !((art.items && art.items.elements) || art.elements) &&
888
- (art.type &&
889
- ((!art.type.ref && !isBuiltinType(art.type)) || art.type.ref))) {
890
- if(art.type.ref)
891
- art = resolvePath(art.type);
892
- else
893
- art = model.definitions[art.type];
894
- }
895
- return art;
896
- }
897
-
898
- /**
899
- * Path resolution, attach artifact to each path step, if found,
900
- * Dereference types and follow associations.
901
- *
902
- * @param {any} path ref object
903
- * @param {any} art start environment
904
- * @returns {any} path with resolved artifacts or artifact
905
- * (if called with simple ref paths)
906
- */
907
- function resolvePath(path, art=undefined) {
908
- let notFound = false;
909
- for(let i = 0; i < path.ref.length && !notFound; i++) {
910
- const ps = path.ref[i];
911
- const id = ps.id || ps;
912
- if(art) {
913
- if(art.target)
914
- art = model.definitions[art.target].elements[id];
915
- else if(art.items && art.items.elements || art.elements) {
916
- art = (art.items && art.items.elements || art.elements)[id];
917
- }
918
- else
919
- art = undefined;
920
- }
921
- else {
922
- art = model.definitions[id];
923
- }
924
- art = resolveType(art);
925
-
926
- // if path step has id, store art
927
- if(ps.id && art)
928
- ps._art = art;
929
- notFound = !art;
930
- }
931
- // if resolve was called on constraint path, path has id.
932
- // Store art and return path, if called recursively for model ref paths,
933
- // return artifact only
934
- if(path.ref[0].id) {
935
- if(art)
936
- path._art = art;
937
- return path;
938
- }
939
- else return art;
940
- }
941
-
942
- /*
943
- Flatten structured leaf types and return an array of paths.
944
-
945
- Argument 'path' must be an object of the form
946
- { _art: <leaf_artifact>, ref: [...] }
947
- with _art identifying ref[ref.length-1]
948
-
949
- A produced path has the form { _art: <ref>, ref: [ <id> (, <id>)* ] }
950
-
951
- Flattening stops on all non structured elements, if followMgdAssoc=false.
952
-
953
- If fullRef is true, a path step is produced as { id: <id>, _art: <link> }
954
- */
955
- function flattenPath(path, fullRef=false, followMgdAssoc=false) {
956
- let art = path._art;
957
- if(art) {
958
- if(art && !((art.items && art.items.elements) || art.elements)) {
959
- if(followMgdAssoc && art.target && art.keys) {
960
- const rc = [];
961
- for(const k of art.keys) {
962
- const nps = { ref: k.ref.map(p => fullRef ? { id: p } : p ) };
963
- setProp(nps, '_art', k._art);
964
- const paths = flattenPath( nps, fullRef, followMgdAssoc );
965
- // prepend prefix path
966
- paths.forEach(p=>p.ref.splice(0, 0, ...path.ref));
967
- rc.push(...paths);
968
- }
969
- return rc;
970
- }
971
- if(art.type && art.type.ref)
972
- art = resolvePath(art.type);
973
- else if(art.type && !isBuiltinType(art.type))
974
- art = model.definitions[art.type];
975
- }
976
- const elements = art.items && art.items.elements || art.elements;
977
- if(elements) {
978
- const rc = []
979
- Object.entries(elements).forEach(([en, elt]) => {
980
- const nps = { ref: [ (fullRef ? { id: en, _art: elt } : en )] };
981
- setProp(nps, '_art', elt);
982
- const paths = flattenPath( nps, fullRef, followMgdAssoc );
983
- // prepend prefix path
984
- paths.forEach(p=>p.ref.splice(0, 0, ...path.ref));
985
- rc.push(...paths);
986
- });
987
- return rc;
988
- }
989
- else
990
- setProp(path, '_art', art);
991
- }
992
- return [path];
993
- }
994
-
995
- /**
996
- * Expand structured expression arguments to flat reference paths.
997
- * Structured elements are real sub element lists and managed associations.
998
- * All unmanaged association definitions are rewritten if applicable (elements/mixins).
999
- * Also, HAVING and WHERE clauses are rewritten. We also check for infix filters and
1000
- * .xpr in columns.
1001
- *
1002
- * @todo Check if can be skipped for abstract entity and or cds.persistence.skip ?
1003
- * @param {CSN.Model} csn
1004
- * @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts
1005
- */
1006
- function expandStructsInExpression(csn, options = {}) {
1007
- applyTransformations(csn, {
1008
- 'on': (parent, name, on, path) => {
1009
- parent.on = expand(parent.on, path.concat(name));
1010
- },
1011
- 'having': (parent, name, having, path) => {
1012
- parent.having = expand(parent.having, path.concat(name));
1013
- },
1014
- 'where': (parent, name, where, path) => {
1015
- parent.where = expand(parent.where, path.concat(name));
1016
- },
1017
- 'xpr': (parent, name, xpr, path) => {
1018
- parent.xpr = expand(parent.xpr, path.concat(name));
1019
- }
1020
- }, [], options);
1021
-
1022
- /*
1023
- flatten structured leaf types and return array of paths
1024
- Flattening stops on all non-structured types.
1025
- */
1026
- function expand(expr, location) {
1027
- if (!Array.isArray(expr))
1028
- return expr; // don't traverse strings, etc.
1029
- const rc = [];
1030
- for(let i = 0; i < expr.length; i++)
1031
- {
1032
- if(Array.isArray(expr[i]))
1033
- rc.push(expr[i].map(e => expand(e, location)));
1034
-
1035
- if(i < expr.length-2)
1036
- {
1037
- let [lhs, op, not, rhs] = expr.slice(i);
1038
- if(not !== 'not') {
1039
- rhs = not;
1040
- not = false;
1041
- }
1042
- if(lhs === undefined || op === undefined || rhs === undefined)
1043
- return expr;
1044
-
1045
- // we might have to ad-hoc resolve a ref, since handleExists is run before hand and generates new refs.
1046
- const lhsArt = lhs._art || lhs.ref && !lhs.$scope && inspectRef(location.concat(i)).art;
1047
- const rhsArt = rhs._art || rhs.ref && !rhs.$scope && inspectRef(location.concat(i+2)).art;
1048
- const lhsIsVal = (lhs.val !== undefined);
1049
- // if ever rhs should be allowed to be a value uncomment this
1050
- const rhsIsVal = (rhs === 'null' /*|| rhs.val !== undefined*/);
1051
-
1052
- // lhs & rhs must be expandable types (structures or managed associations)
1053
- // if ever lhs should be allowed to be a value uncomment this
1054
- if(!(lhsIsVal /*&& rhsIsVal*/) &&
1055
- !(isDollarSelfOrProjectionOperand(lhs) || isDollarSelfOrProjectionOperand(rhs)) &&
1056
- RelationalOperators.includes(op) &&
1057
- (lhsIsVal || (lhsArt && lhs.ref && isExpandable(lhsArt))) &&
1058
- (rhsIsVal || (rhsArt && rhs.ref && isExpandable(rhsArt)))
1059
- ) {
1060
-
1061
- if(RestrictedOperators.includes(op)) {
1062
- message('expr-unexpected-operator', location, { op }, 'Unexpected operator $(OP) in structural comparison');
1063
- }
1064
- // if path is scalar and no assoc or has no type (@Core.Computed) use original expression
1065
- // only do the expansion on (managed) assocs and (items.)elements, array of check in ON cond is done elsewhere
1066
- const lhspaths = lhsIsVal ? [] : flattenPath({ _art: lhsArt, ref: lhs.ref }, false, true );
1067
- const rhspaths = rhsIsVal ? [] : flattenPath({ _art: rhsArt, ref: rhs.ref }, false, true );
1068
-
1069
- // mapping dict for lhs/rhs for mismatch check
1070
- // strip lhs/rhs prefix from flattened paths to check remaining common trailing path
1071
- // if path is idempotent, it doesn't produce new flattened paths (ends on scalar type)
1072
- // key is then empty string on both sides '' (=> equality)
1073
- // Path matches if lhs/rhs are available
1074
- const xref = createXRef(lhspaths, rhspaths, lhs, rhs, lhsIsVal, rhsIsVal);
1075
- const xrefkeys = Object.keys(xref);
1076
- const xrefvalues = Object.values(xref);
1077
- let cont = true;
1078
-
1079
- const prefix = (lhs, op, rhs) => {
1080
- return `${lhsIsVal ? lhs.val : lhs.ref.join('.')} ${op} ${rhsIsVal ? rhs : rhs.ref.join('.')}`
1081
- }
1082
- if(op === 'like' && xrefvalues.reduce((a, v) => {
1083
- return (v.lhs && v.rhs) ? a + 1: a;
1084
- }, 0) === 0) {
1085
- // error if intersection of paths is zero
1086
- error(null, location,
1087
- {
1088
- prefix: prefix(lhs, op, rhs)
1089
- },
1090
- 'Expected compatible types for $(PREFIX)');
1091
- cont = false;
1092
- }
1093
-
1094
- cont && xrefkeys.forEach(xn => {
1095
- const x = xref[xn];
1096
- // do the paths match?
1097
- if(op !== 'like' && !(x.lhs && x.rhs)) {
1098
- if(xn.length) {
1099
- error('expr-invalid-expansion', location, {
1100
- value: prefix(lhs, op, rhs),
1101
- name: xn,
1102
- alias: (x.lhs ? rhs : lhs).ref.join('.')
1103
- },
1104
- 'Missing sub path $(NAME) in $(ALIAS) for tuple expansion of $(VALUE); both sides must expand to the same sub paths');
1105
- }
1106
- else {
1107
- error(null, location,
1108
- {
1109
- prefix: prefix(lhs, op, rhs),
1110
- name: (x.lhs ? lhs : rhs).ref.join('.'),
1111
- alias: (x.lhs ? rhs : lhs).ref.join('.')
1112
- },
1113
- '$(PREFIX): Path $(NAME) does not match $(ALIAS)');
1114
- }
1115
- cont = false;
1116
- }
1117
- // lhs && rhs are present, consistency checks that affect both ends
1118
- else {
1119
- // is lhs scalar?
1120
- // eslint-disable-next-line sonarjs/no-gratuitous-expressions
1121
- if(!lhsIsVal && x.lhs && !isScalarOrNoType(x.lhs._art)) {
1122
- error(null, location,
1123
- {
1124
- prefix: prefix(lhs, op, rhs),
1125
- name: `${x.lhs.ref.join('.')}${(xn.length ? '.' + xn : '')}`
1126
- },
1127
- '$(PREFIX): Path $(NAME) must end on a scalar type')
1128
- cont = false;
1129
- }
1130
- // is rhs scalar?
1131
- if(!rhsIsVal && x.rhs && !isScalarOrNoType(x.rhs._art)) {
1132
- error(null, location,
1133
- {
1134
- prefix: prefix(lhs, op, rhs),
1135
- name: `${x.rhs.ref.join('.')}${(xn.length ? '.' + xn : '')}`
1136
- },
1137
- '$(PREFIX): Path $(NAME) must end on a scalar type');
1138
- cont = false;
1139
- }
1140
- // info about type incompatibility if no other errors occurred
1141
- // eslint-disable-next-line sonarjs/no-gratuitous-expressions
1142
- if(!(lhsIsVal || rhsIsVal) && x.lhs && x.rhs && xn && cont) {
1143
- const lhst = getType(x.lhs._art);
1144
- const rhst = getType(x.rhs._art);
1145
- if(lhst !== rhst) {
1146
- info(null, location,
1147
- {
1148
- prefix: prefix(lhs, op, rhs),
1149
- name: xn
1150
- },
1151
- '$(PREFIX): Types for sub path $(NAME) don\'t match');
1152
- }
1153
- }
1154
- }
1155
- });
1156
- // don't continue if there are path errors
1157
- if(!cont)
1158
- return expr;
1159
-
1160
- // if lhs and rhs are refs set operator from 'like' to '='
1161
- // eslint-disable-next-line sonarjs/no-gratuitous-expressions
1162
- if(op === 'like' && !(lhsIsVal || rhsIsVal)) {
1163
- op = '=';
1164
- }
1165
- // t_0 OR ... OR t_n with t = (a <not equal> b)
1166
- const bop = (op === 'is' && not) || op === '!=' || op === '<>' ? 'or' : 'and';
1167
- const xpr = { xpr: [] };
1168
- xrefvalues.filter(x => x.lhs && x.rhs).forEach((x,i) => {
1169
- xpr.i = i;
1170
- if(i>0) {
1171
- xpr.xpr.push(bop);
1172
- }
1173
- xpr.xpr.push(x.lhs);
1174
- xpr.xpr.push(op);
1175
- if(not)
1176
- xpr.xpr.push('not')
1177
- xpr.xpr.push(x.rhs);
1178
- });
1179
- if(xpr.i > 0) {
1180
- delete xpr.i;
1181
- rc.push(xpr);
1182
- }
1183
- else
1184
- rc.push(...xpr.xpr);
1185
- i += not ? 3 : 2;
1186
- }
1187
- else
1188
- rc.push(expr[i]);
1189
- }
1190
- else
1191
- rc.push(expr[i]);
1192
- }
1193
- return rc;
1194
-
1195
- function createXRef(lhspaths, rhspaths, lhs, rhs, lhsIsVal, rhsIsVal) {
1196
- // mapping dict for lhs/rhs for mismatch check
1197
- // strip lhs/rhs prefix from flattened paths to check remaining common trailing path
1198
- // if path is idempotent, it doesn't produce new flattened paths (ends on scalar type)
1199
- // key is then empty string on both sides '' (=> equality)
1200
- // Path matches if lhs/rhs are available
1201
- let xref;
1202
- if(!lhsIsVal) {
1203
- xref = lhspaths.reduce((a, v) => {
1204
- a[v.ref.slice(lhs.ref.length).join('.')] = rhsIsVal ? { lhs: v, rhs } : { lhs: v };
1205
- return a;
1206
- }, Object.create(null));
1207
-
1208
- rhspaths.forEach(v => {
1209
- const k = v.ref.slice(rhs.ref.length).join('.');
1210
- if(xref[k])
1211
- xref[k].rhs = v;
1212
- else
1213
- xref[k] = { rhs: v };
1214
- });
1215
- }
1216
- else if(!rhsIsVal) {
1217
- xref = rhspaths.reduce((a, v) => {
1218
- a[v.ref.slice(rhs.ref.length).join('.')] = lhsIsVal ? { lhs, rhs: v } : { rhs: v };
1219
- return a;
1220
- }, Object.create(null));
1221
-
1222
- lhspaths.forEach(v => {
1223
- const k = v.ref.slice(lhs.ref.length).join('.');
1224
- if(xref[k])
1225
- xref[k].lhs = v;
1226
- else
1227
- xref[k] = { lhs: v };
1228
- });
1229
- }
1230
- return xref;
1231
- }
1232
-
1233
- function getType(art) {
1234
- const effArt = effectiveType(art);
1235
- return Object.keys(effArt).length ? effArt : art.type;
1236
- }
1237
-
1238
- function isExpandable(art) {
1239
- art = effectiveType(art);
1240
- if(art) {
1241
- // items in ON conds are illegal but this should be checked elsewhere
1242
- const elements = art.elements || (art.items && art.items.elements);
1243
- return !!(elements || art.target && art.keys)
1244
- }
1245
- return false;
1246
- }
1247
-
1248
- function isScalarOrNoType(art) {
1249
- art = effectiveType(art);
1250
- if (art) {
1251
- const type = art.type || art.items?.type;
1252
- // items in ON-conditions are illegal but this should be checked elsewhere
1253
- const elements = art.elements || (art.items && art.items.elements);
1254
- // @Core.Computed has no type
1255
- return (!elements && !type ||
1256
- (type && isBuiltinType(type) &&
1257
- type !== 'cds.Association' && type !== 'cds.Composition' && type !== 'cds.Map'))
1258
- }
1259
- return false;
1260
- }
895
+ 'Value $(OTHERPROP) of annotation $(ANNO) is overwritten with new value $(PROP)');
1261
896
  }
897
+ return wasOverwritten;
1262
898
  }
1263
-
1264
899
  }
1265
900
 
1266
901
  /**
@@ -1276,19 +911,17 @@ function rewriteBuiltinTypeRef(csn) {
1276
911
  const special$self = !csn?.definitions?.$self && '$self';
1277
912
  applyTransformations(csn, {
1278
913
  type: (parent, _prop, type) => {
1279
- if(type?.ref && (
914
+ if (type?.ref && (
1280
915
  isBuiltinType(type.ref[0]) ||
1281
916
  type.ref[0] === special$self)
1282
- ) {
917
+ )
1283
918
  parent.type = type.ref[0];
1284
- }
1285
- }
919
+ },
1286
920
  });
1287
921
  }
1288
922
 
1289
923
  module.exports = {
1290
924
  // This function retrieves the actual exports
1291
925
  getTransformers,
1292
- RelationalOperators,
1293
926
  rewriteBuiltinTypeRef,
1294
927
  };