activerecord 4.2.11.3 → 5.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1029 -1349
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -7
- data/examples/performance.rb +2 -2
- data/lib/active_record.rb +7 -3
- data/lib/active_record/aggregations.rb +35 -25
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations.rb +305 -204
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +10 -8
- data/lib/active_record/associations/association_scope.rb +73 -102
- data/lib/active_record/associations/belongs_to_association.rb +20 -32
- data/lib/active_record/associations/builder/association.rb +28 -34
- data/lib/active_record/associations/builder/belongs_to.rb +41 -18
- data/lib/active_record/associations/builder/collection_association.rb +8 -24
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +11 -11
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +10 -5
- data/lib/active_record/associations/builder/singular_association.rb +2 -9
- data/lib/active_record/associations/collection_association.rb +40 -43
- data/lib/active_record/associations/collection_proxy.rb +55 -29
- data/lib/active_record/associations/foreign_association.rb +1 -1
- data/lib/active_record/associations/has_many_association.rb +20 -71
- data/lib/active_record/associations/has_many_through_association.rb +8 -52
- data/lib/active_record/associations/has_one_association.rb +12 -5
- data/lib/active_record/associations/join_dependency.rb +28 -18
- data/lib/active_record/associations/join_dependency/join_association.rb +13 -12
- data/lib/active_record/associations/preloader.rb +13 -4
- data/lib/active_record/associations/preloader/association.rb +45 -51
- data/lib/active_record/associations/preloader/collection_association.rb +0 -6
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/through_association.rb +5 -4
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +11 -3
- data/lib/active_record/attribute.rb +61 -17
- data/lib/active_record/attribute/user_provided_default.rb +23 -0
- data/lib/active_record/attribute_assignment.rb +27 -140
- data/lib/active_record/attribute_decorators.rb +6 -5
- data/lib/active_record/attribute_methods.rb +79 -26
- data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
- data/lib/active_record/attribute_methods/dirty.rb +46 -86
- data/lib/active_record/attribute_methods/primary_key.rb +2 -2
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +26 -42
- data/lib/active_record/attribute_methods/serialization.rb +13 -16
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +42 -9
- data/lib/active_record/attribute_methods/write.rb +13 -24
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set.rb +30 -3
- data/lib/active_record/attribute_set/builder.rb +6 -4
- data/lib/active_record/attributes.rb +194 -81
- data/lib/active_record/autosave_association.rb +33 -15
- data/lib/active_record/base.rb +30 -18
- data/lib/active_record/callbacks.rb +36 -40
- data/lib/active_record/coders/yaml_column.rb +20 -8
- data/lib/active_record/collection_cache_key.rb +31 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +431 -122
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +40 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +62 -8
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -38
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +229 -185
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +275 -115
- data/lib/active_record/connection_adapters/abstract/transaction.rb +32 -33
- data/lib/active_record/connection_adapters/abstract_adapter.rb +83 -32
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +384 -221
- data/lib/active_record/connection_adapters/column.rb +27 -41
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -21
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +57 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +69 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +59 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +22 -101
- data/lib/active_record/connection_adapters/postgresql/column.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +23 -57
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +23 -16
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +18 -11
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +174 -128
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -112
- data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +15 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +134 -110
- data/lib/active_record/connection_adapters/statement_pool.rb +28 -11
- data/lib/active_record/connection_handling.rb +5 -5
- data/lib/active_record/core.rb +72 -104
- data/lib/active_record/counter_cache.rb +9 -20
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +110 -76
- data/lib/active_record/errors.rb +72 -47
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +19 -4
- data/lib/active_record/fixtures.rb +76 -40
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +27 -40
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/legacy_yaml_adapter.rb +18 -2
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +10 -14
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +40 -22
- data/lib/active_record/migration.rb +304 -133
- data/lib/active_record/migration/command_recorder.rb +59 -18
- data/lib/active_record/migration/compatibility.rb +90 -0
- data/lib/active_record/model_schema.rb +92 -40
- data/lib/active_record/nested_attributes.rb +45 -34
- data/lib/active_record/null_relation.rb +15 -7
- data/lib/active_record/persistence.rb +112 -72
- data/lib/active_record/querying.rb +6 -5
- data/lib/active_record/railtie.rb +20 -13
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +47 -38
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +182 -57
- data/lib/active_record/relation.rb +152 -100
- data/lib/active_record/relation/batches.rb +133 -33
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/calculations.rb +80 -101
- data/lib/active_record/relation/delegation.rb +6 -19
- data/lib/active_record/relation/finder_methods.rb +58 -46
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +13 -42
- data/lib/active_record/relation/predicate_builder.rb +99 -105
- data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +78 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +17 -0
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +274 -238
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +3 -6
- data/lib/active_record/relation/where_clause.rb +173 -0
- data/lib/active_record/relation/where_clause_factory.rb +37 -0
- data/lib/active_record/result.rb +4 -3
- data/lib/active_record/runtime_registry.rb +1 -1
- data/lib/active_record/sanitization.rb +94 -65
- data/lib/active_record/schema.rb +23 -22
- data/lib/active_record/schema_dumper.rb +33 -22
- data/lib/active_record/schema_migration.rb +10 -4
- data/lib/active_record/scoping.rb +17 -6
- data/lib/active_record/scoping/default.rb +19 -6
- data/lib/active_record/scoping/named.rb +39 -28
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +2 -4
- data/lib/active_record/statement_cache.rb +15 -13
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +54 -0
- data/lib/active_record/table_metadata.rb +64 -0
- data/lib/active_record/tasks/database_tasks.rb +30 -40
- data/lib/active_record/tasks/mysql_database_tasks.rb +7 -15
- data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +16 -9
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +138 -56
- data/lib/active_record/type.rb +66 -17
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +2 -45
- data/lib/active_record/type/date_time.rb +2 -49
- data/lib/active_record/type/internal/abstract_json.rb +33 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +9 -14
- data/lib/active_record/type/time.rb +3 -21
- data/lib/active_record/type/type_map.rb +4 -4
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/validations.rb +33 -32
- data/lib/active_record/validations/absence.rb +24 -0
- data/lib/active_record/validations/associated.rb +10 -3
- data/lib/active_record/validations/length.rb +36 -0
- data/lib/active_record/validations/presence.rb +12 -12
- data/lib/active_record/validations/uniqueness.rb +24 -21
- data/lib/rails/generators/active_record/migration.rb +7 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
- data/lib/rails/generators/active_record/migration/templates/migration.rb +4 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +21 -15
- data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
- metadata +50 -35
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -31
- data/lib/active_record/type/decimal.rb +0 -64
- data/lib/active_record/type/decimal_without_scale.rb +0 -11
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -59
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -40
- data/lib/active_record/type/text.rb +0 -11
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/unsigned_integer.rb +0 -15
- data/lib/active_record/type/value.rb +0 -110
@@ -0,0 +1,51 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Relation
|
3
|
+
module RecordFetchWarning
|
4
|
+
# When this module is prepended to ActiveRecord::Relation and
|
5
|
+
# `config.active_record.warn_on_records_fetched_greater_than` is
|
6
|
+
# set to an integer, if the number of records a query returns is
|
7
|
+
# greater than the value of `warn_on_records_fetched_greater_than`,
|
8
|
+
# a warning is logged. This allows for the detection of queries that
|
9
|
+
# return a large number of records, which could cause memory bloat.
|
10
|
+
#
|
11
|
+
# In most cases, fetching large number of records can be performed
|
12
|
+
# efficiently using the ActiveRecord::Batches methods.
|
13
|
+
# See active_record/lib/relation/batches.rb for more information.
|
14
|
+
def exec_queries
|
15
|
+
QueryRegistry.reset
|
16
|
+
|
17
|
+
super.tap do
|
18
|
+
if logger && warn_on_records_fetched_greater_than
|
19
|
+
if @records.length > warn_on_records_fetched_greater_than
|
20
|
+
logger.warn "Query fetched #{@records.size} #{@klass} records: #{QueryRegistry.queries.join(";")}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# :stopdoc:
|
27
|
+
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
|
28
|
+
payload = args.last
|
29
|
+
|
30
|
+
QueryRegistry.queries << payload[:sql]
|
31
|
+
end
|
32
|
+
# :startdoc:
|
33
|
+
|
34
|
+
class QueryRegistry # :nodoc:
|
35
|
+
extend ActiveSupport::PerThreadRegistry
|
36
|
+
|
37
|
+
attr_accessor :queries
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
reset
|
41
|
+
end
|
42
|
+
|
43
|
+
def reset
|
44
|
+
@queries = []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
ActiveRecord::Relation.prepend ActiveRecord::Relation::RecordFetchWarning
|
@@ -10,7 +10,7 @@ module ActiveRecord
|
|
10
10
|
clone
|
11
11
|
end
|
12
12
|
|
13
|
-
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an
|
13
|
+
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
|
14
14
|
# Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
|
15
15
|
#
|
16
16
|
# Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) )
|
@@ -33,7 +33,7 @@ module ActiveRecord
|
|
33
33
|
elsif other
|
34
34
|
spawn.merge!(other)
|
35
35
|
else
|
36
|
-
|
36
|
+
raise ArgumentError, "invalid argument: #{other.inspect}."
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -62,16 +62,13 @@ module ActiveRecord
|
|
62
62
|
# Post.order('id asc').only(:where) # discards the order condition
|
63
63
|
# Post.order('id asc').only(:where, :order) # uses the specified order
|
64
64
|
def only(*onlies)
|
65
|
-
if onlies.any? { |o| o == :where }
|
66
|
-
onlies << :bind
|
67
|
-
end
|
68
65
|
relation_with values.slice(*onlies)
|
69
66
|
end
|
70
67
|
|
71
68
|
private
|
72
69
|
|
73
70
|
def relation_with(values) # :nodoc:
|
74
|
-
result = Relation.create(klass, table, values)
|
71
|
+
result = Relation.create(klass, table, predicate_builder, values)
|
75
72
|
result.extend(*extending_values) if extending_values.any?
|
76
73
|
result
|
77
74
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Relation
|
3
|
+
class WhereClause # :nodoc:
|
4
|
+
attr_reader :binds
|
5
|
+
|
6
|
+
delegate :any?, :empty?, to: :predicates
|
7
|
+
|
8
|
+
def initialize(predicates, binds)
|
9
|
+
@predicates = predicates
|
10
|
+
@binds = binds
|
11
|
+
end
|
12
|
+
|
13
|
+
def +(other)
|
14
|
+
WhereClause.new(
|
15
|
+
predicates + other.predicates,
|
16
|
+
binds + other.binds,
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def merge(other)
|
21
|
+
WhereClause.new(
|
22
|
+
predicates_unreferenced_by(other) + other.predicates,
|
23
|
+
non_conflicting_binds(other) + other.binds,
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def except(*columns)
|
28
|
+
WhereClause.new(
|
29
|
+
predicates_except(columns),
|
30
|
+
binds_except(columns),
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def or(other)
|
35
|
+
if empty?
|
36
|
+
self
|
37
|
+
elsif other.empty?
|
38
|
+
other
|
39
|
+
else
|
40
|
+
WhereClause.new(
|
41
|
+
[ast.or(other.ast)],
|
42
|
+
binds + other.binds
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_h(table_name = nil)
|
48
|
+
equalities = predicates.grep(Arel::Nodes::Equality)
|
49
|
+
if table_name
|
50
|
+
equalities = equalities.select do |node|
|
51
|
+
node.left.relation.name == table_name
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h
|
56
|
+
|
57
|
+
equalities.map { |node|
|
58
|
+
name = node.left.name
|
59
|
+
[name, binds.fetch(name.to_s) {
|
60
|
+
case node.right
|
61
|
+
when Array then node.right.map(&:val)
|
62
|
+
when Arel::Nodes::Casted, Arel::Nodes::Quoted
|
63
|
+
node.right.val
|
64
|
+
end
|
65
|
+
}]
|
66
|
+
}.to_h
|
67
|
+
end
|
68
|
+
|
69
|
+
def ast
|
70
|
+
Arel::Nodes::And.new(predicates_with_wrapped_sql_literals)
|
71
|
+
end
|
72
|
+
|
73
|
+
def ==(other)
|
74
|
+
other.is_a?(WhereClause) &&
|
75
|
+
predicates == other.predicates &&
|
76
|
+
binds == other.binds
|
77
|
+
end
|
78
|
+
|
79
|
+
def invert
|
80
|
+
WhereClause.new(inverted_predicates, binds)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.empty
|
84
|
+
new([], [])
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
attr_reader :predicates
|
90
|
+
|
91
|
+
def referenced_columns
|
92
|
+
@referenced_columns ||= begin
|
93
|
+
equality_nodes = predicates.select { |n| equality_node?(n) }
|
94
|
+
Set.new(equality_nodes, &:left)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def predicates_unreferenced_by(other)
|
101
|
+
predicates.reject do |n|
|
102
|
+
equality_node?(n) && other.referenced_columns.include?(n.left)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def equality_node?(node)
|
107
|
+
node.respond_to?(:operator) && node.operator == :==
|
108
|
+
end
|
109
|
+
|
110
|
+
def non_conflicting_binds(other)
|
111
|
+
conflicts = referenced_columns & other.referenced_columns
|
112
|
+
conflicts.map! { |node| node.name.to_s }
|
113
|
+
binds.reject { |attr| conflicts.include?(attr.name) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def inverted_predicates
|
117
|
+
predicates.map { |node| invert_predicate(node) }
|
118
|
+
end
|
119
|
+
|
120
|
+
def invert_predicate(node)
|
121
|
+
case node
|
122
|
+
when NilClass
|
123
|
+
raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
|
124
|
+
when Arel::Nodes::In
|
125
|
+
Arel::Nodes::NotIn.new(node.left, node.right)
|
126
|
+
when Arel::Nodes::Equality
|
127
|
+
Arel::Nodes::NotEqual.new(node.left, node.right)
|
128
|
+
when String
|
129
|
+
Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
|
130
|
+
else
|
131
|
+
Arel::Nodes::Not.new(node)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def predicates_except(columns)
|
136
|
+
predicates.reject do |node|
|
137
|
+
case node
|
138
|
+
when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
|
139
|
+
subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
|
140
|
+
columns.include?(subrelation.name.to_s)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def binds_except(columns)
|
146
|
+
binds.reject do |attr|
|
147
|
+
columns.include?(attr.name)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def predicates_with_wrapped_sql_literals
|
152
|
+
non_empty_predicates.map do |node|
|
153
|
+
if Arel::Nodes::Equality === node
|
154
|
+
node
|
155
|
+
else
|
156
|
+
wrap_sql_literal(node)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def non_empty_predicates
|
162
|
+
predicates - ['']
|
163
|
+
end
|
164
|
+
|
165
|
+
def wrap_sql_literal(node)
|
166
|
+
if ::String === node
|
167
|
+
node = Arel.sql(node)
|
168
|
+
end
|
169
|
+
Arel::Nodes::Grouping.new(node)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Relation
|
3
|
+
class WhereClauseFactory # :nodoc:
|
4
|
+
def initialize(klass, predicate_builder)
|
5
|
+
@klass = klass
|
6
|
+
@predicate_builder = predicate_builder
|
7
|
+
end
|
8
|
+
|
9
|
+
def build(opts, other)
|
10
|
+
binds = []
|
11
|
+
|
12
|
+
case opts
|
13
|
+
when String, Array
|
14
|
+
parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
|
15
|
+
when Hash
|
16
|
+
attributes = predicate_builder.resolve_column_aliases(opts)
|
17
|
+
attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
|
18
|
+
attributes.stringify_keys!
|
19
|
+
|
20
|
+
attributes, binds = predicate_builder.create_binds(attributes)
|
21
|
+
|
22
|
+
parts = predicate_builder.build_from_hash(attributes)
|
23
|
+
when Arel::Nodes::Node
|
24
|
+
parts = [opts]
|
25
|
+
else
|
26
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
27
|
+
end
|
28
|
+
|
29
|
+
WhereClause.new(parts, binds)
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
attr_reader :klass, :predicate_builder
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/active_record/result.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
###
|
3
|
-
# This class encapsulates a
|
4
|
-
#
|
3
|
+
# This class encapsulates a result returned from calling
|
4
|
+
# {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
|
5
|
+
# on any database connection adapter. For example:
|
5
6
|
#
|
6
7
|
# result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
|
7
8
|
# result # => #<ActiveRecord::Result:0xdeadbeef>
|
@@ -81,7 +82,7 @@ module ActiveRecord
|
|
81
82
|
def cast_values(type_overrides = {}) # :nodoc:
|
82
83
|
types = columns.map { |name| column_type(name, type_overrides) }
|
83
84
|
result = rows.map do |values|
|
84
|
-
types.zip(values).map { |type, value| type.
|
85
|
+
types.zip(values).map { |type, value| type.deserialize(value) }
|
85
86
|
end
|
86
87
|
|
87
88
|
columns.one? ? result.map!(&:first) : result
|
@@ -7,7 +7,7 @@ module ActiveRecord
|
|
7
7
|
#
|
8
8
|
# returns the connection handler local to the current thread.
|
9
9
|
#
|
10
|
-
# See the documentation of
|
10
|
+
# See the documentation of ActiveSupport::PerThreadRegistry
|
11
11
|
# for further details.
|
12
12
|
class RuntimeRegistry # :nodoc:
|
13
13
|
extend ActiveSupport::PerThreadRegistry
|
@@ -3,28 +3,34 @@ module ActiveRecord
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
module ClassMethods
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
# Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
|
11
|
-
def sanitize(object) #:nodoc:
|
6
|
+
# Used to sanitize objects before they're used in an SQL SELECT statement.
|
7
|
+
# Delegates to {connection.quote}[rdoc-ref:ConnectionAdapters::Quoting#quote].
|
8
|
+
def sanitize(object) # :nodoc:
|
12
9
|
connection.quote(object)
|
13
10
|
end
|
11
|
+
alias_method :quote_value, :sanitize
|
14
12
|
|
15
13
|
protected
|
16
14
|
|
17
|
-
# Accepts an array
|
15
|
+
# Accepts an array or string of SQL conditions and sanitizes
|
18
16
|
# them into a valid SQL fragment for a WHERE clause.
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
|
17
|
+
#
|
18
|
+
# sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
|
19
|
+
# # => "name='foo''bar' and group_id=4"
|
20
|
+
#
|
21
|
+
# sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
22
|
+
# # => "name='foo''bar' and group_id='4'"
|
23
|
+
#
|
24
|
+
# sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
|
25
|
+
# # => "name='foo''bar' and group_id='4'"
|
26
|
+
#
|
27
|
+
# sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
|
28
|
+
# # => "name='foo''bar' and group_id='4'"
|
29
|
+
def sanitize_sql_for_conditions(condition)
|
23
30
|
return nil if condition.blank?
|
24
31
|
|
25
32
|
case condition
|
26
33
|
when Array; sanitize_sql_array(condition)
|
27
|
-
when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
|
28
34
|
else condition
|
29
35
|
end
|
30
36
|
end
|
@@ -33,7 +39,18 @@ module ActiveRecord
|
|
33
39
|
|
34
40
|
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
35
41
|
# them into a valid SQL fragment for a SET clause.
|
36
|
-
#
|
42
|
+
#
|
43
|
+
# sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
|
44
|
+
# # => "name=NULL and group_id=4"
|
45
|
+
#
|
46
|
+
# sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
|
47
|
+
# # => "name=NULL and group_id=4"
|
48
|
+
#
|
49
|
+
# Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 })
|
50
|
+
# # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
|
51
|
+
#
|
52
|
+
# sanitize_sql_for_assignment("name=NULL and group_id='4'")
|
53
|
+
# # => "name=NULL and group_id='4'"
|
37
54
|
def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name)
|
38
55
|
case assignments
|
39
56
|
when Array; sanitize_sql_array(assignments)
|
@@ -42,17 +59,37 @@ module ActiveRecord
|
|
42
59
|
end
|
43
60
|
end
|
44
61
|
|
62
|
+
# Accepts an array, or string of SQL conditions and sanitizes
|
63
|
+
# them into a valid SQL fragment for a ORDER clause.
|
64
|
+
#
|
65
|
+
# sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
|
66
|
+
# # => "field(id, 1,3,2)"
|
67
|
+
#
|
68
|
+
# sanitize_sql_for_order("id ASC")
|
69
|
+
# # => "id ASC"
|
70
|
+
def sanitize_sql_for_order(condition)
|
71
|
+
if condition.is_a?(Array) && condition.first.to_s.include?('?')
|
72
|
+
sanitize_sql_array(condition)
|
73
|
+
else
|
74
|
+
condition
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
45
78
|
# Accepts a hash of SQL conditions and replaces those attributes
|
46
|
-
# that correspond to a
|
47
|
-
# aggregate attribute values.
|
79
|
+
# that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
|
80
|
+
# relationship with their expanded aggregate attribute values.
|
81
|
+
#
|
48
82
|
# Given:
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
83
|
+
#
|
84
|
+
# class Person < ActiveRecord::Base
|
85
|
+
# composed_of :address, class_name: "Address",
|
86
|
+
# mapping: [%w(address_street street), %w(address_city city)]
|
87
|
+
# end
|
88
|
+
#
|
53
89
|
# Then:
|
54
|
-
#
|
55
|
-
#
|
90
|
+
#
|
91
|
+
# { address: Address.new("813 abc st.", "chicago") }
|
92
|
+
# # => { address_street: "813 abc st.", address_city: "chicago" }
|
56
93
|
def expand_hash_conditions_for_aggregates(attrs)
|
57
94
|
expanded_attrs = {}
|
58
95
|
attrs.each do |attr, value|
|
@@ -72,46 +109,32 @@ module ActiveRecord
|
|
72
109
|
expanded_attrs
|
73
110
|
end
|
74
111
|
|
75
|
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
|
76
|
-
# { name: "foo'bar", group_id: 4 }
|
77
|
-
# # => "name='foo''bar' and group_id= 4"
|
78
|
-
# { status: nil, group_id: [1,2,3] }
|
79
|
-
# # => "status IS NULL and group_id IN (1,2,3)"
|
80
|
-
# { age: 13..18 }
|
81
|
-
# # => "age BETWEEN 13 AND 18"
|
82
|
-
# { 'other_records.id' => 7 }
|
83
|
-
# # => "`other_records`.`id` = 7"
|
84
|
-
# { other_records: { id: 7 } }
|
85
|
-
# # => "`other_records`.`id` = 7"
|
86
|
-
# And for value objects on a composed_of relationship:
|
87
|
-
# { address: Address.new("123 abc st.", "chicago") }
|
88
|
-
# # => "address_street='123 abc st.' and address_city='chicago'"
|
89
|
-
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
|
90
|
-
ActiveSupport::Deprecation.warn(<<-EOWARN)
|
91
|
-
sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
92
|
-
EOWARN
|
93
|
-
attrs = PredicateBuilder.resolve_column_aliases self, attrs
|
94
|
-
attrs = expand_hash_conditions_for_aggregates(attrs)
|
95
|
-
|
96
|
-
table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
|
97
|
-
PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
|
98
|
-
connection.visitor.compile b
|
99
|
-
}.join(' AND ')
|
100
|
-
end
|
101
|
-
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
|
102
|
-
|
103
112
|
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
|
104
|
-
#
|
105
|
-
#
|
113
|
+
#
|
114
|
+
# sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
|
115
|
+
# # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
|
106
116
|
def sanitize_sql_hash_for_assignment(attrs, table)
|
107
117
|
c = connection
|
108
118
|
attrs.map do |attr, value|
|
109
|
-
|
119
|
+
value = type_for_attribute(attr.to_s).serialize(value)
|
120
|
+
"#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
|
110
121
|
end.join(', ')
|
111
122
|
end
|
112
123
|
|
113
124
|
# Sanitizes a +string+ so that it is safe to use within an SQL
|
114
|
-
# LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%"
|
125
|
+
# LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
|
126
|
+
#
|
127
|
+
# sanitize_sql_like("100%")
|
128
|
+
# # => "100\\%"
|
129
|
+
#
|
130
|
+
# sanitize_sql_like("snake_cased_string")
|
131
|
+
# # => "snake\\_cased\\_string"
|
132
|
+
#
|
133
|
+
# sanitize_sql_like("100%", "!")
|
134
|
+
# # => "100!%"
|
135
|
+
#
|
136
|
+
# sanitize_sql_like("snake_cased_string", "!")
|
137
|
+
# # => "snake!_cased!_string"
|
115
138
|
def sanitize_sql_like(string, escape_character = "\\")
|
116
139
|
pattern = Regexp.union(escape_character, "%", "_")
|
117
140
|
string.gsub(pattern) { |x| [escape_character, x].join }
|
@@ -119,7 +142,15 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
119
142
|
|
120
143
|
# Accepts an array of conditions. The array has each value
|
121
144
|
# sanitized and interpolated into the SQL statement.
|
122
|
-
#
|
145
|
+
#
|
146
|
+
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
|
147
|
+
# # => "name='foo''bar' and group_id=4"
|
148
|
+
#
|
149
|
+
# sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
150
|
+
# # => "name='foo''bar' and group_id=4"
|
151
|
+
#
|
152
|
+
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
|
153
|
+
# # => "name='foo''bar' and group_id='4'"
|
123
154
|
def sanitize_sql_array(ary)
|
124
155
|
statement, *values = ary
|
125
156
|
if values.first.is_a?(Hash) && statement =~ /:\w+/
|
@@ -133,7 +164,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
133
164
|
end
|
134
165
|
end
|
135
166
|
|
136
|
-
def replace_bind_variables(statement, values)
|
167
|
+
def replace_bind_variables(statement, values) # :nodoc:
|
137
168
|
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
|
138
169
|
bound = values.dup
|
139
170
|
c = connection
|
@@ -142,7 +173,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
142
173
|
end
|
143
174
|
end
|
144
175
|
|
145
|
-
def replace_bind_variable(value, c = connection)
|
176
|
+
def replace_bind_variable(value, c = connection) # :nodoc:
|
146
177
|
if ActiveRecord::Relation === value
|
147
178
|
value.to_sql
|
148
179
|
else
|
@@ -150,10 +181,10 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
150
181
|
end
|
151
182
|
end
|
152
183
|
|
153
|
-
def replace_named_bind_variables(statement, bind_vars)
|
154
|
-
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
|
184
|
+
def replace_named_bind_variables(statement, bind_vars) # :nodoc:
|
185
|
+
statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
|
155
186
|
if $1 == ':' # skip postgresql casts
|
156
|
-
|
187
|
+
match # return the whole match
|
157
188
|
elsif bind_vars.include?(match = $2.to_sym)
|
158
189
|
replace_bind_variable(bind_vars[match])
|
159
190
|
else
|
@@ -162,10 +193,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
162
193
|
end
|
163
194
|
end
|
164
195
|
|
165
|
-
def quote_bound_value(value, c = connection
|
166
|
-
if
|
167
|
-
c.quote(value, column)
|
168
|
-
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
196
|
+
def quote_bound_value(value, c = connection) # :nodoc:
|
197
|
+
if value.respond_to?(:map) && !value.acts_like?(:string)
|
169
198
|
if value.respond_to?(:empty?) && value.empty?
|
170
199
|
c.quote(nil)
|
171
200
|
else
|
@@ -176,7 +205,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
176
205
|
end
|
177
206
|
end
|
178
207
|
|
179
|
-
def raise_if_bind_arity_mismatch(statement, expected, provided)
|
208
|
+
def raise_if_bind_arity_mismatch(statement, expected, provided) # :nodoc:
|
180
209
|
unless expected == provided
|
181
210
|
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
|
182
211
|
end
|
@@ -185,7 +214,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
185
214
|
|
186
215
|
# TODO: Deprecate this
|
187
216
|
def quoted_id
|
188
|
-
self.class.quote_value(
|
217
|
+
self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
|
189
218
|
end
|
190
219
|
end
|
191
220
|
end
|