From ab2a96d9f3644568135edc3aa817ced8f503aff0 Mon Sep 17 00:00:00 2001 From: Aleksandar Jankovic Date: Tue, 25 Feb 2020 12:35:44 +0100 Subject: [PATCH] table: fix data race in select builder usage When table is used with SelectBuilder it triggers data race on Where() condition usage. This change copies underlining slices on table creation to allow safe concurrent usage of the table. --- table/table.go | 3 ++- table/table_test.go | 58 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/table/table.go b/table/table.go index 9e6acdf..7d1ecd4 100644 --- a/table/table.go +++ b/table/table.go @@ -45,7 +45,8 @@ func New(m Metadata) *Table { // nolint: gocritic for _, k := range m.SortKey { t.primaryKeyCmp = append(t.primaryKeyCmp, qb.Eq(k)) } - t.partKeyCmp = t.primaryKeyCmp[:len(t.metadata.PartKey)] + t.partKeyCmp = make([]qb.Cmp, len(m.PartKey)) + copy(t.partKeyCmp, t.primaryKeyCmp[:len(t.metadata.PartKey)]) // prepare get stmt t.get.stmt, t.get.names = qb.Select(m.Name).Where(t.primaryKeyCmp...).ToCql() diff --git a/table/table_test.go b/table/table_test.go index 8afea64..151a5c9 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -5,9 +5,11 @@ package table import ( + "sync" "testing" "github.com/google/go-cmp/cmp" + "github.com/scylladb/gocqlx/qb" ) func TestTableGet(t *testing.T) { @@ -219,3 +221,59 @@ func TestTableDelete(t *testing.T) { } } } + +func TestTableConcurrentUsage(t *testing.T) { + table := []struct { + Name string + M Metadata + C []string + N []string + S string + }{ + { + Name: "Full select", + M: Metadata{ + Name: "table", + Columns: []string{"a", "b", "c", "d"}, + PartKey: []string{"a"}, + SortKey: []string{"b"}, + }, + N: []string{"a", "b"}, + S: "SELECT * FROM table WHERE a=? AND b=? ", + }, + { + Name: "Sub select", + M: Metadata{ + Name: "table", + Columns: []string{"a", "b", "c", "d"}, + PartKey: []string{"a"}, + SortKey: []string{"b"}, + }, + C: []string{"d"}, + N: []string{"a", "b"}, + S: "SELECT d FROM table WHERE a=? AND b=? ", + }, + } + + parallelCount := 3 + // run SelectBuilder on the data set in parallel + for _, test := range table { + var wg sync.WaitGroup + testTable := New(test.M) + wg.Add(parallelCount) + for i := 0; i < parallelCount; i++ { + go func() { + defer wg.Done() + stmt, names := testTable.SelectBuilder(test.C...). + Where(qb.Eq("b")).ToCql() + if diff := cmp.Diff(test.S, stmt); diff != "" { + t.Error(diff) + } + if diff := cmp.Diff(test.N, names); diff != "" { + t.Error(diff, names) + } + }() + } + wg.Wait() + } +}