mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 02:44: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
130 lines
2.7 KiB
Go
130 lines
2.7 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// query is a collection of queryParts which, when applied together, make up an
|
|
// executable SQL query.
|
|
type query []queryPart
|
|
|
|
func (q query) String() string {
|
|
var sb strings.Builder
|
|
for i := range q {
|
|
sb.WriteString(q[i].String())
|
|
if i < len(q)-1 {
|
|
sb.WriteRune('\n')
|
|
}
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// Reader returns the query as an io.Reader so that it can be passed to, for
|
|
// example, http.Post().
|
|
func (q query) Reader() io.Reader {
|
|
readers := make([]io.Reader, 0, len(q))
|
|
for i := range q {
|
|
readers = append(readers, q[i].Reader())
|
|
}
|
|
return io.MultiReader(readers...)
|
|
}
|
|
|
|
// queryPart is an interface representing anything which can use to build up a
|
|
// query.
|
|
type queryPart interface {
|
|
fmt.Stringer
|
|
Reader() io.Reader
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// raw
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Ensure type implements interface.
|
|
var _ queryPart = (*partRaw)(nil)
|
|
|
|
type partRaw struct {
|
|
raw string
|
|
}
|
|
|
|
func newPartRaw(s string) *partRaw {
|
|
return &partRaw{
|
|
raw: s,
|
|
}
|
|
}
|
|
|
|
func (p *partRaw) Reader() io.Reader {
|
|
return strings.NewReader(p.raw + "\n")
|
|
}
|
|
|
|
func (p *partRaw) String() string {
|
|
return p.raw
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// file
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Ensure type implements interface.
|
|
var _ queryPart = (*partFile)(nil)
|
|
|
|
type partFile struct {
|
|
file *os.File
|
|
}
|
|
|
|
func newPartFile(f *os.File) *partFile {
|
|
return &partFile{
|
|
file: f,
|
|
}
|
|
}
|
|
|
|
func (p *partFile) Reader() io.Reader {
|
|
return p.file
|
|
}
|
|
|
|
func (p *partFile) String() string {
|
|
return fmt.Sprintf("[file: %s]", p.file.Name())
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// batch file
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Ensure type implements interface.
|
|
var _ queryPart = (*partBatchFile)(nil)
|
|
|
|
type partBatchFile struct {
|
|
file *os.File
|
|
}
|
|
|
|
func (p *partBatchFile) Reader() io.Reader {
|
|
return p.file
|
|
}
|
|
|
|
func (p *partBatchFile) String() string {
|
|
return p.file.Name()
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// terminator (i.e. ";")
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Ensure type implements interface.
|
|
var _ queryPart = (*partTerminator)(nil)
|
|
|
|
type partTerminator struct{}
|
|
|
|
func newPartTerminator() *partTerminator {
|
|
return &partTerminator{}
|
|
}
|
|
|
|
func (p *partTerminator) Reader() io.Reader {
|
|
return nil
|
|
}
|
|
|
|
func (p *partTerminator) String() string {
|
|
return terminationChar
|
|
}
|