activerecord-cockroachdb-adapter 6.0.2 → 6.1.0beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 872261a41d4e766a58bd7ea377e28d7dd9507031cfb88288843c5ef0484b8773
4
- data.tar.gz: 9c56d0bb58eccc334d3ab5657226bb397076c3bf363a22b339fda0e92a5a99bc
3
+ metadata.gz: 5d0954fd425d90342f04c1b6559b78e398be9e523e67779476d81f925a1aa611
4
+ data.tar.gz: 1f990c34784394bafa77933eb0d49965a0eac81151f2468c92a66c1bc9beb78a
5
5
  SHA512:
6
- metadata.gz: 6ff0ba0686a110f4f23621ca68474763122933e4611111fa4d5d87a67cf96364e08b09d393b980598424e4507fd2834e98b43c40aab8945551c52317c60f91fe
7
- data.tar.gz: 7369deb96883410baca55b5c2464efc38f36ce291eb4a20eebac6bfe6a1b0304aa0f2e4174baa5608f099695c0742d27898ba9e8a16e07a4e5b68663f9d8d5db
6
+ metadata.gz: 3074426e81a83bb4ae2e50047c6db97dc75d9b8a822c3fd1153fb3403457bb56e7f36acb42802e5dd777c056dd0864db7dc26e875e7a644cb158f0a1c8001f3a
7
+ data.tar.gz: 8ff7c637812c1b262452ca1ef6ad8308a3f620a47e3798c6ef7b93096fedb362ce52372f6c0b870af1b475a31cf83f670f4e6d65498c231f5759a475820568af
data/CONTRIBUTING.md CHANGED
@@ -78,6 +78,26 @@ RAILS_SOURCE="path/to/local_copy" bundle exec rake test
78
78
 
79
79
  `test/config.yml` assumes CockroachDB will be running at localhost:26257 with a root user. Make changes to `test/config.yml` as needed.
80
80
 
81
+ ### Run Tests from a Backup
82
+
83
+ Loading the full test schema every time a test runs can take a while, so for cases where loading the schema sequentially is unimportant, it is possible to use a backup to set up the database. This is significantly faster than the standard method and is provided to run individual tests faster, but should not be used to validate a build.
84
+
85
+ First create the template database.
86
+
87
+ ```bash
88
+ bundle exec rake db:create_test_template
89
+ ```
90
+
91
+ This will create a template database for the current version (ex. `activerecord_test_template611` for version 6.1.1) and create a `BACKUP` in the `nodelocal://self/activerecord-crdb-adapter/#{activerecord_version}` directory.
92
+
93
+ To load from the template, use the `COCKROACH_LOAD_FROM_TEMPLATE` flag.
94
+
95
+ ```bash
96
+ COCKROACH_LOAD_FROM_TEMPLATE=1 TEST_FILES="test/cases/adapters/postgresql/ddl_test.rb" bundle exec rake test
97
+ ```
98
+
99
+ And the `activerecord_unittest` database will use the `RESTORE` command to load the schema from the template database.
100
+
81
101
  # Improvements
82
102
 
83
103
 
data/README.md CHANGED
@@ -22,13 +22,6 @@ development:
22
22
  user: <username>
23
23
  ```
24
24
 
25
- ## Configuration
26
-
27
- In addition to the standard adapter settings, CockroachDB also supports the following:
28
-
29
- - `use_follower_reads_for_type_introspection`: Use follower reads on queries to the `pg_type` catalog when set to `true`. This helps to speed up initialization by reading historical data, but may not find recently created user-defined types.
30
- - `disable_cockroachdb_telemetry`: Determines if a telemetry call is made to the database when the connection pool is initialized. Setting this to `true` will prevent the call from being made.
31
-
32
25
  ## Working with Spatial Data
33
26
 
34
27
  The adapter uses [RGeo](https://github.com/rgeo/rgeo) and [RGeo-ActiveRecord](https://github.com/rgeo/rgeo-activerecord) to represent geometric and geographic data as Ruby objects and easily interface them with the adapter. The following is a brief introduction to RGeo and tips to help setup your spatial application. More documentation about RGeo can be found in the [YARD Docs](https://rubydoc.info/github/rgeo/rgeo) and [wiki](https://github.com/rgeo/rgeo/wiki).
data/Rakefile CHANGED
@@ -2,10 +2,30 @@ require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
  require_relative 'test/support/paths_cockroachdb'
4
4
  require_relative 'test/support/rake_helpers'
5
+ require_relative 'test/support/template_creator'
5
6
 
6
7
  task test: ["test:cockroachdb"]
7
8
  task default: [:test]
8
9
 
10
+ namespace :db do
11
+ task "create_test_template" do
12
+ ENV['DEBUG_COCKROACHDB_ADAPTER'] = "1"
13
+ ENV['COCKROACH_SKIP_LOAD_SCHEMA'] = "1"
14
+ ENV["ARCONN"] = "cockroachdb"
15
+
16
+ TemplateCreator.connect
17
+ require_relative 'test/cases/helper'
18
+
19
+ # TODO: look into this more, but for some reason the blob alias
20
+ # is not defined while running this task.
21
+ ActiveRecord::ConnectionAdapters::CockroachDB::TableDefinition.class_eval do
22
+ alias :blob :binary
23
+ end
24
+
25
+ TemplateCreator.create_test_template
26
+ end
27
+ end
28
+
9
29
  namespace :test do
10
30
  Rake::TestTask.new("cockroachdb") do |t|
11
31
  t.libs = ARTest::CockroachDB.test_load_paths
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "activerecord-cockroachdb-adapter"
7
- spec.version = "6.0.2"
7
+ spec.version = "6.1.0beta1"
8
8
  spec.licenses = ["Apache-2.0"]
9
9
  spec.authors = ["Cockroach Labs"]
10
10
  spec.email = ["cockroach-db@googlegroups.com"]
@@ -13,8 +13,8 @@ Gem::Specification.new do |spec|
13
13
  spec.description = "Allows the use of CockroachDB as a backend for ActiveRecord and Rails apps."
14
14
  spec.homepage = "https://github.com/cockroachdb/activerecord-cockroachdb-adapter"
15
15
 
16
- spec.add_dependency "activerecord", "~> 6.0.3"
17
- spec.add_dependency "pg", ">= 0.20"
16
+ spec.add_dependency "activerecord", "~> 6.1"
17
+ spec.add_dependency "pg", "~> 1.2"
18
18
  spec.add_dependency "rgeo-activerecord", "~> 7.0.0"
19
19
 
20
20
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
@@ -11,7 +11,6 @@ connections:
11
11
  user: root
12
12
  requiressl: disable
13
13
  min_messages: warning
14
- disable_cockroachdb_telemetry: true
15
14
  arunit_without_prepared_statements:
16
15
  database: activerecord_unittest
17
16
  host: localhost
@@ -20,7 +19,6 @@ connections:
20
19
  requiressl: disable
21
20
  min_messages: warning
22
21
  prepared_statements: false
23
- disable_cockroachdb_telemetry: true
24
22
  arunit2:
25
23
  database: activerecord_unittest2
26
24
  host: localhost
@@ -28,4 +26,3 @@ connections:
28
26
  user: root
29
27
  requiressl: disable
30
28
  min_messages: warning
31
- disable_cockroachdb_telemetry: true
@@ -0,0 +1,126 @@
1
+ # frozen-string-literal: true
2
+
3
+ require "active_support/duration"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters
7
+ module CockroachDB
8
+ module OID
9
+ module Interval # :nodoc:
10
+ DEFAULT_PRECISION = 6 # microseconds
11
+
12
+ def cast_value(value)
13
+ case value
14
+ when ::ActiveSupport::Duration
15
+ value
16
+ when ::String
17
+ begin
18
+ PostgresqlInterval::Parser.parse(value)
19
+ rescue PostgresqlInterval::ParseError
20
+ # Try ISO 8601
21
+ super
22
+ end
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ def serialize(value)
29
+ precision = self.precision || DEFAULT_PRECISION
30
+ case value
31
+ when ::ActiveSupport::Duration
32
+ serialize_duration(value, precision)
33
+ when ::Numeric
34
+ serialize_duration(value.seconds, precision)
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ def type_cast_for_schema(value)
41
+ serialize(value).inspect
42
+ end
43
+
44
+ private
45
+
46
+ # Convert an ActiveSupport::Duration to
47
+ # the postgres interval style
48
+ # ex. 1 year 2 mons 3 days 4 hours 5 minutes 6 seconds
49
+ def serialize_duration(value, precision)
50
+ yrs = value.parts.fetch(:years, 0)
51
+ mons = value.parts.fetch(:months, 0)
52
+ days = value.parts.fetch(:days, 0)
53
+ hrs = value.parts.fetch(:hours, 0)
54
+ mins = value.parts.fetch(:minutes, 0)
55
+ secs = value.parts.fetch(:seconds, 0).round(precision)
56
+
57
+ "#{yrs} years #{mons} mons #{days} days #{hrs} hours #{mins} minutes #{secs} seconds"
58
+ end
59
+ end
60
+
61
+ PostgreSQL::OID::Interval.prepend(Interval)
62
+ end
63
+
64
+ module PostgresqlInterval
65
+ class Parser
66
+ PARTS = ActiveSupport::Duration::PARTS
67
+ PARTS_IN_SECONDS = ActiveSupport::Duration::PARTS_IN_SECONDS
68
+
69
+ # modified regex from https://github.com/jeremyevans/sequel/blob/master/lib/sequel/extensions/pg_interval.rb#L86
70
+ REGEX = /\A([+-]?\d+ years?\s?)?([+-]?\d+ mons?\s?)?([+-]?\d+ days?\s?)?(?:([+-])?(\d{2,10}):(\d\d):(\d\d(\.\d+)?))?\z/
71
+
72
+ def self.parse(string)
73
+ matches = REGEX.match(string)
74
+ raise(ParseError) unless matches
75
+
76
+ # 1 => years, 2 => months, 3 => days, 4 => nil, 5 => hours,
77
+ # 6 => minutes, 7 => seconds with fraction digits, 8 => fractional portion of 7
78
+ duration = 0
79
+ parts = {}
80
+
81
+ if matches[1]
82
+ val = matches[1].to_i
83
+ duration += val * PARTS_IN_SECONDS[:years]
84
+ parts[:years] = val
85
+ end
86
+
87
+ if matches[2]
88
+ val = matches[2].to_i
89
+ duration += val * PARTS_IN_SECONDS[:months]
90
+ parts[:months] = val
91
+ end
92
+
93
+ if matches[3]
94
+ val = matches[3].to_i
95
+ duration += val * PARTS_IN_SECONDS[:days]
96
+ parts[:days] = val
97
+ end
98
+
99
+ if matches[5]
100
+ val = matches[5].to_i
101
+ duration += val * PARTS_IN_SECONDS[:hours]
102
+ parts[:hours] = val
103
+ end
104
+
105
+ if matches[6]
106
+ val = matches[6].to_i
107
+ duration += val * PARTS_IN_SECONDS[:minutes]
108
+ parts[:minutes] = val
109
+ end
110
+
111
+ if matches[7]
112
+ val = matches[7].to_f
113
+ duration += val * PARTS_IN_SECONDS[:seconds]
114
+ parts[:seconds] = val
115
+ end
116
+
117
+ ActiveSupport::Duration.new(duration, parts)
118
+ end
119
+ end
120
+
121
+ class ParseError < StandardError
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -4,7 +4,7 @@ module ActiveRecord
4
4
  module SchemaStatements
5
5
  include ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements
6
6
 
7
- def add_index(table_name, column_name, options = {})
7
+ def add_index(table_name, column_name, **options)
8
8
  super
9
9
  rescue ActiveRecord::StatementInvalid => error
10
10
  if debugging? && error.cause.class == PG::FeatureNotSupported
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  # transactions will be retried until they pass or the max retry limit is
10
10
  # exceeded.
11
11
  def within_new_transaction(isolation: nil, joinable: true, attempts: 0)
12
- super
12
+ super(isolation: isolation, joinable: joinable)
13
13
  rescue ActiveRecord::StatementInvalid => error
14
14
  raise unless retryable? error
15
15
  raise if attempts >= @connection.max_transaction_retries
@@ -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,6 +1,6 @@
1
1
  require "rgeo/active_record"
2
2
 
3
- require 'active_record/connection_adapters/postgresql_adapter'
3
+ require "active_record/connection_adapters/postgresql_adapter"
4
4
  require "active_record/connection_adapters/cockroachdb/column_methods"
5
5
  require "active_record/connection_adapters/cockroachdb/schema_statements"
6
6
  require "active_record/connection_adapters/cockroachdb/referential_integrity"
@@ -13,8 +13,8 @@ require "active_record/connection_adapters/cockroachdb/attribute_methods"
13
13
  require "active_record/connection_adapters/cockroachdb/column"
14
14
  require "active_record/connection_adapters/cockroachdb/spatial_column_info"
15
15
  require "active_record/connection_adapters/cockroachdb/setup"
16
- require "active_record/connection_adapters/cockroachdb/oid/type_map_initializer"
17
16
  require "active_record/connection_adapters/cockroachdb/oid/spatial"
17
+ require "active_record/connection_adapters/cockroachdb/oid/interval"
18
18
  require "active_record/connection_adapters/cockroachdb/arel_tosql"
19
19
 
20
20
  # Run to ignore spatial tables that will break schemna dumper.
@@ -25,25 +25,29 @@ module ActiveRecord
25
25
  module ConnectionHandling
26
26
  def cockroachdb_connection(config)
27
27
  # This is copied from the PostgreSQL adapter.
28
- conn_params = config.symbolize_keys
29
-
30
- conn_params.delete_if { |_, v| v.nil? }
28
+ conn_params = config.symbolize_keys.compact
31
29
 
32
30
  # Map ActiveRecords param names to PGs.
33
31
  conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
34
32
  conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
35
33
 
36
34
  # Forward only valid config params to PG::Connection.connect.
37
- valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:sslmode, :application_name]
35
+ valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
38
36
  conn_params.slice!(*valid_conn_param_keys)
39
37
 
40
- conn = PG.connect(conn_params)
41
- ConnectionAdapters::CockroachDBAdapter.new(conn, logger, conn_params, config)
42
- rescue ::PG::Error, ActiveRecord::ActiveRecordError => error
43
- if error.message.include?("does not exist")
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])
44
48
  raise ActiveRecord::NoDatabaseError
45
49
  else
46
- raise
50
+ raise ActiveRecord::ConnectionNotEstablished, error.message
47
51
  end
48
52
  end
49
53
  end
@@ -51,35 +55,6 @@ end
51
55
 
52
56
  module ActiveRecord
53
57
  module ConnectionAdapters
54
- module CockroachDBConnectionPool
55
- def initialize(spec)
56
- super(spec)
57
- disable_telemetry = spec.config[:disable_cockroachdb_telemetry]
58
- adapter = spec.config[:adapter]
59
- return if disable_telemetry || adapter != "cockroachdb"
60
-
61
-
62
- begin
63
- with_connection do |conn|
64
- if conn.active?
65
- begin
66
- query = "SELECT crdb_internal.increment_feature_counter('ActiveRecord %d.%d')"
67
- conn.execute(query % [ActiveRecord::VERSION::MAJOR, ActiveRecord::VERSION::MINOR])
68
- rescue ActiveRecord::StatementInvalid
69
- # The increment_feature_counter built-in is not supported on this
70
- # CockroachDB version. Ignore.
71
- rescue StandardError => e
72
- conn.logger.warn "Unexpected error when incrementing feature counter: #{e}"
73
- end
74
- end
75
- end
76
- rescue StandardError
77
- # Prevent failures on db creation and parallel testing.
78
- end
79
- end
80
- end
81
- ConnectionPool.prepend(CockroachDBConnectionPool)
82
-
83
58
  class CockroachDBAdapter < PostgreSQLAdapter
84
59
  ADAPTER_NAME = "CockroachDB".freeze
85
60
  DEFAULT_PRIMARY_KEY = "rowid"
@@ -106,56 +81,6 @@ module ActiveRecord
106
81
  include CockroachDB::DatabaseStatements
107
82
  include CockroachDB::Quoting
108
83
 
109
- # override
110
- # This method makes a sql query to gather information about columns
111
- # in a table. It returns an array of arrays (one for each col) and
112
- # passes each to the SchemaStatements#new_column_from_field method
113
- # as the field parameter. This data is then used to format the column
114
- # objects for the model and sent to the OID for data casting.
115
- #
116
- # The issue with the default method is that the sql_type field is
117
- # retrieved with the `format_type` function, but this is implemented
118
- # differently in CockroachDB than PostGIS, so geometry/geography
119
- # types are missing information which makes parsing them impossible.
120
- # Below is an example of what `format_type` returns for a geometry
121
- # column.
122
- #
123
- # column_type: geometry(POINT, 4326)
124
- # Expected: geometry(POINT, 4326)
125
- # Actual: geometry
126
- #
127
- # The solution is to make the default query with super, then
128
- # iterate through the columns and if it is a spatial type,
129
- # access the proper column_type with the information_schema.columns
130
- # table.
131
- #
132
- # @see: https://github.com/rails/rails/blob/8695b028261bdd244e254993255c6641bdbc17a5/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L829
133
- def column_definitions(table_name)
134
- fields = super
135
- # iterate through and identify all spatial fields based on format_type
136
- # being geometry or geography, then query for the information_schema.column
137
- # column_type because that contains the necessary information.
138
- fields.map do |field|
139
- dtype = field[1]
140
- if dtype == 'geometry' || dtype == 'geography'
141
- col_name = field[0]
142
- data_type = \
143
- query(<<~SQL, "SCHEMA")
144
- SELECT c.data_type
145
- FROM information_schema.columns c
146
- WHERE c.table_name = #{quote(table_name)}
147
- AND c.column_name = #{quote(col_name)}
148
- SQL
149
- field[1] = data_type[0][0]
150
- end
151
- field
152
- end
153
- end
154
-
155
- def arel_visitor
156
- Arel::Visitors::CockroachDB.new(self)
157
- end
158
-
159
84
  def self.spatial_column_options(key)
160
85
  SPATIAL_COLUMN_OPTIONS[key]
161
86
  end
@@ -247,10 +172,6 @@ module ActiveRecord
247
172
  @crdb_version >= 202
248
173
  end
249
174
 
250
- def supports_partitioned_indexes?
251
- false
252
- end
253
-
254
175
  # This is hardcoded to 63 (as previously was in ActiveRecord 5.0) to aid in
255
176
  # migration from PostgreSQL to CockroachDB. In practice, this limitation
256
177
  # is arbitrary since CockroachDB supports index name lengths and table alias
@@ -268,7 +189,6 @@ module ActiveRecord
268
189
 
269
190
  def initialize(connection, logger, conn_params, config)
270
191
  super(connection, logger, conn_params, config)
271
-
272
192
  crdb_version_string = query_value("SHOW crdb_version")
273
193
  if crdb_version_string.include? "v1."
274
194
  version_num = 1
@@ -311,19 +231,31 @@ module ActiveRecord
311
231
  end
312
232
  end
313
233
 
314
- # NOTE(joey): PostgreSQL intervals have a precision.
315
- # CockroachDB intervals do not, so overide the type
316
- # definition. Returning a ArgumentError may not be correct.
317
- # This needs to be tested.
318
- m.register_type "interval" do |_, _, sql_type|
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.
237
+ super(m)
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|
319
242
  precision = extract_precision(sql_type)
320
- if precision
321
- 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)
322
257
  end
323
- OID::SpecializedString.new(:interval, precision: precision)
324
258
  end
325
-
326
- super(m)
327
259
  end
328
260
 
329
261
  # Configures the encoding, verbosity, schema search path, and time zone of the connection.
@@ -434,93 +366,77 @@ module ActiveRecord
434
366
  end
435
367
 
436
368
  # override
437
- # This method loads info about data types from the database to
438
- # populate the TypeMap.
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.
439
374
  #
440
- # Currently, querying from the pg_type catalog can be slow due to geo-partitioning
441
- # so this modified query uses AS OF SYSTEM TIME '-10s' to read historical data.
442
- def load_additional_types(oids = nil)
443
- if @config[:use_follower_reads_for_type_introspection]
444
- initializer = OID::TypeMapInitializer.new(type_map)
445
-
446
- query = <<~SQL
447
- SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
448
- FROM pg_type as t
449
- LEFT JOIN pg_range as r ON oid = rngtypid AS OF SYSTEM TIME '-10s'
450
- SQL
451
-
452
- if oids
453
- query += "WHERE t.oid IN (%s)" % oids.join(", ")
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)
454
396
  else
455
- query += initializer.query_conditions_for_initial_load
456
- end
457
-
458
- execute_and_clear(query, "SCHEMA", []) do |records|
459
- initializer.run(records)
397
+ field
460
398
  end
461
- else
462
- super
463
399
  end
464
- rescue ActiveRecord::StatementInvalid => e
465
- raise e unless e.cause.is_a? PG::InvalidCatalogName
466
- # use original if database is younger than 10s
467
- super
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
468
417
  end
469
418
 
470
419
  # override
471
- # This method maps data types to their proper decoder.
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.
472
424
  #
473
- # Currently, querying from the pg_type catalog can be slow due to geo-partitioning
474
- # so this modified query uses AS OF SYSTEM TIME '-10s' to read historical data.
475
- def add_pg_decoders
476
- if @config[:use_follower_reads_for_type_introspection]
477
- @default_timezone = nil
478
- @timestamp_decoder = nil
479
-
480
- coders_by_name = {
481
- "int2" => PG::TextDecoder::Integer,
482
- "int4" => PG::TextDecoder::Integer,
483
- "int8" => PG::TextDecoder::Integer,
484
- "oid" => PG::TextDecoder::Integer,
485
- "float4" => PG::TextDecoder::Float,
486
- "float8" => PG::TextDecoder::Float,
487
- "numeric" => PG::TextDecoder::Numeric,
488
- "bool" => PG::TextDecoder::Boolean,
489
- "timestamp" => PG::TextDecoder::TimestampUtc,
490
- "timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
491
- }
492
-
493
- known_coder_types = coders_by_name.keys.map { |n| quote(n) }
494
- query = <<~SQL % known_coder_types.join(", ")
495
- SELECT t.oid, t.typname
496
- FROM pg_type as t AS OF SYSTEM TIME '-10s'
497
- WHERE t.typname IN (%s)
498
- SQL
499
-
500
- coders = execute_and_clear(query, "SCHEMA", []) do |result|
501
- result
502
- .map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
503
- .compact
504
- end
505
-
506
- map = PG::TypeMapByOid.new
507
- coders.each { |coder| map.add_coder(coder) }
508
- @connection.type_map_for_results = map
509
-
510
- @type_map_for_results = PG::TypeMapByOid.new
511
- @type_map_for_results.default_type_map = map
512
- @type_map_for_results.add_coder(PG::TextDecoder::Bytea.new(oid: 17, name: "bytea"))
513
-
514
- # extract timestamp decoder for use in update_typemap_for_default_timezone
515
- @timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
516
- update_typemap_for_default_timezone
517
- else
518
- super
519
- end
520
- rescue ActiveRecord::StatementInvalid => e
521
- raise e unless e.cause.is_a? PG::InvalidCatalogName
522
- # use original if database is younger than 10s
523
- super
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
524
440
  end
525
441
 
526
442
  def arel_visitor
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: 6.0.2
4
+ version: 6.1.0beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cockroach Labs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-19 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,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 6.0.3
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: 6.0.3
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
39
  - !ruby/object:Gem::Version
40
- version: '0.20'
40
+ version: '1.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rgeo-activerecord
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -62,7 +62,6 @@ extra_rdoc_files: []
62
62
  files:
63
63
  - ".gitignore"
64
64
  - ".gitmodules"
65
- - CHANGELOG.md
66
65
  - CONTRIBUTING.md
67
66
  - Gemfile
68
67
  - LICENSE
@@ -82,8 +81,8 @@ files:
82
81
  - lib/active_record/connection_adapters/cockroachdb/column_methods.rb
83
82
  - lib/active_record/connection_adapters/cockroachdb/database_statements.rb
84
83
  - lib/active_record/connection_adapters/cockroachdb/database_tasks.rb
84
+ - lib/active_record/connection_adapters/cockroachdb/oid/interval.rb
85
85
  - lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb
86
- - lib/active_record/connection_adapters/cockroachdb/oid/type_map_initializer.rb
87
86
  - lib/active_record/connection_adapters/cockroachdb/quoting.rb
88
87
  - lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb
89
88
  - lib/active_record/connection_adapters/cockroachdb/schema_statements.rb
@@ -110,9 +109,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
110
109
  version: '0'
111
110
  required_rubygems_version: !ruby/object:Gem::Requirement
112
111
  requirements:
113
- - - ">="
112
+ - - ">"
114
113
  - !ruby/object:Gem::Version
115
- version: '0'
114
+ version: 1.3.1
116
115
  requirements: []
117
116
  rubygems_version: 3.0.3
118
117
  signing_key:
data/CHANGELOG.md DELETED
@@ -1,39 +0,0 @@
1
- # Changelog
2
-
3
- ## 6.0.2 - 2021-05-20
4
-
5
- - Fix a bug where starting the driver can result in a ConnectionNotEstablished error.
6
-
7
- ## 6.0.1 - 2021-05-14
8
-
9
- - Fix a bug where starting the driver can result in a NoDatabaseError.
10
-
11
- ## 6.0.0 - 2021-04-26
12
-
13
- - Add a telemetry query on start-up. This helps the Cockroach Labs team
14
- prioritize support for the adapter. It can be disabled by setting the
15
- `disable_cockroachdb_telemetry` configuration option to false.
16
-
17
- ## 6.0.0-beta.5 - 2021-04-02
18
-
19
- - Added a configuration option named `use_follower_reads_for_type_introspection`.
20
- If true, it improves the speed of type introspection by allowing potentially stale
21
- type metadata to be read. Defaults to false.
22
-
23
- ## 6.0.0-beta.4 - 2021-03-06
24
-
25
- - Improved connection performance by refactoring an introspection
26
- that loads types.
27
- - Changed version numbers to semver.
28
-
29
- ## 6.0.0beta3
30
-
31
- - Added support for spatial features.
32
-
33
- ## 6.0.0beta2
34
-
35
- - Updated transaction retry logic to work with Rails 6.
36
-
37
- ## 6.0.0beta1
38
-
39
- - Initial support for Rails 6.
@@ -1,26 +0,0 @@
1
- module ActiveRecord
2
- module ConnectionAdapters
3
- module CockroachDB
4
- module OID
5
- module TypeMapInitializer
6
- # override
7
- # Replaces the query with a faster version that doesn't rely on the
8
- # use of 'array_in(cstring,oid,integer)'::regprocedure.
9
- def query_conditions_for_initial_load
10
- known_type_names = @store.keys.map { |n| "'#{n}'" }
11
- known_type_types = %w('r' 'e' 'd')
12
- <<~SQL % [known_type_names.join(", "), known_type_types.join(", ")]
13
- WHERE
14
- t.typname IN (%s)
15
- OR t.typtype IN (%s)
16
- OR (t.typarray = 0 AND t.typcategory='A')
17
- OR t.typelem != 0
18
- SQL
19
- end
20
- end
21
-
22
- PostgreSQL::OID::TypeMapInitializer.prepend(TypeMapInitializer)
23
- end
24
- end
25
- end
26
- end