activerecord-cockroachdb-adapter 8.0.0 → 8.0.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/.github/workflows/ci.yml +4 -1
- data/CHANGELOG.md +4 -0
- data/CONTRIBUTING.md +4 -3
- data/lib/active_record/connection_adapters/cockroachdb/database_statements.rb +8 -2
- data/lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb +85 -9
- data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +0 -26
- data/lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb +21 -0
- data/lib/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9721532ff04318993f0835604dff8899289ee81e84e0f6daff67ad8a0024b608
|
4
|
+
data.tar.gz: 82db81fab9b8358ea7953bfd7a9a3863794ea0946b56dfd5b57aefe7f2951eba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6089da8ff8fcbb51435f6c408c663fe31d76c2652e7053b1f074ca36769ef88e99356a25b3530eb36dc2dbbc93e3a1db14d52ef76323abda301a093ed0217200
|
7
|
+
data.tar.gz: c236373fdc39156b05aa546b596eda2aba501de27f9a486947fc4ec25a8f871210e436675e191d0b369ad1d159dd851544d0df538725f1bef18b3fc2efb1354f
|
data/.github/workflows/ci.yml
CHANGED
@@ -88,4 +88,7 @@ jobs:
|
|
88
88
|
done
|
89
89
|
cat ${{ github.workspace }}/setup.sql | cockroach sql --insecure
|
90
90
|
- name: Test
|
91
|
-
run: bundle exec rake test
|
91
|
+
run: bundle exec rake test
|
92
|
+
env:
|
93
|
+
TESTOPTS: "--profile=5"
|
94
|
+
RAILS_MINITEST_PLUGIN: "1" # Make sure that we use the minitest plugin for profiling.
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
## Ongoing
|
4
4
|
|
5
|
+
## 8.0.1 - 2025-03-04
|
6
|
+
|
7
|
+
- Fixed retry logic after transaction rollback ([#364](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/364))
|
8
|
+
|
5
9
|
## 8.0.0 - 2024-12-19
|
6
10
|
|
7
11
|
- Add support for Rails 8.0 ([#356](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/356))
|
data/CONTRIBUTING.md
CHANGED
@@ -115,9 +115,10 @@ This section intent to help you with a checklist.
|
|
115
115
|
- Verify the written text at the beginning of the test suite, there are likely
|
116
116
|
some changes in excluded tests.
|
117
117
|
- Check for some important methods, some will change for sure:
|
118
|
-
- [
|
119
|
-
- [
|
120
|
-
- [
|
118
|
+
- [ ] `def new_column_from_field(`
|
119
|
+
- [ ] `def column_definitions(`
|
120
|
+
- [ ] `def pk_and_sequence_for(`
|
121
|
+
- [ ] `def foreign_keys(` and `def all_foreign_keys(`
|
121
122
|
- [ ] ...
|
122
123
|
- Check for setups containing `drop_table` in the test suite.
|
123
124
|
Especially if you have tons of failure, this is likely the cause.
|
@@ -24,8 +24,14 @@ module ActiveRecord
|
|
24
24
|
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
|
25
25
|
statements = table_deletes + fixture_inserts
|
26
26
|
|
27
|
-
|
28
|
-
|
27
|
+
begin # much faster without disabling referential integrity, worth trying.
|
28
|
+
transaction(requires_new: true) do
|
29
|
+
execute_batch(statements, "Fixtures Load")
|
30
|
+
end
|
31
|
+
rescue
|
32
|
+
disable_referential_integrity do
|
33
|
+
execute_batch(statements, "Fixtures Load")
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -34,11 +34,18 @@ module ActiveRecord
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def disable_referential_integrity
|
37
|
-
foreign_keys =
|
37
|
+
foreign_keys = all_foreign_keys
|
38
38
|
|
39
|
-
foreign_keys.
|
40
|
-
remove_foreign_key
|
39
|
+
statements = foreign_keys.map do |foreign_key|
|
40
|
+
# We do not use the `#remove_foreign_key` method here because it
|
41
|
+
# checks for foreign keys existance in the schema cache. This method
|
42
|
+
# is performance critical and we know the foreign key exist.
|
43
|
+
at = create_alter_table foreign_key.from_table
|
44
|
+
at.drop_foreign_key foreign_key.name
|
45
|
+
|
46
|
+
schema_creation.accept(at)
|
41
47
|
end
|
48
|
+
execute_batch(statements, "Disable referential integrity -> remove foreign keys")
|
42
49
|
|
43
50
|
yield
|
44
51
|
|
@@ -52,19 +59,88 @@ module ActiveRecord
|
|
52
59
|
ActiveRecord::Base.table_name_suffix = ""
|
53
60
|
|
54
61
|
begin
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
62
|
+
# Avoid having PG:DuplicateObject error if a test is ran in transaction.
|
63
|
+
# TODO: verify that there is no cache issue related to running this (e.g: fk
|
64
|
+
# still in cache but not in db)
|
65
|
+
#
|
66
|
+
# We avoid using `foreign_key_exists?` here because it checks the schema cache
|
67
|
+
# for every key. This method is performance critical for the test suite, hence
|
68
|
+
# we use the `#all_foreign_keys` method that only make one query to the database.
|
69
|
+
already_inserted_foreign_keys = all_foreign_keys
|
70
|
+
statements = foreign_keys.map do |foreign_key|
|
71
|
+
next if already_inserted_foreign_keys.any? { |fk| fk.from_table == foreign_key.from_table && fk.options[:name] == foreign_key.options[:name] }
|
72
|
+
|
73
|
+
options = foreign_key_options(foreign_key.from_table, foreign_key.to_table, foreign_key.options)
|
74
|
+
at = create_alter_table foreign_key.from_table
|
75
|
+
at.add_foreign_key foreign_key.to_table, options
|
60
76
|
|
61
|
-
|
77
|
+
schema_creation.accept(at)
|
62
78
|
end
|
79
|
+
execute_batch(statements.compact, "Disable referential integrity -> add foreign keys")
|
63
80
|
ensure
|
64
81
|
ActiveRecord::Base.table_name_prefix = old_prefix
|
65
82
|
ActiveRecord::Base.table_name_suffix = old_suffix
|
66
83
|
end
|
67
84
|
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Copy/paste of the `#foreign_keys(table)` method adapted to return every single
|
89
|
+
# foreign key in the database.
|
90
|
+
def all_foreign_keys
|
91
|
+
fk_info = internal_exec_query(<<~SQL, "SCHEMA")
|
92
|
+
SELECT CASE
|
93
|
+
WHEN n1.nspname = current_schema()
|
94
|
+
THEN ''
|
95
|
+
ELSE n1.nspname || '.'
|
96
|
+
END || t1.relname AS from_table,
|
97
|
+
CASE
|
98
|
+
WHEN n2.nspname = current_schema()
|
99
|
+
THEN ''
|
100
|
+
ELSE n2.nspname || '.'
|
101
|
+
END || t2.relname AS to_table,
|
102
|
+
a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred,
|
103
|
+
c.conkey, c.confkey, c.conrelid, c.confrelid
|
104
|
+
FROM pg_constraint c
|
105
|
+
JOIN pg_class t1 ON c.conrelid = t1.oid
|
106
|
+
JOIN pg_class t2 ON c.confrelid = t2.oid
|
107
|
+
JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
|
108
|
+
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
|
109
|
+
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
110
|
+
JOIN pg_namespace n1 ON t1.relnamespace = n1.oid
|
111
|
+
JOIN pg_namespace n2 ON t2.relnamespace = n2.oid
|
112
|
+
WHERE c.contype = 'f'
|
113
|
+
ORDER BY c.conname
|
114
|
+
SQL
|
115
|
+
|
116
|
+
fk_info.map do |row|
|
117
|
+
from_table = PostgreSQL::Utils.unquote_identifier(row["from_table"])
|
118
|
+
to_table = PostgreSQL::Utils.unquote_identifier(row["to_table"])
|
119
|
+
conkey = row["conkey"].scan(/\d+/).map(&:to_i)
|
120
|
+
confkey = row["confkey"].scan(/\d+/).map(&:to_i)
|
121
|
+
|
122
|
+
if conkey.size > 1
|
123
|
+
column = column_names_from_column_numbers(row["conrelid"], conkey)
|
124
|
+
primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
|
125
|
+
else
|
126
|
+
column = PostgreSQL::Utils.unquote_identifier(row["column"])
|
127
|
+
primary_key = row["primary_key"]
|
128
|
+
end
|
129
|
+
|
130
|
+
options = {
|
131
|
+
column: column,
|
132
|
+
name: row["name"],
|
133
|
+
primary_key: primary_key
|
134
|
+
}
|
135
|
+
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
136
|
+
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
137
|
+
options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
|
138
|
+
|
139
|
+
options[:validate] = row["valid"]
|
140
|
+
|
141
|
+
ForeignKeyDefinition.new(from_table, to_table, options)
|
142
|
+
end
|
143
|
+
end
|
68
144
|
end
|
69
145
|
end
|
70
146
|
end
|
@@ -216,7 +216,6 @@ module ActiveRecord
|
|
216
216
|
options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
|
217
217
|
|
218
218
|
options[:validate] = row["valid"]
|
219
|
-
to_table = PostgreSQL::Utils.unquote_identifier(row["to_table"])
|
220
219
|
|
221
220
|
ForeignKeyDefinition.new(table_name, to_table, options)
|
222
221
|
end
|
@@ -296,31 +295,6 @@ module ActiveRecord
|
|
296
295
|
sql
|
297
296
|
end
|
298
297
|
|
299
|
-
# This overrides the method from PostegreSQL adapter
|
300
|
-
# Resets the sequence of a table's primary key to the maximum value.
|
301
|
-
def reset_pk_sequence!(table, pk = nil, sequence = nil)
|
302
|
-
unless pk && sequence
|
303
|
-
default_pk, default_sequence = pk_and_sequence_for(table)
|
304
|
-
|
305
|
-
pk ||= default_pk
|
306
|
-
sequence ||= default_sequence
|
307
|
-
end
|
308
|
-
|
309
|
-
if @logger && pk && !sequence
|
310
|
-
@logger.warn "#{table} has primary key #{pk} with no default sequence."
|
311
|
-
end
|
312
|
-
|
313
|
-
if pk && sequence
|
314
|
-
quoted_sequence = quote_table_name(sequence)
|
315
|
-
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
|
316
|
-
if max_pk.nil?
|
317
|
-
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
318
|
-
end
|
319
|
-
|
320
|
-
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
321
|
-
end
|
322
|
-
end
|
323
|
-
|
324
298
|
# override
|
325
299
|
def native_database_types
|
326
300
|
# Add spatial types
|
@@ -45,6 +45,27 @@ module ActiveRecord
|
|
45
45
|
within_new_transaction(isolation: isolation, joinable: joinable, attempts: attempts + 1) { yield }
|
46
46
|
end
|
47
47
|
|
48
|
+
# OVERRIDE: the `rescue ActiveRecord::StatementInvalid` block is new, see comment.
|
49
|
+
def rollback_transaction(transaction = nil)
|
50
|
+
@connection.lock.synchronize do
|
51
|
+
transaction ||= @stack.last
|
52
|
+
begin
|
53
|
+
transaction.rollback
|
54
|
+
rescue ActiveRecord::StatementInvalid => err
|
55
|
+
# This is important to make Active Record aware the record was not inserted/saved
|
56
|
+
# Otherwise Active Record will assume save was successful and it doesn't retry the transaction
|
57
|
+
# See this thread for more details:
|
58
|
+
# https://github.com/cockroachdb/activerecord-cockroachdb-adapter/issues/258#issuecomment-2256633329
|
59
|
+
transaction.rollback_records if err.cause.is_a?(PG::NoActiveSqlTransaction)
|
60
|
+
|
61
|
+
raise
|
62
|
+
ensure
|
63
|
+
@stack.pop if @stack.last == transaction
|
64
|
+
end
|
65
|
+
transaction.rollback_records
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
48
69
|
def retryable?(error)
|
49
70
|
return true if serialization_error?(error)
|
50
71
|
return true if error.is_a? ActiveRecord::SerializationFailure
|
data/lib/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-cockroachdb-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 8.0.
|
4
|
+
version: 8.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cockroach Labs
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-03-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|