activerecord 4.2.0 → 5.0.0

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

Potentially problematic release.


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

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