@sap/cds-compiler 3.4.4 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +1 -0
  3. package/bin/cds_update_identifiers.js +5 -5
  4. package/bin/cdsc.js +12 -12
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +9 -1
  7. package/doc/CHANGELOG_DEPRECATED.md +2 -0
  8. package/lib/api/main.js +58 -59
  9. package/lib/api/options.js +4 -2
  10. package/lib/api/validate.js +2 -2
  11. package/lib/base/cleanSymbols.js +2 -3
  12. package/lib/base/dictionaries.js +6 -6
  13. package/lib/base/error.js +2 -2
  14. package/lib/base/keywords.js +6 -6
  15. package/lib/base/location.js +11 -12
  16. package/lib/base/message-registry.js +124 -28
  17. package/lib/base/messages.js +247 -179
  18. package/lib/base/model.js +14 -11
  19. package/lib/base/node-helpers.js +9 -10
  20. package/lib/base/optionProcessorHelper.js +138 -129
  21. package/lib/checks/actionsFunctions.js +5 -5
  22. package/lib/checks/annotationsOData.js +4 -4
  23. package/lib/checks/arrayOfs.js +1 -1
  24. package/lib/checks/cdsPersistence.js +1 -1
  25. package/lib/checks/checkForTypes.js +3 -3
  26. package/lib/checks/defaultValues.js +3 -3
  27. package/lib/checks/elements.js +7 -7
  28. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  29. package/lib/checks/foreignKeys.js +1 -1
  30. package/lib/checks/invalidTarget.js +4 -4
  31. package/lib/checks/managedInType.js +1 -1
  32. package/lib/checks/managedWithoutKeys.js +1 -1
  33. package/lib/checks/nonexpandableStructured.js +5 -3
  34. package/lib/checks/nullableKeys.js +1 -1
  35. package/lib/checks/onConditions.js +5 -6
  36. package/lib/checks/parameters.js +1 -1
  37. package/lib/checks/queryNoDbArtifacts.js +2 -2
  38. package/lib/checks/selectItems.js +4 -4
  39. package/lib/checks/sql-snippets.js +4 -4
  40. package/lib/checks/types.js +7 -7
  41. package/lib/checks/utils.js +4 -4
  42. package/lib/checks/validator.js +16 -13
  43. package/lib/compiler/.eslintrc.json +1 -1
  44. package/lib/compiler/assert-consistency.js +0 -1
  45. package/lib/compiler/builtins.js +1 -1
  46. package/lib/compiler/checks.js +73 -15
  47. package/lib/compiler/define.js +3 -7
  48. package/lib/compiler/extend.js +212 -32
  49. package/lib/compiler/finalize-parse-cdl.js +7 -2
  50. package/lib/compiler/index.js +17 -14
  51. package/lib/compiler/populate.js +2 -5
  52. package/lib/compiler/propagator.js +2 -0
  53. package/lib/compiler/shared.js +23 -12
  54. package/lib/compiler/tweak-assocs.js +5 -6
  55. package/lib/compiler/utils.js +6 -0
  56. package/lib/edm/annotations/genericTranslation.js +553 -319
  57. package/lib/edm/annotations/preprocessAnnotations.js +39 -35
  58. package/lib/edm/csn2edm.js +88 -75
  59. package/lib/edm/edm.js +17 -3
  60. package/lib/edm/edmAnnoPreprocessor.js +5 -5
  61. package/lib/edm/edmPreprocessor.js +106 -76
  62. package/lib/edm/edmUtils.js +41 -2
  63. package/lib/gen/Dictionary.json +34 -0
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +66 -63
  66. package/lib/gen/language.tokens +81 -81
  67. package/lib/gen/languageLexer.interp +4 -10
  68. package/lib/gen/languageLexer.js +854 -869
  69. package/lib/gen/languageLexer.tokens +79 -81
  70. package/lib/gen/languageParser.js +14360 -14146
  71. package/lib/inspect/inspectModelStatistics.js +2 -2
  72. package/lib/inspect/inspectPropagation.js +6 -6
  73. package/lib/inspect/inspectUtils.js +2 -2
  74. package/lib/json/from-csn.js +82 -40
  75. package/lib/json/to-csn.js +82 -157
  76. package/lib/language/.eslintrc.json +1 -4
  77. package/lib/language/genericAntlrParser.js +59 -38
  78. package/lib/language/language.g4 +1508 -1490
  79. package/lib/language/multiLineStringParser.js +1 -1
  80. package/lib/main.js +3 -3
  81. package/lib/model/csnUtils.js +130 -122
  82. package/lib/model/revealInternalProperties.js +1 -1
  83. package/lib/model/sortViews.js +4 -6
  84. package/lib/modelCompare/utils/filter.js +4 -3
  85. package/lib/optionProcessor.js +5 -0
  86. package/lib/render/DuplicateChecker.js +1 -1
  87. package/lib/render/manageConstraints.js +12 -12
  88. package/lib/render/toCdl.js +225 -159
  89. package/lib/render/toHdbcds.js +63 -63
  90. package/lib/render/toRename.js +5 -5
  91. package/lib/render/toSql.js +55 -65
  92. package/lib/render/utils/common.js +20 -37
  93. package/lib/render/utils/delta.js +3 -3
  94. package/lib/render/utils/sql.js +22 -6
  95. package/lib/render/utils/stringEscapes.js +3 -3
  96. package/lib/transform/db/applyTransformations.js +3 -3
  97. package/lib/transform/db/assertUnique.js +13 -12
  98. package/lib/transform/db/associations.js +5 -5
  99. package/lib/transform/db/cdsPersistence.js +10 -8
  100. package/lib/transform/db/constraints.js +14 -14
  101. package/lib/transform/db/expansion.js +20 -22
  102. package/lib/transform/db/flattening.js +24 -42
  103. package/lib/transform/db/groupByOrderBy.js +3 -3
  104. package/lib/transform/db/temporal.js +6 -6
  105. package/lib/transform/db/transformExists.js +23 -23
  106. package/lib/transform/db/views.js +16 -16
  107. package/lib/transform/draft/db.js +10 -10
  108. package/lib/transform/draft/odata.js +2 -2
  109. package/lib/transform/forOdataNew.js +12 -40
  110. package/lib/transform/forRelationalDB.js +16 -6
  111. package/lib/transform/localized.js +2 -2
  112. package/lib/transform/odata/toFinalBaseType.js +41 -27
  113. package/lib/transform/odata/typesExposure.js +106 -62
  114. package/lib/transform/parseExpr.js +209 -106
  115. package/lib/transform/transformUtilsNew.js +2 -2
  116. package/lib/transform/translateAssocsToJoins.js +24 -19
  117. package/lib/transform/universalCsn/coreComputed.js +10 -10
  118. package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
  119. package/lib/transform/universalCsn/utils.js +3 -3
  120. package/lib/utils/file.js +5 -5
  121. package/lib/utils/moduleResolve.js +13 -13
  122. package/lib/utils/objectUtils.js +6 -6
  123. package/lib/utils/term.js +5 -2
  124. package/lib/utils/timetrace.js +51 -24
  125. package/package.json +5 -7
  126. package/share/messages/check-proper-type-of.md +1 -1
  127. package/share/messages/message-explanations.json +1 -1
  128. package/share/messages/redirected-to-complex.md +4 -4
  129. package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
@@ -1,11 +1,17 @@
1
+ // @ts-nocheck
2
+
1
3
  'use strict'
2
4
 
5
+ // const funkyfuncs = Object.keys(require('../compiler/builtins.js').
6
+ // specialFunctions).filter(n => n.length).map(n=>n.toLowerCase());
7
+
3
8
  /**
4
- * parseExpr accepts any JSON object and tries to convert a token stream expression
9
+ * parseExpr() accepts any JSON object and tries to convert a token stream expression
5
10
  * array into an AST like expression with CDL operator precedence.
6
11
  *
7
12
  * The following operators are supported:
8
13
  *
14
+ * Unary: +/-
9
15
  * Multiplication/Division: '*', '/'
10
16
  * Addition/Subtraction: '+', '-'
11
17
  * Concatenation: '||'
@@ -13,7 +19,7 @@
13
19
  * Unary: 'is [not] null', 'not'
14
20
  * Conditional: 'case [when then]+ [else]? end', 'and', 'or'
15
21
  *
16
- * Not yet implmemented: 'new'
22
+ * Not yet implemented: 'new'
17
23
  *
18
24
  * This is not an optimized LL(1) parser but a token 'sniffer'. A stream is
19
25
  * cracked up in sub streams and passed down to the next higher function.
@@ -27,95 +33,122 @@
27
33
  * This parser intentionally does no error handling. If a clause is malformed, it is accepted as is.
28
34
  *
29
35
  * @param {any} xpr A JSON object.
30
- * @param {Boolean} array Bias AST representation.
36
+ * @param {Object} state Objet
37
+ * anno: Don't eliminate arrays with single entry in statetations as they are collections
38
+ * array: Bias AST representation.
39
+ * nary: return n-ary or binary tree
31
40
  */
32
41
 
33
- function parseExpr(xpr, array=true) {
34
- return parseExprInt(xpr);
42
+ function parseExpr(xpr, state = { anno: 0, array: true, nary: true }) {
43
+ // Notes:
44
+ // - Variables `s` and `e` are used as index variables into `xpr`s for start and end.
45
+ // - xpr's are our CSN expressions, see <https://pages.github.tools.sap/cap/docs/cds/cxn>
46
+
47
+ return parseExprInt(xpr, state);
35
48
 
36
- function parseExprInt(xpr) {
37
- return conditionOR(...CaseWhen(xpr));
49
+ function parseExprInt(xpr, state) {
50
+ return conditionOR(...CaseWhen(xpr), state);
38
51
  }
39
52
 
40
53
  function CaseWhen(xpr) {
41
- if(Array.isArray(xpr))
42
- inner(xpr);
54
+ if(Array.isArray(xpr)) {
55
+ recurseIntoCases();
56
+ }
43
57
  return [xpr, 0, Array.isArray(xpr) ? xpr.length : 1];
44
58
 
45
- // replace case/end from inner to outer
46
- function inner(pxpr, lvl=0) {
47
- const s = pxpr.findIndex(t => t === 'case');
48
- if(s >= 0) {
49
- let e = findLastIndex(pxpr, 'end');
50
- pxpr = pxpr.slice(s+1, e);
51
- const dist = inner(pxpr, lvl+1);
52
- e -= dist;
53
- if(dist > 0)
54
- pxpr = xpr.slice(s+1, e+1);
55
- const caseTree = array ? [ 'case' ] : { 'case': [] };
56
- let i = pxpr.findIndex(t => t === 'else');
57
- let elseCond = undefined;
58
- if(i >= 0) {
59
- elseCond = pxpr.slice(i+1);
60
- pxpr = pxpr.slice(0, i);
59
+ function recurseIntoCases(casePos=-1, lvl=-1) {
60
+ for(let c = casePos+1; c < xpr.length; c++) {
61
+ if(xpr[c] === 'case') {
62
+ recurseIntoCases(c, lvl+1)
61
63
  }
62
- i = pxpr.findIndex(t => t === 'when');
63
- while(i >= 0) {
64
- pxpr = pxpr.slice(i+1);
65
- const when = { 'when': [] };
66
- if(array)
67
- caseTree.push('when');
68
- else
69
- caseTree.case.push(when);
70
- i = pxpr.findIndex(t => t === 'then');
71
- if(i >= 0) {
72
- const arg = pxpr.slice(0, i);
73
- if(array)
74
- caseTree.push(arg);
75
- else
76
- when.when.push(arg.length === 1 ? arg[0] : arg);
77
- }
78
- pxpr = pxpr.slice(i+1);
79
- i = pxpr.findIndex(t => t === 'when');
80
- const arg = ((i >= 0) ? pxpr.slice(0, i) : pxpr);
81
- if(array)
82
- caseTree.push('then', arg);
64
+ }
65
+ if(lvl > -1) {
66
+ let endPos = casePos;
67
+ while(xpr[endPos] !== 'end' && endPos < xpr.length) endPos++;
68
+ if(xpr[endPos] === 'end') {
69
+ const caseTree = rewriteCaseBlock(casePos, endPos);
70
+ if(casePos === 0 && endPos === xpr.length-1)
71
+ xpr = caseTree;
83
72
  else
84
- when.when.push(arg.length === 1 ? arg[0] : arg);
73
+ xpr.splice(casePos, endPos-casePos+1, caseTree);
85
74
  }
86
- if(elseCond) {
87
- if(array)
88
- caseTree.push('else', elseCond);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * @param {number} casePos
80
+ * @param {number} endPos
81
+ * @return {Array|object}
82
+ */
83
+ function rewriteCaseBlock(casePos, endPos) {
84
+ const caseTree = state.array ? [ 'case' ] : { 'case': [] };
85
+
86
+ let elsePos = endPos;
87
+ let whenPos = casePos;
88
+
89
+ while(xpr[elsePos] !== 'else' && elsePos > casePos) elsePos--;
90
+ let elseCond = undefined;
91
+ if(xpr[elsePos] === 'else') {
92
+ elseCond = xpr.slice(elsePos+1, endPos);
93
+ endPos = elsePos;
94
+ }
95
+
96
+ while(xpr[whenPos] !== 'when' && whenPos < endPos) whenPos++;
97
+ if(xpr[whenPos] === 'when' && whenPos - (casePos+1) >= 1) {
98
+ const arg = xpr.slice(casePos+1, whenPos)
99
+ if(state.array)
100
+ caseTree.push(arg);
101
+ else
102
+ caseTree.case.push(arg);
103
+ }
104
+
105
+ while(xpr[whenPos] === 'when') {
106
+ const when = { 'when': [] };
107
+ if(state.array)
108
+ caseTree.push('when');
109
+ else
110
+ caseTree.case.push(when);
111
+
112
+ let thenPos = whenPos+1;
113
+ while(xpr[thenPos] !== 'then' && thenPos < endPos) thenPos++;
114
+ if(xpr[thenPos] === 'then') {
115
+ const when = xpr.slice(whenPos+1, thenPos);
116
+ if(state.array)
117
+ caseTree.push(when);
89
118
  else
90
- caseTree.case.push(elseCond.length === 1 ? elseCond[0] : elseCond);
119
+ when.when.push(when.length === 1 ? when[0] : when);
91
120
  }
92
- if(array)
93
- caseTree.push('end');
94
- if(lvl > 0)
95
- xpr.splice(s+1, e-s+1, caseTree);
96
- else {
97
- xpr = caseTree;
121
+
122
+ whenPos = thenPos+1;
123
+ while(xpr[whenPos] !== 'when' && whenPos < endPos) whenPos++;
124
+ if(xpr[whenPos] === 'when' || whenPos === endPos) {
125
+ const then = xpr.slice(thenPos+1, whenPos);
126
+ if(state.array)
127
+ caseTree.push('then', then);
128
+ else
129
+ when.when.push(then.length === 1 ? then[0] : then);
98
130
  }
99
- return e-s+1;
100
131
  }
101
- else
102
- return 0;
103
- }
104
-
105
- function findLastIndex(expr, token, l=expr.length-1) {
106
- while(l >= 0 && expr[l] !== token) l--;
107
- return l;
132
+ if(elseCond) {
133
+ if(state.array)
134
+ caseTree.push('else', elseCond);
135
+ else
136
+ caseTree.case.push(elseCond.length === 1 ? elseCond[0] : elseCond);
137
+ }
138
+ if(state.array)
139
+ caseTree.push('end');
140
+ return caseTree;
108
141
  }
109
142
  }
110
143
 
111
- function conditionOR(xpr, s, e) {
112
- return binaryExpr(xpr, ['or'], conditionAnd, s, e);
144
+ function conditionOR(xpr, s, e, state) {
145
+ return binaryExpr(xpr, ['or'], conditionAnd, s, e, state);
113
146
  }
114
- function conditionAnd(xpr, s, e) {
147
+
148
+ function conditionAnd(xpr, s, e, state) {
115
149
  return binaryExpr(xpr, (xpr, s, e) => {
116
150
  let a = s-1;
117
151
  let b;
118
- // scan for 'and', skip 'between/and'
119
152
  do {
120
153
  b = false;
121
154
  for(a++; xpr[a] !== 'and' && a < e; a++) {
@@ -128,26 +161,31 @@ function parseExpr(xpr, array=true) {
128
161
  return [1, a]
129
162
  else
130
163
  return [1, -1];
131
- }, conditionTerm, s, e);
164
+ }, conditionTerm, s, e, state);
132
165
  }
133
166
 
134
- function conditionTerm(xpr, s, e) {
167
+ function conditionTerm(xpr, s, e, state) {
135
168
  if(Array.isArray(xpr)) {
136
169
  if(xpr.length >= 3 && xpr[s+1] === 'is') {
170
+ const isnull = conditionOR(xpr[s], state);
137
171
  if(xpr[s+2] === 'null')
138
- return array ? [ conditionOR(xpr[s]), 'is', 'null' ] : { 'isNull': conditionOR(xpr[s]) };
172
+ return state.array ? [ isnull, 'is', 'null' ] : { 'isNull': isnull };
139
173
  else if(xpr[s+2] === 'not' && xpr[s+3] === 'null')
140
- return array ? [ conditionOR(xpr[s]), 'is', 'not', 'null' ] : { 'isNotNull': conditionOR(xpr[s]) };
174
+ return state.array ? [ isnull, 'is', 'not', 'null' ] : { 'isNotNull': isnull };
175
+ }
176
+ if(xpr[s] === 'not') {
177
+ const not = conditionTerm(xpr, s+1, e, state)
178
+ return state.array ? [ 'not', not ] : { 'not': not };
179
+ }
180
+ if(xpr[s] === 'exists') {
181
+ const exists = conditionTerm(xpr, s+1, e, state)
182
+ return state.array ? [ 'exists', exists ] : { 'exists': exists };
141
183
  }
142
- if(xpr[s] === 'not')
143
- return array ? [ 'not', conditionTerm(xpr, s+1, e) ] : { 'not': conditionTerm(xpr, s+1, e) };
144
- if(xpr[s] === 'exists')
145
- return array ? [ 'exists', conditionOR(xpr[s+1]) ] : { 'exists': conditionOR(xpr[s+1]) };
146
184
  }
147
- return compareTerm(xpr, s, e);
185
+ return compareTerm(xpr, s, e, state);
148
186
  }
149
187
 
150
- function compareTerm(xpr, s, e) {
188
+ function compareTerm(xpr, s, e, state) {
151
189
  if(Array.isArray(xpr)) {
152
190
  let i = s;
153
191
  while(i < e && xpr[i] !== 'between') i++;
@@ -155,20 +193,26 @@ function parseExpr(xpr, array=true) {
155
193
  while(i < e && xpr[i] !== 'and') i++;
156
194
  const a = i < e ? i : -1;
157
195
  if(b >= 0) {
158
- const expr = expression(xpr, s, b);
159
- const between = array ? [ expr, 'between' ] : { 'between': [ expr ] };
196
+ let token = [ 'between' ];
197
+ const not = (xpr[b-1] === 'not');
198
+ if(not)
199
+ token.splice(0,0, 'not');
200
+ const expr = expression(xpr, s, not ? b-1 : b, state);
201
+ const between = state.array
202
+ ? [ expr, ...token ]
203
+ : { 'between': [ expr ] };
160
204
  if(a >= 0) {
161
- const lower = expression(xpr, b+1, a);
162
- const upper = expression(xpr, a+1, e);
163
- if(array)
205
+ const lower = expression(xpr, b+1, a, state);
206
+ const upper = expression(xpr, a+1, e, state);
207
+ if(state.array)
164
208
  between.push(lower, 'and', upper);
165
209
  else {
166
210
  between.between.push(lower, upper);
167
211
  }
168
212
  }
169
213
  else {
170
- const unspec = expression(xpr, b+1, e);
171
- if(array)
214
+ const unspec = expression(xpr, b+1, e, state);
215
+ if(state.array)
172
216
  between.push(unspec);
173
217
  else
174
218
  between.between.push(unspec);
@@ -176,55 +220,114 @@ function parseExpr(xpr, array=true) {
176
220
  return between;
177
221
  }
178
222
  }
179
- return binaryExpr(xpr, ['=', '<>', '>', '>=', '<', '<=', '!=', 'like', 'in'], expression, s, e);
223
+ return binaryExpr(xpr, (xpr, s, e) => {
224
+ const token = ['=', '<>', '>', '>=', '<', '<=', '!=', 'like', 'in'];
225
+ while(s < e && !token.includes(xpr[s])) s++;
226
+ if(s < e) {
227
+ if(xpr[s-1] === 'not' && (xpr[s] === 'in' || xpr[s] === 'like'))
228
+ return [2, s-1];
229
+ else
230
+ return [1, s];
231
+ }
232
+ return [1, -1];
233
+ }, expression, s, e, state);
180
234
  }
181
235
 
182
- function expression(xpr, s, e) {
183
- return binaryExpr(xpr, ['||'], exprAddSub, s, e);
236
+ function expression(xpr, s, e, state) {
237
+ return binaryExpr(xpr, ['||'], exprAddSub, s, e, state);
184
238
  }
185
239
 
186
- function exprAddSub(xpr, s, e) {
187
- return binaryExpr(xpr, ['+', '-'], exprMulDiv, s, e);
240
+ function exprAddSub(xpr, s, e, state) {
241
+ return binaryExpr(xpr, (xpr, s, e) => {
242
+ const skips = [ '+', '-', '*', '/' ];
243
+ let found = false;
244
+ let p=s;
245
+ while(!found && p < e) {
246
+ found = ((xpr[p] === '+' || xpr[p] === '-') && p > s && !skips.includes(xpr[p-1]) && p < e);
247
+ if(!found) p++;
248
+ }
249
+ if(found)
250
+ return [1, p];
251
+ return [1, -1];
252
+ }, exprMulDiv, s, e, state);
253
+ }
254
+
255
+ function exprMulDiv(xpr, s, e, state) {
256
+ return binaryExpr(xpr, ['*', '/'], unary, s, e, state);
188
257
  }
189
258
 
190
- function exprMulDiv(xpr, s, e) {
191
- return binaryExpr(xpr, ['*', '/'], terminal, s, e);
259
+ function unary(xpr, s, e, state) {
260
+ if(Array.isArray(xpr)) {
261
+ if(xpr[s] === '+' || xpr[s] === '-') {
262
+ return [ xpr[s], unary(xpr, s+1, e, state) ];
263
+ }
264
+ }
265
+ return terminal(xpr, s, e, state);
192
266
  }
267
+ function terminal(xpr, s, e, state) {
268
+ const csnarray = [
269
+ 'ref', 'args', 'columns', 'keys', 'expand', 'inline',
270
+ 'requires', 'extensions', 'includes', 'excluding'
271
+ ];
272
+ const xprarray = [
273
+ 'xpr', 'on', 'where', 'orderBy', 'groupBy', 'having' ];
193
274
 
194
- function terminal(xpr, s, e) {
195
275
  if(Array.isArray(xpr) && xpr.length > 0) {
196
- if(e-s <= 1)
197
- return parseExprInt(xpr[e-1]);
276
+ if(e-s <= 1 && state.anno === 0)
277
+ return parseExprInt(xpr[e-1], state);
198
278
  else
199
- return xpr.slice(s, e).map(parseExprInt);
279
+ return xpr.slice(s, e).map(ix => parseExprInt(ix, state));
200
280
  }
201
281
  if (typeof xpr === 'object') {
282
+ // if(xpr?.func && funkyfuncs.includes(xpr?.func))
283
+ // return xpr;
202
284
  for(let n in xpr) {
203
- xpr[n] = parseExprInt(xpr[n]);
285
+ const x = xpr[n];
286
+ if(n[0] === '@')
287
+ state.anno++;
288
+ if(Array.isArray(x)) {
289
+ if(csnarray.includes(n) || state.anno !== 0)
290
+ xpr[n] = x.map(ix => parseExprInt(ix, state));
291
+ else if(xprarray.includes(n) && x.length === 1)
292
+ xpr[n] = x.map(ix => parseExprInt(ix, state));
293
+ else
294
+ xpr[n] = parseExprInt(x, state);
295
+ }
296
+ else
297
+ xpr[n] = parseExprInt(x, state);
298
+ if(n[0] === '@')
299
+ state.anno--;
204
300
  }
205
301
  }
206
302
  return xpr;
207
303
  }
208
304
 
209
- function binaryExpr(xpr, token, next, s, e) {
305
+ function binaryExpr(xpr, token, next, s, e, state) {
306
+ const expr = [];
210
307
  if (Array.isArray(xpr)) {
211
308
  let [tl, p] = findToken(s, e);
212
309
  if (p >= 0) {
213
- let lhs = next(xpr, s, p);
214
- let op = xpr[p];
310
+ let lhs = next(xpr, s, p, state);
311
+ expr.push(lhs);
312
+ let op = xpr.slice(p, p+tl);
215
313
  s = p+tl;
216
314
  [tl, p] = findToken(s, e);
217
315
  while(p >= 0) {
218
- let rhs = next(xpr, s, p);
219
- lhs = array ? [ lhs, op, rhs ] : { [op]: [lhs, rhs] };
220
- op = xpr[p];
316
+ let rhs = next(xpr, s, p, state);
317
+ expr.push(...op, rhs);
318
+ lhs = state.array ? [ lhs, ...op, rhs ] : { [op.join('')]: [lhs, rhs] };
319
+ op = xpr.slice(p, p+tl);
221
320
  s = p+tl;
222
321
  [tl, p] = findToken(s, e);
223
322
  }
224
- return array ? [ lhs, op, next(xpr, s, e) ] : { [op]: [lhs, next(xpr, s, e)] };
323
+ expr.push(...op, next(xpr, s, e, state));
324
+ if (state.array)
325
+ return (state.nary ? expr : [ lhs, ...op, next(xpr, s, e, state) ])
326
+ else
327
+ return { [op.join('')]: [lhs, next(xpr, s, e, state)] };
225
328
  }
226
329
  }
227
- return next(xpr, s, e);
330
+ return next(xpr, s, e, state);
228
331
 
229
332
  function findToken(s, e) {
230
333
  if(typeof token === 'function')
@@ -10,9 +10,9 @@ const { setProp } = require('../base/model');
10
10
  const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
11
11
  const { cloneCsnNonDict, cloneCsnDictionary, getUtils } = require('../model/csnUtils');
12
12
  const { typeParameters, isBuiltinType } = require('../compiler/builtins');
13
- const { ModelError } = require("../base/error");
13
+ const { ModelError } = require('../base/error');
14
14
  const { forEach } = require('../utils/objectUtils');
15
- const { pathName } = require("../compiler/utils");
15
+ const { pathName } = require('../compiler/utils');
16
16
 
17
17
  const RestrictedOperators = ['<', '>', '>=', '<='];
18
18
  const RelationalOperators = ['=', '!=', '<>', 'is' /*, 'like'*/,...RestrictedOperators];
@@ -12,16 +12,16 @@ const { timetrace } = require('../utils/timetrace');
12
12
  const internalArtifactKinds = ['builtin', '$parameters', 'param'];
13
13
 
14
14
  function translateAssocsToJoinsCSN(csn, options){
15
- timetrace.start('Recompiling model');
15
+ timetrace.start('A2J: Recompiling model');
16
16
  // Do not re-complain about localized
17
17
  const compileOptions = { ...options, $skipNameCheck: true };
18
18
  delete compileOptions.csnFlavor;
19
19
  const model = recompileX(csn, compileOptions);
20
- timetrace.stop();
21
- timetrace.start('Translating associations to joins');
20
+ timetrace.stop('A2J: Recompiling model');
21
+ timetrace.start('A2J: Translating associations to joins');
22
22
  translateAssocsToJoins(model, options);
23
- timetrace.stop();
24
- timetrace.start('Post-processing columns');
23
+ timetrace.stop('A2J: Translating associations to joins');
24
+ timetrace.start('A2J: Post-processing columns');
25
25
  // Use the effective elements list as columns
26
26
  forEachDefinition(model, art => {
27
27
  if (art.$queries) {
@@ -36,7 +36,7 @@ function translateAssocsToJoinsCSN(csn, options){
36
36
  }
37
37
  }
38
38
  });
39
- timetrace.stop();
39
+ timetrace.stop('A2J: Post-processing columns');
40
40
 
41
41
  if (options.messages) {
42
42
  // Make sure that we don't complain twice about the same things
@@ -46,8 +46,7 @@ function translateAssocsToJoinsCSN(csn, options){
46
46
  // If A2J reports error - end! Continuing with a broken CSN makes no sense
47
47
  makeMessageFunction(model, options).throwWithAnyError();
48
48
  // FIXME: Move this somewhere more appropriate
49
- const compact = compactModel(model, compileOptions);
50
- return compact;
49
+ return compactModel(model, compileOptions);
51
50
  }
52
51
 
53
52
  function translateAssocsToJoins(model, inputOptions = {})
@@ -329,9 +328,10 @@ function translateAssocsToJoins(model, inputOptions = {})
329
328
 
330
329
  for(let i = 0; i < tail.length-1; i++) {
331
330
  if(tail[i]._navigation && tail[i]._navigation.$njr) {
332
- // the correct flattened foreign key must match the leaf artifact of this path
331
+ // the correct flattened foreign key must match the leaf artifact and access path prefix of this path
333
332
  const leafArt = tail[tail.length-1]._artifact;
334
- const fk = tail[i]._artifact.$flatSrcFKs.find(f => f._artifact === leafArt);
333
+ const tailPath = tail.map(p=>p.id).join(pathDelimiter);
334
+ const fk = tail[i]._artifact.$flatSrcFKs.find(f => f._artifact === leafArt && f.acc.startsWith(tailPath));
335
335
  if(!fk) {
336
336
  // const revealInternalProperties = require('../model/revealInternalProperties.js');
337
337
  // console.log('++++++++ Path tail: ', revealInternalProperties(tail[tail.length-1]._artifact));
@@ -565,7 +565,7 @@ function translateAssocsToJoins(model, inputOptions = {})
565
565
  QATs until a QA has been found).
566
566
  */
567
567
  if(!assoc.$flatSrcFKs)
568
- setProp(assoc, '$flatSrcFKs', flattenElement(assoc, true, assoc.name.id));
568
+ setProp(assoc, '$flatSrcFKs', flattenElement(assoc, true, assoc.name.id, assoc.name.id));
569
569
  if(!assoc.$flatTgtFKs)
570
570
  setProp(assoc, '$flatTgtFKs', flattenElement(assoc, false));
571
571
 
@@ -664,7 +664,8 @@ function translateAssocsToJoins(model, inputOptions = {})
664
664
  }
665
665
  // If this is a backlink condition, produce the
666
666
  // ON cond of the forward assoc with swapped src/tgt aliases
667
- else if(i < args.length-2 && args[i].path && args[i+1] === '=' && args[i+2].path)
667
+ else if(i < args.length-2 && args[i].path &&
668
+ args[i+1]?.literal === 'token' && args[i+1]?.val === '=' && args[i+2].path)
668
669
  {
669
670
  let fwdAssoc = getForwardAssociation(args[i].path, args[i+2].path);
670
671
  if(fwdAssoc)
@@ -1088,11 +1089,11 @@ function translateAssocsToJoins(model, inputOptions = {})
1088
1089
  respecting the src or the target side of the ON condition.
1089
1090
  Return an array of column names and it's leaf element.
1090
1091
  */
1091
- function flattenElement(element, srcSide, prefix)
1092
+ function flattenElement(element, srcSide, prefix, acc)
1092
1093
  {
1093
1094
  // terminate if element is unstructured
1094
1095
  if(!element.foreignKeys && !element.elements)
1095
- return [ { id: prefix, _artifact: element } ];
1096
+ return [ { id: prefix, _artifact: element, acc } ];
1096
1097
 
1097
1098
  let paths = [];
1098
1099
  // get paths of managed assocs (unmanaged assocs are not allowed in FK paths)
@@ -1103,7 +1104,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1103
1104
  let fk = element.foreignKeys[fkn];
1104
1105
  // once a fk is to be followed, treat all sub patsh as srcSide, this will add fk.name.id only
1105
1106
  if(srcSide)
1106
- paths = paths.concat(flattenElement(fk.targetElement._artifact, true, fk.name.id));
1107
+ paths = paths.concat(flattenElement(fk.targetElement._artifact, true, fk.name.id, fk.targetElement.path.map(ps => ps.id).join(pathDelimiter)));
1107
1108
  else
1108
1109
  {
1109
1110
  // consume path segments until the next assoc and substitute against fk alias until path is eaten up
@@ -1113,7 +1114,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1113
1114
  [tail, fkPrefix] = substituteFKAliasForPath(assocStep, tail, fkPrefix);
1114
1115
  [assocStep, tail, fkPrefix] = pathAsStringUpToAssoc(tail, fkPrefix);
1115
1116
  }
1116
- paths = paths.concat(flattenElement(fk.targetElement._artifact, true, fkPrefix));
1117
+ paths = paths.concat(flattenElement(fk.targetElement._artifact, true, fkPrefix, fk.targetElement.path.map(ps => ps.id).join(pathDelimiter)));
1117
1118
  }
1118
1119
  }
1119
1120
  }
@@ -1123,11 +1124,15 @@ function translateAssocsToJoins(model, inputOptions = {})
1123
1124
  for(let n in element.elements)
1124
1125
  {
1125
1126
  let elt = element.elements[n];
1126
- paths = paths.concat(flattenElement(elt, true, elt.name.id));
1127
+ paths = paths.concat(flattenElement(elt, true, elt.name.id, elt.name.id));
1127
1128
  }
1128
1129
  }
1129
1130
  return paths.map(p => {
1130
- return { id: (prefix ? prefix + pathDelimiter : '' ) + p.id, _artifact: p._artifact }
1131
+ return {
1132
+ id: (prefix ? prefix + pathDelimiter : '' ) + p.id,
1133
+ acc: (acc ? acc + pathDelimiter : '') + p.acc,
1134
+ _artifact: p._artifact
1135
+ }
1131
1136
  } );
1132
1137
  }
1133
1138
 
@@ -1556,7 +1561,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1556
1561
  if(!qat[qatChildrenName]) {
1557
1562
  setProp(qat, '$njr', true);
1558
1563
  // flatten left hand side ON condition paths ( => foreign keys to the source side)
1559
- setProp(art, '$flatSrcFKs', flattenElement(art, true, art.name.id));
1564
+ setProp(art, '$flatSrcFKs', flattenElement(art, true, art.name.id, art.name.id));
1560
1565
  }
1561
1566
  }
1562
1567
  else {
@@ -10,17 +10,17 @@ const { setAnnotationIfNotDefined } = require('./utils');
10
10
  *
11
11
  * @param {CSN.Model} csn
12
12
  */
13
- function setCoreComputedOnViews(csn) {
13
+ function setCoreComputedOnViews( csn ) {
14
14
  const {
15
15
  artifactRef, getColumn, getElement, getOrigin,
16
16
  } = getUtils(csn, 'init-all');
17
17
 
18
- forEachDefinition(csn, (artifact) => {
18
+ forEachDefinition(csn, (artifact, name, prop, path) => {
19
19
  if (artifact.query || artifact.projection) {
20
20
  forAllQueries(getNormalizedQuery(artifact).query, (query) => {
21
21
  if (query.SELECT)
22
22
  traverseQueryAndAttachCoreComputed(query, query.SELECT.elements || artifact.elements);
23
- });
23
+ }, path);
24
24
  }
25
25
  });
26
26
  /**
@@ -33,7 +33,7 @@ function setCoreComputedOnViews(csn) {
33
33
  * @param {CSN.Query} query
34
34
  * @param {CSN.Elements} elements
35
35
  */
36
- function traverseQueryAndAttachCoreComputed(query, elements) {
36
+ function traverseQueryAndAttachCoreComputed( query, elements ) {
37
37
  for (const [ name, element ] of Object.entries(elements)) {
38
38
  const ancestor = getAncestor(element, name, query.SELECT);
39
39
 
@@ -53,7 +53,7 @@ function setCoreComputedOnViews(csn) {
53
53
  * @param {CSN.QuerySelect} base
54
54
  * @returns {CSN.Column|CSN.Element}
55
55
  */
56
- function getAncestor(element, name, base) {
56
+ function getAncestor( element, name, base ) {
57
57
  const column = getColumn(element);
58
58
  if (column)
59
59
  return column;
@@ -81,7 +81,7 @@ function setCoreComputedOnViews(csn) {
81
81
  * @returns {CSN.Element}
82
82
  * @todo cleanup throw(s) - but leave in during dev
83
83
  */
84
- function getElementFromFrom(name, base) {
84
+ function getElementFromFrom( name, base ) {
85
85
  if (base.SELECT && base.SELECT.elements) {
86
86
  return getAncestor(base.SELECT.elements[name], name, base.SELECT);
87
87
  }
@@ -108,7 +108,7 @@ function setCoreComputedOnViews(csn) {
108
108
  * @param {string} name
109
109
  * @returns {CSN.Element|null} Null if no element was found
110
110
  */
111
- function checkJoinSources(args, name) {
111
+ function checkJoinSources( args, name ) {
112
112
  for (const arg of args) {
113
113
  if (arg.args) { // Join after join - A join B on <..> join C on <..>
114
114
  const result = checkJoinSources(arg.args, name);
@@ -130,7 +130,7 @@ function setCoreComputedOnViews(csn) {
130
130
  *
131
131
  * @param {CSN.Column} column
132
132
  */
133
- function attachCoreComputed(column) {
133
+ function attachCoreComputed( column ) {
134
134
  if (needsCoreComputed(column))
135
135
  setAnnotationIfNotDefined(getElement(column), '@Core.Computed', true);
136
136
  }
@@ -141,7 +141,7 @@ function setCoreComputedOnViews(csn) {
141
141
  * @param {CSN.Column} column
142
142
  * @returns {boolean}
143
143
  */
144
- function needsCoreComputed(column) {
144
+ function needsCoreComputed( column ) {
145
145
  return column &&
146
146
  (
147
147
  column.xpr || column.func || column.val !== undefined || column.param || column.SELECT || column.SET ||
@@ -155,7 +155,7 @@ function setCoreComputedOnViews(csn) {
155
155
  * @param {CSN.Column} column
156
156
  * @param {Function} callback
157
157
  */
158
- function traverseExpandInline(column, callback) {
158
+ function traverseExpandInline( column, callback ) {
159
159
  if (column.expand) {
160
160
  column.expand.forEach((col) => {
161
161
  callback(col);