syncbase/sb51: Make query demo tool a proper syncbase client.
Replaced query/demo with 'sb51 sh', the first version of a
Syncbase generic client and admin tool.
'sb51 sh' connects to a database on a Syncbase instance,
optionally creating and populating it with demo tables, and
opens a SyncQL shell.
Added abort on error when the shell is not connected to a tty.
Change-Id: I8290df33abdceb49716cb99acd2cb4f25622d84c
diff --git a/v23/syncbase/nosql/internal/query/demo/db/db.go b/v23/syncbase/nosql/internal/query/demo/db/db.go
deleted file mode 100644
index 9ec201a..0000000
--- a/v23/syncbase/nosql/internal/query/demo/db/db.go
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package db
-
-import (
- "errors"
- "fmt"
- "time"
-
- "v.io/syncbase/v23/syncbase/nosql/query_db"
- "v.io/v23"
- "v.io/v23/context"
- "v.io/v23/vdl"
- _ "v.io/x/ref/runtime/factories/generic"
- "v.io/x/ref/test"
-)
-
-var d *demoDB
-
-func InitDB() v23.Shutdown {
- d = createDB()
- var shutdown v23.Shutdown
- d.ctx, shutdown = test.V23Init()
- return shutdown
-}
-
-type demoDB struct {
- ctx *context.T
- tables []table
-}
-
-type kv struct {
- key string
- value *vdl.Value
-}
-
-type table struct {
- name string
- rows []kv
-}
-
-type keyValueStreamImpl struct {
- table table
- cursor int
- keyRanges query_db.KeyRanges
- rangeCursor int
-}
-
-func compareKeyToLimit(key, limit string) int {
- if limit == "" || key < limit {
- return -1
- } else if key == limit {
- return 0
- } else {
- return 1
- }
-}
-
-func (kvs *keyValueStreamImpl) Advance() bool {
- for true {
- kvs.cursor++ // initialized to -1
- if kvs.cursor >= len(kvs.table.rows) {
- return false
- }
- // does it match any keyRange
- for kvs.rangeCursor < len(kvs.keyRanges) {
- if kvs.table.rows[kvs.cursor].key >= kvs.keyRanges[kvs.rangeCursor].Start && compareKeyToLimit(kvs.table.rows[kvs.cursor].key, kvs.keyRanges[kvs.rangeCursor].Limit) < 0 {
- return true
- }
- // Keys and keyRanges are both sorted low to high, so we can increment
- // rangeCursor if the keyRange.Limit is < the key.
- if kvs.keyRanges[kvs.rangeCursor].Limit < kvs.table.rows[kvs.cursor].key {
- kvs.rangeCursor++
- if kvs.rangeCursor >= len(kvs.keyRanges) {
- return false
- }
- } else {
- break
- }
- }
- }
- return false
-}
-
-func (kvs *keyValueStreamImpl) KeyValue() (string, *vdl.Value) {
- return kvs.table.rows[kvs.cursor].key, kvs.table.rows[kvs.cursor].value
-}
-
-func (kvs *keyValueStreamImpl) Err() error {
- return nil
-}
-
-func (kvs *keyValueStreamImpl) Cancel() {
-}
-
-func (t table) Scan(keyRanges query_db.KeyRanges) (query_db.KeyValueStream, error) {
- var keyValueStreamImpl keyValueStreamImpl
- keyValueStreamImpl.table = t
- keyValueStreamImpl.cursor = -1
- keyValueStreamImpl.keyRanges = keyRanges
- return &keyValueStreamImpl, nil
-}
-
-// GetTableNames is not part of the query_db.Database interface.
-// Tables are discovered outside the query package.
-// TODO(jkline): Consider system tables to discover info such as this.
-func GetTableNames() []string {
- tables := []string{}
- for _, table := range d.tables {
- tables = append(tables, table.name)
- }
- return tables
-}
-
-// GetDatabase in not part of the query_db.Database interface.
-// Database instances are obtained outside the query package.
-func GetDatabase() query_db.Database {
- return d
-}
-
-func (db demoDB) GetContext() *context.T {
- return db.ctx
-}
-
-func (d demoDB) GetTable(table string) (query_db.Table, error) {
- for _, t := range d.tables {
- if t.name == table {
- return t, nil
- }
- }
- return nil, errors.New(fmt.Sprintf("No such table: %s.", table))
-}
-
-func createDB() *demoDB {
- d = &demoDB{}
- var custTable table
- custTable.name = "Customer"
- custTable.rows = []kv{
- kv{
- "001",
- vdl.ValueOf(Customer{"John Smith", 1, true, AddressInfo{"1 Main St.", "Palo Alto", "CA", "94303"}, CreditReport{Agency: CreditAgencyEquifax, Report: AgencyReportEquifaxReport{EquifaxCreditReport{'A'}}}}),
- },
- kv{
- "001001",
- vdl.ValueOf(Invoice{1, 1000, 42, AddressInfo{"1 Main St.", "Palo Alto", "CA", "94303"}}),
- },
- kv{
- "001002",
- vdl.ValueOf(Invoice{1, 1003, 7, AddressInfo{"2 Main St.", "Palo Alto", "CA", "94303"}}),
- },
- kv{
- "001003",
- vdl.ValueOf(Invoice{1, 1005, 88, AddressInfo{"3 Main St.", "Palo Alto", "CA", "94303"}}),
- },
- kv{
- "002",
- vdl.ValueOf(Customer{"Bat Masterson", 2, true, AddressInfo{"777 Any St.", "Collins", "IA", "50055"}, CreditReport{Agency: CreditAgencyTransUnion, Report: AgencyReportTransUnionReport{TransUnionCreditReport{80}}}}),
- },
- kv{
- "002001",
- vdl.ValueOf(Invoice{2, 1001, 166, AddressInfo{"777 Any St.", "Collins", "IA", "50055"}}),
- },
- kv{
- "002002",
- vdl.ValueOf(Invoice{2, 1002, 243, AddressInfo{"888 Any St.", "Collins", "IA", "50055"}}),
- },
- kv{
- "002003",
- vdl.ValueOf(Invoice{2, 1004, 787, AddressInfo{"999 Any St.", "Collins", "IA", "50055"}}),
- },
- kv{
- "002004",
- vdl.ValueOf(Invoice{2, 1006, 88, AddressInfo{"101010 Any St.", "Collins", "IA", "50055"}}),
- },
- }
- d.tables = append(d.tables, custTable)
-
- var numTable table
- numTable.name = "Numbers"
- numTable.rows = []kv{
- kv{
- "001",
- vdl.ValueOf(Numbers{byte(12), uint16(1234), uint32(5678), uint64(999888777666), int16(9876), int32(876543), int64(128), float32(3.14159), float64(2.71828182846), complex64(123.0 + 7.0i), complex128(456.789 + 10.1112i)}),
- },
- kv{
- "002",
- vdl.ValueOf(Numbers{byte(9), uint16(99), uint32(999), uint64(9999999), int16(9), int32(99), int64(88), float32(1.41421356237), float64(1.73205080757), complex64(9.87 + 7.65i), complex128(4.32 + 1.0i)}),
- },
- kv{
- "003",
- vdl.ValueOf(Numbers{byte(210), uint16(210), uint32(210), uint64(210), int16(210), int32(210), int64(210), float32(210.0), float64(210.0), complex64(210.0 + 0.0i), complex128(210.0 + 0.0i)}),
- },
- }
- d.tables = append(d.tables, numTable)
-
- var compositeTable table
- compositeTable.name = "Composites"
- compositeTable.rows = []kv{
- kv{
- "uno",
- vdl.ValueOf(Composite{Array2String{"foo", "bar"}, []int32{1, 2}, map[int32]struct{}{1: struct{}{}, 2: struct{}{}}, map[string]int32{"foo": 1, "bar": 2}}),
- },
- }
- d.tables = append(d.tables, compositeTable)
-
- var recursiveTable table
- recursiveTable.name = "Recursives"
- recursiveTable.rows = []kv{
- kv{
- "alpha",
- vdl.ValueOf(Recursive{nil, &Times{time.Unix(123456789, 42244224), time.Duration(1337)}, map[Array2String]Recursive{
- Array2String{"a", "b"}: Recursive{},
- Array2String{"x", "y"}: Recursive{vdl.ValueOf(CreditReport{Agency: CreditAgencyExperian, Report: AgencyReportExperianReport{ExperianCreditReport{ExperianRatingGood}}}), nil, map[Array2String]Recursive{
- Array2String{"alpha", "beta"}: Recursive{vdl.ValueOf(FooType{Bar: BarType{Baz: BazType{Name: "hello", TitleOrValue: TitleOrValueTypeValue{Value: 42}}}}), nil, nil},
- }},
- Array2String{"u", "v"}: Recursive{vdl.ValueOf(vdl.TypeOf(Recursive{})), nil, nil},
- }}),
- },
- }
- d.tables = append(d.tables, recursiveTable)
-
- return d
-}
diff --git a/v23/syncbase/nosql/internal/query/demo/db/doc.go b/v23/syncbase/nosql/internal/query/demo/db/doc.go
deleted file mode 100644
index e6a1508..0000000
--- a/v23/syncbase/nosql/internal/query/demo/db/doc.go
+++ /dev/null
@@ -1,6 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// An in-memory implementation of query_db.
-package db
diff --git a/v23/syncbase/nosql/internal/query/demo/demo.go b/v23/syncbase/nosql/internal/query/demo/demo.go
deleted file mode 100644
index da46e61..0000000
--- a/v23/syncbase/nosql/internal/query/demo/demo.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
- "flag"
- "fmt"
- "io"
- "os"
- "strconv"
- "strings"
-
- isatty "github.com/mattn/go-isatty"
-
- "v.io/syncbase/v23/syncbase/nosql/internal/query"
- "v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db"
- "v.io/syncbase/v23/syncbase/nosql/internal/query/demo/reader"
- "v.io/syncbase/v23/syncbase/nosql/internal/query/demo/writer"
- "v.io/syncbase/v23/syncbase/nosql/query_db"
- _ "v.io/x/ref/runtime/factories/generic"
-)
-
-var (
- format = flag.String("format", "table",
- "Output format. 'table': human-readable table; 'csv': comma-separated values, use -csv-delimiter to control the delimiter; 'json': JSON objects.")
- csvDelimiter = flag.String("csv-delimiter", ",", "Delimiter to use when printing data as CSV (e.g. \"\t\", \",\")")
-)
-
-func dumpDB(d query_db.Database) {
- for _, table := range db.GetTableNames() {
- fmt.Printf("table: %s\n", table)
- queryExec(d, fmt.Sprintf("select k, v from %s", table))
- }
-}
-
-// Split an error message into an offset and the remaining (i.e., rhs of offset) message.
-// The convention for syncql is "<module><optional-rpc>[offset}<remaining-message>".
-func splitError(err error) (int64, string) {
- errMsg := err.Error()
- idx1 := strings.Index(errMsg, "[")
- idx2 := strings.Index(errMsg, "]")
- if idx1 == -1 || idx2 == -1 {
- return 0, errMsg
- }
- offsetString := errMsg[idx1+1 : idx2]
- offset, err := strconv.ParseInt(offsetString, 10, 64)
- if err != nil {
- return 0, errMsg
- }
- return offset, errMsg[idx2+1:]
-}
-
-func queryExec(d query_db.Database, q string) {
- if columnNames, rs, err := query.Exec(d, q); err != nil {
- off, msg := splitError(err)
- fmt.Fprintf(os.Stderr, "%s\n", q)
- fmt.Fprintf(os.Stderr, "%s^\n", strings.Repeat(" ", int(off)))
- fmt.Fprintf(os.Stderr, "Error: %s\n", msg)
- } else {
- if *format == "table" {
- if err := writer.WriteTable(os.Stdout, columnNames, rs); err != nil {
- fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
- }
- } else if *format == "csv" {
- if err := writer.WriteCSV(os.Stdout, columnNames, rs, *csvDelimiter); err != nil {
- fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
- }
- } else if *format == "json" {
- if err := writer.WriteJson(os.Stdout, columnNames, rs); err != nil {
- fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
- }
- } else {
- panic(fmt.Sprintf("invalid format flag value: %v", *format))
- }
- }
-}
-
-func main() {
- shutdown := db.InitDB()
- defer shutdown()
-
- if *format != "table" && *format != "csv" && *format != "json" {
- fmt.Fprintf(os.Stderr, "Unsupported -format %q. Must be one of 'table', 'csv', or 'json'.\n", *format)
- os.Exit(-1)
- }
-
- var input *reader.T
- isTerminal := isatty.IsTerminal(os.Stdin.Fd())
- if isTerminal {
- input = reader.NewInteractive()
- } else {
- input = reader.NewNonInteractive()
- }
- defer input.Close()
-
- d := db.GetDatabase()
- for true {
- if q, err := input.GetQuery(); err != nil {
- if err == io.EOF {
- if isTerminal {
- // ctrl-d
- fmt.Println()
- }
- break
- } else {
- // ctrl-c
- break
- }
- } else {
- tq := strings.ToLower(strings.TrimSpace(q))
- if tq == "" || tq == "exit" || tq == "quit" {
- break
- }
- if tq == "dump" {
- dumpDB(d)
- } else {
- queryExec(d, q)
- }
- }
- }
-}
diff --git a/v23/syncbase/nosql/internal/query/demo/doc.go b/v23/syncbase/nosql/internal/query/demo/doc.go
deleted file mode 100644
index 28c7fba..0000000
--- a/v23/syncbase/nosql/internal/query/demo/doc.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// This demo program exercises the syncbase query language.
-// The program's in-memory database implements the following
-// interfaces in query_db:
-// Database
-// Table
-// KeyValueStream
-//
-// The user can enter the following at the command line:
-// 1. dump - to get a dump of the database
-// 2. a syncbase select statement - which is executed and results printed to stdout
-// 3. exit (or empty line) - to exit the program
-//
-// To build example:
-// v23 go install v.io/syncbase/v23/syncbase/nosql/internal/query/demo
-//
-// To run example:
-// $V23_ROOT/roadmap/go/bin/demo
-//
-// Sample run:
-// > $V23_ROOT/roadmap/go/bin/demo
-// Enter query (or 'dump' or 'exit')? select v.Name, v.Address.State from Customer where t = "Customer"
-// John Smith,CA
-// Bat Masterson,IA
-// Enter query (or 'dump' or 'exit')? select v.CustID, v.InvoiceNum, v.ShipTo.Zip, v.Amount from Customer where t = "Invoice" and v.Amount > 100
-// 2,1001,50055,166
-// 2,1002,50055,243
-// 2,1004,50055,787
-// Enter query (or 'dump' or 'exit')? select k, v fro Customer
-// select k, v fro Customer
-// ^
-// Error: [Off:12] Expected 'from', found 'fro'
-// Enter query (or 'dump' or 'exit')? select k, v from Customer
-// 001,{John Smith 1 true 65 {1 Main St. Palo Alto CA 94303}
-// 001001,{1 1000 42 {1 Main St. Palo Alto CA 94303}}
-// 001002,{1 1003 7 {2 Main St. Palo Alto CA 94303}}
-// 001003,{1 1005 88 {3 Main St. Palo Alto CA 94303}}
-// 002,{Bat Masterson 2 true 66 {777 Any St. Collins IA 50055}
-// 002001,{2 1001 166 {777 Any St. collins IA 50055}}
-// 002002,{2 1002 243 {888 Any St. collins IA 50055}}
-// 002003,{2 1004 787 {999 Any St. collins IA 50055}}
-// 002004,{2 1006 88 {101010 Any St. collins IA 50055}}
-// exit
-// >
-package main
diff --git a/x/ref/syncbase/sb51/doc.go b/x/ref/syncbase/sb51/doc.go
new file mode 100644
index 0000000..73609c1
--- /dev/null
+++ b/x/ref/syncbase/sb51/doc.go
@@ -0,0 +1,55 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Antimony (sb51) is a Syncbase general-purpose client and management utility.
+// It currently supports experimenting with the Syncbase query language.
+//
+// The 'sh' command connects to a specified database on a Syncbase instance,
+// creating it if it does not exist if -create-missing is specified.
+// The user can then enter the following at the command line:
+// 1. dump - to get a dump of the database
+// 2. a syncbase select statement - which is executed and results printed to stdout
+// 3. make-demo - to create demo tables in the database to experiment with, equivalent to -make-demo flag
+// 4. exit (or quit) - to exit the program
+//
+// When the shell is running non-interactively (stdin not connected to a tty),
+// errors cause the shell to exit with a non-zero status.
+//
+// To build client:
+// v23 go install v.io/syncbase/x/ref/syncbase/sb51
+//
+// To run client:
+// $V23_ROOT/roadmap/go/bin/sb51 sh <appname> <dbname>
+//
+// Sample run (assuming a syncbase service is mounted at '/:8101/syncbase',
+// otherwise specify using -service flag):
+// > $V23_ROOT/roadmap/go/bin/sb51 sh -create-missing -make-demo -format=csv demoapp demodb
+// ? select v.Name, v.Address.State from DemoCustomers where t = "Customer";
+// v.Name,v.Address.State
+// John Smith,CA
+// Bat Masterson,IA
+// ? select v.CustId, v.InvoiceNum, v.ShipTo.Zip, v.Amount from DemoCustomers where t = "Invoice" and v.Amount > 100;
+// v.CustId,v.InvoiceNum,v.ShipTo.Zip,v.Amount
+// 2,1001,50055,166
+// 2,1002,50055,243
+// 2,1004,50055,787
+// ? select k, v fro DemoCustomers;
+// Error:
+// select k, v fro DemoCustomers
+// ^
+// 13: Expected 'from', found fro.
+// ? select k, v from DemoCustomers;
+// k,v
+// 001,"{Name: ""John Smith"", Id: 1, Active: true, Address: {Street: ""1 Main St."", City: ""Palo Alto"", State: ""CA"", Zip: ""94303""}, Credit: {Agency: Equifax, Report: EquifaxReport: {Rating: 65}}}"
+// 001001,"{CustId: 1, InvoiceNum: 1000, Amount: 42, ShipTo: {Street: ""1 Main St."", City: ""Palo Alto"", State: ""CA"", Zip: ""94303""}}"
+// 001002,"{CustId: 1, InvoiceNum: 1003, Amount: 7, ShipTo: {Street: ""2 Main St."", City: ""Palo Alto"", State: ""CA"", Zip: ""94303""}}"
+// 001003,"{CustId: 1, InvoiceNum: 1005, Amount: 88, ShipTo: {Street: ""3 Main St."", City: ""Palo Alto"", State: ""CA"", Zip: ""94303""}}"
+// 002,"{Name: ""Bat Masterson"", Id: 2, Active: true, Address: {Street: ""777 Any St."", City: ""Collins"", State: ""IA"", Zip: ""50055""}, Credit: {Agency: TransUnion, Report: TransUnionReport: {Rating: 80}}}"
+// 002001,"{CustId: 2, InvoiceNum: 1001, Amount: 166, ShipTo: {Street: ""777 Any St."", City: ""Collins"", State: ""IA"", Zip: ""50055""}}"
+// 002002,"{CustId: 2, InvoiceNum: 1002, Amount: 243, ShipTo: {Street: ""888 Any St."", City: ""Collins"", State: ""IA"", Zip: ""50055""}}"
+// 002003,"{CustId: 2, InvoiceNum: 1004, Amount: 787, ShipTo: {Street: ""999 Any St."", City: ""Collins"", State: ""IA"", Zip: ""50055""}}"
+// 002004,"{CustId: 2, InvoiceNum: 1006, Amount: 88, ShipTo: {Street: ""101010 Any St."", City: ""Collins"", State: ""IA"", Zip: ""50055""}}"
+// ? exit;
+// >
+package main
diff --git a/x/ref/syncbase/sb51/internal/demodb/db.go b/x/ref/syncbase/sb51/internal/demodb/db.go
new file mode 100644
index 0000000..2674361
--- /dev/null
+++ b/x/ref/syncbase/sb51/internal/demodb/db.go
@@ -0,0 +1,138 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package demodb
+
+import (
+ "fmt"
+ "time"
+
+ wire "v.io/syncbase/v23/services/syncbase/nosql"
+ "v.io/syncbase/v23/syncbase/nosql"
+ "v.io/v23/context"
+ "v.io/v23/vdl"
+)
+
+type kv struct {
+ key string
+ value *vdl.Value
+}
+
+type table struct {
+ name string
+ rows []kv
+}
+
+const demoPrefix = "Demo"
+
+var demoTables = []table{
+ table{
+ name: "Customers",
+ rows: []kv{
+ kv{
+ "001",
+ vdl.ValueOf(Customer{"John Smith", 1, true, AddressInfo{"1 Main St.", "Palo Alto", "CA", "94303"}, CreditReport{Agency: CreditAgencyEquifax, Report: AgencyReportEquifaxReport{EquifaxCreditReport{'A'}}}}),
+ },
+ kv{
+ "001001",
+ vdl.ValueOf(Invoice{1, 1000, 42, AddressInfo{"1 Main St.", "Palo Alto", "CA", "94303"}}),
+ },
+ kv{
+ "001002",
+ vdl.ValueOf(Invoice{1, 1003, 7, AddressInfo{"2 Main St.", "Palo Alto", "CA", "94303"}}),
+ },
+ kv{
+ "001003",
+ vdl.ValueOf(Invoice{1, 1005, 88, AddressInfo{"3 Main St.", "Palo Alto", "CA", "94303"}}),
+ },
+ kv{
+ "002",
+ vdl.ValueOf(Customer{"Bat Masterson", 2, true, AddressInfo{"777 Any St.", "Collins", "IA", "50055"}, CreditReport{Agency: CreditAgencyTransUnion, Report: AgencyReportTransUnionReport{TransUnionCreditReport{80}}}}),
+ },
+ kv{
+ "002001",
+ vdl.ValueOf(Invoice{2, 1001, 166, AddressInfo{"777 Any St.", "Collins", "IA", "50055"}}),
+ },
+ kv{
+ "002002",
+ vdl.ValueOf(Invoice{2, 1002, 243, AddressInfo{"888 Any St.", "Collins", "IA", "50055"}}),
+ },
+ kv{
+ "002003",
+ vdl.ValueOf(Invoice{2, 1004, 787, AddressInfo{"999 Any St.", "Collins", "IA", "50055"}}),
+ },
+ kv{
+ "002004",
+ vdl.ValueOf(Invoice{2, 1006, 88, AddressInfo{"101010 Any St.", "Collins", "IA", "50055"}}),
+ },
+ },
+ },
+ table{
+ name: "Numbers",
+ rows: []kv{
+ kv{
+ "001",
+ vdl.ValueOf(Numbers{byte(12), uint16(1234), uint32(5678), uint64(999888777666), int16(9876), int32(876543), int64(128), float32(3.14159), float64(2.71828182846), complex64(123.0 + 7.0i), complex128(456.789 + 10.1112i)}),
+ },
+ kv{
+ "002",
+ vdl.ValueOf(Numbers{byte(9), uint16(99), uint32(999), uint64(9999999), int16(9), int32(99), int64(88), float32(1.41421356237), float64(1.73205080757), complex64(9.87 + 7.65i), complex128(4.32 + 1.0i)}),
+ },
+ kv{
+ "003",
+ vdl.ValueOf(Numbers{byte(210), uint16(210), uint32(210), uint64(210), int16(210), int32(210), int64(210), float32(210.0), float64(210.0), complex64(210.0 + 0.0i), complex128(210.0 + 0.0i)}),
+ },
+ },
+ },
+ table{
+ name: "Composites",
+ rows: []kv{
+ kv{
+ "uno",
+ vdl.ValueOf(Composite{Array2String{"foo", "bar"}, []int32{1, 2}, map[int32]struct{}{1: struct{}{}, 2: struct{}{}}, map[string]int32{"foo": 1, "bar": 2}}),
+ },
+ },
+ },
+ table{
+ name: "Recursives",
+ rows: []kv{
+ kv{
+ "alpha",
+ vdl.ValueOf(Recursive{nil, &Times{time.Unix(123456789, 42244224), time.Duration(1337)}, map[Array2String]Recursive{
+ Array2String{"a", "b"}: Recursive{},
+ Array2String{"x", "y"}: Recursive{vdl.ValueOf(CreditReport{Agency: CreditAgencyExperian, Report: AgencyReportExperianReport{ExperianCreditReport{ExperianRatingGood}}}), nil, map[Array2String]Recursive{
+ Array2String{"alpha", "beta"}: Recursive{vdl.ValueOf(FooType{Bar: BarType{Baz: BazType{Name: "hello", TitleOrValue: TitleOrValueTypeValue{Value: 42}}}}), nil, nil},
+ }},
+ Array2String{"u", "v"}: Recursive{vdl.ValueOf(vdl.TypeOf(Recursive{})), nil, nil},
+ }}),
+ },
+ },
+ },
+}
+
+// Creates demo tables in the provided database. Tables are deleted and
+// recreated if they already exist.
+func PopulateDemoDB(ctx *context.T, db nosql.Database) error {
+ for i, t := range demoTables {
+ tn := demoPrefix + t.name
+ if err := db.DeleteTable(ctx, tn); err != nil {
+ return fmt.Errorf("failed deleting table %s (%d/%d): %v", tn, i+1, len(demoTables), err)
+ }
+ if err := db.CreateTable(ctx, tn, nil); err != nil {
+ return fmt.Errorf("failed creating table %s (%d/%d): %v", tn, i+1, len(demoTables), err)
+ }
+ if err := nosql.RunInBatch(ctx, db, wire.BatchOptions{}, func(db nosql.BatchDatabase) error {
+ dt := db.Table(tn)
+ for _, kv := range t.rows {
+ if err := dt.Put(ctx, kv.key, kv.value); err != nil {
+ return err
+ }
+ }
+ return nil
+ }); err != nil {
+ return fmt.Errorf("failed populating table %s (%d/%d): %v", tn, i+1, len(demoTables), err)
+ }
+ }
+ return nil
+}
diff --git a/v23/syncbase/nosql/internal/query/demo/db/db_objects.vdl b/x/ref/syncbase/sb51/internal/demodb/db_objects.vdl
similarity index 98%
rename from v23/syncbase/nosql/internal/query/demo/db/db_objects.vdl
rename to x/ref/syncbase/sb51/internal/demodb/db_objects.vdl
index 60e1004..cbf119a 100644
--- a/v23/syncbase/nosql/internal/query/demo/db/db_objects.vdl
+++ b/x/ref/syncbase/sb51/internal/demodb/db_objects.vdl
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package db
+package demodb
import "time"
diff --git a/v23/syncbase/nosql/internal/query/demo/db/db_objects.vdl.go b/x/ref/syncbase/sb51/internal/demodb/db_objects.vdl.go
similarity index 82%
rename from v23/syncbase/nosql/internal/query/demo/db/db_objects.vdl.go
rename to x/ref/syncbase/sb51/internal/demodb/db_objects.vdl.go
index 66e4ad4..23481f8 100644
--- a/v23/syncbase/nosql/internal/query/demo/db/db_objects.vdl.go
+++ b/x/ref/syncbase/sb51/internal/demodb/db_objects.vdl.go
@@ -5,7 +5,7 @@
// This file was auto-generated by the vanadium vdl tool.
// Source: db_objects.vdl
-package db
+package demodb
import (
// VDL system imports
@@ -25,7 +25,7 @@
}
func (AddressInfo) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.AddressInfo"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.AddressInfo"`
}) {
}
@@ -60,7 +60,7 @@
return nil
}
*x = -1
- return fmt.Errorf("unknown label %q in db.CreditAgency", label)
+ return fmt.Errorf("unknown label %q in demodb.CreditAgency", label)
}
// String returns the string label of x.
@@ -77,7 +77,7 @@
}
func (CreditAgency) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.CreditAgency"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.CreditAgency"`
Enum struct{ Equifax, Experian, TransUnion string }
}) {
}
@@ -109,7 +109,7 @@
return nil
}
*x = -1
- return fmt.Errorf("unknown label %q in db.ExperianRating", label)
+ return fmt.Errorf("unknown label %q in demodb.ExperianRating", label)
}
// String returns the string label of x.
@@ -124,7 +124,7 @@
}
func (ExperianRating) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.ExperianRating"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.ExperianRating"`
Enum struct{ Good, Bad string }
}) {
}
@@ -134,7 +134,7 @@
}
func (EquifaxCreditReport) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.EquifaxCreditReport"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.EquifaxCreditReport"`
}) {
}
@@ -143,7 +143,7 @@
}
func (ExperianCreditReport) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.ExperianCreditReport"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.ExperianCreditReport"`
}) {
}
@@ -152,7 +152,7 @@
}
func (TransUnionCreditReport) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.TransUnionCreditReport"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.TransUnionCreditReport"`
}) {
}
@@ -176,7 +176,7 @@
AgencyReportTransUnionReport struct{ Value TransUnionCreditReport }
// __AgencyReportReflect describes the AgencyReport union type.
__AgencyReportReflect struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.AgencyReport"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.AgencyReport"`
Type AgencyReport
Union struct {
EquifaxReport AgencyReportEquifaxReport
@@ -207,7 +207,7 @@
}
func (CreditReport) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.CreditReport"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.CreditReport"`
}) {
}
@@ -220,7 +220,7 @@
}
func (Customer) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.Customer"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.Customer"`
}) {
}
@@ -232,7 +232,7 @@
}
func (Invoice) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.Invoice"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.Invoice"`
}) {
}
@@ -251,7 +251,7 @@
}
func (Numbers) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.Numbers"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.Numbers"`
}) {
}
@@ -260,7 +260,7 @@
}
func (FooType) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.FooType"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.FooType"`
}) {
}
@@ -269,7 +269,7 @@
}
func (BarType) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.BarType"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.BarType"`
}) {
}
@@ -291,7 +291,7 @@
TitleOrValueTypeValue struct{ Value int64 }
// __TitleOrValueTypeReflect describes the TitleOrValueType union type.
__TitleOrValueTypeReflect struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.TitleOrValueType"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.TitleOrValueType"`
Type TitleOrValueType
Union struct {
Title TitleOrValueTypeTitle
@@ -316,14 +316,14 @@
}
func (BazType) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.BazType"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.BazType"`
}) {
}
type Array2String [2]string
func (Array2String) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.Array2String"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.Array2String"`
}) {
}
@@ -335,7 +335,7 @@
}
func (Composite) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.Composite"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.Composite"`
}) {
}
@@ -345,7 +345,7 @@
}
func (Times) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.Times"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.Times"`
}) {
}
@@ -356,7 +356,7 @@
}
func (Recursive) __VDLReflect(struct {
- Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.Recursive"`
+ Name string `vdl:"v.io/syncbase/x/ref/syncbase/sb51/internal/demodb.Recursive"`
}) {
}
diff --git a/x/ref/syncbase/sb51/internal/demodb/doc.go b/x/ref/syncbase/sb51/internal/demodb/doc.go
new file mode 100644
index 0000000..3daf52f
--- /dev/null
+++ b/x/ref/syncbase/sb51/internal/demodb/doc.go
@@ -0,0 +1,7 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package demodb supports loading an example database into Syncbase for
+// experimentation and testing purposes.
+package demodb
diff --git a/v23/syncbase/nosql/internal/query/demo/reader/reader.go b/x/ref/syncbase/sb51/internal/reader/reader.go
similarity index 95%
rename from v23/syncbase/nosql/internal/query/demo/reader/reader.go
rename to x/ref/syncbase/sb51/internal/reader/reader.go
index a50260a..930c140 100644
--- a/v23/syncbase/nosql/internal/query/demo/reader/reader.go
+++ b/x/ref/syncbase/sb51/internal/reader/reader.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Package reader provides an object that read queries from various input
+// Package reader provides an object that reads queries from various input
// sources (e.g. stdin, pipe).
package reader
@@ -118,7 +118,7 @@
}
func (i *interactive) InitialPrompt() (string, error) {
- return i.line.Prompt("Enter query or 'dump'? ")
+ return i.line.Prompt("? ")
}
func (i *interactive) ContinuePrompt() (string, error) {
diff --git a/v23/syncbase/nosql/internal/query/demo/reader/reader_test.go b/x/ref/syncbase/sb51/internal/reader/reader_test.go
similarity index 100%
rename from v23/syncbase/nosql/internal/query/demo/reader/reader_test.go
rename to x/ref/syncbase/sb51/internal/reader/reader_test.go
diff --git a/v23/syncbase/nosql/internal/query/demo/writer/doc.go b/x/ref/syncbase/sb51/internal/writer/doc.go
similarity index 82%
rename from v23/syncbase/nosql/internal/query/demo/writer/doc.go
rename to x/ref/syncbase/sb51/internal/writer/doc.go
index 327277e..315ba57 100644
--- a/v23/syncbase/nosql/internal/query/demo/writer/doc.go
+++ b/x/ref/syncbase/sb51/internal/writer/doc.go
@@ -3,4 +3,6 @@
// license that can be found in the LICENSE file.
// Package writer provides functions for formatting query results.
+//
+// TODO(ivanpi): Export as VDL formatter library.
package writer
diff --git a/v23/syncbase/nosql/internal/query/demo/writer/writer.go b/x/ref/syncbase/sb51/internal/writer/writer.go
similarity index 97%
rename from v23/syncbase/nosql/internal/query/demo/writer/writer.go
rename to x/ref/syncbase/sb51/internal/writer/writer.go
index 4d04360..c7900b6 100644
--- a/v23/syncbase/nosql/internal/query/demo/writer/writer.go
+++ b/x/ref/syncbase/sb51/internal/writer/writer.go
@@ -13,7 +13,7 @@
"strings"
"unicode/utf8"
- "v.io/syncbase/v23/syncbase/nosql/internal/query"
+ "v.io/syncbase/v23/syncbase/nosql"
"v.io/v23/vdl"
vtime "v.io/v23/vdlroot/time"
)
@@ -27,7 +27,7 @@
)
// WriteTable formats the results as ASCII tables.
-func WriteTable(out io.Writer, columnNames []string, rs query.ResultStream) error {
+func WriteTable(out io.Writer, columnNames []string, rs nosql.ResultStream) error {
// Buffer the results so we can compute the column widths.
columnWidths := make([]int, len(columnNames))
for i, cName := range columnNames {
@@ -103,7 +103,7 @@
}
// WriteCSV formats the results as CSV as specified by https://tools.ietf.org/html/rfc4180.
-func WriteCSV(out io.Writer, columnNames []string, rs query.ResultStream, delimiter string) error {
+func WriteCSV(out io.Writer, columnNames []string, rs nosql.ResultStream, delimiter string) error {
delim := ""
for _, cName := range columnNames {
str := doubleQuoteForCSV(cName, delimiter)
@@ -140,7 +140,7 @@
}
// WriteJson formats the result as a JSON array of arrays (rows) of values.
-func WriteJson(out io.Writer, columnNames []string, rs query.ResultStream) error {
+func WriteJson(out io.Writer, columnNames []string, rs nosql.ResultStream) error {
io.WriteString(out, "[")
jsonColNames := make([][]byte, len(columnNames))
for i, cName := range columnNames {
diff --git a/v23/syncbase/nosql/internal/query/demo/writer/writer_test.go b/x/ref/syncbase/sb51/internal/writer/writer_test.go
similarity index 98%
rename from v23/syncbase/nosql/internal/query/demo/writer/writer_test.go
rename to x/ref/syncbase/sb51/internal/writer/writer_test.go
index 55a3e00..d19b43f 100644
--- a/v23/syncbase/nosql/internal/query/demo/writer/writer_test.go
+++ b/x/ref/syncbase/sb51/internal/writer/writer_test.go
@@ -10,9 +10,9 @@
"testing"
"time"
- "v.io/syncbase/v23/syncbase/nosql/internal/query"
- "v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db"
- "v.io/syncbase/v23/syncbase/nosql/internal/query/demo/writer"
+ "v.io/syncbase/v23/syncbase/nosql"
+ db "v.io/syncbase/x/ref/syncbase/sb51/internal/demodb"
+ "v.io/syncbase/x/ref/syncbase/sb51/internal/writer"
"v.io/v23/vdl"
)
@@ -21,7 +21,7 @@
curr int
}
-func newResultStream(iRows [][]interface{}) query.ResultStream {
+func newResultStream(iRows [][]interface{}) nosql.ResultStream {
vRows := make([][]*vdl.Value, len(iRows))
for i, iRow := range iRows {
vRow := make([]*vdl.Value, len(iRow))
diff --git a/x/ref/syncbase/sb51/main.go b/x/ref/syncbase/sb51/main.go
new file mode 100644
index 0000000..9968722
--- /dev/null
+++ b/x/ref/syncbase/sb51/main.go
@@ -0,0 +1,34 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Antimony (sb51) - Syncbase general-purpose client and management utility.
+// Currently supports SyncQL select queries.
+
+package main
+
+import (
+ "flag"
+
+ "v.io/x/lib/cmdline"
+ _ "v.io/x/ref/runtime/factories/generic"
+)
+
+func main() {
+ cmdline.Main(cmdSb51)
+}
+
+var cmdSb51 = &cmdline.Command{
+ Name: "sb51",
+ Short: "Antimony - Vanadium Syncbase client and management utility",
+ Long: `
+Syncbase general-purpose client and management utility.
+Currently supports starting a SyncQL shell.
+`,
+ Children: []*cmdline.Command{cmdSbShell},
+}
+
+var (
+ // TODO(ivanpi): Decide on convention for local syncbase service name.
+ flagSbService = flag.String("service", "/:8101/syncbase", "Location of the Syncbase service to connect to. Can be absolute or relative to the namespace root.")
+)
diff --git a/x/ref/syncbase/sb51/shell.go b/x/ref/syncbase/sb51/shell.go
new file mode 100644
index 0000000..a41d245
--- /dev/null
+++ b/x/ref/syncbase/sb51/shell.go
@@ -0,0 +1,240 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Syncbase client shell. Currently supports SyncQL select queries.
+
+package main
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "strconv"
+ "strings"
+
+ isatty "github.com/mattn/go-isatty"
+
+ "v.io/syncbase/v23/syncbase"
+ "v.io/syncbase/v23/syncbase/nosql"
+ "v.io/syncbase/x/ref/syncbase/sb51/internal/demodb"
+ "v.io/syncbase/x/ref/syncbase/sb51/internal/reader"
+ "v.io/syncbase/x/ref/syncbase/sb51/internal/writer"
+ "v.io/v23/context"
+ "v.io/x/lib/cmdline"
+ "v.io/x/ref/lib/v23cmd"
+)
+
+var cmdSbShell = &cmdline.Command{
+ Runner: v23cmd.RunnerFunc(runSbShell),
+ Name: "sh",
+ Short: "Start a SyncQL shell",
+ Long: `
+Connect to a database on the Syncbase service and start a SyncQL shell.
+`,
+ ArgsName: "<app_name> <db_name>",
+ ArgsLong: `
+<app_name> and <db_name> specify the database to execute queries against.
+The database must exist unless -create-missing is specified.
+`,
+}
+
+var (
+ flagFormat string
+ flagCSVDelimiter string
+ flagCreateIfNotExists bool
+ flagMakeDemoTables bool
+)
+
+func init() {
+ cmdSbShell.Flags.StringVar(&flagFormat, "format", "table", "Output format. 'table': human-readable table; 'csv': comma-separated values, use -csv-delimiter to control the delimiter; 'json': JSON objects.")
+ cmdSbShell.Flags.StringVar(&flagCSVDelimiter, "csv-delimiter", ",", "Delimiter to use when printing data as CSV (e.g. \"\t\", \",\")")
+ cmdSbShell.Flags.BoolVar(&flagCreateIfNotExists, "create-missing", false, "Create the app and/or database if they do not exist yet.")
+ cmdSbShell.Flags.BoolVar(&flagMakeDemoTables, "make-demo", false, "(Re)create demo tables in the database.")
+}
+
+func validateFlags() error {
+ if flagFormat != "table" && flagFormat != "csv" && flagFormat != "json" {
+ return fmt.Errorf("Unsupported -format %q. Must be one of 'table', 'csv', or 'json'.", flagFormat)
+ }
+ if len(flagCSVDelimiter) == 0 {
+ return fmt.Errorf("-csv-delimiter cannot be empty.")
+ }
+ return nil
+}
+
+// Starts a SyncQL shell against the specified database.
+// Runs in interactive or batch mode depending on stdin.
+func runSbShell(ctx *context.T, env *cmdline.Env, args []string) error {
+ // TODO(ivanpi): Add 'use' statement, default to no app/database selected.
+ if len(args) != 2 {
+ return env.UsageErrorf("exactly two arguments expected")
+ }
+ appName, dbName := args[0], args[1]
+ if err := validateFlags(); err != nil {
+ return env.UsageErrorf("%v", err)
+ }
+
+ sbs := syncbase.NewService(*flagSbService)
+ d, err := openAppDB(ctx, sbs, appName, dbName, flagCreateIfNotExists)
+ if err != nil {
+ return err
+ }
+
+ if flagMakeDemoTables {
+ if err := makeDemoDB(ctx, d); err != nil {
+ return err
+ }
+ }
+
+ var input *reader.T
+ // TODO(ivanpi): This is hacky, it would be better for lib/cmdline to support IsTerminal.
+ stdinFile, ok := env.Stdin.(*os.File)
+ isTerminal := ok && isatty.IsTerminal(stdinFile.Fd())
+ if isTerminal {
+ input = reader.NewInteractive()
+ } else {
+ input = reader.NewNonInteractive()
+ }
+ defer input.Close()
+
+stmtLoop:
+ for true {
+ if q, err := input.GetQuery(); err != nil {
+ if err == io.EOF {
+ if isTerminal {
+ // ctrl-d
+ fmt.Println()
+ }
+ break
+ } else {
+ // ctrl-c
+ break
+ }
+ } else {
+ var err error
+ tq := strings.Fields(q)
+ if len(tq) > 0 {
+ switch strings.ToLower(tq[0]) {
+ case "exit", "quit":
+ break stmtLoop
+ case "dump":
+ err = dumpDB(ctx, env.Stdout, d)
+ case "make-demo":
+ err = makeDemoDB(ctx, d)
+ case "select":
+ err = queryExec(ctx, env.Stdout, d, q)
+ default:
+ err = fmt.Errorf("unknown statement: '%s'; expected one of: 'select', 'make-demo', 'dump', 'exit', 'quit'", strings.ToLower(tq[0]))
+ }
+ }
+ if err != nil {
+ if isTerminal {
+ fmt.Fprintln(env.Stderr, "Error:", err)
+ } else {
+ // If running non-interactively, errors stop execution.
+ return err
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func openAppDB(ctx *context.T, sbs syncbase.Service, appName, dbName string, createIfNotExists bool) (nosql.Database, error) {
+ app := sbs.App(appName)
+ if exists, err := app.Exists(ctx); err != nil {
+ return nil, fmt.Errorf("failed checking for app %q: %v", app.FullName(), err)
+ } else if !exists {
+ if !createIfNotExists {
+ return nil, fmt.Errorf("app %q does not exist", app.FullName())
+ }
+ if err := app.Create(ctx, nil); err != nil {
+ return nil, err
+ }
+ }
+ d := app.NoSQLDatabase(dbName)
+ if exists, err := d.Exists(ctx); err != nil {
+ return nil, fmt.Errorf("failed checking for db %q: %v", d.FullName(), err)
+ } else if !exists {
+ if !createIfNotExists {
+ return nil, fmt.Errorf("db %q does not exist", d.FullName())
+ }
+ if err := d.Create(ctx, nil); err != nil {
+ return nil, err
+ }
+ }
+ return d, nil
+}
+
+func dumpDB(ctx *context.T, w io.Writer, d nosql.Database) error {
+ tables, err := d.ListTables(ctx)
+ if err != nil {
+ return fmt.Errorf("failed listing tables: %v", err)
+ }
+ var errs []error
+ for _, table := range tables {
+ fmt.Fprintf(w, "table: %s\n", table)
+ if err := queryExec(ctx, w, d, fmt.Sprintf("select k, v from %s", table)); err != nil {
+ errs = append(errs, fmt.Errorf("> %s: %v", table, err))
+ }
+ }
+ if len(errs) > 0 {
+ err := fmt.Errorf("failed dumping %d of %d tables:", len(errs), len(tables))
+ for _, e := range errs {
+ err = fmt.Errorf("%v\n%v", err, e)
+ }
+ return err
+ }
+ return nil
+}
+
+func makeDemoDB(ctx *context.T, d nosql.Database) error {
+ if err := demodb.PopulateDemoDB(ctx, d); err != nil {
+ return fmt.Errorf("failed making demo tables: %v", err)
+ }
+ return nil
+}
+
+// Split an error message into an offset and the remaining (i.e., rhs of offset) message.
+// The convention for syncql is "<module><optional-rpc>[offset]<remaining-message>".
+func splitError(err error) (int64, string) {
+ errMsg := err.Error()
+ idx1 := strings.Index(errMsg, "[")
+ idx2 := strings.Index(errMsg, "]")
+ if idx1 == -1 || idx2 == -1 {
+ return 0, errMsg
+ }
+ offsetString := errMsg[idx1+1 : idx2]
+ offset, err := strconv.ParseInt(offsetString, 10, 64)
+ if err != nil {
+ return 0, errMsg
+ }
+ return offset, errMsg[idx2+1:]
+}
+
+func queryExec(ctx *context.T, w io.Writer, d nosql.Database, q string) error {
+ if columnNames, rs, err := d.Exec(ctx, q); err != nil {
+ off, msg := splitError(err)
+ return fmt.Errorf("\n%s\n%s^\n%d: %s", q, strings.Repeat(" ", int(off)), off+1, msg)
+ } else {
+ switch flagFormat {
+ case "table":
+ if err := writer.WriteTable(w, columnNames, rs); err != nil {
+ return err
+ }
+ case "csv":
+ if err := writer.WriteCSV(w, columnNames, rs, flagCSVDelimiter); err != nil {
+ return err
+ }
+ case "json":
+ if err := writer.WriteJson(w, columnNames, rs); err != nil {
+ return err
+ }
+ default:
+ panic(fmt.Sprintf("invalid format flag value: %v", flagFormat))
+ }
+ }
+ return nil
+}