activerecord 7.0.4 → 7.1.5.1

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 (246) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1971 -1243
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -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 +20 -4
  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 +20 -14
  15. data/lib/active_record/associations/collection_proxy.rb +20 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  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/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader.rb +13 -10
  25. data/lib/active_record/associations/singular_association.rb +1 -1
  26. data/lib/active_record/associations/through_association.rb +22 -11
  27. data/lib/active_record/associations.rb +333 -222
  28. data/lib/active_record/attribute_assignment.rb +0 -2
  29. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  30. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  31. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  32. data/lib/active_record/attribute_methods/query.rb +28 -16
  33. data/lib/active_record/attribute_methods/read.rb +21 -8
  34. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -4
  36. data/lib/active_record/attribute_methods/write.rb +6 -6
  37. data/lib/active_record/attribute_methods.rb +148 -26
  38. data/lib/active_record/attributes.rb +3 -3
  39. data/lib/active_record/autosave_association.rb +59 -10
  40. data/lib/active_record/base.rb +7 -2
  41. data/lib/active_record/callbacks.rb +16 -32
  42. data/lib/active_record/coders/column_serializer.rb +61 -0
  43. data/lib/active_record/coders/json.rb +1 -1
  44. data/lib/active_record/coders/yaml_column.rb +70 -42
  45. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  47. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -7
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +155 -25
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +297 -127
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -103
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +254 -125
  60. data/lib/active_record/connection_adapters/column.rb +9 -0
  61. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -14
  64. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  71. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  72. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  74. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  81. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  82. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  83. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +365 -61
  85. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
  87. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  88. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  91. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +213 -85
  94. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  97. data/lib/active_record/connection_adapters.rb +3 -1
  98. data/lib/active_record/connection_handling.rb +72 -95
  99. data/lib/active_record/core.rb +181 -154
  100. data/lib/active_record/counter_cache.rb +52 -27
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  102. data/lib/active_record/database_configurations/database_config.rb +9 -3
  103. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  104. data/lib/active_record/database_configurations/url_config.rb +17 -11
  105. data/lib/active_record/database_configurations.rb +86 -33
  106. data/lib/active_record/delegated_type.rb +15 -10
  107. data/lib/active_record/deprecator.rb +7 -0
  108. data/lib/active_record/destroy_association_async_job.rb +3 -1
  109. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  110. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  111. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  112. data/lib/active_record/encryption/config.rb +25 -1
  113. data/lib/active_record/encryption/configurable.rb +12 -19
  114. data/lib/active_record/encryption/context.rb +10 -3
  115. data/lib/active_record/encryption/contexts.rb +5 -1
  116. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  117. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  121. data/lib/active_record/encryption/key_generator.rb +12 -1
  122. data/lib/active_record/encryption/message_serializer.rb +2 -0
  123. data/lib/active_record/encryption/properties.rb +3 -3
  124. data/lib/active_record/encryption/scheme.rb +22 -21
  125. data/lib/active_record/encryption.rb +3 -0
  126. data/lib/active_record/enum.rb +112 -28
  127. data/lib/active_record/errors.rb +112 -18
  128. data/lib/active_record/explain.rb +23 -3
  129. data/lib/active_record/explain_subscriber.rb +1 -1
  130. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  131. data/lib/active_record/fixture_set/render_context.rb +2 -0
  132. data/lib/active_record/fixture_set/table_row.rb +29 -8
  133. data/lib/active_record/fixtures.rb +135 -71
  134. data/lib/active_record/future_result.rb +40 -5
  135. data/lib/active_record/gem_version.rb +4 -4
  136. data/lib/active_record/inheritance.rb +30 -16
  137. data/lib/active_record/insert_all.rb +57 -10
  138. data/lib/active_record/integration.rb +8 -8
  139. data/lib/active_record/internal_metadata.rb +120 -30
  140. data/lib/active_record/locking/optimistic.rb +33 -19
  141. data/lib/active_record/locking/pessimistic.rb +5 -2
  142. data/lib/active_record/log_subscriber.rb +29 -12
  143. data/lib/active_record/marshalling.rb +59 -0
  144. data/lib/active_record/message_pack.rb +124 -0
  145. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  146. data/lib/active_record/middleware/database_selector.rb +9 -11
  147. data/lib/active_record/middleware/shard_selector.rb +3 -1
  148. data/lib/active_record/migration/command_recorder.rb +105 -7
  149. data/lib/active_record/migration/compatibility.rb +163 -58
  150. data/lib/active_record/migration/default_strategy.rb +23 -0
  151. data/lib/active_record/migration/execution_strategy.rb +19 -0
  152. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  153. data/lib/active_record/migration.rb +271 -114
  154. data/lib/active_record/model_schema.rb +69 -44
  155. data/lib/active_record/nested_attributes.rb +37 -8
  156. data/lib/active_record/normalization.rb +167 -0
  157. data/lib/active_record/persistence.rb +195 -42
  158. data/lib/active_record/promise.rb +84 -0
  159. data/lib/active_record/query_cache.rb +4 -22
  160. data/lib/active_record/query_logs.rb +87 -51
  161. data/lib/active_record/query_logs_formatter.rb +41 -0
  162. data/lib/active_record/querying.rb +15 -2
  163. data/lib/active_record/railtie.rb +107 -45
  164. data/lib/active_record/railties/controller_runtime.rb +14 -9
  165. data/lib/active_record/railties/databases.rake +144 -150
  166. data/lib/active_record/railties/job_runtime.rb +23 -0
  167. data/lib/active_record/readonly_attributes.rb +32 -5
  168. data/lib/active_record/reflection.rb +189 -45
  169. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  170. data/lib/active_record/relation/batches.rb +190 -61
  171. data/lib/active_record/relation/calculations.rb +232 -81
  172. data/lib/active_record/relation/delegation.rb +23 -9
  173. data/lib/active_record/relation/finder_methods.rb +77 -16
  174. data/lib/active_record/relation/merger.rb +2 -0
  175. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  177. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  178. data/lib/active_record/relation/predicate_builder.rb +26 -14
  179. data/lib/active_record/relation/query_attribute.rb +25 -1
  180. data/lib/active_record/relation/query_methods.rb +408 -76
  181. data/lib/active_record/relation/spawn_methods.rb +18 -1
  182. data/lib/active_record/relation.rb +103 -37
  183. data/lib/active_record/result.rb +25 -9
  184. data/lib/active_record/runtime_registry.rb +24 -1
  185. data/lib/active_record/sanitization.rb +51 -11
  186. data/lib/active_record/schema.rb +2 -3
  187. data/lib/active_record/schema_dumper.rb +50 -7
  188. data/lib/active_record/schema_migration.rb +68 -33
  189. data/lib/active_record/scoping/default.rb +15 -5
  190. data/lib/active_record/scoping/named.rb +2 -2
  191. data/lib/active_record/scoping.rb +2 -1
  192. data/lib/active_record/secure_password.rb +60 -0
  193. data/lib/active_record/secure_token.rb +21 -3
  194. data/lib/active_record/signed_id.rb +7 -5
  195. data/lib/active_record/store.rb +9 -9
  196. data/lib/active_record/suppressor.rb +3 -1
  197. data/lib/active_record/table_metadata.rb +16 -3
  198. data/lib/active_record/tasks/database_tasks.rb +152 -108
  199. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  200. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  201. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  202. data/lib/active_record/test_fixtures.rb +114 -96
  203. data/lib/active_record/timestamp.rb +30 -16
  204. data/lib/active_record/token_for.rb +113 -0
  205. data/lib/active_record/touch_later.rb +11 -6
  206. data/lib/active_record/transactions.rb +39 -13
  207. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  208. data/lib/active_record/type/internal/timezone.rb +7 -2
  209. data/lib/active_record/type/serialized.rb +8 -4
  210. data/lib/active_record/type/time.rb +4 -0
  211. data/lib/active_record/validations/absence.rb +1 -1
  212. data/lib/active_record/validations/numericality.rb +5 -4
  213. data/lib/active_record/validations/presence.rb +5 -28
  214. data/lib/active_record/validations/uniqueness.rb +47 -2
  215. data/lib/active_record/validations.rb +8 -4
  216. data/lib/active_record/version.rb +1 -1
  217. data/lib/active_record.rb +130 -17
  218. data/lib/arel/errors.rb +10 -0
  219. data/lib/arel/factory_methods.rb +4 -0
  220. data/lib/arel/filter_predications.rb +1 -1
  221. data/lib/arel/nodes/and.rb +4 -0
  222. data/lib/arel/nodes/binary.rb +6 -1
  223. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  224. data/lib/arel/nodes/cte.rb +36 -0
  225. data/lib/arel/nodes/filter.rb +1 -1
  226. data/lib/arel/nodes/fragments.rb +35 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  228. data/lib/arel/nodes/leading_join.rb +8 -0
  229. data/lib/arel/nodes/node.rb +111 -2
  230. data/lib/arel/nodes/sql_literal.rb +6 -0
  231. data/lib/arel/nodes/table_alias.rb +4 -0
  232. data/lib/arel/nodes.rb +4 -0
  233. data/lib/arel/predications.rb +2 -0
  234. data/lib/arel/table.rb +9 -5
  235. data/lib/arel/tree_manager.rb +5 -1
  236. data/lib/arel/visitors/mysql.rb +8 -1
  237. data/lib/arel/visitors/to_sql.rb +83 -18
  238. data/lib/arel/visitors/visitor.rb +2 -2
  239. data/lib/arel.rb +16 -2
  240. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  241. data/lib/rails/generators/active_record/migration.rb +3 -1
  242. data/lib/rails/generators/active_record/model/USAGE +113 -0
  243. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  244. metadata +51 -15
  245. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  246. 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"
@@ -10,44 +11,23 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
10
11
  require "active_record/connection_adapters/sqlite3/schema_dumper"
11
12
  require "active_record/connection_adapters/sqlite3/schema_statements"
12
13
 
13
- gem "sqlite3", "~> 1.4"
14
+ gem "sqlite3", ">= 1.4"
14
15
  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,11 +281,12 @@ 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)}"
241
- rename_table_indexes(table_name, new_name)
289
+ rename_table_indexes(table_name, new_name, **options)
242
290
  end
243
291
 
244
292
  def add_column(table_name, column_name, type, **options) # :nodoc:
@@ -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,6 +410,10 @@ 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
418
  SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
345
419
  end
@@ -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,7 +454,7 @@ 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
@@ -394,14 +465,17 @@ module ActiveRecord
394
465
  when /^null$/i
395
466
  nil
396
467
  # Quoted types
397
- when /^'(.*)'$/m
468
+ when /^'([^|]*)'$/m
398
469
  $1.gsub("''", "'")
399
470
  # Quoted types
400
- when /^"(.*)"$/m
471
+ when /^"([^|]*)"$/m
401
472
  $1.gsub('""', '"')
402
473
  # Numeric types
403
474
  when /\A-?\d+(\.\d*)?\z/
404
475
  $&
476
+ # Binary columns
477
+ when /x'(.*)'/
478
+ [ $1 ].pack("H*")
405
479
  else
406
480
  # Anything else is blank or some function
407
481
  # and we can't know the value of that, so return nil.
@@ -414,7 +488,7 @@ module ActiveRecord
414
488
  end
415
489
 
416
490
  def has_default_function?(default_value, default)
417
- !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
491
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
418
492
  end
419
493
 
420
494
  # See: https://www.sqlite.org/lang_altertable.html
@@ -480,14 +554,24 @@ module ActiveRecord
480
554
  if column.has_default?
481
555
  type = lookup_cast_type_from_column(column)
482
556
  default = type.deserialize(column.default)
557
+ default = -> { column.default_function } if default.nil?
483
558
  end
484
559
 
485
- @definition.column(column_name, column.type,
486
- limit: column.limit, default: default,
487
- precision: column.precision, scale: column.scale,
488
- null: column.null, collation: column.collation,
560
+ column_options = {
561
+ limit: column.limit,
562
+ precision: column.precision,
563
+ scale: column.scale,
564
+ null: column.null,
565
+ collation: column.collation,
489
566
  primary_key: column_name == from_primary_key
490
- )
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)
491
575
  end
492
576
 
493
577
  yield @definition if block_given?
@@ -535,7 +619,7 @@ module ActiveRecord
535
619
  quoted_columns = columns.map { |col| quote_column_name(col) } * ","
536
620
  quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
537
621
 
538
- exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
622
+ internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
539
623
  SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
540
624
  end
541
625
 
@@ -545,22 +629,24 @@ module ActiveRecord
545
629
  # Older versions of SQLite return:
546
630
  # column *column_name* is not unique
547
631
  if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
548
- RecordNotUnique.new(message, sql: sql, binds: binds)
632
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
549
633
  elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
550
- NotNullViolation.new(message, sql: sql, binds: binds)
634
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
551
635
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
552
- InvalidForeignKey.new(message, sql: sql, binds: binds)
636
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
553
637
  elsif exception.message.match?(/called on a closed database/i)
554
- ConnectionNotEstablished.new(exception)
638
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
555
639
  else
556
640
  super
557
641
  end
558
642
  end
559
643
 
560
- 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
561
646
 
562
647
  def table_structure_with_collation(table_name, basic_structure)
563
648
  collation_hash = {}
649
+ auto_increments = {}
564
650
  sql = <<~SQL
565
651
  SELECT sql FROM
566
652
  (SELECT * FROM sqlite_master UNION ALL
@@ -582,6 +668,7 @@ module ActiveRecord
582
668
  # This regex will match the column name and collation type and will save
583
669
  # the value in $1 and $2 respectively.
584
670
  collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
671
+ auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
585
672
  end
586
673
 
587
674
  basic_structure.map do |column|
@@ -591,6 +678,10 @@ module ActiveRecord
591
678
  column["collation"] = collation_hash[column_name]
592
679
  end
593
680
 
681
+ if auto_increments.has_key?(column_name)
682
+ column["auto_increment"] = true
683
+ end
684
+
594
685
  column
595
686
  end
596
687
  else
@@ -607,17 +698,54 @@ module ActiveRecord
607
698
  end
608
699
 
609
700
  def connect
610
- @connection = ::SQLite3::Database.new(
611
- @config[:database].to_s,
612
- @config.merge(results_as_hash: true)
613
- )
614
- 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
615
712
  end
616
713
 
617
714
  def configure_connection
618
- @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
619
725
 
620
- 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")
621
749
  end
622
750
  end
623
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,99 @@
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
+ verified!
50
+ handle_warnings(sql)
51
+ result
52
+ end
53
+ end
54
+ end
55
+
56
+ def last_inserted_id(result)
57
+ result.last_insert_id
58
+ end
59
+
60
+ def sync_timezone_changes(conn)
61
+ # Sync any changes since connection last established.
62
+ if default_timezone == :local
63
+ conn.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
64
+ else
65
+ conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
66
+ end
67
+ end
68
+
69
+ def execute_batch(statements, name = nil)
70
+ statements = statements.map { |sql| transform_query(sql) }
71
+ combine_multi_statements(statements).each do |statement|
72
+ with_raw_connection do |conn|
73
+ raw_execute(statement, name)
74
+ conn.next_result while conn.more_results_exist?
75
+ end
76
+ end
77
+ end
78
+
79
+ def multi_statements_enabled?
80
+ !!@config[:multi_statement]
81
+ end
82
+
83
+ def with_multi_statements
84
+ if multi_statements_enabled?
85
+ return yield
86
+ end
87
+
88
+ with_raw_connection do |conn|
89
+ conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
90
+
91
+ yield
92
+ ensure
93
+ conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end