sskirby-activerecord 3.2.1
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.
- data/CHANGELOG.md +6749 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +222 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +177 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +147 -0
- data/lib/active_record/aggregations.rb +255 -0
- data/lib/active_record/associations.rb +1604 -0
- data/lib/active_record/associations/alias_tracker.rb +79 -0
- data/lib/active_record/associations/association.rb +239 -0
- data/lib/active_record/associations/association_scope.rb +119 -0
- data/lib/active_record/associations/belongs_to_association.rb +79 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
- data/lib/active_record/associations/builder/association.rb +55 -0
- data/lib/active_record/associations/builder/belongs_to.rb +85 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
- data/lib/active_record/associations/builder/has_many.rb +71 -0
- data/lib/active_record/associations/builder/has_one.rb +62 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +574 -0
- data/lib/active_record/associations/collection_proxy.rb +132 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
- data/lib/active_record/associations/has_many_association.rb +108 -0
- data/lib/active_record/associations/has_many_through_association.rb +180 -0
- data/lib/active_record/associations/has_one_association.rb +73 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency.rb +214 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +154 -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_helper.rb +55 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +127 -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 +15 -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 +67 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +83 -0
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods.rb +272 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +101 -0
- data/lib/active_record/attribute_methods/primary_key.rb +114 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +135 -0
- data/lib/active_record/attribute_methods/serialization.rb +93 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
- data/lib/active_record/attribute_methods/write.rb +69 -0
- data/lib/active_record/autosave_association.rb +422 -0
- data/lib/active_record/base.rb +716 -0
- data/lib/active_record/callbacks.rb +275 -0
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/column.rb +270 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/counter_cache.rb +119 -0
- data/lib/active_record/dynamic_finder_match.rb +56 -0
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/dynamic_scope_match.rb +23 -0
- data/lib/active_record/errors.rb +195 -0
- data/lib/active_record/explain.rb +85 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures.rb +906 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/identity_map.rb +156 -0
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +183 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +68 -0
- data/lib/active_record/migration.rb +765 -0
- data/lib/active_record/migration/command_recorder.rb +105 -0
- data/lib/active_record/model_schema.rb +366 -0
- data/lib/active_record/nested_attributes.rb +469 -0
- data/lib/active_record/observer.rb +121 -0
- data/lib/active_record/persistence.rb +372 -0
- data/lib/active_record/query_cache.rb +74 -0
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +119 -0
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/controller_runtime.rb +49 -0
- data/lib/active_record/railties/databases.rake +620 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +534 -0
- data/lib/active_record/relation.rb +534 -0
- data/lib/active_record/relation/batches.rb +90 -0
- data/lib/active_record/relation/calculations.rb +354 -0
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +398 -0
- data/lib/active_record/relation/predicate_builder.rb +58 -0
- data/lib/active_record/relation/query_methods.rb +417 -0
- data/lib/active_record/relation/spawn_methods.rb +148 -0
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +204 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/serialization.rb +18 -0
- data/lib/active_record/serializers/xml_serializer.rb +202 -0
- data/lib/active_record/session_store.rb +358 -0
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +73 -0
- data/lib/active_record/timestamp.rb +113 -0
- data/lib/active_record/transactions.rb +360 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations.rb +83 -0
- data/lib/active_record/validations/associated.rb +43 -0
- data/lib/active_record/validations/uniqueness.rb +180 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +25 -0
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
- metadata +242 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
class Base
|
|
3
|
+
class ConnectionSpecification #:nodoc:
|
|
4
|
+
attr_reader :config, :adapter_method
|
|
5
|
+
def initialize (config, adapter_method)
|
|
6
|
+
@config, @adapter_method = config, adapter_method
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Builds a ConnectionSpecification from user input
|
|
11
|
+
class Resolver # :nodoc:
|
|
12
|
+
attr_reader :config, :klass, :configurations
|
|
13
|
+
|
|
14
|
+
def initialize(config, configurations)
|
|
15
|
+
@config = config
|
|
16
|
+
@configurations = configurations
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def spec
|
|
20
|
+
case config
|
|
21
|
+
when nil
|
|
22
|
+
raise AdapterNotSpecified unless defined?(Rails.env)
|
|
23
|
+
resolve_string_connection Rails.env
|
|
24
|
+
when Symbol, String
|
|
25
|
+
resolve_string_connection config.to_s
|
|
26
|
+
when Hash
|
|
27
|
+
resolve_hash_connection config
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
def resolve_string_connection(spec) # :nodoc:
|
|
33
|
+
hash = configurations.fetch(spec) do |k|
|
|
34
|
+
connection_url_to_hash(k)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
|
|
38
|
+
|
|
39
|
+
resolve_hash_connection hash
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def resolve_hash_connection(spec) # :nodoc:
|
|
43
|
+
spec = spec.symbolize_keys
|
|
44
|
+
|
|
45
|
+
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
|
|
46
|
+
|
|
47
|
+
begin
|
|
48
|
+
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
|
|
49
|
+
rescue LoadError => e
|
|
50
|
+
raise LoadError, "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e.message})", e.backtrace
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
adapter_method = "#{spec[:adapter]}_connection"
|
|
54
|
+
|
|
55
|
+
ConnectionSpecification.new(spec, adapter_method)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def connection_url_to_hash(url) # :nodoc:
|
|
59
|
+
config = URI.parse url
|
|
60
|
+
adapter = config.scheme
|
|
61
|
+
adapter = "postgresql" if adapter == "postgres"
|
|
62
|
+
spec = { :adapter => adapter,
|
|
63
|
+
:username => config.user,
|
|
64
|
+
:password => config.password,
|
|
65
|
+
:port => config.port,
|
|
66
|
+
:database => config.path.sub(%r{^/},""),
|
|
67
|
+
:host => config.host }
|
|
68
|
+
spec.reject!{ |_,value| !value }
|
|
69
|
+
if config.query
|
|
70
|
+
options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
|
|
71
|
+
spec.merge!(options)
|
|
72
|
+
end
|
|
73
|
+
spec
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# :singleton-method:
|
|
80
|
+
# The connection handler
|
|
81
|
+
class_attribute :connection_handler, :instance_writer => false
|
|
82
|
+
self.connection_handler = ConnectionAdapters::ConnectionHandler.new
|
|
83
|
+
|
|
84
|
+
# Returns the connection currently associated with the class. This can
|
|
85
|
+
# also be used to "borrow" the connection to do database work that isn't
|
|
86
|
+
# easily done without going straight to SQL.
|
|
87
|
+
def connection
|
|
88
|
+
self.class.connection
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Establishes the connection to the database. Accepts a hash as input where
|
|
92
|
+
# the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
|
|
93
|
+
# example for regular databases (MySQL, Postgresql, etc):
|
|
94
|
+
#
|
|
95
|
+
# ActiveRecord::Base.establish_connection(
|
|
96
|
+
# :adapter => "mysql",
|
|
97
|
+
# :host => "localhost",
|
|
98
|
+
# :username => "myuser",
|
|
99
|
+
# :password => "mypass",
|
|
100
|
+
# :database => "somedatabase"
|
|
101
|
+
# )
|
|
102
|
+
#
|
|
103
|
+
# Example for SQLite database:
|
|
104
|
+
#
|
|
105
|
+
# ActiveRecord::Base.establish_connection(
|
|
106
|
+
# :adapter => "sqlite",
|
|
107
|
+
# :database => "path/to/dbfile"
|
|
108
|
+
# )
|
|
109
|
+
#
|
|
110
|
+
# Also accepts keys as strings (for parsing from YAML for example):
|
|
111
|
+
#
|
|
112
|
+
# ActiveRecord::Base.establish_connection(
|
|
113
|
+
# "adapter" => "sqlite",
|
|
114
|
+
# "database" => "path/to/dbfile"
|
|
115
|
+
# )
|
|
116
|
+
#
|
|
117
|
+
# Or a URL:
|
|
118
|
+
#
|
|
119
|
+
# ActiveRecord::Base.establish_connection(
|
|
120
|
+
# "postgres://myuser:mypass@localhost/somedatabase"
|
|
121
|
+
# )
|
|
122
|
+
#
|
|
123
|
+
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
|
|
124
|
+
# may be returned on an error.
|
|
125
|
+
def self.establish_connection(spec = ENV["DATABASE_URL"])
|
|
126
|
+
resolver = ConnectionSpecification::Resolver.new spec, configurations
|
|
127
|
+
spec = resolver.spec
|
|
128
|
+
|
|
129
|
+
unless respond_to?(spec.adapter_method)
|
|
130
|
+
raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
remove_connection
|
|
134
|
+
connection_handler.establish_connection name, spec
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class << self
|
|
138
|
+
# Returns the connection currently associated with the class. This can
|
|
139
|
+
# also be used to "borrow" the connection to do database work unrelated
|
|
140
|
+
# to any of the specific Active Records.
|
|
141
|
+
def connection
|
|
142
|
+
retrieve_connection
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def connection_id
|
|
146
|
+
Thread.current['ActiveRecord::Base.connection_id']
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def connection_id=(connection_id)
|
|
150
|
+
Thread.current['ActiveRecord::Base.connection_id'] = connection_id
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Returns the configuration of the associated connection as a hash:
|
|
154
|
+
#
|
|
155
|
+
# ActiveRecord::Base.connection_config
|
|
156
|
+
# # => {:pool=>5, :timeout=>5000, :database=>"db/development.sqlite3", :adapter=>"sqlite3"}
|
|
157
|
+
#
|
|
158
|
+
# Please use only for reading.
|
|
159
|
+
def connection_config
|
|
160
|
+
connection_pool.spec.config
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def connection_pool
|
|
164
|
+
connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def retrieve_connection
|
|
168
|
+
connection_handler.retrieve_connection(self)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Returns true if Active Record is connected.
|
|
172
|
+
def connected?
|
|
173
|
+
connection_handler.connected?(self)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def remove_connection(klass = self)
|
|
177
|
+
connection_handler.remove_connection(klass)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def clear_active_connections!
|
|
181
|
+
connection_handler.clear_active_connections!
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
delegate :clear_reloadable_connections!,
|
|
185
|
+
:clear_all_connections!,:verify_active_connections!, :to => :connection_handler
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module ConnectionAdapters # :nodoc:
|
|
3
|
+
module DatabaseLimits
|
|
4
|
+
|
|
5
|
+
# Returns the maximum length of a table alias.
|
|
6
|
+
def table_alias_length
|
|
7
|
+
255
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Returns the maximum length of a column name.
|
|
11
|
+
def column_name_length
|
|
12
|
+
64
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Returns the maximum length of a table name.
|
|
16
|
+
def table_name_length
|
|
17
|
+
64
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns the maximum length of an index name.
|
|
21
|
+
def index_name_length
|
|
22
|
+
64
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Returns the maximum number of columns per table.
|
|
26
|
+
def columns_per_table
|
|
27
|
+
1024
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the maximum number of indexes per table.
|
|
31
|
+
def indexes_per_table
|
|
32
|
+
16
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns the maximum number of columns in a multicolumn index.
|
|
36
|
+
def columns_per_multicolumn_index
|
|
37
|
+
16
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the maximum number of elements in an IN (x,y,z) clause.
|
|
41
|
+
# nil means no limit.
|
|
42
|
+
def in_clause_length
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns the maximum length of an SQL query.
|
|
47
|
+
def sql_query_length
|
|
48
|
+
1048575
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns maximum number of joins in a single query.
|
|
52
|
+
def joins_per_query
|
|
53
|
+
256
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module ConnectionAdapters # :nodoc:
|
|
3
|
+
module DatabaseStatements
|
|
4
|
+
# Converts an arel AST to SQL
|
|
5
|
+
def to_sql(arel)
|
|
6
|
+
if arel.respond_to?(:ast)
|
|
7
|
+
visitor.accept(arel.ast)
|
|
8
|
+
else
|
|
9
|
+
arel
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Returns an array of record hashes with the column names as keys and
|
|
14
|
+
# column values as values.
|
|
15
|
+
def select_all(arel, name = nil, binds = [])
|
|
16
|
+
select(to_sql(arel), name, binds)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns a record hash with the column names as keys and column values
|
|
20
|
+
# as values.
|
|
21
|
+
def select_one(arel, name = nil)
|
|
22
|
+
result = select_all(arel, name)
|
|
23
|
+
result.first if result
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns a single value from a record
|
|
27
|
+
def select_value(arel, name = nil)
|
|
28
|
+
if result = select_one(arel, name)
|
|
29
|
+
result.values.first
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns an array of the values of the first column in a select:
|
|
34
|
+
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
|
35
|
+
def select_values(arel, name = nil)
|
|
36
|
+
result = select_rows(to_sql(arel), name)
|
|
37
|
+
result.map { |v| v[0] }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns an array of arrays containing the field values.
|
|
41
|
+
# Order is the same as that returned by +columns+.
|
|
42
|
+
def select_rows(sql, name = nil)
|
|
43
|
+
end
|
|
44
|
+
undef_method :select_rows
|
|
45
|
+
|
|
46
|
+
# Executes the SQL statement in the context of this connection.
|
|
47
|
+
def execute(sql, name = nil)
|
|
48
|
+
end
|
|
49
|
+
undef_method :execute
|
|
50
|
+
|
|
51
|
+
# Executes +sql+ statement in the context of this connection using
|
|
52
|
+
# +binds+ as the bind substitutes. +name+ is logged along with
|
|
53
|
+
# the executed +sql+ statement.
|
|
54
|
+
def exec_query(sql, name = 'SQL', binds = [])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Executes insert +sql+ statement in the context of this connection using
|
|
58
|
+
# +binds+ as the bind substitutes. +name+ is the logged along with
|
|
59
|
+
# the executed +sql+ statement.
|
|
60
|
+
def exec_insert(sql, name, binds)
|
|
61
|
+
exec_query(sql, name, binds)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Executes delete +sql+ statement in the context of this connection using
|
|
65
|
+
# +binds+ as the bind substitutes. +name+ is the logged along with
|
|
66
|
+
# the executed +sql+ statement.
|
|
67
|
+
def exec_delete(sql, name, binds)
|
|
68
|
+
exec_query(sql, name, binds)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Executes update +sql+ statement in the context of this connection using
|
|
72
|
+
# +binds+ as the bind substitutes. +name+ is the logged along with
|
|
73
|
+
# the executed +sql+ statement.
|
|
74
|
+
def exec_update(sql, name, binds)
|
|
75
|
+
exec_query(sql, name, binds)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Returns the last auto-generated ID from the affected table.
|
|
79
|
+
#
|
|
80
|
+
# +id_value+ will be returned unless the value is nil, in
|
|
81
|
+
# which case the database will attempt to calculate the last inserted
|
|
82
|
+
# id and return that value.
|
|
83
|
+
#
|
|
84
|
+
# If the next id was calculated in advance (as in Oracle), it should be
|
|
85
|
+
# passed in as +id_value+.
|
|
86
|
+
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
|
|
87
|
+
sql, binds = sql_for_insert(to_sql(arel), pk, id_value, sequence_name, binds)
|
|
88
|
+
value = exec_insert(sql, name, binds)
|
|
89
|
+
id_value || last_inserted_id(value)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Executes the update statement and returns the number of rows affected.
|
|
93
|
+
def update(arel, name = nil, binds = [])
|
|
94
|
+
exec_update(to_sql(arel), name, binds)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Executes the delete statement and returns the number of rows affected.
|
|
98
|
+
def delete(arel, name = nil, binds = [])
|
|
99
|
+
exec_delete(to_sql(arel), name, binds)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Checks whether there is currently no transaction active. This is done
|
|
103
|
+
# by querying the database driver, and does not use the transaction
|
|
104
|
+
# house-keeping information recorded by #increment_open_transactions and
|
|
105
|
+
# friends.
|
|
106
|
+
#
|
|
107
|
+
# Returns true if there is no transaction active, false if there is a
|
|
108
|
+
# transaction active, and nil if this information is unknown.
|
|
109
|
+
#
|
|
110
|
+
# Not all adapters supports transaction state introspection. Currently,
|
|
111
|
+
# only the PostgreSQL adapter supports this.
|
|
112
|
+
def outside_transaction?
|
|
113
|
+
nil
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Returns +true+ when the connection adapter supports prepared statement
|
|
117
|
+
# caching, otherwise returns +false+
|
|
118
|
+
def supports_statement_cache?
|
|
119
|
+
false
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Runs the given block in a database transaction, and returns the result
|
|
123
|
+
# of the block.
|
|
124
|
+
#
|
|
125
|
+
# == Nested transactions support
|
|
126
|
+
#
|
|
127
|
+
# Most databases don't support true nested transactions. At the time of
|
|
128
|
+
# writing, the only database that supports true nested transactions that
|
|
129
|
+
# we're aware of, is MS-SQL.
|
|
130
|
+
#
|
|
131
|
+
# In order to get around this problem, #transaction will emulate the effect
|
|
132
|
+
# of nested transactions, by using savepoints:
|
|
133
|
+
# http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
|
|
134
|
+
# Savepoints are supported by MySQL and PostgreSQL, but not SQLite3.
|
|
135
|
+
#
|
|
136
|
+
# It is safe to call this method if a database transaction is already open,
|
|
137
|
+
# i.e. if #transaction is called within another #transaction block. In case
|
|
138
|
+
# of a nested call, #transaction will behave as follows:
|
|
139
|
+
#
|
|
140
|
+
# - The block will be run without doing anything. All database statements
|
|
141
|
+
# that happen within the block are effectively appended to the already
|
|
142
|
+
# open database transaction.
|
|
143
|
+
# - However, if +:requires_new+ is set, the block will be wrapped in a
|
|
144
|
+
# database savepoint acting as a sub-transaction.
|
|
145
|
+
#
|
|
146
|
+
# === Caveats
|
|
147
|
+
#
|
|
148
|
+
# MySQL doesn't support DDL transactions. If you perform a DDL operation,
|
|
149
|
+
# then any created savepoints will be automatically released. For example,
|
|
150
|
+
# if you've created a savepoint, then you execute a CREATE TABLE statement,
|
|
151
|
+
# then the savepoint that was created will be automatically released.
|
|
152
|
+
#
|
|
153
|
+
# This means that, on MySQL, you shouldn't execute DDL operations inside
|
|
154
|
+
# a #transaction call that you know might create a savepoint. Otherwise,
|
|
155
|
+
# #transaction will raise exceptions when it tries to release the
|
|
156
|
+
# already-automatically-released savepoints:
|
|
157
|
+
#
|
|
158
|
+
# Model.connection.transaction do # BEGIN
|
|
159
|
+
# Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
|
|
160
|
+
# Model.connection.create_table(...)
|
|
161
|
+
# # active_record_1 now automatically released
|
|
162
|
+
# end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
|
|
163
|
+
# end
|
|
164
|
+
def transaction(options = {})
|
|
165
|
+
options.assert_valid_keys :requires_new, :joinable
|
|
166
|
+
|
|
167
|
+
last_transaction_joinable = defined?(@transaction_joinable) ? @transaction_joinable : nil
|
|
168
|
+
if options.has_key?(:joinable)
|
|
169
|
+
@transaction_joinable = options[:joinable]
|
|
170
|
+
else
|
|
171
|
+
@transaction_joinable = true
|
|
172
|
+
end
|
|
173
|
+
requires_new = options[:requires_new] || !last_transaction_joinable
|
|
174
|
+
|
|
175
|
+
transaction_open = false
|
|
176
|
+
@_current_transaction_records ||= []
|
|
177
|
+
|
|
178
|
+
begin
|
|
179
|
+
if block_given?
|
|
180
|
+
if requires_new || open_transactions == 0
|
|
181
|
+
if open_transactions == 0
|
|
182
|
+
begin_db_transaction
|
|
183
|
+
elsif requires_new
|
|
184
|
+
create_savepoint
|
|
185
|
+
end
|
|
186
|
+
increment_open_transactions
|
|
187
|
+
transaction_open = true
|
|
188
|
+
@_current_transaction_records.push([])
|
|
189
|
+
end
|
|
190
|
+
yield
|
|
191
|
+
end
|
|
192
|
+
rescue Exception => database_transaction_rollback
|
|
193
|
+
if transaction_open && !outside_transaction?
|
|
194
|
+
transaction_open = false
|
|
195
|
+
decrement_open_transactions
|
|
196
|
+
if open_transactions == 0
|
|
197
|
+
rollback_db_transaction
|
|
198
|
+
rollback_transaction_records(true)
|
|
199
|
+
else
|
|
200
|
+
rollback_to_savepoint
|
|
201
|
+
rollback_transaction_records(false)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
|
|
205
|
+
end
|
|
206
|
+
ensure
|
|
207
|
+
@transaction_joinable = last_transaction_joinable
|
|
208
|
+
|
|
209
|
+
if outside_transaction?
|
|
210
|
+
@open_transactions = 0
|
|
211
|
+
elsif transaction_open
|
|
212
|
+
decrement_open_transactions
|
|
213
|
+
begin
|
|
214
|
+
if open_transactions == 0
|
|
215
|
+
commit_db_transaction
|
|
216
|
+
commit_transaction_records
|
|
217
|
+
else
|
|
218
|
+
release_savepoint
|
|
219
|
+
save_point_records = @_current_transaction_records.pop
|
|
220
|
+
unless save_point_records.blank?
|
|
221
|
+
@_current_transaction_records.push([]) if @_current_transaction_records.empty?
|
|
222
|
+
@_current_transaction_records.last.concat(save_point_records)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
rescue Exception => database_transaction_rollback
|
|
226
|
+
if open_transactions == 0
|
|
227
|
+
rollback_db_transaction
|
|
228
|
+
rollback_transaction_records(true)
|
|
229
|
+
else
|
|
230
|
+
rollback_to_savepoint
|
|
231
|
+
rollback_transaction_records(false)
|
|
232
|
+
end
|
|
233
|
+
raise
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Register a record with the current transaction so that its after_commit and after_rollback callbacks
|
|
239
|
+
# can be called.
|
|
240
|
+
def add_transaction_record(record)
|
|
241
|
+
last_batch = @_current_transaction_records.last
|
|
242
|
+
last_batch << record if last_batch
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Begins the transaction (and turns off auto-committing).
|
|
246
|
+
def begin_db_transaction() end
|
|
247
|
+
|
|
248
|
+
# Commits the transaction (and turns on auto-committing).
|
|
249
|
+
def commit_db_transaction() end
|
|
250
|
+
|
|
251
|
+
# Rolls back the transaction (and turns on auto-committing). Must be
|
|
252
|
+
# done if the transaction block raises an exception or returns false.
|
|
253
|
+
def rollback_db_transaction() end
|
|
254
|
+
|
|
255
|
+
def default_sequence_name(table, column)
|
|
256
|
+
nil
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Set the sequence to the max value of the table's column.
|
|
260
|
+
def reset_sequence!(table, column, sequence = nil)
|
|
261
|
+
# Do nothing by default. Implement for PostgreSQL, Oracle, ...
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Inserts the given fixture into the table. Overridden in adapters that require
|
|
265
|
+
# something beyond a simple insert (eg. Oracle).
|
|
266
|
+
def insert_fixture(fixture, table_name)
|
|
267
|
+
columns = Hash[columns(table_name).map { |c| [c.name, c] }]
|
|
268
|
+
|
|
269
|
+
key_list = []
|
|
270
|
+
value_list = fixture.map do |name, value|
|
|
271
|
+
key_list << quote_column_name(name)
|
|
272
|
+
quote(value, columns[name])
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def empty_insert_statement_value
|
|
279
|
+
"VALUES(DEFAULT)"
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def case_sensitive_equality_operator
|
|
283
|
+
"="
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
|
|
287
|
+
"WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})"
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Sanitizes the given LIMIT parameter in order to prevent SQL injection.
|
|
291
|
+
#
|
|
292
|
+
# The +limit+ may be anything that can evaluate to a string via #to_s. It
|
|
293
|
+
# should look like an integer, or a comma-delimited list of integers, or
|
|
294
|
+
# an Arel SQL literal.
|
|
295
|
+
#
|
|
296
|
+
# Returns Integer and Arel::Nodes::SqlLiteral limits as is.
|
|
297
|
+
# Returns the sanitized limit parameter, either as an integer, or as a
|
|
298
|
+
# string which contains a comma-delimited list of integers.
|
|
299
|
+
def sanitize_limit(limit)
|
|
300
|
+
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
|
|
301
|
+
limit
|
|
302
|
+
elsif limit.to_s =~ /,/
|
|
303
|
+
Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
|
|
304
|
+
else
|
|
305
|
+
Integer(limit)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
|
|
310
|
+
# on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
|
|
311
|
+
# an UPDATE statement, so in the mysql adapters we redefine this to do that.
|
|
312
|
+
def join_to_update(update, select) #:nodoc:
|
|
313
|
+
subselect = select.clone
|
|
314
|
+
subselect.projections = [update.key]
|
|
315
|
+
|
|
316
|
+
update.where update.key.in(subselect)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
protected
|
|
320
|
+
# Returns an array of record hashes with the column names as keys and
|
|
321
|
+
# column values as values.
|
|
322
|
+
def select(sql, name = nil, binds = [])
|
|
323
|
+
end
|
|
324
|
+
undef_method :select
|
|
325
|
+
|
|
326
|
+
# Returns the last auto-generated ID from the affected table.
|
|
327
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
|
328
|
+
execute(sql, name)
|
|
329
|
+
id_value
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Executes the update statement and returns the number of rows affected.
|
|
333
|
+
def update_sql(sql, name = nil)
|
|
334
|
+
execute(sql, name)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Executes the delete statement and returns the number of rows affected.
|
|
338
|
+
def delete_sql(sql, name = nil)
|
|
339
|
+
update_sql(sql, name)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Send a rollback message to all records after they have been rolled back. If rollback
|
|
343
|
+
# is false, only rollback records since the last save point.
|
|
344
|
+
def rollback_transaction_records(rollback)
|
|
345
|
+
if rollback
|
|
346
|
+
records = @_current_transaction_records.flatten
|
|
347
|
+
@_current_transaction_records.clear
|
|
348
|
+
else
|
|
349
|
+
records = @_current_transaction_records.pop
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
unless records.blank?
|
|
353
|
+
records.uniq.each do |record|
|
|
354
|
+
begin
|
|
355
|
+
record.rolledback!(rollback)
|
|
356
|
+
rescue Exception => e
|
|
357
|
+
record.logger.error(e) if record.respond_to?(:logger) && record.logger
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Send a commit message to all records after they have been committed.
|
|
364
|
+
def commit_transaction_records
|
|
365
|
+
records = @_current_transaction_records.flatten
|
|
366
|
+
@_current_transaction_records.clear
|
|
367
|
+
unless records.blank?
|
|
368
|
+
records.uniq.each do |record|
|
|
369
|
+
begin
|
|
370
|
+
record.committed!
|
|
371
|
+
rescue Exception => e
|
|
372
|
+
record.logger.error(e) if record.respond_to?(:logger) && record.logger
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
|
379
|
+
[sql, binds]
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def last_inserted_id(result)
|
|
383
|
+
row = result.rows.first
|
|
384
|
+
row && row.first
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|