Skip to content

Commit

Permalink
Optimize transform.restrict. Push whatever can be pushed down the t…
Browse files Browse the repository at this point in the history
…ree.
  • Loading branch information
blambeau committed Jun 27, 2024
1 parent 887888f commit 466495c
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.23.1

* Optimize `summarize.restrict`. Push whatever can be pushed down the
tree.

## 0.23.0 - 2024-06-27

* Add `Bmg.json` and `Bmg.yaml` factory methods, to get relations on top of
Expand Down
20 changes: 20 additions & 0 deletions lib/bmg/operator/summarize.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@ def initialize(type, operand, by, summarization)

attr_reader :by, :summarization

protected # optimization

def _restrict(type, predicate)
return super unless type.knows_attrlist?

# bottom only uses attributes of the `by` list
# and can be pushed down the tree
summaries = type.attrlist - by
top, bottom = predicate.and_split(summaries)
if top == predicate
super
else
op = operand
op = op.restrict(bottom)
op = op.summarize(by, summarization)
op = op.restrict(top)
op
end
end

public

def each
Expand Down
100 changes: 100 additions & 0 deletions spec/unit/optimizer/test_summarize.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
require 'spec_helper'
module Bmg
describe "summarize optimization" do

context "summarize.restrict" do
subject {
Relation.new([
{ a: 1, b: 2 },
{ a: 11, b: 2 }
]).summarize(by, sums).restrict(predicate)
}

context 'when no optimization is possible' do
let(:by) {
[:a]
}
let(:sums) {
{:b => :sum}
}
let(:predicate) {
Predicate.gt(:b, 100)
}

it 'does not optimize' do
expect(subject).to be_a(Operator::Restrict)
expect(operand).to be_a(Operator::Summarize)
expect(subject.send(:predicate)).to eql(predicate)
end
end

context 'when predicate is fully on by' do
let(:by) {
[:a]
}
let(:sums) {
{:b => :sum}
}
let(:predicate) {
Predicate.eq(:a, 1)
}

it 'pushes restrict down the tree' do
expect(subject).to be_a(Operator::Summarize)
expect(operand).to be_a(Operator::Restrict)
expect(operand.send(:predicate)).to eql(predicate)
end
end

context 'when predicate is on both' do
let(:by) {
[:a]
}
let(:sums) {
{:b => :sum}
}
let(:p1) {
Predicate.eq(:a, 1)
}
let(:p2) {
Predicate.lt(:b, 2)
}
let(:predicate) {
p1 & p2
}

it 'splits the predicate' do
expect(subject).to be_a(Operator::Restrict)
expect(subject.send(:predicate)).to eql(p2)
expect(operand).to be_a(Operator::Summarize)
expect(operand.send(:operand)).to be_a(Operator::Restrict)
expect(operand.send(:operand).send(:predicate)).to eql(p1)
end
end

context "when predicate is on both but can't be split" do
let(:by) {
[:a]
}
let(:sums) {
{:b => :sum}
}
let(:p1) {
Predicate.eq(:a, 1)
}
let(:p2) {
Predicate.lt(:b, 2)
}
let(:predicate) {
p1 | p2
}

it 'does not optimize' do
expect(subject).to be_a(Operator::Restrict)
expect(operand).to be_a(Operator::Summarize)
expect(subject.send(:predicate)).to eql(predicate)
end
end
end
end
end

0 comments on commit 466495c

Please sign in to comment.