synbase: syncQL: demo program enhancements

Respond to kash feedback on demo program:
- add column names
  Note: the column names do not line up with the rows.
        Max column widths aren't known until the entire
        result set is iterated over.
  Note2: as <name> would be useful to support since column names
         are now being returned.  I'll do that in a later CL.
- I checked in third_party github.com/peterh/liner for
  libreadline functionality and use it here
  (currently history is not persisted across executions)
- ctrl-d now exits (ctrl-c, quit and exit work too).
- make sure a newline is emitted on ctrl-c.
- change dump command to use the query language (select k, v from ...)
  This prints out the values with %v and it is easy to see types
  (and strings have quotes around them).
- add a missing closing paren in three error messages
- offset no longer printed in error messages.

This change does NOT do the following:
implement distinct(t)

Change-Id: I640280cd174c1735f5a431f62f86dba8c18e571b
diff --git a/v23/syncbase/nosql/internal/query/demo/db/db.go b/v23/syncbase/nosql/internal/query/demo/db/db.go
new file mode 100644
index 0000000..d59af00
--- /dev/null
+++ b/v23/syncbase/nosql/internal/query/demo/db/db.go
@@ -0,0 +1,177 @@
+// 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"
+	"strings"
+
+	"v.io/syncbase/v23/syncbase/nosql/internal/query/query_db"
+	"v.io/v23/vdl"
+)
+
+var d *demoDB
+
+func init() {
+	d = createDB()
+}
+
+type demoDB struct {
+	tables []table
+}
+
+type kv struct {
+	key   string
+	value *vdl.Value
+}
+
+type table struct {
+	name string
+	rows []kv
+}
+
+type keyValueStreamImpl struct {
+	table        table
+	cursor       int
+	prefixes     []string
+	prefixCursor int
+}
+
+func (kvs *keyValueStreamImpl) Advance() bool {
+	for true {
+		kvs.cursor++ // initialized to -1
+		if kvs.cursor >= len(kvs.table.rows) {
+			return false
+		}
+		for kvs.prefixCursor < len(kvs.prefixes) {
+			// does it match any prefix
+			if kvs.prefixes[kvs.prefixCursor] == "" || strings.HasPrefix(kvs.table.rows[kvs.cursor].key, kvs.prefixes[kvs.prefixCursor]) {
+				return true
+			}
+			// Keys and prefixes are both sorted low to high, so we can increment
+			// prefixCursor if the prefix is < the key.
+			if kvs.prefixes[kvs.prefixCursor] < kvs.table.rows[kvs.cursor].key {
+				kvs.prefixCursor++
+				if kvs.prefixCursor >= len(kvs.prefixes) {
+					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(prefixes []string) (query_db.KeyValueStream, error) {
+	var keyValueStreamImpl keyValueStreamImpl
+	keyValueStreamImpl.table = t
+	keyValueStreamImpl.cursor = -1
+	keyValueStreamImpl.prefixes = prefixes
+	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 (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)
+
+	return d
+}
diff --git a/v23/syncbase/nosql/internal/query/demo/db_objects.vdl b/v23/syncbase/nosql/internal/query/demo/db/db_objects.vdl
similarity index 98%
rename from v23/syncbase/nosql/internal/query/demo/db_objects.vdl
rename to v23/syncbase/nosql/internal/query/demo/db/db_objects.vdl
index a6338c0..e21e4f5 100644
--- a/v23/syncbase/nosql/internal/query/demo/db_objects.vdl
+++ b/v23/syncbase/nosql/internal/query/demo/db/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 main
+package db
 
 type AddressInfo struct {
 	Street string
diff --git a/v23/syncbase/nosql/internal/query/demo/db_objects.vdl.go b/v23/syncbase/nosql/internal/query/demo/db/db_objects.vdl.go
similarity index 94%
rename from v23/syncbase/nosql/internal/query/demo/db_objects.vdl.go
rename to v23/syncbase/nosql/internal/query/demo/db/db_objects.vdl.go
index dd96a5c..72bab1a 100644
--- a/v23/syncbase/nosql/internal/query/demo/db_objects.vdl.go
+++ b/v23/syncbase/nosql/internal/query/demo/db/db_objects.vdl.go
@@ -5,7 +5,7 @@
 // This file was auto-generated by the vanadium vdl tool.
 // Source: db_objects.vdl
 
-package main
+package db
 
 import (
 	// VDL system imports
@@ -21,7 +21,7 @@
 }
 
 func (AddressInfo) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.AddressInfo"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.AddressInfo"`
 }) {
 }
 
@@ -56,7 +56,7 @@
 		return nil
 	}
 	*x = -1
-	return fmt.Errorf("unknown label %q in main.CreditAgency", label)
+	return fmt.Errorf("unknown label %q in db.CreditAgency", label)
 }
 
 // String returns the string label of x.
@@ -73,7 +73,7 @@
 }
 
 func (CreditAgency) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.CreditAgency"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.CreditAgency"`
 	Enum struct{ Equifax, Experian, TransUnion string }
 }) {
 }
@@ -105,7 +105,7 @@
 		return nil
 	}
 	*x = -1
-	return fmt.Errorf("unknown label %q in main.ExperianRating", label)
+	return fmt.Errorf("unknown label %q in db.ExperianRating", label)
 }
 
 // String returns the string label of x.
@@ -120,7 +120,7 @@
 }
 
 func (ExperianRating) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.ExperianRating"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.ExperianRating"`
 	Enum struct{ Good, Bad string }
 }) {
 }
@@ -130,7 +130,7 @@
 }
 
 func (EquifaxCreditReport) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.EquifaxCreditReport"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.EquifaxCreditReport"`
 }) {
 }
 
@@ -139,7 +139,7 @@
 }
 
 func (ExperianCreditReport) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.ExperianCreditReport"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.ExperianCreditReport"`
 }) {
 }
 
@@ -148,7 +148,7 @@
 }
 
 func (TransUnionCreditReport) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.TransUnionCreditReport"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.TransUnionCreditReport"`
 }) {
 }
 
@@ -172,7 +172,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.AgencyReport"`
+		Name  string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.AgencyReport"`
 		Type  AgencyReport
 		Union struct {
 			EquifaxReport    AgencyReportEquifaxReport
@@ -203,7 +203,7 @@
 }
 
 func (CreditReport) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.CreditReport"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.CreditReport"`
 }) {
 }
 
@@ -216,7 +216,7 @@
 }
 
 func (Customer) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.Customer"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.Customer"`
 }) {
 }
 
@@ -228,7 +228,7 @@
 }
 
 func (Invoice) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.Invoice"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.Invoice"`
 }) {
 }
 
@@ -247,7 +247,7 @@
 }
 
 func (Numbers) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.Numbers"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.Numbers"`
 }) {
 }
 
@@ -256,7 +256,7 @@
 }
 
 func (FooType) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.FooType"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.FooType"`
 }) {
 }
 
@@ -265,7 +265,7 @@
 }
 
 func (BarType) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.BarType"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.BarType"`
 }) {
 }
 
@@ -287,7 +287,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.TitleOrValueType"`
+		Name  string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.TitleOrValueType"`
 		Type  TitleOrValueType
 		Union struct {
 			Title TitleOrValueTypeTitle
@@ -312,7 +312,7 @@
 }
 
 func (BazType) __VDLReflect(struct {
-	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo.BazType"`
+	Name string `vdl:"v.io/syncbase/v23/syncbase/nosql/internal/query/demo/db.BazType"`
 }) {
 }
 
diff --git a/v23/syncbase/nosql/internal/query/demo/db/doc.go b/v23/syncbase/nosql/internal/query/demo/db/doc.go
new file mode 100644
index 0000000..e6a1508
--- /dev/null
+++ b/v23/syncbase/nosql/internal/query/demo/db/doc.go
@@ -0,0 +1,6 @@
+// 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
index 81274e0..e5c5e47 100644
--- a/v23/syncbase/nosql/internal/query/demo/demo.go
+++ b/v23/syncbase/nosql/internal/query/demo/demo.go
@@ -5,186 +5,41 @@
 package main
 
 import (
-	"bufio"
-	"errors"
 	"fmt"
+	"io"
 	"os"
 	"strings"
 
+	"github.com/peterh/liner"
 	"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/query_db"
-	"v.io/v23/vdl"
 )
 
-type demoDB struct {
-	tables []table
-}
-
-type kv struct {
-	key   string
-	value *vdl.Value
-}
-
-type table struct {
-	name string
-	rows []kv
-}
-
-type keyValueStreamImpl struct {
-	table        table
-	cursor       int
-	prefixes     []string
-	prefixCursor int
-}
-
-func (kvs *keyValueStreamImpl) Advance() bool {
-	for true {
-		kvs.cursor++ // initialized to -1
-		if kvs.cursor >= len(kvs.table.rows) {
-			return false
-		}
-		for kvs.prefixCursor < len(kvs.prefixes) {
-			// does it match any prefix
-			if kvs.prefixes[kvs.prefixCursor] == "" || strings.HasPrefix(kvs.table.rows[kvs.cursor].key, kvs.prefixes[kvs.prefixCursor]) {
-				return true
-			}
-			// Keys and prefixes are both sorted low to high, so we can increment
-			// prefixCursor if the prefix is < the key.
-			if kvs.prefixes[kvs.prefixCursor] < kvs.table.rows[kvs.cursor].key {
-				kvs.prefixCursor++
-				if kvs.prefixCursor >= len(kvs.prefixes) {
-					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(prefixes []string) (query_db.KeyValueStream, error) {
-	var keyValueStreamImpl keyValueStreamImpl
-	keyValueStreamImpl.table = t
-	keyValueStreamImpl.cursor = -1
-	keyValueStreamImpl.prefixes = prefixes
-	return &keyValueStreamImpl, nil
-}
-
-func (db demoDB) GetTable(table string) (query_db.Table, error) {
-	for _, t := range db.tables {
-		if t.name == table {
-			return t, nil
-		}
-	}
-	return nil, errors.New(fmt.Sprintf("No such table: %s.", table))
-
-}
-
-func createDB() query_db.Database {
-	var db 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"}}),
-		},
-	}
-	db.tables = append(db.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)}),
-		},
-	}
-	db.tables = append(db.tables, numTable)
-
-	return db
-}
-
-func dumpDB(db query_db.Database) {
-	switch db := db.(type) {
-	case demoDB:
-		for _, t := range db.tables {
-			fmt.Printf("table: %s\n", t.name)
-			for _, row := range t.rows {
-				fmt.Printf("key: %s, value: %v", row.key, row.value)
-			}
-		}
+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))
 	}
 }
 
-func dumpAddress(a *AddressInfo) string {
-	return fmt.Sprintf("Street:%v,City:%v,State:%v,Zip:%v", a.Street, a.City, a.State, a.Zip)
-}
-
-func queryExec(db query_db.Database, q string) {
-	if rs, err := query.Exec(db, q); err != nil {
+func queryExec(d query_db.Database, q string) {
+	if columnNames, rs, err := query.Exec(d, q); err != nil {
 		fmt.Fprintf(os.Stderr, "%s\n", q)
 		fmt.Fprintf(os.Stderr, "%s^\n", strings.Repeat(" ", int(err.Off)))
-		fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
+		fmt.Fprintf(os.Stderr, "Error: %s\n", err.Msg)
 	} else {
+		sep := ""
+		for _, cName := range columnNames {
+			fmt.Printf("%s%s", sep, cName)
+			sep = " | "
+		}
+		fmt.Printf("\n")
 		for rs.Advance() {
-			for i, column := range rs.Result() {
-				if i != 0 {
-					fmt.Printf(",")
-				}
-				fmt.Printf("%v", column)
+			sep = ""
+			for _, column := range rs.Result() {
+				fmt.Printf("%s%v", sep, column)
+				sep = " | "
 			}
 			fmt.Printf("\n")
 		}
@@ -195,22 +50,30 @@
 }
 
 func main() {
-	db := createDB()
-	reader := bufio.NewReader(os.Stdin)
+	line := liner.NewLiner()
+	defer line.Close()
+	line.SetCtrlCAborts(true)
+
+	d := db.GetDatabase()
 	for true {
-		fmt.Printf("Enter query (or 'dump' or 'exit')? ")
-		q, err := reader.ReadString('\n')
-		if err != nil {
-			fmt.Fprintf(os.Stderr, fmt.Sprintf("Input error: %s\n", err.Error()))
-		} else {
-			// kill the newline
-			q = q[0 : len(q)-1]
-			if q == "" || strings.ToLower(q) == "exit" {
-				os.Exit(0)
-			} else if strings.ToLower(q) == "dump" {
-				dumpDB(db)
+		if q, err := line.Prompt("Enter query or 'dump'? "); err != nil {
+			if err == io.EOF {
+				// ctrl-d
+				fmt.Println()
+				break
 			} else {
-				queryExec(db, q)
+				// ctrl-c
+				break
+			}
+		} else {
+			if q == "" || strings.ToLower(q) == "exit" || strings.ToLower(q) == "quit" {
+				break
+			}
+			line.AppendHistory(q)
+			if strings.ToLower(q) == "dump" {
+				dumpDB(d)
+			} else {
+				queryExec(d, q)
 			}
 		}
 	}
diff --git a/v23/syncbase/nosql/internal/query/query.go b/v23/syncbase/nosql/internal/query/query.go
index 5c76cd2..c0a17ea 100644
--- a/v23/syncbase/nosql/internal/query/query.go
+++ b/v23/syncbase/nosql/internal/query/query.go
@@ -26,19 +26,19 @@
 	Cancel()
 }
 
-func Exec(db query_db.Database, q string) (ResultStream, *QueryError) {
+func Exec(db query_db.Database, q string) ([]string, ResultStream, *QueryError) {
 	s, err := query_parser.Parse(q)
 	if err != nil {
-		return nil, ErrorFromSyntax(err)
+		return nil, nil, ErrorFromSyntax(err)
 	}
 	if err := query_checker.Check(db, s); err != nil {
-		return nil, ErrorFromSemantic(err)
+		return nil, nil, ErrorFromSemantic(err)
 	}
 	switch sel := (*s).(type) {
 	case query_parser.SelectStatement:
 		return execSelect(db, &sel)
 	default:
-		return nil, Error((*s).Offset(), fmt.Sprintf("Cannot exec statement type %v", reflect.TypeOf(*s)))
+		return nil, nil, Error((*s).Offset(), fmt.Sprintf("Cannot exec statement type %v", reflect.TypeOf(*s)))
 	}
 }
 
@@ -156,14 +156,28 @@
 	rs.keyValueStream.Cancel()
 }
 
-func execSelect(db query_db.Database, s *query_parser.SelectStatement) (ResultStream, *QueryError) {
+func getColumnHeadings(s *query_parser.SelectStatement) []string {
+	columnHeaders := []string{}
+	for _, field := range s.Select.Columns {
+		sep := ""
+		columnName := ""
+		for _, segment := range field.Segments {
+			columnName = columnName + sep + segment.Value
+			sep = "."
+		}
+		columnHeaders = append(columnHeaders, columnName)
+	}
+	return columnHeaders
+}
+
+func execSelect(db query_db.Database, s *query_parser.SelectStatement) ([]string, ResultStream, *QueryError) {
 	prefixes := CompileKeyPrefixes(s.Where)
 	keyValueStream, err := s.From.Table.DBTable.Scan(prefixes)
 	if err != nil {
-		return nil, Error(s.Off, err.Error())
+		return nil, nil, Error(s.Off, err.Error())
 	}
 	var resultStream resultStreamImpl
 	resultStream.selectStatement = s
 	resultStream.keyValueStream = keyValueStream
-	return &resultStream, nil
+	return getColumnHeadings(s), &resultStream, nil
 }
diff --git a/v23/syncbase/nosql/internal/query/query_parser/query_parser.go b/v23/syncbase/nosql/internal/query/query_parser/query_parser.go
index 2e45daf..66964c5 100644
--- a/v23/syncbase/nosql/internal/query/query_parser/query_parser.go
+++ b/v23/syncbase/nosql/internal/query/query_parser/query_parser.go
@@ -684,7 +684,7 @@
 			}
 			token = scanToken(s)
 		default:
-			return nil, nil, Error(token.Off, fmt.Sprintf("Expected operator ('like', 'not like', '=', '<>', '<', '<=', '>', '>=', 'equal' or 'not equal', found '%s'.", token.Value))
+			return nil, nil, Error(token.Off, fmt.Sprintf("Expected operator ('like', 'not like', '=', '<>', '<', '<=', '>', '>=', 'equal' or 'not equal'), found '%s'.", token.Value))
 		}
 	} else {
 		switch token.Tok {
@@ -715,7 +715,7 @@
 				operator.Type = GreaterThan
 			}
 		default:
-			return nil, nil, Error(token.Off, fmt.Sprintf("Expected operator ('like', 'not like', '=', '<>', 'equal' or 'not equal', found '%s'.", token.Value))
+			return nil, nil, Error(token.Off, fmt.Sprintf("Expected operator ('like', 'not like', '=', '<>', 'equal' or 'not equal'), found '%s'.", token.Value))
 		}
 	}
 
@@ -735,7 +735,7 @@
 	case "or":
 		operator.Type = Or
 	default:
-		return nil, nil, Error(token.Off, fmt.Sprintf("Expected operator ('and' or 'or', found '%s'.", token.Value))
+		return nil, nil, Error(token.Off, fmt.Sprintf("Expected operator ('and' or 'or'), found '%s'.", token.Value))
 	}
 
 	token = scanToken(s)
diff --git a/v23/syncbase/nosql/internal/query/query_parser/query_parser_test.go b/v23/syncbase/nosql/internal/query/query_parser/query_parser_test.go
index 8053e43..ee19add 100644
--- a/v23/syncbase/nosql/internal/query/query_parser/query_parser_test.go
+++ b/v23/syncbase/nosql/internal/query/query_parser/query_parser_test.go
@@ -2211,7 +2211,7 @@
 		{"select foo from Customer Invoice", query_parser.Error(25, "Unexpected: 'Invoice'.")},
 		{"select (foo) from (Customer)", query_parser.Error(7, "Expected identifier, found '('.")},
 		{"select foo, bar from Customer where a = (b)", query_parser.Error(40, "Expected operand, found '('.")},
-		{"select foo, bar from Customer where a = b and (c) = d", query_parser.Error(48, "Expected operator ('like', 'not like', '=', '<>', 'equal' or 'not equal', found ')'.")},
+		{"select foo, bar from Customer where a = b and (c) = d", query_parser.Error(48, "Expected operator ('like', 'not like', '=', '<>', 'equal' or 'not equal'), found ')'.")},
 		{"select foo, bar from Customer where a = b and c =", query_parser.Error(49, "Unexpected end of statement, expected operand.")},
 		{"select foo, bar from Customer where a = ", query_parser.Error(40, "Unexpected end of statement, expected operand.")},
 		{"select foo, bar from Customer where a", query_parser.Error(37, "Unexpected end of statement, expected operator.")},
@@ -2219,11 +2219,11 @@
 		{"select a from", query_parser.Error(13, "Unexpected end of statement.")},
 		{"select a from b where c = d and e =", query_parser.Error(35, "Unexpected end of statement, expected operand.")},
 		{"select a from b where c = d and f", query_parser.Error(33, "Unexpected end of statement, expected operator.")},
-		{"select a from b where c = d and f *", query_parser.Error(34, "Expected operator ('like', 'not like', '=', '<>', 'equal' or 'not equal', found '*'.")},
+		{"select a from b where c = d and f *", query_parser.Error(34, "Expected operator ('like', 'not like', '=', '<>', 'equal' or 'not equal'), found '*'.")},
 		{"select a from b where c <", query_parser.Error(25, "Unexpected end of statement, expected operand.")},
 		{"select a from b where c not", query_parser.Error(27, "Expected 'equal' or 'like'")},
 		{"select a from b where c not 8", query_parser.Error(28, "Expected 'equal' or 'like'")},
-		{"select x from y where a and b = c", query_parser.Error(24, "Expected operator ('like', 'not like', '=', '<>', '<', '<=', '>', '>=', 'equal' or 'not equal', found 'and'.")},
+		{"select x from y where a and b = c", query_parser.Error(24, "Expected operator ('like', 'not like', '=', '<>', '<', '<=', '>', '>=', 'equal' or 'not equal'), found 'and'.")},
 		{"select v from Customer limit 100 offset a", query_parser.Error(40, "Expected positive integer literal., found 'a'.")},
 		{"select v from Customer limit -100 offset 5", query_parser.Error(29, "Expected positive integer literal., found '-'.")},
 		{"select v from Customer limit 100 offset -5", query_parser.Error(40, "Expected positive integer literal., found '-'.")},
diff --git a/v23/syncbase/nosql/internal/query/test/query_test.go b/v23/syncbase/nosql/internal/query/test/query_test.go
index f61b8b1..6c597eb 100644
--- a/v23/syncbase/nosql/internal/query/test/query_test.go
+++ b/v23/syncbase/nosql/internal/query/test/query_test.go
@@ -589,7 +589,7 @@
 	}
 
 	for _, test := range basic {
-		rs, err := query.Exec(db, test.query)
+		_, rs, err := query.Exec(db, test.query)
 		if err != nil {
 			t.Errorf("query: %s; got %v, want nil", test.query, err)
 		} else {
@@ -1191,7 +1191,7 @@
 	}
 
 	for _, test := range basic {
-		_, err := query.Exec(db, test.query)
+		_, _, err := query.Exec(db, test.query)
 		if !reflect.DeepEqual(err, test.err) {
 			t.Errorf("query: %s; got %v, want %v", test.query, err, test.err)
 		}