@sap/cds-dk 10.0.3 → 10.0.4

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 (41) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/node_modules/@cap-js/db-service/CHANGELOG.md +768 -0
  3. package/node_modules/@cap-js/db-service/LICENSE +201 -0
  4. package/node_modules/@cap-js/db-service/README.md +26 -0
  5. package/node_modules/@cap-js/db-service/index.js +19 -0
  6. package/node_modules/@cap-js/db-service/lib/InsertResults.js +122 -0
  7. package/node_modules/@cap-js/db-service/lib/SQLService.js +581 -0
  8. package/node_modules/@cap-js/db-service/lib/common/DatabaseService.js +179 -0
  9. package/node_modules/@cap-js/db-service/lib/common/factory.d.ts +5 -0
  10. package/node_modules/@cap-js/db-service/lib/common/generic-pool.js +310 -0
  11. package/node_modules/@cap-js/db-service/lib/common/session-context.js +46 -0
  12. package/node_modules/@cap-js/db-service/lib/converters.d.ts +24 -0
  13. package/node_modules/@cap-js/db-service/lib/cql-functions.js +464 -0
  14. package/node_modules/@cap-js/db-service/lib/cqn2pql.js +116 -0
  15. package/node_modules/@cap-js/db-service/lib/cqn2sql.js +1612 -0
  16. package/node_modules/@cap-js/db-service/lib/cqn4sql.js +2895 -0
  17. package/node_modules/@cap-js/db-service/lib/deep-queries.js +290 -0
  18. package/node_modules/@cap-js/db-service/lib/fill-in-keys.js +75 -0
  19. package/node_modules/@cap-js/db-service/lib/infer/cqn.d.ts +42 -0
  20. package/node_modules/@cap-js/db-service/lib/infer/index.js +1290 -0
  21. package/node_modules/@cap-js/db-service/lib/infer/join-tree.js +278 -0
  22. package/node_modules/@cap-js/db-service/lib/infer/pseudos.js +24 -0
  23. package/node_modules/@cap-js/db-service/lib/search.js +220 -0
  24. package/node_modules/@cap-js/db-service/lib/utils.js +177 -0
  25. package/node_modules/@cap-js/db-service/package.json +36 -0
  26. package/node_modules/@cap-js/sqlite/CHANGELOG.md +404 -0
  27. package/node_modules/@cap-js/sqlite/LICENSE +201 -0
  28. package/node_modules/@cap-js/sqlite/README.md +34 -0
  29. package/node_modules/@cap-js/sqlite/cds-plugin.js +0 -0
  30. package/node_modules/@cap-js/sqlite/index.js +2 -0
  31. package/node_modules/@cap-js/sqlite/lib/SQLiteService.js +324 -0
  32. package/node_modules/@cap-js/sqlite/lib/cql-functions.js +160 -0
  33. package/node_modules/@cap-js/sqlite/lib/node-sqlite.js +36 -0
  34. package/node_modules/@cap-js/sqlite/lib/session.json +3 -0
  35. package/node_modules/@cap-js/sqlite/lib/sql.js.js +99 -0
  36. package/node_modules/@cap-js/sqlite/package.json +68 -0
  37. package/node_modules/iconv-lite/encodings/sbcs-data.js +2 -0
  38. package/node_modules/iconv-lite/encodings/utf32.js +10 -3
  39. package/node_modules/iconv-lite/package.json +2 -2
  40. package/node_modules/iconv-lite/types/encodings.d.ts +2 -0
  41. package/package.json +5 -3
@@ -0,0 +1,464 @@
1
+ 'use strict'
2
+
3
+ const cds = require('@sap/cds')
4
+
5
+ // OData: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_CanonicalFunctions
6
+ const StandardFunctions = {
7
+ /**
8
+ * Generates SQL statement that produces a runtime compatible error object
9
+ * @param {string|object} message - The i18n key or message of the error object
10
+ * @param {Array<xpr>} args - The arguments to apply to the i18n string
11
+ * @param {Array<xpr>} targets - The name of the element that the error is related to
12
+ * @return {string} - SQL statement
13
+ */
14
+ error: function (message, args, targets) {
15
+ targets = targets && (targets.list || (targets.val || targets.ref) && [targets])
16
+ if (Array.isArray(targets)) targets = targets.map(e => e.ref && { val: e.ref.at(-1) } || e)
17
+ args = args && (args.list || (args.val || args.ref) && [args])
18
+
19
+ return `(${this.SELECT({
20
+ SELECT: {
21
+ expand: 'root',
22
+ columns: [
23
+ {
24
+ __proto__: (message || { val: null }),
25
+ as: 'message',
26
+ },
27
+ args ? {
28
+ func: 'json_array',
29
+ args: args,
30
+ as: 'args',
31
+ element: cds.builtin.types.Map,
32
+ } : { val: null, as: 'args' },
33
+ targets ? {
34
+ func: 'json_array',
35
+ args: targets,
36
+ as: 'targets',
37
+ element: cds.builtin.types.Map,
38
+ } : { val: null, as: 'targets' },
39
+ ]
40
+ },
41
+ elements: {
42
+ message: cds.builtin.types.String,
43
+ args: cds.builtin.types.Map,
44
+ targets: cds.builtin.types.Map,
45
+ }
46
+ })})`
47
+ },
48
+
49
+ /**
50
+ * Generates SQL statement that produces a boolean value indicating whether the search term is contained in the given columns
51
+ * @param {string} ref - The reference object containing column information
52
+ * @param {string} arg - The argument object containing the search value
53
+ * @returns {string} - SQL statement
54
+ */
55
+ search: function (ref, arg) {
56
+ if (!('val' in arg)) throw new Error('Only single value arguments are allowed for $search')
57
+ // Only apply first search term, rest is ignored
58
+ const sub = /("")|("(?:[^"]|\\")*(?:[^\\]|\\\\)")|(\S*)/.exec(arg.val)
59
+ let val
60
+ try {
61
+ val = (sub[2] ? JSON.parse(sub[2]) : sub[3]) || ''
62
+ } catch {
63
+ val = sub[2] || sub[3] || ''
64
+ }
65
+ arg.val = val
66
+ const refs = ref.list || [ref]
67
+ return `(${refs.map(ref => this.expr({
68
+ func: 'contains',
69
+ args: [
70
+ { func: 'tolower', args: [ref] },
71
+ { func: 'tolower', args: [arg] },
72
+ ]
73
+ })).join(' or ')})`
74
+ },
75
+
76
+ // ==============================
77
+ // Aggregation Functions
78
+ // ==============================
79
+
80
+ /**
81
+ * Generates SQL statement that produces the average of a given expression
82
+ * @param {string} x - The expression to average
83
+ * @returns {string} - SQL statement
84
+ */
85
+ average: x => `avg(${x})`,
86
+
87
+ /**
88
+ * Generates SQL statement that produces the number of elements in a given collection
89
+ * @param {string} x - The collection input
90
+ * @returns {string} - SQL statement
91
+ */
92
+ count: x => `count(${x?.val || x || '*'})`,
93
+
94
+ /**
95
+ * Generates SQL statement that produces the number of distinct values of a given expression
96
+ * @param {string} x - The expression input
97
+ * @returns {string} - SQL statement
98
+ */
99
+ countdistinct: x => `count(distinct ${x.val || x || '*'})`,
100
+
101
+ // ==============================
102
+ // String Functions
103
+ // ==============================
104
+
105
+ /**
106
+ * Generates SQL statement that produces the length of a given string
107
+ * @param {string} x - The string input
108
+ * @returns {string} - SQL statement
109
+ */
110
+ length: x => `length(${x})`,
111
+
112
+ /**
113
+ * Generates SQL statement that produces a string with all provided strings concatenated
114
+ * @param {...string} args - The strings to concatenate
115
+ * @returns {string} - SQL statement
116
+ */
117
+ concat: (...args) => args.map(a => (a.xpr ? `(${a})` : a)).join(' || '),
118
+
119
+ /**
120
+ * Generates SQL statement that produces the substring of a given string
121
+ * @example
122
+ * // returns 'bc'
123
+ * {func:'substring',args:[{val:'abc'},{val:1}]}
124
+ * @example
125
+ * // returns 'b'
126
+ * {func:'substring',args:[{val:'abc'},{val:1},{val:1}]}
127
+ * @param {string} x - The string input
128
+ * @param {string} y - The starting position
129
+ * @param {string} [z] - Optional length of the substring
130
+ * @returns {string} - SQL statement
131
+ */
132
+ substring: (x, y, z) =>
133
+ z
134
+ ? `substr(${x}, case when ${y} < 0 then length(${x}) + ${y} + 1 else ${y} + 1 end, ${z})`
135
+ : `substr(${x}, case when ${y} < 0 then length(${x}) + ${y} + 1 else ${y} + 1 end)`,
136
+
137
+ /**
138
+ * Generates SQL statement that produces the lower case value of a given string
139
+ * @param {string} x - The string input
140
+ * @returns {string} - SQL statement
141
+ */
142
+ tolower: x => `lower(${x})`,
143
+
144
+ /**
145
+ * Generates SQL statement that produces the upper case value of a given string
146
+ * @param {string} x - The string input
147
+ * @returns {string} - SQL statement
148
+ */
149
+ toupper: x => `upper(${x})`,
150
+
151
+ /**
152
+ * Generates SQL statement that produces the trimmed value of a given string
153
+ * @param {string} x - The string input
154
+ * @returns {string} - SQL statement
155
+ */
156
+ trim: x => `trim(${x})`,
157
+
158
+ // ==============================
159
+ // Arithmetic Functions
160
+ // ==============================
161
+
162
+ /**
163
+ * Generates SQL statement that produces the rounded up value of a given number
164
+ * @param {string} x - The number input
165
+ * @returns {string} - SQL statement
166
+ */
167
+ ceiling: x => `ceil(${x})`,
168
+
169
+ /**
170
+ * Generates SQL statement that produces the rounded down value of a given number
171
+ * @param {string} x - The number input
172
+ * @returns {string} - SQL statement
173
+ */
174
+ floor: x => `floor(${x})`,
175
+
176
+ /**
177
+ * Generates SQL statement that produces the rounded value of a given number
178
+ * @param {string} x - The number input
179
+ * @param {string} p - The precision
180
+ * @returns {string} - SQL statement
181
+ */
182
+ round: (x, p) => `round(${x}${p ? `,${p}` : ''})`,
183
+
184
+ // ==============================
185
+ // Date and Time Functions
186
+ // ==============================
187
+
188
+ /**
189
+ * Generates SQL statement that produces current point in time (date and time with time zone)
190
+ * @returns {string} - SQL statement
191
+ */
192
+ now: function () {
193
+ return this.expr({ func: 'session_context', args: [{ val: '$now' }] })
194
+ },
195
+
196
+ /**
197
+ * Maximum date time value
198
+ * @returns {string} - SQL statement
199
+ */
200
+ maxdatetime: () => `'9999-12-31T23:59:59.999Z'`,
201
+
202
+ /**
203
+ * Minimum date time value
204
+ * @returns {string} - SQL statement
205
+ */
206
+ mindatetime: () => `'0001-01-01T00:00:00.000Z'`,
207
+ }
208
+
209
+ const HANAFunctions = {
210
+ /**
211
+ * Generates SQL statement that calls the session_context function with the given parameter
212
+ * @param {string} x - The session variable name or SQL expression
213
+ * @returns {string} - SQL statement
214
+ */
215
+ session_context: x => `session_context('${x.val}')`,
216
+
217
+ /**
218
+ * Generates SQL statement for the current date
219
+ * @returns {string} - SQL statement
220
+ */
221
+ current_date: () => 'current_date',
222
+
223
+ /**
224
+ * Generates SQL statement for the current time
225
+ * @param {string} [p] - Optional precision parameter
226
+ * @returns {string} - SQL statement
227
+ */
228
+ current_time: p => (p ? `current_time(${p})` : 'current_time'),
229
+
230
+ /**
231
+ * Generates SQL statement for the current timestamp
232
+ * @param {string} [p] - Optional precision parameter
233
+ * @returns {string} - SQL statement
234
+ */
235
+ current_timestamp: p => (p ? `current_timestamp(${p})` : 'current_timestamp'),
236
+
237
+ /**
238
+ * Generates SQL statement for the hierarchy function
239
+ * @param {string} [p] -
240
+ * @returns {string} - SQL statement
241
+ */
242
+ HIERARCHY: function (args) {
243
+ let uniqueCounter = this._with?.length ?? 0
244
+ let src = args.xpr[1]
245
+
246
+ // Ensure that the orderBy column are exposed by the source for hierarchy sorting
247
+ let orderBy = args.xpr.find((_, i, arr) => /ORDER/i.test(arr[i - 2]) && /BY/i.test(arr[i - 1]))
248
+ // use ![] instead of ""
249
+ if (orderBy && typeof orderBy === 'string') {
250
+ orderBy = orderBy.replace(/"([^"]*)"/g, '![$1]');
251
+ }
252
+
253
+ const passThroughColumns = src.SELECT.columns.map(c => ({ ref: ['Source', this.column_name(c)] }))
254
+ src.as = 'H' + (uniqueCounter++)
255
+ src = this.expr(this.with(src))
256
+
257
+ let recursive = cds.ql(`
258
+ SELECT
259
+ 1 as HIERARCHY_LEVEL,
260
+ NODE_ID as HIERARCHY_ROOT_ID
261
+ FROM ${src} AS Source
262
+ WHERE parent_ID IS NULL
263
+ UNION ALL
264
+ SELECT
265
+ Parent.HIERARCHY_LEVEL + 1,
266
+ Parent.HIERARCHY_ROOT_ID
267
+ FROM ${src} AS Source
268
+ JOIN H${uniqueCounter} AS Parent ON Source.PARENT_ID=Parent.NODE_ID
269
+ ORDER BY HIERARCHY_LEVEL DESC${orderBy ? `,${orderBy}` : ''}`)
270
+ recursive.as = 'H' + (uniqueCounter++)
271
+ recursive.SET.args[0].SELECT.columns = [...recursive.SET.args[0].SELECT.columns, ...passThroughColumns]
272
+ recursive.SET.args[1].SELECT.columns = [...recursive.SET.args[1].SELECT.columns, ...passThroughColumns]
273
+ recursive = this.expr(this.with(recursive))
274
+
275
+ let ranked = cds.ql(`
276
+ SELECT
277
+ HIERARCHY_LEVEL,
278
+ row_number() over () as HIERARCHY_RANK,
279
+ HIERARCHY_ROOT_ID
280
+ FROM ${recursive} AS Source`)
281
+ ranked.as = 'H' + (uniqueCounter++)
282
+ ranked.SELECT.columns = [...ranked.SELECT.columns, ...passThroughColumns]
283
+ ranked = this.expr(this.with(ranked))
284
+
285
+ let Hierarchy = cds.ql(`
286
+ SELECT
287
+ HIERARCHY_LEVEL,
288
+ HIERARCHY_RANK,
289
+ (SELECT HIERARCHY_RANK FROM ${ranked} AS Ranked WHERE Ranked.NODE_ID = Source.PARENT_ID) AS HIERARCHY_PARENT_RANK,
290
+ (SELECT HIERARCHY_RANK FROM ${ranked} AS Ranked WHERE Ranked.NODE_ID = Source.HIERARCHY_ROOT_ID) AS HIERARCHY_ROOT_RANK,
291
+ coalesce(
292
+ (SELECT MIN(HIERARCHY_RANK) FROM ${ranked} AS Ranked WHERE Ranked.HIERARCHY_RANK > Source.HIERARCHY_RANK AND Ranked.HIERARCHY_LEVEL <= Source.HIERARCHY_LEVEL),
293
+ (SELECT MAX(HIERARCHY_RANK) + 1 FROM ${ranked})
294
+ ) - Source.HIERARCHY_RANK AS HIERARCHY_TREE_SIZE
295
+ FROM ${ranked} AS Source`)
296
+ Hierarchy.as = `H${uniqueCounter}`
297
+ Hierarchy.SELECT.columns = [...Hierarchy.SELECT.columns, ...passThroughColumns]
298
+ Hierarchy = this.expr(this.with(Hierarchy))
299
+
300
+ return Hierarchy
301
+ },
302
+
303
+ /**
304
+ * Generates SQL statement for the hierarchy_descendants function
305
+ * @param {string} [p] -
306
+ * @returns {string} - SQL statement
307
+ */
308
+ HIERARCHY_DESCENDANTS: function (args) {
309
+ // Find Hierarchy function call source query
310
+ const passThroughColumns = args.xpr[1].args[0].xpr[1].SELECT.columns.map(c => ({ ref: [this.column_name(c)] }))
311
+ // REVISIT: currently only supports func: HIERARCHY as source
312
+ const src = this.expr(args.xpr[1])
313
+
314
+ let uniqueCounter = this._with?.length ?? 0
315
+
316
+ let alias = args.xpr.find((_, i, arr) => /AS/i.test(arr[i - 1]))
317
+ const where = args.xpr.find((a, i, arr) => a.xpr && /WHERE/i.test(arr[i - 1]) && /START/i.test(arr[i - 2]))
318
+ const distance = args.xpr.find((a, i, arr) => typeof a.val === 'number' && (/DISTANCE/i.test(arr[i - 1]) || /DISTANCE/i.test(arr[i - 2])))
319
+ const distanceFrom = args.xpr.find((a, i, arr) => /FROM/.test(a) && /DISTANCE/i.test(arr[i - 1]))
320
+
321
+ if (alias.startsWith('"') && alias.endsWith('"')) alias = alias.slice(1, -1).replace(/""/g, '"')
322
+
323
+ let HierarchyDescendants = cds.ql(`
324
+ SELECT
325
+ HIERARCHY_LEVEL,
326
+ HIERARCHY_PARENT_RANK,
327
+ HIERARCHY_RANK,
328
+ HIERARCHY_ROOT_RANK,
329
+ HIERARCHY_TREE_SIZE,
330
+ 0 as HIERARCHY_DISTANCE
331
+ FROM ${src} AS ![${alias}]
332
+ UNION ALL
333
+ SELECT
334
+ Source.HIERARCHY_LEVEL,
335
+ Source.HIERARCHY_PARENT_RANK,
336
+ Source.HIERARCHY_RANK,
337
+ Source.HIERARCHY_ROOT_RANK,
338
+ Source.HIERARCHY_TREE_SIZE,
339
+ Child.HIERARCHY_DISTANCE + 1
340
+ FROM ${src} AS Source
341
+ JOIN H${uniqueCounter} AS Child ON Source.PARENT_ID=Child.NODE_ID`)
342
+ HierarchyDescendants.as = 'H' + uniqueCounter
343
+ HierarchyDescendants.SET.args[0].SELECT.where = where.xpr
344
+ HierarchyDescendants.SET.args[0].SELECT.columns = [...HierarchyDescendants.SET.args[0].SELECT.columns, ...passThroughColumns.map(r => ({ ref: [alias, r.ref[0]] }))]
345
+ HierarchyDescendants.SET.args[1].SELECT.columns = [...HierarchyDescendants.SET.args[1].SELECT.columns, ...passThroughColumns.map(r => ({ ref: ['Source', r.ref[0]] }))]
346
+
347
+ HierarchyDescendants = this.with(HierarchyDescendants)
348
+ HierarchyDescendants.as = 'HierarchyDescendants'
349
+
350
+ return this.expr({
351
+ SELECT: {
352
+ columns: [
353
+ { ref: ['HIERARCHY_LEVEL'] },
354
+ { ref: ['HIERARCHY_PARENT_RANK'] },
355
+ { ref: ['HIERARCHY_RANK'] },
356
+ { ref: ['HIERARCHY_ROOT_RANK'] },
357
+ { ref: ['HIERARCHY_TREE_SIZE'] },
358
+ {
359
+ SELECT: {
360
+ columns: [{ func: 'MAX', args: [{ ref: ['HIERARCHY_DISTANCE'] }] }],
361
+ from: HierarchyDescendants,
362
+ where: [{ ref: [HierarchyDescendants.as, 'HIERARCHY_RANK'] }, '=', { ref: [src, 'HIERARCHY_RANK'] }]
363
+ },
364
+ as: 'HIERARCHY_DISTANCE',
365
+ },
366
+ ...passThroughColumns,
367
+ ],
368
+ from: { ref: [src] },
369
+ where: [
370
+ { ref: ['HIERARCHY_RANK'] },
371
+ 'IN',
372
+ {
373
+ SELECT: {
374
+ columns: [{ ref: ['HIERARCHY_RANK'] }],
375
+ from: HierarchyDescendants,
376
+ where: [{ ref: ['HIERARCHY_DISTANCE'] }, distanceFrom ? '>=' : '=', distance]
377
+ }
378
+ }
379
+ ]
380
+ }
381
+ })
382
+ },
383
+
384
+ /**
385
+ * Generates SQL statement for the hierarchy_ancestors function
386
+ * @param {string} [p] -
387
+ * @returns {string} - SQL statement
388
+ */
389
+ HIERARCHY_ANCESTORS: function (args) {
390
+ // Find Hierarchy function call source query
391
+ const passThroughColumns = args.xpr[1].args[0].xpr[1].SELECT.columns.map(c => ({ ref: [this.column_name(c)] }))
392
+ // REVISIT: currently only supports func: HIERARCHY as source
393
+ const src = this.expr(args.xpr[1])
394
+
395
+ let uniqueCounter = this._with?.length ?? 0
396
+
397
+ let alias = args.xpr.find((_, i, arr) => /AS/i.test(arr[i - 1]))
398
+ const where = args.xpr.find((a, i, arr) => a.xpr && /WHERE/i.test(arr[i - 1]) && /START/i.test(arr[i - 2]))
399
+
400
+ if (alias.startsWith('"') && alias.endsWith('"')) alias = alias.slice(1, -1).replace(/""/g, '"')
401
+
402
+ let HierarchyAncestors = cds.ql(`
403
+ SELECT
404
+ HIERARCHY_LEVEL,
405
+ HIERARCHY_PARENT_RANK,
406
+ HIERARCHY_RANK,
407
+ HIERARCHY_ROOT_RANK,
408
+ HIERARCHY_TREE_SIZE,
409
+ 0 as HIERARCHY_DISTANCE
410
+ FROM ${src} AS ![${alias}]
411
+ UNION ALL
412
+ SELECT
413
+ Source.HIERARCHY_LEVEL,
414
+ Source.HIERARCHY_PARENT_RANK,
415
+ Source.HIERARCHY_RANK,
416
+ Source.HIERARCHY_ROOT_RANK,
417
+ Source.HIERARCHY_TREE_SIZE,
418
+ Child.HIERARCHY_DISTANCE - 1
419
+ FROM ${src} AS Source
420
+ JOIN H${uniqueCounter} AS Child ON Source.NODE_ID=Child.PARENT_ID`)
421
+ HierarchyAncestors.as = 'H' + uniqueCounter
422
+ HierarchyAncestors.SET.args[0].SELECT.where = where.xpr
423
+ HierarchyAncestors.SET.args[0].SELECT.columns = [...HierarchyAncestors.SET.args[0].SELECT.columns, ...passThroughColumns.map(r => ({ ref: [alias, r.ref[0]] }))]
424
+ HierarchyAncestors.SET.args[1].SELECT.columns = [...HierarchyAncestors.SET.args[1].SELECT.columns, ...passThroughColumns.map(r => ({ ref: ['Source', r.ref[0]] }))]
425
+
426
+ HierarchyAncestors = this.with(HierarchyAncestors)
427
+ HierarchyAncestors.as = 'HierarchyAncestors'
428
+ return this.expr({
429
+ SELECT: {
430
+ columns: [
431
+ { ref: ['HIERARCHY_LEVEL'] },
432
+ { ref: ['HIERARCHY_PARENT_RANK'] },
433
+ { ref: ['HIERARCHY_RANK'] },
434
+ { ref: ['HIERARCHY_ROOT_RANK'] },
435
+ { ref: ['HIERARCHY_TREE_SIZE'] },
436
+ {
437
+ SELECT: {
438
+ columns: [{ func: 'MIN', args: [{ ref: ['HIERARCHY_DISTANCE'] }] }],
439
+ from: HierarchyAncestors,
440
+ where: [{ ref: [HierarchyAncestors.as, 'HIERARCHY_RANK'] }, '=', { ref: [src, 'HIERARCHY_RANK'] }]
441
+ },
442
+ as: 'HIERARCHY_DISTANCE',
443
+ },
444
+ ...passThroughColumns,
445
+ ],
446
+ from: { ref: [src] },
447
+ where: [
448
+ { ref: ['HIERARCHY_RANK'] },
449
+ 'IN',
450
+ {
451
+ SELECT: {
452
+ columns: [{ ref: ['HIERARCHY_RANK'] }],
453
+ from: HierarchyAncestors,
454
+ }
455
+ }
456
+ ]
457
+ }
458
+ })
459
+ },
460
+ }
461
+
462
+ for (let each in HANAFunctions) HANAFunctions[each.toUpperCase()] = HANAFunctions[each]
463
+
464
+ module.exports = { ...StandardFunctions, ...HANAFunctions }
@@ -0,0 +1,116 @@
1
+ const cds = require('@sap/cds')
2
+
3
+ const CQN2SQL = require('./cqn2sql.js').class
4
+
5
+ class CQN2PQLRenderer extends CQN2SQL {
6
+
7
+ SELECT(q) {
8
+ this.values = undefined // inline all values
9
+ return (this.sql = super.SELECT(q)
10
+ .replaceAll('\n FROM', '\nFROM')
11
+ .replaceAll(/([^ ]) (FROM|WHERE|GROUP BY|HAVING|ORDER BY|LIMIT) /g, (a, b, c) => `${b}\n${c} `)
12
+ )
13
+ }
14
+
15
+ SELECT_columns(q) {
16
+ return super.SELECT_columns(q).map((c, i) => `${(i % 5 === 0) ? '\n ' : ' '}${c}${/ as /i.test(c) ? '\n' : ''}`).join(',')
17
+ }
18
+
19
+ column_expr(x, q) {
20
+ // omit alias when target is a single source
21
+ if (q.SELECT.from.ref && x?.ref) x.ref = x.ref.slice(-1)
22
+ return super.column_expr(x, q)
23
+ }
24
+
25
+ SELECT_expand(q, sql) { return sql }
26
+
27
+ INSERT_entries(q) {
28
+ super.INSERT_entries(q)
29
+ this.sql = this.sql
30
+ .replaceAll(/AS (.*?)([, ])(?=[^\n])/ig, (a, b, c) => `AS ${b}${c}\n${c === ',' ? ' ' : ''}`)
31
+ .replaceAll(/ *= */ig, ' = ')
32
+ .replaceAll('value AS "$$value$$"', 'value')
33
+ .replaceAll(' WHERE ', '\nWHERE ')
34
+ .replaceAll(' SELECT ', '\nSELECT')
35
+ .replaceAll('(SELECT ', '(SELECT\n ')
36
+ .replaceAll('))', ')\n)')
37
+ }
38
+
39
+ INSERT_rows(q) {
40
+ super.INSERT_rows(q)
41
+ this.sql = this.sql.replaceAll('SELECT', '\nSELECT')
42
+ }
43
+
44
+ UPSERT(q) {
45
+ super.UPSERT(q)
46
+ this.sql = this.sql
47
+ .replaceAll('INSERT', 'UPSERT')
48
+ .replaceAll(/AS (.*?)([, ])(?=[^\n])/ig, (a, b, c) => `AS ${b}${c}\n${c === ',' ? ' ' : ''}`)
49
+ .replaceAll(/ *= */ig, ' = ')
50
+ .replaceAll('value AS "$$value$$"', 'value')
51
+ .replaceAll(' WHERE ', '\nWHERE ')
52
+ .replaceAll(' SELECT ', '\nSELECT')
53
+ .replaceAll('(SELECT ', '(SELECT\n ')
54
+ .replaceAll('))', ')\n)')
55
+ }
56
+
57
+ expr(x) {
58
+ const wrap = x.cast ? sql => `cast(${sql} as ${this.type4(x.cast)})` : sql => sql
59
+ if (typeof x === 'string') throw cds.error`Unsupported expr: ${x}`
60
+ if (x.param) return wrap(this.param(x))
61
+ if ('ref' in x) return wrap(this.ref(x))
62
+ if ('val' in x) return wrap(this.val(x))
63
+ if ('func' in x) return wrap(this.func(x))
64
+ if ('xpr' in x) return wrap(this.xpr(x))
65
+ if ('list' in x) return wrap(this.list(x))
66
+ if ('SELECT' in x) return wrap(`(\n ${this.SELECT(x).replaceAll('\n', '\n ')}\n )`)
67
+ else throw cds.error`Unsupported expr: ${x}`
68
+ }
69
+
70
+ quote(s) { return s }
71
+
72
+ managed(columns, elements) {
73
+ const keys = ObjectKeys(elements).filter(e => elements[e].key && !elements[e].isAssociation)
74
+ const keyZero = keys[0]
75
+
76
+ const ret = super.managed(columns, elements)
77
+
78
+ ret.forEach(c => {
79
+ const { name, insert, update, onInsert, onUpdate } = c
80
+ const element = elements?.[name]
81
+ c.upsert = keyZero && (
82
+ // upsert requires the keys to be provided for the existance join (default values optional)
83
+ element?.key
84
+ // If both insert and update have the same managed definition exclude the old value check
85
+ || (onInsert && onUpdate && insert === update)
86
+ ? `${insert} as ${name}`
87
+ : `!OLD.${keyZero} ? ${
88
+ // If key of old is null execute insert
89
+ insert
90
+ } : ${
91
+ // Else execute managed update or keep old if no new data if provided
92
+ onUpdate ? update : `(${this.managed_default(name, `OLD.${name}`, update)})`
93
+ } as ${name}`
94
+ )
95
+ if (c.upsert) c.upsert = '\n ' + c.upsert
96
+ })
97
+ return ret
98
+ }
99
+
100
+ managed_default(name, managed, src) {
101
+ return `!${src} ? ${managed} : ${src}`
102
+ }
103
+
104
+ managed_extract(name) {
105
+ const { UPSERT, INSERT } = this.cqn
106
+ const extract = !(INSERT?.entries || UPSERT?.entries) && (INSERT?.rows || UPSERT?.rows)
107
+ ? `value[${this.columns.indexOf(name)}]`
108
+ : `value[${JSON.stringify(name)}]`
109
+ const sql = extract
110
+ return { extract, sql }
111
+ }
112
+ }
113
+
114
+ const ObjectKeys = o => (o && [...ObjectKeys(o.__proto__), ...Object.keys(o)]) || []
115
+
116
+ module.exports = CQN2PQLRenderer