require File.expand_path('../../../spec_helper', __FILE__)
require 'sequel/oracle_extensions/merge'
require 'sequel/oracle_extensions/hints'

describe "Sequel::OracleExtensions::Hints" do

  CLAUSES = %w(SELECT INSERT UPDATE DELETE MERGE)
  TYPES = CLAUSES.map{|clause| clause.downcase.intern}

  before(:all) do
    @db = Sequel.connect(DATABASE_URL)
  end

	def apply_hints!(*args)
	  @old_hints = @ds.opts[:hints]
	  @new_ds    = @ds.__send__(@method, *args)
	
	  @new_ds.should be_kind_of(Sequel::Dataset)
	  @new_ds.opts[:hints].should_not be_empty
	end
  
  it "hooks into dataset clause methods" do
    [Sequel::Dataset, Sequel::Oracle::DatasetMethods].each do |klass|
	    CLAUSES.each do |clause|
	      next unless klass.const_defined?(k = :"#{clause}_CLAUSE_METHODS")
		    klass.const_get(k).first.should == "#{clause.downcase}_hint_sql".intern
		  end
	  end
  end

  share_examples_for "dataset modifying" do
    after(:each) do
		  @ds.should equal(@new_ds)
		  @ds.opts[:hints].should_not == @old_hints
    end
  end
  
  share_examples_for "dataset cloning" do
    after(:each) do
		  @ds.should_not equal(@new_ds)
		  @ds.opts[:hints].should == @old_hints
    end
  end
  
  share_examples_for "standard callspec" do
	  it "callspec (String) applies :select hints" do
	    apply_hints! @hints.first
      hints_to_check(@new_ds, :select, @hints[0,1]).should == @hints[0,1]
	  end
	  it "callspec (String, ...) applies :select hints" do
	    apply_hints! *@hints
      hints_to_check(@new_ds, :select, @hints).should == @hints
	  end
	  it "callspec (clause, String) applies clause hints" do
	    TYPES.each do |type|
        apply_hints! type, @hints.first
	      hints_to_check(@new_ds, type, @hints[0,1]).should == @hints[0,1]
	    end
	  end
	  it "callspec (clause, String, ...) applies clause hints" do
	    TYPES.each do |type|
        apply_hints! type, *@hints
	      hints_to_check(@new_ds, type, @hints).should == @hints
	    end
	  end
  end

  share_examples_for "clause-specific callspec" do
	  it "callspec (String) applies hints" do
	    apply_hints! @hints.first
      hints_to_check(@new_ds, @clause, @hints[0,1]).should == @hints[0,1]
	  end
	  it "callspec (String, ...) applies hints" do
	    apply_hints! *@hints
      hints_to_check(@new_ds, @clause, @hints).should == @hints
	  end
  end
  
  describe "hints" do
    before(:each){ @ds, @hints = @db[:dual], ['foo', 'bar'] }
    
    COMMON_GROUP_BODY = Proc.new do |group,name|
      group.class_eval do
			  describe "##{name}" do
		      before(:each){ @method = :"#{name}" }
			    it_should_behave_like "dataset cloning"
			    it_should_behave_like "standard callspec"
			  end
			  describe "##{name}!" do
		      before(:each){ @method = :"#{name}!" }
			    it_should_behave_like "dataset modifying"
			    it_should_behave_like "standard callspec"
			  end
			  TYPES.each do |clause|
				  describe "##{clause}_#{name}" do
			      before(:each){ @clause, @method = clause, :"#{clause}_#{name}"}
				    it_should_behave_like "dataset cloning"
				    it_should_behave_like "clause-specific callspec"
				  end
				  describe "##{clause}_#{name}!" do
			      before(:each){ @clause, @method = clause, :"#{clause}_#{name}!"}
				    it_should_behave_like "dataset modifying"
				    it_should_behave_like "clause-specific callspec"
				  end
			  end
		  end
    end
	    
	  describe "adding hints" do
	    def hints_to_check(ds, type, input)
	      ds.opts[:hints][type][(@orig_hints[type].length rescue 0), input.length]
	    end
	    COMMON_GROUP_BODY.call self, :hint
	  end
	  
	  describe "overwriting hints" do
	    def hints_to_check(ds, type, input)
	      ds.opts[:hints][type]
	    end
	    COMMON_GROUP_BODY.call self, :hints
	  end
	  
    describe "#hint_sql" do
	    it "generates clause-specific hint SQL" do
	      # set them all up front so we can test whether they get mixed up.
        TYPES.each do |type| @ds.hints! type, "hint for #{type}" end
	      TYPES.each do |type|
	        sql = (clause = type.to_s.upcase).dup
	        @ds.hint_sql(type, sql).should equal(sql)
	        sql.should == "#{clause} /*+ hint for #{type} */"
	      end
	    end
	    
	    it "skips empty hints" do
	      TYPES.each do |type|
	        sql = (clause = type.to_s.upcase).dup
	        @ds.hint_sql(type, sql).should be_nil
	        sql.should == clause
	      end
	    end
	    
      TYPES.each do |type|
		    it "is called by ##{type}_hint_sql" do
	        clause = type.to_s.upcase
		      @ds.should_receive(:hint_sql).with(type, clause)
		      @ds.__send__ :"#{type}_hint_sql", clause
		    end
		    it "is called by ##{type}_sql" do
	        args, clause = [], type.to_s.upcase
		      @ds.should_receive(:hint_sql).with(type, clause)
		      args.push :dual, :dual, :x if type == :merge
		      @ds.__send__ :"#{type}_sql", *args
		    end
	    end
    end
  end
end