activerecord 1.0.0 → 4.0.0
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 +7 -0
- data/CHANGELOG.md +2102 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +213 -0
- data/examples/performance.rb +172 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record/aggregations.rb +180 -84
- data/lib/active_record/associations/alias_tracker.rb +76 -0
- data/lib/active_record/associations/association.rb +248 -0
- data/lib/active_record/associations/association_scope.rb +135 -0
- data/lib/active_record/associations/belongs_to_association.rb +92 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
- data/lib/active_record/associations/builder/association.rb +108 -0
- data/lib/active_record/associations/builder/belongs_to.rb +98 -0
- data/lib/active_record/associations/builder/collection_association.rb +89 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +25 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +608 -0
- data/lib/active_record/associations/collection_proxy.rb +986 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
- data/lib/active_record/associations/has_many_association.rb +116 -85
- data/lib/active_record/associations/has_many_through_association.rb +197 -0
- data/lib/active_record/associations/has_one_association.rb +102 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_dependency.rb +235 -0
- data/lib/active_record/associations/join_helper.rb +45 -0
- data/lib/active_record/associations/preloader/association.rb +121 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +63 -0
- data/lib/active_record/associations/preloader.rb +178 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +87 -0
- data/lib/active_record/associations.rb +1437 -431
- data/lib/active_record/attribute_assignment.rb +201 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
- data/lib/active_record/attribute_methods/dirty.rb +118 -0
- data/lib/active_record/attribute_methods/primary_key.rb +122 -0
- data/lib/active_record/attribute_methods/query.rb +40 -0
- data/lib/active_record/attribute_methods/read.rb +107 -0
- data/lib/active_record/attribute_methods/serialization.rb +162 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
- data/lib/active_record/attribute_methods/write.rb +63 -0
- data/lib/active_record/attribute_methods.rb +393 -0
- data/lib/active_record/autosave_association.rb +426 -0
- data/lib/active_record/base.rb +268 -930
- data/lib/active_record/callbacks.rb +203 -230
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
- data/lib/active_record/connection_adapters/column.rb +318 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
- data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +463 -0
- data/lib/active_record/counter_cache.rb +122 -0
- data/lib/active_record/dynamic_matchers.rb +131 -0
- data/lib/active_record/errors.rb +213 -0
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +55 -0
- data/lib/active_record/fixtures.rb +892 -138
- data/lib/active_record/inheritance.rb +200 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locale/en.yml +47 -0
- data/lib/active_record/locking/optimistic.rb +181 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +82 -0
- data/lib/active_record/migration/command_recorder.rb +164 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +1015 -0
- data/lib/active_record/model_schema.rb +345 -0
- data/lib/active_record/nested_attributes.rb +546 -0
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +509 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/querying.rb +62 -0
- data/lib/active_record/railtie.rb +205 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +50 -0
- data/lib/active_record/railties/databases.rake +402 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +30 -0
- data/lib/active_record/reflection.rb +544 -87
- data/lib/active_record/relation/batches.rb +93 -0
- data/lib/active_record/relation/calculations.rb +399 -0
- data/lib/active_record/relation/delegation.rb +125 -0
- data/lib/active_record/relation/finder_methods.rb +349 -0
- data/lib/active_record/relation/merger.rb +161 -0
- data/lib/active_record/relation/predicate_builder.rb +106 -0
- data/lib/active_record/relation/query_methods.rb +1044 -0
- data/lib/active_record/relation/spawn_methods.rb +73 -0
- data/lib/active_record/relation.rb +655 -0
- data/lib/active_record/result.rb +67 -0
- data/lib/active_record/runtime_registry.rb +17 -0
- data/lib/active_record/sanitization.rb +168 -0
- data/lib/active_record/schema.rb +65 -0
- data/lib/active_record/schema_dumper.rb +204 -0
- data/lib/active_record/schema_migration.rb +39 -0
- data/lib/active_record/scoping/default.rb +146 -0
- data/lib/active_record/scoping/named.rb +175 -0
- data/lib/active_record/scoping.rb +82 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/serializers/xml_serializer.rb +197 -0
- data/lib/active_record/statement_cache.rb +26 -0
- data/lib/active_record/store.rb +156 -0
- data/lib/active_record/tasks/database_tasks.rb +203 -0
- data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
- data/lib/active_record/test_case.rb +96 -0
- data/lib/active_record/timestamp.rb +119 -0
- data/lib/active_record/transactions.rb +366 -69
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +49 -0
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +225 -0
- data/lib/active_record/validations.rb +64 -185
- data/lib/active_record/version.rb +11 -0
- data/lib/active_record.rb +149 -24
- data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record.rb +23 -0
- metadata +261 -161
- data/CHANGELOG +0 -581
- data/README +0 -361
- data/RUNNING_UNIT_TESTS +0 -36
- data/dev-utils/eval_debugger.rb +0 -9
- data/examples/associations.png +0 -0
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -88
- data/install.rb +0 -60
- data/lib/active_record/associations/association_collection.rb +0 -70
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
- data/lib/active_record/deprecated_associations.rb +0 -70
- data/lib/active_record/observer.rb +0 -71
- data/lib/active_record/support/class_attribute_accessors.rb +0 -43
- data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
- data/lib/active_record/support/clean_logger.rb +0 -10
- data/lib/active_record/support/inflector.rb +0 -70
- data/lib/active_record/vendor/mysql.rb +0 -1117
- data/lib/active_record/vendor/simple.rb +0 -702
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -59
- data/rakefile +0 -122
- data/test/abstract_unit.rb +0 -16
- data/test/aggregations_test.rb +0 -34
- data/test/all.sh +0 -8
- data/test/associations_test.rb +0 -477
- data/test/base_test.rb +0 -513
- data/test/class_inheritable_attributes_test.rb +0 -33
- data/test/connections/native_mysql/connection.rb +0 -24
- data/test/connections/native_postgresql/connection.rb +0 -24
- data/test/connections/native_sqlite/connection.rb +0 -24
- data/test/deprecated_associations_test.rb +0 -336
- data/test/finder_test.rb +0 -67
- data/test/fixtures/accounts/signals37 +0 -3
- data/test/fixtures/accounts/unknown +0 -2
- data/test/fixtures/auto_id.rb +0 -4
- data/test/fixtures/column_name.rb +0 -3
- data/test/fixtures/companies/first_client +0 -6
- data/test/fixtures/companies/first_firm +0 -4
- data/test/fixtures/companies/second_client +0 -6
- data/test/fixtures/company.rb +0 -37
- data/test/fixtures/company_in_module.rb +0 -33
- data/test/fixtures/course.rb +0 -3
- data/test/fixtures/courses/java +0 -2
- data/test/fixtures/courses/ruby +0 -2
- data/test/fixtures/customer.rb +0 -30
- data/test/fixtures/customers/david +0 -6
- data/test/fixtures/db_definitions/mysql.sql +0 -96
- data/test/fixtures/db_definitions/mysql2.sql +0 -4
- data/test/fixtures/db_definitions/postgresql.sql +0 -113
- data/test/fixtures/db_definitions/postgresql2.sql +0 -4
- data/test/fixtures/db_definitions/sqlite.sql +0 -85
- data/test/fixtures/db_definitions/sqlite2.sql +0 -4
- data/test/fixtures/default.rb +0 -2
- data/test/fixtures/developer.rb +0 -8
- data/test/fixtures/developers/david +0 -2
- data/test/fixtures/developers/jamis +0 -2
- data/test/fixtures/developers_projects/david_action_controller +0 -2
- data/test/fixtures/developers_projects/david_active_record +0 -2
- data/test/fixtures/developers_projects/jamis_active_record +0 -2
- data/test/fixtures/entrant.rb +0 -3
- data/test/fixtures/entrants/first +0 -3
- data/test/fixtures/entrants/second +0 -3
- data/test/fixtures/entrants/third +0 -3
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/movie.rb +0 -5
- data/test/fixtures/movies/first +0 -2
- data/test/fixtures/movies/second +0 -2
- data/test/fixtures/project.rb +0 -3
- data/test/fixtures/projects/action_controller +0 -2
- data/test/fixtures/projects/active_record +0 -2
- data/test/fixtures/reply.rb +0 -21
- data/test/fixtures/subscriber.rb +0 -5
- data/test/fixtures/subscribers/first +0 -2
- data/test/fixtures/subscribers/second +0 -2
- data/test/fixtures/topic.rb +0 -20
- data/test/fixtures/topics/first +0 -9
- data/test/fixtures/topics/second +0 -8
- data/test/fixtures_test.rb +0 -20
- data/test/inflector_test.rb +0 -104
- data/test/inheritance_test.rb +0 -125
- data/test/lifecycle_test.rb +0 -110
- data/test/modules_test.rb +0 -21
- data/test/multiple_db_test.rb +0 -46
- data/test/pk_test.rb +0 -57
- data/test/reflection_test.rb +0 -78
- data/test/thread_safety_test.rb +0 -33
- data/test/transactions_test.rb +0 -83
- data/test/unconnected_test.rb +0 -24
- data/test/validations_test.rb +0 -126
@@ -0,0 +1,390 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters # :nodoc:
|
3
|
+
module DatabaseStatements
|
4
|
+
def initialize
|
5
|
+
super
|
6
|
+
reset_transaction
|
7
|
+
end
|
8
|
+
|
9
|
+
# Converts an arel AST to SQL
|
10
|
+
def to_sql(arel, binds = [])
|
11
|
+
if arel.respond_to?(:ast)
|
12
|
+
binds = binds.dup
|
13
|
+
visitor.accept(arel.ast) do
|
14
|
+
quote(*binds.shift.reverse)
|
15
|
+
end
|
16
|
+
else
|
17
|
+
arel
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns an array of record hashes with the column names as keys and
|
22
|
+
# column values as values.
|
23
|
+
def select_all(arel, name = nil, binds = [])
|
24
|
+
select(to_sql(arel, binds), name, binds)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns a record hash with the column names as keys and column values
|
28
|
+
# as values.
|
29
|
+
def select_one(arel, name = nil, binds = [])
|
30
|
+
result = select_all(arel, name, binds)
|
31
|
+
result.first if result
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns a single value from a record
|
35
|
+
def select_value(arel, name = nil, binds = [])
|
36
|
+
if result = select_one(arel, name, binds)
|
37
|
+
result.values.first
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns an array of the values of the first column in a select:
|
42
|
+
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
43
|
+
def select_values(arel, name = nil)
|
44
|
+
result = select_rows(to_sql(arel, []), name)
|
45
|
+
result.map { |v| v[0] }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns an array of arrays containing the field values.
|
49
|
+
# Order is the same as that returned by +columns+.
|
50
|
+
def select_rows(sql, name = nil)
|
51
|
+
end
|
52
|
+
undef_method :select_rows
|
53
|
+
|
54
|
+
# Executes the SQL statement in the context of this connection.
|
55
|
+
def execute(sql, name = nil)
|
56
|
+
end
|
57
|
+
undef_method :execute
|
58
|
+
|
59
|
+
# Executes +sql+ statement in the context of this connection using
|
60
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
61
|
+
# the executed +sql+ statement.
|
62
|
+
def exec_query(sql, name = 'SQL', binds = [])
|
63
|
+
end
|
64
|
+
|
65
|
+
# Executes insert +sql+ statement in the context of this connection using
|
66
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
67
|
+
# the executed +sql+ statement.
|
68
|
+
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
|
69
|
+
exec_query(sql, name, binds)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Executes delete +sql+ statement in the context of this connection using
|
73
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
74
|
+
# the executed +sql+ statement.
|
75
|
+
def exec_delete(sql, name, binds)
|
76
|
+
exec_query(sql, name, binds)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Executes update +sql+ statement in the context of this connection using
|
80
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
81
|
+
# the executed +sql+ statement.
|
82
|
+
def exec_update(sql, name, binds)
|
83
|
+
exec_query(sql, name, binds)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the last auto-generated ID from the affected table.
|
87
|
+
#
|
88
|
+
# +id_value+ will be returned unless the value is nil, in
|
89
|
+
# which case the database will attempt to calculate the last inserted
|
90
|
+
# id and return that value.
|
91
|
+
#
|
92
|
+
# If the next id was calculated in advance (as in Oracle), it should be
|
93
|
+
# passed in as +id_value+.
|
94
|
+
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
|
95
|
+
sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
|
96
|
+
value = exec_insert(sql, name, binds, pk, sequence_name)
|
97
|
+
id_value || last_inserted_id(value)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Executes the update statement and returns the number of rows affected.
|
101
|
+
def update(arel, name = nil, binds = [])
|
102
|
+
exec_update(to_sql(arel, binds), name, binds)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Executes the delete statement and returns the number of rows affected.
|
106
|
+
def delete(arel, name = nil, binds = [])
|
107
|
+
exec_delete(to_sql(arel, binds), name, binds)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns +true+ when the connection adapter supports prepared statement
|
111
|
+
# caching, otherwise returns +false+
|
112
|
+
def supports_statement_cache?
|
113
|
+
false
|
114
|
+
end
|
115
|
+
|
116
|
+
# Runs the given block in a database transaction, and returns the result
|
117
|
+
# of the block.
|
118
|
+
#
|
119
|
+
# == Nested transactions support
|
120
|
+
#
|
121
|
+
# Most databases don't support true nested transactions. At the time of
|
122
|
+
# writing, the only database that supports true nested transactions that
|
123
|
+
# we're aware of, is MS-SQL.
|
124
|
+
#
|
125
|
+
# In order to get around this problem, #transaction will emulate the effect
|
126
|
+
# of nested transactions, by using savepoints:
|
127
|
+
# http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
|
128
|
+
# Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8'
|
129
|
+
# supports savepoints.
|
130
|
+
#
|
131
|
+
# It is safe to call this method if a database transaction is already open,
|
132
|
+
# i.e. if #transaction is called within another #transaction block. In case
|
133
|
+
# of a nested call, #transaction will behave as follows:
|
134
|
+
#
|
135
|
+
# - The block will be run without doing anything. All database statements
|
136
|
+
# that happen within the block are effectively appended to the already
|
137
|
+
# open database transaction.
|
138
|
+
# - However, if +:requires_new+ is set, the block will be wrapped in a
|
139
|
+
# database savepoint acting as a sub-transaction.
|
140
|
+
#
|
141
|
+
# === Caveats
|
142
|
+
#
|
143
|
+
# MySQL doesn't support DDL transactions. If you perform a DDL operation,
|
144
|
+
# then any created savepoints will be automatically released. For example,
|
145
|
+
# if you've created a savepoint, then you execute a CREATE TABLE statement,
|
146
|
+
# then the savepoint that was created will be automatically released.
|
147
|
+
#
|
148
|
+
# This means that, on MySQL, you shouldn't execute DDL operations inside
|
149
|
+
# a #transaction call that you know might create a savepoint. Otherwise,
|
150
|
+
# #transaction will raise exceptions when it tries to release the
|
151
|
+
# already-automatically-released savepoints:
|
152
|
+
#
|
153
|
+
# Model.connection.transaction do # BEGIN
|
154
|
+
# Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
|
155
|
+
# Model.connection.create_table(...)
|
156
|
+
# # active_record_1 now automatically released
|
157
|
+
# end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# == Transaction isolation
|
161
|
+
#
|
162
|
+
# If your database supports setting the isolation level for a transaction, you can set
|
163
|
+
# it like so:
|
164
|
+
#
|
165
|
+
# Post.transaction(isolation: :serializable) do
|
166
|
+
# # ...
|
167
|
+
# end
|
168
|
+
#
|
169
|
+
# Valid isolation levels are:
|
170
|
+
#
|
171
|
+
# * <tt>:read_uncommitted</tt>
|
172
|
+
# * <tt>:read_committed</tt>
|
173
|
+
# * <tt>:repeatable_read</tt>
|
174
|
+
# * <tt>:serializable</tt>
|
175
|
+
#
|
176
|
+
# You should consult the documentation for your database to understand the
|
177
|
+
# semantics of these different levels:
|
178
|
+
#
|
179
|
+
# * http://www.postgresql.org/docs/9.1/static/transaction-iso.html
|
180
|
+
# * https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
|
181
|
+
#
|
182
|
+
# An <tt>ActiveRecord::TransactionIsolationError</tt> will be raised if:
|
183
|
+
#
|
184
|
+
# * The adapter does not support setting the isolation level
|
185
|
+
# * You are joining an existing open transaction
|
186
|
+
# * You are creating a nested (savepoint) transaction
|
187
|
+
#
|
188
|
+
# The mysql, mysql2 and postgresql adapters support setting the transaction
|
189
|
+
# isolation level. However, support is disabled for mysql versions below 5,
|
190
|
+
# because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
|
191
|
+
# which means the isolation level gets persisted outside the transaction.
|
192
|
+
def transaction(options = {})
|
193
|
+
options.assert_valid_keys :requires_new, :joinable, :isolation
|
194
|
+
|
195
|
+
if !options[:requires_new] && current_transaction.joinable?
|
196
|
+
if options[:isolation]
|
197
|
+
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
|
198
|
+
end
|
199
|
+
|
200
|
+
yield
|
201
|
+
else
|
202
|
+
within_new_transaction(options) { yield }
|
203
|
+
end
|
204
|
+
rescue ActiveRecord::Rollback
|
205
|
+
# rollbacks are silently swallowed
|
206
|
+
end
|
207
|
+
|
208
|
+
def within_new_transaction(options = {}) #:nodoc:
|
209
|
+
transaction = begin_transaction(options)
|
210
|
+
yield
|
211
|
+
rescue Exception => error
|
212
|
+
rollback_transaction if transaction
|
213
|
+
raise
|
214
|
+
ensure
|
215
|
+
begin
|
216
|
+
commit_transaction unless error
|
217
|
+
rescue Exception
|
218
|
+
rollback_transaction
|
219
|
+
raise
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def current_transaction #:nodoc:
|
224
|
+
@transaction
|
225
|
+
end
|
226
|
+
|
227
|
+
def transaction_open?
|
228
|
+
@transaction.open?
|
229
|
+
end
|
230
|
+
|
231
|
+
def begin_transaction(options = {}) #:nodoc:
|
232
|
+
@transaction = @transaction.begin(options)
|
233
|
+
end
|
234
|
+
|
235
|
+
def commit_transaction #:nodoc:
|
236
|
+
@transaction = @transaction.commit
|
237
|
+
end
|
238
|
+
|
239
|
+
def rollback_transaction #:nodoc:
|
240
|
+
@transaction = @transaction.rollback
|
241
|
+
end
|
242
|
+
|
243
|
+
def reset_transaction #:nodoc:
|
244
|
+
@transaction = ClosedTransaction.new(self)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Register a record with the current transaction so that its after_commit and after_rollback callbacks
|
248
|
+
# can be called.
|
249
|
+
def add_transaction_record(record)
|
250
|
+
@transaction.add_record(record)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Begins the transaction (and turns off auto-committing).
|
254
|
+
def begin_db_transaction() end
|
255
|
+
|
256
|
+
def transaction_isolation_levels
|
257
|
+
{
|
258
|
+
read_uncommitted: "READ UNCOMMITTED",
|
259
|
+
read_committed: "READ COMMITTED",
|
260
|
+
repeatable_read: "REPEATABLE READ",
|
261
|
+
serializable: "SERIALIZABLE"
|
262
|
+
}
|
263
|
+
end
|
264
|
+
|
265
|
+
# Begins the transaction with the isolation level set. Raises an error by
|
266
|
+
# default; adapters that support setting the isolation level should implement
|
267
|
+
# this method.
|
268
|
+
def begin_isolated_db_transaction(isolation)
|
269
|
+
raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
|
270
|
+
end
|
271
|
+
|
272
|
+
# Commits the transaction (and turns on auto-committing).
|
273
|
+
def commit_db_transaction() end
|
274
|
+
|
275
|
+
# Rolls back the transaction (and turns on auto-committing). Must be
|
276
|
+
# done if the transaction block raises an exception or returns false.
|
277
|
+
def rollback_db_transaction() end
|
278
|
+
|
279
|
+
def default_sequence_name(table, column)
|
280
|
+
nil
|
281
|
+
end
|
282
|
+
|
283
|
+
# Set the sequence to the max value of the table's column.
|
284
|
+
def reset_sequence!(table, column, sequence = nil)
|
285
|
+
# Do nothing by default. Implement for PostgreSQL, Oracle, ...
|
286
|
+
end
|
287
|
+
|
288
|
+
# Inserts the given fixture into the table. Overridden in adapters that require
|
289
|
+
# something beyond a simple insert (eg. Oracle).
|
290
|
+
def insert_fixture(fixture, table_name)
|
291
|
+
columns = schema_cache.columns_hash(table_name)
|
292
|
+
|
293
|
+
key_list = []
|
294
|
+
value_list = fixture.map do |name, value|
|
295
|
+
key_list << quote_column_name(name)
|
296
|
+
quote(value, columns[name])
|
297
|
+
end
|
298
|
+
|
299
|
+
execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
|
300
|
+
end
|
301
|
+
|
302
|
+
def empty_insert_statement_value
|
303
|
+
"DEFAULT VALUES"
|
304
|
+
end
|
305
|
+
|
306
|
+
def case_sensitive_equality_operator
|
307
|
+
"="
|
308
|
+
end
|
309
|
+
|
310
|
+
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
|
311
|
+
"WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
|
312
|
+
end
|
313
|
+
|
314
|
+
# Sanitizes the given LIMIT parameter in order to prevent SQL injection.
|
315
|
+
#
|
316
|
+
# The +limit+ may be anything that can evaluate to a string via #to_s. It
|
317
|
+
# should look like an integer, or a comma-delimited list of integers, or
|
318
|
+
# an Arel SQL literal.
|
319
|
+
#
|
320
|
+
# Returns Integer and Arel::Nodes::SqlLiteral limits as is.
|
321
|
+
# Returns the sanitized limit parameter, either as an integer, or as a
|
322
|
+
# string which contains a comma-delimited list of integers.
|
323
|
+
def sanitize_limit(limit)
|
324
|
+
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
|
325
|
+
limit
|
326
|
+
elsif limit.to_s =~ /,/
|
327
|
+
Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
|
328
|
+
else
|
329
|
+
Integer(limit)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
|
334
|
+
# on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
|
335
|
+
# an UPDATE statement, so in the mysql adapters we redefine this to do that.
|
336
|
+
def join_to_update(update, select) #:nodoc:
|
337
|
+
key = update.key
|
338
|
+
subselect = subquery_for(key, select)
|
339
|
+
|
340
|
+
update.where key.in(subselect)
|
341
|
+
end
|
342
|
+
|
343
|
+
def join_to_delete(delete, select, key) #:nodoc:
|
344
|
+
subselect = subquery_for(key, select)
|
345
|
+
|
346
|
+
delete.where key.in(subselect)
|
347
|
+
end
|
348
|
+
|
349
|
+
protected
|
350
|
+
|
351
|
+
# Return a subquery for the given key using the join information.
|
352
|
+
def subquery_for(key, select)
|
353
|
+
subselect = select.clone
|
354
|
+
subselect.projections = [key]
|
355
|
+
subselect
|
356
|
+
end
|
357
|
+
|
358
|
+
# Returns an array of record hashes with the column names as keys and
|
359
|
+
# column values as values.
|
360
|
+
def select(sql, name = nil, binds = [])
|
361
|
+
end
|
362
|
+
undef_method :select
|
363
|
+
|
364
|
+
# Returns the last auto-generated ID from the affected table.
|
365
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
366
|
+
execute(sql, name)
|
367
|
+
id_value
|
368
|
+
end
|
369
|
+
|
370
|
+
# Executes the update statement and returns the number of rows affected.
|
371
|
+
def update_sql(sql, name = nil)
|
372
|
+
execute(sql, name)
|
373
|
+
end
|
374
|
+
|
375
|
+
# Executes the delete statement and returns the number of rows affected.
|
376
|
+
def delete_sql(sql, name = nil)
|
377
|
+
update_sql(sql, name)
|
378
|
+
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
|
+
end
|
389
|
+
end
|
390
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters # :nodoc:
|
3
|
+
module QueryCache
|
4
|
+
class << self
|
5
|
+
def included(base) #:nodoc:
|
6
|
+
dirties_query_cache base, :insert, :update, :delete
|
7
|
+
end
|
8
|
+
|
9
|
+
def dirties_query_cache(base, *method_names)
|
10
|
+
method_names.each do |method_name|
|
11
|
+
base.class_eval <<-end_code, __FILE__, __LINE__ + 1
|
12
|
+
def #{method_name}(*) # def update_with_query_dirty(*)
|
13
|
+
clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
|
14
|
+
super # super
|
15
|
+
end # end
|
16
|
+
end_code
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :query_cache, :query_cache_enabled
|
22
|
+
|
23
|
+
# Enable the query cache within the block.
|
24
|
+
def cache
|
25
|
+
old, @query_cache_enabled = @query_cache_enabled, true
|
26
|
+
yield
|
27
|
+
ensure
|
28
|
+
clear_query_cache
|
29
|
+
@query_cache_enabled = old
|
30
|
+
end
|
31
|
+
|
32
|
+
def enable_query_cache!
|
33
|
+
@query_cache_enabled = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def disable_query_cache!
|
37
|
+
@query_cache_enabled = false
|
38
|
+
end
|
39
|
+
|
40
|
+
# Disable the query cache within the block.
|
41
|
+
def uncached
|
42
|
+
old, @query_cache_enabled = @query_cache_enabled, false
|
43
|
+
yield
|
44
|
+
ensure
|
45
|
+
@query_cache_enabled = old
|
46
|
+
end
|
47
|
+
|
48
|
+
# Clears the query cache.
|
49
|
+
#
|
50
|
+
# One reason you may wish to call this method explicitly is between queries
|
51
|
+
# that ask the database to randomize results. Otherwise the cache would see
|
52
|
+
# the same SQL query and repeatedly return the same result each time, silently
|
53
|
+
# undermining the randomness you were expecting.
|
54
|
+
def clear_query_cache
|
55
|
+
@query_cache.clear
|
56
|
+
end
|
57
|
+
|
58
|
+
def select_all(arel, name = nil, binds = [])
|
59
|
+
if @query_cache_enabled && !locked?(arel)
|
60
|
+
sql = to_sql(arel, binds)
|
61
|
+
cache_sql(sql, binds) { super(sql, name, binds) }
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def cache_sql(sql, binds)
|
70
|
+
result =
|
71
|
+
if @query_cache[sql].key?(binds)
|
72
|
+
ActiveSupport::Notifications.instrument("sql.active_record",
|
73
|
+
:sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
|
74
|
+
@query_cache[sql][binds]
|
75
|
+
else
|
76
|
+
@query_cache[sql][binds] = yield
|
77
|
+
end
|
78
|
+
|
79
|
+
# FIXME: we should guarantee that all cached items are Result
|
80
|
+
# objects. Then we can avoid this conditional
|
81
|
+
if ActiveRecord::Result === result
|
82
|
+
result.dup
|
83
|
+
else
|
84
|
+
result.collect { |row| row.dup }
|
85
|
+
end
|
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
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'active_support/core_ext/big_decimal/conversions'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters # :nodoc:
|
5
|
+
module Quoting
|
6
|
+
# Quotes the column value to help prevent
|
7
|
+
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
|
8
|
+
def quote(value, column = nil)
|
9
|
+
# records are quoted as their primary key
|
10
|
+
return value.quoted_id if value.respond_to?(:quoted_id)
|
11
|
+
|
12
|
+
case value
|
13
|
+
when String, ActiveSupport::Multibyte::Chars
|
14
|
+
value = value.to_s
|
15
|
+
return "'#{quote_string(value)}'" unless column
|
16
|
+
|
17
|
+
case column.type
|
18
|
+
when :binary then "'#{quote_string(column.string_to_binary(value))}'"
|
19
|
+
when :integer then value.to_i.to_s
|
20
|
+
when :float then value.to_f.to_s
|
21
|
+
else
|
22
|
+
"'#{quote_string(value)}'"
|
23
|
+
end
|
24
|
+
|
25
|
+
when true, false
|
26
|
+
if column && column.type == :integer
|
27
|
+
value ? '1' : '0'
|
28
|
+
else
|
29
|
+
value ? quoted_true : quoted_false
|
30
|
+
end
|
31
|
+
# BigDecimals need to be put in a non-normalized form and quoted.
|
32
|
+
when nil then "NULL"
|
33
|
+
when BigDecimal then value.to_s('F')
|
34
|
+
when Numeric, ActiveSupport::Duration then value.to_s
|
35
|
+
when Date, Time then "'#{quoted_date(value)}'"
|
36
|
+
when Symbol then "'#{quote_string(value.to_s)}'"
|
37
|
+
when Class then "'#{value.to_s}'"
|
38
|
+
else
|
39
|
+
"'#{quote_string(YAML.dump(value))}'"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Cast a +value+ to a type that the database understands. For example,
|
44
|
+
# SQLite does not understand dates, so this method will convert a Date
|
45
|
+
# to a String.
|
46
|
+
def type_cast(value, column)
|
47
|
+
return value.id if value.respond_to?(:quoted_id)
|
48
|
+
|
49
|
+
case value
|
50
|
+
when String, ActiveSupport::Multibyte::Chars
|
51
|
+
value = value.to_s
|
52
|
+
return value unless column
|
53
|
+
|
54
|
+
case column.type
|
55
|
+
when :binary then value
|
56
|
+
when :integer then value.to_i
|
57
|
+
when :float then value.to_f
|
58
|
+
else
|
59
|
+
value
|
60
|
+
end
|
61
|
+
|
62
|
+
when true, false
|
63
|
+
if column && column.type == :integer
|
64
|
+
value ? 1 : 0
|
65
|
+
else
|
66
|
+
value ? 't' : 'f'
|
67
|
+
end
|
68
|
+
# BigDecimals need to be put in a non-normalized form and quoted.
|
69
|
+
when nil then nil
|
70
|
+
when BigDecimal then value.to_s('F')
|
71
|
+
when Numeric then value
|
72
|
+
when Date, Time then quoted_date(value)
|
73
|
+
when Symbol then value.to_s
|
74
|
+
else
|
75
|
+
to_type = column ? " to #{column.type}" : ""
|
76
|
+
raise TypeError, "can't cast #{value.class}#{to_type}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Quotes a string, escaping any ' (single quote) and \ (backslash)
|
81
|
+
# characters.
|
82
|
+
def quote_string(s)
|
83
|
+
s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Quotes the column name. Defaults to no quoting.
|
87
|
+
def quote_column_name(column_name)
|
88
|
+
column_name
|
89
|
+
end
|
90
|
+
|
91
|
+
# Quotes the table name. Defaults to column name quoting.
|
92
|
+
def quote_table_name(table_name)
|
93
|
+
quote_column_name(table_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Override to return the quoted table name for assignment. Defaults to
|
97
|
+
# table quoting.
|
98
|
+
#
|
99
|
+
# This works for mysql and mysql2 where table.column can be used to
|
100
|
+
# resolve ambiguity.
|
101
|
+
#
|
102
|
+
# We override this in the sqlite and postgresql adapters to use only
|
103
|
+
# the column name (as per syntax requirements).
|
104
|
+
def quote_table_name_for_assignment(table, attr)
|
105
|
+
quote_table_name("#{table}.#{attr}")
|
106
|
+
end
|
107
|
+
|
108
|
+
def quoted_true
|
109
|
+
"'t'"
|
110
|
+
end
|
111
|
+
|
112
|
+
def quoted_false
|
113
|
+
"'f'"
|
114
|
+
end
|
115
|
+
|
116
|
+
def quoted_date(value)
|
117
|
+
if value.acts_like?(:time)
|
118
|
+
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
|
119
|
+
|
120
|
+
if value.respond_to?(zone_conversion_method)
|
121
|
+
value = value.send(zone_conversion_method)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
value.to_s(:db)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|