activerecord 3.2.22.5 → 4.2.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1632 -609
- data/MIT-LICENSE +1 -1
- data/README.rdoc +37 -41
- data/examples/performance.rb +31 -19
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +56 -42
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -36
- data/lib/active_record/associations/association.rb +73 -55
- data/lib/active_record/associations/association_scope.rb +143 -82
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +125 -31
- data/lib/active_record/associations/builder/belongs_to.rb +89 -61
- data/lib/active_record/associations/builder/collection_association.rb +69 -49
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +12 -51
- data/lib/active_record/associations/builder/singular_association.rb +23 -17
- data/lib/active_record/associations/collection_association.rb +251 -177
- data/lib/active_record/associations/collection_proxy.rb +963 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +113 -22
- data/lib/active_record/associations/has_many_through_association.rb +99 -39
- data/lib/active_record/associations/has_one_association.rb +43 -20
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +230 -156
- data/lib/active_record/associations/preloader/association.rb +96 -55
- data/lib/active_record/associations/preloader/collection_association.rb +3 -3
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +62 -33
- data/lib/active_record/associations/preloader.rb +101 -79
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +30 -16
- data/lib/active_record/associations.rb +463 -345
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +142 -151
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +137 -57
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +73 -106
- data/lib/active_record/attribute_methods/serialization.rb +44 -94
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
- data/lib/active_record/attribute_methods/write.rb +57 -44
- data/lib/active_record/attribute_methods.rb +301 -141
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +246 -217
- data/lib/active_record/base.rb +70 -474
- data/lib/active_record/callbacks.rb +66 -28
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
- data/lib/active_record/connection_adapters/column.rb +31 -245
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
- data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
- data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +157 -105
- data/lib/active_record/dynamic_matchers.rb +119 -63
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +94 -36
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +9 -5
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +302 -215
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +143 -70
- data/lib/active_record/integration.rb +65 -12
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +73 -52
- data/lib/active_record/locking/pessimistic.rb +5 -5
- data/lib/active_record/log_subscriber.rb +24 -21
- data/lib/active_record/migration/command_recorder.rb +124 -32
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +511 -213
- data/lib/active_record/model_schema.rb +91 -117
- data/lib/active_record/nested_attributes.rb +184 -130
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +276 -117
- data/lib/active_record/query_cache.rb +19 -37
- data/lib/active_record/querying.rb +28 -18
- data/lib/active_record/railtie.rb +73 -40
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +141 -416
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +1 -4
- data/lib/active_record/reflection.rb +513 -154
- data/lib/active_record/relation/batches.rb +91 -43
- data/lib/active_record/relation/calculations.rb +199 -161
- data/lib/active_record/relation/delegation.rb +116 -25
- data/lib/active_record/relation/finder_methods.rb +362 -248
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +135 -43
- data/lib/active_record/relation/query_methods.rb +928 -167
- data/lib/active_record/relation/spawn_methods.rb +48 -149
- data/lib/active_record/relation.rb +352 -207
- data/lib/active_record/result.rb +101 -10
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +56 -59
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +106 -63
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +50 -57
- data/lib/active_record/scoping/named.rb +73 -109
- data/lib/active_record/scoping.rb +58 -123
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +12 -22
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +168 -15
- data/lib/active_record/tasks/database_tasks.rb +299 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +23 -16
- data/lib/active_record/transactions.rb +125 -79
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +24 -16
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +123 -64
- data/lib/active_record/validations.rb +36 -29
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +66 -46
- data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +101 -45
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -17,6 +17,15 @@ module ActiveRecord
|
|
17
17
|
64
|
18
18
|
end
|
19
19
|
|
20
|
+
# Returns the maximum allowed length for an index name. This
|
21
|
+
# limit is enforced by rails and Is less than or equal to
|
22
|
+
# <tt>index_name_length</tt>. The gap between
|
23
|
+
# <tt>index_name_length</tt> is to allow internal rails
|
24
|
+
# operations to use prefixes in temporary operations.
|
25
|
+
def allowed_index_name_length
|
26
|
+
index_name_length
|
27
|
+
end
|
28
|
+
|
20
29
|
# Returns the maximum length of an index name.
|
21
30
|
def index_name_length
|
22
31
|
64
|
@@ -1,33 +1,46 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module ConnectionAdapters # :nodoc:
|
3
3
|
module DatabaseStatements
|
4
|
+
def initialize
|
5
|
+
super
|
6
|
+
reset_transaction
|
7
|
+
end
|
8
|
+
|
4
9
|
# Converts an arel AST to SQL
|
5
10
|
def to_sql(arel, binds = [])
|
6
11
|
if arel.respond_to?(:ast)
|
7
|
-
visitor.accept(arel.ast)
|
8
|
-
|
9
|
-
end
|
12
|
+
collected = visitor.accept(arel.ast, collector)
|
13
|
+
collected.compile(binds.dup, self)
|
10
14
|
else
|
11
15
|
arel
|
12
16
|
end
|
13
17
|
end
|
14
18
|
|
15
|
-
#
|
16
|
-
#
|
19
|
+
# This is used in the StatementCache object. It returns an object that
|
20
|
+
# can be used to query the database repeatedly.
|
21
|
+
def cacheable_query(arel) # :nodoc:
|
22
|
+
if prepared_statements
|
23
|
+
ActiveRecord::StatementCache.query visitor, arel.ast
|
24
|
+
else
|
25
|
+
ActiveRecord::StatementCache.partial_query visitor, arel.ast, collector
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns an ActiveRecord::Result instance.
|
17
30
|
def select_all(arel, name = nil, binds = [])
|
31
|
+
arel, binds = binds_from_relation arel, binds
|
18
32
|
select(to_sql(arel, binds), name, binds)
|
19
33
|
end
|
20
34
|
|
21
35
|
# Returns a record hash with the column names as keys and column values
|
22
36
|
# as values.
|
23
|
-
def select_one(arel, name = nil)
|
24
|
-
|
25
|
-
result.first if result
|
37
|
+
def select_one(arel, name = nil, binds = [])
|
38
|
+
select_all(arel, name, binds).first
|
26
39
|
end
|
27
40
|
|
28
41
|
# Returns a single value from a record
|
29
|
-
def select_value(arel, name = nil)
|
30
|
-
if result = select_one(arel, name)
|
42
|
+
def select_value(arel, name = nil, binds = [])
|
43
|
+
if result = select_one(arel, name, binds)
|
31
44
|
result.values.first
|
32
45
|
end
|
33
46
|
end
|
@@ -35,13 +48,13 @@ module ActiveRecord
|
|
35
48
|
# Returns an array of the values of the first column in a select:
|
36
49
|
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
37
50
|
def select_values(arel, name = nil)
|
38
|
-
|
39
|
-
|
51
|
+
arel, binds = binds_from_relation arel, []
|
52
|
+
select_rows(to_sql(arel, binds), name, binds).map(&:first)
|
40
53
|
end
|
41
54
|
|
42
55
|
# Returns an array of arrays containing the field values.
|
43
56
|
# Order is the same as that returned by +columns+.
|
44
|
-
def select_rows(sql, name = nil)
|
57
|
+
def select_rows(sql, name = nil, binds = [])
|
45
58
|
end
|
46
59
|
undef_method :select_rows
|
47
60
|
|
@@ -57,21 +70,26 @@ module ActiveRecord
|
|
57
70
|
end
|
58
71
|
|
59
72
|
# Executes insert +sql+ statement in the context of this connection using
|
60
|
-
# +binds+ as the bind substitutes. +name+ is
|
73
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
61
74
|
# the executed +sql+ statement.
|
62
|
-
def exec_insert(sql, name, binds)
|
75
|
+
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
|
63
76
|
exec_query(sql, name, binds)
|
64
77
|
end
|
65
78
|
|
66
79
|
# Executes delete +sql+ statement in the context of this connection using
|
67
|
-
# +binds+ as the bind substitutes. +name+ is
|
80
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
68
81
|
# the executed +sql+ statement.
|
69
82
|
def exec_delete(sql, name, binds)
|
70
83
|
exec_query(sql, name, binds)
|
71
84
|
end
|
72
85
|
|
86
|
+
# Executes the truncate statement.
|
87
|
+
def truncate(table_name, name = nil)
|
88
|
+
raise NotImplementedError
|
89
|
+
end
|
90
|
+
|
73
91
|
# Executes update +sql+ statement in the context of this connection using
|
74
|
-
# +binds+ as the bind substitutes. +name+ is
|
92
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
75
93
|
# the executed +sql+ statement.
|
76
94
|
def exec_update(sql, name, binds)
|
77
95
|
exec_query(sql, name, binds)
|
@@ -87,7 +105,7 @@ module ActiveRecord
|
|
87
105
|
# passed in as +id_value+.
|
88
106
|
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
|
89
107
|
sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
|
90
|
-
value = exec_insert(sql, name, binds)
|
108
|
+
value = exec_insert(sql, name, binds, pk, sequence_name)
|
91
109
|
id_value || last_inserted_id(value)
|
92
110
|
end
|
93
111
|
|
@@ -101,20 +119,6 @@ module ActiveRecord
|
|
101
119
|
exec_delete(to_sql(arel, binds), name, binds)
|
102
120
|
end
|
103
121
|
|
104
|
-
# Checks whether there is currently no transaction active. This is done
|
105
|
-
# by querying the database driver, and does not use the transaction
|
106
|
-
# house-keeping information recorded by #increment_open_transactions and
|
107
|
-
# friends.
|
108
|
-
#
|
109
|
-
# Returns true if there is no transaction active, false if there is a
|
110
|
-
# transaction active, and nil if this information is unknown.
|
111
|
-
#
|
112
|
-
# Not all adapters supports transaction state introspection. Currently,
|
113
|
-
# only the PostgreSQL adapter supports this.
|
114
|
-
def outside_transaction?
|
115
|
-
nil
|
116
|
-
end
|
117
|
-
|
118
122
|
# Returns +true+ when the connection adapter supports prepared statement
|
119
123
|
# caching, otherwise returns +false+
|
120
124
|
def supports_statement_cache?
|
@@ -133,7 +137,8 @@ module ActiveRecord
|
|
133
137
|
# In order to get around this problem, #transaction will emulate the effect
|
134
138
|
# of nested transactions, by using savepoints:
|
135
139
|
# http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
|
136
|
-
# Savepoints are supported by MySQL and PostgreSQL
|
140
|
+
# Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8'
|
141
|
+
# supports savepoints.
|
137
142
|
#
|
138
143
|
# It is safe to call this method if a database transaction is already open,
|
139
144
|
# i.e. if #transaction is called within another #transaction block. In case
|
@@ -158,101 +163,117 @@ module ActiveRecord
|
|
158
163
|
# already-automatically-released savepoints:
|
159
164
|
#
|
160
165
|
# Model.connection.transaction do # BEGIN
|
161
|
-
# Model.connection.transaction(:
|
166
|
+
# Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
|
162
167
|
# Model.connection.create_table(...)
|
163
168
|
# # active_record_1 now automatically released
|
164
169
|
# end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
|
165
170
|
# end
|
171
|
+
#
|
172
|
+
# == Transaction isolation
|
173
|
+
#
|
174
|
+
# If your database supports setting the isolation level for a transaction, you can set
|
175
|
+
# it like so:
|
176
|
+
#
|
177
|
+
# Post.transaction(isolation: :serializable) do
|
178
|
+
# # ...
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# Valid isolation levels are:
|
182
|
+
#
|
183
|
+
# * <tt>:read_uncommitted</tt>
|
184
|
+
# * <tt>:read_committed</tt>
|
185
|
+
# * <tt>:repeatable_read</tt>
|
186
|
+
# * <tt>:serializable</tt>
|
187
|
+
#
|
188
|
+
# You should consult the documentation for your database to understand the
|
189
|
+
# semantics of these different levels:
|
190
|
+
#
|
191
|
+
# * http://www.postgresql.org/docs/9.1/static/transaction-iso.html
|
192
|
+
# * https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
|
193
|
+
#
|
194
|
+
# An <tt>ActiveRecord::TransactionIsolationError</tt> will be raised if:
|
195
|
+
#
|
196
|
+
# * The adapter does not support setting the isolation level
|
197
|
+
# * You are joining an existing open transaction
|
198
|
+
# * You are creating a nested (savepoint) transaction
|
199
|
+
#
|
200
|
+
# The mysql, mysql2 and postgresql adapters support setting the transaction
|
201
|
+
# isolation level. However, support is disabled for MySQL versions below 5,
|
202
|
+
# because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
|
203
|
+
# which means the isolation level gets persisted outside the transaction.
|
166
204
|
def transaction(options = {})
|
167
|
-
options.assert_valid_keys :requires_new, :joinable
|
205
|
+
options.assert_valid_keys :requires_new, :joinable, :isolation
|
168
206
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
else
|
173
|
-
@transaction_joinable = true
|
174
|
-
end
|
175
|
-
requires_new = options[:requires_new] || !last_transaction_joinable
|
176
|
-
|
177
|
-
transaction_open = false
|
178
|
-
@_current_transaction_records ||= []
|
179
|
-
|
180
|
-
begin
|
181
|
-
if block_given?
|
182
|
-
if requires_new || open_transactions == 0
|
183
|
-
if open_transactions == 0
|
184
|
-
begin_db_transaction
|
185
|
-
elsif requires_new
|
186
|
-
create_savepoint
|
187
|
-
end
|
188
|
-
increment_open_transactions
|
189
|
-
transaction_open = true
|
190
|
-
@_current_transaction_records.push([])
|
191
|
-
end
|
192
|
-
yield
|
193
|
-
end
|
194
|
-
rescue Exception => database_transaction_rollback
|
195
|
-
if transaction_open && !outside_transaction?
|
196
|
-
transaction_open = false
|
197
|
-
decrement_open_transactions
|
198
|
-
if open_transactions == 0
|
199
|
-
rollback_db_transaction
|
200
|
-
rollback_transaction_records(true)
|
201
|
-
else
|
202
|
-
rollback_to_savepoint
|
203
|
-
rollback_transaction_records(false)
|
204
|
-
end
|
205
|
-
end
|
206
|
-
raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
|
207
|
-
end
|
208
|
-
ensure
|
209
|
-
@transaction_joinable = last_transaction_joinable
|
210
|
-
|
211
|
-
if outside_transaction?
|
212
|
-
@open_transactions = 0
|
213
|
-
elsif transaction_open
|
214
|
-
decrement_open_transactions
|
215
|
-
begin
|
216
|
-
if open_transactions == 0
|
217
|
-
commit_db_transaction
|
218
|
-
commit_transaction_records
|
219
|
-
else
|
220
|
-
release_savepoint
|
221
|
-
save_point_records = @_current_transaction_records.pop
|
222
|
-
unless save_point_records.blank?
|
223
|
-
@_current_transaction_records.push([]) if @_current_transaction_records.empty?
|
224
|
-
@_current_transaction_records.last.concat(save_point_records)
|
225
|
-
end
|
226
|
-
end
|
227
|
-
rescue Exception => database_transaction_rollback
|
228
|
-
if open_transactions == 0
|
229
|
-
rollback_db_transaction
|
230
|
-
rollback_transaction_records(true)
|
231
|
-
else
|
232
|
-
rollback_to_savepoint
|
233
|
-
rollback_transaction_records(false)
|
234
|
-
end
|
235
|
-
raise
|
207
|
+
if !options[:requires_new] && current_transaction.joinable?
|
208
|
+
if options[:isolation]
|
209
|
+
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
|
236
210
|
end
|
211
|
+
yield
|
212
|
+
else
|
213
|
+
transaction_manager.within_new_transaction(options) { yield }
|
237
214
|
end
|
215
|
+
rescue ActiveRecord::Rollback
|
216
|
+
# rollbacks are silently swallowed
|
217
|
+
end
|
218
|
+
|
219
|
+
attr_reader :transaction_manager #:nodoc:
|
220
|
+
|
221
|
+
delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager
|
222
|
+
|
223
|
+
def transaction_open?
|
224
|
+
current_transaction.open?
|
225
|
+
end
|
226
|
+
|
227
|
+
def reset_transaction #:nodoc:
|
228
|
+
@transaction_manager = TransactionManager.new(self)
|
238
229
|
end
|
239
230
|
|
240
231
|
# Register a record with the current transaction so that its after_commit and after_rollback callbacks
|
241
232
|
# can be called.
|
242
233
|
def add_transaction_record(record)
|
243
|
-
|
244
|
-
|
234
|
+
current_transaction.add_record(record)
|
235
|
+
end
|
236
|
+
|
237
|
+
def transaction_state
|
238
|
+
current_transaction.state
|
245
239
|
end
|
246
240
|
|
247
241
|
# Begins the transaction (and turns off auto-committing).
|
248
242
|
def begin_db_transaction() end
|
249
243
|
|
244
|
+
def transaction_isolation_levels
|
245
|
+
{
|
246
|
+
read_uncommitted: "READ UNCOMMITTED",
|
247
|
+
read_committed: "READ COMMITTED",
|
248
|
+
repeatable_read: "REPEATABLE READ",
|
249
|
+
serializable: "SERIALIZABLE"
|
250
|
+
}
|
251
|
+
end
|
252
|
+
|
253
|
+
# Begins the transaction with the isolation level set. Raises an error by
|
254
|
+
# default; adapters that support setting the isolation level should implement
|
255
|
+
# this method.
|
256
|
+
def begin_isolated_db_transaction(isolation)
|
257
|
+
raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
|
258
|
+
end
|
259
|
+
|
250
260
|
# Commits the transaction (and turns on auto-committing).
|
251
261
|
def commit_db_transaction() end
|
252
262
|
|
253
263
|
# Rolls back the transaction (and turns on auto-committing). Must be
|
254
264
|
# done if the transaction block raises an exception or returns false.
|
255
|
-
def rollback_db_transaction
|
265
|
+
def rollback_db_transaction
|
266
|
+
exec_rollback_db_transaction
|
267
|
+
end
|
268
|
+
|
269
|
+
def exec_rollback_db_transaction() end #:nodoc:
|
270
|
+
|
271
|
+
def rollback_to_savepoint(name = nil)
|
272
|
+
exec_rollback_to_savepoint(name)
|
273
|
+
end
|
274
|
+
|
275
|
+
def exec_rollback_to_savepoint(name = nil) #:nodoc:
|
276
|
+
end
|
256
277
|
|
257
278
|
def default_sequence_name(table, column)
|
258
279
|
nil
|
@@ -266,27 +287,24 @@ module ActiveRecord
|
|
266
287
|
# Inserts the given fixture into the table. Overridden in adapters that require
|
267
288
|
# something beyond a simple insert (eg. Oracle).
|
268
289
|
def insert_fixture(fixture, table_name)
|
290
|
+
fixture = fixture.stringify_keys
|
269
291
|
columns = schema_cache.columns_hash(table_name)
|
270
292
|
|
271
293
|
key_list = []
|
272
294
|
value_list = fixture.map do |name, value|
|
273
|
-
|
274
|
-
|
295
|
+
if column = columns[name]
|
296
|
+
key_list << quote_column_name(name)
|
297
|
+
quote(value, column)
|
298
|
+
else
|
299
|
+
raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
|
300
|
+
end
|
275
301
|
end
|
276
302
|
|
277
303
|
execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
|
278
304
|
end
|
279
305
|
|
280
306
|
def empty_insert_statement_value
|
281
|
-
"VALUES
|
282
|
-
end
|
283
|
-
|
284
|
-
def case_sensitive_equality_operator
|
285
|
-
"="
|
286
|
-
end
|
287
|
-
|
288
|
-
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
|
289
|
-
"WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
|
307
|
+
"DEFAULT VALUES"
|
290
308
|
end
|
291
309
|
|
292
310
|
# Sanitizes the given LIMIT parameter in order to prevent SQL injection.
|
@@ -301,7 +319,7 @@ module ActiveRecord
|
|
301
319
|
def sanitize_limit(limit)
|
302
320
|
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
|
303
321
|
limit
|
304
|
-
elsif limit.to_s
|
322
|
+
elsif limit.to_s.include?(',')
|
305
323
|
Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
|
306
324
|
else
|
307
325
|
Integer(limit)
|
@@ -309,21 +327,35 @@ module ActiveRecord
|
|
309
327
|
end
|
310
328
|
|
311
329
|
# The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
|
312
|
-
# on
|
313
|
-
# an UPDATE statement, so in the
|
330
|
+
# on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
|
331
|
+
# an UPDATE statement, so in the MySQL adapters we redefine this to do that.
|
314
332
|
def join_to_update(update, select) #:nodoc:
|
315
|
-
|
316
|
-
subselect
|
333
|
+
key = update.key
|
334
|
+
subselect = subquery_for(key, select)
|
317
335
|
|
318
|
-
update.where
|
336
|
+
update.where key.in(subselect)
|
337
|
+
end
|
338
|
+
|
339
|
+
def join_to_delete(delete, select, key) #:nodoc:
|
340
|
+
subselect = subquery_for(key, select)
|
341
|
+
|
342
|
+
delete.where key.in(subselect)
|
319
343
|
end
|
320
344
|
|
321
345
|
protected
|
322
|
-
|
323
|
-
#
|
346
|
+
|
347
|
+
# Returns a subquery for the given key using the join information.
|
348
|
+
def subquery_for(key, select)
|
349
|
+
subselect = select.clone
|
350
|
+
subselect.projections = [key]
|
351
|
+
subselect
|
352
|
+
end
|
353
|
+
|
354
|
+
# Returns an ActiveRecord::Result instance.
|
324
355
|
def select(sql, name = nil, binds = [])
|
356
|
+
exec_query(sql, name, binds)
|
325
357
|
end
|
326
|
-
|
358
|
+
|
327
359
|
|
328
360
|
# Returns the last auto-generated ID from the affected table.
|
329
361
|
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
@@ -341,50 +373,21 @@ module ActiveRecord
|
|
341
373
|
update_sql(sql, name)
|
342
374
|
end
|
343
375
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
if rollback
|
348
|
-
records = @_current_transaction_records.flatten
|
349
|
-
@_current_transaction_records.clear
|
350
|
-
else
|
351
|
-
records = @_current_transaction_records.pop
|
352
|
-
end
|
376
|
+
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
377
|
+
[sql, binds]
|
378
|
+
end
|
353
379
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
record.rolledback!(rollback)
|
358
|
-
rescue Exception => e
|
359
|
-
record.logger.error(e) if record.respond_to?(:logger) && record.logger
|
360
|
-
end
|
361
|
-
end
|
362
|
-
end
|
380
|
+
def last_inserted_id(result)
|
381
|
+
row = result.rows.first
|
382
|
+
row && row.first
|
363
383
|
end
|
364
384
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
@_current_transaction_records.clear
|
369
|
-
unless records.blank?
|
370
|
-
records.uniq.each do |record|
|
371
|
-
begin
|
372
|
-
record.committed!
|
373
|
-
rescue Exception => e
|
374
|
-
record.logger.error(e) if record.respond_to?(:logger) && record.logger
|
375
|
-
end
|
376
|
-
end
|
385
|
+
def binds_from_relation(relation, binds)
|
386
|
+
if relation.is_a?(Relation) && binds.empty?
|
387
|
+
relation, binds = relation.arel, relation.bind_values
|
377
388
|
end
|
389
|
+
[relation, binds]
|
378
390
|
end
|
379
|
-
|
380
|
-
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
381
|
-
[sql, binds]
|
382
|
-
end
|
383
|
-
|
384
|
-
def last_inserted_id(result)
|
385
|
-
row = result.rows.first
|
386
|
-
row && row.first
|
387
|
-
end
|
388
391
|
end
|
389
392
|
end
|
390
393
|
end
|
@@ -2,17 +2,17 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters # :nodoc:
|
3
3
|
module QueryCache
|
4
4
|
class << self
|
5
|
-
def included(base)
|
6
|
-
dirties_query_cache base, :insert, :update, :delete
|
5
|
+
def included(base) #:nodoc:
|
6
|
+
dirties_query_cache base, :insert, :update, :delete, :rollback_to_savepoint, :rollback_db_transaction
|
7
7
|
end
|
8
8
|
|
9
9
|
def dirties_query_cache(base, *method_names)
|
10
10
|
method_names.each do |method_name|
|
11
11
|
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
|
12
|
-
def #{method_name}(*)
|
13
|
-
clear_query_cache if @query_cache_enabled
|
14
|
-
super
|
15
|
-
end
|
12
|
+
def #{method_name}(*)
|
13
|
+
clear_query_cache if @query_cache_enabled
|
14
|
+
super
|
15
|
+
end
|
16
16
|
end_code
|
17
17
|
end
|
18
18
|
end
|
@@ -20,13 +20,19 @@ module ActiveRecord
|
|
20
20
|
|
21
21
|
attr_reader :query_cache, :query_cache_enabled
|
22
22
|
|
23
|
+
def initialize(*)
|
24
|
+
super
|
25
|
+
@query_cache = Hash.new { |h,sql| h[sql] = {} }
|
26
|
+
@query_cache_enabled = false
|
27
|
+
end
|
28
|
+
|
23
29
|
# Enable the query cache within the block.
|
24
30
|
def cache
|
25
31
|
old, @query_cache_enabled = @query_cache_enabled, true
|
26
32
|
yield
|
27
33
|
ensure
|
28
|
-
clear_query_cache
|
29
34
|
@query_cache_enabled = old
|
35
|
+
clear_query_cache unless @query_cache_enabled
|
30
36
|
end
|
31
37
|
|
32
38
|
def enable_query_cache!
|
@@ -57,6 +63,7 @@ module ActiveRecord
|
|
57
63
|
|
58
64
|
def select_all(arel, name = nil, binds = [])
|
59
65
|
if @query_cache_enabled && !locked?(arel)
|
66
|
+
arel, binds = binds_from_relation arel, binds
|
60
67
|
sql = to_sql(arel, binds)
|
61
68
|
cache_sql(sql, binds) { super(sql, name, binds) }
|
62
69
|
else
|
@@ -65,26 +72,24 @@ module ActiveRecord
|
|
65
72
|
end
|
66
73
|
|
67
74
|
private
|
68
|
-
def cache_sql(sql, binds)
|
69
|
-
result =
|
70
|
-
if @query_cache[sql].key?(binds)
|
71
|
-
ActiveSupport::Notifications.instrument("sql.active_record",
|
72
|
-
:sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
|
73
|
-
@query_cache[sql][binds]
|
74
|
-
else
|
75
|
-
@query_cache[sql][binds] = yield
|
76
|
-
end
|
77
|
-
|
78
|
-
result.collect { |row| row.dup }
|
79
|
-
end
|
80
75
|
|
81
|
-
|
82
|
-
|
83
|
-
|
76
|
+
def cache_sql(sql, binds)
|
77
|
+
result =
|
78
|
+
if @query_cache[sql].key?(binds)
|
79
|
+
ActiveSupport::Notifications.instrument("sql.active_record",
|
80
|
+
:sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
|
81
|
+
@query_cache[sql][binds]
|
84
82
|
else
|
85
|
-
|
83
|
+
@query_cache[sql][binds] = yield
|
86
84
|
end
|
87
|
-
|
85
|
+
result.dup
|
86
|
+
end
|
87
|
+
|
88
|
+
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
|
89
|
+
# queries should not be cached.
|
90
|
+
def locked?(arel)
|
91
|
+
arel.respond_to?(:locked) && arel.locked
|
92
|
+
end
|
88
93
|
end
|
89
94
|
end
|
90
95
|
end
|