@sap/cds-compiler 3.1.2 → 3.3.2

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 (100) hide show
  1. package/CHANGELOG.md +80 -3
  2. package/bin/cdsc.js +1 -1
  3. package/doc/CHANGELOG_BETA.md +18 -0
  4. package/lib/api/main.js +8 -13
  5. package/lib/base/error.js +2 -2
  6. package/lib/base/keywords.js +2 -24
  7. package/lib/base/message-registry.js +43 -14
  8. package/lib/base/messages.js +20 -10
  9. package/lib/base/model.js +1 -1
  10. package/lib/checks/actionsFunctions.js +1 -1
  11. package/lib/checks/annotationsOData.js +2 -2
  12. package/lib/checks/arrayOfs.js +15 -7
  13. package/lib/checks/cdsPersistence.js +1 -1
  14. package/lib/checks/checkForTypes.js +48 -0
  15. package/lib/checks/defaultValues.js +2 -2
  16. package/lib/checks/elements.js +81 -6
  17. package/lib/checks/foreignKeys.js +12 -13
  18. package/lib/checks/invalidTarget.js +10 -11
  19. package/lib/checks/managedInType.js +21 -15
  20. package/lib/checks/nullableKeys.js +1 -1
  21. package/lib/checks/onConditions.js +9 -9
  22. package/lib/checks/parameters.js +21 -0
  23. package/lib/checks/selectItems.js +1 -1
  24. package/lib/checks/types.js +2 -2
  25. package/lib/checks/utils.js +17 -7
  26. package/lib/checks/validator.js +26 -14
  27. package/lib/compiler/assert-consistency.js +13 -6
  28. package/lib/compiler/builtins.js +8 -0
  29. package/lib/compiler/checks.js +40 -33
  30. package/lib/compiler/define.js +50 -44
  31. package/lib/compiler/extend.js +303 -37
  32. package/lib/compiler/kick-start.js +2 -35
  33. package/lib/compiler/populate.js +83 -62
  34. package/lib/compiler/propagator.js +1 -1
  35. package/lib/compiler/resolve.js +61 -104
  36. package/lib/compiler/shared.js +16 -6
  37. package/lib/compiler/tweak-assocs.js +25 -12
  38. package/lib/compiler/utils.js +2 -2
  39. package/lib/edm/annotations/genericTranslation.js +3 -3
  40. package/lib/edm/csn2edm.js +10 -10
  41. package/lib/edm/edm.js +17 -9
  42. package/lib/edm/edmPreprocessor.js +53 -30
  43. package/lib/edm/edmUtils.js +7 -2
  44. package/lib/gen/Dictionary.json +14 -0
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +3 -2
  47. package/lib/gen/languageParser.js +4205 -4100
  48. package/lib/inspect/inspectModelStatistics.js +1 -1
  49. package/lib/inspect/inspectPropagation.js +23 -9
  50. package/lib/json/csnVersion.js +1 -1
  51. package/lib/json/from-csn.js +26 -19
  52. package/lib/json/to-csn.js +47 -5
  53. package/lib/language/antlrParser.js +1 -1
  54. package/lib/language/genericAntlrParser.js +29 -13
  55. package/lib/language/language.g4 +28 -8
  56. package/lib/main.d.ts +3 -6
  57. package/lib/model/.eslintrc.json +13 -0
  58. package/lib/model/api.js +4 -2
  59. package/lib/model/csnRefs.js +74 -47
  60. package/lib/model/csnUtils.js +236 -218
  61. package/lib/model/enrichCsn.js +41 -31
  62. package/lib/model/revealInternalProperties.js +61 -57
  63. package/lib/model/sortViews.js +31 -31
  64. package/lib/modelCompare/compare.js +6 -6
  65. package/lib/optionProcessor.js +5 -0
  66. package/lib/render/manageConstraints.js +2 -2
  67. package/lib/render/toCdl.js +31 -44
  68. package/lib/render/toHdbcds.js +7 -5
  69. package/lib/render/toRename.js +4 -4
  70. package/lib/render/toSql.js +11 -5
  71. package/lib/render/utils/common.js +20 -9
  72. package/lib/render/utils/sql.js +5 -5
  73. package/lib/transform/db/applyTransformations.js +32 -3
  74. package/lib/transform/db/expansion.js +81 -37
  75. package/lib/transform/db/flattening.js +1 -1
  76. package/lib/transform/db/temporal.js +1 -1
  77. package/lib/transform/db/transformExists.js +1 -1
  78. package/lib/transform/forOdataNew.js +10 -7
  79. package/lib/transform/{forHanaNew.js → forRelationalDB.js} +7 -7
  80. package/lib/transform/localized.js +28 -19
  81. package/lib/transform/odata/toFinalBaseType.js +8 -11
  82. package/lib/transform/odata/typesExposure.js +1 -1
  83. package/lib/transform/transformUtilsNew.js +101 -39
  84. package/lib/transform/translateAssocsToJoins.js +5 -4
  85. package/lib/utils/moduleResolve.js +5 -5
  86. package/lib/utils/objectUtils.js +3 -3
  87. package/package.json +2 -2
  88. package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
  89. package/share/messages/check-proper-type-of.md +4 -4
  90. package/share/messages/check-proper-type.md +2 -2
  91. package/share/messages/duplicate-autoexposed.md +4 -4
  92. package/share/messages/extend-repeated-intralayer.md +4 -5
  93. package/share/messages/extend-unrelated-layer.md +4 -4
  94. package/share/messages/message-explanations.json +3 -1
  95. package/share/messages/redirected-to-ambiguous.md +7 -6
  96. package/share/messages/redirected-to-complex.md +63 -0
  97. package/share/messages/redirected-to-unrelated.md +6 -5
  98. package/share/messages/rewrite-not-supported.md +4 -4
  99. package/share/messages/syntax-expected-integer.md +3 -3
  100. package/share/messages/wildcard-excluding-one.md +37 -0
@@ -124,7 +124,7 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
124
124
  }
125
125
  },
126
126
  }, [ (definitions, artifactName, artifact) => {
127
- // Replace events, actions and functions with simple dummies - they don't have effect on forHanaNew stuff
127
+ // Replace events, actions and functions with simple dummies - they don't have effect on forRelationalDB stuff
128
128
  // and that way they contain no references and don't hurt.
129
129
 
130
130
  // Do not do for OData
@@ -81,7 +81,7 @@ function getViewDecorator(csn, messageFunctions) {
81
81
  }
82
82
  }
83
83
  else {
84
- info(null, [ 'definitions', artifactName ], `No temporal WHERE clause added as "${from[0].error_parent}"."${from[0].name}" and "${to[0].error_parent}"."${to[0].name}" are not of same origin`);
84
+ info(null, [ 'definitions', artifactName ], `No temporal WHERE clause added as "${from[0].errorParent}"."${from[0].name}" and "${to[0].errorParent}"."${to[0].name}" are not of same origin`);
85
85
  }
86
86
  }
87
87
  else if (from.length > 0 || to.length > 0) {
@@ -799,7 +799,7 @@ function handleExists(csn, options, error) {
799
799
  where.push({ ref: [ base, ...part.ref.slice(1) ] });
800
800
  }
801
801
  else if (part.$scope === '$self') { // source side - "absolute" scope
802
- // Same message as in forHanaNew/transformDollarSelfComparisonWithUnmanagedAssoc
802
+ // Same message as in forRelationalDB/transformDollarSelfComparisonWithUnmanagedAssoc
803
803
  error(null, part.$path, 'An association that uses "$self" in its ON-condition can\'t be compared to "$self"');
804
804
  }
805
805
  else if (partInspect.art) { // source side - with local scope
@@ -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' && 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
  }
@@ -983,7 +983,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
983
983
  const absolute = node.type;
984
984
  const parameters = node.parameters || [];
985
985
  // :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
986
+ // forRelationalDB tested against the parameters of the type definition which is not available in CSN
987
987
  for (const name in parameters) {
988
988
  const param = parameters[name];
989
989
  if (!node[param] && absolute !== 'cds.hana.ST_POINT' && absolute !== 'cds.hana.ST_GEOMETRY')
@@ -1124,5 +1124,5 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
1124
1124
 
1125
1125
 
1126
1126
  module.exports = {
1127
- transformForHanaWithCsn,
1127
+ transformForRelationalDBWithCsn,
1128
1128
  };
@@ -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,7 @@ 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, `Can't create artificial type "${fullQualifiedNewTypeName}" for "${memberName}" because the name is already used`);
112
112
  return;
113
113
  }
114
114
  }
@@ -13,13 +13,15 @@ const { typeParameters, isBuiltinType } = require('../compiler/builtins');
13
13
  const { ModelError } = require("../base/error");
14
14
  const { forEach } = require('../utils/objectUtils');
15
15
 
16
+ const RestrictedOperators = ['<', '>', '>=', '<='];
17
+ const RelationalOperators = ['=', '!=', '<>', 'is' /*, 'like'*/,...RestrictedOperators];
16
18
  // Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).
17
19
  // Use 'pathDelimiter' for flattened names (e.g. of struct elements or foreign key elements).
18
20
  // 'model' is compacted new style CSN
19
21
  // TODO: Error and warnings handling with compacted CSN? - currently just throw new ModelError for everything
20
22
  // TODO: check the situation with assocs with values. In compacted CSN such elements have only "@Core.Computed": true
21
23
  function getTransformers(model, options, pathDelimiter = '_') {
22
- const { error, warning, info } = makeMessageFunction(model, options);
24
+ const { message, error, warning, info } = makeMessageFunction(model, options);
23
25
  const csnUtils = getUtils(model);
24
26
  const {
25
27
  getCsnDef,
@@ -500,7 +502,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
500
502
  * Create a 'DraftAdministrativeData' projection on entity 'DRAFT.DraftAdministrativeData'
501
503
  * in service 'service' and add it to the model.
502
504
  *
503
- * For forHanaNew, use String(36) instead of UUID and UTCTimestamp instead of Timestamp
505
+ * For forRelationalDB, use String(36) instead of UUID and UTCTimestamp instead of Timestamp
504
506
  *
505
507
  * @param {string} service
506
508
  * @param {boolean} [hanaMode=false] Turn UUID into String(36)
@@ -1113,51 +1115,64 @@ function getTransformers(model, options, pathDelimiter = '_') {
1113
1115
  for(let i = 0; i < expr.length; i++)
1114
1116
  {
1115
1117
  if(Array.isArray(expr[i]))
1116
- rc.push(expr[i].map(expand, location));
1118
+ rc.push(expr[i].map(e => expand(e, location)));
1117
1119
 
1118
1120
  if(i < expr.length-2)
1119
1121
  {
1120
- const [lhs, op, rhs] = expr.slice(i);
1122
+ let [lhs, op, not, rhs] = expr.slice(i);
1123
+ if(not !== 'not') {
1124
+ rhs = not;
1125
+ not = false;
1126
+ }
1127
+ if(lhs === undefined || op === undefined || rhs === undefined)
1128
+ return expr;
1121
1129
 
1122
1130
  // we might have to ad-hoc resolve a ref, since handleExists is run before hand and generates new refs.
1123
1131
  const lhsArt = lhs._art || lhs.ref && !lhs.$scope && inspectRef(location.concat(i)).art;
1124
1132
  const rhsArt = rhs._art || rhs.ref && !rhs.$scope && inspectRef(location.concat(i+2)).art;
1125
- // lhs & rhs must be expandable types (structures or managed associations)
1126
- if(lhsArt && rhsArt &&
1127
- lhs.ref && rhs.ref &&
1128
- isExpandable(lhsArt) && isExpandable(rhsArt) &&
1129
- ['=', '<', '>', '>=', '<=', '!=', '<>'].includes(op) &&
1130
- !(isDollarSelfOrProjectionOperand(lhs) || isDollarSelfOrProjectionOperand(rhs))) {
1133
+ const lhsIsVal = (lhs.val !== undefined);
1134
+ // if ever rhs should be alowed to be a value uncomment this
1135
+ const rhsIsVal = (rhs === 'null' /*|| rhs.val !== undefined*/);
1131
1136
 
1137
+ // lhs & rhs must be expandable types (structures or managed associations)
1138
+ // if ever lhs should be alowed to be a value uncomment this
1139
+ if(!(lhsIsVal /*&& rhsIsVal*/) &&
1140
+ !(isDollarSelfOrProjectionOperand(lhs) || isDollarSelfOrProjectionOperand(rhs)) &&
1141
+ RelationalOperators.includes(op) &&
1142
+ (lhsIsVal || (lhsArt && lhs.ref && isExpandable(lhsArt))) &&
1143
+ (rhsIsVal || (rhsArt && rhs.ref && isExpandable(rhsArt)))
1144
+ ) {
1145
+
1146
+ if(RestrictedOperators.includes(op)) {
1147
+ message('expr-unexpected-operator', location, { op }, 'Unexpected operator $(OP) in structural comparison');
1148
+ }
1132
1149
  // if path is scalar and no assoc or has no type (@Core.Computed) use original expression
1133
1150
  // only do the expansion on (managed) assocs and (items.)elements, array of check in ON cond is done elsewhere
1134
- const lhspaths = /*isScalarOrNoType(lhs._art) ? [ lhs ] : */ flattenPath({ _art: lhsArt, ref: lhs.ref }, false, true );
1135
- const rhspaths = /*isScalarOrNoType(rhs._art) ? [ rhs ] : */ flattenPath({ _art: rhsArt, ref: rhs.ref }, false, true );
1151
+ const lhspaths = lhsIsVal ? [] : flattenPath({ _art: lhsArt, ref: lhs.ref }, false, true );
1152
+ const rhspaths = rhsIsVal ? [] : flattenPath({ _art: rhsArt, ref: rhs.ref }, false, true );
1136
1153
 
1137
1154
  // mapping dict for lhs/rhs for mismatch check
1138
1155
  // strip lhs/rhs prefix from flattened paths to check remaining common trailing path
1139
1156
  // if path is idempotent, it doesn't produce new flattened paths (ends on scalar type)
1140
1157
  // key is then empty string on both sides '' (=> equality)
1141
1158
  // Path matches if lhs/rhs are available
1142
- const xref = lhspaths.reduce((a, v) => {
1143
- a[v.ref.slice(lhs.ref.length).join('.')] = { lhs: v };
1144
- return a;
1145
- }, Object.create(null));
1146
-
1147
- rhspaths.forEach(v => {
1148
- const k = v.ref.slice(rhs.ref.length).join('.');
1149
- if(xref[k])
1150
- xref[k].rhs = v;
1151
- else
1152
- xref[k] = { rhs: v };
1153
- });
1154
-
1159
+ const xref = createXRef(lhspaths, rhspaths, lhs, rhs, lhsIsVal, rhsIsVal);
1160
+ const xrefkeys = Object.keys(xref);
1161
+ const xrefvalues = Object.values(xref);
1155
1162
  let cont = true;
1156
- for(const xn in xref) {
1157
- const x = xref[xn];
1158
1163
 
1164
+ if(op === 'like' && xrefvalues.reduce((a, v) => {
1165
+ return (v.lhs && v.rhs) ? a + 1: a;
1166
+ }, 0) === 0) {
1167
+ // error if intersection of paths is zero
1168
+ error(null, location, `Expected compatible types for '${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}'`);
1169
+ cont = false;
1170
+ }
1171
+
1172
+ cont && xrefkeys.forEach(xn => {
1173
+ const x = xref[xn];
1159
1174
  // do the paths match?
1160
- if(!(x.lhs && x.rhs)) {
1175
+ if(op !== 'like' && !(x.lhs && x.rhs)) {
1161
1176
  if(xn.length)
1162
1177
  error(null, location, `'${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}': Sub path '${xn}' not found in ${((x.lhs ? rhs : lhs).ref.join('.'))}`)
1163
1178
  else
@@ -1167,17 +1182,17 @@ function getTransformers(model, options, pathDelimiter = '_') {
1167
1182
  // lhs && rhs are present, consistency checks that affect both ends
1168
1183
  else {
1169
1184
  // is lhs scalar?
1170
- if(!isScalarOrNoType(x.lhs._art)) {
1185
+ if(!lhsIsVal && x.lhs && !isScalarOrNoType(x.lhs._art)) {
1171
1186
  error(null, location, `'${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}': Path '${x.lhs.ref.join('.')}${(xn.length ? '.' + xn : '')}' must end on a scalar type`)
1172
1187
  cont = false;
1173
1188
  }
1174
1189
  // is rhs scalar?
1175
- if(!isScalarOrNoType(x.rhs._art)) {
1190
+ if(!rhsIsVal && x.rhs && !isScalarOrNoType(x.rhs._art)) {
1176
1191
  error(null, location, `'${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}': Path '${x.rhs.ref.join('.')}${(xn.length ? '.' + xn : '')}' must end on a scalar type`)
1177
1192
  cont = false;
1178
1193
  }
1179
1194
  // info about type incompatibility if no other errors occured
1180
- if(xn && cont) {
1195
+ if(!(lhsIsVal || rhsIsVal) && x.lhs && x.rhs && xn && cont) {
1181
1196
  const lhst = getType(x.lhs._art);
1182
1197
  const rhst = getType(x.rhs._art);
1183
1198
  if(lhst !== rhst) {
@@ -1185,20 +1200,29 @@ function getTransformers(model, options, pathDelimiter = '_') {
1185
1200
  }
1186
1201
  }
1187
1202
  }
1188
- }
1203
+ });
1189
1204
  // don't continue if there are path errors
1190
1205
  if(!cont)
1191
1206
  return expr;
1192
1207
 
1193
- Object.keys(xref).forEach((k, i) => {
1194
- const x = xref[k];
1208
+ // if lhs and rhs are refs set operator from 'like' to '='
1209
+ if(op === 'like' && !(lhsIsVal || rhsIsVal)) {
1210
+ op = '=';
1211
+ }
1212
+ // t_0 OR ... OR t_n with t = (a <not equal> b)
1213
+ const bop = (op === 'is' && not) || op === '!=' || op === '<>' ? 'or' : 'and';
1214
+ rc.push('(');
1215
+ xrefvalues.filter(x => x.lhs && x.rhs).forEach((x,i) => {
1195
1216
  if(i>0)
1196
- rc.push('and');
1217
+ rc.push(bop);
1197
1218
  rc.push(x.lhs);
1198
1219
  rc.push(op);
1220
+ if(not)
1221
+ rc.push('not')
1199
1222
  rc.push(x.rhs);
1200
1223
  });
1201
- i += 2;
1224
+ rc.push(')');
1225
+ i += not ? 3 : 2;
1202
1226
  }
1203
1227
  else
1204
1228
  rc.push(expr[i]);
@@ -1208,6 +1232,44 @@ function getTransformers(model, options, pathDelimiter = '_') {
1208
1232
  }
1209
1233
  return rc;
1210
1234
 
1235
+ function createXRef(lhspaths, rhspaths, lhs, rhs, lhsIsVal, rhsIsVal) {
1236
+ // mapping dict for lhs/rhs for mismatch check
1237
+ // strip lhs/rhs prefix from flattened paths to check remaining common trailing path
1238
+ // if path is idempotent, it doesn't produce new flattened paths (ends on scalar type)
1239
+ // key is then empty string on both sides '' (=> equality)
1240
+ // Path matches if lhs/rhs are available
1241
+ let xref;
1242
+ if(!lhsIsVal) {
1243
+ xref = lhspaths.reduce((a, v) => {
1244
+ a[v.ref.slice(lhs.ref.length).join('.')] = rhsIsVal ? { lhs: v, rhs } : { lhs: v };
1245
+ return a;
1246
+ }, Object.create(null));
1247
+
1248
+ rhspaths.forEach(v => {
1249
+ const k = v.ref.slice(rhs.ref.length).join('.');
1250
+ if(xref[k])
1251
+ xref[k].rhs = v;
1252
+ else
1253
+ xref[k] = { rhs: v };
1254
+ });
1255
+ }
1256
+ else if(!rhsIsVal) {
1257
+ xref = rhspaths.reduce((a, v) => {
1258
+ a[v.ref.slice(rhs.ref.length).join('.')] = lhsIsVal ? { lhs, rhs: v } : { rhs: v };
1259
+ return a;
1260
+ }, Object.create(null));
1261
+
1262
+ lhspaths.forEach(v => {
1263
+ const k = v.ref.slice(lhs.ref.length).join('.');
1264
+ if(xref[k])
1265
+ xref[k].lhs = v;
1266
+ else
1267
+ xref[k] = { lhs: v };
1268
+ });
1269
+ }
1270
+ return xref;
1271
+ }
1272
+
1211
1273
  function getType(art) {
1212
1274
  const effart = effectiveType(art);
1213
1275
  return Object.keys(effart).length ? effart : art.type;
@@ -1218,7 +1280,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
1218
1280
  if(art) {
1219
1281
  // items in ON conds are illegal but this should be checked elsewhere
1220
1282
  const elements = art.elements || (art.items && art.items.elements);
1221
- return (elements || art.target && art.keys)
1283
+ return !!(elements || art.target && art.keys)
1222
1284
  }
1223
1285
  return false;
1224
1286
  }
@@ -1241,7 +1303,6 @@ function getTransformers(model, options, pathDelimiter = '_') {
1241
1303
 
1242
1304
  }
1243
1305
 
1244
-
1245
1306
  /**
1246
1307
  * Modify the given CSN/artifact in-place, applying the given customTransformations.
1247
1308
  * Dictionaries are correctly handled - a "type" transformer will not be called on an entity called "type".
@@ -1333,5 +1394,6 @@ function transformModel(csn, customTransformations, transformNonEnumerableElemen
1333
1394
  module.exports = {
1334
1395
  // This function retrieves the actual exports
1335
1396
  getTransformers,
1336
- transformModel
1397
+ transformModel,
1398
+ RelationalOperators
1337
1399
  };
@@ -9,7 +9,7 @@ const { deduplicateMessages } = require('../base/messages');
9
9
  const { timetrace } = require('../utils/timetrace');
10
10
  // Paths that start with an artifact of protected kind are special
11
11
  // either ignore them in QAT building or in path rewriting
12
- const internalArtifactKinds = ['builtin'/*, '$parameters'*/, 'param'];
12
+ const internalArtifactKinds = ['builtin', '$parameters', 'param'];
13
13
 
14
14
  function translateAssocsToJoinsCSN(csn, options){
15
15
  timetrace.start('Recompiling model');
@@ -444,7 +444,7 @@ function translateAssocsToJoins(model, inputOptions = {})
444
444
  }
445
445
  else if(art.target) { // it's not an artifact, so it should be an assoc step
446
446
  if(joinTree === undefined)
447
- throw Error('Cannot follow Associations without starting Entity');
447
+ throw Error('Can\'t follow Associations without starting Entity');
448
448
 
449
449
  if(!childQat.$QA)
450
450
  childQat.$QA = createQA(env, art.target._artifact, art.name.id, childQat._namedArgs);
@@ -762,7 +762,8 @@ function translateAssocsToJoins(model, inputOptions = {})
762
762
 
763
763
  let [head, ...tail] = path;
764
764
 
765
- if(internalArtifactKinds.includes(head._artifact.kind)) // don't rewrite path
765
+ // don't rewrite path
766
+ if(internalArtifactKinds.includes(head._artifact.kind))
766
767
  return pathNode;
767
768
 
768
769
  // strip the absolute path indicators
@@ -1536,7 +1537,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1536
1537
  ( // not leaf
1537
1538
  i < tail.length-1 &&
1538
1539
  // path terminates on a scalar type
1539
- // _effectiveType.elements can be removed if forHanaNew can expand fk paths correctly
1540
+ // _effectiveType.elements can be removed if forRelationalDB can expand fk paths correctly
1540
1541
  !(tail[tail.length-1]._artifact._effectiveType.elements || tail[tail.length-1]._artifact._effectiveType.target) &&
1541
1542
  // association is managed
1542
1543
  art.foreignKeys &&
@@ -173,13 +173,13 @@ function _errorFileNotFound(dep, options, { error }) {
173
173
  resolved = resolved.replace( /\\/g, '/' );
174
174
  for (const from of dep.usingFroms) {
175
175
  error( 'file-not-readable', from.location, { file: resolved },
176
- 'Cannot read file $(FILE)' );
176
+ 'Can\'t read file $(FILE)' );
177
177
  }
178
178
  }
179
179
  else if (isLocalFile( dep.module ) ) {
180
180
  for (const from of dep.usingFroms) {
181
181
  error( 'file-unknown-local', from.location, { file: dep.module },
182
- 'Cannot find local module $(FILE)' );
182
+ 'Can\'t find local module $(FILE)' );
183
183
  }
184
184
  }
185
185
  else {
@@ -187,8 +187,8 @@ function _errorFileNotFound(dep, options, { error }) {
187
187
  for (const from of dep.usingFroms) {
188
188
  error( 'file-unknown-package', from.location,
189
189
  { file: dep.module, '#': internal }, {
190
- std: 'Cannot find package $(FILE)',
191
- internal: 'Cannot find package module $(FILE)',
190
+ std: 'Can\'t find package $(FILE)',
191
+ internal: 'Can\'t find package module $(FILE)',
192
192
  } );
193
193
  }
194
194
  }
@@ -417,7 +417,7 @@ function resolveCDS(moduleName, options, callback) {
417
417
  * @returns {Error}
418
418
  */
419
419
  function makeNotFoundError() {
420
- const moduleError = new Error(`Cannot find module '${ moduleName }' from '${ options.basedir }'`);
420
+ const moduleError = new Error(`Can't find module '${ moduleName }' from '${ options.basedir }'`);
421
421
  // eslint-disable-next-line
422
422
  moduleError['code'] = 'MODULE_NOT_FOUND';
423
423
  return moduleError;
@@ -37,7 +37,7 @@ function createDict(obj) {
37
37
  */
38
38
  function forEach(obj, callback) {
39
39
  for (const key in obj) {
40
- if (Object.hasOwnProperty.call(obj, key))
40
+ if (Object.prototype.hasOwnProperty.call(obj, key))
41
41
  callback(key, obj[key]);
42
42
  }
43
43
  }
@@ -51,7 +51,7 @@ function forEach(obj, callback) {
51
51
  */
52
52
  function forEachValue(o, callback) {
53
53
  for (const key in o) {
54
- if (Object.hasOwnProperty.call(o, key))
54
+ if (Object.prototype.hasOwnProperty.call(o, key))
55
55
  callback(o[key]);
56
56
  }
57
57
  }
@@ -65,7 +65,7 @@ function forEachValue(o, callback) {
65
65
  */
66
66
  function forEachKey(o, callback) {
67
67
  for (const key in o) {
68
- if (Object.hasOwnProperty.call(o, key))
68
+ if (Object.prototype.hasOwnProperty.call(o, key))
69
69
  callback(key);
70
70
  }
71
71
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-compiler",
3
- "version": "3.1.2",
3
+ "version": "3.3.2",
4
4
  "description": "CDS (Core Data Services) compiler and backends",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "author": "SAP SE (https://www.sap.com)",
@@ -16,7 +16,7 @@
16
16
  "scripts": {
17
17
  "download": "node scripts/downloadANTLR.js",
18
18
  "gen": "node ./scripts/build.js && node scripts/genGrammarChecksum.js",
19
- "xmakeBeforeInstall": "echo \"Due to binary mirror, use sqlite 5.0.11 explicitly\" && npm install --save --save-exact --no-package-lock sqlite3@5.0.11",
19
+ "xmakeBeforeInstall": "echo \"Due to binary mirror, use sqlite 5.1.1 explicitly\" && npm install --save --save-exact --no-package-lock sqlite3@5.1.1",
20
20
  "xmakeAfterInstall": "npm run gen",
21
21
  "xmakePrepareRelease": "echo \"$(node scripts/stripReadme.js README.md)\" > README.md && node scripts/assertSnapshotVersioning.js && node scripts/assertChangelog.js && node scripts/cleanup.js --remove-dev",
22
22
  "test": "node scripts/verifyGrammarChecksum.js && mocha --reporter min --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter min --reporter-option maxDiffSize=0 test/ test3/",