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.
@@ -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 = exec_query(<<~SQL, "SCHEMA")
42
- SELECT t2.relname AS to_table, 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
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: row["column"],
85
+ column: column,
58
86
  name: row["name"],
59
- primary_key: row["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, row["to_table"], options)
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, generated, hidden = field
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
- serial =
86
- if (match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/))
87
- sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
88
- end
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: 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
- class << self
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 = model.connection_db_config.adapter.to_sym
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
- # This is copied from the PostgreSQL adapter.
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".freeze
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
- # CockroachDB 20.1 can run queries that work against PostgreSQL 10+.
158
- def postgresql_version
159
- 100000
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 supports_bulk_alter?
163
- false
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 supports_json?
167
- # FIXME(joey): Add a version check.
168
- true
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 supports_partial_index?
184
- @crdb_version >= 2020
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
- @crdb_version >= 2110
192
+ true
212
193
  end
213
194
 
214
195
  def supports_string_to_array_coercion?
215
- @crdb_version >= 2020
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 is hardcoded to 63 (as previously was in ActiveRecord 5.0) to aid in
227
- # migration from PostgreSQL to CockroachDB. In practice, this limitation
228
- # is arbitrary since CockroachDB supports index name lengths and table alias
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
- # Note that in the migration to ActiveRecord 5.1, this was changed in
233
- # PostgreSQLAdapter to use `SHOW max_identifier_length` (which does not
234
- # exist in CockroachDB). Therefore, we have to redefine this here.
235
- def max_identifier_length
236
- 63
237
- end
238
- alias index_name_length max_identifier_length
239
- alias table_alias_length max_identifier_length
240
-
241
- def initialize(connection, logger, conn_params, config)
242
- super(connection, logger, conn_params, config)
243
-
244
- # crdb_version is the version of the binary running on the node. We
245
- # really want to use `SHOW CLUSTER SETTING version` to get the cluster
246
- # version, but that is only available to admins. Instead, we can use
247
- # crdb_internal.is_at_least_version, but that's only available in 22.1.
248
- crdb_version_string = query_value("SHOW crdb_version")
249
- if crdb_version_string.include? "v22.1"
250
- version_num = query_value(<<~SQL, "VERSION")
251
- SELECT
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| "'#{s}'" }.join(", ")
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 \"#{name}\" AS ENUM (#{sql_values});
261
+ CREATE TYPE IF NOT EXISTS #{quote_table_name(name)} AS ENUM (#{sql_values});
313
262
  SQL
314
- exec_query(query)
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
- #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated,
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
- target_types = [
521
- /geometry/,
522
- /geography/,
523
- /interval/,
524
- /numeric/
525
- ]
526
-
527
- re = Regexp.union(target_types)
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[9] = true if crdb_fields[field[0]][3]
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[9] && !primary_key(table_name)
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
- FROM information_schema.columns c
556
- WHERE c.table_name = #{quote(table_name)}
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
- query = <<~SQL
599
- SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
600
- FROM pg_type as t
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
- @connection.type_map_for_results = map
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