diff --git a/LICENSE b/LICENSE index 9c3985ece51..ae5ae84dd5f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016-2024 Kenneth Shaw +Copyright (c) 2015-2024 Kenneth Shaw Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 9c07d62f94a..b99417c3dde 100644 --- a/README.md +++ b/README.md @@ -705,25 +705,30 @@ General Query Execute \g [(OPTIONS)] [FILE] or ; execute query (and send results to file or |pipe) - \crosstabview [(OPTIONS)] [COLUMNS] execute query and display results in crosstab + \go [(OPTIONS)] [FILE] alias for \g \G [(OPTIONS)] [FILE] as \g, but forces vertical output mode + \ego [(OPTIONS)] [FILE] alias for \G + \gx [(OPTIONS)] [FILE] as \g, but forces expanded output mode \gexec execute query and execute each value of the result \gset [PREFIX] execute query and store results in usql variables - \gx [(OPTIONS)] [FILE] as \g, but forces expanded output mode + \crosstabview [(OPTIONS)] [COLUMNS] execute query and display results in crosstab + \chart CHART [(OPTIONS)] execute query and display results as a chart \watch [(OPTIONS)] [DURATION] execute query every specified interval \bind [PARAM]... set query parameters Query Buffer \e [FILE] [LINE] edit the query buffer (or file) with external editor + \edit [-exec] edit the query (or exec) buffer \p show the contents of the query buffer \raw show the raw (non-interpolated) contents of the query buffer + \exec show the contents of the exec buffer \r reset (clear) the query buffer \w FILE write query buffer to file Help \? [commands] show help on backslash commands \? options show help on usql command-line options - \? variables show help on special variables + \? variables show help on special usql variables Input/Output \copy SRC DST QUERY TABLE copy query from source url to table on destination url @@ -735,6 +740,12 @@ Input/Output \i FILE execute commands from file \ir FILE as \i, but relative to location of current script +Conditional + \if EXPR begin conditional block + \elif EXPR alternative within current conditional block + \else final alternative within current conditional block + \endif end conditional block + Informational \d[S+] [NAME] list tables, views, and sequences or describe table, view, sequence, or index \da[S+] [PATTERN] list aggregates @@ -761,17 +772,18 @@ Formatting Transaction \begin begin a transaction - \begin [-read-only] [ISOLATION] begin a transaction with isolation level + \begin -read-only ISOLATION begin a transaction with isolation level \commit commit current transaction \rollback rollback (abort) current transaction Connection \c DSN connect to database url \c DRIVER PARAMS... connect to database with driver and parameters - \cset [NAME [DSN]] set named connection, or list all if no parameters + \cset show named connections + \cset NAME DSN set named connection \cset NAME DRIVER PARAMS... define named connection for database driver \Z close database connection - \password [USERNAME] change the password for a user + \password [USER] change password for user \conninfo display information about the current database connection Operating System @@ -782,7 +794,7 @@ Operating System \timing [on|off] toggle timing of commands Variables - \prompt [-TYPE] VAR [PROMPT] prompt user to set variable + \prompt [-TYPE] VAR [PROMPT] prompt user to set variable \set [NAME [VALUE]] set internal variable, or list all if no parameters \unset NAME unset (delete) internal variable ``` diff --git a/drivers/completer/completer.go b/drivers/completer/completer.go index 23be68e0943..a8e5fde0e75 100644 --- a/drivers/completer/completer.go +++ b/drivers/completer/completer.go @@ -2,7 +2,6 @@ package completer import ( - "fmt" "log" "os" "path/filepath" @@ -690,13 +689,13 @@ func CompleteFromListCase(ct caseType, text []rune, options ...string) [][]rune } func completeFromVariables(text []rune, prefix, suffix string, needValue bool) [][]rune { - vars := env.All() + vars := env.Vars().Vars() names := make([]string, 0, len(vars)) for name, value := range vars { if needValue && value == "" { continue } - names = append(names, fmt.Sprintf("%s%s%s", prefix, name, suffix)) + names = append(names, prefix+name+suffix) } return CompleteFromListCase(MATCH_CASE, text, names...) } @@ -930,7 +929,7 @@ func (c completer) getNamespaces(f metadata.Filter) []string { return names } -func (c completer) completeWithAttributes(ct caseType, selectable string, text []rune, options ...string) [][]rune { +func (c completer) completeWithAttributes(_ caseType, selectable string, text []rune, options ...string) [][]rune { names := make([]string, 0, 10) if r, ok := c.reader.(metadata.ColumnReader); ok { parent := parseParentIdentifier(selectable) diff --git a/drivers/metadata/writer.go b/drivers/metadata/writer.go index db45af2a49d..689c81981d1 100644 --- a/drivers/metadata/writer.go +++ b/drivers/metadata/writer.go @@ -143,7 +143,7 @@ func (w DefaultWriter) DescribeFunctions(u *dburl.URL, funcTypes, pattern string } return v }) - params := env.Pall() + params := env.Vars().Print() params["title"] = "List of functions" return tblfmt.EncodeAll(w.w, res, params) } @@ -271,7 +271,7 @@ func (w DefaultWriter) describeTableDetails(typ, sp, tp string, verbose, showSys } return v }) - params := env.Pall() + params := env.Vars().Print() params["title"] = fmt.Sprintf("%s %s\n", typ, qualifiedIdentifier(sp, tp)) return w.encodeWithSummary(res, params, w.tableDetailsSummary(sp, tp)) } @@ -501,7 +501,7 @@ func (w DefaultWriter) describeSequences(sp, tp string, verbose, showSystem bool s := res.Get() // wrap current record into a separate recordSet rows := NewSequenceSet([]Sequence{*s}) - params := env.Pall() + params := env.Vars().Print() params["footer"] = "off" params["title"] = fmt.Sprintf("Sequence \"%s.%s\"\n", s.Schema, s.Name) err = tblfmt.EncodeAll(w.w, rows, params) @@ -532,7 +532,7 @@ func (w DefaultWriter) describeIndex(i *Index) error { return []interface{}{f.Name, f.DataType} }) - params := env.Pall() + params := env.Vars().Print() params["title"] = fmt.Sprintf("Index %s\n", qualifiedIdentifier(i.Schema, i.Name)) return w.encodeWithSummary(res, params, func(out io.Writer, _ int) (int, error) { primary := "" @@ -559,7 +559,7 @@ func (w DefaultWriter) ListAllDbs(u *dburl.URL, pattern string, verbose bool) er } defer res.Close() - params := env.Pall() + params := env.Vars().Print() params["title"] = "List of databases" return tblfmt.EncodeAll(w.w, res, params) } @@ -611,7 +611,7 @@ func (w DefaultWriter) ListTables(u *dburl.URL, tableTypes, pattern string, verb return v }) - params := env.Pall() + params := env.Vars().Print() params["title"] = "List of relations" return tblfmt.EncodeAll(w.w, res, params) } @@ -635,7 +635,7 @@ func (w DefaultWriter) ListSchemas(u *dburl.URL, pattern string, verbose, showSy return !ok }) } - params := env.Pall() + params := env.Vars().Print() params["title"] = "List of schemas" return tblfmt.EncodeAll(w.w, res, params) } @@ -683,7 +683,7 @@ func (w DefaultWriter) ListIndexes(u *dburl.URL, pattern string, verbose, showSy return v }) - params := env.Pall() + params := env.Vars().Print() params["title"] = "List of indexes" return tblfmt.EncodeAll(w.w, res, params) } @@ -767,7 +767,7 @@ func (w DefaultWriter) ShowStats(u *dburl.URL, statTypes, pattern string, verbos return v }) - params := env.Pall() + params := env.Vars().Print() params["title"] = "Column stats" return tblfmt.EncodeAll(w.w, res, params) } @@ -816,7 +816,7 @@ func (w DefaultWriter) ListPrivilegeSummaries(u *dburl.URL, pattern string, show return v }) - params := env.Pall() + params := env.Vars().Print() params["title"] = "Access privileges" return tblfmt.EncodeAll(w.w, res, params) } diff --git a/drivers/snowflake/snowflake.go b/drivers/snowflake/snowflake.go index 317d2490e67..3e61512e594 100644 --- a/drivers/snowflake/snowflake.go +++ b/drivers/snowflake/snowflake.go @@ -47,14 +47,13 @@ func init() { }) } -func listAllDbs(db drivers.DB, w io.Writer, pattern string, verbose bool) error { +func listAllDbs(db drivers.DB, w io.Writer, _ string, _ bool) error { rows, err := db.Query("SHOW databases") if err != nil { return err } defer rows.Close() - - params := env.Pall() + params := env.Vars().Print() params["title"] = "List of databases" return tblfmt.EncodeAll(w, rows, params) } diff --git a/env/env.go b/env/env.go index 6a9aac3e16c..f7fb9569098 100644 --- a/env/env.go +++ b/env/env.go @@ -4,6 +4,7 @@ package env import ( "bytes" + "fmt" "io" "os" "os/exec" @@ -13,6 +14,7 @@ import ( "runtime" "strconv" "strings" + "unicode" "unicode/utf8" "github.com/kenshaw/rasterm" @@ -20,6 +22,24 @@ import ( "github.com/xo/usql/text" ) +// vars are environment variables. +var vars *Variables + +func init() { + vars = NewDefaultVars() +} + +// Vars returns the environment variables. +func Vars() *Variables { + return vars +} + +// Get returns a standard variable. +func Get(name string) string { + value, _ := vars.Get(name) + return value +} + // Getenv tries retrieving successive keys from os environment variables. func Getenv(keys ...string) (string, bool) { m := make(map[string]string) @@ -73,30 +93,28 @@ func OpenFile(u *user.User, path string) (string, *os.File, error) { return path, f, nil } -// EditFile edits a file. If path is empty, then a temporary file will be created. -func EditFile(u *user.User, path, line, s string) ([]rune, error) { - ed := All()["EDITOR"] - if ed == "" { - if p, err := exec.LookPath("vi"); err == nil { - ed = p - } else { +// EditFile edits a file. If path is empty, then a temporary file will be +// created. +func EditFile(u *user.User, path, line string, buf []byte) ([]byte, error) { + ed, _ := vars.Get("EDITOR") + switch { + case ed == "": + if ed, _ = exec.LookPath("vi"); ed == "" { return nil, text.ErrNoEditorDefined } - } - if path != "" { + case path != "": path = passfile.Expand(u.HomeDir, path) - } else { + default: f, err := os.CreateTemp("", text.CommandLower()+".*.sql") if err != nil { return nil, err } - err = f.Close() - if err != nil { + path = f.Name() + if _, err = f.Write(append(bytes.TrimSuffix(buf, lineend), '\n')); err != nil { + f.Close() return nil, err } - path = f.Name() - err = os.WriteFile(path, []byte(strings.TrimSuffix(s, "\n")+"\n"), 0o644) - if err != nil { + if err = f.Close(); err != nil { return nil, err } } @@ -111,10 +129,7 @@ func EditFile(u *user.User, path, line, s string) ([]rune, error) { } // create command c := exec.Command(ed, args...) - c.Stdin = os.Stdin - c.Stdout = os.Stdout - c.Stderr = os.Stderr - // run + c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr if err := c.Run(); err != nil { return nil, err } @@ -123,7 +138,7 @@ func EditFile(u *user.User, path, line, s string) ([]rune, error) { if err != nil { return nil, err } - return []rune(strings.TrimSuffix(string(buf), "\n")), nil + return bytes.TrimSuffix(buf, lineend), nil } // HistoryFile returns the path to the history file. @@ -234,23 +249,24 @@ func Exec(s string) (string, error) { return "", err } // remove ending \r\n - buf = bytes.TrimSuffix(buf, []byte{'\n'}) + buf = bytes.TrimSuffix(buf, lineend) buf = bytes.TrimSuffix(buf, []byte{'\r'}) return string(buf), nil } -var cleanDoubleRE = regexp.MustCompile(`(^|[^\\])''`) - -// Dequote unquotes a string. -func Dequote(s string, quote byte) (string, error) { - if len(s) < 2 || s[len(s)-1] != quote { +// Unquote unquotes a string. +func Unquote(s string) (string, error) { + switch n := len(s); { + case n == 0: + return "", nil + case n < 2, s[n-1] != s[0], s[0] != '\'' && s[0] != '"' && s[0] != '`': return "", text.ErrUnterminatedQuotedString } + quote := s[0] s = s[1 : len(s)-1] if quote == '\'' { - s = cleanDoubleRE.ReplaceAllString(s, "$1\\'") + s = cleanDoubleRE.ReplaceAllString(s, `$1\'`) } - // this is the last part of strconv.Unquote var runeTmp [utf8.UTFMax]byte buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations. @@ -273,54 +289,37 @@ func Dequote(s string, quote byte) (string, error) { return string(buf), nil } -// Getvar retrieves an environment variable. -func Getvar(s string, v Vars) (bool, string, error) { - q, n := "", s - if c := s[0]; c == '\'' || c == '"' { - var err error - if n, err = Dequote(s, c); err != nil { - return false, "", err - } - q = string(c) - } - if val, ok := v[n]; ok { - return true, q + val + q, nil - } - return false, s, nil -} - -// Unquote returns a func that unquotes strings for the user. +// Untick returns a func that unquotes and unticks strings for the user. // // When exec is true, backtick'd strings will be executed using the provided // user's shell (see Exec). -func Unquote(u *user.User, exec bool, v Vars) func(string, bool) (bool, string, error) { - return func(s string, isvar bool) (bool, string, error) { - // log.Printf(">>> UNQUOTE: %q", s) - if isvar { - return Getvar(s, v) - } - if len(s) < 2 { - return false, "", text.ErrInvalidQuotedString +func Untick(u *user.User, v *Variables, exec bool) func(string, bool) (string, bool, error) { + return func(s string, isvar bool) (string, bool, error) { + // fmt.Fprintf(os.Stderr, "untick: %q\n", s) + switch { + case isvar: + value, ok := v.Get(s) + return value, ok, nil + case len(s) < 2: + return "", false, text.ErrInvalidQuotedString } c := s[0] - z, err := Dequote(s, c) - if err != nil { - return false, "", err - } - if c == '\'' || c == '"' { - return true, z, nil - } - if c != '`' { - return false, "", text.ErrInvalidQuotedString - } - if !exec { - return true, z, nil + z, err := Unquote(s) + switch { + case err != nil: + return "", false, err + case c == '\'', c == '"': + return z, true, nil + case c != '`': + return "", false, text.ErrInvalidQuotedString + case !exec: + return z, true, nil } res, err := Exec(z) if err != nil { - return false, "", err + return "", false, err } - return true, res, nil + return res, true, nil } } @@ -334,6 +333,54 @@ func Quote(s string) string { // environment variable. func TermGraphics() rasterm.TermType { var typ rasterm.TermType - _ = typ.UnmarshalText([]byte(Get("TERM_GRAPHICS"))) + s, _ := vars.Get("TERM_GRAPHICS") + _ = typ.UnmarshalText([]byte(s)) return typ } + +// ValidIdentifier returns an error when n is not a valid identifier. +func ValidIdentifier(n string) error { + r := []rune(n) + rlen := len(r) + if rlen < 1 { + return text.ErrInvalidIdentifier + } + for i := 0; i < rlen; i++ { + if c := r[i]; c != '_' && !unicode.IsLetter(c) && !unicode.IsNumber(c) { + return text.ErrInvalidIdentifier + } + } + return nil +} + +func ParseBool(value, name string) (string, error) { + switch strings.ToLower(value) { + case "1", "t", "tr", "tru", "true", "on": + return "on", nil + case "0", "f", "fa", "fal", "fals", "false", "of", "off": + return "off", nil + } + return "", fmt.Errorf(text.FormatFieldInvalidValue, value, name, "Boolean") +} + +func ParseKeywordBool(value, name string, keywords ...string) (string, error) { + v := strings.ToLower(value) + switch v { + case "1", "t", "tr", "tru", "true", "on": + return "on", nil + case "0", "f", "fa", "fal", "fals", "false", "of", "off": + return "off", nil + } + for _, k := range keywords { + if v == k { + return v, nil + } + } + return "", fmt.Errorf(text.FormatFieldInvalid, value, name) +} + +// lineend is the line ending. +var lineend = []byte{'\n'} + +// cleanDoubleRE matches double quotes. +var cleanDoubleRE = regexp.MustCompile(`(^|[^\\])''`) diff --git a/env/list.go b/env/list.go new file mode 100644 index 00000000000..b5e2a246b27 --- /dev/null +++ b/env/list.go @@ -0,0 +1,297 @@ +package env + +import ( + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "runtime" + "strings" + "unicode" + + "github.com/xo/usql/text" + "github.com/yookoala/realpath" +) + +// Listing writes a formatted listing of the special environment variables to +// w, separated in sections based on variable type. +func Listing(w io.Writer) { + varsWithDesc := make([]string, len(varNames)) + for i, v := range varNames { + varsWithDesc[i] = v.String() + } + pvarsWithDesc := make([]string, len(pvarNames)) + for i, v := range pvarNames { + pvarsWithDesc[i] = v.String() + } + + // determine config dir name + configDir, configExtra := buildConfigDir("config.yaml") + + // environment var names + configDesc := configDir + if configExtra != "" { + configDesc = configExtra + } + ev := []varName{ + { + text.CommandUpper() + "_CONFIG", + fmt.Sprintf(`config file path (default %q)`, configDesc), + }, + } + envVarsWithDesc := make([]string, len(envVarNames)+1) + for i, v := range append(ev, envVarNames...) { + envVarsWithDesc[i] = v.String() + } + + if configExtra != "" { + configExtra = " (" + configExtra + ")" + } + + template := `List of specially treated variables + +%s variables: +Usage: + %[1]s --set=NAME=VALUE + or \set NAME VALUE inside %[1]s + +%[2]s + +Display settings: +Usage: + %[1]s --pset=NAME[=VALUE] + or \pset NAME [VALUE] inside %[1]s + +%[3]s + +Environment variables: +Usage: + NAME=VALUE [NAME=VALUE] %[1]s ... + or \setenv NAME [VALUE] inside %[1]s + +%[4]s + +Connection variables: +Usage: + %[1]s --cset NAME[=DSN] + or \cset NAME [DSN] inside %[1]s + or \cset NAME DRIVER PARAMS... inside %[1]s + or define in %[5]s%[6]s +` + fmt.Fprintf( + w, template, + text.CommandName, + strings.TrimRightFunc(strings.Join(varsWithDesc, ""), unicode.IsSpace), + strings.TrimRightFunc(strings.Join(pvarsWithDesc, ""), unicode.IsSpace), + strings.TrimRightFunc(strings.Join(envVarsWithDesc, ""), unicode.IsSpace), + configDir, + configExtra, + ) +} + +func buildConfigDir(configName string) (string, string) { + dir := `$HOME/.config/usql` + switch runtime.GOOS { + case "darwin": + dir = `$HOME/Library/Application Support` + case "windows": + dir = `%AppData%\usql` + } + configDir, err := os.UserConfigDir() + if err != nil { + return filepath.Join(dir, configName), "" + } + if configDir, err = realpath.Realpath(configDir); err != nil { + return filepath.Join(dir, configName), "" + } + return filepath.Join(dir, configName), filepath.Join(configDir, "usql", configName) +} + +type varName struct { + name string + desc string +} + +func (v varName) String() string { + return fmt.Sprintf(" %s\n %s\n", v.name, v.desc) +} + +var varNames = []varName{ + { + `ECHO_HIDDEN`, + `if set, display internal queries executed by backslash commands; if set to "noexec", shows queries without execution`, + }, + { + `ON_ERROR_STOP`, + `stop batch execution after error`, + }, + { + `PROMPT1`, + `specifies the standard ` + text.CommandName + ` prompt`, + }, + { + `QUIET`, + `run quietly (same as -q option)`, + }, + { + `ROW_COUNT`, + `number of rows returned or affected by last query, or 0`, + }, +} + +var ( + formatRE = regexp.MustCompile(`^(unaligned|aligned|wrapped|html|asciidoc|latex|latex-longtable|troff-ms|csv|json|vertical)$`) + linestlyeRE = regexp.MustCompile(`^(ascii|old-ascii|unicode)$`) + borderRE = regexp.MustCompile(`^(single|double)$`) +) + +var pvarNames = []varName{ + { + `border`, + `border style (number)`, + }, + { + `columns`, + `target width for the wrapped format`, + }, + { + `csv_fieldsep`, + `field separator for CSV output (default ",")`, + }, + { + `expanded`, + `expanded output [on, off, auto]`, + }, + { + `fieldsep`, + `field separator for unaligned output (default "|")`, + }, + { + `fieldsep_zero`, + `set field separator for unaligned output to a zero byte`, + }, + { + `footer`, + `enable or disable display of the table footer [on, off]`, + }, + { + `format`, + `set output format [unaligned, aligned, wrapped, vertical, html, asciidoc, csv, json, ...]`, + }, + { + `linestyle`, + `set the border line drawing style [ascii, old-ascii, unicode]`, + }, + { + `null`, + `set the string to be printed in place of a null value`, + }, + { + `numericlocale`, + `enable display of a locale-specific character to separate groups of digits`, + }, + { + `pager_min_lines`, + `minimum number of lines required in the output to use a pager, 0 to disable (default 0)`, + }, + { + `pager`, + `control when an external pager is used [on, off, always]`, + }, + { + `recordsep`, + `record (line) separator for unaligned output`, + }, + { + `recordsep_zero`, + `set record separator for unaligned output to a zero byte`, + }, + { + `tableattr`, + `specify attributes for table tag in html format, or proportional column widths for left-aligned data types in latex-longtable format`, + }, + { + `time`, + `format used to display time/date column values (default RFC3339Nano)`, + }, + { + `timezone`, + `the timezone to display dates in (default "")`, + }, + { + `title`, + `set the table title for subsequently printed tables`, + }, + { + `tuples_only`, + `if set, only actual table data is shown`, + }, + { + `unicode_border_linestyle`, + `set the style of Unicode line drawing [single, double]`, + }, + { + `unicode_column_linestyle`, + `set the style of Unicode line drawing [single, double]`, + }, + { + `unicode_header_linestyle`, + `set the style of Unicode line drawing [single, double]`, + }, +} + +var envVarNames = []varName{ + { + text.CommandUpper() + `_EDITOR, EDITOR, VISUAL`, + `editor used by the \e, \ef, and \ev commands`, + }, + { + text.CommandUpper() + `_EDITOR_LINENUMBER_ARG`, + `how to specify a line number when invoking the editor`, + }, + { + text.CommandUpper() + `_HISTORY`, + `alternative location for the command history file`, + }, + { + text.CommandUpper() + `_PAGER, PAGER`, + `name of external pager program`, + }, + { + text.CommandUpper() + `_SHOW_HOST_INFORMATION`, + `display host information when connecting to a database`, + }, + { + text.CommandUpper() + `RC`, + `alternative location for the user's .usqlrc file`, + }, + { + text.CommandUpper() + `_SSLMODE, SSLMODE`, + `when set to 'retry', allows connections to attempt to reconnect when no ?sslmode= was specified on the url`, + }, + { + `SYNTAX_HL`, + `enable syntax highlighting`, + }, + { + `SYNTAX_HL_FORMAT`, + `chroma library formatter name`, + }, + { + `SYNTAX_HL_STYLE`, + `chroma library style name (default "monokai")`, + }, + { + `SYNTAX_HL_OVERRIDE_BG`, + `enables overriding the background color of the chroma styles`, + }, + { + `TERM_GRAPHICS`, + `use the specified terminal graphics`, + }, + { + `SHELL`, + `shell used by the \! command`, + }, +} diff --git a/env/types.go b/env/types.go deleted file mode 100644 index 3a5ae72f300..00000000000 --- a/env/types.go +++ /dev/null @@ -1,719 +0,0 @@ -package env - -import ( - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "regexp" - "runtime" - "sort" - "strconv" - "strings" - "time" - "unicode" - - syslocale "github.com/jeandeaual/go-locale" - "github.com/xo/terminfo" - "github.com/xo/usql/text" - "github.com/yookoala/realpath" -) - -type varName struct { - name string - desc string -} - -func (v varName) String() string { - return fmt.Sprintf(" %s\n %s\n", v.name, v.desc) -} - -var varNames = []varName{ - { - "ECHO_HIDDEN", - "if set, display internal queries executed by backslash commands; if set to \"noexec\", just show them without execution", - }, - { - "ON_ERROR_STOP", - "stop batch execution after error", - }, - { - "PROMPT1", - "specifies the standard " + text.CommandName + " prompt", - }, - { - "QUIET", - "run quietly (same as -q option)", - }, - { - "ROW_COUNT", - "number of rows returned or affected by last query, or 0", - }, -} - -var pvarNames = []varName{ - { - "border", - "border style (number)", - }, - { - "columns", - "target width for the wrapped format", - }, - { - "csv_fieldsep", - `field separator for CSV output (default ",")`, - }, - { - "expanded", - "expanded output [on, off, auto]", - }, - { - "fieldsep", - `field separator for unaligned output (default "|")`, - }, - { - "fieldsep_zero", - "set field separator for unaligned output to a zero byte", - }, - { - "footer", - "enable or disable display of the table footer [on, off]", - }, - { - "format", - "set output format [unaligned, aligned, wrapped, vertical, html, asciidoc, csv, json, ...]", - }, - { - "linestyle", - "set the border line drawing style [ascii, old-ascii, unicode]", - }, - { - "null", - "set the string to be printed in place of a null value", - }, - { - "numericlocale", - "enable display of a locale-specific character to separate groups of digits", - }, - { - "pager_min_lines", - "minimum number of lines required in the output to use a pager, 0 to disable (default)", - }, - { - "pager", - "control when an external pager is used [on, off, always]", - }, - { - "recordsep", - "record (line) separator for unaligned output", - }, - { - "recordsep_zero", - "set record separator for unaligned output to a zero byte", - }, - { - "tableattr", - "specify attributes for table tag in html format, or proportional column widths for left-aligned data types in latex-longtable format", - }, - { - "time", - `format used to display time/date column values (default "RFC3339Nano")`, - }, - { - "timezone", - `the timezone to display dates in (default '')`, - }, - { - "title", - "set the table title for subsequently printed tables", - }, - { - "tuples_only", - "if set, only actual table data is shown", - }, - { - "unicode_border_linestyle", - "set the style of Unicode line drawing [single, double]", - }, - { - "unicode_column_linestyle", - "set the style of Unicode line drawing [single, double]", - }, - { - "unicode_header_linestyle", - "set the style of Unicode line drawing [single, double]", - }, -} - -var envVarNames = []varName{ - { - text.CommandUpper() + "_EDITOR, EDITOR, VISUAL", - "editor used by the \\e, \\ef, and \\ev commands", - }, - { - text.CommandUpper() + "_EDITOR_LINENUMBER_ARG", - "how to specify a line number when invoking the editor", - }, - { - text.CommandUpper() + "_HISTORY", - "alternative location for the command history file", - }, - { - text.CommandUpper() + "_PAGER, PAGER", - "name of external pager program", - }, - { - text.CommandUpper() + "_SHOW_HOST_INFORMATION", - "display host information when connecting to a database", - }, - { - text.CommandUpper() + "RC", - "alternative location for the user's .usqlrc file", - }, - { - text.CommandUpper() + "_SSLMODE, SSLMODE", - "when set to 'retry', allows connections to attempt to reconnect when no ?sslmode= was specified on the url", - }, - { - "SYNTAX_HL", - "enable syntax highlighting", - }, - { - "SYNTAX_HL_FORMAT", - "chroma library formatter name", - }, - { - "SYNTAX_HL_STYLE", - `chroma library style name (default "monokai")`, - }, - { - "SYNTAX_HL_OVERRIDE_BG", - "enables overriding the background color of the chroma styles", - }, - { - "TERM_GRAPHICS", - `use the specified terminal graphics`, - }, - { - "SHELL", - "shell used by the \\! command", - }, -} - -// Vars is a map of variables to their values. -type Vars map[string]string - -// Set sets a variable name. -func (v Vars) Set(name, value string) { - v[name] = value -} - -// Unset unsets a variable name. -func (v Vars) Unset(name string) { - delete(v, name) -} - -// All returns all variables as a map. -func (v Vars) All() map[string]string { - return map[string]string(v) -} - -// vars are the environment variables. -var vars Vars - -// pvars are the environment printing variables. -var pvars Vars - -// cvars are the environment named connections. -var cvars map[string][]string - -func init() { - cmdNameUpper := strings.ToUpper(text.CommandName) - // get USQL_* variables - enableHostInformation := "true" - if v, _ := Getenv(cmdNameUpper + "_SHOW_HOST_INFORMATION"); v != "" { - enableHostInformation = v - } - // get NO_COLOR - noColor := false - if s, ok := Getenv("NO_COLOR"); ok { - noColor = s != "0" && s != "false" && s != "off" - } - // get color level - colorLevel, _ := terminfo.ColorLevelFromEnv() - enableSyntaxHL := "true" - if noColor || colorLevel < terminfo.ColorLevelBasic { - enableSyntaxHL = "false" - } - // pager - pagerCmd, ok := Getenv(cmdNameUpper+"_PAGER", "PAGER") - pager := "off" - if !ok { - for _, s := range []string{"less", "more"} { - if _, err := exec.LookPath(s); err == nil { - pagerCmd = s - break - } - } - } - if pagerCmd != "" { - pager = "on" - } - // editor - editorCmd, _ := Getenv(cmdNameUpper+"_EDITOR", "EDITOR", "VISUAL") - // sslmode - sslmode, ok := Getenv(cmdNameUpper+"_SSLMODE", "SSLMODE") - if !ok { - sslmode = "retry" - } - vars = Vars{ - // usql related logic - "SHOW_HOST_INFORMATION": enableHostInformation, - "PAGER": pagerCmd, - "EDITOR": editorCmd, - "QUIET": "off", - "ON_ERROR_STOP": "off", - // prompts - "PROMPT1": "%S%N%m%/%R%# ", - // syntax highlighting variables - "SYNTAX_HL": enableSyntaxHL, - "SYNTAX_HL_FORMAT": colorLevel.ChromaFormatterName(), - "SYNTAX_HL_STYLE": "monokai", - "SYNTAX_HL_OVERRIDE_BG": "true", - "SSLMODE": sslmode, - "TERM_GRAPHICS": "none", - } - // determine locale - locale := "en-US" - if s, err := syslocale.GetLocale(); err == nil { - locale = s - } - pvars = Vars{ - "border": "1", - "columns": "0", - "csv_fieldsep": ",", - "expanded": "off", - "fieldsep": "|", - "fieldsep_zero": "off", - "footer": "on", - "format": "aligned", - "linestyle": "ascii", - "locale": locale, - "null": "", - "numericlocale": "off", - "pager_min_lines": "0", - "pager": pager, - "recordsep": "\n", - "recordsep_zero": "off", - "tableattr": "", - "time": "RFC3339Nano", - "timezone": "", - "title": "", - "tuples_only": "off", - "unicode_border_linestyle": "single", - "unicode_column_linestyle": "single", - "unicode_header_linestyle": "single", - } - cvars = make(map[string][]string) -} - -// ValidIdentifier returns an error when n is not a valid identifier. -func ValidIdentifier(n string) error { - r := []rune(n) - rlen := len(r) - if rlen < 1 { - return text.ErrInvalidIdentifier - } - for i := 0; i < rlen; i++ { - if c := r[i]; c != '_' && !unicode.IsLetter(c) && !unicode.IsNumber(c) { - return text.ErrInvalidIdentifier - } - } - return nil -} - -// Set sets a variable. -func Set(name, value string) error { - if err := ValidIdentifier(name); err != nil { - return err - } - if name == "ON_ERROR_STOP" || name == "QUIET" { - if value == "" { - value = "on" - } else { - var err error - if value, err = ParseBool(value, name); err != nil { - return err - } - } - } - vars.Set(name, value) - return nil -} - -// Unset unsets a variable. -func Unset(name string) error { - if err := ValidIdentifier(name); err != nil { - return err - } - vars.Unset(name) - return nil -} - -// All returns all variables. -func All() Vars { - m := make(Vars) - for k, v := range vars { - m[k] = v - } - return m -} - -// Pall returns all p variables. -func Pall() Vars { - m := make(Vars) - for k, v := range pvars { - m[k] = v - } - return m -} - -// Pwrite writes the p variables to the writer. -func Pwrite(w io.Writer) error { - keys := make([]string, len(pvars)) - var i, width int - for k := range pvars { - keys[i], width = k, max(len(k), width) - i++ - } - sort.Strings(keys) - for _, k := range keys { - val := pvars[k] - switch k { - case "csv_fieldsep", "fieldsep", "recordsep", "null": - val = strconv.QuoteToASCII(val) - case "tableattr", "title": - if val != "" { - val = strconv.QuoteToASCII(val) - } - } - fmt.Fprintln(w, k+strings.Repeat(" ", width-len(k)), val) - } - return nil -} - -var ( - formatRE = regexp.MustCompile(`^(unaligned|aligned|wrapped|html|asciidoc|latex|latex-longtable|troff-ms|csv|json|vertical)$`) - linestlyeRE = regexp.MustCompile(`^(ascii|old-ascii|unicode)$`) - borderRE = regexp.MustCompile(`^(single|double)$`) -) - -func ParseBool(value, name string) (string, error) { - switch strings.ToLower(value) { - case "1", "t", "tr", "tru", "true", "on": - return "on", nil - case "0", "f", "fa", "fal", "fals", "false", "of", "off": - return "off", nil - } - return "", fmt.Errorf(text.FormatFieldInvalidValue, value, name, "Boolean") -} - -func ParseKeywordBool(value, name string, keywords ...string) (string, error) { - v := strings.ToLower(value) - switch v { - case "1", "t", "tr", "tru", "true", "on": - return "on", nil - case "0", "f", "fa", "fal", "fals", "false", "of", "off": - return "off", nil - } - for _, k := range keywords { - if v == k { - return v, nil - } - } - return "", fmt.Errorf(text.FormatFieldInvalid, value, name) -} - -func Get(name string) string { - return vars[name] -} - -func Pget(name string) (string, error) { - v, ok := pvars[name] - if !ok { - return "", fmt.Errorf(text.UnknownFormatFieldName, name) - } - return v, nil -} - -// Ptoggle toggles a p variable. -func Ptoggle(name, extra string) (string, error) { - _, ok := pvars[name] - if !ok { - return "", fmt.Errorf(text.UnknownFormatFieldName, name) - } - switch name { - case "border", "columns", "pager_min_lines": - case "pager": - switch pvars[name] { - case "on", "always": - pvars[name] = "off" - case "off": - pvars[name] = "on" - default: - panic(fmt.Sprintf("invalid state for field %s", name)) - } - case "expanded": - switch pvars[name] { - case "on", "auto": - pvars[name] = "off" - case "off": - pvars[name] = "on" - default: - panic(fmt.Sprintf("invalid state for field %s", name)) - } - case "fieldsep_zero", "footer", "numericlocale", "recordsep_zero", "tuples_only": - switch pvars[name] { - case "on": - pvars[name] = "off" - case "off": - pvars[name] = "on" - default: - panic(fmt.Sprintf("invalid state for field %s", name)) - } - case "format": - switch { - case extra != "" && pvars[name] != extra: - pvars[name] = extra - case pvars[name] == "aligned": - pvars[name] = "unaligned" - default: - pvars[name] = "aligned" - } - case "linestyle": - case "csv_fieldsep", "fieldsep", "null", "recordsep", "time", "timezone", "locale": - case "tableattr", "title": - pvars[name] = "" - case "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle": - default: - panic(fmt.Sprintf("field %s was defined in package pvars variable, but not in switch", name)) - } - return pvars[name], nil -} - -// Pset sets a p variable. -func Pset(name, value string) (string, error) { - _, ok := pvars[name] - if !ok { - return "", fmt.Errorf(text.UnknownFormatFieldName, name) - } - switch name { - case "border", "columns", "pager_min_lines": - i, _ := strconv.Atoi(value) - pvars[name] = fmt.Sprintf("%d", i) - case "pager": - s, err := ParseKeywordBool(value, name, "always") - if err != nil { - return "", text.ErrInvalidFormatPagerType - } - pvars[name] = s - case "expanded": - s, err := ParseKeywordBool(value, name, "auto") - if err != nil { - return "", text.ErrInvalidFormatExpandedType - } - pvars[name] = s - case "fieldsep_zero", "footer", "numericlocale", "recordsep_zero", "tuples_only": - s, err := ParseBool(value, name) - if err != nil { - return "", err - } - pvars[name] = s - case "format": - if !formatRE.MatchString(value) { - return "", text.ErrInvalidFormatType - } - pvars[name] = value - case "linestyle": - if !linestlyeRE.MatchString(value) { - return "", text.ErrInvalidFormatLineStyle - } - pvars[name] = value - case "csv_fieldsep", "fieldsep", "null", "recordsep", "tableattr", "time", "title", "locale": - pvars[name] = value - case "timezone": - if _, err := time.LoadLocation(value); err != nil { - return "", text.ErrInvalidTimezoneLocation - } - pvars[name] = value - case "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle": - if !borderRE.MatchString(value) { - return "", text.ErrInvalidFormatBorderLineStyle - } - pvars[name] = value - default: - panic(fmt.Sprintf("field %s was defined in package pvars variable, but not in switch", name)) - } - return pvars[name], nil -} - -// Cset sets a named connection for the environment. -func Cset(name string, vals ...string) error { - if err := ValidIdentifier(name); err != nil { - return err - } - if _, ok := cvars[name]; len(vals) == 0 || vals[0] == "" && ok { - delete(cvars, name) - } else { - v := make([]string, len(vals)) - copy(v, vals) - cvars[name] = v - } - return nil -} - -// Cget returns the environment's named connection. -func Cget(name string) ([]string, bool) { - vals, ok := cvars[name] - if !ok { - return nil, false - } - v := make([]string, len(vals)) - copy(v, vals) - return v, true -} - -// Call returns all named connections from the environment. -func Call() map[string][]string { - m := make(map[string][]string, len(cvars)) - for k, vals := range cvars { - v := make([]string, len(vals)) - copy(v, vals) - m[k] = v - } - return m -} - -// timeConsts are well known time consts. -var timeConsts = map[string]string{ - "ANSIC": time.ANSIC, - "UnixDate": time.UnixDate, - "RubyDate": time.RubyDate, - "RFC822": time.RFC822, - "RFC822Z": time.RFC822Z, - "RFC850": time.RFC850, - "RFC1123": time.RFC1123, - "RFC1123Z": time.RFC1123Z, - "RFC3339": time.RFC3339, - "RFC3339Nano": time.RFC3339Nano, - "Kitchen": time.Kitchen, - "Stamp": time.Stamp, - "StampMilli": time.StampMilli, - "StampMicro": time.StampMicro, - "StampNano": time.StampNano, -} - -// GoTime returns the user's time format converted to Go's time.Format value. -func GoTime() string { - tfmt := pvars["time"] - if s, ok := timeConsts[tfmt]; ok { - return s - } - return tfmt -} - -// Listing writes a formatted listing of the special environment variables to -// w, separated in sections based on variable type. -func Listing(w io.Writer) { - varsWithDesc := make([]string, len(varNames)) - for i, v := range varNames { - varsWithDesc[i] = v.String() - } - pvarsWithDesc := make([]string, len(pvarNames)) - for i, v := range pvarNames { - pvarsWithDesc[i] = v.String() - } - - // determine config dir name - configDir, configExtra := buildConfigDir("config.yaml") - - // environment var names - configDesc := configDir - if configExtra != "" { - configDesc = configExtra - } - ev := []varName{ - { - text.CommandUpper() + "_CONFIG", - fmt.Sprintf(`config file path (default %q)`, configDesc), - }, - } - envVarsWithDesc := make([]string, len(envVarNames)+1) - for i, v := range append(ev, envVarNames...) { - envVarsWithDesc[i] = v.String() - } - - if configExtra != "" { - configExtra = " (" + configExtra + ")" - } - - template := `List of specially treated variables - -%s variables: -Usage: - %[1]s --set=NAME=VALUE - or \set NAME VALUE inside %[1]s - -%[2]s - -Display settings: -Usage: - %[1]s --pset=NAME[=VALUE] - or \pset NAME [VALUE] inside %[1]s - -%[3]s - -Environment variables: -Usage: - NAME=VALUE [NAME=VALUE] %[1]s ... - or \setenv NAME [VALUE] inside %[1]s - -%[4]s - -Connection variables: -Usage: - %[1]s --cset NAME[=DSN] - or \cset NAME [DSN] inside %[1]s - or \cset NAME DRIVER PARAMS... inside %[1]s - or define in %[5]s%[6]s -` - fmt.Fprintf( - w, template, - text.CommandName, - strings.TrimRightFunc(strings.Join(varsWithDesc, ""), unicode.IsSpace), - strings.TrimRightFunc(strings.Join(pvarsWithDesc, ""), unicode.IsSpace), - strings.TrimRightFunc(strings.Join(envVarsWithDesc, ""), unicode.IsSpace), - configDir, - configExtra, - ) -} - -func buildConfigDir(configName string) (string, string) { - dir := `$HOME/.config/usql` - switch runtime.GOOS { - case "darwin": - dir = `$HOME/Library/Application Support` - case "windows": - dir = `%AppData%\usql` - } - configDir, err := os.UserConfigDir() - if err != nil { - return filepath.Join(dir, configName), "" - } - if configDir, err = realpath.Realpath(configDir); err != nil { - return filepath.Join(dir, configName), "" - } - return filepath.Join(dir, configName), filepath.Join(configDir, "usql", configName) -} diff --git a/env/vars.go b/env/vars.go new file mode 100644 index 00000000000..bdf2a65aa9f --- /dev/null +++ b/env/vars.go @@ -0,0 +1,403 @@ +package env + +import ( + "fmt" + "io" + "maps" + "os/exec" + "slices" + "strconv" + "strings" + "time" + + syslocale "github.com/jeandeaual/go-locale" + "github.com/xo/terminfo" + "github.com/xo/usql/text" +) + +// Variables handles the standard, print, and connection variables. +type Variables struct { + // vars holds standard variables. + vars map[string]string + // prnt holds print variables ("print" is a reserved word). + prnt map[string]string + // conn holds connection variables. + conn map[string][]string +} + +// NewVars creates a set of empty variables. +func NewVars() *Variables { + return &Variables{ + vars: make(map[string]string), + prnt: make(map[string]string), + conn: make(map[string][]string), + } +} + +// NewDefaultVars creates standard, print, and connection variables, based on +// environment variables. +func NewDefaultVars() *Variables { + cmdNameUpper := strings.ToUpper(text.CommandName) + // get USQL_* variables + showHostInformation := "true" + if v, _ := Getenv(cmdNameUpper + "_SHOW_HOST_INFORMATION"); v != "" { + showHostInformation = v + } + // get NO_COLOR + noColor := false + if s, ok := Getenv("NO_COLOR"); ok { + noColor = s != "0" && s != "false" && s != "off" + } + // get color level + colorLevel, _ := terminfo.ColorLevelFromEnv() + enableSyntaxHL := "true" + if noColor || colorLevel < terminfo.ColorLevelBasic { + enableSyntaxHL = "false" + } + // pager + pagerCmd, ok := Getenv(cmdNameUpper+"_PAGER", "PAGER") + pager := "off" + if !ok { + for _, s := range []string{"less", "more"} { + if _, err := exec.LookPath(s); err == nil { + pagerCmd = s + break + } + } + } + if pagerCmd != "" { + pager = "on" + } + // editor + editorCmd, _ := Getenv(cmdNameUpper+"_EDITOR", "EDITOR", "VISUAL") + // sslmode + sslmode, ok := Getenv(cmdNameUpper+"_SSLMODE", "SSLMODE") + if !ok { + sslmode = "retry" + } + // determine locale + locale := "en-US" + if s, err := syslocale.GetLocale(); err == nil { + locale = s + } + return &Variables{ + vars: map[string]string{ + // usql related logic + "SHOW_HOST_INFORMATION": showHostInformation, + "PAGER": pagerCmd, + "EDITOR": editorCmd, + "QUIET": "off", + "ON_ERROR_STOP": "off", + // prompts + "PROMPT1": "%S%N%m%/%R%# ", + // syntax highlighting variables + "SYNTAX_HL": enableSyntaxHL, + "SYNTAX_HL_FORMAT": colorLevel.ChromaFormatterName(), + "SYNTAX_HL_STYLE": "monokai", + "SYNTAX_HL_OVERRIDE_BG": "true", + "SSLMODE": sslmode, + "TERM_GRAPHICS": "none", + }, + prnt: map[string]string{ + "border": "1", + "columns": "0", + "csv_fieldsep": ",", + "expanded": "off", + "fieldsep": "|", + "fieldsep_zero": "off", + "footer": "on", + "format": "aligned", + "linestyle": "ascii", + "locale": locale, + "null": "", + "numericlocale": "off", + "pager_min_lines": "0", + "pager": pager, + "recordsep": "\n", + "recordsep_zero": "off", + "tableattr": "", + "time": "RFC3339Nano", + "timezone": "", + "title": "", + "tuples_only": "off", + "unicode_border_linestyle": "single", + "unicode_column_linestyle": "single", + "unicode_header_linestyle": "single", + }, + conn: make(map[string][]string), + } +} + +// Vars returns a copy of the standard variables. +func (v *Variables) Vars() map[string]string { + return maps.Clone(v.vars) +} + +// Print returns a copy of the print variables. +func (v *Variables) Print() map[string]string { + return maps.Clone(v.prnt) +} + +// Conn returns a copy of the connection variables. +func (v *Variables) Conn() map[string][]string { + return maps.Clone(v.conn) +} + +// Get retrieves a standard variable. +func (v *Variables) Get(name string) (string, bool) { + value, ok := v.vars[name] + return value, ok +} + +// Set sets a standard variable. +func (v *Variables) Set(name, value string) error { + if err := ValidIdentifier(name); err != nil { + return err + } + switch name { + case "ON_ERROR_STOP", "QUIET": + if value == "" { + value = "on" + } else { + var err error + if value, err = ParseBool(value, name); err != nil { + return err + } + } + } + v.vars[name] = value + return nil +} + +// Unset unsets a standard variable. +func (v *Variables) Unset(name string) error { + if err := ValidIdentifier(name); err != nil { + return err + } + delete(v.vars, name) + return nil +} + +// Dump dumps the standard variables to w. +func (v *Variables) Dump(w io.Writer) error { + for _, k := range slices.Sorted(maps.Keys(v.vars)) { + _, _ = fmt.Fprintln(w, k, "=", Quote(v.vars[k])) + } + return nil +} + +// GetPrint returns a print variable. +func (v *Variables) GetPrint(name string) (string, error) { + if val, ok := v.prnt[name]; ok { + return val, nil + } + return "", fmt.Errorf(text.UnknownFormatFieldName, name) +} + +// SetPrint sets a print variable. +func (v *Variables) SetPrint(name, value string) (string, error) { + if _, ok := v.prnt[name]; !ok { + return "", fmt.Errorf(text.UnknownFormatFieldName, name) + } + switch name { + case "border", "columns", "pager_min_lines": + i, _ := strconv.Atoi(value) + v.prnt[name] = fmt.Sprintf("%d", i) + case "pager": + s, err := ParseKeywordBool(value, name, "always") + if err != nil { + return "", text.ErrInvalidFormatPagerType + } + v.prnt[name] = s + case "expanded": + s, err := ParseKeywordBool(value, name, "auto") + if err != nil { + return "", text.ErrInvalidFormatExpandedType + } + v.prnt[name] = s + case "fieldsep_zero", "footer", "numericlocale", "recordsep_zero", "tuples_only": + s, err := ParseBool(value, name) + if err != nil { + return "", err + } + v.prnt[name] = s + case "format": + if !formatRE.MatchString(value) { + return "", text.ErrInvalidFormatType + } + v.prnt[name] = value + case "linestyle": + if !linestlyeRE.MatchString(value) { + return "", text.ErrInvalidFormatLineStyle + } + v.prnt[name] = value + case "csv_fieldsep", "fieldsep", "null", "recordsep", "tableattr", "time", "title", "locale": + v.prnt[name] = value + case "timezone": + if _, err := time.LoadLocation(value); err != nil { + return "", text.ErrInvalidTimezoneLocation + } + v.prnt[name] = value + case "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle": + if !borderRE.MatchString(value) { + return "", text.ErrInvalidFormatBorderLineStyle + } + v.prnt[name] = value + default: + panic(fmt.Sprintf("field %s was defined in the print variables, but not in switch", name)) + } + return v.prnt[name], nil +} + +// TogglePrint toggles a print variable. +func (v *Variables) TogglePrint(name, extra string) (string, error) { + if _, ok := v.prnt[name]; !ok { + return "", fmt.Errorf(text.UnknownFormatFieldName, name) + } + switch name { + case "border", "columns", "pager_min_lines": + case "pager": + switch v.prnt[name] { + case "on", "always": + v.prnt[name] = "off" + case "off": + v.prnt[name] = "on" + default: + panic(fmt.Sprintf("invalid state for field %s", name)) + } + case "expanded": + switch v.prnt[name] { + case "on", "auto": + v.prnt[name] = "off" + case "off": + v.prnt[name] = "on" + default: + panic(fmt.Sprintf("invalid state for field %s", name)) + } + case "fieldsep_zero", "footer", "numericlocale", "recordsep_zero", "tuples_only": + switch v.prnt[name] { + case "on": + v.prnt[name] = "off" + case "off": + v.prnt[name] = "on" + default: + panic(fmt.Sprintf("invalid state for field %s", name)) + } + case "format": + switch { + case extra != "" && v.prnt[name] != extra: + v.prnt[name] = extra + case v.prnt[name] == "aligned": + v.prnt[name] = "unaligned" + default: + v.prnt[name] = "aligned" + } + case "linestyle": + case "csv_fieldsep", "fieldsep", "null", "recordsep", "time", "timezone", "locale": + case "tableattr", "title": + v.prnt[name] = "" + case "unicode_border_linestyle", "unicode_column_linestyle", "unicode_header_linestyle": + default: + panic(fmt.Sprintf("field %s was defined in the print variables, but not in switch", name)) + } + return v.prnt[name], nil +} + +// DumpPrint dumps the print variables to w. +func (v *Variables) DumpPrint(w io.Writer) error { + width, keys := 0, maps.Keys(v.prnt) + for k := range keys { + width = max(len(k), width) + } + for _, k := range slices.Sorted(keys) { + val := v.prnt[k] + switch k { + case "csv_fieldsep", "fieldsep", "recordsep", "null": + val = strconv.QuoteToASCII(val) + case "tableattr", "title": + if val != "" { + val = strconv.QuoteToASCII(val) + } + } + fmt.Fprintf(w, "%-*s %s\n", width, k, val) + // k+strings.Repeat(" ", width-len(k)), val) + } + return nil +} + +// PrintTimeFormat returns the user's time format converted to Go's time.Format +// value. +func (v *Variables) PrintTimeFormat() string { + tfmt := v.prnt["time"] + if s, ok := timeConsts[tfmt]; ok { + return s + } + return tfmt +} + +// SetConn sets a named connection variable. +func (v *Variables) SetConn(name string, vals ...string) error { + if err := ValidIdentifier(name); err != nil { + return err + } + if _, ok := v.conn[name]; len(vals) == 0 || vals[0] == "" && ok { + delete(v.conn, name) + } else { + v.conn[name] = slices.Clone(vals) + } + return nil +} + +// GetConn returns a connection variable. +func (v *Variables) GetConn(name string) ([]string, bool) { + vals, ok := v.conn[name] + if !ok { + return nil, false + } + return slices.Clone(vals), true +} + +// DumpConn dumps the connection variables to w. +func (v *Variables) DumpConn(w io.Writer) error { + for _, k := range slices.Sorted(maps.Keys(v.conn)) { + fmt.Fprintln(w, k, "=", Quote(strings.Join(v.conn[k], " "))) + } + return nil +} + +// timeConsts are well known time consts. +var timeConsts = map[string]string{ + "ANSIC": time.ANSIC, + "UnixDate": time.UnixDate, + "RubyDate": time.RubyDate, + "RFC822": time.RFC822, + "RFC822Z": time.RFC822Z, + "RFC850": time.RFC850, + "RFC1123": time.RFC1123, + "RFC1123Z": time.RFC1123Z, + "RFC3339": time.RFC3339, + "RFC3339Nano": time.RFC3339Nano, + "Kitchen": time.Kitchen, + "Stamp": time.Stamp, + "StampMilli": time.StampMilli, + "StampMicro": time.StampMicro, + "StampNano": time.StampNano, +} + +/* +// Get retrieves a standard variable. +func (v *Vars) Get(s string) (string, bool, error) { +func (v *Vars) Unquote() + q, n := "", s + if c := s[0]; c == '\'' || c == '"' { + var err error + if n, err = Unquote(s); err != nil { + return "", false, err + } + q = string(c) + } + if val, ok := v.v[n]; ok { + return q + val + q, true, nil + } + return s, false, nil +*/ diff --git a/gen.go b/gen.go index 7cb0b7fcdd7..21c18da464f 100644 --- a/gen.go +++ b/gen.go @@ -3,10 +3,12 @@ package main import ( + "bufio" "bytes" "errors" "flag" "fmt" + "go/ast" "go/format" "go/parser" "go/token" @@ -18,54 +20,15 @@ import ( "sort" "strings" "time" + "unicode" "github.com/mattn/go-runewidth" "github.com/xo/dburl" "github.com/yookoala/realpath" ) -type DriverInfo struct { - // Tag is the build Tag / name of the directory the driver lives in. - Tag string - // Driver is the Go SQL Driver Driver (parsed from the import tagged with // - // DRIVER: ), otherwise same as the tag / directory Driver. - Driver string - // Pkg is the imported driver package, taken from the import tagged with - // DRIVER. - Pkg string - // Desc is the descriptive text of the driver, parsed from doc comment, ie, - // "Package defines and registers usql's ." - Desc string - // URL is the driver's reference URL, parsed from doc comment's "See: ". - URL string - // CGO is whether or not the driver requires CGO, based on presence of - // 'Requires CGO.' in the comment - CGO bool - // Aliases are the parsed Alias: entries. - Aliases [][]string - // Wire indicates it is a Wire compatible driver. - Wire bool - // Group is the build Group - Group string -} - -// baseDrivers are drivers included in a build with no build tags listed. -var baseDrivers = map[string]DriverInfo{} - -// mostDrivers are drivers included with the most tag. Populated below. -var mostDrivers = map[string]DriverInfo{} - -// allDrivers are drivers forced to 'all' build tag. -var allDrivers = map[string]DriverInfo{} - -// badDrivers are drivers forced to 'bad' build tag. -var badDrivers = map[string]DriverInfo{} - -// wireDrivers are the wire compatible drivers. -var wireDrivers = map[string]DriverInfo{} - func main() { - licenseStart := flag.Int("license-start", 2016, "license start year") + licenseStart := flag.Int("license-start", 2015, "license start year") licenseAuthor := flag.String("license-author", "Kenneth Shaw", "license author") dburlGen := flag.Bool("dburl-gen", false, "enable dburl generation") dburlDir := flag.String("dburl-dir", getDburlDir(), "dburl dir") @@ -85,6 +48,9 @@ func run(licenseStart int, licenseAuthor string, dburlGen bool, dburlDir string, if err := loadDrivers(filepath.Join(wd, "drivers")); err != nil { return err } + if err := loadCommands(filepath.Join(wd, "metacmd", "cmds.go")); err != nil { + return err + } if err := writeInternal(filepath.Join(wd, "internal"), baseDrivers, mostDrivers, allDrivers, badDrivers); err != nil { return err } @@ -94,6 +60,9 @@ func run(licenseStart int, licenseAuthor string, dburlGen bool, dburlDir string, if err := writeLicenseFiles(licenseStart, licenseAuthor); err != nil { return err } + if err := writeCommands(filepath.Join(wd, "metacmd", "descs.go")); err != nil { + return err + } if dburlGen { if err := writeReadme(dburlDir, false); err != nil { return err @@ -105,17 +74,6 @@ func run(licenseStart int, licenseAuthor string, dburlGen bool, dburlDir string, return nil } -func getDburlDir() string { - dir := filepath.Join(os.Getenv("GOPATH"), "src/github.com/xo/dburl") - var err error - if dir, err = realpath.Realpath(dir); err != nil { - panic(err) - } - return dir -} - -var dirRE = regexp.MustCompile(`^([^/]+)/([^\./]+)\.go$`) - // loadDrivers loads the driver descriptions. func loadDrivers(wd string) error { skipDirs := []string{"completer", "metadata"} @@ -159,83 +117,35 @@ func loadDrivers(wd string) error { } return nil }) - if err != nil { - return err - } - return nil + return err } -func parseDriverInfo(tag, filename string) (DriverInfo, error) { - f, err := parser.ParseFile(token.NewFileSet(), filename, nil, parser.ParseComments) +// loadCommands loads command descriptions. +func loadCommands(name string) error { + f, err := parser.ParseFile(token.NewFileSet(), name, nil, parser.ParseComments) if err != nil { - return DriverInfo{}, err + return err } - name := tag - var pkg string - for _, imp := range f.Imports { - if imp.Comment == nil || len(imp.Comment.List) == 0 || !strings.Contains(imp.Comment.List[0].Text, "DRIVER") { + cmds = make(map[string][]desc) + for _, d := range f.Decls { + section, descs, ok, err := decodeCommand(d) + switch { + case err != nil: + return err + case !ok: continue } - pkg = imp.Path.Value[1 : len(imp.Path.Value)-1] - if i := strings.Index(imp.Comment.List[0].Text, ":"); i != -1 { - name = strings.TrimSpace(imp.Comment.List[0].Text[i+1:]) - } - break + cmds[section] = append(cmds[section], descs...) } - // parse doc comment - comment := f.Doc.Text() - prefix := "Package " + tag + " defines and registers usql's " - if !strings.HasPrefix(comment, prefix) { - return DriverInfo{}, fmt.Errorf("invalid doc comment prefix for driver %q", tag) - } - desc := strings.TrimPrefix(comment, prefix) - i := strings.Index(desc, " driver.") - if i == -1 { - return DriverInfo{}, fmt.Errorf("cannot find description suffix for driver %q", tag) - } - desc = strings.TrimSpace(desc[:i]) - if desc == "" { - return DriverInfo{}, fmt.Errorf("unable to parse description for driver %q", tag) - } - // parse alias: - var aliases [][]string - aliasesm := aliasRE.FindAllStringSubmatch(comment, -1) - for _, m := range aliasesm { - s := strings.Split(m[1], ",") - aliases = append(aliases, []string{ - strings.TrimSpace(s[0]), - strings.TrimSpace(s[1]), - }) - } - // parse see: url - urlm := seeRE.FindAllStringSubmatch(comment, -1) - if urlm == nil { - return DriverInfo{}, fmt.Errorf("missing See: for driver %q", tag) - } - // parse group: - group := "most" - if groupm := groupRE.FindAllStringSubmatch(comment, -1); groupm != nil { - group = strings.TrimSpace(groupm[0][1]) + // check all sections have at least one command + for _, section := range sections { + if descs, ok := cmds[section]; !ok || len(descs) == 0 { + return fmt.Errorf("section %q has no meta commands", section) + } } - return DriverInfo{ - Tag: tag, - Driver: name, - Pkg: pkg, - Desc: cleanRE.ReplaceAllString(desc, ""), - URL: strings.TrimSpace(urlm[0][1]), - CGO: strings.Contains(cleanRE.ReplaceAllString(comment, ""), "Requires CGO."), - Aliases: aliases, - Group: group, - }, nil + return nil } -var ( - aliasRE = regexp.MustCompile(`(?m)^Alias:\s+(.*)$`) - seeRE = regexp.MustCompile(`(?m)^See:\s+(.*)$`) - groupRE = regexp.MustCompile(`(?m)^Group:\s+(.*)$`) - cleanRE = regexp.MustCompile(`[\r\n]`) -) - func writeInternal(wd string, drivers ...map[string]DriverInfo) error { // build known build tags var known []DriverInfo @@ -286,33 +196,6 @@ func writeInternal(wd string, drivers ...map[string]DriverInfo) error { return nil } -const internalTagGo = `//go:build %s -package internal - -// Code generated by gen.go. DO NOT EDIT. - -import ( - _ %q // %s driver -)` - -const internalGo = `// Package internal provides a way to obtain information about which database -// drivers were included at build. -package internal - -// Code generated by gen.go. DO NOT EDIT. - -// KnownBuildTags returns a map of known driver names to its respective build -// tags. -func KnownBuildTags() map[string]string{ - return map[string]string{%s - } -}` - -const ( - driverTableStart = "" - driverTableEnd = "" -) - func writeReadme(dir string, includeTagSummary bool) error { readme := filepath.Join(dir, "README.md") buf, err := os.ReadFile(readme) @@ -325,10 +208,10 @@ func writeReadme(dir string, includeTagSummary bool) error { return errors.New("unable to find driver table start/end in README.md") } b := new(bytes.Buffer) - if _, err := b.Write(append(buf[:start+len(driverTableStart)], '\n')); err != nil { + if _, err := b.Write(append(buf[:start+len(driverTableStart)], '\n', '\n')); err != nil { return err } - if _, err := b.Write([]byte(buildDriverTable(includeTagSummary))); err != nil { + if _, err := b.Write(append([]byte(buildDriverTable(includeTagSummary)), '\n')); err != nil { return err } if _, err := b.Write(buf[end:]); err != nil { @@ -337,6 +220,200 @@ func writeReadme(dir string, includeTagSummary bool) error { return os.WriteFile(readme, b.Bytes(), 0o644) } +func writeLicenseFiles(licenseStart int, licenseAuthor string) error { + s := fmt.Sprintf(license, licenseStart, time.Now().Year(), licenseAuthor) + if err := os.WriteFile("LICENSE", append([]byte(s), '\n'), 0o644); err != nil { + return err + } + textGo := fmt.Sprintf(licenseTextGo, s) + if err := os.WriteFile("text/license.go", []byte(textGo), 0o644); err != nil { + return err + } + return nil +} + +func writeDburlLicense(dir string, licenseStart int, licenseAuthor string) error { + s := fmt.Sprintf(license, licenseStart, time.Now().Year(), licenseAuthor) + if err := os.WriteFile(filepath.Join(dir, "LICENSE"), append([]byte(s), '\n'), 0o644); err != nil { + return err + } + return nil +} + +func writeCommands(name string) error { + // format and write internal.go + var names, descs string + for _, s := range sections { + names += fmt.Sprintf("%q,\n", s) + descs += fmt.Sprintf("// %s\n\t{\n", s) + for _, desc := range cmds[s] { + descs += fmt.Sprintf("\t%s,\n", desc) + } + descs += fmt.Sprint("},\n") + } + buf, err := format.Source([]byte(fmt.Sprintf(descsTpl, names, descs))) + if err != nil { + return err + } + return os.WriteFile(name, buf, 0644) +} + +var ( + // baseDrivers are drivers included in a build with no build tags listed. + baseDrivers = map[string]DriverInfo{} + // mostDrivers are drivers included with the most tag. Populated below. + mostDrivers = map[string]DriverInfo{} + // allDrivers are drivers forced to 'all' build tag. + allDrivers = map[string]DriverInfo{} + // badDrivers are drivers forced to 'bad' build tag. + badDrivers = map[string]DriverInfo{} + // wireDrivers are the wire compatible drivers. + wireDrivers = map[string]DriverInfo{} +) + +// cmds are the meta command descriptions. +var cmds map[string][]desc + +type DriverInfo struct { + // Tag is the build Tag / name of the directory the driver lives in. + Tag string + // Driver is the Go SQL Driver Driver (parsed from the import tagged with // + // DRIVER: ), otherwise same as the tag / directory Driver. + Driver string + // Pkg is the imported driver package, taken from the import tagged with + // DRIVER. + Pkg string + // Desc is the descriptive text of the driver, parsed from doc comment, ie, + // "Package defines and registers usql's ." + Desc string + // URL is the driver's reference URL, parsed from doc comment's "See: ". + URL string + // CGO is whether or not the driver requires CGO, based on presence of + // 'Requires CGO.' in the comment + CGO bool + // Aliases are the parsed Alias: entries. + Aliases [][]string + // Wire indicates it is a Wire compatible driver. + Wire bool + // Group is the build Group + Group string +} + +func parseDriverInfo(tag, filename string) (DriverInfo, error) { + f, err := parser.ParseFile(token.NewFileSet(), filename, nil, parser.ParseComments) + if err != nil { + return DriverInfo{}, err + } + name := tag + var pkg string + for _, imp := range f.Imports { + if imp.Comment == nil || len(imp.Comment.List) == 0 || !strings.Contains(imp.Comment.List[0].Text, "DRIVER") { + continue + } + pkg = imp.Path.Value[1 : len(imp.Path.Value)-1] + if i := strings.Index(imp.Comment.List[0].Text, ":"); i != -1 { + name = strings.TrimSpace(imp.Comment.List[0].Text[i+1:]) + } + break + } + // parse doc comment + comment := f.Doc.Text() + prefix := "Package " + tag + " defines and registers usql's " + if !strings.HasPrefix(comment, prefix) { + return DriverInfo{}, fmt.Errorf("invalid doc comment prefix for driver %q", tag) + } + desc := strings.TrimPrefix(comment, prefix) + i := strings.Index(desc, " driver.") + if i == -1 { + return DriverInfo{}, fmt.Errorf("cannot find description suffix for driver %q", tag) + } + desc = strings.TrimSpace(desc[:i]) + if desc == "" { + return DriverInfo{}, fmt.Errorf("unable to parse description for driver %q", tag) + } + // parse alias: + var aliases [][]string + aliasesm := aliasRE.FindAllStringSubmatch(comment, -1) + for _, m := range aliasesm { + s := strings.Split(m[1], ",") + aliases = append(aliases, []string{ + strings.TrimSpace(s[0]), + strings.TrimSpace(s[1]), + }) + } + // parse see: url + urlm := seeRE.FindAllStringSubmatch(comment, -1) + if urlm == nil { + return DriverInfo{}, fmt.Errorf("missing See: for driver %q", tag) + } + // parse group: + group := "most" + if groupm := groupRE.FindAllStringSubmatch(comment, -1); groupm != nil { + group = strings.TrimSpace(groupm[0][1]) + } + return DriverInfo{ + Tag: tag, + Driver: name, + Pkg: pkg, + Desc: cleanRE.ReplaceAllString(desc, ""), + URL: strings.TrimSpace(urlm[0][1]), + CGO: strings.Contains(cleanRE.ReplaceAllString(comment, ""), "Requires CGO."), + Aliases: aliases, + Group: group, + }, nil +} + +// desc is a meta command description. +type desc struct { + Name string + Params string + Desc string + Func string + Hidden bool + Alias string +} + +func newDesc(funcName, alias string, v []string) desc { + name, params, descstr := v[0], "", "" + switch len(v) { + case 1: + if i := strings.Index(name, ":"); i != -1 { + name, alias = name[:i], name[i+1:] + } + case 2: + descstr = v[1] + case 3: + params, descstr = v[1], v[2] + } + return desc{ + Name: name, + Params: params, + Desc: descstr, + Func: funcName, + Alias: alias, + } +} + +func (d desc) String() string { + if d.Desc != "" { + s := strings.ReplaceAll(fmt.Sprintf("%q", d.Desc), "{{CommandName}}", `" + text.CommandName + "`) + return fmt.Sprintf("{%q, %q, %s, %s, %t}", d.Name, d.Params, s, d.Func, false) + } + s := `alias for \` + d.Alias + return fmt.Sprintf("{%q, %q, %q, %s, %t}", d.Name, d.Params, s, d.Func, true) +} + +func findCommand(name string) string { + for _, s := range sections { + for _, d := range cmds[s] { + if d.Func == name { + return d.Name + } + } + } + panic(fmt.Sprintf("unable to find command for %s", name)) +} + func buildDriverTable(includeTagSummary bool) string { hdr := []string{"Database", "Scheme / Tag", "Scheme Aliases", "Driver Package / Notes"} widths := []int{len(hdr[0]), len(hdr[1]), len(hdr[2]), len(hdr[3])} @@ -369,16 +446,6 @@ func buildDriverTable(includeTagSummary bool) string { return s + "\n" + buildTableLinks(baseDrivers, mostDrivers, allDrivers, badDrivers) } -var baseOrder = map[string]int{ - "postgres": 0, - "mysql": 1, - "sqlserver": 2, - "oracle": 3, - "sqlite3": 4, - "clickhouse": 5, - "csvq": 6, -} - func buildRows(m map[string]DriverInfo, widths []int) ([][]string, []int) { var drivers []DriverInfo for _, v := range m { @@ -441,7 +508,14 @@ func buildAliases(v DriverInfo) string { func tableRows(widths []int, c rune, rows ...[]string) string { padding := string(c) if len(rows) == 0 { - rows = [][]string{make([]string, len(widths))} + v := make([]string, len(widths)) + if c == '-' { + for i, w := range widths { + v[i] = strings.Repeat(padding, w) + } + padding = " " + } + rows = [][]string{v} } var s string for _, row := range rows { @@ -470,26 +544,200 @@ func buildTableLinks(drivers ...map[string]DriverInfo) string { return s } -func writeLicenseFiles(licenseStart int, licenseAuthor string) error { - s := fmt.Sprintf(license, licenseStart, time.Now().Year(), licenseAuthor) - if err := os.WriteFile("LICENSE", append([]byte(s), '\n'), 0o644); err != nil { - return err +func getDburlDir() string { + dir := filepath.Join(os.Getenv("GOPATH"), "src/github.com/xo/dburl") + var err error + if dir, err = realpath.Realpath(dir); err != nil { + panic(err) } - textGo := fmt.Sprintf(licenseTextGo, s) - if err := os.WriteFile("text/license.go", []byte(textGo), 0o644); err != nil { - return err + return dir +} + +// decodeCommand decodes a command. +func decodeCommand(d ast.Decl) (string, []desc, bool, error) { + f, ok := d.(*ast.FuncDecl) + if !ok || !isCommand(f) { + return "", nil, false, nil + } + switch section, descs, err := decodeCommandDoc(f); { + case err != nil: + return "", nil, false, err + case !slices.Contains(sections, section): + return "", nil, false, fmt.Errorf("meta command %s has invalid section name %q", f.Name, section) + case len(descs) == 0: + return "", nil, false, fmt.Errorf("meta command %s has no valid command descriptions", f.Name) + default: + return section, descs, true, nil } - return nil } -func writeDburlLicense(dir string, licenseStart int, licenseAuthor string) error { - s := fmt.Sprintf(license, licenseStart, time.Now().Year(), licenseAuthor) - if err := os.WriteFile(filepath.Join(dir, "LICENSE"), append([]byte(s), '\n'), 0o644); err != nil { - return err +// isCommand returns true if a meta command matches the signature for a command +// func. +func isCommand(f *ast.FuncDecl) bool { + switch { + case !unicode.IsUpper(rune(f.Name.String()[0])), + f.Type.Params.NumFields() != 1, + f.Type.Results.NumFields() != 1, + fmt.Sprint(f.Type.Results.List[0].Type) != "error": + return false } - return nil + if e, ok := f.Type.Params.List[0].Type.(*ast.StarExpr); !ok || fmt.Sprint(e.X) != "Params" { + return false + } + return true +} + +// decodeCommandDoc decodes a meta command's doc comment. +func decodeCommandDoc(f *ast.FuncDecl) (string, []desc, error) { + name, doc := f.Name.String()+" is a ", f.Doc.Text() + if !strings.HasPrefix(doc, name) { + return "", nil, fmt.Errorf("meta command %s doc comment does not start with %q", f.Name, name) + } + i := strings.Index(doc, "meta command (") + if i == -1 { + return "", nil, fmt.Errorf("meta command %s doc comment missing %q", f.Name, "meta command (") + } + section := strings.TrimSpace(doc[len(name):i]) + if i = strings.Index(doc, "Descs:\n\n"); i == -1 { + return "", nil, fmt.Errorf("meta command %s doc comment missing %q", f.Name, "Descs:") + } + descs, err := decodeCommandDescs(f.Name.String(), doc[i+len("Descs:\n\n"):]) + if err != nil { + return "", nil, fmt.Errorf("meta command %s has invalid desc: %v", f.Name, err) + } + return section, descs, nil } +// decodeCommandDescs +func decodeCommandDescs(funcName string, doc string) ([]desc, error) { + s := bufio.NewScanner(strings.NewReader(doc)) + var descs []desc + var alias string + for i := 0; s.Scan(); i++ { + line := s.Text() + if !strings.HasPrefix(line, "\t") { + return nil, fmt.Errorf("line %d does not start with \\t", i+1) + } + v := strings.Split(line[1:], "\t") + switch { + case len(v) == 0: + return nil, fmt.Errorf("line %d is invalid", i+1) + case len(v[0]) == 0: + return nil, fmt.Errorf("line %d has invalid name", i+1) + } + descs = append(descs, newDesc(funcName, alias, v)) + if alias == "" { + alias = descs[0].Name + } + } + return descs, nil +} + +var baseOrder = map[string]int{ + "postgres": 0, + "mysql": 1, + "sqlserver": 2, + "oracle": 3, + "sqlite3": 4, + "clickhouse": 5, + "csvq": 6, +} + +// sections are the section names for meta commands. +var sections = []string{ + "General", + "Help", + "Query Execute", + //"Query View", + "Query Buffer", + "Informational", + "Variables", + "Connection", + "Conditional", + "Input/Output", + "Transaction", + "Operating System/Environment", + //"Formatting", +} + +// regexps. +var ( + aliasRE = regexp.MustCompile(`(?m)^Alias:\s+(.*)$`) + seeRE = regexp.MustCompile(`(?m)^See:\s+(.*)$`) + groupRE = regexp.MustCompile(`(?m)^Group:\s+(.*)$`) + cleanRE = regexp.MustCompile(`[\r\n]`) + dirRE = regexp.MustCompile(`^([^/]+)/([^\./]+)\.go$`) +) + +const ( + driverTableStart = "" + driverTableEnd = "" +) + +const descsTpl = `package metacmd + +// Code generated by gen.go. DO NOT EDIT. + +import ( + "github.com/xo/usql/text" +) + +// sections are the command description sections. +var sections = []string{ + %s +} + +// descs are the command descriptions. +var descs [][]desc + +// cmds are the command lookup map. +var cmds map[string]func(*Params) error + +func init() { + descs = [][]desc{ + %s + } + cmds = make(map[string]func(*Params) error) + for i := range sections { + for _, desc := range descs[i] { + for _, n := range desc.Names() { + cmds[n] = desc.Func + } + } + } +} +` + +const internalTagGo = `//go:build %s +package internal + +// Code generated by gen.go. DO NOT EDIT. + +import ( + _ %q // %s driver +)` + +const internalGo = `// Package internal provides a way to obtain information about which database +// drivers were included at build. +package internal + +// Code generated by gen.go. DO NOT EDIT. + +// KnownBuildTags returns a map of known driver names to its respective build +// tags. +func KnownBuildTags() map[string]string{ + return map[string]string{%s + } +}` + +const licenseTextGo = `package text + +// Code generated by gen.go. DO NOT EDIT. + +// License contains the license text for usql. +const License = ` + "`%s`" + ` +` + const license = `The MIT License (MIT) Copyright (c) %d-%d %s @@ -511,11 +759,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.` - -const licenseTextGo = `package text - -// Code generated by gen.go. DO NOT EDIT. - -// License contains the license text for usql. -const License = ` + "`%s`" + ` -` diff --git a/go.mod b/go.mod index 076a36db951..f06bcabcb99 100644 --- a/go.mod +++ b/go.mod @@ -2,16 +2,14 @@ module github.com/xo/usql go 1.23 -toolchain go1.23rc2 - require ( github.com/ClickHouse/clickhouse-go/v2 v2.30.0 github.com/IBM/nzgo/v12 v12.0.9 github.com/MichaelS11/go-cql-driver v0.1.1 - github.com/SAP/go-hdb v1.12.4 + github.com/SAP/go-hdb v1.12.5 github.com/VoltDB/voltdb-client-go v1.0.16 github.com/alecthomas/chroma/v2 v2.14.0 - github.com/alexbrainman/odbc v0.0.0-20240810052813-bcbcb6842ce9 + github.com/alexbrainman/odbc v0.0.0-20241104074637-25af894ea08b github.com/aliyun/aliyun-tablestore-go-sql-driver v0.0.0-20220418015234-4d337cb3eed9 github.com/amsokol/ignite-go-client v0.12.2 github.com/apache/arrow/go/v17 v17.0.0 @@ -39,7 +37,7 @@ require ( github.com/kenshaw/colors v0.1.6 github.com/kenshaw/rasterm v0.1.11 github.com/lib/pq v1.10.9 - github.com/marcboeker/go-duckdb v1.8.2 + github.com/marcboeker/go-duckdb v1.8.3 github.com/mattn/go-adodb v0.0.1 github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-sqlite3 v1.14.24 @@ -51,23 +49,22 @@ require ( github.com/prestodb/presto-go-client v0.0.0-20240426182841-905ac40a1783 github.com/proullon/ramsql v0.1.4 github.com/sijms/go-ora/v2 v2.8.22 - github.com/snowflakedb/gosnowflake v1.11.2 + github.com/snowflakedb/gosnowflake v1.12.0 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/thda/tds v0.1.7 - github.com/trinodb/trino-go-client v0.319.0 + github.com/trinodb/trino-go-client v0.320.0 github.com/uber/athenadriver v1.1.15 github.com/vertica/vertica-sql-go v1.3.3 github.com/xo/dburl v0.23.2 github.com/xo/echartsgoja v0.1.1 github.com/xo/resvg v0.6.0 - github.com/xo/tblfmt v0.13.2 + github.com/xo/tblfmt v0.14.0 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e - github.com/ydb-platform/ydb-go-sdk/v3 v3.88.0 + github.com/ydb-platform/ydb-go-sdk/v3 v3.90.0 github.com/yookoala/realpath v1.0.0 github.com/ziutek/mymysql v1.5.4 - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c gorm.io/driver/bigquery v1.2.0 modernc.org/ql v1.4.7 modernc.org/sqlite v1.33.1 @@ -78,14 +75,14 @@ require ( require ( cel.dev/expr v0.18.0 // indirect cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.9.9 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect - cloud.google.com/go/bigquery v1.63.1 // indirect + cloud.google.com/go/auth v0.10.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect + cloud.google.com/go/bigquery v1.64.0 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect cloud.google.com/go/iam v1.2.2 // indirect cloud.google.com/go/longrunning v0.6.2 // indirect cloud.google.com/go/monitoring v1.21.2 // indirect - cloud.google.com/go/spanner v1.70.0 // indirect + cloud.google.com/go/spanner v1.71.0 // indirect dario.cat/mergo v1.0.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect @@ -95,40 +92,41 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.3.0 // indirect github.com/BurntSushi/toml v1.4.0 // indirect github.com/ClickHouse/ch-go v0.63.1 // indirect github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect github.com/DataDog/zstd v1.5.6 // indirect - github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.1 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.3 // indirect + github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/aliyun/aliyun-tablestore-go-sdk v1.7.17 // indirect github.com/andybalholm/brotli v1.1.1 // indirect + github.com/apache/arrow-go/v18 v18.0.0 // indirect github.com/apache/arrow/go/v12 v12.0.1 // indirect github.com/apache/arrow/go/v15 v15.0.2 // indirect github.com/apache/thrift v0.21.0 // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/aws/aws-sdk-go v1.55.5 // indirect - github.com/aws/aws-sdk-go-v2 v1.32.2 // indirect + github.com/aws/aws-sdk-go-v2 v1.32.3 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.41 // indirect - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.12 // indirect - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.33 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.2 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.42 // indirect + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.13 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.35 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.3 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 // indirect - github.com/aws/aws-sdk-go-v2/service/s3 v1.66.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2 // indirect github.com/aws/smithy-go v1.22.0 // indirect github.com/beltran/gohive v1.7.0 // indirect github.com/beltran/gosasl v0.0.0-20241020113154-d984219623d1 // indirect @@ -167,19 +165,19 @@ require ( github.com/docker/cli v26.1.4+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/dop251/goja v0.0.0-20241009100908-5f46f2705ca3 // indirect + github.com/dop251/goja v0.0.0-20241024094426-79f3a7efcdbd // indirect github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.7.0 // indirect github.com/edsrzf/mmap-go v1.2.0 // indirect - github.com/elastic/go-sysinfo v1.14.2 // indirect + github.com/elastic/go-sysinfo v1.15.0 // indirect github.com/elastic/go-windows v1.0.2 // indirect github.com/envoyproxy/go-control-plane v0.13.1 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/exasol/error-reporting-go v0.2.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/gabriel-vasile/mimetype v1.4.6 // indirect github.com/getsentry/sentry-go v0.29.1 // indirect github.com/go-faster/city v1.0.1 // indirect @@ -195,7 +193,7 @@ require ( github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godror/knownpb v0.2.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang-module/carbon/v2 v2.4.1 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect @@ -206,7 +204,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/flatbuffers/go v0.0.0-20230110200425-62e4d2e5b215 // indirect github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 // indirect - github.com/google/pprof v0.0.0-20241023014458-598669927662 // indirect + github.com/google/pprof v0.0.0-20241101162523-b92577c0c142 // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect @@ -222,7 +220,7 @@ require ( github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/icholy/digest v0.1.23 // indirect + github.com/icholy/digest v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -277,7 +275,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.60.0 // indirect + github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect @@ -319,6 +317,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.28.0 // indirect + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect @@ -329,10 +328,10 @@ require ( golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect - google.golang.org/api v0.203.0 // indirect - google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect + google.golang.org/api v0.204.0 // indirect + google.golang.org/genproto v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 3e0dba008ce..7f1e4d9685b 100644 --- a/go.sum +++ b/go.sum @@ -101,10 +101,10 @@ cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVo cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= -cloud.google.com/go/auth v0.9.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ= -cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= -cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= -cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/auth v0.10.0 h1:tWlkvFAh+wwTOzXIjrwM64karR1iTBZ/GRr0S/DULYo= +cloud.google.com/go/auth v0.10.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= +cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= @@ -133,8 +133,8 @@ cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/Zur cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= -cloud.google.com/go/bigquery v1.63.1 h1:/6syiWrSpardKNxdvldS5CUTRJX1iIkSPXCjLjiGL+g= -cloud.google.com/go/bigquery v1.63.1/go.mod h1:ufaITfroCk17WTqBhMpi8CRjsfHjMX07pDrQaRKKX2o= +cloud.google.com/go/bigquery v1.64.0 h1:vSSZisNyhr2ioJE1OuYBQrnrpB7pIhRQm4jfjc7E/js= +cloud.google.com/go/bigquery v1.64.0/go.mod h1:gy8Ooz6HF7QmA+TRtX8tZmXBKH5mCFBwUApGAb3zI7Y= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= @@ -208,8 +208,8 @@ cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOX cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= -cloud.google.com/go/datacatalog v1.22.1 h1:i0DyKb/o7j+0vgaFtimcRFjYsD6wFw1jpnODYUyiYRs= -cloud.google.com/go/datacatalog v1.22.1/go.mod h1:MscnJl9B2lpYlFoxRjicw19kFTwEke8ReKL5Y/6TWg8= +cloud.google.com/go/datacatalog v1.22.2 h1:9Bi8YO+WBE0YSSQL1tX62Gy/KcdNGLufyVlEJ0eYMrc= +cloud.google.com/go/datacatalog v1.22.2/go.mod h1:9Wamq8TDfL2680Sav7q3zEhBJSPBrDxJU8WtPJ25dBM= cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= @@ -530,8 +530,8 @@ cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+ cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= -cloud.google.com/go/spanner v1.70.0 h1:nj6p/GJTgMDiSQ1gQ034ItsKuJgHiMOjtOlONOg8PSo= -cloud.google.com/go/spanner v1.70.0/go.mod h1:X5T0XftydYp0K1adeJQDJtdWpbrOeJ7wHecM4tK6FiE= +cloud.google.com/go/spanner v1.71.0 h1:Gl4eX16JEvqlm3yvZ2lKENaQ6FvCkM3YZfO/Hq8OqZU= +cloud.google.com/go/spanner v1.71.0/go.mod h1:mw98ua5ggQXVWwp83yjwggqEmW9t8rjs9Po1ohcUGW4= cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= @@ -649,8 +649,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.0 h1:YjxrAyf/5z9yK0ecQsKjgSdaC4FjXUbwlgxLz05E3YY= +github.com/AzureAD/microsoft-authentication-library-for-go v1.3.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= @@ -664,10 +664,10 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7Oputl github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY= github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.1 h1:QS8cUM05V5E7rpcNy3iBrrrYwqMLjg3NcDaXwpqr8x8= -github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.1/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.3 h1:cb3br57K508pQEFgBxn9GDhPS9HefpyMPK1RzmtMNzk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.3/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 h1:DBjmt6/otSdULyJdVg2BlG0qGZO5tKL4VzOs0jpvw5Q= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/IBM/nzgo/v12 v12.0.9 h1:SwzYFU5ooXsTZsQhU6OsbUhs/fQyLvCtlJYSEZ58mN0= github.com/IBM/nzgo/v12 v12.0.9/go.mod h1:4pvfEkfsrAdqlljsp8HNwv/uzNKy2fzoXBB1aRIssJg= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c h1:RGWPOewvKIROun94nF7v2cua9qP+thov/7M50KEoeSU= @@ -684,8 +684,8 @@ github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/SAP/go-hdb v1.12.4 h1:Oje5j/Ua6Yh5qwSy5xhXpJoooEi6vaj/ubbk3q5DWJQ= -github.com/SAP/go-hdb v1.12.4/go.mod h1:baXEfHaYh5yPBSS+N6MsENYcton1coQMx3HlNHOx8vU= +github.com/SAP/go-hdb v1.12.5 h1:RFvIUnbflFwD5p0kSMZF+UJ4POIInNiSNXLDbOAGvqQ= +github.com/SAP/go-hdb v1.12.5/go.mod h1:baXEfHaYh5yPBSS+N6MsENYcton1coQMx3HlNHOx8vU= github.com/UNO-SOFT/zlog v0.8.1 h1:TEFkGJHtUfTRgMkLZiAjLSHALjwSBdw6/zByMC5GJt4= github.com/UNO-SOFT/zlog v0.8.1/go.mod h1:yqFOjn3OhvJ4j7ArJqQNA+9V+u6t9zSAyIZdWdMweWc= github.com/VoltDB/voltdb-client-go v1.0.16 h1:lnemSbNt+ceZ8/S/NAuQHDRmK1aSSR/s8UK9apszfmA= @@ -702,8 +702,8 @@ github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46 github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/alexbrainman/odbc v0.0.0-20240810052813-bcbcb6842ce9 h1:f0LbXXOcD5fIN3hoBw3cbxy1C8rkrRi1Ul3H4KNjhTg= -github.com/alexbrainman/odbc v0.0.0-20240810052813-bcbcb6842ce9/go.mod h1:c5eyz5amZqTKvY3ipqerFO/74a/8CYmXOahSr40c+Ww= +github.com/alexbrainman/odbc v0.0.0-20241104074637-25af894ea08b h1:0nzpVhkR1u+6gm/6EM+o48MDjmV9O4ot4UeunKgP31w= +github.com/alexbrainman/odbc v0.0.0-20241104074637-25af894ea08b/go.mod h1:c5eyz5amZqTKvY3ipqerFO/74a/8CYmXOahSr40c+Ww= github.com/aliyun/aliyun-tablestore-go-sdk v1.7.3/go.mod h1:PWqq46gZJf7mnYTAuTmxKgx6EwJu3oBpOs1s2V0EZPM= github.com/aliyun/aliyun-tablestore-go-sdk v1.7.17 h1:88DbDTaKw+M8NI1ok57p7peVS7pwkDqeJWX1x4IjqYc= github.com/aliyun/aliyun-tablestore-go-sdk v1.7.17/go.mod h1:JzOJMpBPGN+4cuYnrGO5wdwphEyqbeGVY2vCaiAcNW8= @@ -715,6 +715,8 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow-go/v18 v18.0.0 h1:1dBDaSbH3LtulTyOVYaBCHO3yVRwjV+TZaqn3g6V7ZM= +github.com/apache/arrow-go/v18 v18.0.0/go.mod h1:t6+cWRSmKgdQ6HsxisQjok+jBpKGhRDiqcf3p0p/F+A= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/arrow/go/v12 v12.0.1 h1:JsR2+hzYYjgSUkBSaahpqCetqZMr76djX80fF/DiJbg= @@ -733,50 +735,50 @@ github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevB github.com/aws/aws-sdk-go v1.37.32/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= -github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= +github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk= +github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA= -github.com/aws/aws-sdk-go-v2/config v1.28.0 h1:FosVYWcqEtWNxHn8gB/Vs6jOlNwSoyOCA/g/sxyySOQ= -github.com/aws/aws-sdk-go-v2/config v1.28.0/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc= -github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8= -github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.12 h1:zYf8E8zaqolHA5nQ+VmX2r3wc4K6xw5i6xKvvMjZBL0= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.12/go.mod h1:vYGIVLASk19Gb0FGwAcwES+qQF/aekD7m2G/X6mBOdQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.33 h1:X+4YY5kZRI/cOoSMVMGTqFXHAMg1bvvay7IBcqHpybQ= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.33/go.mod h1:DPynzu+cn92k5UQ6tZhX+wfTB4ah6QDU/NgdHqatmvk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60= +github.com/aws/aws-sdk-go-v2/config v1.28.1 h1:oxIvOUXy8x0U3fR//0eq+RdCKimWI900+SV+10xsCBw= +github.com/aws/aws-sdk-go-v2/config v1.28.1/go.mod h1:bRQcttQJiARbd5JZxw6wG0yIK3eLeSCPdg6uqmmlIiI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.42 h1:sBP0RPjBU4neGpIYyx8mkU2QqLPl5u9cmdTWVzIpHkM= +github.com/aws/aws-sdk-go-v2/credentials v1.17.42/go.mod h1:FwZBfU530dJ26rv9saAbxa9Ej3eF/AK0OAY86k13n4M= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.13 h1:EiyBn76ZpKQJWRNhgxvgloj6Xmazck05+RS6j0gfy1Y= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.13/go.mod h1:gKf4BQBfUke2acRFz76+Tyqz4A9Me0aMEnDUZwEZ+R0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18 h1:68jFVtt3NulEzojFesM/WVarlFpCaXLKaBxDpzkQ9OQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18/go.mod h1:Fjnn5jQVIo6VyedMc0/EhPpfNlPl7dHV916O6B+49aE= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.35 h1:ihPPdcCVSN0IvBByXwqVp28/l4VosBZ6sDulcvU2J7w= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.35/go.mod h1:JkgEhs3SVF51Dj3m1Bj+yL8IznpxzkwlA3jLg3x7Kls= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 h1:Jw50LwEkVjuVzE1NzkhNKkBf9cRN7MtE1F/b2cOKTUM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22/go.mod h1:Y/SmAyPcOTmpeVaWSzSKiILfXTVJwrGmYZhcRbhWuEY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 h1:981MHwBaRZM7+9QSR6XamDzF/o7ouUGxFzr+nVSIhrs= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22/go.mod h1:1RA1+aBEfn+CAB/Mh0MB6LsdCYCnjZm7tKXtnk499ZQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 h1:7edmS3VOBDhK00b/MwGtGglCm7hhwNYnjJs/PgFdMQE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21/go.mod h1:Q9o5h4HoIWG8XfzxqiuK/CGUbepCJ8uTlaE3bAbxytQ= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 h1:kJqyYcGqhWFmXqjRrtFFD4Oc9FXiskhsll2xnlpe8Do= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2/go.mod h1:+t2Zc5VNOzhaWzpGE+cEYZADsgAAQT5v55AO+fhU+2s= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.2 h1:E7Tuo0ipWpBl0f3uThz8cZsuyD5H8jLCnbtbKR4YL2s= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.2/go.mod h1:txOfweuNPBLhHodsV+C2lvPPRTommVTWbts9SZV6Myc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22 h1:yV+hCAHZZYJQcwAaszoBNwLbPItHvApxT0kVIw6jRgs= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22/go.mod h1:kbR1TL8llqB1eGnVbybcA4/wgScxdylOdyAd51yxPdw= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.3 h1:pS5ka5Z026eG29K3cce+yxG39i5COQARcgheeK9NKQE= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.3/go.mod h1:MBT8rSGSZjJiV6X7rlrVGoIt+mCoaw0VbpdVtsrsJfk= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.3 h1:BjzvhVB6Nnx+Xqlnc5JWkQYuWClxUFcvLzZIqFO31lI= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.3/go.mod h1:/6lakUr7RXajwpensF1miKadiR+xTlHV7mma5axITxY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 h1:4FMHqLfk0efmTqhXVRL5xYRqlEBNBiRI7N6w4jsEdd4= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2/go.mod h1:LWoqeWlK9OZeJxsROW2RqrSPvQHKTpp69r/iDjwsSaw= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 h1:1G7TTQNPNv5fhCyIQGYk8FOggLgkzKq6c4Y1nOGzAOE= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2/go.mod h1:+ybYGLXoF7bcD7wIcMcklxyABZQmuBf1cHUhvY6FGIo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 h1:t7iUP9+4wdc5lt3E41huP+GvQZJD38WLsgVp4iOtAjg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2/go.mod h1:/niFCtmuQNxqx9v8WAPq5qh7EH25U4BF6tjoyq9bObM= -github.com/aws/aws-sdk-go-v2/service/s3 v1.66.0 h1:xA6XhTF7PE89BCNHJbQi8VvPzcgMtmGC5dr8S8N7lHk= -github.com/aws/aws-sdk-go-v2/service/s3 v1.66.0/go.mod h1:cB6oAuus7YXRZhWCc1wIwPywwZ1XwweNp2TVAEGYeB8= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3 h1:kT6BcZsmMtNkP/iYMcRG+mIEA/IbeiUimXtGmqF39y0= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3/go.mod h1:Z8uGua2k4PPaGOYn66pK02rhMrot3Xk3tpBuUFPomZU= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.3 h1:wudRPcZMKytcywXERkR6PLqD8gPx754ZyIOo0iVg488= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.3/go.mod h1:yRo5Kj5+m/ScVIZpQOquQvDtSrDM1JLRCnvglBcdNmw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 h1:qcxX0JYlgWH3hpPUnd6U0ikcl6LLA9sLkXE2w1fpMvY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3/go.mod h1:cLSNEmI45soc+Ef8K/L+8sEA3A3pYFEYf5B5UI+6bH4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3 h1:ZC7Y/XgKUxwqcdhO5LE8P6oGP1eh6xlQReWNKfhvJno= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3/go.mod h1:WqfO7M9l9yUAw0HcHaikwRd/H6gzYdz7vjejCA5e2oY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2 h1:p9TNFL8bFUMd+38YIpTAXpoxyz0MxC7FlbFEH4P4E1U= +github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2/go.mod h1:fNjyo0Coen9QTwQLWeV6WO2Nytwiu+cCcWaTdKCAqqE= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.3 h1:UTpsIf0loCIWEbrqdLb+0RxnTXfWh2vhw4nQmFi4nPc= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.3/go.mod h1:FZ9j3PFHHAR+w0BSEjK955w5YD2UwB/l/H0yAK3MJvI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3 h1:2YCmIXv3tmiItw0LlYf6v7gEHebLY45kBEnPezbUKyU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3/go.mod h1:u19stRyNPxGhj6dRm+Cdgu6N75qnbW7+QN0q0dsAk58= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.3 h1:wVnQ6tigGsRqSWDEEyH6lSAJ9OyFUsSnbaUWChuSGzs= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.3/go.mod h1:VZa9yTFyj4o10YGsmDO4gbQJUvvhY72fhumT8W4LqsE= github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/beltran/gohive v1.7.0 h1:Jvz6yrWuAAUWZ1Y84+24NjMcWYkUZZBUE7/sTWtLKY0= @@ -918,8 +920,8 @@ github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6 github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dop251/goja v0.0.0-20241009100908-5f46f2705ca3 h1:MXsAuToxwsTn5BEEYm2DheqIiC4jWGmkEJ1uy+KFhvQ= -github.com/dop251/goja v0.0.0-20241009100908-5f46f2705ca3/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4= +github.com/dop251/goja v0.0.0-20241024094426-79f3a7efcdbd h1:QMSNEh9uQkDjyPwu/J541GgSH+4hw+0skJDIj9HJ3mE= +github.com/dop251/goja v0.0.0-20241024094426-79f3a7efcdbd/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4= github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc h1:MKYt39yZJi0Z9xEeRmDX2L4ocE0ETKcHKw6MVL3R+co= github.com/dop251/goja_nodejs v0.0.0-20240728170619-29b559befffc/go.mod h1:VULptt4Q/fNzQUJlqY/GP3qHyU7ZH46mFkBZe0ZTokU= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -931,8 +933,8 @@ github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8E github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84= github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= github.com/elastic/go-sysinfo v1.8.1/go.mod h1:JfllUnzoQV/JRYymbH3dO1yggI3mV2oTKSXsDHM+uIM= -github.com/elastic/go-sysinfo v1.14.2 h1:DeIy+pVfdRsd08Nx2Xjh+dUS+jrEEI7LGc29U/BKVWo= -github.com/elastic/go-sysinfo v1.14.2/go.mod h1:jPSuTgXG+dhhh0GKIyI2Cso+w5lPJ5PvVqKlL8LV/Hk= +github.com/elastic/go-sysinfo v1.15.0 h1:54pRFlAYUlVNQ2HbXzLVZlV+fxS7Eax49stzg95M4Xw= +github.com/elastic/go-sysinfo v1.15.0/go.mod h1:jPSuTgXG+dhhh0GKIyI2Cso+w5lPJ5PvVqKlL8LV/Hk= github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI= github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8= @@ -971,8 +973,9 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= github.com/getsentry/sentry-go v0.29.1 h1:DyZuChN8Hz3ARxGVV8ePaNXh1dQ7d76AiB117xcREwA= @@ -1035,8 +1038,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gohxs/readline v0.0.0-20171011095936-a780388e6e7c h1:yE35fKFwcelIte3q5q1/cPiY7pI7vvf5/j/0ddxNCKs= github.com/gohxs/readline v0.0.0-20171011095936-a780388e6e7c/go.mod h1:9S/fKAutQ6wVHqm1jnp9D9sc5hu689s9AaTWFS92LaU= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-module/carbon/v2 v2.4.1 h1:cYUD8T+rHeX+qIybGYpnJ8I90F10dvyEF67VNOO+zZM= @@ -1143,8 +1146,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20241023014458-598669927662 h1:SKMkD83p7FwUqKmBsPdLHF5dNyxq3jOWwu9w9UyH5vA= -github.com/google/pprof v0.0.0-20241023014458-598669927662/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241101162523-b92577c0c142 h1:sAGdeJj0bnMgUNVeUpp6AYlVdCt3/GdI3pGRqsNSQLs= +github.com/google/pprof v0.0.0-20241101162523-b92577c0c142/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= @@ -1218,8 +1221,8 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/icholy/digest v0.1.23 h1:4hX2pIloP0aDx7RJW0JewhPPy3R8kU+vWKdxPsCCGtY= -github.com/icholy/digest v0.1.23/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y= +github.com/icholy/digest v1.0.0 h1:bAIdA3hQvaYwEASgKNucB+sGShK1FOlMhwlC1GP41oE= +github.com/icholy/digest v1.0.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -1310,8 +1313,8 @@ github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuz github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/marcboeker/go-duckdb v1.8.2 h1:gHcFjt+HcPSpDVjPSzwof+He12RS+KZPwxcfoVP8Yx4= -github.com/marcboeker/go-duckdb v1.8.2/go.mod h1:2oV8BZv88S16TKGKM+Lwd0g7DX84x0jMxjTInThC8Is= +github.com/marcboeker/go-duckdb v1.8.3 h1:ZkYwiIZhbYsT6MmJsZ3UPTHrTZccDdM4ztoqSlEMXiQ= +github.com/marcboeker/go-duckdb v1.8.3/go.mod h1:C9bYRE1dPYb1hhfu/SSomm78B0FXmNgRvv6YBW/Hooc= github.com/mattn/go-adodb v0.0.1 h1:g/pk3V8m/WFX2IQRI58wAC24OQUFFXEiNsvs7dQ1WKg= github.com/mattn/go-adodb v0.0.1/go.mod h1:jaSTRde4bohMuQgYQPxW3xRTPtX/cZKyxPrFVseJULo= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -1425,8 +1428,8 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= -github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= @@ -1472,8 +1475,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/snowflakedb/gosnowflake v1.11.2 h1:eAMsxrCiC6ij5wX3dHx1TQCBOdDmCK062Ir8rndUkRg= -github.com/snowflakedb/gosnowflake v1.11.2/go.mod h1:WFe+8mpsapDaQjHX6BqJBKtfQCGlGD3lHKeDsKfpx2A= +github.com/snowflakedb/gosnowflake v1.12.0 h1:Saez8egtn5xAoVMBxFaMu9MYfAG9SS9dpAEXD1/ECIo= +github.com/snowflakedb/gosnowflake v1.12.0/go.mod h1:wHfYmZi3zvtWItojesAhWWXBN7+niex2R1h/S7QCZYg= github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y= github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -1519,8 +1522,8 @@ github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmN github.com/thda/tds v0.1.7 h1:s29kbnJK0agL3ps85A/sb9XS2uxgKF5UJ6AZjbyqXX4= github.com/thda/tds v0.1.7/go.mod h1:isLIF1oZdXfkqVMJM8RyNrsjlHPlTKnPlnsBs7ngZcM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/trinodb/trino-go-client v0.319.0 h1:tu4pW1yrK40pTbQyERsm4K06Tx8LBsyGuGobX9biG3o= -github.com/trinodb/trino-go-client v0.319.0/go.mod h1:F+7TZRD0+0M8XqYsgXT8+EJT1pSlbxTECVD1BDzCc70= +github.com/trinodb/trino-go-client v0.320.0 h1:z0LJU21PN68YGZzqFczroKv0mARRpdRpvHqu34+Pdh4= +github.com/trinodb/trino-go-client v0.320.0/go.mod h1:F+7TZRD0+0M8XqYsgXT8+EJT1pSlbxTECVD1BDzCc70= github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/uber-go/tally v3.3.17+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU= @@ -1547,8 +1550,8 @@ github.com/xo/echartsgoja v0.1.1/go.mod h1:u2iiKyIA1H3URjh9+8Nltj8cP33R19JHasXhS github.com/xo/resvg v0.6.0 h1:GsovErv9JuOnGttOA8RhQcBI7DEEVpEiIEKBuJVRS4g= github.com/xo/resvg v0.6.0/go.mod h1:xsIgOmL6UD2xRHIm2Laepjm/b4auoPMxAAqOHkvbSes= github.com/xo/tblfmt v0.0.0-20190609041254-28c54ec42ce8/go.mod h1:3U5kKQdIhwACye7ml3acccHmjGExY9WmUGU7rnDWgv0= -github.com/xo/tblfmt v0.13.2 h1:GzHsRYduJxKqwNekczv2ZJwpznsSU89Dr2L+qVyW63Y= -github.com/xo/tblfmt v0.13.2/go.mod h1:BLPC+dRy68cgSK/mPgQRfFQ/xLg231Fyic178ybjB34= +github.com/xo/tblfmt v0.14.0 h1:CyQAPLD210/FAWRFZtJxgo7ta/KTvCSAGIW8PoUjZEA= +github.com/xo/tblfmt v0.14.0/go.mod h1:AST4+nRe6gFIgZ0DXoqttFdDE6hUhz55iVyN7UJ4TOU= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ= @@ -1557,8 +1560,8 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/ydb-platform/ydb-go-genproto v0.0.0-20241022174402-dd276c7f197b h1:8yiv/W+1xTdifJh1Stkck0gFJjys9kg0/r86Buljuss= github.com/ydb-platform/ydb-go-genproto v0.0.0-20241022174402-dd276c7f197b/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= -github.com/ydb-platform/ydb-go-sdk/v3 v3.88.0 h1:ngS/hHEq4Fod2lCO55lkOSzJOCmBcAT6GL549BemhqE= -github.com/ydb-platform/ydb-go-sdk/v3 v3.88.0/go.mod h1:ah+n4KaEcANFM+CBKnJ5VrmR0IvWfYFvkofW/56076c= +github.com/ydb-platform/ydb-go-sdk/v3 v3.90.0 h1:7oQryahxtCLBZ4QqQBN2xrLGGT++X0m10VGUnYljDkE= +github.com/ydb-platform/ydb-go-sdk/v3 v3.90.0/go.mod h1:ah+n4KaEcANFM+CBKnJ5VrmR0IvWfYFvkofW/56076c= github.com/yookoala/realpath v1.0.0 h1:7OA9pj4FZd+oZDsyvXWQvjn5oBdcHRTV44PpdMSuImQ= github.com/yookoala/realpath v1.0.0/go.mod h1:gJJMA9wuX7AcqLy1+ffPatSCySA1FQ2S8Ya9AIoYBpE= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= @@ -2071,8 +2074,8 @@ gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJ gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ= -gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= @@ -2134,8 +2137,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU= -google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI= +google.golang.org/api v0.204.0 h1:3PjmQQEDkR/ENVZZwIYB4W/KzYtN8OrqnNcHWpeR8E4= +google.golang.org/api v0.204.0/go.mod h1:69y8QSoKIbL9F94bWgWAq6wGqGwyjBgi2y8rAK8zLag= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2275,12 +2278,12 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= -google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= -google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw= -google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto v0.0.0-20241104194629-dd2ea8efbc28 h1:KJjNNclfpIkVqrZlTWcgOOaVQ00LdBnoEaRfkUx760s= +google.golang.org/genproto v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:mt9/MofW7AWQ+Gy179ChOnvmJatV8YHUmrcedo9CIFI= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/handler/handler.go b/handler/handler.go index d62725c6889..20a6ff92912 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "log" + "maps" "net/url" "os" "os/exec" @@ -18,7 +19,7 @@ import ( "path" "path/filepath" "regexp" - "sort" + "slices" "strconv" "strings" "syscall" @@ -52,36 +53,44 @@ import ( // for usql's interactive command-line and manages most of the core/high-level logic. // // Manages the active statement buffer, application IO, executing/querying SQL -// statements, and handles backslash (\) commands encountered in the input +// statements, and handles backslash (\) meta commands encountered in the input // stream. type Handler struct { - l rline.IO + // l is the readline handler. + l rline.IO + // user is the user. user *user.User - // wd is the working directoyr. + // wd is the working directory. wd string // charts is the charts filesystem. charts billy.Filesystem // nopw indicates not asking for password. nopw bool - // timing of every command executed + // timing of every command executed. timing bool - // singleLineMode is single line mode + // singleLineMode is single line mode. singleLineMode bool - // query statement buffer + // buf is the query statement buffer. buf *stmt.Stmt - // lastExec statement - lastExec string - lastPrefix string - lastPrint string - lastRaw string - // batch - batch bool + // lastExec is the last executed query statement. + lastExec string + // lastExecPrefix is the last executed query statement prefix. + lastExecPrefix string + // lastPrint is the last executed printable query statement. + lastPrint string + // lastRaw is the last executed raw query statement. + lastRaw string + // batch indicates a batch has been started. + batch bool + // batchEnd is the batch end string. batchEnd string - // bind are bound values for exec statements + // bind are bound values for that will be used for statement execution. bind []interface{} - // connection - u *dburl.URL + // u is the active connection information. + u *dburl.URL + // db is the active database connection. db *sql.DB + // tx is the active transaction, if any. tx *sql.Tx // out file or pipe out io.WriteCloser @@ -117,11 +126,6 @@ func New(l rline.IO, user *user.User, wd string, charts billy.Filesystem, nopw b return h } -// SetSingleLineMode sets the single line mode toggle. -func (h *Handler) SetSingleLineMode(singleLineMode bool) { - h.singleLineMode = singleLineMode -} - // GetTiming gets the timing toggle. func (h *Handler) GetTiming() bool { return h.timing @@ -132,108 +136,11 @@ func (h *Handler) SetTiming(timing bool) { h.timing = timing } -// outputHighlighter returns s as a highlighted string, based on the current -// buffer and syntax highlighting settings. -func (h *Handler) outputHighlighter(s string) string { - // bail when string is empty (ie, contains no printable, non-space - // characters) or if syntax highlighting is not enabled - if empty(s) || env.All()["SYNTAX_HL"] != "true" { - return s - } - // count end lines - var endl string - if m := linetermRE.FindStringSubmatch(s); m != nil { - s = strings.TrimSuffix(s, m[0]) - endl += m[0] - } - // leading whitespace - var leading string - // capture current query statement buffer - orig := h.buf.RawString() - full := orig - if full != "" { - full += "\n" - } else { - // get leading whitespace - if i := strings.IndexFunc(s, func(r rune) bool { - return !stmt.IsSpaceOrControl(r) - }); i != -1 { - leading = s[:i] - } - } - full += s - // setup statement parser - st := drivers.NewStmt(h.u, func() func() ([]rune, error) { - y := strings.Split(orig, "\n") - if y[0] == "" { - y[0] = s - } else { - y = append(y, s) - } - return func() ([]rune, error) { - if len(y) > 0 { - z := y[0] - y = y[1:] - return []rune(z), nil - } - return nil, io.EOF - } - }()) - // accumulate all "active" statements in buffer, breaking either at - // EOF or when a \ cmd has been encountered - var err error - var cmd, final string -loop: - for { - cmd, _, err = st.Next(env.Unquote(h.user, false, env.All())) - switch { - case err != nil && err != io.EOF: - return s + endl - case err == io.EOF: - break loop - } - if st.Ready() || cmd != "" { - final += st.RawString() - st.Reset(nil) - // grab remaining whitespace to add to final - l := len(final) - // find first non empty character - if i := strings.IndexFunc(full[l:], func(r rune) bool { - return !stmt.IsSpaceOrControl(r) - }); i != -1 { - final += full[l : l+i] - } - } - } - if !st.Ready() && cmd == "" { - final += st.RawString() - } - final = leading + final - // determine whatever is remaining after "active" - var remaining string - if fnl := len(final); fnl < len(full) { - remaining = full[fnl:] - } - // this happens when a read line is empty and/or has only - // whitespace and a \ cmd - if s == remaining { - return s + endl - } - // highlight entire final accumulated buffer - b := new(bytes.Buffer) - if err := h.Highlight(b, final); err != nil { - return s + endl - } - colored := b.String() - // return only last line plus whatever remaining string (ie, after - // a \ cmd) and the end line count - ss := strings.Split(colored, "\n") - return lastcolor(colored) + ss[len(ss)-1] + remaining + endl +// SetSingleLineMode sets the single line mode toggle. +func (h *Handler) SetSingleLineMode(singleLineMode bool) { + h.singleLineMode = singleLineMode } -// helpQuitExitRE is a regexp to use to match help, quit, or exit messages. -var helpQuitExitRE = regexp.MustCompile(fmt.Sprintf(`(?im)^(%s|%s|%s)\s*$`, text.HelpPrefix, text.QuitPrefix, text.ExitPrefix)) - // Run executes queries and commands. func (h *Handler) Run() error { stdout, stderr, iactive := h.l.Stdout(), h.l.Stderr(), h.l.Interactive() @@ -249,69 +156,39 @@ func (h *Handler) Run() error { fmt.Fprintln(stdout, text.WelcomeDesc) fmt.Fprintln(stdout) } + var cmd string + var paramstr string + var err error + var opt metacmd.Option + var cont bool var lastErr error + var execute bool for { - var execute bool + execute = false // set prompt if iactive { h.l.Prompt(h.Prompt(env.Get("PROMPT1"))) } // read next statement/command - cmd, paramstr, err := h.buf.Next(env.Unquote(h.user, false, env.All())) - switch { + switch cmd, paramstr, err = h.buf.Next(env.Untick(h.user, env.Vars(), false)); { case h.singleLineMode && err == nil: execute = h.buf.Len != 0 case err == rline.ErrInterrupt: h.buf.Reset(nil) continue + case err == io.EOF: + return lastErr case err != nil: - if err == io.EOF { - return lastErr - } return err + case cmd != "": + opt, cont, lastErr = h.apply(stdout, stderr, strings.TrimPrefix(cmd, `\`), paramstr) } - var opt metacmd.Option - if cmd != "" { - cmd = strings.TrimPrefix(cmd, `\`) - params := stmt.DecodeParams(paramstr) - // decode - r, err := metacmd.Decode(cmd, params) - if err != nil { - lastErr = WrapErr(cmd, err) - switch { - case err == text.ErrUnknownCommand: - fmt.Fprintln(stderr, fmt.Sprintf(text.InvalidCommand, cmd)) - case err == text.ErrMissingRequiredArgument: - fmt.Fprintln(stderr, fmt.Sprintf(text.MissingRequiredArg, cmd)) - default: - fmt.Fprintln(stderr, "error:", err) - } - continue - } - // run - opt, err = r.Run(h) - if err != nil && err != rline.ErrInterrupt { - lastErr = WrapErr(cmd, err) - fmt.Fprintln(stderr, "error:", err) - continue - } - // print unused command parameters - for { - ok, arg, err := params.Get(func(s string, isvar bool) (bool, string, error) { - return true, s, nil - }) - if err != nil { - fmt.Fprintln(stderr, "error:", err) - } - if !ok { - break - } - fmt.Fprintln(stdout, fmt.Sprintf(text.ExtraArgumentIgnored, cmd, arg)) - } + if cont { + continue } // help, exit, quit intercept if iactive && len(h.buf.Buf) >= 4 { - i, first := stmt.RunesLastIndex(h.buf.Buf, '\n'), false + i, first := lastIndex(h.buf.Buf, '\n'), false if i == -1 { i, first = 0, true } @@ -365,7 +242,7 @@ func (h *Handler) Run() error { } // append to last h.lastExec += lend + h.buf.String() - h.lastPrefix = h.buf.Prefix + h.lastExecPrefix = h.buf.Prefix h.lastPrint += lend + h.buf.PrintString() h.lastRaw += lend + h.buf.RawString() h.buf.Reset(nil) @@ -373,12 +250,12 @@ func (h *Handler) Run() error { if h.batchEnd != typ { continue } - h.lastPrefix = h.batchEnd + h.lastExecPrefix = h.batchEnd h.batch, h.batchEnd = false, "" } } if h.buf.Len != 0 { - h.lastExec, h.lastPrefix, h.lastPrint, h.lastRaw = h.buf.String(), h.buf.Prefix, h.buf.PrintString(), h.buf.RawString() + h.lastExec, h.lastExecPrefix, h.lastPrint, h.lastRaw = h.buf.String(), h.buf.Prefix, h.buf.PrintString(), h.buf.RawString() h.buf.Reset(nil) } // log.Printf(">> PROCESS EXECUTE: (%s) `%s`", h.lastPrefix, h.last) @@ -395,9 +272,9 @@ func (h *Handler) Run() error { out = h.out } ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) - if err = h.Execute(ctx, out, opt, h.lastPrefix, h.lastExec, forceBatch, h.unbind()...); err != nil { + if err = h.Execute(ctx, out, opt, h.lastExecPrefix, h.lastExec, forceBatch, h.unbind()...); err != nil { lastErr = WrapErr(h.lastExec, err) - if env.All()["ON_ERROR_STOP"] == "on" { + if env.Get("ON_ERROR_STOP") == "on" { if iactive { fmt.Fprintln(stderr, "error:", err) h.buf.Reset([]rune{}) // empty the buffer so no other statements are run @@ -416,6 +293,143 @@ func (h *Handler) Run() error { } } +// apply applies the command against the handler. +func (h *Handler) apply(stdout, stderr io.Writer, cmd, paramstr string) (metacmd.Option, bool, error) { + // cmd = strings.TrimPrefix(cmd, `\`) + params := stmt.NewParams(paramstr) + // decode + f, err := metacmd.Decode(cmd, params) + if err != nil { + switch err = WrapErr(cmd, err); { + case err == text.ErrUnknownCommand: + fmt.Fprintln(stderr, fmt.Sprintf(text.InvalidCommand, cmd)) + case err == text.ErrMissingRequiredArgument: + fmt.Fprintln(stderr, fmt.Sprintf(text.MissingRequiredArg, cmd)) + default: + fmt.Fprintln(stderr, "error:", err) + } + return metacmd.Option{}, true, err + } + // run + opt, err := f(h) + if err != nil && err != rline.ErrInterrupt { + fmt.Fprintln(stderr, "error:", err) + return metacmd.Option{}, true, WrapErr(cmd, err) + } +loop: + // print unused command parameters + for { + switch arg, ok, err := params.Arg(); { + case err != nil: + fmt.Fprintln(stderr, "error:", err) + case !ok: + break loop + default: + fmt.Fprintln(stdout, fmt.Sprintf(text.ExtraArgumentIgnored, cmd, arg)) + } + } + return opt, false, nil +} + +// outputHighlighter returns s as a highlighted string, based on the current +// buffer and syntax highlighting settings. +func (h *Handler) outputHighlighter(s string) string { + // bail when string is empty (ie, contains no printable, non-space + // characters) or if syntax highlighting is not enabled + if empty(s) || env.Get("SYNTAX_HL") != "true" { + return s + } + // count end lines + var endl string + if m := lineendRE.FindStringSubmatch(s); m != nil { + s = strings.TrimSuffix(s, m[0]) + endl += m[0] + } + // leading whitespace + var leading string + // capture current query statement buffer + orig := h.buf.RawString() + full := orig + if full != "" { + full += "\n" + } else { + // get leading whitespace + if i := strings.IndexFunc(s, func(r rune) bool { + return !isSpaceOrControl(r) + }); i != -1 { + leading = s[:i] + } + } + full += s + // setup statement parser + st := drivers.NewStmt(h.u, func() func() ([]rune, error) { + y := strings.Split(orig, "\n") + if y[0] == "" { + y[0] = s + } else { + y = append(y, s) + } + return func() ([]rune, error) { + if len(y) > 0 { + z := y[0] + y = y[1:] + return []rune(z), nil + } + return nil, io.EOF + } + }()) + // accumulate all "active" statements in buffer, breaking either at + // EOF or when a \ cmd has been encountered + var err error + var cmd, final string +loop: + for { + cmd, _, err = st.Next(env.Untick(h.user, env.Vars(), false)) + switch { + case err != nil && err != io.EOF: + return s + endl + case err == io.EOF: + break loop + } + if st.Ready() || cmd != "" { + final += st.RawString() + st.Reset(nil) + // grab remaining whitespace to add to final + l := len(final) + // find first non empty character + if i := strings.IndexFunc(full[l:], func(r rune) bool { + return !isSpaceOrControl(r) + }); i != -1 { + final += full[l : l+i] + } + } + } + if !st.Ready() && cmd == "" { + final += st.RawString() + } + final = leading + final + // determine whatever is remaining after "active" + var remaining string + if fnl := len(final); fnl < len(full) { + remaining = full[fnl:] + } + // this happens when a read line is empty and/or has only + // whitespace and a \ cmd + if s == remaining { + return s + endl + } + // highlight entire final accumulated buffer + b := new(bytes.Buffer) + if err := h.Highlight(b, final); err != nil { + return s + endl + } + colored := b.String() + // return only last line plus whatever remaining string (ie, after + // a \ cmd) and the end line count + ss := strings.Split(colored, "\n") + return lastcolor(colored) + ss[len(ss)-1] + remaining + endl +} + // Execute executes a query against the connected database. func (h *Handler) Execute(ctx context.Context, w io.Writer, opt metacmd.Option, prefix, sqlstr string, forceTrans bool, bind ...interface{}) error { if h.db == nil { @@ -459,7 +473,7 @@ func (h *Handler) Execute(ctx context.Context, w io.Writer, opt metacmd.Option, // Reset resets the handler's query statement buffer. func (h *Handler) Reset(r []rune) { h.buf.Reset(r) - h.lastExec, h.lastPrefix, h.lastPrint, h.lastRaw, h.batch, h.batchEnd = "", "", "", "", false, "" + h.lastExec, h.lastExecPrefix, h.lastPrint, h.lastRaw, h.batch, h.batchEnd = "", "", "", "", false, "" } // Bind sets the bind parameters for the next query execution. @@ -723,14 +737,13 @@ func (h *Handler) Buf() *stmt.Stmt { // Highlight highlights using the current environment settings. func (h *Handler) Highlight(w io.Writer, buf string) error { - vars := env.All() // create lexer, formatter, styler l := chroma.Coalesce(drivers.Lexer(h.u)) - f := formatters.Get(vars["SYNTAX_HL_FORMAT"]) - s := styles.Get(vars["SYNTAX_HL_STYLE"]) + f := formatters.Get(env.Get("SYNTAX_HL_FORMAT")) + s := styles.Get(env.Get("SYNTAX_HL_STYLE")) // override background - if vars["SYNTAX_HL_OVERRIDE_BG"] != "false" { - s = ustyles.Get(vars["SYNTAX_HL_STYLE"]) + if env.Get("SYNTAX_HL_OVERRIDE_BG") != "false" { + s = ustyles.Get(env.Get("SYNTAX_HL_STYLE")) } // tokenize stream it, err := l.Tokenise(nil, buf) @@ -758,7 +771,7 @@ func (h *Handler) Open(ctx context.Context, params ...string) error { return text.ErrPreviousTransactionExists } if len(params) == 1 { - if v, ok := env.Cget(params[0]); ok { + if v, ok := env.Vars().GetConn(params[0]); ok { params = v } } @@ -843,13 +856,9 @@ func (h *Handler) connStrings() []string { } } } - names = append(names, fmt.Sprintf("%s://%s%s%s%s", entry.Protocol, user, host, port, dbname)) - } - for name := range env.Call() { - names = append(names, name) + names = append(names, entry.Protocol+"://"+user+host+port+dbname) } - sort.Strings(names) - return names + return append(names, slices.Sorted(maps.Keys(env.Vars().Conn()))...) } // forceParams forces connection parameters on a database URL, adding any @@ -874,13 +883,13 @@ func (h *Handler) forceParams(u *dburl.URL) { // Password collects a password from input, and returns a modified DSN // including the collected password. func (h *Handler) Password(dsn string) (string, error) { - switch v, ok := env.Cget(dsn); { + switch conn, ok := env.Vars().GetConn(dsn); { case dsn == "": return "", text.ErrMissingDSN - case ok && len(v) < 2: + case ok && len(conn) < 2: return "", text.ErrNamedConnectionIsNotAURL case ok: - dsn = v[0] + dsn = conn[0] } u, err := dburl.Parse(dsn) if err != nil { @@ -946,9 +955,8 @@ func (h *Handler) ReadVar(typ, prompt string) (string, error) { _, err = strconv.ParseFloat(v, 64) case "bool": var b bool - b, err = strconv.ParseBool(v) - if err == nil { - v = fmt.Sprintf("%v", b) + if b, err = strconv.ParseBool(v); err == nil { + v = fmt.Sprintf("%t", b) } } if err != nil { @@ -1077,7 +1085,7 @@ func (h *Handler) doExecChart(ctx context.Context, w io.Writer, opt metacmd.Opti } // process row(s) transposed := make([][]string, len(cols)) - clen, tfmt := len(cols), env.GoTime() + clen, tfmt := len(cols), env.Vars().PrintTimeFormat() for rows.Next() { row, err := h.scan(rows, clen, tfmt) if err != nil { @@ -1165,7 +1173,7 @@ func (h *Handler) doExecSet(ctx context.Context, w io.Writer, opt metacmd.Option // process row(s) var i int var row []string - clen, tfmt := len(cols), env.GoTime() + clen, tfmt := len(cols), env.Vars().PrintTimeFormat() for rows.Next() { if i == 0 { row, err = h.scan(rows, clen, tfmt) @@ -1184,7 +1192,7 @@ func (h *Handler) doExecSet(ctx context.Context, w io.Writer, opt metacmd.Option if err = env.ValidIdentifier(n); err != nil { return fmt.Errorf(text.CouldNotSetVariable, n) } - _ = env.Set(n, row[i]) + _ = env.Vars().Set(n, row[i]) } return nil } @@ -1218,8 +1226,8 @@ func (h *Handler) doQuery(ctx context.Context, w io.Writer, opt metacmd.Option, return err } defer rows.Close() - params := env.Pall() - params["time"] = env.GoTime() + params := env.Vars().Print() + params["time"] = env.Vars().PrintTimeFormat() for k, v := range opt.Params { params[k] = v } @@ -1242,7 +1250,7 @@ func (h *Handler) doQuery(ctx context.Context, w io.Writer, opt metacmd.Option, w = pipe } } else if opt.Exec != metacmd.ExecWatch { - params["pager_cmd"] = env.All()["PAGER"] + params["pager_cmd"] = env.Get("PAGER") } // set up column type config var extra []tblfmt.Option @@ -1295,8 +1303,10 @@ func (h *Handler) doExecRows(ctx context.Context, w io.Writer, rows *sql.Rows) e return err } // process rows - res := metacmd.Option{Exec: metacmd.ExecOnly} - clen, tfmt := len(cols), env.GoTime() + res := metacmd.Option{ + Exec: metacmd.ExecOnly, + } + clen, tfmt := len(cols), env.Vars().PrintTimeFormat() for rows.Next() { if clen != 0 { row, err := h.scan(rows, clen, tfmt) @@ -1373,13 +1383,13 @@ func (h *Handler) scan(rows *sql.Rows, clen int, tfmt string) ([]string, error) func (h *Handler) doExec(ctx context.Context, w io.Writer, _ metacmd.Option, typ, sqlstr string, bind []interface{}) error { res, err := h.DB().ExecContext(ctx, sqlstr, bind...) if err != nil { - _ = env.Set("ROW_COUNT", "0") + _ = env.Vars().Set("ROW_COUNT", "0") return err } // get affected count, err := drivers.RowsAffected(h.u, res) if err != nil { - _ = env.Set("ROW_COUNT", "0") + _ = env.Vars().Set("ROW_COUNT", "0") return err } // print name @@ -1391,7 +1401,7 @@ func (h *Handler) doExec(ctx context.Context, w io.Writer, _ metacmd.Option, typ } fmt.Fprintln(w) } - return env.Set("ROW_COUNT", strconv.FormatInt(count, 10)) + return env.Vars().Set("ROW_COUNT", strconv.FormatInt(count, 10)) } // Begin begins a transaction. @@ -1447,6 +1457,26 @@ func (h *Handler) Rollback() error { return nil } +// If starts an if block. +func (h *Handler) If(ok bool) error { + return nil +} + +// ElseIf starts an else if block. +func (h *Handler) ElseIf(ok bool) error { + return nil +} + +// Else starts an else block. +func (h *Handler) Else(bool) error { + return nil +} + +// EndIf closes an if block. +func (h *Handler) EndIf(bool) error { + return nil +} + // IncludeReader includes the content of rdr. func (h *Handler) IncludeReader(rdr io.Reader, path string) error { r := bufio.NewReader(rdr) @@ -1559,13 +1589,14 @@ func (e *Error) Error() string { } // Unwrap returns the original error. -func (e *Error) Unwrap() error { return e.Err } +func (e *Error) Unwrap() error { + return e.Err +} func readerOpts() []metadata.ReaderOption { var opts []metadata.ReaderOption - envs := env.All() - if envs["ECHO_HIDDEN"] == "on" || envs["ECHO_HIDDEN"] == "noexec" { - if envs["ECHO_HIDDEN"] == "noexec" { + if env.Get("ECHO_HIDDEN") == "on" || env.Get("ECHO_HIDDEN") == "noexec" { + if env.Get("ECHO_HIDDEN") == "noexec" { opts = append(opts, metadata.WithDryRun(true)) } opts = append( @@ -1610,7 +1641,7 @@ func peekEnding(w io.Writer, r *bufio.Reader) error { return werr } -// grab grabs i from r, or returns 0 if i >= end. +// grab returns the i'th rune from r when i < end, otherwise 0. func grab(r []rune, i, end int) rune { if i < end { return r[i] @@ -1618,19 +1649,14 @@ func grab(r []rune, i, end int) rune { return 0 } -// linetermRE is the end of line terminal. -var linetermRE = regexp.MustCompile(`(?:\r?\n)+$`) - -// empty reports whether s contains at least one printable, non-space character. +// empty reports whether s contains at least one printable, non-space +// character. func empty(s string) bool { - i := strings.IndexFunc(s, func(r rune) bool { + return strings.IndexFunc(s, func(r rune) bool { return unicode.IsPrint(r) && !unicode.IsSpace(r) - }) - return i == -1 + }) == -1 } -var ansiRE = regexp.MustCompile(`\x1b[[0-9]+([:;][0-9]+)*m`) - // lastcolor returns the last defined color in s, if any. func lastcolor(s string) string { if i := strings.LastIndex(s, "\n"); i != -1 { @@ -1641,3 +1667,27 @@ func lastcolor(s string) string { } return strings.Join(ansiRE.FindAllString(s, -1), "") } + +// isSpaceOrControl returns true when r is a space or control character. +func isSpaceOrControl(r rune) bool { + return unicode.IsSpace(r) || unicode.IsControl(r) +} + +// lastIndex returns the last index in r of needle, or -1 if not found. +func lastIndex(r []rune, needle rune) int { + for i := len(r) - 1; i >= 0; i-- { + if r[i] == needle { + return i + } + } + return -1 +} + +// ansiRE matches ansi escape (color) codes. +var ansiRE = regexp.MustCompile(`\x1b[[0-9]+([:;][0-9]+)*m`) + +// lineendRE is the end of line terminal. +var lineendRE = regexp.MustCompile(`(?:\r?\n)+$`) + +// helpQuitExitRE is a regexp to use to match help, quit, or exit messages. +var helpQuitExitRE = regexp.MustCompile(`(?im)^+(` + strings.Join([]string{text.HelpPrefix, text.QuitPrefix, text.ExitPrefix}, "|") + `)\s*$`) diff --git a/main.go b/main.go index a2726be29bc..5c7a2f3e01f 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,6 @@ // Command usql is the universal command-line interface for SQL databases. +// +//go:debug x509negativeserial=1 package main //go:generate go run gen.go diff --git a/metacmd/cmds.go b/metacmd/cmds.go index e979ff6ed40..8989d999c4d 100644 --- a/metacmd/cmds.go +++ b/metacmd/cmds.go @@ -18,1060 +18,1016 @@ import ( "github.com/xo/usql/drivers" "github.com/xo/usql/env" "github.com/xo/usql/text" - "golang.org/x/exp/maps" ) -// Cmd is a command implementation. -type Cmd struct { - Section Section - Descs []Desc - Process func(*Params) error +// Question is a Help meta command (\?). Writes a help message to the output. +// +// Descs: +// +// ? [commands] show help on meta (backslash) commands +// ? options show help on {{CommandName}} command-line options +// ? variables show help on special {{CommandName}} variables +func Question(p *Params) error { + name, err := p.Next(false) + if err != nil { + return err + } + stdout, stderr := p.Handler.IO().Stdout(), p.Handler.IO().Stderr() + var cmd *exec.Cmd + var wc io.WriteCloser + if pager := env.Get("PAGER"); p.Handler.IO().Interactive() && pager != "" { + if wc, cmd, err = env.Pipe(stdout, stderr, pager); err != nil { + return err + } + stdout = wc + } + switch name = strings.TrimSpace(strings.ToLower(name)); { + case name == "options": + text.Usage(stdout, true) + case name == "variables": + env.Listing(stdout) + default: + Dump(stdout, name == "commands") + } + if cmd != nil { + if err := wc.Close(); err != nil { + return err + } + return cmd.Wait() + } + return nil } -// cmds is the set of commands. -var cmds []Cmd +// Quit is a General meta command (\q \quit). Quits the application. +// +// Descs: +// +// q quit {{CommandName}} +// quit +func Quit(p *Params) error { + p.Option.Quit = true + return nil +} -// cmdMap is the map of commands and their aliases. -var cmdMap map[string]Metacmd +// Copyright is a General meta command (\copyright). Writes the +// application's copyright message to the output. +// +// Descs: +// +// copyright show {{CommandName}} usage and distribution terms +func Copyright(p *Params) error { + stdout := p.Handler.IO().Stdout() + if typ := env.TermGraphics(); typ.Available() { + typ.Encode(stdout, text.Logo) + } + fmt.Fprintln(stdout, text.Copyright) + return nil +} -// sectMap is the map of sections to its respective commands. -var sectMap map[Section][]Metacmd +// Connect is a Connection meta command (\c, \connect). Opens (connects) a +// database connection. +// +// Descs: +// +// c URL connect to database URL +// c DRIVER PARAMS... connect to database with driver and parameters +// connect +func Connect(p *Params) error { + vals, err := p.All(true) + if err != nil { + return err + } + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + return p.Handler.Open(ctx, vals...) +} -func init() { - cmds = []Cmd{ - Question: { - Section: SectionHelp, - Descs: []Desc{ - {"?", "[commands]", "show help on backslash commands"}, - {"?", "options", "show help on " + text.CommandName + " command-line options"}, - {"?", "variables", "show help on special " + text.CommandName + " variables"}, - }, - Process: func(p *Params) error { - name, err := p.Get(false) - if err != nil { - return err - } - stdout, stderr := p.Handler.IO().Stdout(), p.Handler.IO().Stderr() - var cmd *exec.Cmd - var wc io.WriteCloser - if pager := env.Get("PAGER"); p.Handler.IO().Interactive() && pager != "" { - if wc, cmd, err = env.Pipe(stdout, stderr, pager); err != nil { - return err - } - stdout = wc - } - switch name = strings.TrimSpace(strings.ToLower(name)); { - case name == "options": - Usage(stdout, true) - case name == "variables": - env.Listing(stdout) - default: - Listing(stdout) - } - if cmd != nil { - if err := wc.Close(); err != nil { - return err - } - return cmd.Wait() - } - return nil - }, - }, - Quit: { - Section: SectionGeneral, - Descs: []Desc{ - {"q", "", "quit " + text.CommandName}, - {"quit", "", ""}, - }, - Process: func(p *Params) error { - p.Option.Quit = true - return nil - }, - }, - Copyright: { - Section: SectionGeneral, - Descs: []Desc{ - {"copyright", "", "show " + text.CommandName + " usage and distribution terms"}, - }, - Process: func(p *Params) error { - stdout := p.Handler.IO().Stdout() - if typ := env.TermGraphics(); typ.Available() { - typ.Encode(stdout, text.Logo) - } - fmt.Fprintln(stdout, text.Copyright) - return nil - }, - }, - ConnectionInfo: { - Section: SectionConnection, - Descs: []Desc{ - {"conninfo", "", "display information about the current database connection"}, - }, - Process: func(p *Params) error { - s := text.NotConnected - if db, u := p.Handler.DB(), p.Handler.URL(); db != nil && u != nil { - s = fmt.Sprintf(text.ConnInfo, u.Driver, u.DSN) - } - fmt.Fprintln(p.Handler.IO().Stdout(), s) - return nil - }, - }, - Drivers: { - Section: SectionGeneral, - Descs: []Desc{ - {"drivers", "", "display information about available database drivers"}, - }, - Process: func(p *Params) error { - stdout, stderr := p.Handler.IO().Stdout(), p.Handler.IO().Stderr() - var cmd *exec.Cmd - var wc io.WriteCloser - if pager := env.Get("PAGER"); p.Handler.IO().Interactive() && pager != "" { - var err error - if wc, cmd, err = env.Pipe(stdout, stderr, pager); err != nil { - return err - } - stdout = wc - } - available := drivers.Available() - names := make([]string, len(available)) - var z int - for k := range available { - names[z] = k - z++ - } - sort.Strings(names) - fmt.Fprintln(stdout, text.AvailableDrivers) - for _, n := range names { - s := " " + n - driver, aliases := dburl.SchemeDriverAndAliases(n) - if driver != n { - s += " (" + driver + ")" - } - if len(aliases) > 0 { - if len(aliases) > 0 { - s += " [" + strings.Join(aliases, ", ") + "]" - } - } - fmt.Fprintln(stdout, s) - } - if cmd != nil { - if err := wc.Close(); err != nil { - return err - } - return cmd.Wait() - } - return nil - }, - }, - Connect: { - Section: SectionConnection, - Descs: []Desc{ - {"c", "DSN", "connect to database url"}, - {"c", "DRIVER PARAMS...", "connect to database with driver and parameters"}, - {"connect", "", ""}, - }, - Process: func(p *Params) error { - vals, err := p.GetAll(true) - if err != nil { - return err - } - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) - defer cancel() - return p.Handler.Open(ctx, vals...) - }, - }, - SetConnVar: { - Section: SectionConnection, - Descs: []Desc{ - {"cset", "[NAME [DSN]]", "set named connection, or list all if no parameters"}, - {"cset", "NAME DRIVER PARAMS...", "define named connection for database driver"}, - }, - Process: func(p *Params) error { - ok, n, err := p.GetOK(true) - switch { - case err != nil: - return err - case ok: - vals, err := p.GetAll(true) - if err != nil { - return err - } - return env.Cset(n, vals...) - } - vals := env.Call() - keys := maps.Keys(vals) - sort.Strings(keys) - out := p.Handler.IO().Stdout() - for _, k := range keys { - fmt.Fprintln(out, k, "=", env.Quote(strings.Join(vals[k], " "))) - } - return nil - }, - }, - Disconnect: { - Section: SectionConnection, - Descs: []Desc{ - {"Z", "", "close database connection"}, - {"disconnect", "", ""}, - }, - Process: func(p *Params) error { - return p.Handler.Close() - }, - }, - Password: { - Section: SectionConnection, - Descs: []Desc{ - {"password", "[USERNAME]", "change the password for a user"}, - {"passwd", "", ""}, - }, - Process: func(p *Params) error { - username, err := p.Get(true) - if err != nil { - return err - } - user, err := p.Handler.ChangePassword(username) - switch { - case err == text.ErrPasswordNotSupportedByDriver || err == text.ErrNotConnected: - return err - case err != nil: - return fmt.Errorf(text.PasswordChangeFailed, user, err) - } - // p.Handler.Print(text.PasswordChangeSucceeded, user) - return nil - }, - }, - Exec: { - Section: SectionQueryExecute, - Descs: []Desc{ - {"g", "[(OPTIONS)] [FILE] or ;", "execute query (and send results to file or |pipe)"}, - {"go", "", ""}, - {"G", "[(OPTIONS)] [FILE]", "as \\g, but forces vertical output mode"}, - {"gx", "[(OPTIONS)] [FILE]", "as \\g, but forces expanded output mode"}, - {"gexec", "", "execute query and execute each value of the result"}, - {"gset", "[PREFIX]", "execute query and store results in " + text.CommandName + " variables"}, - {"crosstabview", "[(OPTIONS)] [COLUMNS]", "execute query and display results in crosstab"}, - {"chart", "CHART [(OPTIONS)]", "execute query and display results as a chart"}, - {"watch", "[(OPTIONS)] [DURATION]", "execute query every specified interval"}, - }, - Process: func(p *Params) error { - p.Option.Exec = ExecOnly - switch p.Name { - case "g", "go": - params, err := p.GetAll(true) - if err != nil { - return err - } - p.Option.ParseParams(params, "pipe") - case "gexec": - p.Option.Exec = ExecExec - case "gset": - p.Option.Exec = ExecSet - params, err := p.GetAll(true) - if err != nil { - return err - } - p.Option.ParseParams(params, "prefix") - case "G": - params, err := p.GetAll(true) - if err != nil { - return err - } - p.Option.ParseParams(params, "pipe") - p.Option.Params["format"] = "vertical" - case "gx": - params, err := p.GetAll(true) - if err != nil { - return err - } - p.Option.ParseParams(params, "pipe") - p.Option.Params["expanded"] = "on" - case "crosstabview": - p.Option.Exec = ExecCrosstab - for i := 0; i < 4; i++ { - ok, col, err := p.GetOK(true) - if err != nil { - return err - } - p.Option.Crosstab = append(p.Option.Crosstab, col) - if !ok { - break - } - } - case "chart": - p.Option.Exec = ExecChart - if p.Option.Params == nil { - p.Option.Params = make(map[string]string, 1) - } - params, err := p.GetAll(true) - if err != nil { - return err - } - for i := 0; i < len(params); i++ { - param := params[i] - if param == "help" { - p.Option.Params["help"] = "" - return nil - } - equal := strings.IndexByte(param, '=') - switch { - case equal == -1 && i >= len(params)-1: - return text.ErrWrongNumberOfArguments - case equal == -1: - i++ - p.Option.Params[param] = params[i] - default: - p.Option.Params[param[:equal]] = param[equal+1:] - } - } - case "watch": - p.Option.Exec = ExecWatch - p.Option.Watch = 2 * time.Second - ok, s, err := p.GetOK(true) - switch { - case err != nil: - return err - case ok: - d, err := time.ParseDuration(s) - if err != nil { - if f, err := strconv.ParseFloat(s, 64); err == nil { - d = time.Duration(f * float64(time.Second)) - } - } - if d == 0 { - return text.ErrInvalidWatchDuration - } - p.Option.Watch = d - } - } - return nil - }, - }, - Bind: { - Section: SectionQueryExecute, - Descs: []Desc{ - {"bind", "[PARAM]...", "set query parameters"}, - }, - Process: func(p *Params) error { - bind, err := p.GetAll(true) - if err != nil { - return err - } - var v []interface{} - if n := len(bind); n != 0 { - v = make([]interface{}, len(bind)) - for i := 0; i < n; i++ { - v[i] = bind[i] - } - } - p.Handler.Bind(v) - return nil - }, - }, - Edit: { - Section: SectionQueryBuffer, - Descs: []Desc{ - {"e", "[FILE] [LINE]", "edit the query buffer (or file) with external editor"}, - {"edit", "[-exec]", "edit the query (or exec) buffer"}, - }, - Process: func(p *Params) error { - var exec bool - ok, path, err := p.GetOptional(true) - if ok { - if path != "exec" { - return fmt.Errorf(text.InvalidOption, path) - } - exec = true - if path, err = p.Get(true); err != nil { - return err - } - } - // get last statement - s, buf := "", p.Handler.Buf() - switch { - case buf.Len != 0 && exec: - s = buf.String() - case buf.Len != 0: - s = buf.RawString() - case exec: - s = p.Handler.LastExec() - default: - s = p.Handler.LastRaw() - } - line, err := p.Get(true) - if err != nil { - return err - } - // reset if no error - n, err := env.EditFile(p.Handler.User(), path, line, s) - if err != nil { - return err - } - // save edited buffer to history - p.Handler.IO().Save(string(n)) - buf.Reset(n) - return nil - }, - }, - Print: { - Section: SectionQueryBuffer, - Descs: []Desc{ - {"p", "", "show the contents of the query buffer"}, - {"print", "", ""}, - {"raw", "", "show the raw (non-interpolated) contents of the query buffer"}, - {"exec", "", "show the contents of the exec buffer"}, - }, - Process: func(p *Params) error { - // get last statement - var s string - switch buf := p.Handler.Buf(); { - case buf.Len != 0 && p.Name == "exec": - s = buf.String() - case buf.Len != 0 && p.Name == "raw": - s = buf.RawString() - case buf.Len != 0: - s = buf.PrintString() - case p.Name == "exec": - s = p.Handler.LastExec() - case p.Name == "raw": - s = p.Handler.LastRaw() - default: - s = p.Handler.LastPrint() - } - switch { - case s == "": - s = text.QueryBufferEmpty - case p.Handler.IO().Interactive() && env.All()["SYNTAX_HL"] == "true": - b := new(bytes.Buffer) - if p.Handler.Highlight(b, s) == nil { - s = b.String() - } - } - fmt.Fprintln(p.Handler.IO().Stdout(), s) - return nil - }, - }, - Reset: { - Section: SectionQueryBuffer, - Descs: []Desc{ - {"r", "", "reset (clear) the query buffer"}, - {"reset", "", ""}, - }, - Process: func(p *Params) error { - p.Handler.Reset(nil) - p.Handler.Print(text.QueryBufferReset) - return nil - }, - }, - Echo: { - Section: SectionInputOutput, - Descs: []Desc{ - {"echo", "[-n] [STRING]", "write string to standard output (-n for no newline)"}, - {"qecho", "[-n] [STRING]", "write string to \\o output stream (-n for no newline)"}, - {"warn", "[-n] [STRING]", "write string to standard error (-n for no newline)"}, - }, - Process: func(p *Params) error { - ok, n, err := p.GetOptional(true) - if err != nil { - return err - } - f := fmt.Fprintln - var vals []string - switch { - case ok && n == "n": - f = fmt.Fprint - case ok: - vals = append(vals, "-"+n) - default: - vals = append(vals, n) - } - v, err := p.GetAll(true) - if err != nil { - return err - } - out := p.Handler.IO().Stdout() - switch o := p.Handler.GetOutput(); { - case p.Name == "qecho" && o != nil: - out = o - case p.Name == "warn": - out = p.Handler.IO().Stderr() - } - f(out, strings.Join(append(vals, v...), " ")) - return nil - }, - }, - Write: { - Section: SectionQueryBuffer, - Descs: []Desc{ - {"w", "FILE", "write query buffer to file"}, - {"write", "", ""}, - }, - Process: func(p *Params) error { - // get last statement - s, buf := p.Handler.LastExec(), p.Handler.Buf() - if buf.Len != 0 { - s = buf.String() - } - file, err := p.Get(true) - if err != nil { - return err - } - return os.WriteFile(file, []byte(strings.TrimSuffix(s, "\n")+"\n"), 0o644) - }, - }, - ChangeDir: { - Section: SectionOperatingSystem, - Descs: []Desc{ - {"cd", "[DIR]", "change the current working directory"}, - }, - Process: func(p *Params) error { - dir, err := p.Get(true) - if err != nil { - return err - } - return env.Chdir(p.Handler.User(), dir) - }, - }, - GetEnv: { - Section: SectionOperatingSystem, - Descs: []Desc{ - {"getenv", "VARNAME ENVVAR", "fetch environment variable"}, - }, - Process: func(p *Params) error { - n, err := p.Get(true) - switch { - case err != nil: - return err - case n == "": - return text.ErrMissingRequiredArgument - } - v, err := p.Get(true) - switch { - case err != nil: - return err - case v == "": - return text.ErrMissingRequiredArgument - } - value, _ := env.Getenv(v) - return env.Set(n, value) - }, - }, - SetEnv: { - Section: SectionOperatingSystem, - Descs: []Desc{ - {"setenv", "NAME [VALUE]", "set or unset environment variable"}, - }, - Process: func(p *Params) error { - n, err := p.Get(true) - if err != nil { - return err - } - v, err := p.Get(true) - if err != nil { - return err - } - return os.Setenv(n, v) - }, - }, - Timing: { - Section: SectionOperatingSystem, - Descs: []Desc{ - {"timing", "[on|off]", "toggle timing of commands"}, - }, - Process: func(p *Params) error { - v, err := p.Get(true) - if err != nil { - return err - } - if v == "" { - p.Handler.SetTiming(!p.Handler.GetTiming()) - } else { - s, err := env.ParseBool(v, "\\timing") - if err != nil { - stderr := p.Handler.IO().Stderr() - fmt.Fprintf(stderr, "error: %v", err) - fmt.Fprintln(stderr) - } - var b bool - if s == "on" { - b = true - } - p.Handler.SetTiming(b) - } - setting := "off" - if p.Handler.GetTiming() { - setting = "on" - } - p.Handler.Print(text.TimingSet, setting) - return nil - }, - }, - Shell: { - Section: SectionOperatingSystem, - Descs: []Desc{ - {"!", "[COMMAND]", "execute command in shell or start interactive shell"}, - }, - Process: func(p *Params) error { - return env.Shell(p.GetRaw()) - }, - }, - Out: { - Section: SectionInputOutput, - Descs: []Desc{ - {"o", "[FILE]", "send all query results to file or |pipe"}, - {"out", "", ""}, - }, - Process: func(p *Params) error { - p.Handler.SetOutput(nil) - params, err := p.GetAll(true) - if err != nil { - return err - } - pipe := strings.Join(params, " ") - if pipe == "" { - return nil - } - var out io.WriteCloser - if pipe[0] == '|' { - out, _, err = env.Pipe(p.Handler.IO().Stdout(), p.Handler.IO().Stderr(), pipe[1:]) - } else { - out, err = os.OpenFile(pipe, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644) - } - if err != nil { - return err - } - p.Handler.SetOutput(out) - return nil - }, - }, - Include: { - Section: SectionInputOutput, - Descs: []Desc{ - {"i", "FILE", "execute commands from file"}, - {"ir", "FILE", "as \\i, but relative to location of current script"}, - {"include", "", ""}, - {"include_relative", "", ""}, - }, - Process: func(p *Params) error { - path, err := p.Get(true) - if err != nil { - return err - } - relative := p.Name == "ir" || p.Name == "include_relative" - if err := p.Handler.Include(path, relative); err != nil { - return fmt.Errorf("%s: %v", path, err) - } - return nil - }, - }, - Transact: { - Section: SectionTransaction, - Descs: []Desc{ - {"begin", "", "begin a transaction"}, - {"begin", "[-read-only] [ISOLATION]", "begin a transaction with isolation level"}, - {"commit", "", "commit current transaction"}, - {"rollback", "", "rollback (abort) current transaction"}, - {"abort", "", ""}, - }, - Process: func(p *Params) error { - switch p.Name { - case "commit": - return p.Handler.Commit() - case "rollback", "abort": - return p.Handler.Rollback() - } - // read begin params - readOnly := false - ok, n, err := p.GetOptional(true) - if ok { - if n != "read-only" { - return fmt.Errorf(text.InvalidOption, n) - } - readOnly = true - if n, err = p.Get(true); err != nil { - return err - } - } - // build tx options - var txOpts *sql.TxOptions - if readOnly || n != "" { - isolation := sql.LevelDefault - switch strings.ToLower(n) { - case "default", "": - case "read-uncommitted": - isolation = sql.LevelReadUncommitted - case "read-committed": - isolation = sql.LevelReadCommitted - case "write-committed": - isolation = sql.LevelWriteCommitted - case "repeatable-read": - isolation = sql.LevelRepeatableRead - case "snapshot": - isolation = sql.LevelSnapshot - case "serializable": - isolation = sql.LevelSerializable - case "linearizable": - isolation = sql.LevelLinearizable - default: - return text.ErrInvalidIsolationLevel - } - txOpts = &sql.TxOptions{ - Isolation: isolation, - ReadOnly: readOnly, - } - } - // begin - return p.Handler.Begin(txOpts) - }, - }, - Prompt: { - Section: SectionVariables, - Descs: []Desc{ - {"prompt", "[-TYPE] VAR [PROMPT]", "prompt user to set variable"}, - }, - Process: func(p *Params) error { - typ := "string" - ok, n, err := p.GetOptional(true) - if err != nil { - return err - } - if ok { - typ = n - n, err = p.Get(true) - if err != nil { - return err - } - } - if n == "" { - return text.ErrMissingRequiredArgument - } - if err := env.ValidIdentifier(n); err != nil { - return err - } - vals, err := p.GetAll(true) - if err != nil { - return err - } - v, err := p.Handler.ReadVar(typ, strings.Join(vals, " ")) - if err != nil { - return err - } - return env.Set(n, v) - }, - }, - SetVar: { - Section: SectionVariables, - Descs: []Desc{ - {"set", "[NAME [VALUE]]", "set internal variable, or list all if no parameters"}, - }, - Process: func(p *Params) error { - ok, n, err := p.GetOK(true) - switch { - case err != nil: - return err - case ok: - vals, err := p.GetAll(true) - if err != nil { - return err - } - return env.Set(n, strings.Join(vals, " ")) - } - vals := env.All() - keys := maps.Keys(vals) - sort.Strings(keys) - out := p.Handler.IO().Stdout() - for _, k := range keys { - fmt.Fprintln(out, k, "=", env.Quote(vals[k])) - } - return nil - }, - }, - Unset: { - Section: SectionVariables, - Descs: []Desc{ - {"unset", "NAME", "unset (delete) internal variable"}, - }, - Process: func(p *Params) error { - n, err := p.Get(true) - if err != nil { - return err - } - return env.Unset(n) - }, - }, - SetPrintVar: { - Section: SectionFormatting, - Descs: []Desc{ - {"pset", "[NAME [VALUE]]", "set table output option"}, - {"a", "", "toggle between unaligned and aligned output mode"}, - {"C", "[STRING]", "set table title, or unset if none"}, - {"f", "[STRING]", "show or set field separator for unaligned query output"}, - {"H", "", "toggle HTML output mode"}, - {"T", "[STRING]", "set HTML tag attributes, or unset if none"}, - {"t", "[on|off]", "show only rows"}, - {"x", "[on|off|auto]", "toggle expanded output"}, - }, - Process: func(p *Params) error { - var ok bool - var val string - var err error - switch p.Name { - case "a", "H": - default: - ok, val, err = p.GetOK(true) - if err != nil { - return err - } - } - // display variables - if p.Name == "pset" && !ok { - return env.Pwrite(p.Handler.IO().Stdout()) - } - var field, extra string - switch p.Name { - case "pset": - field = val - ok, val, err = p.GetOK(true) - if err != nil { - return err - } - case "a": - field = "format" - case "C": - field = "title" - case "f": - field = "fieldsep" - case "H": - field, extra = "format", "html" - case "t": - field = "tuples_only" - case "T": - field = "tableattr" - case "x": - field = "expanded" - } - if !ok { - if val, err = env.Ptoggle(field, extra); err != nil { - return err - } - } else { - if val, err = env.Pset(field, val); err != nil { - return err - } - } - // special replacement name for expanded field, when 'auto' - if field == "expanded" && val == "auto" { - field = "expanded_auto" - } - // format output - mask := text.FormatFieldNameSetMap[field] - unsetMask := text.FormatFieldNameUnsetMap[field] - switch { - case strings.Contains(mask, "%d"): - i, _ := strconv.Atoi(val) - p.Handler.Print(mask, i) - case unsetMask != "" && val == "": - p.Handler.Print(unsetMask) - case !strings.Contains(mask, "%"): - p.Handler.Print(mask) - default: - if field == "time" { - val = fmt.Sprintf("%q", val) - if tfmt := env.GoTime(); tfmt != val { - val = fmt.Sprintf("%s (%q)", val, tfmt) - } - } - p.Handler.Print(mask, val) - } - return nil - }, - }, - Describe: { - Section: SectionInformational, - Descs: []Desc{ - {"d[S+]", "[NAME]", "list tables, views, and sequences or describe table, view, sequence, or index"}, - {"da[S+]", "[PATTERN]", "list aggregates"}, - {"df[S+]", "[PATTERN]", "list functions"}, - {"di[S+]", "[PATTERN]", "list indexes"}, - {"dm[S+]", "[PATTERN]", "list materialized views"}, - {"dn[S+]", "[PATTERN]", "list schemas"}, - {"dp[S]", "[PATTERN]", "list table, view, and sequence access privileges"}, - {"ds[S+]", "[PATTERN]", "list sequences"}, - {"dt[S+]", "[PATTERN]", "list tables"}, - {"dv[S+]", "[PATTERN]", "list views"}, - {"l[+]", "", "list databases"}, - }, - Process: func(p *Params) error { - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) - defer cancel() - m, err := p.Handler.MetadataWriter(ctx) - if err != nil { - return err - } - verbose := strings.ContainsRune(p.Name, '+') - showSystem := strings.ContainsRune(p.Name, 'S') - name := strings.TrimRight(p.Name, "S+") - pattern, err := p.Get(true) - if err != nil { - return err - } - switch name { - case "d": - if pattern != "" { - return m.DescribeTableDetails(p.Handler.URL(), pattern, verbose, showSystem) - } - return m.ListTables(p.Handler.URL(), "tvmsE", pattern, verbose, showSystem) - case "df", "da": - return m.DescribeFunctions(p.Handler.URL(), name, pattern, verbose, showSystem) - case "dt", "dtv", "dtm", "dts", "dv", "dm", "ds": - return m.ListTables(p.Handler.URL(), name, pattern, verbose, showSystem) - case "dn": - return m.ListSchemas(p.Handler.URL(), pattern, verbose, showSystem) - case "di": - return m.ListIndexes(p.Handler.URL(), pattern, verbose, showSystem) - case "l": - return m.ListAllDbs(p.Handler.URL(), pattern, verbose) - case "dp": - return m.ListPrivilegeSummaries(p.Handler.URL(), pattern, showSystem) - } - return nil - }, - }, - Stats: { - Section: SectionInformational, - Descs: []Desc{ - {"ss[+]", "[TABLE|QUERY] [k]", "show stats for a table or a query"}, - }, - Process: func(p *Params) error { - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) - defer cancel() - m, err := p.Handler.MetadataWriter(ctx) - if err != nil { - return err - } - verbose := strings.ContainsRune(p.Name, '+') - name := strings.TrimRight(p.Name, "+") - pattern, err := p.Get(true) - if err != nil { - return err - } - k := 0 - if verbose { - k = 3 - } - if name == "ss" { - name = "sswnulhmkf" - } - ok, val, err := p.GetOK(true) - if err != nil { - return err - } - if ok { - verbose = true - k, err = strconv.Atoi(val) - if err != nil { - return err - } - } - return m.ShowStats(p.Handler.URL(), name, pattern, verbose, k) - }, - }, - Copy: { - Section: SectionInputOutput, - Descs: []Desc{ - {"copy", "SRC DST QUERY TABLE", "copy query from source url to table on destination url"}, - {"copy", "SRC DST QUERY TABLE(A,...)", "copy query from source url to columns of table on destination url"}, - }, - Process: func(p *Params) error { - ctx := context.Background() - stdout, stderr := p.Handler.IO().Stdout, p.Handler.IO().Stderr - srcDsn, err := p.Get(true) - if err != nil { - return err - } - srcURL, err := dburl.Parse(srcDsn) - if err != nil { - return err - } - destDsn, err := p.Get(true) - if err != nil { - return err - } - destURL, err := dburl.Parse(destDsn) - if err != nil { - return err - } - query, err := p.Get(true) - if err != nil { - return err - } - table, err := p.Get(true) - if err != nil { - return err - } - src, err := drivers.Open(ctx, srcURL, stdout, stderr) - if err != nil { - return err - } - defer src.Close() - dest, err := drivers.Open(ctx, destURL, stdout, stderr) - if err != nil { - return err - } - defer dest.Close() - ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) - defer cancel() - // get the result set - r, err := src.QueryContext(ctx, query) - if err != nil { - return err - } - defer r.Close() - n, err := drivers.Copy(ctx, destURL, stdout, stderr, r, table) - if err != nil { - return err - } - p.Handler.Print("COPY %d", n) +// SetConn is a Connection meta command (\cset). Sets a connection variable. +// +// Descs: +// +// cset show named connections +// cset NAME URL set named connection to URL +// cset NAME DRIVER PARAMS... set named connection for database driver and parameters +func SetConn(p *Params) error { + switch n, ok, err := p.NextOK(true); { + case err != nil: + return err + case ok: + vals, err := p.All(true) + if err != nil { + return err + } + return env.Vars().SetConn(n, vals...) + } + return env.Vars().DumpConn(p.Handler.IO().Stdout()) +} + +// Copy is a Input/Output meta command (\copy). Copies data between databases. +// +// Descs: +// +// copy SRC DST QUERY TABLE copy query from source url to table on destination url +// copy SRC DST QUERY TABLE(A,...) copy query from source url to columns of table on destination url +func Copy(p *Params) error { + ctx := context.Background() + stdout, stderr := p.Handler.IO().Stdout, p.Handler.IO().Stderr + srcDsn, err := p.Next(true) + if err != nil { + return err + } + srcURL, err := dburl.Parse(srcDsn) + if err != nil { + return err + } + destDsn, err := p.Next(true) + if err != nil { + return err + } + destURL, err := dburl.Parse(destDsn) + if err != nil { + return err + } + query, err := p.Next(true) + if err != nil { + return err + } + table, err := p.Next(true) + if err != nil { + return err + } + src, err := drivers.Open(ctx, srcURL, stdout, stderr) + if err != nil { + return err + } + defer src.Close() + dest, err := drivers.Open(ctx, destURL, stdout, stderr) + if err != nil { + return err + } + defer dest.Close() + ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) + defer cancel() + // get the result set + r, err := src.QueryContext(ctx, query) + if err != nil { + return err + } + defer r.Close() + n, err := drivers.Copy(ctx, destURL, stdout, stderr, r, table) + if err != nil { + return err + } + p.Handler.Print("COPY %d", n) + return nil +} + +// Disconnect is a Connection meta command (\Z). Closes (disconnects) the +// current database connection. +// +// Descs: +// +// Z close database connection +// disconnect +func Disconnect(p *Params) error { + return p.Handler.Close() +} + +// Password is a Connection meta command (\password). Changes the database +// user's password. +// +// Descs: +// +// password [USER] change password for user +// passwd +func Password(p *Params) error { + username, err := p.Next(true) + if err != nil { + return err + } + user, err := p.Handler.ChangePassword(username) + switch { + case err == text.ErrPasswordNotSupportedByDriver || err == text.ErrNotConnected: + return err + case err != nil: + return fmt.Errorf(text.PasswordChangeFailed, user, err) + } + // p.Handler.Print(text.PasswordChangeSucceeded, user) + return nil +} + +// ConnectionInfo is a Connection meta command (\conninfo). Writes information +// about the connection to the output. +// +// Descs: +// +// conninfo display information about the current database connection +func ConnectionInfo(p *Params) error { + s := text.NotConnected + if db, u := p.Handler.DB(), p.Handler.URL(); db != nil && u != nil { + s = fmt.Sprintf(text.ConnInfo, u.Driver, u.DSN) + } + fmt.Fprintln(p.Handler.IO().Stdout(), s) + return nil +} + +// Drivers is a General meta command (\drivers). Writes information about the +// database drivers the application was built with to the output. +// +// Descs: +// +// drivers display information about available database drivers +func Drivers(p *Params) error { + stdout, stderr := p.Handler.IO().Stdout(), p.Handler.IO().Stderr() + var cmd *exec.Cmd + var wc io.WriteCloser + if pager := env.Get("PAGER"); p.Handler.IO().Interactive() && pager != "" { + var err error + if wc, cmd, err = env.Pipe(stdout, stderr, pager); err != nil { + return err + } + stdout = wc + } + available := drivers.Available() + names := make([]string, len(available)) + var z int + for k := range available { + names[z] = k + z++ + } + sort.Strings(names) + fmt.Fprintln(stdout, text.AvailableDrivers) + for _, n := range names { + s := " " + n + driver, aliases := dburl.SchemeDriverAndAliases(n) + if driver != n { + s += " (" + driver + ")" + } + if len(aliases) > 0 { + if len(aliases) > 0 { + s += " [" + strings.Join(aliases, ", ") + "]" + } + } + fmt.Fprintln(stdout, s) + } + if cmd != nil { + if err := wc.Close(); err != nil { + return err + } + return cmd.Wait() + } + return nil +} + +// Describe is a Informational meta command (\d and variants). Queries the open +// database connection for information about the database schema and writes the +// information to the output. +// +// Descs: +// +// d[S+] [NAME] list tables, views, and sequences or describe table, view, sequence, or index +// da[S+] [PATTERN] list aggregates +// df[S+] [PATTERN] list functions +// di[S+] [PATTERN] list indexes +// dm[S+] [PATTERN] list materialized views +// dn[S+] [PATTERN] list schemas +// dp[S] [PATTERN] list table, view, and sequence access privileges +// ds[S+] [PATTERN] list sequences +// dt[S+] [PATTERN] list tables +// dv[S+] [PATTERN] list views +// l[+] list databases +func Describe(p *Params) error { + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + m, err := p.Handler.MetadataWriter(ctx) + if err != nil { + return err + } + verbose := strings.ContainsRune(p.Name, '+') + showSystem := strings.ContainsRune(p.Name, 'S') + name := strings.TrimRight(p.Name, "S+") + pattern, err := p.Next(true) + if err != nil { + return err + } + switch name { + case "d": + if pattern != "" { + return m.DescribeTableDetails(p.Handler.URL(), pattern, verbose, showSystem) + } + return m.ListTables(p.Handler.URL(), "tvmsE", pattern, verbose, showSystem) + case "df", "da": + return m.DescribeFunctions(p.Handler.URL(), name, pattern, verbose, showSystem) + case "dt", "dtv", "dtm", "dts", "dv", "dm", "ds": + return m.ListTables(p.Handler.URL(), name, pattern, verbose, showSystem) + case "dn": + return m.ListSchemas(p.Handler.URL(), pattern, verbose, showSystem) + case "di": + return m.ListIndexes(p.Handler.URL(), pattern, verbose, showSystem) + case "l": + return m.ListAllDbs(p.Handler.URL(), pattern, verbose) + case "dp": + return m.ListPrivilegeSummaries(p.Handler.URL(), pattern, showSystem) + } + return nil +} + +// Execute is a Query Execute meta command (\g and variants). Executes the +// active query on the open database connection. +// +// Descs: +// +// g [(OPTIONS)] [FILE] or ; execute query (and send results to file or |pipe) +// go:g +// G [(OPTIONS)] [FILE] as \g, but forces vertical output mode +// ego:G +// gx [(OPTIONS)] [FILE] as \g, but forces expanded output mode +// gexec execute query and execute each value of the result +// gset [PREFIX] execute query and store results in {{CommandName}} variables +// crosstabview [(OPTIONS)] [COLUMNS] execute query and display results in crosstab +// chart CHART [(OPTIONS)] execute query and display results as a chart +// watch [(OPTIONS)] [DURATION] execute query every specified interval +func Execute(p *Params) error { + p.Option.Exec = ExecOnly + switch p.Name { + case "g", "go", "G", "ego", "gx", "gset": + params, err := p.All(true) + if err != nil { + return err + } + p.Option.ParseParams(params, "pipe") + switch p.Name { + case "G", "ego": + p.Option.Params["format"] = "vertical" + case "gx": + p.Option.Params["expanded"] = "on" + case "gset": + p.Option.Exec = ExecSet + } + case "gexec": + p.Option.Exec = ExecExec + case "crosstabview": + p.Option.Exec = ExecCrosstab + for i := 0; i < 4; i++ { + col, ok, err := p.NextOK(true) + if err != nil { + return err + } + p.Option.Crosstab = append(p.Option.Crosstab, col) + if !ok { + break + } + } + case "chart": + p.Option.Exec = ExecChart + if p.Option.Params == nil { + p.Option.Params = make(map[string]string, 1) + } + params, err := p.All(true) + if err != nil { + return err + } + for i := 0; i < len(params); i++ { + param := params[i] + if param == "help" { + p.Option.Params["help"] = "" return nil - }, - }, - } - // set up map - cmdMap = make(map[string]Metacmd, len(cmds)) - sectMap = make(map[Section][]Metacmd, len(SectionOrder)) - for i, c := range cmds { - mc := Metacmd(i) - if mc == None { - continue + } + equal := strings.IndexByte(param, '=') + switch { + case equal == -1 && i >= len(params)-1: + return text.ErrWrongNumberOfArguments + case equal == -1: + i++ + p.Option.Params[param] = params[i] + default: + p.Option.Params[param[:equal]] = param[equal+1:] + } } - name := c.Descs[0].Name - if pos := strings.IndexRune(name, '['); pos != -1 { - mods := strings.TrimRight(name[pos+1:], "]") - name = name[:pos] - cmdMap[name+mods] = mc - if len(mods) > 1 { - for _, r := range mods { - cmdMap[name+string(r)] = mc + case "watch": + p.Option.Exec = ExecWatch + p.Option.Watch = 2 * time.Second + switch s, ok, err := p.NextOK(true); { + case err != nil: + return err + case ok: + d, err := time.ParseDuration(s) + if err != nil { + if f, err := strconv.ParseFloat(s, 64); err == nil { + d = time.Duration(f * float64(time.Second)) } } + if d == 0 { + return text.ErrInvalidWatchDuration + } + p.Option.Watch = d } - cmdMap[name] = mc - for _, d := range c.Descs { - if pos := strings.IndexRune(d.Name, '['); pos != -1 { - mods := strings.TrimRight(d.Name[pos+1:], "]") - d.Name = d.Name[:pos] - cmdMap[d.Name+mods] = mc - if len(mods) > 1 { - for _, r := range mods { - cmdMap[d.Name+string(r)] = mc - } - } + } + return nil +} + +// Bind is a Query Execute meta command (\bind). Sets (or unsets) variables to +// be used when executing a query. +// +// Descs: +// +// bind [PARAM]... set query parameters +func Bind(p *Params) error { + bind, err := p.All(true) + if err != nil { + return err + } + var v []interface{} + if n := len(bind); n != 0 { + v = make([]interface{}, len(bind)) + for i := 0; i < n; i++ { + v[i] = bind[i] + } + } + p.Handler.Bind(v) + return nil +} + +// Edit is a Query Buffer meta command (\e \edit). Opens the query buffer for +// editing in an external application. +// +// Descs: +// +// e [FILE] [LINE] edit the query buffer (or file) with external editor +// edit [-exec] edit the query (or exec) buffer +func Edit(p *Params) error { + var exec bool + path, ok, err := p.NextOpt(true) + if ok { + if path != "exec" { + return fmt.Errorf(text.InvalidOption, path) + } + exec = true + if path, err = p.Next(true); err != nil { + return err + } + } + // get last statement + s, buf := "", p.Handler.Buf() + switch { + case buf.Len != 0 && exec: + s = buf.String() + case buf.Len != 0: + s = buf.RawString() + case exec: + s = p.Handler.LastExec() + default: + s = p.Handler.LastRaw() + } + line, err := p.Next(true) + if err != nil { + return err + } + // reset if no error + out, err := env.EditFile(p.Handler.User(), path, line, []byte(s)) + if err != nil { + return err + } + // save edited buffer to history + p.Handler.IO().Save(string(out)) + buf.Reset([]rune(string(out))) + return nil +} + +// Print is a Query Buffer meta command (\p, \print, \raw, \exec). Writes the +// query buffer to the output. +// +// Descs: +// +// p show the contents of the query buffer +// print +// raw show the raw (non-interpolated) contents of the query buffer +// exec show the contents of the exec buffer +func Print(p *Params) error { + // get last statement + var s string + switch buf := p.Handler.Buf(); { + case buf.Len != 0 && p.Name == "exec": + s = buf.String() + case buf.Len != 0 && p.Name == "raw": + s = buf.RawString() + case buf.Len != 0: + s = buf.PrintString() + case p.Name == "exec": + s = p.Handler.LastExec() + case p.Name == "raw": + s = p.Handler.LastRaw() + default: + s = p.Handler.LastPrint() + } + switch { + case s == "": + s = text.QueryBufferEmpty + case p.Handler.IO().Interactive() && env.Get("SYNTAX_HL") == "true": + b := new(bytes.Buffer) + if p.Handler.Highlight(b, s) == nil { + s = b.String() + } + } + fmt.Fprintln(p.Handler.IO().Stdout(), s) + return nil +} + +// Reset is a Query Buffer meta command (\r, \reset). Clears (resets) the query +// buffer. +// +// Descs: +// +// r reset (clear) the query buffer +// reset +func Reset(p *Params) error { + p.Handler.Reset(nil) + p.Handler.Print(text.QueryBufferReset) + return nil +} + +// Echo is a Input/Output meta command (\echo, \warn, \qecho). Writes a message +// to the output. +// +// Descs: +// +// echo [-n] [MESSAGE]... write message to standard output (-n for no newline) +// qecho [-n] [MESSAGE]... write message to \o output stream (-n for no newline) +// warn [-n] [MESSAGE]... write message to standard error (-n for no newline) +func Echo(p *Params) error { + n, ok, err := p.NextOpt(true) + if err != nil { + return err + } + f := fmt.Fprintln + var vals []string + switch { + case ok && n == "n": + f = fmt.Fprint + case ok: + vals = append(vals, "-"+n) + default: + vals = append(vals, n) + } + v, err := p.All(true) + if err != nil { + return err + } + out := p.Handler.IO().Stdout() + switch o := p.Handler.GetOutput(); { + case p.Name == "qecho" && o != nil: + out = o + case p.Name == "warn": + out = p.Handler.IO().Stderr() + } + f(out, strings.Join(append(vals, v...), " ")) + return nil +} + +// Write is a Query Buffer meta command (\w \write). Writes the query buffer to +// a file. +// +// Descs: +// +// w FILE write query buffer to file +// write +func Write(p *Params) error { + // get last statement + s, buf := p.Handler.LastExec(), p.Handler.Buf() + if buf.Len != 0 { + s = buf.String() + } + name, err := p.Next(true) + if err != nil { + return err + } + return os.WriteFile(name, []byte(strings.TrimSuffix(s, "\n")+"\n"), 0o644) +} + +// Chdir is a Operating System/Environment meta command (\cd). Changes the +// current directory for the Operating System/Environment. +// +// Descs: +// +// cd [DIR] change the current working directory +func Chdir(p *Params) error { + dir, err := p.Next(true) + if err != nil { + return err + } + return env.Chdir(p.Handler.User(), dir) +} + +// Getenv is a Operating System/Environment meta command (\getenv). Sets the +// application's variable value returned from the Operating +// System/Environment's variables. +// +// Descs: +// +// getenv VARNAME ENVVAR fetch environment variable +func Getenv(p *Params) error { + n, err := p.Next(true) + switch { + case err != nil: + return err + case n == "": + return text.ErrMissingRequiredArgument + } + v, err := p.Next(true) + switch { + case err != nil: + return err + case v == "": + return text.ErrMissingRequiredArgument + } + value, _ := env.Getenv(v) + return env.Vars().Set(n, value) +} + +// Setenv is a Operating System/Environment meta command (\setenv). Sets (or +// unsets) a Operating System/Environment variable. Environment variables set +// this way will be passed to any child processes. +// +// Descs: +// +// setenv NAME [VALUE] set or unset environment variable +func Setenv(p *Params) error { + n, err := p.Next(true) + if err != nil { + return err + } + v, err := p.Next(true) + if err != nil { + return err + } + return os.Setenv(n, v) +} + +// Shell is a Operating System/Environment meta command (\!). Executes a +// command using the Operating System/Environment's shell. +// +// Descs: +// +// ! [COMMAND] execute command in shell or start interactive shell +func Shell(p *Params) error { + return env.Shell(p.Raw()) +} + +// Out is a Input/Output meta command (\o \out). Sets (redirects) the output to +// a file or a command. +// +// Descs: +// +// o [FILE] send all query results to file or |pipe +// out +func Out(p *Params) error { + p.Handler.SetOutput(nil) + params, err := p.All(true) + if err != nil { + return err + } + pipe := strings.Join(params, " ") + if pipe == "" { + return nil + } + var out io.WriteCloser + if pipe[0] == '|' { + out, _, err = env.Pipe(p.Handler.IO().Stdout(), p.Handler.IO().Stderr(), pipe[1:]) + } else { + out, err = os.OpenFile(pipe, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644) + } + if err != nil { + return err + } + p.Handler.SetOutput(out) + return nil +} + +// Include is a Input/Output meta command (\i, \include and variants). Includes +// (runs) the specified file in the current execution environment. +// +// Descs: +// +// i FILE execute commands from file +// include:i +// ir FILE as \i, but relative to location of current script +// include_relative:ir +func Include(p *Params) error { + path, err := p.Next(true) + if err != nil { + return err + } + relative := p.Name == "ir" || p.Name == "include_relative" + if err := p.Handler.Include(path, relative); err != nil { + return fmt.Errorf("%s: %v", path, err) + } + return nil +} + +// Transact is a Transaction meta command (\begin, \commit, \rollback). Begins, +// commits, or rollsback the current database transaction on the open database +// connection. +// +// Descs: +// +// begin begin a transaction +// begin -read-only ISOLATION begin a transaction with isolation level +// commit commit current transaction +// rollback rollback (abort) current transaction +// abort:rollback +func Transact(p *Params) error { + switch p.Name { + case "commit": + return p.Handler.Commit() + case "rollback", "abort": + return p.Handler.Rollback() + } + // read begin params + readOnly := false + n, ok, err := p.NextOpt(true) + if ok { + if n != "read-only" { + return fmt.Errorf(text.InvalidOption, n) + } + readOnly = true + if n, err = p.Next(true); err != nil { + return err + } + } + // build tx options + var txOpts *sql.TxOptions + if readOnly || n != "" { + isolation := sql.LevelDefault + switch strings.ToLower(n) { + case "default", "": + case "read-uncommitted": + isolation = sql.LevelReadUncommitted + case "read-committed": + isolation = sql.LevelReadCommitted + case "write-committed": + isolation = sql.LevelWriteCommitted + case "repeatable-read": + isolation = sql.LevelRepeatableRead + case "snapshot": + isolation = sql.LevelSnapshot + case "serializable": + isolation = sql.LevelSerializable + case "linearizable": + isolation = sql.LevelLinearizable + default: + return text.ErrInvalidIsolationLevel + } + txOpts = &sql.TxOptions{ + Isolation: isolation, + ReadOnly: readOnly, + } + } + // begin + return p.Handler.Begin(txOpts) +} + +// Prompt is a Variables meta command (\prompt). Prompts the user for input, +// setting a application variable to the user's response. +// +// Descs: +// +// prompt [-TYPE] VAR [PROMPT] prompt user to set variable +func Prompt(p *Params) error { + typ := "string" + n, ok, err := p.NextOpt(true) + if err != nil { + return err + } + if ok { + typ = n + n, err = p.Next(true) + if err != nil { + return err + } + } + if n == "" { + return text.ErrMissingRequiredArgument + } + if err := env.ValidIdentifier(n); err != nil { + return err + } + vals, err := p.All(true) + if err != nil { + return err + } + v, err := p.Handler.ReadVar(typ, strings.Join(vals, " ")) + if err != nil { + return err + } + return env.Vars().Set(n, v) +} + +// Set is a Variables meta command (\set). Sets (or lists) the application variables. +// +// Descs: +// +// set [NAME [VALUE]] set internal variable, or list all if no parameters +func Set(p *Params) error { + switch n, ok, err := p.NextOK(true); { + case err != nil: + return err + case ok: + vals, err := p.All(true) + if err != nil { + return err + } + return env.Vars().Set(n, strings.Join(vals, " ")) + } + return env.Vars().Dump(p.Handler.IO().Stdout()) +} + +// Unset is a Variables meta command (\unset). Unsets a application variable. +// +// Descs: +// +// unset NAME unset (delete) internal variable +func Unset(p *Params) error { + n, err := p.Next(true) + if err != nil { + return err + } + return env.Vars().Unset(n) +} + +// SetPrint is a Variables meta command (\pset, \a, \C, \f, \H, \t, \T, \x). +// Sets, toggles, or displays the application's print formatting variables. +// +// Descs: +// +// pset [NAME [VALUE]] set table output option +// a toggle between unaligned and aligned output mode +// C [TITLE] set table title, or unset if none +// f [SEPARATOR] show or set field separator for unaligned query output +// H toggle HTML output mode +// T [ATTRIBUTES] set HTML
tag attributes, or unset if none +// t [on|off] show only rows +// x [on|off|auto] toggle expanded output +func SetPrint(p *Params) error { + var ok bool + var val string + var err error + switch p.Name { + case "a", "H": + default: + if val, ok, err = p.NextOK(true); err != nil { + return err + } + } + // display variables + if p.Name == "pset" && !ok { + return env.Vars().DumpPrint(p.Handler.IO().Stdout()) + } + var field, extra string + switch p.Name { + case "pset": + field = val + if val, ok, err = p.NextOK(true); err != nil { + return err + } + case "a": + field = "format" + case "C": + field = "title" + case "f": + field = "fieldsep" + case "H": + field, extra = "format", "html" + case "t": + field = "tuples_only" + case "T": + field = "tableattr" + case "x": + field = "expanded" + } + if !ok { + if val, err = env.Vars().TogglePrint(field, extra); err != nil { + return err + } + } else { + if val, err = env.Vars().SetPrint(field, val); err != nil { + return err + } + } + // special replacement name for expanded field, when 'auto' + if field == "expanded" && val == "auto" { + field = "expanded_auto" + } + // format output + mask := text.FormatFieldNameSetMap[field] + unsetMask := text.FormatFieldNameUnsetMap[field] + switch { + case strings.Contains(mask, "%d"): + i, _ := strconv.Atoi(val) + p.Handler.Print(mask, i) + case unsetMask != "" && val == "": + p.Handler.Print(unsetMask) + case !strings.Contains(mask, "%"): + p.Handler.Print(mask) + default: + if field == "time" { + val = fmt.Sprintf("%q", val) + if tfmt := env.Vars().PrintTimeFormat(); tfmt != val { + val = fmt.Sprintf("%s (%q)", val, tfmt) } - cmdMap[d.Name] = mc } - sectMap[c.Section] = append(sectMap[c.Section], mc) + p.Handler.Print(mask, val) } + return nil } -// Usage is used by the [Question] command to display command line options. -var Usage = func(io.Writer, bool) { +// Timing is a Operating System/Environment meta command (\timing). Sets (or +// toggles) writing timing information for executed queries to the output. +// +// Descs: +// +// timing [on|off] toggle timing of commands +func Timing(p *Params) error { + v, err := p.Next(true) + if err != nil { + return err + } + if v == "" { + p.Handler.SetTiming(!p.Handler.GetTiming()) + } else { + s, err := env.ParseBool(v, `\timing`) + if err != nil { + stderr := p.Handler.IO().Stderr() + fmt.Fprintf(stderr, "error: %v", err) + fmt.Fprintln(stderr) + } + var b bool + if s == "on" { + b = true + } + p.Handler.SetTiming(b) + } + setting := "off" + if p.Handler.GetTiming() { + setting = "on" + } + p.Handler.Print(text.TimingSet, setting) + return nil +} + +// Stats is a Informational meta command (\ss and variants). Queries the open +// database connection for stats and writes it to the output. +// +// Descs: +// +// ss[+] [TABLE|QUERY] [k] show stats for a table or a query +func Stats(p *Params) error { + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + m, err := p.Handler.MetadataWriter(ctx) + if err != nil { + return err + } + verbose := strings.ContainsRune(p.Name, '+') + name := strings.TrimRight(p.Name, "+") + pattern, err := p.Next(true) + if err != nil { + return err + } + k := 0 + if verbose { + k = 3 + } + if name == "ss" { + name = "sswnulhmkf" + } + val, ok, err := p.NextOK(true) + switch { + case err != nil: + return err + case ok: + verbose = true + if k, err = strconv.Atoi(val); err != nil { + return err + } + } + return m.ShowStats(p.Handler.URL(), name, pattern, verbose, k) +} + +// Conditional is a Conditional meta command (\if, \elif, \else, \endif). +// Starts, closes, and ends a conditional block within the application. +// +// Descs: +// +// if EXPR begin conditional block +// elif EXPR alternative within current conditional block +// else final alternative within current conditional block +// endif end conditional block +func Conditional(p *Params) error { + switch p.Name { + case "if": + case "elif": + case "else": + case "endif": + } + return nil } diff --git a/metacmd/descs.go b/metacmd/descs.go new file mode 100644 index 00000000000..dc0600bbdf9 --- /dev/null +++ b/metacmd/descs.go @@ -0,0 +1,161 @@ +package metacmd + +// Code generated by gen.go. DO NOT EDIT. + +import ( + "github.com/xo/usql/text" +) + +// sections are the command description sections. +var sections = []string{ + "General", + "Help", + "Query Execute", + "Query Buffer", + "Informational", + "Variables", + "Connection", + "Conditional", + "Input/Output", + "Transaction", + "Operating System/Environment", +} + +// descs are the command descriptions. +var descs [][]desc + +// cmds are the command lookup map. +var cmds map[string]func(*Params) error + +func init() { + descs = [][]desc{ + // General + { + {"q", "", "quit " + text.CommandName + "", Quit, false}, + {"quit", "", "alias for \\q", Quit, true}, + {"copyright", "", "show " + text.CommandName + " usage and distribution terms", Copyright, false}, + {"drivers", "", "display information about available database drivers", Drivers, false}, + }, + // Help + { + {"?", "[commands]", "show help on meta (backslash) commands", Question, false}, + {"?", "options", "show help on " + text.CommandName + " command-line options", Question, false}, + {"?", "variables", "show help on special " + text.CommandName + " variables", Question, false}, + }, + // Query Execute + { + {"g", "[(OPTIONS)] [FILE] or ;", "execute query (and send results to file or |pipe)", Execute, false}, + {"go", "[(OPTIONS)] [FILE]", "alias for \\g", Execute, false}, + {"G", "[(OPTIONS)] [FILE]", "as \\g, but forces vertical output mode", Execute, false}, + {"ego", "[(OPTIONS)] [FILE]", "alias for \\G", Execute, false}, + {"gx", "[(OPTIONS)] [FILE]", "as \\g, but forces expanded output mode", Execute, false}, + {"gexec", "", "execute query and execute each value of the result", Execute, false}, + {"gset", "[PREFIX]", "execute query and store results in " + text.CommandName + " variables", Execute, false}, + {"crosstabview", "[(OPTIONS)] [COLUMNS]", "execute query and display results in crosstab", Execute, false}, + {"chart", "CHART [(OPTIONS)]", "execute query and display results as a chart", Execute, false}, + {"watch", "[(OPTIONS)] [DURATION]", "execute query every specified interval", Execute, false}, + {"bind", "[PARAM]...", "set query parameters", Bind, false}, + }, + // Query Buffer + { + {"e", "[FILE] [LINE]", "edit the query buffer (or file) with external editor", Edit, false}, + {"edit", "[-exec]", "edit the query (or exec) buffer", Edit, false}, + {"p", "", "show the contents of the query buffer", Print, false}, + {"print", "", "alias for \\p", Print, true}, + {"raw", "", "show the raw (non-interpolated) contents of the query buffer", Print, false}, + {"exec", "", "show the contents of the exec buffer", Print, false}, + {"r", "", "reset (clear) the query buffer", Reset, false}, + {"reset", "", "alias for \\r", Reset, true}, + {"w", "FILE", "write query buffer to file", Write, false}, + {"write", "", "alias for \\w", Write, true}, + }, + // Informational + { + {"d[S+]", "[NAME]", "list tables, views, and sequences or describe table, view, sequence, or index", Describe, false}, + {"da[S+]", "[PATTERN]", "list aggregates", Describe, false}, + {"df[S+]", "[PATTERN]", "list functions", Describe, false}, + {"di[S+]", "[PATTERN]", "list indexes", Describe, false}, + {"dm[S+]", "[PATTERN]", "list materialized views", Describe, false}, + {"dn[S+]", "[PATTERN]", "list schemas", Describe, false}, + {"dp[S]", "[PATTERN]", "list table, view, and sequence access privileges", Describe, false}, + {"ds[S+]", "[PATTERN]", "list sequences", Describe, false}, + {"dt[S+]", "[PATTERN]", "list tables", Describe, false}, + {"dv[S+]", "[PATTERN]", "list views", Describe, false}, + {"l[+]", "", "list databases", Describe, false}, + {"ss[+]", "[TABLE|QUERY] [k]", "show stats for a table or a query", Stats, false}, + }, + // Variables + { + {"prompt", "[-TYPE] VAR [PROMPT]", "prompt user to set variable", Prompt, false}, + {"set", "[NAME [VALUE]]", "set internal variable, or list all if no parameters", Set, false}, + {"unset", "NAME", "unset (delete) internal variable", Unset, false}, + {"pset", "[NAME [VALUE]]", "set table output option", SetPrint, false}, + {"a", "", "toggle between unaligned and aligned output mode", SetPrint, false}, + {"C", "[TITLE]", "set table title, or unset if none", SetPrint, false}, + {"f", "[SEPARATOR]", "show or set field separator for unaligned query output", SetPrint, false}, + {"H", "", "toggle HTML output mode", SetPrint, false}, + {"T", "[ATTRIBUTES]", "set HTML
tag attributes, or unset if none", SetPrint, false}, + {"t", "[on|off]", "show only rows", SetPrint, false}, + {"x", "[on|off|auto]", "toggle expanded output", SetPrint, false}, + }, + // Connection + { + {"c", "URL", "connect to database URL", Connect, false}, + {"c", "DRIVER PARAMS...", "connect to database with driver and parameters", Connect, false}, + {"connect", "", "alias for \\c", Connect, true}, + {"cset", "", "show named connections", SetConn, false}, + {"cset", "NAME URL", "set named connection to URL", SetConn, false}, + {"cset", "NAME DRIVER PARAMS...", "set named connection for database driver and parameters", SetConn, false}, + {"Z", "", "close database connection", Disconnect, false}, + {"disconnect", "", "alias for \\Z", Disconnect, true}, + {"password", "[USER]", "change password for user", Password, false}, + {"passwd", "", "alias for \\password", Password, true}, + {"conninfo", "", "display information about the current database connection", ConnectionInfo, false}, + }, + // Conditional + { + {"if", "EXPR", "begin conditional block", Conditional, false}, + {"elif", "EXPR", "alternative within current conditional block", Conditional, false}, + {"else", "", "final alternative within current conditional block", Conditional, false}, + {"endif", "", "end conditional block", Conditional, false}, + }, + // Input/Output + { + {"copy", "SRC DST QUERY TABLE", "copy query from source url to table on destination url", Copy, false}, + {"copy", "SRC DST QUERY TABLE(A,...)", "copy query from source url to columns of table on destination url", Copy, false}, + {"echo", "[-n] [MESSAGE]...", "write message to standard output (-n for no newline)", Echo, false}, + {"qecho", "[-n] [MESSAGE]...", "write message to \\o output stream (-n for no newline)", Echo, false}, + {"warn", "[-n] [MESSAGE]...", "write message to standard error (-n for no newline)", Echo, false}, + {"o", "[FILE]", "send all query results to file or |pipe", Out, false}, + {"out", "", "alias for \\o", Out, true}, + {"i", "FILE", "execute commands from file", Include, false}, + {"include", "", "alias for \\i", Include, true}, + {"ir", "FILE", "as \\i, but relative to location of current script", Include, false}, + {"include_relative", "", "alias for \\ir", Include, true}, + }, + // Transaction + { + {"begin", "", "begin a transaction", Transact, false}, + {"begin", "-read-only ISOLATION", "begin a transaction with isolation level", Transact, false}, + {"commit", "", "commit current transaction", Transact, false}, + {"rollback", "", "rollback (abort) current transaction", Transact, false}, + {"abort", "", "alias for \\rollback", Transact, true}, + }, + // Operating System/Environment + { + {"cd", "[DIR]", "change the current working directory", Chdir, false}, + {"getenv", "VARNAME ENVVAR", "fetch environment variable", Getenv, false}, + {"setenv", "NAME [VALUE]", "set or unset environment variable", Setenv, false}, + {"!", "[COMMAND]", "execute command in shell or start interactive shell", Shell, false}, + {"timing", "[on|off]", "toggle timing of commands", Timing, false}, + }, + } + cmds = make(map[string]func(*Params) error) + for i := range sections { + for _, desc := range descs[i] { + for _, n := range desc.Names() { + cmds[n] = desc.Func + } + } + } +} diff --git a/metacmd/metacmd.go b/metacmd/metacmd.go index 0960260fd3f..62b2e3d0b53 100644 --- a/metacmd/metacmd.go +++ b/metacmd/metacmd.go @@ -3,95 +3,272 @@ package metacmd import ( + "context" + "database/sql" + "fmt" + "io" + "os/user" + "strings" + "time" + + "github.com/mattn/go-runewidth" + "github.com/xo/dburl" + "github.com/xo/usql/drivers" + "github.com/xo/usql/drivers/metadata" + "github.com/xo/usql/env" + "github.com/xo/usql/rline" "github.com/xo/usql/stmt" "github.com/xo/usql/text" ) -// Metacmd represents a command and associated meta information about it. -type Metacmd uint +// Handler is the shared interface for a command handler. +type Handler interface { + // IO handles the handler's IO. + IO() rline.IO + // User returns the current user. + User() *user.User + // URL returns the current database URL. + URL() *dburl.URL + // DB returns the current database connection. + DB() drivers.DB + // LastExec returns the last executed query. + LastExec() string + // LastPrint returns the last executed printable query. + LastPrint() string + // LastRaw returns the last raw (non-interpolated) query. + LastRaw() string + // Buf returns the current query buffer. + Buf() *stmt.Stmt + // Reset resets the last and current query buffer. + Reset([]rune) + // Bind binds query parameters. + Bind([]interface{}) + // Open opens a database connection. + Open(context.Context, ...string) error + // Close closes the current database connection. + Close() error + // ChangePassword changes the password for a user. + ChangePassword(string) (string, error) + // ReadVar reads a variable of a specified type. + ReadVar(string, string) (string, error) + // Include includes a file. + Include(string, bool) error + // Begin begins a transaction. + Begin(*sql.TxOptions) error + // Commit commits the current transaction. + Commit() error + // Rollback aborts the current transaction. + Rollback() error + // Highlight highlights the statement. + Highlight(io.Writer, string) error + // GetTiming mode. + GetTiming() bool + // SetTiming mode. + SetTiming(bool) + // GetOutput writer. + GetOutput() io.Writer + // SetOutput writer. + SetOutput(io.WriteCloser) + // MetadataWriter retrieves the metadata writer for the handler. + MetadataWriter(context.Context) (metadata.Writer, error) + // Print formats according to a format specifier and writes to handler's standard output. + Print(string, ...interface{}) +} + +// Dump writes the command descriptions to w, separated by section. +func Dump(w io.Writer, hidden bool) error { + n := 0 + for i := range sections { + for _, desc := range descs[i] { + if !desc.Hidden || hidden { + n = max(n, runewidth.StringWidth(desc.Name)+1+runewidth.StringWidth(desc.Params)) + } + } + } + for i, s := range sections { + if i != 0 { + fmt.Fprintln(w) + } + fmt.Fprintln(w, s) + for _, desc := range descs[i] { + if !desc.Hidden || hidden { + _, _ = fmt.Fprintf(w, " \\%- *s %s\n", n, desc.Name+" "+desc.Params, desc.Desc) + } + } + } + return nil +} // Decode converts a command name (or alias) into a Runner. -func Decode(name string, params *stmt.Params) (Runner, error) { - mc, ok := cmdMap[name] +func Decode(name string, params *stmt.Params) (func(Handler) (Option, error), error) { + f, ok := cmds[name] if !ok || name == "" { return nil, text.ErrUnknownCommand } - cmd := cmds[mc] - return RunnerFunc(func(h Handler) (Option, error) { + return func(h Handler) (Option, error) { p := &Params{ Handler: h, Name: name, Params: params, } - err := cmd.Process(p) + err := f(p) return p.Option, err - }), nil + }, nil +} + +// Params wraps metacmd parameters. +type Params struct { + // Handler is the process handler. + Handler Handler + // Name is the name of the metacmd. + Name string + // Params are the actual statement parameters. + Params *stmt.Params + // Option contains resulting command execution options. + Option Option +} + +// Next returns the next command parameter, using env.Untick. +func (p *Params) Next(exec bool) (string, error) { + v, _, err := p.Params.Next(env.Untick( + p.Handler.User(), + env.Vars(), + exec, + )) + if err != nil { + return "", err + } + return v, nil +} + +// NextOK returns the next command parameter, using env.Untick. +func (p *Params) NextOK(exec bool) (string, bool, error) { + return p.Params.Next(env.Untick( + p.Handler.User(), + env.Vars(), + exec, + )) +} + +// NextOpt returns the next command parameter, using env.Untick. Returns true +// when the value is prefixed with a "-", along with the value sans the "-" +// prefix. Otherwise returns false and the value. +func (p *Params) NextOpt(exec bool) (string, bool, error) { + v, err := p.Next(exec) + switch { + case err != nil: + return "", false, err + case len(v) > 0 && v[0] == '-': + return v[1:], true, nil + } + return v, false, nil +} + +// All gets all remaining command parameters using env.Untick. +func (p *Params) All(exec bool) ([]string, error) { + return p.Params.All(env.Untick( + p.Handler.User(), + env.Vars(), + exec, + )) +} + +// Raw returns the remaining command parameters as a raw string. +// +// Note: no other processing is done to interpolate variables or to decode +// string values. +func (p *Params) Raw() string { + return p.Params.Raw() +} + +// Option contains parsed result options of a metacmd. +type Option struct { + // Quit instructs the handling code to quit. + Quit bool + // Exec informs the handling code of the type of execution. + Exec ExecType + // Params are accompanying string parameters for execution. + Params map[string]string + // Crosstab are the crosstab column parameters. + Crosstab []string + // Watch is the watch duration interval. + Watch time.Duration +} + +func (opt *Option) ParseParams(params []string, defaultKey string) error { + if opt.Params == nil { + opt.Params = make(map[string]string, len(params)) + } + formatOpts := false + for i, param := range params { + if len(param) == 0 { + continue + } + if !formatOpts { + if param[0] == '(' { + formatOpts = true + } else { + opt.Params[defaultKey] = strings.Join(params[i:], " ") + return nil + } + } + parts := strings.SplitN(param, "=", 2) + if len(parts) == 1 { + return text.ErrInvalidFormatOption + } + opt.Params[strings.TrimLeft(parts[0], "(")] = strings.TrimRight(parts[1], ")") + if formatOpts && param[len(param)-1] == ')' { + formatOpts = false + } + } + return nil } -// Command types. +// ExecType represents the type of execution requested. +type ExecType int + const ( - // None is an empty command. - None Metacmd = iota - // Question is question meta command (\?) - Question - // Quit is the quit meta command (\?). - Quit - // Copyright is the copyright meta command (\copyright). - Copyright - // Connect is the connect meta command (\c, \connect). - Connect - // SetConnVar is the set conn var command (\cset). - SetConnVar - // Copy is the copy meta command (\copy). - Copy - // Disconnect is the disconnect meta command (\Z). - Disconnect - // Password is the change password meta command (\password). - Password - // ConnectionInfo is the connection info meta command (\conninfo). - ConnectionInfo - // Drivers is the driver info meta command (\drivers). - Drivers - // Describe is the describe meta command (\d and variants). - Describe - // Exec is the execute meta command (\g and variants). - Exec - // Bind is the bind meta command (\bind). - Bind - // Edit is the edit query buffer meta command (\e). - Edit - // Print is the print query buffer meta command (\p, \print, \raw). - Print - // Reset is the reset query buffer meta command (\r, \reset). - Reset - // Echo is the echo meta command (\echo, \warn, \qecho). - Echo - // Write is the write meta command (\w). - Write - // ChangeDir is the system change directory meta command (\cd). - ChangeDir - // GetEnv is the system get environment variable meta command (\getenv). - GetEnv - // SetEnv is the system set environment variable meta command (\setenv). - SetEnv - // Shell is the system shell exec meta command (\!). - Shell - // Out is the switch output meta command (\o). - Out - // Include is the system include file meta command (\i and variants). - Include - // Transact is the transaction meta command (\begin, \commit, \rollback). - Transact - // Prompt is the variable prompt meta command (\prompt). - Prompt - // SetVar is the set variable meta command (\set). - SetVar - // Unset is the variable unset meta command (\unset). - Unset - // SetPrintVar is the set print variable meta commands (\pset, \a, \C, \f, \H, \t, \T, \x). - SetPrintVar - // Timing is the timing meta command (\timing). - Timing - // Stats is the show stats meta command (\ss and variants). - Stats + // ExecNone indicates no execution. + ExecNone ExecType = iota + // ExecOnly indicates plain execution only (\g). + ExecOnly + // ExecPipe indicates execution and piping results (\g |file) + ExecPipe + // ExecSet indicates execution and setting the resulting columns as + // variables (\gset). + ExecSet + // ExecExec indicates execution and executing the resulting rows (\gexec). + ExecExec + // ExecCrosstab indicates execution using crosstabview (\crosstabview). + ExecCrosstab + // ExecChart indicates execution using chart (\chart). + ExecChart + // ExecWatch indicates repeated execution with a fixed time interval. + ExecWatch ) + +// desc wraps a meta command description. +type desc struct { + Name string + Params string + Desc string + Func func(*Params) error + Hidden bool +} + +// Names returns the names for the command. +func (d desc) Names() []string { + switch i := strings.Index(d.Name, "["); { + case i == -1: + return []string{d.Name} + case !strings.HasSuffix(d.Name, "]"): + panic(fmt.Sprintf("invalid command %q", d.Name)) + default: + name := d.Name[:i] + v := []string{name} + for _, s := range d.Name[i+1 : len(d.Name)-1] { + v = append(v, name+string(s)) + } + return v + } +} diff --git a/metacmd/section.go b/metacmd/section.go deleted file mode 100644 index 0fbd7ca6d8f..00000000000 --- a/metacmd/section.go +++ /dev/null @@ -1,101 +0,0 @@ -package metacmd - -import ( - "fmt" - "io" - "strings" -) - -// Desc holds information about a command or alias description. -type Desc struct { - Name string - Params string - Desc string -} - -// Section is a meta command section. -type Section string - -// Meta command section types. -const ( - SectionGeneral Section = "General" - SectionQueryExecute Section = "Query Execute" - SectionQueryBuffer Section = "Query Buffer" - SectionHelp Section = "Help" - SectionTransaction Section = "Transaction" - SectionInputOutput Section = "Input/Output" - SectionInformational Section = "Informational" - SectionFormatting Section = "Formatting" - SectionConnection Section = "Connection" - SectionOperatingSystem Section = "Operating System" - SectionVariables Section = "Variables" - // SectionLargeObjects Section = "Large Objects" -) - -// String satisfies stringer. -func (s Section) String() string { - return string(s) -} - -// SectionOrder is the order of sections to display via Listing. -var SectionOrder = []Section{ - SectionGeneral, - SectionQueryExecute, - SectionQueryBuffer, - SectionHelp, - SectionInputOutput, - SectionInformational, - SectionFormatting, - SectionTransaction, - SectionConnection, - SectionOperatingSystem, - SectionVariables, -} - -// Listing writes the formatted command listing to w, separated into different -// sections for all known commands. -func Listing(w io.Writer) { - sectionDescs := make(map[Section][][]string, len(SectionOrder)) - var plen int - for _, section := range SectionOrder { - var descs [][]string - for _, c := range sectMap[section] { - cmd := cmds[c] - for i, d := range cmd.Descs { - if d.Desc == "" && d.Params == "" { - continue - } - s, opts := optText(cmd.Descs[i]) - descs, plen = add(descs, ` \`+strings.TrimSpace(d.Name)+opts, s, plen) - } - } - sectionDescs[section] = descs - } - for i, section := range SectionOrder { - if i != 0 { - fmt.Fprintln(w) - } - fmt.Fprintln(w, section) - for _, line := range sectionDescs[section] { - fmt.Fprintln(w, rpad(line[0], plen), "", line[1]) - } - } -} - -// rpad right pads a string. -func rpad(s string, l int) string { - return s + strings.Repeat(" ", l-len(s)) -} - -// add adds b, c to a, returning the max of pad or len(b). -func add(a [][]string, b, c string, pad int) ([][]string, int) { - return append(a, []string{b, c}), max(pad, len(b)) -} - -// optText returns a string and the opt text. -func optText(desc Desc) (string, string) { - if desc.Params != "" { - return desc.Desc, " " + desc.Params - } - return desc.Desc, desc.Params -} diff --git a/metacmd/types.go b/metacmd/types.go deleted file mode 100644 index 21427545e27..00000000000 --- a/metacmd/types.go +++ /dev/null @@ -1,219 +0,0 @@ -package metacmd - -import ( - "context" - "database/sql" - "io" - "os/user" - "strings" - "time" - - "github.com/xo/dburl" - "github.com/xo/usql/drivers" - "github.com/xo/usql/drivers/metadata" - "github.com/xo/usql/env" - "github.com/xo/usql/rline" - "github.com/xo/usql/stmt" - "github.com/xo/usql/text" -) - -// Handler is the shared interface for a command handler. -type Handler interface { - // IO handles the handler's IO. - IO() rline.IO - // User returns the current user. - User() *user.User - // URL returns the current database URL. - URL() *dburl.URL - // DB returns the current database connection. - DB() drivers.DB - // LastExec returns the last executed query. - LastExec() string - // LastPrint returns the last executed printable query. - LastPrint() string - // LastRaw returns the last raw (non-interpolated) query. - LastRaw() string - // Buf returns the current query buffer. - Buf() *stmt.Stmt - // Reset resets the last and current query buffer. - Reset([]rune) - // Bind binds query parameters. - Bind([]interface{}) - // Open opens a database connection. - Open(context.Context, ...string) error - // Close closes the current database connection. - Close() error - // ChangePassword changes the password for a user. - ChangePassword(string) (string, error) - // ReadVar reads a variable of a specified type. - ReadVar(string, string) (string, error) - // Include includes a file. - Include(string, bool) error - // Begin begins a transaction. - Begin(*sql.TxOptions) error - // Commit commits the current transaction. - Commit() error - // Rollback aborts the current transaction. - Rollback() error - // Highlight highlights the statement. - Highlight(io.Writer, string) error - // GetTiming mode. - GetTiming() bool - // SetTiming mode. - SetTiming(bool) - // GetOutput writer. - GetOutput() io.Writer - // SetOutput writer. - SetOutput(io.WriteCloser) - // MetadataWriter retrieves the metadata writer for the handler. - MetadataWriter(context.Context) (metadata.Writer, error) - // Print formats according to a format specifier and writes to handler's standard output. - Print(string, ...interface{}) -} - -// Runner is a runner interface type. -type Runner interface { - Run(Handler) (Option, error) -} - -// RunnerFunc is a type wrapper for a single func satisfying Runner.Run. -type RunnerFunc func(Handler) (Option, error) - -// Run satisfies the Runner interface. -func (f RunnerFunc) Run(h Handler) (Option, error) { - return f(h) -} - -// ExecType represents the type of execution requested. -type ExecType int - -const ( - // ExecNone indicates no execution. - ExecNone ExecType = iota - // ExecOnly indicates plain execution only (\g). - ExecOnly - // ExecPipe indicates execution and piping results (\g |file) - ExecPipe - // ExecSet indicates execution and setting the resulting columns as - // variables (\gset). - ExecSet - // ExecExec indicates execution and executing the resulting rows (\gexec). - ExecExec - // ExecCrosstab indicates execution using crosstabview (\crosstabview). - ExecCrosstab - // ExecChart indicates execution using chart (\chart). - ExecChart - // ExecWatch indicates repeated execution with a fixed time interval. - ExecWatch -) - -// Option contains parsed result options of a metacmd. -type Option struct { - // Quit instructs the handling code to quit. - Quit bool - // Exec informs the handling code of the type of execution. - Exec ExecType - // Params are accompanying string parameters for execution. - Params map[string]string - // Crosstab are the crosstab column parameters. - Crosstab []string - // Watch is the watch duration interval. - Watch time.Duration -} - -func (opt *Option) ParseParams(params []string, defaultKey string) error { - if opt.Params == nil { - opt.Params = make(map[string]string, len(params)) - } - formatOptions := false - for i, param := range params { - if len(param) == 0 { - continue - } - if !formatOptions { - if param[0] == '(' { - formatOptions = true - } else { - opt.Params[defaultKey] = strings.Join(params[i:], " ") - return nil - } - } - parts := strings.SplitN(param, "=", 2) - if len(parts) == 1 { - return text.ErrInvalidFormatOption - } - opt.Params[strings.TrimLeft(parts[0], "(")] = strings.TrimRight(parts[1], ")") - if formatOptions && param[len(param)-1] == ')' { - formatOptions = false - } - } - return nil -} - -// Params wraps metacmd parameters. -type Params struct { - // Handler is the process handler. - Handler Handler - // Name is the name of the metacmd. - Name string - // Params are the actual statement parameters. - Params *stmt.Params - // Option contains resulting command execution options. - Option Option -} - -// Get returns the next command parameter, using env.Unquote to decode quoted -// strings. -func (p *Params) Get(exec bool) (string, error) { - _, v, err := p.Params.Get(env.Unquote( - p.Handler.User(), - exec, - env.All(), - )) - if err != nil { - return "", err - } - return v, nil -} - -// GetOK returns the next command parameter, using env.Unquote to decode quoted -// strings. -func (p *Params) GetOK(exec bool) (bool, string, error) { - return p.Params.Get(env.Unquote( - p.Handler.User(), - exec, - env.All(), - )) -} - -// GetOptional returns the next command parameter, using env.Unquote to decode -// quoted strings, returns true when the value is prefixed with a "-", along -// with the value sans the "-" prefix. Otherwise returns false and the value. -func (p *Params) GetOptional(exec bool) (bool, string, error) { - v, err := p.Get(exec) - if err != nil { - return false, "", err - } - if len(v) > 0 && v[0] == '-' { - return true, v[1:], nil - } - return false, v, nil -} - -// GetAll gets all remaining command parameters using env.Unquote to decode -// quoted strings. -func (p *Params) GetAll(exec bool) ([]string, error) { - return p.Params.GetAll(env.Unquote( - p.Handler.User(), - exec, - env.All(), - )) -} - -// GetRaw gets the remaining command parameters as a raw string. -// -// Note: no other processing is done to interpolate variables or to decode -// string values. -func (p *Params) GetRaw() string { - return p.Params.GetRaw() -} diff --git a/run.go b/run.go index 3fb181a5c2e..efdaa90b851 100644 --- a/run.go +++ b/run.go @@ -1,11 +1,9 @@ -//go:debug x509negativeserial=1 package main import ( "context" "errors" "fmt" - "io" "os" "os/user" "path/filepath" @@ -21,7 +19,6 @@ import ( "github.com/xo/dburl" "github.com/xo/usql/env" "github.com/xo/usql/handler" - "github.com/xo/usql/metacmd" "github.com/xo/usql/rline" "github.com/xo/usql/text" ) @@ -143,6 +140,7 @@ func New(cliargs []string) ContextExecutor { c.SetVersionTemplate("{{ .Name }} {{ .Version }}\n") c.SetArgs(cliargs[1:]) c.SetUsageTemplate(text.UsageTemplate) + text.UsageString = c.UsageString flags := c.Flags() flags.SortFlags = false @@ -169,36 +167,30 @@ func New(cliargs []string) ContextExecutor { flags.BoolVarP(&args.ForcePassword, "password", "W", false, "force password prompt (should happen automatically)") flags.BoolVarP(&args.SingleTransaction, "single-transaction", "1", false, "execute as a single transaction (if non-interactive)") - ss := func(v *[]string, name, short, usage, placeholder string, vals ...string) { - f := flags.VarPF(vs{v, vals, placeholder}, name, short, usage) - if placeholder == "" { - f.DefValue, f.NoOptDefVal = "true", "true" - } - } // set - ss(&args.Vars, "set", "v", `set variable NAME to VALUE (see \set command, aliases: --var --variable)`, "NAME=VALUE") - ss(&args.Vars, "var", "", "set variable NAME to VALUE", "NAME=VALUE") - ss(&args.Vars, "variable", "", "set variable NAME to VALUE", "NAME=VALUE") + sf(flags, &args.Vars, "set", "v", `set variable NAME to VALUE (see \set command, aliases: --var --variable)`, "NAME=VALUE") + sf(flags, &args.Vars, "var", "", "set variable NAME to VALUE", "NAME=VALUE") + sf(flags, &args.Vars, "variable", "", "set variable NAME to VALUE", "NAME=VALUE") // cset - ss(&args.Cvars, "cset", "N", `set named connection NAME to DSN (see \cset command)`, "NAME=DSN") + sf(flags, &args.Cvars, "cset", "N", `set named connection NAME to DSN (see \cset command)`, "NAME=DSN") // pset - ss(&args.Pvars, "pset", "P", `set printing option VAR to ARG (see \pset command)`, "VAR=ARG") + sf(flags, &args.Pvars, "pset", "P", `set printing option VAR to ARG (see \pset command)`, "VAR=ARG") // pset flags - ss(&args.Pvars, "field-separator", "F", `field separator for unaligned and CSV output (default "|" and ",")`, "FIELD-SEPARATOR", "fieldsep=%q", "csv_fieldsep=%q") - ss(&args.Pvars, "record-separator", "R", `record separator for unaligned and CSV output (default \n)`, "RECORD-SEPARATOR", "recordsep=%q") - ss(&args.Pvars, "table-attr", "T", "set HTML table tag attributes (e.g., width, border)", "TABLE-ATTR", "tableattr=%q") + sf(flags, &args.Pvars, "field-separator", "F", `field separator for unaligned and CSV output (default "|" and ",")`, "FIELD-SEPARATOR", "fieldsep=%q", "csv_fieldsep=%q") + sf(flags, &args.Pvars, "record-separator", "R", `record separator for unaligned and CSV output (default \n)`, "RECORD-SEPARATOR", "recordsep=%q") + sf(flags, &args.Pvars, "table-attr", "T", "set HTML table tag attributes (e.g., width, border)", "TABLE-ATTR", "tableattr=%q") // pset bools - ss(&args.Pvars, "no-align", "A", "unaligned table output mode", "", "format=unaligned") - ss(&args.Pvars, "html", "H", "HTML table output mode", "", "format=html") - ss(&args.Pvars, "tuples-only", "t", "print rows only", "", "tuples_only=on") - ss(&args.Pvars, "expanded", "x", "turn on expanded table output", "", "expanded=on") - ss(&args.Pvars, "field-separator-zero", "z", "set field separator for unaligned and CSV output to zero byte", "", "fieldsep_zero=on") - ss(&args.Pvars, "record-separator-zero", "0", "set record separator for unaligned and CSV output to zero byte", "", "recordsep_zero=on") - ss(&args.Pvars, "json", "J", "JSON output mode", "", "format=json") - ss(&args.Pvars, "csv", "C", "CSV output mode", "", "format=csv") - ss(&args.Pvars, "vertical", "G", "vertical output mode", "", "format=vertical") + sf(flags, &args.Pvars, "no-align", "A", "unaligned table output mode", "", "format=unaligned") + sf(flags, &args.Pvars, "html", "H", "HTML table output mode", "", "format=html") + sf(flags, &args.Pvars, "tuples-only", "t", "print rows only", "", "tuples_only=on") + sf(flags, &args.Pvars, "expanded", "x", "turn on expanded table output", "", "expanded=on") + sf(flags, &args.Pvars, "field-separator-zero", "z", "set field separator for unaligned and CSV output to zero byte", "", "fieldsep_zero=on") + sf(flags, &args.Pvars, "record-separator-zero", "0", "set record separator for unaligned and CSV output to zero byte", "", "recordsep_zero=on") + sf(flags, &args.Pvars, "json", "J", "JSON output mode", "", "format=json") + sf(flags, &args.Pvars, "csv", "C", "CSV output mode", "", "format=csv") + sf(flags, &args.Pvars, "vertical", "G", "vertical output mode", "", "format=vertical") // set bools - ss(&args.Vars, "quiet", "q", "run quietly (no messages, only query output)", "", "QUIET=on") + sf(flags, &args.Vars, "quiet", "q", "run quietly (no messages, only query output)", "", "QUIET=on") // app config _ = flags.StringP("config", "", "", "config file") @@ -221,14 +213,6 @@ func New(cliargs []string) ContextExecutor { flags.Lookup(name).Hidden = true } - // expose to metacmd - metacmd.Usage = func(w io.Writer, banner bool) { - s := c.UsageString() - if banner { - s = text.Short() + "\n\n" + s - } - _, _ = w.Write([]byte(s)) - } return c } @@ -259,7 +243,7 @@ func Run(ctx context.Context, args *Args) error { if s, _ := env.Getenv(text.CommandUpper()+"_TERM_GRAPHICS", "TERM_GRAPHICS"); s != "" { typ = s } - if err := env.Set("TERM_GRAPHICS", typ); err != nil { + if err := env.Vars().Set("TERM_GRAPHICS", typ); err != nil { return err } } @@ -276,9 +260,9 @@ func Run(ctx context.Context, args *Args) error { // set vars for _, v := range args.Vars { if i := strings.Index(v, "="); i != -1 { - _ = env.Set(v[:i], v[i+1:]) + _ = env.Vars().Set(v[:i], v[i+1:]) } else { - _ = env.Unset(v) + _ = env.Vars().Unset(v) } } // set cvars @@ -286,15 +270,15 @@ func Run(ctx context.Context, args *Args) error { if i := strings.Index(v, "="); i != -1 { s := v[i+1:] if c := s[0]; c == '\'' || c == '"' { - if s, err = env.Dequote(s, c); err != nil { + if s, err = env.Unquote(s); err != nil { return err } } - if err = env.Cset(v[:i], s); err != nil { + if err = env.Vars().SetConn(v[:i], s); err != nil { return err } } else { - if err = env.Cset(v, ""); err != nil { + if err = env.Vars().SetConn(v, ""); err != nil { return err } } @@ -304,15 +288,15 @@ func Run(ctx context.Context, args *Args) error { if i := strings.Index(v, "="); i != -1 { s := v[i+1:] if c := s[0]; c == '\'' || c == '"' { - if s, err = env.Dequote(s, c); err != nil { + if s, err = env.Unquote(s); err != nil { return err } } - if _, err = env.Pset(v[:i], s); err != nil { + if _, err = env.Vars().SetPrint(v[:i], s); err != nil { return err } } else { - if _, err = env.Ptoggle(v, ""); err != nil { + if _, err = env.Vars().TogglePrint(v, ""); err != nil { return err } } @@ -518,19 +502,19 @@ func chartsFS(v *viper.Viper) (billy.Filesystem, error) { return fs.Chroot(chartsPath) } -// setConn sets a named connection. -func setConn(name string, v interface{}) error { - switch x := v.(type) { +// setConn sets a connection name to a DSN built from the passed value. +func setConn(name string, value interface{}) error { + switch x := value.(type) { case string: - return env.Cset(name, x) + return env.Vars().SetConn(name, x) case []interface{}: - return env.Cset(name, convSlice(x)...) + return env.Vars().SetConn(name, convSlice(x)...) case map[string]interface{}: urlstr, err := dburl.BuildURL(x) if err != nil { return err } - return env.Cset(name, urlstr) + return env.Vars().SetConn(name, urlstr) } return text.ErrInvalidConfig } @@ -555,6 +539,14 @@ func runCommandOrFiles(h *handler.Handler, commandsOrFiles []CommandOrFile) func } } +// sf sets a flag. +func sf(flags *pflag.FlagSet, v *[]string, name, short, usage, placeholder string, vals ...string) { + f := flags.VarPF(vs{v, vals, placeholder}, name, short, usage) + if placeholder == "" { + f.DefValue, f.NoOptDefVal = "true", "true" + } +} + // convSlice converts a generic slice to a string slice. func convSlice(v []interface{}) []string { s := make([]string, len(v)) diff --git a/stmt/params.go b/stmt/params.go index 7bdaac93efd..2e3c9e30a5b 100644 --- a/stmt/params.go +++ b/stmt/params.go @@ -12,8 +12,8 @@ type Params struct { Len int } -// DecodeParams decodes command parameters. -func DecodeParams(params string) *Params { +// NewParams creates command parameters. +func NewParams(params string) *Params { r := []rune(params) return &Params{ R: r, @@ -21,21 +21,21 @@ func DecodeParams(params string) *Params { } } -// GetRaw reads all remaining runes. No substitution or whitespace removal is +// Raw reads all remaining runes. No substitution or whitespace removal is // performed. -func (p *Params) GetRaw() string { +func (p *Params) Raw() string { s := string(p.R) p.R, p.Len = p.R[:0], 0 return s } -// Get reads the next command parameter using the provided substitution func. +// Next reads the next command parameter using the provided substitution func. // True indicates there are runes remaining in the command parameters to // process. -func (p *Params) Get(unquote func(string, bool) (bool, string, error)) (bool, string, error) { +func (p *Params) Next(unquote func(string, bool) (string, bool, error)) (string, bool, error) { i, _ := findNonSpace(p.R, 0, p.Len) if i >= p.Len { - return false, "", nil + return "", false, nil } var ok bool var quote rune @@ -50,10 +50,9 @@ loop: if !ok { break loop } - ok, z, err := unquote(string(p.R[start:i+1]), false) - switch { + switch z, ok, err := unquote(string(p.R[start:i+1]), false); { case err != nil: - return false, "", err + return "", false, err case ok: p.R, p.Len = substitute(p.R, start, p.Len, i-start+1, z) i = start + len([]rune(z)) - 1 @@ -64,10 +63,9 @@ loop: quote = c case c == ':' && next != ':': if v := readVar(p.R, i, p.Len, next); v != nil { - ok, z, err := unquote(v.Name, true) - switch { + switch z, ok, err := unquote(v.Name, true); { case err != nil: - return false, "", err + return "", false, err case ok || v.Quote == '?': p.R, p.Len = v.Substitute(p.R, z, ok) i += v.Len - 1 @@ -80,27 +78,35 @@ loop: } } if quote != 0 { - return false, "", text.ErrUnterminatedQuotedString + return "", false, text.ErrUnterminatedQuotedString } - v := string(p.R[start:i]) + s := string(p.R[start:i]) p.R = p.R[i:] p.Len = len(p.R) - return true, v, nil + return s, true, nil } -// GetAll retrieves all remaining command parameters using the provided +// All retrieves all remaining command parameters using the provided // substitution func. Will return on the first encountered error. -func (p *Params) GetAll(unquote func(string, bool) (bool, string, error)) ([]string, error) { - var s []string +func (p *Params) All(unquote func(string, bool) (string, bool, error)) ([]string, error) { + var v []string +loop: for { - ok, v, err := p.Get(unquote) - if err != nil { - return s, err - } - if !ok { - break + switch s, ok, err := p.Next(unquote); { + case err != nil: + return v, err + case !ok: + break loop + default: + v = append(v, s) } - s = append(s, v) } - return s, nil + return v, nil +} + +// Arg retrieves the next argument, without decoding. +func (p *Params) Arg() (string, bool, error) { + return p.Next(func(s string, _ bool) (string, bool, error) { + return s, true, nil + }) } diff --git a/stmt/params_test.go b/stmt/params_test.go index 4297f3d58dc..091a9268aaf 100644 --- a/stmt/params_test.go +++ b/stmt/params_test.go @@ -10,10 +10,10 @@ import ( "github.com/xo/usql/text" ) -func TestDecodeParamsGetRaw(t *testing.T) { +func TestParamsGetRaw(t *testing.T) { const exp = ` 'a string' "another string" ` - p := DecodeParams(exp) - s := p.GetRaw() + p := NewParams(exp) + s := p.Raw() if s != exp { t.Errorf("expected %q, got: %q", exp, s) } @@ -22,7 +22,7 @@ func TestDecodeParamsGetRaw(t *testing.T) { t.Fatalf("expected no error, got: %v", err) } unquote := testUnquote(t, u) - switch ok, s, err := p.Get(unquote); { + switch s, ok, err := p.Next(unquote); { case err != nil: t.Fatalf("expected no error, got: %v", err) case s != "": @@ -30,7 +30,7 @@ func TestDecodeParamsGetRaw(t *testing.T) { case ok: t.Errorf("expected ok=false, got: %t", ok) } - switch v, err := p.GetAll(unquote); { + switch v, err := p.All(unquote); { case err != nil: t.Fatalf("expected no error, got: %v", err) case len(v) != 0: @@ -38,7 +38,7 @@ func TestDecodeParamsGetRaw(t *testing.T) { } } -func TestDecodeParamsGetAll(t *testing.T) { +func TestParamsGetAll(t *testing.T) { u, err := user.Current() if err != nil { t.Fatalf("expected no error, got: %v", err) @@ -102,7 +102,7 @@ func TestDecodeParamsGetAll(t *testing.T) { } for i, test := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { - vals, err := DecodeParams(test.s).GetAll(testUnquote(t, u)) + vals, err := NewParams(test.s).All(testUnquote(t, u)) if err != test.err { t.Fatalf("expected error %v, got: %v", test.err, err) } @@ -113,13 +113,13 @@ func TestDecodeParamsGetAll(t *testing.T) { } } -func testUnquote(t *testing.T, u *user.User) func(string, bool) (bool, string, error) { +func testUnquote(t *testing.T, u *user.User) func(string, bool) (string, bool, error) { t.Helper() - f := env.Unquote(u, false, env.Vars{ - "foo": "bar", - "型示": "yes", - }) - return func(s string, isvar bool) (bool, string, error) { + vars := env.NewVars() + vars.Set("foo", "bar") + vars.Set("型示", "yes") + f := env.Untick(u, vars, false) + return func(s string, isvar bool) (string, bool, error) { // t.Logf("test %d %q s: %q, isvar: %t", i, teststr, s, isvar) return f(s, isvar) } diff --git a/stmt/parse.go b/stmt/parse.go index f5f42c664a5..3e5e20ef60d 100644 --- a/stmt/parse.go +++ b/stmt/parse.go @@ -11,7 +11,7 @@ const prefixCount = 6 // maxVarNameLen is the maximum var name length. const maxVarNameLen = 128 -// grab grabs i from r, or returns 0 if i >= end. +// grab returns the i'th rune from r when i < end, otherwise 0. func grab(r []rune, i, end int) rune { if i < end { return r[i] @@ -22,7 +22,7 @@ func grab(r []rune, i, end int) rune { // findSpace finds first space rune in r, returning end if not found. func findSpace(r []rune, i, end int) (int, bool) { for ; i < end; i++ { - if IsSpaceOrControl(r[i]) { + if isSpaceOrControl(r[i]) { return i, true } } @@ -32,7 +32,7 @@ func findSpace(r []rune, i, end int) (int, bool) { // findNonSpace finds first non space rune in r, returning end if not found. func findNonSpace(r []rune, i, end int) (int, bool) { for ; i < end; i++ { - if !IsSpaceOrControl(r[i]) { + if !isSpaceOrControl(r[i]) { return i, true } } @@ -256,7 +256,7 @@ loop: } // add space when remaining runes begin with space, and previous // captured word did not - if sl := len(s); end > 0 && sl != 0 && IsSpaceOrControl(r[0]) && !IsSpaceOrControl(s[sl-1]) { + if sl := len(s); end > 0 && sl != 0 && isSpaceOrControl(r[0]) && !isSpaceOrControl(s[sl-1]) { s = append(s, ' ') } // end of statement, max words, or punctuation that can be ignored diff --git a/stmt/stmt.go b/stmt/stmt.go index 3cd21e43a0b..2cea5efc2cc 100644 --- a/stmt/stmt.go +++ b/stmt/stmt.go @@ -69,23 +69,23 @@ func (b *Stmt) PrintString() string { if b.Len == 0 { return "" } - i, s, z := 0, string(b.Buf), new(bytes.Buffer) + i, s, w := 0, string(b.Buf), new(bytes.Buffer) // deinterpolate vars for _, v := range b.Vars { if v.Quote != '\\' { continue } if len(s) > i { - z.WriteString(s[i:v.I]) + w.WriteString(s[i:v.I]) } - z.WriteString(v.String()) + w.WriteString(v.String()) i = v.I + v.Len } // add remaining if len(s) > i { - z.WriteString(s[i:]) + w.WriteString(s[i:]) } - return z.String() + return w.String() } // RawString returns the non-interpolated version of the statement buffer. @@ -135,9 +135,6 @@ func (b *Stmt) Reset(r []rune) { } } -// lineend is the slice to use when appending a line. -var lineend = []rune{'\n'} - // Next reads the next statement from the rune source, returning when either // the statement has been terminated, or a meta command has been read from the // rune source. After a call to Next, the collected statement is available in @@ -172,7 +169,7 @@ var lineend = []rune{'\n'} // buf.Reset(nil) // } // } -func (b *Stmt) Next(unquote func(string, bool) (bool, string, error)) (string, string, error) { +func (b *Stmt) Next(unquote func(string, bool) (string, bool, error)) (string, string, error) { var err error var i int // no runes to process, grab more @@ -228,7 +225,7 @@ parse: case c == ':' && next != ':': if v := readVar(b.r, i, b.rlen, next); v != nil { b.Vars = append(b.Vars, v) - ok, z, _ := unquote(v.Name, true) + z, ok, _ := unquote(v.Name, true) if v.Defined = ok || v.Quote == '?'; v.Defined { b.r, b.rlen = v.Substitute(b.r, z, ok) } @@ -454,21 +451,20 @@ func WithAllowHashComments(enable bool) Option { } } -// IsSpaceOrControl is a special test for either a space or a control (ie, \b) +// isSpaceOrControl is a special test for either a space or a control (ie, \b) // characters. -func IsSpaceOrControl(r rune) bool { +func isSpaceOrControl(r rune) bool { return unicode.IsSpace(r) || unicode.IsControl(r) } -// RunesLastIndex returns the last index in r of needle, or -1 if not found. -func RunesLastIndex(r []rune, needle rune) int { - i := len(r) - 1 - for ; i >= 0; i-- { +// lastIndex returns the last index in r of needle, or -1 if not found. +func lastIndex(r []rune, needle rune) int { + for i := len(r) - 1; i >= 0; i-- { if r[i] == needle { return i } } - return i + return -1 } // trueFalse returns TRUE or FALSE. @@ -478,3 +474,6 @@ func trueFalse(ok bool) string { } return "FALSE" } + +// lineend is the slice to use when appending a line. +var lineend = []rune{'\n'} diff --git a/stmt/stmt_test.go b/stmt/stmt_test.go index 54c3ed2b437..4170e250480 100644 --- a/stmt/stmt_test.go +++ b/stmt/stmt_test.go @@ -87,7 +87,7 @@ func TestNextResetState(t *testing.T) { if err != nil { t.Fatalf("expected no error, got: %v", err) } - unquote := env.Unquote(u, false, env.Vars{}) + unquote := env.Untick(u, env.NewVars(), false) tests := []struct { s string stmts []string diff --git a/text/errors.go b/text/errors.go index 832917064c5..10c17b448f0 100644 --- a/text/errors.go +++ b/text/errors.go @@ -6,45 +6,45 @@ import ( var ( // ErrNotConnected is the not connected error. - ErrNotConnected = errors.New("not connected") + ErrNotConnected = errors.New(`not connected`) // ErrNoSuchFileOrDirectory is the no such file or directory error. - ErrNoSuchFileOrDirectory = errors.New("no such file or directory") + ErrNoSuchFileOrDirectory = errors.New(`no such file or directory`) // ErrCannotIncludeDirectories is the cannot include directories error. - ErrCannotIncludeDirectories = errors.New("cannot include directories") + ErrCannotIncludeDirectories = errors.New(`cannot include directories`) // ErrMissingDSN is the missing dsn error. - ErrMissingDSN = errors.New("missing dsn") + ErrMissingDSN = errors.New(`missing dsn`) // ErrNoPreviousTransactionExists is the no previous transaction exists error. - ErrNoPreviousTransactionExists = errors.New("no previous transaction exists") + ErrNoPreviousTransactionExists = errors.New(`no previous transaction exists`) // ErrPreviousTransactionExists is the previous transaction exists error. - ErrPreviousTransactionExists = errors.New("previous transaction exists") + ErrPreviousTransactionExists = errors.New(`previous transaction exists`) // ErrPasswordAttemptsExhausted is the exhausted password attempts error. - ErrPasswordAttemptsExhausted = errors.New("password attempts exhausted") + ErrPasswordAttemptsExhausted = errors.New(`password attempts exhausted`) // ErrSingleTransactionCannotBeUsedWithInteractiveMode is the single transaction cannot be used with interactive mode error. - ErrSingleTransactionCannotBeUsedWithInteractiveMode = errors.New("--single-transaction cannot be used with interactive mode") + ErrSingleTransactionCannotBeUsedWithInteractiveMode = errors.New(`--single-transaction cannot be used with interactive mode`) // ErrNoEditorDefined is the no editor defined error. - ErrNoEditorDefined = errors.New("no editor defined") + ErrNoEditorDefined = errors.New(`no editor defined`) // ErrUnknownCommand is the unknown command error. - ErrUnknownCommand = errors.New("unknown command") + ErrUnknownCommand = errors.New(`unknown command`) // ErrMissingRequiredArgument is the missing required argument error. - ErrMissingRequiredArgument = errors.New("missing required argument") + ErrMissingRequiredArgument = errors.New(`missing required argument`) // ErrDriverNotAvailable is the driver not available error. - ErrDriverNotAvailable = errors.New("driver not available") + ErrDriverNotAvailable = errors.New(`driver not available`) // ErrPasswordNotSupportedByDriver is the password not supported by driver error. ErrPasswordNotSupportedByDriver = errors.New(`\password not supported by driver`) // ErrUnterminatedQuotedString is the unterminated quoted string error. - ErrUnterminatedQuotedString = errors.New("unterminated quoted string") + ErrUnterminatedQuotedString = errors.New(`unterminated quoted string`) // ErrNoShellAvailable is the no SHELL available error. - ErrNoShellAvailable = errors.New("no SHELL available") + ErrNoShellAvailable = errors.New(`no SHELL available`) // ErrNotInteractive is the not interactive error. - ErrNotInteractive = errors.New("not interactive") + ErrNotInteractive = errors.New(`not interactive`) // ErrInvalidType is the invalid type error. - ErrInvalidType = errors.New("invalid -TYPE: TYPE must be password, string, int, uint, float, or bool") + ErrInvalidType = errors.New(`invalid -TYPE: TYPE must be password, string, int, uint, float, or bool`) // ErrInvalidIdentifier is the invalid identifier error. - ErrInvalidIdentifier = errors.New("invalid identifier") + ErrInvalidIdentifier = errors.New(`invalid identifier`) // ErrInvalidValue is the invalid value error. - ErrInvalidValue = errors.New("invalid value") + ErrInvalidValue = errors.New(`invalid value`) // ErrTooManyRows is the too many rows error. - ErrTooManyRows = errors.New("too many rows") + ErrTooManyRows = errors.New(`too many rows`) // ErrInvalidFormatType is the invalid format type error. ErrInvalidFormatType = errors.New(`\pset: allowed formats are unaligned, aligned, wrapped, html, asciidoc, latex, latex-longtable, troff-ms, json, csv`) // ErrInvalidFormatPagerType is the invalid format pager error. @@ -64,21 +64,25 @@ var ( // ErrInvalidQuotedString is the invalid quoted string error. ErrInvalidQuotedString = errors.New(`invalid quoted string`) // ErrInvalidFormatOption is the invalid format option error. - ErrInvalidFormatOption = errors.New("invalid format option") + ErrInvalidFormatOption = errors.New(`invalid format option`) // ErrInvalidWatchDuration is the invalid watch duration error. - ErrInvalidWatchDuration = errors.New("invalid watch duration") + ErrInvalidWatchDuration = errors.New(`invalid watch duration`) // ErrUnableToNormalizeURL is the unable to normalize URL error. - ErrUnableToNormalizeURL = errors.New("unable to normalize URL") + ErrUnableToNormalizeURL = errors.New(`unable to normalize URL`) // ErrInvalidIsolationLevel is the invalid isolation level error. - ErrInvalidIsolationLevel = errors.New("invalid isolation level") + ErrInvalidIsolationLevel = errors.New(`invalid isolation level`) // ErrNotSupported is the not supported error. - ErrNotSupported = errors.New("not supported") + ErrNotSupported = errors.New(`not supported`) // ErrWrongNumberOfArguments is the wrong number of arguments error. - ErrWrongNumberOfArguments = errors.New("wrong number of arguments") + ErrWrongNumberOfArguments = errors.New(`wrong number of arguments`) // ErrUnknownFileType is the unknown file type error. - ErrUnknownFileType = errors.New("unknown file type") + ErrUnknownFileType = errors.New(`unknown file type`) // ErrNamedConnectionIsNotAURL is the named connection is not a url error. - ErrNamedConnectionIsNotAURL = errors.New("named connection is not a url") + ErrNamedConnectionIsNotAURL = errors.New(`named connection is not a url`) // ErrInvalidConfig is the invalid config error. - ErrInvalidConfig = errors.New("invalid config") + ErrInvalidConfig = errors.New(`invalid config`) + // ErrIfEscaped is the if escaped error. + ErrIfEscaped = errors.New(`\if escaped`) + // ErrEndIfNoMatchingIf is the endif no matching if error. + ErrEndIfNoMatchingIf = errors.New(`\endif: no matching \if`) ) diff --git a/text/license.go b/text/license.go index b0c6c2ba704..d971dbf1e70 100644 --- a/text/license.go +++ b/text/license.go @@ -5,7 +5,7 @@ package text // License contains the license text for usql. const License = `The MIT License (MIT) -Copyright (c) 2016-2024 Kenneth Shaw +Copyright (c) 2015-2024 Kenneth Shaw Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/text/text.go b/text/text.go index a9b79e628fb..75379fe855e 100644 --- a/text/text.go +++ b/text/text.go @@ -7,41 +7,44 @@ import ( _ "embed" "image" "image/png" + "io" "regexp" "strings" ) // Various usql text bits. var ( - CommandName = `usql` - CommandVersion = `0.0.0-dev` - PassfileName = CommandName + `pass` - ConfigName = "config" - Banner = `the universal command-line interface for SQL databases` - CommandHelpHint = `hint: try "` + CommandName + ` --help" for more information.` - GoInstallHint = "\ntry:\n\n go install -tags 'most %s' github.com/xo/usql@%s\n\n" - NotConnected = `(not connected)` - HelpPrefix = `help` - QuitPrefix = `quit` - ExitPrefix = `exit` - WelcomeDesc = `Type "` + HelpPrefix + `" for help.` - QueryBufferEmpty = `Query buffer is empty.` - QueryBufferReset = `Query buffer reset (cleared).` - InvalidCommand = `Invalid command \%s. Try \? for help.` - ExtraArgumentIgnored = `\%s: extra argument %q ignored` - MissingRequiredArg = `\%s: missing required argument` - Copyright = CommandName + ", " + Banner + ".\n\n" + License - RowCount = `(%d rows)` - AvailableDrivers = `Available Drivers:` - ConnInfo = `Connected with driver %s (%s)` - EnterPassword = `Enter password: ` - EnterPreviousPassword = `Enter previous password: ` - PasswordsDoNotMatch = `Passwords do not match, trying again ...` - NewPassword = `Enter new password: ` - ConfirmPassword = `Confirm password: ` - PasswordChangeFailed = `\password for %q failed: %v` - CouldNotSetVariable = `could not set variable %q` - ChartParseFailed = `\chart: invalid argument for %q: %v` + CommandName = `usql` + CommandVersion = `0.0.0-dev` + PassfileName = CommandName + `pass` + ConfigName = "config" + Banner = `the universal command-line interface for SQL databases` + CommandHelpHint = `hint: try "` + CommandName + ` --help" for more information.` + GoInstallHint = "\ntry:\n\n go install -tags 'most %s' github.com/xo/usql@%s\n\n" + NotConnected = `(not connected)` + HelpPrefix = `help` + QuitPrefix = `quit` + ExitPrefix = `exit` + WelcomeDesc = `Type "` + HelpPrefix + `" for help.` + QueryBufferEmpty = `Query buffer is empty.` + QueryBufferReset = `Query buffer reset (cleared).` + InvalidCommand = `Invalid command \%s. Try \? for help.` + ExtraArgumentIgnored = `\%s: extra argument %q ignored` + MissingRequiredArg = `\%s: missing required argument` + Copyright = CommandName + ", " + Banner + ".\n\n" + License + RowCount = `(%d rows)` + AvailableDrivers = `Available Drivers:` + ConnInfo = `Connected with driver %s (%s)` + EnterPassword = `Enter password: ` + EnterPreviousPassword = `Enter previous password: ` + PasswordsDoNotMatch = `Passwords do not match, trying again ...` + NewPassword = `Enter new password: ` + ConfirmPassword = `Confirm password: ` + PasswordChangeFailed = `\password for %q failed: %v` + CouldNotSetVariable = `could not set variable %q` + ChartParseFailed = `\chart: invalid argument for %q: %v` + CommandIgnoredUseEndIf = `%s command ignored; use \endif or %s to exit current \if block` + UnrecognizedValueForCond = `unrecognized value %q for "\%s expression": Boolean expected` // PasswordChangeSucceeded = `\password succeeded for %q` HelpDesc string HelpDescShort = `Use \? for help or press control-C to clear the input buffer.` @@ -156,6 +159,20 @@ var Short = func() string { return Command() + ", " + Banner } +// UsageString is used to return the +var UsageString = func() string { + return "" +} + +// Usage displays writes the command line options to w, optionally including a +// banner. +func Usage(w io.Writer, banner bool) { + if banner { + _, _ = w.Write([]byte(Short() + "\n\n")) + } + _, _ = w.Write([]byte(UsageString())) +} + // Logo is the logo. var Logo image.Image