activerecord 7.1.5.2 → 8.1.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +567 -2443
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +3 -2
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +18 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +36 -5
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +268 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +8 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +250 -315
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +20 -27
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +96 -68
- data/lib/active_record/autosave_association.rb +93 -58
- data/lib/active_record/base.rb +5 -7
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
- data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +66 -0
- data/lib/active_record/connection_handling.rb +88 -46
- data/lib/active_record/core.rb +142 -58
- data/lib/active_record/counter_cache.rb +52 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +28 -5
- data/lib/active_record/database_configurations/hash_config.rb +103 -41
- data/lib/active_record/database_configurations/url_config.rb +30 -1
- data/lib/active_record/database_configurations.rb +8 -4
- data/lib/active_record/delegated_type.rb +43 -19
- data/lib/active_record/dynamic_matchers.rb +56 -71
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +12 -12
- data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
- data/lib/active_record/encryption/encryptor.rb +58 -22
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +57 -36
- data/lib/active_record/errors.rb +76 -26
- data/lib/active_record/explain.rb +14 -25
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +39 -35
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +5 -3
- data/lib/active_record/insert_all.rb +28 -20
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +15 -7
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -34
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +46 -14
- data/lib/active_record/migration/compatibility.rb +40 -25
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +119 -103
- data/lib/active_record/model_schema.rb +66 -78
- data/lib/active_record/nested_attributes.rb +17 -9
- data/lib/active_record/persistence.rb +161 -454
- data/lib/active_record/query_cache.rb +33 -15
- data/lib/active_record/query_logs.rb +106 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +28 -16
- data/lib/active_record/railtie.rb +61 -74
- data/lib/active_record/railties/controller_runtime.rb +19 -5
- data/lib/active_record/railties/databases.rake +64 -78
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +147 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +153 -73
- data/lib/active_record/relation/calculations.rb +146 -96
- data/lib/active_record/relation/delegation.rb +30 -23
- data/lib/active_record/relation/finder_methods.rb +64 -34
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +25 -10
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +351 -124
- data/lib/active_record/relation/spawn_methods.rb +9 -25
- data/lib/active_record/relation/where_clause.rb +17 -30
- data/lib/active_record/relation.rb +590 -124
- data/lib/active_record/result.rb +131 -59
- data/lib/active_record/runtime_registry.rb +44 -21
- data/lib/active_record/sanitization.rb +33 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +66 -31
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +68 -20
- data/lib/active_record/statement_cache.rb +29 -25
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +7 -33
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +118 -90
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +123 -89
- data/lib/active_record/testing/query_assertions.rb +127 -0
- data/lib/active_record/timestamp.rb +6 -4
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +129 -0
- data/lib/active_record/transactions.rb +97 -18
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +12 -7
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +5 -4
- data/lib/active_record/validations/associated.rb +10 -4
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +203 -59
- data/lib/arel/alias_predication.rb +3 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +6 -5
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -4
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +14 -5
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -3
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +56 -12
- data/lib/arel/visitors/sqlite.rb +80 -8
- data/lib/arel/visitors/to_sql.rb +34 -37
- data/lib/arel.rb +10 -4
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +23 -14
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -167
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
|
@@ -21,135 +21,131 @@ module ActiveRecord
|
|
|
21
21
|
SQLite3::ExplainPrettyPrinter.new.pp(result)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
|
|
64
|
+
def default_insert_value(column) # :nodoc:
|
|
65
|
+
if column.default_function
|
|
66
|
+
Arel.sql(column.default_function)
|
|
67
|
+
else
|
|
68
|
+
column.default
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
114
72
|
private
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
73
|
+
def internal_begin_transaction(mode, isolation)
|
|
74
|
+
if isolation
|
|
75
|
+
raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
|
|
76
|
+
raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
internal_execute("BEGIN #{mode} TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
|
80
|
+
if isolation
|
|
81
|
+
@previous_read_uncommitted = query_value("PRAGMA read_uncommitted")
|
|
82
|
+
internal_execute("PRAGMA read_uncommitted=ON", "TRANSACTION", allow_retry: true, materialize_transactions: false)
|
|
122
83
|
end
|
|
123
84
|
end
|
|
124
85
|
|
|
125
|
-
def
|
|
126
|
-
|
|
127
|
-
|
|
86
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
|
87
|
+
total_changes_before_query = raw_connection.total_changes
|
|
88
|
+
affected_rows = nil
|
|
128
89
|
|
|
129
|
-
|
|
130
|
-
|
|
90
|
+
if batch
|
|
91
|
+
raw_connection.execute_batch2(sql)
|
|
92
|
+
else
|
|
93
|
+
stmt = if prepare
|
|
94
|
+
@statements[sql] ||= raw_connection.prepare(sql)
|
|
95
|
+
@statements[sql].reset!
|
|
96
|
+
else
|
|
97
|
+
# Don't cache statements if they are not prepared.
|
|
98
|
+
raw_connection.prepare(sql)
|
|
99
|
+
end
|
|
100
|
+
begin
|
|
101
|
+
unless binds.nil? || binds.empty?
|
|
102
|
+
stmt.bind_params(type_casted_binds)
|
|
103
|
+
end
|
|
104
|
+
result = if stmt.column_count.zero? # No return
|
|
105
|
+
stmt.step
|
|
131
106
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
107
|
+
affected_rows = if raw_connection.total_changes > total_changes_before_query
|
|
108
|
+
raw_connection.changes
|
|
109
|
+
else
|
|
110
|
+
0
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
ActiveRecord::Result.empty(affected_rows: affected_rows)
|
|
114
|
+
else
|
|
115
|
+
rows = stmt.to_a
|
|
135
116
|
|
|
136
|
-
|
|
137
|
-
|
|
117
|
+
affected_rows = if raw_connection.total_changes > total_changes_before_query
|
|
118
|
+
raw_connection.changes
|
|
119
|
+
else
|
|
120
|
+
0
|
|
121
|
+
end
|
|
138
122
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
result
|
|
123
|
+
ActiveRecord::Result.new(stmt.columns, rows, stmt.types.map { |t| type_map.lookup(t) }, affected_rows: affected_rows)
|
|
124
|
+
end
|
|
125
|
+
ensure
|
|
126
|
+
stmt.close unless prepare
|
|
144
127
|
end
|
|
145
128
|
end
|
|
129
|
+
verified!
|
|
130
|
+
|
|
131
|
+
notification_payload[:affected_rows] = affected_rows
|
|
132
|
+
notification_payload[:row_count] = result&.length || 0
|
|
133
|
+
result
|
|
146
134
|
end
|
|
147
135
|
|
|
148
|
-
def
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
136
|
+
def cast_result(result)
|
|
137
|
+
# Given that SQLite3 doesn't have a Result type, raw_execute already returns an ActiveRecord::Result
|
|
138
|
+
# so we have nothing to cast here.
|
|
139
|
+
result
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def affected_rows(result)
|
|
143
|
+
result.affected_rows
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def execute_batch(statements, name = nil, **kwargs)
|
|
147
|
+
sql = combine_multi_statements(statements)
|
|
148
|
+
raw_execute(sql, name, batch: true, **kwargs)
|
|
153
149
|
end
|
|
154
150
|
|
|
155
151
|
def build_truncate_statement(table_name)
|
|
@@ -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
|
-
|
|
11
|
-
|
|
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
|
|
15
|
-
|
|
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
|
|
19
|
-
|
|
66
|
+
def quote_string(s)
|
|
67
|
+
::SQLite3::Database.quote(s)
|
|
20
68
|
end
|
|
21
69
|
|
|
22
|
-
def
|
|
23
|
-
|
|
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)
|
|
@@ -32,18 +80,10 @@ module ActiveRecord
|
|
|
32
80
|
"x'#{value.hex}'"
|
|
33
81
|
end
|
|
34
82
|
|
|
35
|
-
def quoted_true
|
|
36
|
-
"1"
|
|
37
|
-
end
|
|
38
|
-
|
|
39
83
|
def unquoted_true
|
|
40
84
|
1
|
|
41
85
|
end
|
|
42
86
|
|
|
43
|
-
def quoted_false
|
|
44
|
-
"0"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
87
|
def unquoted_false
|
|
48
88
|
0
|
|
49
89
|
end
|
|
@@ -63,7 +103,7 @@ module ActiveRecord
|
|
|
63
103
|
|
|
64
104
|
def type_cast(value) # :nodoc:
|
|
65
105
|
case value
|
|
66
|
-
when BigDecimal
|
|
106
|
+
when BigDecimal, Rational
|
|
67
107
|
value.to_f
|
|
68
108
|
when String
|
|
69
109
|
if value.encoding == Encoding::ASCII_8BIT
|
|
@@ -75,43 +115,6 @@ module ActiveRecord
|
|
|
75
115
|
super
|
|
76
116
|
end
|
|
77
117
|
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
118
|
end
|
|
116
119
|
end
|
|
117
120
|
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,12 +5,53 @@ 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
|
-
schema_type(column) == :integer
|
|
22
|
+
schema_type(column) == :integer && primary_key_has_autoincrement?
|
|
10
23
|
end
|
|
11
24
|
|
|
12
25
|
def explicit_primary_key_default?(column)
|
|
13
|
-
column.bigint?
|
|
26
|
+
column.bigint? || (column.type == :integer && !primary_key_has_autoincrement?)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def primary_key_has_autoincrement?
|
|
30
|
+
return false unless table_name
|
|
31
|
+
|
|
32
|
+
table_sql = @connection.query_value(<<~SQL, "SCHEMA")
|
|
33
|
+
SELECT sql FROM sqlite_master WHERE name = #{@connection.quote(table_name)} AND type = 'table'
|
|
34
|
+
UNION ALL
|
|
35
|
+
SELECT sql FROM sqlite_temp_master WHERE name = #{@connection.quote(table_name)} AND type = 'table'
|
|
36
|
+
SQL
|
|
37
|
+
|
|
38
|
+
table_sql.to_s.match?(/\bAUTOINCREMENT\b/i)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def prepare_column_options(column)
|
|
42
|
+
spec = super
|
|
43
|
+
|
|
44
|
+
if @connection.supports_virtual_columns? && column.virtual?
|
|
45
|
+
spec[:as] = extract_expression_for_virtual_column(column)
|
|
46
|
+
spec[:stored] = column.virtual_stored?
|
|
47
|
+
spec = { type: schema_type(column).inspect }.merge!(spec)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
spec
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def extract_expression_for_virtual_column(column)
|
|
54
|
+
column.default_function.inspect
|
|
14
55
|
end
|
|
15
56
|
end
|
|
16
57
|
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)
|
|
@@ -60,26 +63,21 @@ module ActiveRecord
|
|
|
60
63
|
end
|
|
61
64
|
|
|
62
65
|
def remove_foreign_key(from_table, to_table = nil, **options)
|
|
63
|
-
return if options.delete(:if_exists)
|
|
66
|
+
return if options.delete(:if_exists) && !foreign_key_exists?(from_table, to_table, **options.slice(:column))
|
|
64
67
|
|
|
65
68
|
to_table ||= options[:to_table]
|
|
66
69
|
options = options.except(:name, :to_table, :validate)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
fkey = foreign_keys.detect do |fk|
|
|
70
|
-
table = to_table || begin
|
|
71
|
-
table = options[:column].to_s.delete_suffix("_id")
|
|
72
|
-
Base.pluralize_table_names ? table.pluralize : table
|
|
73
|
-
end
|
|
74
|
-
table = strip_table_name_prefix_and_suffix(table)
|
|
75
|
-
fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
|
|
76
|
-
fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
|
|
77
|
-
end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
|
|
70
|
+
fkey = foreign_key_for!(from_table, to_table: to_table, **options)
|
|
78
71
|
|
|
72
|
+
foreign_keys = foreign_keys(from_table)
|
|
79
73
|
foreign_keys.delete(fkey)
|
|
80
74
|
alter_table(from_table, foreign_keys)
|
|
81
75
|
end
|
|
82
76
|
|
|
77
|
+
def virtual_table_exists?(table_name)
|
|
78
|
+
query_values(data_source_sql(table_name, type: "VIRTUAL TABLE"), "SCHEMA").any?
|
|
79
|
+
end
|
|
80
|
+
|
|
83
81
|
def check_constraints(table_name)
|
|
84
82
|
table_sql = query_value(<<-SQL, "SCHEMA")
|
|
85
83
|
SELECT sql
|
|
@@ -137,18 +135,27 @@ module ActiveRecord
|
|
|
137
135
|
|
|
138
136
|
type_metadata = fetch_type_metadata(field["type"])
|
|
139
137
|
default_value = extract_value_from_default(default)
|
|
140
|
-
|
|
138
|
+
generated_type = extract_generated_type(field)
|
|
139
|
+
|
|
140
|
+
if generated_type.present?
|
|
141
|
+
default_function = default
|
|
142
|
+
else
|
|
143
|
+
default_function = extract_default_function(default_value, default)
|
|
144
|
+
end
|
|
145
|
+
|
|
141
146
|
rowid = is_column_the_rowid?(field, definitions)
|
|
142
147
|
|
|
143
148
|
Column.new(
|
|
144
149
|
field["name"],
|
|
150
|
+
lookup_cast_type(field["type"]),
|
|
145
151
|
default_value,
|
|
146
152
|
type_metadata,
|
|
147
153
|
field["notnull"].to_i == 0,
|
|
148
154
|
default_function,
|
|
149
155
|
collation: field["collation"],
|
|
150
156
|
auto_increment: field["auto_increment"],
|
|
151
|
-
rowid: rowid
|
|
157
|
+
rowid: rowid,
|
|
158
|
+
generated_type: generated_type
|
|
152
159
|
)
|
|
153
160
|
end
|
|
154
161
|
|
|
@@ -166,7 +173,8 @@ module ActiveRecord
|
|
|
166
173
|
scope = quoted_scope(name, type: type)
|
|
167
174
|
scope[:type] ||= "'table','view'"
|
|
168
175
|
|
|
169
|
-
sql = +"SELECT name FROM
|
|
176
|
+
sql = +"SELECT name FROM pragma_table_list WHERE schema <> 'temp'"
|
|
177
|
+
sql << " AND name NOT IN ('sqlite_sequence', 'sqlite_schema')"
|
|
170
178
|
sql << " AND name = #{scope[:name]}" if scope[:name]
|
|
171
179
|
sql << " AND type IN (#{scope[:type]})"
|
|
172
180
|
sql
|
|
@@ -179,12 +187,27 @@ module ActiveRecord
|
|
|
179
187
|
"'table'"
|
|
180
188
|
when "VIEW"
|
|
181
189
|
"'view'"
|
|
190
|
+
when "VIRTUAL TABLE"
|
|
191
|
+
"'virtual'"
|
|
182
192
|
end
|
|
183
193
|
scope = {}
|
|
184
194
|
scope[:name] = quote(name) if name
|
|
185
195
|
scope[:type] = type if type
|
|
186
196
|
scope
|
|
187
197
|
end
|
|
198
|
+
|
|
199
|
+
def assert_valid_deferrable(deferrable)
|
|
200
|
+
return if !deferrable || %i(immediate deferred).include?(deferrable)
|
|
201
|
+
|
|
202
|
+
raise ArgumentError, "deferrable must be `:immediate` or `:deferred`, got: `#{deferrable.inspect}`"
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def extract_generated_type(field)
|
|
206
|
+
case field["hidden"]
|
|
207
|
+
when 2 then :virtual
|
|
208
|
+
when 3 then :stored
|
|
209
|
+
end
|
|
210
|
+
end
|
|
188
211
|
end
|
|
189
212
|
end
|
|
190
213
|
end
|