@sap/cds-compiler 4.3.2 → 4.4.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 (81) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/lib/api/main.js +14 -24
  3. package/lib/api/options.js +1 -0
  4. package/lib/api/trace.js +38 -0
  5. package/lib/base/location.js +46 -1
  6. package/lib/base/message-registry.js +68 -16
  7. package/lib/base/messages.js +8 -3
  8. package/lib/checks/.eslintrc.json +1 -0
  9. package/lib/checks/actionsFunctions.js +1 -1
  10. package/lib/checks/annotationsOData.js +2 -2
  11. package/lib/checks/selectItems.js +4 -1
  12. package/lib/compiler/assert-consistency.js +3 -2
  13. package/lib/compiler/base.js +1 -1
  14. package/lib/compiler/builtins.js +25 -1
  15. package/lib/compiler/checks.js +6 -5
  16. package/lib/compiler/define.js +12 -10
  17. package/lib/compiler/extend.js +22 -22
  18. package/lib/compiler/finalize-parse-cdl.js +1 -1
  19. package/lib/compiler/generate.js +70 -53
  20. package/lib/compiler/kick-start.js +7 -5
  21. package/lib/compiler/populate.js +31 -22
  22. package/lib/compiler/propagator.js +6 -2
  23. package/lib/compiler/resolve.js +52 -17
  24. package/lib/compiler/shared.js +74 -38
  25. package/lib/compiler/tweak-assocs.js +64 -23
  26. package/lib/compiler/utils.js +40 -23
  27. package/lib/edm/.eslintrc.json +2 -0
  28. package/lib/edm/EdmPrimitiveTypeDefinitions.js +252 -0
  29. package/lib/edm/annotations/edmJson.js +994 -0
  30. package/lib/edm/annotations/genericTranslation.js +75 -421
  31. package/lib/edm/annotations/vocabularyDefinitions.js +160 -0
  32. package/lib/edm/csn2edm.js +12 -5
  33. package/lib/edm/edm.js +14 -73
  34. package/lib/edm/edmPreprocessor.js +6 -0
  35. package/lib/gen/Dictionary.json +187 -16
  36. package/lib/gen/language.checksum +1 -1
  37. package/lib/gen/language.interp +1 -1
  38. package/lib/gen/languageLexer.interp +1 -1
  39. package/lib/gen/languageLexer.js +1129 -671
  40. package/lib/gen/languageParser.js +4285 -4283
  41. package/lib/json/from-csn.js +13 -18
  42. package/lib/json/to-csn.js +11 -6
  43. package/lib/language/antlrParser.js +0 -1
  44. package/lib/language/docCommentParser.js +1 -1
  45. package/lib/language/errorStrategy.js +95 -30
  46. package/lib/language/genericAntlrParser.js +21 -1
  47. package/lib/main.js +13 -3
  48. package/lib/model/csnRefs.js +42 -8
  49. package/lib/model/csnUtils.js +14 -2
  50. package/lib/model/enrichCsn.js +33 -5
  51. package/lib/model/revealInternalProperties.js +5 -0
  52. package/lib/modelCompare/compare.js +76 -14
  53. package/lib/modelCompare/utils/filter.js +19 -12
  54. package/lib/optionProcessor.js +2 -0
  55. package/lib/render/.eslintrc.json +1 -1
  56. package/lib/render/manageConstraints.js +1 -0
  57. package/lib/render/toHdbcds.js +3 -0
  58. package/lib/render/toRename.js +3 -1
  59. package/lib/render/toSql.js +46 -92
  60. package/lib/render/utils/common.js +76 -0
  61. package/lib/render/utils/delta.js +17 -3
  62. package/lib/sql-identifier.js +1 -1
  63. package/lib/transform/db/.eslintrc.json +1 -0
  64. package/lib/transform/db/applyTransformations.js +30 -4
  65. package/lib/transform/db/associations.js +22 -10
  66. package/lib/transform/db/backlinks.js +6 -2
  67. package/lib/transform/db/expansion.js +2 -2
  68. package/lib/transform/db/transformExists.js +13 -39
  69. package/lib/transform/draft/db.js +14 -3
  70. package/lib/transform/draft/odata.js +5 -18
  71. package/lib/transform/effective/associations.js +46 -15
  72. package/lib/transform/effective/main.js +7 -2
  73. package/lib/transform/effective/misc.js +43 -24
  74. package/lib/transform/effective/queries.js +20 -22
  75. package/lib/transform/effective/types.js +6 -2
  76. package/lib/transform/forOdata.js +5 -2
  77. package/lib/transform/localized.js +1 -1
  78. package/lib/transform/parseExpr.js +73 -21
  79. package/lib/transform/translateAssocsToJoins.js +22 -15
  80. package/lib/utils/term.js +2 -2
  81. package/package.json +2 -1
@@ -0,0 +1,994 @@
1
+ 'use strict';
2
+
3
+ const edmUtils = require('../edmUtils.js');
4
+ const { parseExpr } = require('../../transform/parseExpr');
5
+ const {
6
+ EdmTypeFacetMap,
7
+ EdmTypeFacetNames,
8
+ EdmPrimitiveTypeMap,
9
+ } = require('../EdmPrimitiveTypeDefinitions.js');
10
+ const { mapCdsToEdmType } = require('../edmUtils.js');
11
+ const { isBuiltinType } = require('../../model/csnUtils');
12
+
13
+ /**
14
+ * Translate a given token stream expression into an edmJson representation
15
+ *
16
+ * Returns $edmJson AST or val if val had no expression
17
+ * @param {object} carrier
18
+ * @param {object} anno
19
+ * @param {object} location
20
+ * @param {object} messageFunctions
21
+ * @returns {object}
22
+ */
23
+
24
+ function xpr2edmJson( carrier, anno, location, options, messageFunctions ) {
25
+ const { message, error } = messageFunctions;
26
+
27
+ const annoVal = carrier[anno];
28
+ if (!annoVal)
29
+ return annoVal;
30
+
31
+ const canonicFunctionDefinitions = {
32
+ 'odata.fillUriTemplate': { min: 2 },
33
+ 'odata.uriEncode': { exact: 1 },
34
+ // OData-ABNF
35
+ // string
36
+ 'odata.concat': { min: 2 },
37
+ 'odata.contains': { exact: 2 },
38
+ 'odata.endswith': { exact: 2 },
39
+ 'odata.indexof': { exact: 2 },
40
+ 'odata.length': { exact: 1 },
41
+ 'odata.matchesPattern': { exact: 2 },
42
+ 'odata.startswith': { exact: 2 },
43
+ 'odata.substring': { min: 2, max: 3 },
44
+ 'odata.tolower': { exact: 1 },
45
+ 'odata.toupper': { exact: 1 },
46
+ 'odata.trim': { exact: 1 },
47
+ // collection
48
+ 'odata.hassubset': { exact: 2 },
49
+ 'odata.hassubsequence': { exact: 2 },
50
+ // date & time
51
+ 'odata.year': { exact: 1 },
52
+ 'odata.month': { exact: 1 },
53
+ 'odata.day': { exact: 1 },
54
+ 'odata.hour': { exact: 1 },
55
+ 'odata.minute': { exact: 1 },
56
+ 'odata.second': { exact: 1 },
57
+ 'odata.fractionalseconds': { exact: 1 },
58
+ 'odata.totalseconds': { exact: 1 },
59
+ 'odata.date': { exact: 1 },
60
+ 'odata.time': { exact: 1 },
61
+ 'odata.totaloffsetminutes': { exact: 1 },
62
+ 'odata.mindatetime': { exact: 0 },
63
+ 'odata.maxdatetime': { exact: 0 },
64
+ 'odata.now': { exact: 0 },
65
+ // arithmetic
66
+ 'odata.round': { exact: 1 },
67
+ 'odata.floor': { exact: 1 },
68
+ 'odata.ceiling': { exact: 1 },
69
+ // geo
70
+ 'odata.geo.distance': { exact: 2 },
71
+ 'odata.geo.length': { exact: 1 },
72
+ 'odata.geo.intersects': { exact: 2 },
73
+ // deprecated
74
+ 'odata.cast': { use: 'cast(…)' },
75
+ 'odata.isof': { use: 'IsOf(…)' },
76
+ 'odata.case': { use: '?:' },
77
+ };
78
+ //----------------------------------
79
+ // Error transformer
80
+ const notADynExpr = (parent, op, xpr, parentparent, parentprop, txt) => {
81
+ error('odata-anno-xpr', location, {
82
+ anno, op: txt ??= op, '#': 'notadynexpr',
83
+ });
84
+ delete parent[op];
85
+ };
86
+ const noOp = () => (true);
87
+ //----------------------------------
88
+ // Create the transformer dictionary
89
+ const transform = {
90
+ args: noOp,
91
+ param: noOp,
92
+ literal: noOp,
93
+ //----------------------------------
94
+ // operators not supported as dynamic expression
95
+ '.': notADynExpr,
96
+ isNull: (p, o) => notADynExpr(p, o, null, null, null, 'is null'),
97
+ isNotNull: (p, o) => notADynExpr(p, o, null, null, null, 'is not null'),
98
+ exists: notADynExpr,
99
+ '#': notADynExpr,
100
+ SELECT: notADynExpr,
101
+ SET: (p, o) => notADynExpr(p, o, null, null, null, 'UNION'),
102
+ like: notADynExpr,
103
+ new: notADynExpr,
104
+ };
105
+
106
+ //----------------------------------
107
+ // list is a $Collection => []
108
+ transform.list = (parent, prop, xpr, parentparent, parentprop) => {
109
+ parentparent[parentprop] = xpr.filter(a => a);
110
+ applyTransformations(parentparent, transform);
111
+ };
112
+ // XPR
113
+ transform.xpr = (parent, prop, xpr, parentparent, parentprop) => {
114
+ // eliminate 'xpr' node by pulling up xpr node to its parent
115
+ parentparent[parentprop] = xpr;
116
+ applyTransformations(parentparent, transform);
117
+ };
118
+ //----------------------------------
119
+ // CASE
120
+ transform.case = (parent, prop, caseExpr) => {
121
+ // transform simple case expression into search case expression
122
+ // case <expr1> when <expr2> ... ===> case when <expr1> = <expr2> ...
123
+ let i = 0;
124
+
125
+ // single leg is not an array
126
+ if (!Array.isArray(caseExpr))
127
+ caseExpr = [ caseExpr ];
128
+
129
+ if (!caseExpr[i].when) {
130
+ caseExpr.filter(elt => elt.when).forEach((when) => {
131
+ when.when[0] = { '=': [ caseExpr[i], when.when[0] ] };
132
+ });
133
+ i++;
134
+ }
135
+ const edmIf = { $If: [ caseExpr[i].when[0], caseExpr[i].when[1] ] };
136
+ let curIf = edmIf;
137
+ for (i++; i < caseExpr.length && caseExpr[i].when; i++) {
138
+ const newEdmIf = { $If: [ caseExpr[i].when[0], caseExpr[i].when[1] ] };
139
+ curIf.$If.push(newEdmIf);
140
+ curIf = newEdmIf;
141
+ }
142
+ // else
143
+ if (i < caseExpr.length)
144
+ curIf.$If.push(caseExpr[i]);
145
+ parent.$If = edmIf.$If;
146
+ delete parent.case;
147
+ applyTransformations(parent, transform);
148
+ };
149
+ transform.$If = noOp;
150
+ //----------------------------------
151
+ // Cast => $Cast
152
+ transform.cast = (parent, prop, castExpr, parentparent, parentprop) => {
153
+ const csnType = castExpr[0];
154
+ // try to resolve to final scalar base type and use that instead of derived type
155
+ if (!isBuiltinType(csnType.type)) {
156
+ const finalType = options.getFinalTypeInfo(csnType.type);
157
+ if (finalType?.type && isBuiltinType(finalType.type)) {
158
+ csnType.type = finalType.type;
159
+ [ 'length', 'precision', 'scale', 'unicode', 'srid' ].forEach((facet) => {
160
+ csnType[facet] ??= finalType[facet];
161
+ });
162
+ }
163
+ }
164
+ const edmTypeName = mapCdsToEdmType(csnType, messageFunctions, options.isV2(), false, location);
165
+ const typeFunc = { func: 'Type', args: [ { val: edmTypeName } ] };
166
+ const castFunc = { func: '$Cast', args: [ typeFunc, castExpr[1] ] };
167
+
168
+ if (csnType.length != null)
169
+ typeFunc.args.push( { func: 'MaxLength', args: [ { val: csnType.length } ] });
170
+ if (csnType.srid != null)
171
+ typeFunc.args.push( { func: 'SRID', args: [ { val: csnType.srid } ] });
172
+ if (csnType.unicode != null)
173
+ typeFunc.args.push( { func: 'Unicode', args: [ { val: csnType.unicode } ] });
174
+ if (csnType.precision != null)
175
+ typeFunc.args.push( { func: 'Precision', args: [ { val: csnType.precision } ] });
176
+ else if (csnType.type === 'cds.Timestamp' && edmTypeName === 'Edm.DateTimeOffset')
177
+ typeFunc.args.push( { func: 'Precision', args: [ { val: 7 } ] });
178
+
179
+ if (edmTypeName === 'Edm.Decimal' && csnType.precision == null && csnType.scale == null || csnType.scale === 'floating')
180
+ typeFunc.args.push( { func: 'Scale', args: [ { val: 'variable' } ] });
181
+
182
+ else if (csnType.scale != null)
183
+ typeFunc.args.push( { func: 'Scale', args: [ { val: csnType.scale } ] });
184
+
185
+
186
+ parentparent[parentprop] = castFunc;
187
+ applyTransformations(parentparent, transform);
188
+ };
189
+ //----------------------------------
190
+ const evalArgs = (argDef, args, propName) => {
191
+ if (Array.isArray(args)) {
192
+ args = args.filter(a => a);
193
+ if (argDef.min != null && (!args || argDef.min > args.length)) {
194
+ error('odata-anno-xpr-args', location, {
195
+ anno, op: `${propName}(…)`, count: argDef.min, '#': 'atleast',
196
+ });
197
+ }
198
+ if (argDef.max != null && (!args || argDef.max < args.length)) {
199
+ error('odata-anno-xpr-args', location, {
200
+ anno, op: `${propName}(…)`, count: argDef.max, '#': 'atmost',
201
+ });
202
+ }
203
+ if (argDef.exact != null && (!args || argDef.exact !== args.length)) {
204
+ if (argDef.exact === 0) {
205
+ error('odata-anno-xpr-args', location, {
206
+ anno, op: `${propName}(…)`,
207
+ });
208
+ }
209
+ else {
210
+ error('odata-anno-xpr-args', location, {
211
+ anno, op: `${propName}(…)`, count: argDef.exact, '#': 'exactly',
212
+ });
213
+ }
214
+ }
215
+ }
216
+ };
217
+
218
+ // Binary Operator Macro
219
+ const op = (opStr, exact = 2) => (parent, prop, xpr) => {
220
+ evalArgs({ exact }, xpr, prop);
221
+ parent[opStr] = xpr;
222
+ delete parent[prop];
223
+ applyTransformations(parent, transform);
224
+ };
225
+ //----------------------------------
226
+ // LOGICAL
227
+ transform.and = op('$And');
228
+ transform.$And = noOp;
229
+ transform.or = op('$Or');
230
+ transform.$Or = noOp;
231
+ transform.not = op('$Not', 1);
232
+ transform.$Not = noOp;
233
+ //----------------------------------
234
+ // RELATIONAL
235
+ transform['='] = op('$Eq');
236
+ transform.$Eq = noOp;
237
+ transform['<>'] = op('$Ne');
238
+ transform['!='] = op('$Ne');
239
+ transform.$Ne = noOp;
240
+ transform['>'] = op('$Gt');
241
+ transform.$Gt = noOp;
242
+ transform['>='] = op('$Ge');
243
+ transform.$Ge = noOp;
244
+ transform['<'] = op('$Lt');
245
+ transform.$Lt = noOp;
246
+ transform['<='] = op('$Le');
247
+ transform.$Le = noOp;
248
+ transform.in = (parent, prop, xpr) => {
249
+ evalArgs({ min: 1 }, xpr[1].list, prop);
250
+ parent.$In = [ xpr[0], ...xpr[1].list ];
251
+ delete parent[prop];
252
+ applyTransformations(parent, transform);
253
+ };
254
+ transform.$In = noOp;
255
+ transform.between = (parent, prop, xpr, parentparent, parentprop) => {
256
+ evalArgs({ exact: 2 }, xpr.slice(1), prop);
257
+ applyTransformations(xpr, transform);
258
+ delete parent[prop];
259
+ parentparent[parentprop]
260
+ = {
261
+ $And: [
262
+ { $Le: [ xpr[1], xpr[0] ] },
263
+ { $Le: [ xpr[0], xpr[2] ] },
264
+ ],
265
+ };
266
+ };
267
+ transform['||'] = (parent, prop, xpr, parentparent, parentprop) => {
268
+ evalArgs({ exact: 2 }, xpr, prop);
269
+ applyTransformations(xpr, transform);
270
+ delete parent[prop];
271
+ parentparent[parentprop].$Apply = [ { $Function: 'odata.concat' }, ...xpr ];
272
+ };
273
+ //----------------------------------
274
+ // ARITHMETICAL AND UNARY
275
+ transform['+'] = (parent, prop, xpr, parentparent, parentprop) => {
276
+ if (Array.isArray(xpr)) {
277
+ op('$Add')(parent, prop, xpr);
278
+ }
279
+ else {
280
+ delete parent[prop];
281
+ parentparent[parentprop] = xpr;
282
+ applyTransformations(parentparent, transform);
283
+ }
284
+ };
285
+ transform.$Add = noOp;
286
+ transform['-'] = (parent, prop, xpr) => {
287
+ op(Array.isArray(xpr) ? '$Sub' : '$Neg')(parent, prop, xpr);
288
+ };
289
+ transform.$Sub = noOp;
290
+ transform.$Neg = noOp;
291
+ transform['*'] = op('$Mul');
292
+ transform.$Mul = noOp;
293
+ transform['/'] = op('$Div');
294
+ transform.$Div = noOp;
295
+ // $DivBy, $Mod are functions
296
+ //----------------------------------
297
+ // LITERALS
298
+ transform.val = (parent, prop, xpr, parentparent, parentprop) => {
299
+ if (xpr === null)
300
+ parent.$Null = true;
301
+ else
302
+ parentparent[parentprop] = xpr;
303
+ };
304
+ transform.ref = (parent, prop, xpr, parentparent, parentprop) => {
305
+ if (xpr.some(ps => ps.args || ps.where)) {
306
+ error('odata-anno-xpr-args', location, {
307
+ anno, elemref: parent, '#': 'wrongref',
308
+ });
309
+ }
310
+ parentparent[parentprop] = { $Path: xpr.map(ps => ps.id || ps).join('/') };
311
+ };
312
+ //----------------------------------
313
+ // Functions
314
+ transform.func = (parent, prop, xpr, parentparent, parentprop) => {
315
+ const rewriteArgs = (argDefs, evalVal = true) => {
316
+ Object.entries(argDefs).forEach(([ argName, argDef ]) => {
317
+ const [ foundProps, newArgs ]
318
+ = parent.args
319
+ ? parent.args.reduce((acc, arg) => {
320
+ ((arg.func === argName) ? acc[0] : acc[1]).push(arg);
321
+ return acc;
322
+ }, [ [], [] ] )
323
+ : [ [], [] ];
324
+ parent.args = newArgs;
325
+ if (foundProps.length !== 1) {
326
+ error('odata-anno-xpr-args', location, {
327
+ anno, op: `${xpr}(…)`, prop: `${argName}(…)`, '#': 'wrongcount',
328
+ });
329
+ }
330
+ else {
331
+ const func = foundProps[0];
332
+ evalArgs(argDef, func.args, argName);
333
+ if (func.args.length) {
334
+ // set prop (eventually undefined)
335
+ parent[argName] = /* func.args[0].ref?.join('.') || */ func.args[0].val;
336
+ if (evalVal && !parent[argName]) {
337
+ error('odata-anno-xpr-args', location, {
338
+ anno, op: `${argName}(…)`, meta: argDef.meta || 'literal', '#': 'wrongval_meta',
339
+ });
340
+ }
341
+ }
342
+ }
343
+ });
344
+ };
345
+
346
+ const rewriteType = () => {
347
+ let rc = true;
348
+ const isDollarFunc = xpr[0] === '$';
349
+
350
+ // Map Edm primitive type funcs to $Type funcs
351
+ let [ foundTypeProps, newArgs ]
352
+ = parent.args
353
+ ? parent.args.reduce((acc, arg) => {
354
+ (arg.func === '$Collection' || arg.func === 'Collection' ? acc[0] : acc[1]).push(arg);
355
+ return acc;
356
+ }, [ [], [] ] )
357
+ : [ [], [] ];
358
+
359
+ if (foundTypeProps.length === 1) {
360
+ const type = foundTypeProps[0];
361
+ evalArgs({ exact: 1 }, type.args, type.func);
362
+ if (type.args?.length === 1) {
363
+ const typeName = type.args[0].func;
364
+ if (EdmPrimitiveTypeMap[`Edm.${type.args[0].func}`])
365
+ newArgs.push({ func: typeName, args: type.args[0].args || [] });
366
+ else
367
+ newArgs.push(type);
368
+
369
+ parent.$Collection = true;
370
+ }
371
+ parent.args = newArgs;
372
+ }
373
+
374
+
375
+ let typePropName = isDollarFunc ? '$Type' : 'Type';
376
+ [ foundTypeProps, newArgs ]
377
+ = parent.args
378
+ ? parent.args.reduce((acc, arg) => {
379
+ (EdmPrimitiveTypeMap[`Edm.${arg.func}`] ? acc[0] : acc[1]).push(arg);
380
+ return acc;
381
+ }, [ [], [] ] )
382
+ : [ [], [] ];
383
+ if (foundTypeProps.length) {
384
+ foundTypeProps.forEach((type) => {
385
+ const edmType = `Edm.${type.func}`;
386
+ const td = EdmPrimitiveTypeMap[edmType];
387
+ const typeFuncDef = {
388
+ func: typePropName,
389
+ args: [ { val: edmType } ],
390
+ };
391
+ evalArgs(td, type.args, type.func);
392
+ if (type.args.length) {
393
+ let i = 0;
394
+ EdmTypeFacetNames.forEach((facetName) => {
395
+ const facetDef = td[facetName];
396
+ if (facetDef && i < type.args.length) {
397
+ const facetFuncDef = {
398
+ func: `${facetName}`,
399
+ args: [ type.args[i++] ],
400
+ };
401
+ typeFuncDef.args.push(facetFuncDef);
402
+ }
403
+ });
404
+ }
405
+ newArgs.push(typeFuncDef);
406
+ });
407
+ parent.args = newArgs;
408
+ }
409
+
410
+ [ foundTypeProps, newArgs ]
411
+ = parent.args
412
+ ? parent.args.reduce((acc, arg) => {
413
+ ((arg.func === '$Type' || arg.func === 'Type') ? acc[0] : acc[1]).push(arg);
414
+ return acc;
415
+ }, [ [], [] ] )
416
+ : [ [], [] ];
417
+
418
+ parent.args = newArgs;
419
+ if (foundTypeProps.length !== 1) {
420
+ typePropName = isDollarFunc ? '$Type' : 'Type';
421
+ error('odata-anno-xpr-type', location, { anno, op: `${xpr}(…)` });
422
+ rc = false;
423
+ }
424
+ else {
425
+ let typeArg;
426
+ const typeProp = foundTypeProps[0];
427
+ typePropName = typeProp.func;
428
+
429
+ const [ collTypes, newTypeArgs ]
430
+ = typeProp.args
431
+ ? typeProp.args.reduce((acc, arg) => {
432
+ ((arg.func === '$Collection' || arg.func === 'Collection') ? acc[0] : acc[1]).push(arg);
433
+ return acc;
434
+ }, [ [], [] ] )
435
+ : [ [], [] ];
436
+ typeProp.args = newTypeArgs;
437
+
438
+ const [ scalarTypes, typeFacets ]
439
+ = typeProp.args.reduce((acc, arg) => {
440
+ ((/* arg.ref || */ arg.val) ? acc[0] : acc[1]).push(arg);
441
+ return acc;
442
+ }, [ [], [] ] );
443
+
444
+ let typeOpStr = collTypes.length
445
+ ? `${typePropName}(${isDollarFunc ? '$Collection' : 'Collection'}(…))`
446
+ : `${typePropName}(…)`;
447
+
448
+ if (collTypes.length) {
449
+ if (collTypes.length > 1 || scalarTypes.length) {
450
+ error('odata-anno-xpr-type', location, { anno, op: `${xpr}(…)` });
451
+ }
452
+ else {
453
+ typeOpStr = `${typePropName}(${collTypes[0].func}(…))`;
454
+ if (collTypes[0].args.length !== 1) {
455
+ error('odata-anno-xpr-type', location, { anno, op: `${xpr}(…)` });
456
+ }
457
+ else {
458
+ typeArg = collTypes[0].args[0];
459
+ parent.$Collection = true;
460
+ }
461
+ }
462
+ }
463
+ else if (scalarTypes.length !== 1) {
464
+ error('odata-anno-xpr-type', location, { anno, op: `${xpr}(…)` });
465
+ rc = false;
466
+ }
467
+ else {
468
+ typeArg = scalarTypes[0];
469
+ }
470
+ if (typeArg && rc) {
471
+ // do final type checks and assignment
472
+ const typeDef = typeArg?.ref?.join('.') || typeArg?.val;
473
+ if (typeof typeDef !== 'string')
474
+ error('odata-anno-xpr-type', location, { anno, op: `${xpr}(…)` });
475
+ else
476
+ parent.$Type = typeDef;
477
+
478
+ const td = EdmPrimitiveTypeMap[typeDef];
479
+ if (td) {
480
+ if (td.v2 !== options.isV2() && td.v4 !== options.isV4()) {
481
+ message('odata-spec-violation-type', location,
482
+ {
483
+ anno,
484
+ type: typeDef,
485
+ version: (options.isV4() ? '4.0' : '2.0'),
486
+ '#': 'incompatible_anno',
487
+ });
488
+ }
489
+ evalArgs(td, typeFacets, typeDef);
490
+ EdmTypeFacetNames.forEach((facetName) => {
491
+ const facetDef = EdmTypeFacetMap[facetName];
492
+ const optional
493
+ = (facetDef.optional !== undefined)
494
+ ? (Array.isArray(facetDef.optional)
495
+ ? facetDef.optional.includes(typeDef)
496
+ : facetDef.optional)
497
+ : false;
498
+
499
+ if (td[facetName]) {
500
+ // ignore facets that are not type relevant
501
+ const facetFuncName = isDollarFunc ? `$${facetName}` : facetName;
502
+ const facetArgs = typeFacets.filter(arg => arg.func === facetName || arg.func === `$${facetName}`);
503
+
504
+ if (facetArgs.length === 0 && !optional && (options.isV2() === facetDef.v2 || options.isV4() === facetDef.v4)) {
505
+ message('odata-spec-violation-type', location,
506
+ {
507
+ anno,
508
+ type: typeDef,
509
+ name: facetName,
510
+ version: (options.isV4() ? '4.0' : '2.0'),
511
+ '#': 'facet_anno',
512
+ });
513
+ }
514
+ else if (facetArgs.length > 1) {
515
+ error('odata-anno-xpr-args', location, {
516
+ anno, op: typeOpStr, prop: `${facetFuncName}(…)`, '#': 'wrongcount',
517
+ });
518
+ }
519
+ else if (facetArgs.length === 1) {
520
+ const facetArg = facetArgs[0];
521
+ if (facetArg.args.length !== 1) {
522
+ error('odata-anno-xpr-args', location, {
523
+ anno, op: `${facetFuncName}(…)`, count: 1, '#': 'exactly',
524
+ });
525
+ }
526
+ else {
527
+ const facetVal = facetArg.args[0].val;
528
+ const isNaN = Number.isNaN(Number.parseInt(facetVal, 10));
529
+ if (isNaN && options.isV4() && facetName === 'Scale' && facetVal !== 'variable') {
530
+ error('odata-anno-xpr-args', location, {
531
+ anno,
532
+ op: `${facetFuncName}(…)`,
533
+ meta: 'number',
534
+ rawvalues: [ 'variable' ],
535
+ '#': 'wrongval_meta_list',
536
+ });
537
+ }
538
+ else if (isNaN && facetName !== 'Scale') {
539
+ error('odata-anno-xpr-args', location, {
540
+ anno, op: `${facetFuncName}(…)`, meta: 'number', '#': 'wrongval_meta',
541
+ });
542
+ }
543
+ else {
544
+ parent[`$${facetName}`] = facetVal;
545
+ }
546
+ }
547
+ }
548
+ }
549
+ });
550
+ if (typeDef === 'Edm.Decimal') {
551
+ if (parent.$Precision && parent.$Scale) {
552
+ const precision = Number.parseInt(parent.$Precision, 10);
553
+ const scale = Number.parseInt(parent.$Scale, 10);
554
+ if (!Number.isNaN(precision) && !Number.isNaN(scale) && scale > precision) {
555
+ message('odata-spec-violation-type', location,
556
+ {
557
+ anno,
558
+ type: typeDef,
559
+ number: scale,
560
+ rawvalue: precision,
561
+ '#': 'scale_anno',
562
+ });
563
+ }
564
+ }
565
+ if (options.isV2() && parent.$Scale === 'variable') {
566
+ parent['@sap.variable.scale'] = true;
567
+ delete parent.$Scale;
568
+ }
569
+ }
570
+ }
571
+ else {
572
+ typeFacets.forEach((facet) => {
573
+ if (facet.args.length === 1 && facet.args[0].val) {
574
+ const facetName = facet.func.startsWith('$') ? facet.func.slice(1) : facet.func;
575
+ if (EdmTypeFacetMap[facetName])
576
+ parent[`$${facetName}`] = facet.args[0].val;
577
+ }
578
+ });
579
+ }
580
+
581
+ delete typeProp.args;
582
+ }
583
+ }
584
+ return rc;
585
+ };
586
+
587
+ const standard = (tgt = parent, x = xpr) => {
588
+ tgt[x] = parent.args?.filter(a => a);
589
+ delete parent.func;
590
+ delete parent.args;
591
+ };
592
+
593
+ const exactArgs = (tgt = parent, x = xpr, count) => {
594
+ standard(tgt, x);
595
+ evalArgs({ exact: count }, tgt[x], xpr);
596
+ };
597
+
598
+ const oneArg = (tgt = parent, x = xpr) => {
599
+ exactArgs(tgt, x, 1);
600
+ };
601
+
602
+ const twoArgs = (tgt = parent, x = xpr) => {
603
+ exactArgs(tgt, x, 2);
604
+ };
605
+
606
+ const dollar = () => {
607
+ parent[`$${xpr}`] = parent[xpr];
608
+ delete parent[xpr];
609
+ };
610
+
611
+ const apply = (argDefs, propName) => {
612
+ rewriteArgs(argDefs); // $Function
613
+ standard();
614
+ let funcName = parent[propName];
615
+ if (funcName) {
616
+ if (!funcName.startsWith('odata.'))
617
+ funcName = `odata.${funcName}`;
618
+
619
+ const argDef = canonicFunctionDefinitions[funcName];
620
+ if (argDef) {
621
+ if (argDef.use) {
622
+ error('odata-anno-xpr', location, {
623
+ anno, op: parent[propName], code: argDef.use, '#': 'use',
624
+ });
625
+ }
626
+ else {
627
+ evalArgs(argDef, parent[xpr], xpr);
628
+ }
629
+ }
630
+ else {
631
+ funcName = parent[propName];
632
+ if (funcName.split('.').length !== 2) {
633
+ error('odata-anno-xpr', location, {
634
+ anno, op: `${propName}(…)`, code: funcName, meta: 'namespace', othermeta: 'function', '#': 'canonfuncalias',
635
+ });
636
+ }
637
+ }
638
+ }
639
+ };
640
+
641
+ // these are the function transformers
642
+ const funcDefs = {
643
+ $Has: twoArgs,
644
+ Has: [ twoArgs, dollar ],
645
+ $DivBy: twoArgs,
646
+ DivBy: [ twoArgs, dollar ],
647
+ $Mod: twoArgs,
648
+ Mod: [ twoArgs, dollar ],
649
+ $Apply: () => {
650
+ apply( { $Function: { exact: 1 } }, '$Function');
651
+ },
652
+ Apply: () => {
653
+ apply({ Function: { exact: 1 } }, 'Function');
654
+ dollar();
655
+ },
656
+ $Cast: () => {
657
+ if (rewriteType())
658
+ oneArg();
659
+ else
660
+ standard();
661
+ },
662
+ $IsOf: () => {
663
+ if (rewriteType())
664
+ oneArg();
665
+ else
666
+ standard();
667
+ },
668
+ IsOf: () => {
669
+ if (rewriteType())
670
+ oneArg();
671
+ else
672
+ standard();
673
+ dollar();
674
+ },
675
+ $LabeledElement: () => {
676
+ rewriteArgs({ $Name: { exact: 1, meta: 'qualified name' } });
677
+ oneArg();
678
+ },
679
+ LabeledElement: () => {
680
+ rewriteArgs({ Name: { exact: 1, meta: 'qualified name' } });
681
+ oneArg();
682
+ dollar();
683
+ },
684
+ $LabeledElementReference: () => {
685
+ oneArg();
686
+ if (parent[xpr].length === 1 && typeof parent[xpr][0].val !== 'string') {
687
+ error('odata-anno-xpr-args', location, {
688
+ anno, op: `${xpr}(…)`, meta: 'literal', '#': 'wrongval_meta',
689
+ });
690
+ }
691
+ },
692
+ $UrlRef: oneArg,
693
+ UrlRef: [ oneArg, dollar ],
694
+ // $Record ???
695
+ $Collection: () => {
696
+ standard(parentparent, parentprop);
697
+ applyTransformations(parentparent, transform);
698
+ },
699
+ $Path: () => {
700
+ oneArg(parent, xpr);
701
+ const args = parent[xpr];
702
+ if (args?.length && typeof args[0].val !== 'string') {
703
+ error('odata-anno-xpr-args', location, {
704
+ anno, op: `${xpr}(…)`, meta: 'string', '#': 'wrongval_meta',
705
+ });
706
+ }
707
+ applyTransformations(parentparent, transform);
708
+ },
709
+ $Null: () => {
710
+ parent[xpr] = true;
711
+ delete parent.func;
712
+ if (parent.args?.length)
713
+ error('odata-anno-xpr-args', location, { anno, op: `${xpr}(…)` });
714
+ delete parent.args;
715
+ },
716
+ };
717
+ funcDefs.LabeledElementReference = [ funcDefs.$LabeledElementReference, dollar ];
718
+ funcDefs.Collection = funcDefs.$Collection;
719
+ funcDefs.Path = funcDefs.$Path;
720
+
721
+ const funcDef = funcDefs[xpr];
722
+ if (funcDef) {
723
+ if (Array.isArray(funcDef))
724
+ funcDef.forEach(f => f());
725
+ else
726
+ funcDef();
727
+ applyTransformations(parent, transform);
728
+ }
729
+ else {
730
+ const funcName = xpr.startsWith('odata.') ? xpr : `odata.${xpr}`;
731
+ const argDef = canonicFunctionDefinitions[funcName];
732
+ if (argDef) {
733
+ if (argDef.use) {
734
+ error('odata-anno-xpr', location, {
735
+ anno, op: `${xpr}(…)`, code: argDef.use, '#': 'use',
736
+ });
737
+ }
738
+ else {
739
+ evalArgs(argDef, parent.args, xpr);
740
+ parentparent[parentprop].$Apply = [ { $Function: funcName }, ...(parent.args || []) ];
741
+ delete parentparent[parentprop].func;
742
+ delete parentparent[parentprop].args;
743
+ applyTransformations(parentparent, transform);
744
+ }
745
+ }
746
+ else {
747
+ error('odata-anno-xpr', location, {
748
+ anno, op: `${xpr}(…)`, '#': 'notadynexpr',
749
+ });
750
+ }
751
+ }
752
+ delete parent[prop];
753
+ };
754
+
755
+ return applyTransformations({ annoVal }, {
756
+ '=': (parent, prop, xpr, parentparent, parentprop) => {
757
+ delete parent['='];
758
+ parentparent[parentprop] = applyTransformations({ $edmJson: parseExpr( parent, { array: false }) }, transform);
759
+ },
760
+ }).annoVal;
761
+
762
+ function applyTransformations( parent, customTransformers ) {
763
+ function standard( _parent, _prop, node ) {
764
+ if (!node || typeof node !== 'object' ||
765
+ !{}.propertyIsEnumerable.call( _parent, _prop ) ||
766
+ (typeof _prop === 'string' && _prop.startsWith('@')))
767
+ return;
768
+
769
+
770
+ if (Array.isArray(node)) {
771
+ node.forEach( (n, i) => standard( node, i, n ) );
772
+ }
773
+ else {
774
+ for (const name of Object.getOwnPropertyNames( node )) {
775
+ const ct = customTransformers[name];
776
+ if (ct) {
777
+ if (Array.isArray(ct))
778
+ ct.forEach(cti => cti(node, name, node[name], _parent, _prop));
779
+ else
780
+ ct(node, name, node[name], _parent, _prop);
781
+ }
782
+ standard(node, name, node[name]);
783
+ }
784
+ }
785
+ }
786
+ for (const name of Object.getOwnPropertyNames( parent ))
787
+ standard( parent, name, parent[name] );
788
+ return parent;
789
+ }
790
+ }
791
+
792
+ // Not everything that can occur in OData annotations can be expressed with
793
+ // corresponding constructs in cds annotations. For these special cases
794
+ // we have a kind of "inline assembler" mode, i.e. you can in cds provide
795
+ // as annotation value a json snippet that looks like the final edm-json.
796
+ // See example in test/odataAnnotations/smallTests/edmJson_noReverse_ok
797
+ // and test3/ODataBackends/DynExpr
798
+
799
+ function getEdmJsonHandler( Edm, options, messageFunctions, handleTerm ) {
800
+ const { message } = messageFunctions;
801
+
802
+ const { v } = options;
803
+ const dynamicExpressions = {
804
+ $And: { create: () => new Edm.Expr(v, 'And'), anno: true },
805
+ $Or: { create: () => new Edm.Expr(v, 'Or'), anno: true },
806
+ $Not: { create: () => new Edm.Expr(v, 'Not'), anno: true },
807
+ $Eq: { create: () => new Edm.Expr(v, 'Eq'), anno: true },
808
+ $Ne: { create: () => new Edm.Expr(v, 'Ne'), anno: true },
809
+ $Gt: { create: () => new Edm.Expr(v, 'Gt'), anno: true },
810
+ $Ge: { create: () => new Edm.Expr(v, 'Ge'), anno: true },
811
+ $Lt: { create: () => new Edm.Expr(v, 'Lt'), anno: true },
812
+ $Le: { create: () => new Edm.Expr(v, 'Le'), anno: true },
813
+ // valueThingName: 'EnumMember' Implicit Cast Rule String => Primitive Type is OK
814
+ $Has: { create: () => new Edm.Expr(v, 'Has'), anno: true },
815
+ $In: { create: () => new Edm.Expr(v, 'In'), anno: true },
816
+ $Add: { create: () => new Edm.Expr(v, 'Add'), anno: true },
817
+ $Sub: { create: () => new Edm.Expr(v, 'Sub'), anno: true },
818
+ $Neg: { create: () => new Edm.Expr(v, 'Neg'), anno: true },
819
+ $Mul: { create: () => new Edm.Expr(v, 'Mul'), anno: true },
820
+ $Div: { create: () => new Edm.Expr(v, 'Div'), anno: true },
821
+ $DivBy: { create: () => new Edm.Expr(v, 'DivBy'), anno: true },
822
+ $Mod: { create: () => new Edm.Expr(v, 'Mod'), anno: true },
823
+ $Apply: {
824
+ create: () => new Edm.Apply(v),
825
+ attr: [ '$Function' ],
826
+ anno: true,
827
+ },
828
+ $Cast: {
829
+ create: () => new Edm.Cast(v),
830
+ attr: [ '$Type', ...EdmTypeFacetNames.map(n => `$${n}`), '@sap.variable.scale' ],
831
+ jsonAttr: [ '$Collection' ],
832
+ anno: true,
833
+ },
834
+ $IsOf: {
835
+ create: () => new Edm.IsOf(v),
836
+ attr: [ '$Type', ...EdmTypeFacetNames.map(n => `$${n}`), '@sap.variable.scale' ],
837
+ jsonAttr: [ '$Collection' ],
838
+ anno: true,
839
+ },
840
+ $If: { create: () => new Edm.If(v), anno: true },
841
+ $LabeledElement: {
842
+ create: () => new Edm.LabeledElement(v),
843
+ attr: [ '$Name' ],
844
+ anno: true,
845
+ },
846
+ $LabeledElementReference: {
847
+ create: obj => new Edm.LabeledElementReference(v, obj.$LabeledElementReference),
848
+ },
849
+ $UrlRef: { create: () => new Edm.UrlRef(v), anno: true },
850
+ $Null: { create: () => new Edm.Null(v), anno: true, children: false },
851
+ };
852
+ Object.entries(dynamicExpressions).forEach(([ k, dv ]) => {
853
+ if (!dv.name)
854
+ dv.name = k.slice(1);
855
+ if (dv.children === undefined)
856
+ dv.children = true;
857
+ });
858
+ const dynamicExpressionNames = Object.keys(dynamicExpressions);
859
+ return { handleEdmJson };
860
+
861
+ function handleEdmJson( obj, msgContext, exprDef ) {
862
+ let edmNode;
863
+ if (obj == null)
864
+ return edmNode;
865
+
866
+ const dynExprs = edmUtils.intersect(dynamicExpressionNames, Object.keys(obj));
867
+
868
+ if (dynExprs.length > 1) {
869
+ message('odata-anno-value', msgContext.location,
870
+ { anno: msgContext.anno(), rawvalues: dynExprs, '#': 'multexpr' });
871
+ return edmNode;
872
+ }
873
+
874
+ if (dynExprs.length === 0) {
875
+ if (typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 1) {
876
+ const k = Object.keys(obj)[0];
877
+ const val = obj[k];
878
+ edmNode = new Edm.ValueThing(v, k[0] === '$' ? k.slice(1) : k, val );
879
+ edmNode.setJSON( { [edmNode.kind]: val } );
880
+ }
881
+ // This thing is either a record or a collection or a literal
882
+ else if (Array.isArray(obj)) {
883
+ // EDM JSON doesn't mention annotations on collections
884
+ edmNode = new Edm.Collection(v);
885
+ obj.forEach(o => edmNode.append(handleEdmJson(o, msgContext)));
886
+ }
887
+ else if (typeof obj === 'object') {
888
+ edmNode = new Edm.Record(v);
889
+ const annos = Object.create(null);
890
+ const props = Object.create(null);
891
+ Object.entries(obj).forEach(([ k, val ]) => {
892
+ if (k === '@type') {
893
+ edmNode.setJSON({ Type: val });
894
+ // try to shorten full qualified type URI to short type name
895
+ const parts = val.split('#');
896
+ const shortTypeName = parts[parts.length - 1];
897
+ edmNode.setXml({ Type: shortTypeName });
898
+ }
899
+ else {
900
+ let child;
901
+ const [ head, tail ] = k.split('@');
902
+ if (tail) {
903
+ child = handleTerm(tail, val, msgContext);
904
+ }
905
+ else {
906
+ child = new Edm.PropertyValue(v, head);
907
+ child.append(handleEdmJson(val, msgContext));
908
+ }
909
+ if (child) {
910
+ if (tail && head.length) {
911
+ if (!annos[head])
912
+ annos[head] = [ child ];
913
+ else
914
+ annos[head].push(child);
915
+ }
916
+ else {
917
+ if (head.length)
918
+ props[head] = child;
919
+ edmNode.append(child);
920
+ }
921
+ }
922
+ }
923
+ });
924
+ // add collected annotations to record members
925
+ Object.entries(annos).forEach(([ n, val ]) => {
926
+ if (props[n])
927
+ props[n].prepend(...val);
928
+ });
929
+ }
930
+ else { // literal
931
+ edmNode = new Edm.ValueThing(v,
932
+ exprDef?.valueThingName || getXmlTypeName(obj), obj);
933
+ // typename for static expression rendering
934
+ edmNode.setJSON( { [getJsonTypeName(obj)]: obj } );
935
+ }
936
+ }
937
+ else {
938
+ // name of special property determines element kind
939
+ exprDef = dynamicExpressions[dynExprs[0]];
940
+ edmNode = exprDef.create(obj);
941
+
942
+ // iterate over each obj.property and translate expression into EDM
943
+ Object.entries(obj).forEach(([ name, val ]) => {
944
+ if (exprDef) {
945
+ if (exprDef.anno && name[0] === '@' && !name.startsWith('@sap.')) {
946
+ edmNode.append(handleTerm(name.slice(1), val, msgContext));
947
+ }
948
+ else if (exprDef.attr && exprDef.attr.includes(name)) {
949
+ if (options.isV2() && name.startsWith('@sap.'))
950
+ edmNode.setXml( { [`sap:${name.slice(5).replace(/\./g, '-')}`]: val } );
951
+ if (name[0] === '$')
952
+ edmNode.setEdmAttribute(name.slice(1), val);
953
+ }
954
+ else if (exprDef.jsonAttr && exprDef.jsonAttr.includes(name)) {
955
+ if (name[0] === '$')
956
+ edmNode.setJSON( { [name.slice(1)]: val });
957
+ }
958
+ else if (exprDef.children) {
959
+ if (Array.isArray(val)) {
960
+ val.forEach((a) => {
961
+ edmNode.append(handleEdmJson(a, msgContext, exprDef));
962
+ });
963
+ }
964
+ else {
965
+ edmNode.append(handleEdmJson(val, msgContext, exprDef));
966
+ }
967
+ }
968
+ }
969
+ });
970
+ }
971
+ return edmNode;
972
+
973
+ function getXmlTypeName( val ) {
974
+ let typeName = 'String';
975
+ if (typeof val === 'boolean')
976
+ typeName = 'Bool';
977
+
978
+ else if (typeof val === 'number')
979
+ typeName = Number.isInteger(val) ? 'Int' : 'Decimal';
980
+
981
+ return typeName;
982
+ }
983
+
984
+ function getJsonTypeName( val ) {
985
+ const typeName = getXmlTypeName(val);
986
+ if (typeName === 'Int')
987
+ return 'Edm.Int32';
988
+ return `Edm.${typeName}`;
989
+ }
990
+ }
991
+ }
992
+
993
+
994
+ module.exports = { xpr2edmJson, getEdmJsonHandler };