activerecord-cockroachdb-adapter 5.2.1 → 6.1.0beta1

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.
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord # :nodoc:
4
+ module ConnectionAdapters # :nodoc:
5
+ module CockroachDB # :nodoc:
6
+ def self.initial_setup
7
+ ::ActiveRecord::SchemaDumper.ignore_tables |= %w[
8
+ geography_columns
9
+ geometry_columns
10
+ layer
11
+ raster_columns
12
+ raster_overviews
13
+ spatial_ref_sys
14
+ topology
15
+ ]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,44 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module CockroachDB
4
+ class SpatialColumnInfo
5
+ def initialize(adapter, table_name)
6
+ @adapter = adapter
7
+ @table_name = table_name
8
+ end
9
+
10
+ def all
11
+ info = @adapter.query(
12
+ "SELECT f_geometry_column,coord_dimension,srid,type FROM geometry_columns WHERE f_table_name='#{@table_name}'"
13
+ )
14
+ result = {}
15
+ info.each do |row|
16
+ name = row[0]
17
+ type = row[3]
18
+ dimension = row[1].to_i
19
+ has_m = !!(type =~ /m$/i)
20
+ type.sub!(/m$/, '')
21
+ has_z = dimension > 3 || dimension == 3 && !has_m
22
+ result[name] = {
23
+ dimension: dimension,
24
+ has_m: has_m,
25
+ has_z: has_z,
26
+ name: name,
27
+ srid: row[2].to_i,
28
+ type: type
29
+ }
30
+ end
31
+ result
32
+ end
33
+
34
+ # do not query the database for non-spatial columns/tables
35
+ def get(column_name, type)
36
+ return unless CockroachDBAdapter.spatial_column_options(type.to_sym)
37
+
38
+ @spatial_column_info ||= all
39
+ @spatial_column_info[column_name]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord # :nodoc:
4
+ module ConnectionAdapters # :nodoc:
5
+ module CockroachDB # :nodoc:
6
+ class TableDefinition < PostgreSQL::TableDefinition # :nodoc:
7
+ include ColumnMethods
8
+
9
+ # Support for spatial columns in tables
10
+ # super: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
11
+ def new_column_definition(name, type, **options)
12
+ if (info = CockroachDBAdapter.spatial_column_options(type.to_sym))
13
+ if (limit = options.delete(:limit)) && limit.is_a?(::Hash)
14
+ options.merge!(limit)
15
+ end
16
+
17
+ geo_type = ColumnDefinitionUtils.geo_type(options[:type] || type || info[:type])
18
+ base_type = info[:type] || (options[:geographic] ? :geography : :geometry)
19
+
20
+ options[:limit] = ColumnDefinitionUtils.limit_from_options(geo_type, options)
21
+ options[:spatial_type] = geo_type
22
+ column = super(name, base_type, **options)
23
+ else
24
+ column = super(name, type, **options)
25
+ end
26
+
27
+ column
28
+ end
29
+ end
30
+
31
+ module ColumnDefinitionUtils
32
+ class << self
33
+ def geo_type(type = 'GEOMETRY')
34
+ g_type = type.to_s.delete('_').upcase
35
+ return 'POINT' if g_type == 'STPOINT'
36
+ return 'POLYGON' if g_type == 'STPOLYGON'
37
+
38
+ g_type
39
+ end
40
+
41
+ def limit_from_options(type, options = {})
42
+ spatial_type = geo_type(type)
43
+ spatial_type << 'Z' if options[:has_z]
44
+ spatial_type << 'M' if options[:has_m]
45
+ spatial_type << ",#{options[:srid] || default_srid(options)}"
46
+ spatial_type
47
+ end
48
+
49
+ def default_srid(options)
50
+ options[:geographic] ? 4326 : CockroachDBAdapter::DEFAULT_SRID
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -8,16 +8,22 @@ module ActiveRecord
8
8
  # transactions that fail due to serialization errors. Failed
9
9
  # transactions will be retried until they pass or the max retry limit is
10
10
  # exceeded.
11
- def within_new_transaction(options = {})
12
- attempts = options.fetch(:attempts, 0)
13
- super
14
- rescue ActiveRecord::SerializationFailure => error
11
+ def within_new_transaction(isolation: nil, joinable: true, attempts: 0)
12
+ super(isolation: isolation, joinable: joinable)
13
+ rescue ActiveRecord::StatementInvalid => error
14
+ raise unless retryable? error
15
15
  raise if attempts >= @connection.max_transaction_retries
16
16
 
17
17
  attempts += 1
18
18
  sleep_seconds = (2 ** attempts + rand) / 10
19
19
  sleep(sleep_seconds)
20
- within_new_transaction(options.merge(attempts: attempts)) { yield }
20
+ within_new_transaction(isolation: isolation, joinable: joinable, attempts: attempts) { yield }
21
+ end
22
+
23
+ def retryable?(error)
24
+ return true if error.is_a? ActiveRecord::SerializationFailure
25
+ return retryable? error.cause if error.cause
26
+ false
21
27
  end
22
28
  end
23
29
  end
@@ -1,12 +1,10 @@
1
1
  module ActiveRecord
2
2
  module Type
3
3
  class << self
4
- private
5
-
6
4
  # Return :postgresql instead of :cockroachdb for current_adapter_name so
7
5
  # we can continue using the ActiveRecord::Types defined in
8
6
  # PostgreSQLAdapter.
9
- def current_adapter_name
7
+ def adapter_name_from(_model)
10
8
  :postgresql
11
9
  end
12
10
  end
@@ -1,33 +1,54 @@
1
- require 'active_record/connection_adapters/postgresql_adapter'
1
+ require "rgeo/active_record"
2
+
3
+ require "active_record/connection_adapters/postgresql_adapter"
4
+ require "active_record/connection_adapters/cockroachdb/column_methods"
2
5
  require "active_record/connection_adapters/cockroachdb/schema_statements"
3
6
  require "active_record/connection_adapters/cockroachdb/referential_integrity"
4
7
  require "active_record/connection_adapters/cockroachdb/transaction_manager"
5
- require "active_record/connection_adapters/cockroachdb/column"
6
8
  require "active_record/connection_adapters/cockroachdb/database_statements"
9
+ require "active_record/connection_adapters/cockroachdb/table_definition"
7
10
  require "active_record/connection_adapters/cockroachdb/quoting"
8
11
  require "active_record/connection_adapters/cockroachdb/type"
9
12
  require "active_record/connection_adapters/cockroachdb/attribute_methods"
13
+ require "active_record/connection_adapters/cockroachdb/column"
14
+ require "active_record/connection_adapters/cockroachdb/spatial_column_info"
15
+ require "active_record/connection_adapters/cockroachdb/setup"
16
+ require "active_record/connection_adapters/cockroachdb/oid/spatial"
17
+ require "active_record/connection_adapters/cockroachdb/oid/interval"
18
+ require "active_record/connection_adapters/cockroachdb/arel_tosql"
19
+
20
+ # Run to ignore spatial tables that will break schemna dumper.
21
+ # Defined in ./setup.rb
22
+ ActiveRecord::ConnectionAdapters::CockroachDB.initial_setup
10
23
 
11
24
  module ActiveRecord
12
25
  module ConnectionHandling
13
26
  def cockroachdb_connection(config)
14
27
  # This is copied from the PostgreSQL adapter.
15
- conn_params = config.symbolize_keys
16
-
17
- conn_params.delete_if { |_, v| v.nil? }
28
+ conn_params = config.symbolize_keys.compact
18
29
 
19
30
  # Map ActiveRecords param names to PGs.
20
31
  conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
21
32
  conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
22
33
 
23
34
  # Forward only valid config params to PG::Connection.connect.
24
- valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:sslmode, :application_name]
35
+ valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
25
36
  conn_params.slice!(*valid_conn_param_keys)
26
37
 
27
- # The postgres drivers don't allow the creation of an unconnected
28
- # PG::Connection object, so just pass a nil connection object for the
29
- # time being.
30
- ConnectionAdapters::CockroachDBAdapter.new(nil, logger, conn_params, config)
38
+ ConnectionAdapters::CockroachDBAdapter.new(
39
+ ConnectionAdapters::CockroachDBAdapter.new_client(conn_params),
40
+ logger,
41
+ conn_params,
42
+ config
43
+ )
44
+ # This rescue flow appears in new_client, but it is needed here as well
45
+ # since Cockroach will sometimes not raise until a query is made.
46
+ rescue ActiveRecord::StatementInvalid => error
47
+ if conn_params && conn_params[:dbname] && error.cause.message.include?(conn_params[:dbname])
48
+ raise ActiveRecord::NoDatabaseError
49
+ else
50
+ raise ActiveRecord::ConnectionNotEstablished, error.message
51
+ end
31
52
  end
32
53
  end
33
54
  end
@@ -38,11 +59,49 @@ module ActiveRecord
38
59
  ADAPTER_NAME = "CockroachDB".freeze
39
60
  DEFAULT_PRIMARY_KEY = "rowid"
40
61
 
62
+ SPATIAL_COLUMN_OPTIONS =
63
+ {
64
+ geography: { geographic: true },
65
+ geometry: {},
66
+ geometry_collection: {},
67
+ line_string: {},
68
+ multi_line_string: {},
69
+ multi_point: {},
70
+ multi_polygon: {},
71
+ spatial: {},
72
+ st_point: {},
73
+ st_polygon: {},
74
+ }
75
+
76
+ # http://postgis.17.x6.nabble.com/Default-SRID-td5001115.html
77
+ DEFAULT_SRID = 0
78
+
41
79
  include CockroachDB::SchemaStatements
42
80
  include CockroachDB::ReferentialIntegrity
43
81
  include CockroachDB::DatabaseStatements
44
82
  include CockroachDB::Quoting
45
83
 
84
+ def self.spatial_column_options(key)
85
+ SPATIAL_COLUMN_OPTIONS[key]
86
+ end
87
+
88
+ def postgis_lib_version
89
+ @postgis_lib_version ||= select_value("SELECT PostGIS_Lib_Version()")
90
+ end
91
+
92
+ def default_srid
93
+ DEFAULT_SRID
94
+ end
95
+
96
+ def srs_database_columns
97
+ {
98
+ auth_name_column: "auth_name",
99
+ auth_srid_column: "auth_srid",
100
+ proj4text_column: "proj4text",
101
+ srtext_column: "srtext",
102
+ }
103
+ end
104
+
46
105
  def debugging?
47
106
  !!ENV["DEBUG_COCKROACHDB_ADAPTER"]
48
107
  end
@@ -73,11 +132,6 @@ module ActiveRecord
73
132
  false
74
133
  end
75
134
 
76
- def supports_ranges?
77
- # See cockroachdb/cockroach#17022
78
- false
79
- end
80
-
81
135
  def supports_materialized_views?
82
136
  false
83
137
  end
@@ -151,21 +205,56 @@ module ActiveRecord
151
205
  end
152
206
  @crdb_version = version_num
153
207
  end
154
-
208
+
209
+ def self.database_exists?(config)
210
+ !!ActiveRecord::Base.cockroachdb_connection(config)
211
+ rescue ActiveRecord::NoDatabaseError
212
+ false
213
+ end
214
+
155
215
  private
156
216
 
157
217
  def initialize_type_map(m = type_map)
218
+ %w(
219
+ geography
220
+ geometry
221
+ geometry_collection
222
+ line_string
223
+ multi_line_string
224
+ multi_point
225
+ multi_polygon
226
+ st_point
227
+ st_polygon
228
+ ).each do |geo_type|
229
+ m.register_type(geo_type) do |oid, _, sql_type|
230
+ CockroachDB::OID::Spatial.new(oid, sql_type)
231
+ end
232
+ end
233
+
234
+ # Belongs after other types are defined because of issues described
235
+ # in this https://github.com/rails/rails/pull/38571
236
+ # Once that PR is merged, we can call super at the top.
158
237
  super(m)
159
- # NOTE(joey): PostgreSQL intervals have a precision.
160
- # CockroachDB intervals do not, so overide the type
161
- # definition. Returning a ArgumentError may not be correct.
162
- # This needs to be tested.
163
- m.register_type "interval" do |_, _, sql_type|
238
+
239
+ # Override numeric type. This is almost identical to the default,
240
+ # except that the conditional based on the fmod is changed.
241
+ m.register_type "numeric" do |_, fmod, sql_type|
164
242
  precision = extract_precision(sql_type)
165
- if precision
166
- raise(ArgumentError, "CockroachDB does not support precision on intervals, but got precision: #{precision}")
243
+ scale = extract_scale(sql_type)
244
+
245
+ # TODO(#178) this should never use DecimalWithoutScale since scale
246
+ # is assumed to be 0 if it is not explicitly defined.
247
+ #
248
+ # If fmod is -1, that means that precision is defined but not
249
+ # scale, or neither is defined.
250
+ if fmod && fmod == -1
251
+ # Below comment is from ActiveRecord
252
+ # FIXME: Remove this class, and the second argument to
253
+ # lookups on PG
254
+ Type::DecimalWithoutScale.new(precision: precision)
255
+ else
256
+ OID::Decimal.new(precision: precision, scale: scale)
167
257
  end
168
- OID::SpecializedString.new(:interval, precision: precision)
169
258
  end
170
259
  end
171
260
 
@@ -276,6 +365,84 @@ module ActiveRecord
276
365
  return "{}"
277
366
  end
278
367
 
368
+ # override
369
+ # This method makes a query to gather information about columns
370
+ # in a table. It returns an array of arrays (one for each col) and
371
+ # passes each to the SchemaStatements#new_column_from_field method
372
+ # as the field parameter. This data is then used to format the column
373
+ # objects for the model and sent to the OID for data casting.
374
+ #
375
+ # Sometimes there are differences between how data is formatted
376
+ # in Postgres and CockroachDB, so additional queries for certain types
377
+ # may be necessary to properly form the column definition.
378
+ #
379
+ # @see: https://github.com/rails/rails/blob/8695b028261bdd244e254993255c6641bdbc17a5/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L829
380
+ def column_definitions(table_name)
381
+ fields = super
382
+
383
+ # Use regex comparison because if a type is an array it will
384
+ # have [] appended to the end of it.
385
+ target_types = [
386
+ /geometry/,
387
+ /geography/,
388
+ /interval/,
389
+ /numeric/
390
+ ]
391
+ re = Regexp.union(target_types)
392
+ fields.map do |field|
393
+ dtype = field[1]
394
+ if re.match(dtype)
395
+ crdb_column_definition(field, table_name)
396
+ else
397
+ field
398
+ end
399
+ end
400
+ end
401
+
402
+ # Use the crdb_sql_type instead of the sql_type returned by
403
+ # column_definitions. This will include limit,
404
+ # precision, and scale information in the type.
405
+ # Ex. geometry -> geometry(point, 4326)
406
+ def crdb_column_definition(field, table_name)
407
+ col_name = field[0]
408
+ data_type = \
409
+ query(<<~SQL, "SCHEMA")
410
+ SELECT c.crdb_sql_type
411
+ FROM information_schema.columns c
412
+ WHERE c.table_name = #{quote(table_name)}
413
+ AND c.column_name = #{quote(col_name)}
414
+ SQL
415
+ field[1] = data_type[0][0].downcase
416
+ field
417
+ end
418
+
419
+ # override
420
+ # This method is used to determine if a
421
+ # FEATURE_NOT_SUPPORTED error from the PG gem should
422
+ # be an ActiveRecord::PreparedStatementCacheExpired
423
+ # error.
424
+ #
425
+ # ActiveRecord handles this by checking that the sql state matches the
426
+ # FEATURE_NOT_SUPPORTED code and that the source function
427
+ # is "RevalidateCachedQuery" since that is the only function
428
+ # in postgres that will create this error.
429
+ #
430
+ # That method will not work for CockroachDB because the error
431
+ # originates from the "runExecBuilder" function, so we need
432
+ # to modify the original to match the CockroachDB behavior.
433
+ def is_cached_plan_failure?(e)
434
+ pgerror = e.cause
435
+
436
+ pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
437
+ pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "runExecBuilder"
438
+ rescue
439
+ false
440
+ end
441
+
442
+ def arel_visitor
443
+ Arel::Visitors::CockroachDB.new(self)
444
+ end
445
+
279
446
  # end private
280
447
  end
281
448
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-cockroachdb-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.1
4
+ version: 6.1.0beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cockroach Labs
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-25 00:00:00.000000000 Z
11
+ date: 2021-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,28 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '5.2'
19
+ version: '6.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '5.2'
26
+ version: '6.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pg
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.20'
33
+ version: '1.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rgeo-activerecord
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
39
46
  - !ruby/object:Gem::Version
40
- version: '0.20'
47
+ version: 7.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 7.0.0
41
55
  description: Allows the use of CockroachDB as a backend for ActiveRecord and Rails
42
56
  apps.
43
57
  email:
@@ -61,13 +75,20 @@ files:
61
75
  - build/local-test.sh
62
76
  - build/teamcity-test.sh
63
77
  - docker.sh
78
+ - lib/active_record/connection_adapters/cockroachdb/arel_tosql.rb
64
79
  - lib/active_record/connection_adapters/cockroachdb/attribute_methods.rb
65
80
  - lib/active_record/connection_adapters/cockroachdb/column.rb
81
+ - lib/active_record/connection_adapters/cockroachdb/column_methods.rb
66
82
  - lib/active_record/connection_adapters/cockroachdb/database_statements.rb
67
83
  - lib/active_record/connection_adapters/cockroachdb/database_tasks.rb
84
+ - lib/active_record/connection_adapters/cockroachdb/oid/interval.rb
85
+ - lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb
68
86
  - lib/active_record/connection_adapters/cockroachdb/quoting.rb
69
87
  - lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb
70
88
  - lib/active_record/connection_adapters/cockroachdb/schema_statements.rb
89
+ - lib/active_record/connection_adapters/cockroachdb/setup.rb
90
+ - lib/active_record/connection_adapters/cockroachdb/spatial_column_info.rb
91
+ - lib/active_record/connection_adapters/cockroachdb/table_definition.rb
71
92
  - lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb
72
93
  - lib/active_record/connection_adapters/cockroachdb/type.rb
73
94
  - lib/active_record/connection_adapters/cockroachdb_adapter.rb
@@ -77,7 +98,7 @@ licenses:
77
98
  - Apache-2.0
78
99
  metadata:
79
100
  allowed_push_host: https://rubygems.org
80
- post_install_message:
101
+ post_install_message:
81
102
  rdoc_options: []
82
103
  require_paths:
83
104
  - lib
@@ -88,12 +109,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
88
109
  version: '0'
89
110
  required_rubygems_version: !ruby/object:Gem::Requirement
90
111
  requirements:
91
- - - ">="
112
+ - - ">"
92
113
  - !ruby/object:Gem::Version
93
- version: '0'
114
+ version: 1.3.1
94
115
  requirements: []
95
- rubygems_version: 3.1.4
96
- signing_key:
116
+ rubygems_version: 3.0.3
117
+ signing_key:
97
118
  specification_version: 4
98
119
  summary: CockroachDB adapter for ActiveRecord.
99
120
  test_files: []