11'use strict'
22
3+ const cds = require ( '@sap/cds' )
4+
35// OData: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_CanonicalFunctions
46const StandardFunctions = {
57 /**
@@ -18,10 +20,15 @@ const StandardFunctions = {
1820 } catch {
1921 val = sub [ 2 ] || sub [ 3 ] || ''
2022 }
21- arg . val = arg . __proto__ . val = val
23+ arg . val = val
2224 const refs = ref . list
23- const { toString } = ref
24- return '(' + refs . map ( ref2 => this . contains ( this . tolower ( toString ( ref2 ) ) , this . tolower ( arg ) ) ) . join ( ' or ' ) + ')'
25+ return `(${ refs . map ( ref => this . expr ( {
26+ func : 'contains' ,
27+ args : [
28+ { func : 'tolower' , args : [ ref ] } ,
29+ { func : 'tolower' , args : [ arg ] } ,
30+ ]
31+ } ) ) . join ( ' or ' ) } )`
2532 } ,
2633
2734 // ==============================
@@ -141,7 +148,7 @@ const StandardFunctions = {
141148 * @returns {string } - SQL statement
142149 */
143150 now : function ( ) {
144- return this . session_context ( { val : '$now' } )
151+ return this . expr ( { func : 'session_context' , args : [ { val : '$now' } ] } )
145152 } ,
146153
147154 /**
@@ -184,6 +191,226 @@ const HANAFunctions = {
184191 * @returns {string } - SQL statement
185192 */
186193 current_timestamp : p => ( p ? `current_timestamp(${ p } )` : 'current_timestamp' ) ,
194+
195+ /**
196+ * Generates SQL statement for the hierarchy function
197+ * @param {string } [p] -
198+ * @returns {string } - SQL statement
199+ */
200+ HIERARCHY : function ( args ) {
201+ let uniqueCounter = this . _with ?. length ?? 0
202+ let src = args . xpr [ 1 ]
203+
204+ // Ensure that the orderBy column are exposed by the source for hierarchy sorting
205+ const orderBy = args . xpr . find ( ( _ , i , arr ) => / O R D E R / i. test ( arr [ i - 2 ] ) && / B Y / i. test ( arr [ i - 1 ] ) )
206+
207+ const passThroughColumns = src . SELECT . columns . map ( c => ( { ref : [ 'Source' , this . column_name ( c ) ] } ) )
208+ src . as = 'H' + ( uniqueCounter ++ )
209+ src = this . expr ( this . with ( src ) )
210+
211+ let recursive = cds . ql ( `
212+ SELECT
213+ 1 as HIERARCHY_LEVEL,
214+ NODE_ID as HIERARCHY_ROOT_ID
215+ FROM ${ src } AS Source
216+ WHERE parent_ID IS NULL
217+ UNION ALL
218+ SELECT
219+ Parent.HIERARCHY_LEVEL + 1,
220+ Parent.HIERARCHY_ROOT_ID
221+ FROM ${ src } AS Source
222+ JOIN H${ uniqueCounter } AS Parent ON Source.PARENT_ID=Parent.NODE_ID
223+ ORDER BY HIERARCHY_LEVEL DESC${ orderBy ? `,${ orderBy } ` : '' } ` )
224+ recursive . as = 'H' + ( uniqueCounter ++ )
225+ recursive . SET . args [ 0 ] . SELECT . columns = [ ...recursive . SET . args [ 0 ] . SELECT . columns , ...passThroughColumns ]
226+ recursive . SET . args [ 1 ] . SELECT . columns = [ ...recursive . SET . args [ 1 ] . SELECT . columns , ...passThroughColumns ]
227+ recursive = this . expr ( this . with ( recursive ) )
228+
229+ let ranked = cds . ql ( `
230+ SELECT
231+ HIERARCHY_LEVEL,
232+ row_number() over () as HIERARCHY_RANK,
233+ HIERARCHY_ROOT_ID
234+ FROM ${ recursive } AS Source` )
235+ ranked . as = 'H' + ( uniqueCounter ++ )
236+ ranked . SELECT . columns = [ ...ranked . SELECT . columns , ...passThroughColumns ]
237+ ranked = this . expr ( this . with ( ranked ) )
238+
239+ let Hierarchy = cds . ql ( `
240+ SELECT
241+ HIERARCHY_LEVEL,
242+ HIERARCHY_RANK,
243+ (SELECT HIERARCHY_RANK FROM ${ ranked } AS Ranked WHERE Ranked.NODE_ID = Source.PARENT_ID) AS HIERARCHY_PARENT_RANK,
244+ (SELECT HIERARCHY_RANK FROM ${ ranked } AS Ranked WHERE Ranked.NODE_ID = Source.HIERARCHY_ROOT_ID) AS HIERARCHY_ROOT_RANK,
245+ coalesce(
246+ (SELECT MIN(HIERARCHY_RANK) FROM ${ ranked } AS Ranked WHERE Ranked.HIERARCHY_RANK > Source.HIERARCHY_RANK AND Ranked.HIERARCHY_LEVEL <= Source.HIERARCHY_LEVEL),
247+ (SELECT MAX(HIERARCHY_RANK) + 1 FROM ${ ranked } )
248+ ) - Source.HIERARCHY_RANK AS HIERARCHY_TREE_SIZE
249+ FROM ${ ranked } AS Source` )
250+ Hierarchy . as = 'H' + ( uniqueCounter ++ )
251+ Hierarchy . SELECT . columns = [ ...Hierarchy . SELECT . columns , ...passThroughColumns ]
252+ Hierarchy = this . expr ( this . with ( Hierarchy ) )
253+
254+ return Hierarchy
255+ } ,
256+
257+ /**
258+ * Generates SQL statement for the hierarchy_descendants function
259+ * @param {string } [p] -
260+ * @returns {string } - SQL statement
261+ */
262+ HIERARCHY_DESCENDANTS : function ( args ) {
263+ // Find Hierarchy function call source query
264+ const passThroughColumns = args . xpr [ 1 ] . args [ 0 ] . xpr [ 1 ] . SELECT . columns . map ( c => ( { ref : [ this . column_name ( c ) ] } ) )
265+ // REVISIT: currently only supports func: HIERARCHY as source
266+ const src = this . expr ( args . xpr [ 1 ] )
267+
268+ let uniqueCounter = this . _with ?. length ?? 0
269+
270+ let alias = args . xpr . find ( ( _ , i , arr ) => / A S / i. test ( arr [ i - 1 ] ) )
271+ const where = args . xpr . find ( ( a , i , arr ) => a . xpr && / W H E R E / i. test ( arr [ i - 1 ] ) && / S T A R T / i. test ( arr [ i - 2 ] ) )
272+ const distance = args . xpr . find ( ( a , i , arr ) => typeof a . val === 'number' && ( / D I S T A N C E / i. test ( arr [ i - 1 ] ) || / D I S T A N C E / i. test ( arr [ i - 2 ] ) ) )
273+ const distanceFrom = args . xpr . find ( ( a , i , arr ) => / F R O M / . test ( a ) && / D I S T A N C E / i. test ( arr [ i - 1 ] ) )
274+
275+ if ( alias . startsWith ( '"' ) && alias . endsWith ( '"' ) ) alias = alias . slice ( 1 , - 1 ) . replace ( / " " / g, '"' )
276+
277+ let HierarchyDescendants = cds . ql ( `
278+ SELECT
279+ HIERARCHY_LEVEL,
280+ HIERARCHY_PARENT_RANK,
281+ HIERARCHY_RANK,
282+ HIERARCHY_ROOT_RANK,
283+ HIERARCHY_TREE_SIZE,
284+ 0 as HIERARCHY_DISTANCE
285+ FROM ${ src } AS ![${ alias } ]
286+ UNION ALL
287+ SELECT
288+ Source.HIERARCHY_LEVEL,
289+ Source.HIERARCHY_PARENT_RANK,
290+ Source.HIERARCHY_RANK,
291+ Source.HIERARCHY_ROOT_RANK,
292+ Source.HIERARCHY_TREE_SIZE,
293+ Child.HIERARCHY_DISTANCE + 1
294+ FROM ${ src } AS Source
295+ JOIN H${ uniqueCounter } AS Child ON Source.PARENT_ID=Child.NODE_ID` )
296+ HierarchyDescendants . as = 'H' + uniqueCounter
297+ HierarchyDescendants . SET . args [ 0 ] . SELECT . where = where . xpr
298+ HierarchyDescendants . SET . args [ 0 ] . SELECT . columns = [ ...HierarchyDescendants . SET . args [ 0 ] . SELECT . columns , ...passThroughColumns . map ( r => ( { ref : [ alias , r . ref [ 0 ] ] } ) ) ]
299+ HierarchyDescendants . SET . args [ 1 ] . SELECT . columns = [ ...HierarchyDescendants . SET . args [ 1 ] . SELECT . columns , ...passThroughColumns . map ( r => ( { ref : [ 'Source' , r . ref [ 0 ] ] } ) ) ]
300+
301+ HierarchyDescendants = this . with ( HierarchyDescendants )
302+ HierarchyDescendants . as = 'HierarchyDescendants'
303+
304+ return this . expr ( {
305+ SELECT : {
306+ columns : [
307+ { ref : [ 'HIERARCHY_LEVEL' ] } ,
308+ { ref : [ 'HIERARCHY_PARENT_RANK' ] } ,
309+ { ref : [ 'HIERARCHY_RANK' ] } ,
310+ { ref : [ 'HIERARCHY_ROOT_RANK' ] } ,
311+ { ref : [ 'HIERARCHY_TREE_SIZE' ] } ,
312+ {
313+ SELECT : {
314+ columns : [ { func : 'MAX' , args : [ { ref : [ 'HIERARCHY_DISTANCE' ] } ] } ] ,
315+ from : HierarchyDescendants ,
316+ where : [ { ref : [ HierarchyDescendants . as , 'HIERARCHY_RANK' ] } , '=' , { ref : [ src , 'HIERARCHY_RANK' ] } ]
317+ } ,
318+ as : 'HIERARCHY_DISTANCE' ,
319+ } ,
320+ ...passThroughColumns ,
321+ ] ,
322+ from : { ref : [ src ] } ,
323+ where : [
324+ { ref : [ 'HIERARCHY_RANK' ] } ,
325+ 'IN' ,
326+ {
327+ SELECT : {
328+ columns : [ { ref : [ 'HIERARCHY_RANK' ] } ] ,
329+ from : HierarchyDescendants ,
330+ where : [ { ref : [ 'HIERARCHY_DISTANCE' ] } , distanceFrom ? '>=' : '=' , distance ]
331+ }
332+ }
333+ ]
334+ }
335+ } )
336+ } ,
337+
338+ /**
339+ * Generates SQL statement for the hierarchy_ancestors function
340+ * @param {string } [p] -
341+ * @returns {string } - SQL statement
342+ */
343+ HIERARCHY_ANCESTORS : function ( args ) {
344+ // Find Hierarchy function call source query
345+ const passThroughColumns = args . xpr [ 1 ] . args [ 0 ] . xpr [ 1 ] . SELECT . columns . map ( c => ( { ref : [ this . column_name ( c ) ] } ) )
346+ // REVISIT: currently only supports func: HIERARCHY as source
347+ const src = this . expr ( args . xpr [ 1 ] )
348+
349+ let uniqueCounter = this . _with ?. length ?? 0
350+
351+ let alias = args . xpr . find ( ( _ , i , arr ) => / A S / i. test ( arr [ i - 1 ] ) )
352+ const where = args . xpr . find ( ( a , i , arr ) => a . xpr && / W H E R E / i. test ( arr [ i - 1 ] ) && / S T A R T / i. test ( arr [ i - 2 ] ) )
353+
354+ if ( alias . startsWith ( '"' ) && alias . endsWith ( '"' ) ) alias = alias . slice ( 1 , - 1 ) . replace ( / " " / g, '"' )
355+
356+ let HierarchyAncestors = cds . ql ( `
357+ SELECT
358+ HIERARCHY_LEVEL,
359+ HIERARCHY_PARENT_RANK,
360+ HIERARCHY_RANK,
361+ HIERARCHY_ROOT_RANK,
362+ HIERARCHY_TREE_SIZE,
363+ 0 as HIERARCHY_DISTANCE
364+ FROM ${ src } AS ![${ alias } ]
365+ UNION ALL
366+ SELECT
367+ Source.HIERARCHY_LEVEL,
368+ Source.HIERARCHY_PARENT_RANK,
369+ Source.HIERARCHY_RANK,
370+ Source.HIERARCHY_ROOT_RANK,
371+ Source.HIERARCHY_TREE_SIZE,
372+ Child.HIERARCHY_DISTANCE - 1
373+ FROM ${ src } AS Source
374+ JOIN H${ uniqueCounter } AS Child ON Source.NODE_ID=Child.PARENT_ID` )
375+ HierarchyAncestors . as = 'H' + uniqueCounter
376+ HierarchyAncestors . SET . args [ 0 ] . SELECT . where = where . xpr
377+ HierarchyAncestors . SET . args [ 0 ] . SELECT . columns = [ ...HierarchyAncestors . SET . args [ 0 ] . SELECT . columns , ...passThroughColumns . map ( r => ( { ref : [ alias , r . ref [ 0 ] ] } ) ) ]
378+ HierarchyAncestors . SET . args [ 1 ] . SELECT . columns = [ ...HierarchyAncestors . SET . args [ 1 ] . SELECT . columns , ...passThroughColumns . map ( r => ( { ref : [ 'Source' , r . ref [ 0 ] ] } ) ) ]
379+
380+ HierarchyAncestors = this . with ( HierarchyAncestors )
381+ HierarchyAncestors . as = 'HierarchyAncestors'
382+ return this . expr ( {
383+ SELECT : {
384+ columns : [
385+ { ref : [ 'HIERARCHY_LEVEL' ] } ,
386+ { ref : [ 'HIERARCHY_PARENT_RANK' ] } ,
387+ { ref : [ 'HIERARCHY_RANK' ] } ,
388+ { ref : [ 'HIERARCHY_ROOT_RANK' ] } ,
389+ { ref : [ 'HIERARCHY_TREE_SIZE' ] } ,
390+ {
391+ SELECT : {
392+ columns : [ { func : 'MIN' , args : [ { ref : [ 'HIERARCHY_DISTANCE' ] } ] } ] ,
393+ from : HierarchyAncestors ,
394+ where : [ { ref : [ HierarchyAncestors . as , 'HIERARCHY_RANK' ] } , '=' , { ref : [ src , 'HIERARCHY_RANK' ] } ]
395+ } ,
396+ as : 'HIERARCHY_DISTANCE' ,
397+ } ,
398+ ...passThroughColumns ,
399+ ] ,
400+ from : { ref : [ src ] } ,
401+ where : [
402+ { ref : [ 'HIERARCHY_RANK' ] } ,
403+ 'IN' ,
404+ {
405+ SELECT : {
406+ columns : [ { ref : [ 'HIERARCHY_RANK' ] } ] ,
407+ from : HierarchyAncestors ,
408+ }
409+ }
410+ ]
411+ }
412+ } )
413+ } ,
187414}
188415
189416for ( let each in HANAFunctions ) HANAFunctions [ each . toUpperCase ( ) ] = HANAFunctions [ each ]
0 commit comments