activerecord 5.2.8.1 → 6.0.0.beta1

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

Potentially problematic release.


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

Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -816
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +4 -2
  7. data/lib/active_record/associations/association.rb +35 -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/belongs_to.rb +14 -50
  12. data/lib/active_record/associations/builder/collection_association.rb +3 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  14. data/lib/active_record/associations/collection_association.rb +11 -25
  15. data/lib/active_record/associations/collection_proxy.rb +32 -6
  16. data/lib/active_record/associations/foreign_association.rb +7 -0
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +25 -18
  19. data/lib/active_record/associations/has_one_association.rb +28 -30
  20. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  21. data/lib/active_record/associations/join_dependency/join_association.rb +11 -26
  22. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  23. data/lib/active_record/associations/join_dependency.rb +15 -20
  24. data/lib/active_record/associations/preloader/association.rb +1 -2
  25. data/lib/active_record/associations/preloader.rb +32 -29
  26. data/lib/active_record/associations/singular_association.rb +2 -16
  27. data/lib/active_record/associations.rb +16 -12
  28. data/lib/active_record/attribute_assignment.rb +7 -10
  29. data/lib/active_record/attribute_methods/dirty.rb +64 -26
  30. data/lib/active_record/attribute_methods/primary_key.rb +8 -7
  31. data/lib/active_record/attribute_methods/read.rb +16 -48
  32. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  34. data/lib/active_record/attribute_methods/write.rb +15 -16
  35. data/lib/active_record/attribute_methods.rb +34 -56
  36. data/lib/active_record/autosave_association.rb +7 -21
  37. data/lib/active_record/base.rb +2 -2
  38. data/lib/active_record/callbacks.rb +3 -17
  39. data/lib/active_record/coders/yaml_column.rb +1 -13
  40. data/lib/active_record/collection_cache_key.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +13 -36
  42. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +25 -84
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -14
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +5 -11
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +15 -11
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -13
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +0 -2
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -27
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +81 -52
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +95 -31
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -90
  53. data/lib/active_record/connection_adapters/connection_specification.rb +52 -42
  54. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +5 -9
  55. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -7
  56. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  57. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +65 -10
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -4
  60. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +16 -1
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  64. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  65. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  66. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  67. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  69. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +11 -36
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +9 -2
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +38 -20
  73. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -1
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +75 -56
  75. data/lib/active_record/connection_adapters/schema_cache.rb +5 -0
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -5
  77. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +14 -9
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +95 -62
  79. data/lib/active_record/connection_handling.rb +132 -26
  80. data/lib/active_record/core.rb +75 -52
  81. data/lib/active_record/counter_cache.rb +4 -29
  82. data/lib/active_record/database_configurations/database_config.rb +37 -0
  83. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  84. data/lib/active_record/database_configurations/url_config.rb +74 -0
  85. data/lib/active_record/database_configurations.rb +184 -0
  86. data/lib/active_record/enum.rb +22 -7
  87. data/lib/active_record/errors.rb +24 -21
  88. data/lib/active_record/explain.rb +1 -1
  89. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  90. data/lib/active_record/fixture_set/render_context.rb +17 -0
  91. data/lib/active_record/fixture_set/table_row.rb +153 -0
  92. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  93. data/lib/active_record/fixtures.rb +140 -472
  94. data/lib/active_record/gem_version.rb +4 -4
  95. data/lib/active_record/inheritance.rb +12 -2
  96. data/lib/active_record/integration.rb +56 -16
  97. data/lib/active_record/internal_metadata.rb +5 -1
  98. data/lib/active_record/locking/optimistic.rb +2 -2
  99. data/lib/active_record/locking/pessimistic.rb +3 -3
  100. data/lib/active_record/log_subscriber.rb +7 -26
  101. data/lib/active_record/migration/command_recorder.rb +35 -5
  102. data/lib/active_record/migration/compatibility.rb +34 -16
  103. data/lib/active_record/migration.rb +38 -37
  104. data/lib/active_record/model_schema.rb +30 -9
  105. data/lib/active_record/nested_attributes.rb +2 -2
  106. data/lib/active_record/no_touching.rb +7 -0
  107. data/lib/active_record/persistence.rb +18 -7
  108. data/lib/active_record/query_cache.rb +11 -4
  109. data/lib/active_record/querying.rb +19 -11
  110. data/lib/active_record/railtie.rb +71 -60
  111. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  112. data/lib/active_record/railties/controller_runtime.rb +30 -35
  113. data/lib/active_record/railties/databases.rake +94 -43
  114. data/lib/active_record/reflection.rb +60 -44
  115. data/lib/active_record/relation/batches.rb +13 -10
  116. data/lib/active_record/relation/calculations.rb +38 -28
  117. data/lib/active_record/relation/delegation.rb +4 -13
  118. data/lib/active_record/relation/finder_methods.rb +12 -25
  119. data/lib/active_record/relation/merger.rb +2 -6
  120. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  121. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  122. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  123. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  124. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  125. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  126. data/lib/active_record/relation/predicate_builder.rb +4 -6
  127. data/lib/active_record/relation/query_attribute.rb +15 -12
  128. data/lib/active_record/relation/query_methods.rb +29 -52
  129. data/lib/active_record/relation/where_clause.rb +4 -0
  130. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  131. data/lib/active_record/relation.rb +150 -69
  132. data/lib/active_record/result.rb +30 -11
  133. data/lib/active_record/sanitization.rb +2 -39
  134. data/lib/active_record/schema.rb +1 -10
  135. data/lib/active_record/schema_dumper.rb +12 -6
  136. data/lib/active_record/schema_migration.rb +4 -0
  137. data/lib/active_record/scoping/default.rb +10 -3
  138. data/lib/active_record/scoping/named.rb +10 -14
  139. data/lib/active_record/scoping.rb +9 -8
  140. data/lib/active_record/statement_cache.rb +32 -5
  141. data/lib/active_record/store.rb +39 -8
  142. data/lib/active_record/table_metadata.rb +1 -4
  143. data/lib/active_record/tasks/database_tasks.rb +89 -23
  144. data/lib/active_record/tasks/mysql_database_tasks.rb +2 -4
  145. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  146. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  147. data/lib/active_record/test_databases.rb +38 -0
  148. data/lib/active_record/test_fixtures.rb +224 -0
  149. data/lib/active_record/timestamp.rb +4 -6
  150. data/lib/active_record/transactions.rb +3 -22
  151. data/lib/active_record/translation.rb +1 -1
  152. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  153. data/lib/active_record/type.rb +3 -4
  154. data/lib/active_record/type_caster/connection.rb +1 -6
  155. data/lib/active_record/type_caster/map.rb +1 -4
  156. data/lib/active_record/validations/uniqueness.rb +13 -25
  157. data/lib/active_record.rb +2 -1
  158. data/lib/arel/alias_predication.rb +9 -0
  159. data/lib/arel/attributes/attribute.rb +37 -0
  160. data/lib/arel/attributes.rb +22 -0
  161. data/lib/arel/collectors/bind.rb +24 -0
  162. data/lib/arel/collectors/composite.rb +31 -0
  163. data/lib/arel/collectors/plain_string.rb +20 -0
  164. data/lib/arel/collectors/sql_string.rb +20 -0
  165. data/lib/arel/collectors/substitute_binds.rb +28 -0
  166. data/lib/arel/crud.rb +42 -0
  167. data/lib/arel/delete_manager.rb +18 -0
  168. data/lib/arel/errors.rb +9 -0
  169. data/lib/arel/expressions.rb +29 -0
  170. data/lib/arel/factory_methods.rb +49 -0
  171. data/lib/arel/insert_manager.rb +49 -0
  172. data/lib/arel/math.rb +45 -0
  173. data/lib/arel/nodes/and.rb +32 -0
  174. data/lib/arel/nodes/ascending.rb +23 -0
  175. data/lib/arel/nodes/binary.rb +52 -0
  176. data/lib/arel/nodes/bind_param.rb +36 -0
  177. data/lib/arel/nodes/case.rb +55 -0
  178. data/lib/arel/nodes/casted.rb +50 -0
  179. data/lib/arel/nodes/count.rb +12 -0
  180. data/lib/arel/nodes/delete_statement.rb +45 -0
  181. data/lib/arel/nodes/descending.rb +23 -0
  182. data/lib/arel/nodes/equality.rb +18 -0
  183. data/lib/arel/nodes/extract.rb +24 -0
  184. data/lib/arel/nodes/false.rb +16 -0
  185. data/lib/arel/nodes/full_outer_join.rb +8 -0
  186. data/lib/arel/nodes/function.rb +44 -0
  187. data/lib/arel/nodes/grouping.rb +8 -0
  188. data/lib/arel/nodes/in.rb +8 -0
  189. data/lib/arel/nodes/infix_operation.rb +80 -0
  190. data/lib/arel/nodes/inner_join.rb +8 -0
  191. data/lib/arel/nodes/insert_statement.rb +37 -0
  192. data/lib/arel/nodes/join_source.rb +20 -0
  193. data/lib/arel/nodes/matches.rb +18 -0
  194. data/lib/arel/nodes/named_function.rb +23 -0
  195. data/lib/arel/nodes/node.rb +50 -0
  196. data/lib/arel/nodes/node_expression.rb +13 -0
  197. data/lib/arel/nodes/outer_join.rb +8 -0
  198. data/lib/arel/nodes/over.rb +15 -0
  199. data/lib/arel/nodes/regexp.rb +16 -0
  200. data/lib/arel/nodes/right_outer_join.rb +8 -0
  201. data/lib/arel/nodes/select_core.rb +63 -0
  202. data/lib/arel/nodes/select_statement.rb +41 -0
  203. data/lib/arel/nodes/sql_literal.rb +16 -0
  204. data/lib/arel/nodes/string_join.rb +11 -0
  205. data/lib/arel/nodes/table_alias.rb +27 -0
  206. data/lib/arel/nodes/terminal.rb +16 -0
  207. data/lib/arel/nodes/true.rb +16 -0
  208. data/lib/arel/nodes/unary.rb +44 -0
  209. data/lib/arel/nodes/unary_operation.rb +20 -0
  210. data/lib/arel/nodes/unqualified_column.rb +22 -0
  211. data/lib/arel/nodes/update_statement.rb +41 -0
  212. data/lib/arel/nodes/values.rb +16 -0
  213. data/lib/arel/nodes/values_list.rb +24 -0
  214. data/lib/arel/nodes/window.rb +126 -0
  215. data/lib/arel/nodes/with.rb +11 -0
  216. data/lib/arel/nodes.rb +67 -0
  217. data/lib/arel/order_predications.rb +13 -0
  218. data/lib/arel/predications.rb +257 -0
  219. data/lib/arel/select_manager.rb +271 -0
  220. data/lib/arel/table.rb +110 -0
  221. data/lib/arel/tree_manager.rb +72 -0
  222. data/lib/arel/update_manager.rb +34 -0
  223. data/lib/arel/visitors/depth_first.rb +199 -0
  224. data/lib/arel/visitors/dot.rb +292 -0
  225. data/lib/arel/visitors/ibm_db.rb +21 -0
  226. data/lib/arel/visitors/informix.rb +56 -0
  227. data/lib/arel/visitors/mssql.rb +143 -0
  228. data/lib/arel/visitors/mysql.rb +83 -0
  229. data/lib/arel/visitors/oracle.rb +159 -0
  230. data/lib/arel/visitors/oracle12.rb +67 -0
  231. data/lib/arel/visitors/postgresql.rb +116 -0
  232. data/lib/arel/visitors/sqlite.rb +39 -0
  233. data/lib/arel/visitors/to_sql.rb +913 -0
  234. data/lib/arel/visitors/visitor.rb +42 -0
  235. data/lib/arel/visitors/where_sql.rb +23 -0
  236. data/lib/arel/visitors.rb +20 -0
  237. data/lib/arel/window_predications.rb +9 -0
  238. data/lib/arel.rb +44 -0
  239. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  240. data/lib/rails/generators/active_record/migration.rb +14 -1
  241. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  242. metadata +107 -29
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  # See https://www.sqlite.org/fileformat2.html#intschema
12
12
  next if row["name"].starts_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(
@@ -81,7 +86,7 @@ module ActiveRecord
81
86
  scope = quoted_scope(name, type: type)
82
87
  scope[:type] ||= "'table','view'"
83
88
 
84
- sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'".dup
89
+ sql = +"SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
85
90
  sql << " AND name = #{scope[:name]}" if scope[:name]
86
91
  sql << " AND type IN (#{scope[:type]})"
87
92
  sql
@@ -9,12 +9,14 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
9
9
  require "active_record/connection_adapters/sqlite3/schema_dumper"
10
10
  require "active_record/connection_adapters/sqlite3/schema_statements"
11
11
 
12
- gem "sqlite3", "~> 1.3", ">= 1.3.6"
12
+ gem "sqlite3", "~> 1.3.6"
13
13
  require "sqlite3"
14
14
 
15
15
  module ActiveRecord
16
16
  module ConnectionHandling # :nodoc:
17
17
  def sqlite3_connection(config)
18
+ config = config.symbolize_keys
19
+
18
20
  # Require database.
19
21
  unless config[:database]
20
22
  raise ArgumentError, "No database file specified. Missing argument: database"
@@ -31,7 +33,7 @@ module ActiveRecord
31
33
 
32
34
  db = SQLite3::Database.new(
33
35
  config[:database].to_s,
34
- results_as_hash: true
36
+ config.merge(results_as_hash: true)
35
37
  )
36
38
 
37
39
  db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
@@ -54,7 +56,7 @@ module ActiveRecord
54
56
  #
55
57
  # * <tt>:database</tt> - Path to the database file.
56
58
  class SQLite3Adapter < AbstractAdapter
57
- ADAPTER_NAME = "SQLite".freeze
59
+ ADAPTER_NAME = "SQLite"
58
60
 
59
61
  include SQLite3::Quoting
60
62
  include SQLite3::SchemaStatements
@@ -74,27 +76,20 @@ 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
 
@@ -103,7 +98,6 @@ module ActiveRecord
103
98
 
104
99
  @active = true
105
100
  @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
106
-
107
101
  configure_connection
108
102
  end
109
103
 
@@ -116,7 +110,11 @@ module ActiveRecord
116
110
  end
117
111
 
118
112
  def supports_partial_index?
119
- sqlite_version >= "3.8.0"
113
+ true
114
+ end
115
+
116
+ def supports_expression_index?
117
+ sqlite_version >= "3.9.0"
120
118
  end
121
119
 
122
120
  def requires_reloading?
@@ -124,7 +122,7 @@ module ActiveRecord
124
122
  end
125
123
 
126
124
  def supports_foreign_keys_in_create?
127
- sqlite_version >= "3.6.19"
125
+ true
128
126
  end
129
127
 
130
128
  def supports_views?
@@ -139,10 +137,6 @@ module ActiveRecord
139
137
  true
140
138
  end
141
139
 
142
- def supports_multi_insert?
143
- sqlite_version >= "3.7.11"
144
- end
145
-
146
140
  def active?
147
141
  @active
148
142
  end
@@ -184,16 +178,23 @@ module ActiveRecord
184
178
  true
185
179
  end
186
180
 
181
+ def supports_lazy_transactions?
182
+ true
183
+ end
184
+
187
185
  # REFERENTIAL INTEGRITY ====================================
188
186
 
189
187
  def disable_referential_integrity # :nodoc:
190
- old = query_value("PRAGMA foreign_keys")
188
+ old_foreign_keys = query_value("PRAGMA foreign_keys")
189
+ old_defer_foreign_keys = query_value("PRAGMA defer_foreign_keys")
191
190
 
192
191
  begin
192
+ execute("PRAGMA defer_foreign_keys = ON")
193
193
  execute("PRAGMA foreign_keys = OFF")
194
194
  yield
195
195
  ensure
196
- execute("PRAGMA foreign_keys = #{old}")
196
+ execute("PRAGMA defer_foreign_keys = #{old_defer_foreign_keys}")
197
+ execute("PRAGMA foreign_keys = #{old_foreign_keys}")
197
198
  end
198
199
  end
199
200
 
@@ -201,12 +202,25 @@ module ActiveRecord
201
202
  # DATABASE STATEMENTS ======================================
202
203
  #++
203
204
 
205
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback) # :nodoc:
206
+ private_constant :READ_QUERY
207
+
208
+ def write_query?(sql) # :nodoc:
209
+ !READ_QUERY.match?(sql)
210
+ end
211
+
204
212
  def explain(arel, binds = [])
205
213
  sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
206
214
  SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
207
215
  end
208
216
 
209
217
  def exec_query(sql, name = nil, binds = [], prepare: false)
218
+ if preventing_writes? && write_query?(sql)
219
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
220
+ end
221
+
222
+ materialize_transactions
223
+
210
224
  type_casted_binds = type_casted_binds(binds)
211
225
 
212
226
  log(sql, name, binds, type_casted_binds) do
@@ -224,11 +238,8 @@ module ActiveRecord
224
238
  stmt.close
225
239
  end
226
240
  else
227
- cache = @statements[sql] ||= {
228
- stmt: @connection.prepare(sql)
229
- }
230
- stmt = cache[:stmt]
231
- cols = cache[:cols] ||= stmt.columns
241
+ stmt = @statements[sql] ||= @connection.prepare(sql)
242
+ cols = stmt.columns
232
243
  stmt.reset!
233
244
  stmt.bind_params(type_casted_binds)
234
245
  records = stmt.to_a
@@ -250,6 +261,12 @@ module ActiveRecord
250
261
  end
251
262
 
252
263
  def execute(sql, name = nil) #:nodoc:
264
+ if preventing_writes? && write_query?(sql)
265
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
266
+ end
267
+
268
+ materialize_transactions
269
+
253
270
  log(sql, name) do
254
271
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
255
272
  @connection.execute(sql)
@@ -290,11 +307,6 @@ module ActiveRecord
290
307
  rename_table_indexes(table_name, new_name)
291
308
  end
292
309
 
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
310
  def add_column(table_name, column_name, type, options = {}) #:nodoc:
299
311
  if invalid_alter_table_type?(type, options)
300
312
  alter_table(table_name) do |definition|
@@ -366,14 +378,6 @@ module ActiveRecord
366
378
  end
367
379
  end
368
380
 
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)
375
- end
376
-
377
381
  def insert_fixtures_set(fixture_set, tables_to_delete = [])
378
382
  disable_referential_integrity do
379
383
  transaction(requires_new: true) do
@@ -387,6 +391,18 @@ module ActiveRecord
387
391
  end
388
392
 
389
393
  private
394
+ # See https://www.sqlite.org/limits.html,
395
+ # the default value is 999 when not configured.
396
+ def bind_params_length
397
+ 999
398
+ end
399
+
400
+ def check_version
401
+ if sqlite_version < "3.8.0"
402
+ raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8."
403
+ end
404
+ end
405
+
390
406
  def initialize_type_map(m = type_map)
391
407
  super
392
408
  register_class_with_limit m, %r(int)i, SQLite3Integer
@@ -407,12 +423,25 @@ module ActiveRecord
407
423
 
408
424
  def alter_table(table_name, options = {})
409
425
  altered_table_name = "a#{table_name}"
410
- caller = lambda { |definition| yield definition if block_given? }
426
+ foreign_keys = foreign_keys(table_name)
427
+
428
+ caller = lambda do |definition|
429
+ rename = options[:rename] || {}
430
+ foreign_keys.each do |fk|
431
+ if column = rename[fk.options[:column]]
432
+ fk.options[:column] = column
433
+ end
434
+ definition.foreign_key(fk.to_table, fk.options)
435
+ end
436
+
437
+ yield definition if block_given?
438
+ end
411
439
 
412
440
  transaction do
413
- move_table(table_name, altered_table_name,
414
- options.merge(temporary: true))
415
- move_table(altered_table_name, table_name, &caller)
441
+ disable_referential_integrity do
442
+ move_table(table_name, altered_table_name, options.merge(temporary: true))
443
+ move_table(altered_table_name, table_name, &caller)
444
+ end
416
445
  end
417
446
  end
418
447
 
@@ -442,6 +471,7 @@ module ActiveRecord
442
471
  primary_key: column_name == from_primary_key
443
472
  )
444
473
  end
474
+
445
475
  yield @definition if block_given?
446
476
  end
447
477
  copy_table_indexes(from, to, options[:rename] || {})
@@ -459,9 +489,12 @@ module ActiveRecord
459
489
  name = name[1..-1]
460
490
  end
461
491
 
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)
492
+ columns = index.columns
493
+ if columns.is_a?(Array)
494
+ to_column_names = columns(to).map(&:name)
495
+ columns = columns.map { |c| rename[c] || c }.select do |column|
496
+ to_column_names.include?(column)
497
+ end
465
498
  end
466
499
 
467
500
  unless columns.empty?
@@ -491,18 +524,18 @@ module ActiveRecord
491
524
  @sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
492
525
  end
493
526
 
494
- def translate_exception(exception, message)
527
+ def translate_exception(exception, message:, sql:, binds:)
495
528
  case exception.message
496
529
  # SQLite 3.8.2 returns a newly formatted error message:
497
530
  # UNIQUE constraint failed: *table_name*.*column_name*
498
531
  # Older versions of SQLite return:
499
532
  # column *column_name* is not unique
500
533
  when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
501
- RecordNotUnique.new(message)
534
+ RecordNotUnique.new(message, sql: sql, binds: binds)
502
535
  when /.* may not be NULL/, /NOT NULL constraint failed: .*/
503
- NotNullViolation.new(message)
536
+ NotNullViolation.new(message, sql: sql, binds: binds)
504
537
  when /FOREIGN KEY constraint failed/i
505
- InvalidForeignKey.new(message)
538
+ InvalidForeignKey.new(message, sql: sql, binds: binds)
506
539
  else
507
540
  super
508
541
  end
@@ -512,7 +545,7 @@ module ActiveRecord
512
545
 
513
546
  def table_structure_with_collation(table_name, basic_structure)
514
547
  collation_hash = {}
515
- sql = <<-SQL
548
+ sql = <<~SQL
516
549
  SELECT sql FROM
517
550
  (SELECT * FROM sqlite_master UNION ALL
518
551
  SELECT * FROM sqlite_temp_master)
@@ -525,9 +558,9 @@ module ActiveRecord
525
558
  result = exec_query(sql, "SCHEMA").first
526
559
 
527
560
  if result
528
- # Splitting with left parentheses and discarding the first part will return all
561
+ # Splitting with left parentheses and picking up last will return all
529
562
  # columns separated with comma(,).
530
- columns_string = result["sql"].split("(", 2).last
563
+ columns_string = result["sql"].split("(").last
531
564
 
532
565
  columns_string.split(",").each do |column_string|
533
566
  # This regex will match the column name and collation type and will save
@@ -545,7 +578,7 @@ module ActiveRecord
545
578
  column
546
579
  end
547
580
  else
548
- basic_structure.to_hash
581
+ basic_structure.to_a
549
582
  end
550
583
  end
551
584
 
@@ -46,41 +46,138 @@ 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
51
53
 
52
- config ||= DEFAULT_ENV.call.to_sym
53
- spec_name = self == Base ? "primary" : name
54
- self.connection_specification_name = spec_name
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 = []
55
70
 
56
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
57
- spec = resolver.resolve(config).symbolize_keys
58
- spec[: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)
74
+
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
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 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_ode) 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
+ # This will connect to a new database for the queries inside the block.
104
+ #
105
+ # ActiveRecord::Base.connected_to(database: :animals_slow_replica) do
106
+ # Dog.run_a_long_query # runs a long query while connected to the +animals_slow_replica+
107
+ # end
108
+ def connected_to(database: nil, role: nil, &blk)
109
+ if database && role
110
+ raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
111
+ elsif database
112
+ if database.is_a?(Hash)
113
+ role, database = database.first
114
+ role = role.to_sym
115
+ else
116
+ role = database.to_sym
117
+ end
118
+
119
+ config_hash = resolve_config_for_connection(database)
120
+ handler = lookup_connection_handler(role)
121
+
122
+ with_handler(role) do
123
+ handler.establish_connection(config_hash)
124
+ yield
125
+ end
126
+ elsif role
127
+ with_handler(role.to_sym, &blk)
128
+ else
129
+ raise ArgumentError, "must provide a `database` or a `role`."
67
130
  end
131
+ end
68
132
 
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
133
+ # Returns true if role is the current connected role.
134
+ #
135
+ # ActiveRecord::Base.connected_to(role: :writing) do
136
+ # ActiveRecord::Base.connected_to?(role: :writing) #=> true
137
+ # ActiveRecord::Base.connected_to?(role: :reading) #=> false
138
+ # end
139
+ def connected_to?(role:)
140
+ current_role == role.to_sym
141
+ end
142
+
143
+ # Returns the symbol representing the current connected role.
144
+ #
145
+ # ActiveRecord::Base.connected_to(role: :writing) do
146
+ # ActiveRecord::Base.current_role #=> :writing
147
+ # end
148
+ #
149
+ # ActiveRecord::Base.connected_to(role: :reading) do
150
+ # ActiveRecord::Base.current_role #=> :reading
151
+ # end
152
+ def current_role
153
+ connection_handlers.key(connection_handler)
154
+ end
155
+
156
+ def lookup_connection_handler(handler_key) # :nodoc:
157
+ connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
158
+ end
159
+
160
+ def with_handler(handler_key, &blk) # :nodoc:
161
+ unless ActiveRecord::Base.connection_handlers.keys.include?(handler_key)
162
+ raise ArgumentError, "The #{handler_key} role does not exist. Add it by establishing a connection with `connects_to` or use an existing role (#{ActiveRecord::Base.connection_handlers.keys.join(", ")})."
73
163
  end
74
164
 
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
83
- end
165
+ handler = lookup_connection_handler(handler_key)
166
+ swap_connection_handler(handler, &blk)
167
+ end
168
+
169
+ def resolve_config_for_connection(config_or_env) # :nodoc:
170
+ raise "Anonymous class is not allowed." unless name
171
+
172
+ config_or_env ||= DEFAULT_ENV.call.to_sym
173
+ pool_name = self == Base ? "primary" : name
174
+ self.connection_specification_name = pool_name
175
+
176
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
177
+ config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
178
+ config_hash[:name] = pool_name
179
+
180
+ config_hash
84
181
  end
85
182
 
86
183
  # Returns the connection currently associated with the class. This can
@@ -141,5 +238,14 @@ module ActiveRecord
141
238
 
142
239
  delegate :clear_active_connections!, :clear_reloadable_connections!,
143
240
  :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
241
+
242
+ private
243
+
244
+ def swap_connection_handler(handler, &blk) # :nodoc:
245
+ old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
246
+ yield
247
+ ensure
248
+ ActiveRecord::Base.connection_handler = old_handler
249
+ end
144
250
  end
145
251
  end