activerecord-cockroachdb-adapter 7.0.3 → 7.1.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.
@@ -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