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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"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() {
|
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