package client import ( "strings" "testing" "time" "github.com/featurebasedb/featurebase/v3/logger" "github.com/featurebasedb/featurebase/v3/test" "github.com/stretchr/testify/assert" ) func TestIngestAPIBatchAdd(t *testing.T) { t.Run("unkeyed", func(t *testing.T) { batch := NewIngestAPIBatch(nil, 10, logger.NopLogger, []*Field{ { name: "a", index: &Index{name: "idxname", options: &IndexOptions{}}, options: &FieldOptions{ fieldType: FieldTypeSet, }, }, { name: "b", index: &Index{name: "idxname", options: &IndexOptions{}}, options: &FieldOptions{ fieldType: FieldTypeSet, keys: true, }, }, { name: "c", index: &Index{name: "idxname", options: &IndexOptions{}}, options: &FieldOptions{ fieldType: FieldTypeTime, keys: true, }, }, }) qt := QuantizedTime{} qt.Set(time.Date(2007, time.January, 1, 15, 0, 0, 0, time.UTC)) err := batch.Add(Row{ ID: uint64(1), Values: []interface{}{uint64(2), "bkey", "ckey"}, Time: qt, }) if err != nil { t.Fatalf("adding row to batch: %v", err) } if batch.records[1]["a"] != uint64(2) { t.Fatalf("unexpected batch.records: %+v", batch.records) } if batch.records[1]["b"] != "bkey" { t.Fatalf("unexpected batch.records: %+v", batch.records) } if batch.records[1]["c"].(map[string]interface{})["time"] != "2007-01-01T15:00:00Z" { t.Fatalf("unexpected batch.records: %+v", batch.records) } if batch.records[1]["c"].(map[string]interface{})["values"] != "ckey" { t.Fatalf("unexpected batch.records: %+v", batch.records) } }) t.Run("keyed", func(t *testing.T) { batch := NewIngestAPIBatch(nil, 10, logger.NopLogger, []*Field{ { name: "a", index: &Index{name: "idxname", options: &IndexOptions{keys: true}}, options: &FieldOptions{ fieldType: FieldTypeSet, }, }, { name: "b", index: &Index{name: "idxname", options: &IndexOptions{keys: true}}, options: &FieldOptions{ fieldType: FieldTypeSet, keys: true, }, }, { name: "c", index: &Index{name: "idxname", options: &IndexOptions{keys: true}}, options: &FieldOptions{ fieldType: FieldTypeTime, keys: true, }, }, }) qt := QuantizedTime{} qt.Set(time.Date(2007, time.January, 1, 15, 0, 0, 0, time.UTC)) err := batch.Add(Row{ ID: "1", Values: []interface{}{uint64(2), "bkey", "ckey"}, Time: qt, }) checkResult := func(batch *ingestAPIBatch, id string, err error) { if err != nil { t.Fatalf("adding row to batch: %v", err) } if batch.recordsK[id]["a"] != uint64(2) { t.Fatalf("unexpected batch.records: %+v", batch.recordsK) } if batch.recordsK[id]["b"] != "bkey" { t.Fatalf("unexpected batch.records: %+v", batch.recordsK) } if batch.recordsK[id]["c"].(map[string]interface{})["time"] != "2007-01-01T15:00:00Z" { t.Fatalf("unexpected batch.records: %+v", batch.recordsK) } if batch.recordsK[id]["c"].(map[string]interface{})["values"] != "ckey" { t.Fatalf("unexpected batch.records: %+v", batch.recordsK) } } checkResult(batch, "1", err) // test wrong type row ID if err := batch.Add(Row{ID: 64.5}); !strings.Contains(err.Error(), "unsupported rowID") { t.Fatalf("unexpected error w/ floating point rowID: %v", err) } // test that byte slice ID works same as string err = batch.Add(Row{ ID: []byte("2"), Values: []interface{}{uint64(2), "bkey", "ckey"}, Time: qt, }) checkResult(batch, "2", err) }) } func TestIngestAPIBatch(t *testing.T) { c := test.MustRunCluster(t, 3) defer c.Close() urls := make([]string, len(c.Nodes)) for i, n := range c.Nodes { urls[i] = n.URL() } // Create a new client for the cluster cli, err := newClientFromAddresses(urls, &ClientOptions{}) if err != nil { t.Fatalf("getting new client: %v", err) } defer cli.Close() _, err = cli.IngestSchema(map[string]interface{}{ "index-name": "test-1", "index-action": "create", "primary-key-type": "uint", "field-action": "create", "fields": []map[string]interface{}{ { "field-name": "astr", "field-type": "string", "field-options": map[string]interface{}{}, }, { "field-name": "bint", "field-type": "int", "field-options": map[string]interface{}{}, }, { "field-name": "cid", "field-type": "id", "field-options": map[string]interface{}{}, }, { "field-name": "dtimestamp", "field-type": "timestamp", "field-options": map[string]interface{}{ "unit": "s", }, }, { "field-name": "etime", "field-type": "string", "field-options": map[string]interface{}{ "time-quantum": "YMD", }, }, { "field-name": "fdecimal", "field-type": "decimal", "field-options": map[string]interface{}{ "scale": 3, }, }, { "field-name": "gbool", "field-type": "bool", "field-options": map[string]interface{}{}, }, }, }) assert.NoError(t, err) schema, err := cli.Schema() if err != nil { t.Fatalf("getting schema: %v", err) } index := schema.Index("test-1") defer cli.DeleteIndex(index) batch := NewIngestAPIBatch(cli, 10, logger.NopLogger, []*Field{ { name: "astr", index: &Index{name: "test-1", options: &IndexOptions{}}, options: &FieldOptions{fieldType: FieldTypeSet, keys: true}, }, { name: "bint", options: &FieldOptions{fieldType: FieldTypeInt}, }, { name: "cid", options: &FieldOptions{fieldType: FieldTypeSet, keys: false}, }, { name: "dtimestamp", options: &FieldOptions{fieldType: FieldTypeTimestamp}, }, { name: "etime", options: &FieldOptions{fieldType: FieldTypeTime, keys: true, timeQuantum: TimeQuantumYearMonthDay}, }, { name: "fdecimal", options: &FieldOptions{fieldType: FieldTypeDecimal, scale: 3}, }, { name: "gbool", options: &FieldOptions{fieldType: FieldTypeBool}, }, }) qt0 := &QuantizedTime{} qt0.Set(time.Date(2010, time.January, 1, 0, 0, 0, 0, time.UTC)) if err := batch.Add(Row{ ID: uint64(7), Values: []interface{}{"a", -2, 9, 1287367623, "e", 1.2345, true}, Time: *qt0, }); err != nil { t.Fatalf("adding row: %v", err) } // test nil value case if err := batch.Add(Row{ ID: uint64(8), Values: []interface{}{nil, nil, nil, nil, nil, nil, nil}, Time: QuantizedTime{}, }); err != nil { t.Fatalf("error adding all nil batch which should affect nothing: %v", err) } if err := batch.Import(); err != nil { t.Fatalf("importing row: %v", err) } if resp, err := cli.Query(NewPQLBaseQuery("Row(astr=a)", &Index{name: "test-1", options: &IndexOptions{}}, nil)); err != nil { t.Fatalf("querying: %v", err) } else if len(resp.Result().Row().Columns) != 1 || resp.Result().Row().Columns[0] != uint64(7) { t.Fatalf("unexpected Row(asr=a) result: %+v", resp.Result().Row().Columns) } if resp, err := cli.Query(NewPQLBaseQuery("Row(bint==-2)", &Index{name: "test-1", options: &IndexOptions{}}, nil)); err != nil { t.Fatalf("querying: %v", err) } else if len(resp.Result().Row().Columns) != 1 || resp.Result().Row().Columns[0] != uint64(7) { t.Fatalf("unexpected Row(bint==-2) result: %+v", resp.Result().Row().Columns) } if resp, err := cli.Query(NewPQLBaseQuery("Row(cid=9)", &Index{name: "test-1", options: &IndexOptions{}}, nil)); err != nil { t.Fatalf("querying: %v", err) } else if len(resp.Result().Row().Columns) != 1 || resp.Result().Row().Columns[0] != uint64(7) { t.Fatalf("unexpected Row(cid=9) result: %+v", resp.Result().Row().Columns) } if resp, err := cli.Query(NewPQLBaseQuery("Row(dtimestamp=='2010-10-18T02:07:03Z')", &Index{name: "test-1", options: &IndexOptions{}}, nil)); err != nil { t.Fatalf("querying: %v", err) } else if len(resp.Result().Row().Columns) != 1 || resp.Result().Row().Columns[0] != uint64(7) { t.Fatalf("unexpected Row(dtimestamp=='2010-10-18T02:07:03Z') result: %+v", resp.Result().Row().Columns) } if resp, err := cli.Query(NewPQLBaseQuery("Row(etime=e, from='2010-01-01', to='2010-01-02')", &Index{name: "test-1", options: &IndexOptions{}}, nil)); err != nil { t.Fatalf("querying: %v", err) } else if len(resp.Result().Row().Columns) != 1 || resp.Result().Row().Columns[0] != uint64(7) { t.Fatalf("unexpected Row(etime=e, from='2010-01-01', to='2010-01-02') result: %+v", resp.Result().Row().Columns) } if resp, err := cli.Query(NewPQLBaseQuery("Row(fdecimal==1.234)", &Index{name: "test-1", options: &IndexOptions{}}, nil)); err != nil { t.Fatalf("querying: %v", err) } else if len(resp.Result().Row().Columns) != 1 || resp.Result().Row().Columns[0] != uint64(7) { t.Fatalf("unexpected Row(fdecimal==1.234) result: %+v", resp.Result().Row().Columns) } if resp, err := cli.Query(NewPQLBaseQuery("Row(gbool=true)", &Index{name: "test-1", options: &IndexOptions{}}, nil)); err != nil { t.Fatalf("querying: %v", err) } else if len(resp.Result().Row().Columns) != 1 || resp.Result().Row().Columns[0] != uint64(7) { t.Fatalf("unexpected Row(gbool=true) result: %+v", resp.Result().Row().Columns) } }