aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb4
-rw-r--r--activerecord/lib/active_record/database_configurations.rb2
-rw-r--r--activerecord/lib/active_record/insert_all.rb62
-rw-r--r--activerecord/lib/active_record/relation.rb6
-rw-r--r--activerecord/lib/arel/visitors/ibm_db.rb3
-rw-r--r--activerecord/lib/arel/visitors/informix.rb3
-rw-r--r--activerecord/lib/arel/visitors/mssql.rb3
-rw-r--r--activerecord/lib/arel/visitors/to_sql.rb10
8 files changed, 54 insertions, 39 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 9d24d839c1..2877530917 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -138,6 +138,10 @@ module ActiveRecord
"'#{quote_string(value.to_s)}'"
end
+ def sanitize_as_sql_comment(value) # :nodoc:
+ value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
+ end
+
private
def type_casted_binds(binds)
if binds.first.is_a?(Array)
diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb
index a6c702cbbc..4656045fe5 100644
--- a/activerecord/lib/active_record/database_configurations.rb
+++ b/activerecord/lib/active_record/database_configurations.rb
@@ -106,7 +106,7 @@ module ActiveRecord
build_db_config = configs.each_pair.flat_map do |env_name, config|
walk_configs(env_name.to_s, "primary", config)
- end.compact
+ end.flatten.compact
if url = ENV["DATABASE_URL"]
build_url_config(url, build_db_config)
diff --git a/activerecord/lib/active_record/insert_all.rb b/activerecord/lib/active_record/insert_all.rb
index 3833cb2fcf..98c98d61cd 100644
--- a/activerecord/lib/active_record/insert_all.rb
+++ b/activerecord/lib/active_record/insert_all.rb
@@ -2,12 +2,14 @@
module ActiveRecord
class InsertAll
- attr_reader :model, :connection, :inserts, :on_duplicate, :returning, :unique_by
+ attr_reader :model, :connection, :inserts, :keys
+ attr_reader :on_duplicate, :returning, :unique_by
def initialize(model, inserts, on_duplicate:, returning: nil, unique_by: nil)
raise ArgumentError, "Empty list of attributes passed" if inserts.blank?
- @model, @connection, @inserts, @on_duplicate, @returning, @unique_by = model, model.connection, inserts, on_duplicate, returning, unique_by
+ @model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s).to_set
+ @on_duplicate, @returning, @unique_by = on_duplicate, returning, unique_by
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
@returning = false if @returning == []
@@ -21,10 +23,6 @@ module ActiveRecord
connection.exec_query to_sql, "Bulk Insert"
end
- def keys
- inserts.first.keys.map(&:to_s)
- end
-
def updatable_columns
keys - readonly_columns - unique_by_columns
end
@@ -37,6 +35,17 @@ module ActiveRecord
on_duplicate == :update
end
+ def map_key_with_value
+ inserts.map do |attributes|
+ attributes = attributes.stringify_keys
+ verify_attributes(attributes)
+
+ keys.map do |key|
+ yield key, attributes[key]
+ end
+ end
+ end
+
private
def ensure_valid_options_for_connection!
if returning && !connection.supports_insert_returning?
@@ -76,6 +85,12 @@ module ActiveRecord
Array.wrap(model.primary_key)
end
+ def verify_attributes(attributes)
+ if keys != attributes.keys.to_set
+ raise ArgumentError, "All objects being inserted must have the same keys"
+ end
+ end
+
class Builder
attr_reader :model
@@ -91,29 +106,11 @@ module ActiveRecord
end
def values_list
- columns = connection.schema_cache.columns_hash(model.table_name)
-
- column_names = columns.keys.to_set
- keys = insert_all.keys.to_set
- unknown_columns = keys - column_names
+ types = extract_types_from_columns_on(model.table_name, keys: insert_all.keys)
- unless unknown_columns.empty?
- raise UnknownAttributeError.new(model.new, unknown_columns.first)
- end
-
- types = keys.map { |key| [ key, connection.lookup_cast_type_from_column(columns[key]) ] }.to_h
-
- values_list = insert_all.inserts.map do |attributes|
- attributes = attributes.stringify_keys
-
- unless attributes.keys.to_set == keys
- raise ArgumentError, "All objects being inserted must have the same keys"
- end
-
- keys.map do |key|
- bind = Relation::QueryAttribute.new(key, attributes[key], types[key])
- connection.with_yaml_fallback(bind.value_for_database)
- end
+ values_list = insert_all.map_key_with_value do |key, value|
+ bind = Relation::QueryAttribute.new(key, value, types[key])
+ connection.with_yaml_fallback(bind.value_for_database)
end
Arel::InsertManager.new.create_values_list(values_list).to_sql
@@ -141,6 +138,15 @@ module ActiveRecord
quote_columns(insert_all.keys).join(",")
end
+ def extract_types_from_columns_on(table_name, keys:)
+ columns = connection.schema_cache.columns_hash(table_name)
+
+ unknown_column = (keys - columns.keys).first
+ raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
+
+ keys.map { |key| [ key, connection.lookup_cast_type_from_column(columns[key]) ] }.to_h
+ end
+
def quote_columns(columns)
columns.map(&connection.method(:quote_column_name))
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 37179774fa..6d954a2b2e 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -342,6 +342,8 @@ module ActiveRecord
# trigger Active Record callbacks or validations. However, values passed to #update_all will still go through
# Active Record's normal type casting and serialization.
#
+ # Note: As Active Record callbacks are not triggered, this method will not automatically update +updated_at+/+updated_on+ columns.
+ #
# ==== Parameters
#
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
@@ -416,10 +418,10 @@ module ActiveRecord
update_all updates
end
- # Touches all records in the current relation without instantiating records first with the updated_at/on attributes
+ # Touches all records in the current relation without instantiating records first with the +updated_at+/+updated_on+ attributes
# set to the current time or the time specified.
# This method can be passed attribute names and an optional time argument.
- # If attribute names are passed, they are updated along with updated_at/on attributes.
+ # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
# If no time argument is passed, the current time is used as default.
#
# === Examples
diff --git a/activerecord/lib/arel/visitors/ibm_db.rb b/activerecord/lib/arel/visitors/ibm_db.rb
index 0ffc0725f7..5cf958f5f0 100644
--- a/activerecord/lib/arel/visitors/ibm_db.rb
+++ b/activerecord/lib/arel/visitors/ibm_db.rb
@@ -10,7 +10,8 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_OptimizerHints(o, collector)
- collector << "/* <OPTGUIDELINES>#{sanitize_as_sql_comment(o).join}</OPTGUIDELINES> */"
+ hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join
+ collector << "/* <OPTGUIDELINES>#{hints}</OPTGUIDELINES> */"
end
def visit_Arel_Nodes_Limit(o, collector)
diff --git a/activerecord/lib/arel/visitors/informix.rb b/activerecord/lib/arel/visitors/informix.rb
index cd43be8858..1a4ad1c8d8 100644
--- a/activerecord/lib/arel/visitors/informix.rb
+++ b/activerecord/lib/arel/visitors/informix.rb
@@ -43,7 +43,8 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_OptimizerHints(o, collector)
- collector << "/*+ #{sanitize_as_sql_comment(o).join(", ")} */"
+ hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(", ")
+ collector << "/*+ #{hints} */"
end
def visit_Arel_Nodes_Offset(o, collector)
diff --git a/activerecord/lib/arel/visitors/mssql.rb b/activerecord/lib/arel/visitors/mssql.rb
index 85815baca2..05fe37c26f 100644
--- a/activerecord/lib/arel/visitors/mssql.rb
+++ b/activerecord/lib/arel/visitors/mssql.rb
@@ -82,7 +82,8 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_OptimizerHints(o, collector)
- collector << "OPTION (#{sanitize_as_sql_comment(o).join(", ")})"
+ hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(", ")
+ collector << "OPTION (#{hints})"
end
def get_offset_limit_clause(o)
diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb
index 583f920290..e44dacd0ff 100644
--- a/activerecord/lib/arel/visitors/to_sql.rb
+++ b/activerecord/lib/arel/visitors/to_sql.rb
@@ -219,7 +219,8 @@ module Arel # :nodoc: all
end
def visit_Arel_Nodes_OptimizerHints(o, collector)
- collector << "/*+ #{sanitize_as_sql_comment(o).join(" ")} */"
+ hints = o.expr.map { |v| sanitize_as_sql_comment(v) }.join(" ")
+ collector << "/*+ #{hints} */"
end
def collect_nodes_for(nodes, collector, spacer, connector = COMMA)
@@ -785,10 +786,9 @@ module Arel # :nodoc: all
@connection.quote_column_name(name)
end
- def sanitize_as_sql_comment(o)
- o.expr.map { |v|
- v.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
- }
+ def sanitize_as_sql_comment(value)
+ return value if Arel::Nodes::SqlLiteral === value
+ @connection.sanitize_as_sql_comment(value)
end
def collect_optimizer_hints(o, collector)