diff --git a/executor.go b/executor.go index 86d333b16..e6669b5e1 100644 --- a/executor.go +++ b/executor.go @@ -279,7 +279,7 @@ func (e *Executor) executeBitmapCallSlice(ctx context.Context, index string, c * // This first performs the TopN() to determine the top results and then // requeries to retrieve the full counts for each of the top results. func (e *Executor) executeTopN(ctx context.Context, index string, c *pql.Call, slices []uint64, opt *ExecOptions) ([]Pair, error) { - rowIDs, _, err := c.UintSliceArg("ids") + idsArg, _, err := c.UintSliceArg("ids") if err != nil { return nil, fmt.Errorf("executeTopN: %v", err) } @@ -296,7 +296,7 @@ func (e *Executor) executeTopN(ctx context.Context, index string, c *pql.Call, s // If this call is against specific ids, or we didn't get results, // or we are part of a larger distributed query then don't refetch. - if len(pairs) == 0 || len(rowIDs) > 0 || opt.Remote { + if len(pairs) == 0 || len(idsArg) > 0 || opt.Remote { return pairs, nil } // Only the original caller should refetch the full counts. @@ -344,6 +344,7 @@ func (e *Executor) executeTopNSlices(ctx context.Context, index string, c *pql.C // executeTopNSlice executes a TopN call for a single slice. func (e *Executor) executeTopNSlice(ctx context.Context, index string, c *pql.Call, slice uint64) ([]Pair, error) { frame, _ := c.Args["frame"].(string) + inverse, _ := c.Args["inverse"].(bool) n, _, err := c.UintArg("n") if err != nil { return nil, fmt.Errorf("executeTopNSlice: %v", err) @@ -380,7 +381,13 @@ func (e *Executor) executeTopNSlice(ctx context.Context, index string, c *pql.Ca frame = DefaultFrame } - f := e.Holder.Fragment(index, frame, ViewStandard, slice) + // Determine view. + view := ViewStandard + if inverse { + view = ViewInverse + } + + f := e.Holder.Fragment(index, frame, view, slice) if f == nil { return nil, nil } diff --git a/executor_test.go b/executor_test.go index 8ae53b0a8..64e47c935 100644 --- a/executor_test.go +++ b/executor_test.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "reflect" + "strconv" "strings" "testing" @@ -274,32 +275,55 @@ func TestExecutor_Execute_SetRowAttrs(t *testing.T) { func TestExecutor_Execute_TopN(t *testing.T) { hldr := MustOpenHolder() defer hldr.Close() + e := NewExecutor(hldr.Holder, NewCluster(1)) // Set bits for rows 0, 10, & 20 across two slices. - hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 0).SetBit(0, 0) - hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 0).SetBit(0, 1) - hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 1).SetBit(0, SliceWidth) - hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 1).SetBit(0, SliceWidth+2) - hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 5).SetBit(0, (5*SliceWidth)+100) - hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 0).SetBit(10, 0) - hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 1).SetBit(10, SliceWidth) - hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 1).SetBit(20, SliceWidth) - hldr.MustCreateRankedFragmentIfNotExists("i", "other", pilosa.ViewStandard, 0).SetBit(0, 0) + if idx, err := hldr.CreateIndex("i", pilosa.IndexOptions{}); err != nil { + t.Fatal(err) + } else if _, err := idx.CreateFrame("f", pilosa.FrameOptions{InverseEnabled: true}); err != nil { + t.Fatal(err) + } else if _, err := idx.CreateFrame("other", pilosa.FrameOptions{InverseEnabled: true}); err != nil { + t.Fatal(err) + } else if _, err := e.Execute(context.Background(), "i", MustParse(` + SetBit(frame=f, rowID=0, columnID=0) + SetBit(frame=f, rowID=0, columnID=1) + SetBit(frame=f, rowID=0, columnID=`+strconv.Itoa(SliceWidth)+`) + SetBit(frame=f, rowID=0, columnID=`+strconv.Itoa(SliceWidth+2)+`) + SetBit(frame=f, rowID=0, columnID=`+strconv.Itoa((5*SliceWidth)+100)+`) + SetBit(frame=f, rowID=10, columnID=0) + SetBit(frame=f, rowID=10, columnID=`+strconv.Itoa(SliceWidth)+`) + SetBit(frame=f, rowID=20, columnID=`+strconv.Itoa(SliceWidth)+`) + SetBit(frame=other, rowID=0, columnID=0) + `), nil, nil); err != nil { + t.Fatal(err) + } hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 0).RecalculateCache() + hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewInverse, 0).RecalculateCache() hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 1).RecalculateCache() hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 5).RecalculateCache() - // Execute query. - e := NewExecutor(hldr.Holder, NewCluster(1)) - if result, err := e.Execute(context.Background(), "i", MustParse(`TopN(frame=f, n=2)`), nil, nil); err != nil { - t.Fatal(err) - } else if !reflect.DeepEqual(result[0], []pilosa.Pair{ - {ID: 0, Count: 5}, - {ID: 10, Count: 2}, - }) { - t.Fatalf("unexpected result: %s", spew.Sdump(result)) - } + t.Run("Standard", func(t *testing.T) { + if result, err := e.Execute(context.Background(), "i", MustParse(`TopN(frame=f, n=2)`), nil, nil); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(result[0], []pilosa.Pair{ + {ID: 0, Count: 5}, + {ID: 10, Count: 2}, + }) { + t.Fatalf("unexpected result: %s", spew.Sdump(result)) + } + }) + + t.Run("Inverse", func(t *testing.T) { + if result, err := e.Execute(context.Background(), "i", MustParse(`TopN(frame=f, inverse=true, n=2)`), nil, nil); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(result[0], []pilosa.Pair{ + {ID: SliceWidth, Count: 3}, + {ID: 0, Count: 2}, + }) { + t.Fatalf("unexpected result: %s", spew.Sdump(result)) + } + }) } func TestExecutor_Execute_TopN_fill(t *testing.T) { hldr := MustOpenHolder() diff --git a/pql/ast.go b/pql/ast.go index 19b5381ed..cf404ad12 100644 --- a/pql/ast.go +++ b/pql/ast.go @@ -184,16 +184,20 @@ func (c *Call) String() string { // SupportsInverse indicates that the call may be on an inverse frame. func (c *Call) SupportsInverse() bool { - if c.Name == "Bitmap" { - return true - } - return false + return c.Name == "Bitmap" || c.Name == "TopN" } // IsInverse specifies if the call is for an inverse view. // Return defaults to false unless absolutely sure of inversion. func (c *Call) IsInverse(rowLabel, columnLabel string) bool { if c.SupportsInverse() { + // Top-n has an explicit inverse flag. + if c.Name == "TopN" { + inverse, _ := c.Args["inverse"].(bool) + return inverse + } + + // Bitmap calls use the row/column labels to determine whether inverse. _, rowOK, rowErr := c.UintArg(rowLabel) _, columnOK, columnErr := c.UintArg(columnLabel) if rowErr != nil || columnErr != nil {