@sap/cds-compiler 2.5.2 → 2.11.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 (102) hide show
  1. package/CHANGELOG.md +235 -9
  2. package/bin/cdsc.js +44 -27
  3. package/bin/cdsse.js +1 -0
  4. package/doc/CHANGELOG_BETA.md +37 -3
  5. package/lib/api/.eslintrc.json +2 -0
  6. package/lib/api/main.js +37 -123
  7. package/lib/api/options.js +27 -15
  8. package/lib/api/validate.js +34 -9
  9. package/lib/backends.js +9 -89
  10. package/lib/base/dictionaries.js +2 -1
  11. package/lib/base/keywords.js +32 -2
  12. package/lib/base/message-registry.js +73 -11
  13. package/lib/base/messages.js +86 -30
  14. package/lib/base/model.js +6 -6
  15. package/lib/base/optionProcessorHelper.js +56 -22
  16. package/lib/checks/defaultValues.js +27 -2
  17. package/lib/checks/elements.js +1 -6
  18. package/lib/checks/foreignKeys.js +0 -6
  19. package/lib/checks/managedWithoutKeys.js +17 -0
  20. package/lib/checks/nonexpandableStructured.js +38 -0
  21. package/lib/checks/onConditions.js +9 -45
  22. package/lib/checks/queryNoDbArtifacts.js +25 -7
  23. package/lib/checks/selectItems.js +29 -2
  24. package/lib/checks/types.js +26 -2
  25. package/lib/checks/unknownMagic.js +41 -0
  26. package/lib/checks/utils.js +61 -0
  27. package/lib/checks/validator.js +60 -7
  28. package/lib/compiler/assert-consistency.js +23 -7
  29. package/lib/compiler/base.js +65 -0
  30. package/lib/compiler/builtins.js +30 -1
  31. package/lib/compiler/checks.js +8 -5
  32. package/lib/compiler/definer.js +157 -133
  33. package/lib/compiler/index.js +89 -31
  34. package/lib/compiler/propagator.js +5 -2
  35. package/lib/compiler/resolver.js +375 -185
  36. package/lib/compiler/shared.js +49 -202
  37. package/lib/compiler/utils.js +173 -0
  38. package/lib/edm/annotations/genericTranslation.js +183 -187
  39. package/lib/edm/csn2edm.js +104 -108
  40. package/lib/edm/edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +388 -146
  42. package/lib/edm/edmUtils.js +104 -34
  43. package/lib/gen/Dictionary.json +22 -0
  44. package/lib/gen/language.checksum +1 -1
  45. package/lib/gen/language.interp +28 -1
  46. package/lib/gen/language.tokens +79 -69
  47. package/lib/gen/languageLexer.interp +28 -1
  48. package/lib/gen/languageLexer.js +879 -805
  49. package/lib/gen/languageLexer.tokens +71 -62
  50. package/lib/gen/languageParser.js +5330 -4300
  51. package/lib/json/from-csn.js +110 -52
  52. package/lib/json/to-csn.js +434 -120
  53. package/lib/language/antlrParser.js +15 -3
  54. package/lib/language/errorStrategy.js +1 -0
  55. package/lib/language/genericAntlrParser.js +93 -26
  56. package/lib/language/language.g4 +172 -31
  57. package/lib/main.d.ts +216 -19
  58. package/lib/main.js +32 -7
  59. package/lib/model/api.js +78 -0
  60. package/lib/model/csnRefs.js +413 -149
  61. package/lib/model/csnUtils.js +286 -75
  62. package/lib/model/enrichCsn.js +50 -6
  63. package/lib/model/revealInternalProperties.js +22 -5
  64. package/lib/modelCompare/compare.js +39 -21
  65. package/lib/optionProcessor.js +35 -18
  66. package/lib/render/.eslintrc.json +4 -1
  67. package/lib/render/DuplicateChecker.js +9 -6
  68. package/lib/render/toCdl.js +121 -36
  69. package/lib/render/toHdbcds.js +148 -98
  70. package/lib/render/toSql.js +114 -43
  71. package/lib/render/utils/common.js +8 -13
  72. package/lib/render/utils/sql.js +3 -3
  73. package/lib/sql-identifier.js +6 -1
  74. package/lib/transform/db/assertUnique.js +5 -6
  75. package/lib/transform/db/constraints.js +281 -106
  76. package/lib/transform/db/draft.js +11 -8
  77. package/lib/transform/db/expansion.js +584 -0
  78. package/lib/transform/db/flattening.js +341 -0
  79. package/lib/transform/db/groupByOrderBy.js +2 -2
  80. package/lib/transform/db/transformExists.js +345 -65
  81. package/lib/transform/db/views.js +438 -0
  82. package/lib/transform/forHanaNew.js +131 -793
  83. package/lib/transform/forOdataNew.js +30 -24
  84. package/lib/transform/localized.js +39 -10
  85. package/lib/transform/odata/attachPath.js +19 -4
  86. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  87. package/lib/transform/odata/referenceFlattener.js +60 -39
  88. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  89. package/lib/transform/odata/structuralPath.js +72 -0
  90. package/lib/transform/odata/structureFlattener.js +19 -18
  91. package/lib/transform/odata/typesExposure.js +22 -12
  92. package/lib/transform/transformUtilsNew.js +144 -78
  93. package/lib/transform/translateAssocsToJoins.js +22 -27
  94. package/lib/transform/universalCsnEnricher.js +67 -0
  95. package/lib/utils/file.js +5 -14
  96. package/lib/utils/moduleResolve.js +6 -8
  97. package/lib/utils/term.js +65 -42
  98. package/lib/utils/timetrace.js +48 -26
  99. package/package.json +1 -1
  100. package/lib/json/walker.js +0 -26
  101. package/lib/transform/sqlite +0 -0
  102. package/lib/utils/string.js +0 -17
@@ -30,7 +30,11 @@ function createOptionProcessor() {
30
30
  optionClashes: [],
31
31
  option,
32
32
  command,
33
- positionalArgument,
33
+ positionalArgument: (argumentDefinition) => {
34
+ // Default positional arguments; may be overwritten by commands.
35
+ _positionalArguments(argumentDefinition);
36
+ return optionProcessor;
37
+ },
34
38
  help,
35
39
  processCmdLine,
36
40
  verifyOptions,
@@ -66,7 +70,12 @@ function createOptionProcessor() {
66
70
  /** @type {object} */
67
71
  const command = {
68
72
  options: {},
73
+ positionalArguments: [],
69
74
  option,
75
+ positionalArgument: (argumentDefinition) => {
76
+ _positionalArguments(argumentDefinition, command.positionalArguments);
77
+ return command;
78
+ },
70
79
  help,
71
80
  ..._parseCommandString(cmdString)
72
81
  };
@@ -96,28 +105,34 @@ function createOptionProcessor() {
96
105
  }
97
106
 
98
107
  /**
99
- * Adds positional arguments to the command line processor. Instructs the processor
100
- * to either require N positional arguments or a dynamic number (but at least one)
101
- * @param {string} positionalArgumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
108
+ * Set the positional arguments to the command line processor. Instructs the processor
109
+ * to either require N positional arguments or a dynamic number (but at least one).
110
+ * Note that you can only call this function once. Only the last invocation sets
111
+ * the positional arguments.
112
+ *
113
+ * @param {string} argumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
114
+ * @param {object[]} argList Array, to which the parsed arguments will be added. Default is global scope.
102
115
  */
103
- function positionalArgument(positionalArgumentDefinition) {
104
- if (optionProcessor.positionalArguments.find((arg) => arg.isDynamic)) {
116
+ function _positionalArguments(argumentDefinition, argList = optionProcessor.positionalArguments) {
117
+ if (argList.find((arg) => arg.isDynamic)) {
105
118
  throw new Error(`Can't add positional arguments after a dynamic one`);
106
119
  }
107
120
 
108
- const registeredNames = optionProcessor.positionalArguments.map((arg) => arg.name);
109
- const args = positionalArgumentDefinition.split(' ');
121
+ const registeredNames = argList.map((arg) => arg.name);
122
+ const args = argumentDefinition.split(' ');
110
123
 
111
124
  for (const arg of args) {
112
- const argName = arg.replace('<', '').replace('>', '').replace('...', '');
125
+ // Remove braces, dots and camelify.
126
+ const argName = arg.replace('<', '').replace('>', '').replace('...', '').replace(/[ -]./g, s => s.substring(1).toUpperCase());
127
+
113
128
  if (registeredNames.includes(argName)) {
114
- throw new Error(`Duplicate positional argument ${arg}`);
129
+ throw new Error(`Duplicate positional argument: ${arg}`);
115
130
  }
116
131
  if (!isParam(arg) && !isDynamicPositionalArgument(arg)) {
117
132
  throw new Error(`Unknown positional argument syntax: ${arg}`)
118
133
  }
119
134
 
120
- optionProcessor.positionalArguments.push({
135
+ argList.push({
121
136
  name: argName,
122
137
  isDynamic: isDynamicPositionalArgument(arg),
123
138
  required: true
@@ -125,7 +140,6 @@ function createOptionProcessor() {
125
140
 
126
141
  registeredNames.push(argName);
127
142
  }
128
- return optionProcessor;
129
143
  }
130
144
 
131
145
  /**
@@ -390,21 +404,38 @@ function createOptionProcessor() {
390
404
  }
391
405
 
392
406
  // Complain about first missing positional arguments
393
- const missingArg = optionProcessor.positionalArguments.find((arg) => arg.required && !result.args[arg.name]);
407
+ const missingArg = getCurrentPositionArguments().find((arg) => arg.required && !result.args[arg.name]);
394
408
  if (missingArg) {
395
- result.errors.push(`Missing positional argument: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
409
+ const forCommand = result.command ? ` for '${ result.command }'` : '';
410
+ result.errors.push(`Missing positional argument${forCommand}: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
396
411
  }
397
412
 
398
413
  return result;
399
414
 
415
+ /**
416
+ * Specific commands may have custom positional arguments.
417
+ * If the current one does, use it instead of the defaults.
418
+ *
419
+ * @returns {object[]} Array of positional argument configurations.
420
+ */
421
+ function getCurrentPositionArguments() {
422
+ const cmd = optionProcessor.commands[result.command];
423
+ const args = ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
424
+ return args;
425
+ }
426
+
400
427
  function processPositionalArgument(argumentValue) {
401
- if ( result.args.length === 0 && optionProcessor.positionalArguments.length === 0 )
428
+ const argList = getCurrentPositionArguments();
429
+ if ( result.args.length === 0 && argList.length === 0 )
402
430
  return;
403
- const inBounds = result.args.length < optionProcessor.positionalArguments.length;
404
- const lastIndex = inBounds ? result.args.length : optionProcessor.positionalArguments.length - 1;
405
- const nextUnsetArgument = optionProcessor.positionalArguments[lastIndex];
431
+ const inBounds = result.args.length < argList.length;
432
+ const lastIndex = inBounds ? result.args.length : argList.length - 1;
433
+ const nextUnsetArgument = argList[lastIndex];
406
434
  if (!inBounds && !nextUnsetArgument.isDynamic) {
407
- result.errors.push(`Too many arguments. Expected ${optionProcessor.positionalArguments.length}`);
435
+ if (result.command)
436
+ result.errors.push(`Too many arguments. '${result.command}' expects ${argList.length}`);
437
+ else
438
+ result.errors.push(`Too many arguments. Expected ${argList.length}`);
408
439
  return;
409
440
  }
410
441
  result.args.length += 1;
@@ -486,7 +517,10 @@ function createOptionProcessor() {
486
517
  }
487
518
 
488
519
  if(options) {
489
- ['defaultStringLength', /*'length', 'precision', 'scale'*/].forEach(facet => {
520
+ [
521
+ 'defaultBinaryLength', 'defaultStringLength',
522
+ /*'length', 'precision', 'scale'*/
523
+ ].forEach(facet => {
490
524
  if(options[facet] && isNaN(options[facet])) {
491
525
  result.push(`Invalid value "${options[facet]}" for option "--${facet}" - not an Integer`);
492
526
  } else {
@@ -577,12 +611,12 @@ function isLongOption(opt) {
577
611
 
578
612
  // Check if 'opt' looks like a "<foobar>" parameter
579
613
  function isParam(opt) {
580
- return /^<[a-zA-Z]+>$/.test(opt);
614
+ return /^<[a-zA-Z-]+>$/.test(opt);
581
615
  }
582
616
 
583
617
  // Check if 'arg' looks like "<foobar...>"
584
618
  function isDynamicPositionalArgument(arg) {
585
- return /^<[a-zA-Z]+[.]{3}>$/.test(arg);
619
+ return /^<[a-zA-Z-]+[.]{3}>$/.test(arg);
586
620
  }
587
621
 
588
622
  module.exports = {
@@ -35,8 +35,33 @@ function validateDefaultValues(member, memberName, prop, path) {
35
35
  * @param {CSN.Path} path Path to the member
36
36
  */
37
37
  function rejectParamDefaultsInHanaCds(member, memberName, prop, path) {
38
- if (member.default && prop === 'params' && this.options.toHana)
38
+ if (member.default && prop === 'params' && this.options.transformation === 'hdbcds')
39
39
  this.error(null, path, 'Parameter default values are not supported in SAP HANA CDS');
40
40
  }
41
41
 
42
- module.exports = { validateDefaultValues, rejectParamDefaultsInHanaCds };
42
+ /**
43
+ * For HANA CDS, we render a default for a mixin if the projected entity contains
44
+ * a derived association with a default defined on it. This leads to a deployment error
45
+ * and should be warned about.
46
+ *
47
+ * @param {CSN.Element} member Member to validate
48
+ * @param {string} memberName Name of the member
49
+ * @param {string} prop Property being looped over
50
+ * @param {CSN.Path} path Path to the member
51
+ */
52
+ function warnAboutDefaultOnAssociationForHanaCds(member, memberName, prop, path) {
53
+ const art = this.csn.definitions[path[1]];
54
+ if (!art.query && this.options.transformation === 'hdbcds' && member.target && member.default) {
55
+ this.warning(null, path, { '#': member._type.type === 'cds.Association' ? 'std' : 'comp' },
56
+ {
57
+ std: 'Unexpected default defined on association',
58
+ comp: 'Unexpected default defined on composition',
59
+ });
60
+ }
61
+ }
62
+
63
+ module.exports = {
64
+ validateDefaultValues,
65
+ rejectParamDefaultsInHanaCds,
66
+ warnAboutDefaultOnAssociationForHanaCds,
67
+ };
@@ -2,7 +2,6 @@
2
2
 
3
3
  const { forEachMember, forEachMemberRecursively } = require('../model/csnUtils');
4
4
  const { isGeoTypeName } = require('../compiler/builtins');
5
- const { isBetaEnabled } = require('../base/model');
6
5
 
7
6
  // Only to be used with validator.js - a correct `this` value needs to be provided!
8
7
 
@@ -103,15 +102,11 @@ function checkVirtualElement(member) {
103
102
  * @param {CSN.Artifact} art The artifact
104
103
  */
105
104
  function checkManagedAssoc(art) {
106
- forEachMemberRecursively(art, (member, memberName) => {
105
+ forEachMemberRecursively(art, (member) => {
107
106
  if (this.csnUtils.isAssocOrComposition(member.type) &&
108
107
  !isManagedComposition.bind(this)(member)) {
109
108
  if (member.on)
110
109
  return;
111
- if (!isBetaEnabled(this.options, 'keylessManagedAssoc') && (!member.keys || member.keys.length === 0)) {
112
- this.error(null, member.$path, { name: memberName },
113
- `The managed association $(NAME) has no foreign keys`);
114
- }
115
110
  const max = member.cardinality && member.cardinality.max;
116
111
  if (max === '*' || Number(max) > 1) {
117
112
  const isNoDb = art['@cds.persistence.skip'] || art['@cds.persistence.exists'];
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const { isBetaEnabled } = require('../base/model');
4
-
5
3
  // Only to be used with validator.js - a correct this value needs to be provided!
6
4
 
7
5
  /**
@@ -20,17 +18,13 @@ function validateForeignKeys(member) {
20
18
 
21
19
  // Declared as arrow-function to keep scope the same (this value)
22
20
  const handleAssociation = (mem) => {
23
- let elementCount = 0;
24
21
  for (let i = 0; i < mem.keys.length; i++) {
25
22
  if (mem.keys[i].ref) {
26
23
  if (!mem.keys[i]._art)
27
24
  continue;
28
25
  // eslint-disable-next-line no-use-before-define
29
26
  checkForItems(mem.keys[i]._art);
30
- elementCount++;
31
27
  }
32
- if (!isBetaEnabled(this.options, 'keylessManagedAssoc') && elementCount === 0)
33
- this.error(null, member.$path, 'Empty structured types/elements must not be used as foreign keys');
34
28
  }
35
29
  };
36
30
 
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+
4
+ /**
5
+ * Trigger a recompilation in case of an association without .keys and without .on
6
+ *
7
+ * @param {CSN.Element} member the element to be checked
8
+ * @param {string} memberName the elements name
9
+ * @param {string} prop which kind of member are we looking at -> only prop "elements"
10
+ */
11
+ function managedWithoutKeys(member, memberName, prop) {
12
+ if (prop === 'elements' && member.target && !member.keys && !member.on) { // trigger recompilation
13
+ throw new Error('Expected association to have either an on-condition or foreign keys.');
14
+ }
15
+ }
16
+
17
+ module.exports = managedWithoutKeys;
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const { otherSideIsExpandableStructure, resolveArtifactType } = require('./utils');
4
+
5
+ /**
6
+ * Check the given expression for non-expandable structure usage
7
+ *
8
+ * @param {object} parent Object with the expression as a property
9
+ * @param {string} name Name of the expression property on parent
10
+ * @param {Array} expression Expression to check - .on .xpr .having and .where
11
+ */
12
+ function nonexpandableStructuredInExpression(parent, name, expression) {
13
+ for (let i = 0; i < expression.length; i++) {
14
+ if (expression[i].ref) {
15
+ const { ref } = expression[i];
16
+ // eslint-disable-next-line prefer-const
17
+ let { _art, $scope } = expression[i];
18
+ if (!_art)
19
+ continue;
20
+ const validStructuredElement = otherSideIsExpandableStructure.call(this, expression, i);
21
+ if (_art) {
22
+ _art = resolveArtifactType.call(this, _art);
23
+ // Paths of an expression may end on a structured element only if both operands in the expression end on a structured element
24
+ if (_art.elements && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
25
+ this.error(null, expression[i].$path, { elemref: { ref } },
26
+ 'Unexpected usage of structured type $(ELEMREF)');
27
+ }
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ module.exports = {
34
+ on: nonexpandableStructuredInExpression,
35
+ having: nonexpandableStructuredInExpression,
36
+ where: nonexpandableStructuredInExpression,
37
+ xpr: nonexpandableStructuredInExpression,
38
+ };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { forEachGeneric, isBuiltinType } = require('../model/csnUtils');
3
+ const { forEachGeneric } = require('../model/csnUtils');
4
+ const { otherSideIsExpandableStructure, resolveArtifactType } = require('./utils');
4
5
 
5
6
  // Only to be used with validator.js - a correct this value needs to be provided!
6
7
 
@@ -42,48 +43,6 @@ function otherSideIsValidDollarSelf(on, startIndex) {
42
43
  return false;
43
44
  }
44
45
 
45
- /**
46
- * Check that the opposite operand to a relational term is something
47
- * structured that can be used for tuple expansion. This can either be a
48
- * real 'elements' thing or a managed association/composition with foreign keys.
49
- *
50
- * @param {Array} on the on condition which to check
51
- * @param {number} startIndex the index of the relational term in the on condition array
52
- * @returns {boolean} indicates whether the other side of a relational term is expandable
53
- */
54
- function otherSideIsExpandableStructure(on, startIndex) {
55
- if (on[startIndex - 1] && [ '=', '<', '>', '>=', '<=', '!=', '<>' ].includes(on[startIndex - 1]))
56
- return isOk(resolveArtifactType.call(this, on[startIndex - 2]._art));
57
-
58
- else if (on[startIndex + 1] && [ '=', '<', '>', '>=', '<=', '!=', '<>' ].includes(on[startIndex + 1]))
59
- return isOk(resolveArtifactType.call(this, on[startIndex + 2]._art));
60
-
61
- return false;
62
-
63
- /**
64
- * Artifact is structured or a managed association/compoisition
65
- *
66
- * @param {CSN.Artifact} art Artifact
67
- * @returns {boolean} True if expandable
68
- */
69
- function isOk(art) {
70
- return !!(art && (art.elements || (art.target && art.keys)));
71
- }
72
- }
73
-
74
- /**
75
- * Get the real type of an artifact
76
- *
77
- * @param {object} art Whatever _art by csnRefs can be - element or artifact
78
- * @returns {object} final artifact type
79
- */
80
- function resolveArtifactType(art) {
81
- if (art && art.type && !isBuiltinType(art.type))
82
- return this.getFinalBaseType(art);
83
-
84
- return art;
85
- }
86
-
87
46
  /**
88
47
  * Validate an on-condition
89
48
  *
@@ -100,6 +59,11 @@ function resolveArtifactType(art) {
100
59
  */
101
60
  function validateOnCondition(member, memberName, property, path) {
102
61
  if (member && member.on) {
62
+ // complain about nullability constraint on managed composition
63
+ if (member.targetAspect && {}.hasOwnProperty.call(member, 'notNull')) {
64
+ this.warning(null, path.concat([ 'on' ]),
65
+ 'Unexpected nullability constraint defined on managed composition');
66
+ }
103
67
  for (let i = 0; i < member.on.length; i++) {
104
68
  if (member.on[i].ref) {
105
69
  const { ref } = member.on[i];
@@ -148,8 +112,8 @@ function validateOnCondition(member, memberName, property, path) {
148
112
  // 2) Path ends on an association (managed or unmanaged) and the other operand is a '$self'
149
113
 
150
114
  // If this path ends structured or on an association, perform the check:
151
- if ((_art.elements || _art.target) &&
152
- !( /* 1) */ (_art.elements || _art.target && _art.keys) && validStructuredElement ||
115
+ if ((_art.target) &&
116
+ !( /* 1) */ (_art.target && _art.keys) && validStructuredElement ||
153
117
  /* 2) */ (_art.target && validDollarSelf)) &&
154
118
  !_art.virtual) {
155
119
  this.error(null, onPath, { elemref: { ref } },
@@ -69,13 +69,31 @@ function checkQueryForNoDBArtifacts(query) {
69
69
  const pathStep = obj.ref[i].id ? obj.ref[i].id : obj.ref[i];
70
70
  const name = art.target ? art.target : pathStep;
71
71
  if (!isPersistedOnDatabase(endArtifact)) {
72
- const cdsPersistenceSkipped = hasAnnotationValue(endArtifact, '@cds.persistence.skip');
73
- this.error( null, obj.$path, {
74
- id: pathStep, elemref: obj, name, '#': cdsPersistenceSkipped ? 'std' : 'abstract',
75
- }, {
76
- std: 'Unexpected “@cds.persistence.skip” annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
77
- abstract: 'Unexpected “abstract” association target $(NAME) of $(ID) in path $(ELEMREF)',
78
- } );
72
+ const nextElement = obj.ref[i + 1];
73
+ /**
74
+ * if we only navigate to foreign keys of the managed association in a view, we do not need to join,
75
+ * thus we can produce the view even if the target of the association is not persisted
76
+ *
77
+ * @param {CSN.Element} assoc association in ref
78
+ * @param {string} nextStep the ref step following the association
79
+ * @returns {boolean} true if no join will be generated
80
+ */
81
+ const isJoinRelevant = (assoc, nextStep) => {
82
+ if (!assoc.keys)
83
+ return true;
84
+ const isExposedColumnAssocOrComposition = this.csnUtils.isAssocOrComposition(obj._art.type);
85
+ return !assoc.keys
86
+ .some(fk => fk.ref[0] === nextStep && !isExposedColumnAssocOrComposition);
87
+ };
88
+ if (isJoinRelevant(art, nextElement)) {
89
+ const cdsPersistenceSkipped = hasAnnotationValue(endArtifact, '@cds.persistence.skip');
90
+ this.error( null, obj.$path, {
91
+ id: pathStep, elemref: obj, name, '#': cdsPersistenceSkipped ? 'std' : 'abstract',
92
+ }, {
93
+ std: 'Unexpected “@cds.persistence.skip” annotation on association target $(NAME) of $(ID) in path $(ELEMREF)',
94
+ abstract: 'Unexpected “abstract” association target $(NAME) of $(ID) in path $(ELEMREF)',
95
+ } );
96
+ }
79
97
  }
80
98
  // check managed association to have foreign keys array filled
81
99
  if (art.keys && leafCount(art) === 0) {
@@ -11,7 +11,7 @@ const { forEachGeneric } = require('../model/csnUtils');
11
11
  */
12
12
  function validateSelectItems(query) {
13
13
  const { SELECT } = query;
14
- if (!SELECT || !SELECT.columns)
14
+ if (!SELECT)
15
15
  return;
16
16
 
17
17
  forEachGeneric(SELECT, 'columns', (selectItem) => {
@@ -23,6 +23,33 @@ function validateSelectItems(query) {
23
23
  'Select items starting with $(NAME) must not contain path steps of type $(TYPE)');
24
24
  }
25
25
  }
26
+ else if (this.options.transformation === 'hdbcds' && selectItem.xpr && selectItem.func) {
27
+ this.error(null, selectItem.$path,
28
+ 'Window functions are not supported by SAP HANA CDS');
29
+ }
26
30
  });
31
+ // .call() with 'this' to ensure we have access to the options
32
+ rejectManagedAssociationsAndStructuresForHdbcsNames.call(this, SELECT, SELECT.$path);
33
+ }
34
+
35
+
36
+ /**
37
+ * For the to.hdbcds transformation with naming mode 'hdbcds', structures and managed associations are not flattened/resolved.
38
+ * It is therefore not possible to publish such elements in a view.
39
+ * This function iterates over all published elements of a query artifact and asserts that no such elements are published.
40
+ *
41
+ * @param {CSN.Artifact} queryArtifact the query artifact which should be checked
42
+ * @param {CSN.Path} artifactPath the path to that artifact
43
+ */
44
+ function rejectManagedAssociationsAndStructuresForHdbcsNames(queryArtifact, artifactPath) {
45
+ if (this.options.transformation === 'hdbcds' && this.options.sqlMapping === 'hdbcds') {
46
+ forEachGeneric(queryArtifact, 'elements', (selectItem, elemName, prop, elementPath) => {
47
+ if (this.csnUtils.isManagedAssociationElement(selectItem))
48
+ this.error('query-unexpected-assoc-hdbcds', elementPath);
49
+ if (this.csnUtils.isStructured(selectItem))
50
+ this.error('query-unexpected-structure-hdbcds', elementPath);
51
+ }, artifactPath);
52
+ }
27
53
  }
28
- module.exports = validateSelectItems;
54
+
55
+ module.exports = { validateSelectItems, rejectManagedAssociationsAndStructuresForHdbcsNames };
@@ -1,9 +1,28 @@
1
1
  'use strict';
2
2
 
3
- const { getUtils, isBuiltinType } = require('../model/csnUtils');
3
+ const { getUtils, isBuiltinType, hasAnnotationValue } = require('../model/csnUtils');
4
4
 
5
5
  // Only to be used with validator.js - a correct this value needs to be provided!
6
6
 
7
+ /**
8
+ * Scale must not be 'variable' or 'floating'
9
+ *
10
+ * scale property is always propagated
11
+ *
12
+ * @param {CSN.Element} member the element to be checked
13
+ * @param {string} memberName the elements name
14
+ * @param {string} prop which kind of member are we looking at -> only prop "elements"
15
+ * @param {CSN.Path} path the path to the member
16
+ */
17
+ function checkDecimalScale(member, memberName, prop, path) {
18
+ if (hasAnnotationValue(this.artifact, '@cds.persistence.exists') ||
19
+ // skip is already filtered in validator, here for completeness
20
+ hasAnnotationValue(this.artifact, '@cds.persistence.skip'))
21
+ return;
22
+ if (member.scale && [ 'variable', 'floating' ].includes(member.scale))
23
+ this.error(null, path, { name: member.scale }, 'Unexpected scale $(NAME)');
24
+ }
25
+
7
26
  /**
8
27
  * View parameter for hana must be of scalar type
9
28
  *
@@ -165,4 +184,9 @@ function hasArtifactTypeInformation(artifact) {
165
184
  artifact.type; // => `type A : [type of] Integer`
166
185
  }
167
186
 
168
- module.exports = { checkTypeDefinitionHasType, checkElementTypeDefinitionHasType, checkTypeIsScalar };
187
+ module.exports = {
188
+ checkTypeDefinitionHasType,
189
+ checkElementTypeDefinitionHasType,
190
+ checkTypeIsScalar,
191
+ checkDecimalScale,
192
+ };
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const { getVariableReplacement } = require('../model/csnUtils');
4
+
5
+ // We only care about the "wild" ones - $at is validated by the compiler
6
+ const magicVariables = {
7
+ $user: [
8
+ 'id', // $user.id
9
+ 'locale', // $user.locale
10
+ ],
11
+ $session: [
12
+ // no valid ways for this
13
+ ],
14
+ };
15
+
16
+ /**
17
+ * Check that the given ref does not use magic variables for which we don't have
18
+ * a valid way of rendering.
19
+ *
20
+ * Valid ways:
21
+ * - We know what to do -> $user.id on HANA
22
+ * - The user tells us what to do -> options.variableReplacements
23
+ *
24
+ * @param {object} parent Object with the ref as a property
25
+ * @param {string} name Name of the ref property on parent
26
+ * @param {Array} ref to check
27
+ */
28
+ function unknownMagicVariable(parent, name, ref) {
29
+ if (parent.$scope && parent.$scope === '$magic') {
30
+ const [ head, ...rest ] = ref;
31
+ const tail = rest.join('.');
32
+ const magicVariable = magicVariables[head];
33
+ if (magicVariable && magicVariable.indexOf(tail) === -1 &&
34
+ getVariableReplacement(ref, this.options) === null)
35
+ this.error(null, parent.$location, { id: tail, elemref: parent }, 'No configuration for magic variable was provided - path $(ELEMREF), step $(ID)');
36
+ }
37
+ }
38
+
39
+ module.exports = {
40
+ ref: unknownMagicVariable,
41
+ };
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ const { isBuiltinType } = require('../model/csnUtils');
4
+
5
+ /**
6
+ * Prepare the ref steps so that they are loggable
7
+ *
8
+ * @param {any} refStep part of a ref
9
+ * @returns {string} Loggable string
10
+ */
11
+ function logReady(refStep) {
12
+ return refStep.id || refStep;
13
+ }
14
+
15
+ /**
16
+ * Check that the opposite operand to a relational term is something
17
+ * structured that can be used for tuple expansion. This can either be a
18
+ * real 'elements' thing or a managed association/composition with foreign keys.
19
+ *
20
+ * @param {Array} on the on condition which to check
21
+ * @param {number} startIndex the index of the relational term in the on condition array
22
+ * @returns {boolean} indicates whether the other side of a relational term is expandable
23
+ */
24
+ function otherSideIsExpandableStructure(on, startIndex) {
25
+ if (on[startIndex - 1] && [ '=', '<', '>', '>=', '<=', '!=', '<>' ].includes(on[startIndex - 1]))
26
+ return isOk(resolveArtifactType.call(this, on[startIndex - 2]._art));
27
+
28
+ else if (on[startIndex + 1] && [ '=', '<', '>', '>=', '<=', '!=', '<>' ].includes(on[startIndex + 1]))
29
+ return isOk(resolveArtifactType.call(this, on[startIndex + 2]._art));
30
+
31
+ return false;
32
+
33
+ /**
34
+ * Artifact is structured or a managed association/compoisition
35
+ *
36
+ * @param {CSN.Artifact} art Artifact
37
+ * @returns {boolean} True if expandable
38
+ */
39
+ function isOk(art) {
40
+ return !!(art && (art.elements || (art.target && art.keys)));
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Get the real type of an artifact
46
+ *
47
+ * @param {object} art Whatever _art by csnRefs can be - element or artifact
48
+ * @returns {object} final artifact type
49
+ */
50
+ function resolveArtifactType(art) {
51
+ if (art && art.type && !isBuiltinType(art.type))
52
+ return this.getFinalBaseType(art);
53
+
54
+ return art;
55
+ }
56
+
57
+ module.exports = {
58
+ logReady,
59
+ otherSideIsExpandableStructure,
60
+ resolveArtifactType,
61
+ };