activerecord 4.2.11.3 → 5.0.7.2
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 +4 -4
- data/CHANGELOG.md +1638 -1132
- data/MIT-LICENSE +2 -2
- data/README.rdoc +7 -8
- data/examples/performance.rb +2 -3
- data/examples/simple.rb +0 -1
- data/lib/active_record.rb +7 -2
- data/lib/active_record/aggregations.rb +34 -21
- data/lib/active_record/association_relation.rb +7 -4
- data/lib/active_record/associations.rb +347 -218
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +22 -10
- data/lib/active_record/associations/association_scope.rb +75 -104
- data/lib/active_record/associations/belongs_to_association.rb +21 -32
- data/lib/active_record/associations/builder/association.rb +28 -34
- data/lib/active_record/associations/builder/belongs_to.rb +43 -18
- data/lib/active_record/associations/builder/collection_association.rb +7 -19
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +16 -11
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +11 -6
- data/lib/active_record/associations/builder/singular_association.rb +13 -11
- data/lib/active_record/associations/collection_association.rb +85 -69
- data/lib/active_record/associations/collection_proxy.rb +104 -46
- data/lib/active_record/associations/foreign_association.rb +1 -1
- data/lib/active_record/associations/has_many_association.rb +21 -78
- data/lib/active_record/associations/has_many_through_association.rb +6 -47
- data/lib/active_record/associations/has_one_association.rb +12 -5
- data/lib/active_record/associations/join_dependency.rb +38 -22
- data/lib/active_record/associations/join_dependency/join_association.rb +15 -14
- data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
- data/lib/active_record/associations/preloader.rb +14 -4
- data/lib/active_record/associations/preloader/association.rb +52 -71
- data/lib/active_record/associations/preloader/collection_association.rb +0 -7
- 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/singular_association.rb +0 -1
- data/lib/active_record/associations/preloader/through_association.rb +36 -17
- data/lib/active_record/associations/singular_association.rb +13 -1
- data/lib/active_record/associations/through_association.rb +12 -4
- data/lib/active_record/attribute.rb +69 -19
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute_assignment.rb +19 -140
- data/lib/active_record/attribute_decorators.rb +6 -5
- data/lib/active_record/attribute_methods.rb +69 -44
- 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 +16 -3
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +31 -59
- data/lib/active_record/attribute_methods/serialization.rb +13 -16
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
- data/lib/active_record/attribute_methods/write.rb +13 -37
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set.rb +32 -3
- data/lib/active_record/attribute_set/builder.rb +42 -16
- data/lib/active_record/attributes.rb +199 -81
- data/lib/active_record/autosave_association.rb +54 -17
- data/lib/active_record/base.rb +32 -23
- data/lib/active_record/callbacks.rb +39 -43
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +20 -8
- data/lib/active_record/collection_cache_key.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +467 -189
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -62
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +39 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +86 -13
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -188
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +407 -156
- data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
- data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -71
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +433 -399
- data/lib/active_record/connection_adapters/column.rb +28 -43
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +108 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -166
- data/lib/active_record/connection_adapters/postgresql/column.rb +33 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -72
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +37 -57
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +13 -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 -4
- 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 +31 -17
- 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 +56 -19
- 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 +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +250 -154
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +264 -170
- 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/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +151 -194
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +37 -14
- data/lib/active_record/core.rb +92 -108
- data/lib/active_record/counter_cache.rb +13 -24
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +116 -76
- data/lib/active_record/errors.rb +87 -48
- data/lib/active_record/explain.rb +20 -9
- 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 +26 -5
- data/lib/active_record/fixtures.rb +77 -41
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +32 -40
- data/lib/active_record/integration.rb +17 -14
- data/lib/active_record/internal_metadata.rb +56 -0
- 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 +15 -15
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +48 -24
- data/lib/active_record/migration.rb +362 -111
- data/lib/active_record/migration/command_recorder.rb +59 -18
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/model_schema.rb +270 -73
- data/lib/active_record/nested_attributes.rb +58 -29
- data/lib/active_record/no_touching.rb +4 -0
- data/lib/active_record/null_relation.rb +16 -8
- data/lib/active_record/persistence.rb +152 -90
- data/lib/active_record/query_cache.rb +18 -23
- data/lib/active_record/querying.rb +12 -11
- data/lib/active_record/railtie.rb +23 -16
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +52 -41
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +302 -115
- data/lib/active_record/relation.rb +187 -120
- data/lib/active_record/relation/batches.rb +141 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/calculations.rb +92 -117
- data/lib/active_record/relation/delegation.rb +8 -20
- data/lib/active_record/relation/finder_methods.rb +173 -89
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +16 -42
- data/lib/active_record/relation/predicate_builder.rb +120 -107
- data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -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/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +308 -244
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +4 -7
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/result.rb +11 -4
- data/lib/active_record/runtime_registry.rb +1 -1
- data/lib/active_record/sanitization.rb +105 -66
- data/lib/active_record/schema.rb +26 -22
- data/lib/active_record/schema_dumper.rb +54 -37
- data/lib/active_record/schema_migration.rb +11 -14
- data/lib/active_record/scoping.rb +34 -16
- data/lib/active_record/scoping/default.rb +28 -10
- data/lib/active_record/scoping/named.rb +59 -26
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +3 -5
- data/lib/active_record/statement_cache.rb +17 -15
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +69 -0
- data/lib/active_record/tasks/database_tasks.rb +66 -49
- data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
- data/lib/active_record/tasks/postgresql_database_tasks.rb +12 -3
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +20 -9
- data/lib/active_record/touch_later.rb +63 -0
- data/lib/active_record/transactions.rb +139 -57
- 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 +15 -14
- data/lib/active_record/type/time.rb +10 -16
- 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 +23 -0
- data/lib/active_record/validations/associated.rb +10 -3
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +11 -12
- data/lib/active_record/validations/uniqueness.rb +33 -33
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
- data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +33 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
- metadata +58 -34
- 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,49 @@
|
|
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 |*, payload|
|
28
|
+
QueryRegistry.queries << payload[:sql]
|
29
|
+
end
|
30
|
+
# :startdoc:
|
31
|
+
|
32
|
+
class QueryRegistry # :nodoc:
|
33
|
+
extend ActiveSupport::PerThreadRegistry
|
34
|
+
|
35
|
+
attr_reader :queries
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@queries = []
|
39
|
+
end
|
40
|
+
|
41
|
+
def reset
|
42
|
+
@queries.clear
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
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) )
|
@@ -29,11 +29,11 @@ module ActiveRecord
|
|
29
29
|
# This is mainly intended for sharing common conditions between multiple associations.
|
30
30
|
def merge(other)
|
31
31
|
if other.is_a?(Array)
|
32
|
-
|
32
|
+
records & other
|
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,174 @@
|
|
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
|
+
@empty ||= 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
|
+
ARRAY_WITH_EMPTY_STRING = ['']
|
162
|
+
def non_empty_predicates
|
163
|
+
predicates - ARRAY_WITH_EMPTY_STRING
|
164
|
+
end
|
165
|
+
|
166
|
+
def wrap_sql_literal(node)
|
167
|
+
if ::String === node
|
168
|
+
node = Arel.sql(node)
|
169
|
+
end
|
170
|
+
Arel::Nodes::Grouping.new(node)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,38 @@
|
|
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
|
+
binds = other
|
26
|
+
else
|
27
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
28
|
+
end
|
29
|
+
|
30
|
+
WhereClause.new(parts, binds)
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
attr_reader :klass, :predicate_builder
|
36
|
+
end
|
37
|
+
end
|
38
|
+
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>
|
@@ -74,14 +75,20 @@ module ActiveRecord
|
|
74
75
|
hash_rows[idx]
|
75
76
|
end
|
76
77
|
|
78
|
+
def first
|
79
|
+
return nil if @rows.empty?
|
80
|
+
Hash[@columns.zip(@rows.first)]
|
81
|
+
end
|
82
|
+
|
77
83
|
def last
|
78
|
-
|
84
|
+
return nil if @rows.empty?
|
85
|
+
Hash[@columns.zip(@rows.last)]
|
79
86
|
end
|
80
87
|
|
81
88
|
def cast_values(type_overrides = {}) # :nodoc:
|
82
89
|
types = columns.map { |name| column_type(name, type_overrides) }
|
83
90
|
result = rows.map do |values|
|
84
|
-
types.zip(values).map { |type, value| type.
|
91
|
+
types.zip(values).map { |type, value| type.deserialize(value) }
|
85
92
|
end
|
86
93
|
|
87
94
|
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 an 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,42 @@ 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
|
+
if value.is_a?(Base)
|
120
|
+
require "active_support/core_ext/string/filters"
|
121
|
+
ActiveSupport::Deprecation.warn(<<-WARNING.squish)
|
122
|
+
Passing `ActiveRecord::Base` objects to
|
123
|
+
`sanitize_sql_hash_for_assignment` (or methods which call it,
|
124
|
+
such as `update_all`) is deprecated. Please pass the id directly,
|
125
|
+
instead.
|
126
|
+
WARNING
|
127
|
+
else
|
128
|
+
value = type_for_attribute(attr.to_s).serialize(value)
|
129
|
+
end
|
130
|
+
"#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
|
110
131
|
end.join(', ')
|
111
132
|
end
|
112
133
|
|
113
134
|
# 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 "%"
|
135
|
+
# LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
|
136
|
+
#
|
137
|
+
# sanitize_sql_like("100%")
|
138
|
+
# # => "100\\%"
|
139
|
+
#
|
140
|
+
# sanitize_sql_like("snake_cased_string")
|
141
|
+
# # => "snake\\_cased\\_string"
|
142
|
+
#
|
143
|
+
# sanitize_sql_like("100%", "!")
|
144
|
+
# # => "100!%"
|
145
|
+
#
|
146
|
+
# sanitize_sql_like("snake_cased_string", "!")
|
147
|
+
# # => "snake!_cased!_string"
|
115
148
|
def sanitize_sql_like(string, escape_character = "\\")
|
116
149
|
pattern = Regexp.union(escape_character, "%", "_")
|
117
150
|
string.gsub(pattern) { |x| [escape_character, x].join }
|
@@ -119,7 +152,15 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
119
152
|
|
120
153
|
# Accepts an array of conditions. The array has each value
|
121
154
|
# sanitized and interpolated into the SQL statement.
|
122
|
-
#
|
155
|
+
#
|
156
|
+
# sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
|
157
|
+
# # => "name='foo''bar' and group_id=4"
|
158
|
+
#
|
159
|
+
# sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
|
160
|
+
# # => "name='foo''bar' and group_id=4"
|
161
|
+
#
|
162
|
+
# sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
|
163
|
+
# # => "name='foo''bar' and group_id='4'"
|
123
164
|
def sanitize_sql_array(ary)
|
124
165
|
statement, *values = ary
|
125
166
|
if values.first.is_a?(Hash) && statement =~ /:\w+/
|
@@ -133,7 +174,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
133
174
|
end
|
134
175
|
end
|
135
176
|
|
136
|
-
def replace_bind_variables(statement, values)
|
177
|
+
def replace_bind_variables(statement, values) # :nodoc:
|
137
178
|
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
|
138
179
|
bound = values.dup
|
139
180
|
c = connection
|
@@ -142,7 +183,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
142
183
|
end
|
143
184
|
end
|
144
185
|
|
145
|
-
def replace_bind_variable(value, c = connection)
|
186
|
+
def replace_bind_variable(value, c = connection) # :nodoc:
|
146
187
|
if ActiveRecord::Relation === value
|
147
188
|
value.to_sql
|
148
189
|
else
|
@@ -150,10 +191,10 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
150
191
|
end
|
151
192
|
end
|
152
193
|
|
153
|
-
def replace_named_bind_variables(statement, bind_vars)
|
154
|
-
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
|
194
|
+
def replace_named_bind_variables(statement, bind_vars) # :nodoc:
|
195
|
+
statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
|
155
196
|
if $1 == ':' # skip postgresql casts
|
156
|
-
|
197
|
+
match # return the whole match
|
157
198
|
elsif bind_vars.include?(match = $2.to_sym)
|
158
199
|
replace_bind_variable(bind_vars[match])
|
159
200
|
else
|
@@ -162,10 +203,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
162
203
|
end
|
163
204
|
end
|
164
205
|
|
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)
|
206
|
+
def quote_bound_value(value, c = connection) # :nodoc:
|
207
|
+
if value.respond_to?(:map) && !value.acts_like?(:string)
|
169
208
|
if value.respond_to?(:empty?) && value.empty?
|
170
209
|
c.quote(nil)
|
171
210
|
else
|
@@ -176,7 +215,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
176
215
|
end
|
177
216
|
end
|
178
217
|
|
179
|
-
def raise_if_bind_arity_mismatch(statement, expected, provided)
|
218
|
+
def raise_if_bind_arity_mismatch(statement, expected, provided) # :nodoc:
|
180
219
|
unless expected == provided
|
181
220
|
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
|
182
221
|
end
|
@@ -184,8 +223,8 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
|
|
184
223
|
end
|
185
224
|
|
186
225
|
# TODO: Deprecate this
|
187
|
-
def quoted_id
|
188
|
-
self.class.quote_value(
|
226
|
+
def quoted_id # :nodoc:
|
227
|
+
self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
|
189
228
|
end
|
190
229
|
end
|
191
230
|
end
|