Skip to content

Commit 48bc65f

Browse files
committed
Refactor column_definitions override, override is_cached_plan_failure?, update connection handling to ActiveRecord 6.1 style, override numeric in initialize_type_map, and remove interval override from initialize_type_map since it had outdated assumptions about the functionality of INTERVAL types.
1 parent ee9264a commit 48bc65f

File tree

1 file changed

+106
-72
lines changed

1 file changed

+106
-72
lines changed

lib/active_record/connection_adapters/cockroachdb_adapter.rb

Lines changed: 106 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
require "active_record/connection_adapters/cockroachdb/spatial_column_info"
1515
require "active_record/connection_adapters/cockroachdb/setup"
1616
require "active_record/connection_adapters/cockroachdb/oid/spatial"
17+
require "active_record/connection_adapters/cockroachdb/oid/interval"
1718
require "active_record/connection_adapters/cockroachdb/arel_tosql"
1819

1920
# Run to ignore spatial tables that will break schemna dumper.
@@ -24,26 +25,22 @@ module ActiveRecord
2425
module ConnectionHandling
2526
def cockroachdb_connection(config)
2627
# This is copied from the PostgreSQL adapter.
27-
conn_params = config.symbolize_keys
28-
29-
conn_params.delete_if { |_, v| v.nil? }
28+
conn_params = config.symbolize_keys.compact
3029

3130
# Map ActiveRecords param names to PGs.
3231
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
3332
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
3433

3534
# Forward only valid config params to PG::Connection.connect.
36-
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:sslmode, :application_name]
35+
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
3736
conn_params.slice!(*valid_conn_param_keys)
3837

39-
conn = PG.connect(conn_params)
40-
ConnectionAdapters::CockroachDBAdapter.new(conn, logger, conn_params, config)
41-
rescue ::PG::Error, ActiveRecord::ActiveRecordError => error
42-
if error.message.include?("does not exist")
43-
raise ActiveRecord::NoDatabaseError
44-
else
45-
raise
46-
end
38+
ConnectionAdapters::CockroachDBAdapter.new(
39+
ConnectionAdapters::CockroachDBAdapter.new_client(conn_params),
40+
logger,
41+
conn_params,
42+
config
43+
)
4744
end
4845
end
4946
end
@@ -76,56 +73,6 @@ class CockroachDBAdapter < PostgreSQLAdapter
7673
include CockroachDB::DatabaseStatements
7774
include CockroachDB::Quoting
7875

79-
# override
80-
# This method makes a sql query to gather information about columns
81-
# in a table. It returns an array of arrays (one for each col) and
82-
# passes each to the SchemaStatements#new_column_from_field method
83-
# as the field parameter. This data is then used to format the column
84-
# objects for the model and sent to the OID for data casting.
85-
#
86-
# The issue with the default method is that the sql_type field is
87-
# retrieved with the `format_type` function, but this is implemented
88-
# differently in CockroachDB than PostGIS, so geometry/geography
89-
# types are missing information which makes parsing them impossible.
90-
# Below is an example of what `format_type` returns for a geometry
91-
# column.
92-
#
93-
# column_type: geometry(POINT, 4326)
94-
# Expected: geometry(POINT, 4326)
95-
# Actual: geometry
96-
#
97-
# The solution is to make the default query with super, then
98-
# iterate through the columns and if it is a spatial type,
99-
# access the proper column_type with the information_schema.columns
100-
# table.
101-
#
102-
# @see: https://github.com/rails/rails/blob/8695b028261bdd244e254993255c6641bdbc17a5/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L829
103-
def column_definitions(table_name)
104-
fields = super
105-
# iterate through and identify all spatial fields based on format_type
106-
# being geometry or geography, then query for the information_schema.column
107-
# column_type because that contains the necessary information.
108-
fields.map do |field|
109-
dtype = field[1]
110-
if dtype == 'geometry' || dtype == 'geography'
111-
col_name = field[0]
112-
data_type = \
113-
query(<<~SQL, "SCHEMA")
114-
SELECT c.data_type
115-
FROM information_schema.columns c
116-
WHERE c.table_name = #{quote(table_name)}
117-
AND c.column_name = #{quote(col_name)}
118-
SQL
119-
field[1] = data_type[0][0]
120-
end
121-
field
122-
end
123-
end
124-
125-
def arel_visitor
126-
Arel::Visitors::CockroachDB.new(self)
127-
end
128-
12976
def self.spatial_column_options(key)
13077
SPATIAL_COLUMN_OPTIONS[key]
13178
end
@@ -276,19 +223,28 @@ def initialize_type_map(m = type_map)
276223
end
277224
end
278225

279-
# NOTE(joey): PostgreSQL intervals have a precision.
280-
# CockroachDB intervals do not, so overide the type
281-
# definition. Returning a ArgumentError may not be correct.
282-
# This needs to be tested.
283-
m.register_type "interval" do |_, _, sql_type|
226+
# Belongs after other types are defined because of issues described
227+
# in this https://github.com/rails/rails/pull/38571
228+
# Once that PR is merged, we can call super at the top.
229+
super(m)
230+
231+
# Override numeric type. This is almost identical to the default,
232+
# except that the conditional based on the fmod is changed.
233+
m.register_type "numeric" do |_, fmod, sql_type|
284234
precision = extract_precision(sql_type)
285-
if precision
286-
raise(ArgumentError, "CockroachDB does not support precision on intervals, but got precision: #{precision}")
235+
scale = extract_scale(sql_type)
236+
237+
# If fmod is -1, that means that precision is defined, but not
238+
# scale or neither is defined.
239+
if fmod && fmod == -1
240+
# Below comment is from ActiveRecord
241+
# FIXME: Remove this class, and the second argument to
242+
# lookups on PG
243+
Type::DecimalWithoutScale.new(precision: precision)
244+
else
245+
OID::Decimal.new(precision: precision, scale: scale)
287246
end
288-
OID::SpecializedString.new(:interval, precision: precision)
289247
end
290-
291-
super(m)
292248
end
293249

294250
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
@@ -398,6 +354,84 @@ def extract_empty_array_from_default(default)
398354
return "{}"
399355
end
400356

357+
# override
358+
# This method makes a query to gather information about columns
359+
# in a table. It returns an array of arrays (one for each col) and
360+
# passes each to the SchemaStatements#new_column_from_field method
361+
# as the field parameter. This data is then used to format the column
362+
# objects for the model and sent to the OID for data casting.
363+
#
364+
# Sometimes there are differences between how data is formatted
365+
# in Postgres and CockroachDB, so additional queries for certain types
366+
# may be necessary to properly form the column definition.
367+
#
368+
# @see: https://github.com/rails/rails/blob/8695b028261bdd244e254993255c6641bdbc17a5/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L829
369+
def column_definitions(table_name)
370+
fields = super
371+
372+
# Use regex comparison because if a type is an array it will
373+
# have [] appended to the end of it.
374+
target_types = [
375+
/geometry/,
376+
/geography/,
377+
/interval/,
378+
/numeric/
379+
]
380+
re = Regexp.union(target_types)
381+
fields.map do |field|
382+
dtype = field[1]
383+
if re.match(dtype)
384+
crdb_column_definition(field, table_name)
385+
else
386+
field
387+
end
388+
end
389+
end
390+
391+
# Use the crdb_sql_type instead of the sql_type returned by
392+
# column_definitions. This will include limit,
393+
# precision, and scale information in the type.
394+
# Ex. geometry -> geometry(point, 4326)
395+
def crdb_column_definition(field, table_name)
396+
col_name = field[0]
397+
data_type = \
398+
query(<<~SQL, "SCHEMA")
399+
SELECT c.crdb_sql_type
400+
FROM information_schema.columns c
401+
WHERE c.table_name = #{quote(table_name)}
402+
AND c.column_name = #{quote(col_name)}
403+
SQL
404+
field[1] = data_type[0][0].downcase
405+
field
406+
end
407+
408+
# override
409+
# This method is used to determine if a
410+
# FEATURE_NOT_SUPPORTED error from the PG gem should
411+
# be an ActiveRecord::PreparedStatementCacheExpired
412+
# error.
413+
#
414+
# ActiveRecord handles this by checking that the sql state matches the
415+
# FEATURE_NOT_SUPPORTED code and that the source function
416+
# is "RevalidateCachedQuery" since that is the only function
417+
# in postgres that will create this error.
418+
#
419+
# That method will not work for CockroachDB because the error
420+
# originates from the "runExecBuilder" function, so we need
421+
# to modify the original to match the CockroachDB behavior.
422+
def is_cached_plan_failure?(e)
423+
pgerror = e.cause
424+
425+
pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
426+
pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "runExecBuilder"
427+
rescue
428+
false
429+
end
430+
431+
def arel_visitor
432+
Arel::Visitors::CockroachDB.new(self)
433+
end
434+
401435
# end private
402436
end
403437
end

0 commit comments

Comments
 (0)