activerecord 5.2.6 → 6.0.0

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

Potentially problematic release.


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

Files changed (268) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +609 -622
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +4 -2
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +4 -2
  7. data/lib/active_record/associations/association.rb +52 -19
  8. data/lib/active_record/associations/association_scope.rb +4 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  11. data/lib/active_record/associations/builder/association.rb +14 -18
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -52
  13. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  15. data/lib/active_record/associations/builder/has_many.rb +2 -0
  16. data/lib/active_record/associations/builder/has_one.rb +35 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  18. data/lib/active_record/associations/collection_association.rb +6 -21
  19. data/lib/active_record/associations/collection_proxy.rb +12 -15
  20. data/lib/active_record/associations/foreign_association.rb +7 -0
  21. data/lib/active_record/associations/has_many_association.rb +2 -10
  22. data/lib/active_record/associations/has_many_through_association.rb +14 -14
  23. data/lib/active_record/associations/has_one_association.rb +28 -30
  24. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  25. data/lib/active_record/associations/join_dependency/join_association.rb +9 -10
  26. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  27. data/lib/active_record/associations/join_dependency.rb +24 -28
  28. data/lib/active_record/associations/preloader/association.rb +38 -36
  29. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  30. data/lib/active_record/associations/preloader.rb +40 -32
  31. data/lib/active_record/associations/singular_association.rb +2 -16
  32. data/lib/active_record/associations.rb +19 -14
  33. data/lib/active_record/attribute_assignment.rb +7 -10
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  35. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  36. data/lib/active_record/attribute_methods/primary_key.rb +15 -22
  37. data/lib/active_record/attribute_methods/query.rb +2 -3
  38. data/lib/active_record/attribute_methods/read.rb +15 -53
  39. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  41. data/lib/active_record/attribute_methods/write.rb +17 -24
  42. data/lib/active_record/attribute_methods.rb +28 -100
  43. data/lib/active_record/attributes.rb +13 -0
  44. data/lib/active_record/autosave_association.rb +5 -9
  45. data/lib/active_record/base.rb +2 -3
  46. data/lib/active_record/callbacks.rb +5 -19
  47. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +94 -16
  48. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  49. data/lib/active_record/connection_adapters/abstract/database_statements.rb +95 -123
  50. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -8
  51. data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +19 -12
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +76 -48
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +132 -53
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +180 -47
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +128 -194
  59. data/lib/active_record/connection_adapters/column.rb +17 -13
  60. data/lib/active_record/connection_adapters/connection_specification.rb +52 -42
  61. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +73 -13
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  64. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  65. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +129 -13
  68. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -9
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +20 -1
  72. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  73. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  79. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  80. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  81. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +12 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +55 -53
  85. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +160 -74
  87. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  88. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -6
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +125 -141
  93. data/lib/active_record/connection_handling.rb +149 -27
  94. data/lib/active_record/core.rb +100 -60
  95. data/lib/active_record/counter_cache.rb +4 -29
  96. data/lib/active_record/database_configurations/database_config.rb +37 -0
  97. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  98. data/lib/active_record/database_configurations/url_config.rb +79 -0
  99. data/lib/active_record/database_configurations.rb +233 -0
  100. data/lib/active_record/dynamic_matchers.rb +1 -1
  101. data/lib/active_record/enum.rb +37 -7
  102. data/lib/active_record/errors.rb +15 -7
  103. data/lib/active_record/explain.rb +1 -1
  104. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  105. data/lib/active_record/fixture_set/render_context.rb +17 -0
  106. data/lib/active_record/fixture_set/table_row.rb +153 -0
  107. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  108. data/lib/active_record/fixtures.rb +145 -472
  109. data/lib/active_record/gem_version.rb +3 -3
  110. data/lib/active_record/inheritance.rb +13 -3
  111. data/lib/active_record/insert_all.rb +179 -0
  112. data/lib/active_record/integration.rb +68 -16
  113. data/lib/active_record/internal_metadata.rb +10 -2
  114. data/lib/active_record/locking/optimistic.rb +5 -6
  115. data/lib/active_record/locking/pessimistic.rb +3 -3
  116. data/lib/active_record/log_subscriber.rb +7 -26
  117. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  118. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  119. data/lib/active_record/middleware/database_selector.rb +75 -0
  120. data/lib/active_record/migration/command_recorder.rb +50 -6
  121. data/lib/active_record/migration/compatibility.rb +76 -49
  122. data/lib/active_record/migration.rb +100 -81
  123. data/lib/active_record/model_schema.rb +30 -9
  124. data/lib/active_record/nested_attributes.rb +2 -2
  125. data/lib/active_record/no_touching.rb +7 -0
  126. data/lib/active_record/persistence.rb +228 -24
  127. data/lib/active_record/query_cache.rb +11 -4
  128. data/lib/active_record/querying.rb +32 -20
  129. data/lib/active_record/railtie.rb +80 -43
  130. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  131. data/lib/active_record/railties/controller_runtime.rb +30 -35
  132. data/lib/active_record/railties/databases.rake +196 -46
  133. data/lib/active_record/reflection.rb +32 -30
  134. data/lib/active_record/relation/batches.rb +13 -10
  135. data/lib/active_record/relation/calculations.rb +53 -47
  136. data/lib/active_record/relation/delegation.rb +26 -43
  137. data/lib/active_record/relation/finder_methods.rb +13 -26
  138. data/lib/active_record/relation/merger.rb +11 -20
  139. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  140. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  141. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  142. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  143. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  144. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  145. data/lib/active_record/relation/predicate_builder.rb +4 -6
  146. data/lib/active_record/relation/query_attribute.rb +13 -8
  147. data/lib/active_record/relation/query_methods.rb +189 -63
  148. data/lib/active_record/relation/spawn_methods.rb +1 -1
  149. data/lib/active_record/relation/where_clause.rb +14 -10
  150. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  151. data/lib/active_record/relation.rb +310 -80
  152. data/lib/active_record/result.rb +30 -11
  153. data/lib/active_record/sanitization.rb +32 -40
  154. data/lib/active_record/schema.rb +2 -11
  155. data/lib/active_record/schema_dumper.rb +22 -7
  156. data/lib/active_record/schema_migration.rb +5 -1
  157. data/lib/active_record/scoping/default.rb +4 -5
  158. data/lib/active_record/scoping/named.rb +19 -15
  159. data/lib/active_record/scoping.rb +8 -8
  160. data/lib/active_record/statement_cache.rb +30 -3
  161. data/lib/active_record/store.rb +87 -8
  162. data/lib/active_record/table_metadata.rb +10 -17
  163. data/lib/active_record/tasks/database_tasks.rb +194 -25
  164. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -5
  165. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  166. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  167. data/lib/active_record/test_databases.rb +23 -0
  168. data/lib/active_record/test_fixtures.rb +224 -0
  169. data/lib/active_record/timestamp.rb +39 -25
  170. data/lib/active_record/touch_later.rb +4 -2
  171. data/lib/active_record/transactions.rb +57 -66
  172. data/lib/active_record/translation.rb +1 -1
  173. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  174. data/lib/active_record/type.rb +3 -4
  175. data/lib/active_record/type_caster/connection.rb +15 -14
  176. data/lib/active_record/type_caster/map.rb +1 -4
  177. data/lib/active_record/validations/uniqueness.rb +15 -27
  178. data/lib/active_record/validations.rb +1 -0
  179. data/lib/active_record.rb +9 -2
  180. data/lib/arel/alias_predication.rb +9 -0
  181. data/lib/arel/attributes/attribute.rb +37 -0
  182. data/lib/arel/attributes.rb +22 -0
  183. data/lib/arel/collectors/bind.rb +24 -0
  184. data/lib/arel/collectors/composite.rb +31 -0
  185. data/lib/arel/collectors/plain_string.rb +20 -0
  186. data/lib/arel/collectors/sql_string.rb +20 -0
  187. data/lib/arel/collectors/substitute_binds.rb +28 -0
  188. data/lib/arel/crud.rb +42 -0
  189. data/lib/arel/delete_manager.rb +18 -0
  190. data/lib/arel/errors.rb +9 -0
  191. data/lib/arel/expressions.rb +29 -0
  192. data/lib/arel/factory_methods.rb +49 -0
  193. data/lib/arel/insert_manager.rb +49 -0
  194. data/lib/arel/math.rb +45 -0
  195. data/lib/arel/nodes/and.rb +32 -0
  196. data/lib/arel/nodes/ascending.rb +23 -0
  197. data/lib/arel/nodes/binary.rb +52 -0
  198. data/lib/arel/nodes/bind_param.rb +36 -0
  199. data/lib/arel/nodes/case.rb +55 -0
  200. data/lib/arel/nodes/casted.rb +50 -0
  201. data/lib/arel/nodes/comment.rb +29 -0
  202. data/lib/arel/nodes/count.rb +12 -0
  203. data/lib/arel/nodes/delete_statement.rb +45 -0
  204. data/lib/arel/nodes/descending.rb +23 -0
  205. data/lib/arel/nodes/equality.rb +18 -0
  206. data/lib/arel/nodes/extract.rb +24 -0
  207. data/lib/arel/nodes/false.rb +16 -0
  208. data/lib/arel/nodes/full_outer_join.rb +8 -0
  209. data/lib/arel/nodes/function.rb +44 -0
  210. data/lib/arel/nodes/grouping.rb +8 -0
  211. data/lib/arel/nodes/in.rb +8 -0
  212. data/lib/arel/nodes/infix_operation.rb +80 -0
  213. data/lib/arel/nodes/inner_join.rb +8 -0
  214. data/lib/arel/nodes/insert_statement.rb +37 -0
  215. data/lib/arel/nodes/join_source.rb +20 -0
  216. data/lib/arel/nodes/matches.rb +18 -0
  217. data/lib/arel/nodes/named_function.rb +23 -0
  218. data/lib/arel/nodes/node.rb +50 -0
  219. data/lib/arel/nodes/node_expression.rb +13 -0
  220. data/lib/arel/nodes/outer_join.rb +8 -0
  221. data/lib/arel/nodes/over.rb +15 -0
  222. data/lib/arel/nodes/regexp.rb +16 -0
  223. data/lib/arel/nodes/right_outer_join.rb +8 -0
  224. data/lib/arel/nodes/select_core.rb +67 -0
  225. data/lib/arel/nodes/select_statement.rb +41 -0
  226. data/lib/arel/nodes/sql_literal.rb +16 -0
  227. data/lib/arel/nodes/string_join.rb +11 -0
  228. data/lib/arel/nodes/table_alias.rb +27 -0
  229. data/lib/arel/nodes/terminal.rb +16 -0
  230. data/lib/arel/nodes/true.rb +16 -0
  231. data/lib/arel/nodes/unary.rb +45 -0
  232. data/lib/arel/nodes/unary_operation.rb +20 -0
  233. data/lib/arel/nodes/unqualified_column.rb +22 -0
  234. data/lib/arel/nodes/update_statement.rb +41 -0
  235. data/lib/arel/nodes/values_list.rb +9 -0
  236. data/lib/arel/nodes/window.rb +126 -0
  237. data/lib/arel/nodes/with.rb +11 -0
  238. data/lib/arel/nodes.rb +68 -0
  239. data/lib/arel/order_predications.rb +13 -0
  240. data/lib/arel/predications.rb +257 -0
  241. data/lib/arel/select_manager.rb +271 -0
  242. data/lib/arel/table.rb +110 -0
  243. data/lib/arel/tree_manager.rb +72 -0
  244. data/lib/arel/update_manager.rb +34 -0
  245. data/lib/arel/visitors/depth_first.rb +204 -0
  246. data/lib/arel/visitors/dot.rb +297 -0
  247. data/lib/arel/visitors/ibm_db.rb +34 -0
  248. data/lib/arel/visitors/informix.rb +62 -0
  249. data/lib/arel/visitors/mssql.rb +157 -0
  250. data/lib/arel/visitors/mysql.rb +83 -0
  251. data/lib/arel/visitors/oracle.rb +159 -0
  252. data/lib/arel/visitors/oracle12.rb +66 -0
  253. data/lib/arel/visitors/postgresql.rb +110 -0
  254. data/lib/arel/visitors/sqlite.rb +39 -0
  255. data/lib/arel/visitors/to_sql.rb +889 -0
  256. data/lib/arel/visitors/visitor.rb +46 -0
  257. data/lib/arel/visitors/where_sql.rb +23 -0
  258. data/lib/arel/visitors.rb +20 -0
  259. data/lib/arel/window_predications.rb +9 -0
  260. data/lib/arel.rb +51 -0
  261. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  262. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  263. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  264. data/lib/rails/generators/active_record/migration.rb +14 -1
  265. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  266. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  267. metadata +108 -26
  268. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -4,17 +4,20 @@ require "active_record/connection_adapters/abstract_adapter"
4
4
  require "active_record/connection_adapters/statement_pool"
5
5
  require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
6
6
  require "active_record/connection_adapters/sqlite3/quoting"
7
+ require "active_record/connection_adapters/sqlite3/database_statements"
7
8
  require "active_record/connection_adapters/sqlite3/schema_creation"
8
9
  require "active_record/connection_adapters/sqlite3/schema_definitions"
9
10
  require "active_record/connection_adapters/sqlite3/schema_dumper"
10
11
  require "active_record/connection_adapters/sqlite3/schema_statements"
11
12
 
12
- gem "sqlite3", "~> 1.3", ">= 1.3.6"
13
+ gem "sqlite3", "~> 1.4"
13
14
  require "sqlite3"
14
15
 
15
16
  module ActiveRecord
16
17
  module ConnectionHandling # :nodoc:
17
18
  def sqlite3_connection(config)
19
+ config = config.symbolize_keys
20
+
18
21
  # Require database.
19
22
  unless config[:database]
20
23
  raise ArgumentError, "No database file specified. Missing argument: database"
@@ -31,11 +34,9 @@ module ActiveRecord
31
34
 
32
35
  db = SQLite3::Database.new(
33
36
  config[:database].to_s,
34
- results_as_hash: true
37
+ config.merge(results_as_hash: true)
35
38
  )
36
39
 
37
- db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
38
-
39
40
  ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
40
41
  rescue Errno::ENOENT => error
41
42
  if error.message.include?("No such file or directory")
@@ -54,10 +55,11 @@ module ActiveRecord
54
55
  #
55
56
  # * <tt>:database</tt> - Path to the database file.
56
57
  class SQLite3Adapter < AbstractAdapter
57
- ADAPTER_NAME = "SQLite".freeze
58
+ ADAPTER_NAME = "SQLite"
58
59
 
59
60
  include SQLite3::Quoting
60
61
  include SQLite3::SchemaStatements
62
+ include SQLite3::DatabaseStatements
61
63
 
62
64
  NATIVE_DATABASE_TYPES = {
63
65
  primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
@@ -74,39 +76,38 @@ module ActiveRecord
74
76
  json: { name: "json" },
75
77
  }
76
78
 
77
- ##
78
- # :singleton-method:
79
- # Indicates whether boolean values are stored in sqlite3 databases as 1
80
- # and 0 or 't' and 'f'. Leaving <tt>ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer</tt>
81
- # set to false is deprecated. SQLite databases have used 't' and 'f' to
82
- # serialize boolean values and must have old data converted to 1 and 0
83
- # (its native boolean serialization) before setting this flag to true.
84
- # Conversion can be accomplished by setting up a rake task which runs
85
- #
86
- # ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1)
87
- # ExampleModel.where("boolean_column = 'f'").update_all(boolean_column: 0)
88
- # for all models and all boolean columns, after which the flag must be set
89
- # to true by adding the following to your <tt>application.rb</tt> file:
90
- #
91
- # Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
92
- class_attribute :represent_boolean_as_integer, default: false
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
93
88
 
94
89
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
95
90
  private
96
91
  def dealloc(stmt)
97
- stmt[:stmt].close unless stmt[:stmt].closed?
92
+ stmt.close unless stmt.closed?
98
93
  end
99
94
  end
100
95
 
101
96
  def initialize(connection, logger, connection_options, config)
102
97
  super(connection, logger, config)
103
-
104
- @active = true
105
- @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
106
-
107
98
  configure_connection
108
99
  end
109
100
 
101
+ def self.database_exists?(config)
102
+ config = config.symbolize_keys
103
+ if config[:database] == ":memory:"
104
+ return true
105
+ else
106
+ database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
107
+ File.exist?(database_file)
108
+ end
109
+ end
110
+
110
111
  def supports_ddl_transactions?
111
112
  true
112
113
  end
@@ -116,15 +117,19 @@ module ActiveRecord
116
117
  end
117
118
 
118
119
  def supports_partial_index?
119
- sqlite_version >= "3.8.0"
120
+ true
121
+ end
122
+
123
+ def supports_expression_index?
124
+ database_version >= "3.9.0"
120
125
  end
121
126
 
122
127
  def requires_reloading?
123
128
  true
124
129
  end
125
130
 
126
- def supports_foreign_keys_in_create?
127
- sqlite_version >= "3.6.19"
131
+ def supports_foreign_keys?
132
+ true
128
133
  end
129
134
 
130
135
  def supports_views?
@@ -139,27 +144,29 @@ module ActiveRecord
139
144
  true
140
145
  end
141
146
 
142
- def supports_multi_insert?
143
- sqlite_version >= "3.7.11"
147
+ def supports_insert_on_conflict?
148
+ database_version >= "3.24.0"
144
149
  end
150
+ alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
151
+ alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
152
+ alias supports_insert_conflict_target? supports_insert_on_conflict?
145
153
 
146
154
  def active?
147
- @active
155
+ !@connection.closed?
156
+ end
157
+
158
+ def reconnect!
159
+ super
160
+ connect if @connection.closed?
148
161
  end
149
162
 
150
163
  # Disconnects from the database if already connected. Otherwise, this
151
164
  # method does nothing.
152
165
  def disconnect!
153
166
  super
154
- @active = false
155
167
  @connection.close rescue nil
156
168
  end
157
169
 
158
- # Clears the prepared statements cache.
159
- def clear_cache!
160
- @statements.clear
161
- end
162
-
163
170
  def supports_index_sort_order?
164
171
  true
165
172
  end
@@ -184,91 +191,34 @@ module ActiveRecord
184
191
  true
185
192
  end
186
193
 
194
+ def supports_lazy_transactions?
195
+ true
196
+ end
197
+
187
198
  # REFERENTIAL INTEGRITY ====================================
188
199
 
189
200
  def disable_referential_integrity # :nodoc:
190
- old = query_value("PRAGMA foreign_keys")
201
+ old_foreign_keys = query_value("PRAGMA foreign_keys")
202
+ old_defer_foreign_keys = query_value("PRAGMA defer_foreign_keys")
191
203
 
192
204
  begin
205
+ execute("PRAGMA defer_foreign_keys = ON")
193
206
  execute("PRAGMA foreign_keys = OFF")
194
207
  yield
195
208
  ensure
196
- execute("PRAGMA foreign_keys = #{old}")
209
+ execute("PRAGMA defer_foreign_keys = #{old_defer_foreign_keys}")
210
+ execute("PRAGMA foreign_keys = #{old_foreign_keys}")
197
211
  end
198
212
  end
199
213
 
200
214
  #--
201
215
  # DATABASE STATEMENTS ======================================
202
216
  #++
203
-
204
217
  def explain(arel, binds = [])
205
218
  sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
206
219
  SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
207
220
  end
208
221
 
209
- def exec_query(sql, name = nil, binds = [], prepare: false)
210
- type_casted_binds = type_casted_binds(binds)
211
-
212
- log(sql, name, binds, type_casted_binds) do
213
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
214
- # Don't cache statements if they are not prepared
215
- unless prepare
216
- stmt = @connection.prepare(sql)
217
- begin
218
- cols = stmt.columns
219
- unless without_prepared_statement?(binds)
220
- stmt.bind_params(type_casted_binds)
221
- end
222
- records = stmt.to_a
223
- ensure
224
- stmt.close
225
- end
226
- else
227
- cache = @statements[sql] ||= {
228
- stmt: @connection.prepare(sql)
229
- }
230
- stmt = cache[:stmt]
231
- cols = cache[:cols] ||= stmt.columns
232
- stmt.reset!
233
- stmt.bind_params(type_casted_binds)
234
- records = stmt.to_a
235
- end
236
-
237
- ActiveRecord::Result.new(cols, records)
238
- end
239
- end
240
- end
241
-
242
- def exec_delete(sql, name = "SQL", binds = [])
243
- exec_query(sql, name, binds)
244
- @connection.changes
245
- end
246
- alias :exec_update :exec_delete
247
-
248
- def last_inserted_id(result)
249
- @connection.last_insert_row_id
250
- end
251
-
252
- def execute(sql, name = nil) #:nodoc:
253
- log(sql, name) do
254
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
255
- @connection.execute(sql)
256
- end
257
- end
258
- end
259
-
260
- def begin_db_transaction #:nodoc:
261
- log("begin transaction", nil) { @connection.transaction }
262
- end
263
-
264
- def commit_db_transaction #:nodoc:
265
- log("commit transaction", nil) { @connection.commit }
266
- end
267
-
268
- def exec_rollback_db_transaction #:nodoc:
269
- log("rollback transaction", nil) { @connection.rollback }
270
- end
271
-
272
222
  # SCHEMA STATEMENTS ========================================
273
223
 
274
224
  def primary_keys(table_name) # :nodoc:
@@ -290,11 +240,6 @@ module ActiveRecord
290
240
  rename_table_indexes(table_name, new_name)
291
241
  end
292
242
 
293
- def valid_alter_table_type?(type, options = {})
294
- !invalid_alter_table_type?(type, options)
295
- end
296
- deprecate :valid_alter_table_type?
297
-
298
243
  def add_column(table_name, column_name, type, options = {}) #:nodoc:
299
244
  if invalid_alter_table_type?(type, options)
300
245
  alter_table(table_name) do |definition|
@@ -308,6 +253,9 @@ module ActiveRecord
308
253
  def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
309
254
  alter_table(table_name) do |definition|
310
255
  definition.remove_column column_name
256
+ definition.foreign_keys.delete_if do |_, fk_options|
257
+ fk_options[:column] == column_name.to_s
258
+ end
311
259
  end
312
260
  end
313
261
 
@@ -366,27 +314,36 @@ module ActiveRecord
366
314
  end
367
315
  end
368
316
 
369
- def insert_fixtures(rows, table_name)
370
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
371
- `insert_fixtures` is deprecated and will be removed in the next version of Rails.
372
- Consider using `insert_fixtures_set` for performance improvement.
373
- MSG
374
- insert_fixtures_set(table_name => rows)
317
+ def build_insert_sql(insert) # :nodoc:
318
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
319
+
320
+ if insert.skip_duplicates?
321
+ sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
322
+ elsif insert.update_duplicates?
323
+ sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
324
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
325
+ end
326
+
327
+ sql
375
328
  end
376
329
 
377
- def insert_fixtures_set(fixture_set, tables_to_delete = [])
378
- disable_referential_integrity do
379
- transaction(requires_new: true) do
380
- tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" }
330
+ def get_database_version # :nodoc:
331
+ SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
332
+ end
381
333
 
382
- fixture_set.each do |table_name, rows|
383
- rows.each { |row| insert_fixture(row, table_name) }
384
- end
385
- end
334
+ def check_version # :nodoc:
335
+ if database_version < "3.8.0"
336
+ raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8."
386
337
  end
387
338
  end
388
339
 
389
340
  private
341
+ # See https://www.sqlite.org/limits.html,
342
+ # the default value is 999 when not configured.
343
+ def bind_params_length
344
+ 999
345
+ end
346
+
390
347
  def initialize_type_map(m = type_map)
391
348
  super
392
349
  register_class_with_limit m, %r(int)i, SQLite3Integer
@@ -405,14 +362,27 @@ module ActiveRecord
405
362
  type.to_sym == :primary_key || options[:primary_key]
406
363
  end
407
364
 
408
- def alter_table(table_name, options = {})
365
+ def alter_table(table_name, foreign_keys = foreign_keys(table_name), **options)
409
366
  altered_table_name = "a#{table_name}"
410
- caller = lambda { |definition| yield definition if block_given? }
367
+
368
+ caller = lambda do |definition|
369
+ rename = options[:rename] || {}
370
+ foreign_keys.each do |fk|
371
+ if column = rename[fk.options[:column]]
372
+ fk.options[:column] = column
373
+ end
374
+ to_table = strip_table_name_prefix_and_suffix(fk.to_table)
375
+ definition.foreign_key(to_table, fk.options)
376
+ end
377
+
378
+ yield definition if block_given?
379
+ end
411
380
 
412
381
  transaction do
413
- move_table(table_name, altered_table_name,
414
- options.merge(temporary: true))
415
- move_table(altered_table_name, table_name, &caller)
382
+ disable_referential_integrity do
383
+ move_table(table_name, altered_table_name, options.merge(temporary: true))
384
+ move_table(altered_table_name, table_name, &caller)
385
+ end
416
386
  end
417
387
  end
418
388
 
@@ -442,6 +412,7 @@ module ActiveRecord
442
412
  primary_key: column_name == from_primary_key
443
413
  )
444
414
  end
415
+
445
416
  yield @definition if block_given?
446
417
  end
447
418
  copy_table_indexes(from, to, options[:rename] || {})
@@ -459,9 +430,12 @@ module ActiveRecord
459
430
  name = name[1..-1]
460
431
  end
461
432
 
462
- to_column_names = columns(to).map(&:name)
463
- columns = index.columns.map { |c| rename[c] || c }.select do |column|
464
- to_column_names.include?(column)
433
+ columns = index.columns
434
+ if columns.is_a?(Array)
435
+ to_column_names = columns(to).map(&:name)
436
+ columns = columns.map { |c| rename[c] || c }.select do |column|
437
+ to_column_names.include?(column)
438
+ end
465
439
  end
466
440
 
467
441
  unless columns.empty?
@@ -487,22 +461,18 @@ module ActiveRecord
487
461
  SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
488
462
  end
489
463
 
490
- def sqlite_version
491
- @sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
492
- end
493
-
494
- def translate_exception(exception, message)
464
+ def translate_exception(exception, message:, sql:, binds:)
495
465
  case exception.message
496
466
  # SQLite 3.8.2 returns a newly formatted error message:
497
467
  # UNIQUE constraint failed: *table_name*.*column_name*
498
468
  # Older versions of SQLite return:
499
469
  # column *column_name* is not unique
500
470
  when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
501
- RecordNotUnique.new(message)
471
+ RecordNotUnique.new(message, sql: sql, binds: binds)
502
472
  when /.* may not be NULL/, /NOT NULL constraint failed: .*/
503
- NotNullViolation.new(message)
473
+ NotNullViolation.new(message, sql: sql, binds: binds)
504
474
  when /FOREIGN KEY constraint failed/i
505
- InvalidForeignKey.new(message)
475
+ InvalidForeignKey.new(message, sql: sql, binds: binds)
506
476
  else
507
477
  super
508
478
  end
@@ -512,7 +482,7 @@ module ActiveRecord
512
482
 
513
483
  def table_structure_with_collation(table_name, basic_structure)
514
484
  collation_hash = {}
515
- sql = <<-SQL
485
+ sql = <<~SQL
516
486
  SELECT sql FROM
517
487
  (SELECT * FROM sqlite_master UNION ALL
518
488
  SELECT * FROM sqlite_temp_master)
@@ -545,7 +515,7 @@ module ActiveRecord
545
515
  column
546
516
  end
547
517
  else
548
- basic_structure.to_hash
518
+ basic_structure.to_a
549
519
  end
550
520
  end
551
521
 
@@ -553,7 +523,21 @@ module ActiveRecord
553
523
  Arel::Visitors::SQLite.new(self)
554
524
  end
555
525
 
526
+ def build_statement_pool
527
+ StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
528
+ end
529
+
530
+ def connect
531
+ @connection = ::SQLite3::Database.new(
532
+ @config[:database].to_s,
533
+ @config.merge(results_as_hash: true)
534
+ )
535
+ configure_connection
536
+ end
537
+
556
538
  def configure_connection
539
+ @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
540
+
557
541
  execute("PRAGMA foreign_keys = ON", "SCHEMA")
558
542
  end
559
543
 
@@ -46,41 +46,150 @@ module ActiveRecord
46
46
  #
47
47
  # The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
48
48
  # may be returned on an error.
49
- def establish_connection(config = nil)
50
- raise "Anonymous class is not allowed." unless name
49
+ def establish_connection(config_or_env = nil)
50
+ config_hash = resolve_config_for_connection(config_or_env)
51
+ connection_handler.establish_connection(config_hash)
52
+ end
53
+
54
+ # Connects a model to the databases specified. The +database+ keyword
55
+ # takes a hash consisting of a +role+ and a +database_key+.
56
+ #
57
+ # This will create a connection handler for switching between connections,
58
+ # look up the config hash using the +database_key+ and finally
59
+ # establishes a connection to that config.
60
+ #
61
+ # class AnimalsModel < ApplicationRecord
62
+ # self.abstract_class = true
63
+ #
64
+ # connects_to database: { writing: :primary, reading: :primary_replica }
65
+ # end
66
+ #
67
+ # Returns an array of established connections.
68
+ def connects_to(database: {})
69
+ connections = []
51
70
 
52
- config ||= DEFAULT_ENV.call.to_sym
53
- spec_name = self == Base ? "primary" : name
54
- self.connection_specification_name = spec_name
71
+ database.each do |role, database_key|
72
+ config_hash = resolve_config_for_connection(database_key)
73
+ handler = lookup_connection_handler(role.to_sym)
55
74
 
56
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
57
- spec = resolver.resolve(config).symbolize_keys
58
- spec[:name] = spec_name
75
+ connections << handler.establish_connection(config_hash)
76
+ end
59
77
 
60
- connection_handler.establish_connection(spec)
78
+ connections
61
79
  end
62
80
 
63
- class MergeAndResolveDefaultUrlConfig # :nodoc:
64
- def initialize(raw_configurations)
65
- @raw_config = raw_configurations.dup
66
- @env = DEFAULT_ENV.call.to_s
67
- end
81
+ # Connects to a database or role (ex writing, reading, or another
82
+ # custom role) for the duration of the block.
83
+ #
84
+ # If a role is passed, Active Record will look up the connection
85
+ # based on the requested role:
86
+ #
87
+ # ActiveRecord::Base.connected_to(role: :writing) do
88
+ # Dog.create! # creates dog using dog writing connection
89
+ # end
90
+ #
91
+ # ActiveRecord::Base.connected_to(role: :reading) do
92
+ # Dog.create! # throws exception because we're on a replica
93
+ # end
94
+ #
95
+ # ActiveRecord::Base.connected_to(role: :unknown_role) do
96
+ # # raises exception due to non-existent role
97
+ # end
98
+ #
99
+ # For cases where you may want to connect to a database outside of the model,
100
+ # you can use +connected_to+ with a +database+ argument. The +database+ argument
101
+ # expects a symbol that corresponds to the database key in your config.
102
+ #
103
+ # ActiveRecord::Base.connected_to(database: :animals_slow_replica) do
104
+ # Dog.run_a_long_query # runs a long query while connected to the +animals_slow_replica+
105
+ # end
106
+ #
107
+ # This will connect to a new database for the queries inside the block. By
108
+ # default the `:writing` role will be used since all connections must be assigned
109
+ # a role. If you would like to use a different role you can pass a hash to database:
110
+ #
111
+ # ActiveRecord::Base.connected_to(database: { readonly_slow: :animals_slow_replica }) do
112
+ # # runs a long query while connected to the +animals_slow_replica+ using the readonly_slow role.
113
+ # Dog.run_a_long_query
114
+ # end
115
+ #
116
+ # When using the database key a new connection will be established every time.
117
+ def connected_to(database: nil, role: nil, &blk)
118
+ if database && role
119
+ raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
120
+ elsif database
121
+ if database.is_a?(Hash)
122
+ role, database = database.first
123
+ role = role.to_sym
124
+ end
125
+
126
+ config_hash = resolve_config_for_connection(database)
127
+ handler = lookup_connection_handler(role)
68
128
 
69
- # Returns fully resolved connection hashes.
70
- # Merges connection information from `ENV['DATABASE_URL']` if available.
71
- def resolve
72
- ConnectionAdapters::ConnectionSpecification::Resolver.new(config).resolve_all
129
+ handler.establish_connection(config_hash)
130
+
131
+ with_handler(role, &blk)
132
+ elsif role
133
+ with_handler(role.to_sym, &blk)
134
+ else
135
+ raise ArgumentError, "must provide a `database` or a `role`."
73
136
  end
137
+ end
74
138
 
75
- private
76
- def config
77
- @raw_config.dup.tap do |cfg|
78
- if url = ENV["DATABASE_URL"]
79
- cfg[@env] ||= {}
80
- cfg[@env]["url"] ||= url
81
- end
82
- end
139
+ # Returns true if role is the current connected role.
140
+ #
141
+ # ActiveRecord::Base.connected_to(role: :writing) do
142
+ # ActiveRecord::Base.connected_to?(role: :writing) #=> true
143
+ # ActiveRecord::Base.connected_to?(role: :reading) #=> false
144
+ # end
145
+ def connected_to?(role:)
146
+ current_role == role.to_sym
147
+ end
148
+
149
+ # Returns the symbol representing the current connected role.
150
+ #
151
+ # ActiveRecord::Base.connected_to(role: :writing) do
152
+ # ActiveRecord::Base.current_role #=> :writing
153
+ # end
154
+ #
155
+ # ActiveRecord::Base.connected_to(role: :reading) do
156
+ # ActiveRecord::Base.current_role #=> :reading
157
+ # end
158
+ def current_role
159
+ connection_handlers.key(connection_handler)
160
+ end
161
+
162
+ def lookup_connection_handler(handler_key) # :nodoc:
163
+ handler_key ||= ActiveRecord::Base.writing_role
164
+ connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
165
+ end
166
+
167
+ def with_handler(handler_key, &blk) # :nodoc:
168
+ handler = lookup_connection_handler(handler_key)
169
+ swap_connection_handler(handler, &blk)
170
+ end
171
+
172
+ def resolve_config_for_connection(config_or_env) # :nodoc:
173
+ raise "Anonymous class is not allowed." unless name
174
+
175
+ config_or_env ||= DEFAULT_ENV.call.to_sym
176
+ pool_name = primary_class? ? "primary" : name
177
+ self.connection_specification_name = pool_name
178
+
179
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
180
+ config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
181
+ config_hash[:name] = pool_name
182
+
183
+ config_hash
184
+ end
185
+
186
+ # Clears the query cache for all connections associated with the current thread.
187
+ def clear_query_caches_for_current_thread
188
+ ActiveRecord::Base.connection_handlers.each_value do |handler|
189
+ handler.connection_pool_list.each do |pool|
190
+ pool.connection.clear_query_cache if pool.active_connection?
83
191
  end
192
+ end
84
193
  end
85
194
 
86
195
  # Returns the connection currently associated with the class. This can
@@ -95,11 +204,15 @@ module ActiveRecord
95
204
  # Return the specification name from the current class or its parent.
96
205
  def connection_specification_name
97
206
  if !defined?(@connection_specification_name) || @connection_specification_name.nil?
98
- return self == Base ? "primary" : superclass.connection_specification_name
207
+ return primary_class? ? "primary" : superclass.connection_specification_name
99
208
  end
100
209
  @connection_specification_name
101
210
  end
102
211
 
212
+ def primary_class? # :nodoc:
213
+ self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
214
+ end
215
+
103
216
  # Returns the configuration of the associated connection as a hash:
104
217
  #
105
218
  # ActiveRecord::Base.connection_config
@@ -141,5 +254,14 @@ module ActiveRecord
141
254
 
142
255
  delegate :clear_active_connections!, :clear_reloadable_connections!,
143
256
  :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
257
+
258
+ private
259
+
260
+ def swap_connection_handler(handler, &blk) # :nodoc:
261
+ old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
262
+ yield
263
+ ensure
264
+ ActiveRecord::Base.connection_handler = old_handler
265
+ end
144
266
  end
145
267
  end