// Copyright 2016 Google LLC // // 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 bttest import ( "context" "fmt" "math/rand" "strconv" "sync" "sync/atomic" "testing" "time" "github.com/golang/protobuf/proto" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" btapb "google.golang.org/genproto/googleapis/bigtable/admin/v2" btpb "google.golang.org/genproto/googleapis/bigtable/v2" "google.golang.org/grpc" ) func TestConcurrentMutationsReadModifyAndGC(t *testing.T) { s := &server{ tables: make(map[string]*table), } ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) defer cancel() if _, err := s.CreateTable( ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t"}); err != nil { t.Fatal(err) } const name = `cluster/tables/t` tbl := s.tables[name] req := &btapb.ModifyColumnFamiliesRequest{ Name: name, Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{ Id: "cf", Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Create{Create: &btapb.ColumnFamily{}}, }}, } _, err := s.ModifyColumnFamilies(ctx, req) if err != nil { t.Fatal(err) } req = &btapb.ModifyColumnFamiliesRequest{ Name: name, Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{ Id: "cf", Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Update{Update: &btapb.ColumnFamily{ GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}, }}, }}, } if _, err := s.ModifyColumnFamilies(ctx, req); err != nil { t.Fatal(err) } var wg sync.WaitGroup var ts int64 ms := func() []*btpb.Mutation { return []*btpb.Mutation{{ Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{ FamilyName: "cf", ColumnQualifier: []byte(`col`), TimestampMicros: atomic.AddInt64(&ts, 1000), }}, }} } rmw := func() *btpb.ReadModifyWriteRowRequest { return &btpb.ReadModifyWriteRowRequest{ TableName: name, RowKey: []byte(fmt.Sprint(rand.Intn(100))), Rules: []*btpb.ReadModifyWriteRule{{ FamilyName: "cf", ColumnQualifier: []byte("col"), Rule: &btpb.ReadModifyWriteRule_IncrementAmount{IncrementAmount: 1}, }}, } } for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() for ctx.Err() == nil { req := &btpb.MutateRowRequest{ TableName: name, RowKey: []byte(fmt.Sprint(rand.Intn(100))), Mutations: ms(), } if _, err := s.MutateRow(ctx, req); err != nil { panic(err) // can't use t.Fatal in goroutine } } }() wg.Add(1) go func() { defer wg.Done() for ctx.Err() == nil { _, _ = s.ReadModifyWriteRow(ctx, rmw()) } }() wg.Add(1) go func() { defer wg.Done() tbl.gc() }() } done := make(chan struct{}) go func() { wg.Wait() close(done) }() select { case <-done: case <-time.After(1 * time.Second): t.Error("Concurrent mutations and GCs haven't completed after 1s") } } func TestCreateTableWithFamily(t *testing.T) { // The Go client currently doesn't support creating a table with column families // in one operation but it is allowed by the API. This must still be supported by the // fake server so this test lives here instead of in the main bigtable // integration test. s := &server{ tables: make(map[string]*table), } ctx := context.Background() newTbl := btapb.Table{ ColumnFamilies: map[string]*btapb.ColumnFamily{ "cf1": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 123}}}, "cf2": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 456}}}, }, } cTbl, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl}) if err != nil { t.Fatalf("Creating table: %v", err) } tbl, err := s.GetTable(ctx, &btapb.GetTableRequest{Name: cTbl.Name}) if err != nil { t.Fatalf("Getting table: %v", err) } cf := tbl.ColumnFamilies["cf1"] if cf == nil { t.Fatalf("Missing col family cf1") } if got, want := cf.GcRule.GetMaxNumVersions(), int32(123); got != want { t.Errorf("Invalid MaxNumVersions: wanted:%d, got:%d", want, got) } cf = tbl.ColumnFamilies["cf2"] if cf == nil { t.Fatalf("Missing col family cf2") } if got, want := cf.GcRule.GetMaxNumVersions(), int32(456); got != want { t.Errorf("Invalid MaxNumVersions: wanted:%d, got:%d", want, got) } } type MockSampleRowKeysServer struct { responses []*btpb.SampleRowKeysResponse grpc.ServerStream } func (s *MockSampleRowKeysServer) Send(resp *btpb.SampleRowKeysResponse) error { s.responses = append(s.responses, resp) return nil } func TestSampleRowKeys(t *testing.T) { s := &server{ tables: make(map[string]*table), } ctx := context.Background() newTbl := btapb.Table{ ColumnFamilies: map[string]*btapb.ColumnFamily{ "cf": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}}, }, } tbl, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl}) if err != nil { t.Fatalf("Creating table: %v", err) } // Populate the table val := []byte("value") rowCount := 1000 for i := 0; i < rowCount; i++ { req := &btpb.MutateRowRequest{ TableName: tbl.Name, RowKey: []byte("row-" + strconv.Itoa(i)), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{ FamilyName: "cf", ColumnQualifier: []byte("col"), TimestampMicros: 1000, Value: val, }}, }}, } if _, err := s.MutateRow(ctx, req); err != nil { t.Fatalf("Populating table: %v", err) } } mock := &MockSampleRowKeysServer{} if err := s.SampleRowKeys(&btpb.SampleRowKeysRequest{TableName: tbl.Name}, mock); err != nil { t.Errorf("SampleRowKeys error: %v", err) } if len(mock.responses) == 0 { t.Fatal("Response count: got 0, want > 0") } // Make sure the offset of the final response is the offset of the final row got := mock.responses[len(mock.responses)-1].OffsetBytes want := int64((rowCount - 1) * len(val)) if got != want { t.Errorf("Invalid offset: got %d, want %d", got, want) } } func TestDropRowRange(t *testing.T) { s := &server{ tables: make(map[string]*table), } ctx := context.Background() newTbl := btapb.Table{ ColumnFamilies: map[string]*btapb.ColumnFamily{ "cf": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}}, }, } tblInfo, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl}) if err != nil { t.Fatalf("Creating table: %v", err) } tbl := s.tables[tblInfo.Name] // Populate the table prefixes := []string{"AAA", "BBB", "CCC", "DDD"} count := 3 doWrite := func() { for _, prefix := range prefixes { for i := 0; i < count; i++ { req := &btpb.MutateRowRequest{ TableName: tblInfo.Name, RowKey: []byte(prefix + strconv.Itoa(i)), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{ FamilyName: "cf", ColumnQualifier: []byte("col"), TimestampMicros: 1000, Value: []byte{}, }}, }}, } if _, err := s.MutateRow(ctx, req); err != nil { t.Fatalf("Populating table: %v", err) } } } } doWrite() tblSize := tbl.rows.Len() req := &btapb.DropRowRangeRequest{ Name: tblInfo.Name, Target: &btapb.DropRowRangeRequest_RowKeyPrefix{RowKeyPrefix: []byte("AAA")}, } if _, err = s.DropRowRange(ctx, req); err != nil { t.Fatalf("Dropping first range: %v", err) } got, want := tbl.rows.Len(), tblSize-count if got != want { t.Errorf("Row count after first drop: got %d (%v), want %d", got, tbl.rows, want) } req = &btapb.DropRowRangeRequest{ Name: tblInfo.Name, Target: &btapb.DropRowRangeRequest_RowKeyPrefix{RowKeyPrefix: []byte("DDD")}, } if _, err = s.DropRowRange(ctx, req); err != nil { t.Fatalf("Dropping second range: %v", err) } got, want = tbl.rows.Len(), tblSize-(2*count) if got != want { t.Errorf("Row count after second drop: got %d (%v), want %d", got, tbl.rows, want) } req = &btapb.DropRowRangeRequest{ Name: tblInfo.Name, Target: &btapb.DropRowRangeRequest_RowKeyPrefix{RowKeyPrefix: []byte("XXX")}, } if _, err = s.DropRowRange(ctx, req); err != nil { t.Fatalf("Dropping invalid range: %v", err) } got, want = tbl.rows.Len(), tblSize-(2*count) if got != want { t.Errorf("Row count after invalid drop: got %d (%v), want %d", got, tbl.rows, want) } req = &btapb.DropRowRangeRequest{ Name: tblInfo.Name, Target: &btapb.DropRowRangeRequest_DeleteAllDataFromTable{DeleteAllDataFromTable: true}, } if _, err = s.DropRowRange(ctx, req); err != nil { t.Fatalf("Dropping all data: %v", err) } got, want = tbl.rows.Len(), 0 if got != want { t.Errorf("Row count after drop all: got %d, want %d", got, want) } // Test that we can write rows, delete some and then write them again. count = 1 doWrite() req = &btapb.DropRowRangeRequest{ Name: tblInfo.Name, Target: &btapb.DropRowRangeRequest_DeleteAllDataFromTable{DeleteAllDataFromTable: true}, } if _, err = s.DropRowRange(ctx, req); err != nil { t.Fatalf("Dropping all data: %v", err) } got, want = tbl.rows.Len(), 0 if got != want { t.Errorf("Row count after drop all: got %d, want %d", got, want) } doWrite() got, want = tbl.rows.Len(), len(prefixes) if got != want { t.Errorf("Row count after rewrite: got %d, want %d", got, want) } req = &btapb.DropRowRangeRequest{ Name: tblInfo.Name, Target: &btapb.DropRowRangeRequest_RowKeyPrefix{RowKeyPrefix: []byte("BBB")}, } if _, err = s.DropRowRange(ctx, req); err != nil { t.Fatalf("Dropping range: %v", err) } doWrite() got, want = tbl.rows.Len(), len(prefixes) if got != want { t.Errorf("Row count after drop range: got %d, want %d", got, want) } } type MockReadRowsServer struct { responses []*btpb.ReadRowsResponse grpc.ServerStream } func (s *MockReadRowsServer) Send(resp *btpb.ReadRowsResponse) error { s.responses = append(s.responses, resp) return nil } func TestReadRows(t *testing.T) { ctx := context.Background() s := &server{ tables: make(map[string]*table), } newTbl := btapb.Table{ ColumnFamilies: map[string]*btapb.ColumnFamily{ "cf0": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}}, }, } tblInfo, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl}) if err != nil { t.Fatalf("Creating table: %v", err) } mreq := &btpb.MutateRowRequest{ TableName: tblInfo.Name, RowKey: []byte("row"), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{ FamilyName: "cf0", ColumnQualifier: []byte("col"), TimestampMicros: 1000, Value: []byte{}, }}, }}, } if _, err := s.MutateRow(ctx, mreq); err != nil { t.Fatalf("Populating table: %v", err) } for _, rowset := range []*btpb.RowSet{ {RowKeys: [][]byte{[]byte("row")}}, {RowRanges: []*btpb.RowRange{{StartKey: &btpb.RowRange_StartKeyClosed{StartKeyClosed: []byte("")}}}}, {RowRanges: []*btpb.RowRange{{StartKey: &btpb.RowRange_StartKeyClosed{StartKeyClosed: []byte("r")}}}}, {RowRanges: []*btpb.RowRange{{ StartKey: &btpb.RowRange_StartKeyClosed{StartKeyClosed: []byte("")}, EndKey: &btpb.RowRange_EndKeyOpen{EndKeyOpen: []byte("s")}, }}}, } { mock := &MockReadRowsServer{} req := &btpb.ReadRowsRequest{TableName: tblInfo.Name, Rows: rowset} if err = s.ReadRows(req, mock); err != nil { t.Fatalf("ReadRows error: %v", err) } if got, want := len(mock.responses), 1; got != want { t.Errorf("%+v: response count: got %d, want %d", rowset, got, want) } } } func TestReadRowsError(t *testing.T) { ctx := context.Background() s := &server{ tables: make(map[string]*table), } newTbl := btapb.Table{ ColumnFamilies: map[string]*btapb.ColumnFamily{ "cf0": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}}, }, } tblInfo, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl}) if err != nil { t.Fatalf("Creating table: %v", err) } mreq := &btpb.MutateRowRequest{ TableName: tblInfo.Name, RowKey: []byte("row"), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{ FamilyName: "cf0", ColumnQualifier: []byte("col"), TimestampMicros: 1000, Value: []byte{}, }}, }}, } if _, err := s.MutateRow(ctx, mreq); err != nil { t.Fatalf("Populating table: %v", err) } mock := &MockReadRowsServer{} req := &btpb.ReadRowsRequest{TableName: tblInfo.Name, Filter: &btpb.RowFilter{ Filter: &btpb.RowFilter_RowKeyRegexFilter{RowKeyRegexFilter: []byte("[")}}, // Invalid regex. } if err = s.ReadRows(req, mock); err == nil { t.Fatal("ReadRows got no error, want error") } } func TestReadRowsAfterDeletion(t *testing.T) { ctx := context.Background() s := &server{ tables: make(map[string]*table), } newTbl := btapb.Table{ ColumnFamilies: map[string]*btapb.ColumnFamily{ "cf0": {}, }, } tblInfo, err := s.CreateTable(ctx, &btapb.CreateTableRequest{ Parent: "cluster", TableId: "t", Table: &newTbl, }) if err != nil { t.Fatalf("Creating table: %v", err) } populateTable(ctx, s) dreq := &btpb.MutateRowRequest{ TableName: tblInfo.Name, RowKey: []byte("row"), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_DeleteFromRow_{ DeleteFromRow: &btpb.Mutation_DeleteFromRow{}, }, }}, } if _, err := s.MutateRow(ctx, dreq); err != nil { t.Fatalf("Deleting from table: %v", err) } mock := &MockReadRowsServer{} req := &btpb.ReadRowsRequest{TableName: tblInfo.Name} if err = s.ReadRows(req, mock); err != nil { t.Fatalf("ReadRows error: %v", err) } if got, want := len(mock.responses), 0; got != want { t.Errorf("response count: got %d, want %d", got, want) } } func TestReadRowsOrder(t *testing.T) { s := &server{ tables: make(map[string]*table), } ctx := context.Background() newTbl := btapb.Table{ ColumnFamilies: map[string]*btapb.ColumnFamily{ "cf0": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}}, }, } tblInfo, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl}) if err != nil { t.Fatalf("Creating table: %v", err) } count := 3 mcf := func(i int) *btapb.ModifyColumnFamiliesRequest { return &btapb.ModifyColumnFamiliesRequest{ Name: tblInfo.Name, Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{ Id: "cf" + strconv.Itoa(i), Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Create{Create: &btapb.ColumnFamily{}}, }}, } } for i := 1; i <= count; i++ { _, err = s.ModifyColumnFamilies(ctx, mcf(i)) if err != nil { t.Fatal(err) } } // Populate the table for fc := 0; fc < count; fc++ { for cc := count; cc > 0; cc-- { for tc := 0; tc < count; tc++ { req := &btpb.MutateRowRequest{ TableName: tblInfo.Name, RowKey: []byte("row"), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{ FamilyName: "cf" + strconv.Itoa(fc), ColumnQualifier: []byte("col" + strconv.Itoa(cc)), TimestampMicros: int64((tc + 1) * 1000), Value: []byte{}, }}, }}, } if _, err := s.MutateRow(ctx, req); err != nil { t.Fatalf("Populating table: %v", err) } } } } req := &btpb.ReadRowsRequest{ TableName: tblInfo.Name, Rows: &btpb.RowSet{RowKeys: [][]byte{[]byte("row")}}, } mock := &MockReadRowsServer{} if err = s.ReadRows(req, mock); err != nil { t.Errorf("ReadRows error: %v", err) } if len(mock.responses) == 0 { t.Fatal("Response count: got 0, want > 0") } if len(mock.responses[0].Chunks) != 27 { t.Fatalf("Chunk count: got %d, want 27", len(mock.responses[0].Chunks)) } testOrder := func(ms *MockReadRowsServer) { var prevFam, prevCol string var prevTime int64 for _, cc := range ms.responses[0].Chunks { if prevFam == "" { prevFam = cc.FamilyName.Value prevCol = string(cc.Qualifier.Value) prevTime = cc.TimestampMicros continue } if cc.FamilyName.Value < prevFam { t.Errorf("Family order is not correct: got %s < %s", cc.FamilyName.Value, prevFam) } else if cc.FamilyName.Value == prevFam { if string(cc.Qualifier.Value) < prevCol { t.Errorf("Column order is not correct: got %s < %s", string(cc.Qualifier.Value), prevCol) } else if string(cc.Qualifier.Value) == prevCol { if cc.TimestampMicros > prevTime { t.Errorf("cell order is not correct: got %d > %d", cc.TimestampMicros, prevTime) } } } prevFam = cc.FamilyName.Value prevCol = string(cc.Qualifier.Value) prevTime = cc.TimestampMicros } } testOrder(mock) // Read with interleave filter inter := &btpb.RowFilter_Interleave{} fnr := &btpb.RowFilter{Filter: &btpb.RowFilter_FamilyNameRegexFilter{FamilyNameRegexFilter: "cf1"}} cqr := &btpb.RowFilter{Filter: &btpb.RowFilter_ColumnQualifierRegexFilter{ColumnQualifierRegexFilter: []byte("col2")}} inter.Filters = append(inter.Filters, fnr, cqr) req = &btpb.ReadRowsRequest{ TableName: tblInfo.Name, Rows: &btpb.RowSet{RowKeys: [][]byte{[]byte("row")}}, Filter: &btpb.RowFilter{ Filter: &btpb.RowFilter_Interleave_{Interleave: inter}, }, } mock = &MockReadRowsServer{} if err = s.ReadRows(req, mock); err != nil { t.Errorf("ReadRows error: %v", err) } if len(mock.responses) == 0 { t.Fatal("Response count: got 0, want > 0") } if len(mock.responses[0].Chunks) != 18 { t.Fatalf("Chunk count: got %d, want 18", len(mock.responses[0].Chunks)) } testOrder(mock) // Check order after ReadModifyWriteRow rmw := func(i int) *btpb.ReadModifyWriteRowRequest { return &btpb.ReadModifyWriteRowRequest{ TableName: tblInfo.Name, RowKey: []byte("row"), Rules: []*btpb.ReadModifyWriteRule{{ FamilyName: "cf3", ColumnQualifier: []byte("col" + strconv.Itoa(i)), Rule: &btpb.ReadModifyWriteRule_IncrementAmount{IncrementAmount: 1}, }}, } } for i := count; i > 0; i-- { if _, err := s.ReadModifyWriteRow(ctx, rmw(i)); err != nil { t.Fatal(err) } } req = &btpb.ReadRowsRequest{ TableName: tblInfo.Name, Rows: &btpb.RowSet{RowKeys: [][]byte{[]byte("row")}}, } mock = &MockReadRowsServer{} if err = s.ReadRows(req, mock); err != nil { t.Errorf("ReadRows error: %v", err) } if len(mock.responses) == 0 { t.Fatal("Response count: got 0, want > 0") } if len(mock.responses[0].Chunks) != 30 { t.Fatalf("Chunk count: got %d, want 30", len(mock.responses[0].Chunks)) } testOrder(mock) } func TestReadRowsWithlabelTransformer(t *testing.T) { ctx := context.Background() s := &server{ tables: make(map[string]*table), } newTbl := btapb.Table{ ColumnFamilies: map[string]*btapb.ColumnFamily{ "cf0": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}}, }, } tblInfo, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl}) if err != nil { t.Fatalf("Creating table: %v", err) } mreq := &btpb.MutateRowRequest{ TableName: tblInfo.Name, RowKey: []byte("row"), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{ FamilyName: "cf0", ColumnQualifier: []byte("col"), TimestampMicros: 1000, Value: []byte{}, }}, }}, } if _, err := s.MutateRow(ctx, mreq); err != nil { t.Fatalf("Populating table: %v", err) } mock := &MockReadRowsServer{} req := &btpb.ReadRowsRequest{ TableName: tblInfo.Name, Filter: &btpb.RowFilter{ Filter: &btpb.RowFilter_ApplyLabelTransformer{ ApplyLabelTransformer: "label", }, }, } if err = s.ReadRows(req, mock); err != nil { t.Fatalf("ReadRows error: %v", err) } if got, want := len(mock.responses), 1; got != want { t.Fatalf("response count: got %d, want %d", got, want) } resp := mock.responses[0] if got, want := len(resp.Chunks), 1; got != want { t.Fatalf("chunks count: got %d, want %d", got, want) } chunk := resp.Chunks[0] if got, want := len(chunk.Labels), 1; got != want { t.Fatalf("labels count: got %d, want %d", got, want) } if got, want := chunk.Labels[0], "label"; got != want { t.Fatalf("label: got %s, want %s", got, want) } mock = &MockReadRowsServer{} req = &btpb.ReadRowsRequest{ TableName: tblInfo.Name, Filter: &btpb.RowFilter{ Filter: &btpb.RowFilter_ApplyLabelTransformer{ ApplyLabelTransformer: "", // invalid label }, }, } if err = s.ReadRows(req, mock); err == nil { t.Fatal("ReadRows want invalid label error, got none") } } func TestCheckAndMutateRowWithoutPredicate(t *testing.T) { s := &server{ tables: make(map[string]*table), } ctx := context.Background() newTbl := btapb.Table{ ColumnFamilies: map[string]*btapb.ColumnFamily{ "cf": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}}, }, } tbl, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl}) if err != nil { t.Fatalf("Creating table: %v", err) } // Populate the table val := []byte("value") mrreq := &btpb.MutateRowRequest{ TableName: tbl.Name, RowKey: []byte("row-present"), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_SetCell_{SetCell: &btpb.Mutation_SetCell{ FamilyName: "cf", ColumnQualifier: []byte("col"), TimestampMicros: 1000, Value: val, }}, }}, } if _, err := s.MutateRow(ctx, mrreq); err != nil { t.Fatalf("Populating table: %v", err) } req := &btpb.CheckAndMutateRowRequest{ TableName: tbl.Name, RowKey: []byte("row-not-present"), } if res, err := s.CheckAndMutateRow(ctx, req); err != nil { t.Errorf("CheckAndMutateRow error: %v", err) } else if got, want := res.PredicateMatched, false; got != want { t.Errorf("Invalid PredicateMatched value: got %t, want %t", got, want) } req = &btpb.CheckAndMutateRowRequest{ TableName: tbl.Name, RowKey: []byte("row-present"), } if res, err := s.CheckAndMutateRow(ctx, req); err != nil { t.Errorf("CheckAndMutateRow error: %v", err) } else if got, want := res.PredicateMatched, true; got != want { t.Errorf("Invalid PredicateMatched value: got %t, want %t", got, want) } } func TestServer_ReadModifyWriteRow(t *testing.T) { s := &server{ tables: make(map[string]*table), } ctx := context.Background() newTbl := btapb.Table{ ColumnFamilies: map[string]*btapb.ColumnFamily{ "cf": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{MaxNumVersions: 1}}}, }, } tbl, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl}) if err != nil { t.Fatalf("Creating table: %v", err) } req := &btpb.ReadModifyWriteRowRequest{ TableName: tbl.Name, RowKey: []byte("row-key"), Rules: []*btpb.ReadModifyWriteRule{ { FamilyName: "cf", ColumnQualifier: []byte("q1"), Rule: &btpb.ReadModifyWriteRule_AppendValue{ AppendValue: []byte("a"), }, }, // multiple ops for same cell { FamilyName: "cf", ColumnQualifier: []byte("q1"), Rule: &btpb.ReadModifyWriteRule_AppendValue{ AppendValue: []byte("b"), }, }, // different cell whose qualifier should sort before the prior rules { FamilyName: "cf", ColumnQualifier: []byte("q0"), Rule: &btpb.ReadModifyWriteRule_IncrementAmount{ IncrementAmount: 1, }, }, }, } got, err := s.ReadModifyWriteRow(ctx, req) if err != nil { t.Fatalf("ReadModifyWriteRow error: %v", err) } want := &btpb.ReadModifyWriteRowResponse{ Row: &btpb.Row{ Key: []byte("row-key"), Families: []*btpb.Family{{ Name: "cf", Columns: []*btpb.Column{ { Qualifier: []byte("q0"), Cells: []*btpb.Cell{{ Value: []byte{0, 0, 0, 0, 0, 0, 0, 1}, }}, }, { Qualifier: []byte("q1"), Cells: []*btpb.Cell{{ Value: []byte("ab"), }}, }, }, }}, }, } diff := cmp.Diff(got, want, cmpopts.IgnoreFields(btpb.Cell{}, "TimestampMicros")) if diff != "" { t.Errorf("unexpected response: %s", diff) } } // helper function to populate table data func populateTable(ctx context.Context, s *server) (*btapb.Table, error) { newTbl := btapb.Table{ ColumnFamilies: map[string]*btapb.ColumnFamily{ "cf0": {GcRule: &btapb.GcRule{Rule: &btapb.GcRule_MaxNumVersions{1}}}, }, } tblInfo, err := s.CreateTable(ctx, &btapb.CreateTableRequest{Parent: "cluster", TableId: "t", Table: &newTbl}) if err != nil { return nil, err } count := 3 mcf := func(i int) *btapb.ModifyColumnFamiliesRequest { return &btapb.ModifyColumnFamiliesRequest{ Name: tblInfo.Name, Modifications: []*btapb.ModifyColumnFamiliesRequest_Modification{{ Id: "cf" + strconv.Itoa(i), Mod: &btapb.ModifyColumnFamiliesRequest_Modification_Create{&btapb.ColumnFamily{}}, }}, } } for i := 1; i <= count; i++ { _, err = s.ModifyColumnFamilies(ctx, mcf(i)) if err != nil { return nil, err } } // Populate the table for fc := 0; fc < count; fc++ { for cc := count; cc > 0; cc-- { for tc := 0; tc < count; tc++ { req := &btpb.MutateRowRequest{ TableName: tblInfo.Name, RowKey: []byte("row"), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_SetCell_{&btpb.Mutation_SetCell{ FamilyName: "cf" + strconv.Itoa(fc), ColumnQualifier: []byte("col" + strconv.Itoa(cc)), TimestampMicros: int64((tc + 1) * 1000), Value: []byte{}, }}, }}, } if _, err := s.MutateRow(ctx, req); err != nil { return nil, err } } } } return tblInfo, nil } func TestFilters(t *testing.T) { tests := []struct { in *btpb.RowFilter out int }{ {in: &btpb.RowFilter{Filter: &btpb.RowFilter_BlockAllFilter{true}}, out: 0}, {in: &btpb.RowFilter{Filter: &btpb.RowFilter_BlockAllFilter{false}}, out: 1}, {in: &btpb.RowFilter{Filter: &btpb.RowFilter_PassAllFilter{true}}, out: 1}, {in: &btpb.RowFilter{Filter: &btpb.RowFilter_PassAllFilter{false}}, out: 0}, } ctx := context.Background() s := &server{ tables: make(map[string]*table), } tblInfo, err := populateTable(ctx, s) if err != nil { t.Fatal(err) } req := &btpb.ReadRowsRequest{ TableName: tblInfo.Name, Rows: &btpb.RowSet{RowKeys: [][]byte{[]byte("row")}}, } for _, tc := range tests { req.Filter = tc.in mock := &MockReadRowsServer{} if err = s.ReadRows(req, mock); err != nil { t.Errorf("ReadRows error: %v", err) continue } if len(mock.responses) != tc.out { t.Errorf("Response count: got %d, want %d", len(mock.responses), tc.out) continue } } } func Test_Mutation_DeleteFromColumn(t *testing.T) { ctx := context.Background() s := &server{ tables: make(map[string]*table), } tblInfo, err := populateTable(ctx, s) if err != nil { t.Fatal(err) } tests := []struct { in *btpb.MutateRowRequest fail bool }{ {in: &btpb.MutateRowRequest{ TableName: tblInfo.Name, RowKey: []byte("row"), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_DeleteFromColumn_{DeleteFromColumn: &btpb.Mutation_DeleteFromColumn{ FamilyName: "cf1", ColumnQualifier: []byte("col1"), TimeRange: &btpb.TimestampRange{ StartTimestampMicros: 2000, EndTimestampMicros: 1000, }, }}, }}, }, fail: true, }, {in: &btpb.MutateRowRequest{ TableName: tblInfo.Name, RowKey: []byte("row"), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_DeleteFromColumn_{DeleteFromColumn: &btpb.Mutation_DeleteFromColumn{ FamilyName: "cf2", ColumnQualifier: []byte("col2"), TimeRange: &btpb.TimestampRange{ StartTimestampMicros: 1000, EndTimestampMicros: 2000, }, }}, }}, }, fail: false, }, {in: &btpb.MutateRowRequest{ TableName: tblInfo.Name, RowKey: []byte("row"), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_DeleteFromColumn_{DeleteFromColumn: &btpb.Mutation_DeleteFromColumn{ FamilyName: "cf3", ColumnQualifier: []byte("col3"), TimeRange: &btpb.TimestampRange{ StartTimestampMicros: 1000, EndTimestampMicros: 0, }, }}, }}, }, fail: false, }, {in: &btpb.MutateRowRequest{ TableName: tblInfo.Name, RowKey: []byte("row"), Mutations: []*btpb.Mutation{{ Mutation: &btpb.Mutation_DeleteFromColumn_{DeleteFromColumn: &btpb.Mutation_DeleteFromColumn{ FamilyName: "cf4", ColumnQualifier: []byte("col4"), TimeRange: &btpb.TimestampRange{ StartTimestampMicros: 0, EndTimestampMicros: 1000, }, }}, }}, }, fail: true, }, } for _, tst := range tests { _, err = s.MutateRow(ctx, tst.in) if err != nil && !tst.fail { t.Errorf("expected passed got failure for : %v \n with err: %v", tst.in, err) } if err == nil && tst.fail { t.Errorf("expected failure got passed for : %v", tst) } } } func TestFilterRow(t *testing.T) { row := &row{ key: "row", families: map[string]*family{ "fam": { name: "fam", cells: map[string][]cell{ "col": {{ts: 100, value: []byte("val")}}, }, }, }, } for _, test := range []struct { filter *btpb.RowFilter want bool }{ // The regexp-based filters perform whole-string, case-sensitive matches. {&btpb.RowFilter{Filter: &btpb.RowFilter_RowKeyRegexFilter{[]byte("row")}}, true}, {&btpb.RowFilter{Filter: &btpb.RowFilter_RowKeyRegexFilter{[]byte("ro")}}, false}, {&btpb.RowFilter{Filter: &btpb.RowFilter_RowKeyRegexFilter{[]byte("ROW")}}, false}, {&btpb.RowFilter{Filter: &btpb.RowFilter_RowKeyRegexFilter{[]byte("moo")}}, false}, {&btpb.RowFilter{Filter: &btpb.RowFilter_FamilyNameRegexFilter{"fam"}}, true}, {&btpb.RowFilter{Filter: &btpb.RowFilter_FamilyNameRegexFilter{"f.*"}}, true}, {&btpb.RowFilter{Filter: &btpb.RowFilter_FamilyNameRegexFilter{"[fam]+"}}, true}, {&btpb.RowFilter{Filter: &btpb.RowFilter_FamilyNameRegexFilter{"fa"}}, false}, {&btpb.RowFilter{Filter: &btpb.RowFilter_FamilyNameRegexFilter{"FAM"}}, false}, {&btpb.RowFilter{Filter: &btpb.RowFilter_FamilyNameRegexFilter{"moo"}}, false}, {&btpb.RowFilter{Filter: &btpb.RowFilter_ColumnQualifierRegexFilter{[]byte("col")}}, true}, {&btpb.RowFilter{Filter: &btpb.RowFilter_ColumnQualifierRegexFilter{[]byte("co")}}, false}, {&btpb.RowFilter{Filter: &btpb.RowFilter_ColumnQualifierRegexFilter{[]byte("COL")}}, false}, {&btpb.RowFilter{Filter: &btpb.RowFilter_ColumnQualifierRegexFilter{[]byte("moo")}}, false}, {&btpb.RowFilter{Filter: &btpb.RowFilter_ValueRegexFilter{[]byte("val")}}, true}, {&btpb.RowFilter{Filter: &btpb.RowFilter_ValueRegexFilter{[]byte("va")}}, false}, {&btpb.RowFilter{Filter: &btpb.RowFilter_ValueRegexFilter{[]byte("VAL")}}, false}, {&btpb.RowFilter{Filter: &btpb.RowFilter_ValueRegexFilter{[]byte("moo")}}, false}, } { got, _ := filterRow(test.filter, row.copy()) if got != test.want { t.Errorf("%s: got %t, want %t", proto.CompactTextString(test.filter), got, test.want) } } } func TestFilterRowWithErrors(t *testing.T) { row := &row{ key: "row", families: map[string]*family{ "fam": { name: "fam", cells: map[string][]cell{ "col": {{ts: 100, value: []byte("val")}}, }, }, }, } for _, test := range []struct { badRegex *btpb.RowFilter }{ {&btpb.RowFilter{Filter: &btpb.RowFilter_RowKeyRegexFilter{[]byte("[")}}}, {&btpb.RowFilter{Filter: &btpb.RowFilter_FamilyNameRegexFilter{"["}}}, {&btpb.RowFilter{Filter: &btpb.RowFilter_ColumnQualifierRegexFilter{[]byte("[")}}}, {&btpb.RowFilter{Filter: &btpb.RowFilter_ValueRegexFilter{[]byte("[")}}}, {&btpb.RowFilter{Filter: &btpb.RowFilter_Chain_{ Chain: &btpb.RowFilter_Chain{Filters: []*btpb.RowFilter{ {Filter: &btpb.RowFilter_ValueRegexFilter{[]byte("[")}}}, }, }}}, {&btpb.RowFilter{Filter: &btpb.RowFilter_Condition_{ Condition: &btpb.RowFilter_Condition{ PredicateFilter: &btpb.RowFilter{Filter: &btpb.RowFilter_ValueRegexFilter{[]byte("[")}}, }, }}}, {&btpb.RowFilter{Filter: &btpb.RowFilter_RowSampleFilter{0.0}}}, // 0.0 is invalid. {&btpb.RowFilter{Filter: &btpb.RowFilter_RowSampleFilter{1.0}}}, // 1.0 is invalid. } { got, err := filterRow(test.badRegex, row.copy()) if got != false { t.Errorf("%s: got true, want false", proto.CompactTextString(test.badRegex)) } if err == nil { t.Errorf("%s: got no error, want error", proto.CompactTextString(test.badRegex)) } } } func TestFilterRowWithRowSampleFilter(t *testing.T) { prev := randFloat randFloat = func() float64 { return 0.5 } defer func() { randFloat = prev }() for _, test := range []struct { p float64 want bool }{ {0.1, false}, // Less than random float. Return no rows. {0.5, false}, // Equal to random float. Return no rows. {0.9, true}, // Greater than random float. Return all rows. } { got, err := filterRow(&btpb.RowFilter{Filter: &btpb.RowFilter_RowSampleFilter{test.p}}, &row{}) if err != nil { t.Fatalf("%f: %v", test.p, err) } if got != test.want { t.Errorf("%v: got %t, want %t", test.p, got, test.want) } } } func TestFilterRowWithBinaryColumnQualifier(t *testing.T) { rs := []byte{128, 128} row := &row{ key: string(rs), families: map[string]*family{ "fam": { name: "fam", cells: map[string][]cell{ string(rs): {{ts: 100, value: []byte("val")}}, }, }, }, } for _, test := range []struct { filter []byte want bool }{ {[]byte{128, 128}, true}, // succeeds, exact match {[]byte{128, 129}, false}, // fails {[]byte{128}, false}, // fails, because the regexp must match the entire input {[]byte{128, '*'}, true}, // succeeds: 0 or more 128s {[]byte{'[', 127, 128, ']', '{', '2', '}'}, true}, // succeeds: exactly two of either 127 or 128 } { got, _ := filterRow(&btpb.RowFilter{Filter: &btpb.RowFilter_ColumnQualifierRegexFilter{test.filter}}, row.copy()) if got != test.want { t.Errorf("%v: got %t, want %t", test.filter, got, test.want) } } }