activerecord 6.0.3.4 → 6.1.2

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

Potentially problematic release.


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

Files changed (245) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +891 -695
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record.rb +7 -14
  6. data/lib/active_record/aggregations.rb +5 -5
  7. data/lib/active_record/association_relation.rb +30 -12
  8. data/lib/active_record/associations.rb +118 -11
  9. data/lib/active_record/associations/alias_tracker.rb +19 -15
  10. data/lib/active_record/associations/association.rb +44 -28
  11. data/lib/active_record/associations/association_scope.rb +19 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +22 -8
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
  14. data/lib/active_record/associations/builder/association.rb +32 -5
  15. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  16. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  18. data/lib/active_record/associations/builder/has_many.rb +6 -2
  19. data/lib/active_record/associations/builder/has_one.rb +11 -14
  20. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  21. data/lib/active_record/associations/collection_association.rb +19 -6
  22. data/lib/active_record/associations/collection_proxy.rb +13 -5
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -2
  25. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  26. data/lib/active_record/associations/has_one_association.rb +15 -1
  27. data/lib/active_record/associations/join_dependency.rb +72 -50
  28. data/lib/active_record/associations/join_dependency/join_association.rb +39 -16
  29. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  30. data/lib/active_record/associations/preloader.rb +11 -5
  31. data/lib/active_record/associations/preloader/association.rb +51 -25
  32. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  33. data/lib/active_record/associations/singular_association.rb +1 -1
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/attribute_assignment.rb +10 -8
  36. data/lib/active_record/attribute_methods.rb +64 -54
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  38. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  39. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  40. data/lib/active_record/attribute_methods/query.rb +3 -6
  41. data/lib/active_record/attribute_methods/read.rb +8 -11
  42. data/lib/active_record/attribute_methods/serialization.rb +11 -5
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  44. data/lib/active_record/attribute_methods/write.rb +12 -20
  45. data/lib/active_record/attributes.rb +33 -8
  46. data/lib/active_record/autosave_association.rb +57 -40
  47. data/lib/active_record/base.rb +2 -14
  48. data/lib/active_record/callbacks.rb +152 -22
  49. data/lib/active_record/coders/yaml_column.rb +1 -1
  50. data/lib/active_record/connection_adapters.rb +50 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +191 -134
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +116 -27
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +228 -83
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +80 -32
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +54 -72
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -96
  64. data/lib/active_record/connection_adapters/column.rb +15 -1
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -25
  68. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  70. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
  71. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  72. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
  73. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +11 -7
  74. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  76. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  77. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  79. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +13 -54
  80. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  82. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  83. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  90. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  91. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  93. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  94. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -58
  96. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  97. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +31 -6
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  101. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +37 -4
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +49 -50
  103. data/lib/active_record/connection_handling.rb +218 -71
  104. data/lib/active_record/core.rb +245 -61
  105. data/lib/active_record/database_configurations.rb +124 -85
  106. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  107. data/lib/active_record/database_configurations/database_config.rb +52 -9
  108. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  109. data/lib/active_record/database_configurations/url_config.rb +15 -40
  110. data/lib/active_record/delegated_type.rb +209 -0
  111. data/lib/active_record/destroy_association_async_job.rb +36 -0
  112. data/lib/active_record/enum.rb +82 -38
  113. data/lib/active_record/errors.rb +47 -12
  114. data/lib/active_record/explain.rb +9 -4
  115. data/lib/active_record/explain_subscriber.rb +1 -1
  116. data/lib/active_record/fixture_set/file.rb +10 -17
  117. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  118. data/lib/active_record/fixture_set/render_context.rb +1 -1
  119. data/lib/active_record/fixture_set/table_row.rb +2 -2
  120. data/lib/active_record/fixtures.rb +58 -9
  121. data/lib/active_record/gem_version.rb +3 -3
  122. data/lib/active_record/inheritance.rb +40 -18
  123. data/lib/active_record/insert_all.rb +35 -6
  124. data/lib/active_record/integration.rb +3 -5
  125. data/lib/active_record/internal_metadata.rb +16 -7
  126. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  127. data/lib/active_record/locking/optimistic.rb +33 -17
  128. data/lib/active_record/locking/pessimistic.rb +6 -2
  129. data/lib/active_record/log_subscriber.rb +27 -8
  130. data/lib/active_record/middleware/database_selector.rb +4 -1
  131. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  132. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  133. data/lib/active_record/migration.rb +113 -83
  134. data/lib/active_record/migration/command_recorder.rb +47 -27
  135. data/lib/active_record/migration/compatibility.rb +68 -17
  136. data/lib/active_record/model_schema.rb +117 -13
  137. data/lib/active_record/nested_attributes.rb +2 -3
  138. data/lib/active_record/no_touching.rb +1 -1
  139. data/lib/active_record/persistence.rb +50 -45
  140. data/lib/active_record/query_cache.rb +15 -5
  141. data/lib/active_record/querying.rb +11 -6
  142. data/lib/active_record/railtie.rb +64 -44
  143. data/lib/active_record/railties/console_sandbox.rb +2 -4
  144. data/lib/active_record/railties/databases.rake +276 -99
  145. data/lib/active_record/readonly_attributes.rb +4 -0
  146. data/lib/active_record/reflection.rb +71 -57
  147. data/lib/active_record/relation.rb +96 -67
  148. data/lib/active_record/relation/batches.rb +38 -31
  149. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  150. data/lib/active_record/relation/calculations.rb +101 -44
  151. data/lib/active_record/relation/delegation.rb +2 -1
  152. data/lib/active_record/relation/finder_methods.rb +45 -15
  153. data/lib/active_record/relation/from_clause.rb +1 -1
  154. data/lib/active_record/relation/merger.rb +27 -25
  155. data/lib/active_record/relation/predicate_builder.rb +59 -38
  156. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  157. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  158. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  159. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  160. data/lib/active_record/relation/query_methods.rb +333 -195
  161. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  162. data/lib/active_record/relation/spawn_methods.rb +8 -7
  163. data/lib/active_record/relation/where_clause.rb +104 -57
  164. data/lib/active_record/result.rb +41 -33
  165. data/lib/active_record/runtime_registry.rb +2 -2
  166. data/lib/active_record/sanitization.rb +6 -17
  167. data/lib/active_record/schema_dumper.rb +34 -4
  168. data/lib/active_record/schema_migration.rb +2 -8
  169. data/lib/active_record/scoping/named.rb +6 -17
  170. data/lib/active_record/secure_token.rb +16 -8
  171. data/lib/active_record/serialization.rb +5 -3
  172. data/lib/active_record/signed_id.rb +116 -0
  173. data/lib/active_record/statement_cache.rb +20 -4
  174. data/lib/active_record/store.rb +2 -2
  175. data/lib/active_record/suppressor.rb +2 -2
  176. data/lib/active_record/table_metadata.rb +42 -51
  177. data/lib/active_record/tasks/database_tasks.rb +140 -113
  178. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  179. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  180. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  181. data/lib/active_record/test_databases.rb +5 -4
  182. data/lib/active_record/test_fixtures.rb +37 -16
  183. data/lib/active_record/timestamp.rb +4 -6
  184. data/lib/active_record/touch_later.rb +21 -21
  185. data/lib/active_record/transactions.rb +19 -66
  186. data/lib/active_record/type.rb +8 -1
  187. data/lib/active_record/type/serialized.rb +6 -2
  188. data/lib/active_record/type/time.rb +10 -0
  189. data/lib/active_record/type_caster/connection.rb +0 -1
  190. data/lib/active_record/type_caster/map.rb +8 -5
  191. data/lib/active_record/validations.rb +1 -0
  192. data/lib/active_record/validations/numericality.rb +35 -0
  193. data/lib/active_record/validations/uniqueness.rb +24 -4
  194. data/lib/arel.rb +5 -13
  195. data/lib/arel/attributes/attribute.rb +4 -0
  196. data/lib/arel/collectors/bind.rb +5 -0
  197. data/lib/arel/collectors/composite.rb +8 -0
  198. data/lib/arel/collectors/sql_string.rb +7 -0
  199. data/lib/arel/collectors/substitute_binds.rb +7 -0
  200. data/lib/arel/nodes.rb +3 -1
  201. data/lib/arel/nodes/binary.rb +82 -8
  202. data/lib/arel/nodes/bind_param.rb +8 -0
  203. data/lib/arel/nodes/casted.rb +21 -9
  204. data/lib/arel/nodes/equality.rb +6 -9
  205. data/lib/arel/nodes/grouping.rb +3 -0
  206. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  207. data/lib/arel/nodes/in.rb +8 -1
  208. data/lib/arel/nodes/infix_operation.rb +13 -1
  209. data/lib/arel/nodes/join_source.rb +1 -1
  210. data/lib/arel/nodes/node.rb +7 -6
  211. data/lib/arel/nodes/ordering.rb +27 -0
  212. data/lib/arel/nodes/sql_literal.rb +3 -0
  213. data/lib/arel/nodes/table_alias.rb +7 -3
  214. data/lib/arel/nodes/unary.rb +0 -1
  215. data/lib/arel/predications.rb +12 -18
  216. data/lib/arel/select_manager.rb +1 -2
  217. data/lib/arel/table.rb +13 -5
  218. data/lib/arel/visitors.rb +0 -7
  219. data/lib/arel/visitors/dot.rb +14 -2
  220. data/lib/arel/visitors/mysql.rb +11 -1
  221. data/lib/arel/visitors/postgresql.rb +15 -4
  222. data/lib/arel/visitors/to_sql.rb +89 -78
  223. data/lib/rails/generators/active_record/migration.rb +6 -1
  224. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  225. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  226. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  227. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  228. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  229. metadata +25 -26
  230. data/lib/active_record/advisory_lock_base.rb +0 -18
  231. data/lib/active_record/attribute_decorators.rb +0 -88
  232. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  233. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  234. data/lib/active_record/define_callbacks.rb +0 -22
  235. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  236. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  237. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  238. data/lib/arel/attributes.rb +0 -22
  239. data/lib/arel/visitors/depth_first.rb +0 -203
  240. data/lib/arel/visitors/ibm_db.rb +0 -34
  241. data/lib/arel/visitors/informix.rb +0 -62
  242. data/lib/arel/visitors/mssql.rb +0 -156
  243. data/lib/arel/visitors/oracle.rb +0 -158
  244. data/lib/arel/visitors/oracle12.rb +0 -65
  245. data/lib/arel/visitors/where_sql.rb +0 -22
@@ -1,8 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/file/atomic"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  class SchemaCache
8
+ def self.load_from(filename)
9
+ return unless File.file?(filename)
10
+
11
+ read(filename) do |file|
12
+ filename.include?(".dump") ? Marshal.load(file) : YAML.load(file)
13
+ end
14
+ end
15
+
16
+ def self.read(filename, &block)
17
+ if File.extname(filename) == ".gz"
18
+ Zlib::GzipReader.open(filename) { |gz|
19
+ yield gz.read
20
+ }
21
+ else
22
+ yield File.read(filename)
23
+ end
24
+ end
25
+ private_class_method :read
26
+
6
27
  attr_reader :version
7
28
  attr_accessor :connection
8
29
 
@@ -26,27 +47,33 @@ module ActiveRecord
26
47
  end
27
48
 
28
49
  def encode_with(coder)
50
+ reset_version!
51
+
29
52
  coder["columns"] = @columns
30
- coder["columns_hash"] = @columns_hash
31
53
  coder["primary_keys"] = @primary_keys
32
54
  coder["data_sources"] = @data_sources
33
55
  coder["indexes"] = @indexes
34
- coder["version"] = connection.migration_context.current_version
56
+ coder["version"] = @version
35
57
  coder["database_version"] = database_version
36
58
  end
37
59
 
38
60
  def init_with(coder)
39
61
  @columns = coder["columns"]
40
- @columns_hash = coder["columns_hash"]
41
62
  @primary_keys = coder["primary_keys"]
42
63
  @data_sources = coder["data_sources"]
43
64
  @indexes = coder["indexes"] || {}
44
65
  @version = coder["version"]
45
66
  @database_version = coder["database_version"]
67
+
68
+ derive_columns_hash_and_deduplicate_values
46
69
  end
47
70
 
48
71
  def primary_keys(table_name)
49
- @primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
72
+ @primary_keys.fetch(table_name) do
73
+ if data_source_exists?(table_name)
74
+ @primary_keys[deep_deduplicate(table_name)] = deep_deduplicate(connection.primary_key(table_name))
75
+ end
76
+ end
50
77
  end
51
78
 
52
79
  # A cached lookup for table existence.
@@ -54,7 +81,7 @@ module ActiveRecord
54
81
  prepare_data_sources if @data_sources.empty?
55
82
  return @data_sources[name] if @data_sources.key? name
56
83
 
57
- @data_sources[name] = connection.data_source_exists?(name)
84
+ @data_sources[deep_deduplicate(name)] = connection.data_source_exists?(name)
58
85
  end
59
86
 
60
87
  # Add internal cache for table with +table_name+.
@@ -73,15 +100,17 @@ module ActiveRecord
73
100
 
74
101
  # Get the columns for a table
75
102
  def columns(table_name)
76
- @columns[table_name] ||= connection.columns(table_name)
103
+ @columns.fetch(table_name) do
104
+ @columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name))
105
+ end
77
106
  end
78
107
 
79
108
  # Get the columns for a table as a hash, key is the column name
80
109
  # value is the column object.
81
110
  def columns_hash(table_name)
82
- @columns_hash[table_name] ||= Hash[columns(table_name).map { |col|
83
- [col.name, col]
84
- }]
111
+ @columns_hash.fetch(table_name) do
112
+ @columns_hash[deep_deduplicate(table_name)] = columns(table_name).index_by(&:name).freeze
113
+ end
85
114
  end
86
115
 
87
116
  # Checks whether the columns hash is already cached for a table.
@@ -90,7 +119,9 @@ module ActiveRecord
90
119
  end
91
120
 
92
121
  def indexes(table_name)
93
- @indexes[table_name] ||= connection.indexes(table_name)
122
+ @indexes.fetch(table_name) do
123
+ @indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name))
124
+ end
94
125
  end
95
126
 
96
127
  def database_version # :nodoc:
@@ -121,21 +152,73 @@ module ActiveRecord
121
152
  @indexes.delete name
122
153
  end
123
154
 
155
+ def dump_to(filename)
156
+ clear!
157
+ connection.data_sources.each { |table| add(table) }
158
+ open(filename) { |f|
159
+ if filename.include?(".dump")
160
+ f.write(Marshal.dump(self))
161
+ else
162
+ f.write(YAML.dump(self))
163
+ end
164
+ }
165
+ end
166
+
124
167
  def marshal_dump
125
- # if we get current version during initialization, it happens stack over flow.
126
- @version = connection.migration_context.current_version
127
- [@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, database_version]
168
+ reset_version!
169
+
170
+ [@version, @columns, {}, @primary_keys, @data_sources, @indexes, database_version]
128
171
  end
129
172
 
130
173
  def marshal_load(array)
131
- @version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
132
- @indexes = @indexes || {}
174
+ @version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
175
+ @indexes ||= {}
176
+
177
+ derive_columns_hash_and_deduplicate_values
133
178
  end
134
179
 
135
180
  private
181
+ def reset_version!
182
+ @version = connection.migration_context.current_version
183
+ end
184
+
185
+ def derive_columns_hash_and_deduplicate_values
186
+ @columns = deep_deduplicate(@columns)
187
+ @columns_hash = @columns.transform_values { |columns| columns.index_by(&:name) }
188
+ @primary_keys = deep_deduplicate(@primary_keys)
189
+ @data_sources = deep_deduplicate(@data_sources)
190
+ @indexes = deep_deduplicate(@indexes)
191
+ end
192
+
193
+ def deep_deduplicate(value)
194
+ case value
195
+ when Hash
196
+ value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) }
197
+ when Array
198
+ value.map { |i| deep_deduplicate(i) }
199
+ when String, Deduplicable
200
+ -value
201
+ else
202
+ value
203
+ end
204
+ end
205
+
136
206
  def prepare_data_sources
137
207
  connection.data_sources.each { |source| @data_sources[source] = true }
138
208
  end
209
+
210
+ def open(filename)
211
+ File.atomic_write(filename) do |file|
212
+ if File.extname(filename) == ".gz"
213
+ zipper = Zlib::GzipWriter.new file
214
+ yield zipper
215
+ zipper.flush
216
+ zipper.close
217
+ else
218
+ yield file
219
+ end
220
+ end
221
+ end
139
222
  end
140
223
  end
141
224
  end
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_record/connection_adapters/deduplicable"
4
+
3
5
  module ActiveRecord
4
6
  # :stopdoc:
5
7
  module ConnectionAdapters
6
8
  class SqlTypeMetadata
9
+ include Deduplicable
10
+
7
11
  attr_reader :sql_type, :type, :limit, :precision, :scale
8
12
 
9
13
  def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil)
@@ -32,6 +36,12 @@ module ActiveRecord
32
36
  precision.hash >> 1 ^
33
37
  scale.hash >> 2
34
38
  end
39
+
40
+ private
41
+ def deduplicated
42
+ @sql_type = -sql_type
43
+ super
44
+ end
35
45
  end
36
46
  end
37
47
  end
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  module SQLite3
6
6
  module DatabaseStatements
7
7
  READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
8
- :begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback, :with
8
+ :pragma
9
9
  ) # :nodoc:
10
10
  private_constant :READ_QUERY
11
11
 
@@ -13,12 +13,18 @@ module ActiveRecord
13
13
  !READ_QUERY.match?(sql)
14
14
  end
15
15
 
16
+ def explain(arel, binds = [])
17
+ sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
18
+ SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
19
+ end
20
+
16
21
  def execute(sql, name = nil) #:nodoc:
17
22
  if preventing_writes? && write_query?(sql)
18
23
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
19
24
  end
20
25
 
21
26
  materialize_transactions
27
+ mark_transaction_written_if_write(sql)
22
28
 
23
29
  log(sql, name) do
24
30
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@@ -33,6 +39,7 @@ module ActiveRecord
33
39
  end
34
40
 
35
41
  materialize_transactions
42
+ mark_transaction_written_if_write(sql)
36
43
 
37
44
  type_casted_binds = type_casted_binds(binds)
38
45
 
@@ -58,7 +65,7 @@ module ActiveRecord
58
65
  records = stmt.to_a
59
66
  end
60
67
 
61
- ActiveRecord::Result.new(cols, records)
68
+ build_result(columns: cols, rows: records)
62
69
  end
63
70
  end
64
71
  end
@@ -69,20 +76,37 @@ module ActiveRecord
69
76
  end
70
77
  alias :exec_update :exec_delete
71
78
 
79
+ def begin_isolated_db_transaction(isolation) #:nodoc
80
+ raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
81
+ raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
82
+
83
+ Thread.current.thread_variable_set("read_uncommitted", @connection.get_first_value("PRAGMA read_uncommitted"))
84
+ @connection.read_uncommitted = true
85
+ begin_db_transaction
86
+ end
87
+
72
88
  def begin_db_transaction #:nodoc:
73
- log("begin transaction", nil) { @connection.transaction }
89
+ log("begin transaction", "TRANSACTION") { @connection.transaction }
74
90
  end
75
91
 
76
92
  def commit_db_transaction #:nodoc:
77
- log("commit transaction", nil) { @connection.commit }
93
+ log("commit transaction", "TRANSACTION") { @connection.commit }
94
+ reset_read_uncommitted
78
95
  end
79
96
 
80
97
  def exec_rollback_db_transaction #:nodoc:
81
- log("rollback transaction", nil) { @connection.rollback }
98
+ log("rollback transaction", "TRANSACTION") { @connection.rollback }
99
+ reset_read_uncommitted
82
100
  end
83
101
 
84
-
85
102
  private
103
+ def reset_read_uncommitted
104
+ read_uncommitted = Thread.current.thread_variable_get("read_uncommitted")
105
+ return unless read_uncommitted
106
+
107
+ @connection.read_uncommitted = read_uncommitted
108
+ end
109
+
86
110
  def execute_batch(statements, name = nil)
87
111
  sql = combine_multi_statements(statements)
88
112
 
@@ -91,6 +115,7 @@ module ActiveRecord
91
115
  end
92
116
 
93
117
  materialize_transactions
118
+ mark_transaction_written_if_write(sql)
94
119
 
95
120
  log(sql, name) do
96
121
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@@ -60,7 +60,7 @@ module ActiveRecord
60
60
  # "table_name"."column_name" | function(one or no argument)
61
61
  ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
62
62
  )
63
- (?:\s+AS\s+(?:\w+|"\w+"))?
63
+ (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
64
64
  )
65
65
  (?:\s*,\s*\g<1>)*
66
66
  \z
@@ -3,8 +3,12 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLite3
6
- class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
6
+ class SchemaCreation < SchemaCreation # :nodoc:
7
7
  private
8
+ def supports_index_using?
9
+ false
10
+ end
11
+
8
12
  def add_column_options!(sql, options)
9
13
  if options[:collation]
10
14
  sql << " COLLATE \"#{options[:collation]}\""
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
10
10
  # Indexes SQLite creates implicitly for internal use start with "sqlite_".
11
11
  # See https://www.sqlite.org/fileformat2.html#intschema
12
- next if row["name"].starts_with?("sqlite_")
12
+ next if row["name"].start_with?("sqlite_")
13
13
 
14
14
  index_sql = query_value(<<~SQL, "SCHEMA")
15
15
  SELECT sql
@@ -61,7 +61,7 @@ module ActiveRecord
61
61
 
62
62
  def remove_foreign_key(from_table, to_table = nil, **options)
63
63
  to_table ||= options[:to_table]
64
- options = options.except(:name, :to_table)
64
+ options = options.except(:name, :to_table, :validate)
65
65
  foreign_keys = foreign_keys(from_table)
66
66
 
67
67
  fkey = foreign_keys.detect do |fk|
@@ -78,6 +78,35 @@ module ActiveRecord
78
78
  alter_table(from_table, foreign_keys)
79
79
  end
80
80
 
81
+ def check_constraints(table_name)
82
+ table_sql = query_value(<<-SQL, "SCHEMA")
83
+ SELECT sql
84
+ FROM sqlite_master
85
+ WHERE name = #{quote_table_name(table_name)} AND type = 'table'
86
+ UNION ALL
87
+ SELECT sql
88
+ FROM sqlite_temp_master
89
+ WHERE name = #{quote_table_name(table_name)} AND type = 'table'
90
+ SQL
91
+
92
+ table_sql.to_s.scan(/CONSTRAINT\s+(?<name>\w+)\s+CHECK\s+\((?<expression>(:?[^()]|\(\g<expression>\))+)\)/i).map do |name, expression|
93
+ CheckConstraintDefinition.new(table_name, expression, name: name)
94
+ end
95
+ end
96
+
97
+ def add_check_constraint(table_name, expression, **options)
98
+ alter_table(table_name) do |definition|
99
+ definition.check_constraint(expression, **options)
100
+ end
101
+ end
102
+
103
+ def remove_check_constraint(table_name, expression = nil, **options)
104
+ check_constraints = check_constraints(table_name)
105
+ chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
106
+ check_constraints.delete_if { |chk| chk.name == chk_name_to_delete }
107
+ alter_table(table_name, foreign_keys(table_name), check_constraints)
108
+ end
109
+
81
110
  def create_schema_dumper(options)
82
111
  SQLite3::SchemaDumper.create(self, options)
83
112
  end
@@ -87,8 +116,12 @@ module ActiveRecord
87
116
  SQLite3::SchemaCreation.new(self)
88
117
  end
89
118
 
90
- def create_table_definition(*args, **options)
91
- SQLite3::TableDefinition.new(self, *args, **options)
119
+ def create_table_definition(name, **options)
120
+ SQLite3::TableDefinition.new(self, name, **options)
121
+ end
122
+
123
+ def validate_index_length!(table_name, new_name, internal = false)
124
+ super unless internal
92
125
  end
93
126
 
94
127
  def new_column_from_field(table_name, field)
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  # Allow database path relative to Rails.root, but only if the database
27
27
  # path is not the special path that tells sqlite to build a database only
28
28
  # in memory.
29
- if ":memory:" != config[:database]
29
+ if ":memory:" != config[:database] && !config[:database].to_s.start_with?("file:")
30
30
  config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
31
31
  dirname = File.dirname(config[:database])
32
32
  Dir.mkdir(dirname) unless File.directory?(dirname)
@@ -48,8 +48,8 @@ module ActiveRecord
48
48
  end
49
49
 
50
50
  module ConnectionAdapters #:nodoc:
51
- # The SQLite3 adapter works SQLite 3.6.16 or newer
52
- # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
51
+ # The SQLite3 adapter works with the sqlite3-ruby drivers
52
+ # (available as gem from https://rubygems.org/gems/sqlite3).
53
53
  #
54
54
  # Options:
55
55
  #
@@ -76,16 +76,6 @@ module ActiveRecord
76
76
  json: { name: "json" },
77
77
  }
78
78
 
79
- def self.represent_boolean_as_integer=(value) # :nodoc:
80
- if value == false
81
- raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."
82
- end
83
-
84
- ActiveSupport::Deprecation.warn(
85
- "`.represent_boolean_as_integer=` is now always true, so setting this is deprecated and will be removed in Rails 6.1."
86
- )
87
- end
88
-
89
79
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
90
80
  private
91
81
  def dealloc(stmt)
@@ -116,6 +106,10 @@ module ActiveRecord
116
106
  true
117
107
  end
118
108
 
109
+ def supports_transaction_isolation?
110
+ true
111
+ end
112
+
119
113
  def supports_partial_index?
120
114
  true
121
115
  end
@@ -132,6 +126,10 @@ module ActiveRecord
132
126
  true
133
127
  end
134
128
 
129
+ def supports_check_constraints?
130
+ true
131
+ end
132
+
135
133
  def supports_views?
136
134
  true
137
135
  end
@@ -175,13 +173,6 @@ module ActiveRecord
175
173
  true
176
174
  end
177
175
 
178
- # Returns 62. SQLite supports index names up to 64
179
- # characters. The rest is used by Rails internally to perform
180
- # temporary rename operations
181
- def allowed_index_name_length
182
- index_name_length - 2
183
- end
184
-
185
176
  def native_database_types #:nodoc:
186
177
  NATIVE_DATABASE_TYPES
187
178
  end
@@ -215,14 +206,6 @@ module ActiveRecord
215
206
  end
216
207
  end
217
208
 
218
- #--
219
- # DATABASE STATEMENTS ======================================
220
- #++
221
- def explain(arel, binds = [])
222
- sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
223
- SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
224
- end
225
-
226
209
  # SCHEMA STATEMENTS ========================================
227
210
 
228
211
  def primary_keys(table_name) # :nodoc:
@@ -230,8 +213,11 @@ module ActiveRecord
230
213
  pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
231
214
  end
232
215
 
233
- def remove_index(table_name, options = {}) #:nodoc:
234
- index_name = index_name_for_remove(table_name, options)
216
+ def remove_index(table_name, column_name = nil, **options) # :nodoc:
217
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
218
+
219
+ index_name = index_name_for_remove(table_name, column_name, options)
220
+
235
221
  exec_query "DROP INDEX #{quote_column_name(index_name)}"
236
222
  end
237
223
 
@@ -240,6 +226,8 @@ module ActiveRecord
240
226
  # Example:
241
227
  # rename_table('octopuses', 'octopi')
242
228
  def rename_table(table_name, new_name)
229
+ schema_cache.clear_data_source_cache!(table_name.to_s)
230
+ schema_cache.clear_data_source_cache!(new_name.to_s)
243
231
  exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
244
232
  rename_table_indexes(table_name, new_name)
245
233
  end
@@ -280,16 +268,11 @@ module ActiveRecord
280
268
  end
281
269
  end
282
270
 
283
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
271
+ def change_column(table_name, column_name, type, **options) #:nodoc:
284
272
  alter_table(table_name) do |definition|
285
273
  definition[column_name].instance_eval do
286
- self.type = type
287
- self.limit = options[:limit] if options.include?(:limit)
288
- self.default = options[:default] if options.include?(:default)
289
- self.null = options[:null] if options.include?(:null)
290
- self.precision = options[:precision] if options.include?(:precision)
291
- self.scale = options[:scale] if options.include?(:scale)
292
- self.collation = options[:collation] if options.include?(:collation)
274
+ self.type = aliased_types(type.to_s, type)
275
+ self.options.merge!(options)
293
276
  end
294
277
  end
295
278
  end
@@ -325,12 +308,17 @@ module ActiveRecord
325
308
  sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
326
309
  elsif insert.update_duplicates?
327
310
  sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
311
+ sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
328
312
  sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
329
313
  end
330
314
 
331
315
  sql
332
316
  end
333
317
 
318
+ def shared_cache? # :nodoc:
319
+ @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
320
+ end
321
+
334
322
  def get_database_version # :nodoc:
335
323
  SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
336
324
  end
@@ -367,7 +355,12 @@ module ActiveRecord
367
355
  options[:null] == false && options[:default].nil?
368
356
  end
369
357
 
370
- def alter_table(table_name, foreign_keys = foreign_keys(table_name), **options)
358
+ def alter_table(
359
+ table_name,
360
+ foreign_keys = foreign_keys(table_name),
361
+ check_constraints = check_constraints(table_name),
362
+ **options
363
+ )
371
364
  altered_table_name = "a#{table_name}"
372
365
 
373
366
  caller = lambda do |definition|
@@ -380,6 +373,10 @@ module ActiveRecord
380
373
  definition.foreign_key(to_table, **fk.options)
381
374
  end
382
375
 
376
+ check_constraints.each do |chk|
377
+ definition.check_constraint(chk.expression, **chk.options)
378
+ end
379
+
383
380
  yield definition if block_given?
384
381
  end
385
382
 
@@ -404,6 +401,7 @@ module ActiveRecord
404
401
  if from_primary_key.is_a?(Array)
405
402
  @definition.primary_keys from_primary_key
406
403
  end
404
+
407
405
  columns(from).each do |column|
408
406
  column_name = options[:rename] ?
409
407
  (options[:rename][column.name] ||
@@ -445,10 +443,10 @@ module ActiveRecord
445
443
 
446
444
  unless columns.empty?
447
445
  # index name can't be the same
448
- opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
449
- opts[:unique] = true if index.unique
450
- opts[:where] = index.where if index.where
451
- add_index(to, columns, opts)
446
+ options = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
447
+ options[:unique] = true if index.unique
448
+ options[:where] = index.where if index.where
449
+ add_index(to, columns, **options)
452
450
  end
453
451
  end
454
452
  end
@@ -467,17 +465,18 @@ module ActiveRecord
467
465
  end
468
466
 
469
467
  def translate_exception(exception, message:, sql:, binds:)
470
- case exception.message
471
468
  # SQLite 3.8.2 returns a newly formatted error message:
472
469
  # UNIQUE constraint failed: *table_name*.*column_name*
473
470
  # Older versions of SQLite return:
474
471
  # column *column_name* is not unique
475
- when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
472
+ if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
476
473
  RecordNotUnique.new(message, sql: sql, binds: binds)
477
- when /.* may not be NULL/, /NOT NULL constraint failed: .*/
474
+ elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
478
475
  NotNullViolation.new(message, sql: sql, binds: binds)
479
- when /FOREIGN KEY constraint failed/i
476
+ elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
480
477
  InvalidForeignKey.new(message, sql: sql, binds: binds)
478
+ elsif exception.message.match?(/called on a closed database/i)
479
+ ConnectionNotEstablished.new(exception)
481
480
  else
482
481
  super
483
482
  end
@@ -497,12 +496,12 @@ module ActiveRecord
497
496
  # Result will have following sample string
498
497
  # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
499
498
  # "password_digest" varchar COLLATE "NOCASE");
500
- result = exec_query(sql, "SCHEMA").first
499
+ result = query_value(sql, "SCHEMA")
501
500
 
502
501
  if result
503
502
  # Splitting with left parentheses and discarding the first part will return all
504
503
  # columns separated with comma(,).
505
- columns_string = result["sql"].split("(", 2).last
504
+ columns_string = result.split("(", 2).last
506
505
 
507
506
  columns_string.split(",").each do |column_string|
508
507
  # This regex will match the column name and collation type and will save
@@ -510,7 +509,7 @@ module ActiveRecord
510
509
  collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
511
510
  end
512
511
 
513
- basic_structure.map! do |column|
512
+ basic_structure.map do |column|
514
513
  column_name = column["name"]
515
514
 
516
515
  if collation_hash.has_key? column_name