Skip to content

Commit

Permalink
Merge pull request #799 from leonidkroka/introduce-support-for-alias-…
Browse files Browse the repository at this point in the history
…attributes

Introduce support for alias attributes
  • Loading branch information
jkowens committed May 10, 2023
2 parents 75b751f + 8a49a74 commit 367a177
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 30 deletions.
2 changes: 1 addition & 1 deletion lib/activerecord-import/adapters/abstract_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def post_sql_statements( table_name, options ) # :nodoc:
post_sql_statements = []

if supports_on_duplicate_key_update? && options[:on_duplicate_key_update]
post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update], options[:primary_key], options[:locking_column] )
post_sql_statements << sql_for_on_duplicate_key_update( table_name, options[:on_duplicate_key_update], options[:model], options[:primary_key], options[:locking_column] )
elsif logger && options[:on_duplicate_key_update]
logger.warn "Ignoring on_duplicate_key_update because it is not supported by the database."
end
Expand Down
22 changes: 13 additions & 9 deletions lib/activerecord-import/adapters/mysql_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,12 @@ def add_column_for_on_duplicate_key_update( column, options = {} ) # :nodoc:
# in +args+.
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
sql = ' ON DUPLICATE KEY UPDATE '.dup
arg = args.first
locking_column = args.last
arg, model, _primary_key, locking_column = args
case arg
when Array
sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arg )
sql << sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arg )
when Hash
sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, arg )
sql << sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, arg )
when String
sql << arg
else
Expand All @@ -101,19 +100,24 @@ def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
sql
end

def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
def sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arr ) # :nodoc:
results = arr.map do |column|
qc = quote_column_name( column )
original_column_name = model.attribute_alias?( column ) ? model.attribute_alias( column ) : column
qc = quote_column_name( original_column_name )
"#{table_name}.#{qc}=VALUES(#{qc})"
end
increment_locking_column!(table_name, results, locking_column)
results.join( ',' )
end

def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
def sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, hsh ) # :nodoc:
results = hsh.map do |column1, column2|
qc1 = quote_column_name( column1 )
qc2 = quote_column_name( column2 )
original_column1_name = model.attribute_alias?( column1 ) ? model.attribute_alias( column1 ) : column1
qc1 = quote_column_name( original_column1_name )

original_column2_name = model.attribute_alias?( column2 ) ? model.attribute_alias( column2 ) : column2
qc2 = quote_column_name( original_column2_name )

"#{table_name}.#{qc1}=VALUES( #{qc2} )"
end
increment_locking_column!(table_name, results, locking_column)
Expand Down
21 changes: 13 additions & 8 deletions lib/activerecord-import/adapters/postgresql_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def sql_for_on_duplicate_key_ignore( table_name, *args ) # :nodoc:
# Returns a generated ON CONFLICT DO UPDATE statement given the passed
# in +args+.
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
arg, primary_key, locking_column = args
arg, model, primary_key, locking_column = args
arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
return unless arg.is_a?( Hash )

Expand All @@ -155,9 +155,9 @@ def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
sql << "#{conflict_target}DO UPDATE SET "
case columns
when Array
sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, columns )
sql << sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, columns )
when Hash
sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, columns )
sql << sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, columns )
when String
sql << columns
else
Expand All @@ -169,19 +169,24 @@ def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
sql
end

def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
def sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arr ) # :nodoc:
results = arr.map do |column|
qc = quote_column_name( column )
original_column_name = model.attribute_alias?( column ) ? model.attribute_alias( column ) : column
qc = quote_column_name( original_column_name )
"#{qc}=EXCLUDED.#{qc}"
end
increment_locking_column!(table_name, results, locking_column)
results.join( ',' )
end

def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
def sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, hsh ) # :nodoc:
results = hsh.map do |column1, column2|
qc1 = quote_column_name( column1 )
qc2 = quote_column_name( column2 )
original_column1_name = model.attribute_alias?( column1 ) ? model.attribute_alias( column1 ) : column1
qc1 = quote_column_name( original_column1_name )

original_column2_name = model.attribute_alias?( column2 ) ? model.attribute_alias( column2 ) : column2
qc2 = quote_column_name( original_column2_name )

"#{qc1}=EXCLUDED.#{qc2}"
end
increment_locking_column!(table_name, results, locking_column)
Expand Down
21 changes: 13 additions & 8 deletions lib/activerecord-import/adapters/sqlite3_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def sql_for_on_duplicate_key_ignore( *args ) # :nodoc:
# Returns a generated ON CONFLICT DO UPDATE statement given the passed
# in +args+.
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
arg, primary_key, locking_column = args
arg, model, primary_key, locking_column = args
arg = { columns: arg } if arg.is_a?( Array ) || arg.is_a?( String )
return unless arg.is_a?( Hash )

Expand All @@ -116,9 +116,9 @@ def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
sql << "#{conflict_target}DO UPDATE SET "
case columns
when Array
sql << sql_for_on_duplicate_key_update_as_array( table_name, locking_column, columns )
sql << sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, columns )
when Hash
sql << sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, columns )
sql << sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, columns )
when String
sql << columns
else
Expand All @@ -130,19 +130,24 @@ def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
sql
end

def sql_for_on_duplicate_key_update_as_array( table_name, locking_column, arr ) # :nodoc:
def sql_for_on_duplicate_key_update_as_array( table_name, model, locking_column, arr ) # :nodoc:
results = arr.map do |column|
qc = quote_column_name( column )
original_column_name = model.attribute_alias?( column ) ? model.attribute_alias( column ) : column
qc = quote_column_name( original_column_name )
"#{qc}=EXCLUDED.#{qc}"
end
increment_locking_column!(table_name, results, locking_column)
results.join( ',' )
end

def sql_for_on_duplicate_key_update_as_hash( table_name, locking_column, hsh ) # :nodoc:
def sql_for_on_duplicate_key_update_as_hash( table_name, model, locking_column, hsh ) # :nodoc:
results = hsh.map do |column1, column2|
qc1 = quote_column_name( column1 )
qc2 = quote_column_name( column2 )
original_column1_name = model.attribute_alias?( column1 ) ? model.attribute_alias( column1 ) : column1
qc1 = quote_column_name( original_column1_name )

original_column2_name = model.attribute_alias?( column2 ) ? model.attribute_alias( column2 ) : column2
qc2 = quote_column_name( original_column2_name )

"#{qc1}=EXCLUDED.#{qc2}"
end
increment_locking_column!(table_name, results, locking_column)
Expand Down
16 changes: 12 additions & 4 deletions lib/activerecord-import/import.rb
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ def import_helper( *args )

if models.first.id.nil?
Array(primary_key).each do |c|
if column_names.include?(c) && columns_hash[c].type == :uuid
if column_names.include?(c) && schema_columns_hash[c].type == :uuid
column_names.delete(c)
end
end
Expand Down Expand Up @@ -778,7 +778,10 @@ def import_with_validations( column_names, array_of_attributes, options = {} )
def import_without_validations_or_callbacks( column_names, array_of_attributes, options = {} )
return ActiveRecord::Import::Result.new([], 0, [], []) if array_of_attributes.empty?

column_names = column_names.map(&:to_sym)
column_names = column_names.map do |name|
original_name = attribute_alias?(name) ? attribute_alias(name) : name
original_name.to_sym
end
scope_columns, scope_values = scope_attributes.to_a.transpose

unless scope_columns.blank?
Expand All @@ -796,7 +799,7 @@ def import_without_validations_or_callbacks( column_names, array_of_attributes,
end

columns = column_names.each_with_index.map do |name, i|
column = columns_hash[name.to_s]
column = schema_columns_hash[name.to_s]
raise ActiveRecord::Import::MissingColumnError.new(name.to_s, i) if column.nil?
column
end
Expand Down Expand Up @@ -867,7 +870,7 @@ def set_attributes_and_mark_clean(models, import_result, timestamps, options)
end

deserialize_value = lambda do |column, value|
column = columns_hash[column]
column = schema_columns_hash[column]
return value unless column
if respond_to?(:type_caster)
type = type_for_attribute(column.name)
Expand Down Expand Up @@ -961,6 +964,11 @@ def import_associations(models, options)
end
end

def schema_columns_hash
load_schema unless schema_loaded?
@schema_columns_hash ||= connection.schema_cache.columns_hash(table_name)
end

# We are eventually going to call Class.import <objects> so we build up a hash
# of class => objects to import.
def find_associated_objects_for_import(associated_objects_by_class, model)
Expand Down
5 changes: 5 additions & 0 deletions test/models/topic.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# frozen_string_literal: true

class Topic < ActiveRecord::Base
if ENV['AR_VERSION'].to_i >= 6.0
self.ignored_columns = [:priority]
end
alias_attribute :name, :title

validates_presence_of :author_name
validates :title, numericality: { only_integer: true }, on: :context_test
validates :title, uniqueness: true
Expand Down
1 change: 1 addition & 0 deletions test/schema/generic_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
t.boolean :approved, default: '1'
t.integer :replies_count
t.integer :parent_id
t.integer :priority, default: 0
t.string :type
t.datetime :created_at
t.datetime :created_on
Expand Down
29 changes: 29 additions & 0 deletions test/support/shared_examples/on_duplicate_key_update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def should_support_basic_on_duplicate_key_update
assert_equal 1, maker.lock_version
end
end

it 'update the lock_version of models separated by namespaces by array' do
makers = [
Bike::Maker.new(name: 'Yamaha'),
Expand Down Expand Up @@ -316,6 +317,34 @@ def should_support_basic_on_duplicate_key_update
should_support_on_duplicate_key_update
should_update_fields_mentioned
end

context "using column aliases" do
let(:columns) { %w( id title author_name author_email_address parent_id ) }
let(:update_columns) { %w(title author_email_address parent_id) }

context "with column aliases in column list" do
let(:columns) { %w( id name author_name author_email_address parent_id ) }
should_support_on_duplicate_key_update
should_update_fields_mentioned
end

context "with column aliases in update columns list" do
let(:update_columns) { %w(name author_email_address parent_id) }
should_support_on_duplicate_key_update
should_update_fields_mentioned
end
end

if ENV['AR_VERSION'].to_i >= 6.0
context "using ignored columns" do
let(:columns) { %w( id title author_name author_email_address parent_id priority ) }
let(:values) { [[99, "Book", "John Doe", "john@doe.com", 17, 1]] }
let(:update_columns) { %w(name author_email_address parent_id priority) }
let(:updated_values) { [[99, "Book - 2nd Edition", "Author Should Not Change", "johndoe@example.com", 57, 2]] }
should_support_on_duplicate_key_update
should_update_fields_mentioned
end
end
end

context "with a table that has a non-standard primary key" do
Expand Down

0 comments on commit 367a177

Please sign in to comment.