activerecord-cockroachdb-adapter 5.2.1 → 6.1.0beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []