mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
* Refactor CLI to mimic psql's meta-commands
This PR adds support for meta-commands (also known as "backslash
commands") like those in psql, Postgres's CLI. Only a few meta-commands
are currently implemented, but this was meant to demonstrate how we
could use something like `\i file.csv` to insert local files into SQL
statements.
* Meta-commands: \file and \include
The initial implementation used `\i` as a streaming file handle.
This commit changes that to `\file`, and then implements `\i` (or
`\include`) as handling multiple sql commands.
* Add meta-command "help" (\?)
This is basically a copy of the psql help output, but includes only
those options we currently support.
* Add support for \o [file], and \timing
The \o meta-command writes query output to a file.
The \timing meta-command turns on/off the timing display sent to stdout.
* Add meta-commands: \l (show databases) and \dt (show tables)
* Add meta-command: \watch [period]
* Update meta-command \connect to take database name instead of ID
* Add support for \echo, \qecho, and \warn
This commit contains an known issue in that the `-n` option will exclude
the line feed, but if the output is the terminal, the readline package
clobbers any content on the current line (i.e. anything without a line
feed). That will need to be addressed at some point.
* Add support for \w [FILE] (write query buffer to file)
* Add SchemaAPI no-op implementation
* Refactor query handler to align with /sql and /databases endpoints
We want to standardize on:
/sql
/databases/{databaseID}/sql
* Add CLI support for expanded, border, tuples_only (and pset)
* Add help text for \pset and \t
91 lines
2.6 KiB
Go
91 lines
2.6 KiB
Go
package cli
|
|
|
|
import (
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/featurebasedb/featurebase/v3/errors"
|
|
)
|
|
|
|
// buffer is a query buffer for SQL statements. Note that this is not a query
|
|
// buffer as you would find on a database server (buffering query results).
|
|
// Rather, this buffers the working SQL statement. The buffer has two
|
|
// components: the buffer of query parts making up the working, incomplete SQL
|
|
// statement, and the last completed SQL statement submitted to the Queryer.
|
|
type buffer struct {
|
|
parts []queryPart
|
|
lastQuery query
|
|
|
|
hasBatchFile bool
|
|
}
|
|
|
|
func newBuffer() *buffer {
|
|
return &buffer{}
|
|
}
|
|
|
|
// addPart adds the given queryPart to the buffer. If the part is of type
|
|
// `partTerminator` (which is generally singified in the CLI by a ";"), the
|
|
// buffer will finalize the query and return it. In all other cases, the
|
|
// returned query is nil.
|
|
func (b *buffer) addPart(part queryPart) (query, error) {
|
|
// Check for part type compatibility. For example, multiple batchFile parts
|
|
// are not allowed in the same query.
|
|
switch part.(type) {
|
|
case *partBatchFile:
|
|
if b.hasBatchFile {
|
|
return nil, errors.Errorf("multiple batch files in one query is not supported")
|
|
}
|
|
b.hasBatchFile = true
|
|
case *partTerminator:
|
|
return b.finalize(), nil
|
|
}
|
|
|
|
b.parts = append(b.parts, part)
|
|
return nil, nil
|
|
}
|
|
|
|
// finalize copies the contents (queryParts) of buffer to lastQuery and then
|
|
// resets the buffer. It returns the query that was finalized.
|
|
func (b *buffer) finalize() query {
|
|
q := make(query, len(b.parts))
|
|
copy(q, b.parts)
|
|
b.lastQuery = q
|
|
b.reset()
|
|
return q
|
|
|
|
}
|
|
|
|
// print returns the contents of the buffer as a string. This is generally used
|
|
// to visually inspect the state of the buffer (for example, when a user issues
|
|
// a `\p` meta-command in the CLI).
|
|
func (b *buffer) print() string {
|
|
if len(b.parts) > 0 {
|
|
return query(b.parts).String()
|
|
} else if b.lastQuery != nil {
|
|
return b.lastQuery.String() + ";"
|
|
}
|
|
return "Query buffer is empty."
|
|
}
|
|
|
|
// reset clears the buffer. It returns a message which may optionally be used to
|
|
// display to a user.
|
|
func (b *buffer) reset() string {
|
|
b.parts = b.parts[:0]
|
|
b.hasBatchFile = false
|
|
return "Query buffer reset (cleared)."
|
|
}
|
|
|
|
func (b *buffer) Reader() io.Reader {
|
|
if len(b.parts) > 0 {
|
|
return query(b.parts).Reader()
|
|
} else if b.lastQuery != nil {
|
|
r := b.lastQuery.Reader()
|
|
// TODO(tlt): terminating the query here results in a line feed just
|
|
// before the semi-colon (for example, when you print out the query
|
|
// buffer using `\w [FILE]`). The removal and re-introduction of line
|
|
// feeds is kind of a mess.
|
|
term := strings.NewReader(";")
|
|
return io.MultiReader(r, term)
|
|
}
|
|
return strings.NewReader("")
|
|
}
|