github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/steampipeconfig/parse/query_invocation.go (about) 1 package parse 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/hashicorp/hcl/v2/hclsyntax" 9 "github.com/turbot/pipe-fittings/hclhelpers" 10 "github.com/turbot/steampipe-plugin-sdk/v5/plugin" 11 "github.com/turbot/steampipe/pkg/steampipeconfig/modconfig" 12 ) 13 14 // ParseQueryInvocation parses a query invocation and extracts the args (if any) 15 // supported formats are: 16 // 17 // 1) positional args 18 // query.my_prepared_statement('val1','val1') 19 // 20 // 2) named args 21 // query.my_prepared_statement(my_arg1 => 'test', my_arg2 => 'test2') 22 func ParseQueryInvocation(arg string) (string, *modconfig.QueryArgs, error) { 23 // TODO strip non printing chars 24 args := &modconfig.QueryArgs{} 25 26 arg = strings.TrimSpace(arg) 27 query := arg 28 var err error 29 openBracketIdx := strings.Index(arg, "(") 30 closeBracketIdx := strings.LastIndex(arg, ")") 31 if openBracketIdx != -1 && closeBracketIdx == len(arg)-1 { 32 argsString := arg[openBracketIdx+1 : len(arg)-1] 33 args, err = parseArgs(argsString) 34 query = strings.TrimSpace(arg[:openBracketIdx]) 35 } 36 return query, args, err 37 } 38 39 // parse the actual args string, i.e. the contents of the bracket 40 // supported formats are: 41 // 42 // 1) positional args 43 // 'val1','val1' 44 // 45 // 2) named args 46 // my_arg1 => 'val1', my_arg2 => 'val2' 47 func parseArgs(argsString string) (*modconfig.QueryArgs, error) { 48 res := modconfig.NewQueryArgs() 49 if len(argsString) == 0 { 50 return res, nil 51 } 52 53 // split on comma to get each arg string (taking quotes and brackets into account) 54 splitArgs, err := splitArgString(argsString) 55 if err != nil { 56 // return empty result, even if we have an error 57 return res, err 58 } 59 60 // first check for named args 61 argMap, err := parseNamedArgs(splitArgs) 62 if err != nil { 63 return res, err 64 } 65 if err := res.SetArgMap(argMap); err != nil { 66 return res, err 67 } 68 69 if res.Empty() { 70 // no named args - fall back on positional 71 argList, err := parsePositionalArgs(splitArgs) 72 if err != nil { 73 return res, err 74 } 75 if err := res.SetArgList(argList); err != nil { 76 return res, err 77 } 78 } 79 // return empty result, even if we have an error 80 return res, err 81 } 82 83 func splitArgString(argsString string) ([]string, error) { 84 var argsList []string 85 openElements := map[string]int{ 86 "quote": 0, 87 "curly": 0, 88 "square": 0, 89 } 90 var currentWord string 91 for _, c := range argsString { 92 // should we split - are we in a block 93 if c == ',' && 94 openElements["quote"] == 0 && openElements["curly"] == 0 && openElements["square"] == 0 { 95 if len(currentWord) > 0 { 96 argsList = append(argsList, currentWord) 97 currentWord = "" 98 } 99 } else { 100 currentWord = currentWord + string(c) 101 } 102 103 // handle brackets and quotes 104 switch c { 105 case '{': 106 if openElements["quote"] == 0 { 107 openElements["curly"]++ 108 } 109 case '}': 110 if openElements["quote"] == 0 { 111 openElements["curly"]-- 112 if openElements["curly"] < 0 { 113 return nil, fmt.Errorf("bad arg syntax") 114 } 115 } 116 case '[': 117 if openElements["quote"] == 0 { 118 openElements["square"]++ 119 } 120 case ']': 121 if openElements["quote"] == 0 { 122 openElements["square"]-- 123 if openElements["square"] < 0 { 124 return nil, fmt.Errorf("bad arg syntax") 125 } 126 } 127 case '"': 128 if openElements["quote"] == 0 { 129 openElements["quote"] = 1 130 } else { 131 openElements["quote"] = 0 132 } 133 } 134 } 135 if len(currentWord) > 0 { 136 argsList = append(argsList, currentWord) 137 } 138 return argsList, nil 139 } 140 141 func parseArg(v string) (any, error) { 142 b, diags := hclsyntax.ParseExpression([]byte(v), "", hcl.Pos{}) 143 if diags.HasErrors() { 144 return "", plugin.DiagsToError("bad arg syntax", diags) 145 } 146 val, diags := b.Value(nil) 147 if diags.HasErrors() { 148 return "", plugin.DiagsToError("bad arg syntax", diags) 149 } 150 return hclhelpers.CtyToGo(val) 151 } 152 153 func parseNamedArgs(argsList []string) (map[string]any, error) { 154 var res = make(map[string]any) 155 for _, p := range argsList { 156 argTuple := strings.Split(strings.TrimSpace(p), "=>") 157 if len(argTuple) != 2 { 158 // not all args have valid syntax - give up 159 return nil, nil 160 } 161 k := strings.TrimSpace(argTuple[0]) 162 val, err := parseArg(argTuple[1]) 163 if err != nil { 164 return nil, err 165 } 166 res[k] = val 167 } 168 return res, nil 169 } 170 171 func parsePositionalArgs(argsList []string) ([]any, error) { 172 // convert to pointer array 173 res := make([]any, len(argsList)) 174 // just treat args as positional args 175 // strip spaces 176 for i, v := range argsList { 177 valStr, err := parseArg(v) 178 if err != nil { 179 return nil, err 180 } 181 res[i] = valStr 182 } 183 184 return res, nil 185 }