activerecord 3.1.12 → 3.2.0.rc1

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 (99) hide show
  1. data/CHANGELOG.md +6263 -103
  2. data/README.rdoc +2 -2
  3. data/examples/performance.rb +55 -31
  4. data/lib/active_record.rb +28 -2
  5. data/lib/active_record/aggregations.rb +2 -2
  6. data/lib/active_record/associations.rb +82 -69
  7. data/lib/active_record/associations/association.rb +2 -37
  8. data/lib/active_record/associations/association_scope.rb +3 -30
  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 +55 -28
  16. data/lib/active_record/associations/collection_proxy.rb +1 -35
  17. data/lib/active_record/associations/has_many_association.rb +5 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +11 -8
  19. data/lib/active_record/associations/join_dependency.rb +1 -1
  20. data/lib/active_record/associations/preloader/association.rb +3 -1
  21. data/lib/active_record/attribute_assignment.rb +221 -0
  22. data/lib/active_record/attribute_methods.rb +212 -32
  23. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  24. data/lib/active_record/attribute_methods/dirty.rb +3 -3
  25. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  26. data/lib/active_record/attribute_methods/read.rb +69 -80
  27. data/lib/active_record/attribute_methods/serialization.rb +89 -0
  28. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
  29. data/lib/active_record/attribute_methods/write.rb +27 -5
  30. data/lib/active_record/autosave_association.rb +23 -8
  31. data/lib/active_record/base.rb +223 -1712
  32. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
  33. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -13
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
  40. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  41. data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
  42. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
  43. data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
  44. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  45. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  46. data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
  47. data/lib/active_record/counter_cache.rb +1 -1
  48. data/lib/active_record/dynamic_matchers.rb +79 -0
  49. data/lib/active_record/errors.rb +11 -1
  50. data/lib/active_record/explain.rb +83 -0
  51. data/lib/active_record/explain_subscriber.rb +21 -0
  52. data/lib/active_record/fixtures.rb +31 -76
  53. data/lib/active_record/fixtures/file.rb +65 -0
  54. data/lib/active_record/identity_map.rb +1 -7
  55. data/lib/active_record/inheritance.rb +167 -0
  56. data/lib/active_record/integration.rb +49 -0
  57. data/lib/active_record/locking/optimistic.rb +19 -11
  58. data/lib/active_record/locking/pessimistic.rb +1 -1
  59. data/lib/active_record/log_subscriber.rb +3 -3
  60. data/lib/active_record/migration.rb +38 -29
  61. data/lib/active_record/migration/command_recorder.rb +7 -7
  62. data/lib/active_record/model_schema.rb +362 -0
  63. data/lib/active_record/nested_attributes.rb +3 -2
  64. data/lib/active_record/persistence.rb +51 -1
  65. data/lib/active_record/querying.rb +58 -0
  66. data/lib/active_record/railtie.rb +24 -28
  67. data/lib/active_record/railties/controller_runtime.rb +3 -1
  68. data/lib/active_record/railties/databases.rake +133 -77
  69. data/lib/active_record/readonly_attributes.rb +26 -0
  70. data/lib/active_record/reflection.rb +7 -15
  71. data/lib/active_record/relation.rb +78 -35
  72. data/lib/active_record/relation/batches.rb +5 -2
  73. data/lib/active_record/relation/calculations.rb +27 -6
  74. data/lib/active_record/relation/delegation.rb +49 -0
  75. data/lib/active_record/relation/finder_methods.rb +5 -4
  76. data/lib/active_record/relation/predicate_builder.rb +13 -16
  77. data/lib/active_record/relation/query_methods.rb +59 -4
  78. data/lib/active_record/result.rb +1 -1
  79. data/lib/active_record/sanitization.rb +194 -0
  80. data/lib/active_record/schema_dumper.rb +5 -2
  81. data/lib/active_record/scoping.rb +152 -0
  82. data/lib/active_record/scoping/default.rb +140 -0
  83. data/lib/active_record/scoping/named.rb +202 -0
  84. data/lib/active_record/serialization.rb +1 -43
  85. data/lib/active_record/serializers/xml_serializer.rb +2 -44
  86. data/lib/active_record/session_store.rb +11 -11
  87. data/lib/active_record/store.rb +50 -0
  88. data/lib/active_record/test_case.rb +11 -7
  89. data/lib/active_record/timestamp.rb +16 -3
  90. data/lib/active_record/transactions.rb +5 -5
  91. data/lib/active_record/translation.rb +22 -0
  92. data/lib/active_record/validations.rb +1 -1
  93. data/lib/active_record/validations/associated.rb +5 -4
  94. data/lib/active_record/validations/uniqueness.rb +4 -4
  95. data/lib/active_record/version.rb +3 -3
  96. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  97. metadata +48 -38
  98. checksums.yaml +0 -7
  99. 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,191 +20,53 @@ 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
130
35
  configure_connection
131
36
  end
132
37
 
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?
38
+ def supports_explain?
156
39
  true
157
40
  end
158
41
 
159
- def native_database_types
160
- NATIVE_DATABASE_TYPES
161
- end
162
-
163
- # QUOTING ==================================================
42
+ # HELPER METHODS ===========================================
164
43
 
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}'"
44
+ def each_hash(result) # :nodoc:
45
+ if block_given?
46
+ result.each(:as => :hash, :symbolize_keys => true) do |row|
47
+ yield row
48
+ end
169
49
  else
170
- super
50
+ to_enum(:each_hash, result)
171
51
  end
172
52
  end
173
53
 
174
- def quote_column_name(name) #:nodoc:
175
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
54
+ def new_column(field, default, type, null, collation) # :nodoc:
55
+ Column.new(field, default, type, null, collation)
176
56
  end
177
57
 
178
- def quote_table_name(name) #:nodoc:
179
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
58
+ def error_number(exception)
59
+ exception.error_number if exception.respond_to?(:error_number)
180
60
  end
181
61
 
62
+ # QUOTING ==================================================
63
+
182
64
  def quote_string(string)
183
65
  @connection.escape(string)
184
66
  end
185
67
 
186
- def quoted_true
187
- QUOTED_TRUE
188
- end
189
-
190
- def quoted_false
191
- QUOTED_FALSE
192
- end
193
-
194
68
  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
69
+ Arel.sql "\0"
209
70
  end
210
71
 
211
72
  # CONNECTION MANAGEMENT ====================================
@@ -220,11 +81,6 @@ module ActiveRecord
220
81
  connect
221
82
  end
222
83
 
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
84
  # Disconnects from the database if already connected.
229
85
  # Otherwise, this method does nothing.
230
86
  def disconnect!
@@ -241,6 +97,80 @@ module ActiveRecord
241
97
 
242
98
  # DATABASE STATEMENTS ======================================
243
99
 
100
+ def explain(arel, binds = [])
101
+ sql = "EXPLAIN #{to_sql(arel)}"
102
+ start = Time.now
103
+ result = exec_query(sql, 'EXPLAIN', binds)
104
+ elapsed = Time.now - start
105
+
106
+ ExplainPrettyPrinter.new.pp(result, elapsed)
107
+ end
108
+
109
+ class ExplainPrettyPrinter # :nodoc:
110
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
111
+ # MySQL shell:
112
+ #
113
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
114
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
115
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
116
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
117
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
118
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
119
+ # 2 rows in set (0.00 sec)
120
+ #
121
+ # This is an exercise in Ruby hyperrealism :).
122
+ def pp(result, elapsed)
123
+ widths = compute_column_widths(result)
124
+ separator = build_separator(widths)
125
+
126
+ pp = []
127
+
128
+ pp << separator
129
+ pp << build_cells(result.columns, widths)
130
+ pp << separator
131
+
132
+ result.rows.each do |row|
133
+ pp << build_cells(row, widths)
134
+ end
135
+
136
+ pp << separator
137
+ pp << build_footer(result.rows.length, elapsed)
138
+
139
+ pp.join("\n") + "\n"
140
+ end
141
+
142
+ private
143
+
144
+ def compute_column_widths(result)
145
+ [].tap do |widths|
146
+ result.columns.each_with_index do |column, i|
147
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
148
+ widths << cells_in_column.map(&:length).max
149
+ end
150
+ end
151
+ end
152
+
153
+ def build_separator(widths)
154
+ padding = 1
155
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
156
+ end
157
+
158
+ def build_cells(items, widths)
159
+ cells = []
160
+ items.each_with_index do |item, i|
161
+ item = 'NULL' if item.nil?
162
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
163
+ cells << item.to_s.send(justifier, widths[i])
164
+ end
165
+ '| ' + cells.join(' | ') + ' |'
166
+ end
167
+
168
+ def build_footer(nrows, elapsed)
169
+ rows_label = nrows == 1 ? 'row' : 'rows'
170
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
171
+ end
172
+ end
173
+
244
174
  # FIXME: re-enable the following once a "better" query_cache solution is in core
245
175
  #
246
176
  # The overrides below perform much better than the originals in AbstractAdapter
@@ -280,449 +210,79 @@ module ActiveRecord
280
210
  # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
281
211
  # made since we established the connection
282
212
  @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
293
- end
294
- end
295
213
 
296
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
297
214
  super
298
- id_value || @connection.last_id
299
215
  end
300
- alias :create :insert_sql
301
216
 
302
- def exec_insert(sql, name, binds)
303
- execute to_sql(sql, binds), name
217
+ def exec_query(sql, name = 'SQL', binds = [])
218
+ result = execute(sql, name)
219
+ ActiveRecord::Result.new(result.fields, result.to_a)
304
220
  end
305
221
 
306
- def exec_delete(sql, name, binds)
307
- execute to_sql(sql, binds), name
308
- @connection.affected_rows
309
- end
310
- alias :exec_update :exec_delete
222
+ alias exec_without_stmt exec_query
311
223
 
312
- def last_inserted_id(result)
313
- @connection.last_id
224
+ # Returns an array of record hashes with the column names as keys and
225
+ # column values as values.
226
+ def select(sql, name = nil, binds = [])
227
+ binds = binds.dup
228
+ exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
314
229
  end
315
230
 
316
- def update_sql(sql, name = nil)
231
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
317
232
  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
233
+ id_value || @connection.last_id
501
234
  end
235
+ alias :create :insert_sql
502
236
 
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
237
+ def exec_insert(sql, name, binds)
238
+ binds = binds.dup
509
239
 
510
- change_column table_name, column_name, column.sql_type, :null => null
240
+ # Pretend to support bind parameters
241
+ execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
511
242
  end
512
243
 
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
244
+ def exec_delete(sql, name, binds)
245
+ binds = binds.dup
529
246
 
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)
247
+ # Pretend to support bind parameters
248
+ execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
249
+ @connection.affected_rows
542
250
  end
251
+ alias :exec_update :exec_delete
543
252
 
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
253
+ def last_inserted_id(result)
254
+ @connection.last_id
567
255
  end
568
256
 
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
257
+ private
576
258
 
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?
259
+ def connect
260
+ @connection = Mysql2::Client.new(@config)
261
+ configure_connection
581
262
  end
582
263
 
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]
264
+ def configure_connection
265
+ @connection.query_options.merge!(:as => :array)
587
266
 
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
595
-
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
267
+ # By default, MySQL 'where id is null' selects the last inserted id.
268
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
269
+ variable_assignments = ['SQL_AUTO_IS_NULL=0']
270
+ encoding = @config[:encoding]
601
271
 
602
- def case_sensitive_equality_operator
603
- "= BINARY"
604
- end
605
- deprecate :case_sensitive_equality_operator
272
+ # make sure we set the encoding
273
+ variable_assignments << "NAMES '#{encoding}'" if encoding
606
274
 
607
- def case_sensitive_modifier(node)
608
- Arel::Nodes::Bin.new(node)
609
- end
275
+ # increase timeout so mysql server doesn't disconnect us
276
+ wait_timeout = @config[:wait_timeout]
277
+ wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
278
+ variable_assignments << "@@wait_timeout = #{wait_timeout}"
610
279
 
611
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
612
- where_sql
280
+ execute("SET #{variable_assignments.join(', ')}", :skip_logging)
613
281
  end
614
282
 
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
283
+ def version
284
+ @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
634
285
  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
286
  end
727
287
  end
728
288
  end