diff --git a/executor.go b/executor.go index 2154b60ff..e74cd63ba 100644 --- a/executor.go +++ b/executor.go @@ -84,29 +84,31 @@ func (e *Executor) Execute(ctx context.Context, index string, q *pql.Query, slic // to send queries to different slices based on orientation. var inverseSlices []uint64 - // If slices aren't specified, then include all of them. - if len(slices) == 0 { - // Determine slices and inverseSlices for use in e.executeCall(). - if needsSlices { - // Round up the number of slices. - idx := e.Holder.Index(index) - if idx == nil { - return nil, ErrIndexNotFound - } - maxSlice := idx.MaxSlice() - maxInverseSlice := idx.MaxInverseSlice() + // If slices are specified, then use that value for slices or + // inverseSlices. If slices aren't specified, then include all of them. + if len(slices) > 0 { + // For inverse queries, the values of `slices` provided to the Execute() method + // on the remote node actually represents inverseSlices. + inverseSlices = slices + } else if needsSlices { + // Round up the number of slices. + idx := e.Holder.Index(index) + if idx == nil { + return nil, ErrIndexNotFound + } + maxSlice := idx.MaxSlice() + maxInverseSlice := idx.MaxInverseSlice() - // Generate a slices of all slices. - slices = make([]uint64, maxSlice+1) - for i := range slices { - slices[i] = uint64(i) - } + // Generate a slices of all slices. + slices = make([]uint64, maxSlice+1) + for i := range slices { + slices[i] = uint64(i) + } - // Generate a slices of all inverse slices. - inverseSlices = make([]uint64, maxInverseSlice+1) - for i := range inverseSlices { - inverseSlices[i] = uint64(i) - } + // Generate a slices of all inverse slices. + inverseSlices = make([]uint64, maxInverseSlice+1) + for i := range inverseSlices { + inverseSlices[i] = uint64(i) } } @@ -118,7 +120,6 @@ func (e *Executor) Execute(ctx context.Context, index string, q *pql.Query, slic // Execute each call serially. results := make([]interface{}, 0, len(q.Calls)) for _, call := range q.Calls { - if call.SupportsInverse() && needsSlices { // Fetch frame & row label based on argument. frame, _ := call.Args["frame"].(string) @@ -147,7 +148,6 @@ func (e *Executor) Execute(ctx context.Context, index string, q *pql.Query, slic // executeCall executes a call. func (e *Executor) executeCall(ctx context.Context, index string, c *pql.Call, slices []uint64, opt *ExecOptions) (interface{}, error) { - if err := e.validateCallArgs(c); err != nil { return nil, err } diff --git a/server/server_test.go b/server/server_test.go index da62273a4..497ffd7da 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -26,6 +26,7 @@ import ( "strings" "testing" "testing/quick" + "time" "github.com/BurntSushi/toml" "github.com/pilosa/pilosa" @@ -234,6 +235,49 @@ func TestMain_SetColumnAttrs(t *testing.T) { } } +// Ensure inverse slices get handled correctly in a multi-node query. +func TestMain_InverseSlices(t *testing.T) { + mains := test.MustRunMainWithCluster(t, 2) + + m0 := mains[0] + m1 := mains[1] + + // Make sure to use node0 in the cluster. + var m *test.Main + if m0.Server.NodeID < m1.Server.NodeID { + m = m0 + } else { + m = m1 + } + + // Create frames. + client := m.Client() + if err := client.CreateIndex(context.Background(), "i", pilosa.IndexOptions{}); err != nil && err != pilosa.ErrIndexExists { + t.Fatal("create index:", err) + } + if err := client.CreateFrame(context.Background(), "i", "f", pilosa.FrameOptions{InverseEnabled: true}); err != nil { + t.Fatal("create frame:", err) + } + + // Write data on cluster. + if _, err := m.Query("i", "", fmt.Sprintf(` + SetBit(col=1, frame="f", row=1000) + SetBit(col=1, frame="f", row=2000) + SetBit(col=1, frame="f", row=%d) + `, 1*pilosa.SliceWidth)); err != nil { + t.Fatal("setting bits:", err) + } + + time.Sleep(1 * time.Second) + + // Query the cluster. + if res, err := m.Query("i", "", `Bitmap(col=1, frame="f")`); err != nil { + t.Fatal("another bitmap query:", err) + } else if res != fmt.Sprintf(`{"results":[{"attrs":{},"bits":[1000,2000,%d]}]}`, 1*pilosa.SliceWidth)+"\n" { + t.Fatalf("unexpected result: %s", res) + } +} + // Ensure program can set bits on one cluster and then restore to a second cluster. func TestMain_FrameRestore(t *testing.T) { mains1 := test.MustRunMainWithCluster(t, 2)