# encoding: utf-8
require 'spec_helper'

describe Algebra::Restriction, '#optimize' do
  subject { object.optimize }

  let(:body)     { LazyEnumerable.new([[1]])               }
  let(:relation) { Relation.new([[:id, Integer]], body)    }
  let(:operand)  { relation                                }
  let(:object)   { described_class.new(operand, predicate) }

  context 'with a tautology' do
    let(:predicate) { Function::Proposition::Tautology.instance }

    it { should be(relation) }

    it 'does not execute body#each' do
      expect(body).not_to receive(:each)
      subject
    end

    it_should_behave_like 'an optimize method'
  end

  context 'with a contradiction' do
    let(:predicate) { Function::Proposition::Contradiction.instance }

    it { should eql(Relation::Empty.new(relation.header)) }

    it 'returns an equivalent relation to the unoptimized operation' do
      should == object
    end

    it 'does not execute body#each' do
      expect(body).not_to receive(:each)
      subject
    end

    it_should_behave_like 'an optimize method'
  end

  context 'with a predicate' do
    let(:predicate) { relation[:id].eq(1) }

    it { should be(object) }

    it 'does not execute body#each' do
      expect(body).not_to receive(:each)
      subject
    end

    it_should_behave_like 'an optimize method'
  end

  context 'with an optimizable predicate' do
    let(:predicate) { relation[:id].eq(1).and(Function::Proposition::Tautology.instance) }

    it { should_not be(object) }

    it { should be_kind_of(described_class) }

    its(:predicate) { should eql(relation[:id].eq(1)) }

    its(:operand) { should be(relation) }

    it 'returns an equivalent relation to the unoptimized operation' do
      should == object
    end

    it 'does not execute body#each' do
      expect(body).not_to receive(:each)
      subject
    end

    it_should_behave_like 'an optimize method'
  end

  context 'with an optimizable operand' do
    let(:operand)    { relation.project(relation.header) }
    let(:predicate)  { relation[:id].eq(1)               }

    it { should_not be(object) }

    it { should be_kind_of(described_class) }

    its(:predicate) { should be(predicate) }

    its(:operand) { should be(relation) }

    it 'returns an equivalent relation to the unoptimized operation' do
      should == object
    end

    it 'does not execute body#each' do
      expect(body).not_to receive(:each)
      subject
    end

    it_should_behave_like 'an optimize method'
  end

  context 'with an empty relation' do
    let(:operand)   { Relation::Empty.new([[:id, Integer]]) }
    let(:predicate) { operand[:id].gte(1)                   }

    it { should be(operand) }

    it 'returns an equivalent relation to the unoptimized operation' do
      should == object
    end

    it 'does not execute body#each' do
      expect(body).not_to receive(:each)
      subject
    end

    it_should_behave_like 'an optimize method'
  end

  context 'with an empty relation when optimized' do
    let(:operand)   { described_class.new(relation, Function::Proposition::Contradiction.instance) }
    let(:predicate) { operand[:id].gte(1)                                                          }

    it { should eql(Relation::Empty.new(relation.header)) }

    it 'returns an equivalent relation to the unoptimized operation' do
      should == object
    end

    it 'does not execute body#each' do
      expect(body).not_to receive(:each)
      subject
    end

    it_should_behave_like 'an optimize method'
  end

  context 'with a restriction' do
    let(:other_predicate) { relation[:id].lt(2)                            }
    let(:operand)         { described_class.new(relation, other_predicate) }
    let(:predicate)       { operand[:id].gte(1)                            }

    it { should_not be(object) }

    it { should be_kind_of(described_class) }

    its(:predicate) { should eql(other_predicate & predicate) }

    its(:operand) { should be(relation) }

    it 'returns an equivalent relation to the unoptimized operation' do
      should == object
    end

    it 'does not execute body#each' do
      expect(body).not_to receive(:each)
      subject
    end

    it_should_behave_like 'an optimize method'
  end

  context 'with a set operation' do
    let(:left)      { Relation.new([[:id, Integer]], LazyEnumerable.new([[1]])) }
    let(:right)     { Relation.new([[:id, Integer]], LazyEnumerable.new([[2]])) }
    let(:operand)   { left.union(right)                                         }
    let(:predicate) { operand[:id].gte(1)                                       }

    it 'pushes the object to each relation' do
      should eql(left.restrict { |r| r.id.gte(1) }.union(right.restrict { |r| r.id.gte(1) }))
    end

    it 'returns an equivalent relation to the unoptimized operation' do
      should == object
    end

    it 'does not execute body#each' do
      expect(body).not_to receive(:each)
      subject
    end

    it_should_behave_like 'an optimize method'
  end

  context 'with a sorted operation' do
    let(:operand)   { relation.sort_by { |r| r.id } }
    let(:predicate) { operand[:id].gte(1)           }

    it 'cancels out the sorted' do
      should eql(relation.restrict { predicate })
    end

    it 'returns an equivalent relation to the unoptimized operation' do
      should == object
    end

    it 'does not execute body#each' do
      expect(body).not_to receive(:each)
      subject
    end

    it_should_behave_like 'an optimize method'
  end

  context 'containing a materialized relation' do
    let(:operand)   { Relation.new([[:id, Integer]], [[1], [2]]) }
    let(:predicate) { operand[:id].eq(1)                         }

    it { should eql(Relation::Materialized.new([[:id, Integer]], [[1]])) }

    it 'returns an equivalent relation to the unoptimized operation' do
      should == object
    end

    it_should_behave_like 'an optimize method'
  end
end
