activerecord 7.0.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1607 -1040
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +17 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +345 -219
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -129
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +504 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +3 -1
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +358 -57
  83. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +343 -181
  85. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +45 -39
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  89. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  92. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  93. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  94. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  95. data/lib/active_record/connection_adapters.rb +3 -1
  96. data/lib/active_record/connection_handling.rb +73 -96
  97. data/lib/active_record/core.rb +136 -148
  98. data/lib/active_record/counter_cache.rb +46 -25
  99. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  100. data/lib/active_record/database_configurations/database_config.rb +9 -3
  101. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  102. data/lib/active_record/database_configurations/url_config.rb +17 -11
  103. data/lib/active_record/database_configurations.rb +87 -34
  104. data/lib/active_record/delegated_type.rb +9 -4
  105. data/lib/active_record/deprecator.rb +7 -0
  106. data/lib/active_record/destroy_association_async_job.rb +2 -0
  107. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  108. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  109. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  110. data/lib/active_record/encryption/config.rb +25 -1
  111. data/lib/active_record/encryption/configurable.rb +13 -14
  112. data/lib/active_record/encryption/context.rb +10 -3
  113. data/lib/active_record/encryption/contexts.rb +8 -4
  114. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  115. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  116. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  117. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  118. data/lib/active_record/encryption/encryptor.rb +7 -7
  119. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  120. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -71
  121. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  122. data/lib/active_record/encryption/key_generator.rb +12 -1
  123. data/lib/active_record/encryption/message.rb +1 -1
  124. data/lib/active_record/encryption/message_serializer.rb +2 -0
  125. data/lib/active_record/encryption/properties.rb +4 -4
  126. data/lib/active_record/encryption/scheme.rb +20 -23
  127. data/lib/active_record/encryption.rb +1 -0
  128. data/lib/active_record/enum.rb +114 -27
  129. data/lib/active_record/errors.rb +108 -15
  130. data/lib/active_record/explain.rb +23 -3
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  133. data/lib/active_record/fixture_set/render_context.rb +2 -0
  134. data/lib/active_record/fixture_set/table_row.rb +29 -8
  135. data/lib/active_record/fixtures.rb +121 -73
  136. data/lib/active_record/future_result.rb +30 -5
  137. data/lib/active_record/gem_version.rb +2 -2
  138. data/lib/active_record/inheritance.rb +30 -16
  139. data/lib/active_record/insert_all.rb +55 -8
  140. data/lib/active_record/integration.rb +10 -10
  141. data/lib/active_record/internal_metadata.rb +118 -30
  142. data/lib/active_record/locking/optimistic.rb +32 -18
  143. data/lib/active_record/locking/pessimistic.rb +8 -5
  144. data/lib/active_record/log_subscriber.rb +39 -17
  145. data/lib/active_record/marshalling.rb +56 -0
  146. data/lib/active_record/message_pack.rb +124 -0
  147. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  148. data/lib/active_record/middleware/database_selector.rb +18 -13
  149. data/lib/active_record/middleware/shard_selector.rb +7 -5
  150. data/lib/active_record/migration/command_recorder.rb +104 -9
  151. data/lib/active_record/migration/compatibility.rb +158 -64
  152. data/lib/active_record/migration/default_strategy.rb +23 -0
  153. data/lib/active_record/migration/execution_strategy.rb +19 -0
  154. data/lib/active_record/migration.rb +271 -117
  155. data/lib/active_record/model_schema.rb +82 -50
  156. data/lib/active_record/nested_attributes.rb +23 -3
  157. data/lib/active_record/normalization.rb +159 -0
  158. data/lib/active_record/persistence.rb +200 -47
  159. data/lib/active_record/promise.rb +84 -0
  160. data/lib/active_record/query_cache.rb +3 -21
  161. data/lib/active_record/query_logs.rb +87 -51
  162. data/lib/active_record/query_logs_formatter.rb +41 -0
  163. data/lib/active_record/querying.rb +16 -3
  164. data/lib/active_record/railtie.rb +127 -61
  165. data/lib/active_record/railties/controller_runtime.rb +12 -8
  166. data/lib/active_record/railties/databases.rake +142 -143
  167. data/lib/active_record/railties/job_runtime.rb +23 -0
  168. data/lib/active_record/readonly_attributes.rb +32 -5
  169. data/lib/active_record/reflection.rb +177 -45
  170. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  171. data/lib/active_record/relation/batches.rb +190 -61
  172. data/lib/active_record/relation/calculations.rb +200 -83
  173. data/lib/active_record/relation/delegation.rb +23 -9
  174. data/lib/active_record/relation/finder_methods.rb +77 -16
  175. data/lib/active_record/relation/merger.rb +2 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  177. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  179. data/lib/active_record/relation/predicate_builder.rb +26 -14
  180. data/lib/active_record/relation/query_attribute.rb +25 -1
  181. data/lib/active_record/relation/query_methods.rb +429 -76
  182. data/lib/active_record/relation/spawn_methods.rb +18 -1
  183. data/lib/active_record/relation.rb +98 -41
  184. data/lib/active_record/result.rb +25 -9
  185. data/lib/active_record/runtime_registry.rb +10 -1
  186. data/lib/active_record/sanitization.rb +57 -16
  187. data/lib/active_record/schema.rb +36 -22
  188. data/lib/active_record/schema_dumper.rb +65 -23
  189. data/lib/active_record/schema_migration.rb +68 -33
  190. data/lib/active_record/scoping/default.rb +20 -12
  191. data/lib/active_record/scoping/named.rb +2 -2
  192. data/lib/active_record/scoping.rb +2 -1
  193. data/lib/active_record/secure_password.rb +60 -0
  194. data/lib/active_record/secure_token.rb +21 -3
  195. data/lib/active_record/serialization.rb +5 -0
  196. data/lib/active_record/signed_id.rb +9 -7
  197. data/lib/active_record/store.rb +16 -11
  198. data/lib/active_record/suppressor.rb +3 -1
  199. data/lib/active_record/table_metadata.rb +16 -3
  200. data/lib/active_record/tasks/database_tasks.rb +138 -107
  201. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  202. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  203. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  204. data/lib/active_record/test_fixtures.rb +123 -99
  205. data/lib/active_record/timestamp.rb +26 -14
  206. data/lib/active_record/token_for.rb +113 -0
  207. data/lib/active_record/touch_later.rb +11 -6
  208. data/lib/active_record/transactions.rb +39 -13
  209. data/lib/active_record/translation.rb +1 -1
  210. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  211. data/lib/active_record/type/internal/timezone.rb +7 -2
  212. data/lib/active_record/type/serialized.rb +8 -4
  213. data/lib/active_record/type/time.rb +4 -0
  214. data/lib/active_record/validations/absence.rb +1 -1
  215. data/lib/active_record/validations/associated.rb +3 -3
  216. data/lib/active_record/validations/numericality.rb +5 -4
  217. data/lib/active_record/validations/presence.rb +5 -28
  218. data/lib/active_record/validations/uniqueness.rb +50 -5
  219. data/lib/active_record/validations.rb +8 -4
  220. data/lib/active_record/version.rb +1 -1
  221. data/lib/active_record.rb +143 -16
  222. data/lib/arel/errors.rb +10 -0
  223. data/lib/arel/factory_methods.rb +4 -0
  224. data/lib/arel/filter_predications.rb +1 -1
  225. data/lib/arel/nodes/and.rb +4 -0
  226. data/lib/arel/nodes/binary.rb +6 -1
  227. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  228. data/lib/arel/nodes/cte.rb +36 -0
  229. data/lib/arel/nodes/filter.rb +1 -1
  230. data/lib/arel/nodes/fragments.rb +35 -0
  231. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  232. data/lib/arel/nodes/leading_join.rb +8 -0
  233. data/lib/arel/nodes/node.rb +111 -2
  234. data/lib/arel/nodes/sql_literal.rb +6 -0
  235. data/lib/arel/nodes/table_alias.rb +4 -0
  236. data/lib/arel/nodes.rb +4 -0
  237. data/lib/arel/predications.rb +2 -0
  238. data/lib/arel/table.rb +9 -5
  239. data/lib/arel/visitors/mysql.rb +8 -1
  240. data/lib/arel/visitors/to_sql.rb +81 -17
  241. data/lib/arel/visitors/visitor.rb +2 -2
  242. data/lib/arel.rb +16 -2
  243. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  244. data/lib/rails/generators/active_record/migration.rb +3 -1
  245. data/lib/rails/generators/active_record/model/USAGE +113 -0
  246. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  247. metadata +50 -15
  248. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  249. data/lib/active_record/null_relation.rb +0 -63
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "active_record/connection_adapters/abstract_adapter"
4
4
  require "active_record/connection_adapters/statement_pool"
5
+ require "active_record/connection_adapters/sqlite3/column"
5
6
  require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
6
7
  require "active_record/connection_adapters/sqlite3/quoting"
7
8
  require "active_record/connection_adapters/sqlite3/database_statements"
@@ -15,39 +16,18 @@ require "sqlite3"
15
16
 
16
17
  module ActiveRecord
17
18
  module ConnectionHandling # :nodoc:
18
- def sqlite3_connection(config)
19
- config = config.symbolize_keys
20
-
21
- # Require database.
22
- unless config[:database]
23
- raise ArgumentError, "No database file specified. Missing argument: database"
24
- end
25
-
26
- # Allow database path relative to Rails.root, but only if the database
27
- # path is not the special path that tells sqlite to build a database only
28
- # in memory.
29
- if ":memory:" != config[:database] && !config[:database].to_s.start_with?("file:")
30
- config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
31
- dirname = File.dirname(config[:database])
32
- Dir.mkdir(dirname) unless File.directory?(dirname)
33
- end
34
-
35
- db = SQLite3::Database.new(
36
- config[:database].to_s,
37
- config.merge(results_as_hash: true)
38
- )
19
+ def sqlite3_adapter_class
20
+ ConnectionAdapters::SQLite3Adapter
21
+ end
39
22
 
40
- ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
41
- rescue Errno::ENOENT => error
42
- if error.message.include?("No such file or directory")
43
- raise ActiveRecord::NoDatabaseError
44
- else
45
- raise
46
- end
23
+ def sqlite3_connection(config)
24
+ sqlite3_adapter_class.new(config)
47
25
  end
48
26
  end
49
27
 
50
28
  module ConnectionAdapters # :nodoc:
29
+ # = Active Record SQLite3 Adapter
30
+ #
51
31
  # The SQLite3 adapter works with the sqlite3-ruby drivers
52
32
  # (available as gem from https://rubygems.org/gems/sqlite3).
53
33
  #
@@ -57,10 +37,42 @@ module ActiveRecord
57
37
  class SQLite3Adapter < AbstractAdapter
58
38
  ADAPTER_NAME = "SQLite"
59
39
 
40
+ class << self
41
+ def new_client(config)
42
+ ::SQLite3::Database.new(config[:database].to_s, config)
43
+ rescue Errno::ENOENT => error
44
+ if error.message.include?("No such file or directory")
45
+ raise ActiveRecord::NoDatabaseError
46
+ else
47
+ raise
48
+ end
49
+ end
50
+
51
+ def dbconsole(config, options = {})
52
+ args = []
53
+
54
+ args << "-#{options[:mode]}" if options[:mode]
55
+ args << "-header" if options[:header]
56
+ args << File.expand_path(config.database, Rails.respond_to?(:root) ? Rails.root : nil)
57
+
58
+ find_cmd_and_exec("sqlite3", *args)
59
+ end
60
+ end
61
+
60
62
  include SQLite3::Quoting
61
63
  include SQLite3::SchemaStatements
62
64
  include SQLite3::DatabaseStatements
63
65
 
66
+ ##
67
+ # :singleton-method:
68
+ # Configure the SQLite3Adapter to be used in a strict strings mode.
69
+ # This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
70
+ # For example, it is possible to create an index for a non existing column.
71
+ # If you wish to enable this mode you can add the following line to your application.rb file:
72
+ #
73
+ # config.active_record.sqlite3_adapter_strict_strings_by_default = true
74
+ class_attribute :strict_strings_by_default, default: false
75
+
64
76
  NATIVE_DATABASE_TYPES = {
65
77
  primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
66
78
  string: { name: "varchar" },
@@ -77,26 +89,48 @@ module ActiveRecord
77
89
  }
78
90
 
79
91
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
92
+ alias reset clear
93
+
80
94
  private
81
95
  def dealloc(stmt)
82
96
  stmt.close unless stmt.closed?
83
97
  end
84
98
  end
85
99
 
86
- def initialize(connection, logger, connection_options, config)
87
- @memory_database = config[:database] == ":memory:"
88
- super(connection, logger, config)
89
- configure_connection
90
- end
100
+ def initialize(...)
101
+ super
91
102
 
92
- def self.database_exists?(config)
93
- config = config.symbolize_keys
94
- if config[:database] == ":memory:"
95
- true
103
+ @memory_database = false
104
+ case @config[:database].to_s
105
+ when ""
106
+ raise ArgumentError, "No database file specified. Missing argument: database"
107
+ when ":memory:"
108
+ @memory_database = true
109
+ when /\Afile:/
96
110
  else
97
- database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
98
- File.exist?(database_file)
111
+ # Otherwise we have a path relative to Rails.root
112
+ @config[:database] = File.expand_path(@config[:database], Rails.root) if defined?(Rails.root)
113
+ dirname = File.dirname(@config[:database])
114
+ unless File.directory?(dirname)
115
+ begin
116
+ Dir.mkdir(dirname)
117
+ rescue Errno::ENOENT => error
118
+ if error.message.include?("No such file or directory")
119
+ raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
120
+ else
121
+ raise
122
+ end
123
+ end
124
+ end
99
125
  end
126
+
127
+ @config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
128
+ @connection_parameters = @config.merge(database: @config[:database].to_s, results_as_hash: true)
129
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
130
+ end
131
+
132
+ def database_exists?
133
+ @config[:database] == ":memory:" || File.exist?(@config[:database].to_s)
100
134
  end
101
135
 
102
136
  def supports_ddl_transactions?
@@ -147,6 +181,10 @@ module ActiveRecord
147
181
  database_version >= "3.8.3"
148
182
  end
149
183
 
184
+ def supports_insert_returning?
185
+ database_version >= "3.35.0"
186
+ end
187
+
150
188
  def supports_insert_on_conflict?
151
189
  database_version >= "3.24.0"
152
190
  end
@@ -159,19 +197,22 @@ module ActiveRecord
159
197
  end
160
198
 
161
199
  def active?
162
- !@connection.closed?
200
+ @raw_connection && !@raw_connection.closed?
163
201
  end
164
202
 
165
- def reconnect!
166
- super
167
- connect if @connection.closed?
203
+ def return_value_after_insert?(column) # :nodoc:
204
+ column.auto_populated?
168
205
  end
169
206
 
207
+ alias :reset! :reconnect!
208
+
170
209
  # Disconnects from the database if already connected. Otherwise, this
171
210
  # method does nothing.
172
211
  def disconnect!
173
212
  super
174
- @connection.close rescue nil
213
+
214
+ @raw_connection&.close rescue nil
215
+ @raw_connection = nil
175
216
  end
176
217
 
177
218
  def supports_index_sort_order?
@@ -184,7 +225,7 @@ module ActiveRecord
184
225
 
185
226
  # Returns the current database encoding format as a string, e.g. 'UTF-8'
186
227
  def encoding
187
- @connection.encoding.to_s
228
+ any_raw_connection.encoding.to_s
188
229
  end
189
230
 
190
231
  def supports_explain?
@@ -211,8 +252,14 @@ module ActiveRecord
211
252
  end
212
253
  end
213
254
 
214
- def all_foreign_keys_valid? # :nodoc:
215
- execute("PRAGMA foreign_key_check").blank?
255
+ def check_all_foreign_keys_valid! # :nodoc:
256
+ sql = "PRAGMA foreign_key_check"
257
+ result = execute(sql)
258
+
259
+ unless result.blank?
260
+ tables = result.map { |row| row["table"] }
261
+ raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
262
+ end
216
263
  end
217
264
 
218
265
  # SCHEMA STATEMENTS ========================================
@@ -234,7 +281,8 @@ module ActiveRecord
234
281
  #
235
282
  # Example:
236
283
  # rename_table('octopuses', 'octopi')
237
- def rename_table(table_name, new_name)
284
+ def rename_table(table_name, new_name, **options)
285
+ validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
238
286
  schema_cache.clear_data_source_cache!(table_name.to_s)
239
287
  schema_cache.clear_data_source_cache!(new_name.to_s)
240
288
  exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
@@ -277,8 +325,10 @@ module ActiveRecord
277
325
  end
278
326
 
279
327
  def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
328
+ validate_change_column_null_argument!(null)
329
+
280
330
  unless null || default.nil?
281
- exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
331
+ internal_exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
282
332
  end
283
333
  alter_table(table_name) do |definition|
284
334
  definition[column_name].null = null
@@ -287,10 +337,7 @@ module ActiveRecord
287
337
 
288
338
  def change_column(table_name, column_name, type, **options) # :nodoc:
289
339
  alter_table(table_name) do |definition|
290
- definition[column_name].instance_eval do
291
- self.type = aliased_types(type.to_s, type)
292
- self.options.merge!(options)
293
- end
340
+ definition.change_column(column_name, type, **options)
294
341
  end
295
342
  end
296
343
 
@@ -300,20 +347,42 @@ module ActiveRecord
300
347
  rename_column_indexes(table_name, column.name, new_column_name)
301
348
  end
302
349
 
350
+ def add_timestamps(table_name, **options)
351
+ options[:null] = false if options[:null].nil?
352
+
353
+ if !options.key?(:precision)
354
+ options[:precision] = 6
355
+ end
356
+
357
+ alter_table(table_name) do |definition|
358
+ definition.column :created_at, :datetime, **options
359
+ definition.column :updated_at, :datetime, **options
360
+ end
361
+ end
362
+
303
363
  def add_reference(table_name, ref_name, **options) # :nodoc:
304
364
  super(table_name, ref_name, type: :integer, **options)
305
365
  end
306
366
  alias :add_belongs_to :add_reference
307
367
 
308
368
  def foreign_keys(table_name)
309
- fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
310
- fk_info.map do |row|
369
+ # SQLite returns 1 row for each column of composite foreign keys.
370
+ fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
371
+ grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
372
+ grouped_fk.map do |group|
373
+ row = group.first
311
374
  options = {
312
- column: row["from"],
313
- primary_key: row["to"],
314
375
  on_delete: extract_foreign_key_action(row["on_delete"]),
315
376
  on_update: extract_foreign_key_action(row["on_update"])
316
377
  }
378
+
379
+ if group.one?
380
+ options[:column] = row["from"]
381
+ options[:primary_key] = row["to"]
382
+ else
383
+ options[:column] = group.map { |row| row["from"] }
384
+ options[:primary_key] = group.map { |row| row["to"] }
385
+ end
317
386
  ForeignKeyDefinition.new(table_name, row["table"], options)
318
387
  end
319
388
  end
@@ -333,6 +402,7 @@ module ActiveRecord
333
402
  end
334
403
  end
335
404
 
405
+ sql << " RETURNING #{insert.returning}" if insert.returning
336
406
  sql
337
407
  end
338
408
 
@@ -340,8 +410,12 @@ module ActiveRecord
340
410
  @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
341
411
  end
342
412
 
413
+ def use_insert_returning?
414
+ @use_insert_returning
415
+ end
416
+
343
417
  def get_database_version # :nodoc:
344
- SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
418
+ SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
345
419
  end
346
420
 
347
421
  def check_version # :nodoc:
@@ -370,12 +444,9 @@ module ActiveRecord
370
444
  end
371
445
 
372
446
  TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
447
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
373
448
 
374
449
  private
375
- def type_map
376
- TYPE_MAP
377
- end
378
-
379
450
  # See https://www.sqlite.org/limits.html,
380
451
  # the default value is 999 when not configured.
381
452
  def bind_params_length
@@ -383,12 +454,43 @@ module ActiveRecord
383
454
  end
384
455
 
385
456
  def table_structure(table_name)
386
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
457
+ structure = internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
387
458
  raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
388
459
  table_structure_with_collation(table_name, structure)
389
460
  end
390
461
  alias column_definitions table_structure
391
462
 
463
+ def extract_value_from_default(default)
464
+ case default
465
+ when /^null$/i
466
+ nil
467
+ # Quoted types
468
+ when /^'([^|]*)'$/m
469
+ $1.gsub("''", "'")
470
+ # Quoted types
471
+ when /^"([^|]*)"$/m
472
+ $1.gsub('""', '"')
473
+ # Numeric types
474
+ when /\A-?\d+(\.\d*)?\z/
475
+ $&
476
+ # Binary columns
477
+ when /x'(.*)'/
478
+ [ $1 ].pack("H*")
479
+ else
480
+ # Anything else is blank or some function
481
+ # and we can't know the value of that, so return nil.
482
+ nil
483
+ end
484
+ end
485
+
486
+ def extract_default_function(default_value, default)
487
+ default if has_default_function?(default_value, default)
488
+ end
489
+
490
+ def has_default_function?(default_value, default)
491
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
492
+ end
493
+
392
494
  # See: https://www.sqlite.org/lang_altertable.html
393
495
  # SQLite has an additional restriction on the ALTER TABLE statement
394
496
  def invalid_alter_table_type?(type, options)
@@ -449,12 +551,27 @@ module ActiveRecord
449
551
  options[:rename][column.name.to_sym] ||
450
552
  column.name) : column.name
451
553
 
452
- @definition.column(column_name, column.type,
453
- limit: column.limit, default: column.default,
454
- precision: column.precision, scale: column.scale,
455
- null: column.null, collation: column.collation,
554
+ if column.has_default?
555
+ type = lookup_cast_type_from_column(column)
556
+ default = type.deserialize(column.default)
557
+ default = -> { column.default_function } if default.nil?
558
+ end
559
+
560
+ column_options = {
561
+ limit: column.limit,
562
+ precision: column.precision,
563
+ scale: column.scale,
564
+ null: column.null,
565
+ collation: column.collation,
456
566
  primary_key: column_name == from_primary_key
457
- )
567
+ }
568
+
569
+ unless column.auto_increment?
570
+ column_options[:default] = default
571
+ end
572
+
573
+ column_type = column.bigint? ? :bigint : column.type
574
+ @definition.column(column_name, column_type, **column_options)
458
575
  end
459
576
 
460
577
  yield @definition if block_given?
@@ -502,7 +619,7 @@ module ActiveRecord
502
619
  quoted_columns = columns.map { |col| quote_column_name(col) } * ","
503
620
  quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
504
621
 
505
- exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
622
+ internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
506
623
  SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
507
624
  end
508
625
 
@@ -512,22 +629,24 @@ module ActiveRecord
512
629
  # Older versions of SQLite return:
513
630
  # column *column_name* is not unique
514
631
  if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
515
- RecordNotUnique.new(message, sql: sql, binds: binds)
632
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
516
633
  elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
517
- NotNullViolation.new(message, sql: sql, binds: binds)
634
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
518
635
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
519
- InvalidForeignKey.new(message, sql: sql, binds: binds)
636
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
520
637
  elsif exception.message.match?(/called on a closed database/i)
521
- ConnectionNotEstablished.new(exception)
638
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
522
639
  else
523
640
  super
524
641
  end
525
642
  end
526
643
 
527
- COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i.freeze
644
+ COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
645
+ PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
528
646
 
529
647
  def table_structure_with_collation(table_name, basic_structure)
530
648
  collation_hash = {}
649
+ auto_increments = {}
531
650
  sql = <<~SQL
532
651
  SELECT sql FROM
533
652
  (SELECT * FROM sqlite_master UNION ALL
@@ -549,6 +668,7 @@ module ActiveRecord
549
668
  # This regex will match the column name and collation type and will save
550
669
  # the value in $1 and $2 respectively.
551
670
  collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
671
+ auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
552
672
  end
553
673
 
554
674
  basic_structure.map do |column|
@@ -558,6 +678,10 @@ module ActiveRecord
558
678
  column["collation"] = collation_hash[column_name]
559
679
  end
560
680
 
681
+ if auto_increments.has_key?(column_name)
682
+ column["auto_increment"] = true
683
+ end
684
+
561
685
  column
562
686
  end
563
687
  else
@@ -574,17 +698,54 @@ module ActiveRecord
574
698
  end
575
699
 
576
700
  def connect
577
- @connection = ::SQLite3::Database.new(
578
- @config[:database].to_s,
579
- @config.merge(results_as_hash: true)
580
- )
581
- configure_connection
701
+ @raw_connection = self.class.new_client(@connection_parameters)
702
+ rescue ConnectionNotEstablished => ex
703
+ raise ex.set_pool(@pool)
704
+ end
705
+
706
+ def reconnect
707
+ if active?
708
+ @raw_connection.rollback rescue nil
709
+ else
710
+ connect
711
+ end
582
712
  end
583
713
 
584
714
  def configure_connection
585
- @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
715
+ if @config[:timeout] && @config[:retries]
716
+ raise ArgumentError, "Cannot specify both timeout and retries arguments"
717
+ elsif @config[:timeout]
718
+ @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
719
+ elsif @config[:retries]
720
+ retries = self.class.type_cast_config_to_integer(@config[:retries])
721
+ raw_connection.busy_handler do |count|
722
+ count <= retries
723
+ end
724
+ end
586
725
 
587
- execute("PRAGMA foreign_keys = ON", "SCHEMA")
726
+ # Enforce foreign key constraints
727
+ # https://www.sqlite.org/pragma.html#pragma_foreign_keys
728
+ # https://www.sqlite.org/foreignkeys.html
729
+ raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
730
+ unless @memory_database
731
+ # Journal mode WAL allows for greater concurrency (many readers + one writer)
732
+ # https://www.sqlite.org/pragma.html#pragma_journal_mode
733
+ raw_execute("PRAGMA journal_mode = WAL", "SCHEMA")
734
+ # Set more relaxed level of database durability
735
+ # 2 = "FULL" (sync on every write), 1 = "NORMAL" (sync every 1000 written pages) and 0 = "NONE"
736
+ # https://www.sqlite.org/pragma.html#pragma_synchronous
737
+ raw_execute("PRAGMA synchronous = NORMAL", "SCHEMA")
738
+ # Set the global memory map so all processes can share some data
739
+ # https://www.sqlite.org/pragma.html#pragma_mmap_size
740
+ # https://www.sqlite.org/mmap.html
741
+ raw_execute("PRAGMA mmap_size = #{128.megabytes}", "SCHEMA")
742
+ end
743
+ # Impose a limit on the WAL file to prevent unlimited growth
744
+ # https://www.sqlite.org/pragma.html#pragma_journal_size_limit
745
+ raw_execute("PRAGMA journal_size_limit = #{64.megabytes}", "SCHEMA")
746
+ # Set the local connection cache to 2000 pages
747
+ # https://www.sqlite.org/pragma.html#pragma_cache_size
748
+ raw_execute("PRAGMA cache_size = 2000", "SCHEMA")
588
749
  end
589
750
  end
590
751
  ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
@@ -42,6 +42,13 @@ module ActiveRecord
42
42
  cache.clear
43
43
  end
44
44
 
45
+ # Clear the pool without deallocating; this is only safe when we
46
+ # know the server has independently deallocated all statements
47
+ # (e.g. due to a reconnect, or a DISCARD ALL)
48
+ def reset
49
+ cache.clear
50
+ end
51
+
45
52
  def delete(key)
46
53
  dealloc cache[key]
47
54
  cache.delete(key)
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Trilogy
6
+ module DatabaseStatements
7
+ def select_all(*, **) # :nodoc:
8
+ result = super
9
+ with_raw_connection do |conn|
10
+ conn.next_result while conn.more_results_exist?
11
+ end
12
+ result
13
+ end
14
+
15
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
16
+ sql = transform_query(sql)
17
+ check_if_write_query(sql)
18
+ mark_transaction_written_if_write(sql)
19
+
20
+ result = raw_execute(sql, name, async: async)
21
+ ActiveRecord::Result.new(result.fields, result.to_a)
22
+ end
23
+
24
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil, returning: nil) # :nodoc:
25
+ sql = transform_query(sql)
26
+ check_if_write_query(sql)
27
+ mark_transaction_written_if_write(sql)
28
+
29
+ raw_execute(to_sql(sql, binds), name)
30
+ end
31
+
32
+ def exec_delete(sql, name = nil, binds = []) # :nodoc:
33
+ sql = transform_query(sql)
34
+ check_if_write_query(sql)
35
+ mark_transaction_written_if_write(sql)
36
+
37
+ result = raw_execute(to_sql(sql, binds), name)
38
+ result.affected_rows
39
+ end
40
+
41
+ alias :exec_update :exec_delete # :nodoc:
42
+
43
+ private
44
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
45
+ log(sql, name, async: async) do
46
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
47
+ sync_timezone_changes(conn)
48
+ result = conn.query(sql)
49
+ handle_warnings(sql)
50
+ result
51
+ end
52
+ end
53
+ end
54
+
55
+ def last_inserted_id(result)
56
+ result.last_insert_id
57
+ end
58
+
59
+ def sync_timezone_changes(conn)
60
+ # Sync any changes since connection last established.
61
+ if default_timezone == :local
62
+ conn.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
63
+ else
64
+ conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
65
+ end
66
+ end
67
+
68
+ def execute_batch(statements, name = nil)
69
+ statements = statements.map { |sql| transform_query(sql) }
70
+ combine_multi_statements(statements).each do |statement|
71
+ with_raw_connection do |conn|
72
+ raw_execute(statement, name)
73
+ conn.next_result while conn.more_results_exist?
74
+ end
75
+ end
76
+ end
77
+
78
+ def multi_statements_enabled?
79
+ !!@config[:multi_statement]
80
+ end
81
+
82
+ def with_multi_statements
83
+ if multi_statements_enabled?
84
+ return yield
85
+ end
86
+
87
+ with_raw_connection do |conn|
88
+ conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
89
+
90
+ yield
91
+ ensure
92
+ conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end