impl rqlite-cli
parent
b7b3ef9416
commit
898f7b815e
@ -1 +1,60 @@
|
||||
This is the rqlite CLI.
|
||||
# rqlite-cli
|
||||
|
||||
`rqlite-cli` is a command line tool for connecting rqlited.
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
go build -o rqlite-cli
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
$> ./rqlite-cli -h
|
||||
Options:
|
||||
|
||||
-h, --help
|
||||
display help
|
||||
|
||||
-P, --scheme[=http]
|
||||
protocol scheme(http or https)
|
||||
|
||||
-H, --host[=127.0.0.1]
|
||||
rqlited host address
|
||||
|
||||
-p, --port[=4001]
|
||||
rqlited listening http(s) port
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```sh
|
||||
# start rqlited
|
||||
$> rqlited ~/node.1
|
||||
|
||||
# start rqlite-cli terminal
|
||||
$> ./rqlite-cli
|
||||
# now, we have enter the rqlite-cli terminal
|
||||
127.0.0.1:4001> create table foo (id integer not null primary key, name text)
|
||||
{
|
||||
"last_insert_id": 2,
|
||||
"rows_affected": 1,
|
||||
"time": 0.00019249700000000002
|
||||
}
|
||||
127.0.0.1:4001> insert into foo(name) values("fiona")
|
||||
{
|
||||
"last_insert_id": 1,
|
||||
"rows_affected": 1,
|
||||
"time": 0.000155756
|
||||
}
|
||||
127.0.0.1:4001> select * from foo
|
||||
+----+-------+
|
||||
| id | name |
|
||||
+----+-------+
|
||||
| 1 | fiona |
|
||||
+----+-------+
|
||||
127.0.0.1:4001> quit
|
||||
bye~
|
||||
$>
|
||||
```
|
||||
|
@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mkideal/cli"
|
||||
)
|
||||
|
||||
// Result represents execute result
|
||||
type Result struct {
|
||||
LastInsertID int `json:"last_insert_id,omitempty"`
|
||||
RowsAffected int `json:"rows_affected,omitempty"`
|
||||
Time float64 `json:"time,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type executeResponse struct {
|
||||
Results []*Result `json:"results,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Time float64 `json:"time,omitempty"`
|
||||
}
|
||||
|
||||
func execute(ctx *cli.Context, cmd, line string, argv *argT) error {
|
||||
urlStr := fmt.Sprintf("%s://%s:%d/db/execute?pretty&timings", argv.Protocol, argv.Host, argv.Port)
|
||||
ret := &executeResponse{}
|
||||
if err := sendRequest(ctx, urlStr, line, ret); err != nil {
|
||||
return err
|
||||
}
|
||||
if ret.Error != "" {
|
||||
return fmt.Errorf(ret.Error)
|
||||
}
|
||||
if len(ret.Results) != 1 {
|
||||
// What's happen? ret.Results.length MUST be 1
|
||||
return fmt.Errorf("unexpected results length: %d", len(ret.Results))
|
||||
}
|
||||
|
||||
result := ret.Results[0]
|
||||
ctx.JSONIndentln(result, "", " ")
|
||||
return nil
|
||||
}
|
@ -1,9 +1,89 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Bowery/prompt"
|
||||
"github.com/mkideal/cli"
|
||||
)
|
||||
|
||||
type argT struct {
|
||||
cli.Helper
|
||||
Protocol string `cli:"s,scheme" usage:"protocol scheme(http or https)" dft:"http"`
|
||||
Host string `cli:"H,host" usage:"rqlited host address" dft:"127.0.0.1"`
|
||||
Port uint16 `cli:"p,port" usage:"rqlited listening http(s) port" dft:"4001"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("I am the rqlite CLI!")
|
||||
cli.SetUsageStyle(cli.ManualStyle)
|
||||
cli.Run(new(argT), func(ctx *cli.Context) error {
|
||||
argv := ctx.Argv().(*argT)
|
||||
if argv.Help {
|
||||
ctx.WriteUsage()
|
||||
return nil
|
||||
}
|
||||
|
||||
prefix := fmt.Sprintf("%s:%d> ", argv.Host, argv.Port)
|
||||
FOR_READ:
|
||||
for {
|
||||
line, err := prompt.Basic(prefix, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
var (
|
||||
index = strings.Index(line, " ")
|
||||
cmd = line
|
||||
)
|
||||
if index >= 0 {
|
||||
cmd = line[:index]
|
||||
}
|
||||
cmd = strings.ToUpper(cmd)
|
||||
switch cmd {
|
||||
case "QUIT", "EXIT":
|
||||
break FOR_READ
|
||||
case "SELECT":
|
||||
err = query(ctx, cmd, line, argv)
|
||||
default:
|
||||
err = execute(ctx, cmd, line, argv)
|
||||
}
|
||||
if err != nil {
|
||||
ctx.String("%s %v\n", ctx.Color().Red("ERR!"), err)
|
||||
}
|
||||
}
|
||||
ctx.String("bye~\n")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func makeJSONBody(line string) string {
|
||||
data, err := json.MarshalIndent([]string{line}, "", " ")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func sendRequest(ctx *cli.Context, urlStr string, line string, ret interface{}) error {
|
||||
data := makeJSONBody(line)
|
||||
resp, err := http.Post(urlStr, "application/json", strings.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(body, ret); err != nil {
|
||||
return fmt.Errorf(string(body))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -0,0 +1,97 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mkideal/cli"
|
||||
"github.com/mkideal/pkg/textutil"
|
||||
)
|
||||
|
||||
// Rows represents query result
|
||||
type Rows struct {
|
||||
Columns []string `json:"columns"`
|
||||
Types []string `json:"types"`
|
||||
Values [][2]interface{} `json:"values"`
|
||||
Time float64 `json:"time"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// RowCount implements textutil.Table interface
|
||||
func (r *Rows) RowCount() int {
|
||||
return len(r.Values) + 1
|
||||
}
|
||||
|
||||
// ColCount implements textutil.Table interface
|
||||
func (r *Rows) ColCount() int {
|
||||
return len(r.Columns)
|
||||
}
|
||||
|
||||
// Get implements textutil.Table interface
|
||||
func (r *Rows) Get(i, j int) string {
|
||||
if i == 0 {
|
||||
if j >= len(r.Columns) {
|
||||
return ""
|
||||
}
|
||||
return r.Columns[j]
|
||||
}
|
||||
if i-1 >= len(r.Values) {
|
||||
return "NULL"
|
||||
}
|
||||
if j >= len(r.Values[i-1]) {
|
||||
return "NULL"
|
||||
}
|
||||
return fmt.Sprintf("%v", r.Values[i-1][j])
|
||||
}
|
||||
|
||||
func (r *Rows) validate() error {
|
||||
if r.Error != "" {
|
||||
return fmt.Errorf(r.Error)
|
||||
}
|
||||
if r.Columns == nil || r.Types == nil || r.Values == nil {
|
||||
return fmt.Errorf("unexpected result")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// headerRenderStyle render the header of result
|
||||
type headerRenderStyle struct {
|
||||
textutil.DefaultStyle
|
||||
}
|
||||
|
||||
func (render headerRenderStyle) CellRender(row, col int, cell string, cw *textutil.ColorWriter) {
|
||||
if row != 0 {
|
||||
fmt.Fprintf(cw, cell)
|
||||
} else {
|
||||
fmt.Fprintf(cw, cw.Color.Cyan(cell))
|
||||
}
|
||||
}
|
||||
|
||||
var headerRender = &headerRenderStyle{}
|
||||
|
||||
type queryResponse struct {
|
||||
Results []*Rows `json:"results"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Time float64 `json:"time"`
|
||||
}
|
||||
|
||||
func query(ctx *cli.Context, cmd, line string, argv *argT) error {
|
||||
urlStr := fmt.Sprintf("%s://%s:%d/db/query?pretty&timings", argv.Protocol, argv.Host, argv.Port)
|
||||
ret := &queryResponse{}
|
||||
if err := sendRequest(ctx, urlStr, line, ret); err != nil {
|
||||
return err
|
||||
}
|
||||
if ret.Error != "" {
|
||||
return fmt.Errorf(ret.Error)
|
||||
}
|
||||
if len(ret.Results) != 1 {
|
||||
// NOTE:What's happen? ret.Results.length MUST be 1
|
||||
return fmt.Errorf("unexpected results length: %d", len(ret.Results))
|
||||
}
|
||||
|
||||
result := ret.Results[0]
|
||||
if err := result.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
textutil.WriteTable(ctx, result, headerRender)
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue