@sap/cds-compiler 3.4.4 → 3.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +1 -0
  3. package/bin/cds_update_identifiers.js +5 -5
  4. package/bin/cdsc.js +12 -12
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +9 -1
  7. package/doc/CHANGELOG_DEPRECATED.md +2 -0
  8. package/lib/api/main.js +58 -59
  9. package/lib/api/options.js +4 -2
  10. package/lib/api/validate.js +2 -2
  11. package/lib/base/cleanSymbols.js +2 -3
  12. package/lib/base/dictionaries.js +6 -6
  13. package/lib/base/error.js +2 -2
  14. package/lib/base/keywords.js +6 -6
  15. package/lib/base/location.js +11 -12
  16. package/lib/base/message-registry.js +124 -28
  17. package/lib/base/messages.js +247 -179
  18. package/lib/base/model.js +14 -11
  19. package/lib/base/node-helpers.js +9 -10
  20. package/lib/base/optionProcessorHelper.js +138 -129
  21. package/lib/checks/actionsFunctions.js +5 -5
  22. package/lib/checks/annotationsOData.js +4 -4
  23. package/lib/checks/arrayOfs.js +1 -1
  24. package/lib/checks/cdsPersistence.js +1 -1
  25. package/lib/checks/checkForTypes.js +3 -3
  26. package/lib/checks/defaultValues.js +3 -3
  27. package/lib/checks/elements.js +7 -7
  28. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  29. package/lib/checks/foreignKeys.js +1 -1
  30. package/lib/checks/invalidTarget.js +4 -4
  31. package/lib/checks/managedInType.js +1 -1
  32. package/lib/checks/managedWithoutKeys.js +1 -1
  33. package/lib/checks/nonexpandableStructured.js +5 -3
  34. package/lib/checks/nullableKeys.js +1 -1
  35. package/lib/checks/onConditions.js +5 -6
  36. package/lib/checks/parameters.js +1 -1
  37. package/lib/checks/queryNoDbArtifacts.js +2 -2
  38. package/lib/checks/selectItems.js +4 -4
  39. package/lib/checks/sql-snippets.js +4 -4
  40. package/lib/checks/types.js +7 -7
  41. package/lib/checks/utils.js +4 -4
  42. package/lib/checks/validator.js +16 -13
  43. package/lib/compiler/.eslintrc.json +1 -1
  44. package/lib/compiler/assert-consistency.js +0 -1
  45. package/lib/compiler/builtins.js +1 -1
  46. package/lib/compiler/checks.js +73 -15
  47. package/lib/compiler/define.js +3 -7
  48. package/lib/compiler/extend.js +212 -32
  49. package/lib/compiler/finalize-parse-cdl.js +7 -2
  50. package/lib/compiler/index.js +17 -14
  51. package/lib/compiler/populate.js +2 -5
  52. package/lib/compiler/propagator.js +2 -0
  53. package/lib/compiler/shared.js +23 -12
  54. package/lib/compiler/tweak-assocs.js +5 -6
  55. package/lib/compiler/utils.js +6 -0
  56. package/lib/edm/annotations/genericTranslation.js +553 -319
  57. package/lib/edm/annotations/preprocessAnnotations.js +39 -35
  58. package/lib/edm/csn2edm.js +88 -75
  59. package/lib/edm/edm.js +17 -3
  60. package/lib/edm/edmAnnoPreprocessor.js +5 -5
  61. package/lib/edm/edmPreprocessor.js +106 -76
  62. package/lib/edm/edmUtils.js +41 -2
  63. package/lib/gen/Dictionary.json +34 -0
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +66 -63
  66. package/lib/gen/language.tokens +81 -81
  67. package/lib/gen/languageLexer.interp +4 -10
  68. package/lib/gen/languageLexer.js +854 -869
  69. package/lib/gen/languageLexer.tokens +79 -81
  70. package/lib/gen/languageParser.js +14360 -14146
  71. package/lib/inspect/inspectModelStatistics.js +2 -2
  72. package/lib/inspect/inspectPropagation.js +6 -6
  73. package/lib/inspect/inspectUtils.js +2 -2
  74. package/lib/json/from-csn.js +82 -40
  75. package/lib/json/to-csn.js +82 -157
  76. package/lib/language/.eslintrc.json +1 -4
  77. package/lib/language/genericAntlrParser.js +59 -38
  78. package/lib/language/language.g4 +1508 -1490
  79. package/lib/language/multiLineStringParser.js +1 -1
  80. package/lib/main.js +3 -3
  81. package/lib/model/csnUtils.js +130 -122
  82. package/lib/model/revealInternalProperties.js +1 -1
  83. package/lib/model/sortViews.js +4 -6
  84. package/lib/modelCompare/utils/filter.js +4 -3
  85. package/lib/optionProcessor.js +5 -0
  86. package/lib/render/DuplicateChecker.js +1 -1
  87. package/lib/render/manageConstraints.js +12 -12
  88. package/lib/render/toCdl.js +225 -159
  89. package/lib/render/toHdbcds.js +63 -63
  90. package/lib/render/toRename.js +5 -5
  91. package/lib/render/toSql.js +55 -65
  92. package/lib/render/utils/common.js +20 -37
  93. package/lib/render/utils/delta.js +3 -3
  94. package/lib/render/utils/sql.js +22 -6
  95. package/lib/render/utils/stringEscapes.js +3 -3
  96. package/lib/transform/db/applyTransformations.js +3 -3
  97. package/lib/transform/db/assertUnique.js +13 -12
  98. package/lib/transform/db/associations.js +5 -5
  99. package/lib/transform/db/cdsPersistence.js +10 -8
  100. package/lib/transform/db/constraints.js +14 -14
  101. package/lib/transform/db/expansion.js +20 -22
  102. package/lib/transform/db/flattening.js +24 -42
  103. package/lib/transform/db/groupByOrderBy.js +3 -3
  104. package/lib/transform/db/temporal.js +6 -6
  105. package/lib/transform/db/transformExists.js +23 -23
  106. package/lib/transform/db/views.js +16 -16
  107. package/lib/transform/draft/db.js +10 -10
  108. package/lib/transform/draft/odata.js +2 -2
  109. package/lib/transform/forOdataNew.js +12 -40
  110. package/lib/transform/forRelationalDB.js +17 -7
  111. package/lib/transform/localized.js +2 -2
  112. package/lib/transform/odata/toFinalBaseType.js +41 -27
  113. package/lib/transform/odata/typesExposure.js +106 -62
  114. package/lib/transform/parseExpr.js +209 -106
  115. package/lib/transform/transformUtilsNew.js +2 -2
  116. package/lib/transform/translateAssocsToJoins.js +24 -19
  117. package/lib/transform/universalCsn/coreComputed.js +10 -10
  118. package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
  119. package/lib/transform/universalCsn/utils.js +3 -3
  120. package/lib/utils/file.js +5 -5
  121. package/lib/utils/moduleResolve.js +13 -13
  122. package/lib/utils/objectUtils.js +6 -6
  123. package/lib/utils/term.js +5 -2
  124. package/lib/utils/timetrace.js +51 -24
  125. package/package.json +5 -7
  126. package/share/messages/check-proper-type-of.md +1 -1
  127. package/share/messages/message-explanations.json +1 -1
  128. package/share/messages/redirected-to-complex.md +4 -4
  129. package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
@@ -8,7 +8,7 @@
8
8
 
9
9
  const { setProp } = require('../../base/model');
10
10
  const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
11
- const { cloneCsnNonDict, isBuiltinType, forEachDefinition, forEachMember } = require('../../model/csnUtils');
11
+ const { cloneCsnNonDict, isBuiltinType, forEachDefinition, forEachMember, forEachGeneric } = require('../../model/csnUtils');
12
12
  const { copyAnnotations } = require('../../model/csnUtils');
13
13
  const { isBetaEnabled } = require('../../base/model.js');
14
14
 
@@ -49,6 +49,31 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
49
49
  }
50
50
  });
51
51
 
52
+ if(isBetaEnabled(options, 'odataTerms')) {
53
+ forEachGeneric(csn, 'vocabularies', (def, defName, _propertyName, path) => {
54
+ // we do expose types only for definition from inside services
55
+ const serviceName = whatsMyServiceName(defName, false);
56
+ // run type exposure only on requested services if not in multi schema mode
57
+ // multi schema mode requires a proper type exposure for all services as a prerequisite
58
+ // for the proxy exposure
59
+ if (serviceName && requestedServiceNames.includes(serviceName)) {
60
+ if(csn.definitions[defName]) {
61
+ // error, duplicate definitions not allowed!
62
+ // TODO: Use path as error location as soon as refs outside definitions are supported
63
+ error('odata-definition-exists', ['vocabularies', defName], { anno: defName, '#':'anno' });
64
+ }
65
+ else {
66
+ // link def into definitions for later use
67
+ def.kind = 'annotation';
68
+ csn.definitions[defName] = def;
69
+ const artificialName = `term_${defName.replace(/\./g, '_')}`;//_${paramName}`;
70
+ const newTypeName = getNewTypeName(undefined, undefined, artificialName, serviceName);
71
+ exposeTypeOf(def, false, defName, defName, serviceName, newTypeName, path.concat(['vocabularies', defName]), undefined, true);
72
+ }
73
+ }
74
+ });
75
+ }
76
+
52
77
  return schemas;
53
78
 
54
79
  /**
@@ -82,14 +107,14 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
82
107
  * @param {String} serviceName
83
108
  * @param {String} newTypeName
84
109
  */
85
- function exposeTypeOf(node, isKey, memberName, defName, serviceName, newTypeName, path, parentName) {
86
- const { isExposable, typeDef, typeName, elements, isAnonymous } = isTypeExposable(node);
110
+ function exposeTypeOf(node, isKey, memberName, defName, serviceName, newTypeName, path, parentName, isTermDef=false) {
111
+ const { isExposable, typeDef, typeName, elements, isAnonymous } = isTypeExposable();
87
112
  if (isExposable) {
88
113
  // this is the name used to register the new type in csn.definitions
89
114
  let fullQualifiedNewTypeName =
90
115
  isMultiSchema
91
- ? (node.type || (node.items && node.items.type)
92
- ? getTypeNameInMultiSchema(node.type|| (node.items && node.items.type), serviceName)
116
+ ? (node.type || (node.items?.type)
117
+ ? getTypeNameInMultiSchema(node.type|| (node.items?.type), serviceName)
93
118
  : getAnonymousTypeNameInMultiSchema(newTypeName, parentName || defName))
94
119
  : `${serviceName}.${newTypeName}`;
95
120
 
@@ -100,7 +125,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
100
125
  isKey = false;
101
126
  // in case this was a named type and if the openess does not match the type definition
102
127
  // expose the type as a new one not changing the original definition.
103
- if((!!node['@open'] !== !!typeDef['@open']) && isBetaEnabled(options, 'odataOpenType'))
128
+ if(elements && (!!node['@open'] !== !!typeDef['@open']) && isBetaEnabled(options, 'odataOpenType'))
104
129
  fullQualifiedNewTypeName += node['@open'] ? '_open' : '_closed';
105
130
  }
106
131
  // check if that type is already defined
@@ -118,51 +143,62 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
118
143
  /* Expose new structured type
119
144
  * Treat items.elements as ordinary elements for now.
120
145
  */
121
- newType = createNewStructType(elements);
122
- // if using node enforces open/closed, set it on type
123
- if(node['@open'] !== undefined)
124
- newType['@open'] = node['@open']
125
- if (node.$location)
126
- setProp(newType, '$location', node.$location);
146
+ if(elements) {
147
+ newType = createNewStructType(elements);
148
+ // if using node enforces open/closed, set it on type
149
+ if(node['@open'] !== undefined)
150
+ newType['@open'] = node['@open']
151
+ if (node.$location)
152
+ setProp(newType, '$location', node.$location);
127
153
 
128
- csn.definitions[fullQualifiedNewTypeName] = newType;
129
- exposedTypes[fullQualifiedNewTypeName] = 1;
154
+ csn.definitions[fullQualifiedNewTypeName] = newType;
155
+ exposedTypes[fullQualifiedNewTypeName] = 1;
130
156
 
131
- // Recurse into elements of 'type' (if any) and expose them as well (is needed)
132
- newType.elements && Object.entries(newType.elements).forEach(([elemName, newElem]) => {
133
- if (node.elements && node.elements[elemName].$location)
134
- setProp(newElem, '$location', node.elements[elemName].$location);
135
- defName = typeDef.kind === 'type' ? typeName : defName;
136
- {
137
- const { isExposable, typeDef, typeName } = exposeTypeOf(newElem, isKey, elemName, defName, serviceName,
138
- getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName);
139
- // if the type for the newElem was not exposed it may be a scalar type def from an external service that hasn't
140
- // been catched by expandToFinalBaseType() (forODataNew must not modify external imported services)
141
- if(!isExposable && isBuiltinType(typeName) && !isBuiltinType((newElem.items?.type || newElem.type))) {
142
- if(typeDef.items) {
143
- newElem.items = typeDef.items;
144
- delete newElem.type;
145
- }
146
- else if(newElem.items) {
147
- newElem.items.type = typeName;
148
- if(typeDef.enum)
149
- newElem.items.enum = typeDef.enum;
150
- }
151
- else {
152
- newElem.type = typeName;
153
- if(typeDef.enum)
154
- newElem.enum = typeDef.enum;
157
+ // Recurse into elements of 'type' (if any) and expose them as well (is needed)
158
+ newType.elements && Object.entries(newType.elements).forEach(([elemName, newElem]) => {
159
+ if (node.elements && node.elements[elemName].$location)
160
+ setProp(newElem, '$location', node.elements[elemName].$location);
161
+ defName = typeDef.kind === 'type' ? typeName : defName;
162
+ {
163
+ const { isExposable, typeDef, typeName } = exposeTypeOf(newElem, isKey, elemName, defName, serviceName,
164
+ getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName, isTermDef);
165
+ // if the type for the newElem was not exposed it may be a scalar type def from an external service that hasn't
166
+ // been catched by expandToFinalBaseType() (forODataNew must not modify external imported services)
167
+ if(!isExposable && isBuiltinType(typeName) && !isBuiltinType((newElem.items?.type || newElem.type))) {
168
+ if(typeDef.items) {
169
+ newElem.items = typeDef.items;
170
+ delete newElem.type;
171
+ }
172
+ else if(newElem.items) {
173
+ newElem.items.type = typeName;
174
+ if(typeDef.enum)
175
+ newElem.items.enum = typeDef.enum;
176
+ }
177
+ else {
178
+ newElem.type = typeName;
179
+ if(typeDef.enum)
180
+ newElem.enum = typeDef.enum;
181
+ }
155
182
  }
156
183
  }
184
+ });
185
+ copyAnnotations(typeDef, newType);
186
+ // if the origin type had items, add items to exposed type
187
+ if(typeDef.kind === 'type') {
188
+ if(typeDef.items) {
189
+ newType.items = { elements: newType.elements };
190
+ delete newType.elements;
191
+ }
157
192
  }
158
- });
159
- copyAnnotations(typeDef, newType);
160
- // if the origin type had items, add items to exposed type
161
- if(typeDef.kind === 'type') {
162
- if(typeDef.items) {
163
- newType.items = { elements: newType.elements };
164
- delete newType.elements;
193
+ }
194
+ else if(isTermDef) {
195
+ newType = Object.create(null);
196
+ for(let n in typeDef) {
197
+ newType[n] = typeDef[n];
165
198
  }
199
+ newType.kind = 'type';
200
+ csn.definitions[fullQualifiedNewTypeName] = newType;
201
+ exposedTypes[fullQualifiedNewTypeName] = 1;
166
202
  }
167
203
  }
168
204
  // adjust current node to new type
@@ -188,36 +224,44 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
188
224
  * - the elements dictionary that needs to be cloned
189
225
  * - the typeDef, either the resolved type def or the node itself
190
226
  * - if structured type was anonymously defined
191
- * @param {object} node
192
227
  * @returns {object} { isExposable, typeDef, typeName, elements, isAnonymous }
193
228
  */
194
- function isTypeExposable(node) {
229
+ function isTypeExposable() {
195
230
  let typeName = undefined;
196
231
  let typeDef = node;
197
- let elements = (node.items && node.items.elements || node.elements)
232
+ let elements = (node.items?.elements || node.elements)
198
233
  // anonymous structured type
199
234
  if(elements)
200
235
  return { isExposable: true, typeDef, typeName, elements, isAnonymous: true };
201
236
  // named type, resolve the type to inspect it
202
- let type = node.items && node.items.type || node.type;
237
+ let type = node.items?.type || node.type;
203
238
  if(type) {
204
239
  typeName = (type.ref && csnUtils.getFinalType(type)) || type;
205
- typeDef = csnUtils.getFinalTypeDef(typeName);
206
240
  const rc = { isExposable: true, typeDef, typeName, isAnonymous: false };
207
- if(!isBuiltinType(typeName) && !isArtifactInService(typeName, serviceName)) {
208
- while(!isBuiltinType(typeName)) {
209
- typeDef = csnUtils.getFinalTypeDef(typeName);
210
- if(typeDef) {
211
- if((rc.elements = (typeDef.items && typeDef.items.elements || typeDef.elements)) !== undefined)
212
- return rc;
213
- type = typeDef.items && typeDef.items.type || typeDef.type;
214
- typeName = (type.ref && csnUtils.getFinalType(type)) || type;
215
- }
216
- else {
217
- throw Error(`Please debug me: ${typeName} not found`);
241
+ if(!isBuiltinType(typeName)) {
242
+ rc.typeDef = typeDef = csnUtils.getFinalTypeDef(typeName);
243
+ if(!isArtifactInService(typeName, serviceName)) {
244
+ while(!isBuiltinType(typeName)) {
245
+ typeDef = csnUtils.getFinalTypeDef(typeName);
246
+ if(typeDef) {
247
+ if((isTermDef && typeDef.enum) || (rc.elements = (typeDef.items?.elements || typeDef.elements)) !== undefined)
248
+ return rc;
249
+ type = typeDef.items?.type || typeDef.type;
250
+ typeName = (type.ref && csnUtils.getFinalType(type)) || type;
251
+ }
252
+ else {
253
+ throw Error(`Please debug me: ${typeName} not found`);
254
+ }
218
255
  }
219
256
  }
257
+ else {
258
+ rc.isExposable = false;
259
+ return rc;
260
+ }
220
261
  }
262
+ // else if(isTermDef && typeDef.enum) {
263
+ // return rc;
264
+ // }
221
265
  }
222
266
  return { isExposable: false, typeDef, typeName, isAnonymous: false };
223
267
  }
@@ -308,7 +352,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
308
352
  */
309
353
  function getNewTypeName(element, elementName, typeNamePrefix, serviceName) {
310
354
  // for the new type name node.type has precedence over node.items.type
311
- const typeName = (!element.elements && element.type || element.items && !element.items.elements && element.items.type);
355
+ const typeName = (!element?.elements && element?.type || !element?.items?.elements && element?.items?.type);
312
356
  return typeName
313
357
  ? `${isMultiSchema
314
358
  ? typeName.split('.').pop() // use leaf element
@@ -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')