activerecord-cockroachdb-adapter 7.0.3 → 7.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +7 -0
- data/.github/workflows/ci.yml +43 -38
- data/.ruby-version +1 -1
- data/CHANGELOG.md +12 -0
- data/Gemfile +14 -1
- data/activerecord-cockroachdb-adapter.gemspec +1 -1
- data/bin/console +17 -33
- data/bin/console_schemas/default.rb +9 -0
- data/bin/console_schemas/schemas.rb +23 -0
- data/bin/start-cockroachdb +6 -21
- data/build/Dockerfile +1 -1
- data/lib/active_record/connection_adapters/cockroachdb/arel_tosql.rb +8 -0
- data/lib/active_record/connection_adapters/cockroachdb/column.rb +3 -7
- data/lib/active_record/connection_adapters/cockroachdb/column_methods.rb +8 -0
- data/lib/active_record/connection_adapters/cockroachdb/database_statements.rb +0 -83
- data/lib/active_record/connection_adapters/cockroachdb/database_tasks.rb +32 -24
- data/lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb +14 -10
- data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +49 -14
- data/lib/active_record/connection_adapters/cockroachdb/type.rb +3 -2
- data/lib/active_record/connection_adapters/cockroachdb_adapter.rb +116 -233
- data/lib/active_record/relation/query_methods_ext.rb +29 -1
- data/lib/arel/nodes/join_source_ext.rb +28 -0
- data/lib/version.rb +1 -1
- data/setup.sql +18 -0
- metadata +9 -5
- data/lib/active_record/connection_adapters/cockroachdb/oid/type_map_initializer.rb +0 -26
@@ -4,6 +4,13 @@ module ActiveRecord
|
|
4
4
|
module SchemaStatements
|
5
5
|
include ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements
|
6
6
|
|
7
|
+
# OVERRIDE: We do not want to see the crdb_internal schema in the names.
|
8
|
+
#
|
9
|
+
# Returns an array of schema names.
|
10
|
+
def schema_names
|
11
|
+
super - ["crdb_internal"]
|
12
|
+
end
|
13
|
+
|
7
14
|
def add_index(table_name, column_name, **options)
|
8
15
|
super
|
9
16
|
rescue ActiveRecord::StatementInvalid => error
|
@@ -36,16 +43,25 @@ module ActiveRecord
|
|
36
43
|
# Modified version of the postgresql foreign_keys method.
|
37
44
|
# Replaces t2.oid::regclass::text with t2.relname since this is
|
38
45
|
# more efficient in CockroachDB.
|
46
|
+
# Also, CockroachDB does not append the schema name in relname,
|
47
|
+
# so we append it manually.
|
39
48
|
def foreign_keys(table_name)
|
40
49
|
scope = quoted_scope(table_name)
|
41
|
-
fk_info =
|
42
|
-
SELECT
|
50
|
+
fk_info = internal_exec_query(<<~SQL, "SCHEMA")
|
51
|
+
SELECT CASE
|
52
|
+
WHEN n2.nspname = current_schema()
|
53
|
+
THEN ''
|
54
|
+
ELSE n2.nspname || '.'
|
55
|
+
END || t2.relname AS to_table,
|
56
|
+
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,
|
57
|
+
c.conkey, c.confkey, c.conrelid, c.confrelid
|
43
58
|
FROM pg_constraint c
|
44
59
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
45
60
|
JOIN pg_class t2 ON c.confrelid = t2.oid
|
46
61
|
JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
|
47
62
|
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
|
48
63
|
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
64
|
+
JOIN pg_namespace n2 ON t2.relnamespace = n2.oid
|
49
65
|
WHERE c.contype = 'f'
|
50
66
|
AND t1.relname = #{scope[:name]}
|
51
67
|
AND t3.nspname = #{scope[:schema]}
|
@@ -53,17 +69,31 @@ module ActiveRecord
|
|
53
69
|
SQL
|
54
70
|
|
55
71
|
fk_info.map do |row|
|
72
|
+
to_table = PostgreSQL::Utils.unquote_identifier(row["to_table"])
|
73
|
+
conkey = row["conkey"].scan(/\d+/).map(&:to_i)
|
74
|
+
confkey = row["confkey"].scan(/\d+/).map(&:to_i)
|
75
|
+
|
76
|
+
if conkey.size > 1
|
77
|
+
column = column_names_from_column_numbers(row["conrelid"], conkey)
|
78
|
+
primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
|
79
|
+
else
|
80
|
+
column = PostgreSQL::Utils.unquote_identifier(row["column"])
|
81
|
+
primary_key = row["primary_key"]
|
82
|
+
end
|
83
|
+
|
56
84
|
options = {
|
57
|
-
column:
|
85
|
+
column: column,
|
58
86
|
name: row["name"],
|
59
|
-
primary_key:
|
87
|
+
primary_key: primary_key
|
60
88
|
}
|
61
|
-
|
62
89
|
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
63
90
|
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
91
|
+
options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
|
92
|
+
|
64
93
|
options[:validate] = row["valid"]
|
94
|
+
to_table = PostgreSQL::Utils.unquote_identifier(row["to_table"])
|
65
95
|
|
66
|
-
ForeignKeyDefinition.new(table_name,
|
96
|
+
ForeignKeyDefinition.new(table_name, to_table, options)
|
67
97
|
end
|
68
98
|
end
|
69
99
|
|
@@ -76,16 +106,20 @@ module ActiveRecord
|
|
76
106
|
|
77
107
|
# override
|
78
108
|
# https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb#L624
|
79
|
-
def new_column_from_field(table_name, field)
|
80
|
-
column_name, type, default, notnull, oid, fmod, collation, comment,
|
109
|
+
def new_column_from_field(table_name, field, _definition)
|
110
|
+
column_name, type, default, notnull, oid, fmod, collation, comment, identity, attgenerated, hidden = field
|
81
111
|
type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
|
82
112
|
default_value = extract_value_from_default(default)
|
83
|
-
default_function = extract_default_function(default_value, default)
|
84
113
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
114
|
+
if attgenerated.present?
|
115
|
+
default_function = default
|
116
|
+
else
|
117
|
+
default_function = extract_default_function(default_value, default)
|
118
|
+
end
|
119
|
+
|
120
|
+
if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
|
121
|
+
serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
|
122
|
+
end
|
89
123
|
|
90
124
|
# {:dimension=>2, :has_m=>false, :has_z=>false, :name=>"latlon", :srid=>0, :type=>"GEOMETRY"}
|
91
125
|
spatial = spatial_column_info(table_name).get(column_name, type_metadata.sql_type)
|
@@ -99,8 +133,9 @@ module ActiveRecord
|
|
99
133
|
collation: collation,
|
100
134
|
comment: comment.presence,
|
101
135
|
serial: serial,
|
136
|
+
identity: identity.presence,
|
102
137
|
spatial: spatial,
|
103
|
-
generated:
|
138
|
+
generated: attgenerated,
|
104
139
|
hidden: hidden
|
105
140
|
)
|
106
141
|
end
|
@@ -1,15 +1,16 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Type
|
3
|
-
|
3
|
+
module CRDBExt
|
4
4
|
# Return :postgresql instead of :cockroachdb for current_adapter_name so
|
5
5
|
# we can continue using the ActiveRecord::Types defined in
|
6
6
|
# PostgreSQLAdapter.
|
7
7
|
def adapter_name_from(model)
|
8
|
-
name =
|
8
|
+
name = super
|
9
9
|
return :postgresql if name == :cockroachdb
|
10
10
|
|
11
11
|
name
|
12
12
|
end
|
13
13
|
end
|
14
|
+
singleton_class.prepend CRDBExt
|
14
15
|
end
|
15
16
|
end
|
@@ -1,5 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "rgeo/active_record"
|
2
4
|
|
5
|
+
require_relative "../../arel/nodes/join_source_ext"
|
3
6
|
require "active_record/connection_adapters/postgresql_adapter"
|
4
7
|
require "active_record/connection_adapters/cockroachdb/attribute_methods"
|
5
8
|
require "active_record/connection_adapters/cockroachdb/column_methods"
|
@@ -15,7 +18,6 @@ require "active_record/connection_adapters/cockroachdb/type"
|
|
15
18
|
require "active_record/connection_adapters/cockroachdb/column"
|
16
19
|
require "active_record/connection_adapters/cockroachdb/spatial_column_info"
|
17
20
|
require "active_record/connection_adapters/cockroachdb/setup"
|
18
|
-
require "active_record/connection_adapters/cockroachdb/oid/type_map_initializer"
|
19
21
|
require "active_record/connection_adapters/cockroachdb/oid/spatial"
|
20
22
|
require "active_record/connection_adapters/cockroachdb/oid/interval"
|
21
23
|
require "active_record/connection_adapters/cockroachdb/oid/date_time"
|
@@ -30,35 +32,15 @@ require_relative "../relation/query_methods_ext"
|
|
30
32
|
ActiveRecord::ConnectionAdapters::CockroachDB.initial_setup
|
31
33
|
|
32
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
|
33
37
|
module ConnectionHandling
|
38
|
+
def cockroachdb_adapter_class
|
39
|
+
ConnectionAdapters::CockroachDBAdapter
|
40
|
+
end
|
41
|
+
|
34
42
|
def cockroachdb_connection(config)
|
35
|
-
|
36
|
-
conn_params = config.symbolize_keys.compact
|
37
|
-
|
38
|
-
# Map ActiveRecords param names to PGs.
|
39
|
-
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
|
40
|
-
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
|
41
|
-
|
42
|
-
# Forward only valid config params to PG::Connection.connect.
|
43
|
-
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
|
44
|
-
conn_params.slice!(*valid_conn_param_keys)
|
45
|
-
|
46
|
-
ConnectionAdapters::CockroachDBAdapter.new(
|
47
|
-
ConnectionAdapters::CockroachDBAdapter.new_client(conn_params),
|
48
|
-
logger,
|
49
|
-
conn_params,
|
50
|
-
config
|
51
|
-
)
|
52
|
-
# This rescue flow appears in new_client, but it is needed here as well
|
53
|
-
# since Cockroach will sometimes not raise until a query is made.
|
54
|
-
rescue ActiveRecord::StatementInvalid => error
|
55
|
-
no_db_err_check1 = conn_params && conn_params[:dbname] && error.cause.message.include?(conn_params[:dbname])
|
56
|
-
no_db_err_check2 = conn_params && conn_params[:dbname] && error.cause.message.include?("pg_type")
|
57
|
-
if no_db_err_check1 || no_db_err_check2
|
58
|
-
raise ActiveRecord::NoDatabaseError
|
59
|
-
else
|
60
|
-
raise ActiveRecord::ConnectionNotEstablished, error.message
|
61
|
-
end
|
43
|
+
cockroachdb_adapter_class.new(config)
|
62
44
|
end
|
63
45
|
end
|
64
46
|
end
|
@@ -100,7 +82,7 @@ module ActiveRecord
|
|
100
82
|
ConnectionPool.prepend(CockroachDBConnectionPool)
|
101
83
|
|
102
84
|
class CockroachDBAdapter < PostgreSQLAdapter
|
103
|
-
ADAPTER_NAME = "CockroachDB"
|
85
|
+
ADAPTER_NAME = "CockroachDB"
|
104
86
|
DEFAULT_PRIMARY_KEY = "rowid"
|
105
87
|
|
106
88
|
SPATIAL_COLUMN_OPTIONS =
|
@@ -154,18 +136,21 @@ module ActiveRecord
|
|
154
136
|
@max_transaction_retries ||= @config.fetch(:max_transaction_retries, 3)
|
155
137
|
end
|
156
138
|
|
157
|
-
|
158
|
-
|
159
|
-
|
139
|
+
def get_database_version
|
140
|
+
major, minor, patch = query_value("SHOW crdb_version").match(/v(\d+).(\d+).(\d+)/)[1..].map(&:to_i)
|
141
|
+
major * 100 * 100 + minor * 100 + patch
|
160
142
|
end
|
143
|
+
undef :postgresql_version
|
144
|
+
alias :cockroachdb_version :database_version
|
161
145
|
|
162
|
-
def
|
163
|
-
|
146
|
+
def supports_datetime_with_precision?
|
147
|
+
# https://github.com/cockroachdb/cockroach/pull/111400
|
148
|
+
database_version >= 23_01_13
|
164
149
|
end
|
165
150
|
|
166
|
-
def
|
167
|
-
#
|
168
|
-
|
151
|
+
def supports_nulls_not_distinct?
|
152
|
+
# https://github.com/cockroachdb/cockroach/issues/115836
|
153
|
+
false
|
169
154
|
end
|
170
155
|
|
171
156
|
def supports_ddl_transactions?
|
@@ -180,8 +165,12 @@ module ActiveRecord
|
|
180
165
|
false
|
181
166
|
end
|
182
167
|
|
183
|
-
def
|
184
|
-
|
168
|
+
def supports_index_include?
|
169
|
+
false
|
170
|
+
end
|
171
|
+
|
172
|
+
def supports_exclusion_constraints?
|
173
|
+
false
|
185
174
|
end
|
186
175
|
|
187
176
|
def supports_expression_index?
|
@@ -191,14 +180,6 @@ module ActiveRecord
|
|
191
180
|
false
|
192
181
|
end
|
193
182
|
|
194
|
-
def supports_datetime_with_precision?
|
195
|
-
false
|
196
|
-
end
|
197
|
-
|
198
|
-
def supports_comments?
|
199
|
-
@crdb_version >= 2010
|
200
|
-
end
|
201
|
-
|
202
183
|
def supports_comments_in_create?
|
203
184
|
false
|
204
185
|
end
|
@@ -208,11 +189,11 @@ module ActiveRecord
|
|
208
189
|
end
|
209
190
|
|
210
191
|
def supports_virtual_columns?
|
211
|
-
|
192
|
+
true
|
212
193
|
end
|
213
194
|
|
214
195
|
def supports_string_to_array_coercion?
|
215
|
-
|
196
|
+
true
|
216
197
|
end
|
217
198
|
|
218
199
|
def supports_partitioned_indexes?
|
@@ -223,77 +204,30 @@ module ActiveRecord
|
|
223
204
|
false
|
224
205
|
end
|
225
206
|
|
226
|
-
# This
|
227
|
-
#
|
228
|
-
#
|
229
|
-
# lengths far greater than this value. For the time being though, we match
|
230
|
-
# the original behavior for PostgreSQL to simplify migrations.
|
207
|
+
# NOTE: This commented bit of code allows to have access to crdb version,
|
208
|
+
# which can be useful for feature detection. However, we currently don't
|
209
|
+
# need, hence we avoid the extra queries.
|
231
210
|
#
|
232
|
-
#
|
233
|
-
#
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
CASE
|
253
|
-
WHEN crdb_internal.is_at_least_version('22.2') THEN 2220
|
254
|
-
WHEN crdb_internal.is_at_least_version('22.1') THEN 2210
|
255
|
-
ELSE 2120
|
256
|
-
END;
|
257
|
-
SQL
|
258
|
-
else
|
259
|
-
# This branch can be removed once the dialect stops supporting v21.2
|
260
|
-
# and earlier.
|
261
|
-
if crdb_version_string.include? "v1."
|
262
|
-
version_num = 1
|
263
|
-
elsif crdb_version_string.include? "v2."
|
264
|
-
version_num 2
|
265
|
-
elsif crdb_version_string.include? "v19.1."
|
266
|
-
version_num = 1910
|
267
|
-
elsif crdb_version_string.include? "v19.2."
|
268
|
-
version_num = 1920
|
269
|
-
elsif crdb_version_string.include? "v20.1."
|
270
|
-
version_num = 2010
|
271
|
-
elsif crdb_version_string.include? "v20.2."
|
272
|
-
version_num = 2020
|
273
|
-
elsif crdb_version_string.include? "v21.1."
|
274
|
-
version_num = 2110
|
275
|
-
else
|
276
|
-
version_num = 2120
|
277
|
-
end
|
278
|
-
end
|
279
|
-
@crdb_version = version_num.to_i
|
280
|
-
|
281
|
-
# NOTE: this is normally in configure_connection, but that is run
|
282
|
-
# before crdb_version is determined. Once all supported versions
|
283
|
-
# of CockroachDB support SET intervalstyle it can safely be moved
|
284
|
-
# back.
|
285
|
-
# Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
|
286
|
-
if @crdb_version >= 2120
|
287
|
-
begin
|
288
|
-
execute("SET intervalstyle_enabled = true", "SCHEMA")
|
289
|
-
execute("SET intervalstyle = iso_8601", "SCHEMA")
|
290
|
-
rescue
|
291
|
-
# Ignore any error. This can happen with a cluster that has
|
292
|
-
# not yet finalized the v21.2 upgrade. v21.2 does not have
|
293
|
-
# a way to tell if the upgrade was finalized (see comment above).
|
294
|
-
end
|
295
|
-
end
|
296
|
-
end
|
211
|
+
# def initialize(connection, logger, conn_params, config)
|
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
|
297
231
|
|
298
232
|
def self.database_exists?(config)
|
299
233
|
!!ActiveRecord::Base.cockroachdb_connection(config)
|
@@ -301,17 +235,32 @@ module ActiveRecord
|
|
301
235
|
false
|
302
236
|
end
|
303
237
|
|
238
|
+
def initialize(...)
|
239
|
+
super
|
240
|
+
|
241
|
+
# This rescue flow appears in new_client, but it is needed here as well
|
242
|
+
# since Cockroach will sometimes not raise until a query is made.
|
243
|
+
rescue ActiveRecord::StatementInvalid => error
|
244
|
+
no_db_err_check1 = @connection_parameters && @connection_parameters[:dbname] && error.cause.message.include?(@connection_parameters[:dbname])
|
245
|
+
no_db_err_check2 = @connection_parameters && @connection_parameters[:dbname] && error.cause.message.include?("pg_type")
|
246
|
+
if no_db_err_check1 || no_db_err_check2
|
247
|
+
raise ActiveRecord::NoDatabaseError
|
248
|
+
else
|
249
|
+
raise ActiveRecord::ConnectionNotEstablished, error.message
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
304
253
|
# override
|
305
254
|
# The PostgreSQLAdapter uses syntax for an anonymous function
|
306
255
|
# (DO $$) that CockroachDB does not support.
|
307
256
|
#
|
308
257
|
# Given a name and an array of values, creates an enum type.
|
309
|
-
def create_enum(name, values)
|
310
|
-
sql_values = values.map { |s|
|
258
|
+
def create_enum(name, values, **options)
|
259
|
+
sql_values = values.map { |s| quote(s) }.join(", ")
|
311
260
|
query = <<~SQL
|
312
|
-
CREATE TYPE IF NOT EXISTS
|
261
|
+
CREATE TYPE IF NOT EXISTS #{quote_table_name(name)} AS ENUM (#{sql_values});
|
313
262
|
SQL
|
314
|
-
|
263
|
+
internal_exec_query(query).tap { reload_type_map }
|
315
264
|
end
|
316
265
|
|
317
266
|
class << self
|
@@ -369,62 +318,11 @@ module ActiveRecord
|
|
369
318
|
|
370
319
|
private
|
371
320
|
|
372
|
-
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
373
|
-
# This is called by #connect and should not be called manually.
|
374
|
-
#
|
375
|
-
# NOTE(joey): This was cradled from postgresql_adapter.rb. This
|
376
|
-
# was due to needing to override configuration statements.
|
377
|
-
def configure_connection
|
378
|
-
if @config[:encoding]
|
379
|
-
@connection.set_client_encoding(@config[:encoding])
|
380
|
-
end
|
381
|
-
self.client_min_messages = @config[:min_messages] || "warning"
|
382
|
-
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
383
|
-
|
384
|
-
# Use standard-conforming strings so we don't have to do the E'...' dance.
|
385
|
-
set_standard_conforming_strings
|
386
|
-
|
387
|
-
variables = @config.fetch(:variables, {}).stringify_keys
|
388
|
-
|
389
|
-
# If using Active Record's time zone support configure the connection to return
|
390
|
-
# TIMESTAMP WITH ZONE types in UTC.
|
391
|
-
unless variables["timezone"]
|
392
|
-
if ActiveRecord.default_timezone == :utc
|
393
|
-
variables["timezone"] = "UTC"
|
394
|
-
elsif @local_tz
|
395
|
-
variables["timezone"] = @local_tz
|
396
|
-
end
|
397
|
-
end
|
398
|
-
|
399
|
-
# NOTE(joey): This is a workaround as CockroachDB 1.1.x
|
400
|
-
# supports SET TIME ZONE <...> and SET "time zone" = <...> but
|
401
|
-
# not SET timezone = <...>.
|
402
|
-
if variables.key?("timezone")
|
403
|
-
tz = variables.delete("timezone")
|
404
|
-
execute("SET TIME ZONE #{quote(tz)}", "SCHEMA")
|
405
|
-
end
|
406
|
-
|
407
|
-
# SET statements from :variables config hash
|
408
|
-
# https://www.postgresql.org/docs/current/static/sql-set.html
|
409
|
-
variables.map do |k, v|
|
410
|
-
if v == ":default" || v == :default
|
411
|
-
# Sets the value to the global or compile default
|
412
|
-
|
413
|
-
# NOTE(joey): I am not sure if simply commenting this out
|
414
|
-
# is technically correct.
|
415
|
-
# execute("SET #{k} = DEFAULT", "SCHEMA")
|
416
|
-
elsif !v.nil?
|
417
|
-
execute("SET SESSION #{k} = #{quote(v)}", "SCHEMA")
|
418
|
-
end
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
321
|
# Override extract_value_from_default because the upstream definition
|
423
322
|
# doesn't handle the variations in CockroachDB's behavior.
|
424
323
|
def extract_value_from_default(default)
|
425
324
|
super ||
|
426
325
|
extract_escaped_string_from_default(default) ||
|
427
|
-
extract_time_from_default(default) ||
|
428
326
|
extract_empty_array_from_default(default) ||
|
429
327
|
extract_decimal_from_default(default)
|
430
328
|
end
|
@@ -442,32 +340,6 @@ module ActiveRecord
|
|
442
340
|
"\"#{$1}\"".undump.gsub("\\'".freeze, "'".freeze)
|
443
341
|
end
|
444
342
|
|
445
|
-
# This method exists to extract the correct time and date defaults for a
|
446
|
-
# couple of reasons.
|
447
|
-
# 1) There's a bug in CockroachDB where the date type is missing from
|
448
|
-
# the column info query.
|
449
|
-
# https://github.com/cockroachdb/cockroach/issues/47285
|
450
|
-
# 2) PostgreSQL's timestamp without time zone type maps to CockroachDB's
|
451
|
-
# TIMESTAMP type. TIMESTAMP includes a UTC time zone while timestamp
|
452
|
-
# without time zone doesn't.
|
453
|
-
# https://www.cockroachlabs.com/docs/v19.2/timestamp.html#variants
|
454
|
-
def extract_time_from_default(default)
|
455
|
-
return unless default =~ /\A'(.*)'\z/
|
456
|
-
|
457
|
-
# If default has a UTC time zone, we'll drop the time zone information
|
458
|
-
# so it acts like PostgreSQL's timestamp without time zone. Then, try
|
459
|
-
# to parse the resulting string to verify if it's a time.
|
460
|
-
time = if default =~ /\A'(.*)(\+00:00)'\z/
|
461
|
-
$1
|
462
|
-
else
|
463
|
-
default
|
464
|
-
end
|
465
|
-
|
466
|
-
Time.parse(time).to_s
|
467
|
-
rescue
|
468
|
-
nil
|
469
|
-
end
|
470
|
-
|
471
343
|
# CockroachDB stores default values for arrays in the `ARRAY[...]` format.
|
472
344
|
# In general, it is hard to parse that, but it is easy to handle the common
|
473
345
|
# case of an empty array.
|
@@ -502,7 +374,8 @@ module ActiveRecord
|
|
502
374
|
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
503
375
|
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
504
376
|
c.collname, NULL AS comment,
|
505
|
-
|
377
|
+
attidentity,
|
378
|
+
attgenerated,
|
506
379
|
NULL as is_hidden
|
507
380
|
FROM pg_attribute a
|
508
381
|
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
@@ -517,26 +390,31 @@ module ActiveRecord
|
|
517
390
|
|
518
391
|
# Use regex comparison because if a type is an array it will
|
519
392
|
# have [] appended to the end of it.
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
393
|
+
re = /\A(?:geometry|geography|interval|numeric)/
|
394
|
+
|
395
|
+
# 0: attname
|
396
|
+
# 1: type
|
397
|
+
# 2: default
|
398
|
+
# 3: attnotnull
|
399
|
+
# 4: atttypid
|
400
|
+
# 5: atttypmod
|
401
|
+
# 6: collname
|
402
|
+
# 7: comment
|
403
|
+
# 8: attidentity
|
404
|
+
# 9: attgenerated
|
405
|
+
# 10: is_hidden
|
528
406
|
fields.map do |field|
|
529
407
|
dtype = field[1]
|
530
408
|
field[1] = crdb_fields[field[0]][2].downcase if re.match(dtype)
|
531
409
|
field[7] = crdb_fields[field[0]][1]&.gsub!(/^\'|\'?$/, '')
|
532
|
-
field[
|
410
|
+
field[10] = true if crdb_fields[field[0]][3]
|
533
411
|
field
|
534
412
|
end
|
535
413
|
fields.delete_if do |field|
|
536
414
|
# Don't include rowid column if it is hidden and the primary key
|
537
415
|
# is not defined (meaning CRDB implicitly created it).
|
538
416
|
if field[0] == CockroachDBAdapter::DEFAULT_PRIMARY_KEY
|
539
|
-
field[
|
417
|
+
field[10] && !primary_key(table_name)
|
540
418
|
else
|
541
419
|
false # Keep this entry.
|
542
420
|
end
|
@@ -549,11 +427,14 @@ module ActiveRecord
|
|
549
427
|
# precision, and scale information in the type.
|
550
428
|
# Ex. geometry -> geometry(point, 4326)
|
551
429
|
def crdb_column_definitions(table_name)
|
430
|
+
table_name = PostgreSQL::Utils.extract_schema_qualified_name(table_name)
|
431
|
+
table = table_name.identifier
|
432
|
+
with_schema = " AND c.table_schema = #{quote(table_name.schema)}" if table_name.schema
|
552
433
|
fields = \
|
553
434
|
query(<<~SQL, "SCHEMA")
|
554
435
|
SELECT c.column_name, c.column_comment, c.crdb_sql_type, c.is_hidden::BOOLEAN
|
555
|
-
|
556
|
-
WHERE c.table_name = #{quote(
|
436
|
+
FROM information_schema.columns c
|
437
|
+
WHERE c.table_name = #{quote(table)}#{with_schema}
|
557
438
|
SQL
|
558
439
|
|
559
440
|
fields.reduce({}) do |a, e|
|
@@ -594,21 +475,10 @@ module ActiveRecord
|
|
594
475
|
def load_additional_types(oids = nil)
|
595
476
|
if @config[:use_follower_reads_for_type_introspection]
|
596
477
|
initializer = OID::TypeMapInitializer.new(type_map)
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
LEFT JOIN pg_range as r ON oid = rngtypid AS OF SYSTEM TIME '-10s'
|
602
|
-
SQL
|
603
|
-
|
604
|
-
if oids
|
605
|
-
query += "WHERE t.oid IN (%s)" % oids.join(", ")
|
606
|
-
else
|
607
|
-
query += initializer.query_conditions_for_initial_load
|
608
|
-
end
|
609
|
-
|
610
|
-
execute_and_clear(query, "SCHEMA", []) do |records|
|
611
|
-
initializer.run(records)
|
478
|
+
load_types_queries_with_aost(initializer, oids) do |query|
|
479
|
+
execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |records|
|
480
|
+
initializer.run(records)
|
481
|
+
end
|
612
482
|
end
|
613
483
|
else
|
614
484
|
super
|
@@ -619,6 +489,21 @@ module ActiveRecord
|
|
619
489
|
super
|
620
490
|
end
|
621
491
|
|
492
|
+
def load_types_queries_with_aost(initializer, oids)
|
493
|
+
query = <<~SQL
|
494
|
+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
|
495
|
+
FROM pg_type as t
|
496
|
+
LEFT JOIN pg_range as r ON oid = rngtypid AS OF SYSTEM TIME '-10s'
|
497
|
+
SQL
|
498
|
+
if oids
|
499
|
+
yield query + "WHERE t.oid IN (%s)" % oids.join(", ")
|
500
|
+
else
|
501
|
+
yield query + initializer.query_conditions_for_known_type_names
|
502
|
+
yield query + initializer.query_conditions_for_known_type_types
|
503
|
+
yield query + initializer.query_conditions_for_array_types
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
622
507
|
# override
|
623
508
|
# This method maps data types to their proper decoder.
|
624
509
|
#
|
@@ -649,15 +534,13 @@ module ActiveRecord
|
|
649
534
|
WHERE t.typname IN (%s)
|
650
535
|
SQL
|
651
536
|
|
652
|
-
coders = execute_and_clear(query, "SCHEMA", []) do |result|
|
653
|
-
result
|
654
|
-
.map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
|
655
|
-
.compact
|
537
|
+
coders = execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
|
538
|
+
result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
|
656
539
|
end
|
657
540
|
|
658
541
|
map = PG::TypeMapByOid.new
|
659
542
|
coders.each { |coder| map.add_coder(coder) }
|
660
|
-
@
|
543
|
+
@raw_connection.type_map_for_results = map
|
661
544
|
|
662
545
|
@type_map_for_results = PG::TypeMapByOid.new
|
663
546
|
@type_map_for_results.default_type_map = map
|