query: wire up query's delete to syncbase.
Previous CLs imlemented delete in the query package. This change
wires it up to syncbase. The complicating factor is that, in
the non-batch case, we don't know if we should create a
snapshot (readonly) or a transaction (read/write) when
query/engine.Exec is called. It is only when the GetTable
function is called (on the database interface) that syncbase
knows whether it should create a snapshot or a transaction.
MultiPart: 2/2
Change-Id: I3c091d331690f8cf404ad50d0b5b9e103d059ac6
diff --git a/query/engine/public/model.go b/query/engine/public/model.go
index 0155323..b747d47 100644
--- a/query/engine/public/model.go
+++ b/query/engine/public/model.go
@@ -16,6 +16,7 @@
// Exec executes a syncQL query and returns the results. Headers (i.e., column
// names) are returned separately from results (i.e., values).
// q : the query (e.g., select v from Customers
+ // (e.g., delete from Customers where k = "101")
Exec(q string) ([]string, syncql.ResultStream, error)
// PrepareStatement parses query q and returns a PreparedStatement. Queries passed to
diff --git a/services/syncbase/nosql/service.vdl b/services/syncbase/nosql/service.vdl
index 4fa6bcf..d93a725 100644
--- a/services/syncbase/nosql/service.vdl
+++ b/services/syncbase/nosql/service.vdl
@@ -47,7 +47,7 @@
ListTables() ([]string | error) {access.Read}
// Exec executes a syncQL query and returns all results as specified by in the
- // query's select clause. Concurrency semantics are documented in model.go.
+ // query's select/delete statement. Concurrency semantics are documented in model.go.
Exec(schemaVersion int32, query string) stream<_, []any> error {access.Read}
// BeginBatch creates a new batch. It returns a "batch suffix" string to
diff --git a/services/syncbase/nosql/service.vdl.go b/services/syncbase/nosql/service.vdl.go
index 7b60bed..9764466 100644
--- a/services/syncbase/nosql/service.vdl.go
+++ b/services/syncbase/nosql/service.vdl.go
@@ -1929,7 +1929,7 @@
// TODO(sadovsky): Maybe switch to streaming RPC.
ListTables(*context.T, ...rpc.CallOpt) ([]string, error)
// Exec executes a syncQL query and returns all results as specified by in the
- // query's select clause. Concurrency semantics are documented in model.go.
+ // query's select/delete statement. Concurrency semantics are documented in model.go.
Exec(_ *context.T, schemaVersion int32, query string, _ ...rpc.CallOpt) (DatabaseExecClientCall, error)
// BeginBatch creates a new batch. It returns a "batch suffix" string to
// append to the object name of this Database, yielding an object name for the
@@ -2210,7 +2210,7 @@
// TODO(sadovsky): Maybe switch to streaming RPC.
ListTables(*context.T, rpc.ServerCall) ([]string, error)
// Exec executes a syncQL query and returns all results as specified by in the
- // query's select clause. Concurrency semantics are documented in model.go.
+ // query's select/delete statement. Concurrency semantics are documented in model.go.
Exec(_ *context.T, _ DatabaseExecServerCall, schemaVersion int32, query string) error
// BeginBatch creates a new batch. It returns a "batch suffix" string to
// append to the object name of this Database, yielding an object name for the
@@ -2353,7 +2353,7 @@
// TODO(sadovsky): Maybe switch to streaming RPC.
ListTables(*context.T, rpc.ServerCall) ([]string, error)
// Exec executes a syncQL query and returns all results as specified by in the
- // query's select clause. Concurrency semantics are documented in model.go.
+ // query's select/delete statement. Concurrency semantics are documented in model.go.
Exec(_ *context.T, _ *DatabaseExecServerCallStub, schemaVersion int32, query string) error
// BeginBatch creates a new batch. It returns a "batch suffix" string to
// append to the object name of this Database, yielding an object name for the
@@ -2513,7 +2513,7 @@
},
{
Name: "Exec",
- Doc: "// Exec executes a syncQL query and returns all results as specified by in the\n// query's select clause. Concurrency semantics are documented in model.go.",
+ Doc: "// Exec executes a syncQL query and returns all results as specified by in the\n// query's select/delete statement. Concurrency semantics are documented in model.go.",
InArgs: []rpc.ArgDesc{
{"schemaVersion", ``}, // int32
{"query", ``}, // string
diff --git a/syncbase/nosql/batch_test.go b/syncbase/nosql/batch_test.go
index d65ebfc..56ac70f 100644
--- a/syncbase/nosql/batch_test.go
+++ b/syncbase/nosql/batch_test.go
@@ -300,6 +300,45 @@
tu.CheckExecError(t, ctx, roBatch, "select k, v from foo", syncql.ErrTableCantAccess.ID)
}
+// Test exec of delete statement in readonly batch (it should fail).
+func TestBatchReadonlyExecDelete(t *testing.T) {
+ ctx, sName, cleanup := tu.SetupOrDie(nil)
+ defer cleanup()
+ a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
+ d := tu.CreateNoSQLDatabase(t, ctx, a, "d")
+ tb := tu.CreateTable(t, ctx, d, "tb")
+
+ foo := Foo{I: 4, S: "f"}
+ if err := tb.Put(ctx, "foo", foo); err != nil {
+ t.Fatalf("tb.Put() failed: %v", err)
+ }
+
+ bar := Bar{F: 0.5, S: "b"}
+ // NOTE: not best practice, but store bar as
+ // optional (by passing the address of bar to Put).
+ // This tests auto-dereferencing.
+ if err := tb.Put(ctx, "bar", &bar); err != nil {
+ t.Fatalf("tb.Put() failed: %v", err)
+ }
+
+ baz := Baz{Name: "John Doe", Active: true}
+ if err := tb.Put(ctx, "baz", baz); err != nil {
+ t.Fatalf("tb.Put() failed: %v", err)
+ }
+
+ // Begin a readonly batch.
+ roBatch, err := d.BeginBatch(ctx, wire.BatchOptions{ReadOnly: true})
+ if err != nil {
+ t.Fatalf("d.BeginBatch() failed: %v", err)
+ }
+
+ // Attempt to delete "foo" k/v pair with a syncQL delete.
+ tu.CheckExecError(t, ctx, roBatch, "delete from tb where k = \"foo\"", syncql.ErrTableCantAccess.ID)
+
+ // start a new batch
+ roBatch.Abort(ctx)
+}
+
// Tests that BatchDatabase.Exec DOES see changes made inside the transaction
// but before Exec is called.
func TestBatchExec(t *testing.T) {
@@ -363,12 +402,11 @@
// Delete the first row (bar) and the last row (newRow).
// Change the baz row. Confirm these rows are no longer fetched and that
// the change to baz is seen.
- if err := rwBatchTb.Delete(ctx, "bar"); err != nil {
- t.Fatalf("rwBatchTb.Delete(bar) failed: %v", err)
- }
- if err := rwBatchTb.Delete(ctx, "newRow"); err != nil {
- t.Fatalf("rwBatchTb.Delete(newRow) failed: %v", err)
- }
+ tu.CheckExec(t, ctx, rwBatch, "delete from tb where k = \"bar\" or k = \"newRow\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(2)},
+ })
baz2 := Baz{Name: "Batman", Active: false}
if err := rwBatchTb.Put(ctx, "baz", baz2); err != nil {
t.Fatalf("tb.Put() failed: %v", err)
@@ -392,12 +430,11 @@
if err := rwBatchTb.Put(ctx, "newRow", newRow2); err != nil {
t.Fatalf("rwBatchTb.Put() failed: %v", err)
}
- if err := rwBatchTb.Delete(ctx, "baz"); err != nil {
- t.Fatalf("rwBatchTb.Delete(baz) failed: %v", err)
- }
- if err := rwBatchTb.Delete(ctx, "foo"); err != nil {
- t.Fatalf("rwBatchTb.Delete(foo) failed: %v", err)
- }
+ tu.CheckExec(t, ctx, rwBatch, "delete from tb where k = \"baz\" or k = \"foo\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(2)},
+ })
tu.CheckExec(t, ctx, rwBatch, "select k, v from tb",
[]string{"k", "v"},
[][]*vdl.Value{
@@ -416,7 +453,6 @@
defer roBatch.Abort(ctx)
// confirm fetching all rows gets the rows committed above
- // as it was never committed
tu.CheckExec(t, ctx, roBatch, "select k, v from tb",
[]string{"k", "v"},
[][]*vdl.Value{
diff --git a/syncbase/nosql/client_test.go b/syncbase/nosql/client_test.go
index 53291cf..6e6985e 100644
--- a/syncbase/nosql/client_test.go
+++ b/syncbase/nosql/client_test.go
@@ -151,6 +151,21 @@
[]*vdl.Value{vdl.ValueOf("baz"), vdl.ValueOf(baz)},
})
+ // Delete baz
+ tu.CheckExec(t, ctx, d, "delete from tb where Type(v) like \"%.Baz\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(1)},
+ })
+
+ // Check that bas is no longer in the table.
+ tu.CheckExec(t, ctx, d, "select k, v from tb",
+ []string{"k", "v"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("bar"), vdl.ValueOf(bar)},
+ []*vdl.Value{vdl.ValueOf("foo"), vdl.ValueOf(foo)},
+ })
+
tu.CheckExecError(t, ctx, d, "select k, v from foo", syncql.ErrTableCantAccess.ID)
}
diff --git a/syncbase/nosql/exec_test.go b/syncbase/nosql/exec_test.go
index 01970c2..7f334f1 100644
--- a/syncbase/nosql/exec_test.go
+++ b/syncbase/nosql/exec_test.go
@@ -51,17 +51,25 @@
var t2015_07_01 time.Time
var t2015_07_01_01_23_45 time.Time
+var customerTable nosql.Table
+var numbersTable nosql.Table
+var fooTable nosql.Table
+var keyIndexDataTable nosql.Table
+var bigTable nosql.Table
+
func setup(t *testing.T) {
var sName string
ctx, sName, cleanup = tu.SetupOrDie(nil)
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
db = tu.CreateNoSQLDatabase(t, ctx, a, "db")
- customerTable := tu.CreateTable(t, ctx, db, "Customer")
- numbersTable := tu.CreateTable(t, ctx, db, "Numbers")
- fooTable := tu.CreateTable(t, ctx, db, "Foo")
- keyIndexDataTable := tu.CreateTable(t, ctx, db, "KeyIndexData")
- bigTable := tu.CreateTable(t, ctx, db, "BigTable")
+ customerTable = tu.CreateTable(t, ctx, db, "Customer")
+ numbersTable = tu.CreateTable(t, ctx, db, "Numbers")
+ fooTable = tu.CreateTable(t, ctx, db, "Foo")
+ keyIndexDataTable = tu.CreateTable(t, ctx, db, "KeyIndexData")
+ bigTable = tu.CreateTable(t, ctx, db, "BigTable")
+}
+func initTables(t *testing.T) {
t20150122131101, _ := time.Parse("Jan 2 2006 15:04:05 -0700 MST", "Jan 22 2015 13:11:01 -0800 PST")
t20150210161202, _ := time.Parse("Jan 2 2006 15:04:05 -0700 MST", "Feb 10 2015 16:12:02 -0800 PST")
t20150311101303, _ := time.Parse("Jan 2 2006 15:04:05 -0700 MST", "Mar 11 2015 10:13:03 -0700 PDT")
@@ -214,6 +222,15 @@
r [][]*vdl.Value
}
+type execDeleteTest struct {
+ delQuery string
+ delHeaders []string
+ delResults [][]*vdl.Value
+ selQuery string
+ selHeaders []string
+ selResults [][]*vdl.Value
+}
+
type preExecFunctionTest struct {
query string
headers []string
@@ -227,6 +244,7 @@
func TestExecSelect(t *testing.T) {
setup(t)
defer cleanup()
+ initTables(t)
basic := []execSelectTest{
{
// Select values for all customer records.
@@ -240,7 +258,7 @@
},
{
// Select values where v.InvoiceNum is nil
- // Since InvoiceNum does not exist for Invoice,
+ // Since InvoiceNum does not exists for Customers,
// this will return just customers.
"select v from Customer where v.InvoiceNum is nil",
[]string{"v"},
@@ -1117,9 +1135,455 @@
}
}
+func TestExecDelete(t *testing.T) {
+ basic := []execDeleteTest{
+ {
+ // Delete all k/v pairs in the customer table.
+ "delete from Customer",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(10)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{},
+ },
+ {
+ // Delete Customer type k/v pairs.
+ "delete from Customer where Type(v) like \"%.Customer\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(3)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001001")},
+ []*vdl.Value{vdl.ValueOf("001002")},
+ []*vdl.Value{vdl.ValueOf("001003")},
+ []*vdl.Value{vdl.ValueOf("002001")},
+ []*vdl.Value{vdl.ValueOf("002002")},
+ []*vdl.Value{vdl.ValueOf("002003")},
+ []*vdl.Value{vdl.ValueOf("002004")},
+ },
+ },
+ {
+ // Delete non-existant k/v pair.
+ "delete from Customer where k = \"foo\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(0)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001")},
+ []*vdl.Value{vdl.ValueOf("001001")},
+ []*vdl.Value{vdl.ValueOf("001002")},
+ []*vdl.Value{vdl.ValueOf("001003")},
+ []*vdl.Value{vdl.ValueOf("002")},
+ []*vdl.Value{vdl.ValueOf("002001")},
+ []*vdl.Value{vdl.ValueOf("002002")},
+ []*vdl.Value{vdl.ValueOf("002003")},
+ []*vdl.Value{vdl.ValueOf("002004")},
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // Delete k/v pairs where v.InvoiceNum is nil
+ // Since InvoiceNum does not exist for Customers,
+ // this will delete all customer records.
+ "delete from Customer where v.InvoiceNum is nil",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(3)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001001")},
+ []*vdl.Value{vdl.ValueOf("001002")},
+ []*vdl.Value{vdl.ValueOf("001003")},
+ []*vdl.Value{vdl.ValueOf("002001")},
+ []*vdl.Value{vdl.ValueOf("002002")},
+ []*vdl.Value{vdl.ValueOf("002003")},
+ []*vdl.Value{vdl.ValueOf("002004")},
+ },
+ },
+ {
+ // Delete values where v.InvoiceNum is nil
+ // or v.Name is nil. This will deleteall customers
+ // with the former and all invoices with the latter.
+ // Hence, all k/v paris will be deleted.
+ "delete from Customer where v.InvoiceNum is nil or v.Name is nil",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(10)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{},
+ },
+ {
+ // Delete where v.InvoiceNum is nil AND v.Name is nil.
+ // Nothing should be deleted.
+ "delete from Customer where v.InvoiceNum is nil and v.Name is nil",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(0)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001")},
+ []*vdl.Value{vdl.ValueOf("001001")},
+ []*vdl.Value{vdl.ValueOf("001002")},
+ []*vdl.Value{vdl.ValueOf("001003")},
+ []*vdl.Value{vdl.ValueOf("002")},
+ []*vdl.Value{vdl.ValueOf("002001")},
+ []*vdl.Value{vdl.ValueOf("002002")},
+ []*vdl.Value{vdl.ValueOf("002003")},
+ []*vdl.Value{vdl.ValueOf("002004")},
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // Delete where v.InvoiceNum is not nil
+ // This will delete all invoices.
+ "delete from Customer where v.InvoiceNum is not nil",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(7)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001")},
+ []*vdl.Value{vdl.ValueOf("002")},
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // Delete where v.InvoiceNum is not nil
+ // or v.Name is not nil. All records are deleted.
+ "delete from Customer where v.InvoiceNum is not nil or v.Name is not nil",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(10)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{},
+ },
+ {
+ // Delete where v.InvoiceNum is nil and v.Name is not nil.
+ // All customers are deleted.
+ "delete from Customer where v.InvoiceNum is nil and v.Name is not nil",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(3)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001001")},
+ []*vdl.Value{vdl.ValueOf("001002")},
+ []*vdl.Value{vdl.ValueOf("001003")},
+ []*vdl.Value{vdl.ValueOf("002001")},
+ []*vdl.Value{vdl.ValueOf("002002")},
+ []*vdl.Value{vdl.ValueOf("002003")},
+ []*vdl.Value{vdl.ValueOf("002004")},
+ },
+ },
+ {
+ // Delete where v.InvoiceNum is not nil
+ // and v.Name is not nil. Expect nothing deleted.
+ "delete from Customer where v.InvoiceNum is not nil and v.Name is not nil",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(0)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001")},
+ []*vdl.Value{vdl.ValueOf("001001")},
+ []*vdl.Value{vdl.ValueOf("001002")},
+ []*vdl.Value{vdl.ValueOf("001003")},
+ []*vdl.Value{vdl.ValueOf("002")},
+ []*vdl.Value{vdl.ValueOf("002001")},
+ []*vdl.Value{vdl.ValueOf("002002")},
+ []*vdl.Value{vdl.ValueOf("002003")},
+ []*vdl.Value{vdl.ValueOf("002004")},
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // Delete all $88 invoices.
+ "delete from Customer where Type(v) like \"%.Invoice\" and v.Amount = 88",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(2)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001")},
+ []*vdl.Value{vdl.ValueOf("001001")},
+ []*vdl.Value{vdl.ValueOf("001002")},
+ []*vdl.Value{vdl.ValueOf("002")},
+ []*vdl.Value{vdl.ValueOf("002001")},
+ []*vdl.Value{vdl.ValueOf("002002")},
+ []*vdl.Value{vdl.ValueOf("002003")},
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // Delete all with a key prefix of "001".
+ "delete from Customer where k like \"001%\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(4)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("002")},
+ []*vdl.Value{vdl.ValueOf("002001")},
+ []*vdl.Value{vdl.ValueOf("002002")},
+ []*vdl.Value{vdl.ValueOf("002003")},
+ []*vdl.Value{vdl.ValueOf("002004")},
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // Delete all with a key prefix of "002".
+ "delete from Customer where k like \"002%\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(5)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001")},
+ []*vdl.Value{vdl.ValueOf("001001")},
+ []*vdl.Value{vdl.ValueOf("001002")},
+ []*vdl.Value{vdl.ValueOf("001003")},
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // Delete all with key prefix NOT like "002%".
+ "delete from Customer where k not like \"002%\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(5)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("002")},
+ []*vdl.Value{vdl.ValueOf("002001")},
+ []*vdl.Value{vdl.ValueOf("002002")},
+ []*vdl.Value{vdl.ValueOf("002003")},
+ []*vdl.Value{vdl.ValueOf("002004")},
+ },
+ },
+ {
+ // delete all with a key prefix of "001" or a key prefix of "002".
+ "delete from Customer where k like \"001%\" or k like \"002%\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(9)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // Delete 002% or 001%
+ "delete from Customer where k like \"002%\" or k like \"001%\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(9)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // Let's play with whitespace and mixed case.
+ " dElEtE from \n Customer WhErE k lIkE \"002%\" oR k LiKe \"001%\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(9)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // Add in a like clause that accepts all strings.
+ " dElEtE from \n Customer WhErE k lIkE \"002%\" oR k LiKe \"001%\" or k lIkE \"%\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(10)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{},
+ },
+ {
+ // delete customers whose last name is Masterson.
+ "delete from Customer where Type(v) like \"%.Customer\" and v.Name like \"%Masterson\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(1)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001")},
+ []*vdl.Value{vdl.ValueOf("001001")},
+ []*vdl.Value{vdl.ValueOf("001002")},
+ []*vdl.Value{vdl.ValueOf("001003")},
+ []*vdl.Value{vdl.ValueOf("002001")},
+ []*vdl.Value{vdl.ValueOf("002002")},
+ []*vdl.Value{vdl.ValueOf("002003")},
+ []*vdl.Value{vdl.ValueOf("002004")},
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // delete where v.Address.City is "Collins" or type is Invoice.
+ "delete from Customer where v.Address.City = \"Collins\" or Type(v) like \"%.Invoice\"",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(8)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001")},
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // delete where v.Bar.Baz.TitleOrValue.Value = 42
+ "delete from Foo where v.Bar.Baz.TitleOrValue.Value = 42",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(1)},
+ },
+ "select k from Foo",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001")},
+ },
+ },
+ {
+ // delete where v.Address.City = "Collins" or (type is Invoice and v.InvoiceNum is not nil).
+ // Limit 3
+ "delete from Customer where v.Address.City = \"Collins\" or (Type(v) like \"%.Invoice\" and v.InvoiceNum is not nil) limit 3",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(3)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001")},
+ []*vdl.Value{vdl.ValueOf("002")},
+ []*vdl.Value{vdl.ValueOf("002001")},
+ []*vdl.Value{vdl.ValueOf("002002")},
+ []*vdl.Value{vdl.ValueOf("002003")},
+ []*vdl.Value{vdl.ValueOf("002004")},
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // delete invoices where date is 2015-03-17
+ "delete from Customer where Type(v) like \"%.Invoice\" and Year(v.InvoiceDate, \"America/Los_Angeles\") = 2015 and Month(v.InvoiceDate, \"America/Los_Angeles\") = 3 and Day(v.InvoiceDate, \"America/Los_Angeles\") = 17",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(2)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf("001")},
+ []*vdl.Value{vdl.ValueOf("001001")},
+ []*vdl.Value{vdl.ValueOf("001002")},
+ []*vdl.Value{vdl.ValueOf("001003")},
+ []*vdl.Value{vdl.ValueOf("002")},
+ []*vdl.Value{vdl.ValueOf("002003")},
+ []*vdl.Value{vdl.ValueOf("002004")},
+ []*vdl.Value{vdl.ValueOf("003")},
+ },
+ },
+ {
+ // Now will always be > 2012, so all customer records will be deleted.
+ "delete from Customer where Now() > Time(\"2006-01-02 MST\", \"2012-03-17 PDT\")",
+ []string{"Count"},
+ [][]*vdl.Value{
+ []*vdl.Value{vdl.ValueOf(10)},
+ },
+ "select k from Customer",
+ []string{"k"},
+ [][]*vdl.Value{},
+ },
+ }
+
+ setup(t)
+ defer cleanup()
+ for _, test := range basic {
+ initTables(t)
+ headers, rs, err := db.Exec(ctx, test.delQuery)
+ if err != nil {
+ t.Errorf("delQuery: %s; got %v, want nil", test.delQuery, err)
+ } else {
+ // Collect results.
+ r := [][]*vdl.Value{}
+ for rs.Advance() {
+ r = append(r, rs.Result())
+ }
+ if !reflect.DeepEqual(test.delResults, r) {
+ t.Errorf("delQuery: %s; got %v, want %v", test.delQuery, r, test.delResults)
+ }
+ if !reflect.DeepEqual(test.delHeaders, headers) {
+ t.Errorf("delQuery: %s; got %v, want %v", test.delQuery, headers, test.delHeaders)
+ }
+ }
+ headers, rs, err = db.Exec(ctx, test.selQuery)
+ if err != nil {
+ t.Errorf("delQuery: %s; got %v, want nil", test.delQuery, err)
+ } else {
+ // Collect results.
+ r := [][]*vdl.Value{}
+ for rs.Advance() {
+ r = append(r, rs.Result())
+ }
+ if !reflect.DeepEqual(test.selResults, r) {
+ t.Errorf("delQuery: %s; got %v, want %v", test.delQuery, r, test.selResults)
+ }
+ if !reflect.DeepEqual(test.selHeaders, headers) {
+ t.Errorf("delQuery: %s; got %v, want %v", test.delQuery, headers, test.selHeaders)
+ }
+ }
+ }
+}
+
func TestQuerySelectClause(t *testing.T) {
setup(t)
defer cleanup()
+ initTables(t)
basic := []execSelectTest{
{
// Select numeric types
@@ -1306,6 +1770,7 @@
func TestQueryWhereClause(t *testing.T) {
setup(t)
defer cleanup()
+ initTables(t)
basic := []execSelectTest{
{
// Select on numeric comparisons with equals
@@ -1637,6 +2102,7 @@
func TestQueryEscapeClause(t *testing.T) {
setup(t)
defer cleanup()
+ initTables(t)
basic := []execSelectTest{
{
"select k from Customer where \"abc%\" like \"abc^%\" escape '^'",
@@ -1727,6 +2193,7 @@
func TestQueryLimitAndOffsetClauses(t *testing.T) {
setup(t)
defer cleanup()
+ initTables(t)
basic := []execSelectTest{
{
"select k from Customer limit 2 offset 3",
@@ -1789,6 +2256,7 @@
func TestPreExecFunctions(t *testing.T) {
setup(t)
defer cleanup()
+ initTables(t)
basic := []preExecFunctionTest{
{
"select Now() from Customer",
@@ -1824,6 +2292,7 @@
func TestExecErrors(t *testing.T) {
setup(t)
defer cleanup()
+ initTables(t)
basic := []execSelectErrorTest{
{
"select a from Customer",
@@ -1873,6 +2342,7 @@
func TestQueryErrors(t *testing.T) {
setup(t)
defer cleanup()
+ initTables(t)
basic := []execSelectErrorTest{
// Produce every error in the book (make that, every one that is possible to produce).
{
@@ -2064,6 +2534,7 @@
func TestQueryErrorsPlatformDependentText(t *testing.T) {
setup(t)
defer cleanup()
+ initTables(t)
basic := []execSelectErrorTest{
// These errors contain installation dependent parts to the error. The test is
// more relaxed (it doesn't check the suffix) in order to account for this.
@@ -2091,6 +2562,7 @@
func TestQueryStatementSizeExceeded(t *testing.T) {
setup(t)
defer cleanup()
+ initTables(t)
q := fmt.Sprintf("select a from b where c = \"%s\"", strings.Repeat("x", 12000))
_, rs, err := db.Exec(ctx, q)
diff --git a/syncbase/nosql/model.go b/syncbase/nosql/model.go
index b0a8aaf..f55be9c 100644
--- a/syncbase/nosql/model.go
+++ b/syncbase/nosql/model.go
@@ -39,10 +39,18 @@
ListTables(ctx *context.T) ([]string, error)
// Exec executes a syncQL query.
+ // For select statements:
// If no error is returned, Exec returns an array of headers (i.e., column
// names) and a result stream which contains an array of values for each row
// that matches the query. The number of values returned in each row of the
// result stream will match the size of the headers string array.
+ //
+ // For delete statements:
+ // If no error is returned, Exec returns an array of headers with exactly
+ // one column: "Count" and a result stream which contains an array of length
+ // one, with a single element of type vdl.Int64. The value represents
+ // the number of k/v pairs deleted by the statement.
+ //
// Concurrency semantics: It is legal to perform writes concurrently with
// Exec. The returned stream reads from a consistent snapshot taken at the
// time of the RPC, and will not reflect subsequent writes to keys not yet