syncbase: syncQL: add back query_checker_test for offset

Add a check for the correct offset in query_checker_test.go.

Change-Id: Ib525ad95e2070b2c88b6a5ec6bcf0667fe208b19
diff --git a/v23/syncbase/nosql/internal/query/query_checker/query_checker_test.go b/v23/syncbase/nosql/internal/query/query_checker/query_checker_test.go
index 93947b2..ba3b5a3 100644
--- a/v23/syncbase/nosql/internal/query/query_checker/query_checker_test.go
+++ b/v23/syncbase/nosql/internal/query/query_checker/query_checker_test.go
@@ -13,67 +13,15 @@
 	"v.io/syncbase/v23/syncbase/nosql/internal/query/query_checker"
 	"v.io/syncbase/v23/syncbase/nosql/internal/query/query_parser"
 	"v.io/syncbase/v23/syncbase/nosql/query_db"
+	"v.io/syncbase/v23/syncbase/nosql/query_exec"
 	"v.io/syncbase/v23/syncbase/nosql/syncql"
 	"v.io/v23"
 	"v.io/v23/context"
 	"v.io/v23/verror"
 	_ "v.io/x/ref/runtime/factories/generic"
 	"v.io/x/ref/test"
-	"strings"
 )
 
-// oneOf represents an expectation that more than one type of error may be
-// returned by the corresponding test.
-type oneOf []interface{}
-
-func (o oneOf) matches(err error) bool {
-	for _, e := range o {
-		switch e := e.(type) {
-		case error:
-			if e.Error() == err.Error() {
-				return true
-			}
-		case vErrorContaining:
-			vErr, ok := err.(verror.E)
-			if !ok {
-				continue
-			}
-			if e.matches(vErr) {
-				return true
-			}
-		}
-	}
-	return false
-}
-
-// containing represents an expectation that an error will be of the given
-// VError type and will contain the given string.
-type vErrorContaining struct {
-	id verror.ID
-	message string
-}
-
-func newVErrorContaining(id verror.ID, message string) vErrorContaining {
-	return vErrorContaining{
-		id: id,
-		message: message,
-	}
-}
-
-func (v vErrorContaining) matches(err verror.E) bool {
-	if err.ID != v.id {
-		return false
-	}
-	if !strings.Contains(err.Error(), v.message) {
-		return false
-	}
-	return true
-}
-
-func (v vErrorContaining) String() string {
-	return fmt.Sprintf("VError containing ID=%s, message=%s", v.id, v.message)
-}
-
 type mockDB struct {
 	ctx *context.T
 }
@@ -133,7 +81,7 @@
 
 type parseSelectErrorTest struct {
 	query string
-	err   interface{}
+	err   error
 }
 
 func TestQueryChecker(t *testing.T) {
@@ -485,53 +433,18 @@
 		{"select v from Customer where v.ZipCode is not 94303", syncql.NewErrIsIsNotRequireRhsNil(db.GetContext(), 46)},
 		{"select v from Customer where v.ZipCode is not true", syncql.NewErrIsIsNotRequireRhsNil(db.GetContext(), 46)},
 		{"select v from Customer where v.ZipCode is not 943.03", syncql.NewErrIsIsNotRequireRhsNil(db.GetContext(), 46)},
-		// TODO(jkline): check offset for the 1.5 tests.
-		{"select v from Customer where Type(v) = \"Customer\" and Year(v.InvoiceDate, \"ABC\") = 2015", oneOf([]interface{}{
-			// Go1.4
-			syncql.NewErrLocationConversionError(db.GetContext(), 74, errors.New("unknown time zone ABC")),
-			// Go1.5
-			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find ABC in zip file"),
-		})},
-		{"select v from Customer where Type(v) = \"Customer\" and Month(v.InvoiceDate, \"ABC\") = 2015", oneOf([]interface{} {
-			// Go1.4
-			syncql.NewErrLocationConversionError(db.GetContext(), 75, errors.New("unknown time zone ABC")),
-			// Go1.5
-			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find ABC in zip file"),
-		})},
-		{"select v from Customer where Type(v) = \"Customer\" and Day(v.InvoiceDate, \"ABC\") = 2015", oneOf([]interface{} {
-			// Go1.4
-			syncql.NewErrLocationConversionError(db.GetContext(), 73, errors.New("unknown time zone ABC")),
-			// Go1.5
-			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find ABC in zip file"),
-		})},
-		{"select v from Customer where Type(v) = \"Customer\" and Hour(v.InvoiceDate, \"ABC\") = 2015", oneOf([]interface{} {
-			// Go1.4
-			syncql.NewErrLocationConversionError(db.GetContext(), 74, errors.New("unknown time zone ABC")),
-			// Go1.5
-			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find ABC in zip file"),
-		})},
-		{"select v from Customer where Type(v) = \"Customer\" and Minute(v.InvoiceDate, \"ABC\") = 2015", oneOf([]interface{} {
-			// Go1.4
-			syncql.NewErrLocationConversionError(db.GetContext(), 76, errors.New("unknown time zone ABC")),
-			// Go1.5
-			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find ABC in zip file"),
-		})},
-		{"select v from Customer where Type(v) = \"Customer\" and Second(v.InvoiceDate, \"ABC\") = 2015", oneOf([]interface{} {
-			// Go1.4
-			syncql.NewErrLocationConversionError(db.GetContext(), 76, errors.New("unknown time zone ABC")),
-			// Go1.5
-			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find ABC in zip file"),
-		})},
+		{"select v from Customer where Type(v) = \"Customer\" and Year(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrLocationConversionError(db.GetContext(), 74, errors.New("unknown time zone ABC"))},
+		{"select v from Customer where Type(v) = \"Customer\" and Month(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrLocationConversionError(db.GetContext(), 75, errors.New("unknown time zone ABC"))},
+		{"select v from Customer where Type(v) = \"Customer\" and Day(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrLocationConversionError(db.GetContext(), 73, errors.New("unknown time zone ABC"))},
+		{"select v from Customer where Type(v) = \"Customer\" and Hour(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrLocationConversionError(db.GetContext(), 74, errors.New("unknown time zone ABC"))},
+		{"select v from Customer where Type(v) = \"Customer\" and Minute(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrLocationConversionError(db.GetContext(), 76, errors.New("unknown time zone ABC"))},
+		{"select v from Customer where Type(v) = \"Customer\" and Second(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrLocationConversionError(db.GetContext(), 76, errors.New("unknown time zone ABC"))},
 		{"select v from Customer where Type(v) = \"Customer\" and Now(v.InvoiceDate, \"ABC\") = 2015", syncql.NewErrFunctionArgCount(db.GetContext(), 54, "Now", 0, 2)},
 		{"select v from Customer where Type(v) = \"Customer\" and Lowercase(v.Name, 2) = \"smith\"", syncql.NewErrFunctionArgCount(db.GetContext(), 54, "Lowercase", 1, 2)},
 		{"select v from Customer where Type(v) = \"Customer\" and Uppercase(v.Name, 2) = \"SMITH\"", syncql.NewErrFunctionArgCount(db.GetContext(), 54, "Uppercase", 1, 2)},
 		{"select Time() from Customer", syncql.NewErrFunctionArgCount(db.GetContext(), 7, "Time", 2, 0)},
-		{"select Year(v.InvoiceDate, \"Foo\") from Customer where Type(v) = \"Invoice\"", oneOf([]interface{}{
-			// Go1.4
-			syncql.NewErrLocationConversionError(db.GetContext(), 27, errors.New("unknown time zone Foo")),
-			// Go1.5
-			newVErrorContaining(syncql.ErrLocationConversionError.ID, "cannot find Foo in zip file"),
-		})},
+		{"select Year(v.InvoiceDate, \"Foo\") from Customer where Type(v) = \"Invoice\"",
+			syncql.NewErrLocationConversionError(db.GetContext(), 27, errors.New("unknown time zone Foo"))},
 		{"select K from Customer where Type(v) = \"Invoice\"", syncql.NewErrDidYouMeanLowercaseK(db.GetContext(), 7)},
 		{"select V from Customer where Type(v) = \"Invoice\"", syncql.NewErrDidYouMeanLowercaseV(db.GetContext(), 7)},
 		{"select k from Customer where K = \"001\"", syncql.NewErrDidYouMeanLowercaseK(db.GetContext(), 29)},
@@ -545,16 +458,11 @@
 			t.Errorf("query: %s; unexpected error: got %v, want nil", test.query, syntaxErr)
 		} else {
 			err := query_checker.Check(&db, s)
-			// Test both that the IDs compare and the text compares (since the offset needs to match).
-			switch e := test.err.(type) {
-			case error:
-				if verror.ErrorID(err) != verror.ErrorID(e) || err.Error() != e.Error() {
-					t.Errorf("query: %s; got %v, want %v", test.query, err, test.err)
-				}
-			case oneOf:
-				if !e.matches(err) {
-					t.Errorf("query: %s; got %v, want one of %s", test.query, err, e)
-				}
+			// Test both that the ID and offset are equal.
+			testErrOff, _ := query_exec.SplitError(test.err)
+			errOff, _ := query_exec.SplitError(err)
+			if verror.ErrorID(test.err) != verror.ErrorID(err) || testErrOff != errOff {
+				t.Errorf("query: %s; got %v, want %v", test.query, err, test.err)
 			}
 		}
 	}
diff --git a/v23/syncbase/nosql/query_exec/error_offset_test.go b/v23/syncbase/nosql/query_exec/error_offset_test.go
new file mode 100644
index 0000000..caff1de
--- /dev/null
+++ b/v23/syncbase/nosql/query_exec/error_offset_test.go
@@ -0,0 +1,66 @@
+// 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 query_exec
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+
+	"v.io/syncbase/v23/syncbase/nosql/query_db"
+	"v.io/syncbase/v23/syncbase/nosql/syncql"
+	"v.io/v23"
+	"v.io/v23/context"
+	_ "v.io/x/ref/runtime/factories/generic"
+	"v.io/x/ref/test"
+)
+
+type mockDB struct {
+	ctx *context.T
+}
+
+func (db *mockDB) GetContext() *context.T {
+	return db.ctx
+}
+
+func init() {
+	var shutdown v23.Shutdown
+	db.ctx, shutdown = test.V23Init()
+	defer shutdown()
+}
+
+func (db *mockDB) GetTable(table string) (query_db.Table, error) {
+	return nil, errors.New(fmt.Sprintf("No such table: %s", table))
+}
+
+var db mockDB
+
+type splitErrorTest struct {
+	err    error
+	offset int64
+	errStr string
+}
+
+func TestSplitError(t *testing.T) {
+	basic := []splitErrorTest{
+		{
+			syncql.NewErrInvalidSelectField(db.GetContext(), 7),
+			7,
+			"Select field must be 'k' or 'v[{.<ident>}...]'.",
+		},
+		{
+			syncql.NewErrTableCantAccess(db.GetContext(), 14, "Bob", errors.New("No such table: Bob")),
+			14,
+			"Table Bob does not exist (or cannot be accessed): No such table: Bob.",
+		},
+	}
+
+	for _, test := range basic {
+		offset, errStr := SplitError(test.err)
+		if offset != test.offset || errStr != test.errStr {
+			t.Errorf("err: %v; got %d:%s, want %d:%s", test.err, offset, errStr, test.offset, test.errStr)
+		}
+	}
+}
diff --git a/v23/syncbase/nosql/query_exec/exec.go b/v23/syncbase/nosql/query_exec/exec.go
index edff51e..a166839 100644
--- a/v23/syncbase/nosql/query_exec/exec.go
+++ b/v23/syncbase/nosql/query_exec/exec.go
@@ -5,6 +5,8 @@
 package query_exec
 
 import (
+	"strconv"
+	"strings"
 	"v.io/syncbase/v23/syncbase/nosql"
 	"v.io/syncbase/v23/syncbase/nosql/internal/query"
 	"v.io/syncbase/v23/syncbase/nosql/query_db"
@@ -16,3 +18,22 @@
 func Exec(db query_db.Database, q string) ([]string, nosql.ResultStream, error) {
 	return query.Exec(db, q)
 }
+
+// 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>".
+// TODO(jkline): find a better place for client utilities (which, in this case, is also
+// used by internal tests).
+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:]
+}