activerecord-cockroachdb-adapter 7.1.1 → 7.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/.github/workflows/ci.yml +6 -3
- data/.gitignore +1 -0
- data/CHANGELOG.md +7 -1
- data/CONTRIBUTING.md +39 -90
- data/Gemfile +4 -4
- data/LICENSE +1 -2
- data/README.md +7 -5
- data/activerecord-cockroachdb-adapter.gemspec +2 -2
- data/bin/console +7 -5
- data/bin/console_schemas/default.rb +2 -0
- data/lib/active_record/connection_adapters/cockroachdb/arel_tosql.rb +14 -0
- data/lib/active_record/connection_adapters/cockroachdb/attribute_methods.rb +16 -0
- data/lib/active_record/connection_adapters/cockroachdb/column.rb +16 -0
- data/lib/active_record/connection_adapters/cockroachdb/column_methods.rb +14 -0
- data/lib/active_record/connection_adapters/cockroachdb/database_statements.rb +27 -3
- data/lib/active_record/connection_adapters/cockroachdb/database_tasks.rb +18 -2
- data/lib/active_record/connection_adapters/cockroachdb/oid/date_time.rb +14 -0
- data/lib/active_record/connection_adapters/cockroachdb/oid/interval.rb +16 -0
- data/lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb +14 -0
- data/lib/active_record/connection_adapters/cockroachdb/quoting.rb +16 -0
- data/lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb +99 -9
- data/lib/active_record/connection_adapters/cockroachdb/schema_creation.rb +14 -1
- data/lib/active_record/connection_adapters/cockroachdb/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +125 -25
- data/lib/active_record/connection_adapters/cockroachdb/setup.rb +14 -0
- data/lib/active_record/connection_adapters/cockroachdb/spatial_column_info.rb +16 -0
- data/lib/active_record/connection_adapters/cockroachdb/table_definition.rb +14 -0
- data/lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb +35 -0
- data/lib/active_record/connection_adapters/cockroachdb/type.rb +16 -0
- data/lib/active_record/connection_adapters/cockroachdb_adapter.rb +56 -55
- data/lib/active_record/migration/cockroachdb/compatibility.rb +16 -0
- data/lib/active_record/relation/query_methods_ext.rb +14 -0
- data/lib/activerecord-cockroachdb-adapter.rb +21 -0
- data/lib/arel/nodes/join_source_ext.rb +16 -0
- data/lib/version.rb +15 -1
- metadata +7 -8
- data/.ruby-version +0 -1
@@ -1,5 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Copyright 2024 The Cockroach Authors.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
3
17
|
# The PostgresSQL Adapter's ReferentialIntegrity module can disable and
|
4
18
|
# re-enable foreign key constraints by disabling all table triggers. Since
|
5
19
|
# triggers are not available in CockroachDB, we have to remove foreign keys and
|
@@ -20,11 +34,18 @@ module ActiveRecord
|
|
20
34
|
end
|
21
35
|
|
22
36
|
def disable_referential_integrity
|
23
|
-
foreign_keys =
|
37
|
+
foreign_keys = all_foreign_keys
|
24
38
|
|
25
|
-
foreign_keys.
|
26
|
-
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)
|
27
47
|
end
|
48
|
+
execute_batch(statements, "Disable referential integrity -> remove foreign keys")
|
28
49
|
|
29
50
|
yield
|
30
51
|
|
@@ -38,19 +59,88 @@ module ActiveRecord
|
|
38
59
|
ActiveRecord::Base.table_name_suffix = ""
|
39
60
|
|
40
61
|
begin
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
46
76
|
|
47
|
-
|
77
|
+
schema_creation.accept(at)
|
48
78
|
end
|
79
|
+
execute_batch(statements.compact, "Disable referential integrity -> add foreign keys")
|
49
80
|
ensure
|
50
81
|
ActiveRecord::Base.table_name_prefix = old_prefix
|
51
82
|
ActiveRecord::Base.table_name_suffix = old_suffix
|
52
83
|
end
|
53
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 = 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
|
54
144
|
end
|
55
145
|
end
|
56
146
|
end
|
@@ -1,5 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Copyright 2024 The Cockroach Authors.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
3
17
|
module ActiveRecord
|
4
18
|
module ConnectionAdapters
|
5
19
|
module CockroachDB
|
@@ -15,4 +29,3 @@ module ActiveRecord
|
|
15
29
|
end
|
16
30
|
end
|
17
31
|
end
|
18
|
-
|
@@ -1,5 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Copyright 2024 The Cockroach Authors.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
3
17
|
module ActiveRecord
|
4
18
|
module ConnectionAdapters
|
5
19
|
module CockroachDB
|
@@ -16,4 +30,3 @@ module ActiveRecord
|
|
16
30
|
end
|
17
31
|
end
|
18
32
|
end
|
19
|
-
|
@@ -1,3 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2024 The Cockroach Authors.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
1
17
|
module ActiveRecord
|
2
18
|
module ConnectionAdapters
|
3
19
|
module CockroachDB
|
@@ -39,6 +55,115 @@ module ActiveRecord
|
|
39
55
|
end
|
40
56
|
end
|
41
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
|
+
|
89
|
+
# OVERRIDE: CockroachDB does not support deferrable constraints.
|
90
|
+
# See: https://go.crdb.dev/issue-v/31632/v23.1
|
91
|
+
def foreign_key_options(from_table, to_table, options)
|
92
|
+
options = super
|
93
|
+
options.delete(:deferrable) unless supports_deferrable_constraints?
|
94
|
+
options
|
95
|
+
end
|
96
|
+
|
97
|
+
# OVERRIDE: Added `unique_rowid` to the last line of the second query.
|
98
|
+
# This is a CockroachDB-specific function used for primary keys.
|
99
|
+
# Also make sure we don't consider `NOT VISIBLE` columns.
|
100
|
+
#
|
101
|
+
# Returns a table's primary key and belonging sequence.
|
102
|
+
def pk_and_sequence_for(table) # :nodoc:
|
103
|
+
# First try looking for a sequence with a dependency on the
|
104
|
+
# given table's primary key.
|
105
|
+
result = query(<<~SQL, "SCHEMA")[0]
|
106
|
+
SELECT attr.attname, nsp.nspname, seq.relname
|
107
|
+
FROM pg_class seq,
|
108
|
+
pg_attribute attr,
|
109
|
+
pg_depend dep,
|
110
|
+
pg_constraint cons,
|
111
|
+
pg_namespace nsp,
|
112
|
+
-- TODO: use the pg_catalog.pg_attribute(attishidden) column when
|
113
|
+
-- it is added instead of joining on crdb_internal.
|
114
|
+
-- See https://github.com/cockroachdb/cockroach/pull/126397
|
115
|
+
crdb_internal.table_columns tc
|
116
|
+
WHERE seq.oid = dep.objid
|
117
|
+
AND seq.relkind = 'S'
|
118
|
+
AND attr.attrelid = dep.refobjid
|
119
|
+
AND attr.attnum = dep.refobjsubid
|
120
|
+
AND attr.attrelid = cons.conrelid
|
121
|
+
AND attr.attnum = cons.conkey[1]
|
122
|
+
AND seq.relnamespace = nsp.oid
|
123
|
+
AND attr.attrelid = tc.descriptor_id
|
124
|
+
AND attr.attname = tc.column_name
|
125
|
+
AND tc.hidden = false
|
126
|
+
AND cons.contype = 'p'
|
127
|
+
AND dep.classid = 'pg_class'::regclass
|
128
|
+
AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
|
129
|
+
SQL
|
130
|
+
|
131
|
+
if result.nil? || result.empty?
|
132
|
+
result = query(<<~SQL, "SCHEMA")[0]
|
133
|
+
SELECT attr.attname, nsp.nspname,
|
134
|
+
CASE
|
135
|
+
WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL
|
136
|
+
WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN
|
137
|
+
substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2),
|
138
|
+
strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1)
|
139
|
+
ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2)
|
140
|
+
END
|
141
|
+
FROM pg_class t
|
142
|
+
JOIN pg_attribute attr ON (t.oid = attrelid)
|
143
|
+
JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
|
144
|
+
JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
|
145
|
+
JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
|
146
|
+
-- TODO: use the pg_catalog.pg_attribute(attishidden) column when
|
147
|
+
-- it is added instead of joining on crdb_internal.
|
148
|
+
-- See https://github.com/cockroachdb/cockroach/pull/126397
|
149
|
+
JOIN crdb_internal.table_columns tc ON (attr.attrelid = tc.descriptor_id AND attr.attname = tc.column_name)
|
150
|
+
WHERE t.oid = #{quote(quote_table_name(table))}::regclass
|
151
|
+
AND tc.hidden = false
|
152
|
+
AND cons.contype = 'p'
|
153
|
+
AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate|gen_random_uuid|unique_rowid'
|
154
|
+
SQL
|
155
|
+
end
|
156
|
+
|
157
|
+
pk = result.shift
|
158
|
+
if result.last
|
159
|
+
[pk, PostgreSQL::Name.new(*result)]
|
160
|
+
else
|
161
|
+
[pk, nil]
|
162
|
+
end
|
163
|
+
rescue
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
|
42
167
|
# override
|
43
168
|
# Modified version of the postgresql foreign_keys method.
|
44
169
|
# Replaces t2.oid::regclass::text with t2.relname since this is
|
@@ -171,31 +296,6 @@ module ActiveRecord
|
|
171
296
|
sql
|
172
297
|
end
|
173
298
|
|
174
|
-
# This overrides the method from PostegreSQL adapter
|
175
|
-
# Resets the sequence of a table's primary key to the maximum value.
|
176
|
-
def reset_pk_sequence!(table, pk = nil, sequence = nil)
|
177
|
-
unless pk && sequence
|
178
|
-
default_pk, default_sequence = pk_and_sequence_for(table)
|
179
|
-
|
180
|
-
pk ||= default_pk
|
181
|
-
sequence ||= default_sequence
|
182
|
-
end
|
183
|
-
|
184
|
-
if @logger && pk && !sequence
|
185
|
-
@logger.warn "#{table} has primary key #{pk} with no default sequence."
|
186
|
-
end
|
187
|
-
|
188
|
-
if pk && sequence
|
189
|
-
quoted_sequence = quote_table_name(sequence)
|
190
|
-
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
|
191
|
-
if max_pk.nil?
|
192
|
-
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
193
|
-
end
|
194
|
-
|
195
|
-
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
299
|
# override
|
200
300
|
def native_database_types
|
201
301
|
# Add spatial types
|
@@ -1,5 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Copyright 2024 The Cockroach Authors.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
3
17
|
module ActiveRecord # :nodoc:
|
4
18
|
module ConnectionAdapters # :nodoc:
|
5
19
|
module CockroachDB # :nodoc:
|
@@ -1,3 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2024 The Cockroach Authors.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
1
17
|
module ActiveRecord
|
2
18
|
module ConnectionAdapters
|
3
19
|
module CockroachDB
|
@@ -1,5 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Copyright 2024 The Cockroach Authors.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
3
17
|
module ActiveRecord # :nodoc:
|
4
18
|
module ConnectionAdapters # :nodoc:
|
5
19
|
module CockroachDB # :nodoc:
|
@@ -1,5 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Copyright 2024 The Cockroach Authors.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
3
17
|
module ActiveRecord
|
4
18
|
module ConnectionAdapters
|
5
19
|
module CockroachDB
|
@@ -31,6 +45,27 @@ module ActiveRecord
|
|
31
45
|
within_new_transaction(isolation: isolation, joinable: joinable, attempts: attempts + 1) { yield }
|
32
46
|
end
|
33
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
|
+
|
34
69
|
def retryable?(error)
|
35
70
|
return true if serialization_error?(error)
|
36
71
|
return true if error.is_a? ActiveRecord::SerializationFailure
|
@@ -1,3 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2024 The Cockroach Authors.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
1
17
|
module ActiveRecord
|
2
18
|
module Type
|
3
19
|
module CRDBExt
|
@@ -1,5 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Copyright 2024 The Cockroach Authors.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
3
17
|
require "rgeo/active_record"
|
4
18
|
|
5
19
|
require_relative "../../arel/nodes/join_source_ext"
|
@@ -31,20 +45,6 @@ require_relative "../relation/query_methods_ext"
|
|
31
45
|
# Defined in ./setup.rb
|
32
46
|
ActiveRecord::ConnectionAdapters::CockroachDB.initial_setup
|
33
47
|
|
34
|
-
module ActiveRecord
|
35
|
-
# TODO: once in rails 7.2, remove this and replace with a `#register` call.
|
36
|
-
# See: https://github.com/rails/rails/commit/22a26d7f74ea8f0d5f7c4169531ae38441cfd5e5#diff-2468c670eb10c24bd2823e42708489a336d6f21c6efc7e3c4a574166fa77bb22
|
37
|
-
module ConnectionHandling
|
38
|
-
def cockroachdb_adapter_class
|
39
|
-
ConnectionAdapters::CockroachDBAdapter
|
40
|
-
end
|
41
|
-
|
42
|
-
def cockroachdb_connection(config)
|
43
|
-
cockroachdb_adapter_class.new(config)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
48
|
module ActiveRecord
|
49
49
|
module ConnectionAdapters
|
50
50
|
module CockroachDBConnectionPool
|
@@ -137,15 +137,23 @@ module ActiveRecord
|
|
137
137
|
end
|
138
138
|
|
139
139
|
def get_database_version
|
140
|
-
|
141
|
-
|
140
|
+
with_raw_connection do |conn|
|
141
|
+
conn.async_exec("SHOW crdb_version") do |result|
|
142
|
+
major, minor, patch = result
|
143
|
+
.getvalue(0, 0)
|
144
|
+
.match(/v(\d+).(\d+).(\d+)/)
|
145
|
+
.captures
|
146
|
+
.map(&:to_i)
|
147
|
+
major * 100 * 100 + minor * 100 + patch
|
148
|
+
end
|
149
|
+
end
|
142
150
|
end
|
143
151
|
undef :postgresql_version
|
144
152
|
alias :cockroachdb_version :database_version
|
145
153
|
|
146
154
|
def supports_datetime_with_precision?
|
147
155
|
# https://github.com/cockroachdb/cockroach/pull/111400
|
148
|
-
|
156
|
+
true
|
149
157
|
end
|
150
158
|
|
151
159
|
def supports_nulls_not_distinct?
|
@@ -162,7 +170,7 @@ module ActiveRecord
|
|
162
170
|
end
|
163
171
|
|
164
172
|
def supports_materialized_views?
|
165
|
-
|
173
|
+
true
|
166
174
|
end
|
167
175
|
|
168
176
|
def supports_index_include?
|
@@ -201,50 +209,44 @@ module ActiveRecord
|
|
201
209
|
end
|
202
210
|
|
203
211
|
def supports_deferrable_constraints?
|
212
|
+
# https://go.crdb.dev/issue-v/31632/v23.1
|
204
213
|
false
|
205
214
|
end
|
206
215
|
|
207
|
-
#
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
# super(connection, logger, conn_params, config)
|
213
|
-
|
214
|
-
# # crdb_version is the version of the binary running on the node. We
|
215
|
-
# # really want to use `SHOW CLUSTER SETTING version` to get the cluster
|
216
|
-
# # version, but that is only available to admins. Instead, we can use
|
217
|
-
# # crdb_internal.is_at_least_version, but that's only available in 22.1.
|
218
|
-
# crdb_version_string = query_value("SHOW crdb_version")
|
219
|
-
# if crdb_version_string.include? "v22.1"
|
220
|
-
# version_num = query_value(<<~SQL, "VERSION")
|
221
|
-
# SELECT
|
222
|
-
# CASE
|
223
|
-
# WHEN crdb_internal.is_at_least_version('22.2') THEN 2220
|
224
|
-
# WHEN crdb_internal.is_at_least_version('22.1') THEN 2210
|
225
|
-
# ELSE 2120
|
226
|
-
# END;
|
227
|
-
# SQL
|
228
|
-
# end
|
229
|
-
# @crdb_version = version_num.to_i
|
230
|
-
# end
|
231
|
-
|
232
|
-
def self.database_exists?(config)
|
233
|
-
!!ActiveRecord::Base.cockroachdb_connection(config)
|
234
|
-
rescue ActiveRecord::NoDatabaseError
|
235
|
-
false
|
216
|
+
def check_version # :nodoc:
|
217
|
+
# https://www.cockroachlabs.com/docs/releases/release-support-policy
|
218
|
+
if database_version < 23_01_12 # < 23.1.12
|
219
|
+
raise "Your version of CockroachDB (#{database_version}) is too old. Active Record supports CockroachDB >= 23.1.12."
|
220
|
+
end
|
236
221
|
end
|
237
222
|
|
238
|
-
def
|
223
|
+
def configure_connection(...)
|
239
224
|
super
|
240
225
|
|
241
226
|
# This rescue flow appears in new_client, but it is needed here as well
|
242
227
|
# since Cockroach will sometimes not raise until a query is made.
|
228
|
+
#
|
229
|
+
# See https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/337#issuecomment-2328419453
|
230
|
+
#
|
231
|
+
# The error conditions used to differ from the ones in new_client, but
|
232
|
+
# the reasons why are no longer relevant. We keep this in sync with new_client
|
233
|
+
# even though some conditions might never be checked.
|
234
|
+
#
|
235
|
+
# See https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/229
|
236
|
+
#
|
237
|
+
# We have to rescue `ActiveRecord::StatementInvalid` instead of `::PG::Error`
|
238
|
+
# here as the error has already been casted (in `#with_raw_connection` as
|
239
|
+
# of Rails 7.2.1).
|
243
240
|
rescue ActiveRecord::StatementInvalid => error
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
241
|
+
conn_params = @connection_parameters
|
242
|
+
if conn_params && conn_params[:dbname] == "postgres"
|
243
|
+
raise ActiveRecord::ConnectionNotEstablished, error.message
|
244
|
+
elsif conn_params && conn_params[:dbname] && error.cause.message.include?(conn_params[:dbname])
|
245
|
+
raise ActiveRecord::NoDatabaseError.db_error(conn_params[:dbname])
|
246
|
+
elsif conn_params && conn_params[:user] && error.cause.message.include?(conn_params[:user])
|
247
|
+
raise ActiveRecord::DatabaseConnectionError.username_error(conn_params[:user])
|
248
|
+
elsif conn_params && conn_params[:host] && error.cause.message.include?(conn_params[:host])
|
249
|
+
raise ActiveRecord::DatabaseConnectionError.hostname_error(conn_params[:host])
|
248
250
|
else
|
249
251
|
raise ActiveRecord::ConnectionNotEstablished, error.message
|
250
252
|
end
|
@@ -292,7 +294,6 @@ module ActiveRecord
|
|
292
294
|
precision = extract_precision(sql_type)
|
293
295
|
scale = extract_scale(sql_type)
|
294
296
|
|
295
|
-
|
296
297
|
# The type for the numeric depends on the width of the field,
|
297
298
|
# so we'll do something special here.
|
298
299
|
#
|
@@ -511,7 +512,7 @@ module ActiveRecord
|
|
511
512
|
# so this modified query uses AS OF SYSTEM TIME '-10s' to read historical data.
|
512
513
|
def add_pg_decoders
|
513
514
|
if @config[:use_follower_reads_for_type_introspection]
|
514
|
-
@
|
515
|
+
@mapped_default_timezone = nil
|
515
516
|
@timestamp_decoder = nil
|
516
517
|
|
517
518
|
coders_by_name = {
|
@@ -526,6 +527,7 @@ module ActiveRecord
|
|
526
527
|
"timestamp" => PG::TextDecoder::TimestampUtc,
|
527
528
|
"timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
|
528
529
|
}
|
530
|
+
coders_by_name["date"] = PG::TextDecoder::Date if decode_dates
|
529
531
|
|
530
532
|
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
|
531
533
|
query = <<~SQL % known_coder_types.join(", ")
|
@@ -533,7 +535,6 @@ module ActiveRecord
|
|
533
535
|
FROM pg_type as t AS OF SYSTEM TIME '-10s'
|
534
536
|
WHERE t.typname IN (%s)
|
535
537
|
SQL
|
536
|
-
|
537
538
|
coders = execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
|
538
539
|
result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
|
539
540
|
end
|