require File.dirname(__FILE__) + "/spec_helper"

describe DataMapper::Adapters::Sql::Commands::LoadCommand do
  
  def conditions_for(klass, options = {})
    database_context = database(:mock)
    DataMapper::Adapters::Sql::Commands::LoadCommand.new(
      database_context.adapter, database_context, klass, options
    ).conditions
  end
  
  it 'empty? should be false if conditions are present' do
    conditions_for(Zoo, :name => 'Galveston').should_not be_empty
  end
  
  it 'should map implicit option names to field names' do
    conditions_for(Zoo, :name => 'Galveston').should eql([["`name` = ?", 'Galveston']])
  end
  
  it 'should qualify with table name when using a join' do
    conditions_for(Zoo, :name => 'Galveston', :include => :exhibits).should eql([["`zoos`.`name` = ?", 'Galveston']])
  end
  
  it 'should use Symbol::Operator to determine operator' do
    conditions_for(Person, :age.gt => 28).should eql([["`age` > ?", 28]])
    conditions_for(Person, :age.gte => 28).should eql([["`age` >= ?", 28]])
    
    conditions_for(Person, :age.lt => 28).should eql([["`age` < ?", 28]])
    conditions_for(Person, :age.lte => 28).should eql([["`age` <= ?", 28]])
    
    conditions_for(Person, :age.not => 28).should eql([["`age` <> ?", 28]])
    conditions_for(Person, :age.eql => 28).should eql([["`age` = ?", 28]])
    
    conditions_for(Person, :name.like => 'S%').should eql([["`name` LIKE ?", 'S%']])
    
    conditions_for(Person, :age.in => [ 28, 29 ]).should eql([["`age` IN ?", [ 28, 29 ]]])
  end
  
  it 'should use an IN clause for an Array' do
    conditions_for(Person, :age => [ 28, 29 ]).should eql([["`age` IN ?", [ 28, 29 ]]])
  end
  
  it 'should use "not" for not-equal operations' do
    conditions_for(Person, :name.not => 'Bob').should eql([["`name` <> ?", 'Bob']])
    conditions_for(Person, :name.not => nil).should eql([["`name` IS NOT ?", nil]])
    conditions_for(Person, :name.not => ['Sam', 'Bob']).should eql([["`name` NOT IN ?", ['Sam', 'Bob']]])
  end

end

describe DataMapper::Adapters::Sql::Commands::LoadCommand do
  
  before(:all) do
    fixtures(:zoos)
    fixtures(:animals)
    fixtures(:people)
  end
  
  after(:all) do
    fixtures(:people)
  end
  
  def loader_for(klass, options = {})
    database_context = database(:mock)
    DataMapper::Adapters::Sql::Commands::LoadCommand.new(database_context.adapter, database_context, klass, options)
  end
  
  it "should return a Struct for custom queries" do
    results = database.query("SELECT * FROM zoos WHERE name = ?", 'Galveston')
    zoo = results.first
    zoo.class.superclass.should == Struct
    zoo.name.should == "Galveston"
  end

  it "should return a simple select statement for a given class" do
    loader_for(Zoo).to_parameterized_sql.first.should == 'SELECT `id`, `name`, `updated_at` FROM `zoos`'
  end

  it "should include only the columns specified in the statement" do
    loader_for(Zoo, :select => [:name]).to_parameterized_sql.first.should == 'SELECT `name` FROM `zoos`'
  end

  it "should optionally include lazy-loaded columns in the statement" do
    loader_for(Zoo, :include => :notes).to_parameterized_sql.first.should == 'SELECT `id`, `name`, `updated_at`, `notes` FROM `zoos`'
  end

  it "should join associations in the statement" do
    loader_for(Zoo, :include => :exhibits).to_parameterized_sql.first.should == <<-EOS.compress_lines
      SELECT `zoos`.`id`, `zoos`.`name`, `zoos`.`updated_at`,
        `exhibits`.`id`, `exhibits`.`name`, `exhibits`.`zoo_id`
      FROM `zoos`
      JOIN `exhibits` ON `exhibits`.`zoo_id` = `zoos`.`id`
    EOS
  end

  it "should join has and belongs to many associtions in the statement" do
    loader_for(Animal, :include => :exhibits).to_parameterized_sql.first.should == <<-EOS.compress_lines
      SELECT `animals`.`id`, `animals`.`name`, `animals`.`nice`,
        `exhibits`.`id`, `exhibits`.`name`, `exhibits`.`zoo_id`,
        `animals_exhibits`.`animal_id`, `animals_exhibits`.`exhibit_id`
      FROM `animals`
      JOIN `animals_exhibits` ON `animals_exhibits`.`animal_id` = `animals`.`id`
      JOIN `exhibits` ON `exhibits`.`id` = `animals_exhibits`.`exhibit_id`
    EOS
  end
  
  it "should shallow-join unmapped tables for has-and-belongs-to-many in the statement" do
    loader_for(Animal, :shallow_include => :exhibits).to_parameterized_sql.first.should == <<-EOS.compress_lines
      SELECT `animals`.`id`, `animals`.`name`, `animals`.`nice`,
        `animals_exhibits`.`animal_id`, `animals_exhibits`.`exhibit_id`
      FROM `animals`
      JOIN `animals_exhibits` ON `animals_exhibits`.`animal_id` = `animals`.`id`
    EOS
  end
  
  it "should allow multiple implicit conditions" do
    expected_sql = <<-EOS.compress_lines
      SELECT `id`, `name`, `age`, `occupation`,
        `type`, `street`, `city`, `state`, `zip_code`
      FROM `people`
      WHERE (`name` = ?) AND (`age` = ?)
    EOS
    
    # NOTE: I'm actually not sure how to test this since the order of the parameters isn't gauranteed.
    # Maybe an ugly OrderedHash passed as the options...
    # loader_for(Person, :name => 'Sam', :age => 29).to_parameterized_sql.should == [expected_sql, 'Sam', 29]
  end
  
  it "should allow block-interception during load" do
    result = false
    Person.first(:intercept_load => lambda { result = true })
    result.should == true
  end
  
  it 'database-specific load should not fail' do

     DataMapper::database do |db|
       froggy = db.first(Animal, :conditions => ['name = ?', 'Frog'])
       froggy.name.should == 'Frog'
     end

   end

   it 'current-database load should not fail' do
     froggy = DataMapper::database.first(Animal).name.should == 'Frog'
   end

   it 'load through ActiveRecord impersonation should not fail' do
     Animal.find(:all).size.should == 16
   end

   it 'load through Og impersonation should not fail' do
     Animal.all.size.should == 16
   end

   it ':conditions option should accept a hash' do
     Animal.all(:conditions => { :name => 'Frog' }).size.should == 1
   end

   it 'non-standard options should be considered part of the conditions' do
     database.logger.debug { 'non-standard options should be considered part of the conditions' }
     zebra = Animal.first(:name => 'Zebra')
     zebra.name.should == 'Zebra'

     elephant = Animal.first(:name => 'Elephant')
     elephant.name.should == 'Elephant'

     aged = Person.all(:age => 29)
     aged.size.should == 2
     aged.first.name.should == 'Sam'
     aged.last.name.should == 'Bob'

     fixtures(:animals)
   end

   it 'should not find deleted objects' do
     database do
       wally = Animal.first(:name => 'Whale')
       wally.new_record?.should == false
       wally.destroy!.should == true

       wallys_evil_twin = Animal.first(:name => 'Whale')
       wallys_evil_twin.should == nil

       wally.new_record?.should == true
       wally.save
       wally.new_record?.should == false

       Animal.first(:name => 'Whale').should == wally
     end
   end

   it 'lazy-loads should issue for whole sets' do
     people = Person.all

     people.each do |person|
       person.notes
     end
   end

   it "should only query once" do
     database do
       zoo = Zoo.first
       same_zoo = Zoo[zoo.id]
       
       zoo.should == same_zoo
     end
   end
   
   it "should return a single object" do
     Zoo.first.should be_a_kind_of(Zoo)
     Zoo[1].should be_a_kind_of(Zoo)
     Zoo.find(1).should be_a_kind_of(Zoo)
   end
   
   # TICKET: http://wm.lighthouseapp.com/projects/4819-datamapper/tickets/90
   it "should return a CLEAN object" do
     Animal[2].should_not be_dirty
     Animal.first(:name => 'Cup').should_not be_dirty
   end
   
   it "should retrieve altered integer columns correctly" do
     pending "see http://wm.lighthouseapp.com/projects/4819-datamapper/tickets/95"
     sam = Person.first
     sam.age = 6471561394
     sam.save
     sam.reload
     sam.original_values[:age].should == 6471561394
     sam.age.should == 6471561394
   end
   
   it "should be able to search on UTF-8 strings" do
     Zoo.create(:name => 'Danish Vowels: Smoot!') # ΓΈΓΈ
     Zoo.first(:name.like => '%Smoot%').should be_a_kind_of(Zoo)
   end
   
   it "should destructively reload the loaded attributes of an object" do
     zoo = Zoo.first(:name => 'Dallas')
     zoo.name.should eql('Dallas')
     zoo.name = 'bob'
     zoo.name.should eql('bob')
     zoo.reload!
     zoo.name.should eql('Dallas')
   end
   
   # See the comment in dataobjects_spec for why this is failing
   unless ENV["ADAPTER"] == "mysql"
     it "should return nil when finding by id, and the id is not present and/or invalid" do
       Zoo.find(nil).should be_nil
     end
  end
   
   # it "should return in order" do
   #   pending("This spec is silly, and nothing but trouble since it depends on the table's clustered index. :-p")
   #   fixtures(:posts)
   #        
   #   one = Post.first
   #   one.title.should eql('One')
   #   two = one.next
   #   two.title.should eql('Two')
   #   one.next.next.previous.previous.next.previous.next.next.title.should eql('Three')
   # end
   
   it "should allow both implicit :conditions and explicit in the same finder" do
     cup = Animal.first(:name => 'Cup', :conditions => ['name <> ?', 'Frog'])
     cup.should == Animal[cup.key]
   end
   
   it "should iterate in batches" do
     
     total = Animal.count
     count = 0
     
     Animal.each(:name.not => nil) do |animal|
       count += 1
     end
     
     count.should == total
     
     count = 0
     
     Animal.each(:order => "id asc", :conditions => ["id > ? AND id < ?", 0, 9985], :limit => 2) do |animal|
       count += 1
     end
     
     count.should == total
   end
   
   it "should get the right object back" do
     a = Animal.first(:name => 'Cup')
     Animal.get(a.id).should == a
     
     b = Person.first(:name => 'Amy')
     Person.get(b.id).should == b
     
     c = Person.first(:name => 'Bob')
     Person.get(c.id).should == c
     
     database.execute("UPDATE people SET type = ? WHERE name = ?", nil, "Bob")
     
     d = Person.first(:name => 'Bob')
     Person.get(d.id).should == d
   end
   
end

=begin
context 'Sub-selection' do
  
  specify 'should return a Cup' do
    Animal[:id.select => { :name => 'cup' }].name.should == 'Cup'
  end
  
  specify 'should return all exhibits for Galveston zoo' do
    Exhibit.all(:zoo_id.select(Zoo) => { :name => 'Galveston' }).size.should == 3
  end
  
  specify 'should allow a sub-select in the select-list' do
    Animal[:select => [ :id.count ]]
  end
end
=end