activerecord 3.1.10 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,17 +1,12 @@
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'
5
2
  require 'active_record/connection_adapters/statement_pool'
6
- require 'arel/visitors/bind_visitor'
3
+ require 'active_support/core_ext/hash/keys'
7
4
 
8
- gem 'mysql', '~> 2.8'
5
+ gem 'mysql', '~> 2.9'
9
6
  require 'mysql'
10
7
 
11
8
  class Mysql
12
9
  class Time
13
- ###
14
- # This monkey patch is for test_additional_columns_from_join_table
15
10
  def to_date
16
11
  Date.new(year, month, day)
17
12
  end
@@ -21,9 +16,9 @@ class Mysql
21
16
  end
22
17
 
23
18
  module ActiveRecord
24
- class Base
19
+ module ConnectionHandling # :nodoc:
25
20
  # Establishes a connection to the database that's used by all Active Record objects.
26
- def self.mysql_connection(config) # :nodoc:
21
+ def mysql_connection(config)
27
22
  config = config.symbolize_keys
28
23
  host = config[:host]
29
24
  port = config[:port]
@@ -39,96 +34,16 @@ module ActiveRecord
39
34
  default_flags |= Mysql::CLIENT_FOUND_ROWS if Mysql.const_defined?(:CLIENT_FOUND_ROWS)
40
35
  options = [host, username, password, database, port, socket, default_flags]
41
36
  ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
37
+ rescue Mysql::Error => error
38
+ if error.message.include?("Unknown database")
39
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
40
+ else
41
+ raise
42
+ end
42
43
  end
43
44
  end
44
45
 
45
46
  module ConnectionAdapters
46
- class MysqlColumn < Column #:nodoc:
47
- class << self
48
- def string_to_time(value)
49
- return super unless Mysql::Time === value
50
- new_time(
51
- value.year,
52
- value.month,
53
- value.day,
54
- value.hour,
55
- value.minute,
56
- value.second,
57
- value.second_part)
58
- end
59
-
60
- def string_to_dummy_time(v)
61
- return super unless Mysql::Time === v
62
- new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part)
63
- end
64
-
65
- def string_to_date(v)
66
- return super unless Mysql::Time === v
67
- new_date(v.year, v.month, v.day)
68
- end
69
- end
70
-
71
- def extract_default(default)
72
- if sql_type =~ /blob/i || type == :text
73
- if default.blank?
74
- return null ? nil : ''
75
- else
76
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
77
- end
78
- elsif missing_default_forged_as_empty_string?(default)
79
- nil
80
- else
81
- super
82
- end
83
- end
84
-
85
- def has_default?
86
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
87
- super
88
- end
89
-
90
- private
91
- def simplified_type(field_type)
92
- return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
93
- return :string if field_type =~ /enum/i
94
- super
95
- end
96
-
97
- def extract_limit(sql_type)
98
- case sql_type
99
- when /blob|text/i
100
- case sql_type
101
- when /tiny/i
102
- 255
103
- when /medium/i
104
- 16777215
105
- when /long/i
106
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
107
- else
108
- super # we could return 65535 here, but we leave it undecorated by default
109
- end
110
- when /^bigint/i; 8
111
- when /^int/i; 4
112
- when /^mediumint/i; 3
113
- when /^smallint/i; 2
114
- when /^tinyint/i; 1
115
- else
116
- super
117
- end
118
- end
119
-
120
- # MySQL misreports NOT NULL column default when none is given.
121
- # We can't detect this for columns which may have a legitimate ''
122
- # default (string) but we can for others (integer, datetime, boolean,
123
- # and the rest).
124
- #
125
- # Test whether the column has default '', is not null, and is not
126
- # a type allowing default ''.
127
- def missing_default_forged_as_empty_string?(default)
128
- type != :string && !null && default == ''
129
- end
130
- end
131
-
132
47
  # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
133
48
  # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
134
49
  #
@@ -142,49 +57,16 @@ module ActiveRecord
142
57
  # * <tt>:database</tt> - The name of the database. No default, must be provided.
143
58
  # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
144
59
  # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
60
+ # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/sql-mode.html)
61
+ # * <tt>:variables</tt> - (Optional) A hash session variables to send as <tt>SET @@SESSION.key = value</tt> 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).
145
62
  # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
146
63
  # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
147
64
  # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
148
65
  # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
149
66
  # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
150
67
  #
151
- class MysqlAdapter < AbstractAdapter
152
-
153
- ##
154
- # :singleton-method:
155
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
156
- # as boolean. If you wish to disable this emulation (which was the default
157
- # behavior in versions 0.13.1 and earlier) you can add the following line
158
- # to your application.rb file:
159
- #
160
- # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
161
- cattr_accessor :emulate_booleans
162
- self.emulate_booleans = true
163
-
164
- ADAPTER_NAME = 'MySQL'
165
-
166
- LOST_CONNECTION_ERROR_MESSAGES = [
167
- "Server shutdown in progress",
168
- "Broken pipe",
169
- "Lost connection to MySQL server during query",
170
- "MySQL server has gone away" ]
171
-
172
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
173
-
174
- NATIVE_DATABASE_TYPES = {
175
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
176
- :string => { :name => "varchar", :limit => 255 },
177
- :text => { :name => "text" },
178
- :integer => { :name => "int", :limit => 4 },
179
- :float => { :name => "float" },
180
- :decimal => { :name => "decimal" },
181
- :datetime => { :name => "datetime" },
182
- :timestamp => { :name => "datetime" },
183
- :time => { :name => "time" },
184
- :date => { :name => "date" },
185
- :binary => { :name => "blob" },
186
- :boolean => { :name => "tinyint", :limit => 1 }
187
- }
68
+ class MysqlAdapter < AbstractMysqlAdapter
69
+ ADAPTER_NAME = 'MySQL'.freeze
188
70
 
189
71
  class StatementPool < ConnectionAdapters::StatementPool
190
72
  def initialize(connection, max = 1000)
@@ -206,7 +88,7 @@ module ActiveRecord
206
88
  end
207
89
 
208
90
  def clear
209
- cache.values.each do |hash|
91
+ cache.each_value do |hash|
210
92
  hash[:stmt].close
211
93
  end
212
94
  cache.clear
@@ -214,122 +96,50 @@ module ActiveRecord
214
96
 
215
97
  private
216
98
  def cache
217
- @cache[$$]
99
+ @cache[Process.pid]
218
100
  end
219
101
  end
220
102
 
221
103
  def initialize(connection, logger, connection_options, config)
222
- super(connection, logger)
223
- @connection_options, @config = connection_options, config
224
- @quoted_column_names, @quoted_table_names = {}, {}
225
- @statements = {}
104
+ super
226
105
  @statements = StatementPool.new(@connection,
227
- config.fetch(:statement_limit) { 1000 })
106
+ self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
228
107
  @client_encoding = nil
229
108
  connect
230
109
  end
231
110
 
232
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
233
- include Arel::Visitors::BindVisitor
234
- end
235
-
236
- def self.visitor_for(pool) # :nodoc:
237
- config = pool.spec.config
238
-
239
- if config.fetch(:prepared_statements) { true }
240
- Arel::Visitors::MySQL.new pool
241
- else
242
- BindSubstitution.new pool
243
- end
244
- end
245
-
246
- def adapter_name #:nodoc:
247
- ADAPTER_NAME
248
- end
249
-
250
- def supports_bulk_alter? #:nodoc:
251
- true
252
- end
253
-
254
111
  # Returns true, since this connection adapter supports prepared statement
255
112
  # caching.
256
113
  def supports_statement_cache?
257
114
  true
258
115
  end
259
116
 
260
- # Returns true, since this connection adapter supports migrations.
261
- def supports_migrations? #:nodoc:
262
- true
263
- end
264
-
265
- # Returns true.
266
- def supports_primary_key? #:nodoc:
267
- true
268
- end
117
+ # HELPER METHODS ===========================================
269
118
 
270
- # Returns true, since this connection adapter supports savepoints.
271
- def supports_savepoints? #:nodoc:
272
- true
273
- end
274
-
275
- def native_database_types #:nodoc:
276
- NATIVE_DATABASE_TYPES
277
- end
278
-
279
-
280
- # QUOTING ==================================================
281
-
282
- def quote(value, column = nil)
283
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
284
- s = column.class.string_to_binary(value).unpack("H*")[0]
285
- "x'#{s}'"
286
- elsif value.kind_of?(BigDecimal)
287
- value.to_s("F")
119
+ def each_hash(result) # :nodoc:
120
+ if block_given?
121
+ result.each_hash do |row|
122
+ row.symbolize_keys!
123
+ yield row
124
+ end
288
125
  else
289
- super
126
+ to_enum(:each_hash, result)
290
127
  end
291
128
  end
292
129
 
293
- def type_cast(value, column)
294
- return super unless value == true || value == false
295
-
296
- value ? 1 : 0
297
- end
298
-
299
- def quote_column_name(name) #:nodoc:
300
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
130
+ def error_number(exception) # :nodoc:
131
+ exception.errno if exception.respond_to?(:errno)
301
132
  end
302
133
 
303
- def quote_table_name(name) #:nodoc:
304
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
305
- end
134
+ # QUOTING ==================================================
306
135
 
307
136
  def quote_string(string) #:nodoc:
308
137
  @connection.quote(string)
309
138
  end
310
139
 
311
- def quoted_true
312
- QUOTED_TRUE
313
- end
314
-
315
- def quoted_false
316
- QUOTED_FALSE
317
- end
318
-
319
- # REFERENTIAL INTEGRITY ====================================
320
-
321
- def disable_referential_integrity #:nodoc:
322
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
323
-
324
- begin
325
- update("SET FOREIGN_KEY_CHECKS = 0")
326
- yield
327
- ensure
328
- update("SET FOREIGN_KEY_CHECKS = #{old}")
329
- end
330
- end
331
-
140
+ #--
332
141
  # CONNECTION MANAGEMENT ====================================
142
+ #++
333
143
 
334
144
  def active?
335
145
  if @connection.respond_to?(:stat)
@@ -349,14 +159,15 @@ module ActiveRecord
349
159
  end
350
160
 
351
161
  def reconnect!
162
+ super
352
163
  disconnect!
353
- clear_cache!
354
164
  connect
355
165
  end
356
166
 
357
167
  # Disconnects from the database if already connected. Otherwise, this
358
168
  # method does nothing.
359
169
  def disconnect!
170
+ super
360
171
  @connection.close rescue nil
361
172
  end
362
173
 
@@ -369,632 +180,319 @@ module ActiveRecord
369
180
  end
370
181
  end
371
182
 
183
+ #--
372
184
  # DATABASE STATEMENTS ======================================
185
+ #++
373
186
 
374
- def select_rows(sql, name = nil)
187
+ def select_rows(sql, name = nil, binds = [])
375
188
  @connection.query_with_result = true
376
- rows = exec_without_stmt(sql, name).rows
189
+ rows = exec_query(sql, name, binds).rows
377
190
  @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
378
191
  rows
379
192
  end
380
193
 
381
194
  # Clears the prepared statements cache.
382
195
  def clear_cache!
196
+ super
383
197
  @statements.clear
384
198
  end
385
199
 
386
- if "<3".respond_to?(:encode)
387
- # Taken from here:
388
- # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb
389
- # Author: TOMITA Masahiro <tommy@tmtm.org>
390
- ENCODINGS = {
391
- "armscii8" => nil,
392
- "ascii" => Encoding::US_ASCII,
393
- "big5" => Encoding::Big5,
394
- "binary" => Encoding::ASCII_8BIT,
395
- "cp1250" => Encoding::Windows_1250,
396
- "cp1251" => Encoding::Windows_1251,
397
- "cp1256" => Encoding::Windows_1256,
398
- "cp1257" => Encoding::Windows_1257,
399
- "cp850" => Encoding::CP850,
400
- "cp852" => Encoding::CP852,
401
- "cp866" => Encoding::IBM866,
402
- "cp932" => Encoding::Windows_31J,
403
- "dec8" => nil,
404
- "eucjpms" => Encoding::EucJP_ms,
405
- "euckr" => Encoding::EUC_KR,
406
- "gb2312" => Encoding::EUC_CN,
407
- "gbk" => Encoding::GBK,
408
- "geostd8" => nil,
409
- "greek" => Encoding::ISO_8859_7,
410
- "hebrew" => Encoding::ISO_8859_8,
411
- "hp8" => nil,
412
- "keybcs2" => nil,
413
- "koi8r" => Encoding::KOI8_R,
414
- "koi8u" => Encoding::KOI8_U,
415
- "latin1" => Encoding::ISO_8859_1,
416
- "latin2" => Encoding::ISO_8859_2,
417
- "latin5" => Encoding::ISO_8859_9,
418
- "latin7" => Encoding::ISO_8859_13,
419
- "macce" => Encoding::MacCentEuro,
420
- "macroman" => Encoding::MacRoman,
421
- "sjis" => Encoding::SHIFT_JIS,
422
- "swe7" => nil,
423
- "tis620" => Encoding::TIS_620,
424
- "ucs2" => Encoding::UTF_16BE,
425
- "ujis" => Encoding::EucJP_ms,
426
- "utf8" => Encoding::UTF_8,
427
- "utf8mb4" => Encoding::UTF_8,
428
- }
429
- else
430
- ENCODINGS = Hash.new { |h,k| h[k] = k }
431
- end
200
+ # Taken from here:
201
+ # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb
202
+ # Author: TOMITA Masahiro <tommy@tmtm.org>
203
+ ENCODINGS = {
204
+ "armscii8" => nil,
205
+ "ascii" => Encoding::US_ASCII,
206
+ "big5" => Encoding::Big5,
207
+ "binary" => Encoding::ASCII_8BIT,
208
+ "cp1250" => Encoding::Windows_1250,
209
+ "cp1251" => Encoding::Windows_1251,
210
+ "cp1256" => Encoding::Windows_1256,
211
+ "cp1257" => Encoding::Windows_1257,
212
+ "cp850" => Encoding::CP850,
213
+ "cp852" => Encoding::CP852,
214
+ "cp866" => Encoding::IBM866,
215
+ "cp932" => Encoding::Windows_31J,
216
+ "dec8" => nil,
217
+ "eucjpms" => Encoding::EucJP_ms,
218
+ "euckr" => Encoding::EUC_KR,
219
+ "gb2312" => Encoding::EUC_CN,
220
+ "gbk" => Encoding::GBK,
221
+ "geostd8" => nil,
222
+ "greek" => Encoding::ISO_8859_7,
223
+ "hebrew" => Encoding::ISO_8859_8,
224
+ "hp8" => nil,
225
+ "keybcs2" => nil,
226
+ "koi8r" => Encoding::KOI8_R,
227
+ "koi8u" => Encoding::KOI8_U,
228
+ "latin1" => Encoding::ISO_8859_1,
229
+ "latin2" => Encoding::ISO_8859_2,
230
+ "latin5" => Encoding::ISO_8859_9,
231
+ "latin7" => Encoding::ISO_8859_13,
232
+ "macce" => Encoding::MacCentEuro,
233
+ "macroman" => Encoding::MacRoman,
234
+ "sjis" => Encoding::SHIFT_JIS,
235
+ "swe7" => nil,
236
+ "tis620" => Encoding::TIS_620,
237
+ "ucs2" => Encoding::UTF_16BE,
238
+ "ujis" => Encoding::EucJP_ms,
239
+ "utf8" => Encoding::UTF_8,
240
+ "utf8mb4" => Encoding::UTF_8,
241
+ }
432
242
 
433
243
  # Get the client encoding for this database
434
244
  def client_encoding
435
245
  return @client_encoding if @client_encoding
436
246
 
437
247
  result = exec_query(
438
- "SHOW VARIABLES WHERE Variable_name = 'character_set_client'",
248
+ "select @@character_set_client",
439
249
  'SCHEMA')
440
250
  @client_encoding = ENCODINGS[result.rows.last.last]
441
251
  end
442
252
 
443
253
  def exec_query(sql, name = 'SQL', binds = [])
444
- log(sql, name, binds) do
445
- exec_stmt(sql, name, binds) do |cols, stmt|
446
- ActiveRecord::Result.new(cols, stmt.to_a) if cols
447
- end
254
+ if without_prepared_statement?(binds)
255
+ result_set, affected_rows = exec_without_stmt(sql, name)
256
+ else
257
+ result_set, affected_rows = exec_stmt(sql, name, binds)
448
258
  end
259
+
260
+ yield affected_rows if block_given?
261
+
262
+ result_set
449
263
  end
450
264
 
451
265
  def last_inserted_id(result)
452
266
  @connection.insert_id
453
267
  end
454
268
 
455
- def exec_without_stmt(sql, name = 'SQL') # :nodoc:
456
- # Some queries, like SHOW CREATE TABLE don't work through the prepared
457
- # statement API. For those queries, we need to use this method. :'(
458
- log(sql, name) do
459
- result = @connection.query(sql)
460
- cols = []
461
- rows = []
269
+ module Fields # :nodoc:
270
+ class DateTime < Type::DateTime # :nodoc:
271
+ def cast_value(value)
272
+ if Mysql::Time === value
273
+ new_time(
274
+ value.year,
275
+ value.month,
276
+ value.day,
277
+ value.hour,
278
+ value.minute,
279
+ value.second,
280
+ value.second_part)
281
+ else
282
+ super
283
+ end
284
+ end
462
285
 
463
- if result
464
- cols = result.fetch_fields.map { |field| field.name }
465
- rows = result.to_a
466
- result.free
286
+ def has_precision?
287
+ precision || 0
467
288
  end
468
- ActiveRecord::Result.new(cols, rows)
469
289
  end
470
- end
471
290
 
472
- # Executes an SQL query and returns a MySQL::Result object. Note that you have to free
473
- # the Result object after you're done using it.
474
- def execute(sql, name = nil) #:nodoc:
475
- if name == :skip_logging
476
- @connection.query(sql)
477
- else
478
- log(sql, name) { @connection.query(sql) }
479
- end
480
- rescue ActiveRecord::StatementInvalid => exception
481
- if exception.message.split(":").first =~ /Packets out of order/
482
- 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."
483
- else
484
- raise
291
+ class Time < Type::Time # :nodoc:
292
+ def cast_value(value)
293
+ if Mysql::Time === value
294
+ new_time(
295
+ 2000,
296
+ 01,
297
+ 01,
298
+ value.hour,
299
+ value.minute,
300
+ value.second,
301
+ value.second_part)
302
+ else
303
+ super
304
+ end
305
+ end
485
306
  end
486
- end
487
307
 
488
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
489
- super sql, name
490
- id_value || @connection.insert_id
491
- end
492
- alias :create :insert_sql
308
+ class << self
309
+ TYPES = Type::HashLookupTypeMap.new # :nodoc:
493
310
 
494
- def update_sql(sql, name = nil) #:nodoc:
495
- super
496
- @connection.affected_rows
497
- end
311
+ delegate :register_type, :alias_type, to: :TYPES
498
312
 
499
- def exec_delete(sql, name, binds)
500
- log(sql, name, binds) do
501
- exec_stmt(sql, name, binds) do |cols, stmt|
502
- stmt.affected_rows
313
+ def find_type(field)
314
+ if field.type == Mysql::Field::TYPE_TINY && field.length > 1
315
+ TYPES.lookup(Mysql::Field::TYPE_LONG)
316
+ else
317
+ TYPES.lookup(field.type)
318
+ end
503
319
  end
504
320
  end
505
- end
506
- alias :exec_update :exec_delete
507
321
 
508
- def begin_db_transaction #:nodoc:
509
- exec_without_stmt "BEGIN"
510
- rescue Mysql::Error
511
- # Transactions aren't supported
512
- end
322
+ register_type Mysql::Field::TYPE_TINY, Type::Boolean.new
323
+ register_type Mysql::Field::TYPE_LONG, Type::Integer.new
324
+ alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG
325
+ alias_type Mysql::Field::TYPE_NEWDECIMAL, Mysql::Field::TYPE_LONG
513
326
 
514
- def commit_db_transaction #:nodoc:
515
- execute "COMMIT"
516
- rescue Exception
517
- # Transactions aren't supported
327
+ register_type Mysql::Field::TYPE_DATE, Type::Date.new
328
+ register_type Mysql::Field::TYPE_DATETIME, Fields::DateTime.new
329
+ register_type Mysql::Field::TYPE_TIME, Fields::Time.new
330
+ register_type Mysql::Field::TYPE_FLOAT, Type::Float.new
518
331
  end
519
332
 
520
- def rollback_db_transaction #:nodoc:
521
- execute "ROLLBACK"
522
- rescue Exception
523
- # Transactions aren't supported
524
- end
525
-
526
- def create_savepoint
527
- execute("SAVEPOINT #{current_savepoint_name}")
528
- end
529
-
530
- def rollback_to_savepoint
531
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
532
- end
533
-
534
- def release_savepoint
535
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
536
- end
537
-
538
- def add_limit_offset!(sql, options) #:nodoc:
539
- limit, offset = options[:limit], options[:offset]
540
- if limit && offset
541
- sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
542
- elsif limit
543
- sql << " LIMIT #{sanitize_limit(limit)}"
544
- elsif offset
545
- sql << " OFFSET #{offset.to_i}"
546
- end
547
- sql
548
- end
549
- deprecate :add_limit_offset!
550
-
551
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
552
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
553
- # these, we must use a subquery. However, MySQL is too stupid to create a
554
- # temporary table for this automatically, so we have to give it some prompting
555
- # in the form of a subsubquery. Ugh!
556
- def join_to_update(update, select) #:nodoc:
557
- if select.limit || select.offset || select.orders.any?
558
- subsubselect = select.clone
559
- subsubselect.projections = [update.key]
560
-
561
- subselect = Arel::SelectManager.new(select.engine)
562
- subselect.project Arel.sql(update.key.name)
563
- subselect.from subsubselect.as('__active_record_temp')
564
-
565
- update.where update.key.in(subselect)
566
- else
567
- update.table select.source
568
- update.wheres = select.constraints
333
+ def initialize_type_map(m) # :nodoc:
334
+ super
335
+ m.register_type %r(time)i, Fields::Time.new
336
+ m.register_type(%r(datetime)i) do |sql_type|
337
+ precision = extract_precision(sql_type)
338
+ Fields::DateTime.new(precision: precision)
569
339
  end
570
340
  end
571
341
 
572
- # SCHEMA STATEMENTS ========================================
573
-
574
- def structure_dump #:nodoc:
575
- if supports_views?
576
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
577
- else
578
- sql = "SHOW TABLES"
579
- end
580
-
581
- select_all(sql).map do |table|
582
- table.delete('Table_type')
583
- sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
584
- exec_without_stmt(sql).first['Create Table'] + ";\n\n"
585
- end.join("")
586
- end
342
+ def exec_without_stmt(sql, name = 'SQL') # :nodoc:
343
+ # Some queries, like SHOW CREATE TABLE don't work through the prepared
344
+ # statement API. For those queries, we need to use this method. :'(
345
+ log(sql, name) do
346
+ result = @connection.query(sql)
347
+ affected_rows = @connection.affected_rows
587
348
 
588
- # Drops the database specified on the +name+ attribute
589
- # and creates it again using the provided +options+.
590
- def recreate_database(name, options = {}) #:nodoc:
591
- drop_database(name)
592
- create_database(name, options)
593
- end
349
+ if result
350
+ types = {}
351
+ fields = []
352
+ result.fetch_fields.each { |field|
353
+ field_name = field.name
354
+ fields << field_name
355
+
356
+ if field.decimals > 0
357
+ types[field_name] = Type::Decimal.new
358
+ else
359
+ types[field_name] = Fields.find_type field
360
+ end
361
+ }
362
+
363
+ result_set = ActiveRecord::Result.new(fields, result.to_a, types)
364
+ result.free
365
+ else
366
+ result_set = ActiveRecord::Result.new([], [])
367
+ end
594
368
 
595
- # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
596
- # Charset defaults to utf8.
597
- #
598
- # Example:
599
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
600
- # create_database 'matt_development'
601
- # create_database 'matt_development', :charset => :big5
602
- def create_database(name, options = {})
603
- if options[:collation]
604
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
605
- else
606
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
369
+ [result_set, affected_rows]
607
370
  end
608
371
  end
609
372
 
610
- # Drops a MySQL database.
611
- #
612
- # Example:
613
- # drop_database 'sebastian_development'
614
- def drop_database(name) #:nodoc:
615
- execute "DROP DATABASE IF EXISTS `#{name}`"
616
- end
617
-
618
- def current_database
619
- select_value 'SELECT DATABASE() as db'
620
- end
621
-
622
- # Returns the database character set.
623
- def charset
624
- show_variable 'character_set_database'
625
- end
626
-
627
- # Returns the database collation strategy.
628
- def collation
629
- show_variable 'collation_database'
630
- end
631
-
632
- def tables(name = nil, database = nil) #:nodoc:
633
- sql = "SHOW TABLES "
634
- sql << "IN #{quote_table_name(database)} " if database
635
-
636
- result = execute(sql, 'SCHEMA')
637
- tables = result.collect { |field| field[0] }
373
+ def execute_and_free(sql, name = nil) # :nodoc:
374
+ result = execute(sql, name)
375
+ ret = yield result
638
376
  result.free
639
- tables
377
+ ret
640
378
  end
641
379
 
642
- def table_exists?(name)
643
- return true if super
644
-
645
- name = name.to_s
646
- schema, table = name.split('.', 2)
647
-
648
- unless table # A table was provided without a schema
649
- table = schema
650
- schema = nil
651
- end
652
-
653
- tables(nil, schema).include? table
654
- end
655
-
656
- def drop_table(table_name, options = {})
657
- super(table_name, options)
380
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
381
+ super sql, name
382
+ id_value || @connection.insert_id
658
383
  end
384
+ alias :create :insert_sql
659
385
 
660
- # Returns an array of indexes for the given table.
661
- def indexes(table_name, name = nil)#:nodoc:
662
- indexes = []
663
- current_index = nil
664
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
665
- result.each do |row|
666
- if current_index != row[2]
667
- next if row[2] == "PRIMARY" # skip the primary key
668
- current_index = row[2]
669
- indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
670
- end
386
+ def exec_delete(sql, name, binds) # :nodoc:
387
+ affected_rows = 0
671
388
 
672
- indexes.last.columns << row[4]
673
- indexes.last.lengths << row[7]
389
+ exec_query(sql, name, binds) do |n|
390
+ affected_rows = n
674
391
  end
675
- result.free
676
- indexes
677
- end
678
392
 
679
- # Returns an array of +MysqlColumn+ objects for the table specified by +table_name+.
680
- def columns(table_name, name = nil)#:nodoc:
681
- sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
682
- result = execute(sql, 'SCHEMA')
683
- columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
684
- result.free
685
- columns
393
+ affected_rows
686
394
  end
395
+ alias :exec_update :exec_delete
687
396
 
688
- def create_table(table_name, options = {}) #:nodoc:
689
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
397
+ def begin_db_transaction #:nodoc:
398
+ exec_query "BEGIN"
690
399
  end
691
400
 
692
- # Renames a table.
693
- #
694
- # Example:
695
- # rename_table('octopuses', 'octopi')
696
- def rename_table(table_name, new_name)
697
- execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
698
- end
401
+ private
699
402
 
700
- def bulk_change_table(table_name, operations) #:nodoc:
701
- sqls = operations.map do |command, args|
702
- table, arguments = args.shift, args
703
- method = :"#{command}_sql"
403
+ def exec_stmt(sql, name, binds)
404
+ cache = {}
405
+ type_casted_binds = binds.map { |col, val|
406
+ [col, type_cast(val, col)]
407
+ }
704
408
 
705
- if respond_to?(method, true)
706
- send(method, table, *arguments)
409
+ log(sql, name, type_casted_binds) do
410
+ if binds.empty?
411
+ stmt = @connection.prepare(sql)
707
412
  else
708
- raise "Unknown method called : #{method}(#{arguments.inspect})"
413
+ cache = @statements[sql] ||= {
414
+ :stmt => @connection.prepare(sql)
415
+ }
416
+ stmt = cache[:stmt]
709
417
  end
710
- end.flatten.join(", ")
711
418
 
712
- execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
713
- end
419
+ begin
420
+ stmt.execute(*type_casted_binds.map { |_, val| val })
421
+ rescue Mysql::Error => e
422
+ # Older versions of MySQL leave the prepared statement in a bad
423
+ # place when an error occurs. To support older MySQL versions, we
424
+ # need to close the statement and delete the statement from the
425
+ # cache.
426
+ stmt.close
427
+ @statements.delete sql
428
+ raise e
429
+ end
714
430
 
715
- def add_column(table_name, column_name, type, options = {})
716
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
717
- end
431
+ cols = nil
432
+ if metadata = stmt.result_metadata
433
+ cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
434
+ field.name
435
+ }
436
+ metadata.free
437
+ end
718
438
 
719
- def change_column_default(table_name, column_name, default) #:nodoc:
720
- column = column_for(table_name, column_name)
721
- change_column table_name, column_name, column.sql_type, :default => default
722
- end
439
+ result_set = ActiveRecord::Result.new(cols, stmt.to_a) if cols
440
+ affected_rows = stmt.affected_rows
723
441
 
724
- def change_column_null(table_name, column_name, null, default = nil)
725
- column = column_for(table_name, column_name)
442
+ stmt.free_result
443
+ stmt.close if binds.empty?
726
444
 
727
- unless null || default.nil?
728
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
445
+ [result_set, affected_rows]
729
446
  end
730
-
731
- change_column table_name, column_name, column.sql_type, :null => null
732
- end
733
-
734
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
735
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
736
- end
737
-
738
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
739
- execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
740
447
  end
741
448
 
742
- # Maps logical Rails types to MySQL-specific data types.
743
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
744
- case type.to_s
745
- when 'integer'
746
- case limit
747
- when 1; 'tinyint'
748
- when 2; 'smallint'
749
- when 3; 'mediumint'
750
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
751
- when 5..8; 'bigint'
752
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
753
- end
754
- when 'text'
755
- case limit
756
- when 0..0xff; 'tinytext'
757
- when nil, 0x100..0xffff; 'text'
758
- when 0x10000..0xffffff; 'mediumtext'
759
- when 0x1000000..0xffffffff; 'longtext'
760
- else raise(ActiveRecordError, "No text type has character length #{limit}")
761
- end
762
- else
763
- super
449
+ def connect
450
+ encoding = @config[:encoding]
451
+ if encoding
452
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
764
453
  end
765
- end
766
454
 
767
- def add_column_position!(sql, options)
768
- if options[:first]
769
- sql << " FIRST"
770
- elsif options[:after]
771
- sql << " AFTER #{quote_column_name(options[:after])}"
455
+ if @config[:sslca] || @config[:sslkey]
456
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
772
457
  end
773
- end
774
458
 
775
- # SHOW VARIABLES LIKE 'name'
776
- def show_variable(name)
777
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
778
- variables.first['Value'] unless variables.empty?
779
- end
459
+ @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
460
+ @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
461
+ @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
780
462
 
781
- # Returns a table's primary key and belonging sequence.
782
- def pk_and_sequence_for(table) #:nodoc:
783
- result = execute("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA')
784
- create_table = result.fetch_hash["Create Table"]
785
- result.free
463
+ @connection.real_connect(*@connection_options)
786
464
 
787
- if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/
788
- keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
789
- keys.length == 1 ? [keys.first, nil] : nil
790
- else
791
- nil
792
- end
793
- end
465
+ # reconnect must be set after real_connect is called, because real_connect sets it to false internally
466
+ @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
794
467
 
795
- # Returns just a table's primary key
796
- def primary_key(table)
797
- pk_and_sequence = pk_and_sequence_for(table)
798
- pk_and_sequence && pk_and_sequence.first
468
+ configure_connection
799
469
  end
800
470
 
801
- def case_sensitive_equality_operator
802
- "= BINARY"
471
+ # Many Rails applications monkey-patch a replacement of the configure_connection method
472
+ # and don't call 'super', so leave this here even though it looks superfluous.
473
+ def configure_connection
474
+ super
803
475
  end
804
- deprecate :case_sensitive_equality_operator
805
476
 
806
- def case_sensitive_modifier(node)
807
- Arel::Nodes::Bin.new(node)
477
+ def select(sql, name = nil, binds = [])
478
+ @connection.query_with_result = true
479
+ rows = super
480
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
481
+ rows
808
482
  end
809
483
 
810
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
811
- where_sql
484
+ # Returns the full version of the connected MySQL server.
485
+ def full_version
486
+ @full_version ||= @connection.server_info
812
487
  end
813
488
 
814
- protected
815
- def quoted_columns_for_index(column_names, options = {})
816
- length = options[:length] if options.is_a?(Hash)
817
-
818
- case length
819
- when Hash
820
- column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
821
- when Fixnum
822
- column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
823
- else
824
- column_names.map {|name| quote_column_name(name) }
825
- end
826
- end
827
-
828
- def translate_exception(exception, message)
829
- return super unless exception.respond_to?(:errno)
830
-
831
- case exception.errno
832
- when 1062
833
- RecordNotUnique.new(message, exception)
834
- when 1452
835
- InvalidForeignKey.new(message, exception)
836
- else
837
- super
838
- end
839
- end
840
-
841
- def add_column_sql(table_name, column_name, type, options = {})
842
- add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
843
- add_column_options!(add_column_sql, options)
844
- add_column_position!(add_column_sql, options)
845
- add_column_sql
846
- end
847
-
848
- def remove_column_sql(table_name, *column_names)
849
- columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
850
- end
851
- alias :remove_columns_sql :remove_column
852
-
853
- def change_column_sql(table_name, column_name, type, options = {})
854
- column = column_for(table_name, column_name)
855
-
856
- unless options_include_default?(options)
857
- options[:default] = column.default
858
- end
859
-
860
- unless options.has_key?(:null)
861
- options[:null] = column.null
862
- end
863
-
864
- change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
865
- add_column_options!(change_column_sql, options)
866
- add_column_position!(change_column_sql, options)
867
- change_column_sql
489
+ def set_field_encoding field_name
490
+ field_name.force_encoding(client_encoding)
491
+ if internal_enc = Encoding.default_internal
492
+ field_name = field_name.encode!(internal_enc)
868
493
  end
869
-
870
- def rename_column_sql(table_name, column_name, new_column_name)
871
- options = {}
872
-
873
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
874
- options[:default] = column.default
875
- options[:null] = column.null
876
- else
877
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
878
- end
879
-
880
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
881
- rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
882
- add_column_options!(rename_column_sql, options)
883
- rename_column_sql
884
- end
885
-
886
- def add_index_sql(table_name, column_name, options = {})
887
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
888
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
889
- end
890
-
891
- def remove_index_sql(table_name, options = {})
892
- index_name = index_name_for_remove(table_name, options)
893
- "DROP INDEX #{index_name}"
894
- end
895
-
896
- def add_timestamps_sql(table_name)
897
- [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
898
- end
899
-
900
- def remove_timestamps_sql(table_name)
901
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
902
- end
903
-
904
- private
905
- def exec_stmt(sql, name, binds)
906
- cache = {}
907
- if binds.empty?
908
- stmt = @connection.prepare(sql)
909
- else
910
- cache = @statements[sql] ||= {
911
- :stmt => @connection.prepare(sql)
912
- }
913
- stmt = cache[:stmt]
914
- end
915
-
916
-
917
- begin
918
- stmt.execute(*binds.map { |col, val| type_cast(val, col) })
919
- rescue Mysql::Error => e
920
- # Older versions of MySQL leave the prepared statement in a bad
921
- # place when an error occurs. To support older mysql versions, we
922
- # need to close the statement and delete the statement from the
923
- # cache.
924
- stmt.close
925
- @statements.delete sql
926
- raise e
927
- end
928
-
929
- cols = nil
930
- if metadata = stmt.result_metadata
931
- cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
932
- field.name
933
- }
934
- end
935
-
936
- result = yield [cols, stmt]
937
-
938
- stmt.result_metadata.free if cols
939
- stmt.free_result
940
- stmt.close if binds.empty?
941
-
942
- result
494
+ field_name
943
495
  end
944
-
945
- def connect
946
- encoding = @config[:encoding]
947
- if encoding
948
- @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
949
- end
950
-
951
- if @config[:sslca] || @config[:sslkey]
952
- @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
953
- end
954
-
955
- @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
956
- @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
957
- @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
958
-
959
- @connection.real_connect(*@connection_options)
960
-
961
- # reconnect must be set after real_connect is called, because real_connect sets it to false internally
962
- @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
963
-
964
- configure_connection
965
- end
966
-
967
- def configure_connection
968
- encoding = @config[:encoding]
969
- execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
970
-
971
- # By default, MySQL 'where id is null' selects the last inserted id.
972
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
973
- execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
974
- end
975
-
976
- def select(sql, name = nil, binds = [])
977
- @connection.query_with_result = true
978
- rows = exec_query(sql, name, binds).to_a
979
- @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
980
- rows
981
- end
982
-
983
- def supports_views?
984
- version[0] >= 5
985
- end
986
-
987
- # Returns the version of the connected MySQL server.
988
- def version
989
- @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
990
- end
991
-
992
- def column_for(table_name, column_name)
993
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
994
- raise "No such column: #{table_name}.#{column_name}"
995
- end
996
- column
997
- end
998
496
  end
999
497
  end
1000
498
  end