activerecord 4.2.11.3 → 5.0.0.beta1

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 (229) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1029 -1349
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record.rb +7 -3
  7. data/lib/active_record/aggregations.rb +35 -25
  8. data/lib/active_record/association_relation.rb +2 -2
  9. data/lib/active_record/associations.rb +305 -204
  10. data/lib/active_record/associations/alias_tracker.rb +19 -16
  11. data/lib/active_record/associations/association.rb +10 -8
  12. data/lib/active_record/associations/association_scope.rb +73 -102
  13. data/lib/active_record/associations/belongs_to_association.rb +20 -32
  14. data/lib/active_record/associations/builder/association.rb +28 -34
  15. data/lib/active_record/associations/builder/belongs_to.rb +41 -18
  16. data/lib/active_record/associations/builder/collection_association.rb +8 -24
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +11 -11
  18. data/lib/active_record/associations/builder/has_many.rb +4 -4
  19. data/lib/active_record/associations/builder/has_one.rb +10 -5
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -9
  21. data/lib/active_record/associations/collection_association.rb +40 -43
  22. data/lib/active_record/associations/collection_proxy.rb +55 -29
  23. data/lib/active_record/associations/foreign_association.rb +1 -1
  24. data/lib/active_record/associations/has_many_association.rb +20 -71
  25. data/lib/active_record/associations/has_many_through_association.rb +8 -52
  26. data/lib/active_record/associations/has_one_association.rb +12 -5
  27. data/lib/active_record/associations/join_dependency.rb +28 -18
  28. data/lib/active_record/associations/join_dependency/join_association.rb +13 -12
  29. data/lib/active_record/associations/preloader.rb +13 -4
  30. data/lib/active_record/associations/preloader/association.rb +45 -51
  31. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  32. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  33. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  34. data/lib/active_record/associations/preloader/through_association.rb +5 -4
  35. data/lib/active_record/associations/singular_association.rb +6 -0
  36. data/lib/active_record/associations/through_association.rb +11 -3
  37. data/lib/active_record/attribute.rb +61 -17
  38. data/lib/active_record/attribute/user_provided_default.rb +23 -0
  39. data/lib/active_record/attribute_assignment.rb +27 -140
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods.rb +79 -26
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  43. data/lib/active_record/attribute_methods/dirty.rb +46 -86
  44. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  45. data/lib/active_record/attribute_methods/query.rb +2 -2
  46. data/lib/active_record/attribute_methods/read.rb +26 -42
  47. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +42 -9
  49. data/lib/active_record/attribute_methods/write.rb +13 -24
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set.rb +30 -3
  52. data/lib/active_record/attribute_set/builder.rb +6 -4
  53. data/lib/active_record/attributes.rb +194 -81
  54. data/lib/active_record/autosave_association.rb +33 -15
  55. data/lib/active_record/base.rb +30 -18
  56. data/lib/active_record/callbacks.rb +36 -40
  57. data/lib/active_record/coders/yaml_column.rb +20 -8
  58. data/lib/active_record/collection_cache_key.rb +31 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +431 -122
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +40 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +62 -8
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -38
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +229 -185
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +52 -13
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +275 -115
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +32 -33
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +83 -32
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +384 -221
  70. data/lib/active_record/connection_adapters/column.rb +27 -41
  71. data/lib/active_record/connection_adapters/connection_specification.rb +2 -21
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +57 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +69 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +59 -0
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +22 -101
  77. data/lib/active_record/connection_adapters/postgresql/column.rb +6 -10
  78. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +3 -3
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  80. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +23 -57
  81. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
  85. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  86. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  87. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +23 -16
  92. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +18 -11
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +174 -128
  101. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -112
  103. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  104. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +15 -0
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +134 -110
  107. data/lib/active_record/connection_adapters/statement_pool.rb +28 -11
  108. data/lib/active_record/connection_handling.rb +5 -5
  109. data/lib/active_record/core.rb +72 -104
  110. data/lib/active_record/counter_cache.rb +9 -20
  111. data/lib/active_record/dynamic_matchers.rb +1 -20
  112. data/lib/active_record/enum.rb +110 -76
  113. data/lib/active_record/errors.rb +72 -47
  114. data/lib/active_record/explain_registry.rb +1 -1
  115. data/lib/active_record/explain_subscriber.rb +1 -1
  116. data/lib/active_record/fixture_set/file.rb +19 -4
  117. data/lib/active_record/fixtures.rb +76 -40
  118. data/lib/active_record/gem_version.rb +4 -4
  119. data/lib/active_record/inheritance.rb +27 -40
  120. data/lib/active_record/integration.rb +4 -4
  121. data/lib/active_record/legacy_yaml_adapter.rb +18 -2
  122. data/lib/active_record/locale/en.yml +3 -2
  123. data/lib/active_record/locking/optimistic.rb +10 -14
  124. data/lib/active_record/locking/pessimistic.rb +1 -1
  125. data/lib/active_record/log_subscriber.rb +40 -22
  126. data/lib/active_record/migration.rb +304 -133
  127. data/lib/active_record/migration/command_recorder.rb +59 -18
  128. data/lib/active_record/migration/compatibility.rb +90 -0
  129. data/lib/active_record/model_schema.rb +92 -40
  130. data/lib/active_record/nested_attributes.rb +45 -34
  131. data/lib/active_record/null_relation.rb +15 -7
  132. data/lib/active_record/persistence.rb +112 -72
  133. data/lib/active_record/querying.rb +6 -5
  134. data/lib/active_record/railtie.rb +20 -13
  135. data/lib/active_record/railties/controller_runtime.rb +1 -1
  136. data/lib/active_record/railties/databases.rake +47 -38
  137. data/lib/active_record/readonly_attributes.rb +1 -1
  138. data/lib/active_record/reflection.rb +182 -57
  139. data/lib/active_record/relation.rb +152 -100
  140. data/lib/active_record/relation/batches.rb +133 -33
  141. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  142. data/lib/active_record/relation/calculations.rb +80 -101
  143. data/lib/active_record/relation/delegation.rb +6 -19
  144. data/lib/active_record/relation/finder_methods.rb +58 -46
  145. data/lib/active_record/relation/from_clause.rb +32 -0
  146. data/lib/active_record/relation/merger.rb +13 -42
  147. data/lib/active_record/relation/predicate_builder.rb +99 -105
  148. data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
  149. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +78 -0
  150. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  151. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  152. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  153. data/lib/active_record/relation/predicate_builder/range_handler.rb +17 -0
  154. data/lib/active_record/relation/query_attribute.rb +19 -0
  155. data/lib/active_record/relation/query_methods.rb +274 -238
  156. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  157. data/lib/active_record/relation/spawn_methods.rb +3 -6
  158. data/lib/active_record/relation/where_clause.rb +173 -0
  159. data/lib/active_record/relation/where_clause_factory.rb +37 -0
  160. data/lib/active_record/result.rb +4 -3
  161. data/lib/active_record/runtime_registry.rb +1 -1
  162. data/lib/active_record/sanitization.rb +94 -65
  163. data/lib/active_record/schema.rb +23 -22
  164. data/lib/active_record/schema_dumper.rb +33 -22
  165. data/lib/active_record/schema_migration.rb +10 -4
  166. data/lib/active_record/scoping.rb +17 -6
  167. data/lib/active_record/scoping/default.rb +19 -6
  168. data/lib/active_record/scoping/named.rb +39 -28
  169. data/lib/active_record/secure_token.rb +38 -0
  170. data/lib/active_record/serialization.rb +2 -4
  171. data/lib/active_record/statement_cache.rb +15 -13
  172. data/lib/active_record/store.rb +8 -3
  173. data/lib/active_record/suppressor.rb +54 -0
  174. data/lib/active_record/table_metadata.rb +64 -0
  175. data/lib/active_record/tasks/database_tasks.rb +30 -40
  176. data/lib/active_record/tasks/mysql_database_tasks.rb +7 -15
  177. data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
  178. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  179. data/lib/active_record/timestamp.rb +16 -9
  180. data/lib/active_record/touch_later.rb +58 -0
  181. data/lib/active_record/transactions.rb +138 -56
  182. data/lib/active_record/type.rb +66 -17
  183. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  184. data/lib/active_record/type/date.rb +2 -45
  185. data/lib/active_record/type/date_time.rb +2 -49
  186. data/lib/active_record/type/internal/abstract_json.rb +33 -0
  187. data/lib/active_record/type/internal/timezone.rb +15 -0
  188. data/lib/active_record/type/serialized.rb +9 -14
  189. data/lib/active_record/type/time.rb +3 -21
  190. data/lib/active_record/type/type_map.rb +4 -4
  191. data/lib/active_record/type_caster.rb +7 -0
  192. data/lib/active_record/type_caster/connection.rb +29 -0
  193. data/lib/active_record/type_caster/map.rb +19 -0
  194. data/lib/active_record/validations.rb +33 -32
  195. data/lib/active_record/validations/absence.rb +24 -0
  196. data/lib/active_record/validations/associated.rb +10 -3
  197. data/lib/active_record/validations/length.rb +36 -0
  198. data/lib/active_record/validations/presence.rb +12 -12
  199. data/lib/active_record/validations/uniqueness.rb +24 -21
  200. data/lib/rails/generators/active_record/migration.rb +7 -0
  201. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  202. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
  203. data/lib/rails/generators/active_record/migration/templates/migration.rb +4 -1
  204. data/lib/rails/generators/active_record/model/model_generator.rb +21 -15
  205. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  206. metadata +50 -35
  207. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  208. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  209. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  210. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  211. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  212. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  213. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  214. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  215. data/lib/active_record/type/big_integer.rb +0 -13
  216. data/lib/active_record/type/binary.rb +0 -50
  217. data/lib/active_record/type/boolean.rb +0 -31
  218. data/lib/active_record/type/decimal.rb +0 -64
  219. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  220. data/lib/active_record/type/decorator.rb +0 -14
  221. data/lib/active_record/type/float.rb +0 -19
  222. data/lib/active_record/type/integer.rb +0 -59
  223. data/lib/active_record/type/mutable.rb +0 -16
  224. data/lib/active_record/type/numeric.rb +0 -36
  225. data/lib/active_record/type/string.rb +0 -40
  226. data/lib/active_record/type/text.rb +0 -11
  227. data/lib/active_record/type/time_value.rb +0 -38
  228. data/lib/active_record/type/unsigned_integer.rb +0 -15
  229. data/lib/active_record/type/value.rb +0 -110
@@ -1,13 +1,10 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  class TransactionState
4
- attr_reader :parent
5
-
6
4
  VALID_STATES = Set.new([:committed, :rolledback, nil])
7
5
 
8
6
  def initialize(state = nil)
9
7
  @state = state
10
- @parent = nil
11
8
  end
12
9
 
13
10
  def finalized?
@@ -27,7 +24,7 @@ module ActiveRecord
27
24
  end
28
25
 
29
26
  def set_state(state)
30
- if !VALID_STATES.include?(state)
27
+ unless VALID_STATES.include?(state)
31
28
  raise ArgumentError, "Invalid transaction state: #{state}"
32
29
  end
33
30
  @state = state
@@ -47,11 +44,12 @@ module ActiveRecord
47
44
  attr_reader :connection, :state, :records, :savepoint_name
48
45
  attr_writer :joinable
49
46
 
50
- def initialize(connection, options)
47
+ def initialize(connection, options, run_commit_callbacks: false)
51
48
  @connection = connection
52
49
  @state = TransactionState.new
53
50
  @records = []
54
51
  @joinable = options.fetch(:joinable, true)
52
+ @run_commit_callbacks = run_commit_callbacks
55
53
  end
56
54
 
57
55
  def add_record(record)
@@ -65,16 +63,11 @@ module ActiveRecord
65
63
  def rollback_records
66
64
  ite = records.uniq
67
65
  while record = ite.shift
68
- begin
69
- record.rolledback! full_rollback?
70
- rescue => e
71
- raise if ActiveRecord::Base.raise_in_transactional_callbacks
72
- record.logger.error(e) if record.respond_to?(:logger) && record.logger
73
- end
66
+ record.rolledback!(force_restore_state: full_rollback?)
74
67
  end
75
68
  ensure
76
69
  ite.each do |i|
77
- i.rolledback!(full_rollback?, false)
70
+ i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
78
71
  end
79
72
  end
80
73
 
@@ -82,20 +75,22 @@ module ActiveRecord
82
75
  @state.set_state(:committed)
83
76
  end
84
77
 
78
+ def before_commit_records
79
+ records.uniq.each(&:before_committed!) if @run_commit_callbacks
80
+ end
81
+
85
82
  def commit_records
86
83
  ite = records.uniq
87
84
  while record = ite.shift
88
- begin
85
+ if @run_commit_callbacks
89
86
  record.committed!
90
- rescue => e
91
- raise if ActiveRecord::Base.raise_in_transactional_callbacks
92
- record.logger.error(e) if record.respond_to?(:logger) && record.logger
87
+ else
88
+ # if not running callbacks, only adds the record to the parent transaction
89
+ record.add_to_transaction
93
90
  end
94
91
  end
95
92
  ensure
96
- ite.each do |i|
97
- i.committed!(false)
98
- end
93
+ ite.each { |i| i.committed!(should_run_callbacks: false) }
99
94
  end
100
95
 
101
96
  def full_rollback?; true; end
@@ -106,8 +101,8 @@ module ActiveRecord
106
101
 
107
102
  class SavepointTransaction < Transaction
108
103
 
109
- def initialize(connection, savepoint_name, options)
110
- super(connection, options)
104
+ def initialize(connection, savepoint_name, options, *args)
105
+ super(connection, options, *args)
111
106
  if options[:isolation]
112
107
  raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
113
108
  end
@@ -117,14 +112,11 @@ module ActiveRecord
117
112
  def rollback
118
113
  connection.rollback_to_savepoint(savepoint_name)
119
114
  super
120
- rollback_records
121
115
  end
122
116
 
123
117
  def commit
124
118
  connection.release_savepoint(savepoint_name)
125
119
  super
126
- parent = connection.transaction_manager.current_transaction
127
- records.each { |r| parent.add_record(r) }
128
120
  end
129
121
 
130
122
  def full_rollback?; false; end
@@ -132,7 +124,7 @@ module ActiveRecord
132
124
 
133
125
  class RealTransaction < Transaction
134
126
 
135
- def initialize(connection, options)
127
+ def initialize(connection, options, *args)
136
128
  super
137
129
  if options[:isolation]
138
130
  connection.begin_isolated_db_transaction(options[:isolation])
@@ -144,13 +136,11 @@ module ActiveRecord
144
136
  def rollback
145
137
  connection.rollback_db_transaction
146
138
  super
147
- rollback_records
148
139
  end
149
140
 
150
141
  def commit
151
142
  connection.commit_db_transaction
152
143
  super
153
- commit_records
154
144
  end
155
145
  end
156
146
 
@@ -161,22 +151,31 @@ module ActiveRecord
161
151
  end
162
152
 
163
153
  def begin_transaction(options = {})
154
+ run_commit_callbacks = !current_transaction.joinable?
164
155
  transaction =
165
156
  if @stack.empty?
166
- RealTransaction.new(@connection, options)
157
+ RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
167
158
  else
168
- SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options)
159
+ SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options,
160
+ run_commit_callbacks: run_commit_callbacks)
169
161
  end
162
+
170
163
  @stack.push(transaction)
171
164
  transaction
172
165
  end
173
166
 
174
167
  def commit_transaction
175
- @stack.pop.commit
168
+ transaction = @stack.last
169
+ transaction.before_commit_records
170
+ @stack.pop
171
+ transaction.commit
172
+ transaction.commit_records
176
173
  end
177
174
 
178
- def rollback_transaction
179
- @stack.pop.rollback
175
+ def rollback_transaction(transaction = nil)
176
+ transaction ||= @stack.pop
177
+ transaction.rollback
178
+ transaction.rollback_records
180
179
  end
181
180
 
182
181
  def within_new_transaction(options = {})
@@ -193,7 +192,7 @@ module ActiveRecord
193
192
  begin
194
193
  commit_transaction
195
194
  rescue Exception
196
- transaction.rollback unless transaction.state.completed?
195
+ rollback_transaction(transaction) unless transaction.state.completed?
197
196
  raise
198
197
  end
199
198
  end
@@ -1,12 +1,10 @@
1
- require 'date'
2
- require 'bigdecimal'
3
- require 'bigdecimal/util'
4
1
  require 'active_record/type'
5
2
  require 'active_support/core_ext/benchmark'
3
+ require 'active_record/connection_adapters/determine_if_preparable_visitor'
6
4
  require 'active_record/connection_adapters/schema_cache'
5
+ require 'active_record/connection_adapters/sql_type_metadata'
7
6
  require 'active_record/connection_adapters/abstract/schema_dumper'
8
7
  require 'active_record/connection_adapters/abstract/schema_creation'
9
- require 'monitor'
10
8
  require 'arel/collectors/bind'
11
9
  require 'arel/collectors/sql_string'
12
10
 
@@ -21,10 +19,10 @@ module ActiveRecord
21
19
  autoload :IndexDefinition
22
20
  autoload :ColumnDefinition
23
21
  autoload :ChangeColumnDefinition
22
+ autoload :ForeignKeyDefinition
24
23
  autoload :TableDefinition
25
24
  autoload :Table
26
25
  autoload :AlterTable
27
- autoload :TimestampDefaultDeprecation
28
26
  end
29
27
 
30
28
  autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
@@ -54,22 +52,21 @@ module ActiveRecord
54
52
  # related classes form the abstraction layer which makes this possible.
55
53
  # An AbstractAdapter represents a connection to a database, and provides an
56
54
  # abstract interface for database-specific functionality such as establishing
57
- # a connection, escaping values, building the right SQL fragments for ':offset'
58
- # and ':limit' options, etc.
55
+ # a connection, escaping values, building the right SQL fragments for +:offset+
56
+ # and +:limit+ options, etc.
59
57
  #
60
58
  # All the concrete database adapters follow the interface laid down in this class.
61
- # ActiveRecord::Base.connection returns an AbstractAdapter object, which
59
+ # {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling#connection] returns an AbstractAdapter object, which
62
60
  # you can use.
63
61
  #
64
62
  # Most of the methods in the adapter are useful during migrations. Most
65
- # notably, the instance methods provided by SchemaStatement are very useful.
63
+ # notably, the instance methods provided by SchemaStatements are very useful.
66
64
  class AbstractAdapter
67
65
  ADAPTER_NAME = 'Abstract'.freeze
68
66
  include Quoting, DatabaseStatements, SchemaStatements
69
67
  include DatabaseLimits
70
68
  include QueryCache
71
69
  include ActiveSupport::Callbacks
72
- include MonitorMixin
73
70
  include ColumnDumper
74
71
 
75
72
  SIMPLE_INT = /\A\d+\z/
@@ -98,14 +95,15 @@ module ActiveRecord
98
95
 
99
96
  attr_reader :prepared_statements
100
97
 
101
- def initialize(connection, logger = nil, pool = nil) #:nodoc:
98
+ def initialize(connection, logger = nil, config = {}) # :nodoc:
102
99
  super()
103
100
 
104
101
  @connection = connection
105
102
  @owner = nil
106
103
  @instrumenter = ActiveSupport::Notifications.instrumenter
107
104
  @logger = logger
108
- @pool = pool
105
+ @config = config
106
+ @pool = nil
109
107
  @schema_cache = SchemaCache.new self
110
108
  @visitor = nil
111
109
  @prepared_statements = false
@@ -125,7 +123,8 @@ module ActiveRecord
125
123
 
126
124
  class BindCollector < Arel::Collectors::Bind
127
125
  def compile(bvs, conn)
128
- super(bvs.map { |bv| conn.quote(*bv.reverse) })
126
+ casted_binds = conn.prepare_binds_for_database(bvs)
127
+ super(casted_binds.map { |value| conn.quote(value) })
129
128
  end
130
129
  end
131
130
 
@@ -151,12 +150,20 @@ module ActiveRecord
151
150
  SchemaCreation.new self
152
151
  end
153
152
 
153
+ # this method must only be called while holding connection pool's mutex
154
154
  def lease
155
- synchronize do
156
- unless in_use?
157
- @owner = Thread.current
155
+ if in_use?
156
+ msg = 'Cannot lease connection, '
157
+ if @owner == Thread.current
158
+ msg << 'it is already leased by the current thread.'
159
+ else
160
+ msg << "it is already in use by a different thread: #{@owner}. " <<
161
+ "Current thread: #{Thread.current}."
158
162
  end
163
+ raise ActiveRecordError, msg
159
164
  end
165
+
166
+ @owner = Thread.current
160
167
  end
161
168
 
162
169
  def schema_cache=(cache)
@@ -164,6 +171,7 @@ module ActiveRecord
164
171
  @schema_cache = cache
165
172
  end
166
173
 
174
+ # this method must only be called while holding connection pool's mutex
167
175
  def expire
168
176
  @owner = nil
169
177
  end
@@ -207,6 +215,11 @@ module ActiveRecord
207
215
  false
208
216
  end
209
217
 
218
+ # Does this adapter support application-enforced advisory locking?
219
+ def supports_advisory_locks?
220
+ false
221
+ end
222
+
210
223
  # Should primary key values be selected from their corresponding
211
224
  # sequence before the insert statement? If true, next_sequence_value
212
225
  # is called before each insert to set the record's primary key.
@@ -255,6 +268,16 @@ module ActiveRecord
255
268
  false
256
269
  end
257
270
 
271
+ # Does this adapter support datetime with precision?
272
+ def supports_datetime_with_precision?
273
+ false
274
+ end
275
+
276
+ # Does this adapter support json data type?
277
+ def supports_json?
278
+ false
279
+ end
280
+
258
281
  # This is meant to be implemented by the adapters that support extensions
259
282
  def disable_extension(name)
260
283
  end
@@ -263,6 +286,20 @@ module ActiveRecord
263
286
  def enable_extension(name)
264
287
  end
265
288
 
289
+ # This is meant to be implemented by the adapters that support advisory
290
+ # locks
291
+ #
292
+ # Return true if we got the lock, otherwise false
293
+ def get_advisory_lock(lock_id) # :nodoc:
294
+ end
295
+
296
+ # This is meant to be implemented by the adapters that support advisory
297
+ # locks.
298
+ #
299
+ # Return true if we released the lock, otherwise false
300
+ def release_advisory_lock(lock_id) # :nodoc:
301
+ end
302
+
266
303
  # A list of extensions, to be filled in by adapters that support them.
267
304
  def extensions
268
305
  []
@@ -273,8 +310,6 @@ module ActiveRecord
273
310
  {}
274
311
  end
275
312
 
276
- # QUOTING ==================================================
277
-
278
313
  # Returns a bind substitution value given a bind +column+
279
314
  # NOTE: The column param is currently being used by the sqlserver-adapter
280
315
  def substitute_at(column, _unused = 0)
@@ -334,7 +369,7 @@ module ActiveRecord
334
369
  end
335
370
 
336
371
  # Checks whether the connection to the database is still active (i.e. not stale).
337
- # This is done under the hood by calling <tt>active?</tt>. If the connection
372
+ # This is done under the hood by calling #active?. If the connection
338
373
  # is no longer active, then this method will reconnect to the database.
339
374
  def verify!(*ignored)
340
375
  reconnect! unless active?
@@ -367,9 +402,18 @@ module ActiveRecord
367
402
  end
368
403
 
369
404
  def case_insensitive_comparison(table, attribute, column, value)
370
- table[attribute].lower.eq(table.lower(value))
405
+ if can_perform_case_insensitive_comparison_for?(column)
406
+ table[attribute].lower.eq(table.lower(value))
407
+ else
408
+ case_sensitive_comparison(table, attribute, column, value)
409
+ end
371
410
  end
372
411
 
412
+ def can_perform_case_insensitive_comparison_for?(column)
413
+ true
414
+ end
415
+ private :can_perform_case_insensitive_comparison_for?
416
+
373
417
  def current_savepoint_name
374
418
  current_transaction.savepoint_name
375
419
  end
@@ -385,8 +429,8 @@ module ActiveRecord
385
429
  end
386
430
  end
387
431
 
388
- def new_column(name, default, cast_type, sql_type = nil, null = true)
389
- Column.new(name, default, cast_type, sql_type, null)
432
+ def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
433
+ Column.new(name, default, sql_type_metadata, null, default_function, collation)
390
434
  end
391
435
 
392
436
  def lookup_cast_type(sql_type) # :nodoc:
@@ -400,15 +444,15 @@ module ActiveRecord
400
444
  protected
401
445
 
402
446
  def initialize_type_map(m) # :nodoc:
403
- register_class_with_limit m, %r(boolean)i, Type::Boolean
404
- register_class_with_limit m, %r(char)i, Type::String
405
- register_class_with_limit m, %r(binary)i, Type::Binary
406
- register_class_with_limit m, %r(text)i, Type::Text
407
- register_class_with_limit m, %r(date)i, Type::Date
408
- register_class_with_limit m, %r(time)i, Type::Time
409
- register_class_with_limit m, %r(datetime)i, Type::DateTime
410
- register_class_with_limit m, %r(float)i, Type::Float
411
- register_class_with_limit m, %r(int)i, Type::Integer
447
+ register_class_with_limit m, %r(boolean)i, Type::Boolean
448
+ register_class_with_limit m, %r(char)i, Type::String
449
+ register_class_with_limit m, %r(binary)i, Type::Binary
450
+ register_class_with_limit m, %r(text)i, Type::Text
451
+ register_class_with_precision m, %r(date)i, Type::Date
452
+ register_class_with_precision m, %r(time)i, Type::Time
453
+ register_class_with_precision m, %r(datetime)i, Type::DateTime
454
+ register_class_with_limit m, %r(float)i, Type::Float
455
+ register_class_with_limit m, %r(int)i, Type::Integer
412
456
 
413
457
  m.alias_type %r(blob)i, 'binary'
414
458
  m.alias_type %r(clob)i, 'text'
@@ -442,6 +486,13 @@ module ActiveRecord
442
486
  end
443
487
  end
444
488
 
489
+ def register_class_with_precision(mapping, key, klass) # :nodoc:
490
+ mapping.register_type(key) do |*args|
491
+ precision = extract_precision(args.last)
492
+ klass.new(precision: precision)
493
+ end
494
+ end
495
+
445
496
  def extract_scale(sql_type) # :nodoc:
446
497
  case sql_type
447
498
  when /\((\d+)\)/ then 0
@@ -488,7 +539,7 @@ module ActiveRecord
488
539
 
489
540
  def translate_exception(exception, message)
490
541
  # override in derived class
491
- ActiveRecord::StatementInvalid.new(message, exception)
542
+ ActiveRecord::StatementInvalid.new(message)
492
543
  end
493
544
 
494
545
  def without_prepared_statement?(binds)
@@ -1,77 +1,29 @@
1
- require 'arel/visitors/bind_visitor'
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_record/connection_adapters/mysql/schema_creation'
3
+ require 'active_record/connection_adapters/mysql/schema_definitions'
4
+ require 'active_record/connection_adapters/mysql/schema_dumper'
5
+
2
6
  require 'active_support/core_ext/string/strip'
3
7
 
4
8
  module ActiveRecord
5
9
  module ConnectionAdapters
6
10
  class AbstractMysqlAdapter < AbstractAdapter
11
+ include MySQL::ColumnDumper
7
12
  include Savepoints
8
13
 
9
- class SchemaCreation < AbstractAdapter::SchemaCreation
10
- def visit_AddColumn(o)
11
- add_column_position!(super, column_options(o))
12
- end
13
-
14
- private
15
-
16
- def visit_DropForeignKey(name)
17
- "DROP FOREIGN KEY #{name}"
18
- end
19
-
20
- def visit_TableDefinition(o)
21
- name = o.name
22
- create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
23
-
24
- statements = o.columns.map { |c| accept c }
25
- statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
26
-
27
- create_sql << "(#{statements.join(', ')}) " if statements.present?
28
- create_sql << "#{o.options}"
29
- create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
30
- create_sql
31
- end
32
-
33
- def visit_ChangeColumnDefinition(o)
34
- column = o.column
35
- options = o.options
36
- sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
37
- change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
38
- add_column_options!(change_column_sql, options.merge(column: column))
39
- add_column_position!(change_column_sql, options)
40
- end
41
-
42
- def add_column_position!(sql, options)
43
- if options[:first]
44
- sql << " FIRST"
45
- elsif options[:after]
46
- sql << " AFTER #{quote_column_name(options[:after])}"
47
- end
48
- sql
49
- end
50
-
51
- def index_in_create(table_name, column_name, options)
52
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
53
- "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
54
- end
14
+ def update_table_definition(table_name, base) # :nodoc:
15
+ MySQL::Table.new(table_name, base)
55
16
  end
56
17
 
57
18
  def schema_creation
58
- SchemaCreation.new self
59
- end
60
-
61
- def prepare_column_options(column, types) # :nodoc:
62
- spec = super
63
- spec.delete(:limit) if :boolean === column.type
64
- spec
19
+ MySQL::SchemaCreation.new(self)
65
20
  end
66
21
 
67
22
  class Column < ConnectionAdapters::Column # :nodoc:
68
- attr_reader :collation, :strict, :extra
23
+ delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
69
24
 
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)
25
+ def initialize(*)
26
+ super
75
27
  assert_valid_default(default)
76
28
  extract_default
77
29
  end
@@ -79,7 +31,7 @@ module ActiveRecord
79
31
  def extract_default
80
32
  if blob_or_text_column?
81
33
  @default = null || strict ? nil : ''
82
- elsif missing_default_forged_as_empty_string?(@default)
34
+ elsif missing_default_forged_as_empty_string?(default)
83
35
  @default = nil
84
36
  end
85
37
  end
@@ -93,15 +45,16 @@ module ActiveRecord
93
45
  sql_type =~ /blob/i || type == :text
94
46
  end
95
47
 
48
+ def unsigned?
49
+ /unsigned/ === sql_type
50
+ end
51
+
96
52
  def case_sensitive?
97
53
  collation && !collation.match(/_ci$/)
98
54
  end
99
55
 
100
- def ==(other)
101
- super &&
102
- collation == other.collation &&
103
- strict == other.strict &&
104
- extra == other.extra
56
+ def auto_increment?
57
+ extra == 'auto_increment'
105
58
  end
106
59
 
107
60
  private
@@ -122,9 +75,32 @@ module ActiveRecord
122
75
  raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
123
76
  end
124
77
  end
78
+ end
79
+
80
+ class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
81
+ attr_reader :extra, :strict
82
+
83
+ def initialize(type_metadata, extra: "", strict: false)
84
+ super(type_metadata)
85
+ @type_metadata = type_metadata
86
+ @extra = extra
87
+ @strict = strict
88
+ end
89
+
90
+ def ==(other)
91
+ other.is_a?(MysqlTypeMetadata) &&
92
+ attributes_for_hash == other.attributes_for_hash
93
+ end
94
+ alias eql? ==
95
+
96
+ def hash
97
+ attributes_for_hash.hash
98
+ end
99
+
100
+ protected
125
101
 
126
102
  def attributes_for_hash
127
- super + [collation, strict, extra]
103
+ [self.class, @type_metadata, extra, strict]
128
104
  end
129
105
  end
130
106
 
@@ -148,17 +124,18 @@ module ActiveRecord
148
124
  QUOTED_TRUE, QUOTED_FALSE = '1', '0'
149
125
 
150
126
  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 }
127
+ primary_key: "int auto_increment PRIMARY KEY",
128
+ string: { name: "varchar", limit: 255 },
129
+ text: { name: "text" },
130
+ integer: { name: "int", limit: 4 },
131
+ float: { name: "float" },
132
+ decimal: { name: "decimal" },
133
+ datetime: { name: "datetime" },
134
+ time: { name: "time" },
135
+ date: { name: "date" },
136
+ binary: { name: "blob" },
137
+ boolean: { name: "tinyint", limit: 1 },
138
+ json: { name: "json" },
162
139
  }
163
140
 
164
141
  INDEX_TYPES = [:fulltext, :spatial]
@@ -166,19 +143,33 @@ module ActiveRecord
166
143
 
167
144
  # FIXME: Make the first parameter more similar for the two adapters
168
145
  def initialize(connection, logger, connection_options, config)
169
- super(connection, logger)
170
- @connection_options, @config = connection_options, config
146
+ super(connection, logger, config)
171
147
  @quoted_column_names, @quoted_table_names = {}, {}
172
148
 
173
149
  @visitor = Arel::Visitors::MySQL.new self
174
150
 
175
151
  if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
176
152
  @prepared_statements = true
153
+ @visitor.extend(DetermineIfPreparableVisitor)
177
154
  else
178
155
  @prepared_statements = false
179
156
  end
180
157
  end
181
158
 
159
+ MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN = 191
160
+ CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32']
161
+ def initialize_schema_migrations_table
162
+ if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
163
+ ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN)
164
+ else
165
+ ActiveRecord::SchemaMigration.create_table
166
+ end
167
+ end
168
+
169
+ def version
170
+ @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
171
+ end
172
+
182
173
  # Returns true, since this connection adapter supports migrations.
183
174
  def supports_migrations?
184
175
  true
@@ -206,6 +197,10 @@ module ActiveRecord
206
197
  version >= '5.0.0'
207
198
  end
208
199
 
200
+ def supports_explain?
201
+ true
202
+ end
203
+
209
204
  def supports_indexes_in_create?
210
205
  true
211
206
  end
@@ -222,6 +217,20 @@ module ActiveRecord
222
217
  version >= '5.6.4'
223
218
  end
224
219
 
220
+ # 5.0.0 definitely supports it, possibly supported by earlier versions but
221
+ # not sure
222
+ def supports_advisory_locks?
223
+ version >= '5.0.0'
224
+ end
225
+
226
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
227
+ select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
228
+ end
229
+
230
+ def release_advisory_lock(lock_name) # :nodoc:
231
+ select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
232
+ end
233
+
225
234
  def native_database_types
226
235
  NATIVE_DATABASE_TYPES
227
236
  end
@@ -238,8 +247,8 @@ module ActiveRecord
238
247
  raise NotImplementedError
239
248
  end
240
249
 
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)
250
+ def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
251
+ Column.new(field, default, sql_type_metadata, null, default_function, collation)
243
252
  end
244
253
 
245
254
  # Must return the MySQL error number from the exception, if the exception has an
@@ -283,10 +292,10 @@ module ActiveRecord
283
292
  end
284
293
 
285
294
  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
295
+ if supports_datetime_with_precision?
289
296
  super
297
+ else
298
+ super.sub(/\.\d{6}\z/, '')
290
299
  end
291
300
  end
292
301
 
@@ -307,6 +316,80 @@ module ActiveRecord
307
316
  # DATABASE STATEMENTS ======================================
308
317
  #++
309
318
 
319
+ def explain(arel, binds = [])
320
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
321
+ start = Time.now
322
+ result = exec_query(sql, 'EXPLAIN', binds)
323
+ elapsed = Time.now - start
324
+
325
+ ExplainPrettyPrinter.new.pp(result, elapsed)
326
+ end
327
+
328
+ class ExplainPrettyPrinter # :nodoc:
329
+ # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
330
+ # MySQL shell:
331
+ #
332
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
333
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
334
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
335
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
336
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
337
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
338
+ # 2 rows in set (0.00 sec)
339
+ #
340
+ # This is an exercise in Ruby hyperrealism :).
341
+ def pp(result, elapsed)
342
+ widths = compute_column_widths(result)
343
+ separator = build_separator(widths)
344
+
345
+ pp = []
346
+
347
+ pp << separator
348
+ pp << build_cells(result.columns, widths)
349
+ pp << separator
350
+
351
+ result.rows.each do |row|
352
+ pp << build_cells(row, widths)
353
+ end
354
+
355
+ pp << separator
356
+ pp << build_footer(result.rows.length, elapsed)
357
+
358
+ pp.join("\n") + "\n"
359
+ end
360
+
361
+ private
362
+
363
+ def compute_column_widths(result)
364
+ [].tap do |widths|
365
+ result.columns.each_with_index do |column, i|
366
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
367
+ widths << cells_in_column.map(&:length).max
368
+ end
369
+ end
370
+ end
371
+
372
+ def build_separator(widths)
373
+ padding = 1
374
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
375
+ end
376
+
377
+ def build_cells(items, widths)
378
+ cells = []
379
+ items.each_with_index do |item, i|
380
+ item = 'NULL' if item.nil?
381
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
382
+ cells << item.to_s.send(justifier, widths[i])
383
+ end
384
+ '| ' + cells.join(' | ') + ' |'
385
+ end
386
+
387
+ def build_footer(nrows, elapsed)
388
+ rows_label = nrows == 1 ? 'row' : 'rows'
389
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
390
+ end
391
+ end
392
+
310
393
  def clear_cache!
311
394
  super
312
395
  reload_type_map
@@ -410,36 +493,70 @@ module ActiveRecord
410
493
  show_variable 'collation_database'
411
494
  end
412
495
 
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
496
+ def tables(name = nil) # :nodoc:
497
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
498
+ #tables currently returns both tables and views.
499
+ This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
500
+ Use #data_sources instead.
501
+ MSG
417
502
 
418
- execute_and_free(sql, 'SCHEMA') do |result|
419
- result.collect { |field| field.first }
503
+ if name
504
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
505
+ Passing arguments to #tables is deprecated without replacement.
506
+ MSG
420
507
  end
508
+
509
+ data_sources
510
+ end
511
+
512
+ def data_sources
513
+ sql = "SELECT table_name FROM information_schema.tables "
514
+ sql << "WHERE table_schema = #{quote(@config[:database])}"
515
+
516
+ select_values(sql, 'SCHEMA')
421
517
  end
422
- alias data_sources tables
423
518
 
424
519
  def truncate(table_name, name = nil)
425
520
  execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
426
521
  end
427
522
 
428
- def table_exists?(name)
429
- return false unless name.present?
430
- return true if tables(nil, nil, name).any?
523
+ def table_exists?(table_name)
524
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
525
+ #table_exists? currently checks both tables and views.
526
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
527
+ Use #data_source_exists? instead.
528
+ MSG
529
+
530
+ data_source_exists?(table_name)
531
+ end
532
+
533
+ def data_source_exists?(table_name)
534
+ return false unless table_name.present?
431
535
 
432
- name = name.to_s
433
- schema, table = name.split('.', 2)
536
+ schema, name = table_name.to_s.split('.', 2)
537
+ schema, name = @config[:database], schema unless name # A table was provided without a schema
434
538
 
435
- unless table # A table was provided without a schema
436
- table = schema
437
- schema = nil
438
- end
539
+ sql = "SELECT table_name FROM information_schema.tables "
540
+ sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
439
541
 
440
- tables(nil, schema, table).any?
542
+ select_values(sql, 'SCHEMA').any?
543
+ end
544
+
545
+ def views # :nodoc:
546
+ select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')
547
+ end
548
+
549
+ def view_exists?(view_name) # :nodoc:
550
+ return false unless view_name.present?
551
+
552
+ schema, name = view_name.to_s.split('.', 2)
553
+ schema, name = @config[:database], schema unless name # A view was provided without a schema
554
+
555
+ sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
556
+ sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
557
+
558
+ select_values(sql, 'SCHEMA').any?
441
559
  end
442
- alias data_source_exists? table_exists?
443
560
 
444
561
  # Returns an array of indexes for the given table.
445
562
  def indexes(table_name, name = nil) #:nodoc:
@@ -470,10 +587,8 @@ module ActiveRecord
470
587
  sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
471
588
  execute_and_free(sql, 'SCHEMA') do |result|
472
589
  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])
590
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
591
+ new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
477
592
  end
478
593
  end
479
594
  end
@@ -506,8 +621,23 @@ module ActiveRecord
506
621
  rename_table_indexes(table_name, new_name)
507
622
  end
508
623
 
624
+ # Drops a table from the database.
625
+ #
626
+ # [<tt>:force</tt>]
627
+ # Set to +:cascade+ to drop dependent objects as well.
628
+ # Defaults to false.
629
+ # [<tt>:if_exists</tt>]
630
+ # Set to +true+ to only drop the table if it exists.
631
+ # Defaults to false.
632
+ # [<tt>:temporary</tt>]
633
+ # Set to +true+ to drop temporary table.
634
+ # Defaults to false.
635
+ #
636
+ # Although this command ignores most +options+ and the block if one is given,
637
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
638
+ # In that case, +options+ and the block will be used by create_table.
509
639
  def drop_table(table_name, options = {})
510
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
640
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
511
641
  end
512
642
 
513
643
  def rename_index(table_name, old_name, new_name)
@@ -520,12 +650,13 @@ module ActiveRecord
520
650
  end
521
651
  end
522
652
 
523
- def change_column_default(table_name, column_name, default) #:nodoc:
653
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
654
+ default = extract_new_default_value(default_or_changes)
524
655
  column = column_for(table_name, column_name)
525
656
  change_column table_name, column_name, column.sql_type, :default => default
526
657
  end
527
658
 
528
- def change_column_null(table_name, column_name, null, default = nil)
659
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
529
660
  column = column_for(table_name, column_name)
530
661
 
531
662
  unless null || default.nil?
@@ -545,8 +676,8 @@ module ActiveRecord
545
676
  end
546
677
 
547
678
  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}"
679
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
680
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
550
681
  end
551
682
 
552
683
  def foreign_keys(table_name)
@@ -561,7 +692,7 @@ module ActiveRecord
561
692
  AND fk.table_name = '#{table_name}'
562
693
  SQL
563
694
 
564
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
695
+ create_table_info = create_table_info(table_name)
565
696
 
566
697
  fk_info.map do |row|
567
698
  options = {
@@ -577,43 +708,37 @@ module ActiveRecord
577
708
  end
578
709
  end
579
710
 
711
+ def table_options(table_name)
712
+ create_table_info = create_table_info(table_name)
713
+
714
+ # strip create_definitions and partition_options
715
+ raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
716
+
717
+ # strip AUTO_INCREMENT
718
+ raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
719
+ end
720
+
580
721
  # 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
722
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
723
+ sql = case type.to_s
590
724
  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
725
+ integer_to_sql(limit)
599
726
  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.")
727
+ text_to_sql(limit)
728
+ when 'blob'
729
+ binary_to_sql(limit)
730
+ when 'binary'
731
+ if (0..0xfff) === limit
732
+ "varbinary(#{limit})"
733
+ else
734
+ binary_to_sql(limit)
613
735
  end
614
736
  else
615
- super
737
+ super(type, limit, precision, scale)
616
738
  end
739
+
740
+ sql << ' unsigned' if unsigned && type != :primary_key
741
+ sql
617
742
  end
618
743
 
619
744
  # SHOW VARIABLES LIKE 'name'
@@ -624,23 +749,20 @@ module ActiveRecord
624
749
  nil
625
750
  end
626
751
 
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
752
+ def primary_keys(table_name) # :nodoc:
753
+ raise ArgumentError unless table_name.present?
754
+
755
+ schema, name = table_name.to_s.split('.', 2)
756
+ schema, name = @config[:database], schema unless name # A table was provided without a schema
639
757
 
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
758
+ select_values(<<-SQL.strip_heredoc, 'SCHEMA')
759
+ SELECT column_name
760
+ FROM information_schema.key_column_usage
761
+ WHERE constraint_name = 'PRIMARY'
762
+ AND table_schema = #{quote(schema)}
763
+ AND table_name = #{quote(name)}
764
+ ORDER BY ordinal_position
765
+ SQL
644
766
  end
645
767
 
646
768
  def case_sensitive_modifier(node, table_attribute)
@@ -664,21 +786,6 @@ module ActiveRecord
664
786
  end
665
787
  end
666
788
 
667
- # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
668
- # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
669
- # distinct queries, and requires that the ORDER BY include the distinct column.
670
- # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
671
- def columns_for_distinct(columns, orders) # :nodoc:
672
- order_columns = orders.reject(&:blank?).map { |s|
673
- # Convert Arel node to string
674
- s = s.to_sql unless s.is_a?(String)
675
- # Remove any ASC/DESC modifiers
676
- s.gsub(/\s+(?:ASC|DESC)\b/i, '')
677
- }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
678
-
679
- [super, *order_columns].join(', ')
680
- end
681
-
682
789
  def strict_mode?
683
790
  self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
684
791
  end
@@ -704,6 +811,7 @@ module ActiveRecord
704
811
  m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
705
812
  m.register_type %r(^float)i, Type::Float.new(limit: 24)
706
813
  m.register_type %r(^double)i, Type::Float.new(limit: 53)
814
+ m.register_type %r(^json)i, MysqlJson.new
707
815
 
708
816
  register_integer_type m, %r(^bigint)i, limit: 8
709
817
  register_integer_type m, %r(^int)i, limit: 4
@@ -712,20 +820,20 @@ module ActiveRecord
712
820
  register_integer_type m, %r(^tinyint)i, limit: 1
713
821
 
714
822
  m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
715
- m.alias_type %r(set)i, 'varchar'
716
823
  m.alias_type %r(year)i, 'integer'
717
824
  m.alias_type %r(bit)i, 'binary'
718
825
 
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
826
  m.register_type(%r(enum)i) do |sql_type|
725
827
  limit = sql_type[/^enum\((.+)\)/i, 1]
726
828
  .split(',').map{|enum| enum.strip.length - 2}.max
727
829
  MysqlString.new(limit: limit)
728
830
  end
831
+
832
+ m.register_type(%r(^set)i) do |sql_type|
833
+ limit = sql_type[/^set\((.+)\)/i, 1]
834
+ .split(',').map{|set| set.strip.length - 1}.sum - 1
835
+ MysqlString.new(limit: limit)
836
+ end
729
837
  end
730
838
 
731
839
  def register_integer_type(mapping, key, options) # :nodoc:
@@ -738,19 +846,16 @@ module ActiveRecord
738
846
  end
739
847
  end
740
848
 
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?
849
+ def extract_precision(sql_type)
850
+ if /time/ === sql_type
851
+ super || 0
852
+ else
853
+ super
854
+ end
855
+ end
750
856
 
751
- subselect = Arel::SelectManager.new(select.engine)
752
- subselect.project Arel.sql(key.name)
753
- subselect.from subsubselect.as('__active_record_temp')
857
+ def fetch_type_metadata(sql_type, extra = "")
858
+ MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
754
859
  end
755
860
 
756
861
  def add_index_length(option_strings, column_names, options = {})
@@ -758,7 +863,7 @@ module ActiveRecord
758
863
  case length
759
864
  when Hash
760
865
  column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
761
- when Integer
866
+ when Fixnum
762
867
  column_names.each {|name| option_strings[name] += "(#{length})"}
763
868
  end
764
869
  end
@@ -781,18 +886,18 @@ module ActiveRecord
781
886
  def translate_exception(exception, message)
782
887
  case error_number(exception)
783
888
  when 1062
784
- RecordNotUnique.new(message, exception)
889
+ RecordNotUnique.new(message)
785
890
  when 1452
786
- InvalidForeignKey.new(message, exception)
891
+ InvalidForeignKey.new(message)
787
892
  else
788
893
  super
789
894
  end
790
895
  end
791
896
 
792
897
  def add_column_sql(table_name, column_name, type, options = {})
793
- td = create_table_definition table_name, options[:temporary], options[:options]
898
+ td = create_table_definition(table_name)
794
899
  cd = td.new_column_definition(column_name, type, options)
795
- schema_creation.visit_AddColumn cd
900
+ schema_creation.accept(AddColumnDefinition.new(cd))
796
901
  end
797
902
 
798
903
  def change_column_sql(table_name, column_name, type, options = {})
@@ -806,21 +911,23 @@ module ActiveRecord
806
911
  options[:null] = column.null
807
912
  end
808
913
 
809
- options[:name] = column.name
810
- schema_creation.accept ChangeColumnDefinition.new column, type, options
914
+ td = create_table_definition(table_name)
915
+ cd = td.new_column_definition(column.name, type, options)
916
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
811
917
  end
812
918
 
813
919
  def rename_column_sql(table_name, column_name, new_column_name)
814
920
  column = column_for(table_name, column_name)
815
921
  options = {
816
- name: new_column_name,
817
922
  default: column.default,
818
923
  null: column.null,
819
- auto_increment: column.extra == "auto_increment"
924
+ auto_increment: column.auto_increment?
820
925
  }
821
926
 
822
927
  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
928
+ td = create_table_definition(table_name)
929
+ cd = td.new_column_definition(new_column_name, current_type, options)
930
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
824
931
  end
825
932
 
826
933
  def remove_column_sql(table_name, column_name, type = nil, options = {})
@@ -832,8 +939,9 @@ module ActiveRecord
832
939
  end
833
940
 
834
941
  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})"
942
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
943
+ index_algorithm[0, 0] = ", " if index_algorithm.present?
944
+ "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
837
945
  end
838
946
 
839
947
  def remove_index_sql(table_name, options = {})
@@ -851,8 +959,17 @@ module ActiveRecord
851
959
 
852
960
  private
853
961
 
854
- def version
855
- @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
962
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
963
+ # to give it some prompting in the form of a subsubquery. Ugh!
964
+ def subquery_for(key, select)
965
+ subsubselect = select.clone
966
+ subsubselect.projections = [key]
967
+
968
+ subselect = Arel::SelectManager.new(select.engine)
969
+ subselect.project Arel.sql(key.name)
970
+ # Materialized subquery by adding distinct
971
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
972
+ subselect.from subsubselect.distinct.as('__active_record_temp')
856
973
  end
857
974
 
858
975
  def mariadb?
@@ -866,24 +983,25 @@ module ActiveRecord
866
983
  def configure_connection
867
984
  variables = @config.fetch(:variables, {}).stringify_keys
868
985
 
869
- # By default, MySQL 'where id is null' selects the last inserted id.
870
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
986
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
871
987
  variables['sql_auto_is_null'] = 0
872
988
 
873
989
  # Increase timeout so the server doesn't disconnect us.
874
- wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
875
- wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
876
- variables["wait_timeout"] = wait_timeout
990
+ wait_timeout = @config[:wait_timeout]
991
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
992
+ variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
993
+
994
+ defaults = [':default', :default].to_set
877
995
 
878
996
  # 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
997
+ # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
880
998
  # If the user has provided another value for sql_mode, don't replace it.
881
- unless variables.has_key?('sql_mode')
999
+ unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict])
882
1000
  variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
883
1001
  end
884
1002
 
885
1003
  # NAMES does not have an equals sign, see
886
- # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
1004
+ # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
887
1005
  # (trailing comma because variable_assignments will always have content)
888
1006
  if @config[:encoding]
889
1007
  encoding = "NAMES #{@config[:encoding]}"
@@ -893,7 +1011,7 @@ module ActiveRecord
893
1011
 
894
1012
  # Gather up all of the SET variables...
895
1013
  variable_assignments = variables.map do |k, v|
896
- if v == ':default' || v == :default
1014
+ if defaults.include?(v)
897
1015
  "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
898
1016
  elsif !v.nil?
899
1017
  "@@SESSION.#{k} = #{quote(v)}"
@@ -914,16 +1032,57 @@ module ActiveRecord
914
1032
  end
915
1033
  end
916
1034
 
917
- class MysqlDateTime < Type::DateTime # :nodoc:
918
- private
1035
+ def create_table_info(table_name) # :nodoc:
1036
+ @create_table_info_cache = {}
1037
+ @create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
1038
+ end
1039
+
1040
+ def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
1041
+ MySQL::TableDefinition.new(name, temporary, options, as)
1042
+ end
919
1043
 
920
- def has_precision?
921
- precision || 0
1044
+ def integer_to_sql(limit) # :nodoc:
1045
+ case limit
1046
+ when 1; 'tinyint'
1047
+ when 2; 'smallint'
1048
+ when 3; 'mediumint'
1049
+ when nil, 4; 'int'
1050
+ when 5..8; 'bigint'
1051
+ when 11; 'int(11)' # backward compatibility with Rails 2.0
1052
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
1053
+ end
1054
+ end
1055
+
1056
+ def text_to_sql(limit) # :nodoc:
1057
+ case limit
1058
+ when 0..0xff; 'tinytext'
1059
+ when nil, 0x100..0xffff; 'text'
1060
+ when 0x10000..0xffffff; 'mediumtext'
1061
+ when 0x1000000..0xffffffff; 'longtext'
1062
+ else raise(ActiveRecordError, "No text type has byte length #{limit}")
1063
+ end
1064
+ end
1065
+
1066
+ def binary_to_sql(limit) # :nodoc:
1067
+ case limit
1068
+ when 0..0xff; 'tinyblob'
1069
+ when nil, 0x100..0xffff; 'blob'
1070
+ when 0x10000..0xffffff; 'mediumblob'
1071
+ when 0x1000000..0xffffffff; 'longblob'
1072
+ else raise(ActiveRecordError, "No binary type has byte length #{limit}")
1073
+ end
1074
+ end
1075
+
1076
+ class MysqlJson < Type::Internal::AbstractJson # :nodoc:
1077
+ def changed_in_place?(raw_old_value, new_value)
1078
+ # Normalization is required because MySQL JSON data format includes
1079
+ # the space between the elements.
1080
+ super(serialize(deserialize(raw_old_value)), new_value)
922
1081
  end
923
1082
  end
924
1083
 
925
1084
  class MysqlString < Type::String # :nodoc:
926
- def type_cast_for_database(value)
1085
+ def serialize(value)
927
1086
  case value
928
1087
  when true then "1"
929
1088
  when false then "0"
@@ -941,6 +1100,10 @@ module ActiveRecord
941
1100
  end
942
1101
  end
943
1102
  end
1103
+
1104
+ ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
1105
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
1106
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
944
1107
  end
945
1108
  end
946
1109
  end