activerecord 3.1.12 → 3.2.22.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

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