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  }