@sap/cds-compiler 3.1.2 → 3.4.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 (117) hide show
  1. package/CHANGELOG.md +101 -3
  2. package/bin/cdsc.js +4 -2
  3. package/doc/CHANGELOG_BETA.md +35 -0
  4. package/lib/api/main.js +153 -29
  5. package/lib/api/validate.js +8 -3
  6. package/lib/base/dictionaries.js +6 -6
  7. package/lib/base/error.js +2 -2
  8. package/lib/base/keywords.js +106 -24
  9. package/lib/base/message-registry.js +177 -79
  10. package/lib/base/messages.js +78 -57
  11. package/lib/base/model.js +2 -1
  12. package/lib/checks/actionsFunctions.js +1 -1
  13. package/lib/checks/annotationsOData.js +2 -2
  14. package/lib/checks/arrayOfs.js +15 -7
  15. package/lib/checks/cdsPersistence.js +1 -1
  16. package/lib/checks/checkForTypes.js +53 -0
  17. package/lib/checks/defaultValues.js +4 -2
  18. package/lib/checks/elements.js +81 -6
  19. package/lib/checks/foreignKeys.js +12 -13
  20. package/lib/checks/invalidTarget.js +10 -11
  21. package/lib/checks/managedInType.js +21 -15
  22. package/lib/checks/nullableKeys.js +1 -1
  23. package/lib/checks/onConditions.js +9 -9
  24. package/lib/checks/parameters.js +23 -0
  25. package/lib/checks/queryNoDbArtifacts.js +1 -1
  26. package/lib/checks/selectItems.js +1 -1
  27. package/lib/checks/sql-snippets.js +12 -10
  28. package/lib/checks/types.js +2 -2
  29. package/lib/checks/utils.js +17 -7
  30. package/lib/checks/validator.js +36 -14
  31. package/lib/compiler/assert-consistency.js +21 -13
  32. package/lib/compiler/builtins.js +8 -0
  33. package/lib/compiler/checks.js +57 -40
  34. package/lib/compiler/define.js +139 -69
  35. package/lib/compiler/extend.js +319 -50
  36. package/lib/compiler/finalize-parse-cdl.js +14 -9
  37. package/lib/compiler/kick-start.js +2 -35
  38. package/lib/compiler/populate.js +111 -68
  39. package/lib/compiler/propagator.js +5 -3
  40. package/lib/compiler/resolve.js +71 -108
  41. package/lib/compiler/shared.js +82 -54
  42. package/lib/compiler/tweak-assocs.js +26 -14
  43. package/lib/compiler/utils.js +13 -2
  44. package/lib/edm/annotations/genericTranslation.js +10 -7
  45. package/lib/edm/csn2edm.js +11 -11
  46. package/lib/edm/edm.js +17 -9
  47. package/lib/edm/edmPreprocessor.js +53 -30
  48. package/lib/edm/edmUtils.js +7 -2
  49. package/lib/gen/Dictionary.json +14 -0
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -2
  52. package/lib/gen/languageParser.js +4312 -4186
  53. package/lib/inspect/inspectModelStatistics.js +1 -1
  54. package/lib/inspect/inspectPropagation.js +23 -9
  55. package/lib/json/csnVersion.js +13 -13
  56. package/lib/json/from-csn.js +161 -172
  57. package/lib/json/to-csn.js +70 -10
  58. package/lib/language/.eslintrc.json +4 -0
  59. package/lib/language/antlrParser.js +8 -11
  60. package/lib/language/docCommentParser.js +1 -2
  61. package/lib/language/errorStrategy.js +54 -27
  62. package/lib/language/genericAntlrParser.js +140 -93
  63. package/lib/language/language.g4 +57 -33
  64. package/lib/language/multiLineStringParser.js +75 -63
  65. package/lib/main.d.ts +3 -6
  66. package/lib/main.js +1 -0
  67. package/lib/model/.eslintrc.json +13 -0
  68. package/lib/model/api.js +4 -2
  69. package/lib/model/csnRefs.js +78 -50
  70. package/lib/model/csnUtils.js +272 -222
  71. package/lib/model/enrichCsn.js +41 -31
  72. package/lib/model/revealInternalProperties.js +61 -57
  73. package/lib/model/sortViews.js +35 -31
  74. package/lib/modelCompare/compare.js +52 -18
  75. package/lib/modelCompare/filter.js +83 -0
  76. package/lib/optionProcessor.js +10 -1
  77. package/lib/render/manageConstraints.js +11 -7
  78. package/lib/render/toCdl.js +151 -106
  79. package/lib/render/toHdbcds.js +8 -6
  80. package/lib/render/toRename.js +4 -4
  81. package/lib/render/toSql.js +17 -7
  82. package/lib/render/utils/common.js +27 -9
  83. package/lib/render/utils/sql.js +5 -5
  84. package/lib/sql-identifier.js +7 -0
  85. package/lib/transform/db/applyTransformations.js +32 -3
  86. package/lib/transform/db/assertUnique.js +27 -38
  87. package/lib/transform/db/expansion.js +92 -41
  88. package/lib/transform/db/flattening.js +1 -1
  89. package/lib/transform/db/temporal.js +3 -1
  90. package/lib/transform/db/transformExists.js +8 -2
  91. package/lib/transform/db/views.js +42 -13
  92. package/lib/transform/draft/db.js +2 -2
  93. package/lib/transform/forOdataNew.js +10 -7
  94. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
  95. package/lib/transform/localized.js +29 -20
  96. package/lib/transform/odata/toFinalBaseType.js +8 -11
  97. package/lib/transform/odata/typesExposure.js +2 -1
  98. package/lib/transform/parseExpr.js +245 -0
  99. package/lib/transform/transformUtilsNew.js +122 -51
  100. package/lib/transform/translateAssocsToJoins.js +17 -16
  101. package/lib/utils/moduleResolve.js +5 -5
  102. package/lib/utils/objectUtils.js +3 -3
  103. package/lib/utils/term.js +5 -5
  104. package/package.json +2 -2
  105. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  106. package/share/messages/check-proper-type-of.md +4 -4
  107. package/share/messages/check-proper-type.md +2 -2
  108. package/share/messages/duplicate-autoexposed.md +4 -4
  109. package/share/messages/extend-repeated-intralayer.md +4 -5
  110. package/share/messages/extend-unrelated-layer.md +4 -4
  111. package/share/messages/message-explanations.json +3 -1
  112. package/share/messages/redirected-to-ambiguous.md +7 -6
  113. package/share/messages/redirected-to-complex.md +63 -0
  114. package/share/messages/redirected-to-unrelated.md +6 -5
  115. package/share/messages/rewrite-not-supported.md +4 -4
  116. package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
  117. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -247,14 +247,14 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
247
247
  if (isUnion(queryPath) && options.transformation === 'hdbcds') {
248
248
  if (isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J) {
249
249
  if (elem.keys)
250
- info(null, queryPath, `Managed association "${elemName}", published in a UNION, will be ignored`);
250
+ info(null, queryPath, { name: elemName }, 'Managed association $(NAME), published in a UNION, will be ignored');
251
251
  else
252
- info(null, queryPath, `Association "${elemName}", published in a UNION, will be ignored`);
252
+ info(null, queryPath, { name: elemName }, 'Association $(NAME), published in a UNION, will be ignored');
253
253
 
254
254
  elem._ignore = true;
255
255
  }
256
256
  else {
257
- error(null, queryPath, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`);
257
+ error(null, queryPath, { name: elemName }, 'Association $(NAME) can\'t be published in a SAP HANA CDS UNION');
258
258
  }
259
259
  }
260
260
  else if (queryPath.length > 4 && options.transformation === 'hdbcds') { // path.length > 4 -> is a subquery
@@ -325,17 +325,40 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
325
325
  * due to an issue with HANA - only for hdbcds-hdbcds, I assume flattening
326
326
  * takes care of this for the other cases already
327
327
  *
328
- * @param {CSN.Query} query
328
+ * @param {CSN.Column} col
329
329
  * @param {CSN.Path} path
330
330
  */
331
- function addImplicitAliasWithAssoc(query, path) {
332
- for (let i = 0; i < query.SELECT.columns.length; i++) {
333
- const col = query.SELECT.columns[i];
334
- if (!col.as && col.ref && col.ref.length > 1) {
335
- const { links } = inspectRef(path.concat([ 'columns', i ]));
336
- if (links && links.slice(0, -1).some(({ art }) => isAssocOrComposition(art && art.type || '')))
337
- col.as = getLastRefStepString(col.ref);
338
- }
331
+ function addImplicitAliasWithAssoc(col, path) {
332
+ if (!col.as && col.ref && col.ref.length > 1) {
333
+ const { links } = inspectRef(path);
334
+ if (links && links.slice(0, -1).some(({ art }) => isAssocOrComposition(art && art.type || '')))
335
+ col.as = getLastRefStepString(col.ref);
336
+ }
337
+ }
338
+
339
+ /**
340
+ * If simply selecting from a param like `:param`, we need to add an implicit alias like `:param as param`
341
+ * due to an issue with HANA
342
+ *
343
+ * @param {CSN.Column} col
344
+ */
345
+ function addImplicitAliasWithLonelyParam(col) {
346
+ if (!col.as && col.param)
347
+ col.as = getLastRefStepString(col.ref);
348
+ }
349
+
350
+
351
+ /**
352
+ * Loop over the columns and call all of the given functions with the column and the path
353
+ *
354
+ * @param {Function[]} functions
355
+ * @param {CSN.Column[]} columns
356
+ * @param {CSN.Path} path
357
+ */
358
+ function processColumns(functions, columns, path) {
359
+ for (let i = 0; i < columns.length; i++) {
360
+ const col = columns[i];
361
+ functions.forEach(fn => fn(col, path.concat(i)));
339
362
  }
340
363
  }
341
364
 
@@ -401,8 +424,14 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
401
424
  query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem]._ignore).map(key => stripLeadingSelf(columnMap[key]));
402
425
  // If following an association, explicitly set the implicit alias
403
426
  // due to an issue with HANA - this seems to only have an effect on ref files with hdbcds-hdbcds, so only run then
427
+ const columnProcessors = [];
428
+ if (options.transformation === 'hdbcds' || options.transformation === 'sql' && options.sqlDialect === 'hana')
429
+ columnProcessors.push(addImplicitAliasWithLonelyParam);
404
430
  if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')
405
- addImplicitAliasWithAssoc(query, path);
431
+ columnProcessors.push(addImplicitAliasWithAssoc);
432
+
433
+ if (columnProcessors.length > 0)
434
+ processColumns(columnProcessors, query.SELECT.columns, path.concat('columns'));
406
435
 
407
436
  delete query.SELECT.excluding; // just to make the output of the new transformer the same as the old
408
437
 
@@ -165,8 +165,8 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
165
165
  }
166
166
 
167
167
  else {
168
- error(null, [ 'definitions', draftRootName ], { name: persistenceName },
169
- `Entity $(NAME) already generated by draft root "${definingDraftRoot}"`);
168
+ error(null, [ 'definitions', draftRootName ], { name: persistenceName, alias: definingDraftRoot },
169
+ 'Entity $(NAME) already generated by draft root $(ALIAS)');
170
170
  }
171
171
 
172
172
  return;
@@ -97,6 +97,7 @@ function transform4odataWithCsn(inputModel, options) {
97
97
  inspectRef,
98
98
  artifactRef,
99
99
  effectiveType,
100
+ getFinalBaseTypeWithProps
100
101
  } = csnUtils;
101
102
 
102
103
  // are we working with structured OData or not
@@ -123,7 +124,7 @@ function transform4odataWithCsn(inputModel, options) {
123
124
  addLocalizationViews(csn, options, { acceptLocalizedView, ignoreUnknownExtensions: true });
124
125
 
125
126
  const cleanup = validate.forOdata(csn, {
126
- message, error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, isAspect, isExternalServiceMember
127
+ message, error, warning, info, inspectRef, effectiveType, getFinalBaseTypeWithProps, artifactRef, csn, options, csnUtils, services, isAspect, isExternalServiceMember
127
128
  });
128
129
 
129
130
 
@@ -307,11 +308,13 @@ function transform4odataWithCsn(inputModel, options) {
307
308
  '@Capabilities.Readable': '@Capabilities.ReadRestrictions.Readable',
308
309
  }
309
310
 
311
+ const shortCuts = Object.keys(mappings);
310
312
  Object.keys(node).forEach( name => {
311
313
  // Rename according to map above
312
- if (mappings[name] != undefined)
313
- renameAnnotation(node, name, mappings[name]);
314
-
314
+ const prefix = shortCuts.find(p => name.startsWith(p + '.') || name === p);
315
+ if(prefix) {
316
+ renameAnnotation(node, name, name.replace(prefix, mappings[prefix]));
317
+ }
315
318
  // Special case: '@important: [true|false]' becomes '@UI.Importance: [#High|#Low]'
316
319
  if (name === '@important') {
317
320
  renameAnnotation(node, name, '@UI.Importance');
@@ -323,7 +326,7 @@ function transform4odataWithCsn(inputModel, options) {
323
326
  // Special case: '@readonly' becomes a triplet of capability restrictions for entities,
324
327
  // but '@Core.Immutable' for everything else.
325
328
  if (!(node['@readonly'] && node['@insertonly'])) {
326
- if (name === '@readonly' && node[name] !== null) {
329
+ if (name === '@readonly' && node[name]) {
327
330
  if (node.kind === 'entity' || node.kind === 'aspect') {
328
331
  setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
329
332
  setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);
@@ -333,7 +336,7 @@ function transform4odataWithCsn(inputModel, options) {
333
336
  }
334
337
  }
335
338
  // @insertonly is effective on entities/queries only
336
- else if (name === '@insertonly' && node[name] !== null) {
339
+ else if (name === '@insertonly' && node[name]) {
337
340
  if (node.kind === 'entity' || node.kind === 'aspect') {
338
341
  setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
339
342
  setAnnotation(node, '@Capabilities.ReadRestrictions.Readable', false);
@@ -342,7 +345,7 @@ function transform4odataWithCsn(inputModel, options) {
342
345
  }
343
346
  }
344
347
  // Only on element level: translate @mandatory
345
- if (name === '@mandatory' && node[name] !== null &&
348
+ if (name === '@mandatory' && node[name] &&
346
349
  node.kind === undefined && node['@Common.FieldControl'] === undefined) {
347
350
  setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' });
348
351
  }
@@ -93,14 +93,14 @@ function forEachDefinition(csn, cb) {
93
93
  * - (240) All artifacts (a), elements, foreign keys, parameters (b) that have a DB representation are annotated
94
94
  * with their database name (as '@cds.persistence.name') according to the naming convention chosen
95
95
  * in 'options.sqlMapping'.
96
- * - (250) Remove name space definitions again (only in forHanaNew). Maybe we can omit inserting namespace definitions
96
+ * - (250) Remove name space definitions again (only in forRelationalDB). Maybe we can omit inserting namespace definitions
97
97
  * completely (TODO)
98
98
  *
99
99
  * @param {CSN.Model} inputModel
100
100
  * @param {CSN.Options} options
101
101
  * @param {string} moduleName The calling compiler module name, e.g. `to.hdi` or `to.hdbcds`.
102
102
  */
103
- function transformForHanaWithCsn(inputModel, options, moduleName) {
103
+ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
104
104
  // copy the model as we don't want to change the input model
105
105
  timetrace.start('HANA transformation');
106
106
  /** @type {CSN.Model} */
@@ -131,7 +131,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
131
131
  forEachDefinition(csn, handleMixinOnConditions);
132
132
 
133
133
  // Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
134
- const cleanup = validate.forHana(csn, {
134
+ const cleanup = validate.forRelationalDB(csn, {
135
135
  message, error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, isAspect
136
136
  });
137
137
 
@@ -310,7 +310,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
310
310
  * For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
311
311
  * hence we do not generate the referential constraints for them.
312
312
  */
313
- if((options.sqlDialect === 'hana' || options.sqlDialect === 'sqlite') && doA2J)
313
+ if(options.sqlDialect !== 'plain' && options.sqlDialect !== 'h2' && doA2J)
314
314
  createReferentialConstraints(csn, options);
315
315
 
316
316
  // no constraints for drafts
@@ -398,7 +398,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
398
398
 
399
399
  if(options.sqlDialect === 'postgres') {
400
400
  killers.length = (parent) => {
401
- if (parent.type === 'cds.Binary' || parent.type === 'cds.hana.BINARY') {
401
+ if (parent.type === 'cds.Binary') {
402
402
  delete parent.length;
403
403
  }
404
404
  }
@@ -625,12 +625,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
625
625
  }
626
626
  else if (options.sqlDialect === 'sqlite') { // view with params
627
627
  // Allow with plain
628
- error(null, [ 'definitions', artifactName ], `SQLite does not support entities with parameters`);
628
+ error(null, [ 'definitions', artifactName ], 'SQLite does not support entities with parameters');
629
629
  }
630
630
  else {
631
631
  for (const pname in artifact.params) {
632
632
  if (pname.match(/\W/g) || pname.match(/^\d/) || pname.match(/^_/)) { // parameter name must be regular SQL identifier
633
- warning(null, [ 'definitions', artifactName, 'params', pname ], `Expecting regular SQL-Identifier`);
633
+ warning(null, [ 'definitions', artifactName, 'params', pname ], 'Expecting regular SQL-Identifier');
634
634
  }
635
635
  else if (options.sqlMapping !== 'plain' && pname.toUpperCase() !== pname) { // not plain mode: param name must be all upper
636
636
  warning(null, [ 'definitions', artifactName, 'params', pname ], { name: options.sqlMapping },
@@ -704,6 +704,11 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
704
704
  node.length = 36;
705
705
  setProp(node, '$renamed', 'cds.UUID');
706
706
  }
707
+
708
+ if(options.sqlDialect === 'h2' && val === 'cds.Decimal' && !node.scale) {
709
+ node[key] = 'cds.DecimalFloat'; // cds.Decimal and cds.Decimal(p) should map do DECFLOAT for h2
710
+ }
711
+
707
712
  // Length/Precision/Scale is done in addDefaultTypeFacets
708
713
  }
709
714
 
@@ -860,7 +865,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
860
865
  // not to the view).
861
866
  // FIXME: This also means that corresponding key fields should be in the select list etc ...
862
867
  if (!art.query && !art.projection && assoc.target && assoc.target != artifactName)
863
- error(null, path, `Only an association that points back to this artifact can be compared to "$self"`);
868
+ error(null, path, { name: '$self' }, 'Only an association that points back to this artifact can be compared to $(NAME)');
864
869
 
865
870
 
866
871
  // Check: The forward link <assocOp> must not contain '$self' in its own ON-condition
@@ -868,7 +873,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
868
873
  const containsDollarSelf = assoc.on.some(isDollarSelfOrProjectionOperand);
869
874
 
870
875
  if (containsDollarSelf)
871
- error(null, path, `An association that uses "$self" in its ON-condition can't be compared to "$self"`);
876
+ error(null, path, { name: '$self' },
877
+ 'An association that uses $(NAME) in its ON-condition can\'t be compared to $(NAME)');
872
878
  }
873
879
 
874
880
  // Transform comparison of $self to managed association into AND-combined foreign key comparisons
@@ -983,7 +989,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
983
989
  const absolute = node.type;
984
990
  const parameters = node.parameters || [];
985
991
  // :FIXME: Is this dead code? node.parameters is always undefined...
986
- // forHana tested against the parameters of the type definition which is not available in CSN
992
+ // forRelationalDB tested against the parameters of the type definition which is not available in CSN
987
993
  for (const name in parameters) {
988
994
  const param = parameters[name];
989
995
  if (!node[param] && absolute !== 'cds.hana.ST_POINT' && absolute !== 'cds.hana.ST_GEOMETRY')
@@ -1086,7 +1092,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1086
1092
  else if (art.elements) {
1087
1093
  // The reference is structured
1088
1094
  if (isFulltextIndex)
1089
- error(null, path, `"${ artName }": A fulltext index can't be defined on a structured element`);
1095
+ error(null, path, { name: artName }, 'A fulltext index can\'t be defined on a structured element $(NAME)');
1090
1096
  // First, compute the name from the path, e.g ['s', 's1', 's2' ] will result in 'S_s1_s2' ...
1091
1097
  const refPath = flattenStructStepsInRef(val.ref, path);
1092
1098
  // ... and take this as the prefix for all elements
@@ -1124,5 +1130,5 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1124
1130
 
1125
1131
 
1126
1132
  module.exports = {
1127
- transformForHanaWithCsn,
1133
+ transformForRelationalDBWithCsn,
1128
1134
  };
@@ -73,7 +73,7 @@ const _targetFor = Symbol('_targetFor');
73
73
  */
74
74
  function _addLocalizationViews(csn, options, useJoins, config) {
75
75
  // Don't try to create convenience views with errors.
76
- if (hasErrors(options.messages))
76
+ if (hasErrors(options.messages)) // TODO: this is actually wrong, consider --test-mode
77
77
  return csn;
78
78
 
79
79
  const messageFunctions = makeMessageFunction(csn, options);
@@ -86,19 +86,8 @@ function _addLocalizationViews(csn, options, useJoins, config) {
86
86
 
87
87
  createDirectConvenienceViews(); // 1
88
88
  createTransitiveConvenienceViews(); // 2 + 3
89
-
90
- forEachDefinition(csn, definition => cleanSymbols(definition, _hasLocalizedView, _isViewForEntity, _isViewForView, _targetFor));
91
-
92
- // In case that the user tried to annotate `localized.*` artifacts, apply them.
93
- applyAnnotationsFromExtensions(csn, {
94
- override: true,
95
- filter: (name) => name.startsWith('localized.'),
96
- notFound(name, index) {
97
- if (!ignoreUnknownExtensions)
98
- messageFunctions.message('anno-undefined-art', [ 'extensions', index ], { name })
99
- },
100
- });
101
-
89
+ cleanDefinitionSymbols();
90
+ applyAnnotationsForLocalizedViews();
102
91
  sortCsnDefinitionsForTests(csn, options);
103
92
  return csn;
104
93
 
@@ -196,7 +185,7 @@ function _addLocalizationViews(csn, options, useJoins, config) {
196
185
 
197
186
  for (const originalElement of textElements) {
198
187
  const elem = entity.elements[originalElement];
199
- // Note: $key is used by forHanaNew.js to indicate that this element was a key in the original,
188
+ // Note: $key is used by forRelationalDB.js to indicate that this element was a key in the original,
200
189
  // user's entity. Keys may have been changed by the backends (e.g. by `@cds.valid.key`)
201
190
  if (!elem.key && !elem.$key)
202
191
  columns.push( createColumnLocalizedElement( originalElement, shouldUseJoin ) );
@@ -605,6 +594,27 @@ function _addLocalizationViews(csn, options, useJoins, config) {
605
594
  // We can assume, that the element exists. This is checked in isEntityPreprocessed()
606
595
  return csn.definitions[artName].elements.texts.target;
607
596
  }
597
+
598
+ function cleanDefinitionSymbols() {
599
+ forEachDefinition(csn, function cleanDefinition(definition) {
600
+ cleanSymbols(definition, _hasLocalizedView, _isViewForEntity, _isViewForView, _targetFor);
601
+ });
602
+ }
603
+
604
+ /**
605
+ * In case that the user tried to annotate `localized.*` artifacts, apply them.
606
+ */
607
+ function applyAnnotationsForLocalizedViews() {
608
+ applyAnnotationsFromExtensions(csn, {
609
+ override: true,
610
+ filter: (name) => name.startsWith('localized.'),
611
+ notFound(name, index) {
612
+ if (!ignoreUnknownExtensions)
613
+ messageFunctions.message('anno-undefined-art', [ 'extensions', index ], { name })
614
+ },
615
+ });
616
+ }
617
+
608
618
  }
609
619
 
610
620
  /**
@@ -636,7 +646,7 @@ function addLocalizationViewsWithJoins(csn, options, config = {}) {
636
646
  * @param {string} [as] Alias for path.
637
647
  * @return {CSN.Column}
638
648
  */
639
- function createColumnRef(ref, as = null) {
649
+ function createColumnRef(ref, as) {
640
650
  const column = { ref };
641
651
  if (as)
642
652
  column.as = as;
@@ -708,10 +718,9 @@ function hasExistingLocalizationViews(csn, options, messageFunctions) {
708
718
  let hasExistingViews = false;
709
719
  let hasNonViews = false;
710
720
 
711
- for (const name in csn.definitions) {
712
- const art = csn.definitions[name];
721
+ forEachDefinition(csn, (def, name) => {
713
722
  if (isInLocalizedNamespace(name) || name === 'localized') {
714
- if (!art.query && !art.projection) {
723
+ if (!def.query && !def.projection) {
715
724
  if (!name.endsWith('.texts')) {
716
725
  hasNonViews = true;
717
726
  messageFunctions.error('reserved-namespace-localized', ['definitions', name], {},
@@ -723,7 +732,7 @@ function hasExistingLocalizationViews(csn, options, messageFunctions) {
723
732
  'Input CSN already contains localization views, no further ones will be created' );
724
733
  }
725
734
  }
726
- }
735
+ });
727
736
  return hasExistingViews || hasNonViews;
728
737
  }
729
738
 
@@ -68,9 +68,9 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
68
68
  // type Foo: array of { qux: Integer };
69
69
  function expandFirstLevelOfArrayed(def) {
70
70
  if (def.items.type && !isBuiltinType(def.items.type)) {
71
- let finalType = csnUtils.getFinalTypeDef(def.items.type);
72
- if (csnUtils.isStructured(finalType)) {
73
- def.items.elements = cloneCsnDictionary(finalType.elements, options);
71
+ let finalBaseType = csnUtils.getFinalBaseTypeWithProps(def.items.type);
72
+ if (finalBaseType?.elements) {
73
+ def.items.elements = cloneCsnDictionary(finalBaseType.elements, options);
74
74
  delete def.items.type;
75
75
  }
76
76
  }
@@ -147,14 +147,11 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
147
147
  // handle array of defined via a named type
148
148
  // example in actions: 'action act() return Primitive; type Primitive: array of String;'
149
149
  const currService = csnUtils.getServiceName(defName);
150
- const finalType = csnUtils.getFinalTypeDef(node.type);
151
- if (finalType.items &&
152
- (isBuiltinType(finalType.items.type) || isBuiltinType(csnUtils.getFinalBaseTypeWithProps(finalType.items.type)?.type)))
153
- {
154
- if (!isArtifactInService(node.type, currService) || !isV4) {
155
- node.items = finalType.items;
156
- delete node.type;
157
- }
150
+ const finalType = csnUtils.getFinalBaseTypeWithProps(node.type);
151
+ const isArrayOfBuiltin = finalType?.items && isBuiltinType(csnUtils.getFinalBaseTypeWithProps(finalType.items.type)?.type)
152
+ if (isArrayOfBuiltin && (!isArtifactInService(node.type, currService) || !isV4)) {
153
+ node.items = finalType.items;
154
+ delete node.type;
158
155
  }
159
156
  }
160
157
 
@@ -108,7 +108,8 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
108
108
  if (newType) {
109
109
  // error, if it was not exposed by us
110
110
  if (!exposedTypes[fullQualifiedNewTypeName]) {
111
- error(null, path, `Cannot create artificial type "${fullQualifiedNewTypeName}" for "${memberName}" because the name is already used`);
111
+ error(null, path, { type: fullQualifiedNewTypeName, name: memberName },
112
+ 'Can\'t create artificial type $(TYPE) for $(NAME) because the name is already used');
112
113
  return;
113
114
  }
114
115
  }
@@ -0,0 +1,245 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * parseExpr accepts any JSON object and tries to convert a token stream expression
5
+ * array into an AST like expression with CDL operator precedence.
6
+ *
7
+ * The following operators are supported:
8
+ *
9
+ * Multiplication/Division: '*', '/'
10
+ * Addition/Subtraction: '+', '-'
11
+ * Concatenation: '||'
12
+ * Relational: '=', '<>', '>', '>=', '<', '<=', '!=', 'like', 'in', 'exists', 'between and'
13
+ * Unary: 'is [not] null', 'not'
14
+ * Conditional: 'case [when then]+ [else]? end', 'and', 'or'
15
+ *
16
+ * Not yet implmemented: 'new'
17
+ *
18
+ * This is not an optimized LL(1) parser but a token 'sniffer'. A stream is
19
+ * cracked up in sub streams and passed down to the next higher function.
20
+ *
21
+ * Complex aggregates like case/when/else/end and between are parsed first to pass down the
22
+ * resulting sub expressions and avoiding 'and' ambiguities.
23
+ *
24
+ * Sub expressions are grouped as arrays, the final AST is an array of nested arrays.
25
+ * Alternatively, an object like AST can be produced by setting argument 'array' to false.
26
+ *
27
+ * This parser intentionally does no error handling. If a clause is malformed, it is accepted as is.
28
+ *
29
+ * @param {any} xpr A JSON object.
30
+ * @param {Boolean} array Bias AST representation.
31
+ */
32
+
33
+ function parseExpr(xpr, array=true) {
34
+ return parseExprInt(xpr);
35
+
36
+ function parseExprInt(xpr) {
37
+ return conditionOR(...CaseWhen(xpr));
38
+ }
39
+
40
+ function CaseWhen(xpr) {
41
+ if(Array.isArray(xpr))
42
+ inner(xpr);
43
+ return [xpr, 0, Array.isArray(xpr) ? xpr.length : 1];
44
+
45
+ // replace case/end from inner to outer
46
+ function inner(pxpr, lvl=0) {
47
+ const s = pxpr.findIndex(t => t === 'case');
48
+ if(s >= 0) {
49
+ let e = findLastIndex(pxpr, 'end');
50
+ pxpr = pxpr.slice(s+1, e);
51
+ const dist = inner(pxpr, lvl+1);
52
+ e -= dist;
53
+ if(dist > 0)
54
+ pxpr = xpr.slice(s+1, e+1);
55
+ const caseTree = array ? [ 'case' ] : { 'case': [] };
56
+ let i = pxpr.findIndex(t => t === 'else');
57
+ let elseCond = undefined;
58
+ if(i >= 0) {
59
+ elseCond = pxpr.slice(i+1);
60
+ pxpr = pxpr.slice(0, i);
61
+ }
62
+ i = pxpr.findIndex(t => t === 'when');
63
+ while(i >= 0) {
64
+ pxpr = pxpr.slice(i+1);
65
+ const when = { 'when': [] };
66
+ if(array)
67
+ caseTree.push('when');
68
+ else
69
+ caseTree.case.push(when);
70
+ i = pxpr.findIndex(t => t === 'then');
71
+ if(i >= 0) {
72
+ const arg = pxpr.slice(0, i);
73
+ if(array)
74
+ caseTree.push(arg);
75
+ else
76
+ when.when.push(arg.length === 1 ? arg[0] : arg);
77
+ }
78
+ pxpr = pxpr.slice(i+1);
79
+ i = pxpr.findIndex(t => t === 'when');
80
+ const arg = ((i >= 0) ? pxpr.slice(0, i) : pxpr);
81
+ if(array)
82
+ caseTree.push('then', arg);
83
+ else
84
+ when.when.push(arg.length === 1 ? arg[0] : arg);
85
+ }
86
+ if(elseCond) {
87
+ if(array)
88
+ caseTree.push('else', elseCond);
89
+ else
90
+ caseTree.case.push(elseCond.length === 1 ? elseCond[0] : elseCond);
91
+ }
92
+ if(array)
93
+ caseTree.push('end');
94
+ if(lvl > 0)
95
+ xpr.splice(s+1, e-s+1, caseTree);
96
+ else {
97
+ xpr = caseTree;
98
+ }
99
+ return e-s+1;
100
+ }
101
+ else
102
+ return 0;
103
+ }
104
+
105
+ function findLastIndex(expr, token, l=expr.length-1) {
106
+ while(l >= 0 && expr[l] !== token) l--;
107
+ return l;
108
+ }
109
+ }
110
+
111
+ function conditionOR(xpr, s, e) {
112
+ return binaryExpr(xpr, ['or'], conditionAnd, s, e);
113
+ }
114
+ function conditionAnd(xpr, s, e) {
115
+ return binaryExpr(xpr, (xpr, s, e) => {
116
+ let a = s-1;
117
+ let b;
118
+ // scan for 'and', skip 'between/and'
119
+ do {
120
+ b = false;
121
+ for(a++; xpr[a] !== 'and' && a < e; a++) {
122
+ if(xpr[a] === 'between')
123
+ b = true;
124
+ }
125
+ } while(b && a < e)
126
+
127
+ if(!b && a < e)
128
+ return [1, a]
129
+ else
130
+ return [1, -1];
131
+ }, conditionTerm, s, e);
132
+ }
133
+
134
+ function conditionTerm(xpr, s, e) {
135
+ if(Array.isArray(xpr)) {
136
+ if(xpr.length >= 3 && xpr[s+1] === 'is') {
137
+ if(xpr[s+2] === 'null')
138
+ return array ? [ conditionOR(xpr[s]), 'is', 'null' ] : { 'isNull': conditionOR(xpr[s]) };
139
+ else if(xpr[s+2] === 'not' && xpr[s+3] === 'null')
140
+ return array ? [ conditionOR(xpr[s]), 'is', 'not', 'null' ] : { 'isNotNull': conditionOR(xpr[s]) };
141
+ }
142
+ if(xpr[s] === 'not')
143
+ return array ? [ 'not', conditionTerm(xpr, s+1, e) ] : { 'not': conditionTerm(xpr, s+1, e) };
144
+ if(xpr[s] === 'exists')
145
+ return array ? [ 'exists', conditionOR(xpr[s+1]) ] : { 'exists': conditionOR(xpr[s+1]) };
146
+ }
147
+ return compareTerm(xpr, s, e);
148
+ }
149
+
150
+ function compareTerm(xpr, s, e) {
151
+ if(Array.isArray(xpr)) {
152
+ let i = s;
153
+ while(i < e && xpr[i] !== 'between') i++;
154
+ const b = i < e ? i : -1;
155
+ while(i < e && xpr[i] !== 'and') i++;
156
+ const a = i < e ? i : -1;
157
+ if(b >= 0) {
158
+ const expr = expression(xpr, s, b);
159
+ const between = array ? [ expr, 'between' ] : { 'between': [ expr ] };
160
+ if(a >= 0) {
161
+ const lower = expression(xpr, b+1, a);
162
+ const upper = expression(xpr, a+1, e);
163
+ if(array)
164
+ between.push(lower, 'and', upper);
165
+ else {
166
+ between.between.push(lower, upper);
167
+ }
168
+ }
169
+ else {
170
+ const unspec = expression(xpr, b+1, e);
171
+ if(array)
172
+ between.push(unspec);
173
+ else
174
+ between.between.push(unspec);
175
+ }
176
+ return between;
177
+ }
178
+ }
179
+ return binaryExpr(xpr, ['=', '<>', '>', '>=', '<', '<=', '!=', 'like', 'in'], expression, s, e);
180
+ }
181
+
182
+ function expression(xpr, s, e) {
183
+ return binaryExpr(xpr, ['||'], exprAddSub, s, e);
184
+ }
185
+
186
+ function exprAddSub(xpr, s, e) {
187
+ return binaryExpr(xpr, ['+', '-'], exprMulDiv, s, e);
188
+ }
189
+
190
+ function exprMulDiv(xpr, s, e) {
191
+ return binaryExpr(xpr, ['*', '/'], terminal, s, e);
192
+ }
193
+
194
+ function terminal(xpr, s, e) {
195
+ if(Array.isArray(xpr) && xpr.length > 0) {
196
+ if(e-s <= 1)
197
+ return parseExprInt(xpr[e-1]);
198
+ else
199
+ return xpr.slice(s, e).map(parseExprInt);
200
+ }
201
+ if (typeof xpr === 'object') {
202
+ for(let n in xpr) {
203
+ xpr[n] = parseExprInt(xpr[n]);
204
+ }
205
+ }
206
+ return xpr;
207
+ }
208
+
209
+ function binaryExpr(xpr, token, next, s, e) {
210
+ if (Array.isArray(xpr)) {
211
+ let [tl, p] = findToken(s, e);
212
+ if (p >= 0) {
213
+ let lhs = next(xpr, s, p);
214
+ let op = xpr[p];
215
+ s = p+tl;
216
+ [tl, p] = findToken(s, e);
217
+ while(p >= 0) {
218
+ let rhs = next(xpr, s, p);
219
+ lhs = array ? [ lhs, op, rhs ] : { [op]: [lhs, rhs] };
220
+ op = xpr[p];
221
+ s = p+tl;
222
+ [tl, p] = findToken(s, e);
223
+ }
224
+ return array ? [ lhs, op, next(xpr, s, e) ] : { [op]: [lhs, next(xpr, s, e)] };
225
+ }
226
+ }
227
+ return next(xpr, s, e);
228
+
229
+ function findToken(s, e) {
230
+ if(typeof token === 'function')
231
+ return token(xpr, s, e);
232
+ else {
233
+ while(s < e && !token.includes(xpr[s])) s++;
234
+ if(s < e)
235
+ return [1, s];
236
+ }
237
+ return [1, -1];
238
+ }
239
+ }
240
+
241
+ }
242
+
243
+ module.exports = {
244
+ parseExpr,
245
+ };