@sap/cds-compiler 3.6.2 → 3.8.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 (89) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +12 -5
  4. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  5. package/doc/CHANGELOG_BETA.md +35 -2
  6. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  7. package/doc/DeprecatedOptions_v2.md +1 -1
  8. package/doc/NameResolution.md +1 -1
  9. package/lib/api/main.js +63 -23
  10. package/lib/api/options.js +1 -0
  11. package/lib/api/validate.js +5 -0
  12. package/lib/base/dictionaries.js +15 -3
  13. package/lib/base/keywords.js +2 -0
  14. package/lib/base/message-registry.js +120 -34
  15. package/lib/base/messages.js +51 -27
  16. package/lib/base/model.js +4 -2
  17. package/lib/base/shuffle.js +2 -1
  18. package/lib/checks/arrayOfs.js +1 -1
  19. package/lib/checks/defaultValues.js +1 -1
  20. package/lib/checks/elements.js +29 -1
  21. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/onConditions.js +15 -9
  25. package/lib/checks/sql-snippets.js +2 -2
  26. package/lib/checks/types.js +5 -1
  27. package/lib/checks/validator.js +7 -3
  28. package/lib/compiler/assert-consistency.js +42 -26
  29. package/lib/compiler/base.js +50 -4
  30. package/lib/compiler/builtins.js +17 -8
  31. package/lib/compiler/checks.js +241 -246
  32. package/lib/compiler/define.js +113 -146
  33. package/lib/compiler/extend.js +889 -383
  34. package/lib/compiler/finalize-parse-cdl.js +5 -58
  35. package/lib/compiler/index.js +1 -1
  36. package/lib/compiler/kick-start.js +7 -8
  37. package/lib/compiler/populate.js +297 -293
  38. package/lib/compiler/propagator.js +27 -18
  39. package/lib/compiler/resolve.js +146 -463
  40. package/lib/compiler/shared.js +36 -79
  41. package/lib/compiler/tweak-assocs.js +30 -28
  42. package/lib/compiler/utils.js +31 -5
  43. package/lib/edm/annotations/genericTranslation.js +131 -59
  44. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  45. package/lib/edm/csn2edm.js +22 -5
  46. package/lib/edm/edm.js +6 -4
  47. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  48. package/lib/edm/edmPreprocessor.js +42 -26
  49. package/lib/gen/Dictionary.json +38 -2
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -1
  52. package/lib/gen/languageLexer.js +1 -1
  53. package/lib/gen/languageParser.js +4828 -4472
  54. package/lib/inspect/inspectPropagation.js +20 -34
  55. package/lib/json/from-csn.js +140 -44
  56. package/lib/json/to-csn.js +114 -122
  57. package/lib/language/errorStrategy.js +2 -0
  58. package/lib/language/genericAntlrParser.js +156 -36
  59. package/lib/language/language.g4 +100 -58
  60. package/lib/language/textUtils.js +13 -0
  61. package/lib/main.d.ts +43 -3
  62. package/lib/main.js +4 -2
  63. package/lib/model/csnRefs.js +15 -3
  64. package/lib/model/csnUtils.js +12 -74
  65. package/lib/model/revealInternalProperties.js +4 -2
  66. package/lib/modelCompare/compare.js +2 -1
  67. package/lib/optionProcessor.js +3 -0
  68. package/lib/render/manageConstraints.js +5 -2
  69. package/lib/render/toCdl.js +216 -104
  70. package/lib/render/toHdbcds.js +2 -9
  71. package/lib/render/toRename.js +14 -51
  72. package/lib/render/toSql.js +4 -3
  73. package/lib/render/utils/common.js +9 -5
  74. package/lib/transform/braceExpression.js +6 -0
  75. package/lib/transform/db/assertUnique.js +2 -1
  76. package/lib/transform/db/expansion.js +2 -0
  77. package/lib/transform/db/flattening.js +37 -36
  78. package/lib/transform/db/rewriteCalculatedElements.js +600 -0
  79. package/lib/transform/db/transformExists.js +4 -0
  80. package/lib/transform/db/views.js +40 -37
  81. package/lib/transform/forOdataNew.js +20 -15
  82. package/lib/transform/forRelationalDB.js +58 -41
  83. package/lib/transform/odata/typesExposure.js +50 -15
  84. package/lib/transform/parseExpr.js +16 -8
  85. package/lib/transform/transformUtilsNew.js +42 -14
  86. package/lib/transform/translateAssocsToJoins.js +60 -37
  87. package/lib/transform/universalCsn/coreComputed.js +15 -7
  88. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  89. package/package.json +2 -1
@@ -60,7 +60,7 @@ const { addLocalizationViews } = require('./localized');
60
60
  // - Mark fields with @odata.on.insert/update as @Core.Computed
61
61
  // (EdmPreproc candidate, check with RT if @Core.Computed required by them)
62
62
  // - Rename shorthand annotations according to a builtin list (EdmPreproc Candidate)
63
- // e.g. @label -> @Common.Label or @important: [true|false] -> @UI.Importance: [#High|#Low]
63
+ // e.g. @label -> @Common.Label
64
64
  // - If the association target is annotated with @cds.odata.valuelist, annotate the
65
65
  // association with @Common.ValueList.viaAssociation (EdmPreproc Candidate)
66
66
  // - Check for @Analytics.Measure and @Aggregation.default (Linter check candidate, remove)
@@ -315,31 +315,36 @@ function transform4odataWithCsn(inputModel, options) {
315
315
  // list.
316
316
  function renameShorthandAnnotations(node) {
317
317
  // FIXME: Verify this list - are they all still required? Do we need any more?
318
- const mappings = {
318
+ const setMappings = {
319
319
  '@label': '@Common.Label',
320
320
  '@title': '@Common.Label',
321
321
  '@description': '@Core.Description',
322
+ };
323
+ const renameMappings = {
322
324
  '@ValueList.entity': '@Common.ValueList.entity',
323
325
  '@ValueList.type': '@Common.ValueList.type',
324
326
  '@Capabilities.Deletable': '@Capabilities.DeleteRestrictions.Deletable',
325
327
  '@Capabilities.Insertable': '@Capabilities.InsertRestrictions.Insertable',
326
328
  '@Capabilities.Updatable': '@Capabilities.UpdateRestrictions.Updatable',
327
329
  '@Capabilities.Readable': '@Capabilities.ReadRestrictions.Readable',
328
- }
330
+ };
329
331
 
330
- const shortCuts = Object.keys(mappings);
332
+ const setShortCuts = Object.keys(setMappings);
333
+ const renameShortCuts = Object.keys(renameMappings);
331
334
  Object.keys(node).forEach( name => {
335
+ if (!name.startsWith('@'))
336
+ return;
332
337
  // Rename according to map above
333
- const prefix = shortCuts.find(p => name.startsWith(p + '.') || name === p);
334
- if(prefix) {
335
- renameAnnotation(node, name, name.replace(prefix, mappings[prefix]));
336
- }
337
- // Special case: '@important: [true|false]' becomes '@UI.Importance: [#High|#Low]'
338
- if (name === '@important') {
339
- renameAnnotation(node, name, '@UI.Importance');
340
- let annotation = node['@UI.Importance'];
341
- if (annotation !== null)
342
- node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' };
338
+ const renamePrefix = (name in renameMappings) ? name : renameShortCuts.find(p => name.startsWith(p + '.'));
339
+ if(renamePrefix) {
340
+ renameAnnotation(node, name, name.replace(renamePrefix, renameMappings[renamePrefix]));
341
+ } else {
342
+ // The two mappings have no overlap, so no need to check for second map if first matched.
343
+ // Rename according to map above
344
+ const setPrefix = (name in setMappings) ? name : setShortCuts.find(p => name.startsWith(p + '.'));
345
+ if(setPrefix) {
346
+ setAnnotation(node, name.replace(setPrefix, setMappings[setPrefix]), node[name]);
347
+ }
343
348
  }
344
349
 
345
350
  // Special case: '@readonly' becomes a triplet of capability restrictions for entities,
@@ -351,7 +356,7 @@ function transform4odataWithCsn(inputModel, options) {
351
356
  setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);
352
357
  setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
353
358
  } else {
354
- renameAnnotation(node, name, '@Core.Computed');
359
+ setAnnotation(node, '@Core.Computed', true);
355
360
  }
356
361
  }
357
362
  // @insertonly is effective on entities/queries only
@@ -9,7 +9,7 @@ const { cloneCsnNonDict,
9
9
  const { makeMessageFunction } = require('../base/messages');
10
10
  const transformUtils = require('./transformUtilsNew');
11
11
  const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
12
- const { csnRefs, pathId } = require('../model/csnRefs');
12
+ const { csnRefs, pathId, traverseQuery } = require('../model/csnRefs');
13
13
  const { checkCSNVersion } = require('../json/csnVersion');
14
14
  const validate = require('../checks/validator');
15
15
  const { rejectManagedAssociationsAndStructuresForHdbcdsNames } = require('../checks/selectItems');
@@ -18,6 +18,7 @@ const { timetrace } = require('../utils/timetrace');
18
18
  const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
19
19
  const { createDict, forEach } = require('../utils/objectUtils');
20
20
  const handleExists = require('./db/transformExists');
21
+ const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities } = require('./db/rewriteCalculatedElements');
21
22
  const replaceAssociationsInGroupByOrderBy = require('./db/groupByOrderBy');
22
23
  const _forEachDefinition = require('../model/csnUtils').forEachDefinition;
23
24
  const flattening = require('./db/flattening');
@@ -126,8 +127,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
126
127
  get$combined,
127
128
  getCsnDef,
128
129
  isAssocOrComposition,
129
- addStringAnnotationTo,
130
- cloneWithTransformations;
130
+ addStringAnnotationTo;
131
131
  // transformUtils
132
132
  let addDefaultTypeFacets,
133
133
  expandStructsInExpression,
@@ -174,18 +174,17 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
174
174
 
175
175
  throwWithAnyError();
176
176
 
177
- // FIXME: This does something very similar to cloneWithTransformations -> refactor?
178
177
  const transformCsn = transformUtils.transformModel;
179
178
 
179
+ forEachDefinition(csn, [
180
+ // (001) Add a temporal where condition to views where applicable before assoc2join
181
+ // assoc2join eventually rewrites the table aliases
182
+ temporal.getViewDecorator(csn, {info}, csnUtils),
183
+ // check unique constraints - further processing is done in rewriteUniqueConstraints
184
+ assertUnique.prepare(csn, options, error, info)
185
+ ]);
180
186
 
181
- timetrace.start('temporal');
182
- // (001) Add a temporal where condition to views where applicable before assoc2join
183
- // assoc2join eventually rewrites the table aliases
184
- forEachDefinition(csn, temporal.getViewDecorator(csn, {info}, csnUtils));
185
- timetrace.stop('temporal');
186
-
187
- // check unique constraints - further processing is done in rewriteUniqueConstraints
188
- assertUnique.prepare(csn, options, error, info);
187
+ rewriteCalculatedElementsInViews(csn, options, pathDelimiter, error);
189
188
 
190
189
  if(doA2J) {
191
190
  // Expand a structured thing in: keys, columns, order by, group by
@@ -199,6 +198,11 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
199
198
  bindCsnReferenceOnly();
200
199
 
201
200
 
201
+ // TODO: Instead of 3 separate applyTransformations, we could have each of them just return the "listeners", merge them into
202
+ // one big listener that then gets passed into one single applyTransformations. Each listener would then have to return an array of callbacks to call.
203
+ // With that, we could still ensure the processing order (assuming we don't run into problems with scoping).
204
+ // To analyze: Increased memory vs. saved cycles
205
+ // Looked at it with AFC: This is only a small part of the overall processing time, enrich step of validator is just as expensive
202
206
  if(doA2J) {
203
207
  const resolved = new WeakMap();
204
208
  // No refs with struct-steps exist anymore
@@ -240,6 +244,8 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
240
244
  }
241
245
  });
242
246
 
247
+ processCalculatedElementsInEntities(csn, options);
248
+
243
249
  timetrace.start('Transform CSN')
244
250
 
245
251
  // (000) Rename primitive types, make UUID a String
@@ -255,15 +261,15 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
255
261
  },
256
262
  }, true);
257
263
 
258
- // (040) Ignore entities and views that are abstract or implemented
259
- // or carry the annotation cds.persistence.skip/exists
260
- // These entities are not removed from the csn, but flagged as "to be ignored"
261
- forEachDefinition(csn, cdsPersistence.getAnnoProcessor());
262
-
263
-
264
- // (050) Check @cds.valid.from/to only on entity
265
- // Views are checked in (001), unbalanced valid.from/to's or mismatching origins
266
- forEachDefinition(csn, temporal.getAnnotationHandler(csn, options, pathDelimiter, {error}));
264
+ forEachDefinition(csn, [
265
+ // (040) Ignore entities and views that are abstract or implemented
266
+ // or carry the annotation cds.persistence.skip/exists
267
+ // These entities are not removed from the csn, but flagged as "to be ignored"
268
+ cdsPersistence.getAnnoProcessor(),
269
+ // (050) Check @cds.valid.from/to only on entity
270
+ // Views are checked in (001), unbalanced valid.from/to's or mismatching origins
271
+ temporal.getAnnotationHandler(csn, options, pathDelimiter, {error})
272
+ ]);
267
273
 
268
274
  // eliminate the doA2J in the functions 'handleManagedAssociationFKs' and 'createForeignKeyElements'
269
275
  doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, pathDelimiter, true, csnUtils, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });
@@ -300,18 +306,19 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
300
306
  }
301
307
  });
302
308
 
303
- // For generating DB stuff:
304
- // - table-entity with parameters: not allowed
305
- // - view with parameters: ok on HANA, not allowed otherwise
306
- // (don't complain about action/function with parameters)
307
- forEachDefinition(csn, handleChecksForWithParameters);
308
-
309
- // Remove .masked
310
- // Check that keys are not explicitly nullable
311
- // Check that Associations are not used in entities/views with parameters
312
- // (150 b) Strip inheritance
313
- // Note that this should happen after implicit redirection, because includes are required for that
314
- forEachDefinition(csn, handleDBChecks);
309
+ forEachDefinition(csn, [
310
+ // For generating DB stuff:
311
+ // - table-entity with parameters: not allowed
312
+ // - view with parameters: ok on HANA, not allowed otherwise
313
+ // (don't complain about action/function with parameters)
314
+ handleChecksForWithParameters,
315
+ // Remove .masked
316
+ // Check that keys are not explicitly nullable
317
+ // Check that Associations are not used in entities/views with parameters
318
+ // (150 b) Strip inheritance
319
+ // Note that this should happen after implicit redirection, because includes are required for that
320
+ handleDBChecks,
321
+ ]);
315
322
 
316
323
  // (170) Transform '$self' in backlink associations to appropriate key comparisons
317
324
  // Must happen before draft processing because the artificial ON-conditions in generated
@@ -449,7 +456,6 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
449
456
  getCsnDef,
450
457
  isAssocOrComposition,
451
458
  addStringAnnotationTo,
452
- cloneWithTransformations
453
459
  } = csnUtils);
454
460
  }
455
461
 
@@ -530,6 +536,20 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
530
536
  if (!artifact._ignore) {
531
537
  // Do things specific for entities and views (pass 2)
532
538
  if ((artifact.kind === 'entity') && artifact.query) {
539
+
540
+ // First pass: Set alias name for SELECTs without table alias. Required for setting proper table aliases
541
+ // for HDBCDS in naming mode HDBCDS. We use the same schema as the core-compiler, so duplicates should
542
+ // have already been reported.
543
+ let selectDepth = 0;
544
+ traverseQuery(artifact.query, null, null, (query, fromSelect) => {
545
+ if (!query.ref && !query.as && fromSelect) {
546
+ // Use +1; for UNION, it's the next select, for SELECT, it's increased later.
547
+ query.as = `$_select_${selectDepth + 1}__`;
548
+ }
549
+ if (query.SELECT)
550
+ ++selectDepth;
551
+ });
552
+
533
553
  const process = (parent, prop, query, path) => {
534
554
  transformEntityOrViewPass2(parent, artifact, artifactName, path.concat(prop))
535
555
  replaceAssociationsInGroupByOrderBy(parent, options, inspectRef, error, path.concat(prop));
@@ -977,25 +997,22 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
977
997
  // this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
978
998
  elemName = elemName.replace(/\./g, pathDelimiter);
979
999
  const assocName = originalAssocName.replace(/\./g, pathDelimiter);
980
- // clone the onCond for later use in the path transformation,
981
- // also assign the _artifact elements of the path elements to the copy
982
- const newOnCond = cloneWithTransformations(assoc.on, {
983
- ref: (value) => cloneWithTransformations(value, {}),
984
- });
1000
+ // clone the onCond for later use in the path transformation
1001
+ const newOnCond = cloneCsnNonDict(assoc.on, options);
985
1002
  applyTransformationsOnNonDictionary({on: newOnCond}, 'on', {
986
1003
  ref: (parent, prop, ref) => {
987
1004
  if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
988
1005
  {
989
1006
  ref.shift();
990
1007
  } else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
991
- // We could also have a $self infront of the assoc name - so we would need to shift twice
1008
+ // We could also have a $self in front of the assoc name - so we would need to shift twice
992
1009
  ref.shift();
993
1010
  ref.shift();
994
1011
  }
995
1012
  else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
996
1013
  ref.unshift(elemName);
997
1014
  // if there was a $self identifier in the forwarding association onCond
998
- // we do not need it any more, as we prepended in the previous step the back association's id
1015
+ // we do not need it anymore, as we prepended in the previous step the back association's id
999
1016
  if (ref[1] === '$self')
1000
1017
  ref.splice(1, 1);
1001
1018
  }
@@ -13,21 +13,66 @@ const { copyAnnotations, getNamespace } = require('../../model/csnUtils');
13
13
  const { isBetaEnabled } = require('../../base/model.js');
14
14
  const { CompilerAssertion } = require('../../base/error');
15
15
 
16
+ /**
17
+ * A given CDS model is a set of n definitions D = {v_1, ..., v_n } spanning a type dependency
18
+ * graph T(D) with vertices v_r, v_d (representing the referrer (using) and defining node of a type)
19
+ * and edges e(v_r, v_d).
20
+ *
21
+ * S may be a proper subset of D and is defined by v_s. Up to n S_i may exist.
22
+ * v is element of S_i, if name(v) starts with name(v_s). Therefore any v can only be member
23
+ * of exactly one S_i and v_s is always member of its own S_i.
24
+ *
25
+ * A complete service type dependency graph Tc(S) is defined as a set of vertices v_r, v_d
26
+ * and edges e(v_r, v_d) such that all v_r, v_d are elements of S (v_rs, v_ds) and with that
27
+ * all edges are { e(v_rs, v_ds) }.
28
+ *
29
+ * The input CSN may contain edges e(v_rs, v_dns) with v_r element of S_i (v_rs) and v_d not element
30
+ * of S_i (v_dns).
31
+ *
32
+ * The aim of this algorithm is to produce Tc's for all requested S_i by 'filling' up the missing
33
+ * vertices v_ds and rewriting all e(v_rs, v_dns) to e(v_rs, v_ds).
34
+ *
35
+ * This can be done pretty easily by (recursively) iterating over all requested v_rs and
36
+ * follow e(v_r, v_d) until a v_dns is found. v_dns is cloned and added to S via
37
+ * name(v_ds) = name(v_s) + '.' + name(v_dns). If v_dns is an anonymous definition, an
38
+ * artificial name representing the path to that node is being used.
39
+ *
40
+ * The algorithm has a beneficial side effect: it creates new (sub) schemas on the fly
41
+ * which are required for the construction of the EDM intermediate representation later on.
42
+ *
43
+ * An OData service contains at least one schema. Only (OData) schemas may contain definitions.
44
+ * By default, the CDS service v_s represents the default (OData) schema.
45
+ * However, there are situations where { v_dns } must be partitioned into (sub) schemas in order to
46
+ * maintain their original name prefixes and to be compatible with later service definitions.
47
+ *
48
+ * If name(v_dns) is made up of segments separated by a dot '.', the first n-1 segments represent
49
+ * the sub schema: name(schema) = name(v_s) + '.' + concat(1,n-1, segments(name(v_dns)), '.')
50
+ *
51
+ * If name(v_dns) has no prefix segments, the fallback schema name is prepended instead:
52
+ * name(v_ds) = name(v_s) + '.' + fallbackschema + name(v_dns);
53
+ *
54
+ * @param {CSN.Model} csn
55
+ * @param {function} whatsMyServiceName
56
+ * @param {string[]} requestedServiceNames
57
+ * @param {String} fallBackSchemaName
58
+ * @param {Object} options
59
+ * @param {Object} csnUtils
60
+ * @param {Object} message
61
+ * @returns {Object} schemas dictionary of (sub) schemas for all requested services
62
+ */
16
63
  function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackSchemaName, options, csnUtils, message) {
17
64
  const { error } = message;
18
65
  const special$self = !csn?.definitions?.$self && '$self';
19
66
  // are we working with OData proxies or cross-service refs
20
67
  const isMultiSchema = options.odataVersion === 'v4' && (options.odataProxies || options.odataXServiceRefs);
21
- // collect in this variable all the newly exposed types
68
+ // service sub schemas as return value
22
69
  const schemas = Object.create(null);
70
+ // exposed types register
23
71
  const exposedTypes = Object.create(null);
24
- // walk through the definitions of the given CSN and expose types where needed
25
72
  forEachDefinition(csn, (def, defName, propertyName, path) => {
26
- // we do expose types only for definition from inside services
27
73
  const serviceName = whatsMyServiceName(defName, false);
28
74
  // run type exposure only on requested services if not in multi schema mode
29
75
  // multi schema mode requires a proper type exposure for all services as a prerequisite
30
- // for the proxy exposure
31
76
  if (serviceName && requestedServiceNames.includes(serviceName)) {
32
77
  if (def.kind === 'type' || def.kind === 'entity') {
33
78
  forEachMember(def, (element, elementName, propertyName, path) => {
@@ -38,13 +83,9 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
38
83
  }, path);
39
84
  }
40
85
 
41
- // For exposed actions and functions that use non-exposed or anonymous structured types, create
42
- // artificial exposing types.
43
- // unbound actions
44
86
  if (def.kind === 'action' || def.kind === 'function') {
45
87
  exposeTypesOfAction(def, defName, defName, serviceName, path);
46
88
  }
47
- // bound actions
48
89
  def.actions && Object.entries(def.actions).forEach(([actionName, action]) => {
49
90
  exposeTypesOfAction(action, `${defName}_${actionName}`, defName, serviceName, path.concat(['actions', actionName]));
50
91
  });
@@ -53,19 +94,13 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
53
94
 
54
95
  if(isBetaEnabled(options, 'odataTerms')) {
55
96
  forEachGeneric(csn, 'vocabularies', (def, defName, _propertyName, path) => {
56
- // we do expose types only for definition from inside services
57
97
  const serviceName = whatsMyServiceName(defName, false);
58
- // run type exposure only on requested services if not in multi schema mode
59
- // multi schema mode requires a proper type exposure for all services as a prerequisite
60
- // for the proxy exposure
61
98
  if (serviceName && requestedServiceNames.includes(serviceName)) {
62
99
  if(csn.definitions[defName]) {
63
- // error, duplicate definitions not allowed!
64
- // TODO: Use path as error location as soon as refs outside definitions are supported
65
100
  error('odata-definition-exists', ['vocabularies', defName], { anno: defName, '#':'anno' });
66
101
  }
67
102
  else {
68
- // link def into definitions for later use
103
+ // link def into definitions for later use
69
104
  def.kind = 'annotation';
70
105
  csn.definitions[defName] = def;
71
106
  const artificialName = `term_${defName.replace(/\./g, '_')}`;//_${paramName}`;
@@ -19,7 +19,7 @@
19
19
  * Unary: 'is [not] null', 'not'
20
20
  * Conditional: 'case [when then]+ [else]? end', 'and', 'or'
21
21
  *
22
- * Not yet implemented: 'new'
22
+ * stand-alone token: 'new'
23
23
  *
24
24
  * This is not an optimized LL(1) parser but a token 'sniffer'. A stream is
25
25
  * cracked up in sub streams and passed down to the next higher function.
@@ -33,8 +33,8 @@
33
33
  * This parser intentionally does no error handling. If a clause is malformed, it is accepted as is.
34
34
  *
35
35
  * @param {any} xpr A JSON object.
36
- * @param {Object} state Objet
37
- * anno: Don't eliminate arrays with single entry in statetations as they are collections
36
+ * @param {Object} state Object
37
+ * anno: Don't eliminate arrays with single entry in statetations (TODO?) as they are collections
38
38
  * array: Bias AST representation.
39
39
  * nary: return n-ary or binary tree
40
40
  */
@@ -42,7 +42,7 @@
42
42
  function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
43
43
  // Notes:
44
44
  // - Variables `s` and `e` are used as index variables into `xpr`s for start and end.
45
- // - xpr's are our CSN expressions, see <https://pages.github.tools.sap/cap/docs/cds/cxn>
45
+ // - xpr's are our CSN expressions, see <https://cap.cloud.sap/docs/cds/cxn>
46
46
 
47
47
  return parseExprInt(xpr, state);
48
48
 
@@ -82,7 +82,7 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
82
82
  */
83
83
  function rewriteCaseBlock(casePos, endPos) {
84
84
  const caseTree = state.array ? [ 'case' ] : { 'case': [] };
85
-
85
+
86
86
  let elsePos = endPos;
87
87
  let whenPos = casePos;
88
88
 
@@ -144,7 +144,7 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
144
144
  function conditionOR(xpr, s, e, state) {
145
145
  return binaryExpr(xpr, ['or'], conditionAnd, s, e, state);
146
146
  }
147
-
147
+
148
148
  function conditionAnd(xpr, s, e, state) {
149
149
  return binaryExpr(xpr, (xpr, s, e) => {
150
150
  let a = s-1;
@@ -283,7 +283,8 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
283
283
  // return xpr;
284
284
  for(let n in xpr) {
285
285
  const x = xpr[n];
286
- if(n[0] === '@')
286
+ const isAnno = n[0] === '@' && isSimpleAnnoValue(x);
287
+ if(isAnno)
287
288
  state.anno++;
288
289
  if(Array.isArray(x)) {
289
290
  if(csnarray.includes(n) || state.anno !== 0)
@@ -295,7 +296,7 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
295
296
  }
296
297
  else
297
298
  xpr[n] = parseExprInt(x, state);
298
- if(n[0] === '@')
299
+ if(isAnno)
299
300
  state.anno--;
300
301
  }
301
302
  }
@@ -343,6 +344,13 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
343
344
 
344
345
  }
345
346
 
347
+ function isSimpleAnnoValue(val) {
348
+ // Expressions as annotation values always have a `=` and another property.
349
+ // TODO: There must be at least one known expression property, otherwise
350
+ // it could be `type: 'unchecked'`.
351
+ return !val?.['='] || Object.keys(val) < 2;
352
+ }
353
+
346
354
  module.exports = {
347
355
  parseExpr,
348
356
  };
@@ -12,7 +12,6 @@ const { cloneCsnNonDict, cloneCsnDictionary, getUtils } = require('../model/csnU
12
12
  const { typeParameters, isBuiltinType } = require('../compiler/builtins');
13
13
  const { ModelError, CompilerAssertion} = require('../base/error');
14
14
  const { forEach } = require('../utils/objectUtils');
15
- const { pathName } = require('../compiler/utils');
16
15
 
17
16
  const RestrictedOperators = ['<', '>', '>=', '<='];
18
17
  const RelationalOperators = ['=', '!=', '<>', 'is' /*, 'like'*/,...RestrictedOperators];
@@ -321,9 +320,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
321
320
  */
322
321
  function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap()) {
323
322
  // Refs of length 1 cannot contain steps - no need to check
324
- if (ref.length < 2) {
325
- return ref;
326
- } else if(scope === '$self' && ref.length === 2) {
323
+ if (ref.length < 2 || (scope === '$self' && ref.length === 2)) {
327
324
  return ref;
328
325
  }
329
326
 
@@ -1136,7 +1133,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
1136
1133
  const rhsIsVal = (rhs === 'null' /*|| rhs.val !== undefined*/);
1137
1134
 
1138
1135
  // lhs & rhs must be expandable types (structures or managed associations)
1139
- // if ever lhs should be alowed to be a value uncomment this
1136
+ // if ever lhs should be allowed to be a value uncomment this
1140
1137
  if(!(lhsIsVal /*&& rhsIsVal*/) &&
1141
1138
  !(isDollarSelfOrProjectionOperand(lhs) || isDollarSelfOrProjectionOperand(rhs)) &&
1142
1139
  RelationalOperators.includes(op) &&
@@ -1162,26 +1159,41 @@ function getTransformers(model, options, pathDelimiter = '_') {
1162
1159
  const xrefvalues = Object.values(xref);
1163
1160
  let cont = true;
1164
1161
 
1162
+ const prefix = (lhs, op, rhs) => {
1163
+ return `${lhsIsVal ? lhs.val : lhs.ref.join('.')} ${op} ${rhsIsVal ? rhs : rhs.ref.join('.')}`
1164
+ }
1165
1165
  if(op === 'like' && xrefvalues.reduce((a, v) => {
1166
1166
  return (v.lhs && v.rhs) ? a + 1: a;
1167
1167
  }, 0) === 0) {
1168
1168
  // error if intersection of paths is zero
1169
- error(null, location, { lhs: pathName(lhs.ref), op, rhs: pathName(rhs.ref) },
1170
- 'Expected compatible types for $(LHS) $(OP) $(RHS)');
1169
+ error(null, location,
1170
+ {
1171
+ prefix: prefix(lhs, op, rhs)
1172
+ },
1173
+ 'Expected compatible types for $(PREFIX)');
1171
1174
  cont = false;
1172
1175
  }
1173
1176
 
1174
1177
  cont && xrefkeys.forEach(xn => {
1175
1178
  const x = xref[xn];
1176
- const prefix = `${pathName(lhs.ref)} ${op} ${pathName(rhs.ref)}`;
1177
1179
  // do the paths match?
1178
1180
  if(op !== 'like' && !(x.lhs && x.rhs)) {
1179
1181
  if(xn.length) {
1180
- error(null, location, { prefix, name: xn, alias: pathName((x.lhs ? rhs : lhs).ref) },
1182
+ error(null, location,
1183
+ {
1184
+ prefix: prefix(lhs, op, rhs),
1185
+ name: xn,
1186
+ alias: (x.lhs ? rhs : lhs).ref.join('.')
1187
+ },
1181
1188
  '$(PREFIX): Sub path $(NAME) not found in $(ALIAS)');
1182
1189
  }
1183
1190
  else {
1184
- error(null, location, { prefix, name: pathName((x.lhs ? lhs : rhs).ref), alias: pathName((x.lhs ? rhs : lhs).ref) },
1191
+ error(null, location,
1192
+ {
1193
+ prefix: prefix(lhs, op, rhs),
1194
+ name: (x.lhs ? lhs : rhs).ref.join('.'),
1195
+ alias: (x.lhs ? rhs : lhs).ref.join('.')
1196
+ },
1185
1197
  '$(PREFIX): Path $(NAME) does not match $(ALIAS)');
1186
1198
  }
1187
1199
  cont = false;
@@ -1189,23 +1201,38 @@ function getTransformers(model, options, pathDelimiter = '_') {
1189
1201
  // lhs && rhs are present, consistency checks that affect both ends
1190
1202
  else {
1191
1203
  // is lhs scalar?
1204
+ // eslint-disable-next-line sonarjs/no-gratuitous-expressions
1192
1205
  if(!lhsIsVal && x.lhs && !isScalarOrNoType(x.lhs._art)) {
1193
- error(null, location, { prefix, name: `${pathName(x.lhs.ref)}${(xn.length ? '.' + xn : '')}` },
1206
+ error(null, location,
1207
+ {
1208
+ prefix: prefix(lhs, op, rhs),
1209
+ name: `${x.lhs.ref.join('.')}${(xn.length ? '.' + xn : '')}`
1210
+ },
1194
1211
  '$(PREFIX): Path $(NAME) must end on a scalar type')
1195
1212
  cont = false;
1196
1213
  }
1197
1214
  // is rhs scalar?
1198
1215
  if(!rhsIsVal && x.rhs && !isScalarOrNoType(x.rhs._art)) {
1199
- error(null, location, { prefix, name: `${pathName(x.rhs.ref)}${(xn.length ? '.' + xn : '')}` },
1216
+ error(null, location,
1217
+ {
1218
+ prefix: prefix(lhs, op, rhs),
1219
+ name: `${x.rhs.ref.join('.')}${(xn.length ? '.' + xn : '')}`
1220
+ },
1200
1221
  '$(PREFIX): Path $(NAME) must end on a scalar type');
1201
1222
  cont = false;
1202
1223
  }
1203
1224
  // info about type incompatibility if no other errors occurred
1225
+ // eslint-disable-next-line sonarjs/no-gratuitous-expressions
1204
1226
  if(!(lhsIsVal || rhsIsVal) && x.lhs && x.rhs && xn && cont) {
1205
1227
  const lhst = getType(x.lhs._art);
1206
1228
  const rhst = getType(x.rhs._art);
1207
1229
  if(lhst !== rhst) {
1208
- info(null, location, { prefix, name: xn },'$(PREFIX): Types for sub path $(NAME) don\'t match');
1230
+ info(null, location,
1231
+ {
1232
+ prefix: prefix(lhs, op, rhs),
1233
+ name: xn
1234
+ },
1235
+ '$(PREFIX): Types for sub path $(NAME) don\'t match');
1209
1236
  }
1210
1237
  }
1211
1238
  }
@@ -1215,6 +1242,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
1215
1242
  return expr;
1216
1243
 
1217
1244
  // if lhs and rhs are refs set operator from 'like' to '='
1245
+ // eslint-disable-next-line sonarjs/no-gratuitous-expressions
1218
1246
  if(op === 'like' && !(lhsIsVal || rhsIsVal)) {
1219
1247
  op = '=';
1220
1248
  }
@@ -1419,7 +1447,7 @@ function rewriteBuiltinTypeRef(csn) {
1419
1447
  const special$self = !csn?.definitions?.$self && '$self';
1420
1448
  applyTransformations(csn, {
1421
1449
  type: (parent, _prop, type) => {
1422
- if(type.ref && (
1450
+ if(type?.ref && (
1423
1451
  isBuiltinType(type.ref[0]) ||
1424
1452
  type.ref[0] === special$self)
1425
1453
  ) {