Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(spanner/spannertest): support UPDATE DML #3201

Merged
merged 1 commit into from Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions spanner/spannertest/README.md
Expand Up @@ -19,16 +19,16 @@ by ascending esotericism:

- expression functions
- more aggregation functions
- INSERT/UPDATE DML statements
- SELECT HAVING
- case insensitivity
- FULL JOIN
- FULL JOIN, multiple joins
- alternate literal types (esp. strings)
- STRUCT types
- transaction simulation
- expression type casting, coercion
- subselects
- FOREIGN KEY and CHECK constraints
- INSERT DML statements
- set operations (UNION, INTERSECT, EXCEPT)
- partition support
- conditional expressions
Expand Down
59 changes: 59 additions & 0 deletions spanner/spannertest/db.go
Expand Up @@ -999,6 +999,65 @@ func (d *database) Execute(stmt spansql.DMLStmt, params queryParams) (int, error
i++
}
return n, nil
case *spansql.Update:
t, err := d.table(stmt.Table)
if err != nil {
return 0, err
}

t.mu.Lock()
defer t.mu.Unlock()

ec := evalContext{
cols: t.cols,
params: params,
}

// Build parallel slices of destination column index and expressions to evaluate.
var dstIndex []int
var expr []spansql.Expr
for _, ui := range stmt.Items {
i, err := ec.resolveColumnIndex(ui.Column)
if err != nil {
return 0, err
}
// TODO: Enforce "A column can appear only once in the SET clause.".
if i < t.pkCols {
return 0, status.Errorf(codes.InvalidArgument, "cannot update primary key %s", ui.Column)
}
dstIndex = append(dstIndex, i)
expr = append(expr, ui.Value)
}

n := 0
values := make(row, len(stmt.Items)) // scratch space for new values
for i := 0; i < len(t.rows); i++ {
ec.row = t.rows[i]
b, err := ec.evalBoolExpr(stmt.Where)
if err != nil {
return 0, err
}
if b != nil && *b {
// Compute every update item.
for j := range dstIndex {
if expr[j] == nil { // DEFAULT
values[j] = nil
continue
}
v, err := ec.evalExpr(expr[j])
if err != nil {
return 0, err
}
values[j] = v
}
// Write them to the row.
for j, v := range values {
t.rows[i][dstIndex[j]] = v
}
n++
}
}
return n, nil
}
}

Expand Down
45 changes: 44 additions & 1 deletion spanner/spannertest/integration_test.go
Expand Up @@ -412,7 +412,7 @@ func TestIntegration_ReadsAndQueries(t *testing.T) {
"Staff",
"PlayerStats",
"JoinA", "JoinB", "JoinC", "JoinD", "JoinE", "JoinF",
"SomeStrings",
"SomeStrings", "Updateable",
}
errc := make(chan error)
for _, table := range allTables {
Expand Down Expand Up @@ -618,6 +618,11 @@ func TestIntegration_ReadsAndQueries(t *testing.T) {
`CREATE TABLE JoinF ( y INT64, z STRING(MAX) ) PRIMARY KEY (y, z)`,
// Some other test tables.
`CREATE TABLE SomeStrings ( i INT64, str STRING(MAX) ) PRIMARY KEY (i)`,
`CREATE TABLE Updateable (
id INT64,
first STRING(MAX),
last STRING(MAX),
) PRIMARY KEY (id)`,
)
if err != nil {
t.Fatalf("Creating sample tables: %v", err)
Expand Down Expand Up @@ -661,11 +666,39 @@ func TestIntegration_ReadsAndQueries(t *testing.T) {
spanner.Insert("SomeStrings", []string{"i", "str"}, []interface{}{1, "abar"}),
spanner.Insert("SomeStrings", []string{"i", "str"}, []interface{}{2, nil}),
spanner.Insert("SomeStrings", []string{"i", "str"}, []interface{}{3, "bbar"}),

spanner.Insert("Updateable", []string{"id", "first", "last"}, []interface{}{0, "joe", nil}),
spanner.Insert("Updateable", []string{"id", "first", "last"}, []interface{}{1, "doe", "joan"}),
spanner.Insert("Updateable", []string{"id", "first", "last"}, []interface{}{2, "wong", "wong"}),
})
if err != nil {
t.Fatalf("Inserting sample data: %v", err)
}

// Perform UPDATE DML; the results are checked later on.
n = 0
_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, tx *spanner.ReadWriteTransaction) error {
for _, u := range []string{
`UPDATE Updateable SET last = "bloggs" WHERE id = 0`,
`UPDATE Updateable SET first = last, last = first WHERE id = 1`,
`UPDATE Updateable SET last = DEFAULT WHERE id = 2`,
`UPDATE Updateable SET first = "noname" WHERE id = 3`, // no id=3
} {
nr, err := tx.Update(ctx, spanner.NewStatement(u))
if err != nil {
return err
}
n += nr
}
return nil
})
if err != nil {
t.Fatalf("Updating with DML: %v", err)
}
if n != 3 {
t.Errorf("Updating with DML affected %d rows, want 3", n)
}

// Do some complex queries.
tests := []struct {
q string
Expand Down Expand Up @@ -976,6 +1009,16 @@ func TestIntegration_ReadsAndQueries(t *testing.T) {
{int64(4), nil, "p"},
},
},
// Check the output of the UPDATE DML.
{
`SELECT id, first, last FROM Updateable ORDER BY id`,
nil,
[][]interface{}{
{int64(0), "joe", "bloggs"},
{int64(1), "joan", "doe"},
{int64(2), "wong", nil},
},
},
// Regression test for aggregating no rows; it used to return an empty row.
// https://github.com/googleapis/google-cloud-go/issues/2793
{
Expand Down