@sap/cds-compiler 2.5.0 → 2.10.4

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 (92) hide show
  1. package/CHANGELOG.md +191 -9
  2. package/bin/cdsc.js +2 -2
  3. package/doc/CHANGELOG_BETA.md +33 -3
  4. package/lib/api/main.js +29 -101
  5. package/lib/api/options.js +15 -11
  6. package/lib/api/validate.js +12 -8
  7. package/lib/backends.js +0 -81
  8. package/lib/base/keywords.js +32 -2
  9. package/lib/base/message-registry.js +63 -9
  10. package/lib/base/messages.js +63 -21
  11. package/lib/base/model.js +2 -3
  12. package/lib/checks/defaultValues.js +27 -2
  13. package/lib/checks/elements.js +1 -6
  14. package/lib/checks/foreignKeys.js +0 -6
  15. package/lib/checks/managedWithoutKeys.js +17 -0
  16. package/lib/checks/nonexpandableStructured.js +38 -0
  17. package/lib/checks/onConditions.js +9 -45
  18. package/lib/checks/queryNoDbArtifacts.js +25 -7
  19. package/lib/checks/selectItems.js +25 -2
  20. package/lib/checks/types.js +26 -2
  21. package/lib/checks/unknownMagic.js +38 -0
  22. package/lib/checks/utils.js +61 -0
  23. package/lib/checks/validator.js +60 -7
  24. package/lib/compiler/assert-consistency.js +16 -7
  25. package/lib/compiler/builtins.js +2 -0
  26. package/lib/compiler/checks.js +6 -4
  27. package/lib/compiler/definer.js +99 -42
  28. package/lib/compiler/index.js +73 -27
  29. package/lib/compiler/resolver.js +288 -157
  30. package/lib/compiler/shared.js +31 -11
  31. package/lib/edm/annotations/genericTranslation.js +182 -186
  32. package/lib/edm/csn2edm.js +103 -108
  33. package/lib/edm/edm.js +18 -21
  34. package/lib/edm/edmPreprocessor.js +361 -114
  35. package/lib/edm/edmUtils.js +103 -33
  36. package/lib/gen/Dictionary.json +22 -0
  37. package/lib/gen/language.checksum +1 -1
  38. package/lib/gen/language.interp +12 -1
  39. package/lib/gen/language.tokens +57 -53
  40. package/lib/gen/languageLexer.interp +10 -1
  41. package/lib/gen/languageLexer.js +770 -744
  42. package/lib/gen/languageLexer.tokens +49 -46
  43. package/lib/gen/languageParser.js +4713 -4279
  44. package/lib/json/from-csn.js +103 -45
  45. package/lib/json/to-csn.js +296 -117
  46. package/lib/language/antlrParser.js +4 -3
  47. package/lib/language/errorStrategy.js +1 -0
  48. package/lib/language/genericAntlrParser.js +21 -12
  49. package/lib/language/language.g4 +99 -31
  50. package/lib/main.d.ts +81 -3
  51. package/lib/main.js +30 -7
  52. package/lib/model/api.js +78 -0
  53. package/lib/model/csnRefs.js +329 -142
  54. package/lib/model/csnUtils.js +235 -58
  55. package/lib/model/enrichCsn.js +18 -1
  56. package/lib/model/revealInternalProperties.js +2 -1
  57. package/lib/modelCompare/compare.js +37 -20
  58. package/lib/optionProcessor.js +9 -3
  59. package/lib/render/.eslintrc.json +4 -1
  60. package/lib/render/DuplicateChecker.js +8 -5
  61. package/lib/render/toCdl.js +112 -33
  62. package/lib/render/toHdbcds.js +134 -64
  63. package/lib/render/toSql.js +91 -38
  64. package/lib/render/utils/common.js +8 -13
  65. package/lib/render/utils/sql.js +3 -3
  66. package/lib/sql-identifier.js +6 -1
  67. package/lib/transform/db/assertUnique.js +5 -6
  68. package/lib/transform/db/constraints.js +29 -13
  69. package/lib/transform/db/draft.js +8 -6
  70. package/lib/transform/db/expansion.js +582 -0
  71. package/lib/transform/db/flattening.js +325 -0
  72. package/lib/transform/db/groupByOrderBy.js +2 -2
  73. package/lib/transform/db/transformExists.js +284 -63
  74. package/lib/transform/forHanaNew.js +98 -381
  75. package/lib/transform/forOdataNew.js +21 -22
  76. package/lib/transform/localized.js +37 -10
  77. package/lib/transform/odata/attachPath.js +19 -4
  78. package/lib/transform/odata/generateForeignKeyElements.js +11 -10
  79. package/lib/transform/odata/referenceFlattener.js +60 -39
  80. package/lib/transform/odata/sortByAssociationDependency.js +2 -2
  81. package/lib/transform/odata/structuralPath.js +72 -0
  82. package/lib/transform/odata/structureFlattener.js +19 -18
  83. package/lib/transform/odata/typesExposure.js +22 -12
  84. package/lib/transform/transformUtilsNew.js +134 -78
  85. package/lib/transform/translateAssocsToJoins.js +17 -14
  86. package/lib/transform/universalCsnEnricher.js +67 -0
  87. package/lib/utils/file.js +0 -11
  88. package/lib/utils/moduleResolve.js +6 -8
  89. package/package.json +1 -1
  90. package/lib/json/walker.js +0 -26
  91. package/lib/transform/sqlite +0 -0
  92. package/lib/utils/string.js +0 -17
@@ -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) => {
@@ -24,5 +24,28 @@ function validateSelectItems(query) {
24
24
  }
25
25
  }
26
26
  });
27
+ // .call() with 'this' to ensure we have access to the options
28
+ rejectManagedAssociationsAndStructuresForHdbcsNames.call(this, SELECT, SELECT.$path);
27
29
  }
28
- module.exports = validateSelectItems;
30
+
31
+
32
+ /**
33
+ * For the to.hdbcds transformation with naming mode 'hdbcds', structures and managed associations are not flattened/resolved.
34
+ * It is therefore not possible to publish such elements in a view.
35
+ * This function iterates over all published elements of a query artifact and asserts that no such elements are published.
36
+ *
37
+ * @param {CSN.Artifact} queryArtifact the query artifact which should be checked
38
+ * @param {CSN.Path} artifactPath the path to that artifact
39
+ */
40
+ function rejectManagedAssociationsAndStructuresForHdbcsNames(queryArtifact, artifactPath) {
41
+ if (this.options.transformation === 'hdbcds' && this.options.sqlMapping === 'hdbcds') {
42
+ forEachGeneric(queryArtifact, 'elements', (selectItem, elemName, prop, elementPath) => {
43
+ if (this.csnUtils.isManagedAssociationElement(selectItem))
44
+ this.error('query-unexpected-assoc-hdbcds', elementPath);
45
+ if (this.csnUtils.isStructured(selectItem))
46
+ this.error('query-unexpected-structure-hdbcds', elementPath);
47
+ }, artifactPath);
48
+ }
49
+ }
50
+
51
+ 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,38 @@
1
+ 'use strict';
2
+
3
+ // We only care about the "wild" ones - $at is validated by the compiler
4
+ const magicVariables = {
5
+ $user: [
6
+ 'id', // $user.id
7
+ 'locale', // $user.locale
8
+ ],
9
+ $session: [
10
+ // no valid ways for this
11
+ ],
12
+ };
13
+
14
+ /**
15
+ * Check that the given ref does not use magic variables for which we don't have
16
+ * a valid way of rendering.
17
+ *
18
+ * Valid ways:
19
+ * - We know what to do -> $user.id on HANA
20
+ * - The user tells us what to do -> options.magicVars
21
+ *
22
+ * @param {object} parent Object with the ref as a property
23
+ * @param {string} name Name of the ref property on parent
24
+ * @param {Array} ref to check
25
+ */
26
+ function unknownMagicVariable(parent, name, ref) {
27
+ if (parent.$scope && parent.$scope === '$magic') {
28
+ const [ head, ...rest ] = ref;
29
+ const tail = rest.join('.');
30
+ const magicVariable = magicVariables[head];
31
+ if (magicVariable && magicVariable.indexOf(tail) === -1)
32
+ this.error(null, parent.$location, { id: tail, elemref: parent }, 'Magic variable is not supported - path $(ELEMREF), step $(ID)');
33
+ }
34
+ }
35
+
36
+ module.exports = {
37
+ ref: unknownMagicVariable,
38
+ };
@@ -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
+ };
@@ -2,13 +2,13 @@
2
2
 
3
3
  const {
4
4
  forEachDefinition, forEachMemberRecursively, forAllQueries,
5
- forEachMember, getNormalizedQuery, hasAnnotationValue,
5
+ forEachMember, getNormalizedQuery, hasAnnotationValue, applyTransformations,
6
6
  } = require('../model/csnUtils');
7
7
  const enrich = require('./enricher');
8
8
 
9
9
  // forHana
10
- const validateSelectItems = require('./selectItems');
11
- const { rejectParamDefaultsInHanaCds } = require('./defaultValues');
10
+ const { validateSelectItems } = require('./selectItems');
11
+ const { rejectParamDefaultsInHanaCds, warnAboutDefaultOnAssociationForHanaCds } = require('./defaultValues');
12
12
  const validateCdsPersistenceAnnotation = require('./cdsPersistence');
13
13
  const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
14
14
  const checkForEmptyOrOnlyVirtual = require('./emptyOrOnlyVirtual');
@@ -23,19 +23,28 @@ const {
23
23
  // both
24
24
  const { validateOnCondition, validateMixinOnCondition } = require('./onConditions');
25
25
  const validateForeignKeys = require('./foreignKeys');
26
- const { checkTypeDefinitionHasType, checkElementTypeDefinitionHasType, checkTypeIsScalar } = require('./types');
26
+ const {
27
+ checkTypeDefinitionHasType, checkElementTypeDefinitionHasType,
28
+ checkTypeIsScalar, checkDecimalScale,
29
+ } = require('./types');
27
30
  const { checkPrimaryKey, checkVirtualElement, checkManagedAssoc } = require('./elements');
28
31
  const checkForInvalidTarget = require('./invalidTarget');
29
32
  const { validateAssociationsInItems } = require('./arrayOfs');
30
33
  const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
31
34
  const checkExplicitlyNullableKeys = require('./nullableKeys');
35
+ const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
36
+ const unknownMagic = require('./unknownMagic');
37
+ const managedWithoutKeys = require('./managedWithoutKeys');
32
38
 
33
39
  const forHanaMemberValidators
34
40
  = [
35
41
  // For HANA CDS specifically, reject any default parameter values, as these are not supported.
36
42
  rejectParamDefaultsInHanaCds,
37
43
  checkTypeIsScalar,
44
+ checkDecimalScale,
38
45
  checkExplicitlyNullableKeys,
46
+ managedWithoutKeys,
47
+ warnAboutDefaultOnAssociationForHanaCds,
39
48
  ];
40
49
 
41
50
  const forHanaArtifactValidators
@@ -46,8 +55,11 @@ const forHanaArtifactValidators
46
55
  checkForEmptyOrOnlyVirtual,
47
56
  ];
48
57
 
49
- const forHanaQueryValidators
50
- = [
58
+ const forHanaCsnValidators = [ nonexpandableStructuredInExpression, unknownMagic ];
59
+ /**
60
+ * @type {Array<(query: CSN.Query, path: CSN.Path) => void>}
61
+ */
62
+ const forHanaQueryValidators = [
51
63
  // TODO reason why this is forHana exclusive
52
64
  validateSelectItems,
53
65
  checkQueryForNoDBArtifacts,
@@ -57,6 +69,7 @@ const forOdataMemberValidators
57
69
  = [
58
70
  // OData allows only simple values, no expressions or functions
59
71
  validateDefaultValues,
72
+ managedWithoutKeys,
60
73
  ];
61
74
 
62
75
  const forOdataArtifactValidators
@@ -75,6 +88,8 @@ const forOdataArtifactValidators
75
88
  checkReadOnlyAndInsertOnly,
76
89
  ];
77
90
 
91
+ const forOdataCsnValidators = [ nonexpandableStructuredInExpression ];
92
+
78
93
  const forOdataQueryValidators = [];
79
94
 
80
95
  const commonMemberValidators
@@ -91,6 +106,7 @@ const commonQueryValidators = [ validateMixinOnCondition ];
91
106
  *
92
107
  * @param {CSN.Model} csn CSN to check
93
108
  * @param {object} that Will be provided to the validators via "this"
109
+ * @param {object[]} [csnValidators=[]] Validations on whole CSN using applyTransformations
94
110
  * @param {Function[]} [memberValidators=[]] Validations on member-level
95
111
  * @param {Function[]} [artifactValidators=[]] Validations on artifact-level
96
112
  * @param {Function[]} [queryValidators=[]] Validations on query-level
@@ -98,12 +114,15 @@ const commonQueryValidators = [ validateMixinOnCondition ];
98
114
  * @returns {Function} Function taking no parameters, that cleans up the attached helpers
99
115
  */
100
116
  function _validate(csn, that,
117
+ csnValidators = [],
101
118
  memberValidators = [],
102
119
  artifactValidators = [],
103
120
  queryValidators = [],
104
121
  iterateOptions = {}) {
105
122
  const { cleanup } = enrich(csn);
106
123
 
124
+ applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], true, { drillRef: true });
125
+
107
126
  forEachDefinition(csn, (artifact, artifactName, prop, path) => {
108
127
  artifactValidators.forEach((artifactValidator) => {
109
128
  artifactValidator.bind(that)(artifact, artifactName, prop, path);
@@ -118,12 +137,44 @@ function _validate(csn, that,
118
137
  }
119
138
 
120
139
  if (queryValidators.length && getNormalizedQuery(artifact).query)
121
- forAllQueries(getNormalizedQuery(artifact).query, queryValidators.map(v => v.bind(that)), path.concat([ 'query' ]));
140
+ forAllQueries(getNormalizedQuery(artifact).query, queryValidators.map(v => v.bind(that)), path.concat([ artifact.projection ? 'projection' : 'query' ]));
122
141
  }, iterateOptions);
123
142
 
124
143
  return cleanup;
125
144
  }
126
145
 
146
+ /**
147
+ * Ensure the CSN validators adhere to the applyTransformation format - also, supply correct this value for each subfunction
148
+ *
149
+ * @param {object[]} csnValidators Validators
150
+ * @param {object} that Value for this
151
+ * @returns {object} Remapped validators.
152
+ */
153
+ function mergeCsnValidators(csnValidators, that) {
154
+ const remapped = {};
155
+ for (const validator of csnValidators) {
156
+ for (const [ n, fns ] of Object.entries(validator)) {
157
+ if (!remapped[n])
158
+ remapped[n] = [];
159
+
160
+ if (Array.isArray(fns)) {
161
+ remapped[n].push((parent, name, prop, path) => fns.forEach(
162
+ fn => fn.bind(that)(parent, name, prop, path)
163
+ ));
164
+ }
165
+ else {
166
+ remapped[n].push((parent, name, prop, path) => fns.bind(that)(parent, name, prop, path));
167
+ }
168
+ }
169
+ }
170
+
171
+ for (const [ n, fns ] of Object.entries(remapped))
172
+ remapped[n] = (parent, name, prop, path) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path));
173
+
174
+
175
+ return remapped;
176
+ }
177
+
127
178
  /**
128
179
  * @param {CSN.Model} csn CSN to check
129
180
  * @param {object} that Will be provided to the validators via "this"
@@ -131,6 +182,7 @@ function _validate(csn, that,
131
182
  */
132
183
  function forHana(csn, that) {
133
184
  return _validate(csn, that,
185
+ forHanaCsnValidators,
134
186
  forHanaMemberValidators.concat(commonMemberValidators),
135
187
  forHanaArtifactValidators.concat(commonArtifactValidators).concat(
136
188
  // why is this hana exclusive
@@ -160,6 +212,7 @@ function forHana(csn, that) {
160
212
  */
161
213
  function forOdata(csn, that) {
162
214
  return _validate(csn, that,
215
+ forOdataCsnValidators,
163
216
  forOdataMemberValidators.concat(commonMemberValidators),
164
217
  forOdataArtifactValidators.concat(commonArtifactValidators).concat(
165
218
  (artifact, artifactName) => {
@@ -97,6 +97,7 @@ function assertConsistency( model, stage ) {
97
97
  '_entities', '$entity',
98
98
  '$blocks',
99
99
  '$newfeatures',
100
+ '$messageFunctions',
100
101
  ],
101
102
  },
102
103
  ':parser': { // top-level from parser
@@ -245,8 +246,8 @@ function assertConsistency( model, stage ) {
245
246
  optional: [
246
247
  'name', '$parens', 'quantifier', 'mixin', 'excludingDict', 'columns', 'elements', '_deps',
247
248
  'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
248
- '_projections', '_block', '_parent', '_main', '_effectiveType',
249
- '$tableAliases', 'kind', '_$next', '_combined',
249
+ '_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
250
+ '$tableAliases', 'kind', '_$next', '_combined', '$inlines',
250
251
  ],
251
252
  },
252
253
  none: { optional: () => true }, // parse error
@@ -310,6 +311,7 @@ function assertConsistency( model, stage ) {
310
311
  rows: { inherits: 'value' },
311
312
  offset: { inherits: 'value' },
312
313
  _combined: { test: TODO },
314
+ $inlines: { test: TODO },
313
315
  type: {
314
316
  kind: true,
315
317
  requires: [ 'location', 'path' ],
@@ -322,7 +324,7 @@ function assertConsistency( model, stage ) {
322
324
  requires: [ 'location' ],
323
325
  optional: [
324
326
  'path', 'elements', '_outer',
325
- 'scope', '_artifact', '$inferred',
327
+ 'scope', '_artifact', '$inferred', '$expand',
326
328
  '_effectiveType', // by propagation
327
329
  ],
328
330
  },
@@ -349,6 +351,7 @@ function assertConsistency( model, stage ) {
349
351
  $delimited: { parser: true, test: isBoolean },
350
352
  scope: { test: isScope },
351
353
  func: { test: TODO },
354
+ suffix: { test: TODO },
352
355
  kind: {
353
356
  isRequired: !stageParser && (() => true),
354
357
  // required to be set by Core Compiler even with parse errors
@@ -396,6 +399,7 @@ function assertConsistency( model, stage ) {
396
399
  optional: [
397
400
  'args',
398
401
  'func',
402
+ 'suffix',
399
403
  'quantifier',
400
404
  '$inferred',
401
405
  '$parens',
@@ -424,7 +428,7 @@ function assertConsistency( model, stage ) {
424
428
  struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
425
429
  args: {
426
430
  inherits: 'value',
427
- optional: [ 'name', '$duplicate', '$expected' ],
431
+ optional: [ 'name', '$duplicate', '$expected', 'args', 'suffix' ],
428
432
  test: args,
429
433
  },
430
434
  on: { kind: true, inherits: 'value', test: expression },
@@ -527,11 +531,11 @@ function assertConsistency( model, stage ) {
527
531
  // query specific
528
532
  'op', 'from', 'elements',
529
533
  '_combined',
530
- '$tableAliases',
534
+ '$tableAliases', '$inlines',
531
535
  ],
532
536
  optional: [
533
537
  '_effectiveType', '$parens',
534
- '_deps',
538
+ '_deps', '$expand',
535
539
  // query specific
536
540
  'where', 'columns', 'mixin', 'quantifier', 'offset',
537
541
  'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
@@ -572,13 +576,18 @@ function assertConsistency( model, stage ) {
572
576
  $duplicates: { parser: true, kind: true, test: TODO }, // array of arts or true
573
577
  $extension: { kind: true, test: TODO }, // TODO: introduce $applied instead or $status
574
578
  $inferred: { parser: true, kind: true, test: isString },
575
- $expand: { kind: true, test: isString },
579
+
580
+ // Helper property for the XSN-to-CSN transformation, see function setExpandStatus():
581
+ // client, universal: render expanded elements? gensrc: produce annotate statements?
582
+ $expand: { kind: true, test: isString }, // TODO: rename it to $elementsExpand ?
583
+
576
584
  $autoexpose: { kind: [ 'entity' ], test: isBoolean, also: [ null, 'Composition' ] },
577
585
  $a2j: { kind: true, enumerable: true, test: TODO },
578
586
  $extra: { parser: true, test: TODO }, // for unexpected properties in CSN
579
587
  $withLocalized: { test: isBoolean },
580
588
  $sources: { parser: true, test: isArray( isString ) },
581
589
  $expected: { parser: true, test: isString },
590
+ $messageFunctions: { test: TODO },
582
591
  };
583
592
  let _noSyntaxErrors = null;
584
593
  assertProp( model, null, stageParser ? ':parser' : ':model', null, true );
@@ -83,6 +83,8 @@ const specialFunctions = {
83
83
  const magicVariables = {
84
84
  $user: {
85
85
  elements: { id: {}, locale: {} },
86
+ // Allow $user.<any>
87
+ $uncheckedElements: true,
86
88
  // Allow shortcut in CDL: `$user` becomes `$user.id` in CSN.
87
89
  $autoElement: 'id',
88
90
  }, // CDS-specific, not part of SQL
@@ -13,16 +13,16 @@
13
13
 
14
14
  'use strict';
15
15
 
16
- const { makeMessageFunction } = require('../base/messages');
17
16
  // const { hasArtifactTypeInformation } = require('../model/csnUtils')
18
17
  const builtins = require('../compiler/builtins');
19
- const { forEachGeneric, forEachDefinition, forEachMember } = require('../base/model');
18
+ const {
19
+ forEachGeneric, forEachDefinition, forEachMember,
20
+ } = require('../base/model');
20
21
 
21
22
  function check( model ) { // = XSN
22
- const { options } = model;
23
23
  const {
24
24
  error, warning, message,
25
- } = makeMessageFunction( model, options, 'compile' );
25
+ } = model.$messageFunctions;
26
26
  forEachDefinition( model, checkArtifact );
27
27
  return;
28
28
 
@@ -66,6 +66,8 @@ function check( model ) { // = XSN
66
66
  }
67
67
 
68
68
  function checkName( construct ) {
69
+ if (model.options.$skipNameCheck)
70
+ return;
69
71
  // TODO: Move a corrected version of this check to definer (but do not rely
70
72
  // on it!): The code below misses to consider CSN input.
71
73
  if (construct.name.id && construct.name.id.indexOf('.') !== -1) {