activerecord 3.2.22.5 → 4.2.11.3

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 (236) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1632 -609
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +37 -41
  5. data/examples/performance.rb +31 -19
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +56 -42
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -36
  10. data/lib/active_record/associations/association.rb +73 -55
  11. data/lib/active_record/associations/association_scope.rb +143 -82
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +125 -31
  15. data/lib/active_record/associations/builder/belongs_to.rb +89 -61
  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 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +23 -17
  21. data/lib/active_record/associations/collection_association.rb +251 -177
  22. data/lib/active_record/associations/collection_proxy.rb +963 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +113 -22
  25. data/lib/active_record/associations/has_many_through_association.rb +99 -39
  26. data/lib/active_record/associations/has_one_association.rb +43 -20
  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 -107
  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 +62 -33
  38. data/lib/active_record/associations/preloader.rb +101 -79
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +30 -16
  41. data/lib/active_record/associations.rb +463 -345
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +142 -151
  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 +137 -57
  47. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +73 -106
  50. data/lib/active_record/attribute_methods/serialization.rb +44 -94
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
  52. data/lib/active_record/attribute_methods/write.rb +57 -44
  53. data/lib/active_record/attribute_methods.rb +301 -141
  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 +246 -217
  58. data/lib/active_record/base.rb +70 -474
  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 +396 -219
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
  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 +261 -169
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
  75. data/lib/active_record/connection_adapters/column.rb +31 -245
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
  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 +430 -999
  114. data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
  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 +157 -105
  119. data/lib/active_record/dynamic_matchers.rb +119 -63
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +94 -36
  122. data/lib/active_record/explain.rb +15 -63
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +9 -5
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +302 -215
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +143 -70
  129. data/lib/active_record/integration.rb +65 -12
  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 +73 -52
  133. data/lib/active_record/locking/pessimistic.rb +5 -5
  134. data/lib/active_record/log_subscriber.rb +24 -21
  135. data/lib/active_record/migration/command_recorder.rb +124 -32
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +511 -213
  138. data/lib/active_record/model_schema.rb +91 -117
  139. data/lib/active_record/nested_attributes.rb +184 -130
  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 +276 -117
  143. data/lib/active_record/query_cache.rb +19 -37
  144. data/lib/active_record/querying.rb +28 -18
  145. data/lib/active_record/railtie.rb +73 -40
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +4 -3
  148. data/lib/active_record/railties/databases.rake +141 -416
  149. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  150. data/lib/active_record/readonly_attributes.rb +1 -4
  151. data/lib/active_record/reflection.rb +513 -154
  152. data/lib/active_record/relation/batches.rb +91 -43
  153. data/lib/active_record/relation/calculations.rb +199 -161
  154. data/lib/active_record/relation/delegation.rb +116 -25
  155. data/lib/active_record/relation/finder_methods.rb +362 -248
  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 -43
  160. data/lib/active_record/relation/query_methods.rb +928 -167
  161. data/lib/active_record/relation/spawn_methods.rb +48 -149
  162. data/lib/active_record/relation.rb +352 -207
  163. data/lib/active_record/result.rb +101 -10
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +56 -59
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +106 -63
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +50 -57
  170. data/lib/active_record/scoping/named.rb +73 -109
  171. data/lib/active_record/scoping.rb +58 -123
  172. data/lib/active_record/serialization.rb +6 -2
  173. data/lib/active_record/serializers/xml_serializer.rb +12 -22
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +168 -15
  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 +23 -16
  181. data/lib/active_record/transactions.rb +125 -79
  182. data/lib/active_record/type/big_integer.rb +13 -0
  183. data/lib/active_record/type/binary.rb +50 -0
  184. data/lib/active_record/type/boolean.rb +31 -0
  185. data/lib/active_record/type/date.rb +50 -0
  186. data/lib/active_record/type/date_time.rb +54 -0
  187. data/lib/active_record/type/decimal.rb +64 -0
  188. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  189. data/lib/active_record/type/decorator.rb +14 -0
  190. data/lib/active_record/type/float.rb +19 -0
  191. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  192. data/lib/active_record/type/integer.rb +59 -0
  193. data/lib/active_record/type/mutable.rb +16 -0
  194. data/lib/active_record/type/numeric.rb +36 -0
  195. data/lib/active_record/type/serialized.rb +62 -0
  196. data/lib/active_record/type/string.rb +40 -0
  197. data/lib/active_record/type/text.rb +11 -0
  198. data/lib/active_record/type/time.rb +26 -0
  199. data/lib/active_record/type/time_value.rb +38 -0
  200. data/lib/active_record/type/type_map.rb +64 -0
  201. data/lib/active_record/type/unsigned_integer.rb +15 -0
  202. data/lib/active_record/type/value.rb +110 -0
  203. data/lib/active_record/type.rb +23 -0
  204. data/lib/active_record/validations/associated.rb +24 -16
  205. data/lib/active_record/validations/presence.rb +67 -0
  206. data/lib/active_record/validations/uniqueness.rb +123 -64
  207. data/lib/active_record/validations.rb +36 -29
  208. data/lib/active_record/version.rb +5 -7
  209. data/lib/active_record.rb +66 -46
  210. data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
  211. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
  212. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  213. data/lib/rails/generators/active_record/migration.rb +11 -8
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
  215. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  216. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  217. data/lib/rails/generators/active_record.rb +3 -11
  218. metadata +101 -45
  219. data/examples/associations.png +0 -0
  220. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  221. data/lib/active_record/associations/join_helper.rb +0 -55
  222. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  223. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  226. data/lib/active_record/dynamic_finder_match.rb +0 -68
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/fixtures/file.rb +0 -65
  229. data/lib/active_record/identity_map.rb +0 -162
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -360
  232. data/lib/active_record/test_case.rb +0 -73
  233. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  234. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  235. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  236. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,83 +1,110 @@
1
- require 'active_support/core_ext/object/blank'
2
1
  require 'arel/visitors/bind_visitor'
2
+ require 'active_support/core_ext/string/strip'
3
3
 
4
4
  module ActiveRecord
5
5
  module ConnectionAdapters
6
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
+
7
67
  class Column < ConnectionAdapters::Column # :nodoc:
8
- attr_reader :collation
68
+ attr_reader :collation, :strict, :extra
9
69
 
10
- def initialize(name, default, sql_type = nil, null = true, collation = nil)
11
- super(name, default, sql_type, null)
70
+ def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
71
+ @strict = strict
12
72
  @collation = collation
73
+ @extra = extra
74
+ super(name, default, cast_type, sql_type, null)
75
+ assert_valid_default(default)
76
+ extract_default
13
77
  end
14
78
 
15
- def extract_default(default)
16
- if sql_type =~ /blob/i || type == :text
17
- if default.blank?
18
- return null ? nil : ''
19
- else
20
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
21
- end
22
- elsif missing_default_forged_as_empty_string?(default)
23
- nil
24
- else
25
- super
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
26
84
  end
27
85
  end
28
86
 
29
87
  def has_default?
30
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
88
+ return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
31
89
  super
32
90
  end
33
91
 
34
- # Must return the relevant concrete adapter
35
- def adapter
36
- raise NotImplementedError
92
+ def blob_or_text_column?
93
+ sql_type =~ /blob/i || type == :text
37
94
  end
38
95
 
39
96
  def case_sensitive?
40
97
  collation && !collation.match(/_ci$/)
41
98
  end
42
99
 
43
- private
44
-
45
- def simplified_type(field_type)
46
- return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
47
-
48
- case field_type
49
- when /enum/i, /set/i then :string
50
- when /year/i then :integer
51
- when /bit/i then :binary
52
- else
53
- super
54
- end
100
+ def ==(other)
101
+ super &&
102
+ collation == other.collation &&
103
+ strict == other.strict &&
104
+ extra == other.extra
55
105
  end
56
106
 
57
- def extract_limit(sql_type)
58
- case sql_type
59
- when /blob|text/i
60
- case sql_type
61
- when /tiny/i
62
- 255
63
- when /medium/i
64
- 16777215
65
- when /long/i
66
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
67
- else
68
- super # we could return 65535 here, but we leave it undecorated by default
69
- end
70
- when /^bigint/i; 8
71
- when /^int/i; 4
72
- when /^mediumint/i; 3
73
- when /^smallint/i; 2
74
- when /^tinyint/i; 1
75
- when /^enum\((.+)\)/i
76
- $1.split(',').map{|enum| enum.strip.length - 2}.max
77
- else
78
- super
79
- end
80
- end
107
+ private
81
108
 
82
109
  # MySQL misreports NOT NULL column default when none is given.
83
110
  # We can't detect this for columns which may have a legitimate ''
@@ -89,6 +116,16 @@ module ActiveRecord
89
116
  def missing_default_forged_as_empty_string?(default)
90
117
  type != :string && !null && default == ''
91
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
92
129
  end
93
130
 
94
131
  ##
@@ -111,23 +148,21 @@ module ActiveRecord
111
148
  QUOTED_TRUE, QUOTED_FALSE = '1', '0'
112
149
 
113
150
  NATIVE_DATABASE_TYPES = {
114
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
151
+ :primary_key => "int(11) auto_increment PRIMARY KEY",
115
152
  :string => { :name => "varchar", :limit => 255 },
116
153
  :text => { :name => "text" },
117
154
  :integer => { :name => "int", :limit => 4 },
118
155
  :float => { :name => "float" },
119
156
  :decimal => { :name => "decimal" },
120
157
  :datetime => { :name => "datetime" },
121
- :timestamp => { :name => "datetime" },
122
158
  :time => { :name => "time" },
123
159
  :date => { :name => "date" },
124
160
  :binary => { :name => "blob" },
125
161
  :boolean => { :name => "tinyint", :limit => 1 }
126
162
  }
127
163
 
128
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
129
- include Arel::Visitors::BindVisitor
130
- end
164
+ INDEX_TYPES = [:fulltext, :spatial]
165
+ INDEX_USINGS = [:btree, :hash]
131
166
 
132
167
  # FIXME: Make the first parameter more similar for the two adapters
133
168
  def initialize(connection, logger, connection_options, config)
@@ -135,17 +170,15 @@ module ActiveRecord
135
170
  @connection_options, @config = connection_options, config
136
171
  @quoted_column_names, @quoted_table_names = {}, {}
137
172
 
138
- if config.fetch(:prepared_statements) { true }
139
- @visitor = Arel::Visitors::MySQL.new self
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
140
177
  else
141
- @visitor = BindSubstitution.new self
178
+ @prepared_statements = false
142
179
  end
143
180
  end
144
181
 
145
- def adapter_name #:nodoc:
146
- self.class::ADAPTER_NAME
147
- end
148
-
149
182
  # Returns true, since this connection adapter supports migrations.
150
183
  def supports_migrations?
151
184
  true
@@ -155,11 +188,6 @@ module ActiveRecord
155
188
  true
156
189
  end
157
190
 
158
- # Returns true, since this connection adapter supports savepoints.
159
- def supports_savepoints?
160
- true
161
- end
162
-
163
191
  def supports_bulk_alter? #:nodoc:
164
192
  true
165
193
  end
@@ -170,10 +198,38 @@ module ActiveRecord
170
198
  true
171
199
  end
172
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
+
173
225
  def native_database_types
174
226
  NATIVE_DATABASE_TYPES
175
227
  end
176
228
 
229
+ def index_algorithms
230
+ { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
231
+ end
232
+
177
233
  # HELPER METHODS ===========================================
178
234
 
179
235
  # The two drivers have slightly different ways of yielding hashes of results, so
@@ -182,12 +238,11 @@ module ActiveRecord
182
238
  raise NotImplementedError
183
239
  end
184
240
 
185
- # Overridden by the adapters to instantiate their specific Column type.
186
- def new_column(field, default, type, null, collation) # :nodoc:
187
- Column.new(field, default, type, null, collation)
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)
188
243
  end
189
244
 
190
- # Must return the Mysql error number from the exception, if the exception has an
245
+ # Must return the MySQL error number from the exception, if the exception has an
191
246
  # error number.
192
247
  def error_number(exception) # :nodoc:
193
248
  raise NotImplementedError
@@ -195,12 +250,9 @@ module ActiveRecord
195
250
 
196
251
  # QUOTING ==================================================
197
252
 
198
- def quote(value, column = nil)
199
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
200
- s = column.class.string_to_binary(value).unpack("H*")[0]
201
- "x'#{s}'"
202
- elsif value.kind_of?(BigDecimal)
203
- value.to_s("F")
253
+ def _quote(value) # :nodoc:
254
+ if value.is_a?(Type::Binary::Data)
255
+ "x'#{value.hex}'"
204
256
  else
205
257
  super
206
258
  end
@@ -218,13 +270,29 @@ module ActiveRecord
218
270
  QUOTED_TRUE
219
271
  end
220
272
 
273
+ def unquoted_true
274
+ 1
275
+ end
276
+
221
277
  def quoted_false
222
278
  QUOTED_FALSE
223
279
  end
224
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
+
225
293
  # REFERENTIAL INTEGRITY ====================================
226
294
 
227
- def disable_referential_integrity(&block) #:nodoc:
295
+ def disable_referential_integrity #:nodoc:
228
296
  old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
229
297
 
230
298
  begin
@@ -235,25 +303,22 @@ module ActiveRecord
235
303
  end
236
304
  end
237
305
 
306
+ #--
238
307
  # DATABASE STATEMENTS ======================================
308
+ #++
309
+
310
+ def clear_cache!
311
+ super
312
+ reload_type_map
313
+ end
239
314
 
240
315
  # Executes the SQL statement in the context of this connection.
241
316
  def execute(sql, name = nil)
242
- if name == :skip_logging
243
- @connection.query(sql)
244
- else
245
- log(sql, name) { @connection.query(sql) }
246
- end
247
- rescue ActiveRecord::StatementInvalid => exception
248
- if exception.message.split(":").first =~ /Packets out of order/
249
- 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."
250
- else
251
- raise
252
- end
317
+ log(sql, name) { @connection.query(sql) }
253
318
  end
254
319
 
255
320
  # MysqlAdapter has to free a result after using it, so we use this method to write
256
- # stuff in a abstract way without concerning ourselves about whether it needs to be
321
+ # stuff in an abstract way without concerning ourselves about whether it needs to be
257
322
  # explicitly freed or not.
258
323
  def execute_and_free(sql, name = nil) #:nodoc:
259
324
  yield execute(sql, name)
@@ -266,85 +331,55 @@ module ActiveRecord
266
331
 
267
332
  def begin_db_transaction
268
333
  execute "BEGIN"
269
- rescue Exception
270
- # Transactions aren't supported
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
271
339
  end
272
340
 
273
341
  def commit_db_transaction #:nodoc:
274
342
  execute "COMMIT"
275
- rescue Exception
276
- # Transactions aren't supported
277
343
  end
278
344
 
279
- def rollback_db_transaction #:nodoc:
345
+ def exec_rollback_db_transaction #:nodoc:
280
346
  execute "ROLLBACK"
281
- rescue Exception
282
- # Transactions aren't supported
283
- end
284
-
285
- def create_savepoint
286
- execute("SAVEPOINT #{current_savepoint_name}")
287
- end
288
-
289
- def rollback_to_savepoint
290
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
291
- end
292
-
293
- def release_savepoint
294
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
295
347
  end
296
348
 
297
349
  # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
298
350
  # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
299
- # these, we must use a subquery. However, MySQL is too stupid to create a
300
- # temporary table for this automatically, so we have to give it some prompting
301
- # in the form of a subsubquery. Ugh!
351
+ # these, we must use a subquery.
302
352
  def join_to_update(update, select) #:nodoc:
303
353
  if select.limit || select.offset || select.orders.any?
304
- subsubselect = select.clone
305
- subsubselect.projections = [update.key]
306
-
307
- subselect = Arel::SelectManager.new(select.engine)
308
- subselect.project Arel.sql(update.key.name)
309
- subselect.from subsubselect.as('__active_record_temp')
310
-
311
- update.where update.key.in(subselect)
354
+ super
312
355
  else
313
356
  update.table select.source
314
357
  update.wheres = select.constraints
315
358
  end
316
359
  end
317
360
 
318
- # SCHEMA STATEMENTS ========================================
319
-
320
- def structure_dump #:nodoc:
321
- if supports_views?
322
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
323
- else
324
- sql = "SHOW TABLES"
325
- end
326
-
327
- select_all(sql).map { |table|
328
- table.delete('Table_type')
329
- sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
330
- exec_query(sql).first['Create Table'] + ";\n\n"
331
- }.join
361
+ def empty_insert_statement_value
362
+ "VALUES ()"
332
363
  end
333
364
 
365
+ # SCHEMA STATEMENTS ========================================
366
+
334
367
  # Drops the database specified on the +name+ attribute
335
368
  # and creates it again using the provided +options+.
336
369
  def recreate_database(name, options = {})
337
370
  drop_database(name)
338
- create_database(name, options)
371
+ sql = create_database(name, options)
372
+ reconnect!
373
+ sql
339
374
  end
340
375
 
341
376
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
342
377
  # Charset defaults to utf8.
343
378
  #
344
379
  # Example:
345
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
380
+ # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
346
381
  # create_database 'matt_development'
347
- # create_database 'matt_development', :charset => :big5
382
+ # create_database 'matt_development', charset: :big5
348
383
  def create_database(name, options = {})
349
384
  if options[:collation]
350
385
  execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
@@ -384,9 +419,14 @@ module ActiveRecord
384
419
  result.collect { |field| field.first }
385
420
  end
386
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
387
427
 
388
428
  def table_exists?(name)
389
- return false unless name
429
+ return false unless name.present?
390
430
  return true if tables(nil, nil, name).any?
391
431
 
392
432
  name = name.to_s
@@ -399,6 +439,7 @@ module ActiveRecord
399
439
 
400
440
  tables(nil, schema, table).any?
401
441
  end
442
+ alias data_source_exists? table_exists?
402
443
 
403
444
  # Returns an array of indexes for the given table.
404
445
  def indexes(table_name, name = nil) #:nodoc:
@@ -409,7 +450,11 @@ module ActiveRecord
409
450
  if current_index != row[:Key_name]
410
451
  next if row[:Key_name] == 'PRIMARY' # skip the primary key
411
452
  current_index = row[:Key_name]
412
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
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)
413
458
  end
414
459
 
415
460
  indexes.last.columns << row[:Column_name]
@@ -421,11 +466,14 @@ module ActiveRecord
421
466
  end
422
467
 
423
468
  # Returns an array of +Column+ objects for the table specified by +table_name+.
424
- def columns(table_name, name = nil)#:nodoc:
469
+ def columns(table_name)#:nodoc:
425
470
  sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
426
471
  execute_and_free(sql, 'SCHEMA') do |result|
427
472
  each_hash(result).map do |field|
428
- new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
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])
429
477
  end
430
478
  end
431
479
  end
@@ -435,7 +483,7 @@ module ActiveRecord
435
483
  end
436
484
 
437
485
  def bulk_change_table(table_name, operations) #:nodoc:
438
- sqls = operations.map do |command, args|
486
+ sqls = operations.flat_map do |command, args|
439
487
  table, arguments = args.shift, args
440
488
  method = :"#{command}_sql"
441
489
 
@@ -444,7 +492,7 @@ module ActiveRecord
444
492
  else
445
493
  raise "Unknown method called : #{method}(#{arguments.inspect})"
446
494
  end
447
- end.flatten.join(", ")
495
+ end.join(", ")
448
496
 
449
497
  execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
450
498
  end
@@ -455,13 +503,24 @@ module ActiveRecord
455
503
  # rename_table('octopuses', 'octopi')
456
504
  def rename_table(table_name, new_name)
457
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}"
458
511
  end
459
512
 
460
- def add_column(table_name, column_name, type, options = {})
461
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
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
462
521
  end
463
522
 
464
- def change_column_default(table_name, column_name, default)
523
+ def change_column_default(table_name, column_name, default) #:nodoc:
465
524
  column = column_for(table_name, column_name)
466
525
  change_column table_name, column_name, column.sql_type, :default => default
467
526
  end
@@ -482,11 +541,52 @@ module ActiveRecord
482
541
 
483
542
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
484
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
485
578
  end
486
579
 
487
580
  # Maps logical Rails types to MySQL-specific data types.
488
581
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
489
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
490
590
  when 'integer'
491
591
  case limit
492
592
  when 1; 'tinyint'
@@ -504,23 +604,24 @@ module ActiveRecord
504
604
  when 0x1000000..0xffffffff; 'longtext'
505
605
  else raise(ActiveRecordError, "No text type has character length #{limit}")
506
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
507
614
  else
508
615
  super
509
616
  end
510
617
  end
511
618
 
512
- def add_column_position!(sql, options)
513
- if options[:first]
514
- sql << " FIRST"
515
- elsif options[:after]
516
- sql << " AFTER #{quote_column_name(options[:after])}"
517
- end
518
- end
519
-
520
619
  # SHOW VARIABLES LIKE 'name'
521
620
  def show_variable(name)
522
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
621
+ variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
523
622
  variables.first['Value'] unless variables.empty?
623
+ rescue ActiveRecord::StatementInvalid
624
+ nil
524
625
  end
525
626
 
526
627
  # Returns a table's primary key and belonging sequence.
@@ -528,7 +629,7 @@ module ActiveRecord
528
629
  execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
529
630
  create_table = each_hash(result).first[:"Create Table"]
530
631
  if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
531
- keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
632
+ keys = $1.split(",").map { |key| key.delete('`"') }
532
633
  keys.length == 1 ? [keys.first, nil] : nil
533
634
  else
534
635
  nil
@@ -542,10 +643,19 @@ module ActiveRecord
542
643
  pk_and_sequence && pk_and_sequence.first
543
644
  end
544
645
 
545
- def case_sensitive_modifier(node)
646
+ def case_sensitive_modifier(node, table_attribute)
647
+ node = Arel::Nodes.build_quoted node, table_attribute
546
648
  Arel::Nodes::Bin.new(node)
547
649
  end
548
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
+
549
659
  def case_insensitive_comparison(table, attribute, column, value)
550
660
  if column.case_sensitive?
551
661
  super
@@ -554,18 +664,101 @@ module ActiveRecord
554
664
  end
555
665
  end
556
666
 
557
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
558
- where_sql
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?
559
688
  end
560
689
 
561
690
  protected
562
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
+
563
756
  def add_index_length(option_strings, column_names, options = {})
564
757
  if options.is_a?(Hash) && length = options[:length]
565
758
  case length
566
759
  when Hash
567
760
  column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
568
- when Fixnum
761
+ when Integer
569
762
  column_names.each {|name| option_strings[name] += "(#{length})"}
570
763
  end
571
764
  end
@@ -597,10 +790,9 @@ module ActiveRecord
597
790
  end
598
791
 
599
792
  def add_column_sql(table_name, column_name, type, options = {})
600
- add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
601
- add_column_options!(add_column_sql, options)
602
- add_column_position!(add_column_sql, options)
603
- add_column_sql
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
604
796
  end
605
797
 
606
798
  def change_column_sql(table_name, column_name, type, options = {})
@@ -614,32 +806,30 @@ module ActiveRecord
614
806
  options[:null] = column.null
615
807
  end
616
808
 
617
- change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
618
- add_column_options!(change_column_sql, options)
619
- add_column_position!(change_column_sql, options)
620
- change_column_sql
809
+ options[:name] = column.name
810
+ schema_creation.accept ChangeColumnDefinition.new column, type, options
621
811
  end
622
812
 
623
813
  def rename_column_sql(table_name, column_name, new_column_name)
624
- options = {}
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
+ }
625
821
 
626
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
627
- options[:default] = column.default
628
- options[:null] = column.null
629
- else
630
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
631
- end
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
632
825
 
633
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
634
- rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
635
- add_column_options!(rename_column_sql, options)
636
- rename_column_sql
826
+ def remove_column_sql(table_name, column_name, type = nil, options = {})
827
+ "DROP #{quote_column_name(column_name)}"
637
828
  end
638
829
 
639
- def remove_column_sql(table_name, *column_names)
640
- columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
830
+ def remove_columns_sql(table_name, *column_names)
831
+ column_names.map {|column_name| remove_column_sql(table_name, column_name) }
641
832
  end
642
- alias :remove_columns_sql :remove_column
643
833
 
644
834
  def add_index_sql(table_name, column_name, options = {})
645
835
  index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
@@ -651,25 +841,105 @@ module ActiveRecord
651
841
  "DROP INDEX #{index_name}"
652
842
  end
653
843
 
654
- def add_timestamps_sql(table_name)
655
- [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
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)]
656
846
  end
657
847
 
658
- def remove_timestamps_sql(table_name)
848
+ def remove_timestamps_sql(table_name, options = {})
659
849
  [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
660
850
  end
661
851
 
662
852
  private
663
853
 
664
- def supports_views?
665
- version[0] >= 5
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
666
915
  end
667
916
 
668
- def column_for(table_name, column_name)
669
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
670
- raise "No such column: #{table_name}.#{column_name}"
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
671
942
  end
672
- column
673
943
  end
674
944
  end
675
945
  end