@sap/cds-compiler 3.4.4 → 3.5.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 (129) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +1 -0
  3. package/bin/cds_update_identifiers.js +5 -5
  4. package/bin/cdsc.js +12 -12
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +9 -1
  7. package/doc/CHANGELOG_DEPRECATED.md +2 -0
  8. package/lib/api/main.js +58 -59
  9. package/lib/api/options.js +4 -2
  10. package/lib/api/validate.js +2 -2
  11. package/lib/base/cleanSymbols.js +2 -3
  12. package/lib/base/dictionaries.js +6 -6
  13. package/lib/base/error.js +2 -2
  14. package/lib/base/keywords.js +6 -6
  15. package/lib/base/location.js +11 -12
  16. package/lib/base/message-registry.js +124 -28
  17. package/lib/base/messages.js +247 -179
  18. package/lib/base/model.js +14 -11
  19. package/lib/base/node-helpers.js +9 -10
  20. package/lib/base/optionProcessorHelper.js +138 -129
  21. package/lib/checks/actionsFunctions.js +5 -5
  22. package/lib/checks/annotationsOData.js +4 -4
  23. package/lib/checks/arrayOfs.js +1 -1
  24. package/lib/checks/cdsPersistence.js +1 -1
  25. package/lib/checks/checkForTypes.js +3 -3
  26. package/lib/checks/defaultValues.js +3 -3
  27. package/lib/checks/elements.js +7 -7
  28. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  29. package/lib/checks/foreignKeys.js +1 -1
  30. package/lib/checks/invalidTarget.js +4 -4
  31. package/lib/checks/managedInType.js +1 -1
  32. package/lib/checks/managedWithoutKeys.js +1 -1
  33. package/lib/checks/nonexpandableStructured.js +5 -3
  34. package/lib/checks/nullableKeys.js +1 -1
  35. package/lib/checks/onConditions.js +5 -6
  36. package/lib/checks/parameters.js +1 -1
  37. package/lib/checks/queryNoDbArtifacts.js +2 -2
  38. package/lib/checks/selectItems.js +4 -4
  39. package/lib/checks/sql-snippets.js +4 -4
  40. package/lib/checks/types.js +7 -7
  41. package/lib/checks/utils.js +4 -4
  42. package/lib/checks/validator.js +16 -13
  43. package/lib/compiler/.eslintrc.json +1 -1
  44. package/lib/compiler/assert-consistency.js +0 -1
  45. package/lib/compiler/builtins.js +1 -1
  46. package/lib/compiler/checks.js +73 -15
  47. package/lib/compiler/define.js +3 -7
  48. package/lib/compiler/extend.js +212 -32
  49. package/lib/compiler/finalize-parse-cdl.js +7 -2
  50. package/lib/compiler/index.js +17 -14
  51. package/lib/compiler/populate.js +2 -5
  52. package/lib/compiler/propagator.js +2 -0
  53. package/lib/compiler/shared.js +23 -12
  54. package/lib/compiler/tweak-assocs.js +5 -6
  55. package/lib/compiler/utils.js +6 -0
  56. package/lib/edm/annotations/genericTranslation.js +553 -319
  57. package/lib/edm/annotations/preprocessAnnotations.js +39 -35
  58. package/lib/edm/csn2edm.js +88 -75
  59. package/lib/edm/edm.js +17 -3
  60. package/lib/edm/edmAnnoPreprocessor.js +5 -5
  61. package/lib/edm/edmPreprocessor.js +106 -76
  62. package/lib/edm/edmUtils.js +41 -2
  63. package/lib/gen/Dictionary.json +34 -0
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +66 -63
  66. package/lib/gen/language.tokens +81 -81
  67. package/lib/gen/languageLexer.interp +4 -10
  68. package/lib/gen/languageLexer.js +854 -869
  69. package/lib/gen/languageLexer.tokens +79 -81
  70. package/lib/gen/languageParser.js +14360 -14146
  71. package/lib/inspect/inspectModelStatistics.js +2 -2
  72. package/lib/inspect/inspectPropagation.js +6 -6
  73. package/lib/inspect/inspectUtils.js +2 -2
  74. package/lib/json/from-csn.js +82 -40
  75. package/lib/json/to-csn.js +82 -157
  76. package/lib/language/.eslintrc.json +1 -4
  77. package/lib/language/genericAntlrParser.js +59 -38
  78. package/lib/language/language.g4 +1508 -1490
  79. package/lib/language/multiLineStringParser.js +1 -1
  80. package/lib/main.js +3 -3
  81. package/lib/model/csnUtils.js +130 -122
  82. package/lib/model/revealInternalProperties.js +1 -1
  83. package/lib/model/sortViews.js +4 -6
  84. package/lib/modelCompare/utils/filter.js +4 -3
  85. package/lib/optionProcessor.js +5 -0
  86. package/lib/render/DuplicateChecker.js +1 -1
  87. package/lib/render/manageConstraints.js +12 -12
  88. package/lib/render/toCdl.js +225 -159
  89. package/lib/render/toHdbcds.js +63 -63
  90. package/lib/render/toRename.js +5 -5
  91. package/lib/render/toSql.js +55 -65
  92. package/lib/render/utils/common.js +20 -37
  93. package/lib/render/utils/delta.js +3 -3
  94. package/lib/render/utils/sql.js +22 -6
  95. package/lib/render/utils/stringEscapes.js +3 -3
  96. package/lib/transform/db/applyTransformations.js +3 -3
  97. package/lib/transform/db/assertUnique.js +13 -12
  98. package/lib/transform/db/associations.js +5 -5
  99. package/lib/transform/db/cdsPersistence.js +10 -8
  100. package/lib/transform/db/constraints.js +14 -14
  101. package/lib/transform/db/expansion.js +20 -22
  102. package/lib/transform/db/flattening.js +24 -42
  103. package/lib/transform/db/groupByOrderBy.js +3 -3
  104. package/lib/transform/db/temporal.js +6 -6
  105. package/lib/transform/db/transformExists.js +23 -23
  106. package/lib/transform/db/views.js +16 -16
  107. package/lib/transform/draft/db.js +10 -10
  108. package/lib/transform/draft/odata.js +2 -2
  109. package/lib/transform/forOdataNew.js +12 -40
  110. package/lib/transform/forRelationalDB.js +17 -7
  111. package/lib/transform/localized.js +2 -2
  112. package/lib/transform/odata/toFinalBaseType.js +41 -27
  113. package/lib/transform/odata/typesExposure.js +106 -62
  114. package/lib/transform/parseExpr.js +209 -106
  115. package/lib/transform/transformUtilsNew.js +2 -2
  116. package/lib/transform/translateAssocsToJoins.js +24 -19
  117. package/lib/transform/universalCsn/coreComputed.js +10 -10
  118. package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
  119. package/lib/transform/universalCsn/utils.js +3 -3
  120. package/lib/utils/file.js +5 -5
  121. package/lib/utils/moduleResolve.js +13 -13
  122. package/lib/utils/objectUtils.js +6 -6
  123. package/lib/utils/term.js +5 -2
  124. package/lib/utils/timetrace.js +51 -24
  125. package/package.json +5 -7
  126. package/share/messages/check-proper-type-of.md +1 -1
  127. package/share/messages/message-explanations.json +1 -1
  128. package/share/messages/redirected-to-complex.md +4 -4
  129. package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
@@ -13,7 +13,7 @@ const { isBetaEnabled } = require('../base/model');
13
13
  * @param {string} prop Ignored property, always "definitions"
14
14
  * @param {CSN.Path} path path to the definition
15
15
  */
16
- function checkActionOrFunction(art, artName, prop, path) {
16
+ function checkActionOrFunction( art, artName, prop, path ) {
17
17
  if (!(art.kind === 'action' || art.kind === 'function') && !art.actions)
18
18
  return;
19
19
 
@@ -21,7 +21,7 @@ function checkActionOrFunction(art, artName, prop, path) {
21
21
  // (this.options.odataProxies || this.options.odataXServiceRefs);
22
22
 
23
23
  const serviceName = this.csnUtils.getServiceName(artName);
24
- if (!serviceName)
24
+ if (!serviceName && art.kind !== 'aspect')
25
25
  this.warning(null, path, {}, 'Functions and actions must be declared in a service');
26
26
 
27
27
  if (art.kind === 'entity') {
@@ -50,7 +50,7 @@ function checkActionOrFunction(art, artName, prop, path) {
50
50
  * @param {CSN.Path} currPath path to the parameter
51
51
  * @param {string} actKind 'action' or 'function'
52
52
  */
53
- function checkActionOrFunctionParameter(param, currPath, actKind) {
53
+ function checkActionOrFunctionParameter( param, currPath, actKind ) {
54
54
  const paramType = param.type ? this.csnUtils.getFinalTypeDef(param.type) : param;
55
55
 
56
56
  if (!isBetaEnabled(this.options, 'optionalActionFunctionParameters') && (param.default || paramType.default)) {
@@ -85,7 +85,7 @@ function checkActionOrFunction(art, artName, prop, path) {
85
85
  * @param {CSN.Path} currPath path to the returns object
86
86
  * @param {string} actKind 'action' or 'function'
87
87
  */
88
- function checkReturns(returns, currPath, actKind) {
88
+ function checkReturns( returns, currPath, actKind ) {
89
89
  const finalReturnType = returns.type ? this.csnUtils.getFinalBaseTypeWithProps(returns.type) : returns;
90
90
  if (!finalReturnType)
91
91
  return; // no type, e.g. `type of V:calculated`; already an error in `checkTypeOfHasProperType()`
@@ -112,7 +112,7 @@ function checkActionOrFunction(art, artName, prop, path) {
112
112
  * @param {string} typeName Name of the type definition
113
113
  * @param {CSN.Path} currPath The current path
114
114
  */
115
- function checkUserDefinedType(type, typeName, currPath) {
115
+ function checkUserDefinedType( type, typeName, currPath ) {
116
116
  // TODO: isBuiltinType does not resolve any type-chains.
117
117
  if (!isBuiltinType(type.type) && type.kind && type.kind !== 'type') {
118
118
  const serviceOfType = this.csnUtils.getServiceName(typeName);
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * @param {CSN.Element} member Member to be checked
14
14
  */
15
- function checkCoreMediaTypeAllowence(member) {
15
+ function checkCoreMediaTypeAllowence( member ) {
16
16
  const allowedCoreMediaTypes = {
17
17
  'cds.String': 1,
18
18
  'cds.LargeString': 1,
@@ -34,7 +34,7 @@ function checkCoreMediaTypeAllowence(member) {
34
34
  *
35
35
  * @param {CSN.Element} member Member to be checked
36
36
  */
37
- function checkAnalytics(member) {
37
+ function checkAnalytics( member ) {
38
38
  if (member['@Analytics.Measure'] && !member['@Aggregation.default']) {
39
39
  this.info(null, member.$path, {},
40
40
  'Annotation “@Analytics.Measure” expects “@Aggregation.default” to be assigned for the same element as well');
@@ -46,7 +46,7 @@ function checkAnalytics(member) {
46
46
  *
47
47
  * @param {(CSN.Artifact|CSN.Element)} node Member or artifact to be checked
48
48
  */
49
- function checkAtSapAnnotations(node) {
49
+ function checkAtSapAnnotations( node ) {
50
50
  Object.keys(node).forEach((prop) => {
51
51
  if (prop.startsWith('@sap.') && typeof node[prop] !== 'boolean' && typeof node[prop] !== 'string')
52
52
  this.warning(null, node.$path, { name: prop }, 'Annotation $(NAME) must have a string or boolean value');
@@ -59,7 +59,7 @@ function checkAtSapAnnotations(node) {
59
59
  * @param {CSN.Artifact} artifact Artifact to be checked
60
60
  * @param {string} artifactName The name of the artifact
61
61
  */
62
- function checkReadOnlyAndInsertOnly(artifact, artifactName) {
62
+ function checkReadOnlyAndInsertOnly( artifact, artifactName ) {
63
63
  if (!this.csnUtils.getServiceName(artifactName))
64
64
  return;
65
65
  if (artifact.kind === 'entity' && artifact['@readonly'] && artifact['@insertonly'])
@@ -14,7 +14,7 @@ const { setProp } = require('../base/model');
14
14
  *
15
15
  * @param {CSN.Artifact} member Member
16
16
  */
17
- function validateAssociationsInItems(member) {
17
+ function validateAssociationsInItems( member ) {
18
18
  const validate = (obj) => {
19
19
  if (obj && obj.elements) {
20
20
  for (const elementName of Object.keys(obj.elements)) {
@@ -10,7 +10,7 @@
10
10
  * @param {string} prop Property being looped over
11
11
  * @param {CSN.Path} path Path to the artifact
12
12
  */
13
- function validateCdsPersistenceAnnotation(artifact, artifactName, prop, path) {
13
+ function validateCdsPersistenceAnnotation( artifact, artifactName, prop, path ) {
14
14
  if (artifact.kind === 'entity') {
15
15
  // filter for 'table', 'udf', 'calcview' === true
16
16
  const persistenceAnnos = [ '@cds.persistence.table', '@cds.persistence.udf', '@cds.persistence.calcview' ];
@@ -10,7 +10,7 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
10
10
  * @param {Array} type type to check
11
11
  * @param {CSN.Path} path
12
12
  */
13
- function checkForHanaTypes(parent, name, type, path) {
13
+ function checkForHanaTypes( parent, name, type, path ) {
14
14
  const artifact = this.csn.definitions[path[1]];
15
15
  if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && typeof parent.type === 'string' && parent.type.startsWith('cds.hana.')) {
16
16
  this.error('ref-unexpected-hana-type', [ ...path, 'type' ], { type: 'cds.hana', value: this.options.sqlDialect },
@@ -26,7 +26,7 @@ function checkForHanaTypes(parent, name, type, path) {
26
26
  * @param {Array} type type to check
27
27
  * @param {CSN.Path} path
28
28
  */
29
- function CheckForUInt8(parent, name, type, path) {
29
+ function CheckForUInt8( parent, name, type, path ) {
30
30
  const artifact = this.csn.definitions[path[1]];
31
31
  if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && parent.type === 'cds.UInt8') {
32
32
  this.error('ref-unexpected-type', [ ...path, 'type' ], { type: 'cds.UInt8', value: this.options.sqlDialect },
@@ -42,7 +42,7 @@ function CheckForUInt8(parent, name, type, path) {
42
42
  * @param {Array} type type to check
43
43
  * @param {CSN.Path} path
44
44
  */
45
- function checkTypes(parent, name, type, path) {
45
+ function checkTypes( parent, name, type, path ) {
46
46
  checkForHanaTypes.bind(this)(parent, name, type, path);
47
47
  if (this.options.sqlDialect === 'postgres' || this.options.sqlDialect === 'h2')
48
48
  CheckForUInt8.bind(this)(parent, name, type, path);
@@ -12,7 +12,7 @@
12
12
  * @param {string} prop Property being looped over
13
13
  * @param {CSN.Path} path Path to the member
14
14
  */
15
- function validateDefaultValues(member, memberName, prop, path) {
15
+ function validateDefaultValues( member, memberName, prop, path ) {
16
16
  if (member.default && this.options.toOdata) {
17
17
  // unary minus is xpr: [ "-", { val: ... } ]
18
18
  if (member.default.xpr) {
@@ -36,7 +36,7 @@ function validateDefaultValues(member, memberName, prop, path) {
36
36
  * @param {string} prop Property being looped over
37
37
  * @param {CSN.Path} path Path to the member
38
38
  */
39
- function rejectParamDefaultsInHanaCds(member, memberName, prop, path) {
39
+ function rejectParamDefaultsInHanaCds( member, memberName, prop, path ) {
40
40
  if (member.default && prop === 'params' && this.options.transformation === 'hdbcds')
41
41
  this.error(null, path, {}, 'Parameter default values are not supported in SAP HANA CDS');
42
42
  }
@@ -51,7 +51,7 @@ function rejectParamDefaultsInHanaCds(member, memberName, prop, path) {
51
51
  * @param {string} prop Property being looped over
52
52
  * @param {CSN.Path} path Path to the member
53
53
  */
54
- function warnAboutDefaultOnAssociationForHanaCds(member, memberName, prop, path) {
54
+ function warnAboutDefaultOnAssociationForHanaCds( member, memberName, prop, path ) {
55
55
  const art = this.csn.definitions[path[1]];
56
56
  if (!art.query && this.options.transformation === 'hdbcds' && member.target && member.default) {
57
57
  this.warning(null, path, { '#': member._type.type === 'cds.Association' ? 'std' : 'comp' },
@@ -11,7 +11,7 @@ const { setProp } = require('../base/model');
11
11
  *
12
12
  * @param {CSN.Artifact} art The artifacts that will be checked
13
13
  */
14
- function checkPrimaryKey(art) {
14
+ function checkPrimaryKey( art ) {
15
15
  if (art.kind !== 'entity' && art.kind !== 'aspect')
16
16
  return;
17
17
  forEachMember(art, (member, memberName, prop, path) => {
@@ -34,7 +34,7 @@ function checkPrimaryKey(art) {
34
34
  * @param {boolean} parentIsKey Whether parent is a key
35
35
  * @param {CSN.Path} parentPath The path of the parent element (optional)
36
36
  */
37
- function checkIfPrimaryKeyIsOfGeoType(member, elemFqName, parentIsKey, parentPath) {
37
+ function checkIfPrimaryKeyIsOfGeoType( member, elemFqName, parentIsKey, parentPath ) {
38
38
  if (member.key || parentIsKey) {
39
39
  const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
40
40
  if (isGeoTypeName(finalBaseType?.type)) {
@@ -63,7 +63,7 @@ function checkPrimaryKey(art) {
63
63
  * @param {boolean} parentIsKey Whether parent is a key
64
64
  * @param {CSN.Path} parentPath The path of the parent element (optional)
65
65
  */
66
- function checkIfPrimaryKeyIsArray(member, elemFqName, parentIsKey, parentPath) {
66
+ function checkIfPrimaryKeyIsArray( member, elemFqName, parentIsKey, parentPath ) {
67
67
  if (member.key || parentIsKey) {
68
68
  const finalBaseType = this.csnUtils.getFinalBaseTypeWithProps(member.type);
69
69
  if (member.items || (finalBaseType && finalBaseType.items)) {
@@ -90,7 +90,7 @@ function checkPrimaryKey(art) {
90
90
  *
91
91
  * @param {CSN.Element} member Element to be checked
92
92
  */
93
- function checkVirtualElement(member) {
93
+ function checkVirtualElement( member ) {
94
94
  if (member.virtual) {
95
95
  if (this.csnUtils.isAssociation(member.type)) { // or Composition ???
96
96
  this.error(null, member.$path, {}, 'Element can\'t be virtual and an association');
@@ -105,7 +105,7 @@ function checkVirtualElement(member) {
105
105
  * @param {CSN.Artifact} art The artifact
106
106
  * @todo this is a member validator, is it not?
107
107
  */
108
- function checkManagedAssoc(art) {
108
+ function checkManagedAssoc( art ) {
109
109
  forEachMemberRecursively(art, (member) => {
110
110
  if (this.csnUtils.isAssocOrComposition(member.type) &&
111
111
  !isManagedComposition.bind(this)(member)) {
@@ -129,7 +129,7 @@ function checkManagedAssoc(art) {
129
129
  * @param {CSN.Element} member The member
130
130
  * @returns {boolean} Whether the member is managed composition
131
131
  */
132
- function isManagedComposition(member) {
132
+ function isManagedComposition( member ) {
133
133
  if (member.targetAspect)
134
134
  return true;
135
135
  if (!member.target)
@@ -148,7 +148,7 @@ function checkManagedAssoc(art) {
148
148
  *
149
149
  * @param {CSN.Artifact} art The artifact
150
150
  */
151
- function checkRecursiveTypeUsage(art) {
151
+ function checkRecursiveTypeUsage( art ) {
152
152
  const visit = (def) => {
153
153
  const loc = def.$path;
154
154
  // recursive types are allowed inside arrays
@@ -11,7 +11,7 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
11
11
  * @param {string} prop Property being looped over
12
12
  * @param {CSN.Path} path Path to the artifact
13
13
  */
14
- function validateEmptyOrOnlyVirtual(artifact, artifactName, prop, path) {
14
+ function validateEmptyOrOnlyVirtual( artifact, artifactName, prop, path ) {
15
15
  if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
16
16
  if (!artifact.elements || !hasRealElements(artifact.elements))
17
17
  this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
@@ -24,7 +24,7 @@ function validateEmptyOrOnlyVirtual(artifact, artifactName, prop, path) {
24
24
  * @param {CSN.Elements} elements Elements to look through
25
25
  * @returns {boolean} True if something would be created on the db from these elements.
26
26
  */
27
- function hasRealElements(elements) {
27
+ function hasRealElements( elements ) {
28
28
  for (const element of Object.values(elements)) {
29
29
  if (!element.virtual) {
30
30
  if (element.elements) {
@@ -12,7 +12,7 @@ const { setProp } = require('../base/model');
12
12
  *
13
13
  * @param {object} member Member
14
14
  */
15
- function validateForeignKeys(member) {
15
+ function validateForeignKeys( member ) {
16
16
  // We have a managed association
17
17
  const isManagedAssoc = mem => mem && mem.target && !mem.on;
18
18
  // We have an unmanaged association
@@ -10,7 +10,7 @@ const { setProp } = require('../base/model');
10
10
  *
11
11
  * @param {object} member Member
12
12
  */
13
- function invalidTarget(member) {
13
+ function invalidTarget( member ) {
14
14
  // Declared as arrow-function to keep scope the same (this value)
15
15
  const handleStructured = (mem) => {
16
16
  for (const elementName of Object.keys(mem.elements)) {
@@ -31,10 +31,10 @@ function invalidTarget(member) {
31
31
  this.error(
32
32
  null,
33
33
  member.$path,
34
- { '#': isAssoc ? 'std' : 'comp', kind: target.kind },
34
+ { '#': isAssoc ? 'std' : 'comp', meta: target.kind },
35
35
  {
36
- std: 'Association target must be an entity but found: $(KIND)',
37
- comp: 'Composition target must be an entity but found: $(KIND)',
36
+ std: 'Association target must be an entity but found: $(META)',
37
+ comp: 'Composition target must be an entity but found: $(META)',
38
38
  }
39
39
  );
40
40
  }
@@ -12,7 +12,7 @@ const { setProp } = require('../base/model');
12
12
  *
13
13
  * @param {object} member Member
14
14
  */
15
- function checkUsedTypesForAnonymousAspectComposition(member) {
15
+ function checkUsedTypesForAnonymousAspectComposition( member ) {
16
16
  // Declared as arrow-function to keep scope the same (this value)
17
17
  const handleAssociation = (mem, fn) => {
18
18
  for (const key of mem.keys) {
@@ -10,7 +10,7 @@ const { ModelError } = require('../base/error');
10
10
  * @param {string} memberName the elements name
11
11
  * @param {string} prop which kind of member are we looking at -> only prop "elements"
12
12
  */
13
- function managedWithoutKeys(member, memberName, prop) {
13
+ function managedWithoutKeys( member, memberName, prop ) {
14
14
  if (prop === 'elements' && member.target && !member.keys && !member.on) { // trigger recompilation
15
15
  throw new ModelError('Expected association to have either an on-condition or foreign keys.');
16
16
  }
@@ -9,7 +9,7 @@ const { otherSideIsExpandableStructure, resolveArtifactType } = require('./utils
9
9
  * @param {string} name Name of the expression property on parent
10
10
  * @param {Array} expression Expression to check - .on .xpr .having and .where
11
11
  */
12
- function nonexpandableStructuredInExpression(parent, name, expression) {
12
+ function nonexpandableStructuredInExpression( parent, name, expression ) {
13
13
  for (let i = 0; i < expression.length; i++) {
14
14
  if (expression[i].ref) {
15
15
  const { ref } = expression[i];
@@ -21,8 +21,10 @@ function nonexpandableStructuredInExpression(parent, name, expression) {
21
21
  if (_art) {
22
22
  _art = resolveArtifactType.call(this, _art);
23
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 } },
24
+ if ((_art?.elements || _art?.keys && (i === 0 || expression[i - 1] !== 'exists')) && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
25
+ this.error('ref-unexpected-structured',
26
+ name === 'on' ? [ ...parent.$path, name, i ] : expression[i].$path,
27
+ { elemref: { ref } },
26
28
  'Unexpected usage of structured type $(ELEMREF)');
27
29
  }
28
30
  }
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @param {CSN.Element} element The element to check
7
7
  */
8
- function checkExplicitlyNullableKeys(element) {
8
+ function checkExplicitlyNullableKeys( element ) {
9
9
  if (element.key && element.notNull === false)
10
10
  this.error(null, element.$path, {}, 'Expecting primary key element to be not nullable');
11
11
  }
@@ -11,7 +11,7 @@ const { otherSideIsExpandableStructure, resolveArtifactType } = require('./utils
11
11
  * @param {any} refStep part of a ref
12
12
  * @returns {string} Loggable string
13
13
  */
14
- function logReady(refStep) {
14
+ function logReady( refStep ) {
15
15
  return refStep.id || refStep;
16
16
  }
17
17
 
@@ -25,7 +25,7 @@ function logReady(refStep) {
25
25
  * @param {number} startIndex Index of the current expression to "look around"
26
26
  * @returns {boolean} True if valid
27
27
  */
28
- function otherSideIsValidDollarSelf(on, startIndex) {
28
+ function otherSideIsValidDollarSelf( on, startIndex ) {
29
29
  if (on[startIndex - 1] && on[startIndex - 1] === '=') {
30
30
  if (on[startIndex - 2]) {
31
31
  const { ref } = on[startIndex - 2];
@@ -57,7 +57,7 @@ function otherSideIsValidDollarSelf(on, startIndex) {
57
57
  * @param {string} property Current property (part of forEachMember)
58
58
  * @param {CSN.Path} path CSN Path to current member
59
59
  */
60
- function validateOnCondition(member, memberName, property, path) {
60
+ function validateOnCondition( member, memberName, property, path ) {
61
61
  if (member && member.on) {
62
62
  // complain about nullability constraint on managed composition
63
63
  if (member.targetAspect && {}.hasOwnProperty.call(member, 'notNull')) {
@@ -116,8 +116,7 @@ function validateOnCondition(member, memberName, property, path) {
116
116
  !( /* 1) */ (_art.target && _art.keys) && validStructuredElement ||
117
117
  /* 2) */ (_art.target && validDollarSelf)) &&
118
118
  !_art.virtual) {
119
- this.error(null, onPath, { elemref: { ref } },
120
- 'The last path of an ON-condition must be a scalar value, path $(ELEMREF)');
119
+ // Do nothing - handled by lib/checks/nonexpandableStructured.js
121
120
  }
122
121
  else if (_art.items && !_art.virtual) {
123
122
  this.error(null, onPath, { elemref: { ref } },
@@ -139,7 +138,7 @@ function validateOnCondition(member, memberName, property, path) {
139
138
  * @param {CSN.Query} query query object
140
139
  * @param {CSN.Path} path path to the query
141
140
  */
142
- function validateMixinOnCondition(query, path) {
141
+ function validateMixinOnCondition( query, path ) {
143
142
  if (query.SELECT && query.SELECT.mixin)
144
143
  forEachGeneric( query.SELECT, 'mixin', validateOnCondition.bind(this), path );
145
144
  }
@@ -10,7 +10,7 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
10
10
  * @param {object} params params
11
11
  * @param {CSN.Path} path
12
12
  */
13
- function checkForParams(parent, name, params, path) {
13
+ function checkForParams( parent, name, params, path ) {
14
14
  const artifact = this.csn.definitions[path[1]];
15
15
  if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact) && !(parent.kind !== 'entity')) {
16
16
  this.error('ref-unexpected-params', [ ...path, 'params' ], { value: this.options.sqlDialect },
@@ -16,7 +16,7 @@ const { hasAnnotationValue, isPersistedOnDatabase, isBuiltinType } = require('..
16
16
  *
17
17
  * @param {CSN.Query} query Query to check
18
18
  */
19
- function checkQueryForNoDBArtifacts(query) {
19
+ function checkQueryForNoDBArtifacts( query ) {
20
20
  /**
21
21
  * Count the leaf-elements resulting from a given element.
22
22
  *
@@ -143,7 +143,7 @@ function checkQueryForNoDBArtifacts(query) {
143
143
  * @param {object} rhs Right hand side of the on-condition part
144
144
  * @returns {Array} Return the association object (index 0) and the corresponding path (index 1).
145
145
  */
146
- function getForwardAssociation(prefix, lhs, rhs) {
146
+ function getForwardAssociation( prefix, lhs, rhs ) {
147
147
  if (lhs && rhs) {
148
148
  if (rhs.ref.length === 1 && rhs.ref[0] === '$self' &&
149
149
  lhs.ref.length > 1 && lhs.ref[0] === prefix)
@@ -13,7 +13,7 @@ const { forEachGeneric, applyTransformationsOnNonDictionary } = require('../mode
13
13
  * @param {CSN.Query} query query object
14
14
  * @todo Why do we care about this with $self?
15
15
  */
16
- function validateSelectItems(query) {
16
+ function validateSelectItems( query ) {
17
17
  const { SELECT } = query;
18
18
  if (!SELECT)
19
19
  return;
@@ -24,7 +24,7 @@ function validateSelectItems(query) {
24
24
  * @param {string} queryPart Part of the query that is being checked
25
25
  * @returns {Function} Function as callback for applyTransformations
26
26
  */
27
- function checkRefForInvalid$Self(queryPart) {
27
+ function checkRefForInvalid$Self( queryPart ) {
28
28
  const signalError = (error, parent, type) => {
29
29
  if (queryPart === 'columns') {
30
30
  error(null, parent.$path,
@@ -78,7 +78,7 @@ function validateSelectItems(query) {
78
78
  * @param {string} prop
79
79
  * @param {Array} where
80
80
  */
81
- function checkFilterForInvalid$Self(parent, prop, where) {
81
+ function checkFilterForInvalid$Self( parent, prop, where ) {
82
82
  where.forEach((whereStep) => {
83
83
  if (whereStep.ref && ( whereStep.ref[0] === '$projection' || whereStep.ref[0] === '$self')) {
84
84
  this.error('expr-where-unexpected-self', whereStep.$path,
@@ -127,7 +127,7 @@ function validateSelectItems(query) {
127
127
  * @param {CSN.Artifact} queryArtifact the query artifact which should be checked
128
128
  * @param {CSN.Path} artifactPath the path to that artifact
129
129
  */
130
- function rejectManagedAssociationsAndStructuresForHdbcdsNames(queryArtifact, artifactPath) {
130
+ function rejectManagedAssociationsAndStructuresForHdbcdsNames( queryArtifact, artifactPath ) {
131
131
  if (this.options.transformation === 'hdbcds' && this.options.sqlMapping === 'hdbcds') {
132
132
  forEachGeneric(queryArtifact, 'elements', (selectItem, elemName, prop, elementPath) => {
133
133
  if (this.csnUtils.isManagedAssociation(selectItem))
@@ -11,7 +11,7 @@
11
11
  * @param {CSN.Path} path
12
12
  * @returns {void}
13
13
  */
14
- function checkSqlAnnotationOnElement(member, memberName, prop, path) {
14
+ function checkSqlAnnotationOnElement( member, memberName, prop, path ) {
15
15
  if (member['@sql.replace'])
16
16
  this.error(null, path, { anno: 'sql.replace' }, 'Annotation $(ANNO) is reserved and must not be used');
17
17
  if (member['@sql.prepend'])
@@ -34,7 +34,7 @@ function checkSqlAnnotationOnElement(member, memberName, prop, path) {
34
34
  * @param {Function} error
35
35
  * @param {CSN.Options} options
36
36
  */
37
- function checkValidAnnoValue(carrier, annotation, path, error, options) {
37
+ function checkValidAnnoValue( carrier, annotation, path, error, options ) {
38
38
  if (carrier[annotation] !== undefined && carrier[annotation] !== null) {
39
39
  if (typeof carrier[annotation] !== 'string')
40
40
  error(null, path, { anno: annotation.slice(1), type: typeof carrier[annotation] }, 'Annotation $(ANNO) must be a string, found $(TYPE)' );
@@ -49,7 +49,7 @@ function checkValidAnnoValue(carrier, annotation, path, error, options) {
49
49
  * @param {CSN.Artifact} artifact
50
50
  * @param {string} artifactName
51
51
  */
52
- function checkSqlAnnotationOnArtifact(artifact, artifactName) {
52
+ function checkSqlAnnotationOnArtifact( artifact, artifactName ) {
53
53
  if (artifact.kind !== 'entity') {
54
54
  if (artifact['@sql.prepend'])
55
55
  this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.prepend', kind: artifact.kind }, 'Annotation $(NAME) can\'t be used on an artifact of kind $(KIND)' );
@@ -83,7 +83,7 @@ const invalidInSnippet = [ ';', '--', '/*', '*/' ];
83
83
  * @param {CSN.Path} path
84
84
  * @param {Function} error
85
85
  */
86
- function guardAgainstInjection(annoName, annoValue, path, error) {
86
+ function guardAgainstInjection( annoName, annoValue, path, error ) {
87
87
  for (const invalid of invalidInSnippet) {
88
88
  if (annoValue.indexOf(invalid) !== -1) // These should probably not be configurable, right?
89
89
  error(null, path, { name: annoName, prop: invalid }, 'Annotation $(NAME) must not contain $(PROP)');
@@ -14,7 +14,7 @@ const { getUtils, hasAnnotationValue } = require('../model/csnUtils');
14
14
  * @param {string} prop which kind of member are we looking at -> only prop "elements"
15
15
  * @param {CSN.Path} path the path to the member
16
16
  */
17
- function checkDecimalScale(member, memberName, prop, path) {
17
+ function checkDecimalScale( member, memberName, prop, path ) {
18
18
  if (hasAnnotationValue(this.artifact, '@cds.persistence.exists') ||
19
19
  // skip is already filtered in validator, here for completeness
20
20
  hasAnnotationValue(this.artifact, '@cds.persistence.skip'))
@@ -31,7 +31,7 @@ function checkDecimalScale(member, memberName, prop, path) {
31
31
  * @param {string} prop which kind of member are we looking at -> only prop "elements"
32
32
  * @param {CSN.Path} path the path to the member
33
33
  */
34
- function checkTypeIsScalar(member, memberName, prop, path) {
34
+ function checkTypeIsScalar( member, memberName, prop, path ) {
35
35
  if ( prop === 'params' && this.csnUtils.isStructured(member))
36
36
  this.error(null, path, {}, 'View parameter type must be scalar');
37
37
  }
@@ -45,7 +45,7 @@ function checkTypeIsScalar(member, memberName, prop, path) {
45
45
  * @param {string} prop which kind of member are we looking at -> only prop "elements"
46
46
  * @param {CSN.Path} path the path to the member
47
47
  */
48
- function checkElementTypeDefinitionHasType(member, memberName, prop, path) {
48
+ function checkElementTypeDefinitionHasType( member, memberName, prop, path ) {
49
49
  // Computed elements, e.g. "1+1 as foo" in a view don't have a valid type and
50
50
  // are skipped here.
51
51
  // Elements in projections are not tested as well
@@ -90,7 +90,7 @@ function checkElementTypeDefinitionHasType(member, memberName, prop, path) {
90
90
  * @param {string} prop which kind of artifact we are looking at
91
91
  * @param {CSN.Path} path the path to the artifact
92
92
  */
93
- function checkTypeDefinitionHasType(artifact, artifactName, prop, path) {
93
+ function checkTypeDefinitionHasType( artifact, artifactName, prop, path ) {
94
94
  if (artifact.kind !== 'type')
95
95
  return;
96
96
 
@@ -125,7 +125,7 @@ function checkTypeDefinitionHasType(artifact, artifactName, prop, path) {
125
125
  * @param {string} derivedTypeName if the type reference is another type/element e.g. type derivedType : MaliciousType; we want to
126
126
  * point at the "MaliciousType" reference, that's why we need to remember the name when drilling down.
127
127
  */
128
- function checkTypeOfHasProperType(artOrElement, name, model, error, path, derivedTypeName = null) {
128
+ function checkTypeOfHasProperType( artOrElement, name, model, error, path, derivedTypeName = null ) {
129
129
  if (!artOrElement.type)
130
130
  return;
131
131
 
@@ -158,7 +158,7 @@ function checkTypeOfHasProperType(artOrElement, name, model, error, path, derive
158
158
  * @param {string} name of the element or the artifact which is dubious
159
159
  * @param {boolean} isElement indicates whether we are dealing with an element or an artifact
160
160
  */
161
- function errorAboutMissingType(error, path, name, isElement = false) {
161
+ function errorAboutMissingType( error, path, name, isElement = false ) {
162
162
  error('check-proper-type', path, { art: name, '#': isElement ? 'elm' : 'std' }, {
163
163
  std: 'Dubious type $(ART) without type information',
164
164
  elm: 'Dubious element $(ART) without type information',
@@ -174,7 +174,7 @@ function errorAboutMissingType(error, path, name, isElement = false) {
174
174
  * @param {CSN.Artifact} artifact the artifact to check
175
175
  * @returns {boolean} indicates whether the artifact has type information
176
176
  */
177
- function hasArtifactTypeInformation(artifact) {
177
+ function hasArtifactTypeInformation( artifact ) {
178
178
  // When is what property set?
179
179
  return artifact.elements || // => `type A {}`
180
180
  artifact.items || // => `type A : array of Integer`
@@ -8,7 +8,7 @@ const { RelationalOperators } = require('../transform/transformUtilsNew');
8
8
  * @param {any} refStep part of a ref
9
9
  * @returns {string} Loggable string
10
10
  */
11
- function logReady(refStep) {
11
+ function logReady( refStep ) {
12
12
  return refStep.id || refStep;
13
13
  }
14
14
 
@@ -23,7 +23,7 @@ function logReady(refStep) {
23
23
  * @param {number} startIndex the index of the relational term in the on condition array
24
24
  * @returns {boolean} indicates whether the other side of a relational term is expandable
25
25
  */
26
- function otherSideIsExpandableStructure(on, startIndex) {
26
+ function otherSideIsExpandableStructure( on, startIndex ) {
27
27
  if (on[startIndex - 1] && RelationalOperators.includes(on[startIndex - 1])) {
28
28
  const lhs = on[startIndex - 2];
29
29
  // if ever lhs is allowed to be a value uncomment this
@@ -46,7 +46,7 @@ function otherSideIsExpandableStructure(on, startIndex) {
46
46
  * @param {CSN.Artifact} art Artifact
47
47
  * @returns {boolean} True if expandable
48
48
  */
49
- function isOk(art) {
49
+ function isOk( art ) {
50
50
  return !!(art && (art.elements || (art.target && art.keys)));
51
51
  }
52
52
  }
@@ -57,7 +57,7 @@ function otherSideIsExpandableStructure(on, startIndex) {
57
57
  * @param {object} art Whatever _art by csnRefs can be - element or artifact
58
58
  * @returns {object} final artifact type
59
59
  */
60
- function resolveArtifactType(art) {
60
+ function resolveArtifactType( art ) {
61
61
  if (art && art.type && !isBuiltinType(art.type))
62
62
  return this.csnUtils.getFinalBaseTypeWithProps(art.type);
63
63
 
@@ -2,7 +2,8 @@
2
2
 
3
3
  const {
4
4
  forEachDefinition, forEachMemberRecursively, forAllQueries,
5
- forEachMember, getNormalizedQuery, hasAnnotationValue, applyTransformations,
5
+ forEachMember, getNormalizedQuery, hasAnnotationValue,
6
+ applyTransformations, functionList,
6
7
  } = require('../model/csnUtils');
7
8
  const enrich = require('./enricher');
8
9
 
@@ -128,12 +129,12 @@ const commonQueryValidators = [ validateMixinOnCondition ];
128
129
  * @param {object} iterateOptions can be used to skip certain kinds from being iterated e.g. 'action' and 'function' for hana
129
130
  * @returns {Function} Function taking no parameters, that cleans up the attached helpers
130
131
  */
131
- function _validate(csn, that,
132
- csnValidators = [],
133
- memberValidators = [],
134
- artifactValidators = [],
135
- queryValidators = [],
136
- iterateOptions = {}) {
132
+ function _validate( csn, that,
133
+ csnValidators = [],
134
+ memberValidators = [],
135
+ artifactValidators = [],
136
+ queryValidators = [],
137
+ iterateOptions = {} ) {
137
138
  const { cleanup } = enrich(csn);
138
139
 
139
140
  applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], { drillRef: true });
@@ -151,8 +152,10 @@ function _validate(csn, that,
151
152
  iterateOptions );
152
153
  }
153
154
 
154
- if (queryValidators.length && getNormalizedQuery(artifact).query)
155
- forAllQueries(getNormalizedQuery(artifact).query, queryValidators.map(v => v.bind(that)), path.concat([ artifact.projection ? 'projection' : 'query' ]));
155
+ if (queryValidators.length && getNormalizedQuery(artifact).query) {
156
+ forAllQueries(getNormalizedQuery(artifact).query, functionList(queryValidators, that),
157
+ path.concat([ artifact.projection ? 'projection' : 'query' ]));
158
+ }
156
159
  }, iterateOptions);
157
160
 
158
161
  return cleanup;
@@ -165,7 +168,7 @@ function _validate(csn, that,
165
168
  * @param {object} that Value for this
166
169
  * @returns {object} Remapped validators.
167
170
  */
168
- function mergeCsnValidators(csnValidators, that) {
171
+ function mergeCsnValidators( csnValidators, that ) {
169
172
  const remapped = {};
170
173
  for (const validator of csnValidators) {
171
174
  for (const [ n, fns ] of Object.entries(validator)) {
@@ -196,7 +199,7 @@ function mergeCsnValidators(csnValidators, that) {
196
199
  * @param {CSN.Options} options
197
200
  * @returns {any[]} Array of validator functions (or objects?)
198
201
  */
199
- function getDBCsnValidators(options) {
202
+ function getDBCsnValidators( options ) {
200
203
  if (options.sqlDialect === 'postgres' || options.sqlDialect === 'h2')
201
204
  return [ ...forRelationalDBCsnValidators, checkForHanaTypes, checkForParams ];
202
205
 
@@ -208,7 +211,7 @@ function getDBCsnValidators(options) {
208
211
  * @param {object} that Will be provided to the validators via "this"
209
212
  * @returns {Function} the validator function with the respective checks for the HANA backend
210
213
  */
211
- function forRelationalDB(csn, that) {
214
+ function forRelationalDB( csn, that ) {
212
215
  return _validate(csn, that,
213
216
  getDBCsnValidators(that.options),
214
217
  forRelationalDBMemberValidators.concat(commonMemberValidators),
@@ -235,7 +238,7 @@ function forRelationalDB(csn, that) {
235
238
  * @param {object} that Will be provided to the validators via "this"
236
239
  * @returns {Function} the validator function with the respective checks for the OData backend
237
240
  */
238
- function forOdata(csn, that) {
241
+ function forOdata( csn, that ) {
239
242
  return _validate(csn, that,
240
243
  forOdataCsnValidators,
241
244
  forOdataMemberValidators.concat(commonMemberValidators),