require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')

describe "OracleEnhancedAdapter establish connection" do

  it "should connect to database" do
    ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
    ActiveRecord::Base.connection.should_not be_nil
    ActiveRecord::Base.connection.class.should == ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter
  end

  it "should connect to database as SYSDBA" do
    ActiveRecord::Base.establish_connection(SYS_CONNECTION_PARAMS)
    ActiveRecord::Base.connection.should_not be_nil
    ActiveRecord::Base.connection.class.should == ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter
  end

  it "should be active after connection to database" do
    ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
    ActiveRecord::Base.connection.should be_active
  end

  it "should not be active after disconnection to database" do
    ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
    ActiveRecord::Base.connection.disconnect!
    ActiveRecord::Base.connection.should_not be_active
  end

  it "should be active after reconnection to database" do
    ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
    ActiveRecord::Base.connection.reconnect!
    ActiveRecord::Base.connection.should be_active
  end
  
end

describe "OracleEnhancedAdapter" do
  before(:all) do
    ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
    @conn = ActiveRecord::Base.connection
  end
  
  describe "database session store" do
    before(:all) do
      @conn.execute <<-SQL
        CREATE TABLE sessions (
          id          NUMBER(38,0) NOT NULL,
          session_id  VARCHAR2(255) DEFAULT NULL,
          data        CLOB DEFAULT NULL,
          created_at  DATE DEFAULT NULL,
          updated_at  DATE DEFAULT NULL,
          PRIMARY KEY (ID)
        )
      SQL
      @conn.execute <<-SQL
        CREATE SEQUENCE sessions_seq  MINVALUE 1 MAXVALUE 999999999999999999999999999
          INCREMENT BY 1 START WITH 10040 CACHE 20 NOORDER NOCYCLE
      SQL
      if ENV['RAILS_GEM_VERSION'] >= '2.3'
        @session_class = ActiveRecord::SessionStore::Session
      else
        @session_class = CGI::Session::ActiveRecordStore::Session
      end
    end

    after(:all) do
      @conn.execute "DROP TABLE sessions"
      @conn.execute "DROP SEQUENCE sessions_seq"
    end

    it "should create sessions table" do
      ActiveRecord::Base.connection.tables.grep("sessions").should_not be_empty
    end

    it "should save session data" do
      @session = @session_class.new :session_id => "111111", :data  => "something" #, :updated_at => Time.now
      @session.save!
      @session = @session_class.find_by_session_id("111111")
      @session.data.should == "something"
    end

    it "should change session data when partial updates enabled" do
      return pending("Not in this ActiveRecord version") unless @session_class.respond_to?(:partial_updates=)
      @session_class.partial_updates = true
      @session = @session_class.new :session_id => "222222", :data  => "something" #, :updated_at => Time.now
      @session.save!
      @session = @session_class.find_by_session_id("222222")
      @session.data = "other thing"
      @session.save!
      # second save should call again blob writing callback
      @session.save!
      @session = @session_class.find_by_session_id("222222")
      @session.data.should == "other thing"
    end

    it "should have one enhanced_write_lobs callback" do
      return pending("Not in this ActiveRecord version") unless @session_class.respond_to?(:after_save_callback_chain)
      @session_class.after_save_callback_chain.select{|cb| cb.method == :enhanced_write_lobs}.should have(1).record
    end

    it "should not set sessions table session_id column type as integer if emulate_integers_by_column_name is true" do
      ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true
      columns = @conn.columns('sessions')
      column = columns.detect{|c| c.name == "session_id"}
      column.type.should == :string
    end

  end

  describe "ignore specified table columns" do
    before(:all) do
      @conn.execute <<-SQL
        CREATE TABLE test_employees (
          id            NUMBER,
          first_name    VARCHAR2(20),
          last_name     VARCHAR2(25),
          email         VARCHAR2(25),
          phone_number  VARCHAR2(20),
          hire_date     DATE,
          job_id        NUMBER,
          salary        NUMBER,
          commission_pct  NUMBER(2,2),
          manager_id    NUMBER(6),
          department_id NUMBER(4,0),
          created_at    DATE
        )
      SQL
      @conn.execute <<-SQL
        CREATE SEQUENCE test_employees_seq  MINVALUE 1
          INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE
      SQL
    end

    after(:all) do
      @conn.execute "DROP TABLE test_employees"
      @conn.execute "DROP SEQUENCE test_employees_seq"
    end

    after(:each) do
      Object.send(:remove_const, "TestEmployee")
      ActiveRecord::Base.connection.clear_ignored_table_columns
    end

    it "should ignore specified table columns" do
      class ::TestEmployee < ActiveRecord::Base
        ignore_table_columns  :phone_number, :hire_date
      end
      TestEmployee.connection.columns('test_employees').select{|c| ['phone_number','hire_date'].include?(c.name) }.should be_empty
    end

    it "should ignore specified table columns specified in several lines" do
      class ::TestEmployee < ActiveRecord::Base
        ignore_table_columns  :phone_number
        ignore_table_columns  :hire_date
      end
      TestEmployee.connection.columns('test_employees').select{|c| ['phone_number','hire_date'].include?(c.name) }.should be_empty
    end

    it "should not ignore unspecified table columns" do
      class ::TestEmployee < ActiveRecord::Base
        ignore_table_columns  :phone_number, :hire_date
      end
      TestEmployee.connection.columns('test_employees').select{|c| c.name == 'email' }.should_not be_empty
    end

    it "should ignore specified table columns in other connection" do
      class ::TestEmployee < ActiveRecord::Base
        ignore_table_columns  :phone_number, :hire_date
      end
      # establish other connection
      other_conn = ActiveRecord::Base.oracle_enhanced_connection(CONNECTION_PARAMS)
      other_conn.columns('test_employees').select{|c| ['phone_number','hire_date'].include?(c.name) }.should be_empty
    end

  end

  describe "without composite_primary_keys" do

    before(:all) do
      Object.send(:remove_const, 'CompositePrimaryKeys') if defined?(CompositePrimaryKeys)
      class ::Employee < ActiveRecord::Base
        set_primary_key :employee_id
      end
    end

    it "should tell ActiveRecord that count distinct is supported" do
      ActiveRecord::Base.connection.supports_count_distinct?.should be_true
    end

    it "should execute correct SQL COUNT DISTINCT statement" do
      lambda { Employee.count(:employee_id, :distinct => true) }.should_not raise_error
    end

  end


  describe "column quoting" do

    def create_test_reserved_words_table
      ActiveRecord::Schema.define do
        suppress_messages do
          create_table :test_reserved_words do |t|
            t.string      :varchar2
            t.integer     :integer
          end
        end
      end
    end

    after(:each) do
      ActiveRecord::Schema.define do
        suppress_messages do
          drop_table :test_reserved_words
        end
      end
      Object.send(:remove_const, "TestReservedWord")
      ActiveRecord::Base.table_name_prefix = nil
    end

    it "should allow creation of a table with oracle reserved words as column names" do
      create_test_reserved_words_table
      class ::TestReservedWord < ActiveRecord::Base; end

      [:varchar2, :integer].each do |attr|
        TestReservedWord.columns_hash[attr.to_s].name.should == attr.to_s
      end
    end

  end

  describe "valid table names" do
    before(:all) do
      @adapter = ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter
    end

    it "should be valid with letters and digits" do
      @adapter.valid_table_name?("abc_123").should be_true
    end

    it "should be valid with schema name" do
      @adapter.valid_table_name?("abc_123.def_456").should be_true
    end

    it "should be valid with $ in name" do
      @adapter.valid_table_name?("sys.v$session").should be_true
    end

    it "should not be valid with two dots in name" do
      @adapter.valid_table_name?("abc_123.def_456.ghi_789").should be_false
    end

    it "should not be valid with invalid characters" do
      @adapter.valid_table_name?("warehouse-things").should be_false
    end

  end

  describe "table quoting" do

    def create_warehouse_things_table
      ActiveRecord::Schema.define do
        suppress_messages do
          create_table "warehouse-things" do |t|
            t.string      :name
            t.integer     :foo
          end
        end
      end
    end

    def create_camel_case_table
      ActiveRecord::Schema.define do
        suppress_messages do
          create_table "CamelCase" do |t|
            t.string      :name
            t.integer     :foo
          end
        end
      end
    end

    after(:each) do
      ActiveRecord::Schema.define do
        suppress_messages do
          drop_table "warehouse-things" rescue nil
          drop_table "CamelCase" rescue nil
        end
      end
      Object.send(:remove_const, "WarehouseThing") rescue nil
      Object.send(:remove_const, "CamelCase") rescue nil
    end

    it "should allow creation of a table with non alphanumeric characters" do
      create_warehouse_things_table
      class ::WarehouseThing < ActiveRecord::Base
        set_table_name "warehouse-things"
      end

      wh = WarehouseThing.create!(:name => "Foo", :foo => 2)
      wh.id.should_not be_nil

      @conn.tables.should include("warehouse-things")
    end

    it "should allow creation of a table with CamelCase name" do
      create_camel_case_table
      class ::CamelCase < ActiveRecord::Base
        set_table_name "CamelCase"
      end

      cc = CamelCase.create!(:name => "Foo", :foo => 2)
      cc.id.should_not be_nil
    
      @conn.tables.should include("CamelCase")
    end

  end

  describe "access table over database link" do
    before(:all) do
      @db_link = "db_link"
      @sys_conn = ActiveRecord::Base.oracle_enhanced_connection(SYSTEM_CONNECTION_PARAMS)
      @sys_conn.drop_table :test_posts rescue nil
      @sys_conn.create_table :test_posts do |t|
        t.string      :title
        # cannot update LOBs over database link
        t.string      :body
        t.timestamps
      end
      @db_link_username = SYSTEM_CONNECTION_PARAMS[:username]
      @db_link_password = SYSTEM_CONNECTION_PARAMS[:password]
      @db_link_database = SYSTEM_CONNECTION_PARAMS[:database]
      @conn.execute "DROP DATABASE LINK #{@db_link}" rescue nil
      @conn.execute "CREATE DATABASE LINK #{@db_link} CONNECT TO #{@db_link_username} IDENTIFIED BY #{@db_link_password} USING '#{@db_link_database}'"
      @conn.execute "CREATE OR REPLACE SYNONYM test_posts FOR test_posts@#{@db_link}"
      @conn.execute "CREATE OR REPLACE SYNONYM test_posts_seq FOR test_posts_seq@#{@db_link}"
      class ::TestPost < ActiveRecord::Base
      end
      TestPost.set_table_name "test_posts"
    end

    after(:all) do
      @conn.execute "DROP SYNONYM test_posts"
      @conn.execute "DROP SYNONYM test_posts_seq"
      @conn.execute "DROP DATABASE LINK #{@db_link}" rescue nil
      @sys_conn.drop_table :test_posts rescue nil
      Object.send(:remove_const, "TestPost") rescue nil
    end

    it "should verify database link" do
      @conn.select_value("select * from dual@#{@db_link}") == 'X'
    end

    it "should get column names" do
      TestPost.column_names.should == ["id", "title", "body", "created_at", "updated_at"]
    end

    it "should create record" do
      p = TestPost.create(:title => "Title", :body => "Body")
      p.id.should_not be_nil
      TestPost.find(p.id).should_not be_nil
    end

  end

end