@sap/cds-compiler 2.11.4 → 2.13.8

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 (133) hide show
  1. package/CHANGELOG.md +159 -1
  2. package/bin/cds_update_identifiers.js +7 -7
  3. package/bin/cdsc.js +22 -23
  4. package/bin/cdsse.js +2 -2
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +25 -6
  7. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  8. package/doc/NameResolution.md +21 -16
  9. package/lib/api/main.js +30 -63
  10. package/lib/api/options.js +5 -5
  11. package/lib/api/validate.js +0 -5
  12. package/lib/backends.js +15 -23
  13. package/lib/base/dictionaries.js +0 -8
  14. package/lib/base/error.js +26 -0
  15. package/lib/base/keywords.js +7 -17
  16. package/lib/base/location.js +9 -4
  17. package/lib/base/message-registry.js +52 -2
  18. package/lib/base/messages.js +16 -26
  19. package/lib/base/model.js +2 -62
  20. package/lib/base/optionProcessorHelper.js +246 -183
  21. package/lib/checks/.eslintrc.json +2 -0
  22. package/lib/checks/actionsFunctions.js +2 -1
  23. package/lib/checks/annotationsOData.js +1 -1
  24. package/lib/checks/cdsPersistence.js +2 -1
  25. package/lib/checks/enricher.js +17 -1
  26. package/lib/checks/foreignKeys.js +4 -4
  27. package/lib/checks/invalidTarget.js +3 -1
  28. package/lib/checks/managedInType.js +4 -4
  29. package/lib/checks/managedWithoutKeys.js +3 -1
  30. package/lib/checks/queryNoDbArtifacts.js +1 -3
  31. package/lib/checks/selectItems.js +4 -4
  32. package/lib/checks/sql-snippets.js +94 -0
  33. package/lib/checks/types.js +1 -1
  34. package/lib/checks/validator.js +12 -7
  35. package/lib/compiler/assert-consistency.js +10 -6
  36. package/lib/compiler/base.js +0 -1
  37. package/lib/compiler/builtins.js +8 -6
  38. package/lib/compiler/checks.js +46 -12
  39. package/lib/compiler/cycle-detector.js +1 -1
  40. package/lib/compiler/define.js +1103 -0
  41. package/lib/compiler/extend.js +983 -0
  42. package/lib/compiler/finalize-parse-cdl.js +231 -0
  43. package/lib/compiler/index.js +33 -14
  44. package/lib/compiler/kick-start.js +190 -0
  45. package/lib/compiler/moduleLayers.js +4 -4
  46. package/lib/compiler/populate.js +1226 -0
  47. package/lib/compiler/propagator.js +113 -47
  48. package/lib/compiler/resolve.js +1433 -0
  49. package/lib/compiler/shared.js +76 -38
  50. package/lib/compiler/tweak-assocs.js +529 -0
  51. package/lib/compiler/utils.js +204 -33
  52. package/lib/edm/.eslintrc.json +5 -0
  53. package/lib/edm/annotations/genericTranslation.js +38 -25
  54. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  55. package/lib/edm/csn2edm.js +10 -9
  56. package/lib/edm/edm.js +19 -20
  57. package/lib/edm/edmPreprocessor.js +166 -95
  58. package/lib/edm/edmUtils.js +127 -34
  59. package/lib/gen/Dictionary.json +92 -43
  60. package/lib/gen/language.checksum +1 -1
  61. package/lib/gen/language.interp +11 -1
  62. package/lib/gen/language.tokens +86 -82
  63. package/lib/gen/languageLexer.interp +18 -1
  64. package/lib/gen/languageLexer.js +925 -847
  65. package/lib/gen/languageLexer.tokens +78 -74
  66. package/lib/gen/languageParser.js +5434 -4298
  67. package/lib/json/from-csn.js +59 -17
  68. package/lib/json/to-csn.js +143 -71
  69. package/lib/language/antlrParser.js +3 -3
  70. package/lib/language/docCommentParser.js +3 -3
  71. package/lib/language/genericAntlrParser.js +144 -54
  72. package/lib/language/language.g4 +424 -203
  73. package/lib/language/multiLineStringParser.js +536 -0
  74. package/lib/main.d.ts +472 -61
  75. package/lib/main.js +38 -11
  76. package/lib/model/api.js +3 -1
  77. package/lib/model/csnRefs.js +321 -204
  78. package/lib/model/csnUtils.js +224 -263
  79. package/lib/model/enrichCsn.js +97 -40
  80. package/lib/model/revealInternalProperties.js +27 -6
  81. package/lib/model/sortViews.js +2 -1
  82. package/lib/modelCompare/compare.js +17 -12
  83. package/lib/optionProcessor.js +7 -6
  84. package/lib/render/DuplicateChecker.js +1 -1
  85. package/lib/render/manageConstraints.js +36 -33
  86. package/lib/render/toCdl.js +174 -275
  87. package/lib/render/toHdbcds.js +201 -115
  88. package/lib/render/toRename.js +7 -10
  89. package/lib/render/toSql.js +149 -75
  90. package/lib/render/utils/common.js +22 -8
  91. package/lib/render/utils/sql.js +10 -7
  92. package/lib/render/utils/stringEscapes.js +111 -0
  93. package/lib/sql-identifier.js +1 -1
  94. package/lib/transform/.eslintrc.json +5 -0
  95. package/lib/transform/braceExpression.js +4 -2
  96. package/lib/transform/db/.eslintrc.json +2 -0
  97. package/lib/transform/db/applyTransformations.js +35 -12
  98. package/lib/transform/db/assertUnique.js +1 -1
  99. package/lib/transform/db/associations.js +187 -0
  100. package/lib/transform/db/cdsPersistence.js +150 -0
  101. package/lib/transform/db/constraints.js +61 -56
  102. package/lib/transform/db/expansion.js +50 -29
  103. package/lib/transform/db/flattening.js +552 -105
  104. package/lib/transform/db/groupByOrderBy.js +3 -1
  105. package/lib/transform/db/temporal.js +236 -0
  106. package/lib/transform/db/transformExists.js +94 -28
  107. package/lib/transform/db/views.js +5 -4
  108. package/lib/transform/draft/.eslintrc.json +38 -0
  109. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  110. package/lib/transform/draft/odata.js +227 -0
  111. package/lib/transform/forHanaNew.js +94 -801
  112. package/lib/transform/forOdataNew.js +22 -175
  113. package/lib/transform/localized.js +36 -32
  114. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  115. package/lib/transform/odata/referenceFlattener.js +95 -89
  116. package/lib/transform/odata/structureFlattener.js +1 -1
  117. package/lib/transform/odata/toFinalBaseType.js +86 -12
  118. package/lib/transform/odata/typesExposure.js +5 -5
  119. package/lib/transform/odata/utils.js +2 -2
  120. package/lib/transform/transformUtilsNew.js +47 -33
  121. package/lib/transform/translateAssocsToJoins.js +10 -27
  122. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  123. package/lib/transform/universalCsn/coreComputed.js +170 -0
  124. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  125. package/lib/transform/universalCsn/utils.js +63 -0
  126. package/lib/utils/file.js +2 -1
  127. package/lib/utils/objectUtils.js +30 -0
  128. package/lib/utils/timetrace.js +8 -2
  129. package/package.json +1 -1
  130. package/share/messages/README.md +26 -0
  131. package/lib/compiler/definer.js +0 -2340
  132. package/lib/compiler/resolver.js +0 -2988
  133. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -4,20 +4,21 @@
4
4
 
5
5
  const { getResultingName } = require('../../model/csnUtils');
6
6
  const { smartId, delimitedId } = require('../../sql-identifier');
7
+ const { ModelError } = require('../../base/error');
7
8
 
8
9
  /**
9
10
  * Render a given referential constraint as part of a SQL CREATE TABLE statement, or as .hdbconstraint artefact.
10
11
  *
11
12
  * @param {CSN.ReferentialConstraint} constraint Content of the constraint
12
13
  * @param {string} indent Indent to render the SQL with
13
- * @param {boolean} toUpperCase Wether to uppercase the identifier
14
+ * @param {boolean} toUpperCase Whether to uppercase the identifier
14
15
  * @param {CSN.Model} csn CSN
15
16
  * @param {CSN.Options} options is needed for the naming mode and the sql dialect
16
- * @param {boolean} alterConstraint whether the constraint should be rendered as part of an ALTER TABLE statement
17
+ * @param {boolean} [alterConstraint=false] whether the constraint should be rendered as part of an ALTER TABLE statement
17
18
  *
18
19
  * @returns {string} SQL statement which can be used to create the referential constraint on the db.
19
20
  */
20
- function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint) {
21
+ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint = false) {
21
22
  let quoteId;
22
23
  // for to.hana we can't utilize the sql identifier utils
23
24
  if (options.transformation === 'hdbcds') {
@@ -51,14 +52,16 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
51
52
  if (!alterConstraint) {
52
53
  result += `${indent}FOREIGN KEY(${constraint.foreignKey.map(quoteId).join(', ')})\n`;
53
54
  result += `${indent}REFERENCES ${quoteId(getResultingName(csn, names, constraint.parentTable))}(${constraint.parentKey.map(quoteId).join(', ')})\n`;
55
+ const onDeleteRemark = constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : '';
56
+
54
57
  // omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
55
58
  if (forSqlite) {
56
59
  if (constraint.onDelete === 'CASCADE' )
57
- result += `${indent}ON DELETE ${constraint.onDelete}${constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : ''}\n`;
60
+ result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;
58
61
  }
59
62
  else {
60
63
  result += `${indent}ON UPDATE RESTRICT\n`;
61
- result += `${indent}ON DELETE ${constraint.onDelete}${constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : ''}\n`;
64
+ result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;
62
65
  }
63
66
  }
64
67
  // constraint enforcement / validation must be switched off using sqlite pragma statement
@@ -119,7 +122,7 @@ function getIdentifierUtils(options) {
119
122
  function prepareIdentifier(name) {
120
123
  // Sanity check
121
124
  if (options.toSql.dialect === 'sqlite' && options.toSql.names !== 'plain')
122
- throw new Error(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
125
+ throw new ModelError(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
123
126
 
124
127
 
125
128
  switch (options.toSql.names) {
@@ -130,7 +133,7 @@ function getIdentifierUtils(options) {
130
133
  case 'hdbcds':
131
134
  return name;
132
135
  default:
133
- throw new Error(`No matching rendering found for naming mode ${options.toSql.names}`);
136
+ throw new ModelError(`No matching rendering found for naming mode ${options.toSql.names}`);
134
137
  }
135
138
  }
136
139
  }
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+
3
+ const controlCharacters = /[\u{0000}-\u{001F}]/u;
4
+ const highSurrogate = /[\u{D800}-\u{DBFF}]/u;
5
+ const lowSurrogate = /[\u{DC00}-\u{DFFF}]/u;
6
+ // Either a high surrogate that is NOT followed by a low one or
7
+ // a low surrogate not preceded by a high one.
8
+ const unpairedSurrogate = /[^\u{D800}-\u{DBFF}][\u{DC00}-\u{DFFF}]|[\u{D800}-\u{DBFF}][^\u{DC00}-\u{DFFF}]/u;
9
+
10
+ /**
11
+ * Returns true if the string contains an unpaired unicode surrogate.
12
+ * See <https://en.wikipedia.org/wiki/UTF-16#U+D800_to_U+DFFF>.
13
+ * As a surrogate pair MUST consist of a high one followed by a low surrogate,
14
+ * an unpaired surrogate MUST be escaped.
15
+ *
16
+ * @param {string} str
17
+ * @return {boolean}
18
+ */
19
+ function hasUnpairedUnicodeSurrogate(str) {
20
+ return unpairedSurrogate.test(str);
21
+ }
22
+
23
+ /**
24
+ * Returns true if the string contains control characters such as LF or NUL.
25
+ *
26
+ * @param {string} str
27
+ * @return {boolean}
28
+ */
29
+ function hasControlCharacters(str) {
30
+ return controlCharacters.test(str);
31
+ }
32
+
33
+ /**
34
+ * Escape the given string according to the given specification in `escapes`.
35
+ *
36
+ * `escapes` is an object where the entries are either:
37
+ * - a mapping from character to string, e.g. `{ '"': '&quot;' }`
38
+ * - `control: (codePoint) => str`
39
+ * A function that returns an escape sequence for the given control character.
40
+ * - `unpairedSurrogate: (codePoint) => str`
41
+ * A function that returns an escape sequence for the given unpaired unicode surrogate.
42
+ *
43
+ * Multi-character keys are not allowed.
44
+ *
45
+ * Character escapes take precedence over `control` and `unpairedSurrogate` escapes,
46
+ * i.e. if you do not want to encode LF (`\n`), add an explicit mapping for it, e.g.
47
+ * `{ '\n': '\n' }`.
48
+ *
49
+ * @example
50
+ * You can use `escapeString()` like this:
51
+ * ```js
52
+ * let escaped = escapeString(str, {
53
+ * '"': '\\"',
54
+ * control: (c) => `\\u{${c.toString(16)}}`;
55
+ * unpairedSurrogate: (c) => `\\u{${c.toString(16)}}`;
56
+ * });
57
+ * ```
58
+ *
59
+ * @param {string} str
60
+ * @param {object} escapes
61
+ * @returns {string}
62
+ */
63
+ function escapeString(str, escapes) {
64
+ const output = [];
65
+
66
+ for (let i = 0; i < str.length; ++i) {
67
+ const char = str[i];
68
+
69
+ if (char in escapes) {
70
+ output.push(escapes[char]);
71
+ continue;
72
+ }
73
+
74
+ // Control Characters: C0
75
+ // See <https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Basic_ASCII_control_codes>
76
+ if (controlCharacters.test(char)) {
77
+ output.push(escapes.control ? escapes.control(char.codePointAt(0)) : char);
78
+ continue;
79
+ }
80
+
81
+ // Unicode Surrogates
82
+ // These characters appear in _pairs_. A high surrogate must be followed by a low surrogate.
83
+ // If this is not the case, either needs to be encoded. This is also done by JSON.
84
+ // See also <https://docs.microsoft.com/en-us/globalization/encoding/surrogate-pairs>
85
+ if (highSurrogate.test(char)) {
86
+ if (i + 1 >= str.length || !lowSurrogate.test(str[i + 1])) {
87
+ output.push(escapes.unpairedSurrogate ? escapes.unpairedSurrogate(char.codePointAt(0)) : char);
88
+ }
89
+ else {
90
+ output.push(char);
91
+ ++i;
92
+ output.push(str[i]);
93
+ }
94
+ }
95
+ else if (lowSurrogate.test(char)) {
96
+ output.push(escapes.unpairedSurrogate ? escapes.unpairedSurrogate(char.codePointAt(0)) : char);
97
+ }
98
+ else {
99
+ // unhandled / non-special character
100
+ output.push(char);
101
+ }
102
+ }
103
+
104
+ return output.join('');
105
+ }
106
+
107
+ module.exports = {
108
+ escapeString,
109
+ hasUnpairedUnicodeSurrogate,
110
+ hasControlCharacters,
111
+ };
@@ -9,7 +9,7 @@
9
9
  // an identifier such that the effective name (the one in the DB schema) is
10
10
  // the same as the name used in CDS.
11
11
  // - 'quoted' and 'hdbcds' for HANA only: similar to the non-provided previous
12
- // mode, with different adoptations to HANA CDS and XS (classic) restrictions.
12
+ // mode, with different adaptations to HANA CDS and XS (classic) restrictions.
13
13
 
14
14
  // The main objective of this file is to support the 'plain' mode in a “smart”
15
15
  // manner. If we would use the CDS name (after `.` to `_` replacements)
@@ -0,0 +1,5 @@
1
+ {
2
+ "rules": {
3
+ "no-shadow": "off"
4
+ }
5
+ }
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  function isAlreadyBraced(expression, start, end){
4
- const isBraced = start - 1 > -1 && end + 1 < expression.length && expression[start-1] === '(' && expression[end+1] === ')';
5
- return isBraced;
4
+ return start - 1 > -1 &&
5
+ end + 1 < expression.length &&
6
+ expression[start-1] === '(' &&
7
+ expression[end+1] === ')';
6
8
  }
7
9
 
8
10
  function binarycomparison(expression, token, index){
@@ -13,6 +13,8 @@
13
13
  // Don't enforce stupid descriptions
14
14
  "jsdoc/require-param-description": "off",
15
15
  "jsdoc/require-returns-description": "off",
16
+ // Sometimes if-else's are more specific
17
+ "sonarjs/prefer-single-boolean-return": "off",
16
18
  // Very whiny and nitpicky
17
19
  "sonarjs/cognitive-complexity": "off",
18
20
  // Does not recognize TS types
@@ -18,12 +18,11 @@ const { setProp } = require('../../base/model');
18
18
  * @param {string} prop The property of parent to start at
19
19
  * @param {object} customTransformers Map of prop to transform and function to apply
20
20
  * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
21
- * @param {boolean} [skipIgnore=true] Wether to skip _ignore elements or not
22
- * @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
21
+ * @param {applyTransformationsOptions} [options={}]
23
22
  * @param {CSN.Path} path Path to parent
24
23
  * @returns {object} parent with transformations applied
25
24
  */
26
- function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers, skipIgnore, options, path = []) {
25
+ function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers, options, path = []) {
27
26
  const transformers = {
28
27
  elements: dictionary,
29
28
  definitions: dictionary,
@@ -32,6 +31,7 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
32
31
  enum: dictionary,
33
32
  mixin: dictionary,
34
33
  ref: pathRef,
34
+ $origin: () => {}, // no-op
35
35
  };
36
36
 
37
37
  const csnPath = [ ...path ];
@@ -50,7 +50,12 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
50
50
  * @param {object} node The value of node[_prop]
51
51
  */
52
52
  function standard( _parent, _prop, node ) {
53
- if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( _parent, _prop ) || (typeof _prop === 'string' && _prop.startsWith('@')) || (skipIgnore && node._ignore))
53
+ if (!node || typeof node !== 'object' ||
54
+ !{}.propertyIsEnumerable.call( _parent, _prop ) ||
55
+ (typeof _prop === 'string' && _prop.startsWith('@')) ||
56
+ (options.skipIgnore && node._ignore) ||
57
+ (options.skipStandard && options.skipStandard[_prop])
58
+ )
54
59
  return;
55
60
 
56
61
  csnPath.push( _prop );
@@ -80,7 +85,7 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
80
85
  */
81
86
  function dictionary( node, _prop, dict ) {
82
87
  // Allow skipping dicts like actions in forHanaNew
83
- if (options.skipDict && options.skipDict[_prop])
88
+ if (options.skipDict && options.skipDict[_prop] || dict === null || dict === undefined) // with universal CSN, dicts might be null
84
89
  return;
85
90
  csnPath.push( _prop );
86
91
  for (const name of Object.getOwnPropertyNames( dict ))
@@ -101,7 +106,10 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
101
106
  function definitions( node, _prop, dict ) {
102
107
  csnPath.push( _prop );
103
108
  for (const name of Object.getOwnPropertyNames( dict )) {
104
- const skip = options && options.skipArtifact && options.skipArtifact(dict[name], name) || false;
109
+ const skip = (options && options.allowArtifact && !options.allowArtifact(dict[name], name)) ||
110
+ (options && options.skipArtifact && options.skipArtifact(dict[name], name)) ||
111
+ (options && options.skip && options.skip.includes(dict[name].kind)) ||
112
+ false;
105
113
  if (!skip) {
106
114
  artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
107
115
  standard( dict, name, dict[name] );
@@ -152,13 +160,15 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
152
160
  * @param {object} csn CSN to enrich in-place
153
161
  * @param {object} customTransformers Map of _prop to transform and function to apply
154
162
  * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
155
- * @param {boolean} [skipIgnore=true] Wether to skip _ignore elements or not
156
- * @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
163
+ * @param {applyTransformationsOptions} [options={}]
157
164
  * @returns {object} CSN with transformations applied
158
165
  */
159
- function applyTransformations( csn, customTransformers = {}, artifactTransformers = [], skipIgnore = true, options = {} ) {
166
+ function applyTransformations( csn, customTransformers = {}, artifactTransformers = [], options = { } ) {
167
+ if (options.skipIgnore === undefined)
168
+ options.skipIgnore = true;
169
+
160
170
  if (csn && csn.definitions)
161
- return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, skipIgnore, options);
171
+ return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, options);
162
172
  return csn;
163
173
  }
164
174
 
@@ -176,14 +186,27 @@ function applyTransformations( csn, customTransformers = {}, artifactTransformer
176
186
  * @param {object} parent The "parent" of which we transform a property of
177
187
  * @param {string} prop The property of parent to start at
178
188
  * @param {object} customTransformers Map of prop to transform and function to apply
189
+ * @param {applyTransformationsOptions} [options={}]
179
190
  * @param {CSN.Path} path Path pointing to parent
180
191
  * @returns {object} parent[prop] with transformations applied
181
192
  */
182
- function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, path = []) {
183
- return applyTransformationsInternal(parent, prop, customTransformers, [], true, {}, path)[prop];
193
+ function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, options = {}, path = []) {
194
+ return applyTransformationsInternal(parent, prop, customTransformers, [], options, path)[prop];
184
195
  }
185
196
 
186
197
  module.exports = {
187
198
  applyTransformations,
188
199
  applyTransformationsOnNonDictionary,
189
200
  };
201
+
202
+
203
+ /**
204
+ * @typedef {object} applyTransformationsOptions
205
+ * @property {(artifact, name) => boolean} [allowArtifact] to only allow certain artifacts
206
+ * @property {(artifact, name) => boolean} [skipArtifact] to skip certain artifacts
207
+ * @property {boolean} [drillRef] whether to drill into infix/args
208
+ * @property {string[]} [skip] skip definitions from certain kind
209
+ * @property {object} [skipStandard] stop drill-down on certain "standard" props
210
+ * @property {object} [skipDict] stop drill-down on certain "dictionary" props
211
+ * @property {boolean} [skipIgnore=true] Whether to skip _ignore elements or not
212
+ */
@@ -158,7 +158,7 @@ function processAssertUnique(csn, options, error, info) {
158
158
  function toRef(val) {
159
159
  let ref = val.split('.');
160
160
  const [ head, ...tail ] = ref;
161
- if ([ '$self', '$projection' ].includes(head))
161
+ if (head === '$self' || head === '$projection')
162
162
  ref = tail;
163
163
  return {
164
164
  ref: ref.map((ps) => {
@@ -0,0 +1,187 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ applyTransformationsOnNonDictionary,
5
+ applyTransformations,
6
+ getUtils,
7
+ } = require('../../model/csnUtils');
8
+
9
+
10
+ /**
11
+ * In all .elements of entities and views (and their bound actions/functions), create the on-condition for
12
+ * a managed associations. This needs to happen after the .keys are expanded and the corresponding elements are created.
13
+ *
14
+ * @param {CSN.Model} csn
15
+ * @param {string} pathDelimiter
16
+ * @returns {CSN.Model} Return the input csn, with the transformations applied
17
+ */
18
+ function attachOnConditions(csn, pathDelimiter) {
19
+ const {
20
+ isManagedAssociation,
21
+ } = getUtils(csn);
22
+
23
+ const alreadyHandled = new WeakMap();
24
+ applyTransformations(csn, {
25
+ elements: (parent, prop, elements) => {
26
+ for (const elemName in elements) {
27
+ const elem = elements[elemName];
28
+ // (140) Generate the ON-condition for managed associations
29
+ if (isManagedAssociation(elem))
30
+ transformManagedAssociation(elem, elemName);
31
+ }
32
+ }, /* only for views and entities */
33
+ }, [], { skipIgnore: false, allowArtifact: artifact => (artifact.kind === 'entity') });
34
+
35
+ return csn;
36
+
37
+ /**
38
+ * Create the foreign key elements for a managed association and build the on-condition
39
+ *
40
+ * @param {Object} elem The association to process
41
+ * @param {string} elemName
42
+ * @returns {void}
43
+ */
44
+ function transformManagedAssociation(elem, elemName) {
45
+ // No need to run over this - we already did, possibly because it was referenced in the ON-Condition
46
+ // of another association - see a few lines lower
47
+ if (alreadyHandled.has(elem))
48
+ return;
49
+ // Assemble an ON-condition with the foreign keys created in earlier steps
50
+ const onCondParts = [];
51
+ let joinWithAnd = false;
52
+ if (elem.keys.length === 0) { // TODO: really kill instead of _ignore?
53
+ elem._ignore = true;
54
+ }
55
+ else {
56
+ for (const foreignKey of elem.keys) {
57
+ // Assemble left hand side of 'assoc.key = fkey'
58
+ const assocKeyArg = {
59
+ ref: [
60
+ elemName,
61
+ ].concat(foreignKey.ref),
62
+ };
63
+ const fkName = `${elemName}${pathDelimiter}${foreignKey.as}`;
64
+ const fKeyArg = {
65
+ ref: [
66
+ fkName,
67
+ ],
68
+ };
69
+
70
+ if (joinWithAnd) { // more than one FK
71
+ onCondParts.push('and');
72
+ }
73
+
74
+ onCondParts.push(
75
+ assocKeyArg
76
+ );
77
+ onCondParts.push('=');
78
+ onCondParts.push(fKeyArg);
79
+
80
+ if (!joinWithAnd)
81
+ joinWithAnd = true;
82
+ }
83
+ elem.on = onCondParts;
84
+ }
85
+
86
+ // If the managed association has a 'key' property => remove it as unmanaged assocs cannot be keys
87
+ // TODO: Are there other modifiers (like 'key') that are valid for managed, but not valid for unmanaged assocs?
88
+ if (elem.key)
89
+ delete elem.key;
90
+
91
+ // If the managed association has a 'not null' property => remove it
92
+ if (elem.notNull)
93
+ delete elem.notNull;
94
+
95
+ // The association is now unmanaged, i.e. actually it should no longer have foreign keys
96
+ // at all. But the processing of backlink associations below expects to have them, so
97
+ // we don't delete them
98
+ // TODO: maybe make non-enumerable, so we become recompilable in the future?
99
+
100
+ // Remember that we already processed this
101
+ alreadyHandled.set(elem, true);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * @param {CSN.Model} csn
107
+ * @param {string} pathDelimiter
108
+ * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
109
+ */
110
+ function getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
111
+ const {
112
+ inspectRef,
113
+ } = getUtils(csn);
114
+
115
+ return handleManagedAssocStepsInOnCondition;
116
+
117
+ /**
118
+ * Loop over all elements and for all unmanaged associations translate
119
+ * <assoc base>.<managed assoc>.<fk> to <assoc base>.<managed assoc>_<fk>
120
+ *
121
+ * Or in other words: Allow using the foreign keys of managed associations in on-conditions
122
+ *
123
+ * @param {CSN.Artifact} artifact Artifact to check
124
+ * @param {string} artifactName Name of the artifact
125
+ */
126
+ function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
127
+ for (const elemName in artifact.elements) {
128
+ const elem = artifact.elements[elemName];
129
+ // The association is an unmanaged on
130
+ if (!elem.keys && elem.target && elem.on) {
131
+ applyTransformationsOnNonDictionary(elem, 'on', {
132
+ ref: (refOwner, prop, ref, path) => {
133
+ // [<assoc base>.]<managed assoc>.<field>
134
+ if (ref.length > 1) {
135
+ const { links } = inspectRef(path);
136
+ if (links) {
137
+ // eslint-disable-next-line for-direction
138
+ for (let i = links.length - 1; i >= 0; i--) {
139
+ const link = links[i];
140
+ // We found the latest managed assoc path step
141
+ if (link.art && link.art.target && link.art.keys &&
142
+ // Doesn't work when ref-target (filter condition) or similar is used
143
+ !ref.slice(i).some(refElement => typeof refElement !== 'string')) {
144
+ // We join the managed assoc with everything following it
145
+ const sourceElementName = ref.slice(i).join(pathDelimiter);
146
+ const source = findSource(links, i - 1) || artifact;
147
+ // allow specifying managed assoc on the source side
148
+ const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
149
+ if (fks && fks.length >= 1) {
150
+ const fk = fks[0];
151
+ const managedAssocStepName = refOwner.ref[i];
152
+ const fkName = `${managedAssocStepName}${pathDelimiter}${fk.as}`;
153
+ if (source && source.elements[fkName])
154
+ refOwner.ref = [ ...ref.slice(0, i), fkName ];
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+ },
161
+ }, {}, [ 'definitions', artifactName, 'elements', elemName ]);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Find out where the managed association is
167
+ *
168
+ * @param {Array} links
169
+ * @param {number} startIndex
170
+ * @returns {Object| undefined} CSN definition of the source of the managed association
171
+ */
172
+ function findSource(links, startIndex) {
173
+ for (let i = startIndex; i >= 0; i--) {
174
+ const link = links[i];
175
+ // We found the latest assoc step - now check where that points to
176
+ if (link.art && link.art.target)
177
+ return csn.definitions[link.art.target];
178
+ }
179
+
180
+ return undefined;
181
+ }
182
+ }
183
+ }
184
+ module.exports = {
185
+ attachOnConditions,
186
+ getManagedAssocStepsInOnConditionFinalizer,
187
+ };
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ forEachGeneric, forEachMemberRecursively, hasAnnotationValue, isPersistedOnDatabase,
5
+ getUtils,
6
+ } = require('../../model/csnUtils');
7
+ const transformUtils = require('../transformUtilsNew');
8
+
9
+ const exists = '@cds.persistence.exists';
10
+
11
+ /**
12
+ * Return a callback function for forEachDefinition that marks artifacts that are abstract or @cds.persistence.exists/skip
13
+ * with _ignore.
14
+ *
15
+ * @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
16
+ */
17
+ function getAnnoProcessor() {
18
+ return handleCdsPersistence;
19
+ /**
20
+ * @param {CSN.Artifact} artifact
21
+ */
22
+ function handleCdsPersistence(artifact) {
23
+ const ignoreArtifact = (artifact.kind === 'entity') &&
24
+ (artifact.abstract ||
25
+ hasAnnotationValue(artifact, '@cds.persistence.skip') ||
26
+ hasAnnotationValue(artifact, exists));
27
+ if (ignoreArtifact)
28
+ artifact._ignore = true;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Return a callback function for forEachDefinition that marks associations with _ignore
34
+ * if their target does not reach the database, i.e. marked with @cds.persistence.skip or is abstract
35
+ *
36
+ * @param {CSN.Model} csn
37
+ * @param {CSN.Options} options
38
+ * @param {object} messageFunctions
39
+ * @param {Function} messageFunctions.info
40
+ * @returns {(artifact: CSN.Artifact, artifactName: string, prop: string, path: CSN.Path) => void} Callback function for forEachDefinition
41
+ */
42
+ function getAssocToSkippedIgnorer(csn, options, messageFunctions) {
43
+ const { info } = messageFunctions;
44
+ const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
45
+
46
+ const { isAssocOrComposition } = getUtils(csn);
47
+
48
+ return ignoreAssociationToSkippedTarget;
49
+ /**
50
+ * Associations that target a @cds.persistence.skip artifact must be removed
51
+ * from the persistence model
52
+ *
53
+ * @param {CSN.Artifact} artifact
54
+ * @param {string} artifactName
55
+ * @param {string} prop
56
+ * @param {CSN.Path} path
57
+ */
58
+ function ignoreAssociationToSkippedTarget(artifact, artifactName, prop, path) {
59
+ if (isPersistedOnDatabase(artifact)) {
60
+ // TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
61
+ if (artifact.query) {
62
+ // If we do A2J, we don't need to check the mixin. Either it is used -> a join
63
+ // or published -> handled via elements/members. Unused mixins are removed anyway.
64
+ if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
65
+ forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
66
+
67
+ else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
68
+ forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
69
+ }
70
+ forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Mark the given member with _ignore if it is an association/composition and it's target is unreachable.
76
+ *
77
+ * @param {CSN.Element} member
78
+ * @param {string} memberName
79
+ * @param {string} prop
80
+ * @param {CSN.Path} path
81
+ * @todo Why do we check for @cds.persistence.exists here if the parent-function only calls this for skip/abstract?
82
+ */
83
+ function ignore(member, memberName, prop, path) {
84
+ if (options.sqlDialect === 'hana' && !member._ignore && member.target && isAssocOrComposition(member.type) && isUnreachableAssociationTarget(csn.definitions[member.target])) {
85
+ const targetAnnotation = hasAnnotationValue(csn.definitions[member.target], exists) ? exists : '@cds.persistence.skip';
86
+ info(null, path,
87
+ { target: member.target, anno: targetAnnotation },
88
+ 'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)');
89
+ member._ignore = true;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Check whether the given artifact is an unreachable association target because it will not "realy" hit the database:
95
+ * - @cds.persistence.skip/exists
96
+ * - abstract
97
+ *
98
+ * @param {CSN.Artifact} art
99
+ * @returns {boolean}
100
+ */
101
+ function isUnreachableAssociationTarget(art) {
102
+ return !isPersistedOnDatabase(art) || hasAnnotationValue(art, exists);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Return a callback function for forEachDefinition that handles artifacts marked with @cds.persistence.table.
108
+ * If a .query artifact has this annotation, the .query will be deleted and it will be treated like a table.
109
+ *
110
+ * @param {CSN.Model} csn
111
+ * @param {CSN.Options} options
112
+ * @param {object} messageFunctions
113
+ * @param {Function} messageFunctions.error
114
+ * @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
115
+ */
116
+ function getPersistenceTableProcessor(csn, options, messageFunctions ) {
117
+ const { error } = messageFunctions;
118
+ const {
119
+ recurseElements,
120
+ } = transformUtils.getTransformers(csn, options, '_');
121
+
122
+ return handleQueryish;
123
+
124
+
125
+ /**
126
+ * @param {CSN.Artifact} artifact
127
+ * @param {string} artifactName
128
+ */
129
+ function handleQueryish(artifact, artifactName) {
130
+ const stripQueryish = artifact.query && hasAnnotationValue(artifact, '@cds.persistence.table');
131
+
132
+ if (stripQueryish) {
133
+ artifact.kind = 'entity';
134
+ delete artifact.query;
135
+
136
+ recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
137
+ // All elements must have a type for this to work
138
+ if (!member._ignore && !member.kind && !member.type)
139
+ error(null, path, 'Expecting element to have a type if view is annotated with “@cds.persistence.table“');
140
+ });
141
+ }
142
+ }
143
+ }
144
+
145
+
146
+ module.exports = {
147
+ getAnnoProcessor,
148
+ getAssocToSkippedIgnorer,
149
+ getPersistenceTableProcessor,
150
+ };