activerecord-cockroachdb-adapter 6.0.0.pre.beta.4 → 6.1.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -10
- data/CONTRIBUTING.md +20 -0
- data/README.md +7 -0
- data/Rakefile +20 -0
- data/activerecord-cockroachdb-adapter.gemspec +3 -3
- data/build/config.teamcity.yml +3 -0
- data/lib/active_record/connection_adapters/cockroachdb/oid/interval.rb +126 -0
- data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +1 -1
- data/lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb +1 -1
- data/lib/active_record/connection_adapters/cockroachdb/type.rb +1 -3
- data/lib/active_record/connection_adapters/cockroachdb_adapter.rb +235 -70
- metadata +11 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50e75b02ff521bc6e8988549a1ceaa7bbd8ed3eddb138e3fdd6f0d7f91b50e23
|
4
|
+
data.tar.gz: f67eac0c8dce552a8c5270d6c0740281fb9853ccde09ed53ebfc4bac21904cc7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ee1478689e350aeb60b9397c106c3576582d9ac8d33af80b4e721f5dfb6aa1509c149250f16869db85fe5ff5b3db3542da0575ad95517681d55fa77db815b19
|
7
|
+
data.tar.gz: 57ba40b8ca956807626b147592f55b1b193fffbcc5947ea47a11e1830f42a231a08dc0393c3f80fded3035b1897b0fa5579f37da27e97f83809946c692156766
|
data/CHANGELOG.md
CHANGED
@@ -1,19 +1,24 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## 6.
|
3
|
+
## 6.1.0 - 2021-04-26
|
4
4
|
|
5
|
-
-
|
6
|
-
|
7
|
-
|
5
|
+
- Add a telemetry query on start-up. This helps the Cockroach Labs team
|
6
|
+
prioritize support for the adapter. It can be disabled by setting the
|
7
|
+
`disable_cockroachdb_telemetry` configuration option to false.
|
8
8
|
|
9
|
-
## 6.0.
|
9
|
+
## 6.1.0-beta.3 - 2021-04-02
|
10
10
|
|
11
|
-
- Added
|
11
|
+
- Added a configuration option named `use_follower_reads_for_type_introspection`.
|
12
|
+
If true, it improves the speed of type introspection by allowing potentially stale
|
13
|
+
type metadata to be read. Defaults to false.
|
12
14
|
|
13
|
-
## 6.0.
|
15
|
+
## 6.1.0-beta.2 - 2021-03-06
|
14
16
|
|
15
|
-
-
|
17
|
+
- Improved connection performance by refactoring an introspection
|
18
|
+
that loads types.
|
19
|
+
- Changed version numbers to semver.
|
16
20
|
|
17
|
-
## 6.
|
21
|
+
## 6.1.0beta1
|
18
22
|
|
19
|
-
- Initial support for Rails 6.
|
23
|
+
- Initial support for Rails 6.1.
|
24
|
+
- Support for spatial functionality.
|
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,6 +22,13 @@ 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
|
+
|
25
32
|
## Working with Spatial Data
|
26
33
|
|
27
34
|
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.
|
7
|
+
spec.version = "6.1.0"
|
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.
|
17
|
-
spec.add_dependency "pg", "
|
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'
|
data/build/config.teamcity.yml
CHANGED
@@ -11,6 +11,7 @@ connections:
|
|
11
11
|
user: root
|
12
12
|
requiressl: disable
|
13
13
|
min_messages: warning
|
14
|
+
disable_cockroachdb_telemetry: true
|
14
15
|
arunit_without_prepared_statements:
|
15
16
|
database: activerecord_unittest
|
16
17
|
host: localhost
|
@@ -19,6 +20,7 @@ connections:
|
|
19
20
|
requiressl: disable
|
20
21
|
min_messages: warning
|
21
22
|
prepared_statements: false
|
23
|
+
disable_cockroachdb_telemetry: true
|
22
24
|
arunit2:
|
23
25
|
database: activerecord_unittest2
|
24
26
|
host: localhost
|
@@ -26,3 +28,4 @@ connections:
|
|
26
28
|
user: root
|
27
29
|
requiressl: disable
|
28
30
|
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
|
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
|
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"
|
@@ -15,6 +15,7 @@ require "active_record/connection_adapters/cockroachdb/spatial_column_info"
|
|
15
15
|
require "active_record/connection_adapters/cockroachdb/setup"
|
16
16
|
require "active_record/connection_adapters/cockroachdb/oid/type_map_initializer"
|
17
17
|
require "active_record/connection_adapters/cockroachdb/oid/spatial"
|
18
|
+
require "active_record/connection_adapters/cockroachdb/oid/interval"
|
18
19
|
require "active_record/connection_adapters/cockroachdb/arel_tosql"
|
19
20
|
|
20
21
|
# Run to ignore spatial tables that will break schemna dumper.
|
@@ -25,25 +26,29 @@ module ActiveRecord
|
|
25
26
|
module ConnectionHandling
|
26
27
|
def cockroachdb_connection(config)
|
27
28
|
# This is copied from the PostgreSQL adapter.
|
28
|
-
conn_params = config.symbolize_keys
|
29
|
-
|
30
|
-
conn_params.delete_if { |_, v| v.nil? }
|
29
|
+
conn_params = config.symbolize_keys.compact
|
31
30
|
|
32
31
|
# Map ActiveRecords param names to PGs.
|
33
32
|
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
|
34
33
|
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
|
35
34
|
|
36
35
|
# Forward only valid config params to PG::Connection.connect.
|
37
|
-
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:
|
36
|
+
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
|
38
37
|
conn_params.slice!(*valid_conn_param_keys)
|
39
38
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
ConnectionAdapters::CockroachDBAdapter.new(
|
40
|
+
ConnectionAdapters::CockroachDBAdapter.new_client(conn_params),
|
41
|
+
logger,
|
42
|
+
conn_params,
|
43
|
+
config
|
44
|
+
)
|
45
|
+
# This rescue flow appears in new_client, but it is needed here as well
|
46
|
+
# since Cockroach will sometimes not raise until a query is made.
|
47
|
+
rescue ActiveRecord::StatementInvalid => error
|
48
|
+
if conn_params && conn_params[:dbname] && error.cause.message.include?(conn_params[:dbname])
|
44
49
|
raise ActiveRecord::NoDatabaseError
|
45
50
|
else
|
46
|
-
raise
|
51
|
+
raise ActiveRecord::ConnectionNotEstablished, error.message
|
47
52
|
end
|
48
53
|
end
|
49
54
|
end
|
@@ -51,6 +56,30 @@ end
|
|
51
56
|
|
52
57
|
module ActiveRecord
|
53
58
|
module ConnectionAdapters
|
59
|
+
module CockroachDBConnectionPool
|
60
|
+
def initialize(pool_config)
|
61
|
+
super(pool_config)
|
62
|
+
disable_telemetry = pool_config.db_config.configuration_hash[:disable_cockroachdb_telemetry]
|
63
|
+
adapter = pool_config.db_config.configuration_hash[:adapter]
|
64
|
+
return if disable_telemetry || adapter != "cockroachdb"
|
65
|
+
|
66
|
+
with_connection do |conn|
|
67
|
+
if conn.active?
|
68
|
+
begin
|
69
|
+
query = "SELECT crdb_internal.increment_feature_counter('ActiveRecord %d.%d')"
|
70
|
+
conn.execute(query % [ActiveRecord::VERSION::MAJOR, ActiveRecord::VERSION::MINOR])
|
71
|
+
rescue ActiveRecord::StatementInvalid
|
72
|
+
# The increment_feature_counter built-in is not supported on this
|
73
|
+
# CockroachDB version. Ignore.
|
74
|
+
rescue StandardError => e
|
75
|
+
conn.logger.warn "Unexpected error when incrementing feature counter: #{e}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
ConnectionPool.prepend(CockroachDBConnectionPool)
|
82
|
+
|
54
83
|
class CockroachDBAdapter < PostgreSQLAdapter
|
55
84
|
ADAPTER_NAME = "CockroachDB".freeze
|
56
85
|
DEFAULT_PRIMARY_KEY = "rowid"
|
@@ -77,56 +106,6 @@ module ActiveRecord
|
|
77
106
|
include CockroachDB::DatabaseStatements
|
78
107
|
include CockroachDB::Quoting
|
79
108
|
|
80
|
-
# override
|
81
|
-
# This method makes a sql query to gather information about columns
|
82
|
-
# in a table. It returns an array of arrays (one for each col) and
|
83
|
-
# passes each to the SchemaStatements#new_column_from_field method
|
84
|
-
# as the field parameter. This data is then used to format the column
|
85
|
-
# objects for the model and sent to the OID for data casting.
|
86
|
-
#
|
87
|
-
# The issue with the default method is that the sql_type field is
|
88
|
-
# retrieved with the `format_type` function, but this is implemented
|
89
|
-
# differently in CockroachDB than PostGIS, so geometry/geography
|
90
|
-
# types are missing information which makes parsing them impossible.
|
91
|
-
# Below is an example of what `format_type` returns for a geometry
|
92
|
-
# column.
|
93
|
-
#
|
94
|
-
# column_type: geometry(POINT, 4326)
|
95
|
-
# Expected: geometry(POINT, 4326)
|
96
|
-
# Actual: geometry
|
97
|
-
#
|
98
|
-
# The solution is to make the default query with super, then
|
99
|
-
# iterate through the columns and if it is a spatial type,
|
100
|
-
# access the proper column_type with the information_schema.columns
|
101
|
-
# table.
|
102
|
-
#
|
103
|
-
# @see: https://github.com/rails/rails/blob/8695b028261bdd244e254993255c6641bdbc17a5/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L829
|
104
|
-
def column_definitions(table_name)
|
105
|
-
fields = super
|
106
|
-
# iterate through and identify all spatial fields based on format_type
|
107
|
-
# being geometry or geography, then query for the information_schema.column
|
108
|
-
# column_type because that contains the necessary information.
|
109
|
-
fields.map do |field|
|
110
|
-
dtype = field[1]
|
111
|
-
if dtype == 'geometry' || dtype == 'geography'
|
112
|
-
col_name = field[0]
|
113
|
-
data_type = \
|
114
|
-
query(<<~SQL, "SCHEMA")
|
115
|
-
SELECT c.data_type
|
116
|
-
FROM information_schema.columns c
|
117
|
-
WHERE c.table_name = #{quote(table_name)}
|
118
|
-
AND c.column_name = #{quote(col_name)}
|
119
|
-
SQL
|
120
|
-
field[1] = data_type[0][0]
|
121
|
-
end
|
122
|
-
field
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def arel_visitor
|
127
|
-
Arel::Visitors::CockroachDB.new(self)
|
128
|
-
end
|
129
|
-
|
130
109
|
def self.spatial_column_options(key)
|
131
110
|
SPATIAL_COLUMN_OPTIONS[key]
|
132
111
|
end
|
@@ -218,6 +197,10 @@ module ActiveRecord
|
|
218
197
|
@crdb_version >= 202
|
219
198
|
end
|
220
199
|
|
200
|
+
def supports_partitioned_indexes?
|
201
|
+
false
|
202
|
+
end
|
203
|
+
|
221
204
|
# This is hardcoded to 63 (as previously was in ActiveRecord 5.0) to aid in
|
222
205
|
# migration from PostgreSQL to CockroachDB. In practice, this limitation
|
223
206
|
# is arbitrary since CockroachDB supports index name lengths and table alias
|
@@ -235,6 +218,7 @@ module ActiveRecord
|
|
235
218
|
|
236
219
|
def initialize(connection, logger, conn_params, config)
|
237
220
|
super(connection, logger, conn_params, config)
|
221
|
+
|
238
222
|
crdb_version_string = query_value("SHOW crdb_version")
|
239
223
|
if crdb_version_string.include? "v1."
|
240
224
|
version_num = 1
|
@@ -277,19 +261,31 @@ module ActiveRecord
|
|
277
261
|
end
|
278
262
|
end
|
279
263
|
|
280
|
-
#
|
281
|
-
#
|
282
|
-
#
|
283
|
-
|
284
|
-
|
264
|
+
# Belongs after other types are defined because of issues described
|
265
|
+
# in this https://github.com/rails/rails/pull/38571
|
266
|
+
# Once that PR is merged, we can call super at the top.
|
267
|
+
super(m)
|
268
|
+
|
269
|
+
# Override numeric type. This is almost identical to the default,
|
270
|
+
# except that the conditional based on the fmod is changed.
|
271
|
+
m.register_type "numeric" do |_, fmod, sql_type|
|
285
272
|
precision = extract_precision(sql_type)
|
286
|
-
|
287
|
-
|
273
|
+
scale = extract_scale(sql_type)
|
274
|
+
|
275
|
+
# TODO(#178) this should never use DecimalWithoutScale since scale
|
276
|
+
# is assumed to be 0 if it is not explicitly defined.
|
277
|
+
#
|
278
|
+
# If fmod is -1, that means that precision is defined but not
|
279
|
+
# scale, or neither is defined.
|
280
|
+
if fmod && fmod == -1
|
281
|
+
# Below comment is from ActiveRecord
|
282
|
+
# FIXME: Remove this class, and the second argument to
|
283
|
+
# lookups on PG
|
284
|
+
Type::DecimalWithoutScale.new(precision: precision)
|
285
|
+
else
|
286
|
+
OID::Decimal.new(precision: precision, scale: scale)
|
288
287
|
end
|
289
|
-
OID::SpecializedString.new(:interval, precision: precision)
|
290
288
|
end
|
291
|
-
|
292
|
-
super(m)
|
293
289
|
end
|
294
290
|
|
295
291
|
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
@@ -399,6 +395,175 @@ module ActiveRecord
|
|
399
395
|
return "{}"
|
400
396
|
end
|
401
397
|
|
398
|
+
# override
|
399
|
+
# This method makes a query to gather information about columns
|
400
|
+
# in a table. It returns an array of arrays (one for each col) and
|
401
|
+
# passes each to the SchemaStatements#new_column_from_field method
|
402
|
+
# as the field parameter. This data is then used to format the column
|
403
|
+
# objects for the model and sent to the OID for data casting.
|
404
|
+
#
|
405
|
+
# Sometimes there are differences between how data is formatted
|
406
|
+
# in Postgres and CockroachDB, so additional queries for certain types
|
407
|
+
# may be necessary to properly form the column definition.
|
408
|
+
#
|
409
|
+
# @see: https://github.com/rails/rails/blob/8695b028261bdd244e254993255c6641bdbc17a5/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L829
|
410
|
+
def column_definitions(table_name)
|
411
|
+
fields = super
|
412
|
+
|
413
|
+
# Use regex comparison because if a type is an array it will
|
414
|
+
# have [] appended to the end of it.
|
415
|
+
target_types = [
|
416
|
+
/geometry/,
|
417
|
+
/geography/,
|
418
|
+
/interval/,
|
419
|
+
/numeric/
|
420
|
+
]
|
421
|
+
re = Regexp.union(target_types)
|
422
|
+
fields.map do |field|
|
423
|
+
dtype = field[1]
|
424
|
+
if re.match(dtype)
|
425
|
+
crdb_column_definition(field, table_name)
|
426
|
+
else
|
427
|
+
field
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
# Use the crdb_sql_type instead of the sql_type returned by
|
433
|
+
# column_definitions. This will include limit,
|
434
|
+
# precision, and scale information in the type.
|
435
|
+
# Ex. geometry -> geometry(point, 4326)
|
436
|
+
def crdb_column_definition(field, table_name)
|
437
|
+
col_name = field[0]
|
438
|
+
data_type = \
|
439
|
+
query(<<~SQL, "SCHEMA")
|
440
|
+
SELECT c.crdb_sql_type
|
441
|
+
FROM information_schema.columns c
|
442
|
+
WHERE c.table_name = #{quote(table_name)}
|
443
|
+
AND c.column_name = #{quote(col_name)}
|
444
|
+
SQL
|
445
|
+
field[1] = data_type[0][0].downcase
|
446
|
+
field
|
447
|
+
end
|
448
|
+
|
449
|
+
# override
|
450
|
+
# This method is used to determine if a
|
451
|
+
# FEATURE_NOT_SUPPORTED error from the PG gem should
|
452
|
+
# be an ActiveRecord::PreparedStatementCacheExpired
|
453
|
+
# error.
|
454
|
+
#
|
455
|
+
# ActiveRecord handles this by checking that the sql state matches the
|
456
|
+
# FEATURE_NOT_SUPPORTED code and that the source function
|
457
|
+
# is "RevalidateCachedQuery" since that is the only function
|
458
|
+
# in postgres that will create this error.
|
459
|
+
#
|
460
|
+
# That method will not work for CockroachDB because the error
|
461
|
+
# originates from the "runExecBuilder" function, so we need
|
462
|
+
# to modify the original to match the CockroachDB behavior.
|
463
|
+
def is_cached_plan_failure?(e)
|
464
|
+
pgerror = e.cause
|
465
|
+
|
466
|
+
pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
|
467
|
+
pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "runExecBuilder"
|
468
|
+
rescue
|
469
|
+
false
|
470
|
+
end
|
471
|
+
|
472
|
+
# override
|
473
|
+
# This method loads info about data types from the database to
|
474
|
+
# populate the TypeMap.
|
475
|
+
#
|
476
|
+
# Currently, querying from the pg_type catalog can be slow due to geo-partitioning
|
477
|
+
# so this modified query uses AS OF SYSTEM TIME '-10s' to read historical data.
|
478
|
+
def load_additional_types(oids = nil)
|
479
|
+
if @config[:use_follower_reads_for_type_introspection]
|
480
|
+
initializer = OID::TypeMapInitializer.new(type_map)
|
481
|
+
|
482
|
+
query = <<~SQL
|
483
|
+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
|
484
|
+
FROM pg_type as t
|
485
|
+
LEFT JOIN pg_range as r ON oid = rngtypid AS OF SYSTEM TIME '-10s'
|
486
|
+
SQL
|
487
|
+
|
488
|
+
if oids
|
489
|
+
query += "WHERE t.oid IN (%s)" % oids.join(", ")
|
490
|
+
else
|
491
|
+
query += initializer.query_conditions_for_initial_load
|
492
|
+
end
|
493
|
+
|
494
|
+
execute_and_clear(query, "SCHEMA", []) do |records|
|
495
|
+
initializer.run(records)
|
496
|
+
end
|
497
|
+
else
|
498
|
+
super
|
499
|
+
end
|
500
|
+
rescue ActiveRecord::StatementInvalid => e
|
501
|
+
raise e unless e.cause.is_a? PG::InvalidCatalogName
|
502
|
+
# use original if database is younger than 10s
|
503
|
+
super
|
504
|
+
end
|
505
|
+
|
506
|
+
# override
|
507
|
+
# This method maps data types to their proper decoder.
|
508
|
+
#
|
509
|
+
# Currently, querying from the pg_type catalog can be slow due to geo-partitioning
|
510
|
+
# so this modified query uses AS OF SYSTEM TIME '-10s' to read historical data.
|
511
|
+
def add_pg_decoders
|
512
|
+
if @config[:use_follower_reads_for_type_introspection]
|
513
|
+
@default_timezone = nil
|
514
|
+
@timestamp_decoder = nil
|
515
|
+
|
516
|
+
coders_by_name = {
|
517
|
+
"int2" => PG::TextDecoder::Integer,
|
518
|
+
"int4" => PG::TextDecoder::Integer,
|
519
|
+
"int8" => PG::TextDecoder::Integer,
|
520
|
+
"oid" => PG::TextDecoder::Integer,
|
521
|
+
"float4" => PG::TextDecoder::Float,
|
522
|
+
"float8" => PG::TextDecoder::Float,
|
523
|
+
"numeric" => PG::TextDecoder::Numeric,
|
524
|
+
"bool" => PG::TextDecoder::Boolean,
|
525
|
+
"timestamp" => PG::TextDecoder::TimestampUtc,
|
526
|
+
"timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
|
527
|
+
}
|
528
|
+
|
529
|
+
known_coder_types = coders_by_name.keys.map { |n| quote(n) }
|
530
|
+
query = <<~SQL % known_coder_types.join(", ")
|
531
|
+
SELECT t.oid, t.typname
|
532
|
+
FROM pg_type as t AS OF SYSTEM TIME '-10s'
|
533
|
+
WHERE t.typname IN (%s)
|
534
|
+
SQL
|
535
|
+
|
536
|
+
coders = execute_and_clear(query, "SCHEMA", []) do |result|
|
537
|
+
result
|
538
|
+
.map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
|
539
|
+
.compact
|
540
|
+
end
|
541
|
+
|
542
|
+
map = PG::TypeMapByOid.new
|
543
|
+
coders.each { |coder| map.add_coder(coder) }
|
544
|
+
@connection.type_map_for_results = map
|
545
|
+
|
546
|
+
@type_map_for_results = PG::TypeMapByOid.new
|
547
|
+
@type_map_for_results.default_type_map = map
|
548
|
+
@type_map_for_results.add_coder(PG::TextDecoder::Bytea.new(oid: 17, name: "bytea"))
|
549
|
+
@type_map_for_results.add_coder(MoneyDecoder.new(oid: 790, name: "money"))
|
550
|
+
|
551
|
+
# extract timestamp decoder for use in update_typemap_for_default_timezone
|
552
|
+
@timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
|
553
|
+
update_typemap_for_default_timezone
|
554
|
+
else
|
555
|
+
super
|
556
|
+
end
|
557
|
+
rescue ActiveRecord::StatementInvalid => e
|
558
|
+
raise e unless e.cause.is_a? PG::InvalidCatalogName
|
559
|
+
# use original if database is younger than 10s
|
560
|
+
super
|
561
|
+
end
|
562
|
+
|
563
|
+
def arel_visitor
|
564
|
+
Arel::Visitors::CockroachDB.new(self)
|
565
|
+
end
|
566
|
+
|
402
567
|
# end private
|
403
568
|
end
|
404
569
|
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: 6.
|
4
|
+
version: 6.1.0
|
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-
|
11
|
+
date: 2021-04-26 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.
|
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.
|
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: '
|
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: '
|
40
|
+
version: '1.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rgeo-activerecord
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -82,6 +82,7 @@ files:
|
|
82
82
|
- lib/active_record/connection_adapters/cockroachdb/column_methods.rb
|
83
83
|
- lib/active_record/connection_adapters/cockroachdb/database_statements.rb
|
84
84
|
- lib/active_record/connection_adapters/cockroachdb/database_tasks.rb
|
85
|
+
- lib/active_record/connection_adapters/cockroachdb/oid/interval.rb
|
85
86
|
- lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb
|
86
87
|
- lib/active_record/connection_adapters/cockroachdb/oid/type_map_initializer.rb
|
87
88
|
- lib/active_record/connection_adapters/cockroachdb/quoting.rb
|
@@ -110,9 +111,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
110
111
|
version: '0'
|
111
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
113
|
requirements:
|
113
|
-
- - "
|
114
|
+
- - ">="
|
114
115
|
- !ruby/object:Gem::Version
|
115
|
-
version:
|
116
|
+
version: '0'
|
116
117
|
requirements: []
|
117
118
|
rubygems_version: 3.1.4
|
118
119
|
signing_key:
|