@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
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { ModelError } = require('../../base/error');
4
+
3
5
  /**
4
6
  * Replace (formerly) managed association in a GROUP BY/ORDER BY with its foreign keys.
5
7
  *
@@ -89,7 +91,7 @@ function replaceAssociationsInGroupByOrderBy(inputQuery, options, inspectRef, er
89
91
  function getForeignKeyRefs(assoc) {
90
92
  return assoc.keys.map((fk) => {
91
93
  if (!fk.$generatedFieldName)
92
- throw new Error(`Expecting generated field name for foreign key: ${JSON.stringify(fk)}`);
94
+ throw new ModelError(`Expecting generated field name for foreign key: ${JSON.stringify(fk)}`);
93
95
 
94
96
  return { ref: [ fk.$generatedFieldName ] };
95
97
  });
@@ -0,0 +1,236 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ getUtils, getNormalizedQuery, hasAnnotationValue, forEachMember,
5
+ } = require('../../model/csnUtils');
6
+ const { implicitAs } = require('../../model/csnRefs');
7
+ const { setProp } = require('../../base/model');
8
+ const { getTransformers } = require('../transformUtilsNew');
9
+
10
+ const validToString = '@cds.valid.to';
11
+ const validFromString = '@cds.valid.from';
12
+ /**
13
+ * Get the forEachDefinition callback function that adds a where condition to views that
14
+ * - are annotated with @cds.valid.from and @cds.valid.to,
15
+ * - have only one @cds.valid.from and @cds.valid.to,
16
+ * - and both annotations come from the same entity
17
+ *
18
+ * If the view has one of the annotations but the other conditions are not met, an error will be raised.
19
+ *
20
+ * @param {CSN.Model} csn
21
+ * @param {object} messageFunctions
22
+ * @param {Function} messageFunctions.info
23
+ * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition applying the where-condition to views.
24
+ */
25
+ function getViewDecorator(csn, messageFunctions) {
26
+ const { info } = messageFunctions;
27
+ const { get$combined } = getUtils(csn);
28
+ return addTemporalWhereConditionToView;
29
+ /**
30
+ * Add a where condition to views that
31
+ * - are annotated with @cds.valid.from and @cds.valid.to,
32
+ * - have only one @cds.valid.from and @cds.valid.to,
33
+ * - and both annotations come from the same entity
34
+ *
35
+ * If the view has one of the annotations but the other conditions are not met, an error will be raised.
36
+ *
37
+ * @param {CSN.Artifact} artifact
38
+ * @param {string} artifactName
39
+ */
40
+ function addTemporalWhereConditionToView(artifact, artifactName) {
41
+ const normalizedQuery = getNormalizedQuery(artifact);
42
+ if (normalizedQuery && normalizedQuery.query && normalizedQuery.query.SELECT) {
43
+ // BLOCKER: We need information to handle $combined
44
+ // What we are trying to achieve by this:
45
+ // Forbid joining/selecting from two or more temporal entities
46
+ // Idea: Follow the query-tree and check each from
47
+ // Collect all source-entities and compute our own $combined
48
+ const $combined = get$combined(normalizedQuery.query);
49
+ const [ from, to ] = getFromToElements($combined);
50
+ // exactly one validFrom & validTo
51
+ if (from.length === 1 && to.length === 1) {
52
+ // and both are from the same origin
53
+ if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
54
+ if (!hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0])) {
55
+ const fromPath = {
56
+ ref: [
57
+ from[0].parent,
58
+ from[0].name,
59
+ ],
60
+ };
61
+
62
+ const toPath = {
63
+ ref: [
64
+ to[0].parent,
65
+ to[0].name,
66
+ ],
67
+ };
68
+
69
+
70
+ const atFrom = { ref: [ '$at', 'from' ] };
71
+ const atTo = { ref: [ '$at', 'to' ] };
72
+
73
+ const cond = [ '(', fromPath, '<', atTo, 'and', toPath, '>', atFrom, ')' ];
74
+
75
+ if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
76
+ normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
77
+ }
78
+ else {
79
+ normalizedQuery.query.SELECT.where = cond;
80
+ }
81
+ }
82
+ }
83
+ else {
84
+ info(null, [ 'definitions', artifactName ], `No temporal WHERE clause added as "${from[0].error_parent}"."${from[0].name}" and "${to[0].error_parent}"."${to[0].name}" are not of same origin`);
85
+ }
86
+ }
87
+ else if (from.length > 0 || to.length > 0) {
88
+ const missingAnnotation = from.length > to.length ? validToString : validFromString;
89
+ info(null, [ 'definitions', artifactName ],
90
+ { anno: missingAnnotation },
91
+ 'No temporal WHERE clause added because $(ANNO) is missing');
92
+ }
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Get all elements tagged with @cds.valid.from/to from the union of all entities of the from-clause.
98
+ *
99
+ * @param {any} combined union of all entities of the from-clause
100
+ * @returns {Array[]} Array where first field is array of elements with @cds.valid.from, second field is array of elements with @cds.valid.to.
101
+ */
102
+ function getFromToElements(combined) {
103
+ const from = [];
104
+ const to = [];
105
+ for (const name in combined) {
106
+ let elt = combined[name];
107
+ if (!Array.isArray(elt))
108
+ elt = [ elt ];
109
+ elt.forEach((e) => {
110
+ if (hasAnnotationValue(e.element, validFromString))
111
+ from.push(e);
112
+
113
+ if (hasAnnotationValue(e.element, validToString))
114
+ to.push(e);
115
+ });
116
+ }
117
+
118
+ return [ from, to ];
119
+ }
120
+
121
+ /**
122
+ * Check if the given SELECT has a falsy @cds.valid.from and a falsy @cds.valid.to
123
+ *
124
+ * @param {CSN.QuerySelect} SELECT
125
+ * @param {CSN.Elements} elements
126
+ * @param {object} from
127
+ * @param {object} to
128
+ * @returns {boolean} True if both are present and false.
129
+ */
130
+ function hasFalsyTemporalAnnotations(SELECT, elements, from, to) {
131
+ let fromElement = elements[from.name];
132
+ let toElement = elements[to.name];
133
+
134
+ if (SELECT.columns) {
135
+ for (const col of SELECT.columns) {
136
+ if (col.ref) {
137
+ const implicitAlias = implicitAs(col.ref);
138
+ if (implicitAlias === from.name)
139
+ fromElement = elements[col.as || implicitAlias];
140
+ else if (implicitAlias === to.name)
141
+ toElement = elements[col.as || implicitAlias];
142
+ }
143
+ }
144
+ }
145
+ return fromElement && toElement &&
146
+ hasAnnotationValue(fromElement, validFromString, false) &&
147
+ hasAnnotationValue(toElement, validToString, false);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Get the forEachDefinition callback function that collects all usages of @cds.valid.from/to/key and checks that
153
+ * - the assignment is on a valid element
154
+ * - the annotation is only assigned once
155
+ * - key is only used in conjunction with from and to
156
+ *
157
+ * Furthermore, @cds.valid.from and @cds.valid.key is processed - @cds.valid.from is marked as key or marked as unique if @cds.valid.key is used.
158
+ * If @cds.valid.key is used, the real key-elements have their key-property removed (set non-enumerable as $key) and instead the @cds.valid.key-marked elements have it added.
159
+ *
160
+ * @param {CSN.Model} csn
161
+ * @param {CSN.Options} options
162
+ * @param {string} pathDelimiter
163
+ * @param {object} messageFunctions
164
+ * @param {Function} messageFunctions.error
165
+ * @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition processing the annotations.
166
+ */
167
+ function getAnnotationHandler(csn, options, pathDelimiter, messageFunctions) {
168
+ const { error } = messageFunctions;
169
+ const {
170
+ extractValidFromToKeyElement, checkAssignment, checkMultipleAssignments, recurseElements,
171
+ } = getTransformers(csn, options, pathDelimiter);
172
+
173
+ return handleTemporalAnnotations;
174
+ /**
175
+ * @param {CSN.Artifact} artifact
176
+ * @param {string} artifactName
177
+ */
178
+ function handleTemporalAnnotations(artifact, artifactName) {
179
+ const validFrom = [];
180
+ const validTo = [];
181
+ const validKey = [];
182
+
183
+ recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
184
+ const [ f, t, k ] = extractValidFromToKeyElement(member, path);
185
+ validFrom.push(...f);
186
+ validTo.push(...t);
187
+ validKey.push(...k);
188
+ });
189
+
190
+ if (artifact.kind === 'entity' && !artifact.query) {
191
+ validFrom.forEach(obj => checkAssignment(validFromString, obj.element, obj.path, artifact));
192
+ validTo.forEach(obj => checkAssignment(validToString, obj.element, obj.path, artifact));
193
+ validKey.forEach(obj => checkAssignment('@cds.valid.key', obj.element, obj.path, artifact));
194
+ checkMultipleAssignments(validFrom, validFromString, artifact, artifactName);
195
+ checkMultipleAssignments(validTo, validToString, artifact, artifactName, true);
196
+ checkMultipleAssignments(validKey, '@cds.valid.key', artifact, artifactName);
197
+ }
198
+
199
+ // if there is an cds.valid.key, make this the only primary key
200
+ // otherwise add all cds.valid.from to primary key tuple
201
+ if (validKey.length) {
202
+ if (!validFrom.length || !validTo.length) {
203
+ error(null, [ 'definitions', artifactName ],
204
+ 'Expecting “@cds.valid.from” and “@cds.valid.to” if “@cds.valid.key” is used');
205
+ }
206
+
207
+ forEachMember(artifact, (member) => {
208
+ if (member.key) {
209
+ member.unique = true;
210
+ delete member.key;
211
+ // Remember that this element was a key in the original artifact.
212
+ // This is needed for localized convenience view generation.
213
+ setProp(member, '$key', true);
214
+ }
215
+ });
216
+ validKey.forEach((member) => {
217
+ member.element.key = true;
218
+ });
219
+
220
+ validFrom.forEach((member) => {
221
+ member.element.unique = true;
222
+ });
223
+ }
224
+ else {
225
+ validFrom.forEach((member) => {
226
+ member.element.key = true;
227
+ });
228
+ }
229
+ }
230
+ }
231
+
232
+
233
+ module.exports = {
234
+ getViewDecorator,
235
+ getAnnotationHandler,
236
+ };
@@ -4,6 +4,7 @@ const { forAllQueries, forEachDefinition, walkCsnPath } = require('../../model/c
4
4
  const { setProp } = require('../../base/model');
5
5
  const { getRealName } = require('../../render/utils/common');
6
6
  const { csnRefs } = require('../../model/csnRefs');
7
+ const { ModelError } = require('../../base/error');
7
8
 
8
9
  /**
9
10
  * Turn a `exists assoc[filter = 100]` into a `exists (select 1 as dummy from assoc.target where <assoc on condition> and assoc.target.filter = 100)`.
@@ -48,7 +49,7 @@ const { csnRefs } = require('../../model/csnRefs');
48
49
  * @param {Function} error
49
50
  */
50
51
  function handleExists(csn, options, error) {
51
- const { inspectRef } = csnRefs(csn);
52
+ let { inspectRef } = csnRefs(csn);
52
53
  const generatedExists = new WeakMap();
53
54
  forEachDefinition(csn, (artifact, artifactName) => {
54
55
  if (artifact.projection) // do the same hack we do for the other stuff...
@@ -81,6 +82,8 @@ function handleExists(csn, options, error) {
81
82
  // to check for further exists
82
83
  const { result, leftovers } = processExists(queryPath, exprPath);
83
84
  walkCsnPath(csn, exprPath.slice(0, -1))[exprPath[exprPath.length - 1]] = result;
85
+ if (leftovers.length > 0)
86
+ inspectRef = csnRefs(csn).inspectRef; // Refresh caches - we need to resolve stuff in the newly created subquery
84
87
  toProcess.push(...leftovers.reverse()); // any leftovers - schedule for further processing
85
88
  }
86
89
  }
@@ -365,16 +368,23 @@ function handleExists(csn, options, error) {
365
368
  const subselect = getSubselect(root.target, ref, sources);
366
369
 
367
370
  const target = subselect.SELECT.from.as; // use subquery alias as target - prevent shadowing
368
- if (root.keys) { // managed assoc
369
- translateManagedAssocToWhere(root, target, subselect, isPrefixedWithTableAlias, base, current);
370
- }
371
- else { // unmanaged assoc
372
- translateUnmanagedAssocToWhere(root, target, subselect, isPrefixedWithTableAlias, base, current);
373
- }
371
+ const extension = root.keys ? translateManagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current) : translateUnmanagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current);
372
+ if (extension.length > 3)
373
+ subselect.SELECT.where.push('('); // add braces around the on-condition part to ensure precedence is kept
374
+
375
+ subselect.SELECT.where.push(...extension);
376
+
377
+ if (extension.length > 3)
378
+ subselect.SELECT.where.push(')');
374
379
 
375
380
  newExpr.push('exists');
376
- if (ref && ref.where)
377
- subselect.SELECT.where.push(...[ 'and', ...remapExistingWhere(target, ref.where) ]);
381
+ if (ref && ref.where) {
382
+ const remappedWhere = remapExistingWhere(target, ref.where);
383
+ if (remappedWhere.length > 3)
384
+ subselect.SELECT.where.push(...[ 'and', '(', ...remappedWhere, ')' ]);
385
+ else
386
+ subselect.SELECT.where.push(...[ 'and', ...remappedWhere ]);
387
+ }
378
388
 
379
389
  newExpr.push(subselect);
380
390
  toContinue.push([ exprPath.concat(newExpr.length - 1), exprPath.concat([ newExpr.length - 1, 'SELECT', 'where' ]) ]);
@@ -412,21 +422,24 @@ function handleExists(csn, options, error) {
412
422
  *
413
423
  * @param {CSN.Element} root
414
424
  * @param {string} target
415
- * @param {CSN.Query} subselect This subselect will in the end replace <assoc> in EXISTS <assoc>
416
425
  * @param {boolean} isPrefixedWithTableAlias
417
426
  * @param {string} base
418
427
  * @param {Token} current
428
+ * @returns {object[]} The stuff to add to the where
419
429
  */
420
- function translateManagedAssocToWhere(root, target, subselect, isPrefixedWithTableAlias, base, current) {
430
+ function translateManagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current) {
431
+ const whereExtension = [];
421
432
  for (let j = 0; j < root.keys.length; j++) {
422
433
  const lop = { ref: [ target, ...root.keys[j].ref ] }; // target side
423
434
  const rop = { ref: (isPrefixedWithTableAlias ? [] : [ base ]).concat([ ...toRawRef(current.ref), ...root.keys[j].ref ]) }; // source side
424
435
 
425
436
  if (j > 0)
426
- subselect.SELECT.where.push('and');
437
+ whereExtension.push('and');
427
438
 
428
- subselect.SELECT.where.push(...[ lop, '=', rop ]);
439
+ whereExtension.push(...[ lop, '=', rop ]);
429
440
  }
441
+
442
+ return whereExtension;
430
443
  }
431
444
 
432
445
  /**
@@ -452,12 +465,13 @@ function handleExists(csn, options, error) {
452
465
  *
453
466
  * @param {CSN.Element} root
454
467
  * @param {string} target
455
- * @param {CSN.Query} subselect This subselect will in the end replace <assoc> in EXISTS <assoc>
456
468
  * @param {boolean} isPrefixedWithTableAlias
457
469
  * @param {string} base
458
470
  * @param {Token} current
471
+ * @returns {object[]} The stuff to add to the where
459
472
  */
460
- function translateUnmanagedAssocToWhere(root, target, subselect, isPrefixedWithTableAlias, base, current) {
473
+ function translateUnmanagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current) {
474
+ const whereExtension = [];
461
475
  for (let j = 0; j < root.on.length; j++) {
462
476
  const part = root.on[j];
463
477
 
@@ -465,7 +479,7 @@ function handleExists(csn, options, error) {
465
479
  // but also keep along stuff like null and undefined, so compiler
466
480
  // can have a chance to complain/ we can fail later nicely maybe
467
481
  if (!(part && part.ref)) {
468
- subselect.SELECT.where.push(part);
482
+ whereExtension.push(part);
469
483
  continue;
470
484
  }
471
485
 
@@ -475,30 +489,82 @@ function handleExists(csn, options, error) {
475
489
  // Dollar Self Backlink
476
490
  if (isValidDollarSelf(root.on[j], root.$path.concat([ 'on', j ]), root.on[j + 1], root.on[j + 2], root.$path.concat([ 'on', j + 2 ]))) {
477
491
  if (root.on[j].ref[0] === '$self' && root.on[j].ref.length === 1)
478
- subselect.SELECT.where.push(...translateDollarSelfToWhere(base, target, root.on[j + 2], root.$path.concat([ 'on', j + 2 ])));
492
+ whereExtension.push(...translateDollarSelfToWhere(base, target, root.on[j + 2], root.$path.concat([ 'on', j + 2 ])));
479
493
  else
480
- subselect.SELECT.where.push(...translateDollarSelfToWhere(base, target, root.on[j], root.$path.concat([ 'on', j ])));
494
+ whereExtension.push(...translateDollarSelfToWhere(base, target, root.on[j], root.$path.concat([ 'on', j ])));
481
495
 
482
496
  j += 2;
483
497
  }
484
498
  else if (links && links[0].art === root) { // target side
485
- subselect.SELECT.where.push({ ref: [ target, ...part.ref.slice(1) ] });
499
+ whereExtension.push({ ref: [ target, ...part.ref.slice(1) ] });
486
500
  }
487
501
  else if (part.$scope === '$self') { // source side - "absolute" scope
488
- // cut off the $self, as we prefix the entity name now
489
- subselect.SELECT.where.push({ ref: [ base, ...part.ref.slice(1) ] });
502
+ const column = part._art._column;
503
+ if (column && column.as) { // Replace with the "original" expression (the .ref, .xpr etc.)
504
+ whereExtension.push(translateToSourceSide(column));
505
+ }
506
+ else {
507
+ whereExtension.push(assignAndDeleteAs({}, part, { ref: [ base, ...part.ref.slice(1) ] }));
508
+ }
490
509
  }
491
510
  else if (art) { // source side - with local scope
492
- if (isPrefixedWithTableAlias)
493
- subselect.SELECT.where.push({ ref: [ ...current.ref.slice(0, -1), ...part.ref ] });
511
+ if (isPrefixedWithTableAlias || part.$scope === 'alias')
512
+ whereExtension.push({ ref: [ ...current.ref.slice(0, -1), ...part.ref ] });
494
513
  else
495
- subselect.SELECT.where.push({ ref: [ base, ...current.ref.slice(0, -1), ...part.ref ] });
514
+ whereExtension.push({ ref: [ base, ...current.ref.slice(0, -1), ...part.ref ] });
496
515
  }
497
516
  else { // operator - or any other leftover
498
- subselect.SELECT.where.push(part);
517
+ whereExtension.push(part);
499
518
  }
500
519
  }
501
520
 
521
+ return whereExtension;
522
+
523
+
524
+ /**
525
+ * Run Object.assign on all of the passed in parameters and delete a .as at the end
526
+ *
527
+ * @param {...any} args
528
+ * @returns {object} The merged args without an .as property
529
+ */
530
+ function assignAndDeleteAs(...args) {
531
+ const obj = Object.assign.apply(null, args);
532
+ delete obj.as;
533
+ return obj;
534
+ }
535
+ /**
536
+ * Translate the given obj (a column-like thing) into an expression that we can use in the WHERE.
537
+ * - Strip off $self/$projection and correctly replace with source expression
538
+ * - Drill further down into .xpr
539
+ * - Correctly set table alias in front of ref
540
+ *
541
+ * @param {object} obj
542
+ * @returns {object}
543
+ */
544
+ function translateToSourceSide(obj) {
545
+ if (obj.ref) {
546
+ if (obj.$scope === '$self') { // TODO: Check with this way down, do we keep the links?
547
+ const column = obj._art._column;
548
+ if (column && column.as)
549
+ return translateToSourceSide(column);
550
+ return assignAndDeleteAs({}, obj, { ref: [ base, ...obj.ref.slice(1) ] });
551
+ }
552
+ else if (typeof obj.$env === 'string') {
553
+ return assignAndDeleteAs({}, obj, { ref: [ obj.$env, ...obj.ref ] });
554
+ }
555
+
556
+ return assignAndDeleteAs({}, obj, { ref: [ ...obj.ref ] });
557
+ }
558
+ else if (obj.xpr) { // we need to drill further down into .xpr
559
+ return assignAndDeleteAs({}, obj, { xpr: obj.xpr.map(translateToSourceSide) });
560
+ }
561
+ else if (obj.args) {
562
+ return assignAndDeleteAs({}, obj, { args: obj.args.map(translateToSourceSide) });
563
+ }
564
+
565
+ return obj;
566
+ }
567
+
502
568
  /**
503
569
  * Check that an expression triple is a valid $self
504
570
  *
@@ -571,7 +637,7 @@ function handleExists(csn, options, error) {
571
637
  }
572
638
 
573
639
  /**
574
- * Check (using inspectRef -> links), wether the first path step is an entity or query source
640
+ * Check (using inspectRef -> links), whether the first path step is an entity or query source
575
641
  *
576
642
  * @param {CSN.Path} path
577
643
  * @returns {boolean}
@@ -621,7 +687,7 @@ function handleExists(csn, options, error) {
621
687
  return error(null, xpr.$path, 'Boolean $env is not handled yet - please report this error!');
622
688
  }
623
689
  else if (xpr.ref) {
624
- throw new Error('Missing $env and missing leading artifact ref - throwing to trigger recompilation!');
690
+ throw new ModelError('Missing $env and missing leading artifact ref - throwing to trigger recompilation!');
625
691
  }
626
692
  }
627
693
 
@@ -706,7 +772,7 @@ function handleExists(csn, options, error) {
706
772
  *
707
773
  * @param {string} base The source entity/query source name
708
774
  * @param {string} target The target entity/query source name
709
- * @param {CSN.Element} assoc The association element - the "not-$self" side of the comparison
775
+ * @param {object} assoc The association element - the "not-$self" side of the comparison
710
776
  * @param {CSN.Path} path
711
777
  * @returns {TokenStream} The WHERE representing the $self comparison
712
778
  */
@@ -5,6 +5,7 @@ const {
5
5
  } = require('../../model/csnUtils');
6
6
  const { implicitAs, csnRefs } = require('../../model/csnRefs');
7
7
  const { isBetaEnabled } = require('../../base/model');
8
+ const { ModelError } = require('../../base/error');
8
9
 
9
10
  /**
10
11
  * If a mixin association is published, return the mixin association.
@@ -38,7 +39,7 @@ function getMixinAssocOfQueryIfPublished(query, association, associationName) {
38
39
  }
39
40
 
40
41
  /**
41
- * Check wether the given artifact uses the given mixin association.
42
+ * Check whether the given artifact uses the given mixin association.
42
43
  *
43
44
  * We can rely on the fact that there can be no usage starting with $self/$projection,
44
45
  * since lib/checks/selectItems.js forbids that.
@@ -182,10 +183,10 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
182
183
  const matchingCombined = $combined[elemName];
183
184
  // Internal errors - this should never happen!
184
185
  if (matchingCombined.length > 1) { // should already be caught by compiler
185
- throw new Error(`Ambiguous name - can't be resolved: ${elemName}. Found in: ${matchingCombined.map(o => o.parent)}`);
186
+ throw new ModelError(`Ambiguous name - can't be resolved: ${elemName}. Found in: ${matchingCombined.map(o => o.parent)}`);
186
187
  }
187
188
  else if (matchingCombined.length === 0) { // no clue how this could happen? Invalid CSN?
188
- throw new Error(`No matching entry found in UNION of all elements for: ${elemName}`);
189
+ throw new ModelError(`No matching entry found in UNION of all elements for: ${elemName}`);
189
190
  }
190
191
  alias = matchingCombined[0].parent;
191
192
  }
@@ -304,7 +305,7 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
304
305
  parent.ref = ref;
305
306
  return ref;
306
307
  },
307
- }, elementsPath.concat(elemName));
308
+ }, {}, elementsPath.concat(elemName));
308
309
  }
309
310
 
310
311
  if (!mixinElem._ignore)
@@ -0,0 +1,38 @@
1
+ {
2
+ "root": true,
3
+ "plugins": ["sonarjs", "jsdoc"],
4
+ "extends": ["../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended", "plugin:jsdoc/recommended"],
5
+ "rules": {
6
+ "prefer-const": "error",
7
+ "quotes": ["error", "single", "avoid-escape"],
8
+ "prefer-template": "error",
9
+ "no-trailing-spaces": "error",
10
+ "template-curly-spacing":["error", "never"],
11
+ "complexity": ["warn", 30],
12
+ "max-len": "off",
13
+ // Don't enforce stupid descriptions
14
+ "jsdoc/require-param-description": "off",
15
+ "jsdoc/require-returns-description": "off",
16
+ // Sometimes if-else's are more specific
17
+ "sonarjs/prefer-single-boolean-return": "off",
18
+ // Very whiny and nitpicky
19
+ "sonarjs/cognitive-complexity": "off",
20
+ // Does not recognize TS types
21
+ "jsdoc/no-undefined-types": "off",
22
+ // Whiny and annoying
23
+ "sonarjs/no-duplicate-string": "off"
24
+ },
25
+ "parserOptions": {
26
+ "ecmaVersion": 2018,
27
+ "sourceType": "script"
28
+ },
29
+ "env": {
30
+ "es6": true,
31
+ "node": true
32
+ },
33
+ "settings": {
34
+ "jsdoc": {
35
+ "mode": "typescript"
36
+ }
37
+ }
38
+ }
@@ -6,6 +6,7 @@ const {
6
6
  } = require('../../model/csnUtils');
7
7
  const { setProp, isDeprecatedEnabled } = require('../../base/model');
8
8
  const { getTransformers } = require('../transformUtilsNew');
9
+ const { ModelError } = require('../../base/error');
9
10
  const draftAnnotation = '@odata.draft.enabled';
10
11
  const booleanBuiltin = 'cds.Boolean';
11
12
 
@@ -38,7 +39,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
38
39
  * @param {string} artifactName
39
40
  */
40
41
  function generateDraft(artifact, artifactName) {
41
- if ((artifact.kind === 'entity' || artifact.kind === 'view') &&
42
+ if ((artifact.kind === 'entity') &&
42
43
  hasAnnotationValue(artifact, draftAnnotation) &&
43
44
  isPartOfService(artifactName)) {
44
45
  // Determine the set of target draft nodes belonging to this draft root (the draft root
@@ -79,7 +80,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
79
80
  const draftNodeName = elem.target;
80
81
  // Sanity check
81
82
  if (!draftNode)
82
- throw new Error(`Expecting target to be resolved: ${JSON.stringify(elem, null, 2)}`);
83
+ throw new ModelError(`Expecting target to be resolved: ${JSON.stringify(elem, null, 2)}`);
83
84
 
84
85
  // Ignore composition if not part of a service
85
86
  if (!isPartOfService(draftNodeName)) {
@@ -89,7 +90,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
89
90
  }
90
91
  // Barf if a draft node other than the root has @odata.draft.enabled itself
91
92
  if (draftNode !== rootArtifact && hasAnnotationValue(draftNode, draftAnnotation)) {
92
- error(null, [ 'definitions', artifactName, 'elements', elemName ], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled');
93
+ error('ref-unexpected-draft-enabled', [ 'definitions', artifactName, 'elements', elemName ], { anno: '@odata.draft.enabled' });
93
94
  delete draftNodes[draftNodeName];
94
95
  continue;
95
96
  }
@@ -110,7 +111,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
110
111
  function generateDraftForHana(artifact, artifactName, draftRootName) {
111
112
  // Sanity check
112
113
  if (!isPartOfService(artifactName))
113
- throw new Error(`Expecting artifact to be part of a service: ${JSON.stringify(artifact)}`);
114
+ throw new ModelError(`Expecting artifact to be part of a service: ${JSON.stringify(artifact)}`);
114
115
 
115
116
 
116
117
  // The name of the draft shadow entity we should generate
@@ -134,7 +135,8 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
134
135
  warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have one key element of type “cds.UUID”');
135
136
 
136
137
 
137
- const matchingService = getMatchingService(artifactName);
138
+ // Ignore boolean return value. We know that we're inside a service or else we wouldn't have reached this code.
139
+ const matchingService = getMatchingService(artifactName) || '';
138
140
  // Generate the DraftAdministrativeData projection into the service, unless there is already one
139
141
  const draftAdminDataProjectionName = `${matchingService}.DraftAdministrativeData`;
140
142
  let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
@@ -311,7 +313,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
311
313
  function getDraftShadowEntityFor(draftNode, draftNodeName) {
312
314
  // Sanity check
313
315
  if (!draftNodes[draftNodeName])
314
- throw new Error(`Not a draft node: ${draftNodeName}`);
316
+ throw new ModelError(`Not a draft node: ${draftNodeName}`);
315
317
 
316
318
  return { shadowTarget: csn.definitions[`${draftNodeName}${draftSuffix}`], shadowTargetName: `${draftNodeName}${draftSuffix}` };
317
319
  }
@@ -336,7 +338,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
336
338
  * Get the service name containing the artifact.
337
339
  *
338
340
  * @param {string} artifactName Absolute name of the artifact
339
- * @returns {boolean|string} Name of the service or false if no match is found.
341
+ * @returns {false|string} Name of the service or false if no match is found.
340
342
  */
341
343
  function getMatchingService(artifactName) {
342
344
  const matches = [];