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.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. metadata +242 -0
@@ -0,0 +1,296 @@
1
+ require 'date'
2
+ require 'bigdecimal'
3
+ require 'bigdecimal/util'
4
+ require 'active_support/core_ext/benchmark'
5
+ require 'active_support/deprecation'
6
+ require 'active_record/connection_adapters/schema_cache'
7
+ require 'monitor'
8
+
9
+ module ActiveRecord
10
+ module ConnectionAdapters # :nodoc:
11
+ extend ActiveSupport::Autoload
12
+
13
+ autoload :Column
14
+
15
+ autoload_under 'abstract' do
16
+ autoload :IndexDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
17
+ autoload :ColumnDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
18
+ autoload :TableDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
19
+ autoload :Table, 'active_record/connection_adapters/abstract/schema_definitions'
20
+
21
+ autoload :SchemaStatements
22
+ autoload :DatabaseStatements
23
+ autoload :DatabaseLimits
24
+ autoload :Quoting
25
+
26
+ autoload :ConnectionPool
27
+ autoload :ConnectionHandler, 'active_record/connection_adapters/abstract/connection_pool'
28
+ autoload :ConnectionManagement, 'active_record/connection_adapters/abstract/connection_pool'
29
+ autoload :ConnectionSpecification
30
+
31
+ autoload :QueryCache
32
+ end
33
+
34
+ # Active Record supports multiple database systems. AbstractAdapter and
35
+ # related classes form the abstraction layer which makes this possible.
36
+ # An AbstractAdapter represents a connection to a database, and provides an
37
+ # abstract interface for database-specific functionality such as establishing
38
+ # a connection, escaping values, building the right SQL fragments for ':offset'
39
+ # and ':limit' options, etc.
40
+ #
41
+ # All the concrete database adapters follow the interface laid down in this class.
42
+ # ActiveRecord::Base.connection returns an AbstractAdapter object, which
43
+ # you can use.
44
+ #
45
+ # Most of the methods in the adapter are useful during migrations. Most
46
+ # notably, the instance methods provided by SchemaStatement are very useful.
47
+ class AbstractAdapter
48
+ include Quoting, DatabaseStatements, SchemaStatements
49
+ include DatabaseLimits
50
+ include QueryCache
51
+ include ActiveSupport::Callbacks
52
+ include MonitorMixin
53
+
54
+ define_callbacks :checkout, :checkin
55
+
56
+ attr_accessor :visitor, :pool
57
+ attr_reader :schema_cache, :last_use, :in_use
58
+ alias :in_use? :in_use
59
+
60
+ def initialize(connection, logger = nil, pool = nil) #:nodoc:
61
+ super()
62
+
63
+ @active = nil
64
+ @connection = connection
65
+ @in_use = false
66
+ @instrumenter = ActiveSupport::Notifications.instrumenter
67
+ @last_use = false
68
+ @logger = logger
69
+ @open_transactions = 0
70
+ @pool = pool
71
+ @query_cache = Hash.new { |h,sql| h[sql] = {} }
72
+ @query_cache_enabled = false
73
+ @schema_cache = SchemaCache.new self
74
+ @visitor = nil
75
+ end
76
+
77
+ def lease
78
+ synchronize do
79
+ unless in_use
80
+ @in_use = true
81
+ @last_use = Time.now
82
+ end
83
+ end
84
+ end
85
+
86
+ def expire
87
+ @in_use = false
88
+ end
89
+
90
+ # Returns the human-readable name of the adapter. Use mixed case - one
91
+ # can always use downcase if needed.
92
+ def adapter_name
93
+ 'Abstract'
94
+ end
95
+
96
+ # Does this adapter support migrations? Backend specific, as the
97
+ # abstract adapter always returns +false+.
98
+ def supports_migrations?
99
+ false
100
+ end
101
+
102
+ # Can this adapter determine the primary key for tables not attached
103
+ # to an Active Record class, such as join tables? Backend specific, as
104
+ # the abstract adapter always returns +false+.
105
+ def supports_primary_key?
106
+ false
107
+ end
108
+
109
+ # Does this adapter support using DISTINCT within COUNT? This is +true+
110
+ # for all adapters except sqlite.
111
+ def supports_count_distinct?
112
+ true
113
+ end
114
+
115
+ # Does this adapter support DDL rollbacks in transactions? That is, would
116
+ # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
117
+ # SQL Server, and others support this. MySQL and others do not.
118
+ def supports_ddl_transactions?
119
+ false
120
+ end
121
+
122
+ def supports_bulk_alter?
123
+ false
124
+ end
125
+
126
+ # Does this adapter support savepoints? PostgreSQL and MySQL do,
127
+ # SQLite < 3.6.8 does not.
128
+ def supports_savepoints?
129
+ false
130
+ end
131
+
132
+ # Should primary key values be selected from their corresponding
133
+ # sequence before the insert statement? If true, next_sequence_value
134
+ # is called before each insert to set the record's primary key.
135
+ # This is false for all adapters but Firebird.
136
+ def prefetch_primary_key?(table_name = nil)
137
+ false
138
+ end
139
+
140
+ # Does this adapter support index sort order?
141
+ def supports_index_sort_order?
142
+ false
143
+ end
144
+
145
+ # Does this adapter support explain? As of this writing sqlite3,
146
+ # mysql2, and postgresql are the only ones that do.
147
+ def supports_explain?
148
+ false
149
+ end
150
+
151
+ # QUOTING ==================================================
152
+
153
+ # Override to return the quoted table name. Defaults to column quoting.
154
+ def quote_table_name(name)
155
+ quote_column_name(name)
156
+ end
157
+
158
+ # Returns a bind substitution value given a +column+ and list of current
159
+ # +binds+
160
+ def substitute_at(column, index)
161
+ Arel.sql '?'
162
+ end
163
+
164
+ # REFERENTIAL INTEGRITY ====================================
165
+
166
+ # Override to turn off referential integrity while executing <tt>&block</tt>.
167
+ def disable_referential_integrity
168
+ yield
169
+ end
170
+
171
+ # CONNECTION MANAGEMENT ====================================
172
+
173
+ # Checks whether the connection to the database is still active. This includes
174
+ # checking whether the database is actually capable of responding, i.e. whether
175
+ # the connection isn't stale.
176
+ def active?
177
+ @active != false
178
+ end
179
+
180
+ # Disconnects from the database if already connected, and establishes a
181
+ # new connection with the database.
182
+ def reconnect!
183
+ @active = true
184
+ end
185
+
186
+ # Disconnects from the database if already connected. Otherwise, this
187
+ # method does nothing.
188
+ def disconnect!
189
+ @active = false
190
+ end
191
+
192
+ # Reset the state of this connection, directing the DBMS to clear
193
+ # transactions and other connection-related server-side state. Usually a
194
+ # database-dependent operation.
195
+ #
196
+ # The default implementation does nothing; the implementation should be
197
+ # overridden by concrete adapters.
198
+ def reset!
199
+ # this should be overridden by concrete adapters
200
+ end
201
+
202
+ ###
203
+ # Clear any caching the database adapter may be doing, for example
204
+ # clearing the prepared statement cache. This is database specific.
205
+ def clear_cache!
206
+ # this should be overridden by concrete adapters
207
+ end
208
+
209
+ # Returns true if its required to reload the connection between requests for development mode.
210
+ # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
211
+ def requires_reloading?
212
+ false
213
+ end
214
+
215
+ # Checks whether the connection to the database is still active (i.e. not stale).
216
+ # This is done under the hood by calling <tt>active?</tt>. If the connection
217
+ # is no longer active, then this method will reconnect to the database.
218
+ def verify!(*ignored)
219
+ reconnect! unless active?
220
+ end
221
+
222
+ # Provides access to the underlying database driver for this adapter. For
223
+ # example, this method returns a Mysql object in case of MysqlAdapter,
224
+ # and a PGconn object in case of PostgreSQLAdapter.
225
+ #
226
+ # This is useful for when you need to call a proprietary method such as
227
+ # PostgreSQL's lo_* methods.
228
+ def raw_connection
229
+ @connection
230
+ end
231
+
232
+ attr_reader :open_transactions
233
+
234
+ def increment_open_transactions
235
+ @open_transactions += 1
236
+ end
237
+
238
+ def decrement_open_transactions
239
+ @open_transactions -= 1
240
+ end
241
+
242
+ def transaction_joinable=(joinable)
243
+ @transaction_joinable = joinable
244
+ end
245
+
246
+ def create_savepoint
247
+ end
248
+
249
+ def rollback_to_savepoint
250
+ end
251
+
252
+ def release_savepoint
253
+ end
254
+
255
+ def case_sensitive_modifier(node)
256
+ node
257
+ end
258
+
259
+ def case_insensitive_comparison(table, attribute, column, value)
260
+ table[attribute].lower.eq(table.lower(value))
261
+ end
262
+
263
+ def current_savepoint_name
264
+ "active_record_#{open_transactions}"
265
+ end
266
+
267
+ # Check the connection back in to the connection pool
268
+ def close
269
+ pool.checkin self
270
+ end
271
+
272
+ protected
273
+
274
+ def log(sql, name = "SQL", binds = [])
275
+ @instrumenter.instrument(
276
+ "sql.active_record",
277
+ :sql => sql,
278
+ :name => name,
279
+ :connection_id => object_id,
280
+ :binds => binds) { yield }
281
+ rescue Exception => e
282
+ message = "#{e.class.name}: #{e.message}: #{sql}"
283
+ @logger.debug message if @logger
284
+ exception = translate_exception(e, message)
285
+ exception.set_backtrace e.backtrace
286
+ raise exception
287
+ end
288
+
289
+ def translate_exception(e, message)
290
+ # override in derived class
291
+ ActiveRecord::StatementInvalid.new(message)
292
+ end
293
+
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,653 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class AbstractMysqlAdapter < AbstractAdapter
6
+ class Column < ConnectionAdapters::Column # :nodoc:
7
+ attr_reader :collation
8
+
9
+ def initialize(name, default, sql_type = nil, null = true, collation = nil)
10
+ super(name, default, sql_type, null)
11
+ @collation = collation
12
+ end
13
+
14
+ def extract_default(default)
15
+ if sql_type =~ /blob/i || type == :text
16
+ if default.blank?
17
+ return null ? nil : ''
18
+ else
19
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
20
+ end
21
+ elsif missing_default_forged_as_empty_string?(default)
22
+ nil
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ def has_default?
29
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
30
+ super
31
+ end
32
+
33
+ # Must return the relevant concrete adapter
34
+ def adapter
35
+ raise NotImplementedError
36
+ end
37
+
38
+ def case_sensitive?
39
+ collation && !collation.match(/_ci$/)
40
+ end
41
+
42
+ private
43
+
44
+ def simplified_type(field_type)
45
+ return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
46
+
47
+ case field_type
48
+ when /enum/i, /set/i then :string
49
+ when /year/i then :integer
50
+ when /bit/i then :binary
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def extract_limit(sql_type)
57
+ case sql_type
58
+ when /blob|text/i
59
+ case sql_type
60
+ when /tiny/i
61
+ 255
62
+ when /medium/i
63
+ 16777215
64
+ when /long/i
65
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
66
+ else
67
+ super # we could return 65535 here, but we leave it undecorated by default
68
+ end
69
+ when /^bigint/i; 8
70
+ when /^int/i; 4
71
+ when /^mediumint/i; 3
72
+ when /^smallint/i; 2
73
+ when /^tinyint/i; 1
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ # MySQL misreports NOT NULL column default when none is given.
80
+ # We can't detect this for columns which may have a legitimate ''
81
+ # default (string) but we can for others (integer, datetime, boolean,
82
+ # and the rest).
83
+ #
84
+ # Test whether the column has default '', is not null, and is not
85
+ # a type allowing default ''.
86
+ def missing_default_forged_as_empty_string?(default)
87
+ type != :string && !null && default == ''
88
+ end
89
+ end
90
+
91
+ ##
92
+ # :singleton-method:
93
+ # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
94
+ # as boolean. If you wish to disable this emulation (which was the default
95
+ # behavior in versions 0.13.1 and earlier) you can add the following line
96
+ # to your application.rb file:
97
+ #
98
+ # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
99
+ class_attribute :emulate_booleans
100
+ self.emulate_booleans = true
101
+
102
+ LOST_CONNECTION_ERROR_MESSAGES = [
103
+ "Server shutdown in progress",
104
+ "Broken pipe",
105
+ "Lost connection to MySQL server during query",
106
+ "MySQL server has gone away" ]
107
+
108
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
109
+
110
+ NATIVE_DATABASE_TYPES = {
111
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
112
+ :string => { :name => "varchar", :limit => 255 },
113
+ :text => { :name => "text" },
114
+ :integer => { :name => "int", :limit => 4 },
115
+ :float => { :name => "float" },
116
+ :decimal => { :name => "decimal" },
117
+ :datetime => { :name => "datetime" },
118
+ :timestamp => { :name => "datetime" },
119
+ :time => { :name => "time" },
120
+ :date => { :name => "date" },
121
+ :binary => { :name => "blob" },
122
+ :boolean => { :name => "tinyint", :limit => 1 }
123
+ }
124
+
125
+ # FIXME: Make the first parameter more similar for the two adapters
126
+ def initialize(connection, logger, connection_options, config)
127
+ super(connection, logger)
128
+ @connection_options, @config = connection_options, config
129
+ @quoted_column_names, @quoted_table_names = {}, {}
130
+ @visitor = Arel::Visitors::MySQL.new self
131
+ end
132
+
133
+ def adapter_name #:nodoc:
134
+ self.class::ADAPTER_NAME
135
+ end
136
+
137
+ # Returns true, since this connection adapter supports migrations.
138
+ def supports_migrations?
139
+ true
140
+ end
141
+
142
+ def supports_primary_key?
143
+ true
144
+ end
145
+
146
+ # Returns true, since this connection adapter supports savepoints.
147
+ def supports_savepoints?
148
+ true
149
+ end
150
+
151
+ def supports_bulk_alter? #:nodoc:
152
+ true
153
+ end
154
+
155
+ # Technically MySQL allows to create indexes with the sort order syntax
156
+ # but at the moment (5.5) it doesn't yet implement them
157
+ def supports_index_sort_order?
158
+ true
159
+ end
160
+
161
+ def native_database_types
162
+ NATIVE_DATABASE_TYPES
163
+ end
164
+
165
+ # HELPER METHODS ===========================================
166
+
167
+ # The two drivers have slightly different ways of yielding hashes of results, so
168
+ # this method must be implemented to provide a uniform interface.
169
+ def each_hash(result) # :nodoc:
170
+ raise NotImplementedError
171
+ end
172
+
173
+ # Overridden by the adapters to instantiate their specific Column type.
174
+ def new_column(field, default, type, null, collation) # :nodoc:
175
+ Column.new(field, default, type, null, collation)
176
+ end
177
+
178
+ # Must return the Mysql error number from the exception, if the exception has an
179
+ # error number.
180
+ def error_number(exception) # :nodoc:
181
+ raise NotImplementedError
182
+ end
183
+
184
+ # QUOTING ==================================================
185
+
186
+ def quote(value, column = nil)
187
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
188
+ s = column.class.string_to_binary(value).unpack("H*")[0]
189
+ "x'#{s}'"
190
+ elsif value.kind_of?(BigDecimal)
191
+ value.to_s("F")
192
+ else
193
+ super
194
+ end
195
+ end
196
+
197
+ def quote_column_name(name) #:nodoc:
198
+ @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
199
+ end
200
+
201
+ def quote_table_name(name) #:nodoc:
202
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
203
+ end
204
+
205
+ def quoted_true
206
+ QUOTED_TRUE
207
+ end
208
+
209
+ def quoted_false
210
+ QUOTED_FALSE
211
+ end
212
+
213
+ # REFERENTIAL INTEGRITY ====================================
214
+
215
+ def disable_referential_integrity(&block) #:nodoc:
216
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
217
+
218
+ begin
219
+ update("SET FOREIGN_KEY_CHECKS = 0")
220
+ yield
221
+ ensure
222
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
223
+ end
224
+ end
225
+
226
+ # DATABASE STATEMENTS ======================================
227
+
228
+ # Executes the SQL statement in the context of this connection.
229
+ def execute(sql, name = nil)
230
+ if name == :skip_logging
231
+ @connection.query(sql)
232
+ else
233
+ log(sql, name) { @connection.query(sql) }
234
+ end
235
+ rescue ActiveRecord::StatementInvalid => exception
236
+ if exception.message.split(":").first =~ /Packets out of order/
237
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
238
+ else
239
+ raise
240
+ end
241
+ end
242
+
243
+ # MysqlAdapter has to free a result after using it, so we use this method to write
244
+ # stuff in a abstract way without concerning ourselves about whether it needs to be
245
+ # explicitly freed or not.
246
+ def execute_and_free(sql, name = nil) #:nodoc:
247
+ yield execute(sql, name)
248
+ end
249
+
250
+ def update_sql(sql, name = nil) #:nodoc:
251
+ super
252
+ @connection.affected_rows
253
+ end
254
+
255
+ def begin_db_transaction
256
+ execute "BEGIN"
257
+ rescue Exception
258
+ # Transactions aren't supported
259
+ end
260
+
261
+ def commit_db_transaction #:nodoc:
262
+ execute "COMMIT"
263
+ rescue Exception
264
+ # Transactions aren't supported
265
+ end
266
+
267
+ def rollback_db_transaction #:nodoc:
268
+ execute "ROLLBACK"
269
+ rescue Exception
270
+ # Transactions aren't supported
271
+ end
272
+
273
+ def create_savepoint
274
+ execute("SAVEPOINT #{current_savepoint_name}")
275
+ end
276
+
277
+ def rollback_to_savepoint
278
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
279
+ end
280
+
281
+ def release_savepoint
282
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
283
+ end
284
+
285
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
286
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
287
+ # these, we must use a subquery. However, MySQL is too stupid to create a
288
+ # temporary table for this automatically, so we have to give it some prompting
289
+ # in the form of a subsubquery. Ugh!
290
+ def join_to_update(update, select) #:nodoc:
291
+ if select.limit || select.offset || select.orders.any?
292
+ subsubselect = select.clone
293
+ subsubselect.projections = [update.key]
294
+
295
+ subselect = Arel::SelectManager.new(select.engine)
296
+ subselect.project Arel.sql(update.key.name)
297
+ subselect.from subsubselect.as('__active_record_temp')
298
+
299
+ update.where update.key.in(subselect)
300
+ else
301
+ update.table select.source
302
+ update.wheres = select.constraints
303
+ end
304
+ end
305
+
306
+ # SCHEMA STATEMENTS ========================================
307
+
308
+ def structure_dump #:nodoc:
309
+ if supports_views?
310
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
311
+ else
312
+ sql = "SHOW TABLES"
313
+ end
314
+
315
+ select_all(sql).map { |table|
316
+ table.delete('Table_type')
317
+ sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
318
+ exec_without_stmt(sql).first['Create Table'] + ";\n\n"
319
+ }.join
320
+ end
321
+
322
+ # Drops the database specified on the +name+ attribute
323
+ # and creates it again using the provided +options+.
324
+ def recreate_database(name, options = {})
325
+ drop_database(name)
326
+ create_database(name, options)
327
+ end
328
+
329
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
330
+ # Charset defaults to utf8.
331
+ #
332
+ # Example:
333
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
334
+ # create_database 'matt_development'
335
+ # create_database 'matt_development', :charset => :big5
336
+ def create_database(name, options = {})
337
+ if options[:collation]
338
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
339
+ else
340
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
341
+ end
342
+ end
343
+
344
+ # Drops a MySQL database.
345
+ #
346
+ # Example:
347
+ # drop_database('sebastian_development')
348
+ def drop_database(name) #:nodoc:
349
+ execute "DROP DATABASE IF EXISTS `#{name}`"
350
+ end
351
+
352
+ def current_database
353
+ select_value 'SELECT DATABASE() as db'
354
+ end
355
+
356
+ # Returns the database character set.
357
+ def charset
358
+ show_variable 'character_set_database'
359
+ end
360
+
361
+ # Returns the database collation strategy.
362
+ def collation
363
+ show_variable 'collation_database'
364
+ end
365
+
366
+ def tables(name = nil, database = nil, like = nil) #:nodoc:
367
+ sql = "SHOW TABLES "
368
+ sql << "IN #{database} " if database
369
+ sql << "LIKE #{quote(like)}" if like
370
+
371
+ execute_and_free(sql, 'SCHEMA') do |result|
372
+ result.collect { |field| field.first }
373
+ end
374
+ end
375
+
376
+ def table_exists?(name)
377
+ return false unless name
378
+ return true if tables(nil, nil, name).any?
379
+
380
+ name = name.to_s
381
+ schema, table = name.split('.', 2)
382
+
383
+ unless table # A table was provided without a schema
384
+ table = schema
385
+ schema = nil
386
+ end
387
+
388
+ tables(nil, schema, table).any?
389
+ end
390
+
391
+ # Returns an array of indexes for the given table.
392
+ def indexes(table_name, name = nil) #:nodoc:
393
+ indexes = []
394
+ current_index = nil
395
+ execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
396
+ each_hash(result) do |row|
397
+ if current_index != row[:Key_name]
398
+ next if row[:Key_name] == 'PRIMARY' # skip the primary key
399
+ current_index = row[:Key_name]
400
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
401
+ end
402
+
403
+ indexes.last.columns << row[:Column_name]
404
+ indexes.last.lengths << row[:Sub_part]
405
+ end
406
+ end
407
+
408
+ indexes
409
+ end
410
+
411
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
412
+ def columns(table_name, name = nil)#:nodoc:
413
+ sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
414
+ execute_and_free(sql, 'SCHEMA') do |result|
415
+ each_hash(result).map do |field|
416
+ new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
417
+ end
418
+ end
419
+ end
420
+
421
+ def create_table(table_name, options = {}) #:nodoc:
422
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
423
+ end
424
+
425
+ def bulk_change_table(table_name, operations) #:nodoc:
426
+ sqls = operations.map do |command, args|
427
+ table, arguments = args.shift, args
428
+ method = :"#{command}_sql"
429
+
430
+ if respond_to?(method)
431
+ send(method, table, *arguments)
432
+ else
433
+ raise "Unknown method called : #{method}(#{arguments.inspect})"
434
+ end
435
+ end.flatten.join(", ")
436
+
437
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
438
+ end
439
+
440
+ # Renames a table.
441
+ #
442
+ # Example:
443
+ # rename_table('octopuses', 'octopi')
444
+ def rename_table(table_name, new_name)
445
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
446
+ end
447
+
448
+ def add_column(table_name, column_name, type, options = {})
449
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
450
+ end
451
+
452
+ def change_column_default(table_name, column_name, default)
453
+ column = column_for(table_name, column_name)
454
+ change_column table_name, column_name, column.sql_type, :default => default
455
+ end
456
+
457
+ def change_column_null(table_name, column_name, null, default = nil)
458
+ column = column_for(table_name, column_name)
459
+
460
+ unless null || default.nil?
461
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
462
+ end
463
+
464
+ change_column table_name, column_name, column.sql_type, :null => null
465
+ end
466
+
467
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
468
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
469
+ end
470
+
471
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
472
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
473
+ end
474
+
475
+ # Maps logical Rails types to MySQL-specific data types.
476
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
477
+ return super unless type.to_s == 'integer'
478
+
479
+ case limit
480
+ when 1; 'tinyint'
481
+ when 2; 'smallint'
482
+ when 3; 'mediumint'
483
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
484
+ when 5..8; 'bigint'
485
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
486
+ end
487
+ end
488
+
489
+ def add_column_position!(sql, options)
490
+ if options[:first]
491
+ sql << " FIRST"
492
+ elsif options[:after]
493
+ sql << " AFTER #{quote_column_name(options[:after])}"
494
+ end
495
+ end
496
+
497
+ # SHOW VARIABLES LIKE 'name'
498
+ def show_variable(name)
499
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
500
+ variables.first['Value'] unless variables.empty?
501
+ end
502
+
503
+ # Returns a table's primary key and belonging sequence.
504
+ def pk_and_sequence_for(table)
505
+ execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
506
+ create_table = each_hash(result).first[:"Create Table"]
507
+ if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/
508
+ keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
509
+ keys.length == 1 ? [keys.first, nil] : nil
510
+ else
511
+ nil
512
+ end
513
+ end
514
+ end
515
+
516
+ # Returns just a table's primary key
517
+ def primary_key(table)
518
+ pk_and_sequence = pk_and_sequence_for(table)
519
+ pk_and_sequence && pk_and_sequence.first
520
+ end
521
+
522
+ def case_sensitive_modifier(node)
523
+ Arel::Nodes::Bin.new(node)
524
+ end
525
+
526
+ def case_insensitive_comparison(table, attribute, column, value)
527
+ if column.case_sensitive?
528
+ super
529
+ else
530
+ table[attribute].eq(value)
531
+ end
532
+ end
533
+
534
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
535
+ where_sql
536
+ end
537
+
538
+ protected
539
+
540
+ def add_index_length(option_strings, column_names, options = {})
541
+ if options.is_a?(Hash) && length = options[:length]
542
+ case length
543
+ when Hash
544
+ column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name)}
545
+ when Fixnum
546
+ column_names.each {|name| option_strings[name] += "(#{length})"}
547
+ end
548
+ end
549
+
550
+ return option_strings
551
+ end
552
+
553
+ def quoted_columns_for_index(column_names, options = {})
554
+ option_strings = Hash[column_names.map {|name| [name, '']}]
555
+
556
+ # add index length
557
+ option_strings = add_index_length(option_strings, column_names, options)
558
+
559
+ # add index sort order
560
+ option_strings = add_index_sort_order(option_strings, column_names, options)
561
+
562
+ column_names.map {|name| quote_column_name(name) + option_strings[name]}
563
+ end
564
+
565
+ def translate_exception(exception, message)
566
+ case error_number(exception)
567
+ when 1062
568
+ RecordNotUnique.new(message, exception)
569
+ when 1452
570
+ InvalidForeignKey.new(message, exception)
571
+ else
572
+ super
573
+ end
574
+ end
575
+
576
+ def add_column_sql(table_name, column_name, type, options = {})
577
+ add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
578
+ add_column_options!(add_column_sql, options)
579
+ add_column_position!(add_column_sql, options)
580
+ add_column_sql
581
+ end
582
+
583
+ def change_column_sql(table_name, column_name, type, options = {})
584
+ column = column_for(table_name, column_name)
585
+
586
+ unless options_include_default?(options)
587
+ options[:default] = column.default
588
+ end
589
+
590
+ unless options.has_key?(:null)
591
+ options[:null] = column.null
592
+ end
593
+
594
+ change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
595
+ add_column_options!(change_column_sql, options)
596
+ add_column_position!(change_column_sql, options)
597
+ change_column_sql
598
+ end
599
+
600
+ def rename_column_sql(table_name, column_name, new_column_name)
601
+ options = {}
602
+
603
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
604
+ options[:default] = column.default
605
+ options[:null] = column.null
606
+ else
607
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
608
+ end
609
+
610
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
611
+ rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
612
+ add_column_options!(rename_column_sql, options)
613
+ rename_column_sql
614
+ end
615
+
616
+ def remove_column_sql(table_name, *column_names)
617
+ columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
618
+ end
619
+ alias :remove_columns_sql :remove_column
620
+
621
+ def add_index_sql(table_name, column_name, options = {})
622
+ index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
623
+ "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
624
+ end
625
+
626
+ def remove_index_sql(table_name, options = {})
627
+ index_name = index_name_for_remove(table_name, options)
628
+ "DROP INDEX #{index_name}"
629
+ end
630
+
631
+ def add_timestamps_sql(table_name)
632
+ [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
633
+ end
634
+
635
+ def remove_timestamps_sql(table_name)
636
+ [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
637
+ end
638
+
639
+ private
640
+
641
+ def supports_views?
642
+ version[0] >= 5
643
+ end
644
+
645
+ def column_for(table_name, column_name)
646
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
647
+ raise "No such column: #{table_name}.#{column_name}"
648
+ end
649
+ column
650
+ end
651
+ end
652
+ end
653
+ end