activerecord-cockroachdb-adapter 7.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 496e736f6d7baf4f0acb18461a70d7686765262e3fd09f4a65ac49534582d871
4
- data.tar.gz: 9bd072d1ac20b7f1099076fb5019c9cfbee305da4463de7f91ec493c8f6eebe3
3
+ metadata.gz: 9721532ff04318993f0835604dff8899289ee81e84e0f6daff67ad8a0024b608
4
+ data.tar.gz: 82db81fab9b8358ea7953bfd7a9a3863794ea0946b56dfd5b57aefe7f2951eba
5
5
  SHA512:
6
- metadata.gz: '044899369534c133039634cc835ce3efd71500a527d9249b10ca34fb8cb343f4754e0c72c8bc0f560267348881fe5d8e7701921b270431dfde1a810f1c48efad'
7
- data.tar.gz: 5c67f56709062e0982998ff59f49f9caa1b14bb630e81339d6183821add59bfaf731f2af5e416b724fc00d3f57e17d63138eabec63166288185d69284bd53891
6
+ metadata.gz: 6089da8ff8fcbb51435f6c408c663fe31d76c2652e7053b1f074ca36769ef88e99356a25b3530eb36dc2dbbc93e3a1db14d52ef76323abda301a093ed0217200
7
+ data.tar.gz: c236373fdc39156b05aa546b596eda2aba501de27f9a486947fc4ec25a8f871210e436675e191d0b369ad1d159dd851544d0df538725f1bef18b3fc2efb1354f
@@ -0,0 +1,46 @@
1
+ <!-- NOTE: This template is copying most of the Rails repo template -->
2
+
3
+ ### Steps to reproduce
4
+
5
+ <!-- (Guidelines for creating a bug report are [available
6
+ here](https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#creating-a-bug-report)) -->
7
+
8
+ <!-- Paste your executable test case created from one of the scripts found [here](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/tree/master/.github/reproduction_scripts) below: -->
9
+
10
+ ```ruby
11
+ # Your reproduction script goes here
12
+ ```
13
+
14
+ ### Expected behavior
15
+
16
+ <!-- Tell us what should happen -->
17
+
18
+ ### Actual behavior
19
+
20
+ <!-- Tell us what happens instead -->
21
+
22
+ ### System configuration
23
+
24
+ <!-- Either fill manually or paste the output of this script within code blocks:
25
+
26
+ ```bash
27
+ bundle info rails | head -1 &&
28
+ ruby -v &&
29
+ bundle info activerecord-cockroachdb-adapter | head -1 &&
30
+ cockroach --version
31
+ ```
32
+ -->
33
+
34
+ <!--
35
+ ```
36
+ # output here (and uncomment!)
37
+ ```
38
+ -->
39
+
40
+ **Rails version**:
41
+
42
+ **Ruby version**:
43
+
44
+ **Adapter version**:
45
+
46
+ **CockroachDB version**:
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Adapted from https://github.com/rails/rails/blob/main/guides/bug_report_templates/active_record_migrations.rb
4
+
5
+ require "bundler/inline"
6
+
7
+ gemfile(true) do
8
+ source "https://rubygems.org"
9
+
10
+ gem "activerecord"
11
+
12
+ gem "activerecord-cockroachdb-adapter"
13
+ end
14
+
15
+ require "activerecord-cockroachdb-adapter"
16
+ require "minitest/autorun"
17
+ require "logger"
18
+
19
+ # You might want to change the database name for another one.
20
+ ActiveRecord::Base.establish_connection("cockroachdb://root@localhost:26257/defaultdb")
21
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
22
+
23
+ ActiveRecord::Schema.define do
24
+ create_table :payments, force: true do |t|
25
+ t.decimal :amount, precision: 10, scale: 0, default: 0, null: false
26
+ end
27
+ end
28
+
29
+ class Payment < ActiveRecord::Base
30
+ end
31
+
32
+ class ChangeAmountToAddScale < ActiveRecord::Migration::Current # or use a specific version via `Migration[number]`
33
+ def change
34
+ reversible do |dir|
35
+ dir.up do
36
+ change_column :payments, :amount, :decimal, precision: 10, scale: 2
37
+ end
38
+
39
+ dir.down do
40
+ change_column :payments, :amount, :decimal, precision: 10, scale: 0
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ class BugTest < ActiveSupport::TestCase
47
+ def test_migration_up
48
+ ChangeAmountToAddScale.migrate(:up)
49
+ Payment.reset_column_information
50
+
51
+ assert_equal "decimal(10,2)", Payment.columns.last.sql_type
52
+ end
53
+
54
+ def test_migration_down
55
+ ChangeAmountToAddScale.migrate(:down)
56
+ Payment.reset_column_information
57
+
58
+ assert_equal "decimal(10)", Payment.columns.last.sql_type
59
+ end
60
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Adapted from https://github.com/rails/rails/blob/main/guides/bug_report_templates/active_record.rb
4
+
5
+ require "bundler/inline"
6
+
7
+ gemfile(true) do
8
+ source "https://rubygems.org"
9
+
10
+ gem "activerecord"
11
+
12
+ gem "activerecord-cockroachdb-adapter"
13
+ end
14
+
15
+ require "activerecord-cockroachdb-adapter"
16
+ require "minitest/autorun"
17
+ require "logger"
18
+
19
+ # You might want to change the database name for another one.
20
+ ActiveRecord::Base.establish_connection("cockroachdb://root@localhost:26257/defaultdb")
21
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
22
+
23
+ ActiveRecord::Schema.define do
24
+ create_table :posts, force: true do |t|
25
+ end
26
+
27
+ create_table :comments, force: true do |t|
28
+ t.integer :post_id
29
+ end
30
+ end
31
+
32
+ class Post < ActiveRecord::Base
33
+ has_many :comments
34
+ end
35
+
36
+ class Comment < ActiveRecord::Base
37
+ belongs_to :post
38
+ end
39
+
40
+ class BugTest < ActiveSupport::TestCase
41
+ def test_association_stuff
42
+ post = Post.create!
43
+ post.comments << Comment.create!
44
+
45
+ assert_equal 1, post.comments.count
46
+ assert_equal 1, Comment.count
47
+ assert_equal post.id, Comment.first.post.id
48
+ end
49
+ end
@@ -40,8 +40,8 @@ jobs:
40
40
  fail-fast: false
41
41
  matrix:
42
42
  # https://www.cockroachlabs.com/docs/releases/release-support-policy
43
- crdb: [v23.2, v24.1, v24.2]
44
- ruby: ["3.3"]
43
+ crdb: [v23.2, v24.1, v24.3]
44
+ ruby: ["3.4"]
45
45
  name: Test (crdb=${{ matrix.crdb }} ruby=${{ matrix.ruby }})
46
46
  steps:
47
47
  - name: Set Up Actions
@@ -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 TESTOPTS='--profile=5'
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,14 @@
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
+
9
+ ## 8.0.0 - 2024-12-19
10
+
11
+ - Add support for Rails 8.0 ([#356](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/356))
12
+
5
13
  ## 7.2.0 - 2024-09-24
6
14
 
7
15
  - Add support for Rails 7.2 ([#337](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/337))
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
- - [x] `def new_column_from_field(`
119
- - [x] `def column_definitions(`
120
- - [x] `def pk_and_sequence_for(`
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.
data/Gemfile CHANGED
@@ -63,7 +63,7 @@ group :development, :test do
63
63
 
64
64
  # Gems used by the ActiveRecord test suite
65
65
  gem "bcrypt", "~> 3.1"
66
- gem "sqlite3", "~> 1.4"
66
+ gem "sqlite3", "~> 2.1"
67
67
 
68
68
  gem "minitest", "~> 5.15"
69
69
  end
data/README.md CHANGED
@@ -8,16 +8,16 @@ of the PostgreSQL adapter that establishes compatibility with [CockroachDB](http
8
8
  Add this line to your project's Gemfile:
9
9
 
10
10
  ```ruby
11
- gem 'activerecord-cockroachdb-adapter', '~> 7.2.0'
11
+ gem 'activerecord-cockroachdb-adapter', '~> 8.0.0'
12
12
  ```
13
13
 
14
- If you're using Rails 7.0, use the `7.0.x` versions of this gem.
15
-
16
14
  If you're using Rails 7.1, use the `7.1.x` versions of this gem.
17
15
 
18
16
  If you're using Rails 7.2, use the `7.2.x` versions of this gem.
19
17
  The minimal CockroachDB version required is 23.1.12 for this version.
20
18
 
19
+ If you're using Rails 8.0, use the `8.0.x` versions of this gem.
20
+
21
21
  In `database.yml`, use the following adapter setting:
22
22
 
23
23
  ```
@@ -14,9 +14,9 @@ Gem::Specification.new do |spec|
14
14
  spec.description = "Allows the use of CockroachDB as a backend for ActiveRecord and Rails apps."
15
15
  spec.homepage = "https://github.com/cockroachdb/activerecord-cockroachdb-adapter"
16
16
 
17
- spec.add_dependency "activerecord", "~> 7.2.0"
17
+ spec.add_dependency "activerecord", "~> 8.0.0"
18
18
  spec.add_dependency "pg", "~> 1.5"
19
- spec.add_dependency "rgeo-activerecord", "~> 7.0.0"
19
+ spec.add_dependency "rgeo-activerecord", "~> 8.0.0"
20
20
 
21
21
  spec.add_development_dependency "benchmark-ips", "~> 2.9.1"
22
22
 
data/bin/console CHANGED
@@ -12,20 +12,22 @@ require "activerecord-cockroachdb-adapter"
12
12
  # structure_load(Post.connection_db_config, "awesome-file.sql")
13
13
  require "active_record/connection_adapters/cockroachdb/database_tasks"
14
14
 
15
+ DB_NAME = "ar_crdb_console"
16
+
15
17
  schema_kind = ENV.fetch("SCHEMA_KIND", ENV.fetch("SCHEMA", "default"))
16
18
 
17
- system("cockroach sql --insecure --host=localhost:26257 --execute='drop database if exists ar_crdb_console'",
19
+ system("cockroach sql --insecure --host=localhost:26257 --execute='drop database if exists #{DB_NAME}'",
18
20
  exception: true)
19
- system("cockroach sql --insecure --host=localhost:26257 --execute='create database ar_crdb_console'",
21
+ system("cockroach sql --insecure --host=localhost:26257 --execute='create database #{DB_NAME}'",
20
22
  exception: true)
21
23
 
22
24
  ActiveRecord::Base.establish_connection(
23
- #Alternative version: "cockroachdb://root@localhost:26257/ar_crdb_console"
25
+ #Alternative version: "cockroachdb://root@localhost:26257/#{DB_NAME}"
24
26
  adapter: "cockroachdb",
25
27
  host: "localhost",
26
28
  port: 26257,
27
29
  user: "root",
28
- database: "ar_crdb_console"
30
+ database: DB_NAME
29
31
  )
30
32
 
31
33
  load "#{__dir__}/console_schemas/#{schema_kind}.rb"
@@ -6,4 +6,6 @@ ActiveRecord::Schema.define do
6
6
  t.string :title
7
7
  t.text :body
8
8
  end
9
+
10
+ add_index("posts", ["title"], name: "index_posts_on_title", unique: true)
9
11
  end
@@ -24,7 +24,11 @@ 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
- with_multi_statements do
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
28
32
  disable_referential_integrity do
29
33
  execute_batch(statements, "Fixtures Load")
30
34
  end
@@ -50,6 +50,14 @@ module ActiveRecord
50
50
  super
51
51
  end
52
52
  end
53
+
54
+ def quoted_date(value)
55
+ # CockroachDB differs from PostgreSQL in its representation of
56
+ # a `timestamp with timezone`, it does not always include the
57
+ # timezone offset (e.g. `+00`), so we need to add it here.
58
+ # This is tested by `BasicsTest#test_default_in_local_time`.
59
+ super + value.strftime("%z")
60
+ end
53
61
  end
54
62
  end
55
63
  end
@@ -34,11 +34,18 @@ module ActiveRecord
34
34
  end
35
35
 
36
36
  def disable_referential_integrity
37
- foreign_keys = tables.map { |table| foreign_keys(table) }.flatten
37
+ foreign_keys = all_foreign_keys
38
38
 
39
- foreign_keys.each do |foreign_key|
40
- remove_foreign_key(foreign_key.from_table, name: foreign_key.options[:name])
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
- foreign_keys.each do |foreign_key|
56
- # Avoid having PG:DuplicateObject error if a test is ran in transaction.
57
- # TODO: verify that there is no cache issue related to running this (e.g: fk
58
- # still in cache but not in db)
59
- next if foreign_key_exists?(foreign_key.from_table, name: foreign_key.options[:name])
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
- add_foreign_key(foreign_key.from_table, foreign_key.to_table, **foreign_key.options)
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
@@ -55,6 +55,37 @@ module ActiveRecord
55
55
  end
56
56
  end
57
57
 
58
+ def primary_keys(table_name)
59
+ return super unless database_version >= 24_02_02
60
+
61
+ query_values(<<~SQL, "SCHEMA")
62
+ SELECT a.attname
63
+ FROM (
64
+ SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
65
+ FROM pg_index
66
+ WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
67
+ AND indisprimary
68
+ ) i
69
+ JOIN pg_attribute a
70
+ ON a.attrelid = i.indrelid
71
+ AND a.attnum = i.indkey[i.idx]
72
+ AND NOT a.attishidden
73
+ ORDER BY i.idx
74
+ SQL
75
+ end
76
+
77
+ def column_names_from_column_numbers(table_oid, column_numbers)
78
+ return super unless database_version >= 24_02_02
79
+
80
+ Hash[query(<<~SQL, "SCHEMA")].values_at(*column_numbers).compact
81
+ SELECT a.attnum, a.attname
82
+ FROM pg_attribute a
83
+ WHERE a.attrelid = #{table_oid}
84
+ AND a.attnum IN (#{column_numbers.join(", ")})
85
+ AND NOT a.attishidden
86
+ SQL
87
+ end
88
+
58
89
  # OVERRIDE: CockroachDB does not support deferrable constraints.
59
90
  # See: https://go.crdb.dev/issue-v/31632/v23.1
60
91
  def foreign_key_options(from_table, to_table, options)
@@ -185,7 +216,6 @@ module ActiveRecord
185
216
  options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
186
217
 
187
218
  options[:validate] = row["valid"]
188
- to_table = PostgreSQL::Utils.unquote_identifier(row["to_table"])
189
219
 
190
220
  ForeignKeyDefinition.new(table_name, to_table, options)
191
221
  end
@@ -265,31 +295,6 @@ module ActiveRecord
265
295
  sql
266
296
  end
267
297
 
268
- # This overrides the method from PostegreSQL adapter
269
- # Resets the sequence of a table's primary key to the maximum value.
270
- def reset_pk_sequence!(table, pk = nil, sequence = nil)
271
- unless pk && sequence
272
- default_pk, default_sequence = pk_and_sequence_for(table)
273
-
274
- pk ||= default_pk
275
- sequence ||= default_sequence
276
- end
277
-
278
- if @logger && pk && !sequence
279
- @logger.warn "#{table} has primary key #{pk} with no default sequence."
280
- end
281
-
282
- if pk && sequence
283
- quoted_sequence = quote_table_name(sequence)
284
- max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
285
- if max_pk.nil?
286
- minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
287
- end
288
-
289
- query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
290
- end
291
- end
292
-
293
298
  # override
294
299
  def native_database_types
295
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
@@ -161,6 +161,15 @@ module ActiveRecord
161
161
  false
162
162
  end
163
163
 
164
+ # Partitioning is quite different from PostgreSQL, so we don't support it.
165
+ # If you need partitioning, you should default to using raw SQL queries.
166
+ #
167
+ # See https://www.postgresql.org/docs/current/ddl-partitioning.html
168
+ # See https://www.cockroachlabs.com/docs/stable/partitioning
169
+ def supports_native_partitioning?
170
+ false
171
+ end
172
+
164
173
  def supports_ddl_transactions?
165
174
  false
166
175
  end
@@ -181,6 +190,17 @@ module ActiveRecord
181
190
  false
182
191
  end
183
192
 
193
+ # OVERRIDE: UNIQUE CONSTRAINTS will create indexes anyway, so we only consider
194
+ # then as indexes.
195
+ # See https://github.com/cockroachdb/activerecord-cockroachdb-adapter/issues/347.
196
+ # See https://www.cockroachlabs.com/docs/stable/unique.
197
+ #
198
+ # NOTE: support is actually partial, one can still use the `#unique_constraints`
199
+ # method to get the unique constraints.
200
+ def supports_unique_constraints?
201
+ false
202
+ end
203
+
184
204
  def supports_expression_index?
185
205
  # Expression indexes are partially supported by CockroachDB v21.2,
186
206
  # but activerecord requires "ON CONFLICT expression" support.
@@ -393,32 +413,29 @@ module ActiveRecord
393
413
  # have [] appended to the end of it.
394
414
  re = /\A(?:geometry|geography|interval|numeric)/
395
415
 
396
- # 0: attname
397
- # 1: type
398
- # 2: default
399
- # 3: attnotnull
400
- # 4: atttypid
401
- # 5: atttypmod
402
- # 6: collname
403
- # 7: comment
404
- # 8: attidentity
405
- # 9: attgenerated
406
- # 10: is_hidden
416
+ f_attname = 0
417
+ f_type = 1
418
+ # f_default = 2
419
+ # f_attnotnull = 3
420
+ # f_atttypid = 4
421
+ # f_atttypmod = 5
422
+ # f_collname = 6
423
+ f_comment = 7
424
+ # f_attidentity = 8
425
+ # f_attgenerated = 9
426
+ f_is_hidden = 10
407
427
  fields.map do |field|
408
- dtype = field[1]
409
- field[1] = crdb_fields[field[0]][2].downcase if re.match(dtype)
410
- field[7] = crdb_fields[field[0]][1]&.gsub!(/^\'|\'?$/, '')
411
- field[10] = true if crdb_fields[field[0]][3]
428
+ dtype = field[f_type]
429
+ field[f_type] = crdb_fields[field[f_attname]][2].downcase if re.match(dtype)
430
+ field[f_comment] = crdb_fields[field[f_attname]][1]&.gsub!(/^\'|\'?$/, '')
431
+ field[f_is_hidden] = true if crdb_fields[field[f_attname]][3]
412
432
  field
413
433
  end
414
434
  fields.delete_if do |field|
415
435
  # Don't include rowid column if it is hidden and the primary key
416
436
  # is not defined (meaning CRDB implicitly created it).
417
- if field[0] == CockroachDBAdapter::DEFAULT_PRIMARY_KEY
418
- field[10] && !primary_key(table_name)
419
- else
420
- false # Keep this entry.
421
- end
437
+ field[f_attname] == CockroachDBAdapter::DEFAULT_PRIMARY_KEY &&
438
+ field[f_is_hidden] && !primary_key(table_name)
422
439
  end
423
440
  end
424
441
 
@@ -458,9 +475,7 @@ module ActiveRecord
458
475
  # That method will not work for CockroachDB because the error
459
476
  # originates from the "runExecBuilder" function, so we need
460
477
  # to modify the original to match the CockroachDB behavior.
461
- def is_cached_plan_failure?(e)
462
- pgerror = e.cause
463
-
478
+ def is_cached_plan_failure?(pgerror)
464
479
  pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
465
480
  pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "runExecBuilder"
466
481
  rescue
@@ -57,7 +57,7 @@ module ActiveRecord
57
57
  def force_index!(index_name, direction: nil)
58
58
  return self unless from_clause_is_a_table_name?
59
59
 
60
- index_name = sanitize_sql(index_name.to_s)
60
+ index_name = model.sanitize_sql(index_name.to_s)
61
61
  direction = direction.to_s.upcase
62
62
  direction = %w[ASC DESC].include?(direction) ? ",#{direction}" : ""
63
63
 
@@ -84,7 +84,7 @@ module ActiveRecord
84
84
  def index_hint!(hint)
85
85
  return self unless from_clause_is_a_table_name?
86
86
 
87
- hint = sanitize_sql(hint.to_s)
87
+ hint = model.sanitize_sql(hint.to_s)
88
88
  @index_hint = hint.to_s
89
89
  self.from_clause = build_from_clause_with_hints
90
90
  self
@@ -120,7 +120,7 @@ module ActiveRecord
120
120
 
121
121
  table_name =
122
122
  if from_clause.empty?
123
- quoted_table_name
123
+ model.quoted_table_name
124
124
  else
125
125
  # Remove previous table hints if any. And spaces.
126
126
  from_clause.value.partition("@").first.strip
data/lib/version.rb CHANGED
@@ -15,5 +15,5 @@
15
15
  # limitations under the License.
16
16
 
17
17
  module ActiveRecord
18
- COCKROACH_DB_ADAPTER_VERSION = "7.2.0"
18
+ COCKROACH_DB_ADAPTER_VERSION = "8.0.1"
19
19
  end
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: 7.2.0
4
+ version: 8.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cockroach Labs
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-30 00:00:00.000000000 Z
11
+ date: 2025-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 7.2.0
19
+ version: 8.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 7.2.0
26
+ version: 8.0.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pg
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 7.0.0
47
+ version: 8.0.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 7.0.0
54
+ version: 8.0.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: benchmark-ips
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -75,6 +75,9 @@ extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
77
  - ".editorconfig"
78
+ - ".github/issue_template.md"
79
+ - ".github/reproduction_scripts/migrations.rb"
80
+ - ".github/reproduction_scripts/models_and_database.rb"
78
81
  - ".github/workflows/ci.yml"
79
82
  - ".github/workflows/docker.yml"
80
83
  - ".gitignore"
@@ -127,7 +130,7 @@ licenses:
127
130
  - Apache-2.0
128
131
  metadata:
129
132
  allowed_push_host: https://rubygems.org
130
- post_install_message:
133
+ post_install_message:
131
134
  rdoc_options: []
132
135
  require_paths:
133
136
  - lib
@@ -142,8 +145,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
142
145
  - !ruby/object:Gem::Version
143
146
  version: '0'
144
147
  requirements: []
145
- rubygems_version: 3.0.3.1
146
- signing_key:
148
+ rubygems_version: 3.5.23
149
+ signing_key:
147
150
  specification_version: 4
148
151
  summary: CockroachDB adapter for ActiveRecord.
149
152
  test_files: []