Skip to content

Commit

Permalink
sql: add a test for the validity of values produced by RETURNING
Browse files Browse the repository at this point in the history
As detected in issue #22304, a mutation with RETURNING could let
values go through to the client that would violate constraints. This
was fixed in a combination of previous patches; this commit adds the
corresponding regression test.

Release note: None
  • Loading branch information
knz committed Apr 21, 2018
1 parent 2d585a0 commit 6dced3e
Showing 1 changed file with 97 additions and 0 deletions.
97 changes: 97 additions & 0 deletions pkg/sql/mutation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2018 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.

package sql_test

import (
"context"
gosql "database/sql"
"testing"

"github.com/cockroachdb/cockroach/pkg/sql/tests"
"github.com/cockroachdb/cockroach/pkg/testutils"
"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
)

// Regression tests for #22304.
// Checks that a mutation with RETURNING checks low-level constraints
// before returning anything -- or that at least no constraint-violating
// values are visible to the client.
func TestConstraintValidationBeforeBuffering(t *testing.T) {
defer leaktest.AfterTest(t)()

params, _ := tests.CreateTestServerParams()
s, db, _ := serverutils.StartServer(t, params)
defer s.Stopper().Stop(context.TODO())

if _, err := db.Exec(`
CREATE DATABASE d;
CREATE TABLE d.a(a INT PRIMARY KEY);
INSERT INTO d.a(a) VALUES (1);
`); err != nil {
t.Fatal(err)
}

step1 := func() (*gosql.Rows, error) {
return db.Query("INSERT INTO d.a(a) TABLE generate_series(1,3000) RETURNING a")
}
step2 := func() (*gosql.Rows, error) {
if _, err := db.Exec(`INSERT INTO d.a(a) TABLE generate_series(2, 3000)`); err != nil {
return nil, err
}
return db.Query("UPDATE d.a SET a = a - 1 WHERE a > 1 RETURNING a")
}
for i, step := range []func() (*gosql.Rows, error){step1, step2} {
rows, err := step()
if err != nil {
if !testutils.IsError(err, `duplicate key value \(a\)=\(1\)`) {
t.Errorf("%d: %v", i, err)
}
} else {
defer rows.Close()

hasNext := rows.Next()
if !hasNext {
t.Errorf("%d: returning claims to return no error, yet returns no rows either", i)
} else {
var val int
err := rows.Scan(&val)

if err != nil {
if !testutils.IsError(err, `duplicate key value \(a\)=\(1\)`) {
t.Errorf("%d: %v", i, err)
}
} else {
// No error. Maybe it'll come later.
if val == 1 {
t.Errorf("%d: returning returns rows, including an invalid duplicate", i)
}

for rows.Next() {
err := rows.Scan(&val)
if err != nil {
if !testutils.IsError(err, `duplicate key value \(a\)=\(1\)`) {
t.Errorf("%d: %v", i, err)
}
}
if val == 1 {
t.Errorf("%d returning returns rows, including an invalid duplicate", i)
}
}
}
}
}
}
}

0 comments on commit 6dced3e

Please sign in to comment.