require 'test/unit'
require 'test/FbTestCases'
require 'fb.so'
include Fb

class ConnectionTestCases < Test::Unit::TestCase
  include FbTestCases
  
  def test_execute
    sql_schema = "CREATE TABLE TEST (ID INT, NAME VARCHAR(20))"
    sql_select = "SELECT * FROM RDB$DATABASE"
    Database.create(@parms) do |connection|
      assert !connection.transaction_started
      connection.execute(sql_schema)
      connection.execute(sql_select)
      assert connection.transaction_started
      connection.commit
      assert !connection.transaction_started
      connection.drop
    end
  end
  
  def test_query_select
    sql_select = "SELECT * FROM RDB$DATABASE"
    Database.create(@parms) do |connection|
      d = connection.query(sql_select)
      assert_instance_of Array, d
      assert_equal 1, d.size
      assert_instance_of Array, d.first
      assert_equal 4, d.first.size

      a = connection.query(:array, sql_select)
      assert_instance_of Array, a
      assert_equal 1, a.size
      assert_instance_of Array, a.first
      assert_equal 4, a.first.size

      h = connection.query(:hash, sql_select)
      assert_instance_of Array, h
      assert_equal 1, h.size
      assert_instance_of Hash, h.first
      assert_equal 4, h.first.keys.size
      assert h.first.keys.include?("RDB$DESCRIPTION")
      assert h.first.keys.include?("RDB$RELATION_ID")
      assert h.first.keys.include?("RDB$SECURITY_CLASS")
      assert h.first.keys.include?("RDB$CHARACTER_SET_NAME")
    end
  end
  
  def test_query_update
    sql_schema = "CREATE TABLE TEST (ID INT, NAME VARCHAR(20))"
    sql_insert = "INSERT INTO TEST (ID, NAME) VALUES (?, ?)"
    sql_update = "UPDATE TEST SET ID = ?, NAME = ? WHERE ID = ?"
    sql_delete = "DELETE FROM TEST WHERE ID = ?"
    sql_select = "SELECT * FROM TEST"
    Database.create(@parms) do |connection|
      su = connection.query(sql_schema)
      assert_equal -1, su
      
      i = connection.query(sql_insert, 1, "NAME")
      assert_equal 1, i
      
      u = connection.query(sql_update, 1, "NAME2", 1)
      assert_equal 1, u
      
      d = connection.query(sql_delete, 1)
      assert_equal 1, d
      
      q = connection.query(sql_select)
      assert_instance_of Array, q
      assert_equal 0, q.size
    end
  end
  
  def test_insert_blobs_text
    sql_schema = "CREATE TABLE TEST (ID INT, NAME VARCHAR(20), MEMO BLOB SUB_TYPE TEXT)"
    sql_insert = "INSERT INTO TEST (ID, NAME, MEMO) VALUES (?, ?, ?)"
    sql_select = "SELECT * FROM TEST ORDER BY ID"
    Database.create(@parms) do |connection|
      connection.execute(sql_schema);
      memo = IO.read("fb.c")
      assert memo.size > 50000
      connection.transaction do
        10.times do |i|
          connection.execute(sql_insert, i, i.to_s, memo);
        end
      end
      connection.execute(sql_select) do |cursor|
        i = 0
        cursor.each :hash do |row|
          assert_equal i, row["ID"]
          assert_equal i.to_s, row["NAME"]
          assert_equal memo, row["MEMO"]
          i += 1
        end
      end
      connection.drop
    end
  end

  def test_insert_blobs_binary
    sql_schema = "CREATE TABLE TEST (ID INT, NAME VARCHAR(20), ATTACHMENT BLOB SEGMENT SIZE 1000)"
    sql_insert = "INSERT INTO TEST (ID, NAME, ATTACHMENT) VALUES (?, ?, ?)"
    sql_select = "SELECT * FROM TEST ORDER BY ID"
    filename = "fb.c"
    Database.create(@parms) do |connection|
      connection.execute(sql_schema);
      attachment = File.open(filename,"rb") do |f|
        f.read * 3
      end
      assert (attachment.size > 150000), "Not expected size"
      connection.transaction do
        3.times do |i|
          connection.execute(sql_insert, i, i.to_s, attachment);
        end
      end
      connection.execute(sql_select) do |cursor|
        i = 0
        cursor.each :array do |row|
          assert_equal i, row[0], "ID's do not match"
          assert_equal i.to_s, row[1], "NAME's do not match"
          assert_equal attachment.size, row[2].size, "ATTACHMENT sizes do not match"
          i += 1
        end
      end
      connection.drop
    end
  end

  def test_rows_affected
    sql_schema = "CREATE TABLE TEST (ID INT, NAME VARCHAR(20))"
    sql_insert = "INSERT INTO TEST (ID, NAME) VALUES (?, ?)"
    sql_update = "UPDATE TEST SET NAME = 'no name' WHERE ID < ?"
    sql_delete = "DELETE FROM TEST WHERE ID > ?"
    sql_select = "SELECT * FROM TEST"
    Database.create(@parms) do |connection|
      connection.execute(sql_schema)
      connection.transaction do
        10.times do |i|
          affected = connection.execute(sql_insert, i, "name");
          assert_equal 1, affected
        end
      end
      affected = connection.execute(sql_update, 5)
      assert_equal 5, affected
      affected = connection.execute(sql_delete, 5)
      assert 4, affected
      rows = connection.execute(sql_select) do |cursor| cursor.fetchall end
      assert 6, rows.size
    end
  end

  def test_multi_insert
    sql_schema = "CREATE TABLE TEST (ID INT, NAME VARCHAR(20))"
    sql_insert = "INSERT INTO TEST (ID, NAME) VALUES (?, ?)"
    sql_select = "SELECT * FROM TEST"
    sql_data = [
      [1, "Name 1"],
      [2, "Name 2"],
      [3, "Name 3"]]
    sql_data1 = [4, "Name 4"]
    sql_data2 = [5, "Name 5"]
    sql_data3 = [6, "Name 6"]
    Database.create(@parms) do |connection|
      connection.execute(sql_schema)
      connection.execute(sql_insert, sql_data)
      rs = connection.query(sql_select)
      assert_equal 3, rs.size

      connection.execute(sql_insert, sql_data1, sql_data2, sql_data3)
      rs = connection.query(sql_select)
      assert_equal 6, rs.size
    end
  end

  def test_dialects
    db = Database.create(@parms) do |connection|
      assert_equal 3, connection.dialect
      assert_equal 3, connection.db_dialect
      connection.drop
    end
  end
  
  def test_open?
    db = Database.create(@parms);
    connection = db.connect
    assert connection.open?
    connection.close
    assert !connection.open?
    db.drop
  end
  
  def test_properties_instance
    db = Database.new(@parms)
    db.create
    db.connect do |connection|
      assert_equal @parms[:database], connection.database
      assert_equal @parms[:username], connection.username
      assert_equal @parms[:password], connection.password
      assert_equal @parms[:role], connection.role
      assert_equal @parms[:charset], connection.charset
      connection.drop
    end
  end
  
  def test_properties_singleton
    Database.create(@parms) do |connection|
      assert_equal @parms[:database], connection.database
      assert_equal @parms[:username], connection.username
      assert_equal @parms[:password], connection.password
      assert_equal @parms[:role], connection.role
      assert_equal @parms[:charset], connection.charset
      connection.drop
    end
  end
  
  def test_drop_instance
    db = Database.create(@parms)
    assert File.exists?(@db_file)
    connection = db.connect
    assert connection.open?    
    connection.drop
    assert !connection.open?
    assert !File.exists?(@db_file)
  end
  
  def test_drop_singleton
    Database.create(@parms) do |connection|
      assert File.exists?(@db_file)
      connection.drop
      assert !File.exists?(@db_file)
    end
  end
  
  def test_to_s
    db = Database.new(@parms)
    db.create
    connection = db.connect
    begin
      assert_equal "#{@parms[:database]} (OPEN)", connection.to_s
    ensure
      connection.close
      assert_equal "#{@parms[:database]} (CLOSED)", connection.to_s
    end
  end
  
  def test_table_names
    sql_schema = <<-END
      CREATE TABLE TEST1 (ID INT);
      CREATE TABLE TEST2 (ID INT);
    END
    Database.create(@parms) do |connection|
      connection.execute_script(sql_schema)
      table_names = connection.table_names
      assert_equal 'TEST1', table_names[0]
      assert_equal 'TEST2', table_names[1]
    end
  end

  def test_table_names_downcased
    sql_schema = <<-END
      CREATE TABLE TEST1 (ID INT);
      CREATE TABLE "Test2" (ID INT);
    END
    Database.create(@parms.merge(:downcase_names => true)) do |connection|
      connection.execute_script(sql_schema)
      table_names = connection.table_names
      assert_equal 'test1', table_names[0]
      assert_equal 'Test2', table_names[1]
    end
  end

  def test_generator_names
    sql_schema = <<-END
      CREATE GENERATOR TEST1_SEQ;
      CREATE GENERATOR TEST2_SEQ;
    END
    Database.create(@parms) do |connection|
      connection.execute_script(sql_schema)
      names = connection.generator_names
      assert_equal 'TEST1_SEQ', names[0]
      assert_equal 'TEST2_SEQ', names[1]
    end
  end

  def test_generator_names_downcased
    sql_schema = <<-END
      CREATE GENERATOR TEST1_SEQ;
      CREATE GENERATOR "TEST2_seq";
    END
    Database.create(@parms.merge(:downcase_names => true)) do |connection|
      connection.execute_script(sql_schema)
      names = connection.generator_names
      assert_equal 'test1_seq', names[0]
      assert_equal 'TEST2_seq', names[1]
    end
  end

  def test_view_names
    sql_schema = <<-END
      CREATE TABLE TEST1 (ID INT, NAME1 VARCHAR(10));
      CREATE TABLE TEST2 (ID INT, NAME2 VARCHAR(10));
      CREATE VIEW VIEW1 AS SELECT TEST1.ID, TEST1.NAME1, TEST2.NAME2 FROM TEST1 JOIN TEST2 ON TEST1.ID = TEST2.ID;
      CREATE VIEW VIEW2 AS SELECT TEST2.ID, TEST1.NAME1, TEST2.NAME2 FROM TEST1 JOIN TEST2 ON TEST1.NAME1 = TEST2.NAME2;
    END
    Database.create(@parms) do |connection|
      connection.execute_script(sql_schema)
      names = connection.view_names
      assert_equal 'VIEW1', names[0]
      assert_equal 'VIEW2', names[1]
    end
  end

  def test_view_names_downcased
    sql_schema = <<-END
      CREATE TABLE TEST1 (ID INT, NAME1 VARCHAR(10));
      CREATE TABLE TEST2 (ID INT, NAME2 VARCHAR(10));
      CREATE VIEW VIEW1 AS SELECT TEST1.ID, TEST1.NAME1, TEST2.NAME2 FROM TEST1 JOIN TEST2 ON TEST1.ID = TEST2.ID;
      CREATE VIEW "View2" AS SELECT TEST2.ID, TEST1.NAME1, TEST2.NAME2 FROM TEST1 JOIN TEST2 ON TEST1.NAME1 = TEST2.NAME2;
    END
    Database.create(@parms.merge(:downcase_names => true)) do |connection|
      connection.execute_script(sql_schema)
      names = connection.view_names
      assert_equal 'view1', names[0]
      assert_equal 'View2', names[1]
    end
  end

  def test_role_names
    sql_schema = <<-END
      create role reader;
      create role writer;
    END
    Database.create(@parms) do |connection|
      connection.execute_script(sql_schema)
      names = connection.role_names
      assert_equal 'READER', names[0]
      assert_equal 'WRITER', names[1]
    end
  end
  
  def test_role_names_downcased
    sql_schema = <<-END
      create role reader;
      create role writer;
    END
    Database.create(@parms.merge(:downcase_names => true)) do |connection|
      connection.execute_script(sql_schema)
      names = connection.role_names
      assert_equal 'reader', names[0]
      assert_equal 'writer', names[1]
    end
  end
  
  def test_procedure_names
    sql_schema = <<-END_SQL
      CREATE PROCEDURE PLUSONE(NUM1 INTEGER) RETURNS (NUM2 INTEGER) AS
      BEGIN
        NUM2 = NUM1 + 1;
        SUSPEND;
      END;
    END_SQL
    Database.create(@parms) do |connection|
      connection.execute(sql_schema)
      names = connection.procedure_names
      assert_equal 'PLUSONE', names[0]
    end
  end

  def test_procedure_names_downcased
    sql_schema = <<-END_SQL
      CREATE PROCEDURE PLUSONE(NUM1 INTEGER) RETURNS (NUM2 INTEGER) AS
      BEGIN
        NUM2 = NUM1 + 1;
        SUSPEND;
      END;
    END_SQL
    Database.create(@parms.merge(:downcase_names => true)) do |connection|
      connection.execute(sql_schema)
      names = connection.procedure_names
      assert_equal 'plusone', names[0]
    end
  end

  def test_index_names
    sql_schema = <<-END
      CREATE TABLE MASTER (ID INT NOT NULL, NAME1 VARCHAR(10));
      CREATE TABLE DETAIL (ID INT NOT NULL, MASTER_ID INT, NAME2 VARCHAR(10));
      ALTER TABLE MASTER ADD CONSTRAINT PK_MASTER PRIMARY KEY(ID);
      ALTER TABLE DETAIL ADD CONSTRAINT PK_DETAIL PRIMARY KEY(ID);
      ALTER TABLE DETAIL ADD CONSTRAINT FK_DETAIL_MASTER_ID FOREIGN KEY(MASTER_ID) REFERENCES MASTER(ID);
      CREATE UNIQUE ASCENDING INDEX IX_MASTER_NAME1 ON MASTER(NAME1);
      CREATE DESCENDING INDEX IX_DETAIL_ID_DESC ON DETAIL(ID);
    END
    Database.create(@parms) do |connection|
      connection.execute_script(sql_schema)
      indexes = connection.indexes # Hash of Structs using index names as keys
      assert_equal 5, indexes.size
      assert indexes.keys.include?('PK_MASTER')
      assert indexes.keys.include?('PK_DETAIL')
      assert indexes.keys.include?('FK_DETAIL_MASTER_ID')
      assert indexes.keys.include?('IX_MASTER_NAME1')
      assert indexes.keys.include?('IX_DETAIL_ID_DESC')
      
      assert indexes['PK_MASTER'].columns.include?('ID')
      assert indexes['PK_DETAIL'].columns.include?('ID')

      master_indexes = indexes.values.select {|ix| ix.table_name == 'MASTER' }
      assert_equal 2, master_indexes.size
      
      detail_indexes = indexes.values.select {|ix| ix.table_name == 'DETAIL' }
      assert_equal 3, detail_indexes.size
      
      assert_equal 'MASTER', indexes['PK_MASTER'].table_name
      assert indexes['PK_MASTER'].unique
      assert !indexes['PK_MASTER'].descending

      assert_equal 'MASTER', indexes['IX_MASTER_NAME1'].table_name
      assert indexes['IX_MASTER_NAME1'].unique
      assert !indexes['IX_MASTER_NAME1'].descending
      
      assert_equal 'DETAIL', indexes['PK_DETAIL'].table_name
      assert indexes['PK_DETAIL'].unique
      assert !indexes['PK_DETAIL'].descending

      assert_equal 'DETAIL', indexes['FK_DETAIL_MASTER_ID'].table_name
      assert !indexes['FK_DETAIL_MASTER_ID'].unique
      assert !indexes['FK_DETAIL_MASTER_ID'].descending

      assert_equal 'DETAIL', indexes['IX_DETAIL_ID_DESC'].table_name
      assert !indexes['IX_DETAIL_ID_DESC'].unique
      assert indexes['IX_DETAIL_ID_DESC'].descending      
      
      connection.drop
    end
  end

  def test_index_names_downcased
    sql_schema = <<-END
      CREATE TABLE MASTER (ID INT NOT NULL, NAME1 VARCHAR(10));
      CREATE TABLE DETAIL (ID INT NOT NULL, MASTER_ID INT, NAME2 VARCHAR(10));
      ALTER TABLE MASTER ADD CONSTRAINT PK_MASTER PRIMARY KEY(ID);
      ALTER TABLE DETAIL ADD CONSTRAINT PK_DETAIL PRIMARY KEY(ID);
      ALTER TABLE DETAIL ADD CONSTRAINT FK_DETAIL_MASTER_ID FOREIGN KEY(MASTER_ID) REFERENCES MASTER(ID);
      CREATE UNIQUE ASCENDING INDEX IX_MASTER_NAME1 ON MASTER(NAME1);
      CREATE DESCENDING INDEX "IX_DETAIL_ID_desc" ON DETAIL(ID);
    END
    Database.create(@parms.merge(:downcase_names => true)) do |connection|
      connection.execute_script(sql_schema)
      indexes = connection.indexes # Hash of Structs using index names as keys
      assert_equal 5, indexes.size
      assert indexes.keys.include?('pk_master')
      assert indexes.keys.include?('pk_detail')
      assert indexes.keys.include?('fk_detail_master_id')
      assert indexes.keys.include?('ix_master_name1')
      assert indexes.keys.include?('IX_DETAIL_ID_desc')
      assert indexes['pk_master'].columns.include?('id'), "columns missing id"
      assert indexes['pk_detail'].columns.include?('id'), "columns missing id"
      connection.drop
    end
  end
end
