Files
gocqlx/iterx_test.go

695 lines
18 KiB
Go
Raw Normal View History

2017-09-21 21:43:27 +02:00
// Copyright (C) 2017 ScyllaDB
// Use of this source code is governed by a ALv2-style
// license that can be found in the LICENSE file.
2017-07-21 15:13:09 +02:00
// +build all integration
2017-07-25 08:25:31 +02:00
package gocqlx_test
2017-07-20 15:55:19 +02:00
import (
"math/big"
"strings"
"testing"
"time"
"github.com/gocql/gocql"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
2017-07-24 16:59:39 +02:00
"github.com/scylladb/gocqlx"
. "github.com/scylladb/gocqlx/gocqlxtest"
"github.com/scylladb/gocqlx/qb"
2017-07-20 15:55:19 +02:00
"gopkg.in/inf.v0"
)
type FullName struct {
FirstName string
LastName string
}
func (n FullName) MarshalCQL(info gocql.TypeInfo) ([]byte, error) {
return []byte(n.FirstName + " " + n.LastName), nil
}
func (n *FullName) UnmarshalCQL(info gocql.TypeInfo, data []byte) error {
t := strings.SplitN(string(data), " ", 2)
n.FirstName, n.LastName = t[0], t[1]
return nil
}
type FullNameUDT struct {
gocqlx.UDT
FullName
}
type FullNamePtrUDT struct {
gocqlx.UDT
*FullName
}
func TestIterxStruct(t *testing.T) {
session := CreateSession(t)
2017-07-20 15:55:19 +02:00
defer session.Close()
if err := session.ExecStmt(`CREATE TYPE gocqlx_test.FullName (first_Name text, last_name text)`); err != nil {
t.Fatal("create type:", err)
}
if err := session.ExecStmt(`CREATE TABLE gocqlx_test.struct_table (
2017-07-20 15:55:19 +02:00
testuuid timeuuid PRIMARY KEY,
testtimestamp timestamp,
testvarchar varchar,
testbigint bigint,
testblob blob,
testbool boolean,
testfloat float,
testdouble double,
testint int,
testdecimal decimal,
testlist list<text>,
testset set<int>,
testmap map<varchar, varchar>,
testvarint varint,
testinet inet,
testcustom text,
testudt gocqlx_test.FullName,
testptrudt gocqlx_test.FullName
2017-07-20 15:55:19 +02:00
)`); err != nil {
t.Fatal("create table:", err)
}
type StructTable struct {
Testuuid gocql.UUID
Testvarchar string
Testbigint int64
Testtimestamp time.Time
Testblob []byte
Testbool bool
Testfloat float32
Testdouble float64
Testint int
Testdecimal *inf.Dec
Testlist []string
Testset []int
Testmap map[string]string
Testvarint *big.Int
Testinet string
Testcustom FullName
Testudt FullNameUDT
Testptrudt FullNamePtrUDT
2017-07-20 15:55:19 +02:00
}
bigInt := new(big.Int)
if _, ok := bigInt.SetString("830169365738487321165427203929228", 10); !ok {
t.Fatal("failed setting bigint by string")
}
m := StructTable{
Testuuid: gocql.TimeUUID(),
Testvarchar: "Test VarChar",
Testbigint: time.Now().Unix(),
Testtimestamp: time.Now().Truncate(time.Millisecond).UTC(),
Testblob: []byte("test blob"),
Testbool: true,
Testfloat: float32(4.564),
Testdouble: float64(4.815162342),
Testint: 2343,
Testdecimal: inf.NewDec(100, 0),
Testlist: []string{"quux", "foo", "bar", "baz", "quux"},
Testset: []int{1, 2, 3, 4, 5, 6, 7, 8, 9},
Testmap: map[string]string{"field1": "val1", "field2": "val2", "field3": "val3"},
Testvarint: bigInt,
Testinet: "213.212.2.19",
Testcustom: FullName{FirstName: "John", LastName: "Doe"},
Testudt: FullNameUDT{FullName: FullName{FirstName: "John", LastName: "Doe"}},
Testptrudt: FullNamePtrUDT{FullName: &FullName{FirstName: "John", LastName: "Doe"}},
2017-07-20 15:55:19 +02:00
}
const insertStmt = `INSERT INTO struct_table (testuuid, testtimestamp, testvarchar, testbigint, testblob, testbool, testfloat,testdouble, testint, testdecimal, testlist, testset, testmap, testvarint, testinet, testcustom, testudt, testptrudt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
if err := session.Query(insertStmt, nil).Bind(
2017-07-20 15:55:19 +02:00
m.Testuuid,
m.Testtimestamp,
m.Testvarchar,
m.Testbigint,
m.Testblob,
m.Testbool,
m.Testfloat,
m.Testdouble,
m.Testint,
m.Testdecimal,
m.Testlist,
m.Testset,
m.Testmap,
m.Testvarint,
m.Testinet,
m.Testcustom,
m.Testudt,
m.Testptrudt).ExecRelease(); err != nil {
2017-07-20 15:55:19 +02:00
t.Fatal("insert:", err)
}
diffOpts := cmpopts.IgnoreUnexported(big.Int{}, inf.Dec{})
const stmt = `SELECT * FROM struct_table`
2017-07-20 15:55:19 +02:00
t.Run("get", func(t *testing.T) {
var v StructTable
if err := session.Query(stmt, nil).Get(&v); err != nil {
t.Fatal("Get() failed:", err)
2017-07-20 15:55:19 +02:00
}
if diff := cmp.Diff(m, v, diffOpts); diff != "" {
t.Fatalf("Get()=%+v expected %+v, diff: %s", v, m, diff)
2017-07-20 15:55:19 +02:00
}
})
t.Run("select", func(t *testing.T) {
var v []StructTable
if err := session.Query(stmt, nil).Select(&v); err != nil {
t.Fatal("Select() failed:", err)
2017-07-20 15:55:19 +02:00
}
if len(v) != 1 {
t.Fatalf("Select()=%+v expected 1 row got %d", v, len(v))
2017-07-20 15:55:19 +02:00
}
if diff := cmp.Diff(m, v[0], diffOpts); diff != "" {
t.Fatalf("Select()[0]=%+v expected %+v, diff: %s", v[0], m, diff)
2017-07-20 15:55:19 +02:00
}
})
t.Run("select ptr", func(t *testing.T) {
var v []*StructTable
if err := session.Query(stmt, nil).Select(&v); err != nil {
t.Fatal("Select() failed:", err)
2017-07-20 15:55:19 +02:00
}
if len(v) != 1 {
t.Fatalf("Select()=%+v expected 1 row got %d", v, len(v))
2017-07-20 15:55:19 +02:00
}
if diff := cmp.Diff(&m, v[0], diffOpts); diff != "" {
t.Fatalf("Select()[0]=%+v expected %+v, diff: %s", v[0], &m, diff)
2017-07-20 15:55:19 +02:00
}
})
t.Run("struct scan", func(t *testing.T) {
var (
v StructTable
n int
)
iter := session.Query(stmt, nil).Iter()
for iter.StructScan(&v) {
n++
}
if err := iter.Close(); err != nil {
t.Fatal("StructScan() failed:", err)
}
if n != 1 {
t.Fatalf("StructScan() expected 1 row got %d", n)
}
if diff := cmp.Diff(m, v, diffOpts); diff != "" {
t.Fatalf("StructScan()=%+v expected %+v, diff: %s", v, m, diff)
}
})
2017-07-20 15:55:19 +02:00
}
func TestIterxScannable(t *testing.T) {
session := CreateSession(t)
defer session.Close()
if err := session.ExecStmt(`CREATE TABLE gocqlx_test.scannable_table (testfullname text PRIMARY KEY)`); err != nil {
t.Fatal("create table:", err)
}
m := FullName{"John", "Doe"}
if err := session.Query(`INSERT INTO scannable_table (testfullname) values (?)`, nil).Bind(m).Exec(); err != nil {
t.Fatal("insert:", err)
}
const stmt = `SELECT testfullname FROM scannable_table`
t.Run("get", func(t *testing.T) {
var v FullName
if err := session.Query(stmt, nil).Get(&v); err != nil {
t.Fatal("Get() failed:", err)
}
if diff := cmp.Diff(m, v); diff != "" {
t.Fatalf("Get()=%+v expected %+v, diff: %s", v, m, diff)
}
})
t.Run("select", func(t *testing.T) {
var v []FullName
if err := session.Query(stmt, nil).Select(&v); err != nil {
t.Fatal("Select() failed:", err)
}
if len(v) != 1 {
t.Fatalf("Select()=%+v expected 1 row got %d", v, len(v))
}
if diff := cmp.Diff(m, v[0]); diff != "" {
t.Fatalf("Select()[0]=%+v expected %+v, diff: %s", v[0], m, diff)
}
})
t.Run("select ptr", func(t *testing.T) {
var v []*FullName
if err := session.Query(stmt, nil).Select(&v); err != nil {
t.Fatal("Select() failed:", err)
}
if len(v) != 1 {
t.Fatalf("Select()=%+v expected 1 row got %d", v, len(v))
}
if diff := cmp.Diff(&m, v[0]); diff != "" {
t.Fatalf("Select()[0]=%+v expected %+v, diff: %s", v[0], &m, diff)
}
})
}
func TestIterxStructOnly(t *testing.T) {
session := CreateSession(t)
defer session.Close()
if err := session.ExecStmt(`CREATE TABLE gocqlx_test.struct_only_table (first_name text, last_name text, PRIMARY KEY (first_name, last_name))`); err != nil {
t.Fatal("create table:", err)
}
m := FullName{"John", "Doe"}
if err := session.Query(`INSERT INTO struct_only_table (first_name, last_name) values (?, ?)`, nil).Bind(m.FirstName, m.LastName).Exec(); err != nil {
t.Fatal("insert:", err)
}
const stmt = `SELECT first_name, last_name FROM struct_only_table`
t.Run("get", func(t *testing.T) {
var v FullName
if err := session.Query(stmt, nil).Iter().StructOnly().Get(&v); err != nil {
t.Fatal("Get() failed:", err)
}
if diff := cmp.Diff(m, v); diff != "" {
t.Fatalf("Get()=%+v expected %+v, diff: %s", v, m, diff)
}
})
t.Run("select", func(t *testing.T) {
var v []FullName
if err := session.Query(stmt, nil).Iter().StructOnly().Select(&v); err != nil {
t.Fatal("Select() failed:", err)
}
if len(v) != 1 {
t.Fatalf("Select()=%+v expected 1 row got %d", v, len(v))
}
if diff := cmp.Diff(m, v[0]); diff != "" {
t.Fatalf("Select()[0]=%+v expected %+v, diff: %s", v[0], m, diff)
}
})
t.Run("select ptr", func(t *testing.T) {
var v []*FullName
if err := session.Query(stmt, nil).Iter().StructOnly().Select(&v); err != nil {
t.Fatal("Select() failed:", err)
}
if len(v) != 1 {
t.Fatalf("Select()=%+v expected 1 row got %d", v, len(v))
}
if diff := cmp.Diff(&m, v[0]); diff != "" {
t.Fatalf("Select()[0]=%+v expected %+v, diff: %s", v[0], &m, diff)
}
})
const golden = "expected 1 column in result"
t.Run("get error", func(t *testing.T) {
var v FullName
err := session.Query(stmt, nil).Get(&v)
if err == nil || !strings.HasPrefix(err.Error(), golden) {
t.Fatalf("Get() error=%q expected %s", err, golden)
}
})
t.Run("select error", func(t *testing.T) {
var v []FullName
err := session.Query(stmt, nil).Select(&v)
if err == nil || !strings.HasPrefix(err.Error(), golden) {
t.Fatalf("Select() error=%q expected %s", err, golden)
}
})
}
func TestIterxStructOnlyUDT(t *testing.T) {
session := CreateSession(t)
defer session.Close()
if err := session.ExecStmt(`CREATE TABLE gocqlx_test.struct_only_udt_table (first_name text, last_name text, PRIMARY KEY (first_name, last_name))`); err != nil {
t.Fatal("create table:", err)
}
m := FullNameUDT{
FullName: FullName{
FirstName: "John",
LastName: "Doe",
},
}
if err := session.Query(`INSERT INTO struct_only_udt_table (first_name, last_name) values (?, ?)`, nil).Bind(m.FirstName, m.LastName).Exec(); err != nil {
t.Fatal("insert:", err)
}
const stmt = `SELECT first_name, last_name FROM struct_only_udt_table`
t.Run("get", func(t *testing.T) {
var v FullNameUDT
if err := session.Query(stmt, nil).Iter().StructOnly().Get(&v); err != nil {
t.Fatal("Get() failed:", err)
}
if diff := cmp.Diff(m, v); diff != "" {
t.Fatalf("Get()=%+v expected %+v, diff: %s", v, m, diff)
}
})
t.Run("select", func(t *testing.T) {
var v []FullNameUDT
if err := session.Query(stmt, nil).Iter().StructOnly().Select(&v); err != nil {
t.Fatal("Select() failed:", err)
}
if len(v) != 1 {
t.Fatalf("Select()=%+v expected 1 row got %d", v, len(v))
}
if diff := cmp.Diff(m, v[0]); diff != "" {
t.Fatalf("Select()[0]=%+v expected %+v, diff: %s", v[0], m, diff)
}
})
t.Run("select ptr", func(t *testing.T) {
var v []*FullNameUDT
if err := session.Query(stmt, nil).Iter().StructOnly().Select(&v); err != nil {
t.Fatal("Select() failed:", err)
}
if len(v) != 1 {
t.Fatalf("Select()=%+v expected 1 row got %d", v, len(v))
}
if diff := cmp.Diff(&m, v[0]); diff != "" {
t.Fatalf("Select()[0]=%+v expected %+v, diff: %s", v[0], &m, diff)
}
})
const golden = "expected 1 column in result"
t.Run("get error", func(t *testing.T) {
var v FullNameUDT
err := session.Query(stmt, nil).Get(&v)
if err == nil || !strings.HasPrefix(err.Error(), golden) {
t.Fatalf("Get() error=%q expected %s", err, golden)
}
})
t.Run("select error", func(t *testing.T) {
var v []FullNameUDT
err := session.Query(stmt, nil).Select(&v)
if err == nil || !strings.HasPrefix(err.Error(), golden) {
t.Fatalf("Select() error=%q expected %s", err, golden)
}
})
}
func TestIterxUnsafe(t *testing.T) {
session := CreateSession(t)
defer session.Close()
if err := session.ExecStmt(`CREATE TABLE gocqlx_test.unsafe_table (testtext text PRIMARY KEY, testtextunbound text)`); err != nil {
t.Fatal("create table:", err)
}
if err := session.Query(`INSERT INTO unsafe_table (testtext, testtextunbound) values (?, ?)`, nil).Bind("test", "test").Exec(); err != nil {
t.Fatal("insert:", err)
}
type UnsafeTable struct {
Testtext string
}
m := UnsafeTable{
Testtext: "test",
}
const (
stmt = `SELECT * FROM unsafe_table`
golden = "missing destination name \"testtextunbound\" in gocqlx_test.UnsafeTable"
)
t.Run("get", func(t *testing.T) {
var v UnsafeTable
err := session.Query(stmt, nil).Get(&v)
if err == nil || !strings.HasPrefix(err.Error(), golden) {
t.Fatalf("Get() error=%q expected %s", err, golden)
}
})
t.Run("select", func(t *testing.T) {
var v []UnsafeTable
err := session.Query(stmt, nil).Select(&v)
if err == nil || !strings.HasPrefix(err.Error(), golden) {
t.Fatalf("Select() error=%q expected %s", err, golden)
}
if cap(v) > 0 {
t.Fatalf("Select() effect alloc cap=%d expected 0", cap(v))
}
})
t.Run("get unsafe", func(t *testing.T) {
var v UnsafeTable
err := session.Query(stmt, nil).Iter().Unsafe().Get(&v)
if err != nil {
t.Fatal("Get() failed:", err)
}
if diff := cmp.Diff(m, v); diff != "" {
t.Fatalf("Get()=%+v expected %+v, diff: %s", v, m, diff)
}
})
t.Run("select unsafe", func(t *testing.T) {
var v []UnsafeTable
err := session.Query(stmt, nil).Iter().Unsafe().Select(&v)
if err != nil {
t.Fatal("Select() failed:", err)
}
if len(v) != 1 {
t.Fatalf("Select()=%+v expected 1 row got %d", v, len(v))
}
if diff := cmp.Diff(m, v[0]); diff != "" {
t.Fatalf("Select()[0]=%+v expected %+v, diff: %s", v[0], m, diff)
}
})
t.Run("select default unsafe", func(t *testing.T) {
gocqlx.DefaultUnsafe = true
defer func() {
gocqlx.DefaultUnsafe = false
}()
var v []UnsafeTable
err := session.Query(stmt, nil).Iter().Select(&v)
if err != nil {
t.Fatal("Select() failed:", err)
}
if len(v) != 1 {
t.Fatalf("Select()=%+v expected 1 row got %d", v, len(v))
}
if diff := cmp.Diff(m, v[0]); diff != "" {
t.Fatalf("Select()[0]=%+v expected %+v, diff: %s", v[0], m, diff)
}
})
}
func TestIterxNotFound(t *testing.T) {
session := CreateSession(t)
defer session.Close()
if err := session.ExecStmt(`CREATE TABLE gocqlx_test.not_found_table (testtext text PRIMARY KEY)`); err != nil {
t.Fatal("create table:", err)
}
type NotFoundTable struct {
Testtext string
}
2017-09-22 15:02:12 +02:00
t.Run("get cql error", func(t *testing.T) {
var v NotFoundTable
err := session.Query(`SELECT * FROM not_found_table WRONG`, nil).RetryPolicy(nil).Get(&v)
2017-09-22 15:02:12 +02:00
if err == nil || !strings.Contains(err.Error(), "WRONG") {
t.Fatalf("Get() error=%q", err)
2017-09-22 15:02:12 +02:00
}
})
t.Run("get", func(t *testing.T) {
var v NotFoundTable
err := session.Query(`SELECT * FROM not_found_table`, nil).Get(&v)
if err != gocql.ErrNotFound {
t.Fatalf("Get() error=%q expected %s", err, gocql.ErrNotFound)
}
})
2017-09-22 15:02:12 +02:00
t.Run("select cql error", func(t *testing.T) {
var v []NotFoundTable
err := session.Query(`SELECT * FROM not_found_table WRONG`, nil).RetryPolicy(nil).Select(&v)
2017-09-22 15:02:12 +02:00
if err == nil || !strings.Contains(err.Error(), "WRONG") {
t.Fatalf("Get() error=%q", err)
2017-09-22 15:02:12 +02:00
}
})
t.Run("select", func(t *testing.T) {
var v []NotFoundTable
err := session.Query(`SELECT * FROM not_found_table`, nil).Select(&v)
if err != nil {
t.Fatalf("Select() error=%q expected %s", err, gocql.ErrNotFound)
}
if cap(v) > 0 {
t.Fatalf("Select() effect alloc cap=%d expected 0", cap(v))
}
})
}
func TestIterxErrorOnNil(t *testing.T) {
session := CreateSession(t)
defer session.Close()
if err := session.ExecStmt(`CREATE TABLE gocqlx_test.nil_table (testtext text PRIMARY KEY)`); err != nil {
t.Fatal("create table:", err)
}
const (
stmt = "SELECT * FROM not_found_table WRONG"
golden = "expected a pointer but got <nil>"
)
t.Run("get", func(t *testing.T) {
err := session.Query(stmt, nil).Get(nil)
if err == nil || err.Error() != golden {
t.Fatalf("Get() error=%q expected %q", err, golden)
}
})
t.Run("select", func(t *testing.T) {
err := session.Query(stmt, nil).Select(nil)
if err == nil || err.Error() != golden {
t.Fatalf("Select() error=%q expected %q", err, golden)
}
})
t.Run("struct scan", func(t *testing.T) {
iter := session.Query(stmt, nil).Iter()
iter.StructScan(nil)
err := iter.Close()
if err == nil || err.Error() != golden {
t.Fatalf("StructScan() error=%q expected %q", err, golden)
}
})
}
func TestIterxPaging(t *testing.T) {
session := CreateSession(t)
defer session.Close()
if err := session.ExecStmt(`CREATE TABLE gocqlx_test.paging_table (id int PRIMARY KEY, val int)`); err != nil {
t.Fatal("create table:", err)
}
if err := session.ExecStmt(`CREATE INDEX id_val_index ON gocqlx_test.paging_table (val)`); err != nil {
t.Fatal("create index:", err)
}
q := session.Query(qb.Insert("gocqlx_test.paging_table").Columns("id", "val").ToCql())
for i := 0; i < 5000; i++ {
if err := q.Bind(i, i).Exec(); err != nil {
t.Fatal(err)
}
}
type Paging struct {
ID int
Val int
}
stmt, names := qb.Select("gocqlx_test.paging_table").
Where(qb.Lt("val")).
AllowFiltering().
Columns("id", "val").ToCql()
iter := session.Query(stmt, names).Bind(100).PageSize(10).Iter()
defer iter.Close()
var cnt int
for {
p := &Paging{}
if !iter.StructScan(p) {
break
}
cnt++
}
if cnt != 100 {
t.Fatal("expected 100", "got", cnt)
}
}
func TestIterx_CASInsertAndUpdates(t *testing.T) {
session := CreateSession(t)
defer session.Close()
const (
id = 0
baseSalary = 1000
minSalary = 2000
)
john := struct {
ID int
Salary int
}{ID: id, Salary: baseSalary}
if err := session.ExecStmt(`CREATE TABLE gocqlx_test.salaries (id int PRIMARY KEY, salary int)`); err != nil {
t.Fatal("create table:", err)
}
insertQ := session.Query(qb.Insert("gocqlx_test.salaries").Columns("id", "salary").Unique().ToCql())
applied, err := insertQ.BindStruct(john).ExecCAS()
if err != nil {
t.Fatal(err)
}
if !applied {
t.Error("Expected first insert success")
}
applied, err = insertQ.BindStruct(john).ExecCASRelease()
if err != nil {
t.Fatal(err)
}
if applied {
t.Error("Expected second insert to not be applied")
}
updateQ := session.Query(qb.Update("gocqlx_test.salaries").
SetNamed("salary", "min_salary").
Where(qb.Eq("id")).
If(qb.LtNamed("salary", "min_salary")).
ToCql(),
)
applied, err = updateQ.BindStructMap(john, qb.M{
"min_salary": minSalary,
}).GetCAS(&john)
if err != nil {
t.Fatal(err)
}
if !applied {
t.Error("Expected update to be applied")
}
if john.Salary != baseSalary {
t.Error("Expected to have pre-image in struct after GetCAS")
}
applied, err = updateQ.BindStructMap(john, qb.M{
"min_salary": minSalary * 2,
}).GetCASRelease(&john)
if err != nil {
t.Fatal(err)
}
if !applied {
t.Error("Expected update to be applied")
}
if john.Salary != minSalary {
t.Error("Expected to have pre-image in struct after GetCAS")
}
}