activerecord 7.0.8 → 7.1.3.4

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 (231) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1554 -1452
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -16
  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 +15 -9
  15. data/lib/active_record/associations/collection_proxy.rb +15 -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.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +31 -7
  22. data/lib/active_record/associations/preloader.rb +13 -10
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +313 -217
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +74 -51
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -124
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
  71. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/quoting.rb +10 -6
  75. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  76. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  77. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  78. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
  80. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  81. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  82. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  83. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  84. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  85. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  86. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  87. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +209 -79
  88. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  89. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  90. data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
  91. data/lib/active_record/connection_adapters.rb +3 -1
  92. data/lib/active_record/connection_handling.rb +72 -95
  93. data/lib/active_record/core.rb +175 -153
  94. data/lib/active_record/counter_cache.rb +46 -25
  95. data/lib/active_record/database_configurations/database_config.rb +9 -3
  96. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  97. data/lib/active_record/database_configurations/url_config.rb +17 -11
  98. data/lib/active_record/database_configurations.rb +86 -33
  99. data/lib/active_record/delegated_type.rb +9 -4
  100. data/lib/active_record/deprecator.rb +7 -0
  101. data/lib/active_record/destroy_association_async_job.rb +2 -0
  102. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  103. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  104. data/lib/active_record/encryption/config.rb +25 -1
  105. data/lib/active_record/encryption/configurable.rb +12 -19
  106. data/lib/active_record/encryption/context.rb +10 -3
  107. data/lib/active_record/encryption/contexts.rb +5 -1
  108. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  109. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
  111. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  112. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  113. data/lib/active_record/encryption/key_generator.rb +12 -1
  114. data/lib/active_record/encryption/message_serializer.rb +2 -0
  115. data/lib/active_record/encryption/properties.rb +3 -3
  116. data/lib/active_record/encryption/scheme.rb +19 -22
  117. data/lib/active_record/encryption.rb +1 -0
  118. data/lib/active_record/enum.rb +112 -28
  119. data/lib/active_record/errors.rb +112 -18
  120. data/lib/active_record/explain.rb +23 -3
  121. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  122. data/lib/active_record/fixture_set/render_context.rb +2 -0
  123. data/lib/active_record/fixture_set/table_row.rb +29 -8
  124. data/lib/active_record/fixtures.rb +135 -71
  125. data/lib/active_record/future_result.rb +31 -5
  126. data/lib/active_record/gem_version.rb +4 -4
  127. data/lib/active_record/inheritance.rb +30 -16
  128. data/lib/active_record/insert_all.rb +57 -10
  129. data/lib/active_record/integration.rb +8 -8
  130. data/lib/active_record/internal_metadata.rb +120 -30
  131. data/lib/active_record/locking/pessimistic.rb +5 -2
  132. data/lib/active_record/log_subscriber.rb +29 -12
  133. data/lib/active_record/marshalling.rb +56 -0
  134. data/lib/active_record/message_pack.rb +124 -0
  135. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  136. data/lib/active_record/middleware/database_selector.rb +6 -8
  137. data/lib/active_record/middleware/shard_selector.rb +3 -1
  138. data/lib/active_record/migration/command_recorder.rb +104 -5
  139. data/lib/active_record/migration/compatibility.rb +139 -5
  140. data/lib/active_record/migration/default_strategy.rb +23 -0
  141. data/lib/active_record/migration/execution_strategy.rb +19 -0
  142. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  143. data/lib/active_record/migration.rb +219 -111
  144. data/lib/active_record/model_schema.rb +64 -44
  145. data/lib/active_record/nested_attributes.rb +24 -6
  146. data/lib/active_record/normalization.rb +167 -0
  147. data/lib/active_record/persistence.rb +188 -37
  148. data/lib/active_record/promise.rb +84 -0
  149. data/lib/active_record/query_cache.rb +3 -21
  150. data/lib/active_record/query_logs.rb +77 -52
  151. data/lib/active_record/query_logs_formatter.rb +41 -0
  152. data/lib/active_record/querying.rb +15 -2
  153. data/lib/active_record/railtie.rb +109 -47
  154. data/lib/active_record/railties/controller_runtime.rb +12 -6
  155. data/lib/active_record/railties/databases.rake +142 -148
  156. data/lib/active_record/railties/job_runtime.rb +23 -0
  157. data/lib/active_record/readonly_attributes.rb +32 -5
  158. data/lib/active_record/reflection.rb +174 -44
  159. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  160. data/lib/active_record/relation/batches.rb +190 -61
  161. data/lib/active_record/relation/calculations.rb +187 -63
  162. data/lib/active_record/relation/delegation.rb +23 -9
  163. data/lib/active_record/relation/finder_methods.rb +77 -16
  164. data/lib/active_record/relation/merger.rb +2 -0
  165. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  167. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  168. data/lib/active_record/relation/predicate_builder.rb +26 -14
  169. data/lib/active_record/relation/query_attribute.rb +2 -1
  170. data/lib/active_record/relation/query_methods.rb +352 -63
  171. data/lib/active_record/relation/spawn_methods.rb +18 -1
  172. data/lib/active_record/relation.rb +91 -35
  173. data/lib/active_record/result.rb +19 -5
  174. data/lib/active_record/runtime_registry.rb +24 -1
  175. data/lib/active_record/sanitization.rb +51 -11
  176. data/lib/active_record/schema.rb +2 -3
  177. data/lib/active_record/schema_dumper.rb +46 -7
  178. data/lib/active_record/schema_migration.rb +68 -33
  179. data/lib/active_record/scoping/default.rb +15 -5
  180. data/lib/active_record/scoping/named.rb +2 -2
  181. data/lib/active_record/scoping.rb +2 -1
  182. data/lib/active_record/secure_password.rb +60 -0
  183. data/lib/active_record/secure_token.rb +21 -3
  184. data/lib/active_record/signed_id.rb +7 -5
  185. data/lib/active_record/store.rb +8 -8
  186. data/lib/active_record/suppressor.rb +3 -1
  187. data/lib/active_record/table_metadata.rb +10 -1
  188. data/lib/active_record/tasks/database_tasks.rb +127 -105
  189. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  190. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  191. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  192. data/lib/active_record/test_fixtures.rb +113 -96
  193. data/lib/active_record/timestamp.rb +27 -15
  194. data/lib/active_record/token_for.rb +113 -0
  195. data/lib/active_record/touch_later.rb +11 -6
  196. data/lib/active_record/transactions.rb +36 -10
  197. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  198. data/lib/active_record/type/internal/timezone.rb +7 -2
  199. data/lib/active_record/type/time.rb +4 -0
  200. data/lib/active_record/validations/absence.rb +1 -1
  201. data/lib/active_record/validations/numericality.rb +5 -4
  202. data/lib/active_record/validations/presence.rb +5 -28
  203. data/lib/active_record/validations/uniqueness.rb +47 -2
  204. data/lib/active_record/validations.rb +8 -4
  205. data/lib/active_record/version.rb +1 -1
  206. data/lib/active_record.rb +121 -16
  207. data/lib/arel/errors.rb +10 -0
  208. data/lib/arel/factory_methods.rb +4 -0
  209. data/lib/arel/nodes/binary.rb +6 -1
  210. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  211. data/lib/arel/nodes/cte.rb +36 -0
  212. data/lib/arel/nodes/fragments.rb +35 -0
  213. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  214. data/lib/arel/nodes/leading_join.rb +8 -0
  215. data/lib/arel/nodes/node.rb +111 -2
  216. data/lib/arel/nodes/sql_literal.rb +6 -0
  217. data/lib/arel/nodes/table_alias.rb +4 -0
  218. data/lib/arel/nodes.rb +4 -0
  219. data/lib/arel/predications.rb +2 -0
  220. data/lib/arel/table.rb +9 -5
  221. data/lib/arel/visitors/mysql.rb +8 -1
  222. data/lib/arel/visitors/to_sql.rb +81 -17
  223. data/lib/arel/visitors/visitor.rb +2 -2
  224. data/lib/arel.rb +16 -2
  225. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  226. data/lib/rails/generators/active_record/migration.rb +3 -1
  227. data/lib/rails/generators/active_record/model/USAGE +113 -0
  228. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  229. metadata +48 -12
  230. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  231. data/lib/active_record/null_relation.rb +0 -63
@@ -8,7 +8,7 @@ module ActiveRecord
8
8
  QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
9
9
 
10
10
  def quote_string(s)
11
- @connection.class.quote(s)
11
+ ::SQLite3::Database.quote(s)
12
12
  end
13
13
 
14
14
  def quote_table_name_for_assignment(table, attr)
@@ -89,7 +89,7 @@ module ActiveRecord
89
89
  (
90
90
  (?:
91
91
  # "table_name"."column_name" | function(one or no argument)
92
- ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
92
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
93
93
  )
94
94
  (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
95
95
  )
@@ -102,8 +102,9 @@ module ActiveRecord
102
102
  (
103
103
  (?:
104
104
  # "table_name"."column_name" | function(one or no argument)
105
- ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
105
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
106
106
  )
107
+ (?:\s+COLLATE\s+(?:\w+|"\w+"))?
107
108
  (?:\s+ASC|\s+DESC)?
108
109
  )
109
110
  (?:\s*,\s*\g<1>)*
@@ -3,6 +3,7 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLite3
6
+ # = Active Record SQLite3 Adapter \Table Definition
6
7
  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
7
8
  def change_column(column_name, type, **options)
8
9
  name = column_name.to_s
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  module SchemaStatements # :nodoc:
7
7
  # Returns an array of indexes for the given table.
8
8
  def indexes(table_name)
9
- exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").filter_map do |row|
9
+ internal_exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").filter_map do |row|
10
10
  # Indexes SQLite creates implicitly for internal use start with "sqlite_".
11
11
  # See https://www.sqlite.org/fileformat2.html#intschema
12
12
  next if row["name"].start_with?("sqlite_")
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
 
24
24
  /\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?(?:\s*\/\*.*\*\/)?\z/i =~ index_sql
25
25
 
26
- columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
26
+ columns = internal_exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
27
27
  col["name"]
28
28
  end
29
29
 
@@ -102,7 +102,9 @@ module ActiveRecord
102
102
  end
103
103
  end
104
104
 
105
- def remove_check_constraint(table_name, expression = nil, **options)
105
+ def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
106
+ return if if_exists && !check_constraint_exists?(table_name, **options)
107
+
106
108
  check_constraints = check_constraints(table_name)
107
109
  chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
108
110
  check_constraints.delete_if { |chk| chk.name == chk_name_to_delete }
@@ -113,9 +115,13 @@ module ActiveRecord
113
115
  SQLite3::SchemaDumper.create(self, options)
114
116
  end
115
117
 
118
+ def schema_creation # :nodoc
119
+ SQLite3::SchemaCreation.new(self)
120
+ end
121
+
116
122
  private
117
- def schema_creation
118
- SQLite3::SchemaCreation.new(self)
123
+ def valid_table_definition_options
124
+ super + [:rename]
119
125
  end
120
126
 
121
127
  def create_table_definition(name, **options)
@@ -126,12 +132,13 @@ module ActiveRecord
126
132
  super unless internal
127
133
  end
128
134
 
129
- def new_column_from_field(table_name, field)
135
+ def new_column_from_field(table_name, field, definitions)
130
136
  default = field["dflt_value"]
131
137
 
132
138
  type_metadata = fetch_type_metadata(field["type"])
133
139
  default_value = extract_value_from_default(default)
134
140
  default_function = extract_default_function(default_value, default)
141
+ rowid = is_column_the_rowid?(field, definitions)
135
142
 
136
143
  Column.new(
137
144
  field["name"],
@@ -139,10 +146,22 @@ module ActiveRecord
139
146
  type_metadata,
140
147
  field["notnull"].to_i == 0,
141
148
  default_function,
142
- collation: field["collation"]
149
+ collation: field["collation"],
150
+ auto_increment: field["auto_increment"],
151
+ rowid: rowid
143
152
  )
144
153
  end
145
154
 
155
+ INTEGER_REGEX = /integer/i
156
+ # if a rowid table has a primary key that consists of a single column
157
+ # and the declared type of that column is "INTEGER" in any mixture of upper and lower case,
158
+ # then the column becomes an alias for the rowid.
159
+ def is_column_the_rowid?(field, column_definitions)
160
+ return false unless INTEGER_REGEX.match?(field["type"]) && field["pk"] == 1
161
+ # is the primary key a single column?
162
+ column_definitions.one? { |c| c["pk"] > 0 }
163
+ end
164
+
146
165
  def data_source_sql(name = nil, type: nil)
147
166
  scope = quoted_scope(name, type: type)
148
167
  scope[:type] ||= "'table','view'"
@@ -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
@@ -297,20 +347,42 @@ module ActiveRecord
297
347
  rename_column_indexes(table_name, column.name, new_column_name)
298
348
  end
299
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
+
300
363
  def add_reference(table_name, ref_name, **options) # :nodoc:
301
364
  super(table_name, ref_name, type: :integer, **options)
302
365
  end
303
366
  alias :add_belongs_to :add_reference
304
367
 
305
368
  def foreign_keys(table_name)
306
- fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
307
- 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
308
374
  options = {
309
- column: row["from"],
310
- primary_key: row["to"],
311
375
  on_delete: extract_foreign_key_action(row["on_delete"]),
312
376
  on_update: extract_foreign_key_action(row["on_update"])
313
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
314
386
  ForeignKeyDefinition.new(table_name, row["table"], options)
315
387
  end
316
388
  end
@@ -330,6 +402,7 @@ module ActiveRecord
330
402
  end
331
403
  end
332
404
 
405
+ sql << " RETURNING #{insert.returning}" if insert.returning
333
406
  sql
334
407
  end
335
408
 
@@ -337,6 +410,10 @@ module ActiveRecord
337
410
  @config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
338
411
  end
339
412
 
413
+ def use_insert_returning?
414
+ @use_insert_returning
415
+ end
416
+
340
417
  def get_database_version # :nodoc:
341
418
  SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
342
419
  end
@@ -367,12 +444,9 @@ module ActiveRecord
367
444
  end
368
445
 
369
446
  TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
447
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
370
448
 
371
449
  private
372
- def type_map
373
- TYPE_MAP
374
- end
375
-
376
450
  # See https://www.sqlite.org/limits.html,
377
451
  # the default value is 999 when not configured.
378
452
  def bind_params_length
@@ -380,7 +454,7 @@ module ActiveRecord
380
454
  end
381
455
 
382
456
  def table_structure(table_name)
383
- 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")
384
458
  raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
385
459
  table_structure_with_collation(table_name, structure)
386
460
  end
@@ -391,14 +465,17 @@ module ActiveRecord
391
465
  when /^null$/i
392
466
  nil
393
467
  # Quoted types
394
- when /^'(.*)'$/m
468
+ when /^'([^|]*)'$/m
395
469
  $1.gsub("''", "'")
396
470
  # Quoted types
397
- when /^"(.*)"$/m
471
+ when /^"([^|]*)"$/m
398
472
  $1.gsub('""', '"')
399
473
  # Numeric types
400
474
  when /\A-?\d+(\.\d*)?\z/
401
475
  $&
476
+ # Binary columns
477
+ when /x'(.*)'/
478
+ [ $1 ].pack("H*")
402
479
  else
403
480
  # Anything else is blank or some function
404
481
  # and we can't know the value of that, so return nil.
@@ -411,7 +488,7 @@ module ActiveRecord
411
488
  end
412
489
 
413
490
  def has_default_function?(default_value, default)
414
- !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)
415
492
  end
416
493
 
417
494
  # See: https://www.sqlite.org/lang_altertable.html
@@ -480,12 +557,21 @@ module ActiveRecord
480
557
  default = -> { column.default_function } if default.nil?
481
558
  end
482
559
 
483
- @definition.column(column_name, column.type,
484
- limit: column.limit, default: default,
485
- precision: column.precision, scale: column.scale,
486
- 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,
487
566
  primary_key: column_name == from_primary_key
488
- )
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)
489
575
  end
490
576
 
491
577
  yield @definition if block_given?
@@ -533,7 +619,7 @@ module ActiveRecord
533
619
  quoted_columns = columns.map { |col| quote_column_name(col) } * ","
534
620
  quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
535
621
 
536
- exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
622
+ internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
537
623
  SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
538
624
  end
539
625
 
@@ -543,22 +629,24 @@ module ActiveRecord
543
629
  # Older versions of SQLite return:
544
630
  # column *column_name* is not unique
545
631
  if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
546
- RecordNotUnique.new(message, sql: sql, binds: binds)
632
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
547
633
  elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
548
- NotNullViolation.new(message, sql: sql, binds: binds)
634
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
549
635
  elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
550
- InvalidForeignKey.new(message, sql: sql, binds: binds)
636
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
551
637
  elsif exception.message.match?(/called on a closed database/i)
552
- ConnectionNotEstablished.new(exception)
638
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
553
639
  else
554
640
  super
555
641
  end
556
642
  end
557
643
 
558
- 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
559
646
 
560
647
  def table_structure_with_collation(table_name, basic_structure)
561
648
  collation_hash = {}
649
+ auto_increments = {}
562
650
  sql = <<~SQL
563
651
  SELECT sql FROM
564
652
  (SELECT * FROM sqlite_master UNION ALL
@@ -580,6 +668,7 @@ module ActiveRecord
580
668
  # This regex will match the column name and collation type and will save
581
669
  # the value in $1 and $2 respectively.
582
670
  collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
671
+ auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
583
672
  end
584
673
 
585
674
  basic_structure.map do |column|
@@ -589,6 +678,10 @@ module ActiveRecord
589
678
  column["collation"] = collation_hash[column_name]
590
679
  end
591
680
 
681
+ if auto_increments.has_key?(column_name)
682
+ column["auto_increment"] = true
683
+ end
684
+
592
685
  column
593
686
  end
594
687
  else
@@ -605,17 +698,54 @@ module ActiveRecord
605
698
  end
606
699
 
607
700
  def connect
608
- @connection = ::SQLite3::Database.new(
609
- @config[:database].to_s,
610
- @config.merge(results_as_hash: true)
611
- )
612
- 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
613
712
  end
614
713
 
615
714
  def configure_connection
616
- @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
617
725
 
618
- 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")
619
749
  end
620
750
  end
621
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)