activerecord 4.2.11.3 → 5.0.7.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1638 -1132
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record.rb +7 -2
  8. data/lib/active_record/aggregations.rb +34 -21
  9. data/lib/active_record/association_relation.rb +7 -4
  10. data/lib/active_record/associations.rb +347 -218
  11. data/lib/active_record/associations/alias_tracker.rb +19 -16
  12. data/lib/active_record/associations/association.rb +22 -10
  13. data/lib/active_record/associations/association_scope.rb +75 -104
  14. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  15. data/lib/active_record/associations/builder/association.rb +28 -34
  16. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  17. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +16 -11
  19. data/lib/active_record/associations/builder/has_many.rb +4 -4
  20. data/lib/active_record/associations/builder/has_one.rb +11 -6
  21. data/lib/active_record/associations/builder/singular_association.rb +13 -11
  22. data/lib/active_record/associations/collection_association.rb +85 -69
  23. data/lib/active_record/associations/collection_proxy.rb +104 -46
  24. data/lib/active_record/associations/foreign_association.rb +1 -1
  25. data/lib/active_record/associations/has_many_association.rb +21 -78
  26. data/lib/active_record/associations/has_many_through_association.rb +6 -47
  27. data/lib/active_record/associations/has_one_association.rb +12 -5
  28. data/lib/active_record/associations/join_dependency.rb +38 -22
  29. data/lib/active_record/associations/join_dependency/join_association.rb +15 -14
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  31. data/lib/active_record/associations/preloader.rb +14 -4
  32. data/lib/active_record/associations/preloader/association.rb +52 -71
  33. data/lib/active_record/associations/preloader/collection_association.rb +0 -7
  34. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +0 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +36 -17
  38. data/lib/active_record/associations/singular_association.rb +13 -1
  39. data/lib/active_record/associations/through_association.rb +12 -4
  40. data/lib/active_record/attribute.rb +69 -19
  41. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  42. data/lib/active_record/attribute_assignment.rb +19 -140
  43. data/lib/active_record/attribute_decorators.rb +6 -5
  44. data/lib/active_record/attribute_methods.rb +69 -44
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  46. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  47. data/lib/active_record/attribute_methods/primary_key.rb +16 -3
  48. data/lib/active_record/attribute_methods/query.rb +2 -2
  49. data/lib/active_record/attribute_methods/read.rb +31 -59
  50. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  52. data/lib/active_record/attribute_methods/write.rb +13 -37
  53. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  54. data/lib/active_record/attribute_set.rb +32 -3
  55. data/lib/active_record/attribute_set/builder.rb +42 -16
  56. data/lib/active_record/attributes.rb +199 -81
  57. data/lib/active_record/autosave_association.rb +54 -17
  58. data/lib/active_record/base.rb +32 -23
  59. data/lib/active_record/callbacks.rb +39 -43
  60. data/lib/active_record/coders/json.rb +1 -1
  61. data/lib/active_record/coders/yaml_column.rb +20 -8
  62. data/lib/active_record/collection_cache_key.rb +50 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +467 -189
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -62
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +39 -4
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +86 -13
  68. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  69. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  70. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -188
  71. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  72. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +407 -156
  73. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  74. data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -71
  75. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +433 -399
  76. data/lib/active_record/connection_adapters/column.rb +28 -43
  77. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  78. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  79. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  80. data/lib/active_record/connection_adapters/mysql/database_statements.rb +108 -0
  81. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  82. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  83. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  84. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  86. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  87. data/lib/active_record/connection_adapters/mysql2_adapter.rb +25 -166
  88. data/lib/active_record/connection_adapters/postgresql/column.rb +33 -11
  89. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -72
  90. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +37 -57
  93. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +3 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -2
  95. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  97. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +13 -3
  98. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  99. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  102. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  106. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +56 -19
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +250 -154
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +2 -2
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +264 -170
  116. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  118. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  121. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +151 -194
  122. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  123. data/lib/active_record/connection_handling.rb +37 -14
  124. data/lib/active_record/core.rb +92 -108
  125. data/lib/active_record/counter_cache.rb +13 -24
  126. data/lib/active_record/dynamic_matchers.rb +1 -20
  127. data/lib/active_record/enum.rb +116 -76
  128. data/lib/active_record/errors.rb +87 -48
  129. data/lib/active_record/explain.rb +20 -9
  130. data/lib/active_record/explain_registry.rb +1 -1
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +26 -5
  133. data/lib/active_record/fixtures.rb +77 -41
  134. data/lib/active_record/gem_version.rb +4 -4
  135. data/lib/active_record/inheritance.rb +32 -40
  136. data/lib/active_record/integration.rb +17 -14
  137. data/lib/active_record/internal_metadata.rb +56 -0
  138. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  139. data/lib/active_record/locale/en.yml +3 -2
  140. data/lib/active_record/locking/optimistic.rb +15 -15
  141. data/lib/active_record/locking/pessimistic.rb +1 -1
  142. data/lib/active_record/log_subscriber.rb +48 -24
  143. data/lib/active_record/migration.rb +362 -111
  144. data/lib/active_record/migration/command_recorder.rb +59 -18
  145. data/lib/active_record/migration/compatibility.rb +126 -0
  146. data/lib/active_record/model_schema.rb +270 -73
  147. data/lib/active_record/nested_attributes.rb +58 -29
  148. data/lib/active_record/no_touching.rb +4 -0
  149. data/lib/active_record/null_relation.rb +16 -8
  150. data/lib/active_record/persistence.rb +152 -90
  151. data/lib/active_record/query_cache.rb +18 -23
  152. data/lib/active_record/querying.rb +12 -11
  153. data/lib/active_record/railtie.rb +23 -16
  154. data/lib/active_record/railties/controller_runtime.rb +1 -1
  155. data/lib/active_record/railties/databases.rake +52 -41
  156. data/lib/active_record/readonly_attributes.rb +1 -1
  157. data/lib/active_record/reflection.rb +302 -115
  158. data/lib/active_record/relation.rb +187 -120
  159. data/lib/active_record/relation/batches.rb +141 -36
  160. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  161. data/lib/active_record/relation/calculations.rb +92 -117
  162. data/lib/active_record/relation/delegation.rb +8 -20
  163. data/lib/active_record/relation/finder_methods.rb +173 -89
  164. data/lib/active_record/relation/from_clause.rb +32 -0
  165. data/lib/active_record/relation/merger.rb +16 -42
  166. data/lib/active_record/relation/predicate_builder.rb +120 -107
  167. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  168. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  169. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  170. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  171. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  172. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  173. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  174. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  175. data/lib/active_record/relation/query_attribute.rb +19 -0
  176. data/lib/active_record/relation/query_methods.rb +308 -244
  177. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  178. data/lib/active_record/relation/spawn_methods.rb +4 -7
  179. data/lib/active_record/relation/where_clause.rb +174 -0
  180. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  181. data/lib/active_record/result.rb +11 -4
  182. data/lib/active_record/runtime_registry.rb +1 -1
  183. data/lib/active_record/sanitization.rb +105 -66
  184. data/lib/active_record/schema.rb +26 -22
  185. data/lib/active_record/schema_dumper.rb +54 -37
  186. data/lib/active_record/schema_migration.rb +11 -14
  187. data/lib/active_record/scoping.rb +34 -16
  188. data/lib/active_record/scoping/default.rb +28 -10
  189. data/lib/active_record/scoping/named.rb +59 -26
  190. data/lib/active_record/secure_token.rb +38 -0
  191. data/lib/active_record/serialization.rb +3 -5
  192. data/lib/active_record/statement_cache.rb +17 -15
  193. data/lib/active_record/store.rb +8 -3
  194. data/lib/active_record/suppressor.rb +58 -0
  195. data/lib/active_record/table_metadata.rb +69 -0
  196. data/lib/active_record/tasks/database_tasks.rb +66 -49
  197. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  198. data/lib/active_record/tasks/postgresql_database_tasks.rb +12 -3
  199. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  200. data/lib/active_record/timestamp.rb +20 -9
  201. data/lib/active_record/touch_later.rb +63 -0
  202. data/lib/active_record/transactions.rb +139 -57
  203. data/lib/active_record/type.rb +66 -17
  204. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  205. data/lib/active_record/type/date.rb +2 -45
  206. data/lib/active_record/type/date_time.rb +2 -49
  207. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  208. data/lib/active_record/type/internal/timezone.rb +15 -0
  209. data/lib/active_record/type/serialized.rb +15 -14
  210. data/lib/active_record/type/time.rb +10 -16
  211. data/lib/active_record/type/type_map.rb +4 -4
  212. data/lib/active_record/type_caster.rb +7 -0
  213. data/lib/active_record/type_caster/connection.rb +29 -0
  214. data/lib/active_record/type_caster/map.rb +19 -0
  215. data/lib/active_record/validations.rb +33 -32
  216. data/lib/active_record/validations/absence.rb +23 -0
  217. data/lib/active_record/validations/associated.rb +10 -3
  218. data/lib/active_record/validations/length.rb +24 -0
  219. data/lib/active_record/validations/presence.rb +11 -12
  220. data/lib/active_record/validations/uniqueness.rb +33 -33
  221. data/lib/rails/generators/active_record/migration.rb +15 -0
  222. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -5
  223. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  224. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  225. data/lib/rails/generators/active_record/model/model_generator.rb +33 -16
  226. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  227. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  228. metadata +58 -34
  229. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  230. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  231. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  232. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  233. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  234. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  235. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  236. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  237. data/lib/active_record/type/big_integer.rb +0 -13
  238. data/lib/active_record/type/binary.rb +0 -50
  239. data/lib/active_record/type/boolean.rb +0 -31
  240. data/lib/active_record/type/decimal.rb +0 -64
  241. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  242. data/lib/active_record/type/decorator.rb +0 -14
  243. data/lib/active_record/type/float.rb +0 -19
  244. data/lib/active_record/type/integer.rb +0 -59
  245. data/lib/active_record/type/mutable.rb +0 -16
  246. data/lib/active_record/type/numeric.rb +0 -36
  247. data/lib/active_record/type/string.rb +0 -40
  248. data/lib/active_record/type/text.rb +0 -11
  249. data/lib/active_record/type/time_value.rb +0 -38
  250. data/lib/active_record/type/unsigned_integer.rb +0 -15
  251. data/lib/active_record/type/value.rb +0 -110
@@ -1,184 +1,93 @@
1
- require 'arel/visitors/bind_visitor'
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_record/connection_adapters/statement_pool'
3
+ require 'active_record/connection_adapters/mysql/column'
4
+ require 'active_record/connection_adapters/mysql/explain_pretty_printer'
5
+ require 'active_record/connection_adapters/mysql/quoting'
6
+ require 'active_record/connection_adapters/mysql/schema_creation'
7
+ require 'active_record/connection_adapters/mysql/schema_definitions'
8
+ require 'active_record/connection_adapters/mysql/schema_dumper'
9
+ require 'active_record/connection_adapters/mysql/type_metadata'
10
+
2
11
  require 'active_support/core_ext/string/strip'
3
12
 
4
13
  module ActiveRecord
5
14
  module ConnectionAdapters
6
15
  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)} "
16
+ include MySQL::Quoting
17
+ include MySQL::ColumnDumper
23
18
 
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
19
+ def update_table_definition(table_name, base) # :nodoc:
20
+ MySQL::Table.new(table_name, base)
59
21
  end
60
22
 
61
- def prepare_column_options(column, types) # :nodoc:
62
- spec = super
63
- spec.delete(:limit) if :boolean === column.type
64
- spec
23
+ def schema_creation # :nodoc:
24
+ MySQL::SchemaCreation.new(self)
65
25
  end
66
26
 
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
27
+ def arel_visitor # :nodoc:
28
+ Arel::Visitors::MySQL.new(self)
129
29
  end
130
30
 
131
31
  ##
132
32
  # :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
33
+ # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
34
+ # as boolean. If you wish to disable this emulation you can add the following line
136
35
  # to your application.rb file:
137
36
  #
138
- # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
37
+ # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
139
38
  class_attribute :emulate_booleans
140
39
  self.emulate_booleans = true
141
40
 
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
41
  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 }
42
+ primary_key: "int auto_increment PRIMARY KEY",
43
+ string: { name: "varchar", limit: 255 },
44
+ text: { name: "text" },
45
+ integer: { name: "int", limit: 4 },
46
+ float: { name: "float" },
47
+ decimal: { name: "decimal" },
48
+ datetime: { name: "datetime" },
49
+ time: { name: "time" },
50
+ date: { name: "date" },
51
+ binary: { name: "blob" },
52
+ boolean: { name: "tinyint", limit: 1 },
53
+ json: { name: "json" },
162
54
  }
163
55
 
164
56
  INDEX_TYPES = [:fulltext, :spatial]
165
57
  INDEX_USINGS = [:btree, :hash]
166
58
 
167
- # FIXME: Make the first parameter more similar for the two adapters
59
+ class StatementPool < ConnectionAdapters::StatementPool
60
+ private def dealloc(stmt)
61
+ stmt[:stmt].close
62
+ end
63
+ end
64
+
168
65
  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 = {}, {}
66
+ super(connection, logger, config)
172
67
 
173
- @visitor = Arel::Visitors::MySQL.new self
68
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
174
69
 
175
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
176
- @prepared_statements = true
177
- else
178
- @prepared_statements = false
70
+ if version < '5.0.0'
71
+ raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.0."
179
72
  end
180
73
  end
181
74
 
75
+ CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32']
76
+
77
+ def internal_string_options_for_primary_key # :nodoc:
78
+ super.tap { |options|
79
+ options[:collation] = collation.sub(/\A[^_]+/, 'utf8') if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
80
+ }
81
+ end
82
+
83
+ def version #:nodoc:
84
+ @version ||= Version.new(version_string)
85
+ end
86
+
87
+ def mariadb? # :nodoc:
88
+ full_version =~ /mariadb/i
89
+ end
90
+
182
91
  # Returns true, since this connection adapter supports migrations.
183
92
  def supports_migrations?
184
93
  true
@@ -192,18 +101,24 @@ module ActiveRecord
192
101
  true
193
102
  end
194
103
 
104
+ # Returns true, since this connection adapter supports prepared statement
105
+ # caching.
106
+ def supports_statement_cache?
107
+ true
108
+ end
109
+
195
110
  # Technically MySQL allows to create indexes with the sort order syntax
196
111
  # but at the moment (5.5) it doesn't yet implement them
197
112
  def supports_index_sort_order?
198
113
  true
199
114
  end
200
115
 
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
116
  def supports_transaction_isolation?
206
- version >= '5.0.0'
117
+ true
118
+ end
119
+
120
+ def supports_explain?
121
+ true
207
122
  end
208
123
 
209
124
  def supports_indexes_in_create?
@@ -215,11 +130,27 @@ module ActiveRecord
215
130
  end
216
131
 
217
132
  def supports_views?
218
- version >= '5.0.0'
133
+ true
219
134
  end
220
135
 
221
136
  def supports_datetime_with_precision?
222
- version >= '5.6.4'
137
+ if mariadb?
138
+ version >= '5.3.0'
139
+ else
140
+ version >= '5.6.4'
141
+ end
142
+ end
143
+
144
+ def supports_advisory_locks?
145
+ true
146
+ end
147
+
148
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
149
+ select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
150
+ end
151
+
152
+ def release_advisory_lock(lock_name) # :nodoc:
153
+ select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
223
154
  end
224
155
 
225
156
  def native_database_types
@@ -238,8 +169,8 @@ module ActiveRecord
238
169
  raise NotImplementedError
239
170
  end
240
171
 
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)
172
+ def new_column(*args) #:nodoc:
173
+ MySQL::Column.new(*args)
243
174
  end
244
175
 
245
176
  # Must return the MySQL error number from the exception, if the exception has an
@@ -248,48 +179,6 @@ module ActiveRecord
248
179
  raise NotImplementedError
249
180
  end
250
181
 
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
182
  # REFERENTIAL INTEGRITY ====================================
294
183
 
295
184
  def disable_referential_integrity #:nodoc:
@@ -303,13 +192,25 @@ module ActiveRecord
303
192
  end
304
193
  end
305
194
 
195
+ # CONNECTION MANAGEMENT ====================================
196
+
197
+ # Clears the prepared statements cache.
198
+ def clear_cache!
199
+ reload_type_map
200
+ @statements.clear
201
+ end
202
+
306
203
  #--
307
204
  # DATABASE STATEMENTS ======================================
308
205
  #++
309
206
 
310
- def clear_cache!
311
- super
312
- reload_type_map
207
+ def explain(arel, binds = [])
208
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
209
+ start = Time.now
210
+ result = exec_query(sql, 'EXPLAIN', binds)
211
+ elapsed = Time.now - start
212
+
213
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
313
214
  end
314
215
 
315
216
  # Executes the SQL statement in the context of this connection.
@@ -317,18 +218,13 @@ module ActiveRecord
317
218
  log(sql, name) { @connection.query(sql) }
318
219
  end
319
220
 
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:
221
+ # Mysql2Adapter doesn't have to free a result after using it, but we use this method
222
+ # to write stuff in an abstract way without concerning ourselves about whether it
223
+ # needs to be explicitly freed or not.
224
+ def execute_and_free(sql, name = nil) # :nodoc:
324
225
  yield execute(sql, name)
325
226
  end
326
227
 
327
- def update_sql(sql, name = nil) #:nodoc:
328
- super
329
- @connection.affected_rows
330
- end
331
-
332
228
  def begin_db_transaction
333
229
  execute "BEGIN"
334
230
  end
@@ -349,7 +245,7 @@ module ActiveRecord
349
245
  # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
350
246
  # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
351
247
  # these, we must use a subquery.
352
- def join_to_update(update, select) #:nodoc:
248
+ def join_to_update(update, select, key) # :nodoc:
353
249
  if select.limit || select.offset || select.orders.any?
354
250
  super
355
251
  else
@@ -382,9 +278,9 @@ module ActiveRecord
382
278
  # create_database 'matt_development', charset: :big5
383
279
  def create_database(name, options = {})
384
280
  if options[:collation]
385
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
281
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
386
282
  else
387
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
283
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
388
284
  end
389
285
  end
390
286
 
@@ -393,7 +289,7 @@ module ActiveRecord
393
289
  # Example:
394
290
  # drop_database('sebastian_development')
395
291
  def drop_database(name) #:nodoc:
396
- execute "DROP DATABASE IF EXISTS `#{name}`"
292
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
397
293
  end
398
294
 
399
295
  def current_database
@@ -410,36 +306,69 @@ module ActiveRecord
410
306
  show_variable 'collation_database'
411
307
  end
412
308
 
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
309
+ def tables(name = nil) # :nodoc:
310
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
311
+ #tables currently returns both tables and views.
312
+ This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
313
+ Use #data_sources instead.
314
+ MSG
417
315
 
418
- execute_and_free(sql, 'SCHEMA') do |result|
419
- result.collect { |field| field.first }
316
+ if name
317
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
318
+ Passing arguments to #tables is deprecated without replacement.
319
+ MSG
420
320
  end
321
+
322
+ data_sources
323
+ end
324
+
325
+ def data_sources
326
+ sql = "SELECT table_name FROM information_schema.tables "
327
+ sql << "WHERE table_schema = DATABASE()"
328
+
329
+ select_values(sql, 'SCHEMA')
421
330
  end
422
- alias data_sources tables
423
331
 
424
332
  def truncate(table_name, name = nil)
425
333
  execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
426
334
  end
427
335
 
428
- def table_exists?(name)
429
- return false unless name.present?
430
- return true if tables(nil, nil, name).any?
336
+ def table_exists?(table_name)
337
+ # Update lib/active_record/internal_metadata.rb when this gets removed
338
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
339
+ #table_exists? currently checks both tables and views.
340
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
341
+ Use #data_source_exists? instead.
342
+ MSG
343
+
344
+ data_source_exists?(table_name)
345
+ end
346
+
347
+ def data_source_exists?(table_name)
348
+ return false unless table_name.present?
431
349
 
432
- name = name.to_s
433
- schema, table = name.split('.', 2)
350
+ schema, name = extract_schema_qualified_name(table_name)
434
351
 
435
- unless table # A table was provided without a schema
436
- table = schema
437
- schema = nil
438
- end
352
+ sql = "SELECT table_name FROM information_schema.tables "
353
+ sql << "WHERE table_schema = #{schema} AND table_name = #{name}"
354
+
355
+ select_values(sql, 'SCHEMA').any?
356
+ end
439
357
 
440
- tables(nil, schema, table).any?
358
+ def views # :nodoc:
359
+ select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')
360
+ end
361
+
362
+ def view_exists?(view_name) # :nodoc:
363
+ return false unless view_name.present?
364
+
365
+ schema, name = extract_schema_qualified_name(view_name)
366
+
367
+ sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
368
+ sql << " AND table_schema = #{schema} AND table_name = #{name}"
369
+
370
+ select_values(sql, 'SCHEMA').any?
441
371
  end
442
- alias data_source_exists? table_exists?
443
372
 
444
373
  # Returns an array of indexes for the given table.
445
374
  def indexes(table_name, name = nil) #:nodoc:
@@ -454,11 +383,11 @@ module ActiveRecord
454
383
  mysql_index_type = row[:Index_type].downcase.to_sym
455
384
  index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
456
385
  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)
386
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], {}, nil, nil, index_type, index_using, row[:Index_comment].presence)
458
387
  end
459
388
 
460
389
  indexes.last.columns << row[:Column_name]
461
- indexes.last.lengths << row[:Sub_part]
390
+ indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
462
391
  end
463
392
  end
464
393
 
@@ -466,20 +395,32 @@ module ActiveRecord
466
395
  end
467
396
 
468
397
  # 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])
398
+ def columns(table_name) # :nodoc:
399
+ table_name = table_name.to_s
400
+ column_definitions(table_name).map do |field|
401
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
402
+ if type_metadata.type == :datetime && field[:Default] =~ /\ACURRENT_TIMESTAMP(?:\(\))?\z/i
403
+ default, default_function = nil, "CURRENT_TIMESTAMP"
404
+ else
405
+ default, default_function = field[:Default], nil
477
406
  end
407
+ new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
478
408
  end
479
409
  end
480
410
 
481
- def create_table(table_name, options = {}) #:nodoc:
482
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
411
+ def table_comment(table_name) # :nodoc:
412
+ schema, name = extract_schema_qualified_name(table_name)
413
+
414
+ select_value(<<-SQL.strip_heredoc, 'SCHEMA')
415
+ SELECT table_comment
416
+ FROM information_schema.tables
417
+ WHERE table_schema = #{schema}
418
+ AND table_name = #{name}
419
+ SQL
420
+ end
421
+
422
+ def create_table(table_name, **options) #:nodoc:
423
+ super(table_name, options: 'ENGINE=InnoDB', **options)
483
424
  end
484
425
 
485
426
  def bulk_change_table(table_name, operations) #:nodoc:
@@ -506,8 +447,23 @@ module ActiveRecord
506
447
  rename_table_indexes(table_name, new_name)
507
448
  end
508
449
 
450
+ # Drops a table from the database.
451
+ #
452
+ # [<tt>:force</tt>]
453
+ # Set to +:cascade+ to drop dependent objects as well.
454
+ # Defaults to false.
455
+ # [<tt>:if_exists</tt>]
456
+ # Set to +true+ to only drop the table if it exists.
457
+ # Defaults to false.
458
+ # [<tt>:temporary</tt>]
459
+ # Set to +true+ to drop temporary table.
460
+ # Defaults to false.
461
+ #
462
+ # Although this command ignores most +options+ and the block if one is given,
463
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
464
+ # In that case, +options+ and the block will be used by create_table.
509
465
  def drop_table(table_name, options = {})
510
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
466
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
511
467
  end
512
468
 
513
469
  def rename_index(table_name, old_name, new_name)
@@ -520,12 +476,13 @@ module ActiveRecord
520
476
  end
521
477
  end
522
478
 
523
- def change_column_default(table_name, column_name, default) #:nodoc:
479
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
480
+ default = extract_new_default_value(default_or_changes)
524
481
  column = column_for(table_name, column_name)
525
482
  change_column table_name, column_name, column.sql_type, :default => default
526
483
  end
527
484
 
528
- def change_column_null(table_name, column_name, null, default = nil)
485
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
529
486
  column = column_for(table_name, column_name)
530
487
 
531
488
  unless null || default.nil?
@@ -545,24 +502,37 @@ module ActiveRecord
545
502
  end
546
503
 
547
504
  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}"
505
+ index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
506
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
507
+ execute add_sql_comment!(sql, comment)
508
+ end
509
+
510
+ def add_sql_comment!(sql, comment) # :nodoc:
511
+ sql << " COMMENT #{quote(comment)}" if comment.present?
512
+ sql
550
513
  end
551
514
 
552
515
  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'
516
+ raise ArgumentError unless table_name.present?
517
+
518
+ schema, name = extract_schema_qualified_name(table_name)
519
+
520
+ fk_info = select_all(<<-SQL.strip_heredoc, 'SCHEMA')
521
+ SELECT fk.referenced_table_name AS 'to_table',
522
+ fk.referenced_column_name AS 'primary_key',
523
+ fk.column_name AS 'column',
524
+ fk.constraint_name AS 'name',
525
+ rc.update_rule AS 'on_update',
526
+ rc.delete_rule AS 'on_delete'
558
527
  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}'
528
+ JOIN information_schema.referential_constraints rc
529
+ USING (constraint_schema, constraint_name)
530
+ WHERE fk.referenced_column_name IS NOT NULL
531
+ AND fk.table_schema = #{schema}
532
+ AND fk.table_name = #{name}
533
+ AND rc.table_name = #{name}
562
534
  SQL
563
535
 
564
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
565
-
566
536
  fk_info.map do |row|
567
537
  options = {
568
538
  column: row['column'],
@@ -570,99 +540,91 @@ module ActiveRecord
570
540
  primary_key: row['primary_key']
571
541
  }
572
542
 
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")
543
+ options[:on_update] = extract_foreign_key_action(row['on_update'])
544
+ options[:on_delete] = extract_foreign_key_action(row['on_delete'])
575
545
 
576
546
  ForeignKeyDefinition.new(table_name, row['to_table'], options)
577
547
  end
578
548
  end
579
549
 
550
+ def table_options(table_name) # :nodoc:
551
+ table_options = {}
552
+
553
+ create_table_info = create_table_info(table_name)
554
+
555
+ # strip create_definitions and partition_options
556
+ raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
557
+
558
+ # strip AUTO_INCREMENT
559
+ raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
560
+
561
+ table_options[:options] = raw_table_options
562
+
563
+ # strip COMMENT
564
+ if raw_table_options.sub!(/ COMMENT='.+'/, '')
565
+ table_options[:comment] = table_comment(table_name)
566
+ end
567
+
568
+ table_options
569
+ end
570
+
580
571
  # 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
572
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
573
+ sql = case type.to_s
590
574
  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
575
+ integer_to_sql(limit)
599
576
  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.")
577
+ text_to_sql(limit)
578
+ when 'blob'
579
+ binary_to_sql(limit)
580
+ when 'binary'
581
+ if (0..0xfff) === limit
582
+ "varbinary(#{limit})"
583
+ else
584
+ binary_to_sql(limit)
613
585
  end
614
586
  else
615
- super
587
+ super(type, limit, precision, scale)
616
588
  end
589
+
590
+ sql << ' unsigned' if unsigned && type != :primary_key
591
+ sql
617
592
  end
618
593
 
619
594
  # SHOW VARIABLES LIKE 'name'
620
595
  def show_variable(name)
621
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
622
- variables.first['Value'] unless variables.empty?
596
+ select_value("SELECT @@#{name}", 'SCHEMA')
623
597
  rescue ActiveRecord::StatementInvalid
624
598
  nil
625
599
  end
626
600
 
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
601
+ def primary_keys(table_name) # :nodoc:
602
+ raise ArgumentError unless table_name.present?
639
603
 
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
604
+ schema, name = extract_schema_qualified_name(table_name)
645
605
 
646
- def case_sensitive_modifier(node, table_attribute)
647
- node = Arel::Nodes.build_quoted node, table_attribute
648
- Arel::Nodes::Bin.new(node)
606
+ select_values(<<-SQL.strip_heredoc, 'SCHEMA')
607
+ SELECT column_name
608
+ FROM information_schema.key_column_usage
609
+ WHERE constraint_name = 'PRIMARY'
610
+ AND table_schema = #{schema}
611
+ AND table_name = #{name}
612
+ ORDER BY ordinal_position
613
+ SQL
649
614
  end
650
615
 
651
616
  def case_sensitive_comparison(table, attribute, column, value)
652
- if column.case_sensitive?
653
- table[attribute].eq(value)
617
+ if !value.nil? && column.collation && !column.case_sensitive?
618
+ table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
654
619
  else
655
620
  super
656
621
  end
657
622
  end
658
623
 
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
624
+ def can_perform_case_insensitive_comparison_for?(column)
625
+ column.case_sensitive?
665
626
  end
627
+ private :can_perform_case_insensitive_comparison_for?
666
628
 
667
629
  # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
668
630
  # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
@@ -704,6 +666,7 @@ module ActiveRecord
704
666
  m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
705
667
  m.register_type %r(^float)i, Type::Float.new(limit: 24)
706
668
  m.register_type %r(^double)i, Type::Float.new(limit: 53)
669
+ m.register_type %r(^json)i, MysqlJson.new
707
670
 
708
671
  register_integer_type m, %r(^bigint)i, limit: 8
709
672
  register_integer_type m, %r(^int)i, limit: 4
@@ -711,26 +674,26 @@ module ActiveRecord
711
674
  register_integer_type m, %r(^smallint)i, limit: 2
712
675
  register_integer_type m, %r(^tinyint)i, limit: 1
713
676
 
714
- m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
715
- m.alias_type %r(set)i, 'varchar'
677
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
716
678
  m.alias_type %r(year)i, 'integer'
717
679
  m.alias_type %r(bit)i, 'binary'
718
680
 
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
681
  m.register_type(%r(enum)i) do |sql_type|
725
682
  limit = sql_type[/^enum\((.+)\)/i, 1]
726
683
  .split(',').map{|enum| enum.strip.length - 2}.max
727
684
  MysqlString.new(limit: limit)
728
685
  end
686
+
687
+ m.register_type(%r(^set)i) do |sql_type|
688
+ limit = sql_type[/^set\((.+)\)/i, 1]
689
+ .split(',').map{|set| set.strip.length - 1}.sum - 1
690
+ MysqlString.new(limit: limit)
691
+ end
729
692
  end
730
693
 
731
694
  def register_integer_type(mapping, key, options) # :nodoc:
732
695
  mapping.register_type(key) do |sql_type|
733
- if /unsigned/i =~ sql_type
696
+ if /\bunsigned\b/ === sql_type
734
697
  Type::UnsignedInteger.new(options)
735
698
  else
736
699
  Type::Integer.new(options)
@@ -738,61 +701,54 @@ module ActiveRecord
738
701
  end
739
702
  end
740
703
 
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?
704
+ def extract_precision(sql_type)
705
+ if /time/ === sql_type
706
+ super || 0
707
+ else
708
+ super
709
+ end
710
+ end
750
711
 
751
- subselect = Arel::SelectManager.new(select.engine)
752
- subselect.project Arel.sql(key.name)
753
- subselect.from subsubselect.as('__active_record_temp')
712
+ def fetch_type_metadata(sql_type, extra = "")
713
+ MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
754
714
  end
755
715
 
756
- def add_index_length(option_strings, column_names, options = {})
757
- if options.is_a?(Hash) && length = options[:length]
716
+ def add_index_length(quoted_columns, **options)
717
+ if length = options[:length]
758
718
  case length
759
719
  when Hash
760
- column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
720
+ length = length.symbolize_keys
721
+ quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? }
761
722
  when Integer
762
- column_names.each {|name| option_strings[name] += "(#{length})"}
723
+ quoted_columns.each { |name, column| column << "(#{length})" }
763
724
  end
764
725
  end
765
726
 
766
- return option_strings
727
+ quoted_columns
767
728
  end
768
729
 
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]}
730
+ def add_options_for_index_columns(quoted_columns, **options)
731
+ quoted_columns = add_index_length(quoted_columns, options)
732
+ super
779
733
  end
780
734
 
781
735
  def translate_exception(exception, message)
782
736
  case error_number(exception)
783
737
  when 1062
784
- RecordNotUnique.new(message, exception)
738
+ RecordNotUnique.new(message)
785
739
  when 1452
786
- InvalidForeignKey.new(message, exception)
740
+ InvalidForeignKey.new(message)
741
+ when 1406
742
+ ValueTooLong.new(message)
787
743
  else
788
744
  super
789
745
  end
790
746
  end
791
747
 
792
748
  def add_column_sql(table_name, column_name, type, options = {})
793
- td = create_table_definition table_name, options[:temporary], options[:options]
749
+ td = create_table_definition(table_name)
794
750
  cd = td.new_column_definition(column_name, type, options)
795
- schema_creation.visit_AddColumn cd
751
+ schema_creation.accept(AddColumnDefinition.new(cd))
796
752
  end
797
753
 
798
754
  def change_column_sql(table_name, column_name, type, options = {})
@@ -806,21 +762,23 @@ module ActiveRecord
806
762
  options[:null] = column.null
807
763
  end
808
764
 
809
- options[:name] = column.name
810
- schema_creation.accept ChangeColumnDefinition.new column, type, options
765
+ td = create_table_definition(table_name)
766
+ cd = td.new_column_definition(column.name, type, options)
767
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
811
768
  end
812
769
 
813
770
  def rename_column_sql(table_name, column_name, new_column_name)
814
771
  column = column_for(table_name, column_name)
815
772
  options = {
816
- name: new_column_name,
817
773
  default: column.default,
818
774
  null: column.null,
819
- auto_increment: column.extra == "auto_increment"
775
+ auto_increment: column.auto_increment?
820
776
  }
821
777
 
822
778
  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
779
+ td = create_table_definition(table_name)
780
+ cd = td.new_column_definition(new_column_name, current_type, options)
781
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
824
782
  end
825
783
 
826
784
  def remove_column_sql(table_name, column_name, type = nil, options = {})
@@ -832,8 +790,9 @@ module ActiveRecord
832
790
  end
833
791
 
834
792
  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})"
793
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
794
+ index_algorithm[0, 0] = ", " if index_algorithm.present?
795
+ "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
837
796
  end
838
797
 
839
798
  def remove_index_sql(table_name, options = {})
@@ -851,12 +810,19 @@ module ActiveRecord
851
810
 
852
811
  private
853
812
 
854
- def version
855
- @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
856
- end
813
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
814
+ # to give it some prompting in the form of a subsubquery. Ugh!
815
+ def subquery_for(key, select)
816
+ subsubselect = select.clone
817
+ subsubselect.projections = [key]
857
818
 
858
- def mariadb?
859
- full_version =~ /mariadb/i
819
+ # Materialize subquery by adding distinct
820
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
821
+ subsubselect.distinct unless select.limit || select.offset || select.orders.any?
822
+
823
+ subselect = Arel::SelectManager.new(select.engine)
824
+ subselect.project Arel.sql(key.name)
825
+ subselect.from subsubselect.as('__active_record_temp')
860
826
  end
861
827
 
862
828
  def supports_rename_index?
@@ -866,8 +832,7 @@ module ActiveRecord
866
832
  def configure_connection
867
833
  variables = @config.fetch(:variables, {}).stringify_keys
868
834
 
869
- # By default, MySQL 'where id is null' selects the last inserted id.
870
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
835
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
871
836
  variables['sql_auto_is_null'] = 0
872
837
 
873
838
  # Increase timeout so the server doesn't disconnect us.
@@ -875,15 +840,27 @@ module ActiveRecord
875
840
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
876
841
  variables["wait_timeout"] = wait_timeout
877
842
 
843
+ defaults = [':default', :default].to_set
844
+
878
845
  # 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
846
+ # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
880
847
  # 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' : ''
848
+ if sql_mode = variables.delete('sql_mode')
849
+ sql_mode = quote(sql_mode)
850
+ elsif !defaults.include?(strict_mode?)
851
+ if strict_mode?
852
+ sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
853
+ else
854
+ sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
855
+ sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
856
+ sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
857
+ end
858
+ sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
883
859
  end
860
+ sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
884
861
 
885
862
  # NAMES does not have an equals sign, see
886
- # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
863
+ # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
887
864
  # (trailing comma because variable_assignments will always have content)
888
865
  if @config[:encoding]
889
866
  encoding = "NAMES #{@config[:encoding]}"
@@ -893,7 +870,7 @@ module ActiveRecord
893
870
 
894
871
  # Gather up all of the SET variables...
895
872
  variable_assignments = variables.map do |k, v|
896
- if v == ':default' || v == :default
873
+ if defaults.include?(v)
897
874
  "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
898
875
  elsif !v.nil?
899
876
  "@@SESSION.#{k} = #{quote(v)}"
@@ -902,31 +879,84 @@ module ActiveRecord
902
879
  end.compact.join(', ')
903
880
 
904
881
  # ...and send them all in one query
905
- @connection.query "SET #{encoding} #{variable_assignments}"
882
+ @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
906
883
  end
907
884
 
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
885
+ def column_definitions(table_name) # :nodoc:
886
+ execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
887
+ each_hash(result)
914
888
  end
915
889
  end
916
890
 
917
- class MysqlDateTime < Type::DateTime # :nodoc:
918
- private
891
+ def extract_foreign_key_action(specifier) # :nodoc:
892
+ case specifier
893
+ when 'CASCADE'; :cascade
894
+ when 'SET NULL'; :nullify
895
+ end
896
+ end
897
+
898
+ def create_table_info(table_name) # :nodoc:
899
+ select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
900
+ end
901
+
902
+ def create_table_definition(*args) # :nodoc:
903
+ MySQL::TableDefinition.new(*args)
904
+ end
919
905
 
920
- def has_precision?
921
- precision || 0
906
+ def extract_schema_qualified_name(string) # :nodoc:
907
+ schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/).map { |s| quote(s) }
908
+ schema, name = "DATABASE()", schema unless name
909
+ [schema, name]
910
+ end
911
+
912
+ def integer_to_sql(limit) # :nodoc:
913
+ case limit
914
+ when 1; 'tinyint'
915
+ when 2; 'smallint'
916
+ when 3; 'mediumint'
917
+ when nil, 4; 'int'
918
+ when 5..8; 'bigint'
919
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
920
+ end
921
+ end
922
+
923
+ def text_to_sql(limit) # :nodoc:
924
+ case limit
925
+ when 0..0xff; 'tinytext'
926
+ when nil, 0x100..0xffff; 'text'
927
+ when 0x10000..0xffffff; 'mediumtext'
928
+ when 0x1000000..0xffffffff; 'longtext'
929
+ else raise(ActiveRecordError, "No text type has byte length #{limit}")
930
+ end
931
+ end
932
+
933
+ def binary_to_sql(limit) # :nodoc:
934
+ case limit
935
+ when 0..0xff; 'tinyblob'
936
+ when nil, 0x100..0xffff; 'blob'
937
+ when 0x10000..0xffffff; 'mediumblob'
938
+ when 0x1000000..0xffffffff; 'longblob'
939
+ else raise(ActiveRecordError, "No binary type has byte length #{limit}")
940
+ end
941
+ end
942
+
943
+ def version_string
944
+ full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
945
+ end
946
+
947
+ class MysqlJson < Type::Internal::AbstractJson # :nodoc:
948
+ def changed_in_place?(raw_old_value, new_value)
949
+ # Normalization is required because MySQL JSON data format includes
950
+ # the space between the elements.
951
+ super(serialize(deserialize(raw_old_value)), new_value)
922
952
  end
923
953
  end
924
954
 
925
955
  class MysqlString < Type::String # :nodoc:
926
- def type_cast_for_database(value)
956
+ def serialize(value)
927
957
  case value
928
- when true then "1"
929
- when false then "0"
958
+ when true then MySQL::Quoting::QUOTED_TRUE
959
+ when false then MySQL::Quoting::QUOTED_FALSE
930
960
  else super
931
961
  end
932
962
  end
@@ -935,12 +965,16 @@ module ActiveRecord
935
965
 
936
966
  def cast_value(value)
937
967
  case value
938
- when true then "1"
939
- when false then "0"
968
+ when true then MySQL::Quoting::QUOTED_TRUE
969
+ when false then MySQL::Quoting::QUOTED_FALSE
940
970
  else super
941
971
  end
942
972
  end
943
973
  end
974
+
975
+ ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
976
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
977
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
944
978
  end
945
979
  end
946
980
  end