github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/builtins/optional/select/select.go (about) 1 package sqlselect 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/lmorg/murex/config" 8 "github.com/lmorg/murex/config/defaults" 9 "github.com/lmorg/murex/lang" 10 "github.com/lmorg/murex/lang/types" 11 ) 12 13 const ( 14 // Config key names 15 sFailColMismatch = "fail-irregular-columns" 16 sTableIncHeadings = "table-includes-headings" 17 sMergeTrailingColumns = "merge-trailing-columns" 18 sPrintHeadings = "print-headings" 19 sDefaultDataType = "data-type" 20 ) 21 22 func init() { 23 lang.DefineMethod("select", cmdSelect, types.Unmarshal, types.Marshal) 24 25 defaults.AppendProfile(` 26 config eval shell safe-commands { -> append select } 27 28 autocomplete set select { [{ 29 "Dynamic": ({ -> select --autocomplete @{$ARGS->@[1..] } }), 30 "AllowMultiple": true, 31 "AnyValue": true, 32 "ExecCmdline": true 33 }] } 34 `) 35 36 config.InitConf.Define("select", sFailColMismatch, config.Properties{ 37 Description: "When importing a table into sqlite3, fail if there is an irregular number of columns", 38 Default: false, 39 DataType: types.Boolean, 40 Global: false, 41 }) 42 43 config.InitConf.Define("select", sTableIncHeadings, config.Properties{ 44 Description: "When importing a table into sqlite3, treat the first row as headings (if `false`, headings are Excel style column references starting at `A`)", 45 Default: true, 46 DataType: types.Boolean, 47 Global: false, 48 }) 49 50 config.InitConf.Define("select", sMergeTrailingColumns, config.Properties{ 51 Description: "When importing a table into sqlite3, if `fail-irregular-columns` is set to `false` and there are more columns than headings, then any additional columns are concatenated into the last column (space delimitated). If `merge-trailing-columns` is set to `false` then any trailing columns are ignored", 52 Default: true, 53 DataType: types.Boolean, 54 Global: false, 55 }) 56 57 config.InitConf.Define("select", sPrintHeadings, config.Properties{ 58 Description: "Print headings when writing results", 59 Default: true, 60 DataType: types.Boolean, 61 Global: false, 62 }) 63 64 config.InitConf.Define("select", sDefaultDataType, config.Properties{ 65 Description: "Default output data type to use when multiple tables used (`select` will use STDIN's data type when executed as a method)", 66 Default: types.JsonLines, 67 DataType: types.String, 68 Global: false, 69 }) 70 } 71 72 func cmdSelect(p *lang.Process) error { 73 confFailColMismatch, err := p.Config.Get("select", sFailColMismatch, types.Boolean) 74 if err != nil { 75 return err 76 } 77 78 confTableIncHeadings, err := p.Config.Get("select", sTableIncHeadings, types.Boolean) 79 if err != nil { 80 return err 81 } 82 83 confMergeTrailingColumns, err := p.Config.Get("select", sMergeTrailingColumns, types.Boolean) 84 if err != nil { 85 return err 86 } 87 88 confPrintHeadings, err := p.Config.Get("select", sPrintHeadings, types.Boolean) 89 if err != nil { 90 return err 91 } 92 93 confDataType, err := p.Config.Get("select", sDefaultDataType, types.String) 94 if err != nil { 95 return err 96 } 97 98 if flag, _ := p.Parameters.String(0); flag == "--autocomplete" { 99 return dynamicAutocomplete(p, confFailColMismatch.(bool), confTableIncHeadings.(bool)) 100 } 101 102 parameters, fromFile, pipes, vars, err := dissectParameters(p) 103 if err != nil { 104 return err 105 } 106 107 return loadTables(p, fromFile, pipes, vars, parameters, confFailColMismatch.(bool), confMergeTrailingColumns.(bool), confTableIncHeadings.(bool), confPrintHeadings.(bool), confDataType.(string)) 108 } 109 110 func dissectParameters(p *lang.Process) (parameters, fromFile string, pipes, vars []string, err error) { 111 if p.IsMethod { 112 s := p.Parameters.StringAll() 113 if rxCheckFrom.MatchString(s) { 114 return "", "", nil, nil, fmt.Errorf("SQL contains FROM clause. This should not be included when using `select` as a method") 115 } 116 return s, "", nil, nil, nil 117 118 } else { 119 params := p.Parameters.StringArray() 120 i := 0 121 for ; i < len(params); i++ { 122 if strings.ToLower(params[i]) == "from" { 123 goto fromFound 124 } 125 } 126 return "", "", nil, nil, fmt.Errorf("invalid usage. `select` should either be called as a method or include a `FROM file` statement") 127 128 fromFound: 129 fromFile = params[i+1] 130 if i == 0 { 131 params = append([]string{"*"}, params...) 132 i++ 133 } 134 if i == len(params)-1 { 135 return "", "", nil, nil, fmt.Errorf("invalid usage: `FROM` used but no source file specified") 136 } 137 138 if rxPipesMatch.MatchString(fromFile) { 139 j := i + 2 140 for ; j < len(params); j++ { 141 if rxPipesMatch.MatchString(params[j]) { 142 fromFile += " " + params[j] 143 } else { 144 break 145 } 146 } 147 fromFile = strings.Replace(fromFile, "<", "", -1) 148 fromFile = strings.Replace(fromFile, ">", "", -1) 149 pipes = rxPipesSplit.Split(fromFile, -1) 150 return strings.Join(append(params[:i], params[j:]...), " "), "", pipes, nil, nil 151 } 152 153 if rxVarsMatch.MatchString(fromFile) { 154 j := i + 2 155 for ; j < len(params); j++ { 156 if rxVarsMatch.MatchString(params[j]) { 157 fromFile += " " + params[j] 158 } else { 159 break 160 } 161 } 162 fromFile = strings.Replace(fromFile, "$", "", -1) 163 vars = rxPipesSplit.Split(fromFile, -1) 164 return strings.Join(append(params[:i], params[j:]...), " "), "", nil, vars, nil 165 } 166 167 return strings.Join(append(params[:i], params[i+2:]...), " "), fromFile, nil, nil, nil 168 } 169 } 170 171 func stringToInterfaceTrim(s []string, max int) []interface{} { 172 slice := make([]interface{}, max) 173 174 if max <= len(s) { 175 var i int 176 for ; i < max; i++ { 177 slice[i] = s[i] 178 } 179 180 return slice 181 } 182 183 var i int 184 for ; i < len(s); i++ { 185 slice[i] = s[i] 186 } 187 188 for ; i < max; i++ { 189 slice[i] = "" 190 } 191 192 return slice 193 } 194 195 func stringToInterfaceMerge(s []string, max int) []interface{} { 196 slice := make([]interface{}, max) 197 198 switch { 199 case max == 0: 200 // return empty slice 201 202 case max < len(s): 203 var i int 204 for ; i < max-1; i++ { 205 slice[i] = s[i] 206 } 207 slice[i] = strings.Join(s[i:], " ") 208 209 case max == len(s): 210 var i int 211 for ; i < max; i++ { 212 slice[i] = s[i] 213 } 214 215 case max > len(s): 216 var i int 217 for ; i < len(s); i++ { 218 slice[i] = s[i] 219 } 220 for ; i < max; i++ { 221 slice[i] = "" 222 } 223 } 224 225 return slice 226 } 227 228 func stringToInterfacePtr(s *[]string, max int) []interface{} { 229 slice := make([]interface{}, max) 230 for i := range slice { 231 slice[i] = &(*s)[i] 232 } 233 234 return slice 235 }