activerecord 4.2.11.1 → 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 +5 -5
  2. data/CHANGELOG.md +1282 -1195
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record.rb +8 -4
  8. data/lib/active_record/aggregations.rb +35 -24
  9. data/lib/active_record/association_relation.rb +3 -3
  10. data/lib/active_record/associations.rb +317 -209
  11. data/lib/active_record/associations/alias_tracker.rb +19 -16
  12. data/lib/active_record/associations/association.rb +11 -9
  13. data/lib/active_record/associations/association_scope.rb +73 -102
  14. data/lib/active_record/associations/belongs_to_association.rb +21 -32
  15. data/lib/active_record/associations/builder/association.rb +28 -34
  16. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  17. data/lib/active_record/associations/builder/collection_association.rb +7 -19
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +14 -11
  19. data/lib/active_record/associations/builder/has_many.rb +4 -4
  20. data/lib/active_record/associations/builder/has_one.rb +11 -6
  21. data/lib/active_record/associations/builder/singular_association.rb +3 -10
  22. data/lib/active_record/associations/collection_association.rb +49 -41
  23. data/lib/active_record/associations/collection_proxy.rb +67 -27
  24. data/lib/active_record/associations/foreign_association.rb +1 -1
  25. data/lib/active_record/associations/has_many_association.rb +20 -71
  26. data/lib/active_record/associations/has_many_through_association.rb +8 -47
  27. data/lib/active_record/associations/has_one_association.rb +12 -5
  28. data/lib/active_record/associations/join_dependency.rb +29 -19
  29. data/lib/active_record/associations/join_dependency/join_association.rb +16 -10
  30. data/lib/active_record/associations/preloader.rb +14 -4
  31. data/lib/active_record/associations/preloader/association.rb +46 -52
  32. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  33. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  35. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  36. data/lib/active_record/associations/singular_association.rb +7 -1
  37. data/lib/active_record/associations/through_association.rb +11 -3
  38. data/lib/active_record/attribute.rb +68 -18
  39. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  40. data/lib/active_record/attribute_assignment.rb +19 -140
  41. data/lib/active_record/attribute_decorators.rb +6 -5
  42. data/lib/active_record/attribute_methods.rb +76 -47
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  44. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  45. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  46. data/lib/active_record/attribute_methods/query.rb +2 -2
  47. data/lib/active_record/attribute_methods/read.rb +31 -59
  48. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -14
  50. data/lib/active_record/attribute_methods/write.rb +13 -37
  51. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  52. data/lib/active_record/attribute_set.rb +30 -3
  53. data/lib/active_record/attribute_set/builder.rb +6 -4
  54. data/lib/active_record/attributes.rb +199 -81
  55. data/lib/active_record/autosave_association.rb +49 -16
  56. data/lib/active_record/base.rb +32 -23
  57. data/lib/active_record/callbacks.rb +39 -43
  58. data/lib/active_record/coders/json.rb +1 -1
  59. data/lib/active_record/coders/yaml_column.rb +20 -8
  60. data/lib/active_record/collection_cache_key.rb +40 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -182
  62. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  63. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -61
  64. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -2
  65. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -10
  66. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  67. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -185
  69. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  70. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +380 -141
  71. data/lib/active_record/connection_adapters/abstract/transaction.rb +51 -34
  72. data/lib/active_record/connection_adapters/abstract_adapter.rb +141 -59
  73. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +401 -370
  74. data/lib/active_record/connection_adapters/column.rb +28 -43
  75. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  76. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  77. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  78. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  79. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  80. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  83. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  84. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  85. data/lib/active_record/connection_adapters/mysql2_adapter.rb +29 -166
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +10 -72
  88. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  90. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -57
  91. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  95. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  103. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  104. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  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 +234 -148
  111. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  112. data/lib/active_record/connection_adapters/postgresql_adapter.rb +248 -160
  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 +149 -192
  119. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  120. data/lib/active_record/connection_handling.rb +37 -14
  121. data/lib/active_record/core.rb +89 -107
  122. data/lib/active_record/counter_cache.rb +13 -24
  123. data/lib/active_record/dynamic_matchers.rb +1 -20
  124. data/lib/active_record/enum.rb +113 -76
  125. data/lib/active_record/errors.rb +87 -48
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +1 -1
  128. data/lib/active_record/fixture_set/file.rb +26 -5
  129. data/lib/active_record/fixtures.rb +76 -40
  130. data/lib/active_record/gem_version.rb +4 -4
  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 +18 -2
  135. data/lib/active_record/locale/en.yml +3 -2
  136. data/lib/active_record/locking/optimistic.rb +15 -15
  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.rb +363 -133
  140. data/lib/active_record/migration/command_recorder.rb +59 -18
  141. data/lib/active_record/migration/compatibility.rb +126 -0
  142. data/lib/active_record/model_schema.rb +129 -41
  143. data/lib/active_record/nested_attributes.rb +58 -29
  144. data/lib/active_record/null_relation.rb +16 -8
  145. data/lib/active_record/persistence.rb +121 -80
  146. data/lib/active_record/query_cache.rb +15 -18
  147. data/lib/active_record/querying.rb +10 -9
  148. data/lib/active_record/railtie.rb +23 -16
  149. data/lib/active_record/railties/controller_runtime.rb +1 -1
  150. data/lib/active_record/railties/databases.rake +69 -46
  151. data/lib/active_record/readonly_attributes.rb +1 -1
  152. data/lib/active_record/reflection.rb +282 -115
  153. data/lib/active_record/relation.rb +176 -116
  154. data/lib/active_record/relation/batches.rb +139 -34
  155. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  156. data/lib/active_record/relation/calculations.rb +79 -108
  157. data/lib/active_record/relation/delegation.rb +7 -20
  158. data/lib/active_record/relation/finder_methods.rb +163 -81
  159. data/lib/active_record/relation/from_clause.rb +32 -0
  160. data/lib/active_record/relation/merger.rb +16 -42
  161. data/lib/active_record/relation/predicate_builder.rb +120 -107
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  163. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  164. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  165. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  166. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  167. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  168. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  169. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  170. data/lib/active_record/relation/query_attribute.rb +19 -0
  171. data/lib/active_record/relation/query_methods.rb +308 -244
  172. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  173. data/lib/active_record/relation/spawn_methods.rb +4 -7
  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/result.rb +4 -3
  177. data/lib/active_record/runtime_registry.rb +1 -1
  178. data/lib/active_record/sanitization.rb +95 -66
  179. data/lib/active_record/schema.rb +26 -22
  180. data/lib/active_record/schema_dumper.rb +62 -38
  181. data/lib/active_record/schema_migration.rb +11 -14
  182. data/lib/active_record/scoping.rb +32 -15
  183. data/lib/active_record/scoping/default.rb +23 -9
  184. data/lib/active_record/scoping/named.rb +49 -28
  185. data/lib/active_record/secure_token.rb +38 -0
  186. data/lib/active_record/serialization.rb +2 -4
  187. data/lib/active_record/statement_cache.rb +16 -14
  188. data/lib/active_record/store.rb +8 -3
  189. data/lib/active_record/suppressor.rb +58 -0
  190. data/lib/active_record/table_metadata.rb +68 -0
  191. data/lib/active_record/tasks/database_tasks.rb +57 -43
  192. data/lib/active_record/tasks/mysql_database_tasks.rb +6 -14
  193. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  194. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  195. data/lib/active_record/timestamp.rb +20 -9
  196. data/lib/active_record/touch_later.rb +58 -0
  197. data/lib/active_record/transactions.rb +138 -56
  198. data/lib/active_record/type.rb +66 -17
  199. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  200. data/lib/active_record/type/date.rb +2 -45
  201. data/lib/active_record/type/date_time.rb +2 -49
  202. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  203. data/lib/active_record/type/internal/timezone.rb +15 -0
  204. data/lib/active_record/type/serialized.rb +15 -14
  205. data/lib/active_record/type/time.rb +10 -16
  206. data/lib/active_record/type/type_map.rb +4 -4
  207. data/lib/active_record/type_caster.rb +7 -0
  208. data/lib/active_record/type_caster/connection.rb +29 -0
  209. data/lib/active_record/type_caster/map.rb +19 -0
  210. data/lib/active_record/validations.rb +33 -32
  211. data/lib/active_record/validations/absence.rb +23 -0
  212. data/lib/active_record/validations/associated.rb +10 -3
  213. data/lib/active_record/validations/length.rb +24 -0
  214. data/lib/active_record/validations/presence.rb +11 -12
  215. data/lib/active_record/validations/uniqueness.rb +30 -29
  216. data/lib/rails/generators/active_record/migration.rb +7 -0
  217. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  218. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  219. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -1
  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 +59 -34
  224. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  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 -64
  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 -110
@@ -1,184 +1,93 @@
1
- require 'arel/visitors/bind_visitor'
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_record/connection_adapters/statement_pool'
3
+ require 'active_record/connection_adapters/mysql/column'
4
+ require 'active_record/connection_adapters/mysql/explain_pretty_printer'
5
+ require 'active_record/connection_adapters/mysql/quoting'
6
+ require 'active_record/connection_adapters/mysql/schema_creation'
7
+ require 'active_record/connection_adapters/mysql/schema_definitions'
8
+ require 'active_record/connection_adapters/mysql/schema_dumper'
9
+ require 'active_record/connection_adapters/mysql/type_metadata'
10
+
2
11
  require 'active_support/core_ext/string/strip'
3
12
 
4
13
  module ActiveRecord
5
14
  module ConnectionAdapters
6
15
  class AbstractMysqlAdapter < AbstractAdapter
7
- include Savepoints
8
-
9
- class SchemaCreation < AbstractAdapter::SchemaCreation
10
- def visit_AddColumn(o)
11
- add_column_position!(super, column_options(o))
12
- end
13
-
14
- private
15
-
16
- def visit_DropForeignKey(name)
17
- "DROP FOREIGN KEY #{name}"
18
- end
19
-
20
- def visit_TableDefinition(o)
21
- name = o.name
22
- create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
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 >= '5.0.0'
117
+ true
118
+ end
119
+
120
+ def supports_explain?
121
+ true
207
122
  end
208
123
 
209
124
  def supports_indexes_in_create?
@@ -215,11 +130,27 @@ module ActiveRecord
215
130
  end
216
131
 
217
132
  def supports_views?
218
- version >= '5.0.0'
133
+ true
219
134
  end
220
135
 
221
136
  def supports_datetime_with_precision?
222
- version >= '5.6.4'
137
+ if mariadb?
138
+ version >= '5.3.0'
139
+ else
140
+ version >= '5.6.4'
141
+ end
142
+ end
143
+
144
+ def supports_advisory_locks?
145
+ true
146
+ end
147
+
148
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
149
+ select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
150
+ end
151
+
152
+ def release_advisory_lock(lock_name) # :nodoc:
153
+ select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
223
154
  end
224
155
 
225
156
  def native_database_types
@@ -238,8 +169,8 @@ module ActiveRecord
238
169
  raise NotImplementedError
239
170
  end
240
171
 
241
- def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
242
- Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
172
+ def new_column(*args) #:nodoc:
173
+ MySQL::Column.new(*args)
243
174
  end
244
175
 
245
176
  # Must return the MySQL error number from the exception, if the exception has an
@@ -248,48 +179,6 @@ module ActiveRecord
248
179
  raise NotImplementedError
249
180
  end
250
181
 
251
- # QUOTING ==================================================
252
-
253
- def _quote(value) # :nodoc:
254
- if value.is_a?(Type::Binary::Data)
255
- "x'#{value.hex}'"
256
- else
257
- super
258
- end
259
- end
260
-
261
- def quote_column_name(name) #:nodoc:
262
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
263
- end
264
-
265
- def quote_table_name(name) #:nodoc:
266
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
267
- end
268
-
269
- def quoted_true
270
- QUOTED_TRUE
271
- end
272
-
273
- def unquoted_true
274
- 1
275
- end
276
-
277
- def quoted_false
278
- QUOTED_FALSE
279
- end
280
-
281
- def unquoted_false
282
- 0
283
- end
284
-
285
- def quoted_date(value)
286
- if supports_datetime_with_precision? && value.acts_like?(:time) && value.respond_to?(:usec)
287
- "#{super}.#{sprintf("%06d", value.usec)}"
288
- else
289
- super
290
- end
291
- end
292
-
293
182
  # REFERENTIAL INTEGRITY ====================================
294
183
 
295
184
  def disable_referential_integrity #:nodoc:
@@ -303,13 +192,25 @@ module ActiveRecord
303
192
  end
304
193
  end
305
194
 
195
+ # CONNECTION MANAGEMENT ====================================
196
+
197
+ # Clears the prepared statements cache.
198
+ def clear_cache!
199
+ reload_type_map
200
+ @statements.clear
201
+ end
202
+
306
203
  #--
307
204
  # DATABASE STATEMENTS ======================================
308
205
  #++
309
206
 
310
- def clear_cache!
311
- super
312
- reload_type_map
207
+ def explain(arel, binds = [])
208
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
209
+ start = Time.now
210
+ result = exec_query(sql, 'EXPLAIN', binds)
211
+ elapsed = Time.now - start
212
+
213
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
313
214
  end
314
215
 
315
216
  # Executes the SQL statement in the context of this connection.
@@ -317,18 +218,13 @@ module ActiveRecord
317
218
  log(sql, name) { @connection.query(sql) }
318
219
  end
319
220
 
320
- # MysqlAdapter has to free a result after using it, so we use this method to write
321
- # stuff in an abstract way without concerning ourselves about whether it needs to be
322
- # explicitly freed or not.
323
- def execute_and_free(sql, name = nil) #:nodoc:
221
+ # Mysql2Adapter doesn't have to free a result after using it, but we use this method
222
+ # to write stuff in an abstract way without concerning ourselves about whether it
223
+ # needs to be explicitly freed or not.
224
+ def execute_and_free(sql, name = nil) # :nodoc:
324
225
  yield execute(sql, name)
325
226
  end
326
227
 
327
- def update_sql(sql, name = nil) #:nodoc:
328
- super
329
- @connection.affected_rows
330
- end
331
-
332
228
  def begin_db_transaction
333
229
  execute "BEGIN"
334
230
  end
@@ -349,7 +245,7 @@ module ActiveRecord
349
245
  # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
350
246
  # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
351
247
  # these, we must use a subquery.
352
- def join_to_update(update, select) #:nodoc:
248
+ def join_to_update(update, select, key) # :nodoc:
353
249
  if select.limit || select.offset || select.orders.any?
354
250
  super
355
251
  else
@@ -382,9 +278,9 @@ module ActiveRecord
382
278
  # create_database 'matt_development', charset: :big5
383
279
  def create_database(name, options = {})
384
280
  if options[:collation]
385
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
281
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
386
282
  else
387
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
283
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
388
284
  end
389
285
  end
390
286
 
@@ -393,7 +289,7 @@ module ActiveRecord
393
289
  # Example:
394
290
  # drop_database('sebastian_development')
395
291
  def drop_database(name) #:nodoc:
396
- execute "DROP DATABASE IF EXISTS `#{name}`"
292
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
397
293
  end
398
294
 
399
295
  def current_database
@@ -410,36 +306,69 @@ module ActiveRecord
410
306
  show_variable 'collation_database'
411
307
  end
412
308
 
413
- def tables(name = nil, database = nil, like = nil) #:nodoc:
414
- sql = "SHOW TABLES "
415
- sql << "IN #{quote_table_name(database)} " if database
416
- sql << "LIKE #{quote(like)}" if like
309
+ def tables(name = nil) # :nodoc:
310
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
311
+ #tables currently returns both tables and views.
312
+ This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
313
+ Use #data_sources instead.
314
+ MSG
417
315
 
418
- execute_and_free(sql, 'SCHEMA') do |result|
419
- result.collect { |field| field.first }
316
+ if name
317
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
318
+ Passing arguments to #tables is deprecated without replacement.
319
+ MSG
420
320
  end
321
+
322
+ data_sources
323
+ end
324
+
325
+ def data_sources
326
+ sql = "SELECT table_name FROM information_schema.tables "
327
+ sql << "WHERE table_schema = #{quote(@config[:database])}"
328
+
329
+ select_values(sql, 'SCHEMA')
421
330
  end
422
- alias data_sources tables
423
331
 
424
332
  def truncate(table_name, name = nil)
425
333
  execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
426
334
  end
427
335
 
428
- def table_exists?(name)
429
- return false unless name.present?
430
- return true if tables(nil, nil, name).any?
336
+ def table_exists?(table_name)
337
+ # Update lib/active_record/internal_metadata.rb when this gets removed
338
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
339
+ #table_exists? currently checks both tables and views.
340
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
341
+ Use #data_source_exists? instead.
342
+ MSG
431
343
 
432
- name = name.to_s
433
- schema, table = name.split('.', 2)
344
+ data_source_exists?(table_name)
345
+ end
434
346
 
435
- unless table # A table was provided without a schema
436
- table = schema
437
- schema = nil
438
- 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)
439
366
 
440
- tables(nil, schema, table).any?
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?
441
371
  end
442
- alias data_source_exists? table_exists?
443
372
 
444
373
  # Returns an array of indexes for the given table.
445
374
  def indexes(table_name, name = nil) #:nodoc:
@@ -454,7 +383,7 @@ module ActiveRecord
454
383
  mysql_index_type = row[:Index_type].downcase.to_sym
455
384
  index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
456
385
  index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
457
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
386
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using, row[:Index_comment].presence)
458
387
  end
459
388
 
460
389
  indexes.last.columns << row[:Column_name]
@@ -466,20 +395,29 @@ module ActiveRecord
466
395
  end
467
396
 
468
397
  # Returns an array of +Column+ objects for the table specified by +table_name+.
469
- def columns(table_name)#:nodoc:
470
- sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
471
- execute_and_free(sql, 'SCHEMA') do |result|
472
- each_hash(result).map do |field|
473
- field_name = set_field_encoding(field[:Field])
474
- sql_type = field[:Type]
475
- cast_type = lookup_cast_type(sql_type)
476
- new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
398
+ def columns(table_name) # :nodoc:
399
+ table_name = table_name.to_s
400
+ column_definitions(table_name).map do |field|
401
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
402
+ if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
403
+ default, default_function = nil, field[:Default]
404
+ else
405
+ default, default_function = field[:Default], nil
477
406
  end
407
+ new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
478
408
  end
479
409
  end
480
410
 
481
- def create_table(table_name, options = {}) #:nodoc:
482
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
411
+ def table_comment(table_name) # :nodoc:
412
+ 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)
483
421
  end
484
422
 
485
423
  def bulk_change_table(table_name, operations) #:nodoc:
@@ -506,8 +444,24 @@ module ActiveRecord
506
444
  rename_table_indexes(table_name, new_name)
507
445
  end
508
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.
509
462
  def drop_table(table_name, options = {})
510
- 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}"
511
465
  end
512
466
 
513
467
  def rename_index(table_name, old_name, new_name)
@@ -520,12 +474,13 @@ module ActiveRecord
520
474
  end
521
475
  end
522
476
 
523
- 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)
524
479
  column = column_for(table_name, column_name)
525
480
  change_column table_name, column_name, column.sql_type, :default => default
526
481
  end
527
482
 
528
- def change_column_null(table_name, column_name, null, default = nil)
483
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
529
484
  column = column_for(table_name, column_name)
530
485
 
531
486
  unless null || default.nil?
@@ -545,11 +500,21 @@ module ActiveRecord
545
500
  end
546
501
 
547
502
  def add_index(table_name, column_name, options = {}) #:nodoc:
548
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
549
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
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
550
511
  end
551
512
 
552
513
  def foreign_keys(table_name)
514
+ raise ArgumentError unless table_name.present?
515
+
516
+ schema, name = extract_schema_qualified_name(table_name)
517
+
553
518
  fk_info = select_all <<-SQL.strip_heredoc
554
519
  SELECT fk.referenced_table_name as 'to_table'
555
520
  ,fk.referenced_column_name as 'primary_key'
@@ -557,11 +522,11 @@ module ActiveRecord
557
522
  ,fk.constraint_name as 'name'
558
523
  FROM information_schema.key_column_usage fk
559
524
  WHERE fk.referenced_column_name is not null
560
- AND fk.table_schema = '#{@config[:database]}'
561
- AND fk.table_name = '#{table_name}'
525
+ AND fk.table_schema = #{quote(schema)}
526
+ AND fk.table_name = #{quote(name)}
562
527
  SQL
563
528
 
564
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
529
+ create_table_info = create_table_info(table_name)
565
530
 
566
531
  fk_info.map do |row|
567
532
  options = {
@@ -577,92 +542,78 @@ module ActiveRecord
577
542
  end
578
543
  end
579
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
+
580
560
  # Maps logical Rails types to MySQL-specific data types.
581
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
582
- case type.to_s
583
- when 'binary'
584
- case limit
585
- when 0..0xfff; "varbinary(#{limit})"
586
- when nil; "blob"
587
- when 0x1000..0xffffffff; "blob(#{limit})"
588
- else raise(ActiveRecordError, "No binary type has character length #{limit}")
589
- end
561
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
562
+ sql = case type.to_s
590
563
  when 'integer'
591
- case limit
592
- when 1; 'tinyint'
593
- when 2; 'smallint'
594
- when 3; 'mediumint'
595
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
596
- when 5..8; 'bigint'
597
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
598
- end
564
+ integer_to_sql(limit)
599
565
  when 'text'
600
- case limit
601
- when 0..0xff; 'tinytext'
602
- when nil, 0x100..0xffff; 'text'
603
- when 0x10000..0xffffff; 'mediumtext'
604
- when 0x1000000..0xffffffff; 'longtext'
605
- else raise(ActiveRecordError, "No text type has character length #{limit}")
606
- end
607
- when 'datetime'
608
- return super unless precision
609
-
610
- case precision
611
- when 0..6; "datetime(#{precision})"
612
- else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
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)
613
574
  end
614
575
  else
615
- super
576
+ super(type, limit, precision, scale)
616
577
  end
578
+
579
+ sql << ' unsigned' if unsigned && type != :primary_key
580
+ sql
617
581
  end
618
582
 
619
583
  # SHOW VARIABLES LIKE 'name'
620
584
  def show_variable(name)
621
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
622
- variables.first['Value'] unless variables.empty?
585
+ select_value("SELECT @@#{name}", 'SCHEMA')
623
586
  rescue ActiveRecord::StatementInvalid
624
587
  nil
625
588
  end
626
589
 
627
- # Returns a table's primary key and belonging sequence.
628
- def pk_and_sequence_for(table)
629
- execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
630
- create_table = each_hash(result).first[:"Create Table"]
631
- if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
632
- keys = $1.split(",").map { |key| key.delete('`"') }
633
- keys.length == 1 ? [keys.first, nil] : nil
634
- else
635
- nil
636
- end
637
- end
638
- end
590
+ def primary_keys(table_name) # :nodoc:
591
+ raise ArgumentError unless table_name.present?
639
592
 
640
- # Returns just a table's primary key
641
- def primary_key(table)
642
- pk_and_sequence = pk_and_sequence_for(table)
643
- pk_and_sequence && pk_and_sequence.first
644
- end
593
+ schema, name = extract_schema_qualified_name(table_name)
645
594
 
646
- def case_sensitive_modifier(node, table_attribute)
647
- node = Arel::Nodes.build_quoted node, table_attribute
648
- 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
649
603
  end
650
604
 
651
605
  def case_sensitive_comparison(table, attribute, column, value)
652
- if column.case_sensitive?
653
- 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))
654
608
  else
655
609
  super
656
610
  end
657
611
  end
658
612
 
659
- def case_insensitive_comparison(table, attribute, column, value)
660
- if column.case_sensitive?
661
- super
662
- else
663
- table[attribute].eq(value)
664
- end
613
+ def can_perform_case_insensitive_comparison_for?(column)
614
+ column.case_sensitive?
665
615
  end
616
+ private :can_perform_case_insensitive_comparison_for?
666
617
 
667
618
  # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
668
619
  # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
@@ -704,6 +655,7 @@ module ActiveRecord
704
655
  m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
705
656
  m.register_type %r(^float)i, Type::Float.new(limit: 24)
706
657
  m.register_type %r(^double)i, Type::Float.new(limit: 53)
658
+ m.register_type %r(^json)i, MysqlJson.new
707
659
 
708
660
  register_integer_type m, %r(^bigint)i, limit: 8
709
661
  register_integer_type m, %r(^int)i, limit: 4
@@ -711,26 +663,26 @@ module ActiveRecord
711
663
  register_integer_type m, %r(^smallint)i, limit: 2
712
664
  register_integer_type m, %r(^tinyint)i, limit: 1
713
665
 
714
- m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
715
- m.alias_type %r(set)i, 'varchar'
666
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
716
667
  m.alias_type %r(year)i, 'integer'
717
668
  m.alias_type %r(bit)i, 'binary'
718
669
 
719
- m.register_type(%r(datetime)i) do |sql_type|
720
- precision = extract_precision(sql_type)
721
- MysqlDateTime.new(precision: precision)
722
- end
723
-
724
670
  m.register_type(%r(enum)i) do |sql_type|
725
671
  limit = sql_type[/^enum\((.+)\)/i, 1]
726
672
  .split(',').map{|enum| enum.strip.length - 2}.max
727
673
  MysqlString.new(limit: limit)
728
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
729
681
  end
730
682
 
731
683
  def register_integer_type(mapping, key, options) # :nodoc:
732
684
  mapping.register_type(key) do |sql_type|
733
- if /unsigned/i =~ sql_type
685
+ if /\bunsigned\z/ === sql_type
734
686
  Type::UnsignedInteger.new(options)
735
687
  else
736
688
  Type::Integer.new(options)
@@ -738,19 +690,16 @@ module ActiveRecord
738
690
  end
739
691
  end
740
692
 
741
- # MySQL is too stupid to create a temporary table for use subquery, so we have
742
- # to give it some prompting in the form of a subsubquery. Ugh!
743
- def subquery_for(key, select)
744
- subsubselect = select.clone
745
- subsubselect.projections = [key]
746
-
747
- # Materialize subquery by adding distinct
748
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
749
- subsubselect.distinct unless select.limit || select.offset || select.orders.any?
693
+ def extract_precision(sql_type)
694
+ if /time/ === sql_type
695
+ super || 0
696
+ else
697
+ super
698
+ end
699
+ end
750
700
 
751
- subselect = Arel::SelectManager.new(select.engine)
752
- subselect.project Arel.sql(key.name)
753
- 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?)
754
703
  end
755
704
 
756
705
  def add_index_length(option_strings, column_names, options = {})
@@ -781,18 +730,20 @@ module ActiveRecord
781
730
  def translate_exception(exception, message)
782
731
  case error_number(exception)
783
732
  when 1062
784
- RecordNotUnique.new(message, exception)
733
+ RecordNotUnique.new(message)
785
734
  when 1452
786
- InvalidForeignKey.new(message, exception)
735
+ InvalidForeignKey.new(message)
736
+ when 1406
737
+ ValueTooLong.new(message)
787
738
  else
788
739
  super
789
740
  end
790
741
  end
791
742
 
792
743
  def add_column_sql(table_name, column_name, type, options = {})
793
- td = create_table_definition table_name, options[:temporary], options[:options]
744
+ td = create_table_definition(table_name)
794
745
  cd = td.new_column_definition(column_name, type, options)
795
- schema_creation.visit_AddColumn cd
746
+ schema_creation.accept(AddColumnDefinition.new(cd))
796
747
  end
797
748
 
798
749
  def change_column_sql(table_name, column_name, type, options = {})
@@ -806,21 +757,23 @@ module ActiveRecord
806
757
  options[:null] = column.null
807
758
  end
808
759
 
809
- options[:name] = column.name
810
- 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))
811
763
  end
812
764
 
813
765
  def rename_column_sql(table_name, column_name, new_column_name)
814
766
  column = column_for(table_name, column_name)
815
767
  options = {
816
- name: new_column_name,
817
768
  default: column.default,
818
769
  null: column.null,
819
- auto_increment: column.extra == "auto_increment"
770
+ auto_increment: column.auto_increment?
820
771
  }
821
772
 
822
773
  current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
823
- schema_creation.accept ChangeColumnDefinition.new column, current_type, options
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))
824
777
  end
825
778
 
826
779
  def remove_column_sql(table_name, column_name, type = nil, options = {})
@@ -832,8 +785,9 @@ module ActiveRecord
832
785
  end
833
786
 
834
787
  def add_index_sql(table_name, column_name, options = {})
835
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
836
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
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}"
837
791
  end
838
792
 
839
793
  def remove_index_sql(table_name, options = {})
@@ -851,12 +805,19 @@ module ActiveRecord
851
805
 
852
806
  private
853
807
 
854
- def version
855
- @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
856
- 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]
857
813
 
858
- def mariadb?
859
- 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')
860
821
  end
861
822
 
862
823
  def supports_rename_index?
@@ -866,24 +827,35 @@ module ActiveRecord
866
827
  def configure_connection
867
828
  variables = @config.fetch(:variables, {}).stringify_keys
868
829
 
869
- # By default, MySQL 'where id is null' selects the last inserted id.
870
- # 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.
871
831
  variables['sql_auto_is_null'] = 0
872
832
 
873
833
  # Increase timeout so the server doesn't disconnect us.
874
- wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
834
+ wait_timeout = @config[:wait_timeout]
875
835
  wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
876
- variables["wait_timeout"] = wait_timeout
836
+ variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
837
+
838
+ defaults = [':default', :default].to_set
877
839
 
878
840
  # Make MySQL reject illegal values rather than truncating or blanking them, see
879
- # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
841
+ # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
880
842
  # If the user has provided another value for sql_mode, don't replace it.
881
- unless variables.has_key?('sql_mode')
882
- variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
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')"
883
854
  end
855
+ sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
884
856
 
885
857
  # NAMES does not have an equals sign, see
886
- # 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
887
859
  # (trailing comma because variable_assignments will always have content)
888
860
  if @config[:encoding]
889
861
  encoding = "NAMES #{@config[:encoding]}"
@@ -893,7 +865,7 @@ module ActiveRecord
893
865
 
894
866
  # Gather up all of the SET variables...
895
867
  variable_assignments = variables.map do |k, v|
896
- if v == ':default' || v == :default
868
+ if defaults.include?(v)
897
869
  "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
898
870
  elsif !v.nil?
899
871
  "@@SESSION.#{k} = #{quote(v)}"
@@ -902,7 +874,13 @@ module ActiveRecord
902
874
  end.compact.join(', ')
903
875
 
904
876
  # ...and send them all in one query
905
- @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
906
884
  end
907
885
 
908
886
  def extract_foreign_key_action(structure, name, action) # :nodoc:
@@ -914,19 +892,68 @@ module ActiveRecord
914
892
  end
915
893
  end
916
894
 
917
- class MysqlDateTime < Type::DateTime # :nodoc:
918
- private
895
+ def create_table_info_cache # :nodoc:
896
+ @create_table_info_cache ||= {}
897
+ end
919
898
 
920
- def has_precision?
921
- 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)
922
949
  end
923
950
  end
924
951
 
925
952
  class MysqlString < Type::String # :nodoc:
926
- def type_cast_for_database(value)
953
+ def serialize(value)
927
954
  case value
928
- when true then "1"
929
- when false then "0"
955
+ when true then MySQL::Quoting::QUOTED_TRUE
956
+ when false then MySQL::Quoting::QUOTED_FALSE
930
957
  else super
931
958
  end
932
959
  end
@@ -935,12 +962,16 @@ module ActiveRecord
935
962
 
936
963
  def cast_value(value)
937
964
  case value
938
- when true then "1"
939
- when false then "0"
965
+ when true then MySQL::Quoting::QUOTED_TRUE
966
+ when false then MySQL::Quoting::QUOTED_FALSE
940
967
  else super
941
968
  end
942
969
  end
943
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)
944
975
  end
945
976
  end
946
977
  end