require 'spec_helper'

describe ThinkingSphinx::Index::Builder do
  let(:internal_attribute_count) {
    Riddle.loaded_version.to_i < 2 ? 3 : 4
  }
  describe ".generate without source scope" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name, last_name
        has birthday
        has id, :as => :internal_id

        set_property :sql_range_step => 1000

        where "birthday <= NOW()"
        group_by "first_name"
      end

      @source = @index.sources.first
    end

    it "should return an index" do
      @index.should be_a_kind_of(ThinkingSphinx::Index)
    end

    it "should have one source for the index" do
      @index.sources.length.should == 1
    end

    it "should have two fields" do
      @source.fields.length.should == 2
      @source.fields[0].unique_name.should == :first_name
      @source.fields[1].unique_name.should == :last_name
    end

    it "should have two attributes alongside the internal ones" do
      @source.attributes.length.should == 2 + internal_attribute_count
      @source.attributes[internal_attribute_count].unique_name.should == :birthday
      @source.attributes[1 + internal_attribute_count].unique_name.should == :internal_id
    end

    it "should have one condition" do
      @source.conditions.length.should == 1
      @source.conditions.first.should == "birthday <= NOW()"
    end

    it "should have one grouping" do
      @source.groupings.length.should == 1
      @source.groupings.first.should == "first_name"
    end

    it "should have one option" do
      @source.options.length.should == 1
      @source.options[:sql_range_step].should == 1000
    end
  end

  describe 'aliased field' do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name, :as => 'name'
      end

      @source = @index.sources.first
    end

    it "should store the alias as a symbol for consistency" do
      @source.fields.last.unique_name.should == :name
    end
  end

  describe 'aliased attribute' do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name
        has :id, :as => 'real_id'
      end

      @source = @index.sources.first
    end

    it "should store the alias as a symbol for consistency" do
      @source.attributes.last.unique_name.should == :real_id
    end
  end

  describe "sortable field" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name, :sortable => true
      end

      @source = @index.sources.first
    end

    it "should have one field" do
      @source.fields.length.should == 1
    end

    it "should have one attribute alongside the internal ones" do
      @source.attributes.length.should == 1 + internal_attribute_count
    end

    it "should set the attribute name to have the _sort suffix" do
      @source.attributes.last.unique_name.should == :first_name_sort
    end

    it "should set the attribute column to be the same as the field" do
      @source.attributes.last.columns.length.should == 1
      @source.attributes.last.columns.first.__name.should == :first_name
    end
  end

  describe '#join' do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name

        join contacts
      end

      @source = @index.sources.first
    end

    it "should include the explicit join" do
      @source.joins.length.should == 1
    end
  end

  describe "faceted field" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name, :facet => true
      end

      @source = @index.sources.first
    end

    after :each do
      Person.sphinx_facets.delete_at(-1)
    end

    it "should have one field" do
      @source.fields.length.should == 1
    end

    it "should have one attribute alongside the internal ones" do
      @source.attributes.length.should == 1 + internal_attribute_count
    end

    it "should set the attribute name to have the _facet suffix" do
      @source.attributes.last.unique_name.should == :first_name_facet
    end

    it "should set the attribute type to integer" do
      @source.attributes.last.type.should == :integer
    end

    it "should set the attribute column to be the same as the field" do
      @source.attributes.last.columns.length.should == 1
      @source.attributes.last.columns.first.__name.should == :first_name
    end
  end

  describe "faceted integer attribute" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Alpha) do
        indexes :name
        has value, :facet => true
      end

      @source = @index.sources.first
    end

    after :each do
      Alpha.sphinx_facets.delete_at(-1)
    end

    it "should have just one attribute alongside the internal ones" do
      @source.attributes.length.should == 1 + internal_attribute_count
    end
  end

  describe "faceted timestamp attribute" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name
        has birthday, :facet => true
      end

      @source = @index.sources.first
    end

    after :each do
      Person.sphinx_facets.delete_at(-1)
    end

    it "should have just one attribute alongside the internal ones" do
      @source.attributes.length.should == 1 + internal_attribute_count
    end
  end

  describe "faceted boolean attribute" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Beta) do
        indexes :name
        has delta, :facet => true
      end

      @source = @index.sources.first
    end

    after :each do
      Beta.sphinx_facets.delete_at(-1)
    end

    it "should have just one attribute alongside the internal ones" do
      @source.attributes.length.should == 1 + internal_attribute_count
    end
  end

  describe "faceted float attribute" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Alpha) do
        indexes :name
        has cost, :facet => true
      end

      @source = @index.sources.first
    end

    after :each do
      Alpha.sphinx_facets.delete_at(-1)
    end

    it "should have just one attribute alongside the internal ones" do
      @source.attributes.length.should == 1 + internal_attribute_count
    end
  end

  describe "faceted string attribute" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name
        has last_name, :facet => true
      end

      @source = @index.sources.first
    end

    after :each do
      Person.sphinx_facets.delete_at(-1)
    end

    it "should have two attributes alongside the internal ones" do
      @source.attributes.length.should == 2 + internal_attribute_count
    end

    it "should set the facet attribute name to have the _facet suffix" do
      @source.attributes.last.unique_name.should == :last_name_facet
    end

    it "should set the attribute type to integer" do
      @source.attributes.last.type.should == :integer
    end

    it "should set the attribute column to be the same as the field" do
      @source.attributes.last.columns.length.should == 1
      @source.attributes.last.columns.first.__name.should == :last_name
    end
  end

  describe 'faceted manual MVA' do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name
        has 'SQL STATEMENT', :type => :multi, :as => :sql, :facet => true
      end

      @source = @index.sources.first
    end

    after :each do
      Person.sphinx_facets.delete_at(-1)
    end

    it "should have two attributes alongside the internal ones" do
      @source.attributes.length.should == 2 + internal_attribute_count
    end

    it "should set the facet attribute name to have the _facet suffix" do
      @source.attributes.last.unique_name.should == :sql_facet
    end

    it "should keep the original attribute's name set as requested" do
      @source.attributes[-2].unique_name.should == :sql
    end

    it "should set the attribute type to multi" do
      @source.attributes.last.type.should == :multi
    end

    it "should set the attribute column to be the same as the field" do
      @source.attributes.last.columns.length.should == 1
      @source.attributes.last.columns.first.__name.should == 'SQL STATEMENT'
    end
  end

  describe 'faceted MVA field' do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes tags(:name), :as => :tags, :facet => true
      end

      @source = @index.sources.first
    end

    after :each do
      Person.sphinx_facets.delete_at(-1)
    end

    it "should have one field" do
      @source.fields.length.should == 1
    end

    it "should have one attribute alongside the internal ones" do
      @source.attributes.length.should == 1 + internal_attribute_count
    end

    it "should set the attribute name to have the _facet suffix" do
      @source.attributes.last.unique_name.should == :tags_facet
    end

    it "should set the attribute type to multi" do
      @source.attributes.last.type.should == :multi
    end

    it "should set the attribute column to be the same as the field" do
      @source.attributes.last.columns.length.should == 1
      @source.attributes.last.columns.first.__name.should == :name
    end
  end

  describe "no fields" do
    it "should raise an exception" do
      lambda {
        ThinkingSphinx::Index::Builder.generate(Person) do
          #
        end
      }.should raise_error
    end
  end

  describe "explicit source" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        define_source do
          indexes first_name, last_name
          has birthday
          has id, :as => :internal_id

          set_property :delta => true

          where "birthday <= NOW()"
          group_by "first_name"
        end
      end

      @source = @index.sources.first
    end

    it "should return an index" do
      @index.should be_a_kind_of(ThinkingSphinx::Index)
    end

    it "should have one source for the index" do
      @index.sources.length.should == 1
    end

    it "should have two fields" do
      @source.fields.length.should == 2
      @source.fields[0].unique_name.should == :first_name
      @source.fields[1].unique_name.should == :last_name
    end

    it "should have two attributes alongside the internal ones" do
      @source.attributes.length.should == 2 + internal_attribute_count
      @source.attributes[internal_attribute_count].unique_name.should == :birthday
      @source.attributes[1 + internal_attribute_count].unique_name.should == :internal_id
    end
  end

  describe "multiple sources" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        define_source do
          indexes first_name
          has birthday
        end

        define_source do
          indexes last_name
          has :id, :as => :internal_id
        end
      end
    end

    it "should have two sources" do
      @index.sources.length.should == 2
    end

    it "should have two fields" do
      @index.fields.length.should == 2
    end

    it "should have one field in each source" do
      @index.sources.each do |source|
        source.fields.length.should == 1
      end
    end

    it "should have two attributes alongside the six internal ones" do
      @index.attributes.length.should == 2 + (internal_attribute_count * 2)
    end

    it "should have one attribute in each source alongside the internal ones" do
      @index.sources.each do |source|
        source.attributes.length.should == 1 + internal_attribute_count
      end
    end
  end

  describe "multiple local indexes" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name

        use_local_index :other_local_index_1
        use_local_indices "other_local_index_2", "other_local_index_3"
      end
    end

    it "should have three additional indexes" do
      @index.additional_indices.length.should == 3
    end

    it "should append _core to the name of each local index" do
      @index.additional_indices[0].should eql("other_local_index_1_core")
      @index.additional_indices[1].should eql("other_local_index_2_core")
      @index.additional_indices[2].should eql("other_local_index_3_core")
    end
  end

  describe "index options" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name

        set_property :charset_type => "utf16"
        set_property :group_concat_max_len => 1024
      end
    end

    it "should store the index setting for the index" do
      @index.local_options[:charset_type].should == "utf16"
    end

    it "should store non-Sphinx settings for the index" do
      @index.local_options[:group_concat_max_len].should == 1024
    end
  end

  describe "delta options" do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name

        set_property :delta => true
      end
    end

    it "should not keep the delta setting in source options" do
      @index.sources.first.options.should be_empty
    end

    it "should not keep the delta setting in index options" do
      @index.local_options.should be_empty
    end

    it "should set the index delta object set" do
      @index.delta_object.should be_a_kind_of(ThinkingSphinx::Deltas::DefaultDelta)
    end
  end

  context 'index options' do
    before :each do
      @index = ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name

        set_property :index_exact_words => true
      end
    end

    it "should track the index_exact_words option to the index" do
      @index.local_options[:index_exact_words].should be_true
    end
  end

  context 'with an explicit name' do
    it "should set the index's name using the provided value" do
      index = ThinkingSphinx::Index::Builder.generate(Person, 'custom') do
        indexes first_name
      end

      index.name.should == 'custom'
    end
  end

  describe "sanitize_sql" do
    def index
      @index ||= ThinkingSphinx::Index::Builder.generate(Person) do
        indexes first_name, last_name
        where sanitize_sql(["gender = ?", "female"])
      end
    end

    it "should be aliased to ActiveRecord::Base.sanitize_sql" do
      index.sources.first.conditions.first.should == index.model.send(:sanitize_sql, ["gender = ?", "female"])
    end
  end
end