activerecord 4.2.6 → 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 (246) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1307 -1105
  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 +3 -3
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +11 -9
  11. data/lib/active_record/associations/association_scope.rb +73 -102
  12. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  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 +7 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +14 -11
  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 +50 -31
  21. data/lib/active_record/associations/collection_proxy.rb +69 -29
  22. data/lib/active_record/associations/foreign_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_association.rb +20 -71
  24. data/lib/active_record/associations/has_many_through_association.rb +8 -47
  25. data/lib/active_record/associations/has_one_association.rb +12 -5
  26. data/lib/active_record/associations/join_dependency/join_association.rb +20 -8
  27. data/lib/active_record/associations/join_dependency.rb +29 -19
  28. data/lib/active_record/associations/preloader/association.rb +46 -52
  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 +14 -4
  34. data/lib/active_record/associations/singular_association.rb +7 -1
  35. data/lib/active_record/associations/through_association.rb +11 -3
  36. data/lib/active_record/associations.rb +317 -209
  37. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  38. data/lib/active_record/attribute.rb +68 -18
  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 +1 -1
  42. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  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 +61 -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 +6 -4
  52. data/lib/active_record/attribute_set.rb +30 -3
  53. data/lib/active_record/attributes.rb +199 -80
  54. data/lib/active_record/autosave_association.rb +49 -16
  55. data/lib/active_record/base.rb +32 -23
  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 +452 -182
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -9
  65. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  66. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -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 +378 -140
  70. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  71. data/lib/active_record/connection_adapters/abstract_adapter.rb +153 -59
  72. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +405 -362
  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 +25 -176
  85. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  86. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
  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 +1 -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 -22
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  95. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  98. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  100. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  102. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  105. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  106. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  107. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  108. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  109. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +234 -148
  110. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  111. data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
  112. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  113. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  114. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  115. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  117. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +148 -203
  118. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  119. data/lib/active_record/connection_handling.rb +37 -14
  120. data/lib/active_record/core.rb +89 -107
  121. data/lib/active_record/counter_cache.rb +13 -24
  122. data/lib/active_record/dynamic_matchers.rb +1 -20
  123. data/lib/active_record/enum.rb +113 -76
  124. data/lib/active_record/errors.rb +87 -48
  125. data/lib/active_record/explain_registry.rb +1 -1
  126. data/lib/active_record/explain_subscriber.rb +1 -1
  127. data/lib/active_record/fixture_set/file.rb +26 -5
  128. data/lib/active_record/fixtures.rb +76 -40
  129. data/lib/active_record/gem_version.rb +3 -3
  130. data/lib/active_record/inheritance.rb +32 -40
  131. data/lib/active_record/integration.rb +4 -4
  132. data/lib/active_record/internal_metadata.rb +56 -0
  133. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  134. data/lib/active_record/locale/en.yml +3 -2
  135. data/lib/active_record/locking/optimistic.rb +15 -15
  136. data/lib/active_record/locking/pessimistic.rb +1 -1
  137. data/lib/active_record/log_subscriber.rb +43 -21
  138. data/lib/active_record/migration/command_recorder.rb +59 -18
  139. data/lib/active_record/migration/compatibility.rb +126 -0
  140. data/lib/active_record/migration.rb +364 -109
  141. data/lib/active_record/model_schema.rb +128 -38
  142. data/lib/active_record/nested_attributes.rb +58 -29
  143. data/lib/active_record/null_relation.rb +16 -8
  144. data/lib/active_record/persistence.rb +121 -80
  145. data/lib/active_record/query_cache.rb +15 -18
  146. data/lib/active_record/querying.rb +10 -9
  147. data/lib/active_record/railtie.rb +27 -18
  148. data/lib/active_record/railties/controller_runtime.rb +1 -1
  149. data/lib/active_record/railties/databases.rake +58 -45
  150. data/lib/active_record/readonly_attributes.rb +1 -1
  151. data/lib/active_record/reflection.rb +282 -115
  152. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  153. data/lib/active_record/relation/batches.rb +139 -34
  154. data/lib/active_record/relation/calculations.rb +80 -102
  155. data/lib/active_record/relation/delegation.rb +7 -20
  156. data/lib/active_record/relation/finder_methods.rb +163 -81
  157. data/lib/active_record/relation/from_clause.rb +32 -0
  158. data/lib/active_record/relation/merger.rb +16 -42
  159. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -15
  160. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  161. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  162. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  163. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  165. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  166. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  167. data/lib/active_record/relation/predicate_builder.rb +120 -107
  168. data/lib/active_record/relation/query_attribute.rb +19 -0
  169. data/lib/active_record/relation/query_methods.rb +308 -244
  170. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  171. data/lib/active_record/relation/spawn_methods.rb +4 -7
  172. data/lib/active_record/relation/where_clause.rb +174 -0
  173. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  174. data/lib/active_record/relation.rb +176 -116
  175. data/lib/active_record/result.rb +4 -3
  176. data/lib/active_record/runtime_registry.rb +1 -1
  177. data/lib/active_record/sanitization.rb +95 -66
  178. data/lib/active_record/schema.rb +26 -22
  179. data/lib/active_record/schema_dumper.rb +62 -38
  180. data/lib/active_record/schema_migration.rb +11 -17
  181. data/lib/active_record/scoping/default.rb +23 -9
  182. data/lib/active_record/scoping/named.rb +49 -28
  183. data/lib/active_record/scoping.rb +32 -15
  184. data/lib/active_record/secure_token.rb +38 -0
  185. data/lib/active_record/serialization.rb +2 -4
  186. data/lib/active_record/statement_cache.rb +16 -14
  187. data/lib/active_record/store.rb +8 -3
  188. data/lib/active_record/suppressor.rb +58 -0
  189. data/lib/active_record/table_metadata.rb +68 -0
  190. data/lib/active_record/tasks/database_tasks.rb +58 -41
  191. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -20
  192. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  193. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  194. data/lib/active_record/timestamp.rb +20 -9
  195. data/lib/active_record/touch_later.rb +58 -0
  196. data/lib/active_record/transactions.rb +138 -56
  197. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  198. data/lib/active_record/type/date.rb +2 -41
  199. data/lib/active_record/type/date_time.rb +2 -49
  200. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  201. data/lib/active_record/type/internal/timezone.rb +15 -0
  202. data/lib/active_record/type/serialized.rb +15 -14
  203. data/lib/active_record/type/time.rb +10 -16
  204. data/lib/active_record/type/type_map.rb +4 -4
  205. data/lib/active_record/type.rb +66 -17
  206. data/lib/active_record/type_caster/connection.rb +29 -0
  207. data/lib/active_record/type_caster/map.rb +19 -0
  208. data/lib/active_record/type_caster.rb +7 -0
  209. data/lib/active_record/validations/absence.rb +23 -0
  210. data/lib/active_record/validations/associated.rb +10 -3
  211. data/lib/active_record/validations/length.rb +24 -0
  212. data/lib/active_record/validations/presence.rb +11 -12
  213. data/lib/active_record/validations/uniqueness.rb +30 -29
  214. data/lib/active_record/validations.rb +33 -32
  215. data/lib/active_record.rb +7 -2
  216. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  217. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  218. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  219. data/lib/rails/generators/active_record/migration.rb +7 -0
  220. data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
  221. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  222. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  223. metadata +58 -34
  224. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  225. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  226. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  227. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  228. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  229. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  230. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  231. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  232. data/lib/active_record/type/big_integer.rb +0 -13
  233. data/lib/active_record/type/binary.rb +0 -50
  234. data/lib/active_record/type/boolean.rb +0 -31
  235. data/lib/active_record/type/decimal.rb +0 -50
  236. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  237. data/lib/active_record/type/decorator.rb +0 -14
  238. data/lib/active_record/type/float.rb +0 -19
  239. data/lib/active_record/type/integer.rb +0 -59
  240. data/lib/active_record/type/mutable.rb +0 -16
  241. data/lib/active_record/type/numeric.rb +0 -36
  242. data/lib/active_record/type/string.rb +0 -40
  243. data/lib/active_record/type/text.rb +0 -11
  244. data/lib/active_record/type/time_value.rb +0 -38
  245. data/lib/active_record/type/unsigned_integer.rb +0 -15
  246. data/lib/active_record/type/value.rb +0 -105
@@ -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)} "
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
16
+ include MySQL::Quoting
17
+ include MySQL::ColumnDumper
50
18
 
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
- def prepare_column_options(column, types) # :nodoc:
62
- spec = super
63
- spec.delete(:limit) if :boolean === column.type
64
- spec
65
- end
66
-
67
- class Column < ConnectionAdapters::Column # :nodoc:
68
- attr_reader :collation, :strict, :extra
69
-
70
- def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
71
- @strict = strict
72
- @collation = collation
73
- @extra = extra
74
- super(name, default, cast_type, sql_type, null)
75
- assert_valid_default(default)
76
- extract_default
77
- end
78
-
79
- def extract_default
80
- if blob_or_text_column?
81
- @default = null || strict ? nil : ''
82
- elsif missing_default_forged_as_empty_string?(@default)
83
- @default = nil
84
- end
85
- end
86
-
87
- def has_default?
88
- return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
89
- super
90
- end
91
-
92
- def blob_or_text_column?
93
- sql_type =~ /blob/i || type == :text
94
- end
95
-
96
- def case_sensitive?
97
- collation && !collation.match(/_ci$/)
98
- end
99
-
100
- def ==(other)
101
- super &&
102
- collation == other.collation &&
103
- strict == other.strict &&
104
- extra == other.extra
105
- end
106
-
107
- private
108
-
109
- # MySQL misreports NOT NULL column default when none is given.
110
- # We can't detect this for columns which may have a legitimate ''
111
- # default (string) but we can for others (integer, datetime, boolean,
112
- # and the rest).
113
- #
114
- # Test whether the column has default '', is not null, and is not
115
- # a type allowing default ''.
116
- def missing_default_forged_as_empty_string?(default)
117
- type != :string && !null && default == ''
118
- end
119
-
120
- def assert_valid_default(default)
121
- if blob_or_text_column? && default.present?
122
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
123
- end
124
- end
125
-
126
- def attributes_for_hash
127
- super + [collation, strict, extra]
128
- end
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 (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) 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(full_version.match(/^\d+\.\d+\.\d+/)[0])
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[0] >= 5
117
+ true
118
+ end
119
+
120
+ def supports_explain?
121
+ true
207
122
  end
208
123
 
209
124
  def supports_indexes_in_create?
@@ -215,7 +130,27 @@ module ActiveRecord
215
130
  end
216
131
 
217
132
  def supports_views?
218
- 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'
219
154
  end
220
155
 
221
156
  def native_database_types
@@ -234,8 +169,8 @@ module ActiveRecord
234
169
  raise NotImplementedError
235
170
  end
236
171
 
237
- def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
238
- Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
172
+ def new_column(*args) #:nodoc:
173
+ MySQL::Column.new(*args)
239
174
  end
240
175
 
241
176
  # Must return the MySQL error number from the exception, if the exception has an
@@ -244,40 +179,6 @@ module ActiveRecord
244
179
  raise NotImplementedError
245
180
  end
246
181
 
247
- # QUOTING ==================================================
248
-
249
- def _quote(value) # :nodoc:
250
- if value.is_a?(Type::Binary::Data)
251
- "x'#{value.hex}'"
252
- else
253
- super
254
- end
255
- end
256
-
257
- def quote_column_name(name) #:nodoc:
258
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
259
- end
260
-
261
- def quote_table_name(name) #:nodoc:
262
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
263
- end
264
-
265
- def quoted_true
266
- QUOTED_TRUE
267
- end
268
-
269
- def unquoted_true
270
- 1
271
- end
272
-
273
- def quoted_false
274
- QUOTED_FALSE
275
- end
276
-
277
- def unquoted_false
278
- 0
279
- end
280
-
281
182
  # REFERENTIAL INTEGRITY ====================================
282
183
 
283
184
  def disable_referential_integrity #:nodoc:
@@ -291,13 +192,25 @@ module ActiveRecord
291
192
  end
292
193
  end
293
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
+
294
203
  #--
295
204
  # DATABASE STATEMENTS ======================================
296
205
  #++
297
206
 
298
- def clear_cache!
299
- super
300
- 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)
301
214
  end
302
215
 
303
216
  # Executes the SQL statement in the context of this connection.
@@ -305,18 +218,13 @@ module ActiveRecord
305
218
  log(sql, name) { @connection.query(sql) }
306
219
  end
307
220
 
308
- # MysqlAdapter has to free a result after using it, so we use this method to write
309
- # stuff in an abstract way without concerning ourselves about whether it needs to be
310
- # explicitly freed or not.
311
- 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:
312
225
  yield execute(sql, name)
313
226
  end
314
227
 
315
- def update_sql(sql, name = nil) #:nodoc:
316
- super
317
- @connection.affected_rows
318
- end
319
-
320
228
  def begin_db_transaction
321
229
  execute "BEGIN"
322
230
  end
@@ -337,7 +245,7 @@ module ActiveRecord
337
245
  # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
338
246
  # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
339
247
  # these, we must use a subquery.
340
- def join_to_update(update, select) #:nodoc:
248
+ def join_to_update(update, select, key) # :nodoc:
341
249
  if select.limit || select.offset || select.orders.any?
342
250
  super
343
251
  else
@@ -370,9 +278,9 @@ module ActiveRecord
370
278
  # create_database 'matt_development', charset: :big5
371
279
  def create_database(name, options = {})
372
280
  if options[:collation]
373
- 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])}"
374
282
  else
375
- 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')}"
376
284
  end
377
285
  end
378
286
 
@@ -381,7 +289,7 @@ module ActiveRecord
381
289
  # Example:
382
290
  # drop_database('sebastian_development')
383
291
  def drop_database(name) #:nodoc:
384
- execute "DROP DATABASE IF EXISTS `#{name}`"
292
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
385
293
  end
386
294
 
387
295
  def current_database
@@ -398,36 +306,69 @@ module ActiveRecord
398
306
  show_variable 'collation_database'
399
307
  end
400
308
 
401
- def tables(name = nil, database = nil, like = nil) #:nodoc:
402
- sql = "SHOW TABLES "
403
- sql << "IN #{quote_table_name(database)} " if database
404
- 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
405
315
 
406
- execute_and_free(sql, 'SCHEMA') do |result|
407
- 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
408
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')
409
330
  end
410
- alias data_sources tables
411
331
 
412
332
  def truncate(table_name, name = nil)
413
333
  execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
414
334
  end
415
335
 
416
- def table_exists?(name)
417
- return false unless name.present?
418
- 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
419
343
 
420
- name = name.to_s
421
- schema, table = name.split('.', 2)
344
+ data_source_exists?(table_name)
345
+ end
422
346
 
423
- unless table # A table was provided without a schema
424
- table = schema
425
- schema = nil
426
- 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
427
357
 
428
- 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 = #{quote(schema)} AND table_name = #{quote(name)}"
369
+
370
+ select_values(sql, 'SCHEMA').any?
429
371
  end
430
- alias data_source_exists? table_exists?
431
372
 
432
373
  # Returns an array of indexes for the given table.
433
374
  def indexes(table_name, name = nil) #:nodoc:
@@ -442,7 +383,7 @@ module ActiveRecord
442
383
  mysql_index_type = row[:Index_type].downcase.to_sym
443
384
  index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
444
385
  index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
445
- 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)
446
387
  end
447
388
 
448
389
  indexes.last.columns << row[:Column_name]
@@ -454,20 +395,29 @@ module ActiveRecord
454
395
  end
455
396
 
456
397
  # Returns an array of +Column+ objects for the table specified by +table_name+.
457
- def columns(table_name)#:nodoc:
458
- sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
459
- execute_and_free(sql, 'SCHEMA') do |result|
460
- each_hash(result).map do |field|
461
- field_name = set_field_encoding(field[:Field])
462
- sql_type = field[:Type]
463
- cast_type = lookup_cast_type(sql_type)
464
- 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
465
406
  end
407
+ new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
466
408
  end
467
409
  end
468
410
 
469
- def create_table(table_name, options = {}) #:nodoc:
470
- 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)
471
421
  end
472
422
 
473
423
  def bulk_change_table(table_name, operations) #:nodoc:
@@ -494,8 +444,24 @@ module ActiveRecord
494
444
  rename_table_indexes(table_name, new_name)
495
445
  end
496
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.
497
462
  def drop_table(table_name, options = {})
498
- 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}"
499
465
  end
500
466
 
501
467
  def rename_index(table_name, old_name, new_name)
@@ -508,12 +474,13 @@ module ActiveRecord
508
474
  end
509
475
  end
510
476
 
511
- 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)
512
479
  column = column_for(table_name, column_name)
513
480
  change_column table_name, column_name, column.sql_type, :default => default
514
481
  end
515
482
 
516
- def change_column_null(table_name, column_name, null, default = nil)
483
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
517
484
  column = column_for(table_name, column_name)
518
485
 
519
486
  unless null || default.nil?
@@ -533,11 +500,21 @@ module ActiveRecord
533
500
  end
534
501
 
535
502
  def add_index(table_name, column_name, options = {}) #:nodoc:
536
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
537
- 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
538
511
  end
539
512
 
540
513
  def foreign_keys(table_name)
514
+ raise ArgumentError unless table_name.present?
515
+
516
+ schema, name = extract_schema_qualified_name(table_name)
517
+
541
518
  fk_info = select_all <<-SQL.strip_heredoc
542
519
  SELECT fk.referenced_table_name as 'to_table'
543
520
  ,fk.referenced_column_name as 'primary_key'
@@ -545,11 +522,11 @@ module ActiveRecord
545
522
  ,fk.constraint_name as 'name'
546
523
  FROM information_schema.key_column_usage fk
547
524
  WHERE fk.referenced_column_name is not null
548
- AND fk.table_schema = '#{@config[:database]}'
549
- AND fk.table_name = '#{table_name}'
525
+ AND fk.table_schema = #{quote(schema)}
526
+ AND fk.table_name = #{quote(name)}
550
527
  SQL
551
528
 
552
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
529
+ create_table_info = create_table_info(table_name)
553
530
 
554
531
  fk_info.map do |row|
555
532
  options = {
@@ -565,92 +542,78 @@ module ActiveRecord
565
542
  end
566
543
  end
567
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
+
568
560
  # Maps logical Rails types to MySQL-specific data types.
569
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
570
- case type.to_s
571
- when 'binary'
572
- case limit
573
- when 0..0xfff; "varbinary(#{limit})"
574
- when nil; "blob"
575
- when 0x1000..0xffffffff; "blob(#{limit})"
576
- else raise(ActiveRecordError, "No binary type has character length #{limit}")
577
- end
561
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
562
+ sql = case type.to_s
578
563
  when 'integer'
579
- case limit
580
- when 1; 'tinyint'
581
- when 2; 'smallint'
582
- when 3; 'mediumint'
583
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
584
- when 5..8; 'bigint'
585
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
586
- end
564
+ integer_to_sql(limit)
587
565
  when 'text'
588
- case limit
589
- when 0..0xff; 'tinytext'
590
- when nil, 0x100..0xffff; 'text'
591
- when 0x10000..0xffffff; 'mediumtext'
592
- when 0x1000000..0xffffffff; 'longtext'
593
- else raise(ActiveRecordError, "No text type has character length #{limit}")
594
- end
595
- when 'datetime'
596
- return super unless precision
597
-
598
- case precision
599
- when 0..6; "datetime(#{precision})"
600
- else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
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)
601
574
  end
602
575
  else
603
- super
576
+ super(type, limit, precision, scale)
604
577
  end
578
+
579
+ sql << ' unsigned' if unsigned && type != :primary_key
580
+ sql
605
581
  end
606
582
 
607
583
  # SHOW VARIABLES LIKE 'name'
608
584
  def show_variable(name)
609
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
610
- variables.first['Value'] unless variables.empty?
585
+ select_value("SELECT @@#{name}", 'SCHEMA')
611
586
  rescue ActiveRecord::StatementInvalid
612
587
  nil
613
588
  end
614
589
 
615
- # Returns a table's primary key and belonging sequence.
616
- def pk_and_sequence_for(table)
617
- execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
618
- create_table = each_hash(result).first[:"Create Table"]
619
- if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
620
- keys = $1.split(",").map { |key| key.delete('`"') }
621
- keys.length == 1 ? [keys.first, nil] : nil
622
- else
623
- nil
624
- end
625
- end
626
- end
590
+ def primary_keys(table_name) # :nodoc:
591
+ raise ArgumentError unless table_name.present?
627
592
 
628
- # Returns just a table's primary key
629
- def primary_key(table)
630
- pk_and_sequence = pk_and_sequence_for(table)
631
- pk_and_sequence && pk_and_sequence.first
632
- end
593
+ schema, name = extract_schema_qualified_name(table_name)
633
594
 
634
- def case_sensitive_modifier(node, table_attribute)
635
- node = Arel::Nodes.build_quoted node, table_attribute
636
- 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
637
603
  end
638
604
 
639
605
  def case_sensitive_comparison(table, attribute, column, value)
640
- if column.case_sensitive?
641
- 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))
642
608
  else
643
609
  super
644
610
  end
645
611
  end
646
612
 
647
- def case_insensitive_comparison(table, attribute, column, value)
648
- if column.case_sensitive?
649
- super
650
- else
651
- table[attribute].eq(value)
652
- end
613
+ def can_perform_case_insensitive_comparison_for?(column)
614
+ column.case_sensitive?
653
615
  end
616
+ private :can_perform_case_insensitive_comparison_for?
654
617
 
655
618
  # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
656
619
  # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
@@ -692,6 +655,7 @@ module ActiveRecord
692
655
  m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
693
656
  m.register_type %r(^float)i, Type::Float.new(limit: 24)
694
657
  m.register_type %r(^double)i, Type::Float.new(limit: 53)
658
+ m.register_type %r(^json)i, MysqlJson.new
695
659
 
696
660
  register_integer_type m, %r(^bigint)i, limit: 8
697
661
  register_integer_type m, %r(^int)i, limit: 4
@@ -699,26 +663,26 @@ module ActiveRecord
699
663
  register_integer_type m, %r(^smallint)i, limit: 2
700
664
  register_integer_type m, %r(^tinyint)i, limit: 1
701
665
 
702
- m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
703
- m.alias_type %r(set)i, 'varchar'
666
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
704
667
  m.alias_type %r(year)i, 'integer'
705
668
  m.alias_type %r(bit)i, 'binary'
706
669
 
707
- m.register_type(%r(datetime)i) do |sql_type|
708
- precision = extract_precision(sql_type)
709
- MysqlDateTime.new(precision: precision)
710
- end
711
-
712
670
  m.register_type(%r(enum)i) do |sql_type|
713
671
  limit = sql_type[/^enum\((.+)\)/i, 1]
714
672
  .split(',').map{|enum| enum.strip.length - 2}.max
715
673
  MysqlString.new(limit: limit)
716
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
717
681
  end
718
682
 
719
683
  def register_integer_type(mapping, key, options) # :nodoc:
720
684
  mapping.register_type(key) do |sql_type|
721
- if /unsigned/i =~ sql_type
685
+ if /\bunsigned\z/ === sql_type
722
686
  Type::UnsignedInteger.new(options)
723
687
  else
724
688
  Type::Integer.new(options)
@@ -726,19 +690,16 @@ module ActiveRecord
726
690
  end
727
691
  end
728
692
 
729
- # MySQL is too stupid to create a temporary table for use subquery, so we have
730
- # to give it some prompting in the form of a subsubquery. Ugh!
731
- def subquery_for(key, select)
732
- subsubselect = select.clone
733
- subsubselect.projections = [key]
734
-
735
- # Materialize subquery by adding distinct
736
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
737
- subsubselect.distinct unless select.limit || select.offset || select.orders.any?
693
+ def extract_precision(sql_type)
694
+ if /time/ === sql_type
695
+ super || 0
696
+ else
697
+ super
698
+ end
699
+ end
738
700
 
739
- subselect = Arel::SelectManager.new(select.engine)
740
- subselect.project Arel.sql(key.name)
741
- 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?)
742
703
  end
743
704
 
744
705
  def add_index_length(option_strings, column_names, options = {})
@@ -746,7 +707,7 @@ module ActiveRecord
746
707
  case length
747
708
  when Hash
748
709
  column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
749
- when Fixnum
710
+ when Integer
750
711
  column_names.each {|name| option_strings[name] += "(#{length})"}
751
712
  end
752
713
  end
@@ -769,18 +730,20 @@ module ActiveRecord
769
730
  def translate_exception(exception, message)
770
731
  case error_number(exception)
771
732
  when 1062
772
- RecordNotUnique.new(message, exception)
733
+ RecordNotUnique.new(message)
773
734
  when 1452
774
- InvalidForeignKey.new(message, exception)
735
+ InvalidForeignKey.new(message)
736
+ when 1406
737
+ ValueTooLong.new(message)
775
738
  else
776
739
  super
777
740
  end
778
741
  end
779
742
 
780
743
  def add_column_sql(table_name, column_name, type, options = {})
781
- td = create_table_definition table_name, options[:temporary], options[:options]
744
+ td = create_table_definition(table_name)
782
745
  cd = td.new_column_definition(column_name, type, options)
783
- schema_creation.visit_AddColumn cd
746
+ schema_creation.accept(AddColumnDefinition.new(cd))
784
747
  end
785
748
 
786
749
  def change_column_sql(table_name, column_name, type, options = {})
@@ -794,21 +757,23 @@ module ActiveRecord
794
757
  options[:null] = column.null
795
758
  end
796
759
 
797
- options[:name] = column.name
798
- 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))
799
763
  end
800
764
 
801
765
  def rename_column_sql(table_name, column_name, new_column_name)
802
766
  column = column_for(table_name, column_name)
803
767
  options = {
804
- name: new_column_name,
805
768
  default: column.default,
806
769
  null: column.null,
807
- auto_increment: column.extra == "auto_increment"
770
+ auto_increment: column.auto_increment?
808
771
  }
809
772
 
810
773
  current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
811
- 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))
812
777
  end
813
778
 
814
779
  def remove_column_sql(table_name, column_name, type = nil, options = {})
@@ -820,8 +785,9 @@ module ActiveRecord
820
785
  end
821
786
 
822
787
  def add_index_sql(table_name, column_name, options = {})
823
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
824
- "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}"
825
791
  end
826
792
 
827
793
  def remove_index_sql(table_name, options = {})
@@ -839,39 +805,57 @@ module ActiveRecord
839
805
 
840
806
  private
841
807
 
842
- def version
843
- @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
844
- 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]
845
813
 
846
- def mariadb?
847
- 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')
848
821
  end
849
822
 
850
823
  def supports_rename_index?
851
- mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
824
+ mariadb? ? false : version >= '5.7.6'
852
825
  end
853
826
 
854
827
  def configure_connection
855
828
  variables = @config.fetch(:variables, {}).stringify_keys
856
829
 
857
- # By default, MySQL 'where id is null' selects the last inserted id.
858
- # 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.
859
831
  variables['sql_auto_is_null'] = 0
860
832
 
861
833
  # Increase timeout so the server doesn't disconnect us.
862
834
  wait_timeout = @config[:wait_timeout]
863
- wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
835
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
864
836
  variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
865
837
 
838
+ defaults = [':default', :default].to_set
839
+
866
840
  # Make MySQL reject illegal values rather than truncating or blanking them, see
867
- # 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
868
842
  # If the user has provided another value for sql_mode, don't replace it.
869
- unless variables.has_key?('sql_mode')
870
- 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')"
871
854
  end
855
+ sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
872
856
 
873
857
  # NAMES does not have an equals sign, see
874
- # 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
875
859
  # (trailing comma because variable_assignments will always have content)
876
860
  if @config[:encoding]
877
861
  encoding = "NAMES #{@config[:encoding]}"
@@ -881,7 +865,7 @@ module ActiveRecord
881
865
 
882
866
  # Gather up all of the SET variables...
883
867
  variable_assignments = variables.map do |k, v|
884
- if v == ':default' || v == :default
868
+ if defaults.include?(v)
885
869
  "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
886
870
  elsif !v.nil?
887
871
  "@@SESSION.#{k} = #{quote(v)}"
@@ -890,7 +874,13 @@ module ActiveRecord
890
874
  end.compact.join(', ')
891
875
 
892
876
  # ...and send them all in one query
893
- @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
894
884
  end
895
885
 
896
886
  def extract_foreign_key_action(structure, name, action) # :nodoc:
@@ -902,19 +892,68 @@ module ActiveRecord
902
892
  end
903
893
  end
904
894
 
905
- class MysqlDateTime < Type::DateTime # :nodoc:
906
- private
895
+ def create_table_info_cache # :nodoc:
896
+ @create_table_info_cache ||= {}
897
+ end
907
898
 
908
- def has_precision?
909
- precision || 0
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)
910
949
  end
911
950
  end
912
951
 
913
952
  class MysqlString < Type::String # :nodoc:
914
- def type_cast_for_database(value)
953
+ def serialize(value)
915
954
  case value
916
- when true then "1"
917
- when false then "0"
955
+ when true then MySQL::Quoting::QUOTED_TRUE
956
+ when false then MySQL::Quoting::QUOTED_FALSE
918
957
  else super
919
958
  end
920
959
  end
@@ -923,12 +962,16 @@ module ActiveRecord
923
962
 
924
963
  def cast_value(value)
925
964
  case value
926
- when true then "1"
927
- when false then "0"
965
+ when true then MySQL::Quoting::QUOTED_TRUE
966
+ when false then MySQL::Quoting::QUOTED_FALSE
928
967
  else super
929
968
  end
930
969
  end
931
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)
932
975
  end
933
976
  end
934
977
  end