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.
- 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
|