activerecord 7.1.5.1 → 8.0.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +369 -2484
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +2 -1
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +21 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +7 -6
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  13. data/lib/active_record/associations/builder/has_many.rb +3 -4
  14. data/lib/active_record/associations/builder/has_one.rb +3 -4
  15. data/lib/active_record/associations/collection_association.rb +17 -9
  16. data/lib/active_record/associations/collection_proxy.rb +14 -1
  17. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  18. data/lib/active_record/associations/errors.rb +265 -0
  19. data/lib/active_record/associations/has_many_association.rb +1 -1
  20. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  21. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +4 -3
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +14 -3
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +92 -295
  29. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  30. data/lib/active_record/attribute_assignment.rb +0 -2
  31. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  32. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  33. data/lib/active_record/attribute_methods/read.rb +1 -13
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
  36. data/lib/active_record/attribute_methods.rb +71 -75
  37. data/lib/active_record/attributes.rb +63 -49
  38. data/lib/active_record/autosave_association.rb +92 -57
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/callbacks.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
  42. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
  48. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  49. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  50. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
  51. data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
  52. data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
  53. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
  60. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
  73. data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
  74. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  75. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  77. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  78. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  79. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
  80. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
  81. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
  82. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  83. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
  84. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  85. data/lib/active_record/connection_adapters.rb +65 -0
  86. data/lib/active_record/connection_handling.rb +74 -37
  87. data/lib/active_record/core.rb +132 -51
  88. data/lib/active_record/counter_cache.rb +19 -10
  89. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  90. data/lib/active_record/database_configurations/database_config.rb +23 -4
  91. data/lib/active_record/database_configurations/hash_config.rb +46 -34
  92. data/lib/active_record/database_configurations/url_config.rb +20 -1
  93. data/lib/active_record/database_configurations.rb +1 -1
  94. data/lib/active_record/delegated_type.rb +41 -17
  95. data/lib/active_record/dynamic_matchers.rb +2 -2
  96. data/lib/active_record/encryption/config.rb +3 -1
  97. data/lib/active_record/encryption/encryptable_record.rb +7 -7
  98. data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
  99. data/lib/active_record/encryption/encryptor.rb +28 -6
  100. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  101. data/lib/active_record/encryption/key_provider.rb +1 -1
  102. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  103. data/lib/active_record/encryption/message_serializer.rb +4 -0
  104. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  105. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  106. data/lib/active_record/encryption/scheme.rb +8 -1
  107. data/lib/active_record/enum.rb +20 -16
  108. data/lib/active_record/errors.rb +54 -20
  109. data/lib/active_record/explain.rb +13 -24
  110. data/lib/active_record/fixtures.rb +37 -33
  111. data/lib/active_record/future_result.rb +21 -13
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +4 -2
  114. data/lib/active_record/insert_all.rb +19 -16
  115. data/lib/active_record/integration.rb +4 -1
  116. data/lib/active_record/internal_metadata.rb +48 -34
  117. data/lib/active_record/locking/optimistic.rb +8 -7
  118. data/lib/active_record/log_subscriber.rb +5 -32
  119. data/lib/active_record/message_pack.rb +1 -1
  120. data/lib/active_record/migration/command_recorder.rb +33 -14
  121. data/lib/active_record/migration/compatibility.rb +8 -3
  122. data/lib/active_record/migration/default_strategy.rb +4 -5
  123. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  124. data/lib/active_record/migration.rb +104 -98
  125. data/lib/active_record/model_schema.rb +32 -70
  126. data/lib/active_record/nested_attributes.rb +15 -9
  127. data/lib/active_record/normalization.rb +3 -7
  128. data/lib/active_record/persistence.rb +127 -451
  129. data/lib/active_record/query_cache.rb +19 -8
  130. data/lib/active_record/query_logs.rb +104 -37
  131. data/lib/active_record/query_logs_formatter.rb +17 -28
  132. data/lib/active_record/querying.rb +24 -12
  133. data/lib/active_record/railtie.rb +26 -68
  134. data/lib/active_record/railties/controller_runtime.rb +13 -4
  135. data/lib/active_record/railties/databases.rake +43 -61
  136. data/lib/active_record/reflection.rb +112 -53
  137. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  138. data/lib/active_record/relation/batches.rb +138 -72
  139. data/lib/active_record/relation/calculations.rb +122 -82
  140. data/lib/active_record/relation/delegation.rb +30 -22
  141. data/lib/active_record/relation/finder_methods.rb +32 -18
  142. data/lib/active_record/relation/merger.rb +12 -14
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  145. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  146. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  147. data/lib/active_record/relation/predicate_builder.rb +16 -3
  148. data/lib/active_record/relation/query_attribute.rb +1 -1
  149. data/lib/active_record/relation/query_methods.rb +317 -101
  150. data/lib/active_record/relation/spawn_methods.rb +3 -19
  151. data/lib/active_record/relation/where_clause.rb +7 -19
  152. data/lib/active_record/relation.rb +561 -119
  153. data/lib/active_record/result.rb +95 -46
  154. data/lib/active_record/runtime_registry.rb +39 -0
  155. data/lib/active_record/sanitization.rb +31 -25
  156. data/lib/active_record/schema.rb +8 -6
  157. data/lib/active_record/schema_dumper.rb +53 -20
  158. data/lib/active_record/schema_migration.rb +31 -14
  159. data/lib/active_record/scoping/named.rb +6 -2
  160. data/lib/active_record/signed_id.rb +24 -4
  161. data/lib/active_record/statement_cache.rb +19 -19
  162. data/lib/active_record/store.rb +7 -3
  163. data/lib/active_record/table_metadata.rb +2 -13
  164. data/lib/active_record/tasks/database_tasks.rb +87 -58
  165. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
  166. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  167. data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
  168. data/lib/active_record/test_fixtures.rb +98 -89
  169. data/lib/active_record/testing/query_assertions.rb +121 -0
  170. data/lib/active_record/timestamp.rb +2 -2
  171. data/lib/active_record/token_for.rb +22 -12
  172. data/lib/active_record/touch_later.rb +1 -1
  173. data/lib/active_record/transaction.rb +132 -0
  174. data/lib/active_record/transactions.rb +72 -17
  175. data/lib/active_record/translation.rb +0 -2
  176. data/lib/active_record/type/serialized.rb +1 -3
  177. data/lib/active_record/type_caster/connection.rb +4 -4
  178. data/lib/active_record/validations/associated.rb +9 -3
  179. data/lib/active_record/validations/uniqueness.rb +23 -18
  180. data/lib/active_record/validations.rb +4 -1
  181. data/lib/active_record.rb +138 -57
  182. data/lib/arel/alias_predication.rb +1 -1
  183. data/lib/arel/collectors/bind.rb +4 -2
  184. data/lib/arel/collectors/composite.rb +7 -0
  185. data/lib/arel/collectors/sql_string.rb +2 -2
  186. data/lib/arel/collectors/substitute_binds.rb +3 -3
  187. data/lib/arel/nodes/binary.rb +1 -7
  188. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  189. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  190. data/lib/arel/nodes/node.rb +5 -4
  191. data/lib/arel/nodes/sql_literal.rb +8 -1
  192. data/lib/arel/nodes.rb +2 -2
  193. data/lib/arel/predications.rb +1 -1
  194. data/lib/arel/select_manager.rb +1 -1
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/tree_manager.rb +3 -2
  197. data/lib/arel/update_manager.rb +2 -1
  198. data/lib/arel/visitors/dot.rb +1 -0
  199. data/lib/arel/visitors/mysql.rb +9 -4
  200. data/lib/arel/visitors/postgresql.rb +1 -12
  201. data/lib/arel/visitors/sqlite.rb +25 -0
  202. data/lib/arel/visitors/to_sql.rb +29 -16
  203. data/lib/arel.rb +7 -3
  204. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  205. metadata +18 -16
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -21,135 +21,111 @@ module ActiveRecord
21
21
  SQLite3::ExplainPrettyPrinter.new.pp(result)
22
22
  end
23
23
 
24
- def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: false) # :nodoc:
25
- sql = transform_query(sql)
26
- check_if_write_query(sql)
27
-
28
- mark_transaction_written_if_write(sql)
29
-
30
- type_casted_binds = type_casted_binds(binds)
31
-
32
- log(sql, name, binds, type_casted_binds, async: async) do
33
- with_raw_connection do |conn|
34
- # Don't cache statements if they are not prepared
35
- unless prepare
36
- stmt = conn.prepare(sql)
37
- begin
38
- cols = stmt.columns
39
- unless without_prepared_statement?(binds)
40
- stmt.bind_params(type_casted_binds)
41
- end
42
- records = stmt.to_a
43
- ensure
44
- stmt.close
45
- end
46
- else
47
- stmt = @statements[sql] ||= conn.prepare(sql)
48
- cols = stmt.columns
49
- stmt.reset!
50
- stmt.bind_params(type_casted_binds)
51
- records = stmt.to_a
52
- end
53
- verified!
54
-
55
- build_result(columns: cols, rows: records)
56
- end
57
- end
58
- end
59
-
60
- def exec_delete(sql, name = "SQL", binds = []) # :nodoc:
61
- internal_exec_query(sql, name, binds)
62
- @raw_connection.changes
24
+ def begin_deferred_transaction(isolation = nil) # :nodoc:
25
+ internal_begin_transaction(:deferred, isolation)
63
26
  end
64
- alias :exec_update :exec_delete
65
27
 
66
28
  def begin_isolated_db_transaction(isolation) # :nodoc:
67
- raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
68
- raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
69
-
70
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
71
- ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted] = conn.get_first_value("PRAGMA read_uncommitted")
72
- conn.read_uncommitted = true
73
- begin_db_transaction
74
- end
29
+ internal_begin_transaction(:deferred, isolation)
75
30
  end
76
31
 
77
32
  def begin_db_transaction # :nodoc:
78
- log("begin transaction", "TRANSACTION") do
79
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
80
- result = conn.transaction
81
- verified!
82
- result
83
- end
84
- end
33
+ internal_begin_transaction(:immediate, nil)
85
34
  end
86
35
 
87
36
  def commit_db_transaction # :nodoc:
88
- log("commit transaction", "TRANSACTION") do
89
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
90
- conn.commit
91
- end
92
- end
93
- reset_read_uncommitted
37
+ internal_execute("COMMIT TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
94
38
  end
95
39
 
96
40
  def exec_rollback_db_transaction # :nodoc:
97
- log("rollback transaction", "TRANSACTION") do
98
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
99
- conn.rollback
100
- end
101
- end
102
- reset_read_uncommitted
41
+ internal_execute("ROLLBACK TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
103
42
  end
104
43
 
105
44
  # https://stackoverflow.com/questions/17574784
106
45
  # https://www.sqlite.org/lang_datefunc.html
107
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')").freeze # :nodoc:
46
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')", retryable: true).freeze # :nodoc:
108
47
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
109
48
 
110
49
  def high_precision_current_timestamp
111
50
  HIGH_PRECISION_CURRENT_TIMESTAMP
112
51
  end
113
52
 
53
+ def execute(...) # :nodoc:
54
+ # SQLite3Adapter was refactored to use ActiveRecord::Result internally
55
+ # but for backward compatibility we have to keep returning arrays of hashes here
56
+ super&.to_a
57
+ end
58
+
59
+ def reset_isolation_level # :nodoc:
60
+ internal_execute("PRAGMA read_uncommitted=#{@previous_read_uncommitted}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
61
+ @previous_read_uncommitted = nil
62
+ end
63
+
114
64
  private
115
- def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: false)
116
- log(sql, name, async: async) do
117
- with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
118
- result = conn.execute(sql)
119
- verified!
120
- result
121
- end
65
+ def internal_begin_transaction(mode, isolation)
66
+ if isolation
67
+ raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
68
+ raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
69
+ end
70
+
71
+ internal_execute("BEGIN #{mode} TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
72
+ if isolation
73
+ @previous_read_uncommitted = query_value("PRAGMA read_uncommitted")
74
+ internal_execute("PRAGMA read_uncommitted=ON", "TRANSACTION", allow_retry: true, materialize_transactions: false)
122
75
  end
123
76
  end
124
77
 
125
- def reset_read_uncommitted
126
- read_uncommitted = ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted]
127
- return unless read_uncommitted
78
+ def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
79
+ if batch
80
+ raw_connection.execute_batch2(sql)
81
+ elsif prepare
82
+ stmt = @statements[sql] ||= raw_connection.prepare(sql)
83
+ stmt.reset!
84
+ stmt.bind_params(type_casted_binds)
85
+
86
+ result = if stmt.column_count.zero? # No return
87
+ stmt.step
88
+ ActiveRecord::Result.empty
89
+ else
90
+ ActiveRecord::Result.new(stmt.columns, stmt.to_a)
91
+ end
92
+ else
93
+ # Don't cache statements if they are not prepared.
94
+ stmt = raw_connection.prepare(sql)
95
+ begin
96
+ unless binds.nil? || binds.empty?
97
+ stmt.bind_params(type_casted_binds)
98
+ end
99
+ result = if stmt.column_count.zero? # No return
100
+ stmt.step
101
+ ActiveRecord::Result.empty
102
+ else
103
+ ActiveRecord::Result.new(stmt.columns, stmt.to_a)
104
+ end
105
+ ensure
106
+ stmt.close
107
+ end
108
+ end
109
+ @last_affected_rows = raw_connection.changes
110
+ verified!
128
111
 
129
- @raw_connection&.read_uncommitted = read_uncommitted
112
+ notification_payload[:row_count] = result&.length || 0
113
+ result
130
114
  end
131
115
 
132
- def execute_batch(statements, name = nil)
133
- statements = statements.map { |sql| transform_query(sql) }
134
- sql = combine_multi_statements(statements)
135
-
136
- check_if_write_query(sql)
137
- mark_transaction_written_if_write(sql)
116
+ def cast_result(result)
117
+ # Given that SQLite3 doesn't really a Result type, raw_execute already return an ActiveRecord::Result
118
+ # and we have nothing to cast here.
119
+ result
120
+ end
138
121
 
139
- log(sql, name) do
140
- with_raw_connection do |conn|
141
- result = conn.execute_batch2(sql)
142
- verified!
143
- result
144
- end
145
- end
122
+ def affected_rows(result)
123
+ @last_affected_rows
146
124
  end
147
125
 
148
- def build_fixture_statements(fixture_set)
149
- fixture_set.flat_map do |table_name, fixtures|
150
- next if fixtures.empty?
151
- fixtures.map { |fixture| build_fixture_sql([fixture], table_name) }
152
- end.compact
126
+ def execute_batch(statements, name = nil, **kwargs)
127
+ sql = combine_multi_statements(statements)
128
+ raw_execute(sql, name, batch: true, **kwargs)
153
129
  end
154
130
 
155
131
  def build_truncate_statement(table_name)
@@ -159,6 +135,14 @@ module ActiveRecord
159
135
  def returning_column_values(result)
160
136
  result.rows.first
161
137
  end
138
+
139
+ def default_insert_value(column)
140
+ if column.default_function
141
+ Arel.sql(column.default_function)
142
+ else
143
+ column.default
144
+ end
145
+ end
162
146
  end
163
147
  end
164
148
  end
@@ -4,23 +4,71 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLite3
6
6
  module Quoting # :nodoc:
7
+ extend ActiveSupport::Concern
8
+
7
9
  QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
8
10
  QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
9
11
 
10
- def quote_string(s)
11
- ::SQLite3::Database.quote(s)
12
+ module ClassMethods # :nodoc:
13
+ def column_name_matcher
14
+ /
15
+ \A
16
+ (
17
+ (?:
18
+ # "table_name"."column_name" | function(one or no argument)
19
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
20
+ )
21
+ (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
22
+ )
23
+ (?:\s*,\s*\g<1>)*
24
+ \z
25
+ /ix
26
+ end
27
+
28
+ def column_name_with_order_matcher
29
+ /
30
+ \A
31
+ (
32
+ (?:
33
+ # "table_name"."column_name" | function(one or no argument)
34
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
35
+ )
36
+ (?:\s+COLLATE\s+(?:\w+|"\w+"))?
37
+ (?:\s+ASC|\s+DESC)?
38
+ )
39
+ (?:\s*,\s*\g<1>)*
40
+ \z
41
+ /ix
42
+ end
43
+
44
+ def quote_column_name(name)
45
+ QUOTED_COLUMN_NAMES[name] ||= %Q("#{name.to_s.gsub('"', '""')}").freeze
46
+ end
47
+
48
+ def quote_table_name(name)
49
+ QUOTED_TABLE_NAMES[name] ||= %Q("#{name.to_s.gsub('"', '""').gsub(".", "\".\"")}").freeze
50
+ end
12
51
  end
13
52
 
14
- def quote_table_name_for_assignment(table, attr)
15
- quote_column_name(attr)
53
+ def quote(value) # :nodoc:
54
+ case value
55
+ when Numeric
56
+ if value.finite?
57
+ super
58
+ else
59
+ "'#{value}'"
60
+ end
61
+ else
62
+ super
63
+ end
16
64
  end
17
65
 
18
- def quote_table_name(name)
19
- QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "\".\"").freeze
66
+ def quote_string(s)
67
+ ::SQLite3::Database.quote(s)
20
68
  end
21
69
 
22
- def quote_column_name(name)
23
- QUOTED_COLUMN_NAMES[name] ||= %Q("#{super.gsub('"', '""')}")
70
+ def quote_table_name_for_assignment(table, attr)
71
+ quote_column_name(attr)
24
72
  end
25
73
 
26
74
  def quoted_time(value)
@@ -63,7 +111,7 @@ module ActiveRecord
63
111
 
64
112
  def type_cast(value) # :nodoc:
65
113
  case value
66
- when BigDecimal
114
+ when BigDecimal, Rational
67
115
  value.to_f
68
116
  when String
69
117
  if value.encoding == Encoding::ASCII_8BIT
@@ -75,43 +123,6 @@ module ActiveRecord
75
123
  super
76
124
  end
77
125
  end
78
-
79
- def column_name_matcher
80
- COLUMN_NAME
81
- end
82
-
83
- def column_name_with_order_matcher
84
- COLUMN_NAME_WITH_ORDER
85
- end
86
-
87
- COLUMN_NAME = /
88
- \A
89
- (
90
- (?:
91
- # "table_name"."column_name" | function(one or no argument)
92
- ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
93
- )
94
- (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
95
- )
96
- (?:\s*,\s*\g<1>)*
97
- \z
98
- /ix
99
-
100
- COLUMN_NAME_WITH_ORDER = /
101
- \A
102
- (
103
- (?:
104
- # "table_name"."column_name" | function(one or no argument)
105
- ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
106
- )
107
- (?:\s+COLLATE\s+(?:\w+|"\w+"))?
108
- (?:\s+ASC|\s+DESC)?
109
- )
110
- (?:\s*,\s*\g<1>)*
111
- \z
112
- /ix
113
-
114
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
115
126
  end
116
127
  end
117
128
  end
@@ -5,6 +5,12 @@ module ActiveRecord
5
5
  module SQLite3
6
6
  class SchemaCreation < SchemaCreation # :nodoc:
7
7
  private
8
+ def visit_ForeignKeyDefinition(o)
9
+ super.dup.tap do |sql|
10
+ sql << " DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" if o.deferrable
11
+ end
12
+ end
13
+
8
14
  def supports_index_using?
9
15
  false
10
16
  end
@@ -13,6 +19,16 @@ module ActiveRecord
13
19
  if options[:collation]
14
20
  sql << " COLLATE \"#{options[:collation]}\""
15
21
  end
22
+
23
+ if as = options[:as]
24
+ sql << " GENERATED ALWAYS AS (#{as})"
25
+
26
+ if options[:stored]
27
+ sql << " STORED"
28
+ else
29
+ sql << " VIRTUAL"
30
+ end
31
+ end
16
32
  super
17
33
  end
18
34
  end
@@ -16,10 +16,23 @@ module ActiveRecord
16
16
  end
17
17
  alias :belongs_to :references
18
18
 
19
+ def new_column_definition(name, type, **options) # :nodoc:
20
+ case type
21
+ when :virtual
22
+ type = options[:type]
23
+ end
24
+
25
+ super
26
+ end
27
+
19
28
  private
20
29
  def integer_like_primary_key_type(type, options)
21
30
  :primary_key
22
31
  end
32
+
33
+ def valid_column_definition_options
34
+ super + [:as, :type, :stored]
35
+ end
23
36
  end
24
37
  end
25
38
  end
@@ -5,6 +5,19 @@ module ActiveRecord
5
5
  module SQLite3
6
6
  class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
7
7
  private
8
+ def virtual_tables(stream)
9
+ virtual_tables = @connection.virtual_tables
10
+ if virtual_tables.any?
11
+ stream.puts
12
+ stream.puts " # Virtual tables defined in this database."
13
+ stream.puts " # Note that virtual tables may not work with other database engines. Be careful if changing database."
14
+ virtual_tables.sort.each do |table_name, options|
15
+ module_name, arguments = options
16
+ stream.puts " create_virtual_table #{table_name.inspect}, #{module_name.inspect}, #{arguments.split(", ").inspect}"
17
+ end
18
+ end
19
+ end
20
+
8
21
  def default_primary_key?(column)
9
22
  schema_type(column) == :integer
10
23
  end
@@ -12,6 +25,22 @@ module ActiveRecord
12
25
  def explicit_primary_key_default?(column)
13
26
  column.bigint?
14
27
  end
28
+
29
+ def prepare_column_options(column)
30
+ spec = super
31
+
32
+ if @connection.supports_virtual_columns? && column.virtual?
33
+ spec[:as] = extract_expression_for_virtual_column(column)
34
+ spec[:stored] = column.virtual_stored?
35
+ spec = { type: schema_type(column).inspect }.merge!(spec)
36
+ end
37
+
38
+ spec
39
+ end
40
+
41
+ def extract_expression_for_virtual_column(column)
42
+ column.default_function.inspect
43
+ end
15
44
  end
16
45
  end
17
46
  end
@@ -27,6 +27,7 @@ module ActiveRecord
27
27
  col["name"]
28
28
  end
29
29
 
30
+ where = where.sub(/\s*\/\*.*\*\/\z/, "") if where
30
31
  orders = {}
31
32
 
32
33
  if columns.any?(&:nil?) # index created with an expression
@@ -53,6 +54,8 @@ module ActiveRecord
53
54
  end
54
55
 
55
56
  def add_foreign_key(from_table, to_table, **options)
57
+ assert_valid_deferrable(options[:deferrable])
58
+
56
59
  alter_table(from_table) do |definition|
57
60
  to_table = strip_table_name_prefix_and_suffix(to_table)
58
61
  definition.foreign_key(to_table, **options)
@@ -72,6 +75,7 @@ module ActiveRecord
72
75
  Base.pluralize_table_names ? table.pluralize : table
73
76
  end
74
77
  table = strip_table_name_prefix_and_suffix(table)
78
+ options = options.slice(*fk.options.keys)
75
79
  fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
76
80
  fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
77
81
  end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
@@ -80,6 +84,10 @@ module ActiveRecord
80
84
  alter_table(from_table, foreign_keys)
81
85
  end
82
86
 
87
+ def virtual_table_exists?(table_name)
88
+ query_values(data_source_sql(table_name, type: "VIRTUAL TABLE"), "SCHEMA").any?
89
+ end
90
+
83
91
  def check_constraints(table_name)
84
92
  table_sql = query_value(<<-SQL, "SCHEMA")
85
93
  SELECT sql
@@ -137,7 +145,14 @@ module ActiveRecord
137
145
 
138
146
  type_metadata = fetch_type_metadata(field["type"])
139
147
  default_value = extract_value_from_default(default)
140
- default_function = extract_default_function(default_value, default)
148
+ generated_type = extract_generated_type(field)
149
+
150
+ if generated_type.present?
151
+ default_function = default
152
+ else
153
+ default_function = extract_default_function(default_value, default)
154
+ end
155
+
141
156
  rowid = is_column_the_rowid?(field, definitions)
142
157
 
143
158
  Column.new(
@@ -148,7 +163,8 @@ module ActiveRecord
148
163
  default_function,
149
164
  collation: field["collation"],
150
165
  auto_increment: field["auto_increment"],
151
- rowid: rowid
166
+ rowid: rowid,
167
+ generated_type: generated_type
152
168
  )
153
169
  end
154
170
 
@@ -166,7 +182,8 @@ module ActiveRecord
166
182
  scope = quoted_scope(name, type: type)
167
183
  scope[:type] ||= "'table','view'"
168
184
 
169
- sql = +"SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
185
+ sql = +"SELECT name FROM pragma_table_list WHERE schema <> 'temp'"
186
+ sql << " AND name NOT IN ('sqlite_sequence', 'sqlite_schema')"
170
187
  sql << " AND name = #{scope[:name]}" if scope[:name]
171
188
  sql << " AND type IN (#{scope[:type]})"
172
189
  sql
@@ -179,12 +196,27 @@ module ActiveRecord
179
196
  "'table'"
180
197
  when "VIEW"
181
198
  "'view'"
199
+ when "VIRTUAL TABLE"
200
+ "'virtual'"
182
201
  end
183
202
  scope = {}
184
203
  scope[:name] = quote(name) if name
185
204
  scope[:type] = type if type
186
205
  scope
187
206
  end
207
+
208
+ def assert_valid_deferrable(deferrable)
209
+ return if !deferrable || %i(immediate deferred).include?(deferrable)
210
+
211
+ raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`"
212
+ end
213
+
214
+ def extract_generated_type(field)
215
+ case field["hidden"]
216
+ when 2 then :virtual
217
+ when 3 then :stored
218
+ end
219
+ end
188
220
  end
189
221
  end
190
222
  end