Skip to content

Commit 2e06103

Browse files
BuonOmorafiss
authored andcommitted
feat: Support Rails 7.1
This PR includes loads of small changes that won't be mentionned here to adapt to the new Rails code. Loads of tests ignored tests have been added back as they were not error prone anymore. There are likely more of these. We removed the deprecated `configure_connection` method override. It is now unnecessary as CockroachDB supports `SET TIMEZONE <...>`. We dropped support for CockroachDB version before 22.X.X. We removed a lot of copy-pasted code in the test thanks to new file separation in Rails 7.1 test configuration. Signed-off-by: Ulysse Buonomo <[email protected]>
1 parent 2d6771e commit 2e06103

File tree

72 files changed

+823
-1474
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+823
-1474
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ on:
1515

1616
# This allows a subsequently queued workflow run to interrupt previous runs.
1717
concurrency:
18-
group: '${{ github.workflow }} @ ${{ github.ref }}'
18+
group: "${{ github.workflow }} @ ${{ github.ref }}"
1919
cancel-in-progress: true
2020

2121
jobs:
@@ -39,7 +39,7 @@ jobs:
3939
strategy:
4040
matrix:
4141
crdb: [v23.1.11]
42-
ruby: [ruby-head]
42+
ruby: [head]
4343
name: Test (crdb=${{ matrix.crdb }} ruby=${{ matrix.ruby }})
4444
steps:
4545
- name: Set Up Actions
@@ -49,8 +49,8 @@ jobs:
4949
- name: Set Up Ruby
5050
uses: ruby/setup-ruby@v1
5151
with:
52-
ruby-version: ${{ matrix.ruby }}
53-
bundler-cache: true
52+
ruby-version: ${{ matrix.ruby }}
53+
bundler-cache: true
5454
- name: Install and Start Cockroachdb
5555
run: |
5656
# Download CockroachDB
@@ -95,4 +95,4 @@ jobs:
9595
SET CLUSTER SETTING sql.defaults.experimental_temporary_tables.enabled = 'true';
9696
"
9797
- name: Test
98-
run: bundle exec rake test
98+
run: bundle exec rake test TESTOPTS='--profile=3'

Gemfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,13 @@ group :development, :test do
4848
# a specific rails codebase.
4949
gem "rails", github: "rails/rails", tag: RailsTag.call
5050

51-
# Needed before rails update for ruby 3.4
52-
gem "mutex_m"
51+
# Needed for the test suite
52+
gem "msgpack", ">= 1.7.0"
5353

5454
gem "rake"
55-
gem "byebug"
55+
gem "debug"
5656
gem "minitest-excludes", "~> 2.0.1"
57+
gem "minitest-github_action_reporter", github: "BuonOmo/minitest-github_action_reporter", require: "minitest/github_action_reporter_plugin"
5758

5859
# Gems used by the ActiveRecord test suite
5960
gem "bcrypt", "~> 3.1.18"

activerecord-cockroachdb-adapter.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
1414
spec.description = "Allows the use of CockroachDB as a backend for ActiveRecord and Rails apps."
1515
spec.homepage = "https://github.com/cockroachdb/activerecord-cockroachdb-adapter"
1616

17-
spec.add_dependency "activerecord", "~> 7.0.3"
17+
spec.add_dependency "activerecord", "~> 7.1.0"
1818
spec.add_dependency "pg", "~> 1.2"
1919
spec.add_dependency "rgeo-activerecord", "~> 7.0.0"
2020

lib/active_record/connection_adapters/cockroachdb/column.rb

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
module ActiveRecord
22
module ConnectionAdapters
33
module CockroachDB
4-
class Column < PostgreSQLColumn
4+
class Column < PostgreSQL::Column
55
# most functions taken from activerecord-postgis-adapter spatial_column
66
# https://github.com/rgeo/activerecord-postgis-adapter/blob/master/lib/active_record/connection_adapters/postgis/spatial_column.rb
77
def initialize(name, default, sql_type_metadata = nil, null = true,
8-
default_function = nil, collation: nil, comment: nil,
8+
default_function = nil, collation: nil, comment: nil, identity: nil,
99
serial: nil, spatial: nil, generated: nil, hidden: nil)
1010
@sql_type_metadata = sql_type_metadata
1111
@geographic = !!(sql_type_metadata.sql_type =~ /geography\(/i)
@@ -30,7 +30,7 @@ def initialize(name, default, sql_type_metadata = nil, null = true,
3030
build_from_sql_type(sql_type_metadata.sql_type)
3131
end
3232
super(name, default, sql_type_metadata, null, default_function,
33-
collation: collation, comment: comment, serial: serial, generated: generated)
33+
collation: collation, comment: comment, serial: serial, generated: generated, identity: identity)
3434
if spatial? && @srid
3535
@limit = { srid: @srid, type: to_type_name(geometric_type) }
3636
@limit[:has_z] = true if @has_z
@@ -53,10 +53,6 @@ def limit
5353
spatial? ? @limit : super
5454
end
5555

56-
def virtual?
57-
@generated.present?
58-
end
59-
6056
def hidden?
6157
@hidden
6258
end

lib/active_record/connection_adapters/cockroachdb/column_methods.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ def st_point(name, options = {})
4545
def st_polygon(name, options = {})
4646
column(name, :st_polygon, **options)
4747
end
48+
49+
private
50+
51+
def valid_column_definition_options
52+
spatial = [:srid, :has_z, :has_m, :geographic, :spatial_type]
53+
crdb = [:hidden]
54+
super + spatial + crdb
55+
end
4856
end
4957
end
5058

lib/active_record/connection_adapters/cockroachdb/database_statements.rb

Lines changed: 0 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,6 @@ module ActiveRecord
22
module ConnectionAdapters
33
module CockroachDB
44
module DatabaseStatements
5-
# Since CockroachDB will run all transactions with serializable isolation,
6-
# READ UNCOMMITTED, READ COMMITTED, and REPEATABLE READ are all aliases
7-
# for SERIALIZABLE. This lets the adapter support all isolation levels,
8-
# but READ UNCOMMITTED has been removed from this list because the
9-
# ActiveRecord transaction isolation test fails for READ UNCOMMITTED.
10-
# See https://www.cockroachlabs.com/docs/v19.2/transactions.html#isolation-levels
11-
def transaction_isolation_levels
12-
{
13-
read_committed: "READ COMMITTED",
14-
repeatable_read: "REPEATABLE READ",
15-
serializable: "SERIALIZABLE",
16-
read_uncommitted: "READ UNCOMMITTED"
17-
}
18-
end
19-
205
# Overridden to avoid using transactions for schema creation.
216
def insert_fixtures_set(fixture_set, tables_to_delete = [])
227
fixture_inserts = build_fixture_statements(fixture_set)
@@ -29,74 +14,6 @@ def insert_fixtures_set(fixture_set, tables_to_delete = [])
2914
end
3015
end
3116
end
32-
33-
private
34-
def execute_batch(statements, name = nil)
35-
statements.each do |statement|
36-
execute(statement, name)
37-
end
38-
end
39-
40-
DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
41-
private_constant :DEFAULT_INSERT_VALUE
42-
43-
def default_insert_value(column)
44-
DEFAULT_INSERT_VALUE
45-
end
46-
47-
def build_fixture_sql(fixtures, table_name)
48-
columns = schema_cache.columns_hash(table_name)
49-
50-
values_list = fixtures.map do |fixture|
51-
fixture = fixture.stringify_keys
52-
53-
unknown_columns = fixture.keys - columns.keys
54-
if unknown_columns.any?
55-
raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.)
56-
end
57-
58-
columns.map do |name, column|
59-
if fixture.key?(name)
60-
type = lookup_cast_type_from_column(column)
61-
with_yaml_fallback(type.serialize(fixture[name]))
62-
else
63-
default_insert_value(column)
64-
end
65-
end
66-
end
67-
68-
table = Arel::Table.new(table_name)
69-
manager = Arel::InsertManager.new
70-
manager.into(table)
71-
72-
if values_list.size == 1
73-
values = values_list.shift
74-
new_values = []
75-
columns.each_key.with_index { |column, i|
76-
unless values[i].equal?(DEFAULT_INSERT_VALUE)
77-
new_values << values[i]
78-
manager.columns << table[column]
79-
end
80-
}
81-
values_list << new_values
82-
else
83-
columns.each_key { |column| manager.columns << table[column] }
84-
end
85-
86-
manager.values = manager.create_values_list(values_list)
87-
manager.to_sql
88-
end
89-
90-
def build_fixture_statements(fixture_set)
91-
fixture_set.map do |table_name, fixtures|
92-
next if fixtures.empty?
93-
build_fixture_sql(fixtures, table_name)
94-
end.compact
95-
end
96-
97-
def with_multi_statements
98-
yield
99-
end
10017
end
10118
end
10219
end

lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module ReferentialIntegrity
1515
# referential integrity (e.g: adding a foreign key with invalid data
1616
# raises).
1717
# So foreign keys should always be valid for that matter.
18-
def all_foreign_keys_valid?
18+
def check_all_foreign_keys_valid!
1919
true
2020
end
2121

@@ -39,16 +39,12 @@ def disable_referential_integrity
3939

4040
begin
4141
foreign_keys.each do |foreign_key|
42-
begin
43-
add_foreign_key(foreign_key.from_table, foreign_key.to_table, **foreign_key.options)
44-
rescue ActiveRecord::StatementInvalid => error
45-
if error.cause.class == PG::DuplicateObject
46-
# This error is safe to ignore because the yielded caller
47-
# already re-added the foreign key constraint.
48-
else
49-
raise error
50-
end
51-
end
42+
# Avoid having PG:DuplicateObject error if a test is ran in transaction.
43+
# TODO: verify that there is no cache issue related to running this (e.g: fk
44+
# still in cache but not in db)
45+
next if foreign_key_exists?(foreign_key.from_table, name: foreign_key.options[:name])
46+
47+
add_foreign_key(foreign_key.from_table, foreign_key.to_table, **foreign_key.options)
5248
end
5349
ensure
5450
ActiveRecord::Base.table_name_prefix = old_prefix

lib/active_record/connection_adapters/cockroachdb/schema_statements.rb

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def foreign_keys(table_name)
4646
THEN ''
4747
ELSE n2.nspname || '.'
4848
END || t2.relname AS to_table,
49-
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
49+
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,
50+
c.conkey, c.confkey, c.conrelid, c.confrelid
5051
FROM pg_constraint c
5152
JOIN pg_class t1 ON c.conrelid = t1.oid
5253
JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -61,15 +62,26 @@ def foreign_keys(table_name)
6162
SQL
6263

6364
fk_info.map do |row|
65+
to_table = PostgreSQL::Utils.unquote_identifier(row["to_table"])
66+
conkey = row["conkey"].scan(/\d+/).map(&:to_i)
67+
confkey = row["confkey"].scan(/\d+/).map(&:to_i)
68+
69+
if conkey.size > 1
70+
column = column_names_from_column_numbers(row["conrelid"], conkey)
71+
primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
72+
else
73+
column = PostgreSQL::Utils.unquote_identifier(row["column"])
74+
primary_key = row["primary_key"]
75+
end
76+
6477
options = {
65-
column: PostgreSQL::Utils.unquote_identifier(row["column"]),
78+
column: column,
6679
name: row["name"],
67-
primary_key: row["primary_key"]
80+
primary_key: primary_key
6881
}
69-
7082
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
7183
options[:on_update] = extract_foreign_key_action(row["on_update"])
72-
options[:deferrable] = extract_foreign_key_deferrable(row["deferrable"], row["deferred"])
84+
options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
7385

7486
options[:validate] = row["valid"]
7587
to_table = PostgreSQL::Utils.unquote_identifier(row["to_table"])
@@ -87,16 +99,20 @@ def default_sequence_name(table_name, pk = "id")
8799

88100
# override
89101
# https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb#L624
90-
def new_column_from_field(table_name, field)
91-
column_name, type, default, notnull, oid, fmod, collation, comment, generated, hidden = field
102+
def new_column_from_field(table_name, field, _definition)
103+
column_name, type, default, notnull, oid, fmod, collation, comment, identity, attgenerated, hidden = field
92104
type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
93105
default_value = extract_value_from_default(default)
94-
default_function = extract_default_function(default_value, default)
95106

96-
serial =
97-
if (match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/))
98-
sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
99-
end
107+
if attgenerated.present?
108+
default_function = default
109+
else
110+
default_function = extract_default_function(default_value, default)
111+
end
112+
113+
if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
114+
serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
115+
end
100116

101117
# {:dimension=>2, :has_m=>false, :has_z=>false, :name=>"latlon", :srid=>0, :type=>"GEOMETRY"}
102118
spatial = spatial_column_info(table_name).get(column_name, type_metadata.sql_type)
@@ -110,8 +126,9 @@ def new_column_from_field(table_name, field)
110126
collation: collation,
111127
comment: comment.presence,
112128
serial: serial,
129+
identity: identity.presence,
113130
spatial: spatial,
114-
generated: generated,
131+
generated: attgenerated,
115132
hidden: hidden
116133
)
117134
end
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
module ActiveRecord
22
module Type
3-
class << self
3+
module CRDBExt
44
# Return :postgresql instead of :cockroachdb for current_adapter_name so
55
# we can continue using the ActiveRecord::Types defined in
66
# PostgreSQLAdapter.
77
def adapter_name_from(model)
8-
name = model.connection_db_config.adapter.to_sym
8+
name = super
99
return :postgresql if name == :cockroachdb
1010

1111
name
1212
end
1313
end
14+
singleton_class.prepend CRDBExt
1415
end
1516
end

0 commit comments

Comments
 (0)