1
0
Fork 0

Merge pull request #118 from mkideal/master

Implement rqlite-cli
master
Philip O'Toole 9 years ago
commit 38524a9b47

2
.gitignore vendored

@ -2,6 +2,7 @@
rqlited
**/rqlited
!**/rqlited/
rqlite-cli
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
@ -36,4 +37,3 @@ _testmain.go
*.exe
*.test
rqlite

@ -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…
Cancel
Save