@@ -2,6 +2,8 @@ package godata
22
33import (
44 "context"
5+ "errors"
6+ "fmt"
57 "regexp"
68 "strings"
79)
@@ -18,25 +20,40 @@ type ComputeItem struct {
1820 Field string // The name of the computed dynamic property.
1921}
2022
23+ // GlobalAllTokenParser is a Tokenizer which matches all tokens and ignores none. It differs from the
24+ // GlobalExpressionTokenizer which ignores whitespace tokens.
25+ var GlobalAllTokenParser * Tokenizer
26+
27+ func init () {
28+ t := NewExpressionParser ().tokenizer
29+ t .TokenMatchers = append (t .IgnoreMatchers , t .TokenMatchers ... )
30+ t .IgnoreMatchers = nil
31+ GlobalAllTokenParser = t
32+ }
33+
2134func ParseComputeString (ctx context.Context , compute string ) (* GoDataComputeQuery , error ) {
22- items := strings .Split (compute , "," )
35+ items , err := SplitComputeItems (compute )
36+ if err != nil {
37+ return nil , err
38+ }
2339
2440 result := make ([]* ComputeItem , 0 )
41+ fields := map [string ]struct {}{}
2542
2643 for _ , v := range items {
2744 v = strings .TrimSpace (v )
2845 parts := strings .Split (v , computeAsSeparator )
2946 if len (parts ) != 2 {
3047 return nil , & GoDataError {
3148 ResponseCode : 400 ,
32- Message : "Invalid $compute query option " ,
49+ Message : "Invalid $compute query format " ,
3350 }
3451 }
3552 field := strings .TrimSpace (parts [1 ])
3653 if ! computeFieldRegex .MatchString (field ) {
3754 return nil , & GoDataError {
3855 ResponseCode : 400 ,
39- Message : "Invalid $compute query option " ,
56+ Message : "Invalid $compute field name " ,
4057 }
4158 }
4259
@@ -45,7 +62,7 @@ func ParseComputeString(ctx context.Context, compute string) (*GoDataComputeQuer
4562 case * GoDataError :
4663 return nil , & GoDataError {
4764 ResponseCode : e .ResponseCode ,
48- Message : "Invalid $compute query option" ,
65+ Message : fmt . Sprintf ( "Invalid $compute query option, %s" , e . Message ) ,
4966 Cause : e ,
5067 }
5168 default :
@@ -62,6 +79,16 @@ func ParseComputeString(ctx context.Context, compute string) (*GoDataComputeQuer
6279 Message : "Invalid $compute query option" ,
6380 }
6481 }
82+
83+ if _ , ok := fields [field ]; ok {
84+ return nil , & GoDataError {
85+ ResponseCode : 400 ,
86+ Message : "Invalid $compute, duplicate field name" ,
87+ }
88+ }
89+
90+ fields [field ] = struct {}{}
91+
6592 result = append (result , & ComputeItem {
6693 Tree : tree .Tree ,
6794 Field : field ,
@@ -71,3 +98,52 @@ func ParseComputeString(ctx context.Context, compute string) (*GoDataComputeQuer
7198
7299 return & GoDataComputeQuery {result , compute }, nil
73100}
101+
102+ // SplitComputeItems splits the input string based on the comma delimiter. It does so with awareness as to
103+ // which commas delimit $compute items and which ones are an inline part of the item, such as a separator
104+ // for function arguments.
105+ //
106+ // For example the input "someFunc(one,two) as three, 1 add 2 as four" results in the
107+ // output ["someFunc(one,two) as three", "1 add 2 as four"]
108+ func SplitComputeItems (in string ) ([]string , error ) {
109+
110+ var ret []string
111+
112+ tokens , err := GlobalAllTokenParser .Tokenize (context .Background (), in )
113+ if err != nil {
114+ return nil , err
115+ }
116+
117+ item := strings.Builder {}
118+ parenGauge := 0
119+
120+ for _ , v := range tokens {
121+ switch v .Type {
122+ case ExpressionTokenOpenParen :
123+ parenGauge ++
124+ case ExpressionTokenCloseParen :
125+ if parenGauge == 0 {
126+ return nil , errors .New ("unmatched parentheses" )
127+ }
128+ parenGauge --
129+ case ExpressionTokenComma :
130+ if parenGauge == 0 {
131+ ret = append (ret , item .String ())
132+ item .Reset ()
133+ continue
134+ }
135+ }
136+
137+ item .WriteString (v .Value )
138+ }
139+
140+ if parenGauge != 0 {
141+ return nil , errors .New ("unmatched parentheses" )
142+ }
143+
144+ if item .Len () > 0 {
145+ ret = append (ret , item .String ())
146+ }
147+
148+ return ret , nil
149+ }
0 commit comments