activerecord 5.2.8.1 → 6.1.6.1

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 (316) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1255 -596
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +9 -8
  7. data/lib/active_record/association_relation.rb +30 -10
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +100 -41
  10. data/lib/active_record/associations/association_scope.rb +23 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +55 -48
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -6
  13. data/lib/active_record/associations/builder/association.rb +45 -22
  14. data/lib/active_record/associations/builder/belongs_to.rb +29 -59
  15. data/lib/active_record/associations/builder/collection_association.rb +8 -17
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -2
  18. data/lib/active_record/associations/builder/has_one.rb +33 -2
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -1
  20. data/lib/active_record/associations/collection_association.rb +44 -34
  21. data/lib/active_record/associations/collection_proxy.rb +25 -21
  22. data/lib/active_record/associations/foreign_association.rb +20 -0
  23. data/lib/active_record/associations/has_many_association.rb +26 -13
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -18
  25. data/lib/active_record/associations/has_one_association.rb +43 -31
  26. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  27. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  28. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  29. data/lib/active_record/associations/join_dependency.rb +91 -60
  30. data/lib/active_record/associations/preloader/association.rb +69 -43
  31. data/lib/active_record/associations/preloader/through_association.rb +49 -40
  32. data/lib/active_record/associations/preloader.rb +47 -34
  33. data/lib/active_record/associations/singular_association.rb +3 -17
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/associations.rb +137 -25
  36. data/lib/active_record/attribute_assignment.rb +17 -19
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
  38. data/lib/active_record/attribute_methods/dirty.rb +101 -40
  39. data/lib/active_record/attribute_methods/primary_key.rb +20 -25
  40. data/lib/active_record/attribute_methods/query.rb +4 -8
  41. data/lib/active_record/attribute_methods/read.rb +14 -56
  42. data/lib/active_record/attribute_methods/serialization.rb +12 -7
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  44. data/lib/active_record/attribute_methods/write.rb +18 -34
  45. data/lib/active_record/attribute_methods.rb +81 -143
  46. data/lib/active_record/attributes.rb +46 -9
  47. data/lib/active_record/autosave_association.rb +57 -42
  48. data/lib/active_record/base.rb +4 -17
  49. data/lib/active_record/callbacks.rb +158 -43
  50. data/lib/active_record/coders/yaml_column.rb +1 -2
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +272 -130
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -14
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +211 -90
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +385 -144
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +167 -69
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -99
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
  64. data/lib/active_record/connection_adapters/column.rb +30 -12
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  67. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +88 -32
  69. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  70. data/lib/active_record/connection_adapters/mysql/quoting.rb +59 -7
  71. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  72. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  73. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +18 -7
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +142 -19
  75. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
  77. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  78. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  79. data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -54
  81. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  93. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  94. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  96. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  98. data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
  99. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  100. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
  101. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  102. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  103. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
  104. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  105. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  106. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -120
  107. data/lib/active_record/connection_adapters/schema_cache.rb +159 -21
  108. data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
  109. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +146 -0
  110. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  111. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  112. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
  113. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +174 -186
  114. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  115. data/lib/active_record/connection_adapters.rb +52 -0
  116. data/lib/active_record/connection_handling.rb +293 -33
  117. data/lib/active_record/core.rb +333 -98
  118. data/lib/active_record/counter_cache.rb +8 -30
  119. data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
  120. data/lib/active_record/database_configurations/database_config.rb +80 -0
  121. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  122. data/lib/active_record/database_configurations/url_config.rb +53 -0
  123. data/lib/active_record/database_configurations.rb +273 -0
  124. data/lib/active_record/delegated_type.rb +209 -0
  125. data/lib/active_record/destroy_association_async_job.rb +36 -0
  126. data/lib/active_record/dynamic_matchers.rb +3 -4
  127. data/lib/active_record/enum.rb +108 -36
  128. data/lib/active_record/errors.rb +62 -19
  129. data/lib/active_record/explain.rb +10 -6
  130. data/lib/active_record/explain_subscriber.rb +1 -1
  131. data/lib/active_record/fixture_set/file.rb +10 -17
  132. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  133. data/lib/active_record/fixture_set/render_context.rb +17 -0
  134. data/lib/active_record/fixture_set/table_row.rb +152 -0
  135. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  136. data/lib/active_record/fixtures.rb +200 -481
  137. data/lib/active_record/gem_version.rb +3 -3
  138. data/lib/active_record/inheritance.rb +53 -24
  139. data/lib/active_record/insert_all.rb +212 -0
  140. data/lib/active_record/integration.rb +67 -17
  141. data/lib/active_record/internal_metadata.rb +28 -9
  142. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  143. data/lib/active_record/locking/optimistic.rb +37 -23
  144. data/lib/active_record/locking/pessimistic.rb +9 -5
  145. data/lib/active_record/log_subscriber.rb +35 -35
  146. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  147. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  148. data/lib/active_record/middleware/database_selector.rb +77 -0
  149. data/lib/active_record/migration/command_recorder.rb +96 -44
  150. data/lib/active_record/migration/compatibility.rb +145 -64
  151. data/lib/active_record/migration/join_table.rb +0 -1
  152. data/lib/active_record/migration.rb +206 -157
  153. data/lib/active_record/model_schema.rb +148 -22
  154. data/lib/active_record/nested_attributes.rb +4 -7
  155. data/lib/active_record/no_touching.rb +8 -1
  156. data/lib/active_record/null_relation.rb +0 -1
  157. data/lib/active_record/persistence.rb +267 -59
  158. data/lib/active_record/query_cache.rb +21 -4
  159. data/lib/active_record/querying.rb +40 -23
  160. data/lib/active_record/railtie.rb +116 -59
  161. data/lib/active_record/railties/console_sandbox.rb +2 -4
  162. data/lib/active_record/railties/controller_runtime.rb +30 -35
  163. data/lib/active_record/railties/databases.rake +411 -80
  164. data/lib/active_record/readonly_attributes.rb +4 -0
  165. data/lib/active_record/reflection.rb +109 -93
  166. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  167. data/lib/active_record/relation/batches.rb +44 -35
  168. data/lib/active_record/relation/calculations.rb +157 -90
  169. data/lib/active_record/relation/delegation.rb +35 -50
  170. data/lib/active_record/relation/finder_methods.rb +64 -39
  171. data/lib/active_record/relation/from_clause.rb +5 -1
  172. data/lib/active_record/relation/merger.rb +32 -40
  173. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  174. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  179. data/lib/active_record/relation/predicate_builder.rb +62 -45
  180. data/lib/active_record/relation/query_attribute.rb +13 -8
  181. data/lib/active_record/relation/query_methods.rb +476 -187
  182. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  183. data/lib/active_record/relation/spawn_methods.rb +9 -9
  184. data/lib/active_record/relation/where_clause.rb +115 -62
  185. data/lib/active_record/relation.rb +379 -115
  186. data/lib/active_record/result.rb +64 -38
  187. data/lib/active_record/runtime_registry.rb +2 -2
  188. data/lib/active_record/sanitization.rb +22 -41
  189. data/lib/active_record/schema.rb +2 -11
  190. data/lib/active_record/schema_dumper.rb +54 -9
  191. data/lib/active_record/schema_migration.rb +7 -9
  192. data/lib/active_record/scoping/default.rb +4 -8
  193. data/lib/active_record/scoping/named.rb +17 -24
  194. data/lib/active_record/scoping.rb +8 -9
  195. data/lib/active_record/secure_token.rb +16 -8
  196. data/lib/active_record/serialization.rb +5 -3
  197. data/lib/active_record/signed_id.rb +116 -0
  198. data/lib/active_record/statement_cache.rb +49 -6
  199. data/lib/active_record/store.rb +88 -9
  200. data/lib/active_record/suppressor.rb +2 -2
  201. data/lib/active_record/table_metadata.rb +42 -43
  202. data/lib/active_record/tasks/database_tasks.rb +277 -81
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  206. data/lib/active_record/test_databases.rb +24 -0
  207. data/lib/active_record/test_fixtures.rb +287 -0
  208. data/lib/active_record/timestamp.rb +43 -32
  209. data/lib/active_record/touch_later.rb +23 -22
  210. data/lib/active_record/transactions.rb +62 -118
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  213. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  214. data/lib/active_record/type/serialized.rb +6 -3
  215. data/lib/active_record/type/time.rb +10 -0
  216. data/lib/active_record/type/type_map.rb +0 -1
  217. data/lib/active_record/type/unsigned_integer.rb +0 -1
  218. data/lib/active_record/type.rb +10 -5
  219. data/lib/active_record/type_caster/connection.rb +15 -15
  220. data/lib/active_record/type_caster/map.rb +8 -8
  221. data/lib/active_record/validations/associated.rb +1 -2
  222. data/lib/active_record/validations/numericality.rb +35 -0
  223. data/lib/active_record/validations/uniqueness.rb +38 -30
  224. data/lib/active_record/validations.rb +4 -3
  225. data/lib/active_record.rb +13 -12
  226. data/lib/arel/alias_predication.rb +9 -0
  227. data/lib/arel/attributes/attribute.rb +41 -0
  228. data/lib/arel/collectors/bind.rb +29 -0
  229. data/lib/arel/collectors/composite.rb +39 -0
  230. data/lib/arel/collectors/plain_string.rb +20 -0
  231. data/lib/arel/collectors/sql_string.rb +27 -0
  232. data/lib/arel/collectors/substitute_binds.rb +35 -0
  233. data/lib/arel/crud.rb +42 -0
  234. data/lib/arel/delete_manager.rb +18 -0
  235. data/lib/arel/errors.rb +9 -0
  236. data/lib/arel/expressions.rb +29 -0
  237. data/lib/arel/factory_methods.rb +49 -0
  238. data/lib/arel/insert_manager.rb +49 -0
  239. data/lib/arel/math.rb +45 -0
  240. data/lib/arel/nodes/and.rb +32 -0
  241. data/lib/arel/nodes/ascending.rb +23 -0
  242. data/lib/arel/nodes/binary.rb +126 -0
  243. data/lib/arel/nodes/bind_param.rb +44 -0
  244. data/lib/arel/nodes/case.rb +55 -0
  245. data/lib/arel/nodes/casted.rb +62 -0
  246. data/lib/arel/nodes/comment.rb +29 -0
  247. data/lib/arel/nodes/count.rb +12 -0
  248. data/lib/arel/nodes/delete_statement.rb +45 -0
  249. data/lib/arel/nodes/descending.rb +23 -0
  250. data/lib/arel/nodes/equality.rb +15 -0
  251. data/lib/arel/nodes/extract.rb +24 -0
  252. data/lib/arel/nodes/false.rb +16 -0
  253. data/lib/arel/nodes/full_outer_join.rb +8 -0
  254. data/lib/arel/nodes/function.rb +44 -0
  255. data/lib/arel/nodes/grouping.rb +11 -0
  256. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  257. data/lib/arel/nodes/in.rb +15 -0
  258. data/lib/arel/nodes/infix_operation.rb +92 -0
  259. data/lib/arel/nodes/inner_join.rb +8 -0
  260. data/lib/arel/nodes/insert_statement.rb +37 -0
  261. data/lib/arel/nodes/join_source.rb +20 -0
  262. data/lib/arel/nodes/matches.rb +18 -0
  263. data/lib/arel/nodes/named_function.rb +23 -0
  264. data/lib/arel/nodes/node.rb +51 -0
  265. data/lib/arel/nodes/node_expression.rb +13 -0
  266. data/lib/arel/nodes/ordering.rb +27 -0
  267. data/lib/arel/nodes/outer_join.rb +8 -0
  268. data/lib/arel/nodes/over.rb +15 -0
  269. data/lib/arel/nodes/regexp.rb +16 -0
  270. data/lib/arel/nodes/right_outer_join.rb +8 -0
  271. data/lib/arel/nodes/select_core.rb +67 -0
  272. data/lib/arel/nodes/select_statement.rb +41 -0
  273. data/lib/arel/nodes/sql_literal.rb +19 -0
  274. data/lib/arel/nodes/string_join.rb +11 -0
  275. data/lib/arel/nodes/table_alias.rb +31 -0
  276. data/lib/arel/nodes/terminal.rb +16 -0
  277. data/lib/arel/nodes/true.rb +16 -0
  278. data/lib/arel/nodes/unary.rb +44 -0
  279. data/lib/arel/nodes/unary_operation.rb +20 -0
  280. data/lib/arel/nodes/unqualified_column.rb +22 -0
  281. data/lib/arel/nodes/update_statement.rb +41 -0
  282. data/lib/arel/nodes/values_list.rb +9 -0
  283. data/lib/arel/nodes/window.rb +126 -0
  284. data/lib/arel/nodes/with.rb +11 -0
  285. data/lib/arel/nodes.rb +70 -0
  286. data/lib/arel/order_predications.rb +13 -0
  287. data/lib/arel/predications.rb +250 -0
  288. data/lib/arel/select_manager.rb +270 -0
  289. data/lib/arel/table.rb +118 -0
  290. data/lib/arel/tree_manager.rb +72 -0
  291. data/lib/arel/update_manager.rb +34 -0
  292. data/lib/arel/visitors/dot.rb +308 -0
  293. data/lib/arel/visitors/mysql.rb +93 -0
  294. data/lib/arel/visitors/postgresql.rb +120 -0
  295. data/lib/arel/visitors/sqlite.rb +38 -0
  296. data/lib/arel/visitors/to_sql.rb +899 -0
  297. data/lib/arel/visitors/visitor.rb +45 -0
  298. data/lib/arel/visitors.rb +13 -0
  299. data/lib/arel/window_predications.rb +9 -0
  300. data/lib/arel.rb +54 -0
  301. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  302. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  303. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  304. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  305. data/lib/rails/generators/active_record/migration.rb +19 -2
  306. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  307. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  308. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  309. metadata +116 -30
  310. data/lib/active_record/attribute_decorators.rb +0 -90
  311. data/lib/active_record/collection_cache_key.rb +0 -53
  312. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  313. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  314. data/lib/active_record/define_callbacks.rb +0 -22
  315. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  316. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -1,8 +1,37 @@
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
+ if filename.include?(".dump")
13
+ Marshal.load(file)
14
+ else
15
+ if YAML.respond_to?(:unsafe_load)
16
+ YAML.unsafe_load(file)
17
+ else
18
+ YAML.load(file)
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def self.read(filename, &block)
25
+ if File.extname(filename) == ".gz"
26
+ Zlib::GzipReader.open(filename) { |gz|
27
+ yield gz.read
28
+ }
29
+ else
30
+ yield File.read(filename)
31
+ end
32
+ end
33
+ private_class_method :read
34
+
6
35
  attr_reader :version
7
36
  attr_accessor :connection
8
37
 
@@ -13,6 +42,7 @@ module ActiveRecord
13
42
  @columns_hash = {}
14
43
  @primary_keys = {}
15
44
  @data_sources = {}
45
+ @indexes = {}
16
46
  end
17
47
 
18
48
  def initialize_dup(other)
@@ -21,26 +51,37 @@ module ActiveRecord
21
51
  @columns_hash = @columns_hash.dup
22
52
  @primary_keys = @primary_keys.dup
23
53
  @data_sources = @data_sources.dup
54
+ @indexes = @indexes.dup
24
55
  end
25
56
 
26
57
  def encode_with(coder)
27
- coder["columns"] = @columns
28
- coder["columns_hash"] = @columns_hash
29
- coder["primary_keys"] = @primary_keys
30
- coder["data_sources"] = @data_sources
31
- coder["version"] = connection.migration_context.current_version
58
+ reset_version!
59
+
60
+ coder["columns"] = @columns
61
+ coder["primary_keys"] = @primary_keys
62
+ coder["data_sources"] = @data_sources
63
+ coder["indexes"] = @indexes
64
+ coder["version"] = @version
65
+ coder["database_version"] = database_version
32
66
  end
33
67
 
34
68
  def init_with(coder)
35
- @columns = coder["columns"]
36
- @columns_hash = coder["columns_hash"]
37
- @primary_keys = coder["primary_keys"]
38
- @data_sources = coder["data_sources"]
39
- @version = coder["version"]
69
+ @columns = coder["columns"]
70
+ @primary_keys = coder["primary_keys"]
71
+ @data_sources = coder["data_sources"]
72
+ @indexes = coder["indexes"] || {}
73
+ @version = coder["version"]
74
+ @database_version = coder["database_version"]
75
+
76
+ derive_columns_hash_and_deduplicate_values
40
77
  end
41
78
 
42
79
  def primary_keys(table_name)
43
- @primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
80
+ @primary_keys.fetch(table_name) do
81
+ if data_source_exists?(table_name)
82
+ @primary_keys[deep_deduplicate(table_name)] = deep_deduplicate(connection.primary_key(table_name))
83
+ end
84
+ end
44
85
  end
45
86
 
46
87
  # A cached lookup for table existence.
@@ -48,7 +89,7 @@ module ActiveRecord
48
89
  prepare_data_sources if @data_sources.empty?
49
90
  return @data_sources[name] if @data_sources.key? name
50
91
 
51
- @data_sources[name] = connection.data_source_exists?(name)
92
+ @data_sources[deep_deduplicate(name)] = connection.data_source_exists?(name)
52
93
  end
53
94
 
54
95
  # Add internal cache for table with +table_name+.
@@ -57,6 +98,7 @@ module ActiveRecord
57
98
  primary_keys(table_name)
58
99
  columns(table_name)
59
100
  columns_hash(table_name)
101
+ indexes(table_name)
60
102
  end
61
103
  end
62
104
 
@@ -66,15 +108,32 @@ module ActiveRecord
66
108
 
67
109
  # Get the columns for a table
68
110
  def columns(table_name)
69
- @columns[table_name] ||= connection.columns(table_name)
111
+ @columns.fetch(table_name) do
112
+ @columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name))
113
+ end
70
114
  end
71
115
 
72
116
  # Get the columns for a table as a hash, key is the column name
73
117
  # value is the column object.
74
118
  def columns_hash(table_name)
75
- @columns_hash[table_name] ||= Hash[columns(table_name).map { |col|
76
- [col.name, col]
77
- }]
119
+ @columns_hash.fetch(table_name) do
120
+ @columns_hash[deep_deduplicate(table_name)] = columns(table_name).index_by(&:name).freeze
121
+ end
122
+ end
123
+
124
+ # Checks whether the columns hash is already cached for a table.
125
+ def columns_hash?(table_name)
126
+ @columns_hash.key?(table_name)
127
+ end
128
+
129
+ def indexes(table_name)
130
+ @indexes.fetch(table_name) do
131
+ @indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name))
132
+ end
133
+ end
134
+
135
+ def database_version # :nodoc:
136
+ @database_version ||= connection.get_database_version
78
137
  end
79
138
 
80
139
  # Clears out internal caches
@@ -83,11 +142,13 @@ module ActiveRecord
83
142
  @columns_hash.clear
84
143
  @primary_keys.clear
85
144
  @data_sources.clear
145
+ @indexes.clear
86
146
  @version = nil
147
+ @database_version = nil
87
148
  end
88
149
 
89
150
  def size
90
- [@columns, @columns_hash, @primary_keys, @data_sources].map(&:size).inject :+
151
+ [@columns, @columns_hash, @primary_keys, @data_sources].sum(&:size)
91
152
  end
92
153
 
93
154
  # Clear out internal caches for the data source +name+.
@@ -96,23 +157,100 @@ module ActiveRecord
96
157
  @columns_hash.delete name
97
158
  @primary_keys.delete name
98
159
  @data_sources.delete name
160
+ @indexes.delete name
161
+ end
162
+
163
+ def dump_to(filename)
164
+ clear!
165
+ connection.data_sources.each { |table| add(table) }
166
+ open(filename) { |f|
167
+ if filename.include?(".dump")
168
+ f.write(Marshal.dump(self))
169
+ else
170
+ f.write(YAML.dump(self))
171
+ end
172
+ }
99
173
  end
100
174
 
101
175
  def marshal_dump
102
- # if we get current version during initialization, it happens stack over flow.
103
- @version = connection.migration_context.current_version
104
- [@version, @columns, @columns_hash, @primary_keys, @data_sources]
176
+ reset_version!
177
+
178
+ [@version, @columns, {}, @primary_keys, @data_sources, @indexes, database_version]
105
179
  end
106
180
 
107
181
  def marshal_load(array)
108
- @version, @columns, @columns_hash, @primary_keys, @data_sources = array
182
+ @version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
183
+ @indexes ||= {}
184
+
185
+ derive_columns_hash_and_deduplicate_values
109
186
  end
110
187
 
111
188
  private
189
+ def reset_version!
190
+ @version = connection.migration_context.current_version
191
+ end
192
+
193
+ def derive_columns_hash_and_deduplicate_values
194
+ @columns = deep_deduplicate(@columns)
195
+ @columns_hash = @columns.transform_values { |columns| columns.index_by(&:name) }
196
+ @primary_keys = deep_deduplicate(@primary_keys)
197
+ @data_sources = deep_deduplicate(@data_sources)
198
+ @indexes = deep_deduplicate(@indexes)
199
+ end
200
+
201
+ if RUBY_VERSION < "2.7"
202
+ def deep_deduplicate(value)
203
+ case value
204
+ when Hash
205
+ value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) }
206
+ when Array
207
+ value.map { |i| deep_deduplicate(i) }
208
+ when String
209
+ if value.tainted?
210
+ # Ruby 2.6 and 2.7 have slightly different implementations of the String#-@ method.
211
+ # In Ruby 2.6, the receiver of the String#-@ method is modified under certain
212
+ # circumstances, and this was later identified as a bug
213
+ # (https://bugs.ruby-lang.org/issues/15926) and only fixed in Ruby 2.7.
214
+ value = value.dup
215
+ end
216
+ -value
217
+ when Deduplicable
218
+ -value
219
+ else
220
+ value
221
+ end
222
+ end
223
+ else
224
+ def deep_deduplicate(value)
225
+ case value
226
+ when Hash
227
+ value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) }
228
+ when Array
229
+ value.map { |i| deep_deduplicate(i) }
230
+ when String, Deduplicable
231
+ -value
232
+ else
233
+ value
234
+ end
235
+ end
236
+ end
112
237
 
113
238
  def prepare_data_sources
114
239
  connection.data_sources.each { |source| @data_sources[source] = true }
115
240
  end
241
+
242
+ def open(filename)
243
+ File.atomic_write(filename) do |file|
244
+ if File.extname(filename) == ".gz"
245
+ zipper = Zlib::GzipWriter.new file
246
+ yield zipper
247
+ zipper.flush
248
+ zipper.close
249
+ else
250
+ yield file
251
+ end
252
+ end
253
+ end
116
254
  end
117
255
  end
118
256
  end
@@ -4,6 +4,8 @@ module ActiveRecord
4
4
  # :stopdoc:
5
5
  module ConnectionAdapters
6
6
  class SqlTypeMetadata
7
+ include Deduplicable
8
+
7
9
  attr_reader :sql_type, :type, :limit, :precision, :scale
8
10
 
9
11
  def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil)
@@ -16,18 +18,27 @@ module ActiveRecord
16
18
 
17
19
  def ==(other)
18
20
  other.is_a?(SqlTypeMetadata) &&
19
- attributes_for_hash == other.attributes_for_hash
21
+ sql_type == other.sql_type &&
22
+ type == other.type &&
23
+ limit == other.limit &&
24
+ precision == other.precision &&
25
+ scale == other.scale
20
26
  end
21
27
  alias eql? ==
22
28
 
23
29
  def hash
24
- attributes_for_hash.hash
30
+ SqlTypeMetadata.hash ^
31
+ sql_type.hash ^
32
+ type.hash ^
33
+ limit.hash ^
34
+ precision.hash >> 1 ^
35
+ scale.hash >> 2
25
36
  end
26
37
 
27
- protected
28
-
29
- def attributes_for_hash
30
- [self.class, sql_type, type, limit, precision, scale]
38
+ private
39
+ def deduplicated
40
+ @sql_type = -sql_type
41
+ super
31
42
  end
32
43
  end
33
44
  end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module SQLite3
6
+ module DatabaseStatements
7
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
8
+ :pragma
9
+ ) # :nodoc:
10
+ private_constant :READ_QUERY
11
+
12
+ def write_query?(sql) # :nodoc:
13
+ !READ_QUERY.match?(sql)
14
+ rescue ArgumentError # Invalid encoding
15
+ !READ_QUERY.match?(sql.b)
16
+ end
17
+
18
+ def explain(arel, binds = [])
19
+ sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
20
+ SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
21
+ end
22
+
23
+ def execute(sql, name = nil) #:nodoc:
24
+ if preventing_writes? && write_query?(sql)
25
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
26
+ end
27
+
28
+ materialize_transactions
29
+ mark_transaction_written_if_write(sql)
30
+
31
+ log(sql, name) do
32
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
33
+ @connection.execute(sql)
34
+ end
35
+ end
36
+ end
37
+
38
+ def exec_query(sql, name = nil, binds = [], prepare: false)
39
+ if preventing_writes? && write_query?(sql)
40
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
41
+ end
42
+
43
+ materialize_transactions
44
+ mark_transaction_written_if_write(sql)
45
+
46
+ type_casted_binds = type_casted_binds(binds)
47
+
48
+ log(sql, name, binds, type_casted_binds) do
49
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
50
+ # Don't cache statements if they are not prepared
51
+ unless prepare
52
+ stmt = @connection.prepare(sql)
53
+ begin
54
+ cols = stmt.columns
55
+ unless without_prepared_statement?(binds)
56
+ stmt.bind_params(type_casted_binds)
57
+ end
58
+ records = stmt.to_a
59
+ ensure
60
+ stmt.close
61
+ end
62
+ else
63
+ stmt = @statements[sql] ||= @connection.prepare(sql)
64
+ cols = stmt.columns
65
+ stmt.reset!
66
+ stmt.bind_params(type_casted_binds)
67
+ records = stmt.to_a
68
+ end
69
+
70
+ build_result(columns: cols, rows: records)
71
+ end
72
+ end
73
+ end
74
+
75
+ def exec_delete(sql, name = "SQL", binds = [])
76
+ exec_query(sql, name, binds)
77
+ @connection.changes
78
+ end
79
+ alias :exec_update :exec_delete
80
+
81
+ def begin_isolated_db_transaction(isolation) #:nodoc
82
+ raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
83
+ raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
84
+
85
+ Thread.current.thread_variable_set("read_uncommitted", @connection.get_first_value("PRAGMA read_uncommitted"))
86
+ @connection.read_uncommitted = true
87
+ begin_db_transaction
88
+ end
89
+
90
+ def begin_db_transaction #:nodoc:
91
+ log("begin transaction", "TRANSACTION") { @connection.transaction }
92
+ end
93
+
94
+ def commit_db_transaction #:nodoc:
95
+ log("commit transaction", "TRANSACTION") { @connection.commit }
96
+ reset_read_uncommitted
97
+ end
98
+
99
+ def exec_rollback_db_transaction #:nodoc:
100
+ log("rollback transaction", "TRANSACTION") { @connection.rollback }
101
+ reset_read_uncommitted
102
+ end
103
+
104
+ private
105
+ def reset_read_uncommitted
106
+ read_uncommitted = Thread.current.thread_variable_get("read_uncommitted")
107
+ return unless read_uncommitted
108
+
109
+ @connection.read_uncommitted = read_uncommitted
110
+ end
111
+
112
+ def execute_batch(statements, name = nil)
113
+ sql = combine_multi_statements(statements)
114
+
115
+ if preventing_writes? && write_query?(sql)
116
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
117
+ end
118
+
119
+ materialize_transactions
120
+ mark_transaction_written_if_write(sql)
121
+
122
+ log(sql, name) do
123
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
124
+ @connection.execute_batch2(sql)
125
+ end
126
+ end
127
+ end
128
+
129
+ def last_inserted_id(result)
130
+ @connection.last_insert_row_id
131
+ end
132
+
133
+ def build_fixture_statements(fixture_set)
134
+ fixture_set.flat_map do |table_name, fixtures|
135
+ next if fixtures.empty?
136
+ fixtures.map { |fixture| build_fixture_sql([fixture], table_name) }
137
+ end.compact
138
+ end
139
+
140
+ def build_truncate_statement(table_name)
141
+ "DELETE FROM #{quote_table_name(table_name)}"
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -13,11 +13,11 @@ module ActiveRecord
13
13
  end
14
14
 
15
15
  def quote_table_name(name)
16
- @quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
16
+ self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
17
17
  end
18
18
 
19
19
  def quote_column_name(name)
20
- @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze
20
+ self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
21
21
  end
22
22
 
23
23
  def quoted_time(value)
@@ -30,23 +30,58 @@ module ActiveRecord
30
30
  end
31
31
 
32
32
  def quoted_true
33
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "1".freeze : "'t'".freeze
33
+ "1"
34
34
  end
35
35
 
36
36
  def unquoted_true
37
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 1 : "t".freeze
37
+ 1
38
38
  end
39
39
 
40
40
  def quoted_false
41
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "0".freeze : "'f'".freeze
41
+ "0"
42
42
  end
43
43
 
44
44
  def unquoted_false
45
- ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 0 : "f".freeze
45
+ 0
46
46
  end
47
47
 
48
- private
48
+ def column_name_matcher
49
+ COLUMN_NAME
50
+ end
51
+
52
+ def column_name_with_order_matcher
53
+ COLUMN_NAME_WITH_ORDER
54
+ end
49
55
 
56
+ COLUMN_NAME = /
57
+ \A
58
+ (
59
+ (?:
60
+ # "table_name"."column_name" | function(one or no argument)
61
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
62
+ )
63
+ (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
64
+ )
65
+ (?:\s*,\s*\g<1>)*
66
+ \z
67
+ /ix
68
+
69
+ COLUMN_NAME_WITH_ORDER = /
70
+ \A
71
+ (
72
+ (?:
73
+ # "table_name"."column_name" | function(one or no argument)
74
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
75
+ )
76
+ (?:\s+ASC|\s+DESC)?
77
+ )
78
+ (?:\s*,\s*\g<1>)*
79
+ \z
80
+ /ix
81
+
82
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
83
+
84
+ private
50
85
  def _type_cast(value)
51
86
  case value
52
87
  when BigDecimal
@@ -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,9 +9,9 @@ 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
- index_sql = query_value(<<-SQL, "SCHEMA")
14
+ index_sql = query_value(<<~SQL, "SCHEMA")
15
15
  SELECT sql
16
16
  FROM sqlite_master
17
17
  WHERE name = #{quote(row['name'])} AND type = 'index'
@@ -21,19 +21,24 @@ module ActiveRecord
21
21
  WHERE name = #{quote(row['name'])} AND type = 'index'
22
22
  SQL
23
23
 
24
- /\sWHERE\s+(?<where>.+)$/i =~ index_sql
24
+ /\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?\z/i =~ index_sql
25
25
 
26
26
  columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
27
27
  col["name"]
28
28
  end
29
29
 
30
- # Add info on sort order for columns (only desc order is explicitly specified, asc is
31
- # the default)
32
30
  orders = {}
33
- if index_sql # index_sql can be null in case of primary key indexes
34
- index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column|
35
- orders[order_column] = :desc
36
- }
31
+
32
+ if columns.any?(&:nil?) # index created with an expression
33
+ columns = expressions
34
+ else
35
+ # Add info on sort order for columns (only desc order is explicitly specified,
36
+ # asc is the default)
37
+ if index_sql # index_sql can be null in case of primary key indexes
38
+ index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column|
39
+ orders[order_column] = :desc
40
+ }
41
+ end
37
42
  end
38
43
 
39
44
  IndexDefinition.new(
@@ -47,6 +52,61 @@ module ActiveRecord
47
52
  end.compact
48
53
  end
49
54
 
55
+ def add_foreign_key(from_table, to_table, **options)
56
+ alter_table(from_table) do |definition|
57
+ to_table = strip_table_name_prefix_and_suffix(to_table)
58
+ definition.foreign_key(to_table, **options)
59
+ end
60
+ end
61
+
62
+ def remove_foreign_key(from_table, to_table = nil, **options)
63
+ to_table ||= options[:to_table]
64
+ options = options.except(:name, :to_table, :validate)
65
+ foreign_keys = foreign_keys(from_table)
66
+
67
+ fkey = foreign_keys.detect do |fk|
68
+ table = to_table || begin
69
+ table = options[:column].to_s.delete_suffix("_id")
70
+ Base.pluralize_table_names ? table.pluralize : table
71
+ end
72
+ table = strip_table_name_prefix_and_suffix(table)
73
+ fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
74
+ fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
75
+ end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
76
+
77
+ foreign_keys.delete(fkey)
78
+ alter_table(from_table, foreign_keys)
79
+ end
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
+
50
110
  def create_schema_dumper(options)
51
111
  SQLite3::SchemaDumper.create(self, options)
52
112
  end
@@ -56,8 +116,12 @@ module ActiveRecord
56
116
  SQLite3::SchemaCreation.new(self)
57
117
  end
58
118
 
59
- def create_table_definition(*args)
60
- SQLite3::TableDefinition.new(*args)
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
61
125
  end
62
126
 
63
127
  def new_column_from_field(table_name, field)
@@ -74,14 +138,14 @@ module ActiveRecord
74
138
  end
75
139
 
76
140
  type_metadata = fetch_type_metadata(field["type"])
77
- Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, table_name, nil, field["collation"])
141
+ Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, collation: field["collation"])
78
142
  end
79
143
 
80
144
  def data_source_sql(name = nil, type: nil)
81
145
  scope = quoted_scope(name, type: type)
82
146
  scope[:type] ||= "'table','view'"
83
147
 
84
- sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'".dup
148
+ sql = +"SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
85
149
  sql << " AND name = #{scope[:name]}" if scope[:name]
86
150
  sql << " AND type IN (#{scope[:type]})"
87
151
  sql