diff --git a/README.md b/README.md index b99417c3dd..8ae189435a 100644 --- a/README.md +++ b/README.md @@ -699,104 +699,124 @@ Type "help" for help. (not connected)=> \? General - \q quit usql - \copyright show usql usage and distribution terms - \drivers display information about available database drivers + \q quit usql + \quit alias for \q + \copyright show usage and distribution terms for usql + \drivers show database drivers available to usql -Query Execute - \g [(OPTIONS)] [FILE] or ; execute query (and send results to file or |pipe) - \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 - \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 +Help + \? [commands] show help on usql's meta (backslash) commands + \? options show help on usql command-line options + \? variables show help on special usql variables -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 +Connection + \c DSN or \c NAME connect to dsn or named database connection + \c DRIVER PARAMS... connect to database with driver and parameters + \connect alias for \c + \Z close (disconnect) database connection + \disconnect alias for \Z + \password [USER] change password for user + \passwd alias for \password + \conninfo display information about the current database connection -Help - \? [commands] show help on backslash commands - \? options show help on usql command-line options - \? variables show help on special usql variables +Query Execute + \g [(OPTIONS)] [FILE] or ; execute query (and send results to file or |pipe) + \go alias for \g + \G [(OPTIONS)] [FILE] as \g, but forces vertical output mode + \ego 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 + \bind [PARAM]... set query parameters + \timing [on|off] toggle timing of commands + +Query View + \crosstab [(OPTIONS)] [COLUMNS] execute query and display results in crosstab + \crosstabview alias for \crosstab + \xtab alias for \crosstab + \chart CHART [(OPTIONS)] execute query and display results as a chart + \watch [(OPTIONS)] [INTERVAL] execute query every specified interval -Input/Output - \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 - \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) - \o [FILE] send all query results to file or |pipe - \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 +Query Buffer + \e [-raw|-exec] [FILE] [LINE] edit the query buffer, raw (non-interpolated) buffer, the + exec buffer, or a file with external editor + \edit alias for \e + \p [-raw|-exec] show the contents of the query buffer, the raw + (non-interpolated) buffer or the exec buffer + \print alias for \p + \raw alias for \p + \exec alias for \p + \w [-raw|-exec] FILE write the contents of the query buffer, raw + (non-interpolated) buffer, or exec buffer to file + \write alias for \w + \r reset (clear) the query buffer + \reset alias for \r Informational - \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 - \ss[+] [TABLE|QUERY] [k] show stats for a table or a query - -Formatting - \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 + \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 + \ss[+] [TABLE|QUERY] [k] show stats for a table or a query -Transaction - \begin begin a transaction - \begin -read-only ISOLATION begin a transaction with isolation level - \commit commit current transaction - \rollback rollback (abort) current transaction +Variables + \set [NAME [VALUE]] set usql application variable, or show all usql application + variables if no parameters + \unset NAME unset (delete) usql application variable + \pset [NAME [VALUE]] set table print formatting option, or show all print + formatting options if no parameters + \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 + \cset [NAME [URL]] set named connection, or show all named connections if no + parameters + \cset NAME DRIVER PARAMS... set named connection for driver and parameters + \prompt [-TYPE] VAR [PROMPT] prompt user to set application variable -Connection - \c DSN connect to database url - \c DRIVER PARAMS... connect to database with driver and 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 [USER] change password for user - \conninfo display information about the current database connection - -Operating System - \cd [DIR] change the current working directory - \getenv VARNAME ENVVAR fetch environment variable - \setenv NAME [VALUE] set or unset environment variable - \! [COMMAND] execute command in shell or start interactive shell - \timing [on|off] toggle timing of commands +Input/Output + \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) + \o [FILE] send all query results to file or |pipe + \out alias for \o + \copy SRC DST QUERY TABLE copy results of query from source database into table on + destination database + \copy SRC DST QUERY TABLE(A,...) copy results of query from source database into table's + columns on destination database + +Control/Conditional + \i FILE execute commands from file + \include alias for \i + \ir FILE as \i, but relative to location of current script + \include_relative alias for \ir + \if EXPR begin conditional block + \elif EXPR alternative within current conditional block + \else final alternative within current conditional block + \endif end conditional block -Variables - \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 +Transaction + \begin [-read-only [ISOLATION]] begin transaction, with optional isolation level + \commit commit current transaction + \rollback rollback (abort) current transaction + \abort alias for \rollback + +Operating System/Environment + \! [COMMAND] execute command in shell or start interactive shell + \cd [DIR] change the current working directory + \getenv VARNAME ENVVAR fetch environment variable + \setenv NAME [VALUE] set or unset environment variable ``` Parameters passed to commands [can be backticked][backticks]. diff --git a/env/list.go b/env/list.go index b5e2a246b2..ee6f857da4 100644 --- a/env/list.go +++ b/env/list.go @@ -16,7 +16,7 @@ import ( // Listing writes a formatted listing of the special environment variables to // w, separated in sections based on variable type. -func Listing(w io.Writer) { +func Listing(w io.Writer) error { varsWithDesc := make([]string, len(varNames)) for i, v := range varNames { varsWithDesc[i] = v.String() @@ -48,39 +48,9 @@ func Listing(w io.Writer) { 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, + w, + variableTpl, text.CommandName, strings.TrimRightFunc(strings.Join(varsWithDesc, ""), unicode.IsSpace), strings.TrimRightFunc(strings.Join(pvarsWithDesc, ""), unicode.IsSpace), @@ -88,6 +58,7 @@ Usage: configDir, configExtra, ) + return nil } func buildConfigDir(configName string) (string, string) { @@ -295,3 +266,34 @@ var envVarNames = []varName{ `shell used by the \! command`, }, } + +const variableTpl = `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 +` diff --git a/gen.go b/gen.go index 21c18da464..51ba0656da 100644 --- a/gen.go +++ b/gen.go @@ -365,42 +365,42 @@ func parseDriverInfo(tag, filename string) (DriverInfo, error) { // desc is a meta command description. type desc struct { - Name string - Params string - Desc string - Func string - Hidden bool - Alias string + Func string + Name string + Params string + Desc string + Hidden bool + Deprecated bool } func newDesc(funcName, alias string, v []string) desc { - name, params, descstr := v[0], "", "" - switch len(v) { - case 1: + name, params, descstr, hidden := v[0], "", "", false + switch n := len(v); { + case n == 1: if i := strings.Index(name, ":"); i != -1 { name, alias = name[:i], name[i+1:] } - case 2: + case n == 2: descstr = v[1] - case 3: + case n >= 3: params, descstr = v[1], v[2] } + if descstr == "" { + hidden, descstr = true, `alias for \`+alias + } return desc{ - Name: name, - Params: params, - Desc: descstr, - Func: funcName, - Alias: alias, + Func: funcName, + Name: name, + Params: params, + Desc: descstr, + Hidden: hidden, + Deprecated: v[len(v)-1] == "DEPRECATED", } } 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) + s := strings.ReplaceAll(d.Desc, "{{CommandName}}", "` + text.CommandName + `") + return fmt.Sprintf("{%s, `%s`, `%s`, `%s`, %t, %t}", d.Func, d.Name, d.Params, s, d.Hidden, d.Deprecated) } func findCommand(name string) string { @@ -647,17 +647,16 @@ var baseOrder = map[string]int{ var sections = []string{ "General", "Help", + "Connection", "Query Execute", - //"Query View", + "Query View", "Query Buffer", "Informational", "Variables", - "Connection", - "Conditional", "Input/Output", + "Control/Conditional", "Transaction", "Operating System/Environment", - //"Formatting", } // regexps. diff --git a/metacmd/cmds.go b/metacmd/cmds.go index 8989d999c4..444876082a 100644 --- a/metacmd/cmds.go +++ b/metacmd/cmds.go @@ -20,44 +20,6 @@ import ( "github.com/xo/usql/text" ) -// 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 -} - // Quit is a General meta command (\q \quit). Quits the application. // // Descs: @@ -74,7 +36,7 @@ func Quit(p *Params) error { // // Descs: // -// copyright show {{CommandName}} usage and distribution terms +// copyright show usage and distribution terms for {{CommandName}} func Copyright(p *Params) error { stdout := p.Handler.IO().Stdout() if typ := env.TermGraphics(); typ.Available() { @@ -84,159 +46,12 @@ func Copyright(p *Params) error { return nil } -// 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...) -} - -// 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 +// drivers show database drivers available to {{CommandName}} func Drivers(p *Params) error { stdout, stderr := p.Handler.IO().Stdout(), p.Handler.IO().Stderr() var cmd *exec.Cmd @@ -264,9 +79,7 @@ func Drivers(p *Params) error { s += " (" + driver + ")" } if len(aliases) > 0 { - if len(aliases) > 0 { - s += " [" + strings.Join(aliases, ", ") + "]" - } + s += " [" + strings.Join(aliases, ", ") + "]" } fmt.Fprintln(stdout, s) } @@ -279,55 +92,40 @@ func Drivers(p *Params) error { 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. +// Help is a Help meta command (\?). Writes a help message 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) +// ? [commands] show help on {{CommandName}}'s meta (backslash) commands +// ? options show help on {{CommandName}} command-line options +// ? variables show help on special {{CommandName}} variables +func Help(p *Params) error { + name, err := p.Next(false) 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 + 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 { - case "d": - if pattern != "" { - return m.DescribeTableDetails(p.Handler.URL(), pattern, verbose, showSystem) + 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 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 cmd.Wait() } return nil } @@ -344,18 +142,17 @@ func Describe(p *Params) error { // 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 { + switch { + case err != nil: return err + case p.Name != "gset": + p.Option.ParseParams(params, "pipe") } - p.Option.ParseParams(params, "pipe") switch p.Name { case "G", "ego": p.Option.Params["format"] = "vertical" @@ -363,65 +160,10 @@ func Execute(p *Params) error { p.Option.Params["expanded"] = "on" case "gset": p.Option.Exec = ExecSet + p.Option.ParseParams(params, "prefix") } 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 - } - 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 - 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 - } } return nil } @@ -440,7 +182,7 @@ func Bind(p *Params) error { var v []interface{} if n := len(bind); n != 0 { v = make([]interface{}, len(bind)) - for i := 0; i < n; i++ { + for i := range n { v[i] = bind[i] } } @@ -448,13 +190,199 @@ func Bind(p *Params) error { return nil } +// Timing is a Query Execute 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) + switch { + case err != nil: + return err + case v == "": + p.Handler.SetTiming(!p.Handler.GetTiming()) + default: + 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 +} + +// Crosstab is a Query View meta command (\crosstab). Executes the active query +// on the open database connection and displays results in a crosstab view. +// +// Descs: +// +// crosstab [(OPTIONS)] [COLUMNS] execute query and display results in crosstab +// crosstabview +// xtab +func Crosstab(p *Params) error { + 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 + } + } + return nil +} + +// Chart is a Query View meta command (\chart). Executes the active query on +// the open database connection and displays results in a chart view. +// +// Descs: +// +// chart CHART [(OPTIONS)] execute query and display results as a chart +func Chart(p *Params) error { + 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 + } + 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:] + } + } + return nil +} + +// Watch is a Query View meta command (\watch). Executes (and re-executes) the +// active query on the open database connection until canceled by the user. +// +// Descs: +// +// watch [(OPTIONS)] [INTERVAL] execute query every specified interval +func Watch(p *Params) error { + 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 + } + return nil +} + +// Connect is a Connection meta command (\c, \connect). Opens (connects) a +// database connection. +// +// Descs: +// +// c DSN or \c NAME connect to dsn or named database connection +// 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...) +} + +// Disconnect is a Connection meta command (\Z). Closes (disconnects) the +// current database connection. +// +// Descs: +// +// Z close (disconnect) 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 +} + // 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 +// e [-raw|-exec] [FILE] [LINE] edit the query buffer, raw (non-interpolated) buffer, the exec buffer, or a file with external editor +// edit func Edit(p *Params) error { var exec bool path, ok, err := p.NextOpt(true) @@ -499,10 +427,10 @@ func Edit(p *Params) error { // // Descs: // -// p show the contents of the query buffer +// p [-raw|-exec] show the contents of the query buffer, the raw (non-interpolated) buffer or the exec buffer // print -// raw show the raw (non-interpolated) contents of the query buffer -// exec show the contents of the exec buffer +// raw +// exec func Print(p *Params) error { // get last statement var s string @@ -533,6 +461,26 @@ func Print(p *Params) error { return nil } +// Write is a Query Buffer meta command (\w \write). Writes the query buffer to +// a file. +// +// Descs: +// +// w [-raw|-exec] FILE write the contents of the query buffer, raw (non-interpolated) buffer, or exec 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) +} + // Reset is a Query Buffer meta command (\r, \reset). Clears (resets) the query // buffer. // @@ -584,127 +532,97 @@ func Echo(p *Params) error { return nil } -// Write is a Query Buffer meta command (\w \write). Writes the query buffer to -// a file. +// Out is a Input/Output meta command (\o \out). Sets (redirects) the output to +// a file or a command. // // 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() +// 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) } - name, err := p.Next(true) if err != nil { return err } - return os.WriteFile(name, []byte(strings.TrimSuffix(s, "\n")+"\n"), 0o644) + p.Handler.SetOutput(out) + return nil } -// Chdir is a Operating System/Environment meta command (\cd). Changes the -// current directory for the Operating System/Environment. +// Copy is a Input/Output meta command (\copy). Copies data between databases. // // Descs: // -// cd [DIR] change the current working directory -func Chdir(p *Params) error { - dir, err := p.Next(true) +// copy SRC DST QUERY TABLE copy results of query from source database into table on destination database +// copy SRC DST QUERY TABLE(A,...) copy results of query from source database into table's columns on destination database +func Copy(p *Params) error { + srcstr, 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: + src, err := dburl.Parse(srcstr) + if err != nil { return err - case n == "": - return text.ErrMissingRequiredArgument } - v, err := p.Next(true) - switch { - case err != nil: + deststr, err := p.Next(true) + if 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) + dest, err := dburl.Parse(deststr) + 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 + } + ctx := context.Background() + stdout, stderr := p.Handler.IO().Stdout, p.Handler.IO().Stderr + srcDb, err := drivers.Open(ctx, src, stdout, stderr) if err != nil { return err } - v, err := p.Next(true) + defer srcDb.Close() + destDb, err := drivers.Open(ctx, dest, stdout, stderr) 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) + defer destDb.Close() + ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) + defer cancel() + // get the result set + r, err := srcDb.QueryContext(ctx, query) 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) - } + defer r.Close() + n, err := drivers.Copy(ctx, dest, stdout, stderr, r, table) if err != nil { return err } - p.Handler.SetOutput(out) + p.Handler.Print("COPY %d", n) return nil } -// Include is a Input/Output meta command (\i, \include and variants). Includes -// (runs) the specified file in the current execution environment. +// Include is a Control/Conditional meta command (\i, \include and variants). +// Includes (runs) the specified file in the current execution environment. // // Descs: // @@ -725,13 +643,12 @@ func Include(p *Params) error { } // Transact is a Transaction meta command (\begin, \commit, \rollback). Begins, -// commits, or rollsback the current database transaction on the open database -// connection. +// commits, or aborts (rollback) the current database transaction on the open +// database connection. // // Descs: // -// begin begin a transaction -// begin -read-only ISOLATION begin a transaction with isolation level +// begin [-read-only [ISOLATION]] begin transaction, with optional isolation level // commit commit current transaction // rollback rollback (abort) current transaction // abort:rollback @@ -786,47 +703,11 @@ func Transact(p *Params) error { 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. +// Set is a Variables meta command (\set). Sets (or shows) the application variables. // // Descs: // -// set [NAME [VALUE]] set internal variable, or list all if no parameters +// set [NAME [VALUE]] set {{CommandName}} application variable, or show all {{CommandName}} application variables if no parameters func Set(p *Params) error { switch n, ok, err := p.NextOK(true); { case err != nil: @@ -845,7 +726,7 @@ func Set(p *Params) error { // // Descs: // -// unset NAME unset (delete) internal variable +// unset NAME unset (delete) {{CommandName}} application variable func Unset(p *Params) error { n, err := p.Next(true) if err != nil { @@ -859,14 +740,14 @@ func Unset(p *Params) error { // // 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 +// pset [NAME [VALUE]] set table print formatting option, or show all print formatting options if no parameters +// a toggle between unaligned and aligned output mode DEPRECATED +// C [TITLE] set table title, or unset if none DEPRECATED +// f [SEPARATOR] show or set field separator for unaligned query output DEPRECATED +// H toggle HTML output mode DEPRECATED +// T [ATTRIBUTES] set HTML
tag attributes, or unset if none DEPRECATED +// t [on|off] show only rows DEPRECATED +// x [on|off|auto] toggle expanded output DEPRECATED func SetPrint(p *Params) error { var ok bool var val string @@ -940,37 +821,112 @@ func SetPrint(p *Params) error { return nil } -// Timing is a Operating System/Environment meta command (\timing). Sets (or -// toggles) writing timing information for executed queries to the output. +// SetConn is a Variables meta command (\cset). Sets a connection variable. // // Descs: // -// timing [on|off] toggle timing of commands -func Timing(p *Params) error { - v, err := p.Next(true) +// cset [NAME [URL]] set named connection, or show all named connections if no parameters +// cset NAME DRIVER PARAMS... set named connection for 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()) +} + +// 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 application variable +func Prompt(p *Params) error { + typ := "string" + n, ok, err := p.NextOpt(true) if err != nil { return err } - if v == "" { - p.Handler.SetTiming(!p.Handler.GetTiming()) - } else { - s, err := env.ParseBool(v, `\timing`) + if ok { + typ = n + n, err = p.Next(true) 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 + return err } - p.Handler.SetTiming(b) } - setting := "off" - if p.Handler.GetTiming() { - setting = "on" + 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) +} + +// 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) } - p.Handler.Print(text.TimingSet, setting) return nil } @@ -1013,8 +969,9 @@ func Stats(p *Params) error { 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. +// Conditional is a Control/Conditional meta command (\if, \elif, \else, +// \endif). Starts, closes, and ends a conditional block within the +// application. // // Descs: // @@ -1031,3 +988,72 @@ func Conditional(p *Params) error { } return nil } + +// 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()) +} + +// 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) +} diff --git a/metacmd/descs.go b/metacmd/descs.go index dc0600bbdf..4e91403209 100644 --- a/metacmd/descs.go +++ b/metacmd/descs.go @@ -10,13 +10,14 @@ import ( var sections = []string{ "General", "Help", + "Connection", "Query Execute", + "Query View", "Query Buffer", "Informational", "Variables", - "Connection", - "Conditional", "Input/Output", + "Control/Conditional", "Transaction", "Operating System/Environment", } @@ -31,123 +32,126 @@ 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}, + {Quit, `q`, ``, `quit ` + text.CommandName + ``, false, false}, + {Quit, `quit`, ``, `alias for \q`, true, false}, + {Copyright, `copyright`, ``, `show usage and distribution terms for ` + text.CommandName + ``, false, false}, + {Drivers, `drivers`, ``, `show database drivers available to ` + text.CommandName + ``, false, 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}, + {Help, `?`, `[commands]`, `show help on ` + text.CommandName + `'s meta (backslash) commands`, false, false}, + {Help, `?`, `options`, `show help on ` + text.CommandName + ` command-line options`, false, false}, + {Help, `?`, `variables`, `show help on special ` + text.CommandName + ` variables`, false, false}, + }, + // Connection + { + {Connect, `c`, `DSN or \c NAME`, `connect to dsn or named database connection`, false, false}, + {Connect, `c`, `DRIVER PARAMS...`, `connect to database with driver and parameters`, false, false}, + {Connect, `connect`, ``, `alias for \c`, true, false}, + {Disconnect, `Z`, ``, `close (disconnect) database connection`, false, false}, + {Disconnect, `disconnect`, ``, `alias for \Z`, true, false}, + {Password, `password`, `[USER]`, `change password for user`, false, false}, + {Password, `passwd`, ``, `alias for \password`, true, false}, + {ConnectionInfo, `conninfo`, ``, `display information about the current database connection`, false, 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}, + {Execute, `g`, `[(OPTIONS)] [FILE] or ;`, `execute query (and send results to file or |pipe)`, false, false}, + {Execute, `go`, ``, `alias for \g`, true, false}, + {Execute, `G`, `[(OPTIONS)] [FILE]`, `as \g, but forces vertical output mode`, false, false}, + {Execute, `ego`, ``, `alias for \G`, true, false}, + {Execute, `gx`, `[(OPTIONS)] [FILE]`, `as \g, but forces expanded output mode`, false, false}, + {Execute, `gexec`, ``, `execute query and execute each value of the result`, false, false}, + {Execute, `gset`, `[PREFIX]`, `execute query and store results in ` + text.CommandName + ` variables`, false, false}, + {Bind, `bind`, `[PARAM]...`, `set query parameters`, false, false}, + {Timing, `timing`, `[on|off]`, `toggle timing of commands`, false, false}, + }, + // Query View + { + {Crosstab, `crosstab`, `[(OPTIONS)] [COLUMNS]`, `execute query and display results in crosstab`, false, false}, + {Crosstab, `crosstabview`, ``, `alias for \crosstab`, true, false}, + {Crosstab, `xtab`, ``, `alias for \crosstab`, true, false}, + {Chart, `chart`, `CHART [(OPTIONS)]`, `execute query and display results as a chart`, false, false}, + {Watch, `watch`, `[(OPTIONS)] [INTERVAL]`, `execute query every specified interval`, false, 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}, + {Edit, `e`, `[-raw|-exec] [FILE] [LINE]`, `edit the query buffer, raw (non-interpolated) buffer, the exec buffer, or a file with external editor`, false, false}, + {Edit, `edit`, ``, `alias for \e`, true, false}, + {Print, `p`, `[-raw|-exec]`, `show the contents of the query buffer, the raw (non-interpolated) buffer or the exec buffer`, false, false}, + {Print, `print`, ``, `alias for \p`, true, false}, + {Print, `raw`, ``, `alias for \p`, true, false}, + {Print, `exec`, ``, `alias for \p`, true, false}, + {Write, `w`, `[-raw|-exec] FILE`, `write the contents of the query buffer, raw (non-interpolated) buffer, or exec buffer to file`, false, false}, + {Write, `write`, ``, `alias for \w`, true, false}, + {Reset, `r`, ``, `reset (clear) the query buffer`, false, false}, + {Reset, `reset`, ``, `alias for \r`, true, false}, }, // 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}, + {Describe, `d[S+]`, `[NAME]`, `list tables, views, and sequences or describe table, view, sequence, or index`, false, false}, + {Describe, `da[S+]`, `[PATTERN]`, `list aggregates`, false, false}, + {Describe, `df[S+]`, `[PATTERN]`, `list functions`, false, false}, + {Describe, `di[S+]`, `[PATTERN]`, `list indexes`, false, false}, + {Describe, `dm[S+]`, `[PATTERN]`, `list materialized views`, false, false}, + {Describe, `dn[S+]`, `[PATTERN]`, `list schemas`, false, false}, + {Describe, `dp[S]`, `[PATTERN]`, `list table, view, and sequence access privileges`, false, false}, + {Describe, `ds[S+]`, `[PATTERN]`, `list sequences`, false, false}, + {Describe, `dt[S+]`, `[PATTERN]`, `list tables`, false, false}, + {Describe, `dv[S+]`, `[PATTERN]`, `list views`, false, false}, + {Describe, `l[+]`, ``, `list databases`, false, false}, + {Stats, `ss[+]`, `[TABLE|QUERY] [k]`, `show stats for a table or a query`, false, 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}, + {Set, `set`, `[NAME [VALUE]]`, `set ` + text.CommandName + ` application variable, or show all ` + text.CommandName + ` application variables if no parameters`, false, false}, + {Unset, `unset`, `NAME`, `unset (delete) ` + text.CommandName + ` application variable`, false, false}, + {SetPrint, `pset`, `[NAME [VALUE]]`, `set table print formatting option, or show all print formatting options if no parameters`, false, false}, + {SetPrint, `a`, ``, `toggle between unaligned and aligned output mode`, false, true}, + {SetPrint, `C`, `[TITLE]`, `set table title, or unset if none`, false, true}, + {SetPrint, `f`, `[SEPARATOR]`, `show or set field separator for unaligned query output`, false, true}, + {SetPrint, `H`, ``, `toggle HTML output mode`, false, true}, + {SetPrint, `T`, `[ATTRIBUTES]`, `set HTML
tag attributes, or unset if none`, false, true}, + {SetPrint, `t`, `[on|off]`, `show only rows`, false, true}, + {SetPrint, `x`, `[on|off|auto]`, `toggle expanded output`, false, true}, + {SetConn, `cset`, `[NAME [URL]]`, `set named connection, or show all named connections if no parameters`, false, false}, + {SetConn, `cset`, `NAME DRIVER PARAMS...`, `set named connection for driver and parameters`, false, false}, + {Prompt, `prompt`, `[-TYPE] VAR [PROMPT]`, `prompt user to set application variable`, false, false}, }, - // Conditional + // Input/Output { - {"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}, + {Echo, `echo`, `[-n] [MESSAGE]...`, `write message to standard output (-n for no newline)`, false, false}, + {Echo, `qecho`, `[-n] [MESSAGE]...`, `write message to \o output stream (-n for no newline)`, false, false}, + {Echo, `warn`, `[-n] [MESSAGE]...`, `write message to standard error (-n for no newline)`, false, false}, + {Out, `o`, `[FILE]`, `send all query results to file or |pipe`, false, false}, + {Out, `out`, ``, `alias for \o`, true, false}, + {Copy, `copy`, `SRC DST QUERY TABLE`, `copy results of query from source database into table on destination database`, false, false}, + {Copy, `copy`, `SRC DST QUERY TABLE(A,...)`, `copy results of query from source database into table's columns on destination database`, false, false}, }, - // Input/Output + // Control/Conditional { - {"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}, + {Include, `i`, `FILE`, `execute commands from file`, false, false}, + {Include, `include`, ``, `alias for \i`, true, false}, + {Include, `ir`, `FILE`, `as \i, but relative to location of current script`, false, false}, + {Include, `include_relative`, ``, `alias for \ir`, true, false}, + {Conditional, `if`, `EXPR`, `begin conditional block`, false, false}, + {Conditional, `elif`, `EXPR`, `alternative within current conditional block`, false, false}, + {Conditional, `else`, ``, `final alternative within current conditional block`, false, false}, + {Conditional, `endif`, ``, `end conditional block`, false, false}, }, // 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}, + {Transact, `begin`, `[-read-only [ISOLATION]]`, `begin transaction, with optional isolation level`, false, false}, + {Transact, `commit`, ``, `commit current transaction`, false, false}, + {Transact, `rollback`, ``, `rollback (abort) current transaction`, false, false}, + {Transact, `abort`, ``, `alias for \rollback`, true, false}, }, // 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}, + {Shell, `!`, `[COMMAND]`, `execute command in shell or start interactive shell`, false, false}, + {Chdir, `cd`, `[DIR]`, `change the current working directory`, false, false}, + {Getenv, `getenv`, `VARNAME ENVVAR`, `fetch environment variable`, false, false}, + {Setenv, `setenv`, `NAME [VALUE]`, `set or unset environment variable`, false, false}, }, } cmds = make(map[string]func(*Params) error) diff --git a/metacmd/metacmd.go b/metacmd/metacmd.go index 62b2e3d0b5..1e34bec132 100644 --- a/metacmd/metacmd.go +++ b/metacmd/metacmd.go @@ -80,7 +80,7 @@ func Dump(w io.Writer, hidden bool) error { n := 0 for i := range sections { for _, desc := range descs[i] { - if !desc.Hidden || hidden { + if (!desc.Hidden && !desc.Deprecated) || hidden { n = max(n, runewidth.StringWidth(desc.Name)+1+runewidth.StringWidth(desc.Params)) } } @@ -91,8 +91,8 @@ func Dump(w io.Writer, hidden bool) error { } 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) + if (!desc.Hidden && !desc.Deprecated) || hidden { + _, _ = fmt.Fprintf(w, " \\%- *s %s\n", n, desc.Name+" "+desc.Params, wrap(desc.Desc, 95, n+5)) } } } @@ -249,11 +249,12 @@ const ( // desc wraps a meta command description. type desc struct { - Name string - Params string - Desc string - Func func(*Params) error - Hidden bool + Func func(*Params) error + Name string + Params string + Desc string + Hidden bool + Deprecated bool } // Names returns the names for the command. @@ -272,3 +273,24 @@ func (d desc) Names() []string { return v } } + +// wrap wraps a line of text to the specified width, and adding the prefix to +// each wrapped line. +func wrap(s string, width, prefixWidth int) string { + words := strings.Fields(strings.TrimSpace(s)) + if len(words) == 0 { + return "" + } + prefix, wrapped := strings.Repeat(" ", prefixWidth), words[0] + left := width - prefixWidth - len(wrapped) + for _, word := range words[1:] { + if left < len(word)+1 { + wrapped += "\n" + prefix + word + left = width - len(word) + } else { + wrapped += " " + word + left -= 1 + len(word) + } + } + return wrapped +}