@sap/cds-compiler 2.11.2 → 2.13.6

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 (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. 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,9 +49,12 @@ 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) => {
55
+ if (artifact.projection) // do the same hack we do for the other stuff...
56
+ artifact.query = { SELECT: artifact.projection };
57
+
54
58
  if (artifact.query) {
55
59
  forAllQueries(artifact.query, (query, path) => {
56
60
  if (!generatedExists.has(query)) {
@@ -78,11 +82,19 @@ function handleExists(csn, options, error) {
78
82
  // to check for further exists
79
83
  const { result, leftovers } = processExists(queryPath, exprPath);
80
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
81
87
  toProcess.push(...leftovers.reverse()); // any leftovers - schedule for further processing
82
88
  }
83
89
  }
84
90
  }, [ 'definitions', artifactName, 'query' ]);
85
91
  }
92
+
93
+ if (artifact.projection) { // undo our hack
94
+ artifact.projection = artifact.query.SELECT;
95
+
96
+ delete artifact.query;
97
+ }
86
98
  });
87
99
 
88
100
  /**
@@ -356,16 +368,23 @@ function handleExists(csn, options, error) {
356
368
  const subselect = getSubselect(root.target, ref, sources);
357
369
 
358
370
  const target = subselect.SELECT.from.as; // use subquery alias as target - prevent shadowing
359
- if (root.keys) { // managed assoc
360
- translateManagedAssocToWhere(root, target, subselect, isPrefixedWithTableAlias, base, current);
361
- }
362
- else { // unmanaged assoc
363
- translateUnmanagedAssocToWhere(root, target, subselect, isPrefixedWithTableAlias, base, current);
364
- }
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(')');
365
379
 
366
380
  newExpr.push('exists');
367
- if (ref && ref.where)
368
- 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
+ }
369
388
 
370
389
  newExpr.push(subselect);
371
390
  toContinue.push([ exprPath.concat(newExpr.length - 1), exprPath.concat([ newExpr.length - 1, 'SELECT', 'where' ]) ]);
@@ -403,21 +422,24 @@ function handleExists(csn, options, error) {
403
422
  *
404
423
  * @param {CSN.Element} root
405
424
  * @param {string} target
406
- * @param {CSN.Query} subselect This subselect will in the end replace <assoc> in EXISTS <assoc>
407
425
  * @param {boolean} isPrefixedWithTableAlias
408
426
  * @param {string} base
409
427
  * @param {Token} current
428
+ * @returns {object[]} The stuff to add to the where
410
429
  */
411
- function translateManagedAssocToWhere(root, target, subselect, isPrefixedWithTableAlias, base, current) {
430
+ function translateManagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current) {
431
+ const whereExtension = [];
412
432
  for (let j = 0; j < root.keys.length; j++) {
413
433
  const lop = { ref: [ target, ...root.keys[j].ref ] }; // target side
414
434
  const rop = { ref: (isPrefixedWithTableAlias ? [] : [ base ]).concat([ ...toRawRef(current.ref), ...root.keys[j].ref ]) }; // source side
415
435
 
416
436
  if (j > 0)
417
- subselect.SELECT.where.push('and');
437
+ whereExtension.push('and');
418
438
 
419
- subselect.SELECT.where.push(...[ lop, '=', rop ]);
439
+ whereExtension.push(...[ lop, '=', rop ]);
420
440
  }
441
+
442
+ return whereExtension;
421
443
  }
422
444
 
423
445
  /**
@@ -443,12 +465,13 @@ function handleExists(csn, options, error) {
443
465
  *
444
466
  * @param {CSN.Element} root
445
467
  * @param {string} target
446
- * @param {CSN.Query} subselect This subselect will in the end replace <assoc> in EXISTS <assoc>
447
468
  * @param {boolean} isPrefixedWithTableAlias
448
469
  * @param {string} base
449
470
  * @param {Token} current
471
+ * @returns {object[]} The stuff to add to the where
450
472
  */
451
- function translateUnmanagedAssocToWhere(root, target, subselect, isPrefixedWithTableAlias, base, current) {
473
+ function translateUnmanagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current) {
474
+ const whereExtension = [];
452
475
  for (let j = 0; j < root.on.length; j++) {
453
476
  const part = root.on[j];
454
477
 
@@ -456,7 +479,7 @@ function handleExists(csn, options, error) {
456
479
  // but also keep along stuff like null and undefined, so compiler
457
480
  // can have a chance to complain/ we can fail later nicely maybe
458
481
  if (!(part && part.ref)) {
459
- subselect.SELECT.where.push(part);
482
+ whereExtension.push(part);
460
483
  continue;
461
484
  }
462
485
 
@@ -466,30 +489,82 @@ function handleExists(csn, options, error) {
466
489
  // Dollar Self Backlink
467
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 ]))) {
468
491
  if (root.on[j].ref[0] === '$self' && root.on[j].ref.length === 1)
469
- 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 ])));
470
493
  else
471
- 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 ])));
472
495
 
473
496
  j += 2;
474
497
  }
475
498
  else if (links && links[0].art === root) { // target side
476
- subselect.SELECT.where.push({ ref: [ target, ...part.ref.slice(1) ] });
499
+ whereExtension.push({ ref: [ target, ...part.ref.slice(1) ] });
477
500
  }
478
501
  else if (part.$scope === '$self') { // source side - "absolute" scope
479
- // cut off the $self, as we prefix the entity name now
480
- 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
+ }
481
509
  }
482
510
  else if (art) { // source side - with local scope
483
- if (isPrefixedWithTableAlias)
484
- 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 ] });
485
513
  else
486
- 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 ] });
487
515
  }
488
516
  else { // operator - or any other leftover
489
- subselect.SELECT.where.push(part);
517
+ whereExtension.push(part);
490
518
  }
491
519
  }
492
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
+
493
568
  /**
494
569
  * Check that an expression triple is a valid $self
495
570
  *
@@ -562,7 +637,7 @@ function handleExists(csn, options, error) {
562
637
  }
563
638
 
564
639
  /**
565
- * 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
566
641
  *
567
642
  * @param {CSN.Path} path
568
643
  * @returns {boolean}
@@ -612,7 +687,7 @@ function handleExists(csn, options, error) {
612
687
  return error(null, xpr.$path, 'Boolean $env is not handled yet - please report this error!');
613
688
  }
614
689
  else if (xpr.ref) {
615
- 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!');
616
691
  }
617
692
  }
618
693
 
@@ -697,7 +772,7 @@ function handleExists(csn, options, error) {
697
772
  *
698
773
  * @param {string} base The source entity/query source name
699
774
  * @param {string} target The target entity/query source name
700
- * @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
701
776
  * @param {CSN.Path} path
702
777
  * @returns {TokenStream} The WHERE representing the $self comparison
703
778
  */