Class: ActiveRecord::ConnectionAdapters::CockroachDBAdapter

Inherits:
PostgreSQLAdapter
  • Object
show all
Includes:
ActiveRecord::ConnectionAdapters::CockroachDB::DatabaseStatements, ActiveRecord::ConnectionAdapters::CockroachDB::Quoting, ActiveRecord::ConnectionAdapters::CockroachDB::ReferentialIntegrity, ActiveRecord::ConnectionAdapters::CockroachDB::SchemaStatements
Defined in:
lib/active_record/connection_adapters/cockroachdb_adapter.rb

Constant Summary collapse

ADAPTER_NAME =
"CockroachDB"
DEFAULT_PRIMARY_KEY =
"rowid"
SPATIAL_COLUMN_OPTIONS =
{
  geography:           { geographic: true },
  geometry:            {},
  geometry_collection: {},
  line_string:         {},
  multi_line_string:   {},
  multi_point:         {},
  multi_polygon:       {},
  spatial:             {},
  st_point:            {},
  st_polygon:          {},
}
DEFAULT_SRID =
0

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ActiveRecord::ConnectionAdapters::CockroachDB::Quoting

#lookup_cast_type, #quote, #quoted_date

Methods included from ActiveRecord::ConnectionAdapters::CockroachDB::DatabaseStatements

#insert_fixtures_set

Methods included from ActiveRecord::ConnectionAdapters::CockroachDB::ReferentialIntegrity

#check_all_foreign_keys_valid!, #disable_referential_integrity

Methods included from ActiveRecord::ConnectionAdapters::CockroachDB::SchemaStatements

#create_schema_dumper, #create_table_definition, #default_sequence_name, #foreign_key_options, #foreign_keys, #indexes, #new_column_from_field, #pk_and_sequence_for, #primary_key, #primary_keys, #schema_creation, #schema_names, #type_to_sql, #unique_constraints

Class Method Details

.initialize_type_map(m = type_map) ⇒ Object



331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 331

def initialize_type_map(m = type_map)
  %w(
    geography
    geometry
    geometry_collection
    line_string
    multi_line_string
    multi_point
    multi_polygon
    st_point
    st_polygon
  ).each do |geo_type|
    m.register_type(geo_type) do |oid, _, sql_type|
      CockroachDB::OID::Spatial.new(oid, sql_type).freeze
    end
  end

  # Belongs after other types are defined because of issues described
  # in this https://github.com/rails/rails/pull/38571
  # Once that PR is merged, we can call super at the top.
  super(m)

  # Override numeric type. This is almost identical to the default,
  # except that the conditional based on the fmod is changed.
  m.register_type "numeric" do |_, fmod, sql_type|
    precision = extract_precision(sql_type)
    scale = extract_scale(sql_type)

    # The type for the numeric depends on the width of the field,
    # so we'll do something special here.
    #
    # When dealing with decimal columns:
    #
    # places after decimal  = fmod - 4 & 0xffff
    # places before decimal = (fmod - 4) >> 16 & 0xffff
    #
    # For older versions of CockroachDB (<v22.1), fmod is -1 for 0 width.
    # If fmod is -1, that means that precision is defined but not
    # scale, or neither is defined.
    if fmod && ((fmod == -1 && !precision.nil?) || (fmod - 4 & 0xffff).zero?)
      # Below comment is from ActiveRecord
      # FIXME: Remove this class, and the second argument to
      # lookups on PG
      Type::DecimalWithoutScale.new(precision: precision)
    else
      ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Decimal.new(precision: precision, scale: scale)
    end
  end
end

.native_database_types ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 113

def self.native_database_types
  return @native_database_types if defined?(@native_database_types)
  # Add spatial types
  @native_database_types = super.merge(
    geography:           { name: "geography" },
    geometry:            { name: "geometry" },
    geometry_collection: { name: "geometry_collection" },
    line_string:         { name: "line_string" },
    multi_line_string:   { name: "multi_line_string" },
    multi_point:         { name: "multi_point" },
    multi_polygon:       { name: "multi_polygon" },
    spatial:             { name: "geometry" },
    st_point:            { name: "st_point" },
    st_polygon:          { name: "st_polygon" }
  )
end

.spatial_column_options(key) ⇒ Object



109
110
111
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 109

def self.spatial_column_options(key)
  SPATIAL_COLUMN_OPTIONS[key]
end

Instance Method Details

#check_version ⇒ Object

:nodoc:



258
259
260
261
262
263
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 258

def check_version # :nodoc:
  # https://www.cockroachlabs.com/docs/releases/release-support-policy
  if database_version < 23_01_12 # < 23.1.12
    raise "Your version of CockroachDB (#{database_version}) is too old. Active Record supports CockroachDB >= 23.1.12."
  end
end

#configure_connection ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 265

def configure_connection(...)
  super

  # Starting in CockroachDB v26.x, tables are created with the
  # `schema_locked` storage parameter enabled by default (controlled by the
  # `sql.create_table_with_schema_locked.enabled` cluster setting). While it
  # improves changefeed performance, it blocks the transactional DDL that
  # Active Record migrations and the test suite rely on: CockroachDB can
  # only auto-unlock a locked table for single-statement implicit
  # transactions, not for DDL run inside a transaction. Opt out at the
  # session level so Active Record keeps working out of the box; users who
  # want the changefeed benefit can still lock individual tables or set
  # `create_table_with_schema_locked: true` in their connection variables.
  #
  # The session variable was introduced in CockroachDB v25.3, and we let an
  # explicit `:variables` entry take precedence over this default.
  #
  # See https://www.cockroachlabs.com/docs/stable/schema-locked
  variables = @config.fetch(:variables, {}).stringify_keys
  if database_version >= 25_03_00 && !variables.key?("create_table_with_schema_locked")
    internal_execute("SET create_table_with_schema_locked = false", "SCHEMA")
  end

# This rescue flow appears in new_client, but it is needed here as well
# since Cockroach will sometimes not raise until a query is made.
#
# See https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/337#issuecomment-2328419453
#
# The error conditions used to differ from the ones in new_client, but
# the reasons why are no longer relevant. We keep this in sync with new_client
# even though some conditions might never be checked.
#
# See https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/229
#
# We have to rescue `ActiveRecord::StatementInvalid` instead of `::PG::Error`
# here as the error has already been casted (in `#with_raw_connection` as
# of Rails 7.2.1).
rescue ActiveRecord::StatementInvalid => error
  conn_params = @connection_parameters
  if conn_params && conn_params[:dbname] == "postgres"
    raise ActiveRecord::ConnectionNotEstablished, error.message
  elsif conn_params && conn_params[:dbname] && error.cause.message.include?(conn_params[:dbname])
    raise ActiveRecord::NoDatabaseError.db_error(conn_params[:dbname])
  elsif conn_params && conn_params[:user] && error.cause.message.include?(conn_params[:user])
    raise ActiveRecord::DatabaseConnectionError.username_error(conn_params[:user])
  elsif conn_params && conn_params[:host] && error.cause.message.include?(conn_params[:host])
    raise ActiveRecord::DatabaseConnectionError.hostname_error(conn_params[:host])
  else
    raise ActiveRecord::ConnectionNotEstablished, error.message
  end
end

#create_enum(name, values, **options) ⇒ Object

override The PostgreSQLAdapter uses syntax for an anonymous function (DO $$) that CockroachDB does not support.

Given a name and an array of values, creates an enum type.



322
323
324
325
326
327
328
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 322

def create_enum(name, values, **options)
  sql_values = values.map { |s| quote(s) }.join(", ")
  query = <<~SQL
    CREATE TYPE IF NOT EXISTS #{quote_table_name(name)} AS ENUM (#{sql_values});
  SQL
  internal_exec_query(query).tap { reload_type_map }
end

#default_srid ⇒ Object



134
135
136
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 134

def default_srid
  DEFAULT_SRID
end

#get_database_version ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 151

def get_database_version
  with_raw_connection do |conn|
    conn.async_exec("SHOW crdb_version") do |result|
      major, minor, patch = result
        .getvalue(0, 0)
        .match(/v(\d+).(\d+).(\d+)/)
        .captures
        .map(&:to_i)
      major * 100 * 100 + minor * 100 + patch
    end
  end
end

#max_transaction_retries ⇒ Object



147
148
149
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 147

def max_transaction_retries
  @max_transaction_retries ||= @config.fetch(:max_transaction_retries, 3)
end

#postgis_lib_version ⇒ Object



130
131
132
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 130

def postgis_lib_version
  @postgis_lib_version ||= select_value("SELECT PostGIS_Lib_Version()")
end

#srs_database_columns ⇒ Object



138
139
140
141
142
143
144
145
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 138

def srs_database_columns
  {
    auth_name_column: "auth_name",
    auth_srid_column: "auth_srid",
    proj4text_column: "proj4text",
    srtext_column:    "srtext",
  }
end

#supports_advisory_locks? ⇒ Boolean

Returns:

  • (Boolean)


233
234
235
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 233

def supports_advisory_locks?
  false
end

#supports_close_prepared? ⇒ Boolean

Returns:

  • (Boolean)


254
255
256
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 254

def supports_close_prepared?
  true
end

#supports_comments_in_create? ⇒ Boolean

Returns:

  • (Boolean)


229
230
231
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 229

def supports_comments_in_create?
  false
end

#supports_datetime_with_precision? ⇒ Boolean

Returns:

  • (Boolean)


166
167
168
169
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 166

def supports_datetime_with_precision?
  # https://github.com/cockroachdb/cockroach/pull/111400
  true
end

#supports_ddl_transactions? ⇒ Boolean

Returns:

  • (Boolean)


185
186
187
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 185

def supports_ddl_transactions?
  false
end

#supports_deferrable_constraints? ⇒ Boolean

Returns:

  • (Boolean)


249
250
251
252
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 249

def supports_deferrable_constraints?
  # https://go.crdb.dev/issue-v/31632/v23.1
  false
end

#supports_exclusion_constraints? ⇒ Boolean

Returns:

  • (Boolean)


201
202
203
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 201

def supports_exclusion_constraints?
  false
end

#supports_expression_index? ⇒ Boolean

Returns:

  • (Boolean)


222
223
224
225
226
227
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 222

def supports_expression_index?
  # Expression indexes are partially supported by CockroachDB v21.2,
  # but activerecord requires "ON CONFLICT expression" support.
  # See https://github.com/cockroachdb/cockroach/issues/67893
  false
end

#supports_extensions? ⇒ Boolean

Returns:

  • (Boolean)


189
190
191
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 189

def supports_extensions?
  false
end

#supports_index_include? ⇒ Boolean

Returns:

  • (Boolean)


197
198
199
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 197

def supports_index_include?
  false
end

#supports_materialized_views? ⇒ Boolean

Returns:

  • (Boolean)


193
194
195
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 193

def supports_materialized_views?
  true
end

#supports_native_partitioning? ⇒ Boolean

Partitioning is quite different from PostgreSQL, so we don't support it. If you need partitioning, you should default to using raw SQL queries.

See https://www.postgresql.org/docs/current/ddl-partitioning.html See https://www.cockroachlabs.com/docs/stable/partitioning

Returns:

  • (Boolean)


181
182
183
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 181

def supports_native_partitioning?
  false
end

#supports_nulls_not_distinct? ⇒ Boolean

Returns:

  • (Boolean)


171
172
173
174
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 171

def supports_nulls_not_distinct?
  # https://github.com/cockroachdb/cockroach/issues/115836
  false
end

#supports_partitioned_indexes? ⇒ Boolean

Returns:

  • (Boolean)


245
246
247
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 245

def supports_partitioned_indexes?
  false
end

#supports_restart_db_transaction? ⇒ Boolean

Returns:

  • (Boolean)


205
206
207
208
209
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 205

def supports_restart_db_transaction?
  # In PostgreSQL, this would call 'ROLLBACK AND CHAIN'
  # which is not available with CRDB.
  false
end

#supports_string_to_array_coercion? ⇒ Boolean

Returns:

  • (Boolean)


241
242
243
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 241

def supports_string_to_array_coercion?
  true
end

#supports_unique_constraints? ⇒ Boolean

OVERRIDE: UNIQUE CONSTRAINTS will create indexes anyway, so we only consider then as indexes. See https://github.com/cockroachdb/activerecord-cockroachdb-adapter/issues/347. See https://www.cockroachlabs.com/docs/stable/unique.

NOTE: support is actually partial, one can still use the #unique_constraints method to get the unique constraints.

Returns:

  • (Boolean)


218
219
220
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 218

def supports_unique_constraints?
  false
end

#supports_virtual_columns? ⇒ Boolean

Returns:

  • (Boolean)


237
238
239
# File 'lib/active_record/connection_adapters/cockroachdb_adapter.rb', line 237

def supports_virtual_columns?
  true
end