From b3d1ad4fe7a4d91bc949cc5f52a7ea92be83c22d Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 5 Jun 2014 00:45:49 +0900 Subject: [PATCH 001/114] Initial support for Rails 4.2 This commit supports following changes, still it needs to support date/time features such as `set_date_columns` - BindSubstitution class removed https://github.com/rails/rails/commit/ee54e9bb3a6d7ffcf756ec1e385399b92354cb6b#diff-c226a4680f86689c3c170d4bc5911e96 - Delegate `Column#type` to the injected type object https://github.com/rails/rails/commit/0b682e4b05c8f58c77c655650af6638c483ac903 --- .../oracle_enhanced_adapter.rb | 24 +++++++--- .../oracle_enhanced_column.rb | 45 ++----------------- .../oracle_enhanced_database_statements.rb | 3 +- .../oracle_enhanced_connection_spec.rb | 2 +- 4 files changed, 23 insertions(+), 51 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index bdd7acff6..64c81d15f 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -384,21 +384,18 @@ def clear end end - class BindSubstitution < Arel::Visitors::Oracle #:nodoc: - include Arel::Visitors::BindVisitor - end - def initialize(connection, logger, config) #:nodoc: super(connection, logger) @quoted_column_names, @quoted_table_names = {}, {} @config = config @statements = StatementPool.new(connection, config.fetch(:statement_limit) { 250 }) @enable_dbms_output = false - if config.fetch(:prepared_statements) { true } - @visitor = Arel::Visitors::Oracle.new self + @visitor = Arel::Visitors::Oracle.new self + + if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @prepared_statements = true else - @visitor = unprepared_visitor + @prepared_statements = false end end @@ -1050,8 +1047,10 @@ def columns_without_cache(table_name, name = nil) #:nodoc: row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i end + cast_type = lookup_cast_type(row['sql_type']) OracleEnhancedColumn.new(oracle_downcase(row['name']), row['data_default'], + cast_type, row['sql_type'], row['nullable'] == 'Y', # pass table name for table specific column definitions @@ -1200,6 +1199,13 @@ def join_to_update(update, select) #:nodoc: protected + def initialize_type_map(m) + super + # oracle + m.register_type %r(date)i, Type::DateTime.new + m.alias_type %r(NUMBER\(1\))i, 'boolean' if OracleEnhancedAdapter.emulate_booleans + end + def translate_exception(exception, message) #:nodoc: case @connection.error_code(exception) when 1 @@ -1213,6 +1219,10 @@ def translate_exception(exception, message) #:nodoc: private + def select(sql, name = nil, binds = []) + exec_query(sql, name, binds) + end + def oracle_downcase(column_name) @connection.oracle_downcase(column_name) end diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column.rb b/lib/active_record/connection_adapters/oracle_enhanced_column.rb index 8a47823a3..2cb98296e 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_column.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_column.rb @@ -4,7 +4,7 @@ class OracleEnhancedColumn < Column attr_reader :table_name, :forced_column_type, :nchar, :virtual_column_data_default, :returning_id #:nodoc: - def initialize(name, default, sql_type = nil, null = true, table_name = nil, forced_column_type = nil, virtual=false, returning_id=false) #:nodoc: + def initialize(name, default, cast_type, sql_type = nil, null = true, table_name = nil, forced_column_type = nil, virtual=false, returning_id=false) #:nodoc: @table_name = table_name @forced_column_type = forced_column_type @virtual = virtual @@ -15,10 +15,10 @@ def initialize(name, default, sql_type = nil, null = true, table_name = nil, for else default_value = self.class.extract_value_from_default(default) end - super(name, default_value, sql_type, null) + super(name, default_value, cast_type, sql_type, null) # Is column NCHAR or NVARCHAR2 (will need to use N'...' value quoting for these data types)? # Define only when needed as adapter "quote" method will check at first if instance variable is defined. - @nchar = true if @type == :string && sql_type[0,1] == 'N' + @nchar = true if @cast_type == :string && sql_type[0,1] == 'N' @object_type = sql_type.include? '.' end @@ -96,45 +96,6 @@ def comment private - def simplified_type(field_type) - forced_column_type || - case field_type - when /decimal|numeric|number/i - if OracleEnhancedAdapter.emulate_booleans && field_type.upcase == "NUMBER(1)" - :boolean - elsif extract_scale(field_type) == 0 || - # if column name is ID or ends with _ID - OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(name, table_name) - :integer - elsif field_type.upcase == "NUMBER" - OracleEnhancedAdapter.number_datatype_coercion - else - :decimal - end - when /raw/i - :raw - when /char/i - if OracleEnhancedAdapter.emulate_booleans_from_strings && - OracleEnhancedAdapter.is_boolean_column?(name, field_type, table_name) - :boolean - else - :string - end - when /date/i - if OracleEnhancedAdapter.emulate_dates_by_column_name && OracleEnhancedAdapter.is_date_column?(name, table_name) - :date - else - :datetime - end - when /timestamp/i - :timestamp - when /time/i - :datetime - else - super - end - end - def self.extract_value_from_default(default) case default when String diff --git a/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb index 44a75c809..413a8da16 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb @@ -16,6 +16,7 @@ def substitute_at(column, index) def clear_cache! @statements.clear + reload_type_map end def exec_query(sql, name = 'SQL', binds = []) @@ -109,7 +110,7 @@ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # def sql_for_insert(sql, pk, id_value, sequence_name, binds) unless id_value || pk.nil? || (defined?(CompositePrimaryKeys) && pk.kind_of?(CompositePrimaryKeys::CompositeKeys)) sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id" - returning_id_col = OracleEnhancedColumn.new("returning_id", nil, "number", true, "dual", :integer, true, true) + returning_id_col = OracleEnhancedColumn.new("returning_id", nil, Type::Value.new, "number", true, "dual", :integer, true, true) (binds = binds.dup) << [returning_id_col, nil] end [sql, binds] diff --git a/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb index 7256810a6..a0c855371 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb @@ -227,7 +227,7 @@ def lookup(path) it "should execute prepared statement with decimal bind parameter " do cursor = @conn.prepare("INSERT INTO test_employees VALUES(:1)") - column = ActiveRecord::ConnectionAdapters::OracleEnhancedColumn.new('age', nil, 'NUMBER(10,2)') + column = ActiveRecord::ConnectionAdapters::OracleEnhancedColumn.new('age', nil, ActiveRecord::ConnectionAdapters::Type::Decimal.new, 'NUMBER(10,2)') column.type.should == :decimal cursor.bind_param(1, "1.5", column) cursor.exec From af27085c34d1ff7bf60cccad4f6f506a10bd13cf Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 27 Aug 2014 21:40:25 +0900 Subject: [PATCH 002/114] Support _field_changed argument changes Refer https://github.com/rails/rails/commit/368cca51 --- .../oracle_enhanced_dirty.rb | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb b/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb index e91fb4c9e..84497b145 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb @@ -5,27 +5,19 @@ module OracleEnhancedDirty #:nodoc: module InstanceMethods #:nodoc: private - def _field_changed?(attr, old, value) + def _field_changed?(attr, old_value) + new_value = read_attribute(attr) + raw_value = read_attribute_before_type_cast(attr) + if column = column_for_attribute(attr) - # Added also :decimal type - if ([:integer, :decimal, :float].include? column.type) && column.null && (old.nil? || old == 0) && value.blank? - # For nullable integer/decimal/float columns, NULL gets stored in database for blank (i.e. '') values. - # Hence we don't record it as a change if the value changes from nil to ''. - # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll - # be typecast back to 0 (''.to_i => 0) - value = nil - elsif column.type == :string && column.null && old.nil? - # Oracle stores empty string '' as NULL - # therefore need to convert empty string value to nil if old value is nil - value = nil if value == '' - elsif old == 0 && value.is_a?(String) && value.present? && non_zero?(value) - value = nil - else - value = column.type_cast(value) + # Oracle stores empty string '' as NULL + # therefore need to convert empty string value to nil if old value is nil + if column.type == :string && column.null && old_value.nil? + new_value = nil if new_value == '' end end - old != value + column_for_attribute(attr).changed?(old_value, new_value, raw_value) end def non_zero?(value) From e210373c62bf3a8c5548e81ea82a72bd9387bccc Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 27 Aug 2014 23:31:40 +0900 Subject: [PATCH 003/114] ActiveRecord::Migrator.proper_table_name has been removed from Rails Refer https://github.com/rails/rails/commit/6da0072 It has not supported `table_name_prefix` and `table_name_suffix` yet. --- .../oracle_enhanced_schema_statements_ext.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb index 9629b49d2..d8ed160d1 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb @@ -95,7 +95,8 @@ def foreign_key_definition(to_table, options = {}) #:nodoc: references_sql = quote_column_name(options[:primary_key] || references || "id") end - table_name = ActiveRecord::Migrator.proper_table_name(to_table) + table_name = to_table + # TODO: Needs support `table_name_prefix` and `table_name_suffix` sql = "FOREIGN KEY (#{columns_sql}) REFERENCES #{quote_table_name(table_name)}(#{references_sql})" From 55aae6c6c85c1a5e08bfafa1c7c952a8e94de639 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 25 Aug 2014 10:08:56 +0900 Subject: [PATCH 004/114] Add new_column method See https://github.com/rails/rails/commit/98a7dde0 --- .../connection_adapters/oracle_enhanced_adapter.rb | 4 ++++ .../connection_adapters/oracle_enhanced_column.rb | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 64c81d15f..61637a8d0 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1197,6 +1197,10 @@ def join_to_update(update, select) #:nodoc: end end + def new_column(name, default, cast_type, sql_type = nil, null = true, table_name = nil, forced_column_type = nil, virtual=false, returning_id=false) + ActiveRecord::ConnectionAdapters::OracleEnhancedColumn.new(name, default, cast_type, sql_type = nil, null = true, table_name = nil, forced_column_type = nil, virtual=false, returning_id=false) + end + protected def initialize_type_map(m) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column.rb b/lib/active_record/connection_adapters/oracle_enhanced_column.rb index 2cb98296e..49efb89e3 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_column.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_column.rb @@ -19,7 +19,8 @@ def initialize(name, default, cast_type, sql_type = nil, null = true, table_name # Is column NCHAR or NVARCHAR2 (will need to use N'...' value quoting for these data types)? # Define only when needed as adapter "quote" method will check at first if instance variable is defined. @nchar = true if @cast_type == :string && sql_type[0,1] == 'N' - @object_type = sql_type.include? '.' + # TODO: Need to investigate when `sql_type` becomes nil + @object_type = sql_type.include? '.' unless sql_type.nil? end def type_cast(value) #:nodoc: From 75511ccb63d5e34e7d7538b63ebb68db65115f3e Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 29 Aug 2014 00:03:26 +0900 Subject: [PATCH 005/114] Column#primary method removed from Rails Refer https://github.com/rails/rails/commit/05dd3df35db8b2d39ed177f4ccfe112bdfe7726d --- .../connection_adapters/oracle_enhanced_adapter_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb index 24c01a4d5..878363584 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb @@ -625,7 +625,7 @@ class ::TestPost < ActiveRecord::Base end it "should clear older cursors when statement limit is reached" do - pk = TestPost.columns.find { |c| c.primary } + pk = TestPost.columns_hash[TestPost.primary_key] sub = @conn.substitute_at(pk, 0) binds = [[pk, 1]] @@ -638,7 +638,7 @@ class ::TestPost < ActiveRecord::Base it "should cache UPDATE statements with bind variables" do lambda { - pk = TestPost.columns.find { |c| c.primary } + pk = TestPost.columns_hash[TestPost.primary_key] sub = @conn.substitute_at(pk, 0) binds = [[pk, 1]] @conn.exec_update("UPDATE test_posts SET id = #{sub}", "SQL", binds) @@ -679,7 +679,7 @@ class ::TestPost < ActiveRecord::Base end it "should explain query with binds" do - pk = TestPost.columns.find { |c| c.primary } + pk = TestPost.columns_hash[TestPost.primary_key] sub = @conn.substitute_at(pk, 0) explain = TestPost.where(TestPost.arel_table[pk.name].eq(sub)).bind([pk, 1]).explain explain.should include("Cost") From 7957a8846aacb753e61ed1a9dc83feaf0eb714e0 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 29 Aug 2014 00:33:26 +0900 Subject: [PATCH 006/114] Types namespace moved `ActiveRecord::ConnectionAdapters::Type::Value` to `ActiveRecord::Type::Value` --- .../connection_adapters/oracle_enhanced_connection_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb index a0c855371..0ceb27965 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb @@ -227,7 +227,7 @@ def lookup(path) it "should execute prepared statement with decimal bind parameter " do cursor = @conn.prepare("INSERT INTO test_employees VALUES(:1)") - column = ActiveRecord::ConnectionAdapters::OracleEnhancedColumn.new('age', nil, ActiveRecord::ConnectionAdapters::Type::Decimal.new, 'NUMBER(10,2)') + column = ActiveRecord::ConnectionAdapters::OracleEnhancedColumn.new('age', nil, ActiveRecord::Type::Decimal.new, 'NUMBER(10,2)') column.type.should == :decimal cursor.bind_param(1, "1.5", column) cursor.exec From 4364869c4516a9632a4acaccae1cd0375050d003 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 29 Aug 2014 06:41:49 +0900 Subject: [PATCH 007/114] Rename type_cast to type_cast_from_database Ref https://github.com/rails/rails/commit/d24e6407a7f5d662cb52ed57efc4d8ee11758170 --- .../oracle_enhanced_data_types_spec.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb index 8d87fc05e..534ba7268 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb @@ -67,21 +67,21 @@ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = false columns = @conn.columns('test_employees') column = columns.detect{|c| c.name == "hire_date"} - column.type_cast(Time.now).class.should == Time + column.type_cast_from_database(Time.now).class.should == Time end it "should return Date value from DATE column if column name contains 'date' and emulate_dates_by_column_name is true" do ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true columns = @conn.columns('test_employees') column = columns.detect{|c| c.name == "hire_date"} - column.type_cast(Time.now).class.should == Date + column.type_cast_from_database(Time.now).class.should == Date end it "should typecast DateTime value to Date value from DATE column if column name contains 'date' and emulate_dates_by_column_name is true" do ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true columns = @conn.columns('test_employees') column = columns.detect{|c| c.name == "hire_date"} - column.type_cast(DateTime.new(1900,1,1)).class.should == Date + column.type_cast_from_database(DateTime.new(1900,1,1)).class.should == Date end describe "/ DATE values from ActiveRecord model" do @@ -295,14 +295,14 @@ class ::TestEmployee < ActiveRecord::Base ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = false columns = @conn.columns('test2_employees') column = columns.detect{|c| c.name == "job_id"} - column.type_cast(1.0).class.should == BigDecimal + column.type_cast_from_database(1.0).class.should == BigDecimal end it "should return Fixnum value from NUMBER column if column name ends with '_id' and emulate_integers_by_column_name is true" do ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true columns = @conn.columns('test2_employees') column = columns.detect{|c| c.name == "job_id"} - column.type_cast(1.0).class.should == Fixnum + column.type_cast_from_database(1.0).class.should == Fixnum end describe "/ NUMBER values from ActiveRecord model" do @@ -452,7 +452,7 @@ def create_employee2 columns = @conn.columns('test3_employees') %w(has_email has_phone active_flag manager_yn).each do |col| column = columns.detect{|c| c.name == col} - column.type_cast("Y").class.should == String + column.type_cast_from_database("Y").class.should == String end end @@ -461,8 +461,8 @@ def create_employee2 columns = @conn.columns('test3_employees') %w(has_email has_phone active_flag manager_yn).each do |col| column = columns.detect{|c| c.name == col} - column.type_cast("Y").class.should == TrueClass - column.type_cast("N").class.should == FalseClass + column.type_cast_from_database("Y").class.should == TrueClass + column.type_cast_from_database("N").class.should == FalseClass end end From 6ff1e1a14e6e82e31cdfe80408aba88d225e793d Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 29 Aug 2014 10:07:11 +0900 Subject: [PATCH 008/114] Use quote_value method to avoid undefined method `type_cast_for_database' for nil:NilClass --- .../connection_adapters/oracle_enhanced_schema_creation.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_creation.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_creation.rb index 99d642b4c..de9cd9507 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_creation.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_creation.rb @@ -54,10 +54,9 @@ def add_column_options!(sql, options) # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly if options_include_default?(options) if type == :text - sql << " DEFAULT #{@conn.quote(options[:default])}" + sql << " DEFAULT #{quote_value(options[:default])}" else - # from abstract adapter - sql << " DEFAULT #{@conn.quote(options[:default], options[:column])}" + sql << " DEFAULT #{quote_value(options[:default], options[:column])}" end end # must explicitly add NULL or NOT NULL to allow change_column to work on migrations From 6e3ee643469f3fcc21b02cec87e0c9532b98f679 Mon Sep 17 00:00:00 2001 From: Leon Guan Date: Sun, 31 Aug 2014 21:30:09 +0800 Subject: [PATCH 009/114] text? has been removed from Column class Detail can be found here: https://github.com/rails/rails/commit/d44702ee45219153c5e56da0a06ffe6ab5d14518 So writing a CLOB column might causing error in 4.2 beta 1. Looks we don't need this check any longer, simply check it against String type might do the job. --- .../connection_adapters/oracle_enhanced_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 61637a8d0..ffbb22ba2 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -802,7 +802,7 @@ def write_lobs(table_name, klass, attributes, columns) #:nodoc: value = attributes[col.name] # changed sequence of next two lines - should check if value is nil before converting to yaml next if value.nil? || (value == '') - value = value.to_yaml if col.text? && klass.serialized_attributes[col.name] + value = value.to_yaml if value.is_a?(String) && klass.serialized_attributes[col.name] uncached do sql = is_with_cpk ? "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} WHERE #{klass.composite_where_clause(id)} FOR UPDATE" : "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} WHERE #{quote_column_name(klass.primary_key)} = #{id} FOR UPDATE" From 0fda4af27e22df654d67684c6c4fa8001c10ec04 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 1 Sep 2014 02:10:05 +0900 Subject: [PATCH 010/114] Support to work with Rails foreien_key implementation Ref: https://github.com/rails/rails/commit/69c711f38cac85e9c8bdbe286591bf88ef720bfa --- .../connection_adapters/oracle_enhanced_schema_dumper.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb index 422912597..820dd5ee6 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb @@ -7,6 +7,8 @@ def self.included(base) #:nodoc: private alias_method_chain :tables, :oracle_enhanced alias_method_chain :indexes, :oracle_enhanced + alias_method_chain :indexes, :oracle_enhanced + alias_method_chain :foreign_keys, :oracle_enhanced end end @@ -56,7 +58,7 @@ def primary_key_trigger(table_name, stream) end end - def foreign_keys(table_name, stream) + def foreign_keys_with_oracle_enhanced(table_name, stream) if @connection.respond_to?(:foreign_keys) && (foreign_keys = @connection.foreign_keys(table_name)).any? add_foreign_key_statements = foreign_keys.map do |foreign_key| statement_parts = [ ('add_foreign_key ' + foreign_key.from_table.inspect) ] From 5c52bfdc15235afb09e9b2c38711684327050c72 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 1 Sep 2014 08:01:24 +0900 Subject: [PATCH 011/114] Add `null: true` to avoid DEPRECATION WARNING DEPRECATION WARNING: `timestamp` was called without specifying an option for `null`. In Rails 5.0, this behavior will change to `null: false`. You should manually specify `null: true` to prevent the behavior of your existing migrations --- .../connection_adapters/oracle_enhanced_adapter_spec.rb | 2 +- .../connection_adapters/oracle_enhanced_context_index_spec.rb | 4 ++-- .../connection_adapters/oracle_enhanced_cpk_spec.rb | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb index 878363584..0d17f3723 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb @@ -473,7 +473,7 @@ class ::CamelCase < ActiveRecord::Base t.string :title # cannot update LOBs over database link t.string :body - t.timestamps + t.timestamps null: true end @db_link_username = SYSTEM_CONNECTION_PARAMS[:username] @db_link_password = SYSTEM_CONNECTION_PARAMS[:password] diff --git a/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb index 2b4356d81..174dc30fb 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb @@ -11,7 +11,7 @@ def create_table_posts t.string :title t.text :body t.integer :comments_count - t.timestamps + t.timestamps null: true t.string :all_text, limit: 2 # will be used for multi-column index end end @@ -23,7 +23,7 @@ def create_table_comments t.integer :post_id t.string :author t.text :body - t.timestamps + t.timestamps null: true end end end diff --git a/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb index 721e61aae..7dcbae1b1 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb @@ -67,12 +67,12 @@ class ::JobHistory < ActiveRecord::Base t.string :type_category, :limit => 15, :null => false t.date :date_value, :null => false t.text :results, :null => false - t.timestamps + t.timestamps null: true end create_table :non_cpk_write_lobs_test, :force => true do |t| t.date :date_value, :null => false t.text :results, :null => false - t.timestamps + t.timestamps null: true end end class ::CpkWriteLobsTest < ActiveRecord::Base From e63681995d2f750d9160c5207914e9631e63bfcc Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 1 Sep 2014 08:22:00 +0900 Subject: [PATCH 012/114] Support NCHAR correctly --- lib/active_record/connection_adapters/oracle_enhanced_column.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column.rb b/lib/active_record/connection_adapters/oracle_enhanced_column.rb index 49efb89e3..5b01747b6 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_column.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_column.rb @@ -18,7 +18,7 @@ def initialize(name, default, cast_type, sql_type = nil, null = true, table_name super(name, default_value, cast_type, sql_type, null) # Is column NCHAR or NVARCHAR2 (will need to use N'...' value quoting for these data types)? # Define only when needed as adapter "quote" method will check at first if instance variable is defined. - @nchar = true if @cast_type == :string && sql_type[0,1] == 'N' + @nchar = true if cast_type.class == ActiveRecord::Type::String && sql_type[0,1] == 'N' # TODO: Need to investigate when `sql_type` becomes nil @object_type = sql_type.include? '.' unless sql_type.nil? end From fddd90e2d231964f85310c3d32cb81a28ea0a1b5 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 1 Sep 2014 08:50:02 +0900 Subject: [PATCH 013/114] Support OracleEnhancedAdapter.emulate_integers_by_column_name in Rails 4.2 --- .../connection_adapters/oracle_enhanced_column.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column.rb b/lib/active_record/connection_adapters/oracle_enhanced_column.rb index 5b01747b6..91ae4ce2b 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_column.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_column.rb @@ -15,6 +15,11 @@ def initialize(name, default, cast_type, sql_type = nil, null = true, table_name else default_value = self.class.extract_value_from_default(default) end + # TODO: Consider to extract to another method + if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(name, table_name) + cast_type = ActiveRecord::Type::Integer.new + end + super(name, default_value, cast_type, sql_type, null) # Is column NCHAR or NVARCHAR2 (will need to use N'...' value quoting for these data types)? # Define only when needed as adapter "quote" method will check at first if instance variable is defined. From d5753196e8532568812e0924c8bbcefd8b8d2347 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 1 Sep 2014 09:40:48 +0900 Subject: [PATCH 014/114] Support OracleEnhancedAdapter.emulate_dates_by_column_name in Rails 4.2 --- .../connection_adapters/oracle_enhanced_column.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column.rb b/lib/active_record/connection_adapters/oracle_enhanced_column.rb index 91ae4ce2b..c78d302a1 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_column.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_column.rb @@ -20,6 +20,9 @@ def initialize(name, default, cast_type, sql_type = nil, null = true, table_name cast_type = ActiveRecord::Type::Integer.new end + if OracleEnhancedAdapter.emulate_dates_by_column_name && OracleEnhancedAdapter.is_date_column?(name, table_name) + cast_type = ActiveRecord::Type::Date.new + end super(name, default_value, cast_type, sql_type, null) # Is column NCHAR or NVARCHAR2 (will need to use N'...' value quoting for these data types)? # Define only when needed as adapter "quote" method will check at first if instance variable is defined. From 45c2b5a0e77f163efb5eca13c7f1813b6c987099 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 1 Sep 2014 20:38:41 +0900 Subject: [PATCH 015/114] Set @nchar and @object_type only when sql_type is true --- .../connection_adapters/oracle_enhanced_column.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column.rb b/lib/active_record/connection_adapters/oracle_enhanced_column.rb index c78d302a1..819b8c9fd 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_column.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_column.rb @@ -26,9 +26,11 @@ def initialize(name, default, cast_type, sql_type = nil, null = true, table_name super(name, default_value, cast_type, sql_type, null) # Is column NCHAR or NVARCHAR2 (will need to use N'...' value quoting for these data types)? # Define only when needed as adapter "quote" method will check at first if instance variable is defined. - @nchar = true if cast_type.class == ActiveRecord::Type::String && sql_type[0,1] == 'N' + if sql_type + @nchar = true if cast_type.class == ActiveRecord::Type::String && sql_type[0,1] == 'N' + @object_type = sql_type.include? '.' + end # TODO: Need to investigate when `sql_type` becomes nil - @object_type = sql_type.include? '.' unless sql_type.nil? end def type_cast(value) #:nodoc: From 5ce38a2b262beb00375ce4dae1bacf3e17e179f5 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 10 Sep 2014 01:14:26 +0900 Subject: [PATCH 016/114] Add `connection.supports_views?` Refer https://github.com/rails/rails/commit/ae9412e857a78236b359eb1b636511d07fe45cf3 --- .../connection_adapters/oracle_enhanced_adapter.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index ffbb22ba2..986970b20 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -425,6 +425,10 @@ def supports_transaction_isolation? #:nodoc: true end + def supports_views? + true + end + NUMBER_MAX_PRECISION = 38 #:stopdoc: From 40102bdb041edd1ef2e8e466cc9c8e0d09d94b84 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 10 Sep 2014 05:32:28 +0900 Subject: [PATCH 017/114] Fix create_savepoint and rollback_to_savepoint name --- .../oracle_enhanced_database_statements.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb index 413a8da16..1f9416c7c 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb @@ -215,11 +215,11 @@ def rollback_db_transaction #:nodoc: end def create_savepoint(name = current_savepoint_name) #:nodoc: - execute("SAVEPOINT #{current_savepoint_name}") + execute("SAVEPOINT #{name}") end def rollback_to_savepoint(name = current_savepoint_name) #:nodoc: - execute("ROLLBACK TO #{current_savepoint_name}") + execute("ROLLBACK TO #{name}") end def release_savepoint(name = current_savepoint_name) #:nodoc: From 6ea1397020f303b8e99ea97b11fa020ddc25e23f Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 19 Aug 2014 06:52:16 +0900 Subject: [PATCH 018/114] Handle forced_column_type temporary --- .../connection_adapters/oracle_enhanced_adapter.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 986970b20..a20c0b402 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1051,7 +1051,12 @@ def columns_without_cache(table_name, name = nil) #:nodoc: row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i end - cast_type = lookup_cast_type(row['sql_type']) + # TODO: It is just for `set_date_columns` now. Needs to be generic + if get_type_for_column(table_name, oracle_downcase(row['name'])) + cast_type = Type::Date.new + else + cast_type = lookup_cast_type(row['sql_type']) + end OracleEnhancedColumn.new(oracle_downcase(row['name']), row['data_default'], cast_type, From 6f0c5999d2ff64037079e9b25e8893bbe1a5cb2f Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 12 Sep 2014 22:28:12 +0900 Subject: [PATCH 019/114] Add `null: true` to avoid DEPRECATION WARNING --- .../connection_adapters/oracle_enhanced_schema_dump_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb index c7a2daf65..edfb3a339 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb @@ -22,7 +22,7 @@ def create_test_posts_table(options = {}) schema_define do create_table :test_posts, options do |t| t.string :title - t.timestamps + t.timestamps null: true end add_index :test_posts, :title end From ec7b5dd035259f7d6ec7d1e1dabc699e7cb6752e Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 1 Oct 2014 22:10:59 +0900 Subject: [PATCH 020/114] Use register_class_with_limit Refer https://github.com/rails/rails/commit/111990513b20c045d4af08b20f07f98c7e2c0b92 --- .../connection_adapters/oracle_enhanced_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index ac4d990eb..2457aee3c 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1211,7 +1211,7 @@ def new_column(name, default, cast_type, sql_type = nil, null = true, table_name def initialize_type_map(m) super # oracle - m.register_type %r(date)i, Type::DateTime.new + register_class_with_limit m, %r(date)i, Type::DateTime m.alias_type %r(NUMBER\(1\))i, 'boolean' if OracleEnhancedAdapter.emulate_booleans end From b5938eb46eaf3148fa5d259999c27e21c6402e21 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 2 Oct 2014 00:55:32 +0900 Subject: [PATCH 021/114] Add Type::Raw type --- activerecord-oracle_enhanced-adapter.gemspec | 1 + .../connection_adapters/oracle_enhanced_adapter.rb | 4 ++++ lib/active_record/type/raw.rb | 11 +++++++++++ 3 files changed, 16 insertions(+) create mode 100644 lib/active_record/type/raw.rb diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index 4f5f5b7c5..0e67f94f7 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -47,6 +47,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. "lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb", "lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb", "lib/active_record/connection_adapters/oracle_enhanced_version.rb", + "lib/active_record/type/raw.rb", "lib/activerecord-oracle_enhanced-adapter.rb", "spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb", "spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb", diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 2457aee3c..76ee759c9 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1212,6 +1212,7 @@ def initialize_type_map(m) super # oracle register_class_with_limit m, %r(date)i, Type::DateTime + register_class_with_limit m, %r(raw)i, Type::Raw m.alias_type %r(NUMBER\(1\))i, 'boolean' if OracleEnhancedAdapter.emulate_booleans end @@ -1336,3 +1337,6 @@ module ActiveRecord # Moved DatabaseStetements require 'active_record/connection_adapters/oracle_enhanced_database_statements' + +# Add Type:Raw +require 'active_record/type/raw' diff --git a/lib/active_record/type/raw.rb b/lib/active_record/type/raw.rb new file mode 100644 index 000000000..756578173 --- /dev/null +++ b/lib/active_record/type/raw.rb @@ -0,0 +1,11 @@ +require 'active_record/type/string' + +module ActiveRecord + module Type + class Raw < String # :nodoc: + def type + :raw + end + end + end +end From fc41835e7a0cbb932d9495d611dd10f5e24a6e62 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 2 Oct 2014 04:42:17 +0900 Subject: [PATCH 022/114] Don't type cast the default on the column --- .../connection_adapters/oracle_enhanced_column_dumper.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb b/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb index e54e96efd..99aaaa060 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb @@ -47,7 +47,8 @@ def prepare_column_options_with_oracle_enhanced(column, types) spec[:scale] = column.scale.inspect if !column.scale.nil? spec[:null] = 'false' if !column.null spec[:as] = column.virtual_column_data_default if column.virtual? - spec[:default] = default_string(column.default) if column.has_default? && !column.virtual? + spec[:default] = schema_default(column) if column.has_default? && !column.virtual? + spec.delete(:default) if spec[:default].nil? if column.virtual? # Supports backwards compatibility with older OracleEnhancedAdapter versions where 'NUMBER' virtual column type is not included in dump From 51f956569c98bdc7d1499cec7aaa71cef7b17829 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 3 Oct 2014 05:17:55 +0900 Subject: [PATCH 023/114] Rails 4.2 support emulate_booleans_from_strings and is_boolean_column? --- .../connection_adapters/oracle_enhanced_adapter.rb | 6 +++--- .../connection_adapters/oracle_enhanced_column.rb | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 76ee759c9..41ba6ce07 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -327,9 +327,9 @@ def self.is_integer_column?(name, table_name = nil) # Check column name to identify if it is boolean (and not String) column. # Is used if +emulate_booleans_from_strings+ option is set to +true+. # Override this method definition in initializer file if different boolean column recognition is needed. - def self.is_boolean_column?(name, field_type, table_name = nil) - return true if ["CHAR(1)","VARCHAR2(1)"].include?(field_type) - field_type =~ /^VARCHAR2/ && (name =~ /_flag$/i || name =~ /_yn$/i) + def self.is_boolean_column?(name, sql_type, table_name = nil) + return true if ["CHAR(1)","VARCHAR2(1)"].include?(sql_type) + sql_type =~ /^VARCHAR2/ && (name =~ /_flag$/i || name =~ /_yn$/i) end # How boolean value should be quoted to String. diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column.rb b/lib/active_record/connection_adapters/oracle_enhanced_column.rb index 819b8c9fd..f3474f577 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_column.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_column.rb @@ -23,6 +23,10 @@ def initialize(name, default, cast_type, sql_type = nil, null = true, table_name if OracleEnhancedAdapter.emulate_dates_by_column_name && OracleEnhancedAdapter.is_date_column?(name, table_name) cast_type = ActiveRecord::Type::Date.new end + + if OracleEnhancedAdapter.emulate_booleans_from_strings && OracleEnhancedAdapter.is_boolean_column?(name, sql_type, table_name) + cast_type = ActiveRecord::Type::Boolean.new + end super(name, default_value, cast_type, sql_type, null) # Is column NCHAR or NVARCHAR2 (will need to use N'...' value quoting for these data types)? # Define only when needed as adapter "quote" method will check at first if instance variable is defined. From cd56b895c9bc38163135bf39838d28062f5d366c Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 6 Oct 2014 21:25:48 +0900 Subject: [PATCH 024/114] Handle NUMBER sql_type as `Type::Integer` cast type since Oracle database NUMBER datatype handles interger and decimal. --- .../connection_adapters/oracle_enhanced_adapter.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 41ba6ce07..d7770c8a4 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1213,6 +1213,15 @@ def initialize_type_map(m) # oracle register_class_with_limit m, %r(date)i, Type::DateTime register_class_with_limit m, %r(raw)i, Type::Raw + m.register_type(%r(NUMBER)i) do |sql_type| + scale = extract_scale(sql_type) + precision = extract_precision(sql_type) + if scale == 0 + Type::Integer.new(precision: precision) + else + Type::Decimal.new(precision: precision, scale: scale) + end + end m.alias_type %r(NUMBER\(1\))i, 'boolean' if OracleEnhancedAdapter.emulate_booleans end From 8d6aac50b5cdcd25fac5d02fc11b3a163ca683e2 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 7 Oct 2014 10:28:31 +0900 Subject: [PATCH 025/114] Address ArgumentError: wrong number of arguments (1 for 2) at `quote_value` --- .../connection_adapters/oracle_enhanced_schema_creation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_creation.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_creation.rb index de9cd9507..b42ffbc60 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_creation.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_creation.rb @@ -54,7 +54,7 @@ def add_column_options!(sql, options) # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly if options_include_default?(options) if type == :text - sql << " DEFAULT #{quote_value(options[:default])}" + sql << " DEFAULT #{@conn.quote(options[:default])}" else sql << " DEFAULT #{quote_value(options[:default], options[:column])}" end From 2e34a2b9ec096143557fadd0e6ad61d014fb610d Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 9 Oct 2014 21:30:08 +0900 Subject: [PATCH 026/114] Support OracleEnhancedAdapter.number_datatype_coercion for Rails 4.2 --- .../connection_adapters/oracle_enhanced_adapter.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index d7770c8a4..8299dd7cb 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1219,7 +1219,11 @@ def initialize_type_map(m) if scale == 0 Type::Integer.new(precision: precision) else - Type::Decimal.new(precision: precision, scale: scale) + if OracleEnhancedAdapter.number_datatype_coercion == :decimal + Type::Decimal.new(precision: precision, scale: scale) + elsif OracleEnhancedAdapter.number_datatype_coercion == :float + Type::Float.new(precision: precision, scale: scale) + end end end m.alias_type %r(NUMBER\(1\))i, 'boolean' if OracleEnhancedAdapter.emulate_booleans From 49502f8a6f65faf2e241cb94f20a58058acdabe4 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 4 Nov 2014 06:36:20 +0900 Subject: [PATCH 027/114] Remove redundant substitute index when constructing bind values refer https://github.com/rails/rails/pull/17463 --- .../connection_adapters/oracle_enhanced_database_statements.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb index 1f9416c7c..4565ddc61 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb @@ -10,7 +10,7 @@ def execute(sql, name = nil) log(sql, name) { @connection.exec(sql) } end - def substitute_at(column, index) + def substitute_at(column, index = 0) Arel::Nodes::BindParam.new (":a#{index + 1}") end From da8e902353c539603c38a597fa28718abd0b6aa9 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 19 Nov 2014 01:03:11 +0900 Subject: [PATCH 028/114] Remove substitute_at method from Oracle enhanced adapter Now Arel takes care of it Refer https://github.com/rails/rails/commit/c01b20b658c9fe4b7d54f4a227a09cb090b5763d --- .../oracle_enhanced_database_statements.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb index 4565ddc61..0594a4f43 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb @@ -10,10 +10,6 @@ def execute(sql, name = nil) log(sql, name) { @connection.exec(sql) } end - def substitute_at(column, index = 0) - Arel::Nodes::BindParam.new (":a#{index + 1}") - end - def clear_cache! @statements.clear reload_type_map From 7a39c4c0edcde8ec2cb9fa3c78a0c6c599a20e58 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 25 Nov 2014 22:34:30 +0900 Subject: [PATCH 029/114] Unit test updated to support `substitute_at` in Arel Refer https://github.com/rails/arel/commit/590c784a30b13153667f8db7915998d7731e24e5 --- .../connection_adapters/oracle_enhanced_adapter_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb index 0d17f3723..a074dd97f 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb @@ -626,7 +626,7 @@ class ::TestPost < ActiveRecord::Base it "should clear older cursors when statement limit is reached" do pk = TestPost.columns_hash[TestPost.primary_key] - sub = @conn.substitute_at(pk, 0) + sub = @conn.substitute_at(pk, 0).to_sql binds = [[pk, 1]] lambda { @@ -639,7 +639,7 @@ class ::TestPost < ActiveRecord::Base it "should cache UPDATE statements with bind variables" do lambda { pk = TestPost.columns_hash[TestPost.primary_key] - sub = @conn.substitute_at(pk, 0) + sub = @conn.substitute_at(pk, 0).to_sql binds = [[pk, 1]] @conn.exec_update("UPDATE test_posts SET id = #{sub}", "SQL", binds) }.should change(@statements, :length).by(+1) From c7fe1a1218cc4571338c690cebce8455126f8864 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 4 Dec 2014 02:03:06 +0900 Subject: [PATCH 030/114] Require 'active_record/base' in rake task This may cause `Rails.application.config.active_record` inside of an initializer no effects. --- .../connection_adapters/oracle_enhanced_database_tasks.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_database_tasks.rb b/lib/active_record/connection_adapters/oracle_enhanced_database_tasks.rb index 6602e48b2..11769d8c2 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_database_tasks.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_database_tasks.rb @@ -1,3 +1,5 @@ +require 'active_record/base' + module ActiveRecord module ConnectionAdapters class OracleEnhancedAdapter From 4bf44d58823c6e5388b5670cb0ad8d7035ab9248 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 20 Jan 2015 13:29:34 +0000 Subject: [PATCH 031/114] Address ORA-00932: inconsistent datatypes: expected NUMBER got DATE Fix #537 --- .../connection_adapters/oracle_enhanced_adapter.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 8299dd7cb..9ce6630e9 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1048,8 +1048,11 @@ def columns_without_cache(table_name, name = nil) #:nodoc: end # TODO: It is just for `set_date_columns` now. Needs to be generic - if get_type_for_column(table_name, oracle_downcase(row['name'])) + case get_type_for_column(table_name, oracle_downcase(row['name'])) + when :date cast_type = Type::Date.new + when :integer + cast_type = Type::Integer.new else cast_type = lookup_cast_type(row['sql_type']) end From 4ac02779a70817a0cb062e5acb40ddfd8e01cef5 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 28 Jan 2015 14:36:37 +0000 Subject: [PATCH 032/114] Change log method signiture to support Rails 4.2 --- .../connection_adapters/oracle_enhanced_adapter.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 9ce6630e9..09c01bb66 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1285,12 +1285,8 @@ def dbms_output_enabled? end protected - def log(sql, name, binds = nil) #:nodoc: - if binds - super sql, name, binds - else - super sql, name - end + def log(sql, name = "SQL", binds = [], statement_name = nil) #:nodoc: + super ensure log_dbms_output if dbms_output_enabled? end From fb9322737a07728c790c5be4c05f5319f4ab4672 Mon Sep 17 00:00:00 2001 From: Stefan Rusterholz Date: Fri, 6 Feb 2015 09:54:10 +0100 Subject: [PATCH 033/114] Remove call to deprecated `serialized_attributes`. According to http://edgeguides.rubyonrails.org/4_2_release_notes.html#active-record-notable-changes (3rd point, "ActiveRecord::Dirty now detects in-place changes to mutable values.") it should be no longer necessary to check whether the attribute is serialized. Note: this change is probably incompatible with rails prior 4.2.0. --- .../connection_adapters/oracle_enhanced_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index abbce7256..0e01d1a11 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -131,7 +131,7 @@ def enhanced_write_lobs def record_changed_lobs @changed_lob_columns = self.class.lob_columns.select do |col| - (self.class.serialized_attributes.keys.include?(col.name) || self.send(:"#{col.name}_changed?")) && !self.class.readonly_attributes.to_a.include?(col.name) + self.attribute_changed?(col.name) && !self.class.readonly_attributes.to_a.include?(col.name) end end end From 232ec82676e7fca5d03b3246e5235cef74adb87c Mon Sep 17 00:00:00 2001 From: Stefan Rusterholz Date: Fri, 6 Feb 2015 14:10:40 +0100 Subject: [PATCH 034/114] Make it easier to spot which version of active record is actually used. --- spec/spec_helper.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5fcaf3252..1f2f54c21 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -16,7 +16,7 @@ ENV['RAILS_GEM_VERSION'] ||= '4.0-master' NO_COMPOSITE_PRIMARY_KEYS = true -puts "==> Running specs with Rails version #{ENV['RAILS_GEM_VERSION']}" +puts "==> Selected Rails version #{ENV['RAILS_GEM_VERSION']}" require 'active_record' @@ -32,6 +32,8 @@ require 'active_record/connection_adapters/oracle_enhanced_adapter' require 'ruby-plsql' +puts "==> Effective ActiveRecord version #{ActiveRecord::VERSION::STRING}" + module LoggerSpecHelper def set_logger @logger = MockLogger.new From c2cde1ba73a4160b21971bb4d8d6d6e97ba71d3c Mon Sep 17 00:00:00 2001 From: Stefan Rusterholz Date: Fri, 6 Feb 2015 14:31:31 +0100 Subject: [PATCH 035/114] Enable loading spec configuration from config file instead of env. --- .gitignore | 1 + spec/spec_config.yaml.template | 10 ++++++++++ spec/spec_helper.rb | 27 ++++++++++++++++++--------- 3 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 spec/spec_config.yaml.template diff --git a/.gitignore b/.gitignore index cf8d5b5d1..f623309f9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ log tmp sqlnet.log Gemfile.lock +/spec/spec_config.yaml diff --git a/spec/spec_config.yaml.template b/spec/spec_config.yaml.template new file mode 100644 index 000000000..f027ae316 --- /dev/null +++ b/spec/spec_config.yaml.template @@ -0,0 +1,10 @@ +rails: + gem_version: '4.0-master' +database: + name: 'orcl' + host: '127.0.0.1' + port: 1521 + user: 'oracle_enhanced' + password: 'oracle_enhanced' + sys_password: 'admin' +timezone: 'Europe/Riga' \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1f2f54c21..2c33952b7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,17 @@ -require 'rubygems' +require "rubygems" require "bundler" +require "yaml" Bundler.setup(:default, :development) $:.unshift(File.expand_path('../../lib', __FILE__)) +config_path = File.expand_path('../spec_config.yaml', __FILE__) +if File.exist?(config_path) + puts "==> Loading config from #{config_path}" + config = YAML.load_file(config_path) +else + puts "==> Loading config from ENV or use default" + config = {"rails" => {}, "database" => {}} +end require 'rspec' @@ -13,7 +22,7 @@ puts "==> Running specs with JRuby version #{JRUBY_VERSION}" end -ENV['RAILS_GEM_VERSION'] ||= '4.0-master' +ENV['RAILS_GEM_VERSION'] ||= config["rails"]["gem_version"] || '4.0-master' NO_COMPOSITE_PRIMARY_KEYS = true puts "==> Selected Rails version #{ENV['RAILS_GEM_VERSION']}" @@ -111,12 +120,12 @@ def schema_define(&block) end end -DATABASE_NAME = ENV['DATABASE_NAME'] || 'orcl' -DATABASE_HOST = ENV['DATABASE_HOST'] -DATABASE_PORT = ENV['DATABASE_PORT'] -DATABASE_USER = ENV['DATABASE_USER'] || 'oracle_enhanced' -DATABASE_PASSWORD = ENV['DATABASE_PASSWORD'] || 'oracle_enhanced' -DATABASE_SYS_PASSWORD = ENV['DATABASE_SYS_PASSWORD'] || 'admin' +DATABASE_NAME = config["database"]["name"] || ENV['DATABASE_NAME'] || 'orcl' +DATABASE_HOST = config["database"]["host"] || ENV['DATABASE_HOST'] || "127.0.0.1" +DATABASE_PORT = config["database"]["port"] || ENV['DATABASE_PORT'] || 1521 +DATABASE_USER = config["database"]["user"] || ENV['DATABASE_USER'] || 'oracle_enhanced' +DATABASE_PASSWORD = config["database"]["password"] || ENV['DATABASE_PASSWORD'] || 'oracle_enhanced' +DATABASE_SYS_PASSWORD = config["database"]["sys_password"] || ENV['DATABASE_SYS_PASSWORD'] || 'admin' CONNECTION_PARAMS = { :adapter => "oracle_enhanced", @@ -150,7 +159,7 @@ def schema_define(&block) # set default time zone in TZ environment variable # which will be used to set session time zone -ENV['TZ'] ||= 'Europe/Riga' +ENV['TZ'] ||= config["timezone"] || 'Europe/Riga' # ActiveRecord::Base.logger = Logger.new(STDOUT) From 826e31f33dce7b9faea495b11c297dd3cbfa4208 Mon Sep 17 00:00:00 2001 From: Stefan Rusterholz Date: Fri, 6 Feb 2015 15:14:50 +0100 Subject: [PATCH 036/114] Added failing spec for #545. --- .../connection_adapters/oracle_enhanced_dirty_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb index 6dfbca9db..41d7ff9ed 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb @@ -147,6 +147,11 @@ class << oci_conn end end + it "should be able to handle attributes which are not backed by a column" do + TestEmployee.create!(:comments => "initial") + @employee = TestEmployee.select("#{TestEmployee.quoted_table_name}.*, 24 ranking").first + expect { @employee.ranking = 25 }.to_not raise_error + end end end From d2819d0d59b0b3be471341bcea7a67020bdff0f1 Mon Sep 17 00:00:00 2001 From: Stefan Rusterholz Date: Fri, 6 Feb 2015 15:16:34 +0100 Subject: [PATCH 037/114] Fix #545. --- .../connection_adapters/oracle_enhanced_dirty.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb b/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb index 84497b145..a5fca7b34 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb @@ -15,9 +15,11 @@ def _field_changed?(attr, old_value) if column.type == :string && column.null && old_value.nil? new_value = nil if new_value == '' end - end - column_for_attribute(attr).changed?(old_value, new_value, raw_value) + column.changed?(old_value, new_value, raw_value) + else + new_value != old_value + end end def non_zero?(value) From e5d0891ac5d2df11efa901d1196cc0e43c249315 Mon Sep 17 00:00:00 2001 From: Stefan Rusterholz Date: Fri, 6 Feb 2015 15:18:01 +0100 Subject: [PATCH 038/114] Squelch warning "#column_for_attribute` will return a null object for non-existent columns in Rails 5. Use `#has_attribute?`". --- .../connection_adapters/oracle_enhanced_dirty.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb b/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb index a5fca7b34..c2133bd49 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb @@ -9,7 +9,9 @@ def _field_changed?(attr, old_value) new_value = read_attribute(attr) raw_value = read_attribute_before_type_cast(attr) - if column = column_for_attribute(attr) + if self.class.columns_hash.include?(attr.to_s) + column = column_for_attribute(attr) + # Oracle stores empty string '' as NULL # therefore need to convert empty string value to nil if old value is nil if column.type == :string && column.null && old_value.nil? From 07bdc881230e06f48bb38c01d314c5f4924cd98e Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 3 Mar 2015 13:28:23 +0000 Subject: [PATCH 039/114] Remove duplicate alias_method_chain for indexes --- .../connection_adapters/oracle_enhanced_schema_dumper.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb index 820dd5ee6..a2efd3a8e 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb @@ -7,7 +7,6 @@ def self.included(base) #:nodoc: private alias_method_chain :tables, :oracle_enhanced alias_method_chain :indexes, :oracle_enhanced - alias_method_chain :indexes, :oracle_enhanced alias_method_chain :foreign_keys, :oracle_enhanced end end From 696e4cd4da28fae590e81c79586e6426da2078b8 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 19 Mar 2015 12:38:34 +0000 Subject: [PATCH 040/114] Use arel 6-0-stable --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 7f75ea331..f56f8ffe3 100644 --- a/Gemfile +++ b/Gemfile @@ -11,7 +11,7 @@ group :development do gem 'actionpack', github: 'rails/rails' gem 'railties', github: 'rails/rails' - gem 'arel', github: 'rails/arel' + gem 'arel', github: 'rails/arel', branch: '6-0-stable' gem 'journey', github: 'rails/journey' gem 'activerecord-deprecated_finders' From 83ffd3e5060bffd71a57db3076e9ea1c0eaaea7d Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sun, 19 Apr 2015 23:59:44 +0000 Subject: [PATCH 041/114] Support 'Y' as true and 'N' as false in Rails 4.2 --- .../connection_adapters/oracle_enhanced_column.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column.rb b/lib/active_record/connection_adapters/oracle_enhanced_column.rb index f3474f577..901f0065c 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_column.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_column.rb @@ -4,6 +4,9 @@ class OracleEnhancedColumn < Column attr_reader :table_name, :forced_column_type, :nchar, :virtual_column_data_default, :returning_id #:nodoc: + FALSE_VALUES << 'N' + TRUE_VALUES << 'Y' + def initialize(name, default, cast_type, sql_type = nil, null = true, table_name = nil, forced_column_type = nil, virtual=false, returning_id=false) #:nodoc: @table_name = table_name @forced_column_type = forced_column_type From e593f83b80fe95bc4c118dbc465614922c7ab158 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 10 Feb 2015 13:28:18 +0000 Subject: [PATCH 042/114] Support :timestamp datatype in Rails 4.2 --- activerecord-oracle_enhanced-adapter.gemspec | 1 + .../connection_adapters/oracle_enhanced_adapter.rb | 5 +++++ .../oracle_enhanced_schema_statements.rb | 5 +++++ lib/active_record/type/timestamp.rb | 9 +++++++++ 4 files changed, 20 insertions(+) create mode 100644 lib/active_record/type/timestamp.rb diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index de7093a2d..1a3ac6e66 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -47,6 +47,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. "lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb", "lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb", "lib/active_record/connection_adapters/oracle_enhanced_version.rb", + "lib/active_record/type/timestamp.rb", "lib/active_record/type/raw.rb", "lib/activerecord-oracle_enhanced-adapter.rb", "spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb", diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index c5a26086a..3f050221c 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1227,6 +1227,8 @@ def initialize_type_map(m) # oracle register_class_with_limit m, %r(date)i, Type::DateTime register_class_with_limit m, %r(raw)i, Type::Raw + register_class_with_limit m, %r(timestamp)i, Type::Timestamp + m.register_type(%r(NUMBER)i) do |sql_type| scale = extract_scale(sql_type) precision = extract_precision(sql_type) @@ -1363,3 +1365,6 @@ module ActiveRecord # Add Type:Raw require 'active_record/type/raw' + +# Add Type:Timestamp +require 'active_record/type/timestamp' diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb index 1086e3615..5e98e2aa3 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb @@ -272,6 +272,7 @@ def add_column(table_name, column_name, type, options = {}) #:nodoc: if type.to_sym == :virtual type = options[:type] end + type = aliased_types(type.to_s, type) add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} " add_column_sql << type_to_sql(type, options[:limit], options[:precision], options[:scale]) if type @@ -286,6 +287,10 @@ def add_column(table_name, column_name, type, options = {}) #:nodoc: clear_table_columns_cache(table_name) end + def aliased_types(name, fallback) + fallback + end + def change_column_default(table_name, column_name, default) #:nodoc: execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}" ensure diff --git a/lib/active_record/type/timestamp.rb b/lib/active_record/type/timestamp.rb new file mode 100644 index 000000000..7e5405da1 --- /dev/null +++ b/lib/active_record/type/timestamp.rb @@ -0,0 +1,9 @@ +module ActiveRecord + module Type + class Timestamp < Value # :nodoc: + def type + :timestamp + end + end + end +end From c1722f0001a894d2f9899038e999ab41acebbe3b Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 22 Apr 2015 18:27:12 +0000 Subject: [PATCH 043/114] Override aliased_types --- .../oracle_enhanced_schema_definitions.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb index 489d2964c..b5b0711ef 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb @@ -1,5 +1,14 @@ module ActiveRecord module ConnectionAdapters + #TODO: Overriding `aliased_types` cause another database adapter behavior changes + #It should be addressed by supporting `create_table_definition` + class TableDefinition + private + def aliased_types(name, fallback) + fallback + end + end + class OracleEnhancedForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc: end From ed0730652f323a89f28ed0491d10b4a211c6b767 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 22 Apr 2015 21:09:49 +0000 Subject: [PATCH 044/114] Revert "Implement possibility of handling of NUMBER columns as :float" This reverts commit f275bc74855c93ce1e0f686aa2dc9a9f4da02efb. Conflicts: lib/active_record/connection_adapters/oracle_enhanced_adapter.rb lib/active_record/connection_adapters/oracle_enhanced_column.rb lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb lib/active_record/connection_adapters/oracle_enhanced_dirty.rb spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb --- .../oracle_enhanced_adapter.rb | 23 +-- .../oracle_enhanced_column.rb | 21 +- .../oracle_enhanced_column_dumper.rb | 10 +- .../oracle_enhanced_dirty.rb | 5 +- .../oracle_enhanced_adapter_spec.rb | 25 --- .../oracle_enhanced_data_types_spec.rb | 68 ++----- .../oracle_enhanced_dirty_spec.rb | 15 +- .../oracle_enhanced_schema_dump_spec.rb | 190 +----------------- 8 files changed, 34 insertions(+), 323 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 3f050221c..6079c0318 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -270,13 +270,6 @@ class OracleEnhancedAdapter < AbstractAdapter cattr_accessor :emulate_dates_by_column_name self.emulate_dates_by_column_name = false - ## - # :singleton-method: - # Specify how `NUMBER` datatype columns, without precision and scale, are handled in Rails world. - # Default is :decimal and other valid option is :float. Be wary of setting it to other values. - cattr_accessor :number_datatype_coercion - self.number_datatype_coercion = :decimal - # Check column name to identify if it is Date (and not Time) column. # Is used if +emulate_dates_by_column_name+ option is set to +true+. # Override this method definition in initializer file if different Date column recognition is needed. @@ -311,7 +304,7 @@ def is_date_column?(name, table_name = nil) #:nodoc: # Is used if +emulate_integers_by_column_name+ option is set to +true+. # Override this method definition in initializer file if different Integer column recognition is needed. def self.is_integer_column?(name, table_name = nil) - !!(name =~ /(^|_)id$/i) + name =~ /(^|_)id$/i end ## @@ -424,8 +417,6 @@ def supports_views? true end - NUMBER_MAX_PRECISION = 38 - #:stopdoc: DEFAULT_NLS_PARAMETERS = { :nls_calendar => nil, @@ -449,10 +440,10 @@ def supports_views? #:stopdoc: NATIVE_DATABASE_TYPES = { - :primary_key => "NUMBER(#{NUMBER_MAX_PRECISION}) NOT NULL PRIMARY KEY", + :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY", :string => { :name => "VARCHAR2", :limit => 255 }, :text => { :name => "CLOB" }, - :integer => { :name => "NUMBER", :limit => NUMBER_MAX_PRECISION }, + :integer => { :name => "NUMBER", :limit => 38 }, :float => { :name => "NUMBER" }, :decimal => { :name => "DECIMAL" }, :datetime => { :name => "DATE" }, @@ -1039,7 +1030,7 @@ def columns_without_cache(table_name, name = nil) #:nodoc: end.map do |row| limit, scale = row['limit'], row['scale'] if limit || scale - row['sql_type'] += "(#{(limit || NUMBER_MAX_PRECISION).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")") + row['sql_type'] += "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")") end if row['sql_type_owner'] @@ -1235,11 +1226,7 @@ def initialize_type_map(m) if scale == 0 Type::Integer.new(precision: precision) else - if OracleEnhancedAdapter.number_datatype_coercion == :decimal - Type::Decimal.new(precision: precision, scale: scale) - elsif OracleEnhancedAdapter.number_datatype_coercion == :float - Type::Float.new(precision: precision, scale: scale) - end + Type::Decimal.new(precision: precision, scale: scale) end end m.alias_type %r(NUMBER\(1\))i, 'boolean' if OracleEnhancedAdapter.emulate_booleans diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column.rb b/lib/active_record/connection_adapters/oracle_enhanced_column.rb index 901f0065c..fd844813b 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_column.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_column.rb @@ -41,24 +41,9 @@ def initialize(name, default, cast_type, sql_type = nil, null = true, table_name end def type_cast(value) #:nodoc: - case type - when :raw - OracleEnhancedColumn.string_to_raw(value) - when :datetime - OracleEnhancedAdapter.emulate_dates ? guess_date_or_time(value) : super - when :float - !value.nil? ? self.class.value_to_decimal(value) : super - else - super - end - end - - def type_cast_code(var_name) - type == :float ? "#{self.class.name}.value_to_decimal(#{var_name})" : super - end - - def klass - type == :float ? BigDecimal : super + return OracleEnhancedColumn::string_to_raw(value) if type == :raw + return guess_date_or_time(value) if type == :datetime && OracleEnhancedAdapter.emulate_dates + super end def virtual? diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb b/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb index 99aaaa060..22dd7e0c0 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb @@ -39,9 +39,9 @@ def prepare_column_options_with_oracle_enhanced(column, types) return prepare_column_options_without_oracle_enhanced(column, types) unless oracle_enhanced_adapter? spec = {} - spec[:name] = column.name.inspect spec[:type] = column.virtual? ? 'virtual' : column.type.to_s + spec[:virtual_type] = column.type.inspect if column.virtual? && column.sql_type != 'NUMBER' spec[:limit] = column.limit.inspect if column.limit != types[column.type][:limit] && column.type != :decimal spec[:precision] = column.precision.inspect if !column.precision.nil? spec[:scale] = column.scale.inspect if !column.scale.nil? @@ -49,14 +49,6 @@ def prepare_column_options_with_oracle_enhanced(column, types) spec[:as] = column.virtual_column_data_default if column.virtual? spec[:default] = schema_default(column) if column.has_default? && !column.virtual? spec.delete(:default) if spec[:default].nil? - - if column.virtual? - # Supports backwards compatibility with older OracleEnhancedAdapter versions where 'NUMBER' virtual column type is not included in dump - if column.sql_type != "NUMBER" || ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.number_datatype_coercion != :decimal - spec[:virtual_type] = column.type.inspect - end - end - spec end diff --git a/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb b/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb index c2133bd49..038b9ed1d 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb @@ -17,7 +17,6 @@ def _field_changed?(attr, old_value) if column.type == :string && column.null && old_value.nil? new_value = nil if new_value == '' end - column.changed?(old_value, new_value, raw_value) else new_value != old_value @@ -26,8 +25,8 @@ def _field_changed?(attr, old_value) def non_zero?(value) value !~ /\A0+(\.0+)?\z/ - end - + end + end end diff --git a/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb index ccab9f5a5..9643c3786 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb @@ -689,29 +689,4 @@ class ::TestPost < ActiveRecord::Base explain.should include("INDEX UNIQUE SCAN") end end if ENV['RAILS_GEM_VERSION'] >= '3.2' - - describe ".is_integer_column?" do - before(:all) do - @adapter = ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter - end - - it "should return TrueClass or FalseClass" do - @adapter.is_integer_column?("adapter_id").should be_a TrueClass - @adapter.is_integer_column?("").should be_a FalseClass - end - - it "should return true if name is 'id'" do - @adapter.is_integer_column?("id").should be_true - end - - it "should return true if name ends with '_id'" do - @adapter.is_integer_column?("_id").should be_true - @adapter.is_integer_column?("foo_id").should be_true - end - - it "should return false if name is 'something_else'" do - @adapter.is_integer_column?("something_else").should be_false - end - end - end diff --git a/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb index 987e552d8..0bd336cab 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb @@ -206,7 +206,6 @@ class ::TestEmployee < ActiveRecord::Base job_id NUMBER, salary NUMBER, commission_pct NUMBER(2,2), - unwise_name_id NUMBER(2,2), manager_id NUMBER(6), is_manager NUMBER(1), department_id NUMBER(4,0), @@ -219,46 +218,17 @@ class ::TestEmployee < ActiveRecord::Base INCREMENT BY 1 START WITH 10040 CACHE 20 NOORDER NOCYCLE SQL end - + after(:all) do @conn.execute "DROP TABLE test2_employees" @conn.execute "DROP SEQUENCE test2_employees_seq" end - context "when number_datatype_coercion is :decimal" do - before { ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.stub(:number_datatype_coercion).and_return(:decimal) } - - it "should set NUMBER column type as decimal if emulate_integers_by_column_name is false" do - ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = false - columns = @conn.columns('test2_employees') - column = columns.detect{|c| c.name == "job_id"} - column.type.should == :decimal - end - - it "should set NUMBER column type as decimal if column name is not 'id' and does not ends with '_id' and emulate_integers_by_column_name is true" do - ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true - columns = @conn.columns('test2_employees') - column = columns.detect{|c| c.name == "salary"} - column.type.should == :decimal - end - end - - context "when number_datatype_coercion is :float" do - before { ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.stub(:number_datatype_coercion).and_return(:float) } - - it "should set NUMBER column type as float if emulate_integers_by_column_name is false" do - ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = false - columns = @conn.columns('test2_employees') - column = columns.detect{|c| c.name == "job_id"} - column.type.should == :float - end - - it "should set NUMBER column type as float if column name is not 'id' and does not ends with '_id' and emulate_integers_by_column_name is true" do - ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true - columns = @conn.columns('test2_employees') - column = columns.detect{|c| c.name == "salary"} - column.type.should == :float - end + it "should set NUMBER column type as decimal if emulate_integers_by_column_name is false" do + ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = false + columns = @conn.columns('test2_employees') + column = columns.detect{|c| c.name == "job_id"} + column.type.should == :decimal end it "should set NUMBER column type as integer if emulate_integers_by_column_name is true" do @@ -270,24 +240,10 @@ class ::TestEmployee < ActiveRecord::Base column.type.should == :integer end - it "should set NUMBER(p,0) column type as integer" do - ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true - columns = @conn.columns('test2_employees') - column = columns.detect{|c| c.name == "department_id"} - column.type.should == :integer - end - - it "should set NUMBER(p,s) column type as integer if column name ends with '_id' and emulate_integers_by_column_name is true" do + it "should set NUMBER column type as decimal if column name does not contain 'id' and emulate_integers_by_column_name is true" do ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true columns = @conn.columns('test2_employees') - column = columns.detect{|c| c.name == "unwise_name_id"} - column.type.should == :integer - end - - it "should set NUMBER(p,s) column type as decimal if column name ends with '_id' and emulate_integers_by_column_name is false" do - ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = false - columns = @conn.columns('test2_employees') - column = columns.detect{|c| c.name == "unwise_name_id"} + column = columns.detect{|c| c.name == "salary"} column.type.should == :decimal end @@ -298,7 +254,7 @@ class ::TestEmployee < ActiveRecord::Base column.type_cast_from_database(1.0).class.should == BigDecimal end - it "should return Fixnum value from NUMBER column if column name ends with '_id' and emulate_integers_by_column_name is true" do + it "should return Fixnum value from NUMBER column if column name contains 'id' and emulate_integers_by_column_name is true" do ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true columns = @conn.columns('test2_employees') column = columns.detect{|c| c.name == "job_id"} @@ -310,7 +266,7 @@ class ::TestEmployee < ActiveRecord::Base class ::Test2Employee < ActiveRecord::Base end end - + after(:each) do Object.send(:remove_const, "Test2Employee") @conn.clear_types_for_columns @@ -650,6 +606,7 @@ class ::TestEmployee < ActiveRecord::Base end + describe "OracleEnhancedAdapter date and timestamp with different NLS date formats" do before(:all) do ActiveRecord::Base.establish_connection(CONNECTION_PARAMS) @@ -906,7 +863,7 @@ class ::TestEmployee < ActiveRecord::Base @employee.reload @employee.last_login_at.should == @today.to_time end - + end describe "OracleEnhancedAdapter handling of CLOB columns" do @@ -1383,6 +1340,7 @@ class ::TestEmployee < ActiveRecord::Base end end + describe "OracleEnhancedAdapter quoting of NCHAR and NVARCHAR2 columns" do before(:all) do ActiveRecord::Base.establish_connection(CONNECTION_PARAMS) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb index 41d7ff9ed..edb152af2 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb @@ -16,7 +16,6 @@ last_name VARCHAR2(25), job_id NUMBER(6,0) NULL, salary NUMBER(8,2), - pto_per_hour NUMBER, comments CLOB, hire_date DATE ) @@ -28,7 +27,7 @@ class TestEmployee < ActiveRecord::Base end end - + after(:all) do Object.send(:remove_const, "TestEmployee") @conn.execute "DROP TABLE test_employees" @@ -63,16 +62,6 @@ class TestEmployee < ActiveRecord::Base @employee.should_not be_changed end - it "should not mark empty float (stored as NULL) as changed when reassigning it" do - ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.stub(:number_datatype_coercion) { :float } - @employee = TestEmployee.create!(:pto_per_hour => '') - @employee.pto_per_hour = '' - @employee.should_not be_changed - @employee.reload - @employee.pto_per_hour = '' - @employee.should_not be_changed - end - it "should not mark empty text (stored as NULL) as changed when reassigning it" do @employee = TestEmployee.create!(:comments => nil) @employee.comments = nil @@ -122,7 +111,7 @@ class TestEmployee < ActiveRecord::Base @employee = TestEmployee.new @employee.job_id = 0 @employee.save!.should be_true - + @employee.should_not be_changed @employee.job_id = '0' diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb index edfb3a339..1b47f25b8 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb @@ -376,7 +376,7 @@ def drop_test_posts_table t.virtual :full_name, :as => "first_name || ', ' || last_name" t.virtual :short_name, :as => "COALESCE(first_name, last_name)", :type => :string, :limit => 300 t.virtual :abbrev_name, :as => "SUBSTR(first_name,1,50) || ' ' || SUBSTR(last_name,1,1) || '.'", :type => "VARCHAR(100)" - t.virtual :name_ratio, :as=>'(LENGTH(first_name)/LENGTH(last_name))' + t.virtual :name_ratio, :as=>'(LENGTH(first_name)*10/LENGTH(last_name)*10)' t.column :full_name_length, :virtual, :as => "length(first_name || ', ' || last_name)", :type => :integer t.virtual :field_with_leading_space, :as => "' ' || first_name || ' '", :limit => 300, :type => :string end @@ -402,30 +402,13 @@ class ::TestName < ActiveRecord::Base end end - context "when number_datatype_coercion is :float" do - before { ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.stub(:number_datatype_coercion).and_return(:float) } - - it 'should dump correctly' do - standard_dump.should =~ /t\.virtual "full_name",(\s*)limit: 512,(\s*)as: "\\"FIRST_NAME\\"\|\|', '\|\|\\"LAST_NAME\\"",(\s*)type: :string/ - standard_dump.should =~ /t\.virtual "short_name",(\s*)limit: 300,(\s*)as:(.*),(\s*)type: :string/ - standard_dump.should =~ /t\.virtual "full_name_length",(\s*)precision: 38,(\s*)scale: 0,(\s*)as:(.*),(\s*)type: :integer/ - standard_dump.should =~ /t\.virtual "name_ratio",(\s*)as:(.*),(\s*)type: :float$/ - standard_dump.should =~ /t\.virtual "abbrev_name",(\s*)limit: 100,(\s*)as:(.*),(\s*)type: :string/ - standard_dump.should =~ /t\.virtual "field_with_leading_space",(\s*)limit: 300,(\s*)as: "' '\|\|\\"FIRST_NAME\\"\|\|' '",(\s*)type: :string/ - end - end - - context "when number_datatype_coercion is :decimal" do - before { ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.stub(:number_datatype_coercion).and_return(:decimal) } - - it 'should dump correctly' do - standard_dump.should =~ /t\.virtual "full_name",(\s*)limit: 512,(\s*)as: "\\"FIRST_NAME\\"\|\|', '\|\|\\"LAST_NAME\\"",(\s*)type: :string/ - standard_dump.should =~ /t\.virtual "short_name",(\s*)limit: 300,(\s*)as:(.*),(\s*)type: :string/ - standard_dump.should =~ /t\.virtual "full_name_length",(\s*)precision: 38,(\s*)scale: 0,(\s*)as:(.*),(\s*)type: :integer/ - standard_dump.should =~ /t\.virtual "name_ratio",(\s*)as:(.*)\"$/ - standard_dump.should =~ /t\.virtual "abbrev_name",(\s*)limit: 100,(\s*)as:(.*),(\s*)type: :string/ - standard_dump.should =~ /t\.virtual "field_with_leading_space",(\s*)limit: 300,(\s*)as: "' '\|\|\\"FIRST_NAME\\"\|\|' '",(\s*)type: :string/ - end + it 'should dump correctly' do + standard_dump.should =~ /t\.virtual "full_name",(\s*)limit: 512,(\s*)as: "\\"FIRST_NAME\\"\|\|', '\|\|\\"LAST_NAME\\"",(\s*)type: :string/ + standard_dump.should =~ /t\.virtual "short_name",(\s*)limit: 300,(\s*)as:(.*),(\s*)type: :string/ + standard_dump.should =~ /t\.virtual "full_name_length",(\s*)precision: 38,(\s*)scale: 0,(\s*)as:(.*),(\s*)type: :integer/ + standard_dump.should =~ /t\.virtual "name_ratio",(\s*)as:(.*)\"$/ # no :type + standard_dump.should =~ /t\.virtual "abbrev_name",(\s*)limit: 100,(\s*)as:(.*),(\s*)type: :string/ + standard_dump.should =~ /t\.virtual "field_with_leading_space",(\s*)limit: 300,(\s*)as: "' '\|\|\\"FIRST_NAME\\"\|\|' '",(\s*)type: :string/ end context 'with column cache' do @@ -470,161 +453,4 @@ class ::TestName < ActiveRecord::Base end end end - - describe "NUMBER columns" do - after(:each) do - schema_define do - drop_table "test_numbers" - end - end - - let(:value_within_max_precision) { (10 ** @conn.class::NUMBER_MAX_PRECISION) - 1 } - let(:value_exceeding_max_precision) { (10 ** @conn.class::NUMBER_MAX_PRECISION) + 1 } - - context "when using ActiveRecord::Schema.define and ActiveRecord::ConnectionAdapters::TableDefinition#float" do - before :each do - schema_define do - create_table :test_numbers, :force => true do |t| - t.float :value - end - end - end - - context "when number_datatype_coercion is :float" do - before { ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.stub(:number_datatype_coercion).and_return(:float) } - - it "should dump correctly" do - standard_dump.should =~ /t\.float "value"$/ - end - end - - context "when number_datatype_coercion is :decimal" do - before { ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.stub(:number_datatype_coercion).and_return(:decimal) } - - it "should dump correctly" do - standard_dump.should =~ /t\.decimal "value"$/ - end - end - end - - context "when using handwritten 'CREATE_TABLE' SQL" do - before :each do - ActiveRecord::Base.establish_connection(CONNECTION_PARAMS) - @conn = ActiveRecord::Base.connection - @conn.execute <<-SQL - CREATE TABLE test_numbers ( - id NUMBER(#{@conn.class::NUMBER_MAX_PRECISION},0) PRIMARY KEY, - value NUMBER - ) - SQL - @conn.execute <<-SQL - CREATE SEQUENCE test_numbers_seq MINVALUE 1 - INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE - SQL - end - - context "when number_datatype_coercion is :float" do - before { ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.stub(:number_datatype_coercion).and_return(:float) } - - it "should dump correctly" do - standard_dump.should =~ /t\.float "value"$/ - end - - describe "ActiveRecord saving" do - before :each do - class ::TestNumber < ActiveRecord::Base - self.table_name = "test_numbers" - end - end - - it "should allow saving of values within NUMBER_MAX_PRECISION" do - number = TestNumber.new(value: value_within_max_precision) - number.save! - number.reload - number.value.should eq(value_within_max_precision) - end - - it "should allow saving of values larger than NUMBER_MAX_PRECISION" do - number = TestNumber.new(value: value_exceeding_max_precision) - number.save! - number.reload - number.value.should eq(value_exceeding_max_precision) - end - - it "should be recreatable from dump and have same properties" do - # Simulating db:schema:dump & db:test:load - 2.times do - create_table_dump = standard_dump[/(create_table.+?end)/m] - - schema_define do - drop_table "test_numbers" - end - - schema_define(&eval("-> * { #{create_table_dump} }")) - end - - number = TestNumber.new(value: value_within_max_precision) - number.save! - - number2 = TestNumber.new(value: value_exceeding_max_precision) - number2.save! - end - end - end - - context "when number_datatype_coercion is :decimal" do - before { ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.stub(:number_datatype_coercion).and_return(:decimal) } - - it "should dump correctly" do - standard_dump.should =~ /t\.decimal "value"$/ - end - - describe "ActiveRecord saving" do - before :each do - class ::TestNumber < ActiveRecord::Base - self.table_name = "test_numbers" - end - end - - it "should allow saving of values within NUMBER_MAX_PRECISION" do - number = TestNumber.new(value: value_within_max_precision) - number.save! - number.reload - number.value.should eq(value_within_max_precision) - end - - it "should allow saving of values larger than NUMBER_MAX_PRECISION" do - number = TestNumber.new(value: value_exceeding_max_precision) - number.save! - number.reload - number.value.should eq(value_exceeding_max_precision) - end - - it "should be recreatable from dump and have same properties" do - # Simulating db:schema:dump & db:test:load - 2.times do |i| - create_table_dump = standard_dump[/(create_table.+?end)/m] - - schema_define do - drop_table "test_numbers" - end - - schema_define(&eval("-> * { #{create_table_dump} }")) - end - - number = TestNumber.new(value: value_within_max_precision) - number.save! - - # Raises 'ORA-01438' as :value column type isn't FLOAT'ish - number2 = TestNumber.new(value: value_exceeding_max_precision) - lambda do - number2.save! - end.should raise_error() { |e| e.message.should =~ /ORA-01438/ } - end - end - end # context (:decimal) - - end # context (handwritten) - end # describe (NUMBER columns) - end From 0a1cbf7ad415386e4c5ecc81db608deb3b707721 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 23 Apr 2015 10:25:16 +0000 Subject: [PATCH 045/114] Raise a better exception for renaming long indexes Also use same method argument names as Rails --- .../oracle_enhanced_schema_statements.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb index 5e98e2aa3..964d28e21 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb @@ -259,11 +259,14 @@ def index_name_exists?(table_name, index_name, default) result == 1 end - def rename_index(table_name, index_name, new_index_name) #:nodoc: - unless index_name_exists?(table_name, index_name, true) - raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" + def rename_index(table_name, old_name, new_name) #:nodoc: + unless index_name_exists?(table_name, old_name, true) + raise ArgumentError, "Index name '#{old_name}' on table '#{table_name}' does not exist" + end + if new_name.length > allowed_index_name_length + raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" end - execute "ALTER INDEX #{quote_column_name(index_name)} rename to #{quote_column_name(new_index_name)}" + execute "ALTER INDEX #{quote_column_name(old_name)} rename to #{quote_column_name(new_name)}" ensure self.all_schema_indexes = nil end From 2234d9f52c6614224eb7ea78b1c9f087ac8fd9a2 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 2 Feb 2015 13:50:47 +0000 Subject: [PATCH 046/114] Address RangeError: 6000000000 is out of range for ActiveRecord::Type::Integer with limit 4 --- .../connection_adapters/oracle_enhanced_adapter.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 6079c0318..5ccb041bb 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1224,7 +1224,12 @@ def initialize_type_map(m) scale = extract_scale(sql_type) precision = extract_precision(sql_type) if scale == 0 - Type::Integer.new(precision: precision) + limit = case + when precision <= 9 then 4 + when precision <= 19 then 8 + else 16 + end + Type::Integer.new(precision: precision, limit: limit) else Type::Decimal.new(precision: precision, scale: scale) end From 02d1fe837b0a6ca5be7d668c0f5cb00884655d39 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sat, 25 Apr 2015 14:17:05 +0000 Subject: [PATCH 047/114] Support :cascade option for drop_table --- .../oracle_enhanced_schema_statements.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb index 964d28e21..8a5e85528 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb @@ -113,14 +113,14 @@ def rename_table(table_name, new_name) #:nodoc: rename_table_indexes(table_name, new_name) end - def drop_table(name, options = {}) #:nodoc: - super(name) - seq_name = options[:sequence_name] || default_sequence_name(name) + def drop_table(table_name, options = {}) #:nodoc: + execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE CONSTRAINTS' if options[:force] == :cascade}" + seq_name = options[:sequence_name] || default_sequence_name(table_name) execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil rescue ActiveRecord::StatementInvalid => e raise e unless options[:if_exists] ensure - clear_table_columns_cache(name) + clear_table_columns_cache(table_name) self.all_schema_indexes = nil end From 9bd188eecd93541c67b4e42aeeee9250a4d2c915 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sat, 25 Apr 2015 15:01:28 +0000 Subject: [PATCH 048/114] Support :bigint datatype refer https://github.com/rails/rails/commit/574234be --- .../connection_adapters/oracle_enhanced_adapter.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 5ccb041bb..a9e72352b 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -454,7 +454,8 @@ def supports_views? :date => { :name => "DATE" }, :binary => { :name => "BLOB" }, :boolean => { :name => "NUMBER", :limit => 1 }, - :raw => { :name => "RAW", :limit => 2000 } + :raw => { :name => "RAW", :limit => 2000 }, + :bigint => { :name => "NUMBER", :limit => 8 } } # if emulate_booleans_from_strings then store booleans in VARCHAR2 NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge( From 53808975b0cd6e493dde759c227226b62ba8928a Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sat, 25 Apr 2015 22:37:46 +0000 Subject: [PATCH 049/114] Make OracleEnhancedForeignKeyDefinition as subclass of ForeignKeyDefinition and move foreign_key related methods from OracleEnhancedSchemaStatementsExt to OracleEnhancedSchemaStatements --- .../oracle_enhanced_adapter.rb | 4 + .../oracle_enhanced_schema_definitions.rb | 2 +- .../oracle_enhanced_schema_statements.rb | 190 +++++++++++++++++ .../oracle_enhanced_schema_statements_ext.rb | 194 ------------------ 4 files changed, 195 insertions(+), 195 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index a9e72352b..b599d65f9 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -413,6 +413,10 @@ def supports_transaction_isolation? #:nodoc: true end + def supports_foreign_keys? + true + end + def supports_views? true end diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb index b5b0711ef..d86ed2807 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb @@ -9,7 +9,7 @@ def aliased_types(name, fallback) end end - class OracleEnhancedForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc: + class OracleEnhancedForeignKeyDefinition < ForeignKeyDefinition end class OracleEnhancedSynonymDefinition < Struct.new(:name, :table_owner, :table_name, :db_link) #:nodoc: diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb index 8a5e85528..3063db3f2 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb @@ -393,6 +393,196 @@ def tablespace(table_name) SQL end + # Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+ + # (syntax and partial implementation taken from http://github.com/matthuhiggins/foreigner) + # + # The foreign key will be named after the from and to tables unless you pass + # :name as an option. + # + # === Examples + # ==== Creating a foreign key + # add_foreign_key(:comments, :posts) + # generates + # ALTER TABLE comments ADD CONSTRAINT + # comments_post_id_fk FOREIGN KEY (post_id) REFERENCES posts (id) + # + # ==== Creating a named foreign key + # add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts') + # generates + # ALTER TABLE comments ADD CONSTRAINT + # comments_belongs_to_posts FOREIGN KEY (post_id) REFERENCES posts (id) + # + # ==== Creating a cascading foreign_key on a custom column + # add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify) + # generates + # ALTER TABLE people ADD CONSTRAINT + # people_best_friend_id_fk FOREIGN KEY (best_friend_id) REFERENCES people (id) + # ON DELETE SET NULL + # + # ==== Creating a composite foreign key + # add_foreign_key(:comments, :posts, :columns => ['post_id', 'author_id'], :name => 'comments_post_fk') + # generates + # ALTER TABLE comments ADD CONSTRAINT + # comments_post_fk FOREIGN KEY (post_id, author_id) REFERENCES posts (post_id, author_id) + # + # === Supported options + # [:column] + # Specify the column name on the from_table that references the to_table. By default this is guessed + # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id" + # as the default :column. + # [:columns] + # An array of column names when defining composite foreign keys. An alias of :column provided for improved readability. + # [:primary_key] + # Specify the column name on the to_table that is referenced by this foreign key. By default this is + # assumed to be "id". Ignored when defining composite foreign keys. + # [:name] + # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column. + # [:dependent] + # If set to :delete, the associated records in from_table are deleted when records in to_table table are deleted. + # If set to :nullify, the foreign key column is set to +NULL+. + def add_foreign_key(from_table, to_table, options = {}) + columns = options[:column] || options[:columns] || "#{to_table.to_s.singularize}_id" + constraint_name = foreign_key_constraint_name(from_table, columns, options) + sql = "ALTER TABLE #{quote_table_name(from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} " + sql << foreign_key_definition(to_table, options) + execute sql + end + + def foreign_key_definition(to_table, options = {}) #:nodoc: + columns = Array(options[:column] || options[:columns]) + + if columns.size > 1 + # composite foreign key + columns_sql = columns.map {|c| quote_column_name(c)}.join(',') + references = options[:references] || columns + references_sql = references.map {|c| quote_column_name(c)}.join(',') + else + columns_sql = quote_column_name(columns.first || "#{to_table.to_s.singularize}_id") + references = options[:references] ? options[:references].first : nil + references_sql = quote_column_name(options[:primary_key] || references || "id") + end + + table_name = to_table + # TODO: Needs support `table_name_prefix` and `table_name_suffix` + + sql = "FOREIGN KEY (#{columns_sql}) REFERENCES #{quote_table_name(table_name)}(#{references_sql})" + + case options[:dependent] + when :nullify + sql << " ON DELETE SET NULL" + when :delete + sql << " ON DELETE CASCADE" + end + sql + end + + # Remove the given foreign key from the table. + # + # ===== Examples + # ====== Remove the suppliers_company_id_fk in the suppliers table. + # remove_foreign_key :suppliers, :companies + # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table. + # remove_foreign_key :accounts, :column => :branch_id + # ====== Remove the foreign key named party_foreign_key in the accounts table. + # remove_foreign_key :accounts, :name => :party_foreign_key + def remove_foreign_key(from_table, options) + if Hash === options + constraint_name = foreign_key_constraint_name(from_table, options[:column], options) + else + constraint_name = foreign_key_constraint_name(from_table, "#{options.to_s.singularize}_id") + end + execute "ALTER TABLE #{quote_table_name(from_table)} DROP CONSTRAINT #{quote_column_name(constraint_name)}" + end + + private + + def foreign_key_constraint_name(table_name, columns, options = {}) + columns = Array(columns) + constraint_name = original_name = options[:name] || "#{table_name}_#{columns.join('_')}_fk" + + return constraint_name if constraint_name.length <= OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH + + # leave just first three letters from each word + constraint_name = constraint_name.split('_').map{|w| w[0,3]}.join('_') + # generate unique name using hash function + if constraint_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH + constraint_name = 'c'+Digest::SHA1.hexdigest(original_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1] + end + @logger.warn "#{adapter_name} shortened foreign key constraint name #{original_name} to #{constraint_name}" if @logger + constraint_name + end + + + public + + # get table foreign keys for schema dump + def foreign_keys(table_name) #:nodoc: + (owner, desc_table_name, db_link) = @connection.describe(table_name) + + fk_info = select_all(<<-SQL, 'Foreign Keys') + SELECT r.table_name to_table + ,rc.column_name references_column + ,cc.column_name + ,c.constraint_name name + ,c.delete_rule + FROM user_constraints#{db_link} c, user_cons_columns#{db_link} cc, + user_constraints#{db_link} r, user_cons_columns#{db_link} rc + WHERE c.owner = '#{owner}' + AND c.table_name = '#{desc_table_name}' + AND c.constraint_type = 'R' + AND cc.owner = c.owner + AND cc.constraint_name = c.constraint_name + AND r.constraint_name = c.r_constraint_name + AND r.owner = c.owner + AND rc.owner = r.owner + AND rc.constraint_name = r.constraint_name + AND rc.position = cc.position + ORDER BY name, to_table, column_name, references_column + SQL + + fks = {} + + fk_info.map do |row| + name = oracle_downcase(row['name']) + fks[name] ||= { :columns => [], :to_table => oracle_downcase(row['to_table']), :references => [] } + fks[name][:columns] << oracle_downcase(row['column_name']) + fks[name][:references] << oracle_downcase(row['references_column']) + case row['delete_rule'] + when 'CASCADE' + fks[name][:dependent] = :delete + when 'SET NULL' + fks[name][:dependent] = :nullify + end + end + + fks.map do |k, v| + options = {:name => k, :columns => v[:columns], :references => v[:references], :dependent => v[:dependent]} + OracleEnhancedForeignKeyDefinition.new(table_name, v[:to_table], options) + end + end + + # REFERENTIAL INTEGRITY ==================================== + + def disable_referential_integrity(&block) #:nodoc: + sql_constraints = <<-SQL + SELECT constraint_name, owner, table_name + FROM user_constraints + WHERE constraint_type = 'R' + AND status = 'ENABLED' + SQL + old_constraints = select_all(sql_constraints) + begin + old_constraints.each do |constraint| + execute "ALTER TABLE #{constraint["table_name"]} DISABLE CONSTRAINT #{constraint["constraint_name"]}" + end + yield + ensure + old_constraints.each do |constraint| + execute "ALTER TABLE #{constraint["table_name"]} ENABLE CONSTRAINT #{constraint["constraint_name"]}" + end + end + end + private def tablespace_for(obj_type, tablespace_option, table_name=nil, column_name=nil) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb index d8ed160d1..0bd1d34c9 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb @@ -3,10 +3,6 @@ module ActiveRecord module ConnectionAdapters module OracleEnhancedSchemaStatementsExt - def supports_foreign_keys? #:nodoc: - true - end - # Create primary key trigger (so that you can skip primary key value in INSERT statement). # By default trigger name will be "table_name_pkt", you can override the name with # :trigger_name option (but it is not recommended to override it as then this trigger will @@ -26,196 +22,6 @@ def add_primary_key_trigger(table_name, options={}) create_primary_key_trigger(table_name, options) end - # Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+ - # (syntax and partial implementation taken from http://github.com/matthuhiggins/foreigner) - # - # The foreign key will be named after the from and to tables unless you pass - # :name as an option. - # - # === Examples - # ==== Creating a foreign key - # add_foreign_key(:comments, :posts) - # generates - # ALTER TABLE comments ADD CONSTRAINT - # comments_post_id_fk FOREIGN KEY (post_id) REFERENCES posts (id) - # - # ==== Creating a named foreign key - # add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts') - # generates - # ALTER TABLE comments ADD CONSTRAINT - # comments_belongs_to_posts FOREIGN KEY (post_id) REFERENCES posts (id) - # - # ==== Creating a cascading foreign_key on a custom column - # add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify) - # generates - # ALTER TABLE people ADD CONSTRAINT - # people_best_friend_id_fk FOREIGN KEY (best_friend_id) REFERENCES people (id) - # ON DELETE SET NULL - # - # ==== Creating a composite foreign key - # add_foreign_key(:comments, :posts, :columns => ['post_id', 'author_id'], :name => 'comments_post_fk') - # generates - # ALTER TABLE comments ADD CONSTRAINT - # comments_post_fk FOREIGN KEY (post_id, author_id) REFERENCES posts (post_id, author_id) - # - # === Supported options - # [:column] - # Specify the column name on the from_table that references the to_table. By default this is guessed - # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id" - # as the default :column. - # [:columns] - # An array of column names when defining composite foreign keys. An alias of :column provided for improved readability. - # [:primary_key] - # Specify the column name on the to_table that is referenced by this foreign key. By default this is - # assumed to be "id". Ignored when defining composite foreign keys. - # [:name] - # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column. - # [:dependent] - # If set to :delete, the associated records in from_table are deleted when records in to_table table are deleted. - # If set to :nullify, the foreign key column is set to +NULL+. - def add_foreign_key(from_table, to_table, options = {}) - columns = options[:column] || options[:columns] || "#{to_table.to_s.singularize}_id" - constraint_name = foreign_key_constraint_name(from_table, columns, options) - sql = "ALTER TABLE #{quote_table_name(from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} " - sql << foreign_key_definition(to_table, options) - execute sql - end - - def foreign_key_definition(to_table, options = {}) #:nodoc: - columns = Array(options[:column] || options[:columns]) - - if columns.size > 1 - # composite foreign key - columns_sql = columns.map {|c| quote_column_name(c)}.join(',') - references = options[:references] || columns - references_sql = references.map {|c| quote_column_name(c)}.join(',') - else - columns_sql = quote_column_name(columns.first || "#{to_table.to_s.singularize}_id") - references = options[:references] ? options[:references].first : nil - references_sql = quote_column_name(options[:primary_key] || references || "id") - end - - table_name = to_table - # TODO: Needs support `table_name_prefix` and `table_name_suffix` - - sql = "FOREIGN KEY (#{columns_sql}) REFERENCES #{quote_table_name(table_name)}(#{references_sql})" - - case options[:dependent] - when :nullify - sql << " ON DELETE SET NULL" - when :delete - sql << " ON DELETE CASCADE" - end - sql - end - - # Remove the given foreign key from the table. - # - # ===== Examples - # ====== Remove the suppliers_company_id_fk in the suppliers table. - # remove_foreign_key :suppliers, :companies - # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table. - # remove_foreign_key :accounts, :column => :branch_id - # ====== Remove the foreign key named party_foreign_key in the accounts table. - # remove_foreign_key :accounts, :name => :party_foreign_key - def remove_foreign_key(from_table, options) - if Hash === options - constraint_name = foreign_key_constraint_name(from_table, options[:column], options) - else - constraint_name = foreign_key_constraint_name(from_table, "#{options.to_s.singularize}_id") - end - execute "ALTER TABLE #{quote_table_name(from_table)} DROP CONSTRAINT #{quote_column_name(constraint_name)}" - end - - private - - def foreign_key_constraint_name(table_name, columns, options = {}) - columns = Array(columns) - constraint_name = original_name = options[:name] || "#{table_name}_#{columns.join('_')}_fk" - - return constraint_name if constraint_name.length <= OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH - - # leave just first three letters from each word - constraint_name = constraint_name.split('_').map{|w| w[0,3]}.join('_') - # generate unique name using hash function - if constraint_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH - constraint_name = 'c'+Digest::SHA1.hexdigest(original_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1] - end - @logger.warn "#{adapter_name} shortened foreign key constraint name #{original_name} to #{constraint_name}" if @logger - constraint_name - end - - - public - - # get table foreign keys for schema dump - def foreign_keys(table_name) #:nodoc: - (owner, desc_table_name, db_link) = @connection.describe(table_name) - - fk_info = select_all(<<-SQL, 'Foreign Keys') - SELECT r.table_name to_table - ,rc.column_name references_column - ,cc.column_name - ,c.constraint_name name - ,c.delete_rule - FROM user_constraints#{db_link} c, user_cons_columns#{db_link} cc, - user_constraints#{db_link} r, user_cons_columns#{db_link} rc - WHERE c.owner = '#{owner}' - AND c.table_name = '#{desc_table_name}' - AND c.constraint_type = 'R' - AND cc.owner = c.owner - AND cc.constraint_name = c.constraint_name - AND r.constraint_name = c.r_constraint_name - AND r.owner = c.owner - AND rc.owner = r.owner - AND rc.constraint_name = r.constraint_name - AND rc.position = cc.position - ORDER BY name, to_table, column_name, references_column - SQL - - fks = {} - - fk_info.map do |row| - name = oracle_downcase(row['name']) - fks[name] ||= { :columns => [], :to_table => oracle_downcase(row['to_table']), :references => [] } - fks[name][:columns] << oracle_downcase(row['column_name']) - fks[name][:references] << oracle_downcase(row['references_column']) - case row['delete_rule'] - when 'CASCADE' - fks[name][:dependent] = :delete - when 'SET NULL' - fks[name][:dependent] = :nullify - end - end - - fks.map do |k, v| - options = {:name => k, :columns => v[:columns], :references => v[:references], :dependent => v[:dependent]} - OracleEnhancedForeignKeyDefinition.new(table_name, v[:to_table], options) - end - end - - # REFERENTIAL INTEGRITY ==================================== - - def disable_referential_integrity(&block) #:nodoc: - sql_constraints = <<-SQL - SELECT constraint_name, owner, table_name - FROM user_constraints - WHERE constraint_type = 'R' - AND status = 'ENABLED' - SQL - old_constraints = select_all(sql_constraints) - begin - old_constraints.each do |constraint| - execute "ALTER TABLE #{constraint["table_name"]} DISABLE CONSTRAINT #{constraint["constraint_name"]}" - end - yield - ensure - old_constraints.each do |constraint| - execute "ALTER TABLE #{constraint["table_name"]} ENABLE CONSTRAINT #{constraint["constraint_name"]}" - end - end - end - # Add synonym to existing table or view or sequence. Can be used to create local synonym to # remote table in other schema or in other database # Examples: From 09b151456f37ce60d50b582b4d136f07d3031c16 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sun, 26 Apr 2015 11:52:35 +0000 Subject: [PATCH 050/114] Remove alias_method_chain :references, :foreign_keys since Rails 4.2 supports native foreign key. References can create foreign keys --- .../oracle_enhanced_schema_definitions.rb | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb index d86ed2807..14628cc0f 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb @@ -42,7 +42,6 @@ def to_sql def self.included(base) #:nodoc: base.class_eval do - alias_method_chain :references, :foreign_keys alias_method_chain :column, :virtual_columns end end @@ -85,7 +84,7 @@ def column_with_virtual_columns(name, type, options = {}) # # Note: No foreign key is created if :polymorphic => true is used. # Note: If no name is specified, the database driver creates one for you! - def references_with_foreign_keys(*args) + def references(*args) options = args.extract_options! index_options = options[:index] fk_options = options.delete(:foreign_key) @@ -98,7 +97,7 @@ def references_with_foreign_keys(*args) end end - references_without_foreign_keys(*(args << options)) + super(*(args << options)) end # Defines a foreign key for the table. +to_table+ can be a single Symbol, or @@ -129,11 +128,6 @@ def foreign_keys end module OracleEnhancedTable - def self.included(base) #:nodoc: - base.class_eval do - alias_method_chain :references, :foreign_keys - end - end # Adds a new foreign key to the table. +to_table+ can be a single Symbol, or # an Array of Symbols. See SchemaStatements#add_foreign_key @@ -180,13 +174,13 @@ def remove_foreign_key(options = {}) # t.references(:goat, :foreign_key => {:dependent => :delete}) # # Note: No foreign key is created if :polymorphic => true is used. - def references_with_foreign_keys(*args) + def references(*args) options = args.extract_options! polymorphic = options[:polymorphic] index_options = options[:index] fk_options = options.delete(:foreign_key) - references_without_foreign_keys(*(args << options)) + super(*(args << options)) # references_without_foreign_keys adds {:type => :integer} args.extract_options! if fk_options && !polymorphic From 5c7ec86ac49be889bca080e7ad3678bf8b8d7f03 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 24 Apr 2015 14:13:57 +0000 Subject: [PATCH 051/114] Return foreign_keys_without_oracle_enhanced when non Oracle database used --- .../connection_adapters/oracle_enhanced_schema_dumper.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb b/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb index a2efd3a8e..bdd93579a 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb @@ -58,6 +58,12 @@ def primary_key_trigger(table_name, stream) end def foreign_keys_with_oracle_enhanced(table_name, stream) + # return original method if not using oracle_enhanced + if (rails_env = defined?(Rails.env) ? Rails.env : (defined?(RAILS_ENV) ? RAILS_ENV : nil)) && + ActiveRecord::Base.configurations[rails_env] && + ActiveRecord::Base.configurations[rails_env]['adapter'] != 'oracle_enhanced' + return foreign_keys_without_oracle_enhanced(table_name, stream) + end if @connection.respond_to?(:foreign_keys) && (foreign_keys = @connection.foreign_keys(table_name)).any? add_foreign_key_statements = foreign_keys.map do |foreign_key| statement_parts = [ ('add_foreign_key ' + foreign_key.from_table.inspect) ] From 1b8281d11b410de6bacdb1172e8fef72e88506ad Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 27 Apr 2015 20:24:24 +0000 Subject: [PATCH 052/114] Modify directory structure --- activerecord-oracle_enhanced-adapter.gemspec | 34 +++++++++---------- .../column.rb} | 0 .../column_dumper.rb} | 0 .../connection.rb} | 4 +-- .../context_index.rb} | 0 .../cpk.rb} | 0 .../database_statements.rb} | 0 .../database_tasks.rb} | 0 .../dirty.rb} | 0 .../jdbc_connection.rb} | 0 .../oci_connection.rb} | 0 .../procedures.rb} | 0 .../schema_creation.rb} | 0 .../schema_definitions.rb} | 0 .../schema_dumper.rb} | 0 .../schema_statements.rb} | 0 .../schema_statements_ext.rb} | 0 .../structure_dump.rb} | 0 .../oracle_enhanced/version.rb | 1 + .../oracle_enhanced_adapter.rb | 30 ++++++++-------- .../oracle_enhanced_version.rb | 1 - .../oracle_enhanced_database_tasks_spec.rb | 2 +- 22 files changed, 36 insertions(+), 36 deletions(-) rename lib/active_record/connection_adapters/{oracle_enhanced_column.rb => oracle_enhanced/column.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_column_dumper.rb => oracle_enhanced/column_dumper.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_connection.rb => oracle_enhanced/connection.rb} (97%) rename lib/active_record/connection_adapters/{oracle_enhanced_context_index.rb => oracle_enhanced/context_index.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_cpk.rb => oracle_enhanced/cpk.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_database_statements.rb => oracle_enhanced/database_statements.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_database_tasks.rb => oracle_enhanced/database_tasks.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_dirty.rb => oracle_enhanced/dirty.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_jdbc_connection.rb => oracle_enhanced/jdbc_connection.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_oci_connection.rb => oracle_enhanced/oci_connection.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_procedures.rb => oracle_enhanced/procedures.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_schema_creation.rb => oracle_enhanced/schema_creation.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_schema_definitions.rb => oracle_enhanced/schema_definitions.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_schema_dumper.rb => oracle_enhanced/schema_dumper.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_schema_statements.rb => oracle_enhanced/schema_statements.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_schema_statements_ext.rb => oracle_enhanced/schema_statements_ext.rb} (100%) rename lib/active_record/connection_adapters/{oracle_enhanced_structure_dump.rb => oracle_enhanced/structure_dump.rb} (100%) create mode 100644 lib/active_record/connection_adapters/oracle_enhanced/version.rb delete mode 100644 lib/active_record/connection_adapters/oracle_enhanced_version.rb diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index 1a3ac6e66..459a30912 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -30,23 +30,23 @@ This adapter is superset of original ActiveRecord Oracle adapter. "activerecord-oracle_enhanced-adapter.gemspec", "lib/active_record/connection_adapters/emulation/oracle_adapter.rb", "lib/active_record/connection_adapters/oracle_enhanced_adapter.rb", - "lib/active_record/connection_adapters/oracle_enhanced_column.rb", - "lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb", - "lib/active_record/connection_adapters/oracle_enhanced_connection.rb", - "lib/active_record/connection_adapters/oracle_enhanced_context_index.rb", - "lib/active_record/connection_adapters/oracle_enhanced_cpk.rb", - "lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb", - "lib/active_record/connection_adapters/oracle_enhanced_dirty.rb", - "lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb", - "lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb", - "lib/active_record/connection_adapters/oracle_enhanced_procedures.rb", - "lib/active_record/connection_adapters/oracle_enhanced_schema_creation.rb", - "lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb", - "lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb", - "lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb", - "lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb", - "lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb", - "lib/active_record/connection_adapters/oracle_enhanced_version.rb", + "lib/active_record/connection_adapters/oracle_enhanced/column.rb", + "lib/active_record/connection_adapters/oracle_enhanced/column_dumper.rb", + "lib/active_record/connection_adapters/oracle_enhanced/connection.rb", + "lib/active_record/connection_adapters/oracle_enhanced/context_index.rb", + "lib/active_record/connection_adapters/oracle_enhanced/cpk.rb", + "lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb", + "lib/active_record/connection_adapters/oracle_enhanced/dirty.rb", + "lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb", + "lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb", + "lib/active_record/connection_adapters/oracle_enhanced/procedures.rb", + "lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb", + "lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb", + "lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb", + "lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb", + "lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb", + "lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb", + "lib/active_record/connection_adapters/oracle_enhanced/version.rb", "lib/active_record/type/timestamp.rb", "lib/active_record/type/raw.rb", "lib/activerecord-oracle_enhanced-adapter.rb", diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column.rb b/lib/active_record/connection_adapters/oracle_enhanced/column.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_column.rb rename to lib/active_record/connection_adapters/oracle_enhanced/column.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb b/lib/active_record/connection_adapters/oracle_enhanced/column_dumper.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb rename to lib/active_record/connection_adapters/oracle_enhanced/column_dumper.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_connection.rb b/lib/active_record/connection_adapters/oracle_enhanced/connection.rb similarity index 97% rename from lib/active_record/connection_adapters/oracle_enhanced_connection.rb rename to lib/active_record/connection_adapters/oracle_enhanced/connection.rb index df8ef8b82..802eeca69 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/connection.rb @@ -109,11 +109,11 @@ class OracleEnhancedConnectionException < StandardError #:nodoc: # if MRI or YARV if !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby' ORACLE_ENHANCED_CONNECTION = :oci - require 'active_record/connection_adapters/oracle_enhanced_oci_connection' + require 'active_record/connection_adapters/oracle_enhanced/oci_connection' # if JRuby elsif RUBY_ENGINE == 'jruby' ORACLE_ENHANCED_CONNECTION = :jdbc - require 'active_record/connection_adapters/oracle_enhanced_jdbc_connection' + require 'active_record/connection_adapters/oracle_enhanced/jdbc_connection' else raise "Unsupported Ruby engine #{RUBY_ENGINE}" end diff --git a/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb b/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_context_index.rb rename to lib/active_record/connection_adapters/oracle_enhanced/context_index.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb b/lib/active_record/connection_adapters/oracle_enhanced/cpk.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_cpk.rb rename to lib/active_record/connection_adapters/oracle_enhanced/cpk.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb rename to lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_database_tasks.rb b/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_database_tasks.rb rename to lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb b/lib/active_record/connection_adapters/oracle_enhanced/dirty.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_dirty.rb rename to lib/active_record/connection_adapters/oracle_enhanced/dirty.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb b/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb rename to lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb b/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb rename to lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb b/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_procedures.rb rename to lib/active_record/connection_adapters/oracle_enhanced/procedures.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_creation.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_schema_creation.rb rename to lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb rename to lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb rename to lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb rename to lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb rename to lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb b/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb similarity index 100% rename from lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb rename to lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced/version.rb b/lib/active_record/connection_adapters/oracle_enhanced/version.rb new file mode 100644 index 000000000..ff87a3c7d --- /dev/null +++ b/lib/active_record/connection_adapters/oracle_enhanced/version.rb @@ -0,0 +1 @@ +ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::VERSION = File.read(File.expand_path('../../../../../VERSION', __FILE__)).chomp diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index b599d65f9..b94a7c68a 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -30,9 +30,9 @@ # portions Copyright 2005 Graham Jenkins require 'active_record/connection_adapters/abstract_adapter' -require 'active_record/connection_adapters/oracle_enhanced_connection' +require 'active_record/connection_adapters/oracle_enhanced/connection' -require 'active_record/connection_adapters/oracle_enhanced_column' +require 'active_record/connection_adapters/oracle_enhanced/column' require 'digest/sha1' @@ -1324,41 +1324,41 @@ def log_dbms_output end # Implementation of standard schema definition statements and extensions for schema definition -require 'active_record/connection_adapters/oracle_enhanced_schema_statements' -require 'active_record/connection_adapters/oracle_enhanced_schema_statements_ext' +require 'active_record/connection_adapters/oracle_enhanced/schema_statements' +require 'active_record/connection_adapters/oracle_enhanced/schema_statements_ext' # Extensions for schema definition -require 'active_record/connection_adapters/oracle_enhanced_schema_definitions' +require 'active_record/connection_adapters/oracle_enhanced/schema_definitions' # Extensions for context index definition -require 'active_record/connection_adapters/oracle_enhanced_context_index' +require 'active_record/connection_adapters/oracle_enhanced/context_index' # Load additional methods for composite_primary_keys support -require 'active_record/connection_adapters/oracle_enhanced_cpk' +require 'active_record/connection_adapters/oracle_enhanced/cpk' # Load patch for dirty tracking methods -require 'active_record/connection_adapters/oracle_enhanced_dirty' +require 'active_record/connection_adapters/oracle_enhanced/dirty' # Patches and enhancements for schema dumper -require 'active_record/connection_adapters/oracle_enhanced_schema_dumper' +require 'active_record/connection_adapters/oracle_enhanced/schema_dumper' # Implementation of structure dump -require 'active_record/connection_adapters/oracle_enhanced_structure_dump' +require 'active_record/connection_adapters/oracle_enhanced/structure_dump' -require 'active_record/connection_adapters/oracle_enhanced_version' +require 'active_record/connection_adapters/oracle_enhanced/version' module ActiveRecord - autoload :OracleEnhancedProcedures, 'active_record/connection_adapters/oracle_enhanced_procedures' + autoload :OracleEnhancedProcedures, 'active_record/connection_adapters/oracle_enhanced/procedures' end # Patches and enhancements for column dumper -require 'active_record/connection_adapters/oracle_enhanced_column_dumper' +require 'active_record/connection_adapters/oracle_enhanced/column_dumper' # Moved SchemaCreation class -require 'active_record/connection_adapters/oracle_enhanced_schema_creation' +require 'active_record/connection_adapters/oracle_enhanced/schema_creation' # Moved DatabaseStetements -require 'active_record/connection_adapters/oracle_enhanced_database_statements' +require 'active_record/connection_adapters/oracle_enhanced/database_statements' # Add Type:Raw require 'active_record/type/raw' diff --git a/lib/active_record/connection_adapters/oracle_enhanced_version.rb b/lib/active_record/connection_adapters/oracle_enhanced_version.rb deleted file mode 100644 index d02d8b98c..000000000 --- a/lib/active_record/connection_adapters/oracle_enhanced_version.rb +++ /dev/null @@ -1 +0,0 @@ -ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::VERSION = File.read(File.expand_path('../../../../VERSION', __FILE__)).chomp diff --git a/spec/active_record/connection_adapters/oracle_enhanced_database_tasks_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_database_tasks_spec.rb index 54e196e3c..6f98925f1 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_database_tasks_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_database_tasks_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' -require 'active_record/connection_adapters/oracle_enhanced_database_tasks' +require 'active_record/connection_adapters/oracle_enhanced/database_tasks' require 'stringio' require 'tempfile' From 968316efb43e184e31f4e126d66c475e137a1491 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 28 Apr 2015 15:31:29 +0000 Subject: [PATCH 053/114] Change module name from ActiveRecord::ConnectionAdapters::OracleEnhancedDatabaseStatements to ActiveRecord::ConnectionAdapters::OracleEnhanced::DatabaseStatements Also remove `class_eval` to include this module, use `include` instead --- .../oracle_enhanced/database_statements.rb | 406 +++++++++--------- .../oracle_enhanced_adapter.rb | 3 + 2 files changed, 205 insertions(+), 204 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb index 0594a4f43..991ed9c76 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb @@ -1,259 +1,257 @@ module ActiveRecord module ConnectionAdapters - module OracleEnhancedDatabaseStatements - # DATABASE STATEMENTS ====================================== - # - # see: abstract/database_statements.rb - - # Executes a SQL statement - def execute(sql, name = nil) - log(sql, name) { @connection.exec(sql) } - end + module OracleEnhanced + module DatabaseStatements + # DATABASE STATEMENTS ====================================== + # + # see: abstract/database_statements.rb + + # Executes a SQL statement + def execute(sql, name = nil) + log(sql, name) { @connection.exec(sql) } + end - def clear_cache! - @statements.clear - reload_type_map - end + def clear_cache! + @statements.clear + reload_type_map + end - def exec_query(sql, name = 'SQL', binds = []) - type_casted_binds = binds.map { |col, val| - [col, type_cast(val, col)] - } - log(sql, name, type_casted_binds) do - cursor = nil - cached = false - if without_prepared_statement?(binds) - cursor = @connection.prepare(sql) - else - unless @statements.key? sql - @statements[sql] = @connection.prepare(sql) - end + def exec_query(sql, name = 'SQL', binds = []) + type_casted_binds = binds.map { |col, val| + [col, type_cast(val, col)] + } + log(sql, name, type_casted_binds) do + cursor = nil + cached = false + if without_prepared_statement?(binds) + cursor = @connection.prepare(sql) + else + unless @statements.key? sql + @statements[sql] = @connection.prepare(sql) + end - cursor = @statements[sql] + cursor = @statements[sql] - binds.each_with_index do |bind, i| - col, val = bind - cursor.bind_param(i + 1, type_cast(val, col), col) - end + binds.each_with_index do |bind, i| + col, val = bind + cursor.bind_param(i + 1, type_cast(val, col), col) + end - cached = true - end + cached = true + end - cursor.exec + cursor.exec - if name == 'EXPLAIN' and sql =~ /^EXPLAIN/ - res = true - else - columns = cursor.get_col_names.map do |col_name| - @connection.oracle_downcase(col_name) - end - rows = [] - fetch_options = {:get_lob_value => (name != 'Writable Large Object')} - while row = cursor.fetch(fetch_options) - rows << row + if name == 'EXPLAIN' and sql =~ /^EXPLAIN/ + res = true + else + columns = cursor.get_col_names.map do |col_name| + @connection.oracle_downcase(col_name) + end + rows = [] + fetch_options = {:get_lob_value => (name != 'Writable Large Object')} + while row = cursor.fetch(fetch_options) + rows << row + end + res = ActiveRecord::Result.new(columns, rows) end - res = ActiveRecord::Result.new(columns, rows) - end - cursor.close unless cached - res + cursor.close unless cached + res + end end - end - def supports_statement_cache? - true - end - - def supports_explain? - true - end + def supports_statement_cache? + true + end - def explain(arel, binds = []) - sql = "EXPLAIN PLAN FOR #{to_sql(arel, binds)}" - return if sql =~ /FROM all_/ - if ORACLE_ENHANCED_CONNECTION == :jdbc - exec_query(sql, 'EXPLAIN', binds) - else - exec_query(sql, 'EXPLAIN') + def supports_explain? + true end - select_values("SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)", 'EXPLAIN').join("\n") - end - # Returns an array of arrays containing the field values. - # Order is the same as that returned by #columns. - def select_rows(sql, name = nil, binds = []) - exec_query(sql, name, binds).rows - end + def explain(arel, binds = []) + sql = "EXPLAIN PLAN FOR #{to_sql(arel, binds)}" + return if sql =~ /FROM all_/ + if ORACLE_ENHANCED_CONNECTION == :jdbc + exec_query(sql, 'EXPLAIN', binds) + else + exec_query(sql, 'EXPLAIN') + end + select_values("SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)", 'EXPLAIN').join("\n") + end - # Executes an INSERT statement and returns the new record's ID - def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: - # if primary key value is already prefetched from sequence - # or if there is no primary key - if id_value || pk.nil? - execute(sql, name) - return id_value + # Returns an array of arrays containing the field values. + # Order is the same as that returned by #columns. + def select_rows(sql, name = nil, binds = []) + exec_query(sql, name, binds).rows end - sql_with_returning = sql + @connection.returning_clause(quote_column_name(pk)) - log(sql, name) do - @connection.exec_with_returning(sql_with_returning) + # Executes an INSERT statement and returns the new record's ID + def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: + # if primary key value is already prefetched from sequence + # or if there is no primary key + if id_value || pk.nil? + execute(sql, name) + return id_value + end + + sql_with_returning = sql + @connection.returning_clause(quote_column_name(pk)) + log(sql, name) do + @connection.exec_with_returning(sql_with_returning) + end end - end - protected :insert_sql - - # New method in ActiveRecord 3.1 - # Will add RETURNING clause in case of trigger generated primary keys - def sql_for_insert(sql, pk, id_value, sequence_name, binds) - unless id_value || pk.nil? || (defined?(CompositePrimaryKeys) && pk.kind_of?(CompositePrimaryKeys::CompositeKeys)) - sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id" - returning_id_col = OracleEnhancedColumn.new("returning_id", nil, Type::Value.new, "number", true, "dual", :integer, true, true) - (binds = binds.dup) << [returning_id_col, nil] + protected :insert_sql + + # New method in ActiveRecord 3.1 + # Will add RETURNING clause in case of trigger generated primary keys + def sql_for_insert(sql, pk, id_value, sequence_name, binds) + unless id_value || pk.nil? || (defined?(CompositePrimaryKeys) && pk.kind_of?(CompositePrimaryKeys::CompositeKeys)) + sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id" + returning_id_col = OracleEnhancedColumn.new("returning_id", nil, Type::Value.new, "number", true, "dual", :integer, true, true) + (binds = binds.dup) << [returning_id_col, nil] + end + [sql, binds] end - [sql, binds] - end - - # New method in ActiveRecord 3.1 - def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) - type_casted_binds = binds.map { |col, val| - [col, type_cast(val, col)] - } - log(sql, name, type_casted_binds) do - returning_id_col = returning_id_index = nil - if without_prepared_statement?(binds) - cursor = @connection.prepare(sql) - else - unless @statements.key? (sql) - @statements[sql] = @connection.prepare(sql) - end - cursor = @statements[sql] + # New method in ActiveRecord 3.1 + def exec_insert(sql, name, binds, pk = nil, sequence_name = nil) + type_casted_binds = binds.map { |col, val| + [col, type_cast(val, col)] + } + log(sql, name, type_casted_binds) do + returning_id_col = returning_id_index = nil + if without_prepared_statement?(binds) + cursor = @connection.prepare(sql) + else + unless @statements.key? (sql) + @statements[sql] = @connection.prepare(sql) + end - binds.each_with_index do |bind, i| - col, val = bind - if col.returning_id? - returning_id_col = [col] - returning_id_index = i + 1 - cursor.bind_returning_param(returning_id_index, Integer) - else - cursor.bind_param(i + 1, type_cast(val, col), col) + cursor = @statements[sql] + + binds.each_with_index do |bind, i| + col, val = bind + if col.returning_id? + returning_id_col = [col] + returning_id_index = i + 1 + cursor.bind_returning_param(returning_id_index, Integer) + else + cursor.bind_param(i + 1, type_cast(val, col), col) + end end end - end - cursor.exec_update + cursor.exec_update - rows = [] - if returning_id_index - returning_id = cursor.get_returning_param(returning_id_index, Integer) - rows << [returning_id] + rows = [] + if returning_id_index + returning_id = cursor.get_returning_param(returning_id_index, Integer) + rows << [returning_id] + end + ActiveRecord::Result.new(returning_id_col || [], rows) end - ActiveRecord::Result.new(returning_id_col || [], rows) end - end - # New method in ActiveRecord 3.1 - def exec_update(sql, name, binds) - log(sql, name, binds) do - cached = false - if without_prepared_statement?(binds) - cursor = @connection.prepare(sql) - else - cursor = if @statements.key?(sql) - @statements[sql] + # New method in ActiveRecord 3.1 + def exec_update(sql, name, binds) + log(sql, name, binds) do + cached = false + if without_prepared_statement?(binds) + cursor = @connection.prepare(sql) else - @statements[sql] = @connection.prepare(sql) - end + cursor = if @statements.key?(sql) + @statements[sql] + else + @statements[sql] = @connection.prepare(sql) + end - binds.each_with_index do |bind, i| - col, val = bind - cursor.bind_param(i + 1, type_cast(val, col), col) + binds.each_with_index do |bind, i| + col, val = bind + cursor.bind_param(i + 1, type_cast(val, col), col) + end + cached = true end - cached = true - end - res = cursor.exec_update - cursor.close unless cached - res + res = cursor.exec_update + cursor.close unless cached + res + end end - end - alias :exec_delete :exec_update + alias :exec_delete :exec_update - def begin_db_transaction #:nodoc: - @connection.autocommit = false - end + def begin_db_transaction #:nodoc: + @connection.autocommit = false + end - def transaction_isolation_levels - # Oracle database supports `READ COMMITTED` and `SERIALIZABLE` - # No read uncommitted nor repeatable read supppoted - # http://docs.oracle.com/cd/E11882_01/server.112/e26088/statements_10005.htm#SQLRF55422 - { - read_committed: "READ COMMITTED", - serializable: "SERIALIZABLE" - } - end + def transaction_isolation_levels + # Oracle database supports `READ COMMITTED` and `SERIALIZABLE` + # No read uncommitted nor repeatable read supppoted + # http://docs.oracle.com/cd/E11882_01/server.112/e26088/statements_10005.htm#SQLRF55422 + { + read_committed: "READ COMMITTED", + serializable: "SERIALIZABLE" + } + end - def begin_isolated_db_transaction(isolation) - begin_db_transaction - execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}" - end + def begin_isolated_db_transaction(isolation) + begin_db_transaction + execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}" + end - def commit_db_transaction #:nodoc: - @connection.commit - ensure - @connection.autocommit = true - 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 rollback_db_transaction #:nodoc: + @connection.rollback + ensure + @connection.autocommit = true + end - def create_savepoint(name = current_savepoint_name) #:nodoc: - execute("SAVEPOINT #{name}") - end + def create_savepoint(name = current_savepoint_name) #:nodoc: + execute("SAVEPOINT #{name}") + end - def rollback_to_savepoint(name = current_savepoint_name) #:nodoc: - execute("ROLLBACK TO #{name}") - end + def rollback_to_savepoint(name = current_savepoint_name) #:nodoc: + execute("ROLLBACK TO #{name}") + end - def release_savepoint(name = current_savepoint_name) #:nodoc: - # there is no RELEASE SAVEPOINT statement in Oracle - end + def release_savepoint(name = current_savepoint_name) #:nodoc: + # there is no RELEASE SAVEPOINT statement in Oracle + end - # Returns default sequence name for table. - # Will take all or first 26 characters of table name and append _seq suffix - def default_sequence_name(table_name, primary_key = nil) - table_name.to_s.gsub /(^|\.)([\w$-]{1,#{sequence_name_length-4}})([\w$-]*)$/, '\1\2_seq' - end + # Returns default sequence name for table. + # Will take all or first 26 characters of table name and append _seq suffix + def default_sequence_name(table_name, primary_key = nil) + table_name.to_s.gsub /(^|\.)([\w$-]{1,#{sequence_name_length-4}})([\w$-]*)$/, '\1\2_seq' + end - # Inserts the given fixture into the table. Overridden to properly handle lobs. - def insert_fixture(fixture, table_name) #:nodoc: - super + # Inserts the given fixture into the table. Overridden to properly handle lobs. + def insert_fixture(fixture, table_name) #:nodoc: + super - if ActiveRecord::Base.pluralize_table_names - klass = table_name.to_s.singularize.camelize - else - klass = table_name.to_s.camelize - end + if ActiveRecord::Base.pluralize_table_names + klass = table_name.to_s.singularize.camelize + else + klass = table_name.to_s.camelize + end - klass = klass.constantize rescue nil - if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base) - write_lobs(table_name, klass, fixture, klass.lob_columns) + klass = klass.constantize rescue nil + if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base) + write_lobs(table_name, klass, fixture, klass.lob_columns) + end end - end - private + private - def select(sql, name = nil, binds = []) - exec_query(sql, name, binds) - end + def select(sql, name = nil, binds = []) + exec_query(sql, name, binds) + end + end end end end - -ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do - include ActiveRecord::ConnectionAdapters::OracleEnhancedDatabaseStatements -end diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index b94a7c68a..cd985844b 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -31,6 +31,7 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_record/connection_adapters/oracle_enhanced/connection' +require 'active_record/connection_adapters/oracle_enhanced/database_statements' require 'active_record/connection_adapters/oracle_enhanced/column' @@ -218,6 +219,8 @@ module ConnectionAdapters #:nodoc: # * :nls_time_tz_format # class OracleEnhancedAdapter < AbstractAdapter + # TODO: Use relative + include ActiveRecord::ConnectionAdapters::OracleEnhanced::DatabaseStatements ## # :singleton-method: From a64b7749c6a96a3745ec01b3d45286d583899da7 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 28 Apr 2015 15:58:48 +0000 Subject: [PATCH 054/114] Move SchemaCreation from ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::SchemaCreation to ActiveRecord::ConnectionAdapters::OracleEnhanced::SchemaCreation --- .../connection_adapters/oracle_enhanced/schema_creation.rb | 7 +------ .../connection_adapters/oracle_enhanced_adapter.rb | 4 ++++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb index b42ffbc60..1730caf2f 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb @@ -1,6 +1,6 @@ module ActiveRecord module ConnectionAdapters - class OracleEnhancedAdapter < AbstractAdapter + module OracleEnhanced class SchemaCreation < AbstractAdapter::SchemaCreation private @@ -78,11 +78,6 @@ def options_include_default?(options) end end - - def schema_creation - SchemaCreation.new self - end - end end end diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index cd985844b..e5d9e526f 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -222,6 +222,10 @@ class OracleEnhancedAdapter < AbstractAdapter # TODO: Use relative include ActiveRecord::ConnectionAdapters::OracleEnhanced::DatabaseStatements + def schema_creation + OracleEnhanced::SchemaCreation.new self + end + ## # :singleton-method: # By default, the OracleEnhancedAdapter will consider all columns of type NUMBER(1) From 2fdbf9b2c037b8cd9b6cc671a4cded7ef0860562 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 28 Apr 2015 16:18:01 +0000 Subject: [PATCH 055/114] Migrate from ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaStatements to ActiveRecord::ConnectionAdapters::OracleEnhanced::SchemaStatements --- .../oracle_enhanced/schema_statements.rb | 1078 ++++++++--------- .../oracle_enhanced_adapter.rb | 2 + 2 files changed, 540 insertions(+), 540 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb index 3063db3f2..23a2fc53e 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb @@ -3,646 +3,644 @@ module ActiveRecord module ConnectionAdapters - module OracleEnhancedSchemaStatements - # SCHEMA STATEMENTS ======================================== - # - # see: abstract/schema_statements.rb - - # Additional options for +create_table+ method in migration files. - # - # You can specify individual starting value in table creation migration file, e.g.: - # - # create_table :users, :sequence_start_value => 100 do |t| - # # ... - # end - # - # You can also specify other sequence definition additional parameters, e.g.: - # - # create_table :users, :sequence_start_value => “100 NOCACHE INCREMENT BY 10” do |t| - # # ... - # end - # - # Create primary key trigger (so that you can skip primary key value in INSERT statement). - # By default trigger name will be "table_name_pkt", you can override the name with - # :trigger_name option (but it is not recommended to override it as then this trigger will - # not be detected by ActiveRecord model and it will still do prefetching of sequence value). - # Example: - # - # create_table :users, :primary_key_trigger => true do |t| - # # ... - # end - # - # It is possible to add table and column comments in table creation migration files: - # - # create_table :employees, :comment => “Employees and contractors” do |t| - # t.string :first_name, :comment => “Given name” - # t.string :last_name, :comment => “Surname” - # end - - def create_table(name, options = {}) - create_sequence = options[:id] != false - column_comments = {} - temporary = options.delete(:temporary) - additional_options = options - table_definition = create_table_definition name, temporary, additional_options - table_definition.primary_key(options[:primary_key] || Base.get_primary_key(name.to_s.singularize)) unless options[:id] == false - - # store that primary key was defined in create_table block - unless create_sequence - class << table_definition - attr_accessor :create_sequence - def primary_key(*args) - self.create_sequence = true - super(*args) + module OracleEnhanced + module SchemaStatements + # SCHEMA STATEMENTS ======================================== + # + # see: abstract/schema_statements.rb + + # Additional options for +create_table+ method in migration files. + # + # You can specify individual starting value in table creation migration file, e.g.: + # + # create_table :users, :sequence_start_value => 100 do |t| + # # ... + # end + # + # You can also specify other sequence definition additional parameters, e.g.: + # + # create_table :users, :sequence_start_value => “100 NOCACHE INCREMENT BY 10” do |t| + # # ... + # end + # + # Create primary key trigger (so that you can skip primary key value in INSERT statement). + # By default trigger name will be "table_name_pkt", you can override the name with + # :trigger_name option (but it is not recommended to override it as then this trigger will + # not be detected by ActiveRecord model and it will still do prefetching of sequence value). + # Example: + # + # create_table :users, :primary_key_trigger => true do |t| + # # ... + # end + # + # It is possible to add table and column comments in table creation migration files: + # + # create_table :employees, :comment => “Employees and contractors” do |t| + # t.string :first_name, :comment => “Given name” + # t.string :last_name, :comment => “Surname” + # end + + def create_table(name, options = {}) + create_sequence = options[:id] != false + column_comments = {} + temporary = options.delete(:temporary) + additional_options = options + table_definition = create_table_definition name, temporary, additional_options + table_definition.primary_key(options[:primary_key] || Base.get_primary_key(name.to_s.singularize)) unless options[:id] == false + + # store that primary key was defined in create_table block + unless create_sequence + class << table_definition + attr_accessor :create_sequence + def primary_key(*args) + self.create_sequence = true + super(*args) + end end end - end - # store column comments - class << table_definition - attr_accessor :column_comments - def column(name, type, options = {}) - if options[:comment] - self.column_comments ||= {} - self.column_comments[name] = options[:comment] + # store column comments + class << table_definition + attr_accessor :column_comments + def column(name, type, options = {}) + if options[:comment] + self.column_comments ||= {} + self.column_comments[name] = options[:comment] + end + super(name, type, options) end - super(name, type, options) end - end - yield table_definition if block_given? - create_sequence = create_sequence || table_definition.create_sequence - column_comments = table_definition.column_comments if table_definition.column_comments - tablespace = tablespace_for(:table, options[:tablespace]) + yield table_definition if block_given? + create_sequence = create_sequence || table_definition.create_sequence + column_comments = table_definition.column_comments if table_definition.column_comments + tablespace = tablespace_for(:table, options[:tablespace]) - if options[:force] && table_exists?(name) - drop_table(name, options) - end + if options[:force] && table_exists?(name) + drop_table(name, options) + end - execute schema_creation.accept table_definition + execute schema_creation.accept table_definition - create_sequence_and_trigger(name, options) if create_sequence + create_sequence_and_trigger(name, options) if create_sequence - add_table_comment name, options[:comment] - column_comments.each do |column_name, comment| - add_comment name, column_name, comment - end - table_definition.indexes.each_pair { |c,o| add_index name, c, o } + add_table_comment name, options[:comment] + column_comments.each do |column_name, comment| + add_comment name, column_name, comment + end + table_definition.indexes.each_pair { |c,o| add_index name, c, o } - unless table_definition.foreign_keys.nil? - table_definition.foreign_keys.each do |foreign_key| - add_foreign_key(table_definition.name, foreign_key.to_table, foreign_key.options) + unless table_definition.foreign_keys.nil? + table_definition.foreign_keys.each do |foreign_key| + add_foreign_key(table_definition.name, foreign_key.to_table, foreign_key.options) + end end end - end - def create_table_definition(name, temporary, options) - TableDefinition.new native_database_types, name, temporary, options - end + def create_table_definition(name, temporary, options) + TableDefinition.new native_database_types, name, temporary, options + end - def rename_table(table_name, new_name) #:nodoc: - if new_name.to_s.length > table_name_length - raise ArgumentError, "New table name '#{new_name}' is too long; the limit is #{table_name_length} characters" + def rename_table(table_name, new_name) #:nodoc: + if new_name.to_s.length > table_name_length + raise ArgumentError, "New table name '#{new_name}' is too long; the limit is #{table_name_length} characters" + end + if "#{new_name}_seq".to_s.length > sequence_name_length + raise ArgumentError, "New sequence name '#{new_name}_seq' is too long; the limit is #{sequence_name_length} characters" + end + execute "RENAME #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" + execute "RENAME #{quote_table_name("#{table_name}_seq")} TO #{quote_table_name("#{new_name}_seq")}" rescue nil + + rename_table_indexes(table_name, new_name) end - if "#{new_name}_seq".to_s.length > sequence_name_length - raise ArgumentError, "New sequence name '#{new_name}_seq' is too long; the limit is #{sequence_name_length} characters" + + def drop_table(table_name, options = {}) #:nodoc: + execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE CONSTRAINTS' if options[:force] == :cascade}" + seq_name = options[:sequence_name] || default_sequence_name(table_name) + execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil + rescue ActiveRecord::StatementInvalid => e + raise e unless options[:if_exists] + ensure + clear_table_columns_cache(table_name) + self.all_schema_indexes = nil end - execute "RENAME #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" - execute "RENAME #{quote_table_name("#{table_name}_seq")} TO #{quote_table_name("#{new_name}_seq")}" rescue nil - rename_table_indexes(table_name, new_name) - end + def initialize_schema_migrations_table + sm_table = ActiveRecord::Migrator.schema_migrations_table_name - def drop_table(table_name, options = {}) #:nodoc: - execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE CONSTRAINTS' if options[:force] == :cascade}" - seq_name = options[:sequence_name] || default_sequence_name(table_name) - execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil - rescue ActiveRecord::StatementInvalid => e - raise e unless options[:if_exists] - ensure - clear_table_columns_cache(table_name) - self.all_schema_indexes = nil - end + unless table_exists?(sm_table) + index_name = "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}" + if index_name.length > index_name_length + truncate_to = index_name_length - index_name.to_s.length - 1 + truncated_name = "unique_schema_migrations"[0..truncate_to] + index_name = "#{Base.table_name_prefix}#{truncated_name}#{Base.table_name_suffix}" + end - def initialize_schema_migrations_table - sm_table = ActiveRecord::Migrator.schema_migrations_table_name + create_table(sm_table, :id => false) do |schema_migrations_table| + schema_migrations_table.column :version, :string, :null => false + end + add_index sm_table, :version, :unique => true, :name => index_name - unless table_exists?(sm_table) - index_name = "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}" - if index_name.length > index_name_length - truncate_to = index_name_length - index_name.to_s.length - 1 - truncated_name = "unique_schema_migrations"[0..truncate_to] - index_name = "#{Base.table_name_prefix}#{truncated_name}#{Base.table_name_suffix}" - end + # Backwards-compatibility: if we find schema_info, assume we've + # migrated up to that point: + si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix + if table_exists?(si_table) + ActiveSupport::Deprecation.warn "Usage of the schema table `#{si_table}` is deprecated. Please switch to using `schema_migrations` table" - create_table(sm_table, :id => false) do |schema_migrations_table| - schema_migrations_table.column :version, :string, :null => false + old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i + assume_migrated_upto_version(old_version) + drop_table(si_table) + end end - add_index sm_table, :version, :unique => true, :name => index_name + end - # Backwards-compatibility: if we find schema_info, assume we've - # migrated up to that point: - si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix - if table_exists?(si_table) - ActiveSupport::Deprecation.warn "Usage of the schema table `#{si_table}` is deprecated. Please switch to using `schema_migrations` table" + # clear cached indexes when adding new index + def add_index(table_name, column_name, options = {}) #:nodoc: + column_names = Array(column_name) + index_name = index_name(table_name, column: column_names) - old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i - assume_migrated_upto_version(old_version) - drop_table(si_table) + if Hash === options # legacy support, since this param was a string + options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :tablespace, :options, :using) + + index_type = options[:unique] ? "UNIQUE" : "" + index_name = options[:name].to_s if options.key?(:name) + tablespace = tablespace_for(:index, options[:tablespace]) + max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length + additional_options = options[:options] + else + if options + message = "Passing a string as third argument of `add_index` is deprecated and will" + + " be removed in Rails 4.1." + + " Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead" + + ActiveSupport::Deprecation.warn message + end + + index_type = options + additional_options = nil + max_index_length = allowed_index_name_length end - end - end - # clear cached indexes when adding new index - def add_index(table_name, column_name, options = {}) #:nodoc: - column_names = Array(column_name) - index_name = index_name(table_name, column: column_names) - - if Hash === options # legacy support, since this param was a string - options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :tablespace, :options, :using) - - index_type = options[:unique] ? "UNIQUE" : "" - index_name = options[:name].to_s if options.key?(:name) - tablespace = tablespace_for(:index, options[:tablespace]) - max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length - additional_options = options[:options] - else - if options - message = "Passing a string as third argument of `add_index` is deprecated and will" + - " be removed in Rails 4.1." + - " Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead" - - ActiveSupport::Deprecation.warn message + if index_name.to_s.length > max_index_length + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" + end + if index_name_exists?(table_name, index_name, false) + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" end + quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ") - index_type = options - additional_options = nil - max_index_length = allowed_index_name_length + execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{additional_options}" + ensure + self.all_schema_indexes = nil end - if index_name.to_s.length > max_index_length - raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" - end - if index_name_exists?(table_name, index_name, false) - raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" + # Remove the given index from the table. + # Gives warning if index does not exist + def remove_index(table_name, options = {}) #:nodoc: + index_name = index_name(table_name, options) + unless index_name_exists?(table_name, index_name, true) + # sometimes options can be String or Array with column names + options = {} unless options.is_a?(Hash) + if options.has_key? :name + options_without_column = options.dup + options_without_column.delete :column + index_name_without_column = index_name(table_name, options_without_column) + return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false) + end + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" + end + remove_index!(table_name, index_name) end - quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ") - execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{additional_options}" - ensure - self.all_schema_indexes = nil - end + # clear cached indexes when removing index + def remove_index!(table_name, index_name) #:nodoc: + execute "DROP INDEX #{quote_column_name(index_name)}" + ensure + self.all_schema_indexes = nil + end - # Remove the given index from the table. - # Gives warning if index does not exist - def remove_index(table_name, options = {}) #:nodoc: - index_name = index_name(table_name, options) - unless index_name_exists?(table_name, index_name, true) + # returned shortened index name if default is too large + def index_name(table_name, options) #:nodoc: + default_name = super(table_name, options).to_s # sometimes options can be String or Array with column names options = {} unless options.is_a?(Hash) - if options.has_key? :name - options_without_column = options.dup - options_without_column.delete :column - index_name_without_column = index_name(table_name, options_without_column) - return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false) + identifier_max_length = options[:identifier_max_length] || index_name_length + return default_name if default_name.length <= identifier_max_length + + # remove 'index', 'on' and 'and' keywords + shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}" + + # leave just first three letters from each word + if shortened_name.length > identifier_max_length + shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_') + end + # generate unique name using hash function + if shortened_name.length > identifier_max_length + shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,identifier_max_length-1] end - raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" + @logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger + shortened_name end - remove_index!(table_name, index_name) - end - # clear cached indexes when removing index - def remove_index!(table_name, index_name) #:nodoc: - execute "DROP INDEX #{quote_column_name(index_name)}" - ensure - self.all_schema_indexes = nil - end + # Verify the existence of an index with a given name. + # + # The default argument is returned if the underlying implementation does not define the indexes method, + # as there's no way to determine the correct answer in that case. + # + # Will always query database and not index cache. + def index_name_exists?(table_name, index_name, default) + (owner, table_name, db_link) = @connection.describe(table_name) + result = select_value(<<-SQL) + SELECT 1 FROM all_indexes#{db_link} i + WHERE i.owner = '#{owner}' + AND i.table_owner = '#{owner}' + AND i.table_name = '#{table_name}' + AND i.index_name = '#{index_name.to_s.upcase}' + SQL + result == 1 + end + + def rename_index(table_name, old_name, new_name) #:nodoc: + unless index_name_exists?(table_name, old_name, true) + raise ArgumentError, "Index name '#{old_name}' on table '#{table_name}' does not exist" + end + if new_name.length > allowed_index_name_length + raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" + end + execute "ALTER INDEX #{quote_column_name(old_name)} rename to #{quote_column_name(new_name)}" + ensure + self.all_schema_indexes = nil + end - # returned shortened index name if default is too large - def index_name(table_name, options) #:nodoc: - default_name = super(table_name, options).to_s - # sometimes options can be String or Array with column names - options = {} unless options.is_a?(Hash) - identifier_max_length = options[:identifier_max_length] || index_name_length - return default_name if default_name.length <= identifier_max_length + def add_column(table_name, column_name, type, options = {}) #:nodoc: + if type.to_sym == :virtual + type = options[:type] + end + type = aliased_types(type.to_s, type) + add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} " + add_column_sql << type_to_sql(type, options[:limit], options[:precision], options[:scale]) if type - # remove 'index', 'on' and 'and' keywords - shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}" + add_column_options!(add_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name)) - # leave just first three letters from each word - if shortened_name.length > identifier_max_length - shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_') - end - # generate unique name using hash function - if shortened_name.length > identifier_max_length - shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,identifier_max_length-1] - end - @logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger - shortened_name - end + add_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, table_name, column_name) if type - # Verify the existence of an index with a given name. - # - # The default argument is returned if the underlying implementation does not define the indexes method, - # as there's no way to determine the correct answer in that case. - # - # Will always query database and not index cache. - def index_name_exists?(table_name, index_name, default) - (owner, table_name, db_link) = @connection.describe(table_name) - result = select_value(<<-SQL) - SELECT 1 FROM all_indexes#{db_link} i - WHERE i.owner = '#{owner}' - AND i.table_owner = '#{owner}' - AND i.table_name = '#{table_name}' - AND i.index_name = '#{index_name.to_s.upcase}' - SQL - result == 1 - end + execute(add_column_sql) - def rename_index(table_name, old_name, new_name) #:nodoc: - unless index_name_exists?(table_name, old_name, true) - raise ArgumentError, "Index name '#{old_name}' on table '#{table_name}' does not exist" + create_sequence_and_trigger(table_name, options) if type && type.to_sym == :primary_key + ensure + clear_table_columns_cache(table_name) end - if new_name.length > allowed_index_name_length - raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" + + def aliased_types(name, fallback) + fallback end - execute "ALTER INDEX #{quote_column_name(old_name)} rename to #{quote_column_name(new_name)}" - ensure - self.all_schema_indexes = nil - end - def add_column(table_name, column_name, type, options = {}) #:nodoc: - if type.to_sym == :virtual - type = options[:type] + def change_column_default(table_name, column_name, default) #:nodoc: + execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}" + ensure + clear_table_columns_cache(table_name) end - type = aliased_types(type.to_s, type) - add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} " - add_column_sql << type_to_sql(type, options[:limit], options[:precision], options[:scale]) if type - add_column_options!(add_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name)) + def change_column_null(table_name, column_name, null, default = nil) #:nodoc: + column = column_for(table_name, column_name) - add_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, table_name, column_name) if type + unless null || default.nil? + execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + end - execute(add_column_sql) + change_column table_name, column_name, column.sql_type, :null => null + end - create_sequence_and_trigger(table_name, options) if type && type.to_sym == :primary_key - ensure - clear_table_columns_cache(table_name) - end + def change_column(table_name, column_name, type, options = {}) #:nodoc: + column = column_for(table_name, column_name) - def aliased_types(name, fallback) - fallback - end + # remove :null option if its value is the same as current column definition + # otherwise Oracle will raise error + if options.has_key?(:null) && options[:null] == column.null + options[:null] = nil + end + if type.to_sym == :virtual + type = options[:type] + end + change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} " + change_column_sql << "#{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" if type - def change_column_default(table_name, column_name, default) #:nodoc: - execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}" - ensure - clear_table_columns_cache(table_name) - end + add_column_options!(change_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name)) - def change_column_null(table_name, column_name, null, default = nil) #:nodoc: - column = column_for(table_name, column_name) + change_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, options[:table_name], options[:column_name]) if type - unless null || default.nil? - execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + execute(change_column_sql) + ensure + clear_table_columns_cache(table_name) end - change_column table_name, column_name, column.sql_type, :null => null - end - - def change_column(table_name, column_name, type, options = {}) #:nodoc: - column = column_for(table_name, column_name) - - # remove :null option if its value is the same as current column definition - # otherwise Oracle will raise error - if options.has_key?(:null) && options[:null] == column.null - options[:null] = nil + def rename_column(table_name, column_name, new_column_name) #:nodoc: + execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}" + self.all_schema_indexes = nil + rename_column_indexes(table_name, column_name, new_column_name) + ensure + clear_table_columns_cache(table_name) end - if type.to_sym == :virtual - type = options[:type] + + def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc: + execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}" + ensure + clear_table_columns_cache(table_name) + self.all_schema_indexes = nil end - change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} " - change_column_sql << "#{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" if type - add_column_options!(change_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name)) + def add_comment(table_name, column_name, comment) #:nodoc: + return if comment.blank? + execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'" + end - change_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, options[:table_name], options[:column_name]) if type + def add_table_comment(table_name, comment) #:nodoc: + return if comment.blank? + execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'" + end - execute(change_column_sql) - ensure - clear_table_columns_cache(table_name) - end + def table_comment(table_name) #:nodoc: + (owner, table_name, db_link) = @connection.describe(table_name) + select_value <<-SQL + SELECT comments FROM all_tab_comments#{db_link} + WHERE owner = '#{owner}' + AND table_name = '#{table_name}' + SQL + end - def rename_column(table_name, column_name, new_column_name) #:nodoc: - execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}" - self.all_schema_indexes = nil - rename_column_indexes(table_name, column_name, new_column_name) - ensure - clear_table_columns_cache(table_name) - end + def column_comment(table_name, column_name) #:nodoc: + (owner, table_name, db_link) = @connection.describe(table_name) + select_value <<-SQL + SELECT comments FROM all_col_comments#{db_link} + WHERE owner = '#{owner}' + AND table_name = '#{table_name}' + AND column_name = '#{column_name.upcase}' + SQL + end - def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc: - execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}" - ensure - clear_table_columns_cache(table_name) - self.all_schema_indexes = nil - end + # Maps logical Rails types to Oracle-specific data types. + def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: + # Ignore options for :text and :binary columns + return super(type, nil, nil, nil) if ['text', 'binary'].include?(type.to_s) - def add_comment(table_name, column_name, comment) #:nodoc: - return if comment.blank? - execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'" - end + super + end - def add_table_comment(table_name, comment) #:nodoc: - return if comment.blank? - execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'" - end + def tablespace(table_name) + select_value <<-SQL + SELECT tablespace_name + FROM user_tables + WHERE table_name='#{table_name.to_s.upcase}' + SQL + end - def table_comment(table_name) #:nodoc: - (owner, table_name, db_link) = @connection.describe(table_name) - select_value <<-SQL - SELECT comments FROM all_tab_comments#{db_link} - WHERE owner = '#{owner}' - AND table_name = '#{table_name}' - SQL - end + # Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+ + # (syntax and partial implementation taken from http://github.com/matthuhiggins/foreigner) + # + # The foreign key will be named after the from and to tables unless you pass + # :name as an option. + # + # === Examples + # ==== Creating a foreign key + # add_foreign_key(:comments, :posts) + # generates + # ALTER TABLE comments ADD CONSTRAINT + # comments_post_id_fk FOREIGN KEY (post_id) REFERENCES posts (id) + # + # ==== Creating a named foreign key + # add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts') + # generates + # ALTER TABLE comments ADD CONSTRAINT + # comments_belongs_to_posts FOREIGN KEY (post_id) REFERENCES posts (id) + # + # ==== Creating a cascading foreign_key on a custom column + # add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify) + # generates + # ALTER TABLE people ADD CONSTRAINT + # people_best_friend_id_fk FOREIGN KEY (best_friend_id) REFERENCES people (id) + # ON DELETE SET NULL + # + # ==== Creating a composite foreign key + # add_foreign_key(:comments, :posts, :columns => ['post_id', 'author_id'], :name => 'comments_post_fk') + # generates + # ALTER TABLE comments ADD CONSTRAINT + # comments_post_fk FOREIGN KEY (post_id, author_id) REFERENCES posts (post_id, author_id) + # + # === Supported options + # [:column] + # Specify the column name on the from_table that references the to_table. By default this is guessed + # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id" + # as the default :column. + # [:columns] + # An array of column names when defining composite foreign keys. An alias of :column provided for improved readability. + # [:primary_key] + # Specify the column name on the to_table that is referenced by this foreign key. By default this is + # assumed to be "id". Ignored when defining composite foreign keys. + # [:name] + # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column. + # [:dependent] + # If set to :delete, the associated records in from_table are deleted when records in to_table table are deleted. + # If set to :nullify, the foreign key column is set to +NULL+. + def add_foreign_key(from_table, to_table, options = {}) + columns = options[:column] || options[:columns] || "#{to_table.to_s.singularize}_id" + constraint_name = foreign_key_constraint_name(from_table, columns, options) + sql = "ALTER TABLE #{quote_table_name(from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} " + sql << foreign_key_definition(to_table, options) + execute sql + end - def column_comment(table_name, column_name) #:nodoc: - (owner, table_name, db_link) = @connection.describe(table_name) - select_value <<-SQL - SELECT comments FROM all_col_comments#{db_link} - WHERE owner = '#{owner}' - AND table_name = '#{table_name}' - AND column_name = '#{column_name.upcase}' - SQL - end + def foreign_key_definition(to_table, options = {}) #:nodoc: + columns = Array(options[:column] || options[:columns]) - # Maps logical Rails types to Oracle-specific data types. - def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: - # Ignore options for :text and :binary columns - return super(type, nil, nil, nil) if ['text', 'binary'].include?(type.to_s) + if columns.size > 1 + # composite foreign key + columns_sql = columns.map {|c| quote_column_name(c)}.join(',') + references = options[:references] || columns + references_sql = references.map {|c| quote_column_name(c)}.join(',') + else + columns_sql = quote_column_name(columns.first || "#{to_table.to_s.singularize}_id") + references = options[:references] ? options[:references].first : nil + references_sql = quote_column_name(options[:primary_key] || references || "id") + end - super - end + table_name = to_table + # TODO: Needs support `table_name_prefix` and `table_name_suffix` - def tablespace(table_name) - select_value <<-SQL - SELECT tablespace_name - FROM user_tables - WHERE table_name='#{table_name.to_s.upcase}' - SQL - end + sql = "FOREIGN KEY (#{columns_sql}) REFERENCES #{quote_table_name(table_name)}(#{references_sql})" - # Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+ - # (syntax and partial implementation taken from http://github.com/matthuhiggins/foreigner) - # - # The foreign key will be named after the from and to tables unless you pass - # :name as an option. - # - # === Examples - # ==== Creating a foreign key - # add_foreign_key(:comments, :posts) - # generates - # ALTER TABLE comments ADD CONSTRAINT - # comments_post_id_fk FOREIGN KEY (post_id) REFERENCES posts (id) - # - # ==== Creating a named foreign key - # add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts') - # generates - # ALTER TABLE comments ADD CONSTRAINT - # comments_belongs_to_posts FOREIGN KEY (post_id) REFERENCES posts (id) - # - # ==== Creating a cascading foreign_key on a custom column - # add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify) - # generates - # ALTER TABLE people ADD CONSTRAINT - # people_best_friend_id_fk FOREIGN KEY (best_friend_id) REFERENCES people (id) - # ON DELETE SET NULL - # - # ==== Creating a composite foreign key - # add_foreign_key(:comments, :posts, :columns => ['post_id', 'author_id'], :name => 'comments_post_fk') - # generates - # ALTER TABLE comments ADD CONSTRAINT - # comments_post_fk FOREIGN KEY (post_id, author_id) REFERENCES posts (post_id, author_id) - # - # === Supported options - # [:column] - # Specify the column name on the from_table that references the to_table. By default this is guessed - # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id" - # as the default :column. - # [:columns] - # An array of column names when defining composite foreign keys. An alias of :column provided for improved readability. - # [:primary_key] - # Specify the column name on the to_table that is referenced by this foreign key. By default this is - # assumed to be "id". Ignored when defining composite foreign keys. - # [:name] - # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column. - # [:dependent] - # If set to :delete, the associated records in from_table are deleted when records in to_table table are deleted. - # If set to :nullify, the foreign key column is set to +NULL+. - def add_foreign_key(from_table, to_table, options = {}) - columns = options[:column] || options[:columns] || "#{to_table.to_s.singularize}_id" - constraint_name = foreign_key_constraint_name(from_table, columns, options) - sql = "ALTER TABLE #{quote_table_name(from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} " - sql << foreign_key_definition(to_table, options) - execute sql - end + case options[:dependent] + when :nullify + sql << " ON DELETE SET NULL" + when :delete + sql << " ON DELETE CASCADE" + end + sql + end - def foreign_key_definition(to_table, options = {}) #:nodoc: - columns = Array(options[:column] || options[:columns]) - - if columns.size > 1 - # composite foreign key - columns_sql = columns.map {|c| quote_column_name(c)}.join(',') - references = options[:references] || columns - references_sql = references.map {|c| quote_column_name(c)}.join(',') - else - columns_sql = quote_column_name(columns.first || "#{to_table.to_s.singularize}_id") - references = options[:references] ? options[:references].first : nil - references_sql = quote_column_name(options[:primary_key] || references || "id") + # Remove the given foreign key from the table. + # + # ===== Examples + # ====== Remove the suppliers_company_id_fk in the suppliers table. + # remove_foreign_key :suppliers, :companies + # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table. + # remove_foreign_key :accounts, :column => :branch_id + # ====== Remove the foreign key named party_foreign_key in the accounts table. + # remove_foreign_key :accounts, :name => :party_foreign_key + def remove_foreign_key(from_table, options) + if Hash === options + constraint_name = foreign_key_constraint_name(from_table, options[:column], options) + else + constraint_name = foreign_key_constraint_name(from_table, "#{options.to_s.singularize}_id") + end + execute "ALTER TABLE #{quote_table_name(from_table)} DROP CONSTRAINT #{quote_column_name(constraint_name)}" end - table_name = to_table - # TODO: Needs support `table_name_prefix` and `table_name_suffix` + private - sql = "FOREIGN KEY (#{columns_sql}) REFERENCES #{quote_table_name(table_name)}(#{references_sql})" + def foreign_key_constraint_name(table_name, columns, options = {}) + columns = Array(columns) + constraint_name = original_name = options[:name] || "#{table_name}_#{columns.join('_')}_fk" - case options[:dependent] - when :nullify - sql << " ON DELETE SET NULL" - when :delete - sql << " ON DELETE CASCADE" + return constraint_name if constraint_name.length <= OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH + + # leave just first three letters from each word + constraint_name = constraint_name.split('_').map{|w| w[0,3]}.join('_') + # generate unique name using hash function + if constraint_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH + constraint_name = 'c'+Digest::SHA1.hexdigest(original_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1] + end + @logger.warn "#{adapter_name} shortened foreign key constraint name #{original_name} to #{constraint_name}" if @logger + constraint_name end - sql - end - # Remove the given foreign key from the table. - # - # ===== Examples - # ====== Remove the suppliers_company_id_fk in the suppliers table. - # remove_foreign_key :suppliers, :companies - # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table. - # remove_foreign_key :accounts, :column => :branch_id - # ====== Remove the foreign key named party_foreign_key in the accounts table. - # remove_foreign_key :accounts, :name => :party_foreign_key - def remove_foreign_key(from_table, options) - if Hash === options - constraint_name = foreign_key_constraint_name(from_table, options[:column], options) - else - constraint_name = foreign_key_constraint_name(from_table, "#{options.to_s.singularize}_id") - end - execute "ALTER TABLE #{quote_table_name(from_table)} DROP CONSTRAINT #{quote_column_name(constraint_name)}" - end - private - - def foreign_key_constraint_name(table_name, columns, options = {}) - columns = Array(columns) - constraint_name = original_name = options[:name] || "#{table_name}_#{columns.join('_')}_fk" - - return constraint_name if constraint_name.length <= OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH - - # leave just first three letters from each word - constraint_name = constraint_name.split('_').map{|w| w[0,3]}.join('_') - # generate unique name using hash function - if constraint_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH - constraint_name = 'c'+Digest::SHA1.hexdigest(original_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1] - end - @logger.warn "#{adapter_name} shortened foreign key constraint name #{original_name} to #{constraint_name}" if @logger - constraint_name - end - - - public - - # get table foreign keys for schema dump - def foreign_keys(table_name) #:nodoc: - (owner, desc_table_name, db_link) = @connection.describe(table_name) - - fk_info = select_all(<<-SQL, 'Foreign Keys') - SELECT r.table_name to_table - ,rc.column_name references_column - ,cc.column_name - ,c.constraint_name name - ,c.delete_rule - FROM user_constraints#{db_link} c, user_cons_columns#{db_link} cc, - user_constraints#{db_link} r, user_cons_columns#{db_link} rc - WHERE c.owner = '#{owner}' - AND c.table_name = '#{desc_table_name}' - AND c.constraint_type = 'R' - AND cc.owner = c.owner - AND cc.constraint_name = c.constraint_name - AND r.constraint_name = c.r_constraint_name - AND r.owner = c.owner - AND rc.owner = r.owner - AND rc.constraint_name = r.constraint_name - AND rc.position = cc.position - ORDER BY name, to_table, column_name, references_column - SQL - - fks = {} - - fk_info.map do |row| - name = oracle_downcase(row['name']) - fks[name] ||= { :columns => [], :to_table => oracle_downcase(row['to_table']), :references => [] } - fks[name][:columns] << oracle_downcase(row['column_name']) - fks[name][:references] << oracle_downcase(row['references_column']) - case row['delete_rule'] - when 'CASCADE' - fks[name][:dependent] = :delete - when 'SET NULL' - fks[name][:dependent] = :nullify + public + + # get table foreign keys for schema dump + def foreign_keys(table_name) #:nodoc: + (owner, desc_table_name, db_link) = @connection.describe(table_name) + + fk_info = select_all(<<-SQL, 'Foreign Keys') + SELECT r.table_name to_table + ,rc.column_name references_column + ,cc.column_name + ,c.constraint_name name + ,c.delete_rule + FROM user_constraints#{db_link} c, user_cons_columns#{db_link} cc, + user_constraints#{db_link} r, user_cons_columns#{db_link} rc + WHERE c.owner = '#{owner}' + AND c.table_name = '#{desc_table_name}' + AND c.constraint_type = 'R' + AND cc.owner = c.owner + AND cc.constraint_name = c.constraint_name + AND r.constraint_name = c.r_constraint_name + AND r.owner = c.owner + AND rc.owner = r.owner + AND rc.constraint_name = r.constraint_name + AND rc.position = cc.position + ORDER BY name, to_table, column_name, references_column + SQL + + fks = {} + + fk_info.map do |row| + name = oracle_downcase(row['name']) + fks[name] ||= { :columns => [], :to_table => oracle_downcase(row['to_table']), :references => [] } + fks[name][:columns] << oracle_downcase(row['column_name']) + fks[name][:references] << oracle_downcase(row['references_column']) + case row['delete_rule'] + when 'CASCADE' + fks[name][:dependent] = :delete + when 'SET NULL' + fks[name][:dependent] = :nullify + end end - end - - fks.map do |k, v| - options = {:name => k, :columns => v[:columns], :references => v[:references], :dependent => v[:dependent]} - OracleEnhancedForeignKeyDefinition.new(table_name, v[:to_table], options) - end - end - # REFERENTIAL INTEGRITY ==================================== - - def disable_referential_integrity(&block) #:nodoc: - sql_constraints = <<-SQL - SELECT constraint_name, owner, table_name - FROM user_constraints - WHERE constraint_type = 'R' - AND status = 'ENABLED' - SQL - old_constraints = select_all(sql_constraints) - begin - old_constraints.each do |constraint| - execute "ALTER TABLE #{constraint["table_name"]} DISABLE CONSTRAINT #{constraint["constraint_name"]}" + fks.map do |k, v| + options = {:name => k, :columns => v[:columns], :references => v[:references], :dependent => v[:dependent]} + OracleEnhancedForeignKeyDefinition.new(table_name, v[:to_table], options) end - yield - ensure - old_constraints.each do |constraint| - execute "ALTER TABLE #{constraint["table_name"]} ENABLE CONSTRAINT #{constraint["constraint_name"]}" + end + + # REFERENTIAL INTEGRITY ==================================== + + def disable_referential_integrity(&block) #:nodoc: + sql_constraints = <<-SQL + SELECT constraint_name, owner, table_name + FROM user_constraints + WHERE constraint_type = 'R' + AND status = 'ENABLED' + SQL + old_constraints = select_all(sql_constraints) + begin + old_constraints.each do |constraint| + execute "ALTER TABLE #{constraint["table_name"]} DISABLE CONSTRAINT #{constraint["constraint_name"]}" + end + yield + ensure + old_constraints.each do |constraint| + execute "ALTER TABLE #{constraint["table_name"]} ENABLE CONSTRAINT #{constraint["constraint_name"]}" + end end end - end - private + private - def tablespace_for(obj_type, tablespace_option, table_name=nil, column_name=nil) - tablespace_sql = '' - if tablespace = (tablespace_option || default_tablespace_for(obj_type)) - tablespace_sql << if [:blob, :clob].include?(obj_type.to_sym) - " LOB (#{quote_column_name(column_name)}) STORE AS #{column_name.to_s[0..10]}_#{table_name.to_s[0..14]}_ls (TABLESPACE #{tablespace})" - else - " TABLESPACE #{tablespace}" + def tablespace_for(obj_type, tablespace_option, table_name=nil, column_name=nil) + tablespace_sql = '' + if tablespace = (tablespace_option || default_tablespace_for(obj_type)) + tablespace_sql << if [:blob, :clob].include?(obj_type.to_sym) + " LOB (#{quote_column_name(column_name)}) STORE AS #{column_name.to_s[0..10]}_#{table_name.to_s[0..14]}_ls (TABLESPACE #{tablespace})" + else + " TABLESPACE #{tablespace}" + end end + tablespace_sql end - tablespace_sql - end - def default_tablespace_for(type) - (default_tablespaces[type] || default_tablespaces[native_database_types[type][:name]]) rescue nil - end + def default_tablespace_for(type) + (default_tablespaces[type] || default_tablespaces[native_database_types[type][:name]]) rescue nil + end - def column_for(table_name, column_name) - unless column = columns(table_name).find { |c| c.name == column_name.to_s } - raise "No such column: #{table_name}.#{column_name}" + def column_for(table_name, column_name) + unless column = columns(table_name).find { |c| c.name == column_name.to_s } + raise "No such column: #{table_name}.#{column_name}" + end + column end - column - end - def create_sequence_and_trigger(table_name, options) - seq_name = options[:sequence_name] || default_sequence_name(table_name) - seq_start_value = options[:sequence_start_value] || default_sequence_start_value - execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}" + def create_sequence_and_trigger(table_name, options) + seq_name = options[:sequence_name] || default_sequence_name(table_name) + seq_start_value = options[:sequence_start_value] || default_sequence_start_value + execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}" - create_primary_key_trigger(table_name, options) if options[:primary_key_trigger] - end + create_primary_key_trigger(table_name, options) if options[:primary_key_trigger] + end - def create_primary_key_trigger(table_name, options) - seq_name = options[:sequence_name] || default_sequence_name(table_name) - trigger_name = options[:trigger_name] || default_trigger_name(table_name) - primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize) - execute compress_lines(<<-SQL) - CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)} - BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW - BEGIN - IF inserting THEN - IF :new.#{quote_column_name(primary_key)} IS NULL THEN - SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual; + def create_primary_key_trigger(table_name, options) + seq_name = options[:sequence_name] || default_sequence_name(table_name) + trigger_name = options[:trigger_name] || default_trigger_name(table_name) + primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize) + execute compress_lines(<<-SQL) + CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)} + BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW + BEGIN + IF inserting THEN + IF :new.#{quote_column_name(primary_key)} IS NULL THEN + SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual; + END IF; END IF; - END IF; - END; - SQL - end + END; + SQL + end - def default_trigger_name(table_name) - # truncate table name if necessary to fit in max length of identifier - "#{table_name.to_s[0,table_name_length-4]}_pkt" - end + def default_trigger_name(table_name) + # truncate table name if necessary to fit in max length of identifier + "#{table_name.to_s[0,table_name_length-4]}_pkt" + end + end end end end - -ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do - include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaStatements -end diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index e5d9e526f..56fc12ed2 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -32,6 +32,7 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_record/connection_adapters/oracle_enhanced/connection' require 'active_record/connection_adapters/oracle_enhanced/database_statements' +require 'active_record/connection_adapters/oracle_enhanced/schema_statements' require 'active_record/connection_adapters/oracle_enhanced/column' @@ -221,6 +222,7 @@ module ConnectionAdapters #:nodoc: class OracleEnhancedAdapter < AbstractAdapter # TODO: Use relative include ActiveRecord::ConnectionAdapters::OracleEnhanced::DatabaseStatements + include ActiveRecord::ConnectionAdapters::OracleEnhanced::SchemaStatements def schema_creation OracleEnhanced::SchemaCreation.new self From 66e22dcd1c8b25f3cf46f779cb71632f6d5f753d Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 30 Apr 2015 12:46:12 +0000 Subject: [PATCH 056/114] Add missing database_tasks.rb to gemspec --- activerecord-oracle_enhanced-adapter.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index 459a30912..531a0de07 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -37,6 +37,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. "lib/active_record/connection_adapters/oracle_enhanced/cpk.rb", "lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb", "lib/active_record/connection_adapters/oracle_enhanced/dirty.rb", + "lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb", "lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb", "lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb", "lib/active_record/connection_adapters/oracle_enhanced/procedures.rb", From 0dcf2c87676135fb79e7d0f7e91a79628f70bc17 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 30 Apr 2015 20:10:40 +0000 Subject: [PATCH 057/114] Revert "Add options_include_default!" This reverts commit 480a30f443bb3777bf600bab99d562780b4e6225. --- .../connection_adapters/oracle_enhanced/schema_creation.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb index 1730caf2f..14bb62dcf 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb @@ -71,12 +71,6 @@ def add_column_options!(sql, options) end end - # This method does not exist in SchemaCreation at Rails 4.0 - # It can be removed only when Oracle enhanced adapter supports Rails 4.1 and higher - def options_include_default?(options) - options.include?(:default) && !(options[:null] == false && options[:default].nil?) - end - end end end From 7deae158b4ab08305fdacc2a837f4f904bbf8dfe Mon Sep 17 00:00:00 2001 From: Razvan Date: Fri, 1 May 2015 11:14:03 -0400 Subject: [PATCH 058/114] Fixed typo in the rake tasks load statement --- lib/activerecord-oracle_enhanced-adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/activerecord-oracle_enhanced-adapter.rb b/lib/activerecord-oracle_enhanced-adapter.rb index 00d2ee387..8a3ab0c3c 100644 --- a/lib/activerecord-oracle_enhanced-adapter.rb +++ b/lib/activerecord-oracle_enhanced-adapter.rb @@ -5,7 +5,7 @@ module ActiveRecord module ConnectionAdapters class OracleEnhancedRailtie < ::Rails::Railtie rake_tasks do - load 'active_record/connection_adapters/oracle_enhanced_database_tasks.rb' + load 'active_record/connection_adapters/oracle_enhanced/database_tasks.rb' end ActiveSupport.on_load(:active_record) do From 617cef2f6285d5277d166139197809acb19d4c4a Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 4 May 2015 21:07:45 +0000 Subject: [PATCH 059/114] Call super when column typs is serialized --- .../oracle_enhanced_adapter.rb | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 56fc12ed2..0f5b50fa6 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -673,22 +673,26 @@ def quote_timestamp_with_to_timestamp(value) #:nodoc: # Cast a +value+ to a type that the database understands. def type_cast(value, column) - case value - when true, false - if emulate_booleans_from_strings || column && column.type == :string - self.class.boolean_to_string(value) - else - value ? 1 : 0 - end - when Date, Time - if value.acts_like?(:time) - zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal - value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value + if column && column.cast_type.is_a?(Type::Serialized) + super + else + case value + when true, false + if emulate_booleans_from_strings || column && column.type == :string + self.class.boolean_to_string(value) + else + value ? 1 : 0 + end + when Date, Time + if value.acts_like?(:time) + zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal + value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value + else + value + end else - value + super end - else - super end end From 490d2c153705da9f11781b7d7327aacb501ff06c Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 5 May 2015 15:37:38 +0000 Subject: [PATCH 060/114] Clear query cache on rollback refer rails#17820 for detail --- .../oracle_enhanced/database_statements.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb index 991ed9c76..3bb5ac37d 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb @@ -205,7 +205,7 @@ def commit_db_transaction #:nodoc: @connection.autocommit = true end - def rollback_db_transaction #:nodoc: + def exec_rollback_db_transaction #:nodoc: @connection.rollback ensure @connection.autocommit = true @@ -215,7 +215,7 @@ def create_savepoint(name = current_savepoint_name) #:nodoc: execute("SAVEPOINT #{name}") end - def rollback_to_savepoint(name = current_savepoint_name) #:nodoc: + def exec_rollback_to_savepoint(name = current_savepoint_name) #:nodoc: execute("ROLLBACK TO #{name}") end From fee9dfa9b83008d9d4660cce3bd73f325b71b5d2 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 6 May 2015 18:32:08 +0000 Subject: [PATCH 061/114] New db/schema.rb files will be created with force: :cascade instead of force: true. Refer https://github.com/rails/rails/pull/18082 --- .../connection_adapters/oracle_enhanced/schema_dumper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb index bdd93579a..b8e3f72f6 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb @@ -173,7 +173,7 @@ def oracle_enhanced_table(table, stream) else tbl.print ", id: false" end - tbl.print ", force: true" + tbl.print ", force: :cascade" tbl.puts " do |t|" # then dump all non-primary key columns From ee2f7914f3835a65396318e01ba4c1037bc0381f Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 8 May 2015 16:52:29 +0000 Subject: [PATCH 062/114] Removed `forced_column_type` by using `cast_type` Use `new_column` method instaead of `OracleEnhancedColumn.new` Add handling Oracle enhanced specific datatype changes such as `emulate_dates_by_column_name` --- .../oracle_enhanced/column.rb | 17 +----- .../oracle_enhanced/database_statements.rb | 2 +- .../oracle_enhanced_adapter.rb | 55 ++++++++++++++----- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/column.rb b/lib/active_record/connection_adapters/oracle_enhanced/column.rb index fd844813b..b8131df4c 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/column.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/column.rb @@ -2,14 +2,13 @@ module ActiveRecord module ConnectionAdapters #:nodoc: class OracleEnhancedColumn < Column - attr_reader :table_name, :forced_column_type, :nchar, :virtual_column_data_default, :returning_id #:nodoc: + attr_reader :table_name, :nchar, :virtual_column_data_default, :returning_id #:nodoc: FALSE_VALUES << 'N' TRUE_VALUES << 'Y' - def initialize(name, default, cast_type, sql_type = nil, null = true, table_name = nil, forced_column_type = nil, virtual=false, returning_id=false) #:nodoc: + def initialize(name, default, cast_type, sql_type = nil, null = true, table_name = nil, virtual=false, returning_id=false) #:nodoc: @table_name = table_name - @forced_column_type = forced_column_type @virtual = virtual @virtual_column_data_default = default.inspect if virtual @returning_id = returning_id @@ -18,18 +17,6 @@ def initialize(name, default, cast_type, sql_type = nil, null = true, table_name else default_value = self.class.extract_value_from_default(default) end - # TODO: Consider to extract to another method - if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(name, table_name) - cast_type = ActiveRecord::Type::Integer.new - end - - if OracleEnhancedAdapter.emulate_dates_by_column_name && OracleEnhancedAdapter.is_date_column?(name, table_name) - cast_type = ActiveRecord::Type::Date.new - end - - if OracleEnhancedAdapter.emulate_booleans_from_strings && OracleEnhancedAdapter.is_boolean_column?(name, sql_type, table_name) - cast_type = ActiveRecord::Type::Boolean.new - end super(name, default_value, cast_type, sql_type, null) # Is column NCHAR or NVARCHAR2 (will need to use N'...' value quoting for these data types)? # Define only when needed as adapter "quote" method will check at first if instance variable is defined. diff --git a/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb index 3bb5ac37d..ba91a7633 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb @@ -107,7 +107,7 @@ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # def sql_for_insert(sql, pk, id_value, sequence_name, binds) unless id_value || pk.nil? || (defined?(CompositePrimaryKeys) && pk.kind_of?(CompositePrimaryKeys::CompositeKeys)) sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id" - returning_id_col = OracleEnhancedColumn.new("returning_id", nil, Type::Value.new, "number", true, "dual", :integer, true, true) + returning_id_col = new_column("returning_id", nil, Type::Value.new, "number", true, "dual", true, true) (binds = binds.dup) << [returning_id_col, nil] end [sql, binds] diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 0f5b50fa6..94bbfa69b 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1067,27 +1067,57 @@ def columns_without_cache(table_name, name = nil) #:nodoc: row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i end - # TODO: It is just for `set_date_columns` now. Needs to be generic - case get_type_for_column(table_name, oracle_downcase(row['name'])) - when :date - cast_type = Type::Date.new - when :integer - cast_type = Type::Integer.new + # TODO: Consider to extract another method such as `get_cast_type` + case row['sql_type'] + when /decimal|numeric|number/i + if get_type_for_column(table_name, oracle_downcase(row['name'])) == :integer + cast_type = Type::Integer.new + elsif OracleEnhancedAdapter.emulate_booleans && row['sql_type'].upcase == "NUMBER(1)" + cast_type = Type::Boolean.new + elsif OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(row['name'], table_name) + cast_type = Type::Integer.new + else + cast_type = lookup_cast_type(row['sql_type']) + end + when /char/i + if get_type_for_column(table_name, oracle_downcase(row['name'])) == :string + cast_type = Type::String.new + elsif get_type_for_column(table_name, oracle_downcase(row['name'])) == :boolean + cast_type = Type::Boolean.new + elsif OracleEnhancedAdapter.emulate_booleans_from_strings && OracleEnhancedAdapter.is_boolean_column?(row['name'], row['sql_type'], table_name) + cast_type = Type::Boolean.new + else + cast_type = lookup_cast_type(row['sql_type']) + end + when /date/i + if get_type_for_column(table_name, oracle_downcase(row['name'])) == :date + cast_type = Type::Date.new + elsif get_type_for_column(table_name, oracle_downcase(row['name'])) == :datetime + cast_type = Type::DateTime.new + elsif OracleEnhancedAdapter.emulate_dates_by_column_name && OracleEnhancedAdapter.is_date_column?(row['name'], table_name) + cast_type = Type::Date.new + else + cast_type = lookup_cast_type(row['sql_type']) + end else cast_type = lookup_cast_type(row['sql_type']) end - OracleEnhancedColumn.new(oracle_downcase(row['name']), + + new_column(oracle_downcase(row['name']), row['data_default'], cast_type, row['sql_type'], row['nullable'] == 'Y', - # pass table name for table specific column definitions table_name, - # pass column type if specified in class definition - get_type_for_column(table_name, oracle_downcase(row['name'])), is_virtual) + is_virtual, + false ) end end + def new_column(name, default, cast_type, sql_type = nil, null = true, table_name = nil, virtual=false, returning_id=false) + OracleEnhancedColumn.new(name, default, cast_type, sql_type, null, table_name, virtual, returning_id) + end + # used just in tests to clear column cache def clear_columns_cache #:nodoc: @@columns_cache = nil @@ -1225,10 +1255,6 @@ def join_to_update(update, select) #:nodoc: end end - def new_column(name, default, cast_type, sql_type = nil, null = true, table_name = nil, forced_column_type = nil, virtual=false, returning_id=false) - ActiveRecord::ConnectionAdapters::OracleEnhancedColumn.new(name, default, cast_type, sql_type = nil, null = true, table_name = nil, forced_column_type = nil, virtual=false, returning_id=false) - end - protected def initialize_type_map(m) @@ -1252,7 +1278,6 @@ def initialize_type_map(m) Type::Decimal.new(precision: precision, scale: scale) end end - m.alias_type %r(NUMBER\(1\))i, 'boolean' if OracleEnhancedAdapter.emulate_booleans end def translate_exception(exception, message) #:nodoc: From 69235886d3208ecf4fdc1b7b1419511c4b857e08 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 11 May 2015 18:48:59 +0000 Subject: [PATCH 063/114] Modify default to `false` if database default value is "N" --- .../connection_adapters/oracle_enhanced_adapter.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 94bbfa69b..c042d2cda 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1065,6 +1065,8 @@ def columns_without_cache(table_name, name = nil) #:nodoc: # match newlines. row['data_default'].sub!(/^'(.*)'$/m, '\1') row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i + # TODO: Needs better fix to fallback "N" to false + row['data_default'] = false if row['data_default'] == "N" end # TODO: Consider to extract another method such as `get_cast_type` From e3b22e8c1dc659702f0086dcd2a2cc14c28e1b62 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 12 May 2015 01:34:04 +0000 Subject: [PATCH 064/114] Migrate from ActiveRecord::ConnectionAdapters::OracleEnhancedColumnDumper to ActiveRecord::ConnectionAdapters::OracleEnhanced::ColumnDumper --- .../oracle_enhanced/column_dumper.rb | 103 +++++++++--------- .../oracle_enhanced_adapter.rb | 2 + 2 files changed, 51 insertions(+), 54 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/column_dumper.rb b/lib/active_record/connection_adapters/oracle_enhanced/column_dumper.rb index 22dd7e0c0..4448bd847 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/column_dumper.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/column_dumper.rb @@ -1,70 +1,65 @@ module ActiveRecord #:nodoc: module ConnectionAdapters #:nodoc: - module OracleEnhancedColumnDumper #:nodoc: + module OracleEnhanced #:nodoc: + module ColumnDumper #:nodoc: + def self.included(base) #:nodoc: + base.class_eval do + private + alias_method_chain :column_spec, :oracle_enhanced + alias_method_chain :prepare_column_options, :oracle_enhanced + alias_method_chain :migration_keys, :oracle_enhanced - def self.included(base) #:nodoc: - base.class_eval do - private - alias_method_chain :column_spec, :oracle_enhanced - alias_method_chain :prepare_column_options, :oracle_enhanced - alias_method_chain :migration_keys, :oracle_enhanced - - def oracle_enhanced_adapter? - # return original method if not using 'OracleEnhanced' - if (rails_env = defined?(Rails.env) ? Rails.env : (defined?(RAILS_ENV) ? RAILS_ENV : nil)) && - ActiveRecord::Base.configurations[rails_env] && - ActiveRecord::Base.configurations[rails_env]['adapter'] != 'oracle_enhanced' - return false - else - return true + def oracle_enhanced_adapter? + # return original method if not using 'OracleEnhanced' + if (rails_env = defined?(Rails.env) ? Rails.env : (defined?(RAILS_ENV) ? RAILS_ENV : nil)) && + ActiveRecord::Base.configurations[rails_env] && + ActiveRecord::Base.configurations[rails_env]['adapter'] != 'oracle_enhanced' + return false + else + return true + end end end end - end - def column_spec_with_oracle_enhanced(column, types) - # return original method if not using 'OracleEnhanced' - return column_spec_without_oracle_enhanced(column, types) unless oracle_enhanced_adapter? - - spec = prepare_column_options(column, types) - (spec.keys - [:name, :type]).each do |k| - key_s = (k == :virtual_type ? "type: " : "#{k.to_s}: ") - spec[k] = key_s + spec[k] + def column_spec_with_oracle_enhanced(column, types) + # return original method if not using 'OracleEnhanced' + return column_spec_without_oracle_enhanced(column, types) unless oracle_enhanced_adapter? + + spec = prepare_column_options(column, types) + (spec.keys - [:name, :type]).each do |k| + key_s = (k == :virtual_type ? "type: " : "#{k.to_s}: ") + spec[k] = key_s + spec[k] + end + spec end - spec - end - def prepare_column_options_with_oracle_enhanced(column, types) - # return original method if not using 'OracleEnhanced' - return prepare_column_options_without_oracle_enhanced(column, types) unless oracle_enhanced_adapter? + def prepare_column_options_with_oracle_enhanced(column, types) + # return original method if not using 'OracleEnhanced' + return prepare_column_options_without_oracle_enhanced(column, types) unless oracle_enhanced_adapter? - spec = {} - spec[:name] = column.name.inspect - spec[:type] = column.virtual? ? 'virtual' : column.type.to_s - spec[:virtual_type] = column.type.inspect if column.virtual? && column.sql_type != 'NUMBER' - spec[:limit] = column.limit.inspect if column.limit != types[column.type][:limit] && column.type != :decimal - spec[:precision] = column.precision.inspect if !column.precision.nil? - spec[:scale] = column.scale.inspect if !column.scale.nil? - spec[:null] = 'false' if !column.null - spec[:as] = column.virtual_column_data_default if column.virtual? - spec[:default] = schema_default(column) if column.has_default? && !column.virtual? - spec.delete(:default) if spec[:default].nil? - spec - end + spec = {} + spec[:name] = column.name.inspect + spec[:type] = column.virtual? ? 'virtual' : column.type.to_s + spec[:virtual_type] = column.type.inspect if column.virtual? && column.sql_type != 'NUMBER' + spec[:limit] = column.limit.inspect if column.limit != types[column.type][:limit] && column.type != :decimal + spec[:precision] = column.precision.inspect if !column.precision.nil? + spec[:scale] = column.scale.inspect if !column.scale.nil? + spec[:null] = 'false' if !column.null + spec[:as] = column.virtual_column_data_default if column.virtual? + spec[:default] = schema_default(column) if column.has_default? && !column.virtual? + spec.delete(:default) if spec[:default].nil? + spec + end - def migration_keys_with_oracle_enhanced - # TODO `& column_specs.map(&:keys).flatten` should be exetuted here - # return original method if not using 'OracleEnhanced' - return migration_keys_without_oracle_enhanced unless oracle_enhanced_adapter? + def migration_keys_with_oracle_enhanced + # TODO `& column_specs.map(&:keys).flatten` should be exetuted here + # return original method if not using 'OracleEnhanced' + return migration_keys_without_oracle_enhanced unless oracle_enhanced_adapter? - [:name, :limit, :precision, :scale, :default, :null, :as, :virtual_type] + [:name, :limit, :precision, :scale, :default, :null, :as, :virtual_type] + end end - - end end end - -ActiveRecord::ConnectionAdapters::ColumnDumper.class_eval do - include ActiveRecord::ConnectionAdapters::OracleEnhancedColumnDumper -end diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index c042d2cda..763d26d39 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -33,6 +33,7 @@ require 'active_record/connection_adapters/oracle_enhanced/connection' require 'active_record/connection_adapters/oracle_enhanced/database_statements' require 'active_record/connection_adapters/oracle_enhanced/schema_statements' +require 'active_record/connection_adapters/oracle_enhanced/column_dumper' require 'active_record/connection_adapters/oracle_enhanced/column' @@ -223,6 +224,7 @@ class OracleEnhancedAdapter < AbstractAdapter # TODO: Use relative include ActiveRecord::ConnectionAdapters::OracleEnhanced::DatabaseStatements include ActiveRecord::ConnectionAdapters::OracleEnhanced::SchemaStatements + include ActiveRecord::ConnectionAdapters::OracleEnhanced::ColumnDumper def schema_creation OracleEnhanced::SchemaCreation.new self From 13a646a5b98c89d0f2fcd9135f8529db5fd90116 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 15 May 2015 18:19:57 +0000 Subject: [PATCH 065/114] Migrated from OracleEnhancedContextIndex to OracleEnhanced::ContextIndex --- .../oracle_enhanced/context_index.rb | 601 +++++++++--------- .../oracle_enhanced_adapter.rb | 2 + 2 files changed, 301 insertions(+), 302 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb b/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb index dcc91597c..0e406ae0b 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb @@ -1,350 +1,347 @@ module ActiveRecord module ConnectionAdapters - module OracleEnhancedContextIndex + module OracleEnhanced + module ContextIndex - # Define full text index with Oracle specific CONTEXT index type - # - # Oracle CONTEXT index by default supports full text indexing of one column. - # This method allows full text index creation also on several columns - # as well as indexing related table columns by generating stored procedure - # that concatenates all columns for indexing as well as generating trigger - # that will update main index column to trigger reindexing of record. - # - # Use +contains+ ActiveRecord model instance method to add CONTAINS where condition - # and order by score of matched results. - # - # Options: - # - # * :name - # * :index_column - # * :index_column_trigger_on - # * :tablespace - # * :sync - 'MANUAL', 'EVERY "interval-string"' or 'ON COMMIT' (defaults to 'MANUAL'). - # * :lexer - Lexer options (e.g. :type => 'BASIC_LEXER', :base_letter => true). - # * :wordlist - Wordlist options (e.g. :type => 'BASIC_WORDLIST', :prefix_index => true). - # * :transactional - When +true+, the CONTAINS operator will process inserted and updated rows. - # - # ===== Examples - # - # ====== Creating single column index - # add_context_index :posts, :title - # search with - # Post.contains(:title, 'word') - # - # ====== Creating index on several columns - # add_context_index :posts, [:title, :body] - # search with (use first column as argument for contains method but it will search in all index columns) - # Post.contains(:title, 'word') - # - # ====== Creating index on several columns with dummy index column and commit option - # add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT' - # search with - # Post.contains(:all_text, 'word') - # - # ====== Creating index with trigger option (will reindex when specified columns are updated) - # add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT', - # :index_column_trigger_on => [:created_at, :updated_at] - # search with - # Post.contains(:all_text, 'word') - # - # ====== Creating index on multiple tables - # add_context_index :posts, - # [:title, :body, - # # specify aliases always with AS keyword - # "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id" - # ], - # :name => 'post_and_comments_index', - # :index_column => :all_text, :index_column_trigger_on => [:updated_at, :comments_count], - # :sync => 'ON COMMIT' - # search in any table columns - # Post.contains(:all_text, 'word') - # search in specified column - # Post.contains(:all_text, "aaa within title") - # Post.contains(:all_text, "bbb within comment_author") - # - # ====== Creating index using lexer - # add_context_index :posts, :title, :lexer => { :type => 'BASIC_LEXER', :base_letter => true, ... } - # - # ====== Creating index using wordlist - # add_context_index :posts, :title, :wordlist => { :type => 'BASIC_WORDLIST', :prefix_index => true, ... } - # - # ====== Creating transactional index (will reindex changed rows when querying) - # add_context_index :posts, :title, :transactional => true - # - def add_context_index(table_name, column_name, options = {}) - self.all_schema_indexes = nil - column_names = Array(column_name) - index_name = options[:name] || index_name(table_name, :column => options[:index_column] || column_names, - # CONEXT index name max length is 25 - :identifier_max_length => 25) + # Define full text index with Oracle specific CONTEXT index type + # + # Oracle CONTEXT index by default supports full text indexing of one column. + # This method allows full text index creation also on several columns + # as well as indexing related table columns by generating stored procedure + # that concatenates all columns for indexing as well as generating trigger + # that will update main index column to trigger reindexing of record. + # + # Use +contains+ ActiveRecord model instance method to add CONTAINS where condition + # and order by score of matched results. + # + # Options: + # + # * :name + # * :index_column + # * :index_column_trigger_on + # * :tablespace + # * :sync - 'MANUAL', 'EVERY "interval-string"' or 'ON COMMIT' (defaults to 'MANUAL'). + # * :lexer - Lexer options (e.g. :type => 'BASIC_LEXER', :base_letter => true). + # * :wordlist - Wordlist options (e.g. :type => 'BASIC_WORDLIST', :prefix_index => true). + # * :transactional - When +true+, the CONTAINS operator will process inserted and updated rows. + # + # ===== Examples + # + # ====== Creating single column index + # add_context_index :posts, :title + # search with + # Post.contains(:title, 'word') + # + # ====== Creating index on several columns + # add_context_index :posts, [:title, :body] + # search with (use first column as argument for contains method but it will search in all index columns) + # Post.contains(:title, 'word') + # + # ====== Creating index on several columns with dummy index column and commit option + # add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT' + # search with + # Post.contains(:all_text, 'word') + # + # ====== Creating index with trigger option (will reindex when specified columns are updated) + # add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT', + # :index_column_trigger_on => [:created_at, :updated_at] + # search with + # Post.contains(:all_text, 'word') + # + # ====== Creating index on multiple tables + # add_context_index :posts, + # [:title, :body, + # # specify aliases always with AS keyword + # "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id" + # ], + # :name => 'post_and_comments_index', + # :index_column => :all_text, :index_column_trigger_on => [:updated_at, :comments_count], + # :sync => 'ON COMMIT' + # search in any table columns + # Post.contains(:all_text, 'word') + # search in specified column + # Post.contains(:all_text, "aaa within title") + # Post.contains(:all_text, "bbb within comment_author") + # + # ====== Creating index using lexer + # add_context_index :posts, :title, :lexer => { :type => 'BASIC_LEXER', :base_letter => true, ... } + # + # ====== Creating index using wordlist + # add_context_index :posts, :title, :wordlist => { :type => 'BASIC_WORDLIST', :prefix_index => true, ... } + # + # ====== Creating transactional index (will reindex changed rows when querying) + # add_context_index :posts, :title, :transactional => true + # + def add_context_index(table_name, column_name, options = {}) + self.all_schema_indexes = nil + column_names = Array(column_name) + index_name = options[:name] || index_name(table_name, :column => options[:index_column] || column_names, + # CONEXT index name max length is 25 + :identifier_max_length => 25) - quoted_column_name = quote_column_name(options[:index_column] || column_names.first) - if options[:index_column_trigger_on] - raise ArgumentError, "Option :index_column should be specified together with :index_column_trigger_on option" \ - unless options[:index_column] - create_index_column_trigger(table_name, index_name, options[:index_column], options[:index_column_trigger_on]) - end + quoted_column_name = quote_column_name(options[:index_column] || column_names.first) + if options[:index_column_trigger_on] + raise ArgumentError, "Option :index_column should be specified together with :index_column_trigger_on option" \ + unless options[:index_column] + create_index_column_trigger(table_name, index_name, options[:index_column], options[:index_column_trigger_on]) + end - sql = "CREATE INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}" - sql << " (#{quoted_column_name})" - sql << " INDEXTYPE IS CTXSYS.CONTEXT" - parameters = [] - if column_names.size > 1 - procedure_name = default_datastore_procedure(index_name) - datastore_name = default_datastore_name(index_name) - create_datastore_procedure(table_name, procedure_name, column_names, options) - create_datastore_preference(datastore_name, procedure_name) - parameters << "DATASTORE #{datastore_name} SECTION GROUP CTXSYS.AUTO_SECTION_GROUP" - end - if options[:tablespace] - storage_name = default_storage_name(index_name) - create_storage_preference(storage_name, options[:tablespace]) - parameters << "STORAGE #{storage_name}" - end - if options[:sync] - parameters << "SYNC(#{options[:sync]})" - end - if options[:lexer] && (lexer_type = options[:lexer][:type]) - lexer_name = default_lexer_name(index_name) - (lexer_options = options[:lexer].dup).delete(:type) - create_lexer_preference(lexer_name, lexer_type, lexer_options) - parameters << "LEXER #{lexer_name}" - end - if options[:wordlist] && (wordlist_type = options[:wordlist][:type]) - wordlist_name = default_wordlist_name(index_name) - (wordlist_options = options[:wordlist].dup).delete(:type) - create_wordlist_preference(wordlist_name, wordlist_type, wordlist_options) - parameters << "WORDLIST #{wordlist_name}" - end - if options[:transactional] - parameters << "TRANSACTIONAL" - end - unless parameters.empty? - sql << " PARAMETERS ('#{parameters.join(' ')}')" + sql = "CREATE INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}" + sql << " (#{quoted_column_name})" + sql << " INDEXTYPE IS CTXSYS.CONTEXT" + parameters = [] + if column_names.size > 1 + procedure_name = default_datastore_procedure(index_name) + datastore_name = default_datastore_name(index_name) + create_datastore_procedure(table_name, procedure_name, column_names, options) + create_datastore_preference(datastore_name, procedure_name) + parameters << "DATASTORE #{datastore_name} SECTION GROUP CTXSYS.AUTO_SECTION_GROUP" + end + if options[:tablespace] + storage_name = default_storage_name(index_name) + create_storage_preference(storage_name, options[:tablespace]) + parameters << "STORAGE #{storage_name}" + end + if options[:sync] + parameters << "SYNC(#{options[:sync]})" + end + if options[:lexer] && (lexer_type = options[:lexer][:type]) + lexer_name = default_lexer_name(index_name) + (lexer_options = options[:lexer].dup).delete(:type) + create_lexer_preference(lexer_name, lexer_type, lexer_options) + parameters << "LEXER #{lexer_name}" + end + if options[:wordlist] && (wordlist_type = options[:wordlist][:type]) + wordlist_name = default_wordlist_name(index_name) + (wordlist_options = options[:wordlist].dup).delete(:type) + create_wordlist_preference(wordlist_name, wordlist_type, wordlist_options) + parameters << "WORDLIST #{wordlist_name}" + end + if options[:transactional] + parameters << "TRANSACTIONAL" + end + unless parameters.empty? + sql << " PARAMETERS ('#{parameters.join(' ')}')" + end + execute sql end - execute sql - end - # Drop full text index with Oracle specific CONTEXT index type - def remove_context_index(table_name, options = {}) - self.all_schema_indexes = nil - unless Hash === options # if column names passed as argument - options = {:column => Array(options)} + # Drop full text index with Oracle specific CONTEXT index type + def remove_context_index(table_name, options = {}) + self.all_schema_indexes = nil + unless Hash === options # if column names passed as argument + options = {:column => Array(options)} + end + index_name = options[:name] || index_name(table_name, + :column => options[:index_column] || options[:column], :identifier_max_length => 25) + execute "DROP INDEX #{index_name}" + drop_ctx_preference(default_datastore_name(index_name)) + drop_ctx_preference(default_storage_name(index_name)) + procedure_name = default_datastore_procedure(index_name) + execute "DROP PROCEDURE #{quote_table_name(procedure_name)}" rescue nil + drop_index_column_trigger(index_name) end - index_name = options[:name] || index_name(table_name, - :column => options[:index_column] || options[:column], :identifier_max_length => 25) - execute "DROP INDEX #{index_name}" - drop_ctx_preference(default_datastore_name(index_name)) - drop_ctx_preference(default_storage_name(index_name)) - procedure_name = default_datastore_procedure(index_name) - execute "DROP PROCEDURE #{quote_table_name(procedure_name)}" rescue nil - drop_index_column_trigger(index_name) - end - private + private - def create_datastore_procedure(table_name, procedure_name, column_names, options) - quoted_table_name = quote_table_name(table_name) - select_queries, column_names = column_names.partition { |c| c.to_s =~ /^\s*SELECT\s+/i } - select_queries = select_queries.map { |s| s.strip.gsub(/\s+/, ' ') } - keys, selected_columns = parse_select_queries(select_queries) - quoted_column_names = (column_names+keys).map{|col| quote_column_name(col)} - execute compress_lines(<<-SQL) - CREATE OR REPLACE PROCEDURE #{quote_table_name(procedure_name)} - (p_rowid IN ROWID, - p_clob IN OUT NOCOPY CLOB) IS - -- add_context_index_parameters #{(column_names+select_queries).inspect}#{!options.empty? ? ', ' << options.inspect[1..-2] : ''} - #{ - selected_columns.map do |cols| - cols.map do |col| - raise ArgumentError, "Alias #{col} too large, should be 28 or less characters long" unless col.length <= 28 - "l_#{col} VARCHAR2(32767);\n" - end.join - end.join - } BEGIN - FOR r1 IN ( - SELECT #{quoted_column_names.join(', ')} - FROM #{quoted_table_name} - WHERE #{quoted_table_name}.ROWID = p_rowid - ) LOOP + def create_datastore_procedure(table_name, procedure_name, column_names, options) + quoted_table_name = quote_table_name(table_name) + select_queries, column_names = column_names.partition { |c| c.to_s =~ /^\s*SELECT\s+/i } + select_queries = select_queries.map { |s| s.strip.gsub(/\s+/, ' ') } + keys, selected_columns = parse_select_queries(select_queries) + quoted_column_names = (column_names+keys).map{|col| quote_column_name(col)} + execute compress_lines(<<-SQL) + CREATE OR REPLACE PROCEDURE #{quote_table_name(procedure_name)} + (p_rowid IN ROWID, + p_clob IN OUT NOCOPY CLOB) IS + -- add_context_index_parameters #{(column_names+select_queries).inspect}#{!options.empty? ? ', ' << options.inspect[1..-2] : ''} #{ - (column_names.map do |col| - col = col.to_s - "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+2}, '<#{col}>');\n" << - "IF LENGTH(r1.#{col}) > 0 THEN\n" << - "DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(r1.#{col}), r1.#{col});\n" << - "END IF;\n" << - "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+3}, '');\n" - end.join) << - (selected_columns.zip(select_queries).map do |cols, query| - (cols.map do |col| - "l_#{col} := '';\n" - end.join) << - "FOR r2 IN (\n" << - query.gsub(/:(\w+)/,"r1.\\1") << "\n) LOOP\n" << - (cols.map do |col| - "l_#{col} := l_#{col} || r2.#{col} || CHR(10);\n" - end.join) << - "END LOOP;\n" << - (cols.map do |col| + selected_columns.map do |cols| + cols.map do |col| + raise ArgumentError, "Alias #{col} too large, should be 28 or less characters long" unless col.length <= 28 + "l_#{col} VARCHAR2(32767);\n" + end.join + end.join + } BEGIN + FOR r1 IN ( + SELECT #{quoted_column_names.join(', ')} + FROM #{quoted_table_name} + WHERE #{quoted_table_name}.ROWID = p_rowid + ) LOOP + #{ + (column_names.map do |col| col = col.to_s "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+2}, '<#{col}>');\n" << - "IF LENGTH(l_#{col}) > 0 THEN\n" << - "DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(l_#{col}), l_#{col});\n" << + "IF LENGTH(r1.#{col}) > 0 THEN\n" << + "DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(r1.#{col}), r1.#{col});\n" << "END IF;\n" << "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+3}, '');\n" + end.join) << + (selected_columns.zip(select_queries).map do |cols, query| + (cols.map do |col| + "l_#{col} := '';\n" + end.join) << + "FOR r2 IN (\n" << + query.gsub(/:(\w+)/,"r1.\\1") << "\n) LOOP\n" << + (cols.map do |col| + "l_#{col} := l_#{col} || r2.#{col} || CHR(10);\n" + end.join) << + "END LOOP;\n" << + (cols.map do |col| + col = col.to_s + "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+2}, '<#{col}>');\n" << + "IF LENGTH(l_#{col}) > 0 THEN\n" << + "DBMS_LOB.WRITEAPPEND(p_clob, LENGTH(l_#{col}), l_#{col});\n" << + "END IF;\n" << + "DBMS_LOB.WRITEAPPEND(p_clob, #{col.length+3}, '');\n" + end.join) end.join) - end.join) - } - END LOOP; - END; - SQL - end + } + END LOOP; + END; + SQL + end - def parse_select_queries(select_queries) - keys = [] - selected_columns = [] - select_queries.each do |query| - # get primary or foreign keys like :id or :something_id - keys << (query.scan(/:\w+/).map{|k| k[1..-1].downcase.to_sym}) - select_part = query.scan(/^select\s.*\sfrom/i).first - selected_columns << select_part.scan(/\sas\s+(\w+)/i).map{|c| c.first} + def parse_select_queries(select_queries) + keys = [] + selected_columns = [] + select_queries.each do |query| + # get primary or foreign keys like :id or :something_id + keys << (query.scan(/:\w+/).map{|k| k[1..-1].downcase.to_sym}) + select_part = query.scan(/^select\s.*\sfrom/i).first + selected_columns << select_part.scan(/\sas\s+(\w+)/i).map{|c| c.first} + end + [keys.flatten.uniq, selected_columns] end - [keys.flatten.uniq, selected_columns] - end - def create_datastore_preference(datastore_name, procedure_name) - drop_ctx_preference(datastore_name) - execute <<-SQL - BEGIN - CTX_DDL.CREATE_PREFERENCE('#{datastore_name}', 'USER_DATASTORE'); - CTX_DDL.SET_ATTRIBUTE('#{datastore_name}', 'PROCEDURE', '#{procedure_name}'); - END; - SQL - end + def create_datastore_preference(datastore_name, procedure_name) + drop_ctx_preference(datastore_name) + execute <<-SQL + BEGIN + CTX_DDL.CREATE_PREFERENCE('#{datastore_name}', 'USER_DATASTORE'); + CTX_DDL.SET_ATTRIBUTE('#{datastore_name}', 'PROCEDURE', '#{procedure_name}'); + END; + SQL + end - def create_storage_preference(storage_name, tablespace) - drop_ctx_preference(storage_name) - sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{storage_name}', 'BASIC_STORAGE');\n" - ['I_TABLE_CLAUSE', 'K_TABLE_CLAUSE', 'R_TABLE_CLAUSE', - 'N_TABLE_CLAUSE', 'I_INDEX_CLAUSE', 'P_TABLE_CLAUSE'].each do |clause| - default_clause = case clause - when 'R_TABLE_CLAUSE'; 'LOB(DATA) STORE AS (CACHE) ' - when 'I_INDEX_CLAUSE'; 'COMPRESS 2 ' - else '' + def create_storage_preference(storage_name, tablespace) + drop_ctx_preference(storage_name) + sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{storage_name}', 'BASIC_STORAGE');\n" + ['I_TABLE_CLAUSE', 'K_TABLE_CLAUSE', 'R_TABLE_CLAUSE', + 'N_TABLE_CLAUSE', 'I_INDEX_CLAUSE', 'P_TABLE_CLAUSE'].each do |clause| + default_clause = case clause + when 'R_TABLE_CLAUSE'; 'LOB(DATA) STORE AS (CACHE) ' + when 'I_INDEX_CLAUSE'; 'COMPRESS 2 ' + else '' + end + sql << "CTX_DDL.SET_ATTRIBUTE('#{storage_name}', '#{clause}', '#{default_clause}TABLESPACE #{tablespace}');\n" end - sql << "CTX_DDL.SET_ATTRIBUTE('#{storage_name}', '#{clause}', '#{default_clause}TABLESPACE #{tablespace}');\n" + sql << "END;\n" + execute sql end - sql << "END;\n" - execute sql - end - def create_lexer_preference(lexer_name, lexer_type, options) - drop_ctx_preference(lexer_name) - sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{lexer_name}', '#{lexer_type}');\n" - options.each do |key, value| - plsql_value = case value - when String; "'#{value}'" - when true; "'YES'" - when false; "'NO'" - when nil; 'NULL' - else value + def create_lexer_preference(lexer_name, lexer_type, options) + drop_ctx_preference(lexer_name) + sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{lexer_name}', '#{lexer_type}');\n" + options.each do |key, value| + plsql_value = case value + when String; "'#{value}'" + when true; "'YES'" + when false; "'NO'" + when nil; 'NULL' + else value + end + sql << "CTX_DDL.SET_ATTRIBUTE('#{lexer_name}', '#{key}', #{plsql_value});\n" end - sql << "CTX_DDL.SET_ATTRIBUTE('#{lexer_name}', '#{key}', #{plsql_value});\n" + sql << "END;\n" + execute sql end - sql << "END;\n" - execute sql - end - def create_wordlist_preference(wordlist_name, wordlist_type, options) - drop_ctx_preference(wordlist_name) - sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{wordlist_name}', '#{wordlist_type}');\n" - options.each do |key, value| - plsql_value = case value - when String; "'#{value}'" - when true; "'YES'" - when false; "'NO'" - when nil; 'NULL' - else value + def create_wordlist_preference(wordlist_name, wordlist_type, options) + drop_ctx_preference(wordlist_name) + sql = "BEGIN\nCTX_DDL.CREATE_PREFERENCE('#{wordlist_name}', '#{wordlist_type}');\n" + options.each do |key, value| + plsql_value = case value + when String; "'#{value}'" + when true; "'YES'" + when false; "'NO'" + when nil; 'NULL' + else value + end + sql << "CTX_DDL.SET_ATTRIBUTE('#{wordlist_name}', '#{key}', #{plsql_value});\n" end - sql << "CTX_DDL.SET_ATTRIBUTE('#{wordlist_name}', '#{key}', #{plsql_value});\n" + sql << "END;\n" + execute sql end - sql << "END;\n" - execute sql - end - def drop_ctx_preference(preference_name) - execute "BEGIN CTX_DDL.DROP_PREFERENCE('#{preference_name}'); END;" rescue nil - end + def drop_ctx_preference(preference_name) + execute "BEGIN CTX_DDL.DROP_PREFERENCE('#{preference_name}'); END;" rescue nil + end - def create_index_column_trigger(table_name, index_name, index_column, index_column_source) - trigger_name = default_index_column_trigger_name(index_name) - columns = Array(index_column_source) - quoted_column_names = columns.map{|col| quote_column_name(col)}.join(', ') - execute compress_lines(<<-SQL) - CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)} - BEFORE UPDATE OF #{quoted_column_names} ON #{quote_table_name(table_name)} FOR EACH ROW - BEGIN - :new.#{quote_column_name(index_column)} := '1'; - END; - SQL - end + def create_index_column_trigger(table_name, index_name, index_column, index_column_source) + trigger_name = default_index_column_trigger_name(index_name) + columns = Array(index_column_source) + quoted_column_names = columns.map{|col| quote_column_name(col)}.join(', ') + execute compress_lines(<<-SQL) + CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)} + BEFORE UPDATE OF #{quoted_column_names} ON #{quote_table_name(table_name)} FOR EACH ROW + BEGIN + :new.#{quote_column_name(index_column)} := '1'; + END; + SQL + end - def drop_index_column_trigger(index_name) - trigger_name = default_index_column_trigger_name(index_name) - execute "DROP TRIGGER #{quote_table_name(trigger_name)}" rescue nil - end + def drop_index_column_trigger(index_name) + trigger_name = default_index_column_trigger_name(index_name) + execute "DROP TRIGGER #{quote_table_name(trigger_name)}" rescue nil + end - def default_datastore_procedure(index_name) - "#{index_name}_prc" - end + def default_datastore_procedure(index_name) + "#{index_name}_prc" + end - def default_datastore_name(index_name) - "#{index_name}_dst" - end + def default_datastore_name(index_name) + "#{index_name}_dst" + end - def default_storage_name(index_name) - "#{index_name}_sto" - end + def default_storage_name(index_name) + "#{index_name}_sto" + end - def default_index_column_trigger_name(index_name) - "#{index_name}_trg" - end + def default_index_column_trigger_name(index_name) + "#{index_name}_trg" + end - def default_lexer_name(index_name) - "#{index_name}_lex" - end + def default_lexer_name(index_name) + "#{index_name}_lex" + end - def default_wordlist_name(index_name) - "#{index_name}_wl" - end + def default_wordlist_name(index_name) + "#{index_name}_wl" + end - module BaseClassMethods - # Declare that model table has context index defined. - # As a result contains class scope method is defined. - def has_context_index - extend ContextIndexClassMethods + module BaseClassMethods + # Declare that model table has context index defined. + # As a result contains class scope method is defined. + def has_context_index + extend ContextIndexClassMethods + end end - end - module ContextIndexClassMethods - # Add context index condition. - def contains(column, query, options ={}) - score_label = options[:label].to_i || 1 - where("CONTAINS(#{connection.quote_column_name(column)}, ?, #{score_label}) > 0", query). - order("SCORE(#{score_label}) DESC") + module ContextIndexClassMethods + # Add context index condition. + def contains(column, query, options ={}) + score_label = options[:label].to_i || 1 + where("CONTAINS(#{connection.quote_column_name(column)}, ?, #{score_label}) > 0", query). + order("SCORE(#{score_label}) DESC") + end end - end + end end - end end -ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do - include ActiveRecord::ConnectionAdapters::OracleEnhancedContextIndex -end - ActiveRecord::Base.class_eval do - extend ActiveRecord::ConnectionAdapters::OracleEnhancedContextIndex::BaseClassMethods + extend ActiveRecord::ConnectionAdapters::OracleEnhanced::ContextIndex::BaseClassMethods end diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 763d26d39..e2b23fcc0 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -34,6 +34,7 @@ require 'active_record/connection_adapters/oracle_enhanced/database_statements' require 'active_record/connection_adapters/oracle_enhanced/schema_statements' require 'active_record/connection_adapters/oracle_enhanced/column_dumper' +require 'active_record/connection_adapters/oracle_enhanced/context_index' require 'active_record/connection_adapters/oracle_enhanced/column' @@ -225,6 +226,7 @@ class OracleEnhancedAdapter < AbstractAdapter include ActiveRecord::ConnectionAdapters::OracleEnhanced::DatabaseStatements include ActiveRecord::ConnectionAdapters::OracleEnhanced::SchemaStatements include ActiveRecord::ConnectionAdapters::OracleEnhanced::ColumnDumper + include ActiveRecord::ConnectionAdapters::OracleEnhanced::ContextIndex def schema_creation OracleEnhanced::SchemaCreation.new self From 737d209b5ff8ae66328fdecb0f0545195013b340 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 15 May 2015 00:33:52 +0000 Subject: [PATCH 066/114] Make OracleEnhancedIndexDefinition as subclass of ActiveRecord::ConnectionAdapters::IndexDefinition --- .../oracle_enhanced/schema_definitions.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb index 14628cc0f..7bfc52e2c 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb @@ -15,8 +15,20 @@ class OracleEnhancedForeignKeyDefinition < ForeignKeyDefinition class OracleEnhancedSynonymDefinition < Struct.new(:name, :table_owner, :table_name, :db_link) #:nodoc: end - class OracleEnhancedIndexDefinition < Struct.new(:table, :name, :unique, :type, :parameters, :statement_parameters, - :tablespace, :columns) #:nodoc: + class OracleEnhancedIndexDefinition < IndexDefinition + attr_accessor :table, :name, :unique, :type, :parameters, :statement_parameters, :tablespace, :columns + + def initialize(table, name, unique, type, parameters, statement_parameters, tablespace, columns) + @table = table + @name = name + @unique = unique + @type = type + @parameters = parameters + @statement_parameters = statement_parameters + @tablespace = tablespace + @columns = columns + super(table, name, unique, columns, nil, nil, nil, nil) + end end module OracleEnhancedSchemaDefinitions #:nodoc: From bfc760b95a74e09ca51224f0fbe32c156affca3c Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 22 May 2015 12:37:34 +0000 Subject: [PATCH 067/114] Refactor add_index and add_index_options --- .../oracle_enhanced/schema_statements.rb | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb index 23a2fc53e..0d788eea0 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb @@ -154,32 +154,25 @@ def initialize_schema_migrations_table end end - # clear cached indexes when adding new index def add_index(table_name, column_name, options = {}) #:nodoc: + index_name, index_type, quoted_column_names, tablespace, index_options = add_index_options(table_name, column_name, options) + execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{index_options}" + ensure + self.all_schema_indexes = nil + end + + def add_index_options(table_name, column_name, options = {}) #:nodoc: column_names = Array(column_name) index_name = index_name(table_name, column: column_names) - if Hash === options # legacy support, since this param was a string - options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :tablespace, :options, :using) + options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :tablespace, :options, :using) - index_type = options[:unique] ? "UNIQUE" : "" - index_name = options[:name].to_s if options.key?(:name) - tablespace = tablespace_for(:index, options[:tablespace]) - max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length - additional_options = options[:options] - else - if options - message = "Passing a string as third argument of `add_index` is deprecated and will" + - " be removed in Rails 4.1." + - " Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead" - - ActiveSupport::Deprecation.warn message - end - - index_type = options - additional_options = nil - max_index_length = allowed_index_name_length - end + index_type = options[:unique] ? "UNIQUE" : "" + index_name = options[:name].to_s if options.key?(:name) + tablespace = tablespace_for(:index, options[:tablespace]) + max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length + #TODO: This option is used for NOLOGGING, needs better argumetn name + index_options = options[:options] if index_name.to_s.length > max_index_length raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters" @@ -187,11 +180,9 @@ def add_index(table_name, column_name, options = {}) #:nodoc: if index_name_exists?(table_name, index_name, false) raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" end - quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ") - execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{additional_options}" - ensure - self.all_schema_indexes = nil + quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ") + [index_name, index_type, quoted_column_names, tablespace, index_options] end # Remove the given index from the table. From b79ebc964b92f4a449e6ceeeff1191472bcb6199 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 27 May 2015 13:00:51 +0000 Subject: [PATCH 068/114] Move types under OracleEnhanced module to prepare implementing Oracle enhanced own behavior `ActiveRecord::Type::Timestamp` to `ActiveRecord::OracleEnhanced::Type::Timestamp` `ActiveRecord::Type::Raw` to `ActiveRecord::OracleEnhanced::Type::Raw` --- .../connection_adapters/oracle_enhanced_adapter.rb | 10 +++++----- lib/active_record/oracle_enhanced/type/raw.rb | 13 +++++++++++++ lib/active_record/oracle_enhanced/type/timestamp.rb | 11 +++++++++++ lib/active_record/type/raw.rb | 11 ----------- lib/active_record/type/timestamp.rb | 9 --------- 5 files changed, 29 insertions(+), 25 deletions(-) create mode 100644 lib/active_record/oracle_enhanced/type/raw.rb create mode 100644 lib/active_record/oracle_enhanced/type/timestamp.rb delete mode 100644 lib/active_record/type/raw.rb delete mode 100644 lib/active_record/type/timestamp.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index e2b23fcc0..5c9cfbbf2 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1266,9 +1266,9 @@ def join_to_update(update, select) #:nodoc: def initialize_type_map(m) super # oracle - register_class_with_limit m, %r(date)i, Type::DateTime - register_class_with_limit m, %r(raw)i, Type::Raw - register_class_with_limit m, %r(timestamp)i, Type::Timestamp + register_class_with_limit m, %r(date)i, Type::DateTime + register_class_with_limit m, %r(raw)i, ActiveRecord::OracleEnhanced::Type::Raw + register_class_with_limit m, %r(timestamp)i, ActiveRecord::OracleEnhanced::Type::Timestamp m.register_type(%r(NUMBER)i) do |sql_type| scale = extract_scale(sql_type) @@ -1405,7 +1405,7 @@ module ActiveRecord require 'active_record/connection_adapters/oracle_enhanced/database_statements' # Add Type:Raw -require 'active_record/type/raw' +require 'active_record/oracle_enhanced/type/raw' # Add Type:Timestamp -require 'active_record/type/timestamp' +require 'active_record/oracle_enhanced/type/timestamp' diff --git a/lib/active_record/oracle_enhanced/type/raw.rb b/lib/active_record/oracle_enhanced/type/raw.rb new file mode 100644 index 000000000..c27aba6e3 --- /dev/null +++ b/lib/active_record/oracle_enhanced/type/raw.rb @@ -0,0 +1,13 @@ +require 'active_record/type/string' + +module ActiveRecord + module OracleEnhanced + module Type + class Raw < ActiveRecord::Type::String # :nodoc: + def type + :raw + end + end + end + end +end diff --git a/lib/active_record/oracle_enhanced/type/timestamp.rb b/lib/active_record/oracle_enhanced/type/timestamp.rb new file mode 100644 index 000000000..020ceaf98 --- /dev/null +++ b/lib/active_record/oracle_enhanced/type/timestamp.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module OracleEnhanced + module Type + class Timestamp < ActiveRecord::Type::Value # :nodoc: + def type + :timestamp + end + end + end + end +end diff --git a/lib/active_record/type/raw.rb b/lib/active_record/type/raw.rb deleted file mode 100644 index 756578173..000000000 --- a/lib/active_record/type/raw.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'active_record/type/string' - -module ActiveRecord - module Type - class Raw < String # :nodoc: - def type - :raw - end - end - end -end diff --git a/lib/active_record/type/timestamp.rb b/lib/active_record/type/timestamp.rb deleted file mode 100644 index 7e5405da1..000000000 --- a/lib/active_record/type/timestamp.rb +++ /dev/null @@ -1,9 +0,0 @@ -module ActiveRecord - module Type - class Timestamp < Value # :nodoc: - def type - :timestamp - end - end - end -end From 1542638bbc99ccbca7c78817b3cb43c1c18c8779 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 27 May 2015 16:15:11 +0000 Subject: [PATCH 069/114] ActiveRecord::OracleEnhanced::Type::Integer for max_value to take 38 digits Oracle enhanced adapter implements NUMBER(p,0) sql_type as a Integer. Then NUMBER(p,0), p as precision can take equal to 38. This value range is larget than ActiveRecord::Type::Integer max_value --- .../oracle_enhanced_adapter.rb | 22 ++++++++++++------- .../oracle_enhanced/type/integer.rb | 13 +++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 lib/active_record/oracle_enhanced/type/integer.rb diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 5c9cfbbf2..2e49a52be 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -1077,11 +1077,11 @@ def columns_without_cache(table_name, name = nil) #:nodoc: case row['sql_type'] when /decimal|numeric|number/i if get_type_for_column(table_name, oracle_downcase(row['name'])) == :integer - cast_type = Type::Integer.new + cast_type = ActiveRecord::OracleEnhanced::Type::Integer.new elsif OracleEnhancedAdapter.emulate_booleans && row['sql_type'].upcase == "NUMBER(1)" cast_type = Type::Boolean.new elsif OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(row['name'], table_name) - cast_type = Type::Integer.new + cast_type = ActiveRecord::OracleEnhanced::Type::Integer.new else cast_type = lookup_cast_type(row['sql_type']) end @@ -1273,19 +1273,22 @@ def initialize_type_map(m) m.register_type(%r(NUMBER)i) do |sql_type| scale = extract_scale(sql_type) precision = extract_precision(sql_type) + limit = extract_limit(sql_type) if scale == 0 - limit = case - when precision <= 9 then 4 - when precision <= 19 then 8 - else 16 - end - Type::Integer.new(precision: precision, limit: limit) + ActiveRecord::OracleEnhanced::Type::Integer.new(precision: precision, limit: limit) else Type::Decimal.new(precision: precision, scale: scale) end end end + def extract_limit(sql_type) #:nodoc: + case sql_type + when /\((.*)\)/ + $1.to_i + end + end + def translate_exception(exception, message) #:nodoc: case @connection.error_code(exception) when 1 @@ -1409,3 +1412,6 @@ module ActiveRecord # Add Type:Timestamp require 'active_record/oracle_enhanced/type/timestamp' + +# Add OracleEnhanced::Type::Integer +require 'active_record/oracle_enhanced/type/integer' diff --git a/lib/active_record/oracle_enhanced/type/integer.rb b/lib/active_record/oracle_enhanced/type/integer.rb new file mode 100644 index 000000000..c4c82e722 --- /dev/null +++ b/lib/active_record/oracle_enhanced/type/integer.rb @@ -0,0 +1,13 @@ +module ActiveRecord + module OracleEnhanced + module Type + class Integer < ActiveRecord::Type::Integer # :nodoc: + private + + def max_value + ("9"*38).to_i + end + end + end + end +end From 6c14612e5d77a27bdaa6711da7a6573610fbe21b Mon Sep 17 00:00:00 2001 From: Eric Guo Date: Fri, 29 May 2015 09:23:04 +0800 Subject: [PATCH 070/114] refer correct location if filess in gemspec --- activerecord-oracle_enhanced-adapter.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index 531a0de07..b3541c92e 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -48,8 +48,8 @@ This adapter is superset of original ActiveRecord Oracle adapter. "lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb", "lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb", "lib/active_record/connection_adapters/oracle_enhanced/version.rb", - "lib/active_record/type/timestamp.rb", - "lib/active_record/type/raw.rb", + "lib/active_record/oracle_enhanced/type/timestamp.rb", + "lib/active_record/oracle_enhanced/type/raw.rb", "lib/activerecord-oracle_enhanced-adapter.rb", "spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb", "spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb", From 9026aa8531530d876cadad4d7f2f8af03c28736f Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 29 May 2015 16:53:38 +0000 Subject: [PATCH 071/114] Add integer.rb to gemspec --- activerecord-oracle_enhanced-adapter.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index b3541c92e..533820a34 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -48,6 +48,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. "lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb", "lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb", "lib/active_record/connection_adapters/oracle_enhanced/version.rb", + "lib/active_record/oracle_enhanced/type/integer.rb", "lib/active_record/oracle_enhanced/type/timestamp.rb", "lib/active_record/oracle_enhanced/type/raw.rb", "lib/activerecord-oracle_enhanced-adapter.rb", From cd8d7cf3cefa9f220a7a50f045b137d36c412f35 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 29 May 2015 12:08:02 +0000 Subject: [PATCH 072/114] Map :bigint as NUMBER(19) sql_type not NUMBER(8) by specifying `:limit => 19` Bigint should be mapped to NUMBER(19,0) not NUMBER(8,0) since it is not enough to store the maximum number of bigint. Oracle NUMBER(p,0) as handled as integer because there is no dedicated integer sql data type exist in Oracle database. Also NUMBER(p,s) precision can take up to 38. p means the number of digits, not the byte length. bigint type needs 19 digits as follows. ```ruby $ irb 2.2.2 :001 > limit = 8 => 8 2.2.2 :002 > maxvalue_of_bigint = 1 << ( limit * 8 - 1) => 9223372036854775808 2.2.2 :003 > puts maxvalue_of_bigint.to_s.length 19 => nil 2.2.2 :004 > ``` --- .../connection_adapters/oracle_enhanced_adapter.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 2e49a52be..951516924 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -472,7 +472,7 @@ def supports_views? :binary => { :name => "BLOB" }, :boolean => { :name => "NUMBER", :limit => 1 }, :raw => { :name => "RAW", :limit => 2000 }, - :bigint => { :name => "NUMBER", :limit => 8 } + :bigint => { :name => "NUMBER", :limit => 19 } } # if emulate_booleans_from_strings then store booleans in VARCHAR2 NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge( @@ -1284,6 +1284,8 @@ def initialize_type_map(m) def extract_limit(sql_type) #:nodoc: case sql_type + when /^bigint/i + 19 when /\((.*)\)/ $1.to_i end From de641c1bc95b56cd9ded1d73fb482cbfc39293a9 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 12 May 2015 19:43:13 +0000 Subject: [PATCH 073/114] Use Oracle BINARY_FLOAT datatype for Rails :float type --- .../oracle_enhanced_adapter.rb | 2 +- .../oracle_enhanced_data_types_spec.rb | 42 +++++++++++++++++++ .../oracle_enhanced_schema_dump_spec.rb | 20 +++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 951516924..f32f32c94 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -461,7 +461,7 @@ def supports_views? :string => { :name => "VARCHAR2", :limit => 255 }, :text => { :name => "CLOB" }, :integer => { :name => "NUMBER", :limit => 38 }, - :float => { :name => "NUMBER" }, + :float => { :name => "BINARY_FLOAT" }, :decimal => { :name => "DECIMAL" }, :datetime => { :name => "DATE" }, # changed to native TIMESTAMP type diff --git a/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb index 0bd336cab..22788e5d7 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb @@ -1401,3 +1401,45 @@ class ::TestItem < ActiveRecord::Base end end + +describe "OracleEnhancedAdapter handling of BINARY_FLOAT columns" do + before(:all) do + ActiveRecord::Base.establish_connection(CONNECTION_PARAMS) + @conn = ActiveRecord::Base.connection + @conn.execute "DROP TABLE test2_employees" rescue nil + @conn.execute <<-SQL + CREATE TABLE test2_employees ( + id NUMBER PRIMARY KEY, + 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), + hourly_rate BINARY_FLOAT, + manager_id NUMBER(6), + is_manager NUMBER(1), + department_id NUMBER(4,0), + created_at DATE + ) + SQL + @conn.execute "DROP SEQUENCE test2_employees_seq" rescue nil + @conn.execute <<-SQL + CREATE SEQUENCE test2_employees_seq MINVALUE 1 + INCREMENT BY 1 START WITH 10040 CACHE 20 NOORDER NOCYCLE + SQL + end + + after(:all) do + @conn.execute "DROP TABLE test2_employees" + @conn.execute "DROP SEQUENCE test2_employees_seq" + end + + it "should set BINARY_FLOAT column type as float" do + columns = @conn.columns('test2_employees') + column = columns.detect{|c| c.name == "hourly_rate"} + column.type.should == :float + end +end diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb index 1b47f25b8..d2f95c69e 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb @@ -453,4 +453,24 @@ class ::TestName < ActiveRecord::Base end end end + + describe ":float datatype" do + before(:each) do + schema_define do + create_table :test_floats, force: true do |t| + t.float :hourly_rate + end + end + end + + after(:each) do + schema_define do + drop_table :test_floats + end + end + + it "should dump float type correctly" do + standard_dump.should =~ /t\.float "hourly_rate"$/ + end + end end From 9e5f8eb523a11cf96982b2e29841d3c7a155971f Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 1 Jun 2015 20:16:37 +0000 Subject: [PATCH 074/114] Create unique constraint when creating unique index to support Rails foreign key syntax --- .../connection_adapters/oracle_enhanced/schema_statements.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb index 0d788eea0..ba8fdf92c 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb @@ -157,6 +157,9 @@ def initialize_schema_migrations_table def add_index(table_name, column_name, options = {}) #:nodoc: index_name, index_type, quoted_column_names, tablespace, index_options = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{index_options}" + if index_type == 'UNIQUE' + execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_column_name(index_name)} #{index_type} (#{quoted_column_names})" + end ensure self.all_schema_indexes = nil end @@ -205,6 +208,8 @@ def remove_index(table_name, options = {}) #:nodoc: # clear cached indexes when removing index def remove_index!(table_name, index_name) #:nodoc: + #TODO: It should execute only when index_type == "UNIQUE" + execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(index_name)}" rescue nil execute "DROP INDEX #{quote_column_name(index_name)}" ensure self.all_schema_indexes = nil From 9d502e76b0e571142ab3b9c3371b313b630054b9 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 2 Jun 2015 12:19:20 +0000 Subject: [PATCH 075/114] Move dump_schema_information to SchemaStatements to follow Rails module configuration --- .../oracle_enhanced/schema_statements.rb | 6 ++++++ .../connection_adapters/oracle_enhanced/structure_dump.rb | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb index 0d788eea0..0cae53fcc 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb @@ -125,6 +125,12 @@ def drop_table(table_name, options = {}) #:nodoc: self.all_schema_indexes = nil end + def dump_schema_information #:nodoc: + sm_table = ActiveRecord::Migrator.schema_migrations_table_name + migrated = select_values("SELECT version FROM #{sm_table} ORDER BY version") + join_with_statement_token(migrated.map{|v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" }) + end + def initialize_schema_migrations_table sm_table = ActiveRecord::Migrator.schema_migrations_table_name diff --git a/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb b/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb index bdc2ea2bd..f50427ad4 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb @@ -134,12 +134,6 @@ def structure_dump_fk_constraints #:nodoc: join_with_statement_token(fks) end - def dump_schema_information #:nodoc: - sm_table = ActiveRecord::Migrator.schema_migrations_table_name - migrated = select_values("SELECT version FROM #{sm_table} ORDER BY version") - join_with_statement_token(migrated.map{|v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" }) - end - # Extract all stored procedures, packages, synonyms and views. def structure_dump_db_stored_code #:nodoc: structure = [] From 739f16ee03cf41bfc0bccc9dafe03621390afc3d Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sat, 13 Jun 2015 01:55:39 +0000 Subject: [PATCH 076/114] Move OracleEnhancedIndexDefinition to OracleEnhanced::IndexDefinition --- .../oracle_enhanced/schema_definitions.rb | 28 ++++++++++--------- .../oracle_enhanced_adapter.rb | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb index 7bfc52e2c..ccaec355d 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb @@ -15,19 +15,21 @@ class OracleEnhancedForeignKeyDefinition < ForeignKeyDefinition class OracleEnhancedSynonymDefinition < Struct.new(:name, :table_owner, :table_name, :db_link) #:nodoc: end - class OracleEnhancedIndexDefinition < IndexDefinition - attr_accessor :table, :name, :unique, :type, :parameters, :statement_parameters, :tablespace, :columns - - def initialize(table, name, unique, type, parameters, statement_parameters, tablespace, columns) - @table = table - @name = name - @unique = unique - @type = type - @parameters = parameters - @statement_parameters = statement_parameters - @tablespace = tablespace - @columns = columns - super(table, name, unique, columns, nil, nil, nil, nil) + module OracleEnhanced + class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition + attr_accessor :table, :name, :unique, :type, :parameters, :statement_parameters, :tablespace, :columns + + def initialize(table, name, unique, type, parameters, statement_parameters, tablespace, columns) + @table = table + @name = name + @unique = unique + @type = type + @parameters = parameters + @statement_parameters = statement_parameters + @tablespace = tablespace + @columns = columns + super(table, name, unique, columns, nil, nil, nil, nil) + end end end diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index f32f32c94..8a6283070 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -920,7 +920,7 @@ def indexes(table_name, name = nil) #:nodoc: statement_parameters = $1 end end - all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'], + all_schema_indexes << OracleEnhanced::IndexDefinition.new(row['table_name'], row['index_name'], row['uniqueness'] == "UNIQUE", row['index_type'] == 'DOMAIN' ? "#{row['ityp_owner']}.#{row['ityp_name']}" : nil, row['parameters'], statement_parameters, row['tablespace_name'] == default_tablespace_name ? nil : row['tablespace_name'], []) From fae0ed02cb7057c3449b7496a006b978e23431d2 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sat, 13 Jun 2015 02:12:35 +0000 Subject: [PATCH 077/114] Move OracleEnhancedSynonymDefinition to OracleEnhanced::SynonymDefinition --- .../oracle_enhanced/schema_definitions.rb | 6 ++++-- .../oracle_enhanced/schema_statements_ext.rb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb index ccaec355d..e413f30cc 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb @@ -12,10 +12,12 @@ def aliased_types(name, fallback) class OracleEnhancedForeignKeyDefinition < ForeignKeyDefinition end - class OracleEnhancedSynonymDefinition < Struct.new(:name, :table_owner, :table_name, :db_link) #:nodoc: - end module OracleEnhanced + + class SynonymDefinition < Struct.new(:name, :table_owner, :table_name, :db_link) #:nodoc: + end + class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition attr_accessor :table, :name, :unique, :type, :parameters, :statement_parameters, :tablespace, :columns diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb index 0bd1d34c9..ba735c9e3 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb @@ -51,7 +51,7 @@ def remove_synonym(name) # get synonyms for schema dump def synonyms #:nodoc: select_all("SELECT synonym_name, table_owner, table_name, db_link FROM user_synonyms").collect do |row| - OracleEnhancedSynonymDefinition.new(oracle_downcase(row['synonym_name']), + OracleEnhanced::SynonymDefinition.new(oracle_downcase(row['synonym_name']), oracle_downcase(row['table_owner']), oracle_downcase(row['table_name']), oracle_downcase(row['db_link'])) end end From 301958198dcc7b098fae09ae36f7e0b61ba17636 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sun, 14 Jun 2015 14:49:20 +0000 Subject: [PATCH 078/114] Rename variable names in create_table to follow Rails implementation --- .../oracle_enhanced/schema_statements.rb | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb index 0cae53fcc..7f7004b4d 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb @@ -40,17 +40,17 @@ module SchemaStatements # t.string :last_name, :comment => “Surname” # end - def create_table(name, options = {}) + def create_table(table_name, options = {}) create_sequence = options[:id] != false column_comments = {} temporary = options.delete(:temporary) additional_options = options - table_definition = create_table_definition name, temporary, additional_options - table_definition.primary_key(options[:primary_key] || Base.get_primary_key(name.to_s.singularize)) unless options[:id] == false + td = create_table_definition table_name, temporary, additional_options + td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false # store that primary key was defined in create_table block unless create_sequence - class << table_definition + class << td attr_accessor :create_sequence def primary_key(*args) self.create_sequence = true @@ -60,7 +60,7 @@ def primary_key(*args) end # store column comments - class << table_definition + class << td attr_accessor :column_comments def column(name, type, options = {}) if options[:comment] @@ -71,28 +71,28 @@ def column(name, type, options = {}) end end - yield table_definition if block_given? - create_sequence = create_sequence || table_definition.create_sequence - column_comments = table_definition.column_comments if table_definition.column_comments + yield td if block_given? + create_sequence = create_sequence || td.create_sequence + column_comments = td.column_comments if td.column_comments tablespace = tablespace_for(:table, options[:tablespace]) - if options[:force] && table_exists?(name) - drop_table(name, options) + if options[:force] && table_exists?(table_name) + drop_table(table_name, options) end - execute schema_creation.accept table_definition + execute schema_creation.accept td - create_sequence_and_trigger(name, options) if create_sequence + create_sequence_and_trigger(table_name, options) if create_sequence - add_table_comment name, options[:comment] + add_table_comment table_name, options[:comment] column_comments.each do |column_name, comment| - add_comment name, column_name, comment + add_comment table_name, column_name, comment end - table_definition.indexes.each_pair { |c,o| add_index name, c, o } + td.indexes.each_pair { |c,o| add_index table_name, c, o } - unless table_definition.foreign_keys.nil? - table_definition.foreign_keys.each do |foreign_key| - add_foreign_key(table_definition.name, foreign_key.to_table, foreign_key.options) + unless td.foreign_keys.nil? + td.foreign_keys.each do |foreign_key| + add_foreign_key(td.table_name, foreign_key.to_table, foreign_key.options) end end end From 8ca25e8ddcefacd0e723206270a8aa9680bb2ab0 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 15 Jun 2015 13:55:47 +0000 Subject: [PATCH 079/114] Modify remove_column to add cascade constraint to avoid ORA-12991 --- .../connection_adapters/oracle_enhanced/schema_statements.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb index ba8fdf92c..23ba244bd 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb @@ -339,7 +339,7 @@ def rename_column(table_name, column_name, new_column_name) #:nodoc: end def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc: - execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}" + execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)} CASCADE CONSTRAINTS" ensure clear_table_columns_cache(table_name) self.all_schema_indexes = nil From a839eec1249ca5700deb52988b51fbd09c01d063 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 15 Jun 2015 22:52:35 +0000 Subject: [PATCH 080/114] Support Rails native foreign key syntax --- .../oracle_enhanced/schema_creation.rb | 21 +- .../oracle_enhanced/schema_definitions.rb | 197 +++--------------- .../oracle_enhanced/schema_dumper.rb | 37 +--- .../oracle_enhanced/schema_statements.rb | 155 ++------------ 4 files changed, 60 insertions(+), 350 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb index 14bb62dcf..0daa8e95c 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb @@ -44,10 +44,6 @@ def default_tablespace_for(type) ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[native_database_types[type][:name]]) rescue nil end - def foreign_key_definition(to_table, options = {}) - @conn.foreign_key_definition(to_table, options) - end - def add_column_options!(sql, options) type = options[:type] || ((column = options[:column]) && column.type) type = type && type.to_sym @@ -71,6 +67,23 @@ def add_column_options!(sql, options) end end + def action_sql(action, dependency) + if action == 'UPDATE' + raise ArgumentError, <<-MSG.strip_heredoc + '#{action}' is not supported by Oracle + MSG + end + case dependency + when :nullify then "ON #{action} SET NULL" + when :cascade then "ON #{action} CASCADE" + else + raise ArgumentError, <<-MSG.strip_heredoc + '#{dependency}' is not supported for #{action} + Supported values are: :nullify, :cascade + MSG + end + end + end end end diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb index e413f30cc..670b1444f 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb @@ -9,18 +9,17 @@ def aliased_types(name, fallback) end end - class OracleEnhancedForeignKeyDefinition < ForeignKeyDefinition - end + module OracleEnhanced + class ForeignKeyDefinition < ActiveRecord::ConnectionAdapters::ForeignKeyDefinition + end - module OracleEnhanced - class SynonymDefinition < Struct.new(:name, :table_owner, :table_name, :db_link) #:nodoc: end class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition attr_accessor :table, :name, :unique, :type, :parameters, :statement_parameters, :tablespace, :columns - + def initialize(table, name, unique, type, parameters, statement_parameters, tablespace, columns) @table = table @name = name @@ -33,184 +32,36 @@ def initialize(table, name, unique, type, parameters, statement_parameters, tabl super(table, name, unique, columns, nil, nil, nil, nil) end end - end - module OracleEnhancedSchemaDefinitions #:nodoc: - def self.included(base) - base::TableDefinition.class_eval do - include OracleEnhancedTableDefinition - end + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition - # Available starting from ActiveRecord 2.1 - base::Table.class_eval do - include OracleEnhancedTable - end if defined?(base::Table) - end - end - - module OracleEnhancedTableDefinition - class ForeignKey < Struct.new(:base, :to_table, :options) #:nodoc: - def to_sql - base.foreign_key_definition(to_table, options) + def raw(name, options={}) + column(name, :raw, options) end - alias to_s :to_sql - end - def self.included(base) #:nodoc: - base.class_eval do - alias_method_chain :column, :virtual_columns + def virtual(* args) + options = args.extract_options! + column_names = args + column_names.each { |name| column(name, :virtual, options) } end - end - - def raw(name, options={}) - column(name, :raw, options) - end - - def virtual(* args) - options = args.extract_options! - column_names = args - column_names.each { |name| column(name, :virtual, options) } - end - def column_with_virtual_columns(name, type, options = {}) - if type == :virtual - default = {:type => options[:type]} - if options[:as] - default[:as] = options[:as] - elsif options[:default] - warn "[DEPRECATION] virtual column `:default` option is deprecated. Please use `:as` instead." - default[:as] = options[:default] - else - raise "No virtual column definition found." + def column(name, type, options = {}) + if type == :virtual + default = {:type => options[:type]} + if options[:as] + default[:as] = options[:as] + elsif options[:default] + warn "[DEPRECATION] virtual column `:default` option is deprecated. Please use `:as` instead." + default[:as] = options[:default] + else + raise "No virtual column definition found." + end + options[:default] = default end - options[:default] = default + super(name, type, options) end - column_without_virtual_columns(name, type, options) - end - - # Adds a :foreign_key option to TableDefinition.references. - # If :foreign_key is true, a foreign key constraint is added to the table. - # You can also specify a hash, which is passed as foreign key options. - # - # ===== Examples - # ====== Add goat_id column and a foreign key to the goats table. - # t.references(:goat, :foreign_key => true) - # ====== Add goat_id column and a cascading foreign key to the goats table. - # t.references(:goat, :foreign_key => {:dependent => :delete}) - # - # Note: No foreign key is created if :polymorphic => true is used. - # Note: If no name is specified, the database driver creates one for you! - def references(*args) - options = args.extract_options! - index_options = options[:index] - fk_options = options.delete(:foreign_key) - if fk_options && !options[:polymorphic] - fk_options = {} if fk_options == true - args.each do |to_table| - foreign_key(to_table, fk_options) - add_index(to_table, "#{to_table}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options - end - end - - super(*(args << options)) - end - - # Defines a foreign key for the table. +to_table+ can be a single Symbol, or - # an Array of Symbols. See SchemaStatements#add_foreign_key - # - # ===== Examples - # ====== Creating a simple foreign key - # t.foreign_key(:people) - # ====== Defining the column - # t.foreign_key(:people, :column => :sender_id) - # ====== Creating a named foreign key - # t.foreign_key(:people, :column => :sender_id, :name => 'sender_foreign_key') - # ====== Defining the column of the +to_table+. - # t.foreign_key(:people, :column => :sender_id, :primary_key => :person_id) - def foreign_key(to_table, options = {}) - #TODO - if ActiveRecord::Base.connection.supports_foreign_keys? - to_table = to_table.to_s.pluralize if ActiveRecord::Base.pluralize_table_names - foreign_keys << ForeignKey.new(@base, to_table, options) - else - raise ArgumentError, "this ActiveRecord adapter is not supporting foreign_key definition" - end - end - - def foreign_keys - @foreign_keys ||= [] - end - end - - module OracleEnhancedTable - - # Adds a new foreign key to the table. +to_table+ can be a single Symbol, or - # an Array of Symbols. See SchemaStatements#add_foreign_key - # - # ===== Examples - # ====== Creating a simple foreign key - # t.foreign_key(:people) - # ====== Defining the column - # t.foreign_key(:people, :column => :sender_id) - # ====== Creating a named foreign key - # t.foreign_key(:people, :column => :sender_id, :name => 'sender_foreign_key') - # ====== Defining the column of the +to_table+. - # t.foreign_key(:people, :column => :sender_id, :primary_key => :person_id) - def foreign_key(to_table, options = {}) - if @base.respond_to?(:supports_foreign_keys?) && @base.supports_foreign_keys? - to_table = to_table.to_s.pluralize if ActiveRecord::Base.pluralize_table_names - @base.add_foreign_key(@table_name, to_table, options) - else - raise ArgumentError, "this ActiveRecord adapter is not supporting foreign_key definition" - end - end - - # Remove the given foreign key from the table. - # - # ===== Examples - # ====== Remove the suppliers_company_id_fk in the suppliers table. - # t.remove_foreign_key :companies - # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table. - # remove_foreign_key :column => :branch_id - # ====== Remove the foreign key named party_foreign_key in the accounts table. - # remove_index :name => :party_foreign_key - def remove_foreign_key(options = {}) - @base.remove_foreign_key(@table_name, options) - end - - # Adds a :foreign_key option to TableDefinition.references. - # If :foreign_key is true, a foreign key constraint is added to the table. - # You can also specify a hash, which is passed as foreign key options. - # - # ===== Examples - # ====== Add goat_id column and a foreign key to the goats table. - # t.references(:goat, :foreign_key => true) - # ====== Add goat_id column and a cascading foreign key to the goats table. - # t.references(:goat, :foreign_key => {:dependent => :delete}) - # - # Note: No foreign key is created if :polymorphic => true is used. - def references(*args) - options = args.extract_options! - polymorphic = options[:polymorphic] - index_options = options[:index] - fk_options = options.delete(:foreign_key) - - super(*(args << options)) - # references_without_foreign_keys adds {:type => :integer} - args.extract_options! - if fk_options && !polymorphic - fk_options = {} if fk_options == true - args.each do |to_table| - foreign_key(to_table, fk_options) - add_index(to_table, "#{to_table}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options - end - end end end end end - -ActiveRecord::ConnectionAdapters.class_eval do - include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaDefinitions -end diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb index b8e3f72f6..4d9d63393 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb @@ -58,42 +58,7 @@ def primary_key_trigger(table_name, stream) end def foreign_keys_with_oracle_enhanced(table_name, stream) - # return original method if not using oracle_enhanced - if (rails_env = defined?(Rails.env) ? Rails.env : (defined?(RAILS_ENV) ? RAILS_ENV : nil)) && - ActiveRecord::Base.configurations[rails_env] && - ActiveRecord::Base.configurations[rails_env]['adapter'] != 'oracle_enhanced' - return foreign_keys_without_oracle_enhanced(table_name, stream) - end - if @connection.respond_to?(:foreign_keys) && (foreign_keys = @connection.foreign_keys(table_name)).any? - add_foreign_key_statements = foreign_keys.map do |foreign_key| - statement_parts = [ ('add_foreign_key ' + foreign_key.from_table.inspect) ] - statement_parts << foreign_key.to_table.inspect - - if foreign_key.options[:columns].size == 1 - column = foreign_key.options[:columns].first - if column != "#{foreign_key.to_table.singularize}_id" - statement_parts << ('column: ' + column.inspect) - end - - if foreign_key.options[:references].first != 'id' - statement_parts << ('primary_key: ' + foreign_key.options[:references].first.inspect) - end - else - statement_parts << ('columns: ' + foreign_key.options[:columns].inspect) - end - - statement_parts << ('name: ' + foreign_key.options[:name].inspect) - - unless foreign_key.options[:dependent].blank? - statement_parts << ('dependent: ' + foreign_key.options[:dependent].inspect) - end - - ' ' + statement_parts.join(', ') - end - - stream.puts add_foreign_key_statements.sort.join("\n") - stream.puts - end + return foreign_keys_without_oracle_enhanced(table_name, stream) end def synonyms(stream) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb index b3265a247..ffcf8d446 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb @@ -90,15 +90,13 @@ def column(name, type, options = {}) end td.indexes.each_pair { |c,o| add_index table_name, c, o } - unless td.foreign_keys.nil? - td.foreign_keys.each do |foreign_key| - add_foreign_key(td.table_name, foreign_key.to_table, foreign_key.options) - end + td.foreign_keys.each_pair do |other_table_name, foreign_key_options| + add_foreign_key(table_name, other_table_name, foreign_key_options) end end def create_table_definition(name, temporary, options) - TableDefinition.new native_database_types, name, temporary, options + ActiveRecord::ConnectionAdapters::OracleEnhanced::TableDefinition.new native_database_types, name, temporary, options end def rename_table(table_name, new_name) #:nodoc: @@ -396,128 +394,14 @@ def tablespace(table_name) SQL end - # Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+ - # (syntax and partial implementation taken from http://github.com/matthuhiggins/foreigner) - # - # The foreign key will be named after the from and to tables unless you pass - # :name as an option. - # - # === Examples - # ==== Creating a foreign key - # add_foreign_key(:comments, :posts) - # generates - # ALTER TABLE comments ADD CONSTRAINT - # comments_post_id_fk FOREIGN KEY (post_id) REFERENCES posts (id) - # - # ==== Creating a named foreign key - # add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts') - # generates - # ALTER TABLE comments ADD CONSTRAINT - # comments_belongs_to_posts FOREIGN KEY (post_id) REFERENCES posts (id) - # - # ==== Creating a cascading foreign_key on a custom column - # add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify) - # generates - # ALTER TABLE people ADD CONSTRAINT - # people_best_friend_id_fk FOREIGN KEY (best_friend_id) REFERENCES people (id) - # ON DELETE SET NULL - # - # ==== Creating a composite foreign key - # add_foreign_key(:comments, :posts, :columns => ['post_id', 'author_id'], :name => 'comments_post_fk') - # generates - # ALTER TABLE comments ADD CONSTRAINT - # comments_post_fk FOREIGN KEY (post_id, author_id) REFERENCES posts (post_id, author_id) - # - # === Supported options - # [:column] - # Specify the column name on the from_table that references the to_table. By default this is guessed - # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id" - # as the default :column. - # [:columns] - # An array of column names when defining composite foreign keys. An alias of :column provided for improved readability. - # [:primary_key] - # Specify the column name on the to_table that is referenced by this foreign key. By default this is - # assumed to be "id". Ignored when defining composite foreign keys. - # [:name] - # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column. - # [:dependent] - # If set to :delete, the associated records in from_table are deleted when records in to_table table are deleted. - # If set to :nullify, the foreign key column is set to +NULL+. def add_foreign_key(from_table, to_table, options = {}) - columns = options[:column] || options[:columns] || "#{to_table.to_s.singularize}_id" - constraint_name = foreign_key_constraint_name(from_table, columns, options) - sql = "ALTER TABLE #{quote_table_name(from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} " - sql << foreign_key_definition(to_table, options) - execute sql - end - - def foreign_key_definition(to_table, options = {}) #:nodoc: - columns = Array(options[:column] || options[:columns]) - - if columns.size > 1 - # composite foreign key - columns_sql = columns.map {|c| quote_column_name(c)}.join(',') - references = options[:references] || columns - references_sql = references.map {|c| quote_column_name(c)}.join(',') - else - columns_sql = quote_column_name(columns.first || "#{to_table.to_s.singularize}_id") - references = options[:references] ? options[:references].first : nil - references_sql = quote_column_name(options[:primary_key] || references || "id") - end - - table_name = to_table - # TODO: Needs support `table_name_prefix` and `table_name_suffix` - - sql = "FOREIGN KEY (#{columns_sql}) REFERENCES #{quote_table_name(table_name)}(#{references_sql})" - - case options[:dependent] - when :nullify - sql << " ON DELETE SET NULL" - when :delete - sql << " ON DELETE CASCADE" - end - sql - end - - # Remove the given foreign key from the table. - # - # ===== Examples - # ====== Remove the suppliers_company_id_fk in the suppliers table. - # remove_foreign_key :suppliers, :companies - # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table. - # remove_foreign_key :accounts, :column => :branch_id - # ====== Remove the foreign key named party_foreign_key in the accounts table. - # remove_foreign_key :accounts, :name => :party_foreign_key - def remove_foreign_key(from_table, options) - if Hash === options - constraint_name = foreign_key_constraint_name(from_table, options[:column], options) - else - constraint_name = foreign_key_constraint_name(from_table, "#{options.to_s.singularize}_id") - end - execute "ALTER TABLE #{quote_table_name(from_table)} DROP CONSTRAINT #{quote_column_name(constraint_name)}" + super end - private - - def foreign_key_constraint_name(table_name, columns, options = {}) - columns = Array(columns) - constraint_name = original_name = options[:name] || "#{table_name}_#{columns.join('_')}_fk" - - return constraint_name if constraint_name.length <= OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH - - # leave just first three letters from each word - constraint_name = constraint_name.split('_').map{|w| w[0,3]}.join('_') - # generate unique name using hash function - if constraint_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH - constraint_name = 'c'+Digest::SHA1.hexdigest(original_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1] - end - @logger.warn "#{adapter_name} shortened foreign key constraint name #{original_name} to #{constraint_name}" if @logger - constraint_name + def remove_foreign_key(from_table, options_or_to_table = {}) + super end - - public - # get table foreign keys for schema dump def foreign_keys(table_name) #:nodoc: (owner, desc_table_name, db_link) = @connection.describe(table_name) @@ -543,24 +427,21 @@ def foreign_keys(table_name) #:nodoc: ORDER BY name, to_table, column_name, references_column SQL - fks = {} - fk_info.map do |row| - name = oracle_downcase(row['name']) - fks[name] ||= { :columns => [], :to_table => oracle_downcase(row['to_table']), :references => [] } - fks[name][:columns] << oracle_downcase(row['column_name']) - fks[name][:references] << oracle_downcase(row['references_column']) - case row['delete_rule'] - when 'CASCADE' - fks[name][:dependent] = :delete - when 'SET NULL' - fks[name][:dependent] = :nullify - end + options = { + column: oracle_downcase(row['column_name']), + name: oracle_downcase(row['name']), + primary_key: oracle_downcase(row['references_column']) + } + options[:on_delete] = extract_foreign_key_action(row['delete_rule']) + OracleEnhanced::ForeignKeyDefinition.new(oracle_downcase(table_name), oracle_downcase(row['to_table']), options) end + end - fks.map do |k, v| - options = {:name => k, :columns => v[:columns], :references => v[:references], :dependent => v[:dependent]} - OracleEnhancedForeignKeyDefinition.new(table_name, v[:to_table], options) + def extract_foreign_key_action(specifier) # :nodoc: + case specifier + when 'CASCADE'; :cascade + when 'SET NULL'; :nullify end end From d1631add792f892fec6df173ff2891f4c3f9c3e3 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Tue, 16 Jun 2015 11:18:59 -0300 Subject: [PATCH 081/114] Adding syntax highlighting on README. [ci skip] --- README.md | 445 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 271 insertions(+), 174 deletions(-) diff --git a/README.md b/README.md index c1d5a80a2..9c68e0c59 100644 --- a/README.md +++ b/README.md @@ -17,17 +17,23 @@ Oracle enhanced adapter version 1.5 just supports Rails 4 and does not support R When using Ruby on Rails version 4 then in Gemfile include - gem "activerecord-oracle_enhanced-adapter", "~> 1.5.0" +```ruby +gem 'activerecord-oracle_enhanced-adapter', '~> 1.5.0' +``` where instead of 1.5.0 you can specify any other desired version. It is recommended to specify version with `~>` which means that use specified version or later patch versions (in this example any later 1.5.x version but not 1.6.x version). Oracle enhanced adapter maintains API backwards compatibility during patch version upgrades and therefore it is safe to always upgrade to latest patch version. If you would like to use latest adapter version from github then specify - gem 'activerecord-oracle_enhanced-adapter', :git => 'git://github.com/rsim/oracle-enhanced.git' +```ruby +gem 'activerecord-oracle_enhanced-adapter', :git => 'git://github.com/rsim/oracle-enhanced.git' +``` If you are using CRuby 1.9.3 or 2.0 then you need to install ruby-oci8 gem as well as Oracle client, e.g. [Oracle Instant Client](http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html). Include in Gemfile also ruby-oci8: - gem 'ruby-oci8', '~> 2.1.0' +```ruby +gem 'ruby-oci8', '~> 2.1.0' +``` If you are using JRuby then you need to download latest [Oracle JDBC driver](http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html) - either ojdbc7.jar or ojdbc6.jar for Java 7, ojdbc6.jar for Java 6 or ojdbc5.jar for Java 5. And copy this file to one of these locations: @@ -38,7 +44,9 @@ If you are using JRuby then you need to download latest [Oracle JDBC driver](htt After specifying necessary gems in Gemfile run - bundle install +```bash +bundle install +``` to install the adapter (or later run `bundle update` to force updating to latest version). @@ -46,17 +54,23 @@ to install the adapter (or later run `bundle update` to force updating to latest When using Ruby on Rails version 3 then in Gemfile include - gem 'activerecord-oracle_enhanced-adapter', '~> 1.4.0' +```ruby +gem 'activerecord-oracle_enhanced-adapter', '~> 1.4.0' +``` where instead of 1.4.0 you can specify any other desired version. It is recommended to specify version with `~>` which means that use specified version or later patch versions (in this example any later 1.4.x version but not 1.5.x version). Oracle enhanced adapter maintains API backwards compatibility during patch version upgrades and therefore it is safe to always upgrade to latest patch version. If you would like to use latest adapter version from github then specify - gem 'activerecord-oracle_enhanced-adapter', :git => 'git://github.com/rsim/oracle-enhanced.git' +```ruby +gem 'activerecord-oracle_enhanced-adapter', :git => 'git://github.com/rsim/oracle-enhanced.git' +``` If you are using MRI 1.8 or 1.9 Ruby implementation then you need to install ruby-oci8 gem as well as Oracle client, e.g. [Oracle Instant Client](http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html). Include in Gemfile also ruby-oci8: - gem 'ruby-oci8', '~> 2.1.0' +```ruby +gem 'ruby-oci8', '~> 2.1.0' +``` If you are using JRuby then you need to download latest [Oracle JDBC driver](http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html) - either ojdbc6.jar for Java 6 or ojdbc5.jar for Java 5. And copy this file to one of these locations: @@ -67,7 +81,9 @@ If you are using JRuby then you need to download latest [Oracle JDBC driver](htt After specifying necessary gems in Gemfile run - bundle install +```bash +bundle install +``` to install the adapter (or later run `bundle update` to force updating to latest version). @@ -75,12 +91,14 @@ to install the adapter (or later run `bundle update` to force updating to latest If you don't use Bundler in Rails 2 application then you need to specify gems in `config/environment.rb`, e.g. - Rails::Initializer.run do |config| - #... - config.gem 'activerecord-oracle_enhanced-adapter', :lib => "active_record/connection_adapters/oracle_enhanced_adapter" - config.gem 'ruby-oci8' - #... - end +```ruby +Rails::Initializer.run do |config| + # ... + config.gem 'activerecord-oracle_enhanced-adapter', :lib => 'active_record/connection_adapters/oracle_enhanced_adapter' + config.gem 'ruby-oci8' + # ... +end +``` But it is recommended to use Bundler for gem version management also for Rails 2.3 applications (search for instructions in Google). @@ -88,7 +106,9 @@ But it is recommended to use Bundler for gem version management also for Rails 2 If you want to use ActiveRecord and Oracle enhanced adapter without Rails and Bundler then install it just as a gem: - gem install activerecord-oracle_enhanced-adapter +```bash +gem install activerecord-oracle_enhanced-adapter +``` USAGE ----- @@ -97,52 +117,64 @@ USAGE In Rails application `config/database.yml` use oracle_enhanced as adapter name, e.g. - development: - adapter: oracle_enhanced - database: xe - username: user - password: secret +```yml +development: + adapter: oracle_enhanced + database: xe + username: user + password: secret +``` If you're connecting to a service name, indicate the service with a leading slash on the database parameter: - development: - adapter: oracle_enhanced - database: /xe - username: user - password: secret +```yml +development: + adapter: oracle_enhanced + database: /xe + username: user + password: secret +``` If `TNS_ADMIN` environment variable is pointing to directory where `tnsnames.ora` file is located then you can use TNS connection name in `database` parameter. Otherwise you can directly specify database host, port (defaults to 1521) and database name in the following way: - development: - adapter: oracle_enhanced - host: localhost - port: 1521 - database: xe - username: user - password: secret +```yml +development: + adapter: oracle_enhanced + host: localhost + port: 1521 + database: xe + username: user + password: secret +``` or you can use Oracle specific format in `database` parameter: - development: - adapter: oracle_enhanced - database: //localhost:1521/xe - username: user - password: secret +```yml +development: + adapter: oracle_enhanced + database: //localhost:1521/xe + username: user + password: secret +``` or you can even use Oracle specific TNS connection description: - development: - adapter: oracle_enhanced - database: "(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=xe)))" - username: user - password: secret +```yml +development: + adapter: oracle_enhanced + database: "(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=xe)))" + username: user + password: secret +``` If you deploy JRuby on Rails application in Java application server that supports JNDI connections then you can specify JNDI connection as well: - development: - adapter: oracle_enhanced - jndi: "jdbc/jndi_connection_name" +```yml +development: + adapter: oracle_enhanced + jndi: "jdbc/jndi_connection_name" +``` To use jndi with Tomcat you need to set the accessToUnderlyingConnectionAllowed to true property on the pool. See the [Tomcat Documentation](http://tomcat.apache.org/tomcat-7.0-doc/jndi-resources-howto.html) for reference. @@ -152,22 +184,28 @@ You can find other available database.yml connection parameters in [oracle_enhan If you want to change Oracle enhanced adapter default settings then create initializer file e.g. `config/initializers/oracle.rb` specify there necessary defaults, e.g.: - # It is recommended to set time zone in TZ environment variable so that the same timezone will be used by Ruby and by Oracle session - ENV['TZ'] = 'UTC' - - ActiveSupport.on_load(:active_record) do - ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do - # id columns and columns which end with _id will always be converted to integers - self.emulate_integers_by_column_name = true - # DATE columns which include "date" in name will be converted to Date, otherwise to Time - self.emulate_dates_by_column_name = true - # true and false will be stored as 'Y' and 'N' - self.emulate_booleans_from_strings = true - # start primary key sequences from 1 (and not 10000) and take just one next value in each session - self.default_sequence_start_value = "1 NOCACHE INCREMENT BY 1" - # other settings ... - end - end +```ruby +# It is recommended to set time zone in TZ environment variable so that the same timezone will be used by Ruby and by Oracle session +ENV['TZ'] = 'UTC' + +ActiveSupport.on_load(:active_record) do + ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do + # id columns and columns which end with _id will always be converted to integers + self.emulate_integers_by_column_name = true + + # DATE columns which include "date" in name will be converted to Date, otherwise to Time + self.emulate_dates_by_column_name = true + + # true and false will be stored as 'Y' and 'N' + self.emulate_booleans_from_strings = true + + # start primary key sequences from 1 (and not 10000) and take just one next value in each session + self.default_sequence_start_value = "1 NOCACHE INCREMENT BY 1" + + # other settings ... + end +end +``` In case of Rails 2 application you do not need to use `ActiveSupport.on_load(:active_record) do ... end` around settings code block. @@ -177,81 +215,108 @@ See other adapter settings in [oracle_enhanced_adapter.rb](http://github.com/rsi If you want to put Oracle enhanced adapter on top of existing schema tables then there are several methods how to override ActiveRecord defaults, see example: - class Employee < ActiveRecord::Base - # specify schema and table name - self.table_name = "hr.hr_employees" - # specify primary key name - self.primary_key = "employee_id" - # specify sequence name - self.sequence_name = "hr.hr_employee_s" - # set which DATE columns should be converted to Ruby Date - set_date_columns :hired_on, :birth_date_on - # set which DATE columns should be converted to Ruby Time - set_datetime_columns :last_login_time - # set which VARCHAR2 columns should be converted to true and false - set_boolean_columns :manager, :active - # set which columns should be ignored in ActiveRecord - ignore_table_columns :attribute1, :attribute2 - end +```ruby +class Employee < ActiveRecord::Base + # specify schema and table name + self.table_name = "hr.hr_employees" + + # specify primary key name + self.primary_key = "employee_id" + + # specify sequence name + self.sequence_name = "hr.hr_employee_s" + + # set which DATE columns should be converted to Ruby Date + set_date_columns :hired_on, :birth_date_on + + # set which DATE columns should be converted to Ruby Time + set_datetime_columns :last_login_time + + # set which VARCHAR2 columns should be converted to true and false + set_boolean_columns :manager, :active + + # set which columns should be ignored in ActiveRecord + ignore_table_columns :attribute1, :attribute2 +end +``` You can also access remote tables over database link using - self.table_name "hr_employees@db_link" +```ruby +self.table_name "hr_employees@db_link" +``` Examples for Rails 3.2 and lower version of Rails - class Employee < ActiveRecord::Base - # specify schema and table name - set_table_name "hr.hr_employees" - # specify primary key name - set_primary_key "employee_id" - # specify sequence name - set_sequence_name "hr.hr_employee_s" - # set which DATE columns should be converted to Ruby Date - set_date_columns :hired_on, :birth_date_on - # set which DATE columns should be converted to Ruby Time - set_datetime_columns :last_login_time - # set which VARCHAR2 columns should be converted to true and false - set_boolean_columns :manager, :active - # set which columns should be ignored in ActiveRecord - ignore_table_columns :attribute1, :attribute2 - end +```ruby +class Employee < ActiveRecord::Base + # specify schema and table name + set_table_name "hr.hr_employees" + + # specify primary key name + set_primary_key "employee_id" + + # specify sequence name + set_sequence_name "hr.hr_employee_s" + + # set which DATE columns should be converted to Ruby Date + set_date_columns :hired_on, :birth_date_on + + # set which DATE columns should be converted to Ruby Time + set_datetime_columns :last_login_time + + # set which VARCHAR2 columns should be converted to true and false + set_boolean_columns :manager, :active + + # set which columns should be ignored in ActiveRecord + ignore_table_columns :attribute1, :attribute2 +end +``` You can also access remote tables over database link using - set_table_name "hr_employees@db_link" +```ruby +set_table_name "hr_employees@db_link" +``` ### Custom create, update and delete methods If you have legacy schema and you are not allowed to do direct INSERTs, UPDATEs and DELETEs in legacy schema tables and need to use existing PL/SQL procedures for create, updated, delete operations then you should add `ruby-plsql` gem to your application, include `ActiveRecord::OracleEnhancedProcedures` in your model and then define custom create, update and delete methods, see example: - class Employee < ActiveRecord::Base - include ActiveRecord::OracleEnhancedProcedures - # when defining create method then return ID of new record that will be assigned to id attribute of new object - set_create_method do - plsql.employees_pkg.create_employee( - :p_first_name => first_name, - :p_last_name => last_name, - :p_employee_id => nil - )[:p_employee_id] - end - set_update_method do - plsql.employees_pkg.update_employee( - :p_employee_id => id, - :p_first_name => first_name, - :p_last_name => last_name - ) - end - set_delete_method do - plsql.employees_pkg.delete_employee( - :p_employee_id => id - ) - end - end +```ruby +class Employee < ActiveRecord::Base + include ActiveRecord::OracleEnhancedProcedures + + # when defining create method then return ID of new record that will be assigned to id attribute of new object + set_create_method do + plsql.employees_pkg.create_employee( + :p_first_name => first_name, + :p_last_name => last_name, + :p_employee_id => nil + )[:p_employee_id] + end + + set_update_method do + plsql.employees_pkg.update_employee( + :p_employee_id => id, + :p_first_name => first_name, + :p_last_name => last_name + ) + end + + set_delete_method do + plsql.employees_pkg.delete_employee( + :p_employee_id => id + ) + end +end +``` In addition in `config/initializers/oracle.rb` initializer specify that ruby-plsql should use ActiveRecord database connection: - plsql.activerecord_class = ActiveRecord::Base +```ruby +plsql.activerecord_class = ActiveRecord::Base +``` ### Oracle CONTEXT index support @@ -259,60 +324,78 @@ Every edition of Oracle database includes [Oracle Text](http://www.oracle.com/te To create simple single column index create migration with, e.g. - add_context_index :posts, :title +```ruby +add_context_index :posts, :title +``` and you can remove context index with - remove_context_index :posts, :title +```ruby +remove_context_index :posts, :title +``` Include in class definition - has_context_index +```ruby +has_context_index +``` and then you can do full text search with - Post.contains(:title, 'word') +```ruby +Post.contains(:title, 'word') +``` You can create index on several columns (which will generate additional stored procedure for providing XML document with specified columns to indexer): - add_context_index :posts, [:title, :body] +```ruby +add_context_index :posts, [:title, :body] +``` And you can search either in all columns or specify in which column you want to search (as first argument you need to specify first column name as this is the column which is referenced during index creation): - Post.contains(:title, 'word') - Post.contains(:title, 'word within title') - Post.contains(:title, 'word within body') +```ruby +Post.contains(:title, 'word') +Post.contains(:title, 'word within title') +Post.contains(:title, 'word within body') +``` See Oracle Text documentation for syntax that you can use in CONTAINS function in SELECT WHERE clause. You can also specify some dummy main column name when creating multiple column index as well as specify to update index automatically after each commit (as otherwise you need to synchronize index manually or schedule periodic update): - add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT' +```ruby +add_context_index :posts, [:title, :body], :index_column => :all_text, :sync => 'ON COMMIT' - Post.contains(:all_text, 'word') +Post.contains(:all_text, 'word') +``` Or you can specify that index should be updated when specified columns are updated (e.g. in ActiveRecord you can specify to trigger index update when created_at or updated_at columns are updated). Otherwise index is updated only when main index column is updated. - add_context_index :posts, [:title, :body], :index_column => :all_text, - :sync => 'ON COMMIT', :index_column_trigger_on => [:created_at, :updated_at] +```ruby +add_context_index :posts, [:title, :body], :index_column => :all_text, + :sync => 'ON COMMIT', :index_column_trigger_on => [:created_at, :updated_at] +``` And you can even create index on multiple tables by providing SELECT statements which should be used to fetch necessary columns from related tables: - add_context_index :posts, - [:title, :body, - # specify aliases always with AS keyword - "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id" - ], - :name => 'post_and_comments_index', - :index_column => :all_text, - :index_column_trigger_on => [:updated_at, :comments_count], - :sync => 'ON COMMIT' - - # search in any table columns - Post.contains(:all_text, 'word') - # search in specified column - Post.contains(:all_text, "aaa within title") - Post.contains(:all_text, "bbb within comment_author") +```ruby +add_context_index :posts, + [:title, :body, + # specify aliases always with AS keyword + "SELECT comments.author AS comment_author, comments.body AS comment_body FROM comments WHERE comments.post_id = :id" + ], + :name => 'post_and_comments_index', + :index_column => :all_text, + :index_column_trigger_on => [:updated_at, :comments_count], + :sync => 'ON COMMIT' + +# search in any table columns +Post.contains(:all_text, 'word') +# search in specified column +Post.contains(:all_text, "aaa within title") +Post.contains(:all_text, "bbb within comment_author") +``` ### Oracle virtual columns support @@ -321,43 +404,53 @@ They can be used as normal fields in the queries, in the foreign key contstraint To define virtual column you can use `virtual` method in the `create_table` block, providing column expression in the `:as` option: - create_table :mytable do |t| - t.decimal :price, :precision => 15, :scale => 2 - t.decimal :quantity, :precision => 15, :scale => 2 - t.virtual :amount, :as => 'price * quantity' - end +```ruby +create_table :mytable do |t| + t.decimal :price, :precision => 15, :scale => 2 + t.decimal :quantity, :precision => 15, :scale => 2 + t.virtual :amount, :as => 'price * quantity' +end +``` Oracle tries to predict type of the virtual column, based on its expression but sometimes it is necessary to state type explicitly. This can be done by providing `:type` option to the `virtual` method: - ... - t.virtual :amount_2, :as => 'ROUND(price * quantity,2)', :type => :decimal, :precision => 15, :scale => 2 - t.virtual :amount_str, :as => "TO_CHAR(quantity) || ' x ' || TO_CHAR(price) || ' USD = ' || TO_CHAR(quantity*price) || ' USD'", - :type => :string, :limit => 100 - ... +```ruby +# ... +t.virtual :amount_2, :as => 'ROUND(price * quantity,2)', :type => :decimal, :precision => 15, :scale => 2 +t.virtual :amount_str, :as => "TO_CHAR(quantity) || ' x ' || TO_CHAR(price) || ' USD = ' || TO_CHAR(quantity*price) || ' USD'", + :type => :string, :limit => 100 +# ... +``` It is possible to add virtual column to existing table: - add_column :mytable, :amount_4, :virtual, :as => 'ROUND(price * quantity,4)', :precision => 38, :scale => 4 +```ruby +add_column :mytable, :amount_4, :virtual, :as => 'ROUND(price * quantity,4)', :precision => 38, :scale => 4 +``` You can use the same options here as in the `create_table` `virtual` method. Changing virtual columns is also possible: - change_column :mytable, :amount, :virtual, :as => 'ROUND(price * quantity,0)', :type => :integer +```ruby +change_column :mytable, :amount, :virtual, :as => 'ROUND(price * quantity,0)', :type => :integer +``` Virtual columns allowed in the foreign key constraints. For example it can be used to force foreign key constraint on polymorphic association: - create_table :comments do |t| - t.string :subject_type - t.integer :subject_id - t.virtual :subject_photo_id, :as => "CASE subject_type WHEN 'Photo' THEN subject_id END" - t.virtual :subject_event_id, :as => "CASE subject_type WHEN 'Event' THEN subject_id END" - end +```ruby +create_table :comments do |t| + t.string :subject_type + t.integer :subject_id + t.virtual :subject_photo_id, :as => "CASE subject_type WHEN 'Photo' THEN subject_id END" + t.virtual :subject_event_id, :as => "CASE subject_type WHEN 'Event' THEN subject_id END" +end - add_foreign_key :comments, :photos, :column => :subject_photo_id - add_foreign_key :comments, :events, :column => :subject_event_id +add_foreign_key :comments, :photos, :column => :subject_photo_id +add_foreign_key :comments, :events, :column => :subject_event_id +``` For backward compatibility reasons it is possible to use `:default` option in the `create_table` instead of `:as` option. But this is deprecated and may be removed in the future version. @@ -373,8 +466,10 @@ There are several additional schema statements and data types available that you * You can add table and column comments with `:comment` option * Default tablespaces can be specified for tables, indexes, clobs and blobs, for example: - ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces = - {:clob => 'TS_LOB', :blob => 'TS_LOB', :index => 'TS_INDEX', :table => 'TS_DATA'} +```ruby +ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces = + {:clob => 'TS_LOB', :blob => 'TS_LOB', :index => 'TS_INDEX', :table => 'TS_DATA'} +``` TROUBLESHOOTING --------------- @@ -395,13 +490,15 @@ Please verify that 3. Verify that activerecord-oracle_enhanced-adapter is working from irb - require 'rubygems' - gem 'activerecord' - gem 'activerecord-oracle_enhanced-adapter' - require 'active_record' - ActiveRecord::Base.establish_connection(:adapter => "oracle_enhanced", :database => "database",:username => "user",:password => "password") +```ruby +require 'rubygems' +gem 'activerecord' +gem 'activerecord-oracle_enhanced-adapter' +require 'active_record' +ActiveRecord::Base.establish_connection(:adapter => "oracle_enhanced", :database => "database",:username => "user",:password => "password") +``` - and see if it is successful (use your correct database, username and password) +and see if it is successful (use your correct database, username and password) ### What to do if Oracle enhanced adapter is not working with Phusion Passenger? From 4233734a737e8e1871037f9df95bd11a7e739c55 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 17 Jun 2015 11:39:32 +0000 Subject: [PATCH 082/114] Shorten foreign key name if it is longer than 30 byte --- .../oracle_enhanced/schema_definitions.rb | 13 +++++++++++++ .../oracle_enhanced/schema_statements.rb | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb index 670b1444f..dbe87ebc5 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb @@ -12,6 +12,13 @@ def aliased_types(name, fallback) module OracleEnhanced class ForeignKeyDefinition < ActiveRecord::ConnectionAdapters::ForeignKeyDefinition + def name + if options[:name].length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH + 'c'+Digest::SHA1.hexdigest(options[:name])[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1] + else + options[:name] + end + end end class SynonymDefinition < Struct.new(:name, :table_owner, :table_name, :db_link) #:nodoc: @@ -62,6 +69,12 @@ def column(name, type, options = {}) end end + + class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable + def add_foreign_key(to_table, options) + @foreign_key_adds << OracleEnhanced::ForeignKeyDefinition.new(name, to_table, options) + end + end end end end diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb index ffcf8d446..ce25600f7 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb @@ -469,6 +469,10 @@ def disable_referential_integrity(&block) #:nodoc: private + def create_alter_table(name) + OracleEnhanced::AlterTable.new create_table_definition(name, false, {}) + end + def tablespace_for(obj_type, tablespace_option, table_name=nil, column_name=nil) tablespace_sql = '' if tablespace = (tablespace_option || default_tablespace_for(obj_type)) From 43ceb036b4c8300474afef2a63f171f8a0394b73 Mon Sep 17 00:00:00 2001 From: swaroopmurthy Date: Thu, 18 Jun 2015 07:11:16 -0400 Subject: [PATCH 083/114] Restore foreign_key_definition method --- .../oracle_enhanced/structure_dump.rb | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb b/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb index f50427ad4..e9b5ed860 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb @@ -134,6 +134,34 @@ def structure_dump_fk_constraints #:nodoc: join_with_statement_token(fks) end + def foreign_key_definition(to_table, options = {}) #:nodoc: + columns = Array(options[:column] || options[:columns]) + + if columns.size > 1 + # composite foreign key + columns_sql = columns.map {|c| quote_column_name(c)}.join(',') + references = options[:references] || columns + references_sql = references.map {|c| quote_column_name(c)}.join(',') + else + columns_sql = quote_column_name(columns.first || "#{to_table.to_s.singularize}_id") + references = options[:references] ? options[:references].first : nil + references_sql = quote_column_name(options[:primary_key] || references || "id") + end + + table_name = to_table + + sql = "FOREIGN KEY (#{columns_sql}) REFERENCES #{quote_table_name(table_name)}(#{references_sql})" + + case options[:dependent] + when :nullify + sql << " ON DELETE SET NULL" + when :delete + sql << " ON DELETE CASCADE" + end + sql + end + + # Extract all stored procedures, packages, synonyms and views. def structure_dump_db_stored_code #:nodoc: structure = [] From 3586737d31257a794718031c2e262e740a7190b2 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 17 Jun 2015 13:48:05 +0000 Subject: [PATCH 084/114] Rails 4.2 support foreign_key and remove_foreign_key in create_table and change_table --- .../oracle_enhanced/schema_definitions.rb | 12 ++++++++++++ .../oracle_enhanced/schema_statements.rb | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb index dbe87ebc5..ed750af35 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb @@ -75,6 +75,18 @@ def add_foreign_key(to_table, options) @foreign_key_adds << OracleEnhanced::ForeignKeyDefinition.new(name, to_table, options) end end + + class Table < ActiveRecord::ConnectionAdapters::Table + def foreign_key(to_table, options = {}) + to_table = to_table.to_s.pluralize if ActiveRecord::Base.pluralize_table_names + @base.add_foreign_key(@name, to_table, options) + end + + def remove_foreign_key(options = {}) + @base.remove_foreign_key(@name, options) + end + end + end end end diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb index ce25600f7..6997bb81e 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb @@ -158,6 +158,10 @@ def initialize_schema_migrations_table end end + def update_table_definition(table_name, base) #:nodoc: + OracleEnhanced::Table.new(table_name, base) + end + def add_index(table_name, column_name, options = {}) #:nodoc: index_name, index_type, quoted_column_names, tablespace, index_options = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{index_options}" From 20e76c8fe94fbaf94674f7484b15b6a736bffa64 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 19 Jun 2015 02:11:29 +0000 Subject: [PATCH 085/114] Support add_foreign_key option :dependent => :delete and :dependent => :nullify --- .../oracle_enhanced/schema_statements.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb index 6997bb81e..fe0a3b9b6 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb @@ -399,6 +399,12 @@ def tablespace(table_name) end def add_foreign_key(from_table, to_table, options = {}) + case options[:dependent] + when :delete then options[:on_delete] = :cascade + when :nullify then options[:on_delete] = :nullify + else + end + super end From cf068c9fa36cf2665724c19ab3c84dae5ac2593f Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 18 Jun 2015 12:11:40 +0000 Subject: [PATCH 086/114] Update History to prepare 1.6.0.beta1 --- History.md | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/History.md b/History.md index 21513c40a..39a6535ef 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,90 @@ +## 1.6.0.beta 1 / 2016-06-19 +* Enhancements and bug fix + * Support Rails 4.2 + * Support Rails native foreign key syntax [#488, #618] + +* Other changes and bug fixes + * Column#primary method removed from Rails [#483] + * ActiveRecord::Migrator.proper_table_name has been removed from Rails [#481] + * New db/schema.rb files will be created with force: :cascade [#593] + * Rails42 add unique index creates unique constraint [#617] + * Modify remove_column to add cascade constraint to avoid ORA-12991 [#617] + * Add `null: true` to avoid DEPRECATION WARNING [#489, #499] + * Rails 4.2 Add `connection.supports_views?` [#496] + * text? has been removed from Column class [#487] + * Remove call to deprecated `serialized_attributes` [#550, #552] + * Support :cascade option for drop_table [#579] + * Raise a better exception for renaming long indexes [#577] + * Override aliased_types [#575] + * Revert "Add options_include_default!" [#586] + * Remove substitute_at method from Oracle enhanced adapter [#520] + * Require 'active_record/base' in rake task #526 + * Make it easier to spot which version of active record is actually used [#550] + * Rails4.2 Add Type::Raw type [#503] + * Support :bigint datatype [#580] + * Map :bigint as NUMBER(19) sql_type not NUMBER(8) [#608] + * Use Oracle BINARY_FLOAT datatype for Rails :float type [#610] + * Revert "Implement possibility of handling of NUMBER columns as :float" [#576] + * Rails 4.2 Support NCHAR correctly [#490] + * Support :timestamp datatype in Rails 4.2 [#575] + * Rails 4.2 Handle NUMBER sql_type as `Type::Integer` cast type [#509] + * ActiveRecord::OracleEnhanced::Type::Integer for max_value to take 38 digits [#605] + * Rails 4.2 add `module OracleEnhanced` and migrate classes/modules under this [#584] + * Migrate to ActiveRecord::ConnectionAdapters::OracleEnhanced::ColumnDumper [#597] + * Migrated from OracleEnhancedContextIndex to OracleEnhanced::ContextIndex [#599] + * Make OracleEnhancedIndexDefinition as subclass of IndexDefinition [#600] + * Refactor add_index and add_index_options [#601] + * Types namespace moved to `ActiveRecord::Type::Value` [#484] + * Add new_column method [#482] + * Rename type_cast to type_cast_from_database [#485] + * Removed `forced_column_type` by using `cast_type` [#595] + * Move dump_schema_information to SchemaStatements [#611] + * Move OracleEnhancedIndexDefinition to OracleEnhanced::IndexDefinition [#614] + * Move OracleEnhancedSynonymDefinition to OracleEnhanced::SynonymDefinition [#615] + * Move types under OracleEnhanced module [#603] + * Make OracleEnhancedForeignKeyDefinition as subclass of ForeignKeyDefinition [#581] + * Support _field_changed argument changes [#479] + * Rails 4.2 Don't type cast the default on the column [#504] + * Rename variable names in create_table to follow Rails implementation [#616] + * Rails 4.2: Fix create_savepoint and rollback_to_savepoint name [#497] + * Shorten foreign key name if it is longer than 30 byte [#621] + * Restore foreign_key_definition [#624] + * Rails 4.2 Support OracleEnhancedAdapter.emulate_integers_by_column_name [#491] + * Rails 4.2 Support OracleEnhancedAdapter.emulate_dates_by_column_name [#492] + * Rails 4.2 Support emulate_booleans_from_strings and is_boolean_column? [#506] + * Rails 4.2 Support OracleEnhancedAdapter.number_datatype_coercion [#512] + * Rails 4.2 Use register_class_with_limit [#502] + * Rails 4.2 Remove redundant substitute index when constructing bind values [#517] + * Rails 4.2 Unit test updated to support `substitute_at` in Arel [#522] + * Change log method signiture to support Rails 4.2 [#539] + * Enable loading spec configuration from config file instead of env [#550] + * Rails42: Issue with non-existent columns [#545, #551] + * Squelch warning "#column_for_attribute` will return a null object + for non-existent columns in Rails 5. Use `#has_attribute?`" [#551] + * Use arel 6-0-stable [#565] + * Support 'Y' as true and 'N' as false in Rails 4.2 [#574, #573] + * Remove alias_method_chain :references, :foreign_keys [#582] + * Use quote_value method to avoid undefined method `type_cast_for_database' for nil:NilClass [#486] + * Rails 4.2: Set @nchar and @object_type only when sql_type is true [#493] + * Rails 4.2: Handle forced_column_type temporary [#498] + * Rails 4.2 Address ArgumentError: wrong number of arguments (1 for 2) at `quote_value` [#511] + * Address ORA-00932: inconsistent datatypes: expected NUMBER got DATE [#538] + * Remove duplicate alias_method_chain for indexes [#560] + * Address RangeError: 6000000000 is out of range for ActiveRecord::Type::Integer + with limit 4 [#578] + * Return foreign_keys_without_oracle_enhanced when non Oracle database used [#583] + * Add missing database_tasks.rb to gemspec [#585] + * Fixed typo in the rake tasks load statement [#587] + * Call super when column typs is serialized [#563, #591] + * Clear query cache on rollback [#592] + * Modify default to `false` if database default value is "N" [#596] + * refer correct location if filess in gemspec [#606] + * Add integer.rb to gemspec [#607] + +* Known Issues + * Override aliased_types [#575] + * Multi column foreign key is not supported + ## 1.5.6 / 2015-03-30 * Enhancements From e1f5dd57fa7ffffe3aa9df829e58b39ef64a1194 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 19 Jun 2015 13:26:09 +0000 Subject: [PATCH 087/114] Release 1.6.0.beta1 --- VERSION | 2 +- activerecord-oracle_enhanced-adapter.gemspec | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index eac1e0ada..cb54d442c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.6 +1.6.0.beta1 diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index 533820a34..160d4c1eb 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -5,12 +5,12 @@ Gem::Specification.new do |s| s.name = %q{activerecord-oracle_enhanced-adapter} - s.version = "1.5.6" + s.version = "1.6.0.beta1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.license = 'MIT' s.authors = [%q{Raimonds Simanovskis}] - s.date = %q{2015-03-30} + s.date = %q{2015-06-19} s.description = %q{Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases. This adapter is superset of original ActiveRecord Oracle adapter. } From 3562b957088a9c9b5f5915b7dd6acd25d53f303b Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Fri, 19 Jun 2015 14:29:31 +0000 Subject: [PATCH 088/114] Bump jeweler version --- Gemfile | 2 +- activerecord-oracle_enhanced-adapter.gemspec | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index f56f8ffe3..08bb20bd8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'http://rubygems.org' group :development do - gem 'jeweler', '~> 1.8' + gem 'jeweler', '~> 2.0' gem 'rspec', '~> 2.4' gem 'rdoc' diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index 160d4c1eb..dd4daa4e7 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -92,7 +92,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. s.specification_version = 3 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - s.add_development_dependency(%q, ["~> 1.8"]) + s.add_development_dependency(%q, ["~> 2.0"]) s.add_development_dependency(%q, ["~> 2.4"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 0"]) @@ -104,7 +104,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. s.add_development_dependency(%q, [">= 0.4.4"]) s.add_development_dependency(%q, [">= 2.0.4"]) else - s.add_dependency(%q, ["~> 1.8"]) + s.add_dependency(%q, ["~> 2.0"]) s.add_dependency(%q, ["~> 2.4"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) @@ -117,7 +117,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. s.add_dependency(%q, [">= 2.0.4"]) end else - s.add_dependency(%q, ["~> 1.8"]) + s.add_dependency(%q, ["~> 2.0"]) s.add_dependency(%q, ["~> 2.4"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) From 2af2cbe53abad5ad0fc82a1a8825aedcc1f3c5aa Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 22 Jun 2015 17:34:15 +0000 Subject: [PATCH 089/114] Add deprecation warnings for Oracle enhanced specific foreign key methods to keep Rails compatibility --- .../connection_adapters/oracle_enhanced/schema_definitions.rb | 2 ++ .../connection_adapters/oracle_enhanced/schema_statements.rb | 3 +++ 2 files changed, 5 insertions(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb index ed750af35..c7b6b792a 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb @@ -78,11 +78,13 @@ def add_foreign_key(to_table, options) class Table < ActiveRecord::ConnectionAdapters::Table def foreign_key(to_table, options = {}) + ActiveSupport::Deprecation.warn "`foreign_key` option will be deprecated. Please use `references` option" to_table = to_table.to_s.pluralize if ActiveRecord::Base.pluralize_table_names @base.add_foreign_key(@name, to_table, options) end def remove_foreign_key(options = {}) + ActiveSupport::Deprecation.warn "`remove_foreign_key` option will be deprecated. Please use `remove_references` option" @base.remove_foreign_key(@name, options) end end diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb index fe0a3b9b6..f40e236d5 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb @@ -399,6 +399,9 @@ def tablespace(table_name) end def add_foreign_key(from_table, to_table, options = {}) + if options[:dependent] + ActiveSupport::Deprecation.warn "`:dependent` option will be deprecated. Please use `:on_delete` option" + end case options[:dependent] when :delete then options[:on_delete] = :cascade when :nullify then options[:on_delete] = :nullify From aeb7688b47cb5781cedfda321dfa28523f13c8e7 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 22 Jun 2015 19:47:22 +0000 Subject: [PATCH 090/114] Skip composite foreign key tests not supported in this version --- .../connection_adapters/oracle_enhanced_schema_dump_spec.rb | 1 + .../oracle_enhanced_schema_statements_spec.rb | 2 ++ .../connection_adapters/oracle_enhanced_structure_dump_spec.rb | 1 + 3 files changed, 4 insertions(+) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb index d2f95c69e..4e30631c5 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb @@ -226,6 +226,7 @@ def drop_test_posts_table end it "should include composite foreign keys" do + pending "Composite foreign keys are not supported in this version" schema_define do add_column :test_posts, :baz_id, :integer add_column :test_posts, :fooz_id, :integer diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb index 8090c474d..82faf7cd5 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb @@ -721,6 +721,7 @@ class ::TestComment < ActiveRecord::Base end it "should add a composite foreign key" do + pending "Composite foreign keys are not supported in this version" schema_define do add_column :test_posts, :baz_id, :integer add_column :test_posts, :fooz_id, :integer @@ -743,6 +744,7 @@ class ::TestComment < ActiveRecord::Base end it "should add a composite foreign key with name" do + pending "Composite foreign keys are not supported in this version" schema_define do add_column :test_posts, :baz_id, :integer add_column :test_posts, :fooz_id, :integer diff --git a/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb index beb1dc128..be44bdf2f 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb @@ -92,6 +92,7 @@ class ::TestPost < ActiveRecord::Base end it "should dump composite foreign keys" do + pending "Composite foreign keys are not supported in this version" @conn.add_column :foos, :fooz_id, :integer @conn.add_column :foos, :baz_id, :integer From 43a0f2be9f5febfc619a44037778a6f2b60580b6 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 22 Jun 2015 20:54:35 +0000 Subject: [PATCH 091/114] Do not dump default foreign key name Refer https://github.com/rails/rails/commit/8768305f20d12c40241396092a63e0d56269fefe and https://github.com/rails/rails/pull/15606 --- .../connection_adapters/oracle_enhanced_schema_dump_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb index 4e30631c5..1f8385f70 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb @@ -180,7 +180,7 @@ def drop_test_posts_table schema_define do add_foreign_key :test_comments, :test_posts end - standard_dump.should =~ /add_foreign_key "test_comments", "test_posts", name: "test_comments_test_post_id_fk"/ + standard_dump.should =~ /add_foreign_key "test_comments", "test_posts"/ end it "should include foreign key with delete dependency in schema dump" do From e4444b9091991cd64db8a7e77fea144c943f2347 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 22 Jun 2015 21:18:45 +0000 Subject: [PATCH 092/114] Schema dump uses Rails default `:on_delete` option instead of `:dependent` option Refer https://github.com/rsim/oracle-enhanced/pull/631 :dependent option will be deprecated. --- .../connection_adapters/oracle_enhanced_schema_dump_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb index 1f8385f70..69c4f0a7a 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb @@ -187,14 +187,14 @@ def drop_test_posts_table schema_define do add_foreign_key :test_comments, :test_posts, dependent: :delete end - standard_dump.should =~ /add_foreign_key "test_comments", "test_posts", name: "test_comments_test_post_id_fk", dependent: :delete/ + standard_dump.should =~ /add_foreign_key "test_comments", "test_posts", on_delete: :cascade/ end it "should include foreign key with nullify dependency in schema dump" do schema_define do add_foreign_key :test_comments, :test_posts, dependent: :nullify end - standard_dump.should =~ /add_foreign_key "test_comments", "test_posts", name: "test_comments_test_post_id_fk", dependent: :nullify/ + standard_dump.should =~ /add_foreign_key "test_comments", "test_posts", on_delete: :nullify/ end it "should not include foreign keys on ignored table names in schema dump" do From 47c201bc40c57d0e32b902c6662c3fcc69570986 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 23 Jun 2015 01:42:06 +0000 Subject: [PATCH 093/114] Use Rails foreign key name https://github.com/rails/rails/pull/18791 --- .../oracle_enhanced_schema_statements_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb index 82faf7cd5..a669468af 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb @@ -627,12 +627,14 @@ class ::TestComment < ActiveRecord::Base end it "should add foreign key" do + fk_name = "fk_rails_#{Digest::SHA256.hexdigest("test_comments_test_post_id_fk").first(10)}" + schema_define do add_foreign_key :test_comments, :test_posts end lambda do TestComment.create(:body => "test", :test_post_id => 1) - end.should raise_error() {|e| e.message.should =~ /ORA-02291.*\.TEST_COMMENTS_TEST_POST_ID_FK/} + end.should raise_error() {|e| e.message.should =~ /ORA-02291.*\.#{fk_name}/i} end context "with table_name_prefix" do @@ -692,12 +694,14 @@ class ::TestComment < ActiveRecord::Base end it "should add foreign key with column" do + fk_name = "fk_rails_#{Digest::SHA256.hexdigest("test_comments_post_id_fk").first(10)}" + schema_define do add_foreign_key :test_comments, :test_posts, :column => "post_id" end lambda do TestComment.create(:body => "test", :post_id => 1) - end.should raise_error() {|e| e.message.should =~ /ORA-02291.*\.TEST_COMMENTS_POST_ID_FK/} + end.should raise_error() {|e| e.message.should =~ /ORA-02291.*\.#{fk_name}/i} end it "should add foreign key with delete dependency" do From 1b1564e265461107bf1b585ceee5184a1d81690e Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 23 Jun 2015 12:55:35 +0000 Subject: [PATCH 094/114] Add deprecate warning if foreign key name length is longer than 30 byte --- .../connection_adapters/oracle_enhanced/schema_definitions.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb index c7b6b792a..98c4cfb96 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb @@ -14,6 +14,7 @@ module OracleEnhanced class ForeignKeyDefinition < ActiveRecord::ConnectionAdapters::ForeignKeyDefinition def name if options[:name].length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH + ActiveSupport::Deprecation.warn "Foreign key name #{options[:name]} is too long. It will not get shorten in later version of Oracle enhanced adapter" 'c'+Digest::SHA1.hexdigest(options[:name])[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1] else options[:name] From ad842ad23e0395ac6d9ff40a0f993d3ac6c76498 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 23 Jun 2015 13:05:54 +0000 Subject: [PATCH 095/114] Foreign key name which is longer than 30 byte will be always shortened using Digest::SHA1.hexdigest Refer #621 and #636 --- .../oracle_enhanced_schema_statements_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb index a669468af..f11230808 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb @@ -680,7 +680,8 @@ class ::TestComment < ActiveRecord::Base end lambda do TestComment.create(:body => "test", :test_post_id => 1) - end.should raise_error() {|e| e.message.should =~ /ORA-02291.*\.TES_COM_TES_POS_ID_FOR_KEY/} + end.should raise_error() {|e| e.message.should =~ + /ORA-02291.*\.C#{Digest::SHA1.hexdigest("test_comments_test_post_id_foreign_key")[0,29].upcase}/} end it "should add foreign key with very long name which is shortened" do From 932d6da51bb62d89ac33c7a5a2240c300c855afb Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 23 Jun 2015 13:16:11 +0000 Subject: [PATCH 096/114] Schema dumper for :integer will not dump :precision 0 --- .../connection_adapters/oracle_enhanced_schema_dump_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb index 69c4f0a7a..4f7c7d1e0 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb @@ -406,7 +406,7 @@ class ::TestName < ActiveRecord::Base it 'should dump correctly' do standard_dump.should =~ /t\.virtual "full_name",(\s*)limit: 512,(\s*)as: "\\"FIRST_NAME\\"\|\|', '\|\|\\"LAST_NAME\\"",(\s*)type: :string/ standard_dump.should =~ /t\.virtual "short_name",(\s*)limit: 300,(\s*)as:(.*),(\s*)type: :string/ - standard_dump.should =~ /t\.virtual "full_name_length",(\s*)precision: 38,(\s*)scale: 0,(\s*)as:(.*),(\s*)type: :integer/ + standard_dump.should =~ /t\.virtual "full_name_length",(\s*)precision: 38,(\s*)as:(.*),(\s*)type: :integer/ standard_dump.should =~ /t\.virtual "name_ratio",(\s*)as:(.*)\"$/ # no :type standard_dump.should =~ /t\.virtual "abbrev_name",(\s*)limit: 100,(\s*)as:(.*),(\s*)type: :string/ standard_dump.should =~ /t\.virtual "field_with_leading_space",(\s*)limit: 300,(\s*)as: "' '\|\|\\"FIRST_NAME\\"\|\|' '",(\s*)type: :string/ From 953fd511840b18a7748e5b7ad4866e8ee01484cf Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 25 Jun 2015 12:21:21 +0000 Subject: [PATCH 097/114] Update foreign key names for add_foreign_key with table_name_prefix or table_name_suffix --- .../oracle_enhanced_schema_statements_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb index f11230808..0454e5422 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb @@ -641,13 +641,14 @@ class ::TestComment < ActiveRecord::Base let(:table_name_prefix) { 'xxx_' } it "should use table_name_prefix for foreign table" do + fk_name = "fk_rails_#{Digest::SHA256.hexdigest("xxx_test_comments_test_post_id_fk").first(10)}" schema_define do add_foreign_key :test_comments, :test_posts end lambda do TestComment.create(:body => "test", :test_post_id => 1) - end.should raise_error() {|e| e.message.should =~ /ORA-02291.*\.XXX_TES_COM_TES_POS_ID_FK/} + end.should raise_error() {|e| e.message.should =~ /ORA-02291.*\.#{fk_name}/i} end end @@ -655,13 +656,14 @@ class ::TestComment < ActiveRecord::Base let(:table_name_suffix) { '_xxx' } it "should use table_name_suffix for foreign table" do + fk_name = "fk_rails_#{Digest::SHA256.hexdigest("test_comments_xxx_test_post_id_fk").first(10)}" schema_define do add_foreign_key :test_comments, :test_posts end lambda do TestComment.create(:body => "test", :test_post_id => 1) - end.should raise_error() {|e| e.message.should =~ /ORA-02291.*\.TES_COM_XXX_TES_POS_ID_FK/} + end.should raise_error() {|e| e.message.should =~ /ORA-02291.*\.#{fk_name}/i} end end From 6b466a99a2d0221c6053a3ff4e704563f32557c8 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 25 Jun 2015 16:08:13 +0000 Subject: [PATCH 098/114] Update history for v1.6.0 --- History.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 39a6535ef..da0d03f64 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,25 @@ -## 1.6.0.beta 1 / 2016-06-19 -* Enhancements and bug fix +## 1.6.0 / 2015-06-25 + +* Changes and bug fixes since 1.6.0.beta1 + + * Add deprecation warnings for Oracle enhanced specific foreign key methods [#631] + * Skip composite foreign key tests not supported in this version [#632] + * Do not dump default foreign key name [#633] + * Schema dump uses `:on_delete` option instead of `:dependent` [#634] + * Use Rails foreign key name in rspec unit tests [#635] + * Add deprecate warning if foreign key name length is longer than 30 byte [#636] + * Foreign key name longer than 30 byte will be shortened using Digest::SHA1.hexdigest [#637] + * Schema dumper for :integer will not dump :precision 0 [#638] + * Update foreign key names for add_foreign_key with table_name_prefix [#643] + +* Known Issues since 1.6.0.beta1 + * table_name_prefix and table_name_suffix changes column names which cause ORA-00904 [#639] + * custom methods should rollback record when exception is raised in after_create callback fails [#640] + * custom methods for create, update and destroy should log create record fails [#641] + +## 1.6.0.beta 1 / 2015-06-19 + +* Enhancements * Support Rails 4.2 * Support Rails native foreign key syntax [#488, #618] From 850d7c47df61c6fb58eb69d608a8ec0db2b19574 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Thu, 25 Jun 2015 16:10:11 +0000 Subject: [PATCH 099/114] Release 1.6.0 --- VERSION | 2 +- activerecord-oracle_enhanced-adapter.gemspec | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index cb54d442c..dc1e644a1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.0.beta1 +1.6.0 diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index dd4daa4e7..0348c0fca 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -5,12 +5,12 @@ Gem::Specification.new do |s| s.name = %q{activerecord-oracle_enhanced-adapter} - s.version = "1.6.0.beta1" + s.version = "1.6.0" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.license = 'MIT' s.authors = [%q{Raimonds Simanovskis}] - s.date = %q{2015-06-19} + s.date = %q{2015-06-25} s.description = %q{Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases. This adapter is superset of original ActiveRecord Oracle adapter. } From a0890f39e8bd388a4af46981a313543fe3cb2a9f Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sun, 28 Jun 2015 20:24:59 +0000 Subject: [PATCH 100/114] Requires activerecord 4.2.1 or higher --- activerecord-oracle_enhanced-adapter.gemspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index 0348c0fca..45953d8b1 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -94,7 +94,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q, ["~> 2.0"]) s.add_development_dependency(%q, ["~> 2.4"]) - s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, ["~> 4.2.1"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 0"]) @@ -106,7 +106,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. else s.add_dependency(%q, ["~> 2.0"]) s.add_dependency(%q, ["~> 2.4"]) - s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, ["~> 4.2.1"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) @@ -120,7 +120,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. s.add_dependency(%q, ["~> 2.0"]) s.add_dependency(%q, ["~> 2.4"]) s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, ["~> 4.2.1"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) From 110db8180d9e6af8759d71a6139ab7a7b1038f56 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 29 Jun 2015 15:17:31 +0000 Subject: [PATCH 101/114] Update matcher to skip sql statements to get `table` metadata --- .../connection_adapters/oracle_enhanced_procedures_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb index 490363faa..e7f49fef0 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb @@ -325,7 +325,8 @@ def raise_make_transaction_rollback :last_name => "Last", :hire_date => @today ) - @logger.logged(:debug).last.should match(/^TestEmployee Create \(\d+\.\d+(ms)?\) custom create method$/) + #TODO: dirty workaround to remove sql statement for `table` method + @logger.logged(:debug)[-2].should match(/^TestEmployee Create \(\d+\.\d+(ms)?\) custom create method$/) clear_logger end From 9226503e30c0da63ec4f077098ab80591b63fba5 Mon Sep 17 00:00:00 2001 From: Kohei Hasegawa Date: Tue, 30 Jun 2015 12:02:29 +0900 Subject: [PATCH 102/114] Update Ruby version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c68e0c59..c3a55aeba 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ If you would like to use latest adapter version from github then specify gem 'activerecord-oracle_enhanced-adapter', :git => 'git://github.com/rsim/oracle-enhanced.git' ``` -If you are using CRuby 1.9.3 or 2.0 then you need to install ruby-oci8 gem as well as Oracle client, e.g. [Oracle Instant Client](http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html). Include in Gemfile also ruby-oci8: +If you are using CRuby >= 1.9.3 then you need to install ruby-oci8 gem as well as Oracle client, e.g. [Oracle Instant Client](http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html). Include in Gemfile also ruby-oci8: ```ruby gem 'ruby-oci8', '~> 2.1.0' From 05ff0c737abfbd384c180720612b08078f312b8e Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 30 Jun 2015 17:45:10 +0000 Subject: [PATCH 103/114] Fix serialized value becomes from yaml to string once saved --- .../connection_adapters/oracle_enhanced_adapter.rb | 2 +- .../oracle_enhanced_data_types_spec.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 8a6283070..9030fc54b 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -826,7 +826,7 @@ def write_lobs(table_name, klass, attributes, columns) #:nodoc: value = attributes[col.name] # changed sequence of next two lines - should check if value is nil before converting to yaml next if value.nil? || (value == '') - value = value.to_yaml if value.is_a?(String) && klass.serialized_attributes[col.name] + value = value.to_yaml if klass.serialized_attributes[col.name] uncached do sql = is_with_cpk ? "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} WHERE #{klass.composite_where_clause(id)} FOR UPDATE" : "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} WHERE #{quote_column_name(klass.primary_key)} = #{id} FOR UPDATE" diff --git a/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb index 22788e5d7..4664b011d 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb @@ -1087,6 +1087,18 @@ class ::TestSerializeEmployee < ActiveRecord::Base @employee.reload @employee.comments.should == "initial serialized data" end + + it "should keep serialized data after save" do + @employee = Test2Employee.new + @employee.comments = {:length=>{:is=>1}} + @employee.save + @employee.reload + @employee.comments.should == {:length=>{:is=>1}} + @employee.comments = {:length=>{:is=>2}} + @employee.save + @employee.reload + @employee.comments.should == {:length=>{:is=>2}} + end end describe "OracleEnhancedAdapter handling of BLOB columns" do From 058250a2100434a900401b26f0ff1cfe89709b38 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 1 Jul 2015 12:39:43 +0000 Subject: [PATCH 104/114] Update readme for v1.6 --- README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c3a55aeba..9c0a8640a 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,22 @@ Oracle enhanced ActiveRecord adapter provides Oracle database access from Ruby o INSTALLATION ------------ +### Rails 4.2 -### Rails 4 +Oracle enhanced adapter version 1.6 just supports Rails 4.2 and does not support Rails 4.1 or lower version of Rails. +When using Ruby on Rails version 4.2 then in Gemfile include -Oracle enhanced adapter version 1.5 just supports Rails 4 and does not support Rails 3.2 or lower version of Rails. +```ruby +gem 'activerecord-oracle_enhanced-adapter', '~> 1.6.0' +``` + +where instead of 1.6.0 you can specify any other desired version. It is recommended to specify version with `~>` which means that use specified version or later patch versions (in this example any later 1.5.x version but not 1.6.x version). Oracle enhanced adapter maintains API backwards compatibility during patch version upgrades and therefore it is safe to always upgrade to latest patch version. + +### Rails 4.0 and 4.1 + +Oracle enhanced adapter version 1.5 supports Rails 4.0 and 4.1 and does not support Rails 3.2 or lower version of Rails. -When using Ruby on Rails version 4 then in Gemfile include +When using Ruby on Rails version 4.0 and 4.1 then in Gemfile include ```ruby gem 'activerecord-oracle_enhanced-adapter', '~> 1.5.0' From 76cb902bb4a79e0879d90792a7bb231b83682134 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 1 Jul 2015 12:44:37 +0000 Subject: [PATCH 105/114] Update history for v1.6.1 --- History.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/History.md b/History.md index da0d03f64..3b38859ad 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,13 @@ +## 1.6.1 / 2015-07-01 + +* Changes and bug fixes since 1.6.0 + + * Oracle enhanced adapter v1.6 requires ActiveRecord 4.2.1 or higher, + ActiveRecord 4.2.0 is not supported.[#651, #652] + * Fix serialized value becomes from yaml to string once saved [#655, #657] + * Update Ruby version in readme [#654] + * Update unit test matcher to skip sql statements to get `table` metadata [#653] + ## 1.6.0 / 2015-06-25 * Changes and bug fixes since 1.6.0.beta1 From da33a2c1719d04033f93a5efeac106ec22aece92 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 1 Jul 2015 12:45:53 +0000 Subject: [PATCH 106/114] Release 1.6.1 --- VERSION | 2 +- activerecord-oracle_enhanced-adapter.gemspec | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index dc1e644a1..9c6d6293b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.0 +1.6.1 diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index 45953d8b1..08b3cd939 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -5,12 +5,12 @@ Gem::Specification.new do |s| s.name = %q{activerecord-oracle_enhanced-adapter} - s.version = "1.6.0" + s.version = "1.6.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.license = 'MIT' s.authors = [%q{Raimonds Simanovskis}] - s.date = %q{2015-06-25} + s.date = %q{2015-07-01} s.description = %q{Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases. This adapter is superset of original ActiveRecord Oracle adapter. } From 23aa92ea6f8886555cdde5137a4a31df0e3d837d Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sun, 5 Jul 2015 14:29:45 +0000 Subject: [PATCH 107/114] Unique constraints not created when function unique index created due to it causes ORA-00904 reported in #662 Note: This attribute cannot be referenced by foreign key since it requires unique constraints created. --- .../oracle_enhanced/schema_statements.rb | 4 +++- .../oracle_enhanced_schema_statements_spec.rb | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb index f40e236d5..a9bcdba1c 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb @@ -166,7 +166,9 @@ def add_index(table_name, column_name, options = {}) #:nodoc: index_name, index_type, quoted_column_names, tablespace, index_options = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{index_options}" if index_type == 'UNIQUE' - execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_column_name(index_name)} #{index_type} (#{quoted_column_names})" + unless quoted_column_names =~ /\(.*\)/ + execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_column_name(index_name)} #{index_type} (#{quoted_column_names})" + end end ensure self.all_schema_indexes = nil diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb index 0454e5422..b520db135 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb @@ -1368,6 +1368,13 @@ class <<@conn @would_execute_sql.should =~ /CREATE +INDEX .* ON .* \(.*\) TABLESPACE #{DATABASE_NON_DEFAULT_TABLESPACE}/ end + it "should create unique function index but not create unique constraints" do + schema_define do + add_index :keyboards, 'lower(name)', unique: true, name: :index_keyboards_on_lower_name + end + @would_execute_sql.should_not =~ /ALTER +TABLE .* ADD CONSTRAINT .* UNIQUE \(.*\(.*\)\)/ + end + describe "#initialize_schema_migrations_table" do # In Rails 2.3 to 3.2.x the index name for the migrations # table is hard-coded. We can modify the index name here From a2b394dfe2485ec30294dda2163e3a08b78a7b97 Mon Sep 17 00:00:00 2001 From: Benjamin Ortega Date: Tue, 14 Jul 2015 11:07:59 -0500 Subject: [PATCH 108/114] create_table should use default tablespace values for lobs --- .../oracle_enhanced/schema_creation.rb | 36 ++++++++--------- .../oracle_enhanced_schema_statements_spec.rb | 39 +++++++++++++++++++ 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb index 0daa8e95c..3cc5ca6e5 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb @@ -5,40 +5,38 @@ class SchemaCreation < AbstractAdapter::SchemaCreation private def visit_ColumnDefinition(o) - if o.type.to_sym == :virtual - sql_type = type_to_sql(o.default[:type], o.limit, o.precision, o.scale) if o.default[:type] - "#{quote_column_name(o.name)} #{sql_type} AS (#{o.default[:as]})" - else - super + case + when o.type.to_sym == :virtual + sql_type = type_to_sql(o.default[:type], o.limit, o.precision, o.scale) if o.default[:type] + return "#{quote_column_name(o.name)} #{sql_type} AS (#{o.default[:as]})" + when [:blob, :clob].include?(sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale).downcase.to_sym) + if (tablespace = default_tablespace_for(sql_type)) + @lob_tablespaces ||= {} + @lob_tablespaces[o.name] = tablespace + end end + super end def visit_TableDefinition(o) - tablespace = tablespace_for(:table, o.options[:tablespace]) create_sql = "CREATE#{' GLOBAL TEMPORARY' if o.temporary} TABLE " create_sql << "#{quote_table_name(o.name)} (" create_sql << o.columns.map { |c| accept c }.join(', ') create_sql << ")" + unless o.temporary + @lob_tablespaces.each do |lob_column, tablespace| + create_sql << " LOB (#{quote_column_name(lob_column)}) STORE AS (TABLESPACE #{tablespace}) \n" + end if defined?(@lob_tablespaces) create_sql << " ORGANIZATION #{o.options[:organization]}" if o.options[:organization] - create_sql << "#{tablespace}" + if (tablespace = o.options[:tablespace] || default_tablespace_for(:table)) + create_sql << " TABLESPACE #{tablespace}" + end end create_sql << " #{o.options[:options]}" create_sql end - def tablespace_for(obj_type, tablespace_option, table_name=nil, column_name=nil) - tablespace_sql = '' - if tablespace = (tablespace_option || default_tablespace_for(obj_type)) - tablespace_sql << if [:blob, :clob].include?(obj_type.to_sym) - " LOB (#{quote_column_name(column_name)}) STORE AS #{column_name.to_s[0..10]}_#{table_name.to_s[0..14]}_ls (TABLESPACE #{tablespace})" - else - " TABLESPACE #{tablespace}" - end - end - tablespace_sql - end - def default_tablespace_for(type) (ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[type] || ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[native_database_types[type][:name]]) rescue nil diff --git a/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb b/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb index b520db135..a4ee5d94c 100644 --- a/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +++ b/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb @@ -804,6 +804,45 @@ class ::TestComment < ActiveRecord::Base end + describe "lob in table definition" do + before do + class ::TestPost < ActiveRecord::Base + end + end + it 'should use default tablespace for clobs' do + ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:clob] = DATABASE_NON_DEFAULT_TABLESPACE + ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:blob] = nil + schema_define do + create_table :test_posts, :force => true do |t| + t.text :test_clob + t.binary :test_blob + end + end + TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_CLOB'").should == DATABASE_NON_DEFAULT_TABLESPACE + TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_BLOB'").should_not == DATABASE_NON_DEFAULT_TABLESPACE + end + + it 'should use default tablespace for blobs' do + ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:blob] = DATABASE_NON_DEFAULT_TABLESPACE + ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces[:clob] = nil + schema_define do + create_table :test_posts, :force => true do |t| + t.text :test_clob + t.binary :test_blob + end + end + TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_BLOB'").should == DATABASE_NON_DEFAULT_TABLESPACE + TestPost.connection.select_value("SELECT tablespace_name FROM user_lobs WHERE table_name='TEST_POSTS' and column_name = 'TEST_CLOB'").should_not == DATABASE_NON_DEFAULT_TABLESPACE + end + + after do + Object.send(:remove_const, "TestPost") + schema_define do + drop_table :test_posts rescue nil + end + end + end + describe "foreign key in table definition" do before(:each) do schema_define do From 2b67d6de72f7908a12901f32f6eb7f4a3305e604 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 20 Jul 2015 15:00:56 +0000 Subject: [PATCH 109/114] Requires activerecord 4.2.1 or higher, not activemodel --- activerecord-oracle_enhanced-adapter.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index 08b3cd939..3e9c74ac4 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -119,8 +119,8 @@ This adapter is superset of original ActiveRecord Oracle adapter. else s.add_dependency(%q, ["~> 2.0"]) s.add_dependency(%q, ["~> 2.4"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, ["~> 4.2.1"]) + s.add_dependency(%q, ["~> 4.2.1"]) + s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) From 33b2cfd0969783a8cd80636acd49410a1e7a3332 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 20 Jul 2015 15:23:24 +0000 Subject: [PATCH 110/114] Update history for v1.6.2 --- History.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/History.md b/History.md index 3b38859ad..3720ea6aa 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,12 @@ +## 1.6.2 / 2015-07-20 + +* Changes and bug fixes since 1.6.1 + + * Oracle enhanced adapter v1.6 requires ActiveRecord 4.2.1 or higher, + ActiveRecord 4.2.0 is not supported.[#672] + * Unique constraints not created when function unique index created [#662, #663] + * create_table should use default tablespace values for lobs [#668] + ## 1.6.1 / 2015-07-01 * Changes and bug fixes since 1.6.0 From f2450153cdadd67f3094cd302a3b633a09b25f4c Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 20 Jul 2015 15:24:57 +0000 Subject: [PATCH 111/114] Release 1.6.2 --- VERSION | 2 +- activerecord-oracle_enhanced-adapter.gemspec | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 9c6d6293b..fdd3be6df 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.1 +1.6.2 diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/activerecord-oracle_enhanced-adapter.gemspec index 3e9c74ac4..2f4a30476 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/activerecord-oracle_enhanced-adapter.gemspec @@ -5,12 +5,12 @@ Gem::Specification.new do |s| s.name = %q{activerecord-oracle_enhanced-adapter} - s.version = "1.6.1" + s.version = "1.6.2" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.license = 'MIT' s.authors = [%q{Raimonds Simanovskis}] - s.date = %q{2015-07-01} + s.date = %q{2015-07-20} s.description = %q{Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases. This adapter is superset of original ActiveRecord Oracle adapter. } From 200e9ba2117290ecc064b35a40eabdc9a34de5d6 Mon Sep 17 00:00:00 2001 From: Walter Davis Date: Tue, 11 Aug 2015 15:50:26 -0400 Subject: [PATCH 112/114] first pass at upgrading to Rails 4.2.x, still missing one element --- Gemfile | 10 ++++----- Rakefile | 10 ++++----- VERSION | 2 +- .../oracle_enhanced/jdbc_connection.rb | 10 +++++++-- .../oracle_enhanced/oci_connection.rb | 6 ++++- .../oracle_enhanced/schema_statements_ext.rb | 11 +++++++++- .../oracle_enhanced/structure_dump.rb | 22 +++++++++---------- .../oracle_enhanced_adapter.rb | 13 +++++++---- ...s-activerecord-oracle_enhanced-adapter.rb} | 0 ...tiverecord-oracle_enhanced-adapter.gemspec | 21 ++++++++++-------- 10 files changed, 66 insertions(+), 39 deletions(-) rename lib/{activerecord-oracle_enhanced-adapter.rb => pmacs-activerecord-oracle_enhanced-adapter.rb} (100%) rename activerecord-oracle_enhanced-adapter.gemspec => pmacs-activerecord-oracle_enhanced-adapter.gemspec (92%) diff --git a/Gemfile b/Gemfile index 08bb20bd8..86ec4ef91 100644 --- a/Gemfile +++ b/Gemfile @@ -5,11 +5,11 @@ group :development do gem 'rspec', '~> 2.4' gem 'rdoc' - gem 'activerecord', github: 'rails/rails' - gem 'activemodel', github: 'rails/rails' - gem 'activesupport', github: 'rails/rails' - gem 'actionpack', github: 'rails/rails' - gem 'railties', github: 'rails/rails' + gem 'activerecord', github: 'rails/rails', branch: '4-2-stable' + gem 'activemodel', github: 'rails/rails', branch: '4-2-stable' + gem 'activesupport', github: 'rails/rails', branch: '4-2-stable' + gem 'actionpack', github: 'rails/rails', branch: '4-2-stable' + gem 'railties', github: 'rails/rails', branch: '4-2-stable' gem 'arel', github: 'rails/arel', branch: '6-0-stable' gem 'journey', github: 'rails/journey' diff --git a/Rakefile b/Rakefile index 1f28bbbcc..8b2ccd24c 100644 --- a/Rakefile +++ b/Rakefile @@ -12,15 +12,15 @@ require 'rake' require 'jeweler' Jeweler::Tasks.new do |gem| - gem.name = "activerecord-oracle_enhanced-adapter" + gem.name = "pmacs-activerecord-oracle_enhanced-adapter" gem.summary = "Oracle enhanced adapter for ActiveRecord" gem.description = <<-EOS Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases. This adapter is superset of original ActiveRecord Oracle adapter. EOS - gem.email = "raimonds.simanovskis@gmail.com" - gem.homepage = "http://github.com/rsim/oracle-enhanced" - gem.authors = ["Raimonds Simanovskis"] + gem.email = "charles.treatman@gmail.com" + gem.homepage = "http://github.com/pmacs/oracle-enhanced" + gem.authors = ["Charles Treatman", "Raimonds Simanovskis"] gem.extra_rdoc_files = ['README.md'] gem.license = 'MIT' end @@ -54,7 +54,7 @@ Rake::RDocTask.new do |rdoc| version = File.exist?('VERSION') ? File.read('VERSION') : "" rdoc.rdoc_dir = 'doc' - rdoc.title = "activerecord-oracle_enhanced-adapter #{version}" + rdoc.title = "pmacs-activerecord-oracle_enhanced-adapter #{version}" rdoc.rdoc_files.include('README*') rdoc.rdoc_files.include('lib/**/*.rb') end diff --git a/VERSION b/VERSION index fdd3be6df..47b998336 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.2 +1.6.2.1 diff --git a/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb b/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb index 1b83623fc..583faebfa 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb @@ -159,8 +159,14 @@ def new_connection(config) self.autocommit = true - # default schema owner - @owner = username.upcase unless username.nil? + schema = config[:schema] && config[:schema].to_s + if schema.blank? + # default schema owner + @owner = username.upcase unless username.nil? + else + exec "alter session set current_schema = #{schema}" + @owner = schema + end @raw_connection end diff --git a/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb b/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb index 138113d0e..c9d749e29 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb @@ -25,7 +25,9 @@ class OracleEnhancedOCIConnection < OracleEnhancedConnection #:nodoc: def initialize(config) @raw_connection = OCI8EnhancedAutoRecover.new(config, OracleEnhancedOCIFactory) # default schema owner - @owner = config[:username].to_s.upcase + @owner = config[:schema] + @owner ||= config[:username] + @owner = @owner.to_s.upcase end def raw_oci_connection @@ -306,6 +308,7 @@ def self.new_connection(config) username = config[:username] && config[:username].to_s password = config[:password] && config[:password].to_s database = config[:database] && config[:database].to_s + schema = config[:schema] && config[:schema].to_s host, port = config[:host], config[:port] privilege = config[:privilege] && config[:privilege].to_sym async = config[:allow_concurrency] @@ -333,6 +336,7 @@ def self.new_connection(config) conn.prefetch_rows = prefetch_rows conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil conn.exec "alter session set time_zone = '#{time_zone}'" unless time_zone.blank? + conn.exec "alter session set current_schema = #{schema}" unless schema.blank? # Initialize NLS parameters OracleEnhancedAdapter::DEFAULT_NLS_PARAMETERS.each do |key, default_value| diff --git a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb index ba735c9e3..0fde27fe7 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb @@ -22,6 +22,15 @@ def add_primary_key_trigger(table_name, options={}) create_primary_key_trigger(table_name, options) end + def table_definition_tablespace + # TODO: Support specifying an :index_tablespace option in create_table? + tablespace_sql = '' + if tablespace = default_tablespace_for(:index) + tablespace_sql << " USING INDEX TABLESPACE #{tablespace}" + end + tablespace_sql + end + # Add synonym to existing table or view or sequence. Can be used to create local synonym to # remote table in other schema or in other database # Examples: @@ -50,7 +59,7 @@ def remove_synonym(name) # get synonyms for schema dump def synonyms #:nodoc: - select_all("SELECT synonym_name, table_owner, table_name, db_link FROM user_synonyms").collect do |row| + select_all("SELECT synonym_name, table_owner, table_name, db_link FROM all_synonyms WHERE owner = SYS_CONTEXT('userenv', 'current_schema')").collect do |row| OracleEnhanced::SynonymDefinition.new(oracle_downcase(row['synonym_name']), oracle_downcase(row['table_owner']), oracle_downcase(row['table_name']), oracle_downcase(row['db_link'])) end diff --git a/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb b/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb index e9b5ed860..9df546c11 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb @@ -10,7 +10,7 @@ def structure_dump #:nodoc: "CREATE SEQUENCE \"#{seq}\"" end select_values("SELECT table_name FROM all_tables t - WHERE owner = SYS_CONTEXT('userenv', 'session_user') AND secondary = 'N' + WHERE owner = SYS_CONTEXT('userenv', 'current_schema') AND secondary = 'N' AND NOT EXISTS (SELECT mv.mview_name FROM all_mviews mv WHERE mv.owner = t.owner AND mv.mview_name = t.table_name) AND NOT EXISTS (SELECT mvl.log_table FROM all_mview_logs mvl WHERE mvl.log_owner = t.owner AND mvl.log_table = t.table_name) ORDER BY 1").each do |table_name| @@ -77,7 +77,7 @@ def structure_dump_primary_key(table) #:nodoc: ON a.constraint_name = c.constraint_name WHERE c.table_name = '#{table.upcase}' AND c.constraint_type = 'P' - AND c.owner = SYS_CONTEXT('userenv', 'session_user') + AND c.owner = SYS_CONTEXT('userenv', 'current_schema') SQL pks.each do |row| opts[:name] = row['constraint_name'] @@ -95,7 +95,7 @@ def structure_dump_unique_keys(table) #:nodoc: ON a.constraint_name = c.constraint_name WHERE c.table_name = '#{table.upcase}' AND c.constraint_type = 'U' - AND c.owner = SYS_CONTEXT('userenv', 'session_user') + AND c.owner = SYS_CONTEXT('userenv', 'current_schema') SQL uks.each do |uk| keys[uk['constraint_name']] ||= [] @@ -123,7 +123,7 @@ def structure_dump_indexes(table_name) #:nodoc: end def structure_dump_fk_constraints #:nodoc: - fks = select_all("SELECT table_name FROM all_tables WHERE owner = SYS_CONTEXT('userenv', 'session_user') ORDER BY 1").map do |table| + fks = select_all("SELECT table_name FROM all_tables WHERE owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY 1").map do |table| if respond_to?(:foreign_keys) && (foreign_keys = foreign_keys(table["table_name"])).any? foreign_keys.map do |fk| sql = "ALTER TABLE #{quote_table_name(fk.from_table)} ADD CONSTRAINT #{quote_column_name(fk.options[:name])} " @@ -169,14 +169,14 @@ def structure_dump_db_stored_code #:nodoc: FROM all_source WHERE type IN ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE') AND name NOT LIKE 'BIN$%' - AND owner = SYS_CONTEXT('userenv', 'session_user') ORDER BY type").each do |source| + AND owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY type").each do |source| ddl = "CREATE OR REPLACE \n" select_all(%Q{ SELECT text FROM all_source WHERE name = '#{source['name']}' AND type = '#{source['type']}' - AND owner = SYS_CONTEXT('userenv', 'session_user') + AND owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY line }).each do |row| ddl << row['text'] @@ -193,9 +193,9 @@ def structure_dump_db_stored_code #:nodoc: # export synonyms select_all("SELECT owner, synonym_name, table_name, table_owner FROM all_synonyms - WHERE owner = SYS_CONTEXT('userenv', 'session_user') ").each do |synonym| - structure << "CREATE OR REPLACE #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']} - FOR #{synonym['table_owner']}.#{synonym['table_name']}" + WHERE owner = SYS_CONTEXT('userenv', 'current_schema') ").each do |synonym| + structure << "CREATE OR REPLACE #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']}" + structure << " FOR #{synonym['table_owner']}.#{synonym['table_name']}" end join_with_statement_token(structure) @@ -206,7 +206,7 @@ def structure_drop #:nodoc: "DROP SEQUENCE \"#{seq}\"" end select_values("SELECT table_name from all_tables t - WHERE owner = SYS_CONTEXT('userenv', 'session_user') AND secondary = 'N' + WHERE owner = SYS_CONTEXT('userenv', 'current_schema') AND secondary = 'N' AND NOT EXISTS (SELECT mv.mview_name FROM all_mviews mv WHERE mv.owner = t.owner AND mv.mview_name = t.table_name) AND NOT EXISTS (SELECT mvl.log_table FROM all_mview_logs mvl WHERE mvl.log_owner = t.owner AND mvl.log_table = t.table_name) ORDER BY 1").each do |table| @@ -218,7 +218,7 @@ def structure_drop #:nodoc: def temp_table_drop #:nodoc: join_with_statement_token(select_values( "SELECT table_name FROM all_tables - WHERE owner = SYS_CONTEXT('userenv', 'session_user') AND secondary = 'N' AND temporary = 'Y' ORDER BY 1").map do |table| + WHERE owner = SYS_CONTEXT('userenv', 'current_schema') AND secondary = 'N' AND temporary = 'Y' ORDER BY 1").map do |table| "DROP TABLE \"#{table}\" CASCADE CONSTRAINTS" end) end diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index 9030fc54b..be10e78ac 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -846,17 +846,22 @@ def current_database # Current database session user def current_user - select_value("SELECT SYS_CONTEXT('userenv', 'session_user') FROM dual") + select_value("SELECT SYS_CONTEXT('userenv', 'current_schema') FROM dual") + end + + # Current database session schema + def current_schema + select_value("SELECT SYS_CONTEXT('userenv', 'current_schema') FROM dual") end # Default tablespace name of current user def default_tablespace - select_value("SELECT LOWER(default_tablespace) FROM user_users WHERE username = SYS_CONTEXT('userenv', 'session_user')") + select_value("SELECT LOWER(default_tablespace) FROM user_users WHERE username = SYS_CONTEXT('userenv', 'current_schema')") end def tables(name = nil) #:nodoc: select_values( - "SELECT DECODE(table_name, UPPER(table_name), LOWER(table_name), table_name) FROM all_tables WHERE owner = SYS_CONTEXT('userenv', 'session_user') AND secondary = 'N'", + "SELECT DECODE(table_name, UPPER(table_name), LOWER(table_name), table_name) FROM all_tables WHERE owner = SYS_CONTEXT('userenv', 'current_schema') AND secondary = 'N'", name) end @@ -869,7 +874,7 @@ def table_exists?(table_name) end def materialized_views #:nodoc: - select_values("SELECT LOWER(mview_name) FROM all_mviews WHERE owner = SYS_CONTEXT('userenv', 'session_user')") + select_values("SELECT LOWER(mview_name) FROM all_mviews WHERE owner = SYS_CONTEXT('userenv', 'current_schema')") end cattr_accessor :all_schema_indexes #:nodoc: diff --git a/lib/activerecord-oracle_enhanced-adapter.rb b/lib/pmacs-activerecord-oracle_enhanced-adapter.rb similarity index 100% rename from lib/activerecord-oracle_enhanced-adapter.rb rename to lib/pmacs-activerecord-oracle_enhanced-adapter.rb diff --git a/activerecord-oracle_enhanced-adapter.gemspec b/pmacs-activerecord-oracle_enhanced-adapter.gemspec similarity index 92% rename from activerecord-oracle_enhanced-adapter.gemspec rename to pmacs-activerecord-oracle_enhanced-adapter.gemspec index 2f4a30476..deae7ae8a 100644 --- a/activerecord-oracle_enhanced-adapter.gemspec +++ b/pmacs-activerecord-oracle_enhanced-adapter.gemspec @@ -4,17 +4,17 @@ # -*- encoding: utf-8 -*- Gem::Specification.new do |s| - s.name = %q{activerecord-oracle_enhanced-adapter} - s.version = "1.6.2" + s.name = %q{pmacs-activerecord-oracle_enhanced-adapter} + s.version = "1.6.2.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.license = 'MIT' - s.authors = [%q{Raimonds Simanovskis}] - s.date = %q{2015-07-20} + s.authors = [%q{Charles Treatman}, %q{Raimonds Simanovskis}] + s.date = %q{2015-08-12} s.description = %q{Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases. This adapter is superset of original ActiveRecord Oracle adapter. } - s.email = %q{raimonds.simanovskis@gmail.com} + s.email = %q{charles.treatman@gmail.com} s.extra_rdoc_files = [ "README.md" ] @@ -27,7 +27,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. "RUNNING_TESTS.md", "Rakefile", "VERSION", - "activerecord-oracle_enhanced-adapter.gemspec", + "pmacs-activerecord-oracle_enhanced-adapter.gemspec", "lib/active_record/connection_adapters/emulation/oracle_adapter.rb", "lib/active_record/connection_adapters/oracle_enhanced_adapter.rb", "lib/active_record/connection_adapters/oracle_enhanced/column.rb", @@ -51,7 +51,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. "lib/active_record/oracle_enhanced/type/integer.rb", "lib/active_record/oracle_enhanced/type/timestamp.rb", "lib/active_record/oracle_enhanced/type/raw.rb", - "lib/activerecord-oracle_enhanced-adapter.rb", + "lib/pmacs-activerecord-oracle_enhanced-adapter.rb", "spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb", "spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb", "spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb", @@ -67,9 +67,9 @@ This adapter is superset of original ActiveRecord Oracle adapter. "spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb", "spec/spec_helper.rb" ] - s.homepage = %q{http://github.com/rsim/oracle-enhanced} + s.homepage = %q{http://github.com/pmacs/oracle-enhanced} s.require_paths = [%q{lib}] - s.rubygems_version = %q{1.8.6} + s.rubygems_version = %q{2.2.2} s.summary = %q{Oracle enhanced adapter for ActiveRecord} s.test_files = [ "spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb", @@ -94,6 +94,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q, ["~> 2.0"]) s.add_development_dependency(%q, ["~> 2.4"]) + s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, ["~> 4.2.1"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 0"]) @@ -106,6 +107,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. else s.add_dependency(%q, ["~> 2.0"]) s.add_dependency(%q, ["~> 2.4"]) + s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 4.2.1"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) @@ -119,6 +121,7 @@ This adapter is superset of original ActiveRecord Oracle adapter. else s.add_dependency(%q, ["~> 2.0"]) s.add_dependency(%q, ["~> 2.4"]) + s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 4.2.1"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0"]) From 01f80a505587b6572856b898042b05cc02d32ca4 Mon Sep 17 00:00:00 2001 From: Walter Davis Date: Wed, 12 Aug 2015 14:13:55 -0400 Subject: [PATCH 113/114] maybe found the right place for the missing tablespace --- .../connection_adapters/oracle_enhanced/structure_dump.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb b/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb index 9df546c11..188186950 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb @@ -127,7 +127,7 @@ def structure_dump_fk_constraints #:nodoc: if respond_to?(:foreign_keys) && (foreign_keys = foreign_keys(table["table_name"])).any? foreign_keys.map do |fk| sql = "ALTER TABLE #{quote_table_name(fk.from_table)} ADD CONSTRAINT #{quote_column_name(fk.options[:name])} " - sql << "#{foreign_key_definition(fk.to_table, fk.options)}" + sql << "#{foreign_key_definition(fk.to_table, fk.options) << table_definition_tablespace}" end end end.flatten.compact From bdc02c1f65148b545e0aa82b79802353bf01873c Mon Sep 17 00:00:00 2001 From: Walter Davis Date: Thu, 13 Aug 2015 15:11:05 -0400 Subject: [PATCH 114/114] removed deprecated serialized_attributes references --- .../connection_adapters/oracle_enhanced/procedures.rb | 4 +--- .../connection_adapters/oracle_enhanced_adapter.rb | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb b/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb index 60b90b8fd..f9c25051e 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb @@ -150,9 +150,7 @@ def _update_record(attribute_names = @attributes.keys) end # update just dirty attributes if partial_writes? - # Serialized attributes should always be written in case they've been - # changed in place. - update_using_custom_method(changed | (attributes.keys & self.class.serialized_attributes.keys)) + update_using_custom_method(changed | attributes.keys) else update_using_custom_method(attribute_names) end diff --git a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb index be10e78ac..1e3dc8cbf 100644 --- a/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +++ b/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb @@ -826,7 +826,7 @@ def write_lobs(table_name, klass, attributes, columns) #:nodoc: value = attributes[col.name] # changed sequence of next two lines - should check if value is nil before converting to yaml next if value.nil? || (value == '') - value = value.to_yaml if klass.serialized_attributes[col.name] + value = value.to_yaml if col.cast_type.is_a?(Type::Serialized) # klass.serialized_attributes[col.name] uncached do sql = is_with_cpk ? "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} WHERE #{klass.composite_where_clause(id)} FOR UPDATE" : "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} WHERE #{quote_column_name(klass.primary_key)} = #{id} FOR UPDATE"