@@ -13,76 +13,72 @@ const odataParser = require('odata-v4-parser');
1313const Problem = require ( '../util/problem' ) ;
1414
1515////////////////////////////////////////
16- // AST NODE TRANSFORMATION
16+ // MAIN ENTRY POINT
1717
18- const extractFunctions = [ 'year' , 'month' , 'day' , 'hour' , 'minute' , 'second' ] ;
19- const methodCall = ( fn , params ) => {
20- // n.b. odata-v4-parser appears to already validate function name and arity.
21- const lowerName = fn . toLowerCase ( ) ;
22- if ( extractFunctions . includes ( lowerName ) )
23- return sql `extract(${ raw ( lowerName ) } from ${ op ( params [ 0 ] ) } )` ; // eslint-disable-line no-use-before-define
24- else if ( fn === 'now' )
25- return sql `now()` ;
26- } ;
27- const binaryOp = ( left , right , operator ) =>
28- // always use parens to ensure the original AST op precedence.
29- sql `(${ op ( left ) } ${ raw ( operator ) } ${ op ( right ) } )` ; // eslint-disable-line no-use-before-define
18+ const odataFilter = ( expr , odataToColumnMap ) => {
19+ if ( expr == null ) return sql `true` ;
3020
31- const op = ( node ) => {
32- if ( node . type === 'FirstMemberExpression' ) {
33- if ( node . raw === '__system/submissionDate' ) {
34- return sql . identifier ( [ 'submissions' , 'createdAt' ] ) ; // TODO: HACK HACK
35- } else if ( node . raw === '__system/updatedAt' ) {
36- return sql . identifier ( [ 'submissions' , 'updatedAt' ] ) ; // TODO: HACK HACK
37- } else if ( node . raw === '__system/submitterId' ) {
38- return sql . identifier ( [ 'submissions' , 'submitterId' ] ) ; // TODO: HACK HACK
39- } else if ( node . raw === '__system/reviewState' ) {
40- return sql . identifier ( [ 'submissions' , 'reviewState' ] ) ; // TODO: HACK HACK
41- } else {
42- throw Problem . internal . unsupportedODataField ( { at : node . position , text : node . raw } ) ;
43- }
44- } else if ( node . type === 'Literal' ) {
45- // for some reason string literals come with their quotes
46- // TODO: we don't unencode single quotes encoded doubly ('') but we don't support
47- // any values w quotes in them yet anyway.
48- return ( node . raw === 'null' ) ? null
49- : ( / ^ ' .* ' $ / . test ( node . raw ) ) ? node . raw . slice ( 1 , node . raw . length - 1 )
50- : node . raw ; // eslint-disable-line indent
51- } else if ( node . type === 'MethodCallExpression' ) {
52- return methodCall ( node . value . method , node . value . parameters ) ;
53- } else if ( node . type === 'EqualsExpression' ) {
54- return binaryOp ( node . value . left , node . value . right , 'is not distinct from' ) ;
55- } else if ( node . type === 'NotEqualsExpression' ) {
56- return binaryOp ( node . value . left , node . value . right , 'is distinct from' ) ;
57- } else if ( node . type === 'LesserThanExpression' ) {
58- return binaryOp ( node . value . left , node . value . right , '<' ) ;
59- } else if ( node . type === 'LesserOrEqualsExpression' ) {
60- return binaryOp ( node . value . left , node . value . right , '<=' ) ;
61- } else if ( node . type === 'GreaterThanExpression' ) {
62- return binaryOp ( node . value . left , node . value . right , '>' ) ;
63- } else if ( node . type === 'GreaterOrEqualsExpression' ) {
64- return binaryOp ( node . value . left , node . value . right , '>=' ) ;
65- } else if ( node . type === 'AndExpression' ) {
66- return binaryOp ( node . value . left , node . value . right , 'and' ) ;
67- } else if ( node . type === 'OrExpression' ) {
68- return binaryOp ( node . value . left , node . value . right , 'or' ) ;
69- } else if ( node . type === 'NotExpression' ) {
70- return sql `(not ${ op ( node . value ) } )` ;
71- } else if ( node . type === 'BoolParenExpression' ) {
72- // Because we add parentheses elsewhere, we don't need to add another set of
73- // parentheses here. The main effect of a BoolParenExpression is the way it
74- // restructures the AST.
75- return op ( node . value ) ;
76- } else {
77- throw Problem . internal . unsupportedODataExpression ( { at : node . position , type : node . type , text : node . raw } ) ;
78- }
79- } ;
21+ ////////////////////////////////////////
22+ // AST NODE TRANSFORMATION
23+ // These functions are defined inside odataFilter() so that they can access odataToColumnMap
24+ // I don't want to pass it to all of them.
8025
81- ////////////////////////////////////////
82- // MAIN ENTRY POINT
26+ const extractFunctions = [ 'year' , 'month' , 'day' , 'hour' , 'minute' , 'second' ] ;
27+ const methodCall = ( fn , params ) => {
28+ // n.b. odata-v4-parser appears to already validate function name and arity.
29+ const lowerName = fn . toLowerCase ( ) ;
30+ if ( extractFunctions . includes ( lowerName ) )
31+ return sql `extract(${ raw ( lowerName ) } from ${ op ( params [ 0 ] ) } )` ; // eslint-disable-line no-use-before-define
32+ else if ( fn === 'now' )
33+ return sql `now()` ;
34+ } ;
35+ const binaryOp = ( left , right , operator ) =>
36+ // always use parens to ensure the original AST op precedence.
37+ sql `(${ op ( left ) } ${ raw ( operator ) } ${ op ( right ) } )` ; // eslint-disable-line no-use-before-define
8338
84- const odataFilter = ( expr ) => {
85- if ( expr == null ) return sql `true` ;
39+ const op = ( node ) => {
40+ if ( node . type === 'FirstMemberExpression' ) {
41+ if ( odataToColumnMap . has ( node . raw ) ) {
42+ return sql . identifier ( odataToColumnMap . get ( node . raw ) . split ( '.' ) ) ;
43+ } else {
44+ throw Problem . internal . unsupportedODataField ( { at : node . position , text : node . raw } ) ;
45+ }
46+ } else if ( node . type === 'Literal' ) {
47+ // for some reason string literals come with their quotes
48+ // TODO: we don't unencode single quotes encoded doubly ('') but we don't support
49+ // any values w quotes in them yet anyway.
50+ return ( node . raw === 'null' ) ? null
51+ : ( / ^ ' .* ' $ / . test ( node . raw ) ) ? node . raw . slice ( 1 , node . raw . length - 1 )
52+ : node . raw ; // eslint-disable-line indent
53+ } else if ( node . type === 'MethodCallExpression' ) {
54+ return methodCall ( node . value . method , node . value . parameters ) ;
55+ } else if ( node . type === 'EqualsExpression' ) {
56+ return binaryOp ( node . value . left , node . value . right , 'is not distinct from' ) ;
57+ } else if ( node . type === 'NotEqualsExpression' ) {
58+ return binaryOp ( node . value . left , node . value . right , 'is distinct from' ) ;
59+ } else if ( node . type === 'LesserThanExpression' ) {
60+ return binaryOp ( node . value . left , node . value . right , '<' ) ;
61+ } else if ( node . type === 'LesserOrEqualsExpression' ) {
62+ return binaryOp ( node . value . left , node . value . right , '<=' ) ;
63+ } else if ( node . type === 'GreaterThanExpression' ) {
64+ return binaryOp ( node . value . left , node . value . right , '>' ) ;
65+ } else if ( node . type === 'GreaterOrEqualsExpression' ) {
66+ return binaryOp ( node . value . left , node . value . right , '>=' ) ;
67+ } else if ( node . type === 'AndExpression' ) {
68+ return binaryOp ( node . value . left , node . value . right , 'and' ) ;
69+ } else if ( node . type === 'OrExpression' ) {
70+ return binaryOp ( node . value . left , node . value . right , 'or' ) ;
71+ } else if ( node . type === 'NotExpression' ) {
72+ return sql `(not ${ op ( node . value ) } )` ;
73+ } else if ( node . type === 'BoolParenExpression' ) {
74+ // Because we add parentheses elsewhere, we don't need to add another set of
75+ // parentheses here. The main effect of a BoolParenExpression is the way it
76+ // restructures the AST.
77+ return op ( node . value ) ;
78+ } else {
79+ throw Problem . internal . unsupportedODataExpression ( { at : node . position , type : node . type , text : node . raw } ) ;
80+ }
81+ } ;
8682
8783 let ast ; // still hate this.
8884 try { ast = odataParser . filter ( expr ) ; } // eslint-disable-line brace-style
0 commit comments