refactor testing to share clusters more often

When doing tests, we create a ton of one-off clusters. This
turns out to be expensive and slow. Fixing it is surprisingly hard.

Fundamentally: If we're sharing clusters, we need to use different
indexes for each test, to avoid clashes. This changes index names.
As a side-effect, this reorders many partition-based things, like
the order keys are returned in. Thus, to fix this, we change a lot
of tests to no longer depend on the *order* in which strings are
returned.

Having done that, we can also discard the ModHasher behavior, since
that only existed to allow us to reliably predict partitioning.

The basic design is as follows: Instead of a cluster being a
[]*Command, a "shareable" cluster is now a []*Command plus some
flags, and a "cluster" is a pointer to a possibly-shared cluster,
plus a link to the specific test using this specific cluster,
and correspondingly, its test name suitably coerced to be a valid
index name prefix.

The "test.Cluster" object now has methods to allow retrieving an
index name, and also implemnts fmt.Formatter to let you use,
e.g., `%i` with it in Sprintf to get "the index name, plus an i".
(This works for everything but %p and %T.)

This allows us to consistently rework all the many things that
use index names in a persistent way.

We also have `MustUnshared` and `MustRunUnsharedCluster` methods
which allow us to specify that a given test needs its own cluster
for some reason. For instance, the tests that want to run backups
need their own isolated cluster, and the tests that want to close
or reopen nodes need their own cluster because a reopened cluster
won't have working GRPC for some reason.

On "closing" a shared cluster (actually the test-specific wrapper
that reflects a given sharing), we delete any indexes starting with
that test's index name prefix. Otherwise, the huge pile of open
indexes prevents `go test -race` from working on MacOS, where we
run out of address space too quickly.

This is fairly enormous but most of the individual changes are
fairly trivial things like replacing the string "i" with "c.Idx()".

We also tweaked a test that failed for me a couple of times to
not depend on sort order.
This commit is contained in:
Seebs 2022-08-08 15:50:44 -05:00 committed by Kasey Rodgers
parent 799e70fe46
commit f6d17b1b58
27 changed files with 1909 additions and 1648 deletions

View file

@ -23,48 +23,25 @@ import (
"time"
"github.com/golang-jwt/jwt"
pilosa "github.com/featurebasedb/featurebase/v3"
"github.com/featurebasedb/featurebase/v3/authn"
"github.com/featurebasedb/featurebase/v3/boltdb"
"github.com/featurebasedb/featurebase/v3/roaring"
"github.com/featurebasedb/featurebase/v3/server"
"github.com/featurebasedb/featurebase/v3/shardwidth"
"github.com/featurebasedb/featurebase/v3/test"
. "github.com/featurebasedb/featurebase/v3/vprint" // nolint:staticcheck
pilosa "github.com/molecula/featurebase/v3"
"github.com/molecula/featurebase/v3/authn"
"github.com/molecula/featurebase/v3/roaring"
"github.com/molecula/featurebase/v3/server"
"github.com/molecula/featurebase/v3/shardwidth"
"github.com/molecula/featurebase/v3/test"
. "github.com/molecula/featurebase/v3/vprint" // nolint:staticcheck
"golang.org/x/sync/errgroup"
)
func TestAPI_Import(t *testing.T) {
c := test.MustRunCluster(t, 3,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node1"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node2"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
)
c := test.MustRunCluster(t, 3)
defer c.Close()
m0 := c.GetNode(0)
m1 := c.GetNode(1)
indexNames := map[bool]string{false: "i", true: "ki"}
indexNames := map[bool]string{false: c.Idx("u"), true: c.Idx("k")}
fieldNames := map[bool]string{false: "f", true: "kf"}
ctx := context.Background()
@ -104,7 +81,7 @@ func TestAPI_Import(t *testing.T) {
t.Run("RowIDColumnKey", func(t *testing.T) {
// Import data with keys to the primary and verify that it gets
// translated and forwarded to the owner of shard 0 (node1; because of offsetModHasher)
// translated and forwarded to the owner of shard 0
req := &pilosa.ImportRequest{
Index: indexNames[true],
Field: fieldNames[false],
@ -152,7 +129,7 @@ func TestAPI_Import(t *testing.T) {
}
})
t.Run("ExpectedErrors", func(t *testing.T) {
t.Skip() // skipping due to change partitioning strategy
t.Skip("partitioning strategy changed, test not supported") // skipping due to change partitioning strategy
ctx := context.Background()
for ik, indexName := range indexNames {
for fk, fieldName := range fieldNames {
@ -224,26 +201,7 @@ func TestAPI_Import(t *testing.T) {
}
func TestAPI_ImportValue(t *testing.T) {
c := test.MustRunCluster(t, 3,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node1"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node2"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
)
c := test.MustRunCluster(t, 3)
defer c.Close()
coord := c.GetPrimary()
@ -253,7 +211,7 @@ func TestAPI_ImportValue(t *testing.T) {
t.Run("ValColumnKey", func(t *testing.T) {
ctx := context.Background()
index := "valck"
index := c.Idx("valck")
field := "f"
_, err := coord.API.CreateIndex(ctx, index, pilosa.IndexOptions{Keys: true})
@ -275,7 +233,7 @@ func TestAPI_ImportValue(t *testing.T) {
colKeys := []string{"col10", "col8", "col9", "col6", "col7", "col4", "col5", "col2", "col3", "col1"}
// Import data with keys to the primary and verify that it gets
// translated and forwarded to the owner of shard 0 (node1; because of offsetModHasher)
// translated and forwarded to the owner of shard 0
req := &pilosa.ImportValueRequest{
Index: index,
Field: field,
@ -293,18 +251,24 @@ func TestAPI_ImportValue(t *testing.T) {
pql := fmt.Sprintf("Row(%s>0)", field)
// Query node0.
if res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: pql}); err != nil {
res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: pql})
if err != nil {
t.Fatal(err)
} else if keys := res.Results[0].(*pilosa.Row).Keys; !reflect.DeepEqual(keys, colKeys) {
}
keys := res.Results[0].(*pilosa.Row).Keys
if !sameStringSlice(keys, colKeys) {
t.Fatalf("unexpected column keys: %+v", keys)
}
// Query node1.
if err := test.RetryUntil(5*time.Second, func() error {
if res, err := m1.API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: pql}); err != nil {
return err
} else if keys := res.Results[0].(*pilosa.Row).Keys; !reflect.DeepEqual(keys, colKeys) {
return fmt.Errorf("unexpected column keys: %+v", keys)
res, err := m1.API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: pql})
if err != nil {
t.Fatal(err)
}
keys := res.Results[0].(*pilosa.Row).Keys
if !sameStringSlice(keys, colKeys) {
t.Fatalf("unexpected column keys: %+v", keys)
}
return nil
}); err != nil {
@ -314,7 +278,7 @@ func TestAPI_ImportValue(t *testing.T) {
t.Run("ValIntEmpty", func(t *testing.T) {
ctx := context.Background()
index := "valintempty"
index := c.Idx("valintempty")
field := "fld"
createIndexForTest(index, coord, t)
createFieldForTest(index, field, coord, t)
@ -387,7 +351,7 @@ func TestAPI_ImportValue(t *testing.T) {
colIDs = append(colIDs, uint64(i))
}
// Import data with keys to node1 and verify that it gets translated and
// forwarded to the owner of shard 0 (node0; because of offsetModHasher)
// forwarded to the owner of shard 0
req := &pilosa.ImportValueRequest{
Index: index,
Field: field,
@ -410,7 +374,7 @@ func TestAPI_ImportValue(t *testing.T) {
t.Run("ValDecimalFieldNegativeScale", func(t *testing.T) {
ctx := context.Background()
index := "valdecneg"
index := c.Idx("valdecneg")
field := "fdecneg"
_, err := m0.API.CreateIndex(ctx, index, pilosa.IndexOptions{})
@ -424,9 +388,9 @@ func TestAPI_ImportValue(t *testing.T) {
})
t.Run("ValTimestampField", func(t *testing.T) {
t.Skip() // skipping due to change partitioning strategy
t.Skip("partition strategy change invalidated") // skipping due to change partitioning strategy
ctx := context.Background()
index := "valts"
index := c.Idx("valts")
field := "fts"
_, err := m1.API.CreateIndex(ctx, index, pilosa.IndexOptions{})
@ -447,7 +411,7 @@ func TestAPI_ImportValue(t *testing.T) {
}
// Import data with keys to node1 and verify that it gets translated and
// forwarded to the owner of shard 0 (node0; because of offsetModHasher)
// forwarded to the owner of shard 0
req := &pilosa.ImportValueRequest{
Index: index,
Field: field,
@ -472,9 +436,9 @@ func TestAPI_ImportValue(t *testing.T) {
})
t.Run("ValStringField", func(t *testing.T) {
t.Skip() // skipping due to change partitioning strategy
t.Skip("partition strategy change invalidated") // skipping due to change partitioning strategy
ctx := context.Background()
index := "valstr"
index := c.Idx("valstr")
field := "fstr"
fgnIndex := "fgnvalstr"
@ -506,8 +470,7 @@ func TestAPI_ImportValue(t *testing.T) {
}
// Import data with keys to the node0 and verify that it gets translated
// and forwarded to the owner of shard 0 (node1; because of
// offsetModHasher)
// and forwarded to the owner of shard 0
req := &pilosa.ImportValueRequest{
Index: index,
Field: field,
@ -534,17 +497,10 @@ func TestAPI_ImportValue(t *testing.T) {
func TestAPI_Ingest(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
c := test.MustRunCluster(t, 1,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
)
c := test.MustRunCluster(t, 1)
defer c.Close()
coord := c.GetPrimary()
index := "ingest"
index := c.Idx()
setField := "set"
timeField := "tq"
intField := "int"
@ -654,7 +610,7 @@ func TestAPI_Ingest(t *testing.T) {
},
},
}
if err := coord.API.ImportRoaringShard(context.Background(), "ingest", 8, request); err != nil {
if err := coord.API.ImportRoaringShard(context.Background(), c.Idx(), 8, request); err != nil {
t.Fatalf("ingesting: %v", err)
}
@ -666,19 +622,19 @@ func TestAPI_Ingest(t *testing.T) {
return res
}
res := mustQuery(t, "ingest", "Row(set=0)")
res := mustQuery(t, c.Idx(), "Row(set=0)")
r := res.Results[0].(*pilosa.Row).Columns()
if len(r) != 1 || r[0] != pilosa.ShardWidth*8+7 {
t.Fatalf("expected row with pilosa.ShardWidth*8+7 set, got %d", r)
}
res = mustQuery(t, "ingest", "Row(set=1)")
res = mustQuery(t, c.Idx(), "Row(set=1)")
r = res.Results[0].(*pilosa.Row).Columns()
if len(r) != 1 || r[0] != pilosa.ShardWidth*8+7 {
t.Fatalf("expected row with pilosa.ShardWidth*8+7 set, got %d", r)
}
res = mustQuery(t, "ingest", "Row(int==1)")
res = mustQuery(t, c.Idx(), "Row(int==1)")
r = res.Results[0].(*pilosa.Row).Columns()
if len(r) != 1 || r[0] != pilosa.ShardWidth*8+7 {
t.Fatalf("expected row with, pilosa.ShardWidth*8+7 set, got %d", r)
@ -699,23 +655,23 @@ func TestAPI_Ingest(t *testing.T) {
},
},
}
if err := coord.API.ImportRoaringShard(context.Background(), "ingest", 8, request); err != nil {
if err := coord.API.ImportRoaringShard(context.Background(), c.Idx(), 8, request); err != nil {
t.Fatalf("ingesting: %v", err)
}
res = mustQuery(t, "ingest", "Row(set=0)")
res = mustQuery(t, c.Idx(), "Row(set=0)")
r = res.Results[0].(*pilosa.Row).Columns()
if len(r) != 0 {
t.Fatalf("expected no values after clearing, got: %v", r)
}
res = mustQuery(t, "ingest", "Row(set=1)")
res = mustQuery(t, c.Idx(), "Row(set=1)")
r = res.Results[0].(*pilosa.Row).Columns()
if len(r) != 0 {
t.Fatalf("expected no values after clearing, got: %v", r)
}
res = mustQuery(t, "ingest", "Row(int==1)")
res = mustQuery(t, c.Idx(), "Row(int==1)")
r = res.Results[0].(*pilosa.Row).Columns()
if len(r) != 0 {
t.Fatalf("expected no values after clearing, got: %v", r)
@ -747,14 +703,7 @@ func BenchmarkIngest(b *testing.B) {
data := ingestBenchmarkHelper()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
c := test.MustRunCluster(b, 1,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
)
c := test.MustRunCluster(b, 1)
defer c.Close()
coord := c.GetPrimary()
@ -762,7 +711,7 @@ func BenchmarkIngest(b *testing.B) {
// m1 := c.GetNode(1)
// m2 := c.GetNode(2)
index := "ingest"
index := c.Idx()
setField := "set"
intField := "int"
tqField := "tq"
@ -798,24 +747,8 @@ func BenchmarkIngest(b *testing.B) {
}
}
// offsetModHasher represents a simple, mod-based hashing offset by 1.
type offsetModHasher struct{}
func (*offsetModHasher) Hash(key uint64, n int) int {
return int(key+1) % n
}
func (*offsetModHasher) Name() string { return "mod" }
func TestAPI_ClearFlagForImportAndImportValues(t *testing.T) {
c := test.MustRunCluster(t, 1,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
)
c := test.MustRunCluster(t, 1)
defer c.Close()
// plan:
@ -828,7 +761,7 @@ func TestAPI_ClearFlagForImportAndImportValues(t *testing.T) {
m0api := m0.API
ctx := context.Background()
index := "i"
index := c.Idx()
fieldAcct0 := "acct0"
opts := pilosa.OptFieldTypeInt(-1000, 1000)
@ -1072,7 +1005,9 @@ type mutexCheckField struct {
}
func TestAPI_MutexCheck(t *testing.T) {
c := test.MustNewCluster(t, 3)
// Can't share this one, because it has to get custom option settings and needs
// replication.
c := test.MustUnsharedCluster(t, 3)
for _, c := range c.Nodes {
c.Config.Cluster.ReplicaN = 2
}
@ -1094,7 +1029,7 @@ func TestAPI_MutexCheck(t *testing.T) {
ctx := context.Background()
for _, keyedIndex := range []bool{false, true} {
indexName := fmt.Sprintf("i%t", keyedIndex)
indexName := c.Idx(map[bool]string{false: "u", true: "k"}[keyedIndex])
index, err := m0.API.CreateIndex(ctx, indexName, pilosa.IndexOptions{Keys: keyedIndex, TrackExistence: true})
if err != nil {
t.Fatalf("creating index: %v", err)
@ -1446,14 +1381,14 @@ func createFieldForTest(index string, field string, coord *test.Command, t *test
func TestVariousApiTranslateCalls(t *testing.T) {
for i := 1; i < 8; i += 3 {
m := test.MustRunCluster(t, i)
defer m.Close()
node := m.GetNode(0)
c := test.MustRunCluster(t, i)
defer c.Close()
node := c.GetNode(0)
api := node.API
// this should never actually get used because we're testing for errors here
r := strings.NewReader("")
// test index
idx, err := api.Holder().CreateIndex("index", pilosa.IndexOptions{})
idx, err := api.Holder().CreateIndex(c.Idx(), pilosa.IndexOptions{})
if err != nil {
t.Fatalf("%v: could not create test index", err)
}
@ -1471,8 +1406,8 @@ func TestVariousApiTranslateCalls(t *testing.T) {
t.Run("translateIndexDbOnNilTranslateStore",
func(t *testing.T) {
err := api.TranslateIndexDB(context.Background(), "index", 0, r)
expected := fmt.Errorf("index %q has no translate store", "index")
err := api.TranslateIndexDB(context.Background(), c.Idx(), 0, r)
expected := fmt.Errorf("index %q has no translate store", c.Idx())
if !reflect.DeepEqual(err, expected) {
t.Fatalf("expected '%#v', got '%#v'", expected, err)
}
@ -1489,8 +1424,8 @@ func TestVariousApiTranslateCalls(t *testing.T) {
t.Run("translateFieldDbOnNilField",
func(t *testing.T) {
err := api.TranslateFieldDB(context.Background(), "index", "nonExistentField", r)
expected := fmt.Errorf("field %q/%q not found", "index", "nonExistentField")
err := api.TranslateFieldDB(context.Background(), c.Idx(), "nonExistentField", r)
expected := fmt.Errorf("field %q/%q not found", c.Idx(), "nonExistentField")
if !reflect.DeepEqual(err, expected) {
t.Fatalf("expected '%#v', got '%#v'", expected, err)
}
@ -1498,7 +1433,7 @@ func TestVariousApiTranslateCalls(t *testing.T) {
t.Run("translateFieldDbNilField_keys",
func(t *testing.T) {
err := api.TranslateFieldDB(context.Background(), "index", "_keys", r)
err := api.TranslateFieldDB(context.Background(), c.Idx(), "_keys", r)
if err != nil {
t.Fatalf("expected 'nil', got '%#v'", err)
}
@ -1508,8 +1443,8 @@ func TestVariousApiTranslateCalls(t *testing.T) {
stores, which is a bug, but one that we will eventually fix. when we do, this
test might come in handy t.Run("translateFieldDbOnNilTranslateStore",
func(t *testing.T) {
err := api.TranslateFieldDB(context.Background(), "index", "field", r)
expected := fmt.Errorf("field %q/%q has no translate store", "index", "field")
err := api.TranslateFieldDB(context.Background(), c.Idx(), "field", r)
expected := fmt.Errorf("field %q/%q has no translate store", c.Idx(), "field")
if !reflect.DeepEqual(err, expected) {
t.Fatalf("expected '%#v', got '%#v'", expected, err)
}
@ -1529,7 +1464,7 @@ func TestAPI_CreateField(t *testing.T) {
nodes[i] = c.GetNode(i)
}
if _, err := nodes[0].API.CreateIndex(ctx, "i", pilosa.IndexOptions{}); err != nil {
if _, err := nodes[0].API.CreateIndex(ctx, c.Idx(), pilosa.IndexOptions{}); err != nil {
t.Fatal(err)
}
eg, ctx := errgroup.WithContext(context.Background())
@ -1537,7 +1472,7 @@ func TestAPI_CreateField(t *testing.T) {
node := n
eg.Go(func() error {
for i := 0; i < 10; i++ {
_, err := node.API.CreateField(ctx, "i", fmt.Sprintf("f%d", i))
_, err := node.API.CreateField(ctx, c.Idx(), fmt.Sprintf("f%d", i))
if err != nil && !errors.Is(err, pilosa.ErrFieldExists) {
return err
}
@ -1557,19 +1492,12 @@ func TestAPI_CreateField(t *testing.T) {
func TestAPI_RBFDebugInfo(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
c := test.MustRunCluster(t, 1,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
)
c := test.MustRunCluster(t, 1)
defer c.Close()
coord := c.GetPrimary()
if _, err := coord.API.CreateIndex(ctx, "i", pilosa.IndexOptions{}); err != nil {
if _, err := coord.API.CreateIndex(ctx, c.Idx(), pilosa.IndexOptions{}); err != nil {
t.Fatal(err)
} else if infos := coord.API.RBFDebugInfo(); infos == nil {
t.Fatal("expected info")
@ -1699,25 +1627,22 @@ f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA==
config.TLS.CertificateKeyPath = writeTestFile(t, "certKey.pem", localhostKey)
config.TLS.CertificatePath = writeTestFile(t, "cert.pem", localhostCert)
c := test.MustRunCluster(t, 3,
c := test.MustRunUnsharedCluster(t, 3,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerClusterHasher(&test.ModHasher{}),
),
server.OptCommandConfig(config),
},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node1"),
pilosa.OptServerClusterHasher(&test.ModHasher{}),
),
server.OptCommandConfig(config),
},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node2"),
pilosa.OptServerClusterHasher(&test.ModHasher{}),
),
server.OptCommandConfig(config),
},
@ -1727,6 +1652,8 @@ f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA==
primaryAPI := c.GetPrimary().API
// needs internal/cluster/message
// Note: This indexName wouldn't be safe on a shared cluster, but we have to use an
// unshared cluster to set up the auth config anyway.
indexName := "test"
_, err := primaryAPI.CreateIndex(adminCtx, indexName, pilosa.IndexOptions{})
if err != nil {

View file

@ -71,7 +71,7 @@ func TestClientAgainstCluster(t *testing.T) {
t.Run(testName, func(t *testing.T) {
// Start size.replicaN cluster
c := test.MustNewCluster(t, size)
c := test.MustUnsharedCluster(t, size)
for _, n := range c.Nodes {
n.Config.Cluster.ReplicaN = replicaN
}

View file

@ -10,11 +10,9 @@ import (
"testing/quick"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/featurebasedb/featurebase/v3/disco"
pnet "github.com/featurebasedb/featurebase/v3/net"
"github.com/featurebasedb/featurebase/v3/roaring"
_ "github.com/featurebasedb/featurebase/v3/vprint"
"github.com/molecula/featurebase/v3/disco"
pnet "github.com/molecula/featurebase/v3/net"
"github.com/molecula/featurebase/v3/roaring"
)
// Ensure the cluster can fairly distribute partitions across the nodes.
@ -25,7 +23,7 @@ func TestCluster_Owners(t *testing.T) {
{URI: NewTestURIFromHostPort("serverB", 1000)},
{URI: NewTestURIFromHostPort("serverC", 1000)},
}),
Hasher: NewTestModHasher(),
Hasher: &disco.Jmphasher{},
ReplicaN: 2,
}
@ -34,14 +32,24 @@ func TestCluster_Owners(t *testing.T) {
// Create a snapshot of the cluster to use for node/partition calculations.
snap := c.NewSnapshot()
// Verify nodes are distributed.
if a := snap.PartitionNodes(0); !reflect.DeepEqual(a, []*disco.Node{cNodes[0], cNodes[1]}) {
t.Fatalf("unexpected owners: %s", spew.Sdump(a))
assigned := make(map[int]int)
for i := 0; i < 256; i++ {
nodes := snap.PartitionNodes(i)
for _, node := range nodes {
for j, n := range cNodes {
if n == node {
assigned[j]++
}
}
}
}
// Verify nodes go around the ring.
if a := snap.PartitionNodes(2); !reflect.DeepEqual(a, []*disco.Node{cNodes[2], cNodes[0]}) {
t.Fatalf("unexpected owners: %s", spew.Sdump(a))
expected := float64((256.0 * 2) / 3) // each partition is on two nodes, there's three nodes
for k, v := range assigned {
ratio := float64(v) / expected
// Empirically, we expect 167/171/174
if ratio < 0.97 || ratio > 1.03 {
t.Fatalf("node %d has %d assigned partitions, expected about %.1f", k, v, expected)
}
}
}
@ -98,10 +106,18 @@ func TestCluster_ContainsShards(t *testing.T) {
// Create a snapshot of the cluster to use for node/partition calculations.
snap := c.NewSnapshot()
shards := snap.ContainsShards("test", roaring.NewBitmap(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10), cNodes[2])
if !reflect.DeepEqual(shards, []uint64{0, 2, 3, 5, 6, 9, 10}) {
t.Fatalf("unexpected shars for node's index: %v", shards)
availableShards := roaring.NewBitmap(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
nodeCounts := make(map[uint64]int)
for _, n := range cNodes {
shards := snap.ContainsShards("test", availableShards, n)
for _, shard := range shards {
nodeCounts[shard]++
}
}
for shard, count := range nodeCounts {
if count != 3 {
t.Fatalf("shard %d on %d nodes, expected 3", shard, count)
}
}
}

View file

@ -217,18 +217,18 @@ func TestImportCommand_RunKeys(t *testing.T) {
cmd := cluster.GetNode(0)
cm.Host = cmd.API.Node().URI.HostPort()
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i", strings.NewReader(`{"options":{"keys": true}}`)))
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", fmt.Sprintf("http://%s/index/%i", cm.Host, cluster), strings.NewReader(`{"options":{"keys": true}}`)))
if err != nil {
t.Fatalf("posting request: %v", err)
}
resp.Body.Close()
resp, err = http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i/field/f", strings.NewReader(`{"options":{"keys": true}}`)))
resp, err = http.DefaultClient.Do(MustNewHTTPRequest("POST", fmt.Sprintf("http://%s/index/%i/field/f", cm.Host, cluster), strings.NewReader(`{"options":{"keys": true}}`)))
if err != nil {
t.Fatalf("posting request: %v", err)
}
resp.Body.Close()
cm.Index = "i"
cm.Index = cluster.Idx("i")
cm.Field = "f"
cm.Paths = []string{file.Name()}
err = cm.Run(ctx)
@ -272,18 +272,18 @@ func TestImportCommand_KeyReplication(t *testing.T) {
cm.Host = host0
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i", strings.NewReader(`{"options":{"keys": true}}`)))
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", fmt.Sprintf("http://%s/index/%i", cm.Host, c), strings.NewReader(`{"options":{"keys": true}}`)))
if err != nil {
t.Fatalf("posting request: %v", err)
}
resp.Body.Close()
resp, err = http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i/field/f", strings.NewReader(`{"options":{"keys": true}}`)))
resp, err = http.DefaultClient.Do(MustNewHTTPRequest("POST", fmt.Sprintf("http://%s/index/%i/field/f", cm.Host, c), strings.NewReader(`{"options":{"keys": true}}`)))
if err != nil {
t.Fatalf("posting request: %v", err)
}
resp.Body.Close()
cm.Index = "i"
cm.Index = c.Idx("i")
cm.Field = "f"
cm.Paths = []string{file.Name()}
err = cm.Run(ctx)
@ -295,7 +295,7 @@ func TestImportCommand_KeyReplication(t *testing.T) {
for _, host := range []string{host0, host1} {
if err := test.RetryUntil(2*time.Second, func() error {
qry := "Count(Row(f=foo0))"
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+host+"/index/i/query", strings.NewReader(qry)))
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", fmt.Sprintf("http://%s/index/%i/query", host, c), strings.NewReader(qry)))
if err != nil {
return fmt.Errorf("Querying data for validation: %s", err)
}
@ -335,18 +335,18 @@ func TestImportCommand_RunValueKeys(t *testing.T) {
cmd := cluster.GetNode(0)
cm.Host = cmd.API.Node().URI.HostPort()
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i", strings.NewReader(`{"options":{"keys": true}}`)))
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", fmt.Sprintf("http://%s/index/%i", cm.Host, cluster), strings.NewReader(`{"options":{"keys": true}}`)))
if err != nil {
t.Fatalf("posting request: %v", err)
}
resp.Body.Close()
resp, err = http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i/field/f", strings.NewReader(`{"options":{"type": "int", "min": 0, "max": 100}}`)))
resp, err = http.DefaultClient.Do(MustNewHTTPRequest("POST", fmt.Sprintf("http://%s/index/%i/field/f", cm.Host, cluster), strings.NewReader(`{"options":{"type": "int", "min": 0, "max": 100}}`)))
if err != nil {
t.Fatalf("posting request: %v", err)
}
resp.Body.Close()
cm.Index = "i"
cm.Index = cluster.Idx("i")
cm.Field = "f"
cm.Paths = []string{file.Name()}
err = cm.Run(ctx)
@ -463,18 +463,18 @@ func TestImportCommand_BugOverwriteValue(t *testing.T) {
cm.Host = cmd.API.Node().URI.HostPort()
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i", strings.NewReader("")))
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", fmt.Sprintf("http://%s/index/%i", cm.Host, cluster), strings.NewReader("")))
if err != nil {
t.Fatalf("posting request: %v", err)
}
resp.Body.Close()
resp, err = http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i/field/f", strings.NewReader(`{"options":{"type": "int", "min": 0, "max":2147483648 }}`)))
resp, err = http.DefaultClient.Do(MustNewHTTPRequest("POST", fmt.Sprintf("http://%s/index/%i/field/f", cm.Host, cluster), strings.NewReader(`{"options":{"type": "int", "min": 0, "max":2147483648 }}`)))
if err != nil {
t.Fatalf("posting request: %v", err)
}
resp.Body.Close()
cm.Index = "i"
cm.Index = cluster.Idx("i")
cm.Field = "f"
cm.Paths = []string{file.Name()}
err = cm.Run(ctx)
@ -525,18 +525,18 @@ func TestImportCommand_RunBool(t *testing.T) {
cmd := cluster.GetNode(0)
cm.Host = cmd.API.Node().URI.HostPort()
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i", strings.NewReader("")))
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", fmt.Sprintf("http://%s/index/%i", cm.Host, cluster), strings.NewReader("")))
if err != nil {
t.Fatalf("posting request: %v", err)
}
resp.Body.Close()
resp, err = http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i/field/f", strings.NewReader(`{"options":{"type": "bool"}}`)))
resp, err = http.DefaultClient.Do(MustNewHTTPRequest("POST", fmt.Sprintf("http://%s/index/%i/field/f", cm.Host, cluster), strings.NewReader(`{"options":{"type": "bool"}}`)))
if err != nil {
t.Fatalf("posting request: %v", err)
}
resp.Body.Close()
cm.Index = "i"
cm.Index = cluster.Idx("i")
cm.Field = "f"
t.Run("Valid", func(t *testing.T) {
@ -711,6 +711,7 @@ func TestImport_AuthOn(t *testing.T) {
t.Fatal(err)
}
// note: cluster isn't shared because of custom opts
cluster := test.MustRunCluster(t, clusterSize, commandOpts...)
defer cluster.Close()
cmd := cluster.GetNode(0)

View file

@ -5,34 +5,23 @@ package pilosa_test
import (
"context"
"fmt"
"reflect"
"testing"
pilosa "github.com/featurebasedb/featurebase/v3"
"github.com/featurebasedb/featurebase/v3/boltdb"
"github.com/featurebasedb/featurebase/v3/server"
"github.com/featurebasedb/featurebase/v3/test"
. "github.com/featurebasedb/featurebase/v3/vprint" // nolint:staticcheck
pilosa "github.com/molecula/featurebase/v3"
"github.com/molecula/featurebase/v3/test"
. "github.com/molecula/featurebase/v3/vprint" // nolint:staticcheck
)
func TestAPI_SimplerOneNode_ImportColumnKey(t *testing.T) {
c := test.MustRunCluster(t, 1,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
)
c := test.MustRunCluster(t, 1)
defer c.Close()
m0 := c.GetNode(0)
t.Run("RowIDColumnKey", func(t *testing.T) {
ctx := context.Background()
indexName := "rick"
indexName := c.Idx()
fieldName := "f"
index, err := m0.API.CreateIndex(ctx, indexName, pilosa.IndexOptions{Keys: true, TrackExistence: true})
@ -66,7 +55,7 @@ func TestAPI_SimplerOneNode_ImportColumnKey(t *testing.T) {
colKeys := []string{"col10", "col8", "col9", "col6", "col7", "col4", "col5", "col2", "col3", "col1"}
// Import data with keys to the primary and verify that it gets
// translated and forwarded to the owner of shard 0 (node1; because of offsetModHasher)
// translated and forwarded to the owner of shard 0
req := &pilosa.ImportRequest{
Index: indexName,
IndexCreatedAt: index.CreatedAt(),
@ -89,9 +78,12 @@ func TestAPI_SimplerOneNode_ImportColumnKey(t *testing.T) {
pql := fmt.Sprintf("Row(%s=%d)", fieldName, rowID)
// Query node0.
if res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: indexName, Query: pql}); err != nil {
res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: indexName, Query: pql})
if err != nil {
t.Fatal(err)
} else if keys := res.Results[0].(*pilosa.Row).Keys; !reflect.DeepEqual(keys, colKeys) {
}
keys := res.Results[0].(*pilosa.Row).Keys
if !sameStringSlice(keys, colKeys) {
t.Fatalf("unexpected column keys: %#v", keys)
}
})

View file

@ -6,6 +6,7 @@ import (
"context"
"fmt"
"math"
"sort"
"testing"
"time"
@ -16,7 +17,9 @@ import (
)
func TestExecutor_DeleteRecords(t *testing.T) {
indexName := "i"
c := test.MustRunCluster(t, 1)
indexName := c.Idx()
defer c.Close()
setup := func(t *testing.T, r *require.Assertions, c *test.Cluster) {
t.Helper()
c.CreateField(t, indexName, pilosa.IndexOptions{TrackExistence: true}, "setfield")
@ -111,13 +114,6 @@ func TestExecutor_DeleteRecords(t *testing.T) {
}
require := require.New(t)
t.Run("DeleteRecords", func(t *testing.T) {
c := test.MustNewCluster(t, 1)
for _, n := range c.Nodes {
n.Config.Cluster.ReplicaN = 1
}
err := c.Start()
require.NoError(err, "Start cluster DeleteRecords")
defer c.Close()
t.Run("Delete", func(t *testing.T) {
setup(t, require, c)
@ -144,7 +140,9 @@ func TestExecutor_DeleteRecords(t *testing.T) {
resp := c.Query(t, indexName, `Extract(All())`)
m := resp.Results[0].(pilosa.ExtractedTable)
before := convertKey(m.Columns)
require.Equal([]string{"one", "A", "B", "C", "D", "two"}, before, "these keyed records before")
sort.Strings(before)
expected := []string{"A", "B", "C", "D", "one", "two"}
require.Equal(expected, before, "these keyed records before")
resp = c.Query(t, indexName, `Delete(ConstRow(columns=["A","one"]))`)
require.NotEmpty(resp.Results)
require.Equal(true, resp.Results[0], "Change should have happened")
@ -152,6 +150,7 @@ func TestExecutor_DeleteRecords(t *testing.T) {
resp = c.Query(t, indexName, `Extract(All())`)
m = resp.Results[0].(pilosa.ExtractedTable)
after := convertKey(m.Columns)
sort.Strings(after)
require.Equal([]string{"B", "C", "D", "two"}, after, "these keyed records after delete")
//validate that column keys got deleted
node := c.GetNode(0)
@ -240,13 +239,9 @@ func TestExecutor_DeleteRecords(t *testing.T) {
})
})
t.Run("DeleteRecordsBigWithRestart", func(t *testing.T) {
c := test.MustNewCluster(t, 1)
for _, n := range c.Nodes {
n.Config.Cluster.ReplicaN = 1
}
err := c.Start()
// restarting doesn't work correctly for a shared cluster
c := test.MustRunUnsharedCluster(t, 1)
defer c.Close()
require.NoError(err, "Start cluster DeleteRecordsBig")
setupBig(t, require, c, 16)
defer tearDown(t, require, c)
node := c.GetNode(0)
@ -258,7 +253,7 @@ func TestExecutor_DeleteRecords(t *testing.T) {
require.NotNil(resp, "Response should not be nil")
require.NotEmpty(resp.Results)
require.Equal(uint64(0), resp.Results[0], "Should have removed")
err = node.Reopen()
err := node.Reopen()
require.NoError(err, "restart cluster DeleteRecordsBig")
err = c.AwaitState(disco.ClusterStateNormal, 10*time.Second)
require.NoError(err, "backToNormal")

View file

@ -3052,7 +3052,23 @@ func (e *executor) executeGroupBy(ctx context.Context, qcx *Qcx, index string, c
if err := ctx.Err(); err != nil {
return err
}
return mergeGroupCounts(other, findGroupCounts(v), limit)
x := mergeGroupCounts(other, findGroupCounts(v), limit)
for i := range x {
gc := &x[i]
for j := range gc.Group {
fr := &gc.Group[j]
if fr.FieldOptions == nil {
// oops, options were omitted possibly by a remote. try to
// guess them from our local options
field := e.Holder.Field(index, fr.Field)
if field != nil {
options := field.Options()
fr.FieldOptions = &options
}
}
}
}
return x
}
// Get full result set.
other, err := e.mapReduce(ctx, index, shards, c, opt, mapFn, reduceFn)

View file

@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"reflect"
"sort"
"strconv"
"testing"
"time"
@ -16,6 +17,30 @@ import (
"github.com/stretchr/testify/assert"
)
// AssertEqual checks a given RowIdentifiers against expected values.
func (r *RowIdentifiers) AssertEqual(tb testing.TB, other *RowIdentifiers) {
sort.Slice(r.Rows, func(i, j int) bool { return r.Rows[i] < r.Rows[j] })
sort.Slice(other.Rows, func(i, j int) bool { return other.Rows[i] < other.Rows[j] })
if len(r.Rows) != len(other.Rows) {
tb.Fatalf("row ID mismatch: got %d, expected %d", r.Rows, other.Rows)
}
for i := range r.Rows {
if r.Rows[i] != other.Rows[i] {
tb.Fatalf("row ID mismatch: got %d, expected %d", r.Rows, other.Rows)
}
}
sort.Strings(r.Keys)
sort.Strings(other.Keys)
if len(r.Keys) != len(other.Keys) {
tb.Fatalf("row keys mismatch: got %s, expected %s", r.Keys, other.Keys)
}
for i := range r.Keys {
if r.Keys[i] != other.Keys[i] {
tb.Fatalf("row keys mismatch: got %s, expected %s", r.Keys, other.Keys)
}
}
}
func TestExecutor_TranslateRowsOnBool(t *testing.T) {
path, _ := testhook.TempDirInDir(t, *TempDir, "pilosa-executor-")
holder := NewHolder(path, mustHolderConfig())

File diff suppressed because it is too large Load diff

View file

@ -218,7 +218,7 @@ func TestHolder_DeleteIndex(t *testing.T) {
// Ensure holder can sync with a remote holder.
func TestHolderSyncer_SyncHolder(t *testing.T) {
c := test.MustNewCluster(t, 2)
c := test.MustUnsharedCluster(t, 2)
c.GetIdleNode(0).Config.Cluster.ReplicaN = 2
c.GetIdleNode(0).Config.AntiEntropy.Interval = 0
c.GetIdleNode(1).Config.Cluster.ReplicaN = 2
@ -230,27 +230,27 @@ func TestHolderSyncer_SyncHolder(t *testing.T) {
}
defer c.Close()
_, err = c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
_, err = c.GetNode(0).API.CreateIndex(context.Background(), c.Idx(), pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index i: %v", err)
}
_, err = c.GetNode(0).API.CreateIndex(context.Background(), "y", pilosa.IndexOptions{})
_, err = c.GetNode(0).API.CreateIndex(context.Background(), c.Idx("y"), pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index y: %v", err)
}
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
_, err = c.GetNode(0).API.CreateField(context.Background(), c.Idx(), "f", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
if err != nil {
t.Fatalf("creating field f: %v", err)
}
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f0", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
_, err = c.GetNode(0).API.CreateField(context.Background(), c.Idx(), "f0", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
if err != nil {
t.Fatalf("creating field f0: %v", err)
}
_, err = c.GetNode(0).API.CreateField(context.Background(), "y", "z", pilosa.OptFieldTypeMutex(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
_, err = c.GetNode(0).API.CreateField(context.Background(), c.Idx("y"), "z", pilosa.OptFieldTypeMutex(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
if err != nil {
t.Fatalf("creating field z in y: %v", err)
}
_, err = c.GetNode(0).API.CreateField(context.Background(), "y", "b", pilosa.OptFieldTypeBool())
_, err = c.GetNode(0).API.CreateField(context.Background(), c.Idx("y"), "b", pilosa.OptFieldTypeBool())
if err != nil {
t.Fatalf("creating field b in y: %v", err)
}
@ -259,29 +259,29 @@ func TestHolderSyncer_SyncHolder(t *testing.T) {
hldr1 := &test.Holder{Holder: c.GetNode(1).Server.Holder()}
// Set data on the local holder.
hldr0.SetBit("i", "f", 0, 10)
hldr0.SetBit("i", "f", 2, 20)
hldr0.SetBit("i", "f", 120, 10)
hldr0.SetBit("i", "f", 200, 4)
hldr0.SetBit(c.Idx(), "f", 0, 10)
hldr0.SetBit(c.Idx(), "f", 2, 20)
hldr0.SetBit(c.Idx(), "f", 120, 10)
hldr0.SetBit(c.Idx(), "f", 200, 4)
hldr0.SetBit("i", "f0", 9, ShardWidth+5)
hldr0.SetBit(c.Idx(), "f0", 9, ShardWidth+5)
// Set a bit to create the fragment.
hldr0.SetBit("y", "z", 0, 0)
hldr0.SetBit("y", "b", 0, 0) // rowID = 0 means false
hldr0.SetBit(c.Idx("y"), "z", 0, 0)
hldr0.SetBit(c.Idx("y"), "b", 0, 0) // rowID = 0 means false
// Set data on the remote holder.
hldr1.SetBit("i", "f", 0, 4000)
hldr1.SetBit("i", "f", 3, 10)
hldr1.SetBit("i", "f", 120, 10)
hldr1.SetBit(c.Idx(), "f", 0, 4000)
hldr1.SetBit(c.Idx(), "f", 3, 10)
hldr1.SetBit(c.Idx(), "f", 120, 10)
hldr1.SetBit("y", "z", 10, (3*ShardWidth)+4)
hldr1.SetBit("y", "z", 10, (3*ShardWidth)+5)
hldr1.SetBit("y", "z", 10, (3*ShardWidth)+7)
hldr1.SetBit(c.Idx("y"), "z", 10, (3*ShardWidth)+4)
hldr1.SetBit(c.Idx("y"), "z", 10, (3*ShardWidth)+5)
hldr1.SetBit(c.Idx("y"), "z", 10, (3*ShardWidth)+7)
hldr1.SetBit("y", "b", 1, (3*ShardWidth)+4) // true
hldr1.SetBit("y", "b", 0, (3*ShardWidth)+5) // false
hldr1.SetBit("y", "b", 1, (3*ShardWidth)+7) // true
hldr1.SetBit(c.Idx("y"), "b", 1, (3*ShardWidth)+4) // true
hldr1.SetBit(c.Idx("y"), "b", 0, (3*ShardWidth)+5) // false
hldr1.SetBit(c.Idx("y"), "b", 1, (3*ShardWidth)+7) // true
err = c.GetNode(0).Server.SyncData()
if err != nil {
@ -294,34 +294,34 @@ func TestHolderSyncer_SyncHolder(t *testing.T) {
// Verify data is the same on both nodes.
for i, hldr := range []*test.Holder{hldr0, hldr1} {
if a := hldr.Row("i", "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{10, 4000}) {
if a := hldr.Row(c.Idx(), "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{10, 4000}) {
t.Errorf("unexpected columns(%d/0): %+v", i, a)
}
if a := hldr.Row("i", "f", 2).Columns(); !reflect.DeepEqual(a, []uint64{20}) {
if a := hldr.Row(c.Idx(), "f", 2).Columns(); !reflect.DeepEqual(a, []uint64{20}) {
t.Errorf("unexpected columns(%d/2): %+v", i, a)
}
if a := hldr.Row("i", "f", 3).Columns(); !reflect.DeepEqual(a, []uint64{10}) {
if a := hldr.Row(c.Idx(), "f", 3).Columns(); !reflect.DeepEqual(a, []uint64{10}) {
t.Errorf("unexpected columns(%d/3): %+v", i, a)
}
if a := hldr.Row("i", "f", 120).Columns(); !reflect.DeepEqual(a, []uint64{10}) {
if a := hldr.Row(c.Idx(), "f", 120).Columns(); !reflect.DeepEqual(a, []uint64{10}) {
t.Errorf("unexpected columns(%d/120): %+v", i, a)
}
if a := hldr.Row("i", "f", 200).Columns(); !reflect.DeepEqual(a, []uint64{4}) {
if a := hldr.Row(c.Idx(), "f", 200).Columns(); !reflect.DeepEqual(a, []uint64{4}) {
t.Errorf("unexpected columns(%d/200): %+v", i, a)
}
if a := hldr.Row("i", "f0", 9).Columns(); !reflect.DeepEqual(a, []uint64{ShardWidth + 5}) {
if a := hldr.Row(c.Idx(), "f0", 9).Columns(); !reflect.DeepEqual(a, []uint64{ShardWidth + 5}) {
t.Errorf("unexpected columns(%d/d/f0): %+v", i, a)
}
if a := hldr.Row("y", "z", 10).Columns(); !reflect.DeepEqual(a, []uint64{(3 * ShardWidth) + 4, (3 * ShardWidth) + 5, (3 * ShardWidth) + 7}) {
if a := hldr.Row(c.Idx("y"), "z", 10).Columns(); !reflect.DeepEqual(a, []uint64{(3 * ShardWidth) + 4, (3 * ShardWidth) + 5, (3 * ShardWidth) + 7}) {
t.Errorf("unexpected columns(%d/y/z): %+v", i, a)
}
if a := hldr.Row("y", "b", 0).Columns(); !reflect.DeepEqual(a, []uint64{0, (3 * ShardWidth) + 5}) {
if a := hldr.Row(c.Idx("y"), "b", 0).Columns(); !reflect.DeepEqual(a, []uint64{0, (3 * ShardWidth) + 5}) {
t.Errorf("unexpected false columns(%d/y/b): %+v", i, a)
}
if a := hldr.Row("y", "b", 1).Columns(); !reflect.DeepEqual(a, []uint64{(3 * ShardWidth) + 4, (3 * ShardWidth) + 7}) {
if a := hldr.Row(c.Idx("y"), "b", 1).Columns(); !reflect.DeepEqual(a, []uint64{(3 * ShardWidth) + 4, (3 * ShardWidth) + 7}) {
t.Errorf("unexpected true columns(%d/y/b): %+v", i, a)
}
}
@ -330,7 +330,7 @@ func TestHolderSyncer_SyncHolder(t *testing.T) {
// Ensure holder can sync with a remote holder and respects
// the row boundaries of the block.
func TestHolderSyncer_BlockIteratorLimits(t *testing.T) {
c := test.MustNewCluster(t, 3)
c := test.MustUnsharedCluster(t, 3)
c.GetIdleNode(0).Config.Cluster.ReplicaN = 3
c.GetIdleNode(0).Config.AntiEntropy.Interval = 0
c.GetIdleNode(1).Config.Cluster.ReplicaN = 3
@ -343,11 +343,11 @@ func TestHolderSyncer_BlockIteratorLimits(t *testing.T) {
}
defer c.Close()
_, err = c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
_, err = c.GetNode(0).API.CreateIndex(context.Background(), c.Idx(), pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index i: %v", err)
}
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
_, err = c.GetNode(0).API.CreateField(context.Background(), c.Idx(), "f", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
if err != nil {
t.Fatalf("creating field f: %v", err)
}
@ -359,13 +359,13 @@ func TestHolderSyncer_BlockIteratorLimits(t *testing.T) {
hldr2 := &test.Holder{Holder: c.GetNode(2).Server.Holder()}
// Set data on the local holder.
hldr0.SetBit("i", "f", blockEdge-1, 10)
hldr0.SetBit("i", "f", blockEdge, 20)
hldr0.SetBit(c.Idx(), "f", blockEdge-1, 10)
hldr0.SetBit(c.Idx(), "f", blockEdge, 20)
// Set the same data on one of the replicas
// so that we have a quorum.
hldr1.SetBit("i", "f", blockEdge-1, 10)
hldr1.SetBit("i", "f", blockEdge, 20)
hldr1.SetBit(c.Idx(), "f", blockEdge-1, 10)
hldr1.SetBit(c.Idx(), "f", blockEdge, 20)
// Leave the third replica empty to force a block merge.
//
@ -376,10 +376,10 @@ func TestHolderSyncer_BlockIteratorLimits(t *testing.T) {
// Verify data is the same on all nodes.
for i, hldr := range []*test.Holder{hldr0, hldr1, hldr2} {
if a := hldr.Row("i", "f", blockEdge-1).Columns(); !reflect.DeepEqual(a, []uint64{10}) {
if a := hldr.Row(c.Idx(), "f", blockEdge-1).Columns(); !reflect.DeepEqual(a, []uint64{10}) {
t.Errorf("unexpected columns(%d/block 0): %+v", i, a)
}
if a := hldr.Row("i", "f", blockEdge).Columns(); !reflect.DeepEqual(a, []uint64{20}) {
if a := hldr.Row(c.Idx(), "f", blockEdge).Columns(); !reflect.DeepEqual(a, []uint64{20}) {
t.Errorf("unexpected columns(%d/block 1): %+v", i, a)
}
}
@ -387,7 +387,7 @@ func TestHolderSyncer_BlockIteratorLimits(t *testing.T) {
// Ensure holder correctly handles clears during block sync.
func TestHolderSyncer_Clears(t *testing.T) {
c := test.MustNewCluster(t, 3)
c := test.MustUnsharedCluster(t, 3)
c.GetIdleNode(0).Config.Cluster.ReplicaN = 3
c.GetIdleNode(0).Config.AntiEntropy.Interval = 0
c.GetIdleNode(1).Config.Cluster.ReplicaN = 3
@ -400,11 +400,11 @@ func TestHolderSyncer_Clears(t *testing.T) {
}
defer c.Close()
_, err = c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
_, err = c.GetNode(0).API.CreateIndex(context.Background(), c.Idx(), pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index i: %v", err)
}
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
_, err = c.GetNode(0).API.CreateField(context.Background(), c.Idx(), "f", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
if err != nil {
t.Fatalf("creating field f: %v", err)
}
@ -415,13 +415,13 @@ func TestHolderSyncer_Clears(t *testing.T) {
// Set data on the local holder that should be cleared
// because it's the only instance of this value.
hldr0.SetBit("i", "f", 0, 30)
hldr0.SetBit(c.Idx(), "f", 0, 30)
// Set similar data on the replicas, but
// different from what's on local. This should end
// up being set on all replicas
hldr1.SetBit("i", "f", 0, 20)
hldr2.SetBit("i", "f", 0, 20)
hldr1.SetBit(c.Idx(), "f", 0, 20)
hldr2.SetBit(c.Idx(), "f", 0, 20)
err = c.GetNode(0).Server.SyncData()
if err != nil {
@ -430,7 +430,7 @@ func TestHolderSyncer_Clears(t *testing.T) {
// Verify data is the same on all nodes.
for i, hldr := range []*test.Holder{hldr0, hldr1, hldr2} {
if a := hldr.Row("i", "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{20}) {
if a := hldr.Row(c.Idx(), "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{20}) {
t.Errorf("unexpected columns(%d): %+v", i, a)
}
}
@ -438,7 +438,7 @@ func TestHolderSyncer_Clears(t *testing.T) {
// Ensure holder can sync time quantum views with a remote holder.
func TestHolderSyncer_TimeQuantum(t *testing.T) {
c := test.MustNewCluster(t, 2)
c := test.MustUnsharedCluster(t, 2)
c.GetIdleNode(0).Config.Cluster.ReplicaN = 2
c.GetIdleNode(0).Config.AntiEntropy.Interval = 0
c.GetIdleNode(1).Config.Cluster.ReplicaN = 2
@ -451,11 +451,11 @@ func TestHolderSyncer_TimeQuantum(t *testing.T) {
quantum := "D"
_, err = c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
_, err = c.GetNode(0).API.CreateIndex(context.Background(), c.Idx(), pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index i: %v", err)
}
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeTime(pilosa.TimeQuantum(quantum), "0"))
_, err = c.GetNode(0).API.CreateField(context.Background(), c.Idx(), "f", pilosa.OptFieldTypeTime(pilosa.TimeQuantum(quantum), "0"))
if err != nil {
t.Fatalf("creating field f: %v", err)
}
@ -466,11 +466,11 @@ func TestHolderSyncer_TimeQuantum(t *testing.T) {
// Set data on the local holder for node0.
t1 := time.Date(2018, 8, 1, 12, 30, 0, 0, time.UTC)
t2 := time.Date(2018, 8, 2, 12, 30, 0, 0, time.UTC)
hldr0.SetBitTime("i", "f", 0, 1, &t1)
hldr0.SetBitTime("i", "f", 0, 2, &t2)
hldr0.SetBitTime(c.Idx(), "f", 0, 1, &t1)
hldr0.SetBitTime(c.Idx(), "f", 0, 2, &t2)
// Set data on node1.
hldr1.SetBitTime("i", "f", 0, 22, &t2)
hldr1.SetBitTime(c.Idx(), "f", 0, 22, &t2)
err = c.GetNode(0).Server.SyncData()
if err != nil {
@ -479,10 +479,10 @@ func TestHolderSyncer_TimeQuantum(t *testing.T) {
// Verify data is the same on both nodes.
for i, hldr := range []*test.Holder{hldr0, hldr1} {
if a := hldr.RowTime("i", "f", 0, t1, quantum).Columns(); !reflect.DeepEqual(a, []uint64{1}) {
if a := hldr.RowTime(c.Idx(), "f", 0, t1, quantum).Columns(); !reflect.DeepEqual(a, []uint64{1}) {
t.Errorf("unexpected columns(%d/0): %+v", i, a)
}
if a := hldr.RowTime("i", "f", 0, t2, quantum).Columns(); !reflect.DeepEqual(a, []uint64{2, 22}) {
if a := hldr.RowTime(c.Idx(), "f", 0, t2, quantum).Columns(); !reflect.DeepEqual(a, []uint64{2, 22}) {
t.Errorf("unexpected columns(%d/0): %+v", i, a)
}
}
@ -491,7 +491,7 @@ func TestHolderSyncer_TimeQuantum(t *testing.T) {
// Ensure holder can sync integer views with a remote holder.
func TestHolderSyncer_IntField(t *testing.T) {
t.Run("BasicSync", func(t *testing.T) {
c := test.MustNewCluster(t, 2)
c := test.MustUnsharedCluster(t, 2)
c.GetIdleNode(0).Config.Cluster.ReplicaN = 2
c.GetIdleNode(0).Config.AntiEntropy.Interval = 0
c.GetIdleNode(1).Config.Cluster.ReplicaN = 2
@ -503,12 +503,12 @@ func TestHolderSyncer_IntField(t *testing.T) {
defer c.Close()
var idx0 *pilosa.Index
idx0, err = c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
idx0, err = c.GetNode(0).API.CreateIndex(context.Background(), c.Idx(), pilosa.IndexOptions{})
_ = idx0
if err != nil {
t.Fatalf("creating index i: %v", err)
}
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeInt(0, 100))
_, err = c.GetNode(0).API.CreateField(context.Background(), c.Idx(), "f", pilosa.OptFieldTypeInt(0, 100))
if err != nil {
t.Fatalf("creating field f: %v", err)
}
@ -517,12 +517,12 @@ func TestHolderSyncer_IntField(t *testing.T) {
hldr1 := &test.Holder{Holder: c.GetNode(1).Server.Holder()}
// Set data on the local holder for node0. columnID=1, value=1
hldr0.SetValue("i", "f", 1, 1)
hldr0.SetValue(c.Idx(), "f", 1, 1)
// in c0 expect the 1 bit
// Set data on node1. columnID=2, value=2
idx1 := hldr1.SetValue("i", "f", 2, 2)
idx1 := hldr1.SetValue(c.Idx(), "f", 2, 2)
_ = idx1
err = c.GetNode(0).Server.SyncData()
@ -537,11 +537,11 @@ func TestHolderSyncer_IntField(t *testing.T) {
// Verify data is the same on both nodes.
for i, hldr := range []*test.Holder{hldr0, hldr1} {
if a, exists := hldr.Value("i", "f", 1); !exists || a != 1 {
if a, exists := hldr.Value(c.Idx(), "f", 1); !exists || a != 1 {
// expects exists==true, a==1
t.Errorf("unexpected value(node%d/0): a:%d, exists: %v", i, a, exists)
}
if a, exists := hldr.Value("i", "f", 2); exists {
if a, exists := hldr.Value(c.Idx(), "f", 2); exists {
t.Errorf("unexpected value(node%d/1): a:%d, exists: %v", i, a, exists)
}
}
@ -549,7 +549,7 @@ func TestHolderSyncer_IntField(t *testing.T) {
t.Run("MultiShard", func(t *testing.T) {
t.Skip() // skipping due to changed partitioning strategy
c := test.MustNewCluster(t, 2)
c := test.MustUnsharedCluster(t, 2)
c.GetIdleNode(0).Config.Cluster.ReplicaN = 2
c.GetIdleNode(0).Config.AntiEntropy.Interval = 0
c.GetIdleNode(1).Config.Cluster.ReplicaN = 2
@ -562,12 +562,12 @@ func TestHolderSyncer_IntField(t *testing.T) {
var idx0 *pilosa.Index
_ = idx0
idx0, err = c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
idx0, err = c.GetNode(0).API.CreateIndex(context.Background(), c.Idx(), pilosa.IndexOptions{})
_ = idx0
if err != nil {
t.Fatalf("creating index i: %v", err)
}
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64))
_, err = c.GetNode(0).API.CreateField(context.Background(), c.Idx(), "f", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64))
if err != nil {
t.Fatalf("creating field f: %v", err)
}
@ -576,18 +576,18 @@ func TestHolderSyncer_IntField(t *testing.T) {
hldr1 := &test.Holder{Holder: c.GetNode(1).Server.Holder()}
// Set data on the local holder for node0.
hldr0.SetValue("i", "f", 1*pilosa.ShardWidth, 11)
hldr0.SetValue("i", "f", 3*pilosa.ShardWidth, 32)
hldr0.SetValue("i", "f", 4*pilosa.ShardWidth, math.MinInt32)
hldr0.SetValue("i", "f", 7*pilosa.ShardWidth, math.MinInt32)
hldr0.SetValue(c.Idx(), "f", 1*pilosa.ShardWidth, 11)
hldr0.SetValue(c.Idx(), "f", 3*pilosa.ShardWidth, 32)
hldr0.SetValue(c.Idx(), "f", 4*pilosa.ShardWidth, math.MinInt32)
hldr0.SetValue(c.Idx(), "f", 7*pilosa.ShardWidth, math.MinInt32)
// Set data on node1.
hldr1.SetValue("i", "f", 0*pilosa.ShardWidth, 2)
hldr1.SetValue("i", "f", 2*pilosa.ShardWidth, 22)
hldr1.SetValue("i", "f", 4*pilosa.ShardWidth, math.MaxInt32)
hldr1.SetValue("i", "f", 7*pilosa.ShardWidth, math.MaxInt32)
hldr1.SetValue(c.Idx(), "f", 0*pilosa.ShardWidth, 2)
hldr1.SetValue(c.Idx(), "f", 2*pilosa.ShardWidth, 22)
hldr1.SetValue(c.Idx(), "f", 4*pilosa.ShardWidth, math.MaxInt32)
hldr1.SetValue(c.Idx(), "f", 7*pilosa.ShardWidth, math.MaxInt32)
// Primary for shards (for index "i"):
// Primary for shards (for index c.Idx()):
// node0: [0,3,7]
// node1: [1,2,4]
@ -604,10 +604,10 @@ func TestHolderSyncer_IntField(t *testing.T) {
// Verify data is the same on both nodes.
for i, hldr := range []*test.Holder{hldr0, hldr1} {
if a := hldr.Range("i", "f", pql.GT, 0); !reflect.DeepEqual(a.Columns(), []uint64{2 * pilosa.ShardWidth, 3 * pilosa.ShardWidth, 4 * pilosa.ShardWidth}) {
if a := hldr.Range(c.Idx(), "f", pql.GT, 0); !reflect.DeepEqual(a.Columns(), []uint64{2 * pilosa.ShardWidth, 3 * pilosa.ShardWidth, 4 * pilosa.ShardWidth}) {
t.Errorf("unexpected columns(node%d/0): %d", i, a.Columns())
}
if a := hldr.Range("i", "f", pql.LT, 0); !reflect.DeepEqual(a.Columns(), []uint64{7 * pilosa.ShardWidth}) {
if a := hldr.Range(c.Idx(), "f", pql.LT, 0); !reflect.DeepEqual(a.Columns(), []uint64{7 * pilosa.ShardWidth}) {
t.Errorf("unexpected columns(node%d/0): %d", i, a.Columns())
}
}

View file

@ -173,10 +173,11 @@ func TestUpdateFieldTTL(t *testing.T) {
},
}
indexName := c.Idx("s")
for i, test := range tests {
t.Run(test.name, func(t *testing.T) {
c.CreateField(t, "ttltest", pilosa.IndexOptions{}, test.name, pilosa.OptFieldTypeTime(pilosa.TimeQuantum("YMD"), test.ttl))
nodeURL := c.Nodes[0].URL() + "/index/ttltest/field/" + test.field
c.CreateField(t, indexName, pilosa.IndexOptions{}, test.name, pilosa.OptFieldTypeTime(pilosa.TimeQuantum("YMD"), test.ttl))
nodeURL := fmt.Sprintf("%s/index/%s/field/%s", c.Nodes[0].URL(), c, test.field)
req, err := gohttp.NewRequest("PATCH", nodeURL, strings.NewReader(test.ttlOption))
if err != nil {
t.Fatal(err)
@ -215,12 +216,21 @@ func TestUpdateFieldTTL(t *testing.T) {
if err != nil {
t.Fatalf("getting schema: %v", err)
}
if ii[0].Fields[i].Name == test.name {
if ii[0].Fields[i].Options.TTL != test.expTTL {
t.Errorf("expected TTL: '%s', got: '%s'", test.expTTL, ii[0].Fields[i].Options.TTL)
for _, idx := range ii {
if idx.Name == indexName {
if len(idx.Fields) <= i {
t.Fatalf("expected %d fields, last %s, got %d fields",
i, test.name, len(idx.Fields))
}
if idx.Fields[i].Name == test.name {
if idx.Fields[i].Options.TTL != test.expTTL {
t.Errorf("expected noStandardView value: '%s', got: '%s'", test.expTTL, idx.Fields[i].Options.TTL)
}
} else {
t.Errorf("unexpected field: '%s', got: '%s'", test.name, idx.Fields[i].Name)
}
break
}
} else {
t.Errorf("unexpected field: '%s', got: '%s'", test.name, ii[0].Fields[i].Name)
}
}
@ -278,11 +288,13 @@ func TestUpdateFieldNoStandardView(t *testing.T) {
expNoStandardView: false,
},
}
// "s" to make it match %s behavior of a cluster
indexName := c.Idx("s")
for i, test := range tests {
t.Run(test.name, func(t *testing.T) {
c.CreateField(t, "ttltest", pilosa.IndexOptions{}, test.name, pilosa.OptFieldTypeTime(pilosa.TimeQuantum("YMD"), "0"))
nodeURL := c.Nodes[0].URL() + "/index/ttltest/field/" + test.field
c.CreateField(t, indexName, pilosa.IndexOptions{}, test.name, pilosa.OptFieldTypeTime(pilosa.TimeQuantum("YMD"), "0"))
nodeURL := fmt.Sprintf("%s/index/%s/field/%s", c.Nodes[0].URL(), c, test.field)
req, err := gohttp.NewRequest("PATCH", nodeURL, strings.NewReader(test.fieldOption))
if err != nil {
t.Fatal(err)
@ -317,33 +329,34 @@ func TestUpdateFieldNoStandardView(t *testing.T) {
if err != nil {
t.Fatalf("getting schema: %v", err)
}
if ii[0].Fields[i].Name == test.name {
if ii[0].Fields[i].Options.NoStandardView != test.expNoStandardView {
t.Errorf("expected noStandardView value: '%t', got: '%t'", test.expNoStandardView, ii[0].Fields[i].Options.NoStandardView)
for _, idx := range ii {
if idx.Name == indexName {
if len(idx.Fields) <= i {
t.Fatalf("expected %d fields, last %s, got %d fields",
i, test.name, len(idx.Fields))
}
if idx.Fields[i].Name == test.name {
if idx.Fields[i].Options.NoStandardView != test.expNoStandardView {
t.Errorf("expected noStandardView value: '%t', got: '%t'", test.expNoStandardView, idx.Fields[i].Options.NoStandardView)
}
} else {
t.Errorf("unexpected field: '%s', got: '%s'", test.name, idx.Fields[i].Name)
}
break
}
} else {
t.Errorf("unexpected field: '%s', got: '%s'", test.name, ii[0].Fields[i].Name)
}
}
})
}
}
func TestIngestSchemaHandler(t *testing.T) {
c := test.MustRunCluster(t, 3,
[]server.CommandOption{
server.OptCommandServerOptions(pilosa.OptServerNodeID("node0"), pilosa.OptServerClusterHasher(&test.ModHasher{}))},
[]server.CommandOption{
server.OptCommandServerOptions(pilosa.OptServerNodeID("node1"), pilosa.OptServerClusterHasher(&test.ModHasher{}))},
[]server.CommandOption{
server.OptCommandServerOptions(pilosa.OptServerNodeID("node2"), pilosa.OptServerClusterHasher(&test.ModHasher{}))},
)
c := test.MustRunCluster(t, 3)
defer c.Close()
schema := `
schema := fmt.Sprintf(`
{
"index-name": "example",
"index-name": "%s",
"primary-key-type": "string",
"index-action": "create",
"fields": [
@ -408,7 +421,7 @@ func TestIngestSchemaHandler(t *testing.T) {
}
]
}
`
`, c)
m := c.GetPrimary()
schemaURL := fmt.Sprintf("%s/internal/schema", m.URL())
resp := test.Do(t, "POST", schemaURL, string(schema))
@ -425,15 +438,16 @@ func TestIngestSchemaHandler(t *testing.T) {
func TestPostFieldWithTTL(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
indexName := c.Idx("%s")
schema := `
schema := fmt.Sprintf(`
{
"index-name": "ttl_test",
"index-name": "%s",
"primary-key-type": "string",
"index-action": "create",
"fields":[]
}
`
`, c)
m := c.GetPrimary()
schemaURL := fmt.Sprintf("%s/internal/schema", m.URL())
resp := test.Do(t, "POST", schemaURL, string(schema))
@ -451,7 +465,7 @@ func TestPostFieldWithTTL(t *testing.T) {
}{
{
name: "t1_48h",
url: fmt.Sprintf("%s/index/ttl_test/field/t1_48h", m.URL()),
url: fmt.Sprintf("%s/index/%s/field/t1_48h", m.URL(), c),
option: `{ "options": {"timeQuantum":"YMDH","type":"time","ttl":"48h" }}`,
expStatus: 200,
expErr: `"success":true`,
@ -459,28 +473,28 @@ func TestPostFieldWithTTL(t *testing.T) {
},
{
name: "t2_unknown_unit",
url: fmt.Sprintf("%s/index/ttl_test/field/t2_unknown_unit", m.URL()),
url: fmt.Sprintf("%s/index/%s/field/t2_unknown_unit", m.URL(), c),
option: `{ "options": {"timeQuantum":"YMDH","type":"time","ttl":"24abc" }}`,
expStatus: 400,
expErr: "cannot parse ttl",
},
{
name: "t3_invalid",
url: fmt.Sprintf("%s/index/ttl_test/field/t3_invalid", m.URL()),
url: fmt.Sprintf("%s/index/%s/field/t3_invalid", m.URL(), c),
option: `{ "options": {"timeQuantum":"YMDH","type":"time","ttl":"abcdef" }}`,
expStatus: 400,
expErr: "cannot parse ttl",
},
{
name: "t4_invalid_empty",
url: fmt.Sprintf("%s/index/ttl_test/field/t4_invalid_empty", m.URL()),
url: fmt.Sprintf("%s/index/%s/field/t4_invalid_empty", m.URL(), c),
option: `{ "options": {"timeQuantum":"YMDH","type":"time","ttl":"" }}`,
expStatus: 400,
expErr: "cannot parse ttl",
},
{
name: "t5_negative",
url: fmt.Sprintf("%s/index/ttl_test/field/t5_negative", m.URL()),
url: fmt.Sprintf("%s/index/%s/field/t5_negative", m.URL(), c),
option: `{ "options": {"timeQuantum":"YMDH","type":"time","ttl":"-24h" }}`,
expStatus: 400,
expErr: "ttl can't be negative",
@ -508,12 +522,21 @@ func TestPostFieldWithTTL(t *testing.T) {
t.Fatalf("getting schema: %v", err)
}
if ii[0].Fields[i].Name == test_i.name {
if ii[0].Fields[i].Options.TTL != test_i.expTTL {
t.Errorf("expected TTL: '%s', got: '%s'", test_i.expTTL, ii[0].Fields[i].Options.TTL)
for _, idx := range ii {
if idx.Name == indexName {
if len(idx.Fields) <= i {
t.Fatalf("expected %d fields, last %s, got %d fields",
i, test_i.name, len(idx.Fields))
}
if idx.Fields[i].Name == test_i.name {
if idx.Fields[i].Options.TTL != test_i.expTTL {
t.Errorf("expected TTL: '%s', got: '%s'", test_i.expTTL, idx.Fields[i].Options.TTL)
}
} else {
t.Errorf("unexpected field: '%s', got: '%s'", test_i.name, idx.Fields[i].Name)
}
break
}
} else {
t.Errorf("unexpected field: '%s', got: '%s'", test_i.name, ii[0].Fields[i].Name)
}
}
}
@ -525,9 +548,9 @@ func TestGetViewAndDelete(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
schema := `
schema := fmt.Sprintf(`
{
"index-name": "example",
"index-name": "%s",
"primary-key-type": "string",
"index-action": "create",
"fields": [
@ -540,7 +563,7 @@ func TestGetViewAndDelete(t *testing.T) {
}
]
}
`
`, c)
m := c.GetPrimary()
schemaURL := fmt.Sprintf("%s/internal/schema", m.URL())
resp := test.Do(t, "POST", schemaURL, string(schema))
@ -549,7 +572,7 @@ func TestGetViewAndDelete(t *testing.T) {
}
// Send sample data
postQueryUrl := fmt.Sprintf("%s/index/example/query", m.URL())
postQueryUrl := fmt.Sprintf("%s/index/%s/query", m.URL(), c)
queryOption := `
Set(1,test_view=1,2001-02-03T04:05)
`
@ -568,7 +591,7 @@ func TestGetViewAndDelete(t *testing.T) {
}
// Call view to get data
viewUrl := fmt.Sprintf("%s/index/example/field/test_view/view", m.URL())
viewUrl := fmt.Sprintf("%s/index/%s/field/test_view/view", m.URL(), c)
respView := test.Do(t, "GET", viewUrl, "")
if respView.StatusCode != gohttp.StatusOK {
t.Errorf("view handler, status: %d, body=%s", respView.StatusCode, respView.Body)
@ -598,7 +621,7 @@ func TestGetViewAndDelete(t *testing.T) {
}
// call delete on view standard_2001020304
deleteViewUrl := fmt.Sprintf("%s/index/example/field/test_view/view/standard_2001020304", m.URL())
deleteViewUrl := fmt.Sprintf("%s/index/%s/field/test_view/view/standard_2001020304", m.URL(), c)
respDelete := test.Do(t, "DELETE", deleteViewUrl, "")
if respDelete.StatusCode != gohttp.StatusOK {
t.Errorf("delete handler, status: %d, body=%s", respDelete.StatusCode, respDelete.Body)
@ -608,7 +631,7 @@ func TestGetViewAndDelete(t *testing.T) {
expectedViewNames = expectedViewNames[:len(expectedViewNames)-1]
// call view again
viewUrl = fmt.Sprintf("%s/index/example/field/test_view/view", m.URL())
viewUrl = fmt.Sprintf("%s/index/%s/field/test_view/view", m.URL(), c)
respView = test.Do(t, "GET", viewUrl, "")
if respView.StatusCode != gohttp.StatusOK {
t.Errorf("view handler after delete, status: %d, body=%s", respView.StatusCode, respView.Body)
@ -641,9 +664,9 @@ func TestTranslationHandlers(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
schema := `
schema := fmt.Sprintf(`
{
"index-name": "example",
"index-name": "%s",
"primary-key-type": "string",
"index-action": "create",
"fields": [
@ -657,7 +680,7 @@ func TestTranslationHandlers(t *testing.T) {
}
]
}
`
`, c)
m := c.GetPrimary()
schemaURL := fmt.Sprintf("%s/internal/schema", m.URL())
resp := test.Do(t, "POST", schemaURL, string(schema))
@ -665,9 +688,9 @@ func TestTranslationHandlers(t *testing.T) {
t.Errorf("invalid status: %d, body=%s", resp.StatusCode, resp.Body)
}
baseURLs := []string{
fmt.Sprintf("%s/internal/translate/index/example/", m.URL()),
fmt.Sprintf("%s/internal/translate/field/example/stringset/", m.URL()),
fmt.Sprintf("%s/internal/translate/field/example/nonexistent/", m.URL()),
fmt.Sprintf("%s/internal/translate/index/%s/", m.URL(), c),
fmt.Sprintf("%s/internal/translate/field/%s/stringset/", m.URL(), c),
fmt.Sprintf("%s/internal/translate/field/%s/nonexistent/", m.URL(), c),
}
for _, url := range baseURLs {
expectFailure := strings.HasSuffix(url, "/nonexistent/")

View file

@ -261,7 +261,7 @@ func isNotFoundError(err error) bool {
// This is a regression test after a customer experienced the same deadlock.
// For details, check out https://molecula.atlassian.net/browse/CORE-919
func TestIndex_RecreateFieldOnRestart(t *testing.T) {
c := test.MustRunCluster(t, 1)
c := test.MustRunUnsharedCluster(t, 1)
defer c.Close()
// create index

View file

@ -15,99 +15,99 @@ import (
"time"
"github.com/davecgh/go-spew/spew"
pilosa "github.com/featurebasedb/featurebase/v3"
"github.com/featurebasedb/featurebase/v3/authn"
"github.com/featurebasedb/featurebase/v3/disco"
"github.com/featurebasedb/featurebase/v3/encoding/proto"
"github.com/featurebasedb/featurebase/v3/pql"
"github.com/featurebasedb/featurebase/v3/server"
"github.com/featurebasedb/featurebase/v3/test"
"github.com/featurebasedb/featurebase/v3/vprint"
pilosa "github.com/molecula/featurebase/v3"
"github.com/molecula/featurebase/v3/authn"
"github.com/molecula/featurebase/v3/disco"
"github.com/molecula/featurebase/v3/encoding/proto"
"github.com/molecula/featurebase/v3/pql"
"github.com/molecula/featurebase/v3/test"
"github.com/molecula/featurebase/v3/vprint"
"github.com/pkg/errors"
"github.com/ricochet2200/go-disk-usage/du"
)
// Test distributed TopN Row count across 3 nodes.
func TestClient_MultiNode(t *testing.T) {
c := test.MustRunCluster(t, 3,
[]server.CommandOption{
server.OptCommandServerOptions(pilosa.OptServerNodeID("node0"), pilosa.OptServerClusterHasher(&test.ModHasher{}))},
[]server.CommandOption{
server.OptCommandServerOptions(pilosa.OptServerNodeID("node1"), pilosa.OptServerClusterHasher(&test.ModHasher{}))},
[]server.CommandOption{
server.OptCommandServerOptions(pilosa.OptServerNodeID("node2"), pilosa.OptServerClusterHasher(&test.ModHasher{}))},
)
c := test.MustRunCluster(t, 3)
defer c.Close()
hldr0 := c.GetHolder(0)
hldr1 := c.GetHolder(1)
hldr2 := c.GetHolder(2)
// Create a dispersed set of bitmaps across 3 nodes such that each
// individual node and shard width increment would reveal a different TopN.
shardNums := []uint64{1, 2, 6}
// This was generated with:
// `owns := s[i].Handler.Handler.API.Cluster.OwnsShards("i", 20, s[i].HostURI())`
owns := [][]uint64{
{1, 3, 4, 8, 10, 13, 17, 19},
{2, 5, 7, 11, 12, 14, 18},
{0, 6, 9, 15, 16, 20},
}
for i, num := range shardNums {
ownsNum := false
for _, ownNum := range owns[i] {
if ownNum == num {
ownsNum = true
break
}
}
if !ownsNum {
t.Fatalf("Trying to use shard %d on host %s, but it doesn't own that shard. It owns %v", num, c.GetNode(i).URL(), owns)
}
}
baseBit0 := pilosa.ShardWidth * shardNums[0]
baseBit1 := pilosa.ShardWidth * shardNums[1]
baseBit2 := pilosa.ShardWidth * shardNums[2]
maxShard := uint64(0)
for _, x := range shardNums {
if x > maxShard {
maxShard = x
}
}
_, err := c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
_, err := c.GetNode(0).API.CreateIndex(context.Background(), c.Idx(), pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, 100))
_, err = c.GetNode(0).API.CreateField(context.Background(), c.Idx(), "f", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, 100))
if err != nil {
t.Fatalf("creating field: %v", err)
}
hldr0.MustSetBits("i", "f", 100, baseBit0+10)
hldr0.MustSetBits("i", "f", 4, baseBit0+10, baseBit0+11, baseBit0+12)
hldr0.MustSetBits("i", "f", 4, baseBit0+10, baseBit0+11, baseBit0+12, baseBit0+13, baseBit0+14, baseBit0+15)
hldr0.MustSetBits("i", "f", 2, baseBit0+1, baseBit0+2, baseBit0+3, baseBit0+4)
hldr0.MustSetBits("i", "f", 3, baseBit0+1, baseBit0+2, baseBit0+3, baseBit0+4, baseBit0+5)
hldr0.MustSetBits("i", "f", 22, baseBit0+1, baseBit0+2)
// Connect to each node to compare results.
client := make([]*Client, 3)
client[0] = MustNewClient(c.GetNode(0).URL(), pilosa.GetHTTPClient(nil))
client[1] = MustNewClient(c.GetNode(1).URL(), pilosa.GetHTTPClient(nil))
client[2] = MustNewClient(c.GetNode(2).URL(), pilosa.GetHTTPClient(nil))
hldr1.MustSetBits("i", "f", 99, baseBit1+1, baseBit1+2, baseBit1+3, baseBit1+4)
hldr1.MustSetBits("i", "f", 100, baseBit1+1, baseBit1+2, baseBit1+3, baseBit1+4, baseBit1+5, baseBit1+6, baseBit1+7, baseBit1+8, baseBit1+9, baseBit1+10)
hldr1.MustSetBits("i", "f", 98, baseBit1+1, baseBit1+2, baseBit1+3, baseBit1+4, baseBit1+5, baseBit1+6)
hldr1.MustSetBits("i", "f", 1, baseBit1+4)
hldr1.MustSetBits("i", "f", 22, baseBit1+1, baseBit1+2, baseBit1+3, baseBit1+4, baseBit1+5)
b0 := uint64(ShardWidth * 0)
b1 := uint64(ShardWidth * 1)
b2 := uint64(ShardWidth * 2)
hldr2.MustSetBits("i", "f", 24, baseBit2+10, baseBit2+11, baseBit2+12, baseBit2+13, baseBit2+14)
hldr2.MustSetBits("i", "f", 20, baseBit2+10, baseBit2+11, baseBit2+12, baseBit2+13)
hldr2.MustSetBits("i", "f", 21, baseBit2+10)
hldr2.MustSetBits("i", "f", 100, baseBit2+10)
hldr2.MustSetBits("i", "f", 99, baseBit2+10, baseBit2+11, baseBit2+12)
hldr2.MustSetBits("i", "f", 98, baseBit2+10, baseBit2+11)
hldr2.MustSetBits("i", "f", 22, baseBit2+10, baseBit2+11, baseBit2+12)
// helper to let us avoid repeating the b0+, etc, over and over
collate := func(a, b, c []uint64) []uint64 {
d := make([]uint64, len(a)+len(b)+len(c))
n := 0
for _, v := range a {
d[n] = b0 + v
n++
}
for _, v := range b {
d[n] = b1 + v
n++
}
for _, v := range c {
d[n] = b2 + v
n++
}
return d
}
// data set. part of the goal of this is to have different top counts using a single node than
// using all three nodes
rows := map[uint64][]uint64{
100: collate([]uint64{10}, []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, []uint64{10}),
4: collate([]uint64{10, 11, 12, 13, 14, 15}, nil, nil),
2: collate([]uint64{1, 2, 3, 4}, nil, nil),
3: collate([]uint64{1, 2, 3, 4, 5}, nil, nil),
99: collate(nil, []uint64{1, 2, 3, 4}, []uint64{10, 11, 12}),
98: collate(nil, []uint64{1, 2, 3, 4, 5, 6}, []uint64{10, 11}),
22: collate([]uint64{1, 2}, []uint64{1, 2, 3, 4, 5}, []uint64{10, 11, 12}),
1: collate(nil, []uint64{4}, nil),
21: collate(nil, nil, []uint64{10}),
}
bits := 0
for _, v := range rows {
bits += len(v)
}
rowIDs := make([]uint64, bits)
colIDs := make([]uint64, bits)
n := 0
for k, cols := range rows {
for _, v := range cols {
rowIDs[n] = k
colIDs[n] = v
n++
}
}
req := &pilosa.ImportRequest{
Index: c.Idx(),
Field: "f",
RowIDs: rowIDs,
ColumnIDs: colIDs,
Shard: ^uint64(0),
}
err = client[0].Import(context.Background(), nil, req, &pilosa.ImportOptions{})
if err != nil {
t.Fatalf("importing data: %v", err)
}
// Rebuild the RankCache.
// We have to do this to avoid the 10-second cache invalidation delay
// built into cache.Invalidate()
@ -124,19 +124,13 @@ func TestClient_MultiNode(t *testing.T) {
t.Fatalf("recalculating cache: %v", err)
}
// Connect to each node to compare results.
client := make([]*Client, 3)
client[0] = MustNewClient(c.GetNode(0).URL(), pilosa.GetHTTPClient(nil))
client[1] = MustNewClient(c.GetNode(1).URL(), pilosa.GetHTTPClient(nil))
client[2] = MustNewClient(c.GetNode(2).URL(), pilosa.GetHTTPClient(nil))
topN := 4
queryRequest := &pilosa.QueryRequest{
Query: fmt.Sprintf(`TopN(f, n=%d)`, topN),
Remote: false,
}
result, err := client[0].Query(context.Background(), "i", queryRequest)
result, err := client[0].Query(context.Background(), c.Idx(), queryRequest)
if err != nil {
t.Fatal(err)
}
@ -157,11 +151,11 @@ func TestClient_MultiNode(t *testing.T) {
t.Fatalf("Invalid TopN result set: %s", spew.Sdump(result))
}
result1, err := client[1].Query(context.Background(), "i", queryRequest)
result1, err := client[1].Query(context.Background(), c.Idx(), queryRequest)
if err != nil {
t.Fatal(err)
}
result2, err := client[2].Query(context.Background(), "i", queryRequest)
result2, err := client[2].Query(context.Background(), c.Idx(), queryRequest)
if err != nil {
t.Fatal(err)
}
@ -181,16 +175,18 @@ func TestClient_Export(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
cmd := cluster.GetNode(0)
keyed := cluster.Idx("k")
unkeyed := cluster.Idx("u")
host := cmd.URL()
cmd.MustCreateIndex(t, "keyed", pilosa.IndexOptions{Keys: true})
cmd.MustCreateIndex(t, "unkeyed", pilosa.IndexOptions{Keys: false})
cmd.MustCreateIndex(t, keyed, pilosa.IndexOptions{Keys: true})
cmd.MustCreateIndex(t, unkeyed, pilosa.IndexOptions{Keys: false})
cmd.MustCreateField(t, "keyed", "keyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000), pilosa.OptFieldKeys())
cmd.MustCreateField(t, "keyed", "unkeyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000))
cmd.MustCreateField(t, "unkeyed", "keyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000), pilosa.OptFieldKeys())
cmd.MustCreateField(t, "unkeyed", "unkeyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000))
cmd.MustCreateField(t, keyed, "keyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000), pilosa.OptFieldKeys())
cmd.MustCreateField(t, keyed, "unkeyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000))
cmd.MustCreateField(t, unkeyed, "keyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000), pilosa.OptFieldKeys())
cmd.MustCreateField(t, unkeyed, "unkeyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000))
c := MustNewClient(host, pilosa.GetHTTPClient(nil))
data := []pilosa.Bit{
@ -207,7 +203,7 @@ func TestClient_Export(t *testing.T) {
t.Run("Export unkeyed,unkeyedf", func(t *testing.T) {
// Populate data.
for _, bit := range data {
_, err := c.Query(context.Background(), "unkeyed", &pilosa.QueryRequest{
_, err := c.Query(context.Background(), unkeyed, &pilosa.QueryRequest{
Query: fmt.Sprintf(`Set(%d, unkeyedf=%d)`, bit.ColumnID, bit.RowID),
Remote: false,
})
@ -220,7 +216,7 @@ func TestClient_Export(t *testing.T) {
bw := bufio.NewWriter(buf)
// Send export request.
if err := c.ExportCSV(context.Background(), "unkeyed", "unkeyedf", 0, bw); err != nil {
if err := c.ExportCSV(context.Background(), unkeyed, "unkeyedf", 0, bw); err != nil {
t.Fatal(err)
}
@ -241,7 +237,7 @@ func TestClient_Export(t *testing.T) {
t.Run("Export unkeyed,keyedf", func(t *testing.T) {
// Populate data.
for _, bit := range data {
_, err := c.Query(context.Background(), "unkeyed", &pilosa.QueryRequest{
_, err := c.Query(context.Background(), unkeyed, &pilosa.QueryRequest{
Query: fmt.Sprintf(`Set(%d, keyedf=%s)`, bit.ColumnID, bit.RowKey),
Remote: false,
})
@ -254,7 +250,7 @@ func TestClient_Export(t *testing.T) {
bw := bufio.NewWriter(buf)
// Send export request.
if err := c.ExportCSV(context.Background(), "unkeyed", "keyedf", 0, bw); err != nil {
if err := c.ExportCSV(context.Background(), unkeyed, "keyedf", 0, bw); err != nil {
t.Fatal(err)
}
@ -275,7 +271,7 @@ func TestClient_Export(t *testing.T) {
t.Run("Export keyed,unkeyedf", func(t *testing.T) {
// Populate data.
for _, bit := range data {
_, err := c.Query(context.Background(), "keyed", &pilosa.QueryRequest{
_, err := c.Query(context.Background(), keyed, &pilosa.QueryRequest{
Query: fmt.Sprintf(`Set("%s", unkeyedf=%d)`, bit.ColumnKey, bit.RowID),
Remote: false,
})
@ -289,34 +285,35 @@ func TestClient_Export(t *testing.T) {
// Send export request for every partition.
for i := 0; i < disco.DefaultPartitionN; i++ {
if err := c.ExportCSV(context.Background(), "keyed", "unkeyedf", uint64(i), bw); err != nil {
if err := c.ExportCSV(context.Background(), keyed, "unkeyedf", uint64(i), bw); err != nil {
t.Fatal(err)
}
}
got := buf.String()
gotSlice := strings.Split(got, "\n")
// Expected output is not sorted because of key sharding.
exp := "" +
"2,col200\n" +
"2,col201\n" +
"2,col202\n" +
"2,col203\n" +
"1,col103\n" +
"1,col102\n" +
"1,col101\n" +
"1,col100\n"
// Verify data.
if got != exp {
t.Fatalf("unexpected export data: %q, expected %q", got, exp)
expSlice := []string{
"1,col103",
"1,col102",
"1,col101",
"1,col100",
"2,col200",
"2,col201",
"2,col202",
"2,col203",
"",
}
if !sameStringSlice(gotSlice, expSlice) {
t.Fatalf("unexpected results: %q", gotSlice)
}
})
t.Run("Export keyed,keyedf", func(t *testing.T) {
// Populate data.
for _, bit := range data {
_, err := c.Query(context.Background(), "keyed", &pilosa.QueryRequest{
_, err := c.Query(context.Background(), keyed, &pilosa.QueryRequest{
Query: fmt.Sprintf(`Set("%s", keyedf=%s)`, bit.ColumnKey, bit.RowKey),
Remote: false,
})
@ -330,27 +327,28 @@ func TestClient_Export(t *testing.T) {
// Send export request.
for i := 0; i < disco.DefaultPartitionN; i++ {
if err := c.ExportCSV(context.Background(), "keyed", "keyedf", uint64(i), bw); err != nil {
if err := c.ExportCSV(context.Background(), keyed, "keyedf", uint64(i), bw); err != nil {
t.Fatal(err)
}
}
got := buf.String()
gotSlice := strings.Split(got, "\n")
// Expected output is unsorted because of key sharding.
exp := "" +
"row2,col200\n" +
"row2,col201\n" +
"row2,col202\n" +
"row2,col203\n" +
"row1,col103\n" +
"row1,col102\n" +
"row1,col101\n" +
"row1,col100\n"
// Verify data.
if got != exp {
t.Fatalf("unexpected export data: %q, expected %q", got, exp)
// Expected output is not sorted because of key sharding.
expSlice := []string{
"row1,col103",
"row1,col102",
"row1,col101",
"row1,col100",
"row2,col200",
"row2,col201",
"row2,col202",
"row2,col203",
"",
}
if !sameStringSlice(gotSlice, expSlice) {
t.Fatalf("unexpected results: %q", gotSlice)
}
})
}
@ -363,16 +361,18 @@ func TestClient_Import(t *testing.T) {
cmd := cluster.GetNode(0)
host := cmd.URL()
api := cmd.API
keyed := cluster.Idx("k")
unkeyed := cluster.Idx("u")
cmd.MustCreateIndex(t, "keyed", pilosa.IndexOptions{Keys: true})
cmd.MustCreateIndex(t, "unkeyed", pilosa.IndexOptions{Keys: false})
cmd.MustCreateIndex(t, keyed, pilosa.IndexOptions{Keys: true})
cmd.MustCreateIndex(t, unkeyed, pilosa.IndexOptions{Keys: false})
cmd.MustCreateField(t, "keyed", "keyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeNone, 0), pilosa.OptFieldKeys())
cmd.MustCreateField(t, "keyed", "unkeyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeNone, 0))
cmd.MustCreateField(t, "unkeyed", "keyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeNone, 0), pilosa.OptFieldKeys())
cmd.MustCreateField(t, "unkeyed", "unkeyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeNone, 0))
cmd.MustCreateField(t, keyed, "keyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeNone, 0), pilosa.OptFieldKeys())
cmd.MustCreateField(t, keyed, "unkeyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeNone, 0))
cmd.MustCreateField(t, unkeyed, "keyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeNone, 0), pilosa.OptFieldKeys())
cmd.MustCreateField(t, unkeyed, "unkeyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeNone, 0))
indexes := map[bool]string{true: "keyed", false: "unkeyed"}
indexes := map[bool]string{true: keyed, false: unkeyed}
fields := map[bool]string{true: "keyedf", false: "unkeyedf"}
recKeys := []string{"rec-a", "rec-b", "rec-c"}
@ -427,13 +427,13 @@ func TestClient_Import(t *testing.T) {
}
}
for keyed, indexName := range indexes {
for useKeys, indexName := range indexes {
for _, fieldName := range fields {
req := pilosa.ImportRequest{
Index: indexName,
Field: fieldName,
}
if indexName == "keyed" {
if indexName == keyed {
req.ColumnKeys = recKeys
req.Shard = ^uint64(0)
} else {
@ -462,14 +462,14 @@ func TestClient_Import(t *testing.T) {
if err != nil {
t.Fatal(err)
}
checkResults(results, keyed, 3)
checkResults(results, useKeys, 3)
// Now clear a bit...
req = pilosa.ImportRequest{
Index: indexName,
Field: fieldName,
}
if indexName == "keyed" {
if indexName == keyed {
req.ColumnKeys = recKeys[2:]
req.Shard = ^uint64(0)
} else {
@ -504,32 +504,33 @@ func TestClient_Import(t *testing.T) {
if err != nil {
t.Fatal(err)
}
checkResults(results, keyed, 2)
checkResults(results, useKeys, 2)
}
}
}
// Ensure client can bulk import data.
func TestClient_ImportRoaring(t *testing.T) {
cluster := test.MustRunCluster(t, 3,
[]server.CommandOption{
server.OptCommandServerOptions(pilosa.OptServerReplicaN(3))},
[]server.CommandOption{
server.OptCommandServerOptions(pilosa.OptServerReplicaN(3))},
[]server.CommandOption{
server.OptCommandServerOptions(pilosa.OptServerReplicaN(3))},
)
cluster := test.MustUnsharedCluster(t, 3)
// Unshared because we want to set ReplicaN = 3 so we can verify data present on all nodes
for _, c := range cluster.Nodes {
c.Config.Cluster.ReplicaN = 3
}
err := cluster.Start()
if err != nil {
t.Fatalf("starting cluster: %v", err)
}
defer cluster.Close()
_, err := cluster.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
_, err = cluster.GetNode(0).API.CreateIndex(context.Background(), cluster.Idx(), pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, err = cluster.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 100))
_, err = cluster.GetNode(0).API.CreateField(context.Background(), cluster.Idx(), "f", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 100))
if err != nil {
t.Fatalf("creating field: %v", err)
}
_, err = cluster.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: "Set(0, f=1)"})
_, err = cluster.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: cluster.Idx(), Query: "Set(0, f=1)"})
if err != nil {
t.Fatalf("querying: %v", err)
}
@ -539,101 +540,101 @@ func TestClient_ImportRoaring(t *testing.T) {
c := MustNewClient(host, pilosa.GetHTTPClient(nil))
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65537]
roaringReq := makeImportRoaringRequest(false, "3B3001000100000900010000000100010009000100")
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, "i", "f", 0, false, roaringReq); err != nil {
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, cluster.Idx(), "f", 0, false, roaringReq); err != nil {
t.Fatal(err)
}
hldr := test.Holder{Holder: cluster.GetNode(0).Server.Holder()}
// Verify data on node 0.
if a := hldr.Row("i", "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65537}) {
if a := hldr.Row(cluster.Idx(), "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65537}) {
t.Fatalf("unexpected columns: %+v", a)
}
if a := hldr.Row("i", "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
if a := hldr.Row(cluster.Idx(), "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
t.Fatalf("unexpected columns: %+v", a)
}
hldr2 := test.Holder{Holder: cluster.GetNode(1).Server.Holder()}
// Verify data on node 1.
if a := hldr2.Row("i", "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65537}) {
if a := hldr2.Row(cluster.Idx(), "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65537}) {
t.Fatalf("unexpected columns: %+v", a)
}
if a := hldr2.Row("i", "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
if a := hldr2.Row(cluster.Idx(), "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
t.Fatalf("unexpected columns: %+v", a)
}
// Ensure that sending a roaring import with the clear flag works as expected.
// [65539, 65540]
roaringReq = makeImportRoaringRequest(true, "3A30000001000000010001001000000003000400")
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, "i", "f", 0, false, roaringReq); err != nil {
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, cluster.Idx(), "f", 0, false, roaringReq); err != nil {
t.Fatal(err)
}
// Verify data on node 0.
if a := hldr.Row("i", "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65537}) {
if a := hldr.Row(cluster.Idx(), "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65537}) {
t.Fatalf("unexpected clear columns: %+v", a)
}
if a := hldr.Row("i", "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
if a := hldr.Row(cluster.Idx(), "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
t.Fatalf("unexpected clear columns: %+v", a)
}
// Verify data on node 1.
if a := hldr2.Row("i", "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65537}) {
if a := hldr2.Row(cluster.Idx(), "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65537}) {
t.Fatalf("unexpected clear columns: %+v", a)
}
if a := hldr2.Row("i", "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
if a := hldr2.Row(cluster.Idx(), "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
t.Fatalf("unexpected clear columns: %+v", a)
}
// Ensure that sending a roaring import with the clear flag works as expected.
// [4, 6, 65537, 65539]
roaringReq = makeImportRoaringRequest(true, "3A300000020000000000010001000100180000001C0000000400060001000300")
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, "i", "f", 0, false, roaringReq); err != nil {
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, cluster.Idx(), "f", 0, false, roaringReq); err != nil {
t.Fatal(err)
}
// Verify data on node 0.
if a := hldr.Row("i", "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3, 5, 7, 8, 9, 10}) {
if a := hldr.Row(cluster.Idx(), "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3, 5, 7, 8, 9, 10}) {
t.Fatalf("unexpected clear columns: %+v", a)
}
if a := hldr.Row("i", "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
if a := hldr.Row(cluster.Idx(), "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
t.Fatalf("unexpected clear columns: %+v", a)
}
// Verify data on node 1.
if a := hldr2.Row("i", "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3, 5, 7, 8, 9, 10}) {
if a := hldr2.Row(cluster.Idx(), "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{1, 2, 3, 5, 7, 8, 9, 10}) {
t.Fatalf("unexpected clear columns: %+v", a)
}
if a := hldr2.Row("i", "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
if a := hldr2.Row(cluster.Idx(), "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
t.Fatalf("unexpected clear columns: %+v", a)
}
// Ensure that sending a roaring import with the clear flag works as expected.
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65537]
roaringReq = makeImportRoaringRequest(true, "3B3001000100000900010000000100010009000100")
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, "i", "f", 0, false, roaringReq); err != nil {
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, cluster.Idx(), "f", 0, false, roaringReq); err != nil {
t.Fatal(err)
}
// Verify data on node 0.
if a := hldr.Row("i", "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{}) {
if a := hldr.Row(cluster.Idx(), "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{}) {
t.Fatalf("unexpected clear columns: %+v", a)
}
if a := hldr.Row("i", "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
if a := hldr.Row(cluster.Idx(), "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
t.Fatalf("unexpected clear columns: %+v", a)
}
// Verify data on node 1.
if a := hldr2.Row("i", "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{}) {
if a := hldr2.Row(cluster.Idx(), "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{}) {
t.Fatalf("unexpected clear columns: %+v", a)
}
if a := hldr2.Row("i", "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
if a := hldr2.Row(cluster.Idx(), "f", 1).Columns(); !reflect.DeepEqual(a, []uint64{0}) {
t.Fatalf("unexpected clear columns: %+v", a)
}
}
// Ensure client can bulk import data with multiple views and not deadlock.
func TestClient_ImportRoaring_MultiView(t *testing.T) {
cluster := test.MustNewCluster(t, 2)
cluster := test.MustUnsharedCluster(t, 2)
for _, c := range cluster.Nodes {
c.Config.Cluster.ReplicaN = 2
}
@ -645,15 +646,15 @@ func TestClient_ImportRoaring_MultiView(t *testing.T) {
api := cluster.GetNode(0).API
_, err = api.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
_, err = api.CreateIndex(context.Background(), cluster.Idx(), pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, err = api.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 100))
_, err = api.CreateField(context.Background(), cluster.Idx(), "f", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 100))
if err != nil {
t.Fatalf("creating field: %v", err)
}
_, err = api.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: "Set(0, f=1)"})
_, err = api.Query(context.Background(), &pilosa.QueryRequest{Index: cluster.Idx(), Query: "Set(0, f=1)"})
if err != nil {
t.Fatalf("querying: %v", err)
}
@ -664,7 +665,7 @@ func TestClient_ImportRoaring_MultiView(t *testing.T) {
req := &pilosa.ImportRoaringRequest{Views: map[string][]byte{}}
req.Views["a"], _ = hex.DecodeString("3B3001000100000900010000000100010009000100")
req.Views["b"], _ = hex.DecodeString("3B3001000100000900010000000100010009000100")
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, "i", "f", 0, false, req); err != nil {
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, cluster.Idx(), "f", 0, false, req); err != nil {
t.Fatal(err)
}
}
@ -676,18 +677,20 @@ func TestClient_ImportKeys(t *testing.T) {
defer cluster.Close()
cmd := cluster.GetNode(0)
host := cmd.URL()
keyed := cluster.Idx("k")
unkeyed := cluster.Idx("u")
cmd.MustCreateIndex(t, "keyed", pilosa.IndexOptions{Keys: true})
cmd.MustCreateIndex(t, "unkeyed", pilosa.IndexOptions{Keys: false})
cmd.MustCreateIndex(t, keyed, pilosa.IndexOptions{Keys: true})
cmd.MustCreateIndex(t, unkeyed, pilosa.IndexOptions{Keys: false})
cmd.MustCreateField(t, "keyed", "keyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000), pilosa.OptFieldKeys())
cmd.MustCreateField(t, "keyed", "unkeyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000))
cmd.MustCreateField(t, "unkeyed", "keyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000), pilosa.OptFieldKeys())
cmd.MustCreateField(t, keyed, "keyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000), pilosa.OptFieldKeys())
cmd.MustCreateField(t, keyed, "unkeyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000))
cmd.MustCreateField(t, unkeyed, "keyedf", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000), pilosa.OptFieldKeys())
// Send import request.
c := MustNewClient(host, pilosa.GetHTTPClient(nil))
baseReq := &pilosa.ImportRequest{
Index: "keyed",
Index: keyed,
Field: "keyedf",
ColumnKeys: []string{"eve", "alice", "bob", "eve", "alice", "eve"},
ColumnIDs: []uint64{1, 2, 3, 1, 2, 1},
@ -697,14 +700,14 @@ func TestClient_ImportKeys(t *testing.T) {
t.Run("Import keyed,keyed", func(t *testing.T) {
req := baseReq.Clone()
req.Index = "keyed"
req.Index = keyed
req.Field = "keyedf"
req.ColumnIDs, req.RowIDs = nil, nil
if err := c.Import(context.Background(), nil, req, &pilosa.ImportOptions{}); err != nil {
t.Fatal(err)
}
resp := cmd.QueryAPI(t, &pilosa.QueryRequest{
Index: "keyed",
Index: keyed,
Query: "TopN(keyedf)",
})
if pairs, ok := resp.Results[0].(*pilosa.PairsField); !ok {
@ -720,14 +723,14 @@ func TestClient_ImportKeys(t *testing.T) {
t.Run("Import keyed,unkeyedf", func(t *testing.T) {
req := baseReq.Clone()
req.Index = "keyed"
req.Index = keyed
req.Field = "unkeyedf"
req.ColumnIDs, req.RowKeys = nil, nil
if err := c.Import(context.Background(), nil, req, &pilosa.ImportOptions{}); err != nil {
t.Fatal(err)
}
resp := cmd.QueryAPI(t, &pilosa.QueryRequest{
Index: "keyed",
Index: keyed,
Query: "TopN(unkeyedf)",
})
if pairs, ok := resp.Results[0].(*pilosa.PairsField); !ok {
@ -743,14 +746,14 @@ func TestClient_ImportKeys(t *testing.T) {
t.Run("Import unkeyed,keyed", func(t *testing.T) {
req := baseReq.Clone()
req.Index = "unkeyed"
req.Index = unkeyed
req.Field = "keyedf"
req.ColumnKeys, req.RowIDs = nil, nil
if err := c.Import(context.Background(), nil, req, &pilosa.ImportOptions{}); err != nil {
t.Fatal(err)
}
resp := cmd.QueryAPI(t, &pilosa.QueryRequest{
Index: "unkeyed",
Index: unkeyed,
Query: "TopN(keyedf)",
})
if pairs, ok := resp.Results[0].(*pilosa.PairsField); !ok {
@ -772,10 +775,11 @@ func TestClient_ImportKeys(t *testing.T) {
cmd1 := cluster.GetNode(1)
host0 := cmd0.URL()
host1 := cmd1.URL()
keyed := cluster.Idx("k")
cmd0.MustCreateIndex(t, "keyed", pilosa.IndexOptions{Keys: true})
cmd0.MustCreateField(t, "keyed", "keyedf0", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000), pilosa.OptFieldKeys())
cmd0.MustCreateField(t, "keyed", "keyedf1", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000), pilosa.OptFieldKeys())
cmd0.MustCreateIndex(t, keyed, pilosa.IndexOptions{Keys: true})
cmd0.MustCreateField(t, keyed, "keyedf0", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000), pilosa.OptFieldKeys())
cmd0.MustCreateField(t, keyed, "keyedf1", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000), pilosa.OptFieldKeys())
// Send import request.
c0 := MustNewClient(host0, pilosa.GetHTTPClient(nil))
@ -784,7 +788,7 @@ func TestClient_ImportKeys(t *testing.T) {
// Import to node0.
t.Run("Import node0", func(t *testing.T) {
req := &pilosa.ImportRequest{
Index: "keyed",
Index: keyed,
Field: "keyedf0",
ColumnKeys: []string{"eve", "alice", "bob", "eve", "alice", "eve"},
RowKeys: []string{"green", "green", "green", "blue", "blue", "purple"},
@ -793,7 +797,7 @@ func TestClient_ImportKeys(t *testing.T) {
t.Fatal(err)
}
resp := cmd0.QueryAPI(t, &pilosa.QueryRequest{
Index: "keyed",
Index: keyed,
Query: "TopN(keyedf0)",
})
if pairs, ok := resp.Results[0].(*pilosa.PairsField); !ok {
@ -810,7 +814,7 @@ func TestClient_ImportKeys(t *testing.T) {
// Import to node1 (ensure import is routed to primary for translation).
t.Run("Import node1", func(t *testing.T) {
req := &pilosa.ImportRequest{
Index: "keyed",
Index: keyed,
Field: "keyedf1",
ColumnKeys: []string{"eve", "alice", "bob", "eve", "alice", "eve"},
RowKeys: []string{"green", "green", "green", "blue", "blue", "purple"},
@ -823,7 +827,7 @@ func TestClient_ImportKeys(t *testing.T) {
time.Sleep(500 * time.Millisecond)
resp := cmd1.QueryAPI(t, &pilosa.QueryRequest{
Index: "keyed",
Index: keyed,
Query: "TopN(keyedf1)",
})
if pairs, ok := resp.Results[0].(*pilosa.PairsField); !ok {
@ -849,7 +853,7 @@ func TestClient_ImportKeys(t *testing.T) {
fldName := "f"
// Load bitmap into cache to ensure cache gets updated.
index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{Keys: true})
index := hldr.MustCreateIndexIfNotExists(cluster.Idx(), pilosa.IndexOptions{Keys: true})
_, err := index.CreateFieldIfNotExists(fldName, pilosa.OptFieldTypeInt(-100, 100))
if err != nil {
t.Fatal(err)
@ -858,7 +862,7 @@ func TestClient_ImportKeys(t *testing.T) {
// Send import request.
c := MustNewClient(host, pilosa.GetHTTPClient(nil))
req := &pilosa.ImportValueRequest{
Index: "i",
Index: cluster.Idx(),
Field: "f",
ColumnKeys: []string{"col1", "col2", "col3"},
Values: []int64{-10, 20, 40},
@ -873,7 +877,7 @@ func TestClient_ImportKeys(t *testing.T) {
Remote: false,
}
result, err := c.Query(context.Background(), "i", queryRequest)
result, err := c.Query(context.Background(), cluster.Idx(), queryRequest)
if err != nil {
t.Fatal(err)
}
@ -884,7 +888,7 @@ func TestClient_ImportKeys(t *testing.T) {
// Clear data.
req = &pilosa.ImportValueRequest{
Index: "i",
Index: cluster.Idx(),
Field: "f",
ColumnKeys: []string{"col2"},
Values: []int64{20},
@ -899,7 +903,7 @@ func TestClient_ImportKeys(t *testing.T) {
Remote: false,
}
result, err = c.Query(context.Background(), "i", queryRequest)
result, err = c.Query(context.Background(), cluster.Idx(), queryRequest)
if err != nil {
t.Fatal(err)
}
@ -924,7 +928,7 @@ func TestClient_ImportIDs(t *testing.T) {
holder := cmd.Server.Holder()
hldr := test.Holder{Holder: holder}
idxName := "i"
idxName := cluster.Idx()
fldName := "f"
// Load bitmap into cache to ensure cache gets updated.
@ -996,7 +1000,7 @@ func TestClient_ImportValue(t *testing.T) {
fldName := "f"
// Load bitmap into cache to ensure cache gets updated.
index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{})
index := hldr.MustCreateIndexIfNotExists(cluster.Idx(), pilosa.IndexOptions{})
_, err := index.CreateFieldIfNotExists(fldName, pilosa.OptFieldTypeInt(-100, 100))
if err != nil {
t.Fatal(err)
@ -1005,7 +1009,7 @@ func TestClient_ImportValue(t *testing.T) {
// Send import request.
c := MustNewClient(host, pilosa.GetHTTPClient(nil))
req := &pilosa.ImportValueRequest{
Index: "i",
Index: cluster.Idx(),
Field: "f",
ColumnIDs: []uint64{1, 2, 3},
Values: []int64{-10, 20, 40},
@ -1015,7 +1019,7 @@ func TestClient_ImportValue(t *testing.T) {
}
// Verify Sum.
if resp, err := c.Query(context.Background(), "i", &pilosa.QueryRequest{Query: `Sum(field=f)`}); err != nil {
if resp, err := c.Query(context.Background(), cluster.Idx(), &pilosa.QueryRequest{Query: `Sum(field=f)`}); err != nil {
t.Fatal(err)
} else if vc, ok := resp.Results[0].(pilosa.ValCount); !ok {
t.Fatalf("expected ValCount; got %T", resp.Results[0])
@ -1024,7 +1028,7 @@ func TestClient_ImportValue(t *testing.T) {
}
// Verify Max.
if resp, err := c.Query(context.Background(), "i", &pilosa.QueryRequest{Query: `Max(field=f)`}); err != nil {
if resp, err := c.Query(context.Background(), cluster.Idx(), &pilosa.QueryRequest{Query: `Max(field=f)`}); err != nil {
t.Fatal(err)
} else if vc, ok := resp.Results[0].(pilosa.ValCount); !ok {
t.Fatalf("expected ValCount; got %T", resp.Results[0])
@ -1037,14 +1041,14 @@ func TestClient_ImportValue(t *testing.T) {
if err != nil {
t.Fatal(err)
}
preIUsage, err := c.GetIndexUsage(context.Background(), "i")
preIUsage, err := c.GetIndexUsage(context.Background(), cluster.Idx())
if err != nil {
t.Fatal(err)
}
// Send import request.
req = &pilosa.ImportValueRequest{
Index: "i",
Index: cluster.Idx(),
Field: "f",
ColumnIDs: []uint64{1, 3},
Values: []int64{-10, 40},
@ -1058,7 +1062,7 @@ func TestClient_ImportValue(t *testing.T) {
if err != nil {
t.Fatal(err)
}
postIUsage, err := c.GetIndexUsage(context.Background(), "i")
postIUsage, err := c.GetIndexUsage(context.Background(), cluster.Idx())
if err != nil {
t.Fatal(err)
}
@ -1076,7 +1080,7 @@ func TestClient_ImportValue(t *testing.T) {
}
// Verify Sum.
if resp, err := c.Query(context.Background(), "i", &pilosa.QueryRequest{Query: `Sum(field=f)`}); err != nil {
if resp, err := c.Query(context.Background(), cluster.Idx(), &pilosa.QueryRequest{Query: `Sum(field=f)`}); err != nil {
t.Fatal(err)
} else if vc, ok := resp.Results[0].(pilosa.ValCount); !ok {
t.Fatalf("expected ValCount; got %T", resp.Results[0])
@ -1085,7 +1089,7 @@ func TestClient_ImportValue(t *testing.T) {
}
// Verify Max.
if resp, err := c.Query(context.Background(), "i", &pilosa.QueryRequest{Query: `Max(field=f)`}); err != nil {
if resp, err := c.Query(context.Background(), cluster.Idx(), &pilosa.QueryRequest{Query: `Max(field=f)`}); err != nil {
t.Fatal(err)
} else if vc, ok := resp.Results[0].(pilosa.ValCount); !ok {
t.Fatalf("expected ValCount; got %T", resp.Results[0])
@ -1104,7 +1108,7 @@ func TestClient_ImportExistence(t *testing.T) {
hldr := test.Holder{Holder: holder}
t.Run("Set", func(t *testing.T) {
idxName := "iset"
idxName := cluster.Idx("s")
fldName := "fset"
index := hldr.MustCreateIndexIfNotExists(idxName, pilosa.IndexOptions{TrackExistence: true})
@ -1116,7 +1120,7 @@ func TestClient_ImportExistence(t *testing.T) {
// Send import request.
c := MustNewClient(host, pilosa.GetHTTPClient(nil))
req := &pilosa.ImportRequest{
Index: "iset",
Index: cluster.Idx("s"),
Field: "fset",
ColumnIDs: []uint64{1, 5, 6},
RowIDs: []uint64{0, 0, 200},
@ -1140,7 +1144,7 @@ func TestClient_ImportExistence(t *testing.T) {
})
t.Run("Int", func(t *testing.T) {
idxName := "iint"
idxName := cluster.Idx("i")
fldName := "fint"
index := hldr.MustCreateIndexIfNotExists(idxName, pilosa.IndexOptions{TrackExistence: true})
@ -1152,7 +1156,7 @@ func TestClient_ImportExistence(t *testing.T) {
// Send import request.
c := MustNewClient(host, pilosa.GetHTTPClient(nil))
req := &pilosa.ImportValueRequest{
Index: "iint",
Index: cluster.Idx("i"),
Field: "fint",
ColumnIDs: []uint64{1, 2, 3},
Values: []int64{-10, 20, 40},
@ -1186,13 +1190,13 @@ func TestClient_FragmentBlocks(t *testing.T) {
holder := cmd.Server.Holder()
hldr := test.Holder{Holder: holder}
hldr.SetBit("i", "f", 0, 1)
hldr.SetBit("i", "f", pilosa.HashBlockSize*3, 100)
hldr.SetBit(cluster.Idx(), "f", 0, 1)
hldr.SetBit(cluster.Idx(), "f", pilosa.HashBlockSize*3, 100)
// Set a bit on a different shard.
hldr.SetBit("i", "f", 0, 1)
hldr.SetBit(cluster.Idx(), "f", 0, 1)
c := MustNewClient(cmd.URL(), pilosa.GetHTTPClient(nil))
blocks, err := c.FragmentBlocks(context.Background(), nil, "i", "f", "standard", 0)
blocks, err := c.FragmentBlocks(context.Background(), nil, cluster.Idx(), "f", "standard", 0)
if err != nil {
t.Fatal(err)
} else if len(blocks) != 2 {
@ -1204,7 +1208,7 @@ func TestClient_FragmentBlocks(t *testing.T) {
}
// Verify data matches local blocks.
if a, err := cmd.API.FragmentBlocks(context.Background(), "i", "f", "standard", 0); err != nil {
if a, err := cmd.API.FragmentBlocks(context.Background(), cluster.Idx(), "f", "standard", 0); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(a, blocks) {
t.Fatalf("blocks mismatch:\n\nexp=%s\n\ngot=%s\n\n", spew.Sdump(a), spew.Sdump(blocks))
@ -1238,7 +1242,7 @@ func TestClient_CreateTimeField(t *testing.T) {
c := MustNewClient(cmd.URL(), pilosa.GetHTTPClient(nil))
index := "cdf"
index := cluster.Idx()
err := c.CreateIndex(context.Background(), index, pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index: %v", err)
@ -1278,7 +1282,7 @@ func TestClient_CreateDecimalField(t *testing.T) {
c := MustNewClient(cmd.URL(), pilosa.GetHTTPClient(nil))
index := "cdf"
index := cluster.Idx()
err := c.CreateIndex(context.Background(), index, pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index: %v", err)
@ -1575,19 +1579,15 @@ func TestClient_ServerInfoHasBackend(t *testing.T) {
pilosa.MustBackendToTxtype(si.StorageBackend) // panics if invalid
}
func TestClient_ImportRoaringExists(t *testing.T) {
cluster := test.MustNewCluster(t, 1)
err := cluster.Start()
if err != nil {
t.Fatalf("starting cluster: %v", err)
}
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
node := cluster.GetNode(0)
_, err = node.API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{TrackExistence: true})
_, err := node.API.CreateIndex(context.Background(), cluster.Idx(), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, err = node.API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 100))
_, err = node.API.CreateField(context.Background(), cluster.Idx(), "f", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 100))
if err != nil {
t.Fatalf("creating field: %v", err)
}
@ -1597,10 +1597,10 @@ func TestClient_ImportRoaringExists(t *testing.T) {
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65537]
roaringReq := makeImportRoaringRequest(false, "3B3001000100000900010000000100010009000100")
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, "i", "f", 0, false, roaringReq); err != nil {
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, cluster.Idx(), "f", 0, false, roaringReq); err != nil {
t.Fatal(err)
}
qr, err := node.API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: "All()"})
qr, err := node.API.Query(context.Background(), &pilosa.QueryRequest{Index: cluster.Idx(), Query: "All()"})
if err != nil {
t.Fatalf(" %v ", err)
}
@ -1609,12 +1609,12 @@ func TestClient_ImportRoaringExists(t *testing.T) {
t.Fatalf(" Row unexpected columns: got %+v expected: %+v", got, []uint64{})
}
roaringReq.UpdateExistence = true
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, "i", "f", 0, false, roaringReq); err != nil {
if err := c.ImportRoaring(context.Background(), &cluster.GetNode(0).API.Node().URI, cluster.Idx(), "f", 0, false, roaringReq); err != nil {
t.Fatal(err)
}
expected := []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65537}
qr, err = node.API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: "All()"})
qr, err = node.API.Query(context.Background(), &pilosa.QueryRequest{Index: cluster.Idx(), Query: "All()"})
if err != nil {
t.Fatalf("Query error: %+v", err)
}

View file

@ -27,7 +27,7 @@ import (
)
func TestHandler_PostSchemaCluster(t *testing.T) {
cluster := test.MustRunCluster(t, 3)
cluster := test.MustRunUnsharedCluster(t, 3)
defer cluster.Close()
cmd := cluster.GetNode(0)
h := cmd.Handler.(*pilosa.Handler).Handler
@ -66,7 +66,8 @@ func TestHandler_PostSchemaCluster(t *testing.T) {
}
func TestHandler_Endpoints(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
// this test is full of hardcoded indexes and things
cluster := test.MustRunUnsharedCluster(t, 1)
defer cluster.Close()
cmd := cluster.GetNode(0)
h := cmd.Handler.(*pilosa.Handler).Handler
@ -175,11 +176,8 @@ func TestHandler_Endpoints(t *testing.T) {
i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{})
const shard = 0
tx0, err := holder.BeginTx(true, i0.Index, shard)
if err != nil {
t.Fatal(err)
}
defer tx0.Rollback()
tx0 := holder.Txf().NewWritableQcx()
defer tx0.Abort()
if f, err := i0.CreateFieldIfNotExists("f1", pilosa.OptFieldTypeDefault()); err != nil {
t.Fatal(err)
} else if _, err := f.SetBit(tx0, 0, 0, nil); err != nil {
@ -188,22 +186,19 @@ func TestHandler_Endpoints(t *testing.T) {
if _, err := i0.CreateFieldIfNotExists("f0", pilosa.OptFieldTypeDefault()); err != nil {
t.Fatal(err)
}
if err := tx0.Commit(); err != nil {
if err := tx0.Finish(); err != nil {
t.Fatal(err)
}
i1 := hldr.MustCreateIndexIfNotExists("i1", pilosa.IndexOptions{})
tx1, err := holder.BeginTx(true, i1.Index, shard)
if err != nil {
t.Fatal(err)
}
defer tx1.Rollback()
tx1 := holder.Txf().NewWritableQcx()
defer tx1.Abort()
if f, err := i1.CreateFieldIfNotExists("f0", pilosa.OptFieldTypeDefault()); err != nil {
t.Fatal(err)
} else if _, err := f.SetBit(tx1, 0, 0, nil); err != nil {
t.Fatal(err)
}
if err := tx1.Commit(); err != nil {
if err := tx1.Finish(); err != nil {
t.Fatal(err)
}
@ -241,11 +236,8 @@ func TestHandler_Endpoints(t *testing.T) {
// i2 is for SchemaDetails
i2 := hldr.MustCreateIndexIfNotExists("i2", pilosa.IndexOptions{})
tx2, err := holder.BeginTx(true, i2.Index, shard)
if err != nil {
t.Fatal(err)
}
defer tx2.Rollback()
tx2 := holder.Txf().NewWritableQcx()
defer tx2.Abort()
if f, err := i2.CreateFieldIfNotExists("f0", pilosa.OptFieldTypeSet(pilosa.CacheTypeRanked, 1000)); err != nil {
t.Fatal(err)
} else if _, err := f.SetBit(tx2, 0, 0, nil); err != nil {
@ -290,7 +282,7 @@ func TestHandler_Endpoints(t *testing.T) {
t.Fatal(err)
}
if err := tx2.Commit(); err != nil {
if err := tx2.Finish(); err != nil {
t.Fatal(err)
}

View file

@ -236,14 +236,14 @@ func TestConcurrentFieldCreation(t *testing.T) {
}
api0 := node0.API
if _, err := api0.CreateIndex(context.Background(), "i", pilosa.IndexOptions{}); err != nil {
if _, err := api0.CreateIndex(context.Background(), cluster.Idx(), pilosa.IndexOptions{}); err != nil {
t.Fatalf("creating index: %v", err)
}
eg := errgroup.Group{}
for i := 0; i < 100; i++ {
i := i
eg.Go(func() error {
if _, err := api0.CreateField(context.Background(), "i", fmt.Sprintf("f%d", i)); err != nil {
if _, err := api0.CreateField(context.Background(), cluster.Idx(), fmt.Sprintf("f%d", i)); err != nil {
return err
}
return nil
@ -388,10 +388,10 @@ func TestMain_RecalculateCaches(t *testing.T) {
// Create the schema.
client0 := cluster.GetNode(0).Client()
if err := client0.CreateIndex(context.Background(), "i", pilosa.IndexOptions{}); err != nil && err != pilosa.ErrIndexExists {
if err := client0.CreateIndex(context.Background(), cluster.Idx(), pilosa.IndexOptions{}); err != nil && err != pilosa.ErrIndexExists {
t.Fatal("create index:", err)
}
if err := client0.CreateField(context.Background(), "i", "f"); err != nil {
if err := client0.CreateField(context.Background(), cluster.Idx(), "f"); err != nil {
t.Fatal("create field:", err)
}
@ -402,7 +402,7 @@ func TestMain_RecalculateCaches(t *testing.T) {
data = append(data, fmt.Sprintf(`Set(%d, f=%d)`, columnID, rowID))
}
}
if _, err := cluster.GetNode(0).Query(t, "i", "", strings.Join(data, "")); err != nil {
if _, err := cluster.GetNode(0).Query(t, cluster.Idx(), "", strings.Join(data, "")); err != nil {
t.Fatal("setting columns:", err)
}
@ -416,7 +416,7 @@ func TestMain_RecalculateCaches(t *testing.T) {
// Run a TopN query on all nodes. The result should be the same as the target.
for _, m := range cluster.Nodes {
res, err := m.Query(t, "i", "", `TopN(f)`)
res, err := m.Query(t, cluster.Idx(), "", `TopN(f)`)
if err != nil {
t.Fatal(err)
}
@ -499,7 +499,7 @@ func (p uint64Slice) Len() int { return len(p) }
func (p uint64Slice) Less(i, j int) bool { return p[i] < p[j] }
func TestClusteringNodesReplica1(t *testing.T) {
cluster := test.MustRunCluster(t, 3)
cluster := test.MustRunUnsharedCluster(t, 3)
defer cluster.Close()
if err := cluster.AwaitState(disco.ClusterStateNormal, 100*time.Millisecond); err != nil {
@ -565,7 +565,7 @@ func TestClusteringNodesReplica2(t *testing.T) {
// Because this test shuts down 2 nodes, it needs to start as a 5-node
// cluster in order to retain enough available nodes for raft leader
// election.
cluster := test.MustNewCluster(t, 5)
cluster := test.MustUnsharedCluster(t, 5)
for _, c := range cluster.Nodes {
c.Config.Cluster.ReplicaN = 2
}
@ -920,10 +920,11 @@ func TestClusterMinMaxSumDecimal(t *testing.T) {
defer cluster.Close()
cmd := cluster.GetNode(0)
cmd.MustCreateIndex(t, "testdec", pilosa.IndexOptions{Keys: true, TrackExistence: true})
cmd.MustCreateField(t, "testdec", "adec", pilosa.OptFieldTypeDecimal(2))
index := cluster.Idx("i")
cmd.MustCreateIndex(t, index, pilosa.IndexOptions{Keys: true, TrackExistence: true})
cmd.MustCreateField(t, index, "adec", pilosa.OptFieldTypeDecimal(2))
test.Do(t, "POST", cluster.GetNode(0).URL()+"/index/testdec/query", `
test.Do(t, "POST", fmt.Sprintf("%s/index/%i/query", cmd.URL(), cluster), `
Set("a", adec=42.2)
Set("b", adec=11.12)
Set("c", adec=13.41)
@ -934,21 +935,21 @@ Set("g", adec=15.52)
Set("h", adec=100.22)
`)
result := test.Do(t, "POST", cluster.GetNode(0).URL()+"/index/testdec/query", "Sum(field=adec)")
result := test.Do(t, "POST", fmt.Sprintf("%s/index/%i/query", cmd.URL(), cluster), "Sum(field=adec)")
if !strings.Contains(result.Body, `"decimalValue":305.59`) {
t.Fatalf("expected decimal sum of 305.59, but got: '%s'", result.Body)
} else if !strings.Contains(result.Body, `"count":8`) {
t.Fatalf("expected count 8, but got: '%s'", result.Body)
}
result = test.Do(t, "POST", cluster.GetNode(0).URL()+"/index/testdec/query", "Max(field=adec)")
result = test.Do(t, "POST", fmt.Sprintf("%s/index/%i/query", cmd.URL(), cluster), "Max(field=adec)")
if !strings.Contains(result.Body, `"decimalValue":100.22`) {
t.Fatalf("expected decimal max of 100.22, but got: '%s'", result.Body)
} else if !strings.Contains(result.Body, `"count":1`) {
t.Fatalf("expected count 1, but got: '%s'", result.Body)
}
result = test.Do(t, "POST", cluster.GetNode(0).URL()+"/index/testdec/query", "Min(field=adec)")
result = test.Do(t, "POST", fmt.Sprintf("%s/index/%i/query", cmd.URL(), cluster), "Min(field=adec)")
if !strings.Contains(result.Body, `"decimalValue":11.12`) {
t.Fatalf("expected decimal min of 11.12, but got: '%s'", result.Body)
} else if !strings.Contains(result.Body, `"count":1`) {
@ -984,7 +985,8 @@ func TestClusterCreatedAtRace(t *testing.T) {
}
for k := 0; k < iterations; k++ {
t.Run(fmt.Sprintf("run-%d", k), func(t *testing.T) {
cluster := test.MustRunCluster(t, 4)
// needs fresh cluster so the index creations are new
cluster := test.MustRunUnsharedCluster(t, 4)
defer cluster.Close()
for _, com := range cluster.Nodes {
@ -1041,7 +1043,7 @@ func TestClusterCreatedAtRace(t *testing.T) {
}
func TestClusterQueryCountInDegraded(t *testing.T) {
cluster := test.MustNewCluster(t, 3)
cluster := test.MustUnsharedCluster(t, 3)
for _, c := range cluster.Nodes {
c.Config.Cluster.ReplicaN = 2
}

View file

@ -22,7 +22,7 @@ func TestViewsRemovalTTL(t *testing.T) {
// Create a client
client := node.Client()
indexName := "i"
indexName := cluster.Idx()
// Create indexes and field with ttl lasting 24 hours
if err := client.CreateIndex(context.Background(), indexName, pilosa.IndexOptions{TrackExistence: true}); err != nil && err != pilosa.ErrIndexExists {
@ -196,7 +196,7 @@ func TestViewsRemovalStandard(t *testing.T) {
// Create a client
client := node.Client()
indexName := "i"
indexName := cluster.Idx()
// Create indexes and field with ttl lasting 24 hours
if err := client.CreateIndex(context.Background(), indexName, pilosa.IndexOptions{TrackExistence: true}); err != nil && err != pilosa.ErrIndexExists {

View file

@ -7,9 +7,9 @@ import (
"math"
"testing"
pilosa "github.com/featurebasedb/featurebase/v3"
"github.com/featurebasedb/featurebase/v3/sql"
"github.com/featurebasedb/featurebase/v3/test"
pilosa "github.com/molecula/featurebase/v3"
"github.com/molecula/featurebase/v3/sql"
"github.com/molecula/featurebase/v3/test"
"vitess.io/vitess/go/vt/sqlparser"
)
@ -34,6 +34,9 @@ func TestHandler(t *testing.T) {
}
func TestSelectHandler_MapSelect(t *testing.T) {
// it's load-bearing that this is the only cluster in this directory right
// now, because that allows us to hardcode index names and not immediately
// die.
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
api := cluster.GetNode(0).API

View file

@ -22,7 +22,7 @@ func TestPlanner_Show(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
index, err := c.GetHolder(0).CreateIndex("i", pilosa.IndexOptions{TrackExistence: true})
index, err := c.GetHolder(0).CreateIndex(c.Idx("i"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -33,7 +33,7 @@ func TestPlanner_Show(t *testing.T) {
t.Fatal(err)
}
index2, err := c.GetHolder(0).CreateIndex("i2", pilosa.IndexOptions{TrackExistence: false})
index2, err := c.GetHolder(0).CreateIndex(c.Idx("l"), pilosa.IndexOptions{TrackExistence: false})
if err != nil {
t.Fatal(err)
}
@ -65,7 +65,7 @@ func TestPlanner_Show(t *testing.T) {
})
t.Run("ShowColumns", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SHOW COLUMNS FROM i`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SHOW COLUMNS FROM %i`, c))
if err != nil {
t.Fatal(err)
}
@ -94,7 +94,7 @@ func TestPlanner_Show(t *testing.T) {
})
t.Run("ShowColumns2", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SHOW COLUMNS FROM i2`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SHOW COLUMNS FROM %l`, c))
if err != nil {
t.Fatal(err)
}
@ -371,7 +371,7 @@ func TestPlanner_CoverCreateTable(t *testing.T) {
// Ensure the schema field options match the expected options.
for _, fld := range fields {
t.Run(fmt.Sprintf("Field:%s", fld.name), func(t *testing.T) {
// Field `_id` isn't returned from FeatureBase in the schema,
// Field fmt.Sprintf(`_id`, c) isn't returned from FeatureBase in the schema,
// but we do want to validate that its type is used to determine
// whether or not the table is keyed.
if fld.name == "_id" {
@ -532,7 +532,7 @@ func TestPlanner_AlterTable(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
index, err := c.GetHolder(0).CreateIndex("i", pilosa.IndexOptions{TrackExistence: true})
index, err := c.GetHolder(0).CreateIndex(c.Idx("i"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -546,7 +546,7 @@ func TestPlanner_AlterTable(t *testing.T) {
server := c.GetNode(0).Server
t.Run("AlterTableDrop", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, server, `alter table i drop column f`)
results, columns, err := sql_test.MustQueryRows(t, server, fmt.Sprintf(`alter table %i drop column f`, c))
if err != nil {
t.Fatal(err)
}
@ -560,7 +560,7 @@ func TestPlanner_AlterTable(t *testing.T) {
})
t.Run("AlterTableAdd", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, server, `alter table i add column f int`)
results, columns, err := sql_test.MustQueryRows(t, server, fmt.Sprintf(`alter table %i add column f int`, c))
if err != nil {
t.Fatal(err)
}
@ -575,7 +575,7 @@ func TestPlanner_AlterTable(t *testing.T) {
t.Run("AlterTableRename", func(t *testing.T) {
t.Skip("not yet implemented")
results, columns, err := sql_test.MustQueryRows(t, server, `alter table i rename column f to g`)
results, columns, err := sql_test.MustQueryRows(t, server, fmt.Sprintf(`alter table %i rename column f to g`, c))
if err != nil {
t.Fatal(err)
}
@ -593,7 +593,7 @@ func TestPlanner_DropTable(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
index, err := c.GetHolder(0).CreateIndex("i", pilosa.IndexOptions{TrackExistence: true})
index, err := c.GetHolder(0).CreateIndex(c.Idx("i"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -605,7 +605,7 @@ func TestPlanner_DropTable(t *testing.T) {
}
t.Run("DropTable", func(t *testing.T) {
_, _, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `DROP TABLE i`)
_, _, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`DROP TABLE %i`, c))
if err != nil {
t.Fatal(err)
}
@ -616,7 +616,7 @@ func TestPlanner_ExpressionsInSelectListParen(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
i0, err := c.GetHolder(0).CreateIndex("i0", pilosa.IndexOptions{TrackExistence: true})
i0, err := c.GetHolder(0).CreateIndex(c.Idx("j"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -627,7 +627,7 @@ func TestPlanner_ExpressionsInSelectListParen(t *testing.T) {
t.Fatal(err)
}
i1, err := c.GetHolder(0).CreateIndex("i1", pilosa.IndexOptions{TrackExistence: true})
i1, err := c.GetHolder(0).CreateIndex(c.Idx("k"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -640,7 +640,7 @@ func TestPlanner_ExpressionsInSelectListParen(t *testing.T) {
// Populate with data.
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
Index: "i0",
Index: c.Idx("j"),
Query: `
Set(1, a=10)
Set(1, b=100)
@ -651,7 +651,7 @@ func TestPlanner_ExpressionsInSelectListParen(t *testing.T) {
}
t.Run("ParenOne", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT (a != b) = false, _id FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT (a != b) = false, _id FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -672,7 +672,7 @@ func TestPlanner_ExpressionsInSelectListParen(t *testing.T) {
})
t.Run("ParenTwo", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT (a != b) = (false), _id FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT (a != b) = (false), _id FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -697,7 +697,7 @@ func TestPlanner_ExpressionsInSelectListLiterals(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
i0, err := c.GetHolder(0).CreateIndex("i0", pilosa.IndexOptions{TrackExistence: true})
i0, err := c.GetHolder(0).CreateIndex(c.Idx("j"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -716,7 +716,7 @@ func TestPlanner_ExpressionsInSelectListLiterals(t *testing.T) {
// Populate with data.
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
Index: "i0",
Index: c.Idx("j"),
Query: `
Set(1, a=10)
Set(1, b=100)
@ -730,7 +730,7 @@ func TestPlanner_ExpressionsInSelectListLiterals(t *testing.T) {
}
t.Run("LiteralsBool", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT false = true, _id FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT false = true, _id FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -751,7 +751,7 @@ func TestPlanner_ExpressionsInSelectListLiterals(t *testing.T) {
})
t.Run("LiteralsInt", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT 1 + 2, _id FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT 1 + 2, _id FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -772,7 +772,7 @@ func TestPlanner_ExpressionsInSelectListLiterals(t *testing.T) {
})
t.Run("LiteralsID", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT _id + 2, _id FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT _id + 2, _id FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -793,7 +793,7 @@ func TestPlanner_ExpressionsInSelectListLiterals(t *testing.T) {
})
t.Run("LiteralsDecimal", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT d + 2.0, _id FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT d + 2.0, _id FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -814,7 +814,7 @@ func TestPlanner_ExpressionsInSelectListLiterals(t *testing.T) {
})
t.Run("LiteralsString", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT str || ' bar', _id FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT str || ' bar', _id FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -839,7 +839,7 @@ func TestPlanner_ExpressionsInSelectListCase(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
i0, err := c.GetHolder(0).CreateIndex("i0", pilosa.IndexOptions{TrackExistence: true})
i0, err := c.GetHolder(0).CreateIndex(c.Idx("j"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -858,7 +858,7 @@ func TestPlanner_ExpressionsInSelectListCase(t *testing.T) {
// Populate with data.
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
Index: "i0",
Index: c.Idx("j"),
Query: `
Set(1, a=10)
Set(1, b=100)
@ -872,7 +872,7 @@ func TestPlanner_ExpressionsInSelectListCase(t *testing.T) {
}
t.Run("CaseWithBase", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT b, case b when 100 then 10 when 201 then 20 else 5 end, _id FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT b, case b when 100 then 10 when 201 then 20 else 5 end, _id FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -894,7 +894,7 @@ func TestPlanner_ExpressionsInSelectListCase(t *testing.T) {
})
t.Run("CaseWithNoBase", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT b, case when b = 100 then 10 when b = 201 then 20 else 5 end, _id FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT b, case when b = 100 then 10 when b = 201 then 20 else 5 end, _id FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -920,7 +920,7 @@ func TestPlanner_Select(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
i0, err := c.GetHolder(0).CreateIndex("i0", pilosa.IndexOptions{TrackExistence: true})
i0, err := c.GetHolder(0).CreateIndex(c.Idx("j"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -931,7 +931,7 @@ func TestPlanner_Select(t *testing.T) {
t.Fatal(err)
}
i1, err := c.GetHolder(0).CreateIndex("i1", pilosa.IndexOptions{TrackExistence: true})
i1, err := c.GetHolder(0).CreateIndex(c.Idx("k"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -944,7 +944,7 @@ func TestPlanner_Select(t *testing.T) {
// Populate with data.
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
Index: "i0",
Index: c.Idx("j"),
Query: `
Set(1, a=10)
Set(1, b=100)
@ -955,7 +955,7 @@ func TestPlanner_Select(t *testing.T) {
}
t.Run("UnqualifiedColumns", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT a, b, _id FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT a, b, _id FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -977,7 +977,7 @@ func TestPlanner_Select(t *testing.T) {
})
t.Run("QualifiedTableRef", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT bar.a, bar.b, bar._id FROM i0 as bar`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT bar.a, bar.b, bar._id FROM %j as bar`, c))
if err != nil {
t.Fatal(err)
}
@ -999,7 +999,7 @@ func TestPlanner_Select(t *testing.T) {
})
t.Run("AliasedUnqualifiedColumns", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT a as foo, b as bar, _id as baz FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT a as foo, b as bar, _id as baz FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -1021,7 +1021,7 @@ func TestPlanner_Select(t *testing.T) {
})
t.Run("QualifiedColumns", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT i0._id, i0.a, i0.b FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT %j._id, %j.a, %j.b FROM %j`, c, c, c, c))
if err != nil {
t.Fatal(err)
}
@ -1043,7 +1043,7 @@ func TestPlanner_Select(t *testing.T) {
})
t.Run("UnqualifiedStar", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT * FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT * FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -1065,7 +1065,7 @@ func TestPlanner_Select(t *testing.T) {
})
t.Run("QualifiedStar", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT i0.* FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT %j.* FROM %j`, c, c))
if err != nil {
t.Fatal(err)
}
@ -1087,7 +1087,7 @@ func TestPlanner_Select(t *testing.T) {
})
t.Run("NoIdentifier", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT a, b FROM i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT a, b FROM %j`, c))
if err != nil {
t.Fatal(err)
}
@ -1108,7 +1108,7 @@ func TestPlanner_Select(t *testing.T) {
})
t.Run("ErrFieldNotFound", func(t *testing.T) {
_, err := c.GetNode(0).Server.CompileExecutionPlan(context.Background(), `SELECT xyz FROM i0`)
_, err := c.GetNode(0).Server.CompileExecutionPlan(context.Background(), fmt.Sprintf(`SELECT xyz FROM %j`, c))
if err == nil || !strings.Contains(err.Error(), `column 'xyz' not found`) {
t.Fatalf("unexpected error: %v", err)
}
@ -1119,7 +1119,7 @@ func TestPlanner_SelectOrderBy(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
i0, err := c.GetHolder(0).CreateIndex("i0", pilosa.IndexOptions{TrackExistence: true})
i0, err := c.GetHolder(0).CreateIndex(c.Idx("j"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -1132,7 +1132,7 @@ func TestPlanner_SelectOrderBy(t *testing.T) {
// Populate with data.
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
Index: "i0",
Index: c.Idx("j"),
Query: `
Set(1, a=10)
Set(1, b=100)
@ -1143,7 +1143,7 @@ func TestPlanner_SelectOrderBy(t *testing.T) {
}
t.Run("OrderBy", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT a, b, _id FROM i0 order by a desc`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT a, b, _id FROM %j order by a desc`, c))
if err != nil {
t.Fatal(err)
}
@ -1169,7 +1169,7 @@ func TestPlanner_SelectSelectSource(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
i0, err := c.GetHolder(0).CreateIndex("i0", pilosa.IndexOptions{TrackExistence: true})
i0, err := c.GetHolder(0).CreateIndex(c.Idx("j"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -1182,7 +1182,7 @@ func TestPlanner_SelectSelectSource(t *testing.T) {
// Populate with data.
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
Index: "i0",
Index: c.Idx("j"),
Query: `
Set(1, a=10)
Set(1, b=100)
@ -1193,7 +1193,7 @@ func TestPlanner_SelectSelectSource(t *testing.T) {
}
t.Run("ParenSource", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT a, b, _id FROM (select * from i0)`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT a, b, _id FROM (select * from %j)`, c))
if err != nil {
t.Fatal(err)
}
@ -1215,7 +1215,7 @@ func TestPlanner_SelectSelectSource(t *testing.T) {
})
t.Run("ParenSourceWithAlias", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT foo.a, b, _id FROM (select * from i0) as foo`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT foo.a, b, _id FROM (select * from %j) as foo`, c))
if err != nil {
t.Fatal(err)
}
@ -1241,7 +1241,7 @@ func TestPlanner_In(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
i0, err := c.GetHolder(0).CreateIndex("i0", pilosa.IndexOptions{TrackExistence: true})
i0, err := c.GetHolder(0).CreateIndex(c.Idx("j"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -1250,7 +1250,7 @@ func TestPlanner_In(t *testing.T) {
t.Fatal(err)
}
i1, err := c.GetHolder(0).CreateIndex("i1", pilosa.IndexOptions{TrackExistence: true})
i1, err := c.GetHolder(0).CreateIndex(c.Idx("k"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -1263,7 +1263,7 @@ func TestPlanner_In(t *testing.T) {
// Populate with data.
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
Index: "i0",
Index: c.Idx("j"),
Query: `
Set(1, a=10)
Set(2, a=20)
@ -1273,7 +1273,7 @@ func TestPlanner_In(t *testing.T) {
}
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
Index: "i1",
Index: c.Idx("k"),
Query: `
Set(1, parentid=1)
Set(1, x=100)
@ -1289,9 +1289,9 @@ func TestPlanner_In(t *testing.T) {
t.Run("Count", func(t *testing.T) {
t.Skip("Need to add join conditions to get this to pass")
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT i0._id, i0.a, i1._id, i1.parentid, i1.x FROM i0 INNER JOIN i1 ON i0._id = i1.parentid`)
//results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(*) FROM i0 INNER JOIN i1 ON i0._id = i1.parentid`)
//results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT a FROM i0 where a = 20`) // SELECT COUNT(*) FROM i0 INNER JOIN i1 ON i0._id = i1.parentid
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT %j._id, %j.a, %k._id, %k.parentid, %k.x FROM %j INNER JOIN %k ON %j._id = %k.parentid`, c, c, c, c, c, c, c, c, c))
//results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT COUNT(*) FROM %j INNER JOIN %k ON %j._id = %k.parentid`, c, c, c, c))
//results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT a FROM %j where a = 20`, c)) // SELECT COUNT(*) FROM %j INNER JOIN %k ON %j._id = %k.parentid
if err != nil {
t.Fatal(err)
}
@ -1310,8 +1310,8 @@ func TestPlanner_In(t *testing.T) {
})
/*t.Run("Count", func(t *testing.T) {
//results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(*) FROM i0 INNER JOIN i1 ON i0._id = i1.parentid`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(*) FROM i0 where i0._id in (select distinct parentid from i1)`) // SELECT COUNT(*) FROM i0 INNER JOIN i1 ON i0._id = i1.parentid
//results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT COUNT(*) FROM %j INNER JOIN %k ON %j._id = %k.parentid`, c, c, c, c))
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT COUNT(*) FROM %j where %j._id in (select distinct parentid from %k)`, c, c, c)) // SELECT COUNT(*) FROM %j INNER JOIN %k ON %j._id = %k.parentid
if err != nil {
t.Fatal(err)
}
@ -1330,7 +1330,7 @@ func TestPlanner_In(t *testing.T) {
})
t.Run("CountWithParentCondition", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(*) FROM i0 where i0._id in (select distinct parentid from i1) and i0.a = 10`) // SELECT COUNT(*) FROM i0 INNER JOIN i1 ON i0._id = i1.parentid WHERE i0.a = 10
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT COUNT(*) FROM %j where %j._id in (select distinct parentid from %k) and %j.a = 10`, c, c, c, c)) // SELECT COUNT(*) FROM %j INNER JOIN %k ON %j._id = %k.parentid WHERE %j.a = 10
if err != nil {
t.Fatal(err)
}
@ -1349,7 +1349,7 @@ func TestPlanner_In(t *testing.T) {
})
t.Run("CountWithParentAndChildCondition", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT COUNT(*) FROM i0 where i0._id in (select distinct parentid from i1 where x = 200) and i0.a = 10`) // SELECT COUNT(*) FROM i0 INNER JOIN i1 ON i0._id = i1.parentid WHERE i0.a = 10
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT COUNT(*) FROM %j where %j._id in (select distinct parentid from %k where x = 200) and %j.a = 10`, c, c, c, c)) // SELECT COUNT(*) FROM %j INNER JOIN %k ON %j._id = %k.parentid WHERE %j.a = 10
if err != nil {
t.Fatal(err)
}
@ -1372,7 +1372,7 @@ func TestPlanner_Distinct(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
i0, err := c.GetHolder(0).CreateIndex("i0", pilosa.IndexOptions{TrackExistence: true})
i0, err := c.GetHolder(0).CreateIndex(c.Idx("j"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -1381,7 +1381,7 @@ func TestPlanner_Distinct(t *testing.T) {
t.Fatal(err)
}
i1, err := c.GetHolder(0).CreateIndex("i1", pilosa.IndexOptions{TrackExistence: true})
i1, err := c.GetHolder(0).CreateIndex(c.Idx("k"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -1394,7 +1394,7 @@ func TestPlanner_Distinct(t *testing.T) {
// Populate with data.
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
Index: "i0",
Index: c.Idx("j"),
Query: `
Set(1, a=10)
Set(2, a=20)
@ -1404,7 +1404,7 @@ func TestPlanner_Distinct(t *testing.T) {
}
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
Index: "i1",
Index: c.Idx("k"),
Query: `
Set(1, parentid=1)
Set(1, x=100)
@ -1419,7 +1419,7 @@ func TestPlanner_Distinct(t *testing.T) {
}
t.Run("SelectDistinct_id", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT distinct _id from i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT distinct _id from %j`, c))
if err != nil {
t.Fatal(err)
}
@ -1441,7 +1441,7 @@ func TestPlanner_Distinct(t *testing.T) {
t.Run("SelectDistinctNonId", func(t *testing.T) {
t.Skip("WIP")
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `SELECT distinct parentid from i1`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`SELECT distinct parentid from %k`, c))
if err != nil {
t.Fatal(err)
}
@ -1462,7 +1462,7 @@ func TestPlanner_Distinct(t *testing.T) {
})
t.Run("SelectDistinctMultiple", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `select distinct _id, parentid from i1`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`select distinct _id, parentid from %k`, c))
if err != nil {
t.Fatal(err)
}
@ -1488,7 +1488,7 @@ func TestPlanner_SelectTop(t *testing.T) {
c := test.MustRunCluster(t, 1)
defer c.Close()
i0, err := c.GetHolder(0).CreateIndex("i0", pilosa.IndexOptions{TrackExistence: true})
i0, err := c.GetHolder(0).CreateIndex(c.Idx("j"), pilosa.IndexOptions{TrackExistence: true})
if err != nil {
t.Fatal(err)
}
@ -1503,7 +1503,7 @@ func TestPlanner_SelectTop(t *testing.T) {
// Populate with data.
if _, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{
Index: "i0",
Index: c.Idx("j"),
Query: `
Set(1, a=10)
Set(2, a=20)
@ -1516,7 +1516,7 @@ func TestPlanner_SelectTop(t *testing.T) {
}
t.Run("SelectTopStar", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `select top(1) * from i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`select top(1) * from %j`, c))
if err != nil {
t.Fatal(err)
}
@ -1537,7 +1537,7 @@ func TestPlanner_SelectTop(t *testing.T) {
})
t.Run("SelectTopNStar", func(t *testing.T) {
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, `select topn(1) * from i0`)
results, columns, err := sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`select topn(1) * from %j`, c))
if err != nil {
t.Fatal(err)
}

View file

@ -112,7 +112,9 @@ func TestStatsCount_TopN(t *testing.T) {
}
func TestStatsCount_Bitmap(t *testing.T) {
c := test.MustRunCluster(t, 1)
// Cluster has to be unhsared because we're mocking the stats which writes
// to a holder in use by other tests.
c := test.MustRunUnsharedCluster(t, 1)
defer c.Close()
hldr := test.Holder{Holder: c.GetNode(0).Server.Holder()}
@ -141,7 +143,8 @@ func TestStatsCount_Bitmap(t *testing.T) {
}
func TestStatsCount_APICalls(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
// We can't share a cluster when we're modifying its stats counter.
cluster := test.MustRunUnsharedCluster(t, 1)
defer cluster.Close()
cmd := cluster.GetNode(0)
h := cmd.Handler.(*pilosa.Handler).Handler

View file

@ -8,8 +8,10 @@ import (
"math"
"sort"
"strings"
"sync"
"testing"
"time"
"unicode"
pilosa "github.com/featurebasedb/featurebase/v3"
"github.com/featurebasedb/featurebase/v3/api/client"
@ -23,22 +25,86 @@ import (
"golang.org/x/sync/errgroup"
)
// modHasher represents a simple, mod-based hashing.
type ModHasher struct{}
func (*ModHasher) Hash(key uint64, n int) int { return int(key) % n }
func (*ModHasher) Name() string { return "mod" }
// Cluster represents a Pilosa cluster (multiple Command instances)
// Cluster represents a per-test wrapper of a "real" cluster.
// Individual tests which request a cluster can get one of these
// back, wrapped with their test name.
type Cluster struct {
Nodes []*Command
tb testing.TB
*ShareableCluster
tb testing.TB
indexName string
indexBytes []byte
}
// Idx produces an index name suitable for this test's
// cluster, using the name itself, or the concatenation
// of that name and any provided strings, joined by underscores.
func (c *Cluster) Idx(of ...string) string {
if len(of) == 0 {
return c.indexName
}
for i := range of {
of[i] = indexName(of[i])
}
return c.indexName + strings.Join(of, "_")
}
// Format implements Formatter, allowing us to use clusters in
// format strings to handle the extremely common problem of "I
// want to embed the index name in this here string". Because we
// are horrible criminals, we append the rune, so you can use
// %i and %j to get index-name-plus-i and index-name-plus-j,
// respectively. We do assume that the rune works as a plain byte,
// though.
func (c *Cluster) Format(state fmt.State, r rune) {
c.indexBytes = append(c.indexBytes[:0], c.indexName...)
c.indexBytes = append(c.indexBytes, byte(r))
_, _ = state.Write(c.indexBytes)
}
func (c *Cluster) Close() error {
if c.shared {
// We're not going to actually close the cluster, BUT, we do want to
// delete and close all the indexes we might have just made.
h := c.GetHolder(0)
indexes := h.Indexes()
var lastErr error
api := c.Nodes[0].API
for _, idx := range indexes {
name := idx.Name()
if strings.HasPrefix(name, c.indexName) {
err := api.DeleteIndex(context.Background(), name)
if err != nil {
lastErr = err
}
}
}
return lastErr
}
if !c.started {
return errors.New("cluster not started yet")
}
return c.ShareableCluster.Close()
}
func (c *Cluster) Start() error {
if c.started {
return errors.New("cluster already started")
}
c.started = true
return c.ShareableCluster.Start()
}
// ShareableCluster represents a featurebase cluster (multiple Command instances)
// without test-specific overhead.
type ShareableCluster struct {
Nodes []*Command
started bool
shared bool
}
// Query executes an API.Query through one of the cluster's node's API. It fails
// the test if there is an error.
func (c *Cluster) Query(t testing.TB, index, query string) pilosa.QueryResponse {
func (c *ShareableCluster) Query(t testing.TB, index, query string) pilosa.QueryResponse {
t.Helper()
if len(c.Nodes) == 0 {
t.Fatal("must have at least one node in cluster to query")
@ -50,7 +116,7 @@ func (c *Cluster) Query(t testing.TB, index, query string) pilosa.QueryResponse
// QueryHTTP executes a PQL query through the HTTP endpoint. It fails
// the test for explicit errors, but returns an error which has the
// response body if the HTTP call returns a non-OK status.
func (c *Cluster) QueryHTTP(t testing.TB, index, query string) (string, error) {
func (c *ShareableCluster) QueryHTTP(t testing.TB, index, query string) (string, error) {
t.Helper()
if len(c.Nodes) == 0 {
t.Fatal("must have at least one node in cluster to query")
@ -61,7 +127,7 @@ func (c *Cluster) QueryHTTP(t testing.TB, index, query string) (string, error) {
// QueryGRPC executes a PQL query through the GRPC endpoint. It fails the
// test if there is an error.
func (c *Cluster) QueryGRPC(t testing.TB, index, query string) *proto.TableResponse {
func (c *ShareableCluster) QueryGRPC(t testing.TB, index, query string) *proto.TableResponse {
t.Helper()
if len(c.Nodes) == 0 {
t.Fatal("must have at least one node in cluster to query")
@ -93,7 +159,7 @@ func (c *Cluster) QueryGRPC(t testing.TB, index, query string) *proto.TableRespo
// starting; it's ok to reference each node by its index in the pre-sorted node
// list. It's also safe to use this method after `MustRunCluster()` if the
// cluster contains only one node.
func (c *Cluster) GetIdleNode(n int) *Command {
func (c *ShareableCluster) GetIdleNode(n int) *Command {
return c.Nodes[n]
}
@ -105,7 +171,7 @@ func (c *Cluster) GetIdleNode(n int) *Command {
// `123`, `789`, then we actually want `GetNode(0)` to return `c.Nodes[1]`, and
// `GetNode(1)` to return `c.Nodes[0]`. This method looks at all the node IDs,
// sorts them, and then returns the node that the test expects.
func (c *Cluster) GetNode(n int) *Command {
func (c *ShareableCluster) GetNode(n int) *Command {
// Put all the node IDs into a list to be sorted.
ids := make([]nodePlace, len(c.Nodes))
for i := range c.Nodes {
@ -126,7 +192,7 @@ func (c *Cluster) GetNode(n int) *Command {
// This used to be node0 in tests, but since implementing etcd, the primary
// can be any node in the cluster, so we have to use this method in tests which
// need to act on the primary.
func (c *Cluster) GetPrimary() *Command {
func (c *ShareableCluster) GetPrimary() *Command {
for _, n := range c.Nodes {
if n.IsPrimary() {
return n
@ -136,7 +202,7 @@ func (c *Cluster) GetPrimary() *Command {
}
// GetNonPrimary gets first first non-primary node in the list of nodes.
func (c *Cluster) GetNonPrimary() *Command {
func (c *ShareableCluster) GetNonPrimary() *Command {
for _, n := range c.Nodes {
if !n.IsPrimary() {
return n
@ -146,7 +212,7 @@ func (c *Cluster) GetNonPrimary() *Command {
}
// GetNonPrimaries gets all nodes except the primary.
func (c *Cluster) GetNonPrimaries() []*Command {
func (c *ShareableCluster) GetNonPrimaries() []*Command {
rtn := make([]*Command, 0)
for _, n := range c.Nodes {
if !n.IsPrimary() {
@ -162,15 +228,15 @@ type nodePlace struct {
idx int
}
func (c *Cluster) GetHolder(n int) *Holder {
func (c *ShareableCluster) GetHolder(n int) *Holder {
return &Holder{Holder: c.GetNode(n).Server.Holder()}
}
func (c *Cluster) Len() int {
func (c *ShareableCluster) Len() int {
return len(c.Nodes)
}
func (c *Cluster) ImportBitsWithTimestamp(t testing.TB, index, field string, rowcols [][2]uint64, timestamps []int64) {
func (c *ShareableCluster) ImportBitsWithTimestamp(t testing.TB, index, field string, rowcols [][2]uint64, timestamps []int64) {
t.Helper()
byShard := make(map[uint64][][2]uint64)
byShardTs := make(map[uint64][]int64)
@ -237,14 +303,15 @@ func (c *Cluster) ImportBitsWithTimestamp(t testing.TB, index, field string, row
}
}
}
func (c *Cluster) ImportBits(t testing.TB, index, field string, rowcols [][2]uint64) {
func (c *ShareableCluster) ImportBits(t testing.TB, index, field string, rowcols [][2]uint64) {
var noTime []int64
c.ImportBitsWithTimestamp(t, index, field, rowcols, noTime)
}
// ImportKeyKey imports data into an index where both the index and
// the field are using string keys.
func (c *Cluster) ImportKeyKey(t testing.TB, index, field string, valAndRecKeys [][2]string) {
func (c *ShareableCluster) ImportKeyKey(t testing.TB, index, field string, valAndRecKeys [][2]string) {
t.Helper()
importRequest := &pilosa.ImportRequest{
Index: index,
@ -273,7 +340,7 @@ type TimeQuantumKey struct {
// ImportTimeQuantumKey imports data into an index where the index is keyd
// and the field is a time-quantum
func (c *Cluster) ImportTimeQuantumKey(t testing.TB, index, field string, entries []TimeQuantumKey) {
func (c *ShareableCluster) ImportTimeQuantumKey(t testing.TB, index, field string, entries []TimeQuantumKey) {
t.Helper()
importRequest := &pilosa.ImportRequest{
Index: index,
@ -303,7 +370,7 @@ type IntKey struct {
}
// ImportIntKey imports int data into an index which uses string keys.
func (c *Cluster) ImportIntKey(t testing.TB, index, field string, pairs []IntKey) {
func (c *ShareableCluster) ImportIntKey(t testing.TB, index, field string, pairs []IntKey) {
t.Helper()
importRequest := &pilosa.ImportValueRequest{
Index: index,
@ -329,7 +396,7 @@ type IntID struct {
}
// ImportIntID imports data into an int field in an unkeyed index.
func (c *Cluster) ImportIntID(t testing.TB, index, field string, pairs []IntID) {
func (c *ShareableCluster) ImportIntID(t testing.TB, index, field string, pairs []IntID) {
t.Helper()
importRequest := &pilosa.ImportValueRequest{
Index: index,
@ -357,7 +424,7 @@ type KeyID struct {
}
//ImportIDKey imports data into an unkeyed set field in a keyed index.
func (c *Cluster) ImportIDKey(t testing.TB, index, field string, pairs []KeyID) {
func (c *ShareableCluster) ImportIDKey(t testing.TB, index, field string, pairs []KeyID) {
t.Helper()
importRequest := &pilosa.ImportRequest{
Index: index,
@ -378,7 +445,7 @@ func (c *Cluster) ImportIDKey(t testing.TB, index, field string, pairs []KeyID)
}
// CreateField creates the index (if necessary) and field specified.
func (c *Cluster) CreateField(t testing.TB, index string, iopts pilosa.IndexOptions, field string, fopts ...pilosa.FieldOption) *pilosa.Field {
func (c *ShareableCluster) CreateField(t testing.TB, index string, iopts pilosa.IndexOptions, field string, fopts ...pilosa.FieldOption) *pilosa.Field {
t.Helper()
idx, err := c.GetPrimary().API.CreateIndex(context.Background(), index, iopts)
if err != nil && !strings.Contains(err.Error(), "index already exists") {
@ -403,11 +470,7 @@ func (c *Cluster) CreateField(t testing.TB, index string, iopts pilosa.IndexOpti
}
// Start runs a Cluster
func (c *Cluster) Start() error {
err := GetPortsGenConfigs(c.tb, c.Nodes)
if err != nil {
return errors.Wrap(err, "configuring cluster ports")
}
func (c *ShareableCluster) Start() error {
var eg errgroup.Group
for _, cc := range c.Nodes {
cc := cc
@ -415,7 +478,7 @@ func (c *Cluster) Start() error {
return cc.Start()
})
}
err = eg.Wait()
err := eg.Wait()
if err != nil {
return errors.Wrap(err, "starting cluster")
}
@ -423,7 +486,17 @@ func (c *Cluster) Start() error {
}
// Close stops a Cluster
func (c *Cluster) Close() error {
func (c *ShareableCluster) Close() error {
if c.shared {
for i := range c.Nodes {
holder := c.GetHolder(i)
indexes := holder.Indexes()
var indexNames []string
for _, idx := range indexes {
indexNames = append(indexNames, idx.Name())
}
}
}
for i, cc := range c.Nodes {
if err := cc.Close(); err != nil {
return errors.Wrapf(err, "stopping server %d", i)
@ -432,7 +505,10 @@ func (c *Cluster) Close() error {
return nil
}
func (c *Cluster) CloseAndRemoveNonPrimary() error {
func (c *ShareableCluster) CloseAndRemoveNonPrimary() error {
if c.shared {
return errors.New("can't close-and-remove in shared cluster")
}
for i, n := range c.Nodes {
if !n.IsPrimary() {
return c.CloseAndRemove(i)
@ -441,7 +517,10 @@ func (c *Cluster) CloseAndRemoveNonPrimary() error {
return errors.New("could not find non-primary node")
}
func (c *Cluster) CloseAndRemove(n int) error {
func (c *ShareableCluster) CloseAndRemove(n int) error {
if c.shared {
return errors.New("can't close-and-remove in shared cluster")
}
if n < 0 || n >= len(c.Nodes) {
return fmt.Errorf("close/remove from cluster: index %d out of range (len %d)", n, len(c.Nodes))
}
@ -455,7 +534,7 @@ func (c *Cluster) CloseAndRemove(n int) error {
// When this happens, we know etcd reached a combination of node states that
// would imply this cluster state, but some nodes may not have caught up yet;
// we just test that the coordinator thought the cluster was in the given state.
func (c *Cluster) AwaitPrimaryState(expectedState disco.ClusterState, timeout time.Duration) error {
func (c *ShareableCluster) AwaitPrimaryState(expectedState disco.ClusterState, timeout time.Duration) error {
if len(c.Nodes) < 1 {
return errors.New("can't await coordinator state on an empty cluster")
}
@ -474,16 +553,15 @@ func (c *Cluster) AwaitPrimaryState(expectedState disco.ClusterState, timeout ti
return errors.New("timed out waiting for cluster to have valid topology")
}
// we used up some of our timeout waiting for this
c.tb.Logf("had to wait %v for cluster topology", elapsed)
timeout -= elapsed
}
onlyCoordinator := &Cluster{Nodes: []*Command{primary}}
onlyCoordinator := &ShareableCluster{Nodes: []*Command{primary}}
return onlyCoordinator.AwaitState(expectedState, timeout)
}
// ExceptionalState returns an error if any node in the cluster is not
// in the expected state.
func (c *Cluster) ExceptionalState(expectedState disco.ClusterState) error {
func (c *ShareableCluster) ExceptionalState(expectedState disco.ClusterState) error {
for _, node := range c.Nodes {
state, err := node.API.State()
if err != nil || state != expectedState {
@ -494,7 +572,7 @@ func (c *Cluster) ExceptionalState(expectedState disco.ClusterState) error {
}
// AwaitState waits for the whole cluster to reach a specified state.
func (c *Cluster) AwaitState(expectedState disco.ClusterState, timeout time.Duration) (err error) {
func (c *ShareableCluster) AwaitState(expectedState disco.ClusterState, timeout time.Duration) (err error) {
if len(c.Nodes) < 1 {
return errors.New("can't await state of an empty cluster")
}
@ -512,7 +590,8 @@ func (c *Cluster) AwaitState(expectedState disco.ClusterState, timeout time.Dura
elapsed, expectedState, err)
}
// MustNewCluster creates a new cluster. If opts contains only one
// MustNewCluster creates a new cluster or returns an existing one. It never shares
// a cluster with non-empty opts. If opts contains only one
// slice of command options, those options are used with every node.
// If it is empty, default options are used. Otherwise, it must contain size
// slices of command options, which are used with corresponding nodes.
@ -522,29 +601,111 @@ func MustNewCluster(tb testing.TB, size int, opts ...[]server.CommandOption) *Cl
}
tb.Helper()
shareable := len(opts) == 0
// We want tests to default to using the in-memory translate store, so we
// prepend opts with that functional option. If a different translate store
// has been specified, it will override this one.
opts = prependOpts(opts, size)
c, err := newCluster(tb, size, opts...)
c, err := newCluster(tb, size, shareable, opts...)
if err != nil {
tb.Fatalf("new cluster: %v", err)
}
return c
}
// newCluster creates a new cluster
func newCluster(tb testing.TB, size int, opts ...[]server.CommandOption) (*Cluster, error) {
if size == 0 {
return nil, errors.New("cluster must contain at least one node")
// MustUnsharedCluster creates a new cluster. If opts contains only one
// slice of command options, those options are used with every node.
// If it is empty, default options are used. Otherwise, it must contain size
// slices of command options, which are used with corresponding nodes. The
// new cluster is always unshared.
func MustUnsharedCluster(tb testing.TB, size int, opts ...[]server.CommandOption) *Cluster {
if size > 1 && !etcd.AllowCluster() {
tb.Skip("Testing PLG which does not allow clustering")
}
tb.Helper()
// We want tests to default to using the in-memory translate store, so we
// prepend opts with that functional option. If a different translate store
// has been specified, it will override this one.
opts = prependOpts(opts, size)
if len(opts) != size && len(opts) != 0 && len(opts) != 1 {
return nil, errors.New("Slice of CommandOptions must be of length 0, 1, or equal to the number of cluster nodes")
c, err := newCluster(tb, size, false, opts...)
if err != nil {
tb.Fatalf("new cluster: %v", err)
}
return c
}
cluster := &Cluster{Nodes: make([]*Command, size), tb: tb}
// MustRunUnsharedCluster creates a new cluster. If opts contains only one
// slice of command options, those options are used with every node.
// If it is empty, default options are used. Otherwise, it must contain size
// slices of command options, which are used with corresponding nodes. The
// new cluster is always unshared. The new cluster is started automatically,
// or the test is failed.
func MustRunUnsharedCluster(tb testing.TB, size int, opts ...[]server.CommandOption) *Cluster {
if size > 1 && !etcd.AllowCluster() {
tb.Skip("Testing PLG which does not allow clustering")
}
tb.Helper()
// We want tests to default to using the in-memory translate store, so we
// prepend opts with that functional option. If a different translate store
// has been specified, it will override this one.
opts = prependOpts(opts, size)
c, err := newCluster(tb, size, false, opts...)
if err != nil {
tb.Fatalf("new cluster: %v", err)
}
err = c.Start()
if err != nil {
tb.Fatalf("starting cluster: %v", err)
}
return c
}
type clusterCache struct {
mu sync.Mutex
clusters map[int]*ShareableCluster
}
// CleanupClusters calls the close functions on any shared clusters that are
// still open.
func (c *clusterCache) CleanupClusters() {
for k, v := range c.clusters {
_ = v.Close()
delete(c.clusters, k)
}
}
func (c *clusterCache) newCluster(tb testing.TB, size int) (*ShareableCluster, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c := c.clusters[size]; c != nil {
return c, nil
}
// Make a whole-test wrapper so that the cluster we create will use the provided tb
// for nearly everything, but the call to TempDir inside NewCommand will pick up a
// persistent directory which outlives the provided TB.
newTB := NewWholeTestRun(tb)
if c.clusters == nil {
c.clusters = make(map[int]*ShareableCluster)
// tb should always be a wholeTestRun for clusterCache, and we need to
// register with that, so our cleanup happens *before* the deletion of
// the directories, otherwise etcd can fail to flush WAL files on
// exit, causing tests to fail.
newTB.Cleanup(c.CleanupClusters)
}
cluster, err := underlyingNewCluster(newTB, size)
if err != nil {
return nil, err
}
cluster.shared = true
c.clusters[size] = cluster
return cluster, nil
}
func underlyingNewCluster(tb DirCleaner, size int, opts ...[]server.CommandOption) (*ShareableCluster, error) {
cluster := &ShareableCluster{Nodes: make([]*Command, size)}
for i := 0; i < size; i++ {
var commandOpts []server.CommandOption
if len(opts) > 0 {
@ -555,17 +716,51 @@ func newCluster(tb testing.TB, size int, opts ...[]server.CommandOption) (*Clust
cluster.Nodes[i] = m
}
// The GetPorts... stuff calls things elsewhere that want a plain testing.TB,
// and doesn't produce permanent directories, I think.
err := GetPortsGenConfigs(tb, cluster.Nodes)
if err != nil {
return nil, errors.Wrap(err, "configuring cluster ports")
}
return cluster, nil
}
var globalClusterCache clusterCache
// newCluster creates a new cluster, using the shared cluster cache if no opts are
// specified.
func newCluster(tb testing.TB, size int, shareable bool, opts ...[]server.CommandOption) (*Cluster, error) {
if size == 0 {
return nil, errors.New("cluster must contain at least one node")
}
if len(opts) != size && len(opts) != 0 && len(opts) != 1 {
return nil, errors.New("Slice of CommandOptions must be of length 0, 1, or equal to the number of cluster nodes")
}
var shared *ShareableCluster
var err error
if !shareable {
shared, err = underlyingNewCluster(tb, size, opts...)
} else {
shared, err = globalClusterCache.newCluster(tb, size)
}
if err != nil {
return nil, err
}
return &Cluster{ShareableCluster: shared, tb: tb, indexName: indexName(tb.Name())}, nil
}
// MustRunCluster creates and starts a new cluster. The opts parameter
// is slightly magical; see MustNewCluster.
func MustRunCluster(tb testing.TB, size int, opts ...[]server.CommandOption) *Cluster {
cluster := MustNewCluster(tb, size, opts...)
err := cluster.Start()
if err != nil {
tb.Fatalf("run cluster: %v", err)
if !cluster.started {
err := cluster.Start()
if err != nil {
tb.Fatalf("run cluster: %v", err)
}
cluster.started = true
}
return cluster
}
@ -613,3 +808,19 @@ func prependTestServerOpts(opts []server.CommandOption) []server.CommandOption {
}
return append(defaultOpts, opts...)
}
func indexName(in string) string {
return strings.Map(func(r rune) rune {
if r < 127 {
switch {
case unicode.IsLetter(r):
return unicode.ToLower(r)
case unicode.IsNumber(r):
return r
case r == '/':
return '_'
}
}
return -1
}, in)
}

View file

@ -6,7 +6,6 @@ import (
"fmt"
"net"
"strings"
"testing"
"github.com/featurebasedb/featurebase/v3/etcd"
"github.com/featurebasedb/featurebase/v3/server"
@ -56,7 +55,7 @@ func listenerWithURL() (listener *net.TCPListener, url string, err error) {
// and modifies the configs of the provided Command objects
// to point to these etcd configs. It uses etcd.GenEtcdConfigs,
// which in turn creates temporary directories and the like.
func GetPortsGenConfigs(tb testing.TB, nodes []*Command) error {
func GetPortsGenConfigs(tb DirCleaner, nodes []*Command) error {
clusterName, cfgs := etcd.GenEtcdConfigs(tb, len(nodes))
for i := range nodes {
if nodes[i].Config == nil {

129
test/glue.go Normal file
View file

@ -0,0 +1,129 @@
// Copyright 2021 Molecula Corp. All rights reserved.
package test
import (
"fmt"
"log"
"os"
"sync"
"testing"
"github.com/molecula/featurebase/v3/testhook"
)
// glue lets us make a thing which isn't a testing.TB, but can be passed
// around to the functions that take testing.TB.
// DirCleaner represents the subset of the testing.TB interface
// we care about, allowing us to take objects which behave like
// that without importing all of testing to get them.
type DirCleaner interface {
Helper()
Errorf(string, ...interface{})
Fatalf(string, ...interface{})
Fatal(...interface{})
Logf(string, ...interface{})
Name() string
TempDir() string
Cleanup(func())
Skip(...interface{})
}
// Verify that a TB is a DirCleaner
var _ DirCleaner = testing.TB(nil)
var _ DirCleaner = &wholeTestRunWrapper{}
// wholeTestRun is a thing that's shaped a bit like testing.TB,
// but it can be used across all the tests, running its cleanup functions
// at the very end of the testing process. this lets us create clusters
// using the same code and logic we would for per-test things, except
// substituting this, and then have a single global post-test-hook run
// their cleanup.
type wholeTestRun struct {
setup sync.Once
mu sync.Mutex
tempDirs []string
cleanupFuncs []func()
}
// wholeTestRunWrapper is a test-specific thing that can refer to the
// global shared state, but also forwards everything *except* TempDir,
// Cleanup, and Logf to the tb it's created with.
type wholeTestRunWrapper struct {
testing.TB
}
// globalT is a system-wide wholeTestRun. the first time it's used, for any
// reason, it registers a cleanup with testhook, which will run at the end
// of the TestMain stuff, but before any previously-registered cleanup
// functions (such as the auditor stuff) so it can ensure that everything's
// been deleted. Basically this exists to let us call `tb.TempDir` on
// things and get a directory which outlives the current test.
var globalT wholeTestRun
// NewWholeTestRun produces a wholeTestRunWrapper around TB, which overrides
// a couple of the TB's methods to get whole-test-friendly behaviors.
func NewWholeTestRun(tb testing.TB) *wholeTestRunWrapper {
return &wholeTestRunWrapper{TB: tb}
}
func (w *wholeTestRun) Teardown() {
w.mu.Lock()
defer w.mu.Unlock()
for _, fn := range w.cleanupFuncs {
fn()
}
fmt.Fprintf(os.Stderr, "tearing down: %d directories\n", len(w.tempDirs))
for _, path := range w.tempDirs {
fmt.Fprintf(os.Stderr, "tearing down: deleting %s\n", path)
// disregard errors because we don't care that much about them;
// we assume RemoveAll probably works unless something's wrong.
// see the comments on the testing package's internal removeAll,
// which suggests the errors only happen on Windows, which we
// don't support.
_ = os.RemoveAll(path)
}
}
func (w *wholeTestRun) Setup() {
w.setup.Do(func() {
testhook.RegisterPostTestHook(func() error {
w.Teardown()
return nil
})
})
}
// We provide a Logf here because the wholeTestRunWrapper could
// be set as a tb-replacement for something which lasts past the
// test that created it. That shouldn't happen, probably.
func (w *wholeTestRunWrapper) Logf(msg string, args ...interface{}) {
log.Printf(msg, args...)
}
func (w *wholeTestRun) TempDir(tb DirCleaner) string {
w.Setup()
w.mu.Lock()
defer w.mu.Unlock()
path, err := os.MkdirTemp("", "test-temp-")
if err != nil {
tb.Fatalf("creating temp dir: %v", err)
}
globalT.tempDirs = append(globalT.tempDirs, path)
return path
}
func (w *wholeTestRun) Cleanup(fn func()) {
w.Setup()
w.mu.Lock()
defer w.mu.Unlock()
w.cleanupFuncs = append(w.cleanupFuncs, fn)
}
func (w *wholeTestRunWrapper) TempDir() string {
return globalT.TempDir(w.TB)
}
func (w *wholeTestRunWrapper) Cleanup(fn func()) {
globalT.Cleanup(fn)
}

View file

@ -14,11 +14,10 @@ import (
"testing"
"time"
pilosa "github.com/featurebasedb/featurebase/v3"
"github.com/featurebasedb/featurebase/v3/disco"
"github.com/featurebasedb/featurebase/v3/encoding/proto"
"github.com/featurebasedb/featurebase/v3/server"
"github.com/featurebasedb/featurebase/v3/testhook"
pilosa "github.com/molecula/featurebase/v3"
"github.com/molecula/featurebase/v3/disco"
"github.com/molecula/featurebase/v3/encoding/proto"
"github.com/molecula/featurebase/v3/server"
)
// //////////////////////////////////////////////////////////////////////////////////
@ -37,11 +36,8 @@ func OptAllowedOrigins(origins []string) server.CommandOption {
}
// newCommand returns a new instance of Main with a temporary data directory and random port.
func newCommand(tb testing.TB, opts ...server.CommandOption) *Command {
path, err := testhook.TempDir(tb, "pilosa-command-")
if err != nil {
panic(err)
}
func newCommand(tb DirCleaner, opts ...server.CommandOption) *Command {
path := tb.TempDir()
// Set aggressive close timeout by default to avoid hanging tests. This was
// a problem with PDK tests which used pilosa/client as well. We put it at the
@ -54,7 +50,7 @@ func newCommand(tb testing.TB, opts ...server.CommandOption) *Command {
m := &Command{commandOptions: opts}
m.Command = server.NewCommand(bytes.NewReader(nil), io.Discard, io.Discard, opts...)
// pick etcd ports using a socket rather than a real port
err = GetPortsGenConfigs(tb, []*Command{m})
err := GetPortsGenConfigs(tb, []*Command{m})
if err != nil {
tb.Fatalf("generating config: %v", err)
}
@ -81,7 +77,7 @@ func newCommand(tb testing.TB, opts ...server.CommandOption) *Command {
}
// NewCommandNode returns a new instance of Command with clustering enabled.
func NewCommandNode(tb testing.TB, opts ...server.CommandOption) *Command {
func NewCommandNode(tb DirCleaner, opts ...server.CommandOption) *Command {
// We want tests to default to using the in-memory translate store, so we
// prepend opts with that functional option. If a different translate store
// has been specified, it will override this one.
@ -96,7 +92,7 @@ func RunCommand(t *testing.T) *Command {
// prefer MustRunCluster since it sets up for using etcd using
// the GenDisCoConfig(size) option.
return MustRunCluster(t, 1).GetNode(0)
return MustRunUnsharedCluster(t, 1).GetNode(0)
}
// Close closes the program and removes the underlying data directory.

View file

@ -1,7 +1,7 @@
{
"indexes": [
{
"name": "equipment",
"name": "e",
"options": {
"keys": true,
"trackExistence": true
@ -137,7 +137,7 @@
"shardWidth": 1048576
},
{
"name": "power_ts",
"name": "t",
"options": {
"keys": true,
"trackExistence": true
@ -285,7 +285,7 @@
"shardWidth": 1048576
},
{
"name": "sites",
"name": "s",
"options": {
"keys": false,
"trackExistence": true

View file

@ -12,12 +12,10 @@ import (
"time"
"github.com/google/go-cmp/cmp"
pilosa "github.com/featurebasedb/featurebase/v3"
"github.com/featurebasedb/featurebase/v3/boltdb"
"github.com/featurebasedb/featurebase/v3/disco"
"github.com/featurebasedb/featurebase/v3/mock"
"github.com/featurebasedb/featurebase/v3/server"
"github.com/featurebasedb/featurebase/v3/test"
pilosa "github.com/molecula/featurebase/v3"
"github.com/molecula/featurebase/v3/disco"
"github.com/molecula/featurebase/v3/mock"
"github.com/molecula/featurebase/v3/test"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)
@ -151,32 +149,7 @@ func TestMultiTranslateEntryReader(t *testing.T) {
}
func TestTranslation_KeyNotFound(t *testing.T) {
c := test.MustRunCluster(t, 4,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node1"),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node2"),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node3"),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
)
c := test.MustRunCluster(t, 4)
defer c.Close()
node0 := c.GetNode(0)
@ -185,19 +158,19 @@ func TestTranslation_KeyNotFound(t *testing.T) {
node3 := c.GetNode(3)
ctx := context.Background()
idx, fld := "i", "f"
index, fld := c.Idx(), "f"
// Create an index with keys.
if _, err := node0.API.CreateIndex(ctx, idx, pilosa.IndexOptions{Keys: true}); err != nil {
if _, err := node0.API.CreateIndex(ctx, index, pilosa.IndexOptions{Keys: true}); err != nil {
t.Fatal(err)
}
// Create an index with keys.
if _, err := node0.API.CreateField(ctx, idx, fld, pilosa.OptFieldKeys()); err != nil {
if _, err := node0.API.CreateField(ctx, index, fld, pilosa.OptFieldKeys()); err != nil {
t.Fatal(err)
}
// write a new key and get id
req, err := node0.API.Serializer.Marshal(&pilosa.TranslateKeysRequest{
Index: idx,
Index: index,
Field: fld,
Keys: []string{"k1"},
NotWritable: false,
@ -217,7 +190,7 @@ func TestTranslation_KeyNotFound(t *testing.T) {
// read non-existing key
req, err = node3.API.Serializer.Marshal(&pilosa.TranslateKeysRequest{
Index: idx,
Index: index,
Field: fld,
Keys: []string{"k2"},
NotWritable: true,
@ -235,7 +208,7 @@ func TestTranslation_KeyNotFound(t *testing.T) {
}
req, err = node1.API.Serializer.Marshal(&pilosa.TranslateKeysRequest{
Index: idx,
Index: index,
Keys: []string{"k2"},
NotWritable: true,
})
@ -252,7 +225,7 @@ func TestTranslation_KeyNotFound(t *testing.T) {
}
req, err = node2.API.Serializer.Marshal(&pilosa.TranslateKeysRequest{
Index: idx,
Index: index,
Field: fld,
Keys: []string{"k2", "k1"},
NotWritable: false,
@ -307,37 +280,18 @@ func TestTranslation_Primary(t *testing.T) {
// non-primary nodes are forwarded to the primary.
t.Run("ForwardFieldKey", func(t *testing.T) {
// Start a 2-node cluster.
c := test.MustRunCluster(t, 3,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node1"),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node2"),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
)
c := test.MustRunCluster(t, 3)
defer c.Close()
node0 := c.GetPrimary()
node1 := c.GetNonPrimary()
ctx := context.Background()
idx := "i"
index := c.Idx()
fld := "f"
// Create an index without keys.
if _, err := node1.API.CreateIndex(ctx, idx,
if _, err := node1.API.CreateIndex(ctx, index,
pilosa.IndexOptions{
Keys: false,
}); err != nil {
@ -345,7 +299,7 @@ func TestTranslation_Primary(t *testing.T) {
}
// Create a field with keys.
if _, err := node1.API.CreateField(ctx, idx, fld,
if _, err := node1.API.CreateField(ctx, index, fld,
pilosa.OptFieldKeys(),
); err != nil {
t.Fatal(err)
@ -357,7 +311,7 @@ func TestTranslation_Primary(t *testing.T) {
// Send a translation request to node1 (non-primary).
_, err := node1.API.Query(ctx,
&pilosa.QueryRequest{Index: idx, Query: pql},
&pilosa.QueryRequest{Index: index, Query: pql},
)
if err != nil {
t.Fatal(err)
@ -368,7 +322,7 @@ func TestTranslation_Primary(t *testing.T) {
// Read the row and ensure the key was set.
qry := fmt.Sprintf(`Row(%s="%s")`, fld, keys[i])
resp, err := node0.API.Query(ctx,
&pilosa.QueryRequest{Index: idx, Query: qry},
&pilosa.QueryRequest{Index: index, Query: qry},
)
if err != nil {
t.Fatal(err)
@ -383,53 +337,28 @@ func TestTranslation_Primary(t *testing.T) {
}
func TestTranslation_TranslateIDsOnCluster(t *testing.T) {
c := test.MustRunCluster(t, 4,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node1"),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node2"),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node3"),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
)
c := test.MustRunCluster(t, 4)
defer c.Close()
coord := c.GetPrimary()
other := c.GetNonPrimary()
ctx := context.Background()
idx, fld := "i", "f"
index, fld := c.Idx(), "f"
// Create an index with keys.
if _, err := coord.API.CreateIndex(ctx, idx, pilosa.IndexOptions{Keys: true}); err != nil {
if _, err := coord.API.CreateIndex(ctx, index, pilosa.IndexOptions{Keys: true}); err != nil {
t.Fatal(err)
}
// Create an index with keys.
if _, err := coord.API.CreateField(ctx, idx, fld, pilosa.OptFieldKeys()); err != nil {
if _, err := coord.API.CreateField(ctx, index, fld, pilosa.OptFieldKeys()); err != nil {
t.Fatal(err)
}
keys := []string{"k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7", "k8", "k9"}
// write a new key and get id
req, err := coord.API.Serializer.Marshal(&pilosa.TranslateKeysRequest{
Index: idx,
Index: index,
Field: fld,
Keys: keys,
NotWritable: false,
@ -451,7 +380,7 @@ func TestTranslation_TranslateIDsOnCluster(t *testing.T) {
// translate ids
req, err = other.API.Serializer.Marshal(&pilosa.TranslateIDsRequest{
Index: idx,
Index: index,
Field: fld,
IDs: ids,
})
@ -473,7 +402,7 @@ func TestTranslation_Cluster_CreateFind(t *testing.T) {
c := test.MustRunCluster(t, 3)
defer c.Close()
c.CreateField(t, "i", pilosa.IndexOptions{Keys: true}, "f", pilosa.OptFieldKeys())
c.CreateField(t, c.Idx(), pilosa.IndexOptions{Keys: true}, "f", pilosa.OptFieldKeys())
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
@ -503,7 +432,7 @@ func TestTranslation_Cluster_CreateFind(t *testing.T) {
for i, keys := range parts {
i, keys := i, keys
g.Go(func() error {
_, err := c.GetNode(i).API.CreateIndexKeys(ctx, "i", keys...)
_, err := c.GetNode(i).API.CreateIndexKeys(ctx, c.Idx(), keys...)
return err
})
}
@ -522,7 +451,7 @@ func TestTranslation_Cluster_CreateFind(t *testing.T) {
}
// Obtain authoritative translations for the keys.
translations, err := c.GetPrimary().API.FindIndexKeys(ctx, "i", keyList...)
translations, err := c.GetPrimary().API.FindIndexKeys(ctx, c.Idx(), keyList...)
if err != nil {
t.Errorf("obtaining authoritative translations: %v", err)
return
@ -540,7 +469,7 @@ func TestTranslation_Cluster_CreateFind(t *testing.T) {
x, api := i, n.API
g.Go(func() (err error) {
defer func() { err = errors.Wrapf(err, "translating on node %d", x) }()
localTranslations, err := api.FindIndexKeys(ctx, "i", keyList...)
localTranslations, err := api.FindIndexKeys(ctx, c.Idx(), keyList...)
if err != nil {
return errors.Wrap(err, "finding translations")
}
@ -557,7 +486,7 @@ func TestTranslation_Cluster_CreateFind(t *testing.T) {
x, api := i, n.API
g.Go(func() (err error) {
defer func() { err = errors.Wrapf(err, "translating on node %d", x) }()
localTranslations, err := api.CreateIndexKeys(ctx, "i", keyList...)
localTranslations, err := api.CreateIndexKeys(ctx, c.Idx(), keyList...)
if err != nil {
return errors.Wrap(err, "finding translations")
}
@ -589,7 +518,7 @@ func TestTranslation_Cluster_CreateFind(t *testing.T) {
for i, keys := range parts {
i, keys := i, keys
g.Go(func() error {
_, err := c.GetNode(i).API.CreateFieldKeys(ctx, "i", "f", keys...)
_, err := c.GetNode(i).API.CreateFieldKeys(ctx, c.Idx(), "f", keys...)
return err
})
}
@ -608,7 +537,7 @@ func TestTranslation_Cluster_CreateFind(t *testing.T) {
}
// Obtain authoritative translations for the keys.
translations, err := c.GetPrimary().API.FindFieldKeys(ctx, "i", "f", keyList...)
translations, err := c.GetPrimary().API.FindFieldKeys(ctx, c.Idx(), "f", keyList...)
if err != nil {
t.Errorf("obtaining authoritative translations: %v", err)
return
@ -626,7 +555,7 @@ func TestTranslation_Cluster_CreateFind(t *testing.T) {
x, api := i, n.API
g.Go(func() (err error) {
defer func() { err = errors.Wrapf(err, "translating on node %d", x) }()
localTranslations, err := api.FindFieldKeys(ctx, "i", "f", keyList...)
localTranslations, err := api.FindFieldKeys(ctx, c.Idx(), "f", keyList...)
if err != nil {
return errors.Wrap(err, "finding translations")
}
@ -643,7 +572,7 @@ func TestTranslation_Cluster_CreateFind(t *testing.T) {
x, api := i, n.API
g.Go(func() (err error) {
defer func() { err = errors.Wrapf(err, "translating on node %d", x) }()
localTranslations, err := api.CreateFieldKeys(ctx, "i", "f", keyList...)
localTranslations, err := api.CreateFieldKeys(ctx, c.Idx(), "f", keyList...)
if err != nil {
return errors.Wrap(err, "finding translations")
}
@ -661,19 +590,20 @@ func TestTranslation_Cluster_CreateFind(t *testing.T) {
func TestTranslation_Cluster_CreateFindUnkeyed(t *testing.T) {
c := test.MustRunCluster(t, 3)
defer c.Close()
i := c.Idx()
c.CreateField(t, "i", pilosa.IndexOptions{}, "f")
c.CreateField(t, i, pilosa.IndexOptions{}, "f")
t.Run("Index", func(t *testing.T) {
t.Run("Create", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := c.GetNonPrimary().API.CreateIndexKeys(ctx, "i", "foo")
_, err := c.GetNonPrimary().API.CreateIndexKeys(ctx, i, "foo")
if err == nil {
t.Fatal("unexpected success")
}
expect := `cannot create keys on unkeyed index "i"`
expect := fmt.Sprintf(`cannot create keys on unkeyed index "%s"`, i)
if got := err.Error(); got != expect {
t.Fatalf("expected error %q but got %q", expect, got)
}
@ -682,11 +612,11 @@ func TestTranslation_Cluster_CreateFindUnkeyed(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := c.GetNonPrimary().API.FindIndexKeys(ctx, "i", "foo")
_, err := c.GetNonPrimary().API.FindIndexKeys(ctx, i, "foo")
if err == nil {
t.Fatal("unexpected success")
}
expect := `cannot find keys on unkeyed index "i"`
expect := fmt.Sprintf(`cannot find keys on unkeyed index "%s"`, i)
if got := err.Error(); got != expect {
t.Fatalf("expected error %q but got %q", expect, got)
}
@ -697,7 +627,7 @@ func TestTranslation_Cluster_CreateFindUnkeyed(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := c.GetNonPrimary().API.CreateFieldKeys(ctx, "i", "f", "foo")
_, err := c.GetNonPrimary().API.CreateFieldKeys(ctx, i, "f", "foo")
if err == nil {
t.Fatal("unexpected success")
}
@ -710,7 +640,7 @@ func TestTranslation_Cluster_CreateFindUnkeyed(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := c.GetNonPrimary().API.FindFieldKeys(ctx, "i", "f", "foo")
_, err := c.GetNonPrimary().API.FindFieldKeys(ctx, i, "f", "foo")
if err == nil {
t.Fatal("unexpected success")
}

View file

@ -7,10 +7,9 @@ import (
"fmt"
"testing"
pilosa "github.com/featurebasedb/featurebase/v3"
"github.com/featurebasedb/featurebase/v3/server"
"github.com/featurebasedb/featurebase/v3/test"
. "github.com/featurebasedb/featurebase/v3/vprint" // nolint:staticcheck
pilosa "github.com/molecula/featurebase/v3"
"github.com/molecula/featurebase/v3/test"
. "github.com/molecula/featurebase/v3/vprint" // nolint:staticcheck
)
func queryIRABit(m0api *pilosa.API, acctOwnerID uint64, iraField string, iraRowID uint64, index string) (bit bool) {
@ -46,21 +45,14 @@ func queryBalances(m0api *pilosa.API, acctOwnerID uint64, fldAcct0, fldAcct1, in
}
func TestAPI_ImportAtomicRecord(t *testing.T) {
c := test.MustRunCluster(t, 1,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateReader(pilosa.GetOpenTranslateReaderFunc(nil)),
)},
)
c := test.MustRunCluster(t, 1)
defer c.Close()
m0 := c.GetNode(0)
m0api := m0.API
ctx := context.Background()
index := "i"
index := c.Idx()
fieldAcct0 := "acct0"
fieldAcct1 := "acct1"

View file

@ -17,7 +17,7 @@ import (
// NewTestCluster returns a cluster with n nodes and uses a mod-based hasher.
func NewTestCluster(tb testing.TB, n int) *cluster {
if n > 1 && etcd.AllowCluster() {
if n > 1 && !etcd.AllowCluster() {
tb.Skipf("cluster size %d not supported in unclustered mode", n)
}
path, err := testhook.TempDir(tb, "pilosa-cluster-")
@ -28,7 +28,7 @@ func NewTestCluster(tb testing.TB, n int) *cluster {
availableShardFileFlushDuration.Set(100 * time.Millisecond)
c := newCluster()
c.ReplicaN = 1
c.Hasher = NewTestModHasher()
c.Hasher = &disco.Jmphasher{}
c.Path = path
nodes := make([]*disco.Node, 0, n)
@ -63,16 +63,6 @@ func NewTestURIFromHostPort(host string, port uint16) pnet.URI {
return *uri
}
// ModHasher represents a simple, mod-based hashing.
type TestModHasher struct{}
// NewTestModHasher returns a new instance of ModHasher with n buckets.
func NewTestModHasher() *TestModHasher { return &TestModHasher{} }
func (*TestModHasher) Hash(key uint64, n int) int { return int(key) % n }
func (*TestModHasher) Name() string { return "mod" }
func TestReplaceFirstFromBack(t *testing.T) {
for name, test := range map[string]struct {
input string