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
@@ -0,0 +1,946 @@
1
+ require 'arel/visitors/bind_visitor'
2
+ require 'active_support/core_ext/string/strip'
3
+
4
+ module ActiveRecord
5
+ module ConnectionAdapters
6
+ class AbstractMysqlAdapter < AbstractAdapter
7
+ include Savepoints
8
+
9
+ class SchemaCreation < AbstractAdapter::SchemaCreation
10
+ def visit_AddColumn(o)
11
+ add_column_position!(super, column_options(o))
12
+ end
13
+
14
+ private
15
+
16
+ def visit_DropForeignKey(name)
17
+ "DROP FOREIGN KEY #{name}"
18
+ end
19
+
20
+ def visit_TableDefinition(o)
21
+ name = o.name
22
+ create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
23
+
24
+ statements = o.columns.map { |c| accept c }
25
+ statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
26
+
27
+ create_sql << "(#{statements.join(', ')}) " if statements.present?
28
+ create_sql << "#{o.options}"
29
+ create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
30
+ create_sql
31
+ end
32
+
33
+ def visit_ChangeColumnDefinition(o)
34
+ column = o.column
35
+ options = o.options
36
+ sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
37
+ change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
38
+ add_column_options!(change_column_sql, options.merge(column: column))
39
+ add_column_position!(change_column_sql, options)
40
+ end
41
+
42
+ def add_column_position!(sql, options)
43
+ if options[:first]
44
+ sql << " FIRST"
45
+ elsif options[:after]
46
+ sql << " AFTER #{quote_column_name(options[:after])}"
47
+ end
48
+ sql
49
+ end
50
+
51
+ def index_in_create(table_name, column_name, options)
52
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
53
+ "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
54
+ end
55
+ end
56
+
57
+ def schema_creation
58
+ SchemaCreation.new self
59
+ end
60
+
61
+ def prepare_column_options(column, types) # :nodoc:
62
+ spec = super
63
+ spec.delete(:limit) if :boolean === column.type
64
+ spec
65
+ end
66
+
67
+ class Column < ConnectionAdapters::Column # :nodoc:
68
+ attr_reader :collation, :strict, :extra
69
+
70
+ def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
71
+ @strict = strict
72
+ @collation = collation
73
+ @extra = extra
74
+ super(name, default, cast_type, sql_type, null)
75
+ assert_valid_default(default)
76
+ extract_default
77
+ end
78
+
79
+ def extract_default
80
+ if blob_or_text_column?
81
+ @default = null || strict ? nil : ''
82
+ elsif missing_default_forged_as_empty_string?(@default)
83
+ @default = nil
84
+ end
85
+ end
86
+
87
+ def has_default?
88
+ return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
89
+ super
90
+ end
91
+
92
+ def blob_or_text_column?
93
+ sql_type =~ /blob/i || type == :text
94
+ end
95
+
96
+ def case_sensitive?
97
+ collation && !collation.match(/_ci$/)
98
+ end
99
+
100
+ def ==(other)
101
+ super &&
102
+ collation == other.collation &&
103
+ strict == other.strict &&
104
+ extra == other.extra
105
+ end
106
+
107
+ private
108
+
109
+ # MySQL misreports NOT NULL column default when none is given.
110
+ # We can't detect this for columns which may have a legitimate ''
111
+ # default (string) but we can for others (integer, datetime, boolean,
112
+ # and the rest).
113
+ #
114
+ # Test whether the column has default '', is not null, and is not
115
+ # a type allowing default ''.
116
+ def missing_default_forged_as_empty_string?(default)
117
+ type != :string && !null && default == ''
118
+ end
119
+
120
+ def assert_valid_default(default)
121
+ if blob_or_text_column? && default.present?
122
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
123
+ end
124
+ end
125
+
126
+ def attributes_for_hash
127
+ super + [collation, strict, extra]
128
+ end
129
+ end
130
+
131
+ ##
132
+ # :singleton-method:
133
+ # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
134
+ # as boolean. If you wish to disable this emulation (which was the default
135
+ # behavior in versions 0.13.1 and earlier) you can add the following line
136
+ # to your application.rb file:
137
+ #
138
+ # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
139
+ class_attribute :emulate_booleans
140
+ self.emulate_booleans = true
141
+
142
+ LOST_CONNECTION_ERROR_MESSAGES = [
143
+ "Server shutdown in progress",
144
+ "Broken pipe",
145
+ "Lost connection to MySQL server during query",
146
+ "MySQL server has gone away" ]
147
+
148
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
149
+
150
+ NATIVE_DATABASE_TYPES = {
151
+ :primary_key => "int(11) auto_increment PRIMARY KEY",
152
+ :string => { :name => "varchar", :limit => 255 },
153
+ :text => { :name => "text" },
154
+ :integer => { :name => "int", :limit => 4 },
155
+ :float => { :name => "float" },
156
+ :decimal => { :name => "decimal" },
157
+ :datetime => { :name => "datetime" },
158
+ :time => { :name => "time" },
159
+ :date => { :name => "date" },
160
+ :binary => { :name => "blob" },
161
+ :boolean => { :name => "tinyint", :limit => 1 }
162
+ }
163
+
164
+ INDEX_TYPES = [:fulltext, :spatial]
165
+ INDEX_USINGS = [:btree, :hash]
166
+
167
+ # FIXME: Make the first parameter more similar for the two adapters
168
+ def initialize(connection, logger, connection_options, config)
169
+ super(connection, logger)
170
+ @connection_options, @config = connection_options, config
171
+ @quoted_column_names, @quoted_table_names = {}, {}
172
+
173
+ @visitor = Arel::Visitors::MySQL.new self
174
+
175
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
176
+ @prepared_statements = true
177
+ else
178
+ @prepared_statements = false
179
+ end
180
+ end
181
+
182
+ # Returns true, since this connection adapter supports migrations.
183
+ def supports_migrations?
184
+ true
185
+ end
186
+
187
+ def supports_primary_key?
188
+ true
189
+ end
190
+
191
+ def supports_bulk_alter? #:nodoc:
192
+ true
193
+ end
194
+
195
+ # Technically MySQL allows to create indexes with the sort order syntax
196
+ # but at the moment (5.5) it doesn't yet implement them
197
+ def supports_index_sort_order?
198
+ true
199
+ end
200
+
201
+ # MySQL 4 technically support transaction isolation, but it is affected by a bug
202
+ # where the transaction level gets persisted for the whole session:
203
+ #
204
+ # http://bugs.mysql.com/bug.php?id=39170
205
+ def supports_transaction_isolation?
206
+ version >= '5.0.0'
207
+ end
208
+
209
+ def supports_indexes_in_create?
210
+ true
211
+ end
212
+
213
+ def supports_foreign_keys?
214
+ true
215
+ end
216
+
217
+ def supports_views?
218
+ version >= '5.0.0'
219
+ end
220
+
221
+ def supports_datetime_with_precision?
222
+ version >= '5.6.4'
223
+ end
224
+
225
+ def native_database_types
226
+ NATIVE_DATABASE_TYPES
227
+ end
228
+
229
+ def index_algorithms
230
+ { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
231
+ end
232
+
233
+ # HELPER METHODS ===========================================
234
+
235
+ # The two drivers have slightly different ways of yielding hashes of results, so
236
+ # this method must be implemented to provide a uniform interface.
237
+ def each_hash(result) # :nodoc:
238
+ raise NotImplementedError
239
+ end
240
+
241
+ def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
242
+ Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
243
+ end
244
+
245
+ # Must return the MySQL error number from the exception, if the exception has an
246
+ # error number.
247
+ def error_number(exception) # :nodoc:
248
+ raise NotImplementedError
249
+ end
250
+
251
+ # QUOTING ==================================================
252
+
253
+ def _quote(value) # :nodoc:
254
+ if value.is_a?(Type::Binary::Data)
255
+ "x'#{value.hex}'"
256
+ else
257
+ super
258
+ end
259
+ end
260
+
261
+ def quote_column_name(name) #:nodoc:
262
+ @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
263
+ end
264
+
265
+ def quote_table_name(name) #:nodoc:
266
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
267
+ end
268
+
269
+ def quoted_true
270
+ QUOTED_TRUE
271
+ end
272
+
273
+ def unquoted_true
274
+ 1
275
+ end
276
+
277
+ def quoted_false
278
+ QUOTED_FALSE
279
+ end
280
+
281
+ def unquoted_false
282
+ 0
283
+ end
284
+
285
+ def quoted_date(value)
286
+ if supports_datetime_with_precision? && value.acts_like?(:time) && value.respond_to?(:usec)
287
+ "#{super}.#{sprintf("%06d", value.usec)}"
288
+ else
289
+ super
290
+ end
291
+ end
292
+
293
+ # REFERENTIAL INTEGRITY ====================================
294
+
295
+ def disable_referential_integrity #:nodoc:
296
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
297
+
298
+ begin
299
+ update("SET FOREIGN_KEY_CHECKS = 0")
300
+ yield
301
+ ensure
302
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
303
+ end
304
+ end
305
+
306
+ #--
307
+ # DATABASE STATEMENTS ======================================
308
+ #++
309
+
310
+ def clear_cache!
311
+ super
312
+ reload_type_map
313
+ end
314
+
315
+ # Executes the SQL statement in the context of this connection.
316
+ def execute(sql, name = nil)
317
+ log(sql, name) { @connection.query(sql) }
318
+ end
319
+
320
+ # MysqlAdapter has to free a result after using it, so we use this method to write
321
+ # stuff in an abstract way without concerning ourselves about whether it needs to be
322
+ # explicitly freed or not.
323
+ def execute_and_free(sql, name = nil) #:nodoc:
324
+ yield execute(sql, name)
325
+ end
326
+
327
+ def update_sql(sql, name = nil) #:nodoc:
328
+ super
329
+ @connection.affected_rows
330
+ end
331
+
332
+ def begin_db_transaction
333
+ execute "BEGIN"
334
+ end
335
+
336
+ def begin_isolated_db_transaction(isolation)
337
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
338
+ begin_db_transaction
339
+ end
340
+
341
+ def commit_db_transaction #:nodoc:
342
+ execute "COMMIT"
343
+ end
344
+
345
+ def exec_rollback_db_transaction #:nodoc:
346
+ execute "ROLLBACK"
347
+ end
348
+
349
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
350
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
351
+ # these, we must use a subquery.
352
+ def join_to_update(update, select) #:nodoc:
353
+ if select.limit || select.offset || select.orders.any?
354
+ super
355
+ else
356
+ update.table select.source
357
+ update.wheres = select.constraints
358
+ end
359
+ end
360
+
361
+ def empty_insert_statement_value
362
+ "VALUES ()"
363
+ end
364
+
365
+ # SCHEMA STATEMENTS ========================================
366
+
367
+ # Drops the database specified on the +name+ attribute
368
+ # and creates it again using the provided +options+.
369
+ def recreate_database(name, options = {})
370
+ drop_database(name)
371
+ sql = create_database(name, options)
372
+ reconnect!
373
+ sql
374
+ end
375
+
376
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
377
+ # Charset defaults to utf8.
378
+ #
379
+ # Example:
380
+ # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
381
+ # create_database 'matt_development'
382
+ # create_database 'matt_development', charset: :big5
383
+ def create_database(name, options = {})
384
+ if options[:collation]
385
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
386
+ else
387
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
388
+ end
389
+ end
390
+
391
+ # Drops a MySQL database.
392
+ #
393
+ # Example:
394
+ # drop_database('sebastian_development')
395
+ def drop_database(name) #:nodoc:
396
+ execute "DROP DATABASE IF EXISTS `#{name}`"
397
+ end
398
+
399
+ def current_database
400
+ select_value 'SELECT DATABASE() as db'
401
+ end
402
+
403
+ # Returns the database character set.
404
+ def charset
405
+ show_variable 'character_set_database'
406
+ end
407
+
408
+ # Returns the database collation strategy.
409
+ def collation
410
+ show_variable 'collation_database'
411
+ end
412
+
413
+ def tables(name = nil, database = nil, like = nil) #:nodoc:
414
+ sql = "SHOW TABLES "
415
+ sql << "IN #{quote_table_name(database)} " if database
416
+ sql << "LIKE #{quote(like)}" if like
417
+
418
+ execute_and_free(sql, 'SCHEMA') do |result|
419
+ result.collect { |field| field.first }
420
+ end
421
+ end
422
+ alias data_sources tables
423
+
424
+ def truncate(table_name, name = nil)
425
+ execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
426
+ end
427
+
428
+ def table_exists?(name)
429
+ return false unless name.present?
430
+ return true if tables(nil, nil, name).any?
431
+
432
+ name = name.to_s
433
+ schema, table = name.split('.', 2)
434
+
435
+ unless table # A table was provided without a schema
436
+ table = schema
437
+ schema = nil
438
+ end
439
+
440
+ tables(nil, schema, table).any?
441
+ end
442
+ alias data_source_exists? table_exists?
443
+
444
+ # Returns an array of indexes for the given table.
445
+ def indexes(table_name, name = nil) #:nodoc:
446
+ indexes = []
447
+ current_index = nil
448
+ execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
449
+ each_hash(result) do |row|
450
+ if current_index != row[:Key_name]
451
+ next if row[:Key_name] == 'PRIMARY' # skip the primary key
452
+ current_index = row[:Key_name]
453
+
454
+ mysql_index_type = row[:Index_type].downcase.to_sym
455
+ index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
456
+ index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
457
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
458
+ end
459
+
460
+ indexes.last.columns << row[:Column_name]
461
+ indexes.last.lengths << row[:Sub_part]
462
+ end
463
+ end
464
+
465
+ indexes
466
+ end
467
+
468
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
469
+ def columns(table_name)#:nodoc:
470
+ sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
471
+ execute_and_free(sql, 'SCHEMA') do |result|
472
+ each_hash(result).map do |field|
473
+ field_name = set_field_encoding(field[:Field])
474
+ sql_type = field[:Type]
475
+ cast_type = lookup_cast_type(sql_type)
476
+ new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
477
+ end
478
+ end
479
+ end
480
+
481
+ def create_table(table_name, options = {}) #:nodoc:
482
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
483
+ end
484
+
485
+ def bulk_change_table(table_name, operations) #:nodoc:
486
+ sqls = operations.flat_map do |command, args|
487
+ table, arguments = args.shift, args
488
+ method = :"#{command}_sql"
489
+
490
+ if respond_to?(method, true)
491
+ send(method, table, *arguments)
492
+ else
493
+ raise "Unknown method called : #{method}(#{arguments.inspect})"
494
+ end
495
+ end.join(", ")
496
+
497
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
498
+ end
499
+
500
+ # Renames a table.
501
+ #
502
+ # Example:
503
+ # rename_table('octopuses', 'octopi')
504
+ def rename_table(table_name, new_name)
505
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
506
+ rename_table_indexes(table_name, new_name)
507
+ end
508
+
509
+ def drop_table(table_name, options = {})
510
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
511
+ end
512
+
513
+ def rename_index(table_name, old_name, new_name)
514
+ if supports_rename_index?
515
+ validate_index_length!(table_name, new_name)
516
+
517
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
518
+ else
519
+ super
520
+ end
521
+ end
522
+
523
+ def change_column_default(table_name, column_name, default) #:nodoc:
524
+ column = column_for(table_name, column_name)
525
+ change_column table_name, column_name, column.sql_type, :default => default
526
+ end
527
+
528
+ def change_column_null(table_name, column_name, null, default = nil)
529
+ column = column_for(table_name, column_name)
530
+
531
+ unless null || default.nil?
532
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
533
+ end
534
+
535
+ change_column table_name, column_name, column.sql_type, :null => null
536
+ end
537
+
538
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
539
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
540
+ end
541
+
542
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
543
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
544
+ rename_column_indexes(table_name, column_name, new_column_name)
545
+ end
546
+
547
+ def add_index(table_name, column_name, options = {}) #:nodoc:
548
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
549
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
550
+ end
551
+
552
+ def foreign_keys(table_name)
553
+ fk_info = select_all <<-SQL.strip_heredoc
554
+ SELECT fk.referenced_table_name as 'to_table'
555
+ ,fk.referenced_column_name as 'primary_key'
556
+ ,fk.column_name as 'column'
557
+ ,fk.constraint_name as 'name'
558
+ FROM information_schema.key_column_usage fk
559
+ WHERE fk.referenced_column_name is not null
560
+ AND fk.table_schema = '#{@config[:database]}'
561
+ AND fk.table_name = '#{table_name}'
562
+ SQL
563
+
564
+ create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
565
+
566
+ fk_info.map do |row|
567
+ options = {
568
+ column: row['column'],
569
+ name: row['name'],
570
+ primary_key: row['primary_key']
571
+ }
572
+
573
+ options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
574
+ options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
575
+
576
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
577
+ end
578
+ end
579
+
580
+ # Maps logical Rails types to MySQL-specific data types.
581
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
582
+ case type.to_s
583
+ when 'binary'
584
+ case limit
585
+ when 0..0xfff; "varbinary(#{limit})"
586
+ when nil; "blob"
587
+ when 0x1000..0xffffffff; "blob(#{limit})"
588
+ else raise(ActiveRecordError, "No binary type has character length #{limit}")
589
+ end
590
+ when 'integer'
591
+ case limit
592
+ when 1; 'tinyint'
593
+ when 2; 'smallint'
594
+ when 3; 'mediumint'
595
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
596
+ when 5..8; 'bigint'
597
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
598
+ end
599
+ when 'text'
600
+ case limit
601
+ when 0..0xff; 'tinytext'
602
+ when nil, 0x100..0xffff; 'text'
603
+ when 0x10000..0xffffff; 'mediumtext'
604
+ when 0x1000000..0xffffffff; 'longtext'
605
+ else raise(ActiveRecordError, "No text type has character length #{limit}")
606
+ end
607
+ when 'datetime'
608
+ return super unless precision
609
+
610
+ case precision
611
+ when 0..6; "datetime(#{precision})"
612
+ else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
613
+ end
614
+ else
615
+ super
616
+ end
617
+ end
618
+
619
+ # SHOW VARIABLES LIKE 'name'
620
+ def show_variable(name)
621
+ variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
622
+ variables.first['Value'] unless variables.empty?
623
+ rescue ActiveRecord::StatementInvalid
624
+ nil
625
+ end
626
+
627
+ # Returns a table's primary key and belonging sequence.
628
+ def pk_and_sequence_for(table)
629
+ execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
630
+ create_table = each_hash(result).first[:"Create Table"]
631
+ if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
632
+ keys = $1.split(",").map { |key| key.delete('`"') }
633
+ keys.length == 1 ? [keys.first, nil] : nil
634
+ else
635
+ nil
636
+ end
637
+ end
638
+ end
639
+
640
+ # Returns just a table's primary key
641
+ def primary_key(table)
642
+ pk_and_sequence = pk_and_sequence_for(table)
643
+ pk_and_sequence && pk_and_sequence.first
644
+ end
645
+
646
+ def case_sensitive_modifier(node, table_attribute)
647
+ node = Arel::Nodes.build_quoted node, table_attribute
648
+ Arel::Nodes::Bin.new(node)
649
+ end
650
+
651
+ def case_sensitive_comparison(table, attribute, column, value)
652
+ if column.case_sensitive?
653
+ table[attribute].eq(value)
654
+ else
655
+ super
656
+ end
657
+ end
658
+
659
+ def case_insensitive_comparison(table, attribute, column, value)
660
+ if column.case_sensitive?
661
+ super
662
+ else
663
+ table[attribute].eq(value)
664
+ end
665
+ end
666
+
667
+ # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
668
+ # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
669
+ # distinct queries, and requires that the ORDER BY include the distinct column.
670
+ # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
671
+ def columns_for_distinct(columns, orders) # :nodoc:
672
+ order_columns = orders.reject(&:blank?).map { |s|
673
+ # Convert Arel node to string
674
+ s = s.to_sql unless s.is_a?(String)
675
+ # Remove any ASC/DESC modifiers
676
+ s.gsub(/\s+(?:ASC|DESC)\b/i, '')
677
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
678
+
679
+ [super, *order_columns].join(', ')
680
+ end
681
+
682
+ def strict_mode?
683
+ self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
684
+ end
685
+
686
+ def valid_type?(type)
687
+ !native_database_types[type].nil?
688
+ end
689
+
690
+ protected
691
+
692
+ def initialize_type_map(m) # :nodoc:
693
+ super
694
+
695
+ register_class_with_limit m, %r(char)i, MysqlString
696
+
697
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
698
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
699
+ m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
700
+ m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
701
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
702
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
703
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
704
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
705
+ m.register_type %r(^float)i, Type::Float.new(limit: 24)
706
+ m.register_type %r(^double)i, Type::Float.new(limit: 53)
707
+
708
+ register_integer_type m, %r(^bigint)i, limit: 8
709
+ register_integer_type m, %r(^int)i, limit: 4
710
+ register_integer_type m, %r(^mediumint)i, limit: 3
711
+ register_integer_type m, %r(^smallint)i, limit: 2
712
+ register_integer_type m, %r(^tinyint)i, limit: 1
713
+
714
+ m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
715
+ m.alias_type %r(set)i, 'varchar'
716
+ m.alias_type %r(year)i, 'integer'
717
+ m.alias_type %r(bit)i, 'binary'
718
+
719
+ m.register_type(%r(datetime)i) do |sql_type|
720
+ precision = extract_precision(sql_type)
721
+ MysqlDateTime.new(precision: precision)
722
+ end
723
+
724
+ m.register_type(%r(enum)i) do |sql_type|
725
+ limit = sql_type[/^enum\((.+)\)/i, 1]
726
+ .split(',').map{|enum| enum.strip.length - 2}.max
727
+ MysqlString.new(limit: limit)
728
+ end
729
+ end
730
+
731
+ def register_integer_type(mapping, key, options) # :nodoc:
732
+ mapping.register_type(key) do |sql_type|
733
+ if /unsigned/i =~ sql_type
734
+ Type::UnsignedInteger.new(options)
735
+ else
736
+ Type::Integer.new(options)
737
+ end
738
+ end
739
+ end
740
+
741
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
742
+ # to give it some prompting in the form of a subsubquery. Ugh!
743
+ def subquery_for(key, select)
744
+ subsubselect = select.clone
745
+ subsubselect.projections = [key]
746
+
747
+ # Materialize subquery by adding distinct
748
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
749
+ subsubselect.distinct unless select.limit || select.offset || select.orders.any?
750
+
751
+ subselect = Arel::SelectManager.new(select.engine)
752
+ subselect.project Arel.sql(key.name)
753
+ subselect.from subsubselect.as('__active_record_temp')
754
+ end
755
+
756
+ def add_index_length(option_strings, column_names, options = {})
757
+ if options.is_a?(Hash) && length = options[:length]
758
+ case length
759
+ when Hash
760
+ column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
761
+ when Integer
762
+ column_names.each {|name| option_strings[name] += "(#{length})"}
763
+ end
764
+ end
765
+
766
+ return option_strings
767
+ end
768
+
769
+ def quoted_columns_for_index(column_names, options = {})
770
+ option_strings = Hash[column_names.map {|name| [name, '']}]
771
+
772
+ # add index length
773
+ option_strings = add_index_length(option_strings, column_names, options)
774
+
775
+ # add index sort order
776
+ option_strings = add_index_sort_order(option_strings, column_names, options)
777
+
778
+ column_names.map {|name| quote_column_name(name) + option_strings[name]}
779
+ end
780
+
781
+ def translate_exception(exception, message)
782
+ case error_number(exception)
783
+ when 1062
784
+ RecordNotUnique.new(message, exception)
785
+ when 1452
786
+ InvalidForeignKey.new(message, exception)
787
+ else
788
+ super
789
+ end
790
+ end
791
+
792
+ def add_column_sql(table_name, column_name, type, options = {})
793
+ td = create_table_definition table_name, options[:temporary], options[:options]
794
+ cd = td.new_column_definition(column_name, type, options)
795
+ schema_creation.visit_AddColumn cd
796
+ end
797
+
798
+ def change_column_sql(table_name, column_name, type, options = {})
799
+ column = column_for(table_name, column_name)
800
+
801
+ unless options_include_default?(options)
802
+ options[:default] = column.default
803
+ end
804
+
805
+ unless options.has_key?(:null)
806
+ options[:null] = column.null
807
+ end
808
+
809
+ options[:name] = column.name
810
+ schema_creation.accept ChangeColumnDefinition.new column, type, options
811
+ end
812
+
813
+ def rename_column_sql(table_name, column_name, new_column_name)
814
+ column = column_for(table_name, column_name)
815
+ options = {
816
+ name: new_column_name,
817
+ default: column.default,
818
+ null: column.null,
819
+ auto_increment: column.extra == "auto_increment"
820
+ }
821
+
822
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
823
+ schema_creation.accept ChangeColumnDefinition.new column, current_type, options
824
+ end
825
+
826
+ def remove_column_sql(table_name, column_name, type = nil, options = {})
827
+ "DROP #{quote_column_name(column_name)}"
828
+ end
829
+
830
+ def remove_columns_sql(table_name, *column_names)
831
+ column_names.map {|column_name| remove_column_sql(table_name, column_name) }
832
+ end
833
+
834
+ def add_index_sql(table_name, column_name, options = {})
835
+ index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
836
+ "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
837
+ end
838
+
839
+ def remove_index_sql(table_name, options = {})
840
+ index_name = index_name_for_remove(table_name, options)
841
+ "DROP INDEX #{index_name}"
842
+ end
843
+
844
+ def add_timestamps_sql(table_name, options = {})
845
+ [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
846
+ end
847
+
848
+ def remove_timestamps_sql(table_name, options = {})
849
+ [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
850
+ end
851
+
852
+ private
853
+
854
+ def version
855
+ @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
856
+ end
857
+
858
+ def mariadb?
859
+ full_version =~ /mariadb/i
860
+ end
861
+
862
+ def supports_rename_index?
863
+ mariadb? ? false : version >= '5.7.6'
864
+ end
865
+
866
+ def configure_connection
867
+ variables = @config.fetch(:variables, {}).stringify_keys
868
+
869
+ # By default, MySQL 'where id is null' selects the last inserted id.
870
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
871
+ variables['sql_auto_is_null'] = 0
872
+
873
+ # Increase timeout so the server doesn't disconnect us.
874
+ wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
875
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
876
+ variables["wait_timeout"] = wait_timeout
877
+
878
+ # Make MySQL reject illegal values rather than truncating or blanking them, see
879
+ # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
880
+ # If the user has provided another value for sql_mode, don't replace it.
881
+ unless variables.has_key?('sql_mode')
882
+ variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
883
+ end
884
+
885
+ # NAMES does not have an equals sign, see
886
+ # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
887
+ # (trailing comma because variable_assignments will always have content)
888
+ if @config[:encoding]
889
+ encoding = "NAMES #{@config[:encoding]}"
890
+ encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
891
+ encoding << ", "
892
+ end
893
+
894
+ # Gather up all of the SET variables...
895
+ variable_assignments = variables.map do |k, v|
896
+ if v == ':default' || v == :default
897
+ "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
898
+ elsif !v.nil?
899
+ "@@SESSION.#{k} = #{quote(v)}"
900
+ end
901
+ # or else nil; compact to clear nils out
902
+ end.compact.join(', ')
903
+
904
+ # ...and send them all in one query
905
+ @connection.query "SET #{encoding} #{variable_assignments}"
906
+ end
907
+
908
+ def extract_foreign_key_action(structure, name, action) # :nodoc:
909
+ if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
910
+ case $1
911
+ when 'CASCADE'; :cascade
912
+ when 'SET NULL'; :nullify
913
+ end
914
+ end
915
+ end
916
+
917
+ class MysqlDateTime < Type::DateTime # :nodoc:
918
+ private
919
+
920
+ def has_precision?
921
+ precision || 0
922
+ end
923
+ end
924
+
925
+ class MysqlString < Type::String # :nodoc:
926
+ def type_cast_for_database(value)
927
+ case value
928
+ when true then "1"
929
+ when false then "0"
930
+ else super
931
+ end
932
+ end
933
+
934
+ private
935
+
936
+ def cast_value(value)
937
+ case value
938
+ when true then "1"
939
+ when false then "0"
940
+ else super
941
+ end
942
+ end
943
+ end
944
+ end
945
+ end
946
+ end