activerecord 3.0.0 → 4.0.0

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 (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,12 +1,24 @@
1
- require 'active_record/connection_adapters/abstract_adapter'
2
- require 'active_support/core_ext/kernel/requires'
3
- require 'active_support/core_ext/object/blank'
4
- require 'set'
1
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
2
+ require 'active_record/connection_adapters/statement_pool'
3
+ require 'active_support/core_ext/hash/keys'
4
+
5
+ gem 'mysql', '~> 2.9'
6
+ require 'mysql'
7
+
8
+ class Mysql
9
+ class Time
10
+ def to_date
11
+ Date.new(year, month, day)
12
+ end
13
+ end
14
+ class Stmt; include Enumerable end
15
+ class Result; include Enumerable end
16
+ end
5
17
 
6
18
  module ActiveRecord
7
- class Base
19
+ module ConnectionHandling # :nodoc:
8
20
  # Establishes a connection to the database that's used by all Active Record objects.
9
- def self.mysql_connection(config) # :nodoc:
21
+ def mysql_connection(config)
10
22
  config = config.symbolize_keys
11
23
  host = config[:host]
12
24
  port = config[:port]
@@ -15,18 +27,6 @@ module ActiveRecord
15
27
  password = config[:password].to_s
16
28
  database = config[:database]
17
29
 
18
- unless defined? Mysql
19
- begin
20
- require 'mysql'
21
- rescue LoadError
22
- raise "!!! Missing the mysql gem. Add it to your Gemfile: gem 'mysql', '2.8.1'"
23
- end
24
-
25
- unless defined?(Mysql::Result) && Mysql::Result.method_defined?(:each_hash)
26
- raise "!!! Outdated mysql gem. Upgrade to 2.8.1 or later. In your Gemfile: gem 'mysql', '2.8.1'"
27
- end
28
- end
29
-
30
30
  mysql = Mysql.init
31
31
  mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
32
32
 
@@ -38,68 +38,6 @@ module ActiveRecord
38
38
  end
39
39
 
40
40
  module ConnectionAdapters
41
- class MysqlColumn < Column #:nodoc:
42
- def extract_default(default)
43
- if sql_type =~ /blob/i || type == :text
44
- if default.blank?
45
- return null ? nil : ''
46
- else
47
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
48
- end
49
- elsif missing_default_forged_as_empty_string?(default)
50
- nil
51
- else
52
- super
53
- end
54
- end
55
-
56
- def has_default?
57
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
58
- super
59
- end
60
-
61
- private
62
- def simplified_type(field_type)
63
- return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
64
- return :string if field_type =~ /enum/i
65
- super
66
- end
67
-
68
- def extract_limit(sql_type)
69
- case sql_type
70
- when /blob|text/i
71
- case sql_type
72
- when /tiny/i
73
- 255
74
- when /medium/i
75
- 16777215
76
- when /long/i
77
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
78
- else
79
- super # we could return 65535 here, but we leave it undecorated by default
80
- end
81
- when /^bigint/i; 8
82
- when /^int/i; 4
83
- when /^mediumint/i; 3
84
- when /^smallint/i; 2
85
- when /^tinyint/i; 1
86
- else
87
- super
88
- end
89
- end
90
-
91
- # MySQL misreports NOT NULL column default when none is given.
92
- # We can't detect this for columns which may have a legitimate ''
93
- # default (string) but we can for others (integer, datetime, boolean,
94
- # and the rest).
95
- #
96
- # Test whether the column has default '', is not null, and is not
97
- # a type allowing default ''.
98
- def missing_default_forged_as_empty_string?(default)
99
- type != :string && !null && default == ''
100
- end
101
- end
102
-
103
41
  # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
104
42
  # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
105
43
  #
@@ -113,122 +51,123 @@ module ActiveRecord
113
51
  # * <tt>:database</tt> - The name of the database. No default, must be provided.
114
52
  # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
115
53
  # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
54
+ # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html)
55
+ # * <tt>:variables</tt> - (Optional) A hash session variables to send as `SET @@SESSION.key = value` on each database connection. Use the value `:default` to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html).
116
56
  # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
117
57
  # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
118
58
  # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
119
59
  # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
120
60
  # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
121
61
  #
122
- class MysqlAdapter < AbstractAdapter
123
-
124
- ##
125
- # :singleton-method:
126
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
127
- # as boolean. If you wish to disable this emulation (which was the default
128
- # behavior in versions 0.13.1 and earlier) you can add the following line
129
- # to your application.rb file:
130
- #
131
- # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
132
- cattr_accessor :emulate_booleans
133
- self.emulate_booleans = true
134
-
135
- ADAPTER_NAME = 'MySQL'.freeze
136
-
137
- LOST_CONNECTION_ERROR_MESSAGES = [
138
- "Server shutdown in progress",
139
- "Broken pipe",
140
- "Lost connection to MySQL server during query",
141
- "MySQL server has gone away" ]
142
-
143
- QUOTED_TRUE, QUOTED_FALSE = '1'.freeze, '0'.freeze
144
-
145
- NATIVE_DATABASE_TYPES = {
146
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze,
147
- :string => { :name => "varchar", :limit => 255 },
148
- :text => { :name => "text" },
149
- :integer => { :name => "int", :limit => 4 },
150
- :float => { :name => "float" },
151
- :decimal => { :name => "decimal" },
152
- :datetime => { :name => "datetime" },
153
- :timestamp => { :name => "datetime" },
154
- :time => { :name => "time" },
155
- :date => { :name => "date" },
156
- :binary => { :name => "blob" },
157
- :boolean => { :name => "tinyint", :limit => 1 }
158
- }
62
+ class MysqlAdapter < AbstractMysqlAdapter
159
63
 
160
- def initialize(connection, logger, connection_options, config)
161
- super(connection, logger)
162
- @connection_options, @config = connection_options, config
163
- @quoted_column_names, @quoted_table_names = {}, {}
164
- connect
165
- end
64
+ class Column < AbstractMysqlAdapter::Column #:nodoc:
65
+ def self.string_to_time(value)
66
+ return super unless Mysql::Time === value
67
+ new_time(
68
+ value.year,
69
+ value.month,
70
+ value.day,
71
+ value.hour,
72
+ value.minute,
73
+ value.second,
74
+ value.second_part)
75
+ end
166
76
 
167
- def adapter_name #:nodoc:
168
- ADAPTER_NAME
169
- end
77
+ def self.string_to_dummy_time(v)
78
+ return super unless Mysql::Time === v
79
+ new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part)
80
+ end
170
81
 
171
- def supports_migrations? #:nodoc:
172
- true
173
- end
82
+ def self.string_to_date(v)
83
+ return super unless Mysql::Time === v
84
+ new_date(v.year, v.month, v.day)
85
+ end
174
86
 
175
- def supports_primary_key? #:nodoc:
176
- true
87
+ def adapter
88
+ MysqlAdapter
89
+ end
177
90
  end
178
91
 
179
- def supports_savepoints? #:nodoc:
180
- true
181
- end
92
+ ADAPTER_NAME = 'MySQL'
182
93
 
183
- def native_database_types #:nodoc:
184
- NATIVE_DATABASE_TYPES
185
- end
94
+ class StatementPool < ConnectionAdapters::StatementPool
95
+ def initialize(connection, max = 1000)
96
+ super
97
+ @cache = Hash.new { |h,pid| h[pid] = {} }
98
+ end
186
99
 
100
+ def each(&block); cache.each(&block); end
101
+ def key?(key); cache.key?(key); end
102
+ def [](key); cache[key]; end
103
+ def length; cache.length; end
104
+ def delete(key); cache.delete(key); end
187
105
 
188
- # QUOTING ==================================================
106
+ def []=(sql, key)
107
+ while @max <= cache.size
108
+ cache.shift.last[:stmt].close
109
+ end
110
+ cache[sql] = key
111
+ end
189
112
 
190
- def quote(value, column = nil)
191
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
192
- s = column.class.string_to_binary(value).unpack("H*")[0]
193
- "x'#{s}'"
194
- elsif value.kind_of?(BigDecimal)
195
- value.to_s("F")
196
- else
197
- super
113
+ def clear
114
+ cache.values.each do |hash|
115
+ hash[:stmt].close
116
+ end
117
+ cache.clear
118
+ end
119
+
120
+ private
121
+ def cache
122
+ @cache[Process.pid]
198
123
  end
199
124
  end
200
125
 
201
- def quote_column_name(name) #:nodoc:
202
- @quoted_column_names[name] ||= "`#{name}`"
126
+ def initialize(connection, logger, connection_options, config)
127
+ super
128
+ @statements = StatementPool.new(@connection,
129
+ self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
130
+ @client_encoding = nil
131
+ connect
203
132
  end
204
133
 
205
- def quote_table_name(name) #:nodoc:
206
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
134
+ # Returns true, since this connection adapter supports prepared statement
135
+ # caching.
136
+ def supports_statement_cache?
137
+ true
207
138
  end
208
139
 
209
- def quote_string(string) #:nodoc:
210
- @connection.quote(string)
140
+ # HELPER METHODS ===========================================
141
+
142
+ def each_hash(result) # :nodoc:
143
+ if block_given?
144
+ result.each_hash do |row|
145
+ row.symbolize_keys!
146
+ yield row
147
+ end
148
+ else
149
+ to_enum(:each_hash, result)
150
+ end
211
151
  end
212
152
 
213
- def quoted_true
214
- QUOTED_TRUE
153
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
154
+ Column.new(field, default, type, null, collation, strict_mode?, extra)
215
155
  end
216
156
 
217
- def quoted_false
218
- QUOTED_FALSE
157
+ def error_number(exception) # :nodoc:
158
+ exception.errno if exception.respond_to?(:errno)
219
159
  end
220
160
 
221
- # REFERENTIAL INTEGRITY ====================================
161
+ # QUOTING ==================================================
222
162
 
223
- def disable_referential_integrity #:nodoc:
224
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
163
+ def type_cast(value, column)
164
+ return super unless value == true || value == false
225
165
 
226
- begin
227
- update("SET FOREIGN_KEY_CHECKS = 0")
228
- yield
229
- ensure
230
- update("SET FOREIGN_KEY_CHECKS = #{old}")
231
- end
166
+ value ? 1 : 0
167
+ end
168
+
169
+ def quote_string(string) #:nodoc:
170
+ @connection.quote(string)
232
171
  end
233
172
 
234
173
  # CONNECTION MANAGEMENT ====================================
@@ -251,11 +190,15 @@ module ActiveRecord
251
190
  end
252
191
 
253
192
  def reconnect!
193
+ super
254
194
  disconnect!
255
195
  connect
256
196
  end
257
197
 
198
+ # Disconnects from the database if already connected. Otherwise, this
199
+ # method does nothing.
258
200
  def disconnect!
201
+ super
259
202
  @connection.close rescue nil
260
203
  end
261
204
 
@@ -272,372 +215,344 @@ module ActiveRecord
272
215
 
273
216
  def select_rows(sql, name = nil)
274
217
  @connection.query_with_result = true
275
- result = execute(sql, name)
276
- rows = []
277
- result.each { |row| rows << row }
278
- result.free
279
- @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
218
+ rows = exec_query(sql, name).rows
219
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
280
220
  rows
281
221
  end
282
222
 
283
- # Executes an SQL query and returns a MySQL::Result object. Note that you have to free
284
- # the Result object after you're done using it.
285
- def execute(sql, name = nil) #:nodoc:
286
- if name == :skip_logging
287
- @connection.query(sql)
288
- else
289
- log(sql, name) { @connection.query(sql) }
290
- end
291
- rescue ActiveRecord::StatementInvalid => exception
292
- if exception.message.split(":").first =~ /Packets out of order/
293
- 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."
294
- else
295
- raise
296
- end
297
- end
223
+ # Clears the prepared statements cache.
224
+ def clear_cache!
225
+ @statements.clear
226
+ end
227
+
228
+ # Taken from here:
229
+ # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb
230
+ # Author: TOMITA Masahiro <tommy@tmtm.org>
231
+ ENCODINGS = {
232
+ "armscii8" => nil,
233
+ "ascii" => Encoding::US_ASCII,
234
+ "big5" => Encoding::Big5,
235
+ "binary" => Encoding::ASCII_8BIT,
236
+ "cp1250" => Encoding::Windows_1250,
237
+ "cp1251" => Encoding::Windows_1251,
238
+ "cp1256" => Encoding::Windows_1256,
239
+ "cp1257" => Encoding::Windows_1257,
240
+ "cp850" => Encoding::CP850,
241
+ "cp852" => Encoding::CP852,
242
+ "cp866" => Encoding::IBM866,
243
+ "cp932" => Encoding::Windows_31J,
244
+ "dec8" => nil,
245
+ "eucjpms" => Encoding::EucJP_ms,
246
+ "euckr" => Encoding::EUC_KR,
247
+ "gb2312" => Encoding::EUC_CN,
248
+ "gbk" => Encoding::GBK,
249
+ "geostd8" => nil,
250
+ "greek" => Encoding::ISO_8859_7,
251
+ "hebrew" => Encoding::ISO_8859_8,
252
+ "hp8" => nil,
253
+ "keybcs2" => nil,
254
+ "koi8r" => Encoding::KOI8_R,
255
+ "koi8u" => Encoding::KOI8_U,
256
+ "latin1" => Encoding::ISO_8859_1,
257
+ "latin2" => Encoding::ISO_8859_2,
258
+ "latin5" => Encoding::ISO_8859_9,
259
+ "latin7" => Encoding::ISO_8859_13,
260
+ "macce" => Encoding::MacCentEuro,
261
+ "macroman" => Encoding::MacRoman,
262
+ "sjis" => Encoding::SHIFT_JIS,
263
+ "swe7" => nil,
264
+ "tis620" => Encoding::TIS_620,
265
+ "ucs2" => Encoding::UTF_16BE,
266
+ "ujis" => Encoding::EucJP_ms,
267
+ "utf8" => Encoding::UTF_8,
268
+ "utf8mb4" => Encoding::UTF_8,
269
+ }
298
270
 
299
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
300
- super sql, name
301
- id_value || @connection.insert_id
302
- end
303
- alias :create :insert_sql
271
+ # Get the client encoding for this database
272
+ def client_encoding
273
+ return @client_encoding if @client_encoding
304
274
 
305
- def update_sql(sql, name = nil) #:nodoc:
306
- super
307
- @connection.affected_rows
275
+ result = exec_query(
276
+ "SHOW VARIABLES WHERE Variable_name = 'character_set_client'",
277
+ 'SCHEMA')
278
+ @client_encoding = ENCODINGS[result.rows.last.last]
308
279
  end
309
280
 
310
- def begin_db_transaction #:nodoc:
311
- execute "BEGIN"
312
- rescue Exception
313
- # Transactions aren't supported
314
- end
281
+ def exec_query(sql, name = 'SQL', binds = [])
282
+ # If the configuration sets prepared_statements:false, binds will
283
+ # always be empty, since the bind variables will have been already
284
+ # substituted and removed from binds by BindVisitor, so this will
285
+ # effectively disable prepared statement usage completely.
286
+ if binds.empty?
287
+ result_set, affected_rows = exec_without_stmt(sql, name)
288
+ else
289
+ result_set, affected_rows = exec_stmt(sql, name, binds)
290
+ end
315
291
 
316
- def commit_db_transaction #:nodoc:
317
- execute "COMMIT"
318
- rescue Exception
319
- # Transactions aren't supported
320
- end
292
+ yield affected_rows if block_given?
321
293
 
322
- def rollback_db_transaction #:nodoc:
323
- execute "ROLLBACK"
324
- rescue Exception
325
- # Transactions aren't supported
294
+ result_set
326
295
  end
327
296
 
328
- def create_savepoint
329
- execute("SAVEPOINT #{current_savepoint_name}")
297
+ def last_inserted_id(result)
298
+ @connection.insert_id
330
299
  end
331
300
 
332
- def rollback_to_savepoint
333
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
334
- end
301
+ module Fields
302
+ class Type
303
+ def type; end
335
304
 
336
- def release_savepoint
337
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
338
- end
305
+ def type_cast_for_write(value)
306
+ value
307
+ end
308
+ end
339
309
 
340
- def add_limit_offset!(sql, options) #:nodoc:
341
- limit, offset = options[:limit], options[:offset]
342
- if limit && offset
343
- sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
344
- elsif limit
345
- sql << " LIMIT #{sanitize_limit(limit)}"
346
- elsif offset
347
- sql << " OFFSET #{offset.to_i}"
310
+ class Identity < Type
311
+ def type_cast(value); value; end
348
312
  end
349
- sql
350
- end
351
313
 
352
- # SCHEMA STATEMENTS ========================================
314
+ class Integer < Type
315
+ def type_cast(value)
316
+ return if value.nil?
353
317
 
354
- def structure_dump #:nodoc:
355
- if supports_views?
356
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
357
- else
358
- sql = "SHOW TABLES"
318
+ value.to_i rescue value ? 1 : 0
319
+ end
359
320
  end
360
321
 
361
- select_all(sql).inject("") do |structure, table|
362
- table.delete('Table_type')
363
- structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
364
- end
365
- end
322
+ class Date < Type
323
+ def type; :date; end
366
324
 
367
- def recreate_database(name, options = {}) #:nodoc:
368
- drop_database(name)
369
- create_database(name, options)
370
- end
325
+ def type_cast(value)
326
+ return if value.nil?
371
327
 
372
- # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
373
- # Charset defaults to utf8.
374
- #
375
- # Example:
376
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
377
- # create_database 'matt_development'
378
- # create_database 'matt_development', :charset => :big5
379
- def create_database(name, options = {})
380
- if options[:collation]
381
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
382
- else
383
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
328
+ # FIXME: probably we can improve this since we know it is mysql
329
+ # specific
330
+ ConnectionAdapters::Column.value_to_date value
331
+ end
384
332
  end
385
- end
386
333
 
387
- def drop_database(name) #:nodoc:
388
- execute "DROP DATABASE IF EXISTS `#{name}`"
389
- end
334
+ class DateTime < Type
335
+ def type; :datetime; end
390
336
 
391
- def current_database
392
- select_value 'SELECT DATABASE() as db'
393
- end
337
+ def type_cast(value)
338
+ return if value.nil?
394
339
 
395
- # Returns the database character set.
396
- def charset
397
- show_variable 'character_set_database'
398
- end
399
-
400
- # Returns the database collation strategy.
401
- def collation
402
- show_variable 'collation_database'
403
- end
340
+ # FIXME: probably we can improve this since we know it is mysql
341
+ # specific
342
+ ConnectionAdapters::Column.string_to_time value
343
+ end
344
+ end
404
345
 
405
- def tables(name = nil) #:nodoc:
406
- tables = []
407
- result = execute("SHOW TABLES", name)
408
- result.each { |field| tables << field[0] }
409
- result.free
410
- tables
411
- end
346
+ class Time < Type
347
+ def type; :time; end
412
348
 
413
- def drop_table(table_name, options = {})
414
- super(table_name, options)
415
- end
349
+ def type_cast(value)
350
+ return if value.nil?
416
351
 
417
- def indexes(table_name, name = nil)#:nodoc:
418
- indexes = []
419
- current_index = nil
420
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
421
- result.each do |row|
422
- if current_index != row[2]
423
- next if row[2] == "PRIMARY" # skip the primary key
424
- current_index = row[2]
425
- indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
352
+ # FIXME: probably we can improve this since we know it is mysql
353
+ # specific
354
+ ConnectionAdapters::Column.string_to_dummy_time value
426
355
  end
427
-
428
- indexes.last.columns << row[4]
429
- indexes.last.lengths << row[7]
430
356
  end
431
- result.free
432
- indexes
433
- end
434
357
 
435
- def columns(table_name, name = nil)#:nodoc:
436
- sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
437
- columns = []
438
- result = execute(sql, :skip_logging)
439
- result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
440
- result.free
441
- columns
442
- end
358
+ class Float < Type
359
+ def type; :float; end
443
360
 
444
- def create_table(table_name, options = {}) #:nodoc:
445
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
446
- end
361
+ def type_cast(value)
362
+ return if value.nil?
447
363
 
448
- def rename_table(table_name, new_name)
449
- execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
450
- end
364
+ value.to_f
365
+ end
366
+ end
451
367
 
452
- def add_column(table_name, column_name, type, options = {})
453
- 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])}"
454
- add_column_options!(add_column_sql, options)
455
- add_column_position!(add_column_sql, options)
456
- execute(add_column_sql)
457
- end
368
+ class Decimal < Type
369
+ def type_cast(value)
370
+ return if value.nil?
458
371
 
459
- def change_column_default(table_name, column_name, default) #:nodoc:
460
- column = column_for(table_name, column_name)
461
- change_column table_name, column_name, column.sql_type, :default => default
462
- end
372
+ ConnectionAdapters::Column.value_to_decimal value
373
+ end
374
+ end
463
375
 
464
- def change_column_null(table_name, column_name, null, default = nil)
465
- column = column_for(table_name, column_name)
376
+ class Boolean < Type
377
+ def type_cast(value)
378
+ return if value.nil?
466
379
 
467
- unless null || default.nil?
468
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
380
+ ConnectionAdapters::Column.value_to_boolean value
381
+ end
469
382
  end
470
383
 
471
- change_column table_name, column_name, column.sql_type, :null => null
472
- end
473
-
474
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
475
- column = column_for(table_name, column_name)
384
+ TYPES = {}
476
385
 
477
- unless options_include_default?(options)
478
- options[:default] = column.default
386
+ # Register an MySQL +type_id+ with a typecasting object in
387
+ # +type+.
388
+ def self.register_type(type_id, type)
389
+ TYPES[type_id] = type
479
390
  end
480
391
 
481
- unless options.has_key?(:null)
482
- options[:null] = column.null
392
+ def self.alias_type(new, old)
393
+ TYPES[new] = TYPES[old]
483
394
  end
484
395
 
485
- 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])}"
486
- add_column_options!(change_column_sql, options)
487
- add_column_position!(change_column_sql, options)
488
- execute(change_column_sql)
489
- end
396
+ register_type Mysql::Field::TYPE_TINY, Fields::Boolean.new
397
+ register_type Mysql::Field::TYPE_LONG, Fields::Integer.new
398
+ alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG
399
+ alias_type Mysql::Field::TYPE_NEWDECIMAL, Mysql::Field::TYPE_LONG
490
400
 
491
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
492
- options = {}
493
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
494
- options[:default] = column.default
495
- options[:null] = column.null
496
- else
497
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
498
- end
499
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
500
- rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
501
- add_column_options!(rename_column_sql, options)
502
- execute(rename_column_sql)
503
- end
401
+ register_type Mysql::Field::TYPE_VAR_STRING, Fields::Identity.new
402
+ register_type Mysql::Field::TYPE_BLOB, Fields::Identity.new
403
+ register_type Mysql::Field::TYPE_DATE, Fields::Date.new
404
+ register_type Mysql::Field::TYPE_DATETIME, Fields::DateTime.new
405
+ register_type Mysql::Field::TYPE_TIME, Fields::Time.new
406
+ register_type Mysql::Field::TYPE_FLOAT, Fields::Float.new
504
407
 
505
- # Maps logical Rails types to MySQL-specific data types.
506
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
507
- return super unless type.to_s == 'integer'
508
-
509
- case limit
510
- when 1; 'tinyint'
511
- when 2; 'smallint'
512
- when 3; 'mediumint'
513
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
514
- when 5..8; 'bigint'
515
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
408
+ Mysql::Field.constants.grep(/TYPE/).map { |class_name|
409
+ Mysql::Field.const_get class_name
410
+ }.reject { |const| TYPES.key? const }.each do |const|
411
+ register_type const, Fields::Identity.new
516
412
  end
517
413
  end
518
414
 
519
- def add_column_position!(sql, options)
520
- if options[:first]
521
- sql << " FIRST"
522
- elsif options[:after]
523
- sql << " AFTER #{quote_column_name(options[:after])}"
524
- end
525
- end
415
+ def exec_without_stmt(sql, name = 'SQL') # :nodoc:
416
+ # Some queries, like SHOW CREATE TABLE don't work through the prepared
417
+ # statement API. For those queries, we need to use this method. :'(
418
+ log(sql, name) do
419
+ result = @connection.query(sql)
420
+ affected_rows = @connection.affected_rows
526
421
 
527
- # SHOW VARIABLES LIKE 'name'
528
- def show_variable(name)
529
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
530
- variables.first['Value'] unless variables.empty?
531
- end
422
+ if result
423
+ types = {}
424
+ result.fetch_fields.each { |field|
425
+ if field.decimals > 0
426
+ types[field.name] = Fields::Decimal.new
427
+ else
428
+ types[field.name] = Fields::TYPES.fetch(field.type) {
429
+ Fields::Identity.new
430
+ }
431
+ end
432
+ }
433
+ result_set = ActiveRecord::Result.new(types.keys, result.to_a, types)
434
+ result.free
435
+ else
436
+ result_set = ActiveRecord::Result.new([], [])
437
+ end
532
438
 
533
- # Returns a table's primary key and belonging sequence.
534
- def pk_and_sequence_for(table) #:nodoc:
535
- keys = []
536
- result = execute("describe #{quote_table_name(table)}")
537
- result.each_hash do |h|
538
- keys << h["Field"]if h["Key"] == "PRI"
439
+ [result_set, affected_rows]
539
440
  end
441
+ end
442
+
443
+ def execute_and_free(sql, name = nil)
444
+ result = execute(sql, name)
445
+ ret = yield result
540
446
  result.free
541
- keys.length == 1 ? [keys.first, nil] : nil
447
+ ret
542
448
  end
543
449
 
544
- # Returns just a table's primary key
545
- def primary_key(table)
546
- pk_and_sequence = pk_and_sequence_for(table)
547
- pk_and_sequence && pk_and_sequence.first
450
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
451
+ super sql, name
452
+ id_value || @connection.insert_id
548
453
  end
454
+ alias :create :insert_sql
455
+
456
+ def exec_delete(sql, name, binds)
457
+ affected_rows = 0
549
458
 
550
- def case_sensitive_equality_operator
551
- "= BINARY"
459
+ exec_query(sql, name, binds) do |n|
460
+ affected_rows = n
461
+ end
462
+
463
+ affected_rows
552
464
  end
465
+ alias :exec_update :exec_delete
553
466
 
554
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
555
- where_sql
467
+ def begin_db_transaction #:nodoc:
468
+ exec_query "BEGIN"
469
+ rescue Mysql::Error
470
+ # Transactions aren't supported
556
471
  end
557
472
 
558
- protected
559
- def quoted_columns_for_index(column_names, options = {})
560
- length = options[:length] if options.is_a?(Hash)
473
+ private
561
474
 
562
- quoted_column_names = case length
563
- when Hash
564
- column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
565
- when Fixnum
566
- column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
475
+ def exec_stmt(sql, name, binds)
476
+ cache = {}
477
+ log(sql, name, binds) do
478
+ if binds.empty?
479
+ stmt = @connection.prepare(sql)
567
480
  else
568
- column_names.map {|name| quote_column_name(name) }
481
+ cache = @statements[sql] ||= {
482
+ :stmt => @connection.prepare(sql)
483
+ }
484
+ stmt = cache[:stmt]
569
485
  end
570
- end
571
-
572
- def translate_exception(exception, message)
573
- return super unless exception.respond_to?(:errno)
574
486
 
575
- case exception.errno
576
- when 1062
577
- RecordNotUnique.new(message, exception)
578
- when 1452
579
- InvalidForeignKey.new(message, exception)
580
- else
581
- super
487
+ begin
488
+ stmt.execute(*binds.map { |col, val| type_cast(val, col) })
489
+ rescue Mysql::Error => e
490
+ # Older versions of MySQL leave the prepared statement in a bad
491
+ # place when an error occurs. To support older mysql versions, we
492
+ # need to close the statement and delete the statement from the
493
+ # cache.
494
+ stmt.close
495
+ @statements.delete sql
496
+ raise e
582
497
  end
583
- end
584
498
 
585
- private
586
- def connect
587
- encoding = @config[:encoding]
588
- if encoding
589
- @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
499
+ cols = nil
500
+ if metadata = stmt.result_metadata
501
+ cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
502
+ field.name
503
+ }
590
504
  end
591
505
 
592
- if @config[:sslca] || @config[:sslkey]
593
- @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
594
- end
506
+ result_set = ActiveRecord::Result.new(cols, stmt.to_a) if cols
507
+ affected_rows = stmt.affected_rows
595
508
 
596
- @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
597
- @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
598
- @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
509
+ stmt.result_metadata.free if cols
510
+ stmt.free_result
511
+ stmt.close if binds.empty?
599
512
 
600
- @connection.real_connect(*@connection_options)
513
+ [result_set, affected_rows]
514
+ end
515
+ end
601
516
 
602
- # reconnect must be set after real_connect is called, because real_connect sets it to false internally
603
- @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
517
+ def connect
518
+ encoding = @config[:encoding]
519
+ if encoding
520
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
521
+ end
604
522
 
605
- configure_connection
523
+ if @config[:sslca] || @config[:sslkey]
524
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
606
525
  end
607
526
 
608
- def configure_connection
609
- encoding = @config[:encoding]
610
- execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
527
+ @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
528
+ @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
529
+ @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
611
530
 
612
- # By default, MySQL 'where id is null' selects the last inserted id.
613
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
614
- execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
615
- end
531
+ @connection.real_connect(*@connection_options)
616
532
 
617
- def select(sql, name = nil)
618
- @connection.query_with_result = true
619
- result = execute(sql, name)
620
- rows = []
621
- result.each_hash { |row| rows << row }
622
- result.free
623
- @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
624
- rows
625
- end
533
+ # reconnect must be set after real_connect is called, because real_connect sets it to false internally
534
+ @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
626
535
 
627
- def supports_views?
628
- version[0] >= 5
629
- end
536
+ configure_connection
537
+ end
630
538
 
631
- def version
632
- @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
633
- end
539
+ # Many Rails applications monkey-patch a replacement of the configure_connection method
540
+ # and don't call 'super', so leave this here even though it looks superfluous.
541
+ def configure_connection
542
+ super
543
+ end
634
544
 
635
- def column_for(table_name, column_name)
636
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
637
- raise "No such column: #{table_name}.#{column_name}"
638
- end
639
- column
640
- end
545
+ def select(sql, name = nil, binds = [])
546
+ @connection.query_with_result = true
547
+ rows = exec_query(sql, name, binds)
548
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
549
+ rows
550
+ end
551
+
552
+ # Returns the version of the connected MySQL server.
553
+ def version
554
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
555
+ end
641
556
  end
642
557
  end
643
558
  end