Files
gocqlx/example_test.go
Dmitry Kropachev 38001d64ac Update gocql version to v1.16.1 (#353)
* Update gocql version to v1.16.1

1. Update gocql to v1.16.1
2. Update golang to 1.25, since new gocql version requres it

* Update golangci to 2.5.0

It is needed since 1.64.8 does not support golang 1.25.
1. Update golangci to 2.5.0
2. Migrate from golangci config v1 to v2
3. Integrate fieldaligment to golangci
4. Drop fieldaligment from Makefile
5. Address complaints
2025-10-28 14:52:22 -04:00

979 lines
24 KiB
Go

// Copyright (C) 2017 ScyllaDB
// Use of this source code is governed by a ALv2-style
// license that can be found in the LICENSE file.
//go:build all || integration
// +build all integration
package gocqlx_test
import (
"fmt"
"math"
"testing"
"time"
"github.com/gocql/gocql"
"golang.org/x/sync/errgroup"
"gopkg.in/inf.v0"
"github.com/scylladb/gocqlx/v3"
"github.com/scylladb/gocqlx/v3/gocqlxtest"
"github.com/scylladb/gocqlx/v3/qb"
"github.com/scylladb/gocqlx/v3/table"
)
// Running examples locally:
// make run-scylla
// make run-examples
func TestExample(t *testing.T) {
cluster := gocqlxtest.CreateCluster()
session, err := gocqlx.WrapSession(cluster.CreateSession())
if err != nil {
t.Fatal("create session:", err)
}
defer session.Close()
_ = session.ExecStmt(`DROP KEYSPACE examples`)
basicCreateAndPopulateKeyspace(t, session, "examples")
createAndPopulateKeyspaceAllTypes(t, session)
basicReadScyllaVersion(t, session)
datatypesBlob(t, session)
datatypesUserDefinedType(t, session)
datatypesUserDefinedTypeWrapper(t, session)
datatypesJSON(t, session)
pagingForwardPaging(t, session)
pagingEfficientFullTableScan(t, session)
lwtLock(t, session)
unsetEmptyValues(t, session)
}
type Song struct {
ID gocql.UUID
Title string
Album string
Artist string
Tags []string
Data []byte
}
type PlaylistItem struct {
ID gocql.UUID
Title string
Album string
Artist string
SongID gocql.UUID
}
// This example shows how to use query builders and table models to build
// queries. It uses "BindStruct" function for parameter binding and "Select"
// function for loading data to a slice.
func basicCreateAndPopulateKeyspace(t *testing.T, session gocqlx.Session, keyspace string) {
t.Helper()
err := session.ExecStmt(fmt.Sprintf(
`CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}`,
keyspace,
))
if err != nil {
t.Fatal("create keyspace:", err)
}
err = session.ExecStmt(fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s.songs (
id uuid PRIMARY KEY,
title text,
album text,
artist text,
tags set<text>,
data blob)`, keyspace))
if err != nil {
t.Fatal("create table:", err)
}
err = session.ExecStmt(fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s.playlists (
id uuid,
title text,
album text,
artist text,
song_id uuid,
PRIMARY KEY (id, title, album, artist))`, keyspace))
if err != nil {
t.Fatal("create table:", err)
}
playlistMetadata := table.Metadata{
Name: fmt.Sprintf("%s.playlists", keyspace),
Columns: []string{"id", "title", "album", "artist", "song_id"},
PartKey: []string{"id"},
SortKey: []string{"title", "album", "artist", "song_id"},
}
playlistTable := table.New(playlistMetadata)
// Insert song using query builder.
insertSong := qb.Insert(fmt.Sprintf("%s.songs", keyspace)).
Columns("id", "title", "album", "artist", "tags", "data").Query(session)
insertSong.BindStruct(Song{
ID: mustParseUUID("756716f7-2e54-4715-9f00-91dcbea6cf50"),
Title: "La Petite Tonkinoise",
Album: "Bye Bye Blackbird",
Artist: "Joséphine Baker",
Tags: []string{"jazz", "2013"},
Data: []byte("music"),
})
if err := insertSong.ExecRelease(); err != nil {
t.Fatal("ExecRelease() failed:", err)
}
// Insert playlist using table model.
insertPlaylist := playlistTable.InsertQuery(session)
insertPlaylist.BindStruct(PlaylistItem{
ID: mustParseUUID("2cc9ccb7-6221-4ccb-8387-f22b6a1b354d"),
Title: "La Petite Tonkinoise",
Album: "Bye Bye Blackbird",
Artist: "Joséphine Baker",
SongID: mustParseUUID("756716f7-2e54-4715-9f00-91dcbea6cf50"),
})
if err := insertPlaylist.ExecRelease(); err != nil {
t.Fatal("ExecRelease() failed:", err)
}
// Query and displays data.
queryPlaylist := playlistTable.SelectQuery(session)
queryPlaylist.BindStruct(&PlaylistItem{
ID: mustParseUUID("2cc9ccb7-6221-4ccb-8387-f22b6a1b354d"),
})
var items []*PlaylistItem
if err := queryPlaylist.Select(&items); err != nil {
t.Fatal("Select() failed:", err)
}
for _, i := range items {
t.Logf("%+v", *i)
}
}
// This example shows how to use query builders and table models to build
// queries with all types. It uses "BindStruct" function for parameter binding and "Select"
// function for loading data to a slice.
func createAndPopulateKeyspaceAllTypes(t *testing.T, session gocqlx.Session) {
t.Helper()
err := session.ExecStmt(`CREATE KEYSPACE IF NOT EXISTS examples WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}`)
if err != nil {
t.Fatal("create keyspace:", err)
}
// generated with schemagen
type CheckTypesStruct struct {
AsciI string
BigInt int64
BloB []byte
BooleaN bool
DatE time.Time
DecimaL inf.Dec
DoublE float64
DuratioN gocql.Duration
FloaT float32
ID [16]byte
InT int32
IneT string
ListInt []int32
MapIntText map[int32]string
SetInt []int32
SmallInt int16
TexT string
TimE time.Duration
TimestamP time.Time
TimeuuiD [16]byte
TinyInt int8
VarChar string
VarInt int64
}
err = session.ExecStmt(`CREATE TABLE IF NOT EXISTS examples.check_types (
asci_i ascii,
big_int bigint,
blo_b blob,
boolea_n boolean,
dat_e date,
decima_l decimal,
doubl_e double,
duratio_n duration,
floa_t float,
ine_t inet,
in_t int,
small_int smallint,
tex_t text,
tim_e time,
timestam_p timestamp,
timeuui_d timeuuid,
tiny_int tinyint,
id uuid PRIMARY KEY,
var_char varchar,
var_int varint,
map_int_text map<int, text>,
list_int list<int>,
set_int set<int>)`)
if err != nil {
t.Fatal("create table:", err)
}
// generated with schemagen
checkTypesTable := table.New(table.Metadata{
Name: "examples.check_types",
Columns: []string{
"asci_i",
"big_int",
"blo_b",
"boolea_n",
"dat_e",
"decima_l",
"doubl_e",
"duratio_n",
"floa_t",
"id",
"in_t",
"ine_t",
"list_int",
"map_int_text",
"set_int",
"small_int",
"tex_t",
"tim_e",
"timestam_p",
"timeuui_d",
"tiny_int",
"var_char",
"var_int",
},
PartKey: []string{"id"},
SortKey: []string{},
})
// Insert song using query builder.
insertCheckTypes := qb.Insert("examples.check_types").
Columns("asci_i", "big_int", "blo_b", "boolea_n", "dat_e", "decima_l", "doubl_e", "duratio_n", "floa_t",
"ine_t", "in_t", "small_int", "tex_t", "tim_e", "timestam_p", "timeuui_d", "tiny_int", "id", "var_char",
"var_int", "map_int_text", "list_int", "set_int").Query(session)
var byteID [16]byte
id := []byte("756716f7-2e54-4715-9f00-91dcbea6cf50")
copy(byteID[:], id)
date := time.Date(2021, time.December, 11, 10, 23, 0, 0, time.UTC)
var double float64 = 1.2 //nolint:staticcheck // type needs to be enforces
var float float32 = 1.3
var integer int32 = 123
listInt := []int32{1, 2, 3}
mapIntStr := map[int32]string{
1: "a",
2: "b",
}
setInt := []int32{2, 4, 6}
var smallInt int16 = 12
var tinyInt int8 = 14
var varInt int64 = 20
insertCheckTypes.BindStruct(CheckTypesStruct{
AsciI: "test qscci",
BigInt: 9223372036854775806, // MAXINT64 - 1,
BloB: []byte("this is blob test"),
BooleaN: false,
DatE: date,
DecimaL: *inf.NewDec(1, 1),
DoublE: double,
DuratioN: gocql.Duration{Months: 1, Days: 1, Nanoseconds: 86400},
FloaT: float,
ID: byteID,
InT: integer,
IneT: "127.0.0.1",
ListInt: listInt,
MapIntText: mapIntStr,
SetInt: setInt,
SmallInt: smallInt,
TexT: "text example",
TimE: 86400000000,
TimestamP: date,
TimeuuiD: gocql.TimeUUID(),
TinyInt: tinyInt,
VarChar: "test varchar",
VarInt: varInt,
})
if err := insertCheckTypes.ExecRelease(); err != nil {
t.Fatal("ExecRelease() failed:", err)
}
// Query and displays data.
queryCheckTypes := checkTypesTable.SelectQuery(session)
queryCheckTypes.BindStruct(&CheckTypesStruct{
ID: byteID,
})
var items []*CheckTypesStruct
if err := queryCheckTypes.Select(&items); err != nil {
t.Fatal("Select() failed:", err)
}
for _, i := range items {
t.Logf("%+v", *i)
}
}
// This example shows how to load a single value using "Get" function.
// Get can also work with UDTs and types that implement gocql marshalling functions.
func basicReadScyllaVersion(t *testing.T, session gocqlx.Session) {
t.Helper()
var releaseVersion string
err := session.Query("SELECT release_version FROM system.local", nil).Get(&releaseVersion)
if err != nil {
t.Fatal("Get() failed:", err)
}
t.Logf("Scylla version is: %s", releaseVersion)
}
// This examples shows how to bind data from a map using "BindMap" function,
// override field name mapping using the "db" tags, with the default mechanism of
// handling situations where driver returns more coluns that we are ready to
// consume.
func datatypesBlob(t *testing.T, session gocqlx.Session) {
t.Helper()
err := session.ExecStmt(`CREATE KEYSPACE IF NOT EXISTS examples WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}`)
if err != nil {
t.Fatal("create keyspace:", err)
}
err = session.ExecStmt(`CREATE TABLE IF NOT EXISTS examples.blobs(k int PRIMARY KEY, b blob, m map<text, blob>)`)
if err != nil {
t.Fatal("create table:", err)
}
// One way to get a byte buffer is to allocate it and fill it yourself:
var buf [16]byte
for i := range buf {
buf[i] = 0xff
}
insert := qb.Insert("examples.blobs").Columns("k", "b", "m").Query(session)
insert.BindMap(qb.M{
"k": 1,
"b": buf[:],
"m": map[string][]byte{"test": buf[:]},
})
if err := insert.ExecRelease(); err != nil {
t.Fatal("ExecRelease() failed:", err)
}
row := &struct {
Buffer []byte `db:"b"`
Mapping map[string][]byte `db:"m"`
}{}
q := qb.Select("examples.blobs").Where(qb.EqLit("k", "1")).Query(session)
// By default missing UDT fields are treated as null instead of failing
if err := q.Iter().Get(row); err != nil {
t.Fatal("Get() failed:", err)
}
t.Logf("%+v", row.Buffer)
t.Logf("%+v", row.Mapping)
}
type Coordinates struct {
gocqlx.UDT
X int
Y int
}
// This example shows how to add User Defined Type marshalling capabilities by
// adding a single line - embedding gocqlx.UDT.
func datatypesUserDefinedType(t *testing.T, session gocqlx.Session) {
t.Helper()
err := session.ExecStmt(`CREATE KEYSPACE IF NOT EXISTS examples WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}`)
if err != nil {
t.Fatal("create keyspace:", err)
}
err = session.ExecStmt(`CREATE TYPE IF NOT EXISTS examples.coordinates(x int, y int)`)
if err != nil {
t.Fatal("create type:", err)
}
err = session.ExecStmt(`CREATE TABLE IF NOT EXISTS examples.udts(k int PRIMARY KEY, c coordinates)`)
if err != nil {
t.Fatal("create table:", err)
}
coordinates1 := Coordinates{X: 12, Y: 34}
coordinates2 := Coordinates{X: 56, Y: 78}
insert := qb.Insert("examples.udts").Columns("k", "c").Query(session)
insert.BindMap(qb.M{
"k": 1,
"c": coordinates1,
})
if err := insert.Exec(); err != nil {
t.Fatal("Exec() failed:", err)
}
insert.BindMap(qb.M{
"k": 2,
"c": coordinates2,
})
if err := insert.Exec(); err != nil {
t.Fatal("Exec() failed:", err)
}
var coordinates []Coordinates
q := qb.Select("examples.udts").Columns("c").Query(session)
if err := q.Select(&coordinates); err != nil {
t.Fatal("Select() failed:", err)
}
for _, c := range coordinates {
t.Logf("%+v", c)
}
}
type coordinates struct {
X int
Y int
}
// This example shows how to add User Defined Type marshalling capabilities to
// types that we cannot modify, like library or transfer objects, without
// rewriting them in runtime.
func datatypesUserDefinedTypeWrapper(t *testing.T, session gocqlx.Session) {
t.Helper()
err := session.ExecStmt(`CREATE KEYSPACE IF NOT EXISTS examples WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}`)
if err != nil {
t.Fatal("create keyspace:", err)
}
err = session.ExecStmt(`CREATE TYPE IF NOT EXISTS examples.coordinates(x int, y int)`)
if err != nil {
t.Fatal("create type:", err)
}
err = session.ExecStmt(`CREATE TABLE IF NOT EXISTS examples.udts_wrapper(k int PRIMARY KEY, c coordinates)`)
if err != nil {
t.Fatal("create table:", err)
}
// Embed coordinates within CoordinatesUDT
c1 := &coordinates{X: 12, Y: 34}
c2 := &coordinates{X: 56, Y: 78}
type CoordinatesUDT struct {
gocqlx.UDT
*coordinates
}
coordinates1 := CoordinatesUDT{coordinates: c1}
coordinates2 := CoordinatesUDT{coordinates: c2}
insert := qb.Insert("examples.udts_wrapper").Columns("k", "c").Query(session)
insert.BindMap(qb.M{
"k": 1,
"c": coordinates1,
})
if err := insert.Exec(); err != nil {
t.Fatal("Exec() failed:", err)
}
insert.BindMap(qb.M{
"k": 2,
"c": coordinates2,
})
if err := insert.Exec(); err != nil {
t.Fatal("Exec() failed:", err)
}
var coordinates []Coordinates
q := qb.Select("examples.udts_wrapper").Columns("c").Query(session)
if err := q.Select(&coordinates); err != nil {
t.Fatal("Select() failed:", err)
}
for _, c := range coordinates {
t.Logf("%+v", c)
}
}
// This example shows how to use query builder to work with
func datatypesJSON(t *testing.T, session gocqlx.Session) {
t.Helper()
err := session.ExecStmt(`CREATE KEYSPACE IF NOT EXISTS examples WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}`)
if err != nil {
t.Fatal("create keyspace:", err)
}
err = session.ExecStmt(`CREATE TABLE IF NOT EXISTS examples.querybuilder_json(id int PRIMARY KEY, name text, specs map<text, text>)`)
if err != nil {
t.Fatal("create table:", err)
}
insert := qb.Insert("examples.querybuilder_json").Json().Query(session)
insert.Bind(`{ "id": 1, "name": "Mouse", "specs": { "color": "silver" } }`)
if err := insert.Exec(); err != nil {
t.Fatal("Exec() failed:", err)
}
insert.Bind(`{ "id": 2, "name": "Keyboard", "specs": { "layout": "qwerty" } }`)
if err := insert.Exec(); err != nil {
t.Fatal("Exec() failed:", err)
}
// fromJson lets you provide individual columns as JSON:
insertFromJSON := qb.Insert("examples.querybuilder_json").
Columns("id", "name").
FuncColumn("specs", qb.Fn("fromJson", "json")).
Query(session)
insertFromJSON.BindMap(qb.M{
"id": 3,
"name": "Screen",
"json": `{ "size": "24-inch" }`,
})
if err := insertFromJSON.Exec(); err != nil {
t.Fatal("Exec() failed:", err)
}
// Reading the whole row as a JSON object:
q := qb.Select("examples.querybuilder_json").
Json().
Where(qb.EqLit("id", "1")).
Query(session)
var jsonString string
if err := q.Get(&jsonString); err != nil {
t.Fatal("Get() failed:", err)
}
t.Logf("Entry #1 as JSON: %s", jsonString)
// Extracting a particular column as JSON:
q = qb.Select("examples.querybuilder_json").
Columns("id", "toJson(specs) AS json_specs").
Where(qb.EqLit("id", "2")).
Query(session)
row := &struct {
ID int
JSONSpecs string
}{}
if err := q.Get(row); err != nil {
t.Fatal("Get() failed:", err)
}
t.Logf("Entry #%d's specs as JSON: %s", row.ID, row.JSONSpecs)
}
type Video struct {
UserID int
UserName string
Added time.Time
VideoID int
Title string
}
func pagingFillTable(t *testing.T, insert *gocqlx.Queryx) {
t.Helper()
// 3 users
for i := 0; i < 3; i++ {
// 49 videos each
for j := 0; j < 49; j++ {
insert.BindStruct(Video{
UserID: i,
UserName: fmt.Sprint("user ", i),
Added: time.Unix(int64(j)*100, 0),
VideoID: i*100 + j,
Title: fmt.Sprint("video ", i*100+j),
})
if err := insert.Exec(); err != nil {
t.Fatal("Exec() failed:", err)
}
}
}
}
// This example shows how to use stateful paging and how "Select" function
// can be used to fetch single page only.
func pagingForwardPaging(t *testing.T, session gocqlx.Session) {
t.Helper()
err := session.ExecStmt(`CREATE KEYSPACE IF NOT EXISTS examples WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}`)
if err != nil {
t.Fatal("create keyspace:", err)
}
err = session.ExecStmt(`CREATE TABLE IF NOT EXISTS examples.paging_forward_paging(
user_id int,
user_name text,
added timestamp,
video_id int,
title text,
PRIMARY KEY (user_id, added, video_id)
) WITH CLUSTERING ORDER BY (added DESC, video_id ASC)`)
if err != nil {
t.Fatal("create table:", err)
}
videoMetadata := table.Metadata{
Name: "examples.paging_forward_paging",
Columns: []string{"user_id", "user_name", "added", "video_id", "title"},
PartKey: []string{"user_id"},
SortKey: []string{"added", "video_id"},
}
videoTable := table.New(videoMetadata)
pagingFillTable(t, videoTable.InsertQuery(session))
// Query and displays data. Iterate over videos of user "1" 10 entries per request.
const itemsPerPage = 10
getUserVideos := func(userID int, page []byte) (userVideos []Video, nextPage []byte, err error) {
q := videoTable.SelectQuery(session).Bind(userID)
defer q.Release()
q.PageState(page)
q.PageSize(itemsPerPage)
iter := q.Iter()
return userVideos, iter.PageState(), iter.Select(&userVideos)
}
var (
userVideos []Video
nextPage []byte
)
for i := 1; ; i++ {
userVideos, nextPage, err = getUserVideos(1, nextPage)
if err != nil {
t.Fatalf("load page %d: %s", i, err)
}
t.Logf("Page %d:", i)
for _, v := range userVideos {
t.Logf("%+v", v)
}
if len(nextPage) == 0 {
break
}
}
}
// This example shows how to efficiently process all rows in a table using
// the "token" function. It implements idea from blog post [1]:
// As a bonus we use "CompileNamedQueryString" to get named parameters out of
// CQL query placeholders like in Python or Java driver.
//
// [1] https://www.scylladb.com/2017/02/13/efficient-full-table-scans-with-scylla-1-6/.
func pagingEfficientFullTableScan(t *testing.T, session gocqlx.Session) {
t.Helper()
err := session.ExecStmt(`CREATE KEYSPACE IF NOT EXISTS examples WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}`)
if err != nil {
t.Fatal("create keyspace:", err)
}
err = session.ExecStmt(`CREATE TABLE IF NOT EXISTS examples.paging_efficient_full_table_scan(
user_id int,
user_name text,
added timestamp,
video_id int,
title text,
PRIMARY KEY (user_id, added, video_id)
) WITH CLUSTERING ORDER BY (added DESC, video_id ASC)`)
if err != nil {
t.Fatal("create table:", err)
}
videoMetadata := table.Metadata{
Name: "examples.paging_efficient_full_table_scan",
Columns: []string{"user_id", "user_name", "added", "video_id", "title"},
PartKey: []string{"user_id"},
SortKey: []string{"added", "video_id"},
}
videoTable := table.New(videoMetadata)
pagingFillTable(t, videoTable.InsertQuery(session))
// Calculate optimal number of workers for the cluster:
var (
nodesInCluster = 1
coresInNode = 1
smudgeFactor = 3
)
workers := nodesInCluster * coresInNode * smudgeFactor
t.Logf("Workers %d", workers)
type tokenRange struct {
Start int64
End int64
}
buf := make(chan tokenRange)
// sequencer pushes token ranges to buf
sequencer := func() error {
span := int64(math.MaxInt64 / (50 * workers))
tr := tokenRange{math.MinInt64, math.MinInt64 + span}
for tr.End > tr.Start {
buf <- tr
tr.Start = tr.End
tr.End += span
}
tr.End = math.MaxInt64
buf <- tr
close(buf)
return nil
}
// worker queries a token ranges generated by sequencer
worker := func() error {
const cql = `SELECT * FROM examples.paging_efficient_full_table_scan WHERE
token(user_id) >= :start AND
token(user_id) < :end`
stmt, names, err := gocqlx.CompileNamedQueryString(cql)
if err != nil {
return err
}
q := session.Query(stmt, names)
defer q.Release()
var v Video
for {
tr, ok := <-buf
if !ok {
break
}
iter := q.BindStruct(tr).Iter()
for iter.StructScan(&v) {
t.Logf("%+v:", v)
}
if err := iter.Close(); err != nil {
return err
}
}
return nil
}
// Query and displays data.
var wg errgroup.Group
wg.Go(sequencer)
for i := 0; i < workers; i++ {
wg.Go(worker)
}
if err := wg.Wait(); err != nil {
t.Fatal(err)
}
}
// This example shows how to use Lightweight Transactions (LWT) aka.
// Compare-And-Set (CAS) functions.
// See: https://docs.scylladb.com/using-scylla/lwt/ for more details.
func lwtLock(t *testing.T, session gocqlx.Session) {
t.Helper()
err := session.ExecStmt(`CREATE KEYSPACE IF NOT EXISTS examples WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}`)
if err != nil {
t.Fatal("create keyspace:", err)
}
type Lock struct {
Name string
Owner string
TTL int64
}
err = session.ExecStmt(`CREATE TABLE examples.lock (name text PRIMARY KEY, owner text)`)
if err != nil {
t.Fatal("create table:", err)
}
extend := func(lock Lock) bool {
q := qb.Update("examples.lock").
Set("owner").
Where(qb.Eq("name")).
If(qb.Eq("owner")).
TTLNamed("ttl").
Query(session).
SerialConsistency(gocql.Serial).
BindStruct(lock)
applied, err := q.ExecCASRelease()
if err != nil {
t.Fatal("ExecCASRelease() failed:", err)
}
return applied
}
acquire := func(lock Lock) (applied bool) {
var prev Lock
defer func() {
t.Logf("Acquire %+v applied %v owner %+v)", lock, applied, prev)
}()
q := qb.Insert("examples.lock").
Columns("name", "owner").
TTLNamed("ttl").
Unique().
Query(session).
SerialConsistency(gocql.Serial).
BindStruct(lock)
applied, err = q.GetCASRelease(&prev)
if err != nil {
t.Fatal("GetCASRelease() failed:", err)
}
if applied {
return true
}
if prev.Owner == lock.Owner {
return extend(lock)
}
return false
}
const (
resource = "acme"
ttl = time.Second
)
l1 := Lock{
Name: resource,
Owner: "1",
TTL: qb.TTL(ttl),
}
l2 := Lock{
Name: resource,
Owner: "2",
TTL: qb.TTL(ttl),
}
if !acquire(l1) {
t.Fatal("l1 failed to acquire lock")
}
if acquire(l2) {
t.Fatal("unexpectedly l2 acquired lock")
}
if !acquire(l1) {
t.Fatal("l1 failed to extend lock")
}
time.Sleep(time.Second)
if !acquire(l2) {
t.Fatal("l2 failed to acquire lock")
}
if acquire(l1) {
t.Fatal("unexpectedly l1 acquired lock")
}
}
// This example shows how to reuse the same insert statement with
// partially filled parameters without generating tombstones for empty columns.
func unsetEmptyValues(t *testing.T, session gocqlx.Session) {
t.Helper()
err := session.ExecStmt(`CREATE KEYSPACE IF NOT EXISTS examples WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}`)
if err != nil {
t.Fatal("create keyspace:", err)
}
type Operation struct {
ID string
ClientID string
Type string
PaymentID string
Fee *inf.Dec
}
err = session.ExecStmt(`CREATE TABLE IF NOT EXISTS examples.operations (
id text PRIMARY KEY,
client_id text,
type text,
payment_id text,
fee decimal)`)
if err != nil {
t.Fatal("create table:", err)
}
insertOperation := qb.Insert("examples.operations").
Columns("id", "client_id", "type", "payment_id", "fee")
// Insert operation with empty paymentID.
insertQuery := insertOperation.Query(session).
WithBindTransformer(gocqlx.UnsetEmptyTransformer).
BindStruct(Operation{
ID: "1",
ClientID: "42",
Type: "Transfer",
Fee: inf.NewDec(1, 1),
})
if err := insertQuery.ExecRelease(); err != nil {
t.Fatal("ExecRelease() failed:", err)
}
// Set default transformer to avoid setting it for each query.
gocqlx.DefaultBindTransformer = gocqlx.UnsetEmptyTransformer
defer func() {
gocqlx.DefaultBindTransformer = nil
}()
// Insert operation with empty fee.
insertQuery = insertOperation.Query(session).
BindStruct(Operation{
ID: "2",
ClientID: "42",
Type: "Input",
PaymentID: "1",
})
if err := insertQuery.ExecRelease(); err != nil {
t.Fatal("ExecRelease() failed:", err)
}
// Query and displays data.
var ops []*Operation
if err := qb.Select("examples.operations").Query(session).Select(&ops); err != nil {
t.Fatal("Select() failed:", err)
}
for _, op := range ops {
t.Logf("%+v", *op)
}
}
func mustParseUUID(s string) gocql.UUID {
u, err := gocql.ParseUUID(s)
if err != nil {
panic(err)
}
return u
}