activerecord 3.1.9 → 3.2.12

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 (112) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +317 -336
  3. data/README.rdoc +3 -3
  4. data/examples/performance.rb +20 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +3 -6
  7. data/lib/active_record/associations/association.rb +3 -42
  8. data/lib/active_record/associations/association_scope.rb +3 -15
  9. data/lib/active_record/associations/builder/association.rb +6 -4
  10. data/lib/active_record/associations/builder/belongs_to.rb +3 -3
  11. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +4 -4
  13. data/lib/active_record/associations/builder/has_one.rb +5 -6
  14. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  15. data/lib/active_record/associations/collection_association.rb +64 -31
  16. data/lib/active_record/associations/collection_proxy.rb +2 -37
  17. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
  18. data/lib/active_record/associations/has_many_association.rb +5 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +28 -9
  20. data/lib/active_record/associations/has_one_association.rb +15 -13
  21. data/lib/active_record/associations/join_dependency.rb +2 -2
  22. data/lib/active_record/associations/preloader.rb +14 -10
  23. data/lib/active_record/associations/through_association.rb +7 -3
  24. data/lib/active_record/associations.rb +92 -76
  25. data/lib/active_record/attribute_assignment.rb +221 -0
  26. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  27. data/lib/active_record/attribute_methods/dirty.rb +21 -11
  28. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  29. data/lib/active_record/attribute_methods/read.rb +73 -83
  30. data/lib/active_record/attribute_methods/serialization.rb +102 -0
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +23 -17
  32. data/lib/active_record/attribute_methods/write.rb +31 -6
  33. data/lib/active_record/attribute_methods.rb +231 -30
  34. data/lib/active_record/autosave_association.rb +43 -22
  35. data/lib/active_record/base.rb +227 -1708
  36. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
  37. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
  38. data/lib/active_record/connection_adapters/abstract/database_statements.rb +6 -33
  39. data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
  40. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -6
  41. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -26
  42. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +674 -0
  45. data/lib/active_record/connection_adapters/column.rb +37 -11
  46. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -581
  47. data/lib/active_record/connection_adapters/mysql_adapter.rb +137 -696
  48. data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -86
  49. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  50. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  51. data/lib/active_record/connection_adapters/sqlite_adapter.rb +55 -32
  52. data/lib/active_record/counter_cache.rb +9 -4
  53. data/lib/active_record/dynamic_finder_match.rb +12 -0
  54. data/lib/active_record/dynamic_matchers.rb +84 -0
  55. data/lib/active_record/errors.rb +11 -1
  56. data/lib/active_record/explain.rb +85 -0
  57. data/lib/active_record/explain_subscriber.rb +25 -0
  58. data/lib/active_record/fixtures/file.rb +65 -0
  59. data/lib/active_record/fixtures.rb +56 -85
  60. data/lib/active_record/identity_map.rb +3 -4
  61. data/lib/active_record/inheritance.rb +174 -0
  62. data/lib/active_record/integration.rb +49 -0
  63. data/lib/active_record/locking/optimistic.rb +30 -25
  64. data/lib/active_record/locking/pessimistic.rb +23 -1
  65. data/lib/active_record/log_subscriber.rb +3 -3
  66. data/lib/active_record/migration/command_recorder.rb +8 -8
  67. data/lib/active_record/migration.rb +68 -35
  68. data/lib/active_record/model_schema.rb +366 -0
  69. data/lib/active_record/nested_attributes.rb +3 -2
  70. data/lib/active_record/persistence.rb +57 -11
  71. data/lib/active_record/querying.rb +58 -0
  72. data/lib/active_record/railtie.rb +31 -29
  73. data/lib/active_record/railties/controller_runtime.rb +3 -1
  74. data/lib/active_record/railties/databases.rake +191 -110
  75. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  76. data/lib/active_record/readonly_attributes.rb +26 -0
  77. data/lib/active_record/reflection.rb +7 -15
  78. data/lib/active_record/relation/batches.rb +5 -2
  79. data/lib/active_record/relation/calculations.rb +47 -15
  80. data/lib/active_record/relation/delegation.rb +49 -0
  81. data/lib/active_record/relation/finder_methods.rb +9 -7
  82. data/lib/active_record/relation/predicate_builder.rb +18 -7
  83. data/lib/active_record/relation/query_methods.rb +75 -9
  84. data/lib/active_record/relation/spawn_methods.rb +11 -2
  85. data/lib/active_record/relation.rb +78 -32
  86. data/lib/active_record/result.rb +1 -1
  87. data/lib/active_record/sanitization.rb +194 -0
  88. data/lib/active_record/schema_dumper.rb +12 -5
  89. data/lib/active_record/scoping/default.rb +142 -0
  90. data/lib/active_record/scoping/named.rb +202 -0
  91. data/lib/active_record/scoping.rb +152 -0
  92. data/lib/active_record/serialization.rb +1 -43
  93. data/lib/active_record/serializers/xml_serializer.rb +4 -45
  94. data/lib/active_record/session_store.rb +17 -15
  95. data/lib/active_record/store.rb +52 -0
  96. data/lib/active_record/test_case.rb +11 -7
  97. data/lib/active_record/timestamp.rb +17 -3
  98. data/lib/active_record/transactions.rb +27 -6
  99. data/lib/active_record/translation.rb +22 -0
  100. data/lib/active_record/validations/associated.rb +5 -4
  101. data/lib/active_record/validations/uniqueness.rb +7 -7
  102. data/lib/active_record/validations.rb +1 -1
  103. data/lib/active_record/version.rb +2 -2
  104. data/lib/active_record.rb +38 -3
  105. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  106. data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
  107. data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
  108. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  109. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  110. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  111. metadata +30 -10
  112. data/lib/active_record/named_scope.rb +0 -200
@@ -1,5 +1,4 @@
1
- # encoding: utf-8
2
- require 'arel/visitors/bind_visitor'
1
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
3
2
 
4
3
  gem 'mysql2', '~> 0.3.10'
5
4
  require 'mysql2'
@@ -21,195 +20,52 @@ module ActiveRecord
21
20
  end
22
21
 
23
22
  module ConnectionAdapters
24
- class Mysql2IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc:
25
- end
23
+ class Mysql2Adapter < AbstractMysqlAdapter
26
24
 
27
- class Mysql2Column < Column
28
- BOOL = "tinyint(1)"
29
- def extract_default(default)
30
- if sql_type =~ /blob/i || type == :text
31
- if default.blank?
32
- return null ? nil : ''
33
- else
34
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
35
- end
36
- elsif missing_default_forged_as_empty_string?(default)
37
- nil
38
- else
39
- super
25
+ class Column < AbstractMysqlAdapter::Column # :nodoc:
26
+ def adapter
27
+ Mysql2Adapter
40
28
  end
41
29
  end
42
30
 
43
- def has_default?
44
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
45
- super
46
- end
47
-
48
- private
49
- def simplified_type(field_type)
50
- return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL)
51
-
52
- case field_type
53
- when /enum/i, /set/i then :string
54
- when /year/i then :integer
55
- when /bit/i then :binary
56
- else
57
- super
58
- end
59
- end
60
-
61
- def extract_limit(sql_type)
62
- case sql_type
63
- when /blob|text/i
64
- case sql_type
65
- when /tiny/i
66
- 255
67
- when /medium/i
68
- 16777215
69
- when /long/i
70
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
71
- else
72
- super # we could return 65535 here, but we leave it undecorated by default
73
- end
74
- when /^bigint/i; 8
75
- when /^int/i; 4
76
- when /^mediumint/i; 3
77
- when /^smallint/i; 2
78
- when /^tinyint/i; 1
79
- else
80
- super
81
- end
82
- end
83
-
84
- # MySQL misreports NOT NULL column default when none is given.
85
- # We can't detect this for columns which may have a legitimate ''
86
- # default (string) but we can for others (integer, datetime, boolean,
87
- # and the rest).
88
- #
89
- # Test whether the column has default '', is not null, and is not
90
- # a type allowing default ''.
91
- def missing_default_forged_as_empty_string?(default)
92
- type != :string && !null && default == ''
93
- end
94
- end
95
-
96
- class Mysql2Adapter < AbstractAdapter
97
- cattr_accessor :emulate_booleans
98
- self.emulate_booleans = true
99
-
100
31
  ADAPTER_NAME = 'Mysql2'
101
- PRIMARY = "PRIMARY"
102
-
103
- LOST_CONNECTION_ERROR_MESSAGES = [
104
- "Server shutdown in progress",
105
- "Broken pipe",
106
- "Lost connection to MySQL server during query",
107
- "MySQL server has gone away" ]
108
-
109
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
110
-
111
- NATIVE_DATABASE_TYPES = {
112
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
113
- :string => { :name => "varchar", :limit => 255 },
114
- :text => { :name => "text" },
115
- :integer => { :name => "int", :limit => 4 },
116
- :float => { :name => "float" },
117
- :decimal => { :name => "decimal" },
118
- :datetime => { :name => "datetime" },
119
- :timestamp => { :name => "datetime" },
120
- :time => { :name => "time" },
121
- :date => { :name => "date" },
122
- :binary => { :name => "blob" },
123
- :boolean => { :name => "tinyint", :limit => 1 }
124
- }
125
32
 
126
33
  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 = {}, {}
34
+ super
35
+ @visitor = BindSubstitution.new self
130
36
  configure_connection
131
37
  end
132
38
 
133
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
134
- include Arel::Visitors::BindVisitor
135
- end
136
-
137
- def self.visitor_for(pool) # :nodoc:
138
- BindSubstitution.new pool
139
- end
140
-
141
- def adapter_name
142
- ADAPTER_NAME
143
- end
144
-
145
- # Returns true, since this connection adapter supports migrations.
146
- def supports_migrations?
147
- true
148
- end
149
-
150
- def supports_primary_key?
151
- true
152
- end
153
-
154
- # Returns true, since this connection adapter supports savepoints.
155
- def supports_savepoints?
39
+ def supports_explain?
156
40
  true
157
41
  end
158
42
 
159
- def native_database_types
160
- NATIVE_DATABASE_TYPES
161
- end
162
-
163
- # QUOTING ==================================================
43
+ # HELPER METHODS ===========================================
164
44
 
165
- def quote(value, column = nil)
166
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
167
- s = column.class.string_to_binary(value).unpack("H*")[0]
168
- "x'#{s}'"
169
- elsif value.kind_of?(BigDecimal)
170
- value.to_s("F")
45
+ def each_hash(result) # :nodoc:
46
+ if block_given?
47
+ result.each(:as => :hash, :symbolize_keys => true) do |row|
48
+ yield row
49
+ end
171
50
  else
172
- super
51
+ to_enum(:each_hash, result)
173
52
  end
174
53
  end
175
54
 
176
- def quote_column_name(name) #:nodoc:
177
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
55
+ def new_column(field, default, type, null, collation) # :nodoc:
56
+ Column.new(field, default, type, null, collation)
178
57
  end
179
58
 
180
- def quote_table_name(name) #:nodoc:
181
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
59
+ def error_number(exception)
60
+ exception.error_number if exception.respond_to?(:error_number)
182
61
  end
183
62
 
63
+ # QUOTING ==================================================
64
+
184
65
  def quote_string(string)
185
66
  @connection.escape(string)
186
67
  end
187
68
 
188
- def quoted_true
189
- QUOTED_TRUE
190
- end
191
-
192
- def quoted_false
193
- QUOTED_FALSE
194
- end
195
-
196
- def substitute_at(column, index)
197
- Arel::Nodes::BindParam.new "\0"
198
- end
199
-
200
- # REFERENTIAL INTEGRITY ====================================
201
-
202
- def disable_referential_integrity(&block) #:nodoc:
203
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
204
-
205
- begin
206
- update("SET FOREIGN_KEY_CHECKS = 0")
207
- yield
208
- ensure
209
- update("SET FOREIGN_KEY_CHECKS = #{old}")
210
- end
211
- end
212
-
213
69
  # CONNECTION MANAGEMENT ====================================
214
70
 
215
71
  def active?
@@ -222,11 +78,6 @@ module ActiveRecord
222
78
  connect
223
79
  end
224
80
 
225
- # this is set to true in 2.3, but we don't want it to be
226
- def requires_reloading?
227
- false
228
- end
229
-
230
81
  # Disconnects from the database if already connected.
231
82
  # Otherwise, this method does nothing.
232
83
  def disconnect!
@@ -243,6 +94,80 @@ module ActiveRecord
243
94
 
244
95
  # DATABASE STATEMENTS ======================================
245
96
 
97
+ def explain(arel, binds = [])
98
+ sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
99
+ start = Time.now
100
+ result = exec_query(sql, 'EXPLAIN', binds)
101
+ elapsed = Time.now - start
102
+
103
+ ExplainPrettyPrinter.new.pp(result, elapsed)
104
+ end
105
+
106
+ class ExplainPrettyPrinter # :nodoc:
107
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
108
+ # MySQL shell:
109
+ #
110
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
111
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
112
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
113
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
114
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
115
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
116
+ # 2 rows in set (0.00 sec)
117
+ #
118
+ # This is an exercise in Ruby hyperrealism :).
119
+ def pp(result, elapsed)
120
+ widths = compute_column_widths(result)
121
+ separator = build_separator(widths)
122
+
123
+ pp = []
124
+
125
+ pp << separator
126
+ pp << build_cells(result.columns, widths)
127
+ pp << separator
128
+
129
+ result.rows.each do |row|
130
+ pp << build_cells(row, widths)
131
+ end
132
+
133
+ pp << separator
134
+ pp << build_footer(result.rows.length, elapsed)
135
+
136
+ pp.join("\n") + "\n"
137
+ end
138
+
139
+ private
140
+
141
+ def compute_column_widths(result)
142
+ [].tap do |widths|
143
+ result.columns.each_with_index do |column, i|
144
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
145
+ widths << cells_in_column.map(&:length).max
146
+ end
147
+ end
148
+ end
149
+
150
+ def build_separator(widths)
151
+ padding = 1
152
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
153
+ end
154
+
155
+ def build_cells(items, widths)
156
+ cells = []
157
+ items.each_with_index do |item, i|
158
+ item = 'NULL' if item.nil?
159
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
160
+ cells << item.to_s.send(justifier, widths[i])
161
+ end
162
+ '| ' + cells.join(' | ') + ' |'
163
+ end
164
+
165
+ def build_footer(nrows, elapsed)
166
+ rows_label = nrows == 1 ? 'row' : 'rows'
167
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
168
+ end
169
+ end
170
+
246
171
  # FIXME: re-enable the following once a "better" query_cache solution is in core
247
172
  #
248
173
  # The overrides below perform much better than the originals in AbstractAdapter
@@ -282,17 +207,21 @@ module ActiveRecord
282
207
  # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
283
208
  # made since we established the connection
284
209
  @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
285
- if name == :skip_logging
286
- @connection.query(sql)
287
- else
288
- log(sql, name) { @connection.query(sql) }
289
- end
290
- rescue ActiveRecord::StatementInvalid => exception
291
- if exception.message.split(":").first =~ /Packets out of order/
292
- 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."
293
- else
294
- raise
295
- end
210
+
211
+ super
212
+ end
213
+
214
+ def exec_query(sql, name = 'SQL', binds = [])
215
+ result = execute(sql, name)
216
+ ActiveRecord::Result.new(result.fields, result.to_a)
217
+ end
218
+
219
+ alias exec_without_stmt exec_query
220
+
221
+ # Returns an array of record hashes with the column names as keys and
222
+ # column values as values.
223
+ def select(sql, name = nil, binds = [])
224
+ exec_query(sql, name).to_a
296
225
  end
297
226
 
298
227
  def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
@@ -315,416 +244,35 @@ module ActiveRecord
315
244
  @connection.last_id
316
245
  end
317
246
 
318
- def update_sql(sql, name = nil)
319
- super
320
- @connection.affected_rows
321
- end
322
-
323
- def begin_db_transaction
324
- execute "BEGIN"
325
- rescue Exception
326
- # Transactions aren't supported
327
- end
328
-
329
- def commit_db_transaction
330
- execute "COMMIT"
331
- rescue Exception
332
- # Transactions aren't supported
333
- end
334
-
335
- def rollback_db_transaction
336
- execute "ROLLBACK"
337
- rescue Exception
338
- # Transactions aren't supported
339
- end
340
-
341
- def create_savepoint
342
- execute("SAVEPOINT #{current_savepoint_name}")
343
- end
344
-
345
- def rollback_to_savepoint
346
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
347
- end
348
-
349
- def release_savepoint
350
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
351
- end
352
-
353
- def add_limit_offset!(sql, options)
354
- limit, offset = options[:limit], options[:offset]
355
- if limit && offset
356
- sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
357
- elsif limit
358
- sql << " LIMIT #{sanitize_limit(limit)}"
359
- elsif offset
360
- sql << " OFFSET #{offset.to_i}"
361
- end
362
- sql
363
- end
364
- deprecate :add_limit_offset!
365
-
366
- # SCHEMA STATEMENTS ========================================
367
-
368
- def structure_dump
369
- if supports_views?
370
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
371
- else
372
- sql = "SHOW TABLES"
373
- end
374
-
375
- select_all(sql).inject("") do |structure, table|
376
- table.delete('Table_type')
377
- structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
378
- end
379
- end
380
-
381
- # Drops the database specified on the +name+ attribute
382
- # and creates it again using the provided +options+.
383
- def recreate_database(name, options = {})
384
- drop_database(name)
385
- create_database(name, options)
386
- end
387
-
388
- # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
389
- # Charset defaults to utf8.
390
- #
391
- # Example:
392
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
393
- # create_database 'matt_development'
394
- # create_database 'matt_development', :charset => :big5
395
- def create_database(name, options = {})
396
- if options[:collation]
397
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
398
- else
399
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
400
- end
401
- end
402
-
403
- # Drops a MySQL database.
404
- #
405
- # Example:
406
- # drop_database('sebastian_development')
407
- def drop_database(name) #:nodoc:
408
- execute "DROP DATABASE IF EXISTS `#{name}`"
409
- end
410
-
411
- def current_database
412
- select_value 'SELECT DATABASE() as db'
413
- end
414
-
415
- # Returns the database character set.
416
- def charset
417
- show_variable 'character_set_database'
418
- end
419
-
420
- # Returns the database collation strategy.
421
- def collation
422
- show_variable 'collation_database'
423
- end
424
-
425
- def tables(name = nil, database = nil) #:nodoc:
426
- sql = "SHOW TABLES "
427
- sql << "IN #{quote_table_name(database)} " if database
428
-
429
- execute(sql, 'SCHEMA').collect do |field|
430
- field.first
431
- end
432
- end
433
-
434
- def table_exists?(name)
435
- return true if super
436
-
437
- name = name.to_s
438
- schema, table = name.split('.', 2)
439
-
440
- unless table # A table was provided without a schema
441
- table = schema
442
- schema = nil
443
- end
444
-
445
- tables(nil, schema).include? table
446
- end
447
-
448
- def drop_table(table_name, options = {})
449
- super(table_name, options)
450
- end
451
-
452
- # Returns an array of indexes for the given table.
453
- def indexes(table_name, name = nil)
454
- indexes = []
455
- current_index = nil
456
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA')
457
- result.each(:symbolize_keys => true, :as => :hash) do |row|
458
- if current_index != row[:Key_name]
459
- next if row[:Key_name] == PRIMARY # skip the primary key
460
- current_index = row[:Key_name]
461
- indexes << Mysql2IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [], [])
462
- end
463
-
464
- indexes.last.columns << row[:Column_name]
465
- indexes.last.lengths << row[:Sub_part]
466
- end
467
- indexes
468
- end
469
-
470
- # Returns an array of +Mysql2Column+ objects for the table specified by +table_name+.
471
- def columns(table_name, name = nil)
472
- sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
473
- columns = []
474
- result = execute(sql, 'SCHEMA')
475
- result.each(:symbolize_keys => true, :as => :hash) { |field|
476
- columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
477
- }
478
- columns
479
- end
480
-
481
- def create_table(table_name, options = {})
482
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
483
- end
484
-
485
- # Renames a table.
486
- #
487
- # Example:
488
- # rename_table('octopuses', 'octopi')
489
- def rename_table(table_name, new_name)
490
- execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
491
- end
492
-
493
- def add_column(table_name, column_name, type, options = {})
494
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
495
- add_column_options!(add_column_sql, options)
496
- add_column_position!(add_column_sql, options)
497
- execute(add_column_sql)
498
- end
499
-
500
- def change_column_default(table_name, column_name, default)
501
- column = column_for(table_name, column_name)
502
- change_column table_name, column_name, column.sql_type, :default => default
503
- end
504
-
505
- def change_column_null(table_name, column_name, null, default = nil)
506
- column = column_for(table_name, column_name)
507
-
508
- unless null || default.nil?
509
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
510
- end
511
-
512
- change_column table_name, column_name, column.sql_type, :null => null
513
- end
514
-
515
- def change_column(table_name, column_name, type, options = {})
516
- column = column_for(table_name, column_name)
517
-
518
- unless options_include_default?(options)
519
- options[:default] = column.default
520
- end
521
-
522
- unless options.has_key?(:null)
523
- options[:null] = column.null
524
- end
525
-
526
- change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
527
- add_column_options!(change_column_sql, options)
528
- add_column_position!(change_column_sql, options)
529
- execute(change_column_sql)
530
- end
531
-
532
- def rename_column(table_name, column_name, new_column_name)
533
- options = {}
534
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
535
- options[:default] = column.default
536
- options[:null] = column.null
537
- else
538
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
539
- end
540
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
541
- rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
542
- add_column_options!(rename_column_sql, options)
543
- execute(rename_column_sql)
544
- end
545
-
546
- # Maps logical Rails types to MySQL-specific data types.
547
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
548
- case type.to_s
549
- when 'integer'
550
- case limit
551
- when 1; 'tinyint'
552
- when 2; 'smallint'
553
- when 3; 'mediumint'
554
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
555
- when 5..8; 'bigint'
556
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
557
- end
558
- when 'text'
559
- case limit
560
- when 0..0xff; 'tinytext'
561
- when nil, 0x100..0xffff; 'text'
562
- when 0x10000..0xffffff; 'mediumtext'
563
- when 0x1000000..0xffffffff; 'longtext'
564
- else raise(ActiveRecordError, "No text type has character length #{limit}")
565
- end
566
- else
567
- super
568
- end
569
- end
570
-
571
- def add_column_position!(sql, options)
572
- if options[:first]
573
- sql << " FIRST"
574
- elsif options[:after]
575
- sql << " AFTER #{quote_column_name(options[:after])}"
576
- end
577
- end
247
+ private
578
248
 
579
- # SHOW VARIABLES LIKE 'name'.
580
- def show_variable(name)
581
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
582
- variables.first['Value'] unless variables.empty?
249
+ def connect
250
+ @connection = Mysql2::Client.new(@config)
251
+ configure_connection
583
252
  end
584
253
 
585
- # Returns a table's primary key and belonging sequence.
586
- def pk_and_sequence_for(table)
587
- result = execute("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA')
588
- create_table = result.first[1]
254
+ def configure_connection
255
+ @connection.query_options.merge!(:as => :array)
589
256
 
590
- if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/
591
- keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
592
- keys.length == 1 ? [keys.first, nil] : nil
593
- else
594
- nil
595
- end
596
- end
597
-
598
- # Returns just a table's primary key
599
- def primary_key(table)
600
- pk_and_sequence = pk_and_sequence_for(table)
601
- pk_and_sequence && pk_and_sequence.first
602
- end
257
+ # By default, MySQL 'where id is null' selects the last inserted id.
258
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
259
+ variable_assignments = ['SQL_AUTO_IS_NULL=0']
260
+ encoding = @config[:encoding]
603
261
 
604
- def case_sensitive_equality_operator
605
- "= BINARY"
606
- end
607
- deprecate :case_sensitive_equality_operator
262
+ # make sure we set the encoding
263
+ variable_assignments << "NAMES '#{encoding}'" if encoding
608
264
 
609
- def case_sensitive_modifier(node)
610
- Arel::Nodes::Bin.new(node)
611
- end
265
+ # increase timeout so mysql server doesn't disconnect us
266
+ wait_timeout = @config[:wait_timeout]
267
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
268
+ variable_assignments << "@@wait_timeout = #{wait_timeout}"
612
269
 
613
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
614
- where_sql
270
+ execute("SET #{variable_assignments.join(', ')}", :skip_logging)
615
271
  end
616
272
 
617
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
618
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
619
- # these, we must use a subquery. However, MySQL is too stupid to create a
620
- # temporary table for this automatically, so we have to give it some prompting
621
- # in the form of a subsubquery. Ugh!
622
- def join_to_update(update, select) #:nodoc:
623
- if select.limit || select.offset || select.orders.any?
624
- subsubselect = select.clone
625
- subsubselect.projections = [update.key]
626
-
627
- subselect = Arel::SelectManager.new(select.engine)
628
- subselect.project Arel.sql(update.key.name)
629
- subselect.from subsubselect.as('__active_record_temp')
630
-
631
- update.where update.key.in(subselect)
632
- else
633
- update.table select.source
634
- update.wheres = select.constraints
635
- end
273
+ def version
274
+ @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
636
275
  end
637
-
638
- protected
639
- def quoted_columns_for_index(column_names, options = {})
640
- length = options[:length] if options.is_a?(Hash)
641
-
642
- quoted_column_names = case length
643
- when Hash
644
- column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
645
- when Fixnum
646
- column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
647
- else
648
- column_names.map {|name| quote_column_name(name) }
649
- end
650
- end
651
-
652
- def translate_exception(exception, message)
653
- return super unless exception.respond_to?(:error_number)
654
-
655
- case exception.error_number
656
- when 1062
657
- RecordNotUnique.new(message, exception)
658
- when 1452
659
- InvalidForeignKey.new(message, exception)
660
- else
661
- super
662
- end
663
- end
664
-
665
- private
666
- def connect
667
- @connection = Mysql2::Client.new(@config)
668
- configure_connection
669
- end
670
-
671
- def configure_connection
672
- @connection.query_options.merge!(:as => :array)
673
-
674
- # By default, MySQL 'where id is null' selects the last inserted id.
675
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
676
- variable_assignments = ['SQL_AUTO_IS_NULL=0']
677
- encoding = @config[:encoding]
678
-
679
- # make sure we set the encoding
680
- variable_assignments << "NAMES '#{encoding}'" if encoding
681
-
682
- # increase timeout so mysql server doesn't disconnect us
683
- wait_timeout = @config[:wait_timeout]
684
- wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
685
- variable_assignments << "@@wait_timeout = #{wait_timeout}"
686
-
687
- execute("SET #{variable_assignments.join(', ')}", :skip_logging)
688
- end
689
-
690
- # Returns an array of record hashes with the column names as keys and
691
- # column values as values.
692
- def select(sql, name = nil, binds = [])
693
- exec_query(sql, name).to_a
694
- end
695
-
696
- def exec_query(sql, name = 'SQL', binds = [])
697
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
698
-
699
- log(sql, name, binds) do
700
- begin
701
- result = @connection.query(sql)
702
- rescue ActiveRecord::StatementInvalid => exception
703
- if exception.message.split(":").first =~ /Packets out of order/
704
- 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."
705
- else
706
- raise
707
- end
708
- end
709
-
710
- ActiveRecord::Result.new(result.fields, result.to_a)
711
- end
712
- end
713
-
714
- def supports_views?
715
- version[0] >= 5
716
- end
717
-
718
- def version
719
- @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
720
- end
721
-
722
- def column_for(table_name, column_name)
723
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
724
- raise "No such column: #{table_name}.#{column_name}"
725
- end
726
- column
727
- end
728
276
  end
729
277
  end
730
278
  end