From 631181ca18d2a21c3e72039b0c7f6c5a0b2fc05a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 1 Mar 2006 16:01:53 +0000 Subject: Renamed the "oci" adapter to "oracle", but kept the old name as an alias (closes #4017) [schoenm@earthlink.net] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3718 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/CHANGELOG | 2 + activerecord/Rakefile | 2 +- activerecord/lib/active_record.rb | 2 +- .../connection_adapters/oci_adapter.rb | 630 -------------------- .../connection_adapters/oracle_adapter.rb | 648 +++++++++++++++++++++ activerecord/test/base_test.rb | 10 +- activerecord/test/binary_test.rb | 2 +- activerecord/test/column_alias_test.rb | 2 +- .../test/connections/native_oci/connection.rb | 25 - .../test/connections/native_oracle/connection.rb | 23 + .../fixtures/db_definitions/create_oracle_db.sh | 0 .../test/fixtures/db_definitions/oci.drop.sql | 61 -- activerecord/test/fixtures/db_definitions/oci.sql | 292 ---------- .../test/fixtures/db_definitions/oci2.drop.sql | 2 - activerecord/test/fixtures/db_definitions/oci2.sql | 6 - .../test/fixtures/db_definitions/oracle.drop.sql | 61 ++ .../test/fixtures/db_definitions/oracle.sql | 292 ++++++++++ .../test/fixtures/db_definitions/oracle2.drop.sql | 2 + .../test/fixtures/db_definitions/oracle2.sql | 6 + activerecord/test/fixtures_test.rb | 10 +- activerecord/test/migration_test.rb | 6 +- activerecord/test/readonly_test.rb | 2 +- activerecord/test/synonym_test_oci.rb | 17 - activerecord/test/synonym_test_oracle.rb | 17 + 24 files changed, 1069 insertions(+), 1051 deletions(-) delete mode 100644 activerecord/lib/active_record/connection_adapters/oci_adapter.rb create mode 100644 activerecord/lib/active_record/connection_adapters/oracle_adapter.rb delete mode 100644 activerecord/test/connections/native_oci/connection.rb create mode 100644 activerecord/test/connections/native_oracle/connection.rb delete mode 100644 activerecord/test/fixtures/db_definitions/create_oracle_db.sh delete mode 100644 activerecord/test/fixtures/db_definitions/oci.drop.sql delete mode 100644 activerecord/test/fixtures/db_definitions/oci.sql delete mode 100644 activerecord/test/fixtures/db_definitions/oci2.drop.sql delete mode 100644 activerecord/test/fixtures/db_definitions/oci2.sql create mode 100644 activerecord/test/fixtures/db_definitions/oracle.drop.sql create mode 100644 activerecord/test/fixtures/db_definitions/oracle.sql create mode 100644 activerecord/test/fixtures/db_definitions/oracle2.drop.sql create mode 100644 activerecord/test/fixtures/db_definitions/oracle2.sql delete mode 100644 activerecord/test/synonym_test_oci.rb create mode 100644 activerecord/test/synonym_test_oracle.rb (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index ecad62c038..e24268ccb9 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Renamed the "oci" adapter to "oracle", but kept the old name as an alias #4017 [schoenm@earthlink.net] + * Fixed that Base.save should always return false if the save didn't succeed, including if it has halted by before_save's #1861, #2477 [DHH] * Speed up class -> connection caching and stale connection verification. #3979 [Stefan Kaes] diff --git a/activerecord/Rakefile b/activerecord/Rakefile index f45c506539..e2d24472e9 100755 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -27,7 +27,7 @@ task :default => [ :test_mysql, :test_sqlite, :test_postgresql ] # Run the unit tests -for adapter in %w( mysql postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oci ) +for adapter in %w( mysql postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oracle ) Rake::TestTask.new("test_#{adapter}") { |t| t.libs << "test" << "test/connections/native_#{adapter}" t.pattern = "test/*_test{,_#{adapter}}.rb" diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index ba306c0dfc..6acdbd968a 100755 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -68,7 +68,7 @@ ActiveRecord::Base.class_eval do end unless defined?(RAILS_CONNECTION_ADAPTERS) - RAILS_CONNECTION_ADAPTERS = %w(mysql postgresql sqlite firebird sqlserver db2 oci) + RAILS_CONNECTION_ADAPTERS = %w(mysql postgresql sqlite firebird sqlserver db2 oracle) end RAILS_CONNECTION_ADAPTERS.each do |adapter| diff --git a/activerecord/lib/active_record/connection_adapters/oci_adapter.rb b/activerecord/lib/active_record/connection_adapters/oci_adapter.rb deleted file mode 100644 index f0b2d0778a..0000000000 --- a/activerecord/lib/active_record/connection_adapters/oci_adapter.rb +++ /dev/null @@ -1,630 +0,0 @@ -# oci_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g -# -# Original author: Graham Jenkins -# -# Current maintainer: Michael Schoen -# -######################################################################### -# -# Implementation notes: -# 1. Redefines (safely) a method in ActiveRecord to make it possible to -# implement an autonumbering solution for Oracle. -# 2. The OCI8 driver is patched to properly handle values for LONG and -# TIMESTAMP columns. The driver-author has indicated that a future -# release of the driver will obviate this patch. -# 3. LOB support is implemented through an after_save callback. -# 4. Oracle does not offer native LIMIT and OFFSET options; this -# functionality is mimiced through the use of nested selects. -# See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064 -# -# Do what you want with this code, at your own peril, but if any -# significant portion of my code remains then please acknowledge my -# contribution. -# portions Copyright 2005 Graham Jenkins - -require 'active_record/connection_adapters/abstract_adapter' -require 'delegate' - -begin - require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8 - - module ActiveRecord - class Base - def self.oci_connection(config) #:nodoc: - # Use OCI8AutoRecover instead of normal OCI8 driver. - ConnectionAdapters::OCIAdapter.new OCI8AutoRecover.new(config), logger - end - - # Enable the id column to be bound into the sql later, by the adapter's insert method. - # This is preferable to inserting the hard-coded value here, because the insert method - # needs to know the id value explicitly. - alias :attributes_with_quotes_pre_oci :attributes_with_quotes #:nodoc: - def attributes_with_quotes(creating = true) #:nodoc: - aq = attributes_with_quotes_pre_oci creating - if connection.class == ConnectionAdapters::OCIAdapter - aq[self.class.primary_key] = ":id" if creating && aq[self.class.primary_key].nil? - end - aq - end - - # After setting large objects to empty, select the OCI8::LOB - # and write back the data. - after_save :write_lobs - def write_lobs() #:nodoc: - if connection.is_a?(ConnectionAdapters::OCIAdapter) - self.class.columns.select { |c| c.type == :binary }.each { |c| - value = self[c.name] - next if value.nil? || (value == '') - lob = connection.select_one( - "SELECT #{ c.name} FROM #{ self.class.table_name } WHERE #{ self.class.primary_key} = #{quote(id)}", - 'Writable Large Object')[c.name] - lob.write value - } - end - end - - private :write_lobs - end - - - module ConnectionAdapters #:nodoc: - class OCIColumn < Column #:nodoc: - attr_reader :sql_type - - # overridden to add the concept of scale, required to differentiate - # between integer and float fields - def initialize(name, default, sql_type, limit, scale, null) - @name, @limit, @sql_type, @scale, @null = name, limit, sql_type, scale, null - - @type = simplified_type(sql_type) - @default = type_cast(default) - - @primary = nil - @text = [:string, :text].include? @type - @number = [:float, :integer].include? @type - end - - def type_cast(value) - return nil if value.nil? || value =~ /^\s*null\s*$/i - case type - when :string then value - when :integer then defined?(value.to_i) ? value.to_i : (value ? 1 : 0) - when :float then value.to_f - when :datetime then cast_to_date_or_time(value) - when :time then cast_to_time(value) - else value - end - end - - private - def simplified_type(field_type) - case field_type - when /char/i : :string - when /num|float|double|dec|real|int/i : @scale == 0 ? :integer : :float - when /date|time/i : @name =~ /_at$/ ? :time : :datetime - when /lob/i : :binary - end - end - - def cast_to_date_or_time(value) - return value if value.is_a? Date - return nil if value.blank? - guess_date_or_time (value.is_a? Time) ? value : cast_to_time(value) - end - - def cast_to_time(value) - return value if value.is_a? Time - time_array = ParseDate.parsedate value - time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1; - Time.send(Base.default_timezone, *time_array) rescue nil - end - - def guess_date_or_time(value) - (value.hour == 0 and value.min == 0 and value.sec == 0) ? - Date.new(value.year, value.month, value.day) : value - end - end - - - # This is an Oracle/OCI adapter for the ActiveRecord persistence - # framework. It relies upon the OCI8 driver, which works with Oracle 8i - # and above. Most recent development has been on Debian Linux against - # a 10g database, ActiveRecord 1.12.1 and OCI8 0.1.13. - # See: http://rubyforge.org/projects/ruby-oci8/ - # - # Usage notes: - # * Key generation assumes a "${table_name}_seq" sequence is available - # for all tables; the sequence name can be changed using - # ActiveRecord::Base.set_sequence_name. When using Migrations, these - # sequences are created automatically. - # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times. - # Consequently some hacks are employed to map data back to Date or Time - # in Ruby. If the column_name ends in _time it's created as a Ruby Time. - # Else if the hours/minutes/seconds are 0, I make it a Ruby Date. Else - # it's a Ruby Time. This is a bit nasty - but if you use Duck Typing - # you'll probably not care very much. In 9i and up it's tempting to - # map DATE to Date and TIMESTAMP to Time, but too many databases use - # DATE for both. Timezones and sub-second precision on timestamps are - # not supported. - # * Default values that are functions (such as "SYSDATE") are not - # supported. This is a restriction of the way ActiveRecord supports - # default values. - # * Support for Oracle8 is limited by Rails' use of ANSI join syntax, which - # is supported in Oracle9i and later. You will need to use #finder_sql for - # has_and_belongs_to_many associations to run against Oracle8. - # - # Options: - # - # * :username -- Defaults to root - # * :password -- Defaults to nothing - # * :host -- Defaults to localhost - class OCIAdapter < AbstractAdapter - - def adapter_name #:nodoc: - 'OCI' - end - - def supports_migrations? #:nodoc: - true - end - - def native_database_types #:nodoc - { - :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY", - :string => { :name => "VARCHAR2", :limit => 255 }, - :text => { :name => "LONG" }, - :integer => { :name => "NUMBER", :limit => 38 }, - :float => { :name => "NUMBER" }, - :datetime => { :name => "DATE" }, - :timestamp => { :name => "DATE" }, - :time => { :name => "DATE" }, - :date => { :name => "DATE" }, - :binary => { :name => "BLOB" }, - :boolean => { :name => "NUMBER", :limit => 1 } - } - end - - - # QUOTING ================================================== - # - # see: abstract/quoting.rb - - # camelCase column names need to be quoted; not that anyone using Oracle - # would really do this, but handling this case means we pass the test... - def quote_column_name(name) #:nodoc: - name =~ /[A-Z]/ ? "\"#{name}\"" : name - end - - def quote_string(string) #:nodoc: - string.gsub(/'/, "''") - end - - def quote(value, column = nil) #:nodoc: - if column and column.type == :binary then %Q{empty_#{ column.sql_type }()} - else case value - when String then %Q{'#{quote_string(value)}'} - when NilClass then 'null' - when TrueClass then '1' - when FalseClass then '0' - when Numeric then value.to_s - when Date, Time then %Q{'#{value.strftime("%Y-%m-%d %H:%M:%S")}'} - else %Q{'#{quote_string(value.to_yaml)}'} - end - end - end - - - # CONNECTION MANAGEMENT ====================================# - - # Returns true if the connection is active. - def active? - # Pings the connection to check if it's still good. Note that an - # #active? method is also available, but that simply returns the - # last known state, which isn't good enough if the connection has - # gone stale since the last use. - @connection.ping - rescue OCIError - false - end - - # Reconnects to the database. - def reconnect! - @connection.reset! - rescue OCIError => e - @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" - end - - - # DATABASE STATEMENTS ====================================== - # - # see: abstract/database_statements.rb - - def select_all(sql, name = nil) #:nodoc: - select(sql, name) - end - - def select_one(sql, name = nil) #:nodoc: - result = select_all(sql, name) - result.size > 0 ? result.first : nil - end - - def execute(sql, name = nil) #:nodoc: - log(sql, name) { @connection.exec sql } - end - - def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: - if pk.nil? # Who called us? What does the sql look like? No idea! - execute sql, name - elsif id_value # Pre-assigned id - log(sql, name) { @connection.exec sql } - else # Assume the sql contains a bind-variable for the id - id_value = select_one("select #{sequence_name}.nextval id from dual")['id'] - log(sql, name) { @connection.exec sql, id_value } - end - - id_value - end - - alias :update :execute #:nodoc: - alias :delete :execute #:nodoc: - - def begin_db_transaction #:nodoc: - @connection.autocommit = false - end - - def commit_db_transaction #:nodoc: - @connection.commit - ensure - @connection.autocommit = true - end - - def rollback_db_transaction #:nodoc: - @connection.rollback - ensure - @connection.autocommit = true - end - - def add_limit_offset!(sql, options) #:nodoc: - offset = options[:offset] || 0 - - if limit = options[:limit] - sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}" - elsif offset > 0 - sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}" - end - end - - def default_sequence_name(table, column) #:nodoc: - "#{table}_seq" - end - - - # SCHEMA STATEMENTS ======================================== - # - # see: abstract/schema_statements.rb - - def tables(name = nil) #:nodoc: - select_all("select lower(table_name) from user_tables").inject([]) do | tabs, t | - tabs << t.to_a.first.last - end - end - - def indexes(table_name, name = nil) #:nodoc: - result = select_all(<<-SQL, name) - SELECT lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name - FROM user_indexes i, user_ind_columns c - WHERE i.table_name = '#{table_name.to_s.upcase}' - AND c.index_name = i.index_name - AND i.index_name NOT IN (SELECT index_name FROM user_constraints WHERE constraint_type = 'P') - ORDER BY i.index_name, c.column_position - SQL - - current_index = nil - indexes = [] - - result.each do |row| - if current_index != row['index_name'] - indexes << IndexDefinition.new(table_name, row['index_name'], row['uniqueness'] == "UNIQUE", []) - current_index = row['index_name'] - end - - indexes.last.columns << row['column_name'] - end - - indexes - end - - def columns(table_name, name = nil) #:nodoc: - table_info = @connection.object_info(table_name) - - table_cols = %Q{ - select column_name, data_type, data_default, nullable, - decode(data_type, 'NUMBER', data_precision, - 'VARCHAR2', data_length, - null) as length, - decode(data_type, 'NUMBER', data_scale, null) as scale - from all_tab_columns - where owner = '#{table_info.schema}' - and table_name = '#{table_info.name}' - } - - select_all(table_cols, name).map do |row| - row['data_default'].sub!(/^'(.*)'\s*$/, '\1') if row['data_default'] - OCIColumn.new( - oci_downcase(row['column_name']), - row['data_default'], - row['data_type'], - row['length'], - row['scale'], - row['nullable'] == 'Y' - ) - end - end - - def create_table(name, options = {}) #:nodoc: - super(name, options) - execute "CREATE SEQUENCE #{name}_seq" unless options[:id] == false - end - - def rename_table(name, new_name) #:nodoc: - execute "RENAME #{name} TO #{new_name}" - execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil - end - - def drop_table(name) #:nodoc: - super(name) - execute "DROP SEQUENCE #{name}_seq" rescue nil - end - - def remove_index(table_name, options = {}) #:nodoc: - execute "DROP INDEX #{index_name(table_name, options)}" - end - - def change_column_default(table_name, column_name, default) #:nodoc: - execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}" - end - - def change_column(table_name, column_name, type, options = {}) #:nodoc: - change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}" - add_column_options!(change_column_sql, options) - execute(change_column_sql) - end - - def rename_column(table_name, column_name, new_column_name) #:nodoc: - execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}" - end - - def remove_column(table_name, column_name) #:nodoc: - execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}" - end - - def structure_dump #:nodoc: - s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq| - structure << "create sequence #{seq.to_a.first.last};\n\n" - end - - select_all("select table_name from user_tables").inject(s) do |structure, table| - ddl = "create table #{table.to_a.first.last} (\n " - cols = select_all(%Q{ - select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable - from user_tab_columns - where table_name = '#{table.to_a.first.last}' - order by column_id - }).map do |row| - col = "#{row['column_name'].downcase} #{row['data_type'].downcase}" - if row['data_type'] =='NUMBER' and !row['data_precision'].nil? - col << "(#{row['data_precision'].to_i}" - col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil? - col << ')' - elsif row['data_type'].include?('CHAR') - col << "(#{row['data_length'].to_i})" - end - col << " default #{row['data_default']}" if !row['data_default'].nil? - col << ' not null' if row['nullable'] == 'N' - col - end - ddl << cols.join(",\n ") - ddl << ");\n\n" - structure << ddl - end - end - - def structure_drop #:nodoc: - s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq| - drop << "drop sequence #{seq.to_a.first.last};\n\n" - end - - select_all("select table_name from user_tables").inject(s) do |drop, table| - drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n" - end - end - - - private - - def select(sql, name = nil) - cursor = log(sql, name) { @connection.exec sql } - cols = cursor.get_col_names.map { |x| oci_downcase(x) } - rows = [] - - while row = cursor.fetch - hash = Hash.new - - cols.each_with_index do |col, i| - hash[col] = - case row[i] - when OCI8::LOB - name == 'Writable Large Object' ? row[i]: row[i].read - when OraDate - (row[i].hour == 0 and row[i].minute == 0 and row[i].second == 0) ? - row[i].to_date : row[i].to_time - else row[i] - end unless col == 'raw_rnum_' - end - - rows << hash - end - - rows - ensure - cursor.close if cursor - end - - # Oracle column names by default are case-insensitive, but treated as upcase; - # for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote - # their column names when creating Oracle tables, which makes then case-sensitive. - # I don't know anybody who does this, but we'll handle the theoretical case of a - # camelCase column name. I imagine other dbs handle this different, since there's a - # unit test that's currently failing test_oci. - def oci_downcase(column_name) - column_name =~ /[a-z]/ ? column_name : column_name.downcase - end - - end - end - end - - - class OCI8 #:nodoc: - - # This OCI8 patch may not longer be required with the upcoming - # release of version 0.2. - class Cursor #:nodoc: - alias :define_a_column_pre_ar :define_a_column - def define_a_column(i) - case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) } - when 8 : @stmt.defineByPos(i, String, 65535) # Read LONG values - when 187 : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values - else define_a_column_pre_ar i - end - end - end - - # missing constant from oci8 - OCI_PTYPE_UNK = 0 - - def object_info(name) - OraObject.new describe(name.to_s, OCI_PTYPE_UNK) - end - - def describe(name, type) - @desc ||= @@env.alloc(OCIDescribe) - @desc.describeAny(@svc, name, type) - @desc.attrGet(OCI_ATTR_PARAM) - end - - class OraObject - attr_reader :schema, :name - def initialize(info) - case info.attrGet(OCI_ATTR_PTYPE) - when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW - @schema = info.attrGet(OCI_ATTR_OBJ_SCHEMA) - @name = info.attrGet(OCI_ATTR_OBJ_NAME) - when OCI_PTYPE_SYN - @schema = info.attrGet(OCI_ATTR_SCHEMA_NAME) - @name = info.attrGet(OCI_ATTR_NAME) - end - end - end - end - - - # The OCIConnectionFactory factors out the code necessary to connect and - # configure an OCI connection. - class OCIConnectionFactory #:nodoc: - def new_connection(username, password, host) - conn = OCI8.new username, password, host - conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'} - conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil - conn.autocommit = true - conn - end - end - - - # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and - # reset functionality. If a call to #exec fails, and autocommit is turned on - # (ie., we're not in the middle of a longer transaction), it will - # automatically reconnect and try again. If autocommit is turned off, - # this would be dangerous (as the earlier part of the implied transaction - # may have failed silently if the connection died) -- so instead the - # connection is marked as dead, to be reconnected on it's next use. - class OCI8AutoRecover < DelegateClass(OCI8) #:nodoc: - attr_accessor :active - alias :active? :active - - cattr_accessor :auto_retry - class << self - alias :auto_retry? :auto_retry - end - @@auto_retry = false - - def initialize(config, factory = OCIConnectionFactory.new) - @active = true - @username, @password, @host = config[:username], config[:password], config[:host] - @factory = factory - @connection = @factory.new_connection @username, @password, @host - super @connection - end - - # Checks connection, returns true if active. Note that ping actively - # checks the connection, while #active? simply returns the last - # known state. - def ping - @connection.exec("select 1 from dual") { |r| nil } - @active = true - rescue - @active = false - raise - end - - # Resets connection, by logging off and creating a new connection. - def reset! - logoff rescue nil - begin - @connection = @factory.new_connection @username, @password, @host - __setobj__ @connection - @active = true - rescue - @active = false - raise - end - end - - # ORA-00028: your session has been killed - # ORA-01012: not logged on - # ORA-03113: end-of-file on communication channel - # ORA-03114: not connected to ORACLE - LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ] - - # Adds auto-recovery functionality. - # - # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11 - def exec(sql, *bindvars) - should_retry = self.class.auto_retry? && autocommit? - - begin - @connection.exec(sql, *bindvars) - rescue OCIError => e - raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code) - @active = false - raise unless should_retry - should_retry = false - reset! rescue nil - retry - end - end - - end - -rescue LoadError - # OCI8 driver is unavailable. - module ActiveRecord # :nodoc: - class Base - def self.oci_connection(config) # :nodoc: - # Set up a reasonable error message - raise LoadError, "Oracle/OCI libraries could not be loaded." - end - end - end -end diff --git a/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb b/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb new file mode 100644 index 0000000000..7489fac327 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb @@ -0,0 +1,648 @@ +# oracle_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g +# +# Original author: Graham Jenkins +# +# Current maintainer: Michael Schoen +# +######################################################################### +# +# Implementation notes: +# 1. Redefines (safely) a method in ActiveRecord to make it possible to +# implement an autonumbering solution for Oracle. +# 2. The OCI8 driver is patched to properly handle values for LONG and +# TIMESTAMP columns. The driver-author has indicated that a future +# release of the driver will obviate this patch. +# 3. LOB support is implemented through an after_save callback. +# 4. Oracle does not offer native LIMIT and OFFSET options; this +# functionality is mimiced through the use of nested selects. +# See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064 +# +# Do what you want with this code, at your own peril, but if any +# significant portion of my code remains then please acknowledge my +# contribution. +# portions Copyright 2005 Graham Jenkins + +require 'active_record/connection_adapters/abstract_adapter' +require 'delegate' + +begin + require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8 + + module ActiveRecord + class Base + def self.oracle_connection(config) #:nodoc: + # Use OCI8AutoRecover instead of normal OCI8 driver. + ConnectionAdapters::OracleAdapter.new OCI8AutoRecover.new(config), logger + end + + # for backwards-compatibility + def self.oci_connection(config) #:nodoc: + config[:database] = config[:host] + self.oracle_connection(config) + end + + # Enable the id column to be bound into the sql later, by the adapter's insert method. + # This is preferable to inserting the hard-coded value here, because the insert method + # needs to know the id value explicitly. + alias :attributes_with_quotes_pre_oracle :attributes_with_quotes #:nodoc: + def attributes_with_quotes(creating = true) #:nodoc: + aq = attributes_with_quotes_pre_oracle creating + if connection.class == ConnectionAdapters::OracleAdapter + aq[self.class.primary_key] = ":id" if creating && aq[self.class.primary_key].nil? + end + aq + end + + # After setting large objects to empty, select the OCI8::LOB + # and write back the data. + after_save :write_lobs + def write_lobs() #:nodoc: + if connection.is_a?(ConnectionAdapters::OracleAdapter) + self.class.columns.select { |c| c.type == :binary }.each { |c| + value = self[c.name] + next if value.nil? || (value == '') + lob = connection.select_one( + "SELECT #{ c.name} FROM #{ self.class.table_name } WHERE #{ self.class.primary_key} = #{quote(id)}", + 'Writable Large Object')[c.name] + lob.write value + } + end + end + + private :write_lobs + end + + + module ConnectionAdapters #:nodoc: + class OracleColumn < Column #:nodoc: + attr_reader :sql_type + + # overridden to add the concept of scale, required to differentiate + # between integer and float fields + def initialize(name, default, sql_type, limit, scale, null) + @name, @limit, @sql_type, @scale, @null = name, limit, sql_type, scale, null + + @type = simplified_type(sql_type) + @default = type_cast(default) + + @primary = nil + @text = [:string, :text].include? @type + @number = [:float, :integer].include? @type + end + + def type_cast(value) + return nil if value.nil? || value =~ /^\s*null\s*$/i + case type + when :string then value + when :integer then defined?(value.to_i) ? value.to_i : (value ? 1 : 0) + when :float then value.to_f + when :datetime then cast_to_date_or_time(value) + when :time then cast_to_time(value) + else value + end + end + + private + def simplified_type(field_type) + case field_type + when /char/i : :string + when /num|float|double|dec|real|int/i : @scale == 0 ? :integer : :float + when /date|time/i : @name =~ /_at$/ ? :time : :datetime + when /lob/i : :binary + end + end + + def cast_to_date_or_time(value) + return value if value.is_a? Date + return nil if value.blank? + guess_date_or_time (value.is_a? Time) ? value : cast_to_time(value) + end + + def cast_to_time(value) + return value if value.is_a? Time + time_array = ParseDate.parsedate value + time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1; + Time.send(Base.default_timezone, *time_array) rescue nil + end + + def guess_date_or_time(value) + (value.hour == 0 and value.min == 0 and value.sec == 0) ? + Date.new(value.year, value.month, value.day) : value + end + end + + + # This is an Oracle/OCI adapter for the ActiveRecord persistence + # framework. It relies upon the OCI8 driver, which works with Oracle 8i + # and above. Most recent development has been on Debian Linux against + # a 10g database, ActiveRecord 1.12.1 and OCI8 0.1.13. + # See: http://rubyforge.org/projects/ruby-oci8/ + # + # Usage notes: + # * Key generation assumes a "${table_name}_seq" sequence is available + # for all tables; the sequence name can be changed using + # ActiveRecord::Base.set_sequence_name. When using Migrations, these + # sequences are created automatically. + # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times. + # Consequently some hacks are employed to map data back to Date or Time + # in Ruby. If the column_name ends in _time it's created as a Ruby Time. + # Else if the hours/minutes/seconds are 0, I make it a Ruby Date. Else + # it's a Ruby Time. This is a bit nasty - but if you use Duck Typing + # you'll probably not care very much. In 9i and up it's tempting to + # map DATE to Date and TIMESTAMP to Time, but too many databases use + # DATE for both. Timezones and sub-second precision on timestamps are + # not supported. + # * Default values that are functions (such as "SYSDATE") are not + # supported. This is a restriction of the way ActiveRecord supports + # default values. + # * Support for Oracle8 is limited by Rails' use of ANSI join syntax, which + # is supported in Oracle9i and later. You will need to use #finder_sql for + # has_and_belongs_to_many associations to run against Oracle8. + # + # Required parameters: + # + # * :username + # * :password + # * :database + class OracleAdapter < AbstractAdapter + + def adapter_name #:nodoc: + 'Oracle' + end + + def supports_migrations? #:nodoc: + true + end + + def native_database_types #:nodoc + { + :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY", + :string => { :name => "VARCHAR2", :limit => 255 }, + :text => { :name => "BLOB" }, + :integer => { :name => "NUMBER", :limit => 38 }, + :float => { :name => "NUMBER" }, + :datetime => { :name => "DATE" }, + :timestamp => { :name => "DATE" }, + :time => { :name => "DATE" }, + :date => { :name => "DATE" }, + :binary => { :name => "BLOB" }, + :boolean => { :name => "NUMBER", :limit => 1 } + } + end + + + # QUOTING ================================================== + # + # see: abstract/quoting.rb + + # camelCase column names need to be quoted; not that anyone using Oracle + # would really do this, but handling this case means we pass the test... + def quote_column_name(name) #:nodoc: + name =~ /[A-Z]/ ? "\"#{name}\"" : name + end + + def quote_string(string) #:nodoc: + string.gsub(/'/, "''") + end + + def quote(value, column = nil) #:nodoc: + if column and column.type == :binary then %Q{empty_#{ column.sql_type }()} + else case value + when String then %Q{'#{quote_string(value)}'} + when NilClass then 'null' + when TrueClass then '1' + when FalseClass then '0' + when Numeric then value.to_s + when Date, Time then %Q{'#{value.strftime("%Y-%m-%d %H:%M:%S")}'} + else %Q{'#{quote_string(value.to_yaml)}'} + end + end + end + + + # CONNECTION MANAGEMENT ==================================== + # + + # Returns true if the connection is active. + def active? + # Pings the connection to check if it's still good. Note that an + # #active? method is also available, but that simply returns the + # last known state, which isn't good enough if the connection has + # gone stale since the last use. + @connection.ping + rescue OCIException + false + end + + # Reconnects to the database. + def reconnect! + @connection.reset! + rescue OCIException => e + @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" + end + + # Disconnects from the database. + def disconnect! + @connection.logoff rescue nil + @connection.active = false + end + + + # DATABASE STATEMENTS ====================================== + # + # see: abstract/database_statements.rb + + def select_all(sql, name = nil) #:nodoc: + select(sql, name) + end + + def select_one(sql, name = nil) #:nodoc: + result = select_all(sql, name) + result.size > 0 ? result.first : nil + end + + def execute(sql, name = nil) #:nodoc: + log(sql, name) { @connection.exec sql } + end + + def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: + if pk.nil? # Who called us? What does the sql look like? No idea! + execute sql, name + elsif id_value # Pre-assigned id + log(sql, name) { @connection.exec sql } + else # Assume the sql contains a bind-variable for the id + id_value = select_one("select #{sequence_name}.nextval id from dual")['id'] + log(sql, name) { @connection.exec sql, id_value } + end + + id_value + end + + alias :update :execute #:nodoc: + alias :delete :execute #:nodoc: + + def begin_db_transaction #:nodoc: + @connection.autocommit = false + end + + def commit_db_transaction #:nodoc: + @connection.commit + ensure + @connection.autocommit = true + end + + def rollback_db_transaction #:nodoc: + @connection.rollback + ensure + @connection.autocommit = true + end + + def add_limit_offset!(sql, options) #:nodoc: + offset = options[:offset] || 0 + + if limit = options[:limit] + sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}" + elsif offset > 0 + sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}" + end + end + + def default_sequence_name(table, column) #:nodoc: + "#{table}_seq" + end + + + # SCHEMA STATEMENTS ======================================== + # + # see: abstract/schema_statements.rb + + def tables(name = nil) #:nodoc: + select_all("select lower(table_name) from user_tables").inject([]) do | tabs, t | + tabs << t.to_a.first.last + end + end + + def indexes(table_name, name = nil) #:nodoc: + result = select_all(<<-SQL, name) + SELECT lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name + FROM user_indexes i, user_ind_columns c + WHERE i.table_name = '#{table_name.to_s.upcase}' + AND c.index_name = i.index_name + AND i.index_name NOT IN (SELECT index_name FROM user_constraints WHERE constraint_type = 'P') + ORDER BY i.index_name, c.column_position + SQL + + current_index = nil + indexes = [] + + result.each do |row| + if current_index != row['index_name'] + indexes << IndexDefinition.new(table_name, row['index_name'], row['uniqueness'] == "UNIQUE", []) + current_index = row['index_name'] + end + + indexes.last.columns << row['column_name'] + end + + indexes + end + + def columns(table_name, name = nil) #:nodoc: + table_info = @connection.object_info(table_name) + + table_cols = %Q{ + select column_name, data_type, data_default, nullable, + decode(data_type, 'NUMBER', data_precision, + 'VARCHAR2', data_length, + null) as length, + decode(data_type, 'NUMBER', data_scale, null) as scale + from all_tab_columns + where owner = '#{table_info.schema}' + and table_name = '#{table_info.name}' + order by column_id + } + + select_all(table_cols, name).map do |row| + row['data_default'].sub!(/^'(.*)'\s*$/, '\1') if row['data_default'] + OracleColumn.new( + oracle_downcase(row['column_name']), + row['data_default'], + row['data_type'], + row['length'].to_i, + row['scale'].to_i, + row['nullable'] == 'Y' + ) + end + end + + def create_table(name, options = {}) #:nodoc: + super(name, options) + execute "CREATE SEQUENCE #{name}_seq" unless options[:id] == false + end + + def rename_table(name, new_name) #:nodoc: + execute "RENAME #{name} TO #{new_name}" + execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil + end + + def drop_table(name) #:nodoc: + super(name) + execute "DROP SEQUENCE #{name}_seq" rescue nil + end + + def remove_index(table_name, options = {}) #:nodoc: + execute "DROP INDEX #{index_name(table_name, options)}" + end + + def change_column_default(table_name, column_name, default) #:nodoc: + execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}" + end + + def change_column(table_name, column_name, type, options = {}) #:nodoc: + change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}" + add_column_options!(change_column_sql, options) + execute(change_column_sql) + end + + def rename_column(table_name, column_name, new_column_name) #:nodoc: + execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}" + end + + def remove_column(table_name, column_name) #:nodoc: + execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}" + end + + def structure_dump #:nodoc: + s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq| + structure << "create sequence #{seq.to_a.first.last};\n\n" + end + + select_all("select table_name from user_tables").inject(s) do |structure, table| + ddl = "create table #{table.to_a.first.last} (\n " + cols = select_all(%Q{ + select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable + from user_tab_columns + where table_name = '#{table.to_a.first.last}' + order by column_id + }).map do |row| + col = "#{row['column_name'].downcase} #{row['data_type'].downcase}" + if row['data_type'] =='NUMBER' and !row['data_precision'].nil? + col << "(#{row['data_precision'].to_i}" + col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil? + col << ')' + elsif row['data_type'].include?('CHAR') + col << "(#{row['data_length'].to_i})" + end + col << " default #{row['data_default']}" if !row['data_default'].nil? + col << ' not null' if row['nullable'] == 'N' + col + end + ddl << cols.join(",\n ") + ddl << ");\n\n" + structure << ddl + end + end + + def structure_drop #:nodoc: + s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq| + drop << "drop sequence #{seq.to_a.first.last};\n\n" + end + + select_all("select table_name from user_tables").inject(s) do |drop, table| + drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n" + end + end + + + private + + def select(sql, name = nil) + cursor = log(sql, name) { @connection.exec sql } + cols = cursor.get_col_names.map { |x| oracle_downcase(x) } + rows = [] + + while row = cursor.fetch + hash = Hash.new + + cols.each_with_index do |col, i| + hash[col] = + case row[i] + when OCI8::LOB + name == 'Writable Large Object' ? row[i]: row[i].read + when OraDate + (row[i].hour == 0 and row[i].minute == 0 and row[i].second == 0) ? + row[i].to_date : row[i].to_time + else row[i] + end unless col == 'raw_rnum_' + end + + rows << hash + end + + rows + ensure + cursor.close if cursor + end + + # Oracle column names by default are case-insensitive, but treated as upcase; + # for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote + # their column names when creating Oracle tables, which makes then case-sensitive. + # I don't know anybody who does this, but we'll handle the theoretical case of a + # camelCase column name. I imagine other dbs handle this different, since there's a + # unit test that's currently failing test_oci. + def oracle_downcase(column_name) + column_name =~ /[a-z]/ ? column_name : column_name.downcase + end + + end + end + end + + + class OCI8 #:nodoc: + + # This OCI8 patch may not longer be required with the upcoming + # release of version 0.2. + class Cursor #:nodoc: + alias :define_a_column_pre_ar :define_a_column + def define_a_column(i) + case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) } + when 8 : @stmt.defineByPos(i, String, 65535) # Read LONG values + when 187 : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values + else define_a_column_pre_ar i + end + end + end + + # missing constant from oci8 < 0.1.14 + OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK) + + def object_info(name) + OraObject.new describe(name.to_s, OCI_PTYPE_UNK) + end + + def describe(name, type) + @desc ||= @@env.alloc(OCIDescribe) + @desc.describeAny(@svc, name, type) + @desc.attrGet(OCI_ATTR_PARAM) + end + + class OraObject + attr_reader :schema, :name + def initialize(info) + case info.attrGet(OCI_ATTR_PTYPE) + when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW + @schema = info.attrGet(OCI_ATTR_OBJ_SCHEMA) + @name = info.attrGet(OCI_ATTR_OBJ_NAME) + when OCI_PTYPE_SYN + @schema = info.attrGet(OCI_ATTR_SCHEMA_NAME) + @name = info.attrGet(OCI_ATTR_NAME) + end + end + end + end + + + # The OracleConnectionFactory factors out the code necessary to connect and + # configure an Oracle/OCI connection. + class OracleConnectionFactory #:nodoc: + def new_connection(username, password, database) + conn = OCI8.new username, password, database + conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'} + conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil + conn.autocommit = true + conn + end + end + + + # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and + # reset functionality. If a call to #exec fails, and autocommit is turned on + # (ie., we're not in the middle of a longer transaction), it will + # automatically reconnect and try again. If autocommit is turned off, + # this would be dangerous (as the earlier part of the implied transaction + # may have failed silently if the connection died) -- so instead the + # connection is marked as dead, to be reconnected on it's next use. + class OCI8AutoRecover < DelegateClass(OCI8) #:nodoc: + attr_accessor :active + alias :active? :active + + cattr_accessor :auto_retry + class << self + alias :auto_retry? :auto_retry + end + @@auto_retry = false + + def initialize(config, factory = OracleConnectionFactory.new) + @active = true + @username, @password, @database = config[:username], config[:password], config[:database] + @factory = factory + @connection = @factory.new_connection @username, @password, @database + super @connection + end + + # Checks connection, returns true if active. Note that ping actively + # checks the connection, while #active? simply returns the last + # known state. + def ping + @connection.exec("select 1 from dual") { |r| nil } + @active = true + rescue + @active = false + raise + end + + # Resets connection, by logging off and creating a new connection. + def reset! + logoff rescue nil + begin + @connection = @factory.new_connection @username, @password, @database + __setobj__ @connection + @active = true + rescue + @active = false + raise + end + end + + # ORA-00028: your session has been killed + # ORA-01012: not logged on + # ORA-03113: end-of-file on communication channel + # ORA-03114: not connected to ORACLE + LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ] + + # Adds auto-recovery functionality. + # + # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11 + def exec(sql, *bindvars) + should_retry = self.class.auto_retry? && autocommit? + + begin + @connection.exec(sql, *bindvars) + rescue OCIException => e + raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code) + @active = false + raise unless should_retry + should_retry = false + reset! rescue nil + retry + end + end + + end + +rescue LoadError + # OCI8 driver is unavailable. + module ActiveRecord # :nodoc: + class Base + def self.oracle_connection(config) # :nodoc: + # Set up a reasonable error message + raise LoadError, "Oracle/OCI libraries could not be loaded." + end + def self.oci_connection(config) # :nodoc: + # Set up a reasonable error message + raise LoadError, "Oracle/OCI libraries could not be loaded." + end + end + end +end diff --git a/activerecord/test/base_test.rb b/activerecord/test/base_test.rb index 4cdf24c03d..114e3cbd9e 100755 --- a/activerecord/test/base_test.rb +++ b/activerecord/test/base_test.rb @@ -17,7 +17,7 @@ class MasterCreditCard < ActiveRecord::Base; end class Post < ActiveRecord::Base; end class Computer < ActiveRecord::Base; end class NonExistentTable < ActiveRecord::Base; end -class TestOCIDefault < ActiveRecord::Base; end +class TestOracleDefault < ActiveRecord::Base; end class LoosePerson < ActiveRecord::Base attr_protected :credit_rating, :administrator @@ -526,8 +526,8 @@ class BasicsTest < Test::Unit::TestCase # Oracle has some funky default handling, so it requires a bit of # extra testing. See ticket #2788. - if current_adapter?(:OCIAdapter) - test = TestOCIDefault.new + if current_adapter?(:OracleAdapter) + test = TestOracleDefault.new assert_equal "X", test.test_char assert_equal "hello", test.test_string assert_equal 3, test.test_int @@ -536,7 +536,7 @@ class BasicsTest < Test::Unit::TestCase def test_utc_as_time_zone # Oracle and SQLServer do not have a TIME datatype. - return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OCIAdapter) + return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter) Topic.default_timezone = :utc attributes = { "bonus_time" => "5:42:00AM" } @@ -704,7 +704,7 @@ class BasicsTest < Test::Unit::TestCase def test_attributes_on_dummy_time # Oracle and SQL Server do not have a TIME datatype. - return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OCIAdapter) + return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter) attributes = { "bonus_time" => "5:42:00AM" diff --git a/activerecord/test/binary_test.rb b/activerecord/test/binary_test.rb index 8ba758a892..e7a87a4833 100644 --- a/activerecord/test/binary_test.rb +++ b/activerecord/test/binary_test.rb @@ -20,7 +20,7 @@ class BinaryTest < Test::Unit::TestCase # Without using prepared statements, it makes no sense to test # BLOB data with DB2 or Firebird, because the length of a statement # is limited to 32KB. - unless %w(SQLServer DB2 OCI Firebird).include? ActiveRecord::Base.connection.adapter_name + unless %w(SQLServer DB2 Oracle Firebird).include? ActiveRecord::Base.connection.adapter_name def test_load_save bin = Binary.new bin.data = @data diff --git a/activerecord/test/column_alias_test.rb b/activerecord/test/column_alias_test.rb index 729a252eef..19526cf9b3 100644 --- a/activerecord/test/column_alias_test.rb +++ b/activerecord/test/column_alias_test.rb @@ -4,7 +4,7 @@ require 'fixtures/topic' class TestColumnAlias < Test::Unit::TestCase fixtures :topics - QUERY = if 'OCI' == ActiveRecord::Base.connection.adapter_name + QUERY = if 'Oracle' == ActiveRecord::Base.connection.adapter_name 'SELECT id AS pk FROM topics WHERE ROWNUM < 2' else 'SELECT id AS pk FROM topics' diff --git a/activerecord/test/connections/native_oci/connection.rb b/activerecord/test/connections/native_oci/connection.rb deleted file mode 100644 index 0b1babb898..0000000000 --- a/activerecord/test/connections/native_oci/connection.rb +++ /dev/null @@ -1,25 +0,0 @@ -print "Using OCI Oracle\n" -require_dependency 'fixtures/course' -require 'logger' - -ActiveRecord::Base.logger = Logger.new STDOUT -ActiveRecord::Base.logger.level = Logger::WARN - -db1 = 'activerecord_unittest' -db2 = 'activerecord_unittest2' - -ActiveRecord::Base.establish_connection( - :adapter => 'oci', - :host => '', # can use an oracle SID - :username => 'arunit', - :password => 'arunit', - :database => db1 -) - -Course.establish_connection( - :adapter => 'oci', - :host => '', # can use an oracle SID - :username => 'arunit2', - :password => 'arunit2', - :database => db2 -) diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb new file mode 100644 index 0000000000..095fe9d5ee --- /dev/null +++ b/activerecord/test/connections/native_oracle/connection.rb @@ -0,0 +1,23 @@ +print "Using Oracle\n" +require_dependency 'fixtures/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new STDOUT +ActiveRecord::Base.logger.level = Logger::WARN + +# Set these to your database connection strings +db = 'activerecord_unit_tests' + +ActiveRecord::Base.establish_connection( + :adapter => 'oracle', + :username => 'arunit', + :password => 'arunit', + :database => db +) + +Course.establish_connection( + :adapter => 'oracle', + :username => 'arunit2', + :password => 'arunit2', + :database => db +) diff --git a/activerecord/test/fixtures/db_definitions/create_oracle_db.sh b/activerecord/test/fixtures/db_definitions/create_oracle_db.sh deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/activerecord/test/fixtures/db_definitions/oci.drop.sql b/activerecord/test/fixtures/db_definitions/oci.drop.sql deleted file mode 100644 index 4d4ddb835d..0000000000 --- a/activerecord/test/fixtures/db_definitions/oci.drop.sql +++ /dev/null @@ -1,61 +0,0 @@ -drop table accounts; -drop table funny_jokes; -drop table companies; -drop table topics; -drop synonym subjects; -drop table developers_projects; -drop table computers; -drop table developers; -drop table projects; -drop table customers; -drop table orders; -drop table movies; -drop table subscribers; -drop table booleantests; -drop table auto_id_tests; -drop table entrants; -drop table colnametests; -drop table mixins; -drop table people; -drop table readers; -drop table binaries; -drop table comments; -drop table authors; -drop table tasks; -drop table categories_posts; -drop table categories; -drop table posts; -drop table fk_test_has_pk; -drop table fk_test_has_fk; -drop table keyboards; -drop table legacy_things; - -drop sequence accounts_seq; -drop sequence funny_jokes_seq; -drop sequence companies_nonstd_seq; -drop sequence topics_seq; -drop sequence developers_seq; -drop sequence projects_seq; -drop sequence developers_projects_seq; -drop sequence customers_seq; -drop sequence orders_seq; -drop sequence movies_seq; -drop sequence subscribers_seq; -drop sequence booleantests_seq; -drop sequence auto_id_tests_seq; -drop sequence entrants_seq; -drop sequence colnametests_seq; -drop sequence mixins_seq; -drop sequence people_seq; -drop sequence binaries_seq; -drop sequence posts_seq; -drop sequence comments_seq; -drop sequence authors_seq; -drop sequence tasks_seq; -drop sequence computers_seq; -drop sequence categories_seq; -drop sequence categories_posts_seq; -drop sequence fk_test_has_pk_seq; -drop sequence fk_test_has_fk_seq; -drop sequence keyboards_seq; -drop sequence legacy_things_seq; diff --git a/activerecord/test/fixtures/db_definitions/oci.sql b/activerecord/test/fixtures/db_definitions/oci.sql deleted file mode 100644 index 7ecbea29b0..0000000000 --- a/activerecord/test/fixtures/db_definitions/oci.sql +++ /dev/null @@ -1,292 +0,0 @@ -create table companies ( - id integer not null, - type varchar(50) default null, - ruby_type varchar(50) default null, - firm_id integer default null references companies initially deferred disable, - name varchar(50) default null, - client_of integer default null references companies initially deferred disable, - companies_count integer default 0, - rating integer default 1, - primary key (id) -); - --- non-standard sequence name used to test set_sequence_name --- -create sequence companies_nonstd_seq minvalue 10000; - -create table funny_jokes ( - id integer not null, - name varchar(50) default null, - primary key (id) -); -create sequence funny_jokes_seq minvalue 10000; - -create table accounts ( - id integer not null, - firm_id integer default null references companies initially deferred disable, - credit_limit integer default null -); -create sequence accounts_seq minvalue 10000; - -create table topics ( - id integer not null, - title varchar(255) default null, - author_name varchar(255) default null, - author_email_address varchar(255) default null, - written_on timestamp default null, - bonus_time timestamp default null, - last_read timestamp default null, - content varchar(4000), - approved integer default 1, - replies_count integer default 0, - parent_id integer references topics initially deferred disable, - type varchar(50) default null, - primary key (id) -); --- try again for 8i -create table topics ( - id integer not null, - title varchar(255) default null, - author_name varchar(255) default null, - author_email_address varchar(255) default null, - written_on date default null, - bonus_time date default null, - last_read date default null, - content varchar(4000), - approved integer default 1, - replies_count integer default 0, - parent_id integer references topics initially deferred disable, - type varchar(50) default null, - primary key (id) -); -create sequence topics_seq minvalue 10000; - -create synonym subjects for topics; - -create table developers ( - id integer not null, - name varchar(100) default null, - salary integer default 70000, - created_at timestamp default null, - updated_at timestamp default null, - primary key (id) -); -create sequence developers_seq minvalue 10000; - -create table projects ( - id integer not null, - name varchar(100) default null, - type varchar(255) default null, - primary key (id) -); -create sequence projects_seq minvalue 10000; - -create table developers_projects ( - developer_id integer not null references developers initially deferred disable, - project_id integer not null references projects initially deferred disable, - joined_on timestamp default null, - access_level integer default 1 -); --- Try again for 8i -create table developers_projects ( - developer_id integer not null references developers initially deferred disable, - project_id integer not null references projects initially deferred disable, - joined_on date default null -); -create sequence developers_projects_seq minvalue 10000; - -create table orders ( - id integer not null, - name varchar(100) default null, - billing_customer_id integer default null, - shipping_customer_id integer default null, - primary key (id) -); -create sequence orders_seq minvalue 10000; - -create table customers ( - id integer not null, - name varchar(100) default null, - balance integer default 0, - address_street varchar(100) default null, - address_city varchar(100) default null, - address_country varchar(100) default null, - gps_location varchar(100) default null, - primary key (id) -); -create sequence customers_seq minvalue 10000; - -create table movies ( - movieid integer not null, - name varchar(100) default null, - primary key (movieid) -); -create sequence movies_seq minvalue 10000; - -create table subscribers ( - nick varchar(100) not null, - name varchar(100) default null, - primary key (nick) -); -create sequence subscribers_seq minvalue 10000; - -create table booleantests ( - id integer not null, - value integer default null, - primary key (id) -); -create sequence booleantests_seq minvalue 10000; - -create table auto_id_tests ( - auto_id integer not null, - value integer default null, - primary key (auto_id) -); -create sequence auto_id_tests_seq minvalue 10000; - -create table entrants ( - id integer not null primary key, - name varchar(255) not null, - course_id integer not null -); -create sequence entrants_seq minvalue 10000; - -create table colnametests ( - id integer not null, - references integer not null, - primary key (id) -); -create sequence colnametests_seq minvalue 10000; - -create table mixins ( - id integer not null, - parent_id integer default null references mixins initially deferred disable, - type varchar(40) default null, - pos integer default null, - lft integer default null, - rgt integer default null, - root_id integer default null, - created_at timestamp default null, - updated_at timestamp default null, - primary key (id) -); --- try again for 8i -create table mixins ( - id integer not null, - parent_id integer default null references mixins initially deferred disable, - type varchar(40) default null, - pos integer default null, - lft integer default null, - rgt integer default null, - root_id integer default null, - created_at date default null, - updated_at date default null, - primary key (id) -); -create sequence mixins_seq minvalue 10000; - -create table people ( - id integer not null, - first_name varchar(40) null, - lock_version integer default 0, - primary key (id) -); -create sequence people_seq minvalue 10000; - -create table readers ( - id integer not null, - post_id integer not null, - person_id integer not null, - primary key (id) -); -create sequence readers_seq minvalue 10000; - -create table binaries ( - id integer not null, - data blob null, - primary key (id) -); -create sequence binaries_seq minvalue 10000; - -create table computers ( - id integer not null primary key, - developer integer not null references developers initially deferred disable, - "extendedWarranty" integer not null -); -create sequence computers_seq minvalue 10000; - -create table posts ( - id integer not null primary key, - author_id integer default null, - title varchar(255) default null, - type varchar(255) default null, - body varchar(3000) default null -); -create sequence posts_seq minvalue 10000; - -create table comments ( - id integer not null primary key, - post_id integer default null, - type varchar(255) default null, - body varchar(3000) default null -); -create sequence comments_seq minvalue 10000; - -create table authors ( - id integer not null primary key, - name varchar(255) default null -); -create sequence authors_seq minvalue 10000; - -create table tasks ( - id integer not null primary key, - starting date default null, - ending date default null -); -create sequence tasks_seq minvalue 10000; - -create table categories ( - id integer not null primary key, - name varchar(255) default null, - type varchar(255) default null -); -create sequence categories_seq minvalue 10000; - -create table categories_posts ( - category_id integer not null references categories initially deferred disable, - post_id integer not null references posts initially deferred disable -); -create sequence categories_posts_seq minvalue 10000; - -create table fk_test_has_pk ( - id integer not null primary key -); -create sequence fk_test_has_pk_seq minvalue 10000; - -create table fk_test_has_fk ( - id integer not null primary key, - fk_id integer not null references fk_test_has_fk initially deferred disable -); -create sequence fk_test_has_fk_seq minvalue 10000; - -create table keyboards ( - key_number integer not null, - name varchar(50) default null -); -create sequence keyboards_seq minvalue 10000; - -create table test_oci_defaults ( - id integer not null primary key, - test_char char(1) default 'X' not null, - test_string varchar2(20) default 'hello' not null, - test_int integer default 3 not null -); -create sequence test_oci_defaults_seq minvalue 10000; - ---This table has an altered lock_version column name. -create table legacy_things ( - id integer not null primary key, - tps_report_number integer default null, - version integer default 0 -); -create sequence legacy_things_seq minvalue 10000; diff --git a/activerecord/test/fixtures/db_definitions/oci2.drop.sql b/activerecord/test/fixtures/db_definitions/oci2.drop.sql deleted file mode 100644 index abe7e55c31..0000000000 --- a/activerecord/test/fixtures/db_definitions/oci2.drop.sql +++ /dev/null @@ -1,2 +0,0 @@ -drop table courses; -drop sequence courses_seq; diff --git a/activerecord/test/fixtures/db_definitions/oci2.sql b/activerecord/test/fixtures/db_definitions/oci2.sql deleted file mode 100644 index 3c171f4f14..0000000000 --- a/activerecord/test/fixtures/db_definitions/oci2.sql +++ /dev/null @@ -1,6 +0,0 @@ -create table courses ( - id int not null primary key, - name varchar(255) not null -); - -create sequence courses_seq minvalue 10000; diff --git a/activerecord/test/fixtures/db_definitions/oracle.drop.sql b/activerecord/test/fixtures/db_definitions/oracle.drop.sql new file mode 100644 index 0000000000..4d4ddb835d --- /dev/null +++ b/activerecord/test/fixtures/db_definitions/oracle.drop.sql @@ -0,0 +1,61 @@ +drop table accounts; +drop table funny_jokes; +drop table companies; +drop table topics; +drop synonym subjects; +drop table developers_projects; +drop table computers; +drop table developers; +drop table projects; +drop table customers; +drop table orders; +drop table movies; +drop table subscribers; +drop table booleantests; +drop table auto_id_tests; +drop table entrants; +drop table colnametests; +drop table mixins; +drop table people; +drop table readers; +drop table binaries; +drop table comments; +drop table authors; +drop table tasks; +drop table categories_posts; +drop table categories; +drop table posts; +drop table fk_test_has_pk; +drop table fk_test_has_fk; +drop table keyboards; +drop table legacy_things; + +drop sequence accounts_seq; +drop sequence funny_jokes_seq; +drop sequence companies_nonstd_seq; +drop sequence topics_seq; +drop sequence developers_seq; +drop sequence projects_seq; +drop sequence developers_projects_seq; +drop sequence customers_seq; +drop sequence orders_seq; +drop sequence movies_seq; +drop sequence subscribers_seq; +drop sequence booleantests_seq; +drop sequence auto_id_tests_seq; +drop sequence entrants_seq; +drop sequence colnametests_seq; +drop sequence mixins_seq; +drop sequence people_seq; +drop sequence binaries_seq; +drop sequence posts_seq; +drop sequence comments_seq; +drop sequence authors_seq; +drop sequence tasks_seq; +drop sequence computers_seq; +drop sequence categories_seq; +drop sequence categories_posts_seq; +drop sequence fk_test_has_pk_seq; +drop sequence fk_test_has_fk_seq; +drop sequence keyboards_seq; +drop sequence legacy_things_seq; diff --git a/activerecord/test/fixtures/db_definitions/oracle.sql b/activerecord/test/fixtures/db_definitions/oracle.sql new file mode 100644 index 0000000000..39099b1747 --- /dev/null +++ b/activerecord/test/fixtures/db_definitions/oracle.sql @@ -0,0 +1,292 @@ +create table companies ( + id integer not null, + type varchar(50) default null, + ruby_type varchar(50) default null, + firm_id integer default null references companies initially deferred disable, + name varchar(50) default null, + client_of integer default null references companies initially deferred disable, + companies_count integer default 0, + rating integer default 1, + primary key (id) +); + +-- non-standard sequence name used to test set_sequence_name +-- +create sequence companies_nonstd_seq minvalue 10000; + +create table funny_jokes ( + id integer not null, + name varchar(50) default null, + primary key (id) +); +create sequence funny_jokes_seq minvalue 10000; + +create table accounts ( + id integer not null, + firm_id integer default null references companies initially deferred disable, + credit_limit integer default null +); +create sequence accounts_seq minvalue 10000; + +create table topics ( + id integer not null, + title varchar(255) default null, + author_name varchar(255) default null, + author_email_address varchar(255) default null, + written_on timestamp default null, + bonus_time timestamp default null, + last_read timestamp default null, + content varchar(4000), + approved integer default 1, + replies_count integer default 0, + parent_id integer references topics initially deferred disable, + type varchar(50) default null, + primary key (id) +); +-- try again for 8i +create table topics ( + id integer not null, + title varchar(255) default null, + author_name varchar(255) default null, + author_email_address varchar(255) default null, + written_on date default null, + bonus_time date default null, + last_read date default null, + content varchar(4000), + approved integer default 1, + replies_count integer default 0, + parent_id integer references topics initially deferred disable, + type varchar(50) default null, + primary key (id) +); +create sequence topics_seq minvalue 10000; + +create synonym subjects for topics; + +create table developers ( + id integer not null, + name varchar(100) default null, + salary integer default 70000, + created_at timestamp default null, + updated_at timestamp default null, + primary key (id) +); +create sequence developers_seq minvalue 10000; + +create table projects ( + id integer not null, + name varchar(100) default null, + type varchar(255) default null, + primary key (id) +); +create sequence projects_seq minvalue 10000; + +create table developers_projects ( + developer_id integer not null references developers initially deferred disable, + project_id integer not null references projects initially deferred disable, + joined_on timestamp default null, + access_level integer default 1 +); +-- Try again for 8i +create table developers_projects ( + developer_id integer not null references developers initially deferred disable, + project_id integer not null references projects initially deferred disable, + joined_on date default null +); +create sequence developers_projects_seq minvalue 10000; + +create table orders ( + id integer not null, + name varchar(100) default null, + billing_customer_id integer default null, + shipping_customer_id integer default null, + primary key (id) +); +create sequence orders_seq minvalue 10000; + +create table customers ( + id integer not null, + name varchar(100) default null, + balance integer default 0, + address_street varchar(100) default null, + address_city varchar(100) default null, + address_country varchar(100) default null, + gps_location varchar(100) default null, + primary key (id) +); +create sequence customers_seq minvalue 10000; + +create table movies ( + movieid integer not null, + name varchar(100) default null, + primary key (movieid) +); +create sequence movies_seq minvalue 10000; + +create table subscribers ( + nick varchar(100) not null, + name varchar(100) default null, + primary key (nick) +); +create sequence subscribers_seq minvalue 10000; + +create table booleantests ( + id integer not null, + value integer default null, + primary key (id) +); +create sequence booleantests_seq minvalue 10000; + +create table auto_id_tests ( + auto_id integer not null, + value integer default null, + primary key (auto_id) +); +create sequence auto_id_tests_seq minvalue 10000; + +create table entrants ( + id integer not null primary key, + name varchar(255) not null, + course_id integer not null +); +create sequence entrants_seq minvalue 10000; + +create table colnametests ( + id integer not null, + references integer not null, + primary key (id) +); +create sequence colnametests_seq minvalue 10000; + +create table mixins ( + id integer not null, + parent_id integer default null references mixins initially deferred disable, + type varchar(40) default null, + pos integer default null, + lft integer default null, + rgt integer default null, + root_id integer default null, + created_at timestamp default null, + updated_at timestamp default null, + primary key (id) +); +-- try again for 8i +create table mixins ( + id integer not null, + parent_id integer default null references mixins initially deferred disable, + type varchar(40) default null, + pos integer default null, + lft integer default null, + rgt integer default null, + root_id integer default null, + created_at date default null, + updated_at date default null, + primary key (id) +); +create sequence mixins_seq minvalue 10000; + +create table people ( + id integer not null, + first_name varchar(40) null, + lock_version integer default 0, + primary key (id) +); +create sequence people_seq minvalue 10000; + +create table readers ( + id integer not null, + post_id integer not null, + person_id integer not null, + primary key (id) +); +create sequence readers_seq minvalue 10000; + +create table binaries ( + id integer not null, + data blob null, + primary key (id) +); +create sequence binaries_seq minvalue 10000; + +create table computers ( + id integer not null primary key, + developer integer not null references developers initially deferred disable, + "extendedWarranty" integer not null +); +create sequence computers_seq minvalue 10000; + +create table posts ( + id integer not null primary key, + author_id integer default null, + title varchar(255) default null, + type varchar(255) default null, + body varchar(3000) default null +); +create sequence posts_seq minvalue 10000; + +create table comments ( + id integer not null primary key, + post_id integer default null, + type varchar(255) default null, + body varchar(3000) default null +); +create sequence comments_seq minvalue 10000; + +create table authors ( + id integer not null primary key, + name varchar(255) default null +); +create sequence authors_seq minvalue 10000; + +create table tasks ( + id integer not null primary key, + starting date default null, + ending date default null +); +create sequence tasks_seq minvalue 10000; + +create table categories ( + id integer not null primary key, + name varchar(255) default null, + type varchar(255) default null +); +create sequence categories_seq minvalue 10000; + +create table categories_posts ( + category_id integer not null references categories initially deferred disable, + post_id integer not null references posts initially deferred disable +); +create sequence categories_posts_seq minvalue 10000; + +create table fk_test_has_pk ( + id integer not null primary key +); +create sequence fk_test_has_pk_seq minvalue 10000; + +create table fk_test_has_fk ( + id integer not null primary key, + fk_id integer not null references fk_test_has_fk initially deferred disable +); +create sequence fk_test_has_fk_seq minvalue 10000; + +create table keyboards ( + key_number integer not null, + name varchar(50) default null +); +create sequence keyboards_seq minvalue 10000; + +create table test_oracle_defaults ( + id integer not null primary key, + test_char char(1) default 'X' not null, + test_string varchar2(20) default 'hello' not null, + test_int integer default 3 not null +); +create sequence test_oracle_defaults_seq minvalue 10000; + +--This table has an altered lock_version column name. +create table legacy_things ( + id integer not null primary key, + tps_report_number integer default null, + version integer default 0 +); +create sequence legacy_things_seq minvalue 10000; diff --git a/activerecord/test/fixtures/db_definitions/oracle2.drop.sql b/activerecord/test/fixtures/db_definitions/oracle2.drop.sql new file mode 100644 index 0000000000..abe7e55c31 --- /dev/null +++ b/activerecord/test/fixtures/db_definitions/oracle2.drop.sql @@ -0,0 +1,2 @@ +drop table courses; +drop sequence courses_seq; diff --git a/activerecord/test/fixtures/db_definitions/oracle2.sql b/activerecord/test/fixtures/db_definitions/oracle2.sql new file mode 100644 index 0000000000..3c171f4f14 --- /dev/null +++ b/activerecord/test/fixtures/db_definitions/oracle2.sql @@ -0,0 +1,6 @@ +create table courses ( + id int not null primary key, + name varchar(255) not null +); + +create sequence courses_seq minvalue 10000; diff --git a/activerecord/test/fixtures_test.rb b/activerecord/test/fixtures_test.rb index a57232e315..51f7109645 100755 --- a/activerecord/test/fixtures_test.rb +++ b/activerecord/test/fixtures_test.rb @@ -61,7 +61,7 @@ class FixturesTest < Test::Unit::TestCase t.column :written_on, :datetime t.column :bonus_time, :time t.column :last_read, :date - t.column :content, :text + t.column :content, :string t.column :approved, :boolean, :default => true t.column :replies_count, :integer, :default => 0 t.column :parent_id, :integer @@ -78,16 +78,16 @@ class FixturesTest < Test::Unit::TestCase topics = create_fixtures("topics") - # Restore prefix/suffix to its previous values - ActiveRecord::Base.table_name_prefix = old_prefix - ActiveRecord::Base.table_name_suffix = old_suffix - firstRow = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'David'") assert_equal("The First Topic", firstRow["title"]) secondRow = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'Mary'") assert_nil(secondRow["author_email_address"]) ensure + # Restore prefix/suffix to its previous values + ActiveRecord::Base.table_name_prefix = old_prefix + ActiveRecord::Base.table_name_suffix = old_suffix + ActiveRecord::Base.connection.drop_table :prefix_topics_suffix rescue nil end end diff --git a/activerecord/test/migration_test.rb b/activerecord/test/migration_test.rb index 8d8deb8026..4fec71d39f 100644 --- a/activerecord/test/migration_test.rb +++ b/activerecord/test/migration_test.rb @@ -84,7 +84,7 @@ if ActiveRecord::Base.connection.supports_migrations? four = columns.detect { |c| c.name == "four" } assert_equal "hello", one.default - if current_adapter?(:OCIAdapter) + if current_adapter?(:OracleAdapter) # Oracle doesn't support native booleans assert_equal true, two.default == 1 assert_equal false, three.default != 0 @@ -153,7 +153,7 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal Fixnum, bob.age.class assert_equal Time, bob.birthday.class - if current_adapter?(:SQLServerAdapter) or current_adapter?(:OCIAdapter) + if current_adapter?(:SQLServerAdapter) or current_adapter?(:OracleAdapter) # SQL Server and Oracle don't differentiate between date/time assert_equal Time, bob.favorite_day.class else @@ -241,7 +241,7 @@ if ActiveRecord::Base.connection.supports_migrations? ActiveRecord::Base.connection.rename_table :octopuses, :octopi assert_nothing_raised do - if current_adapter?(:OCIAdapter) + if current_adapter?(:OracleAdapter) # Oracle requires the explicit sequence for the pk ActiveRecord::Base.connection.execute "INSERT INTO octopi (id, url) VALUES (octopi_seq.nextval, 'http://www.foreverflying.com/octopus-black7.jpg')" else diff --git a/activerecord/test/readonly_test.rb b/activerecord/test/readonly_test.rb index bd9b8ef1b7..55f5cbe4ca 100755 --- a/activerecord/test/readonly_test.rb +++ b/activerecord/test/readonly_test.rb @@ -79,7 +79,7 @@ class ReadOnlyTest < Test::Unit::TestCase # Oracle barfs on this because the join includes unqualified and # conflicting column names - unless current_adapter?(:OCIAdapter) + unless current_adapter?(:OracleAdapter) Post.with_scope(:find => { :joins => ', developers' }) do assert Post.find(1).readonly? assert Post.find(1, :readonly => true).readonly? diff --git a/activerecord/test/synonym_test_oci.rb b/activerecord/test/synonym_test_oci.rb deleted file mode 100644 index fcfb2f3bd1..0000000000 --- a/activerecord/test/synonym_test_oci.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'abstract_unit' -require 'fixtures/topic' -require 'fixtures/subject' - -# confirm that synonyms work just like tables; in this case -# the "subjects" table in Oracle (defined in oci.sql) is just -# a synonym to the "topics" table - -class TestOracleSynonym < Test::Unit::TestCase - - def test_oracle_synonym - topic = Topic.new - subject = Subject.new - assert_equal(topic.attributes, subject.attributes) - end - -end diff --git a/activerecord/test/synonym_test_oracle.rb b/activerecord/test/synonym_test_oracle.rb new file mode 100644 index 0000000000..fcfb2f3bd1 --- /dev/null +++ b/activerecord/test/synonym_test_oracle.rb @@ -0,0 +1,17 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/subject' + +# confirm that synonyms work just like tables; in this case +# the "subjects" table in Oracle (defined in oci.sql) is just +# a synonym to the "topics" table + +class TestOracleSynonym < Test::Unit::TestCase + + def test_oracle_synonym + topic = Topic.new + subject = Subject.new + assert_equal(topic.attributes, subject.attributes) + end + +end -- cgit v1.2.3