activerecord-cockroachdb-adapter 7.0.3 → 7.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +7 -0
- data/.github/workflows/ci.yml +24 -12
- data/CHANGELOG.md +7 -0
- data/Gemfile +10 -1
- data/activerecord-cockroachdb-adapter.gemspec +1 -1
- data/bin/console +17 -33
- data/bin/console_schemas/default.rb +9 -0
- data/bin/console_schemas/schemas.rb +23 -0
- data/bin/start-cockroachdb +5 -5
- data/build/Dockerfile +1 -1
- data/lib/active_record/connection_adapters/cockroachdb/arel_tosql.rb +8 -0
- data/lib/active_record/connection_adapters/cockroachdb/column.rb +3 -7
- data/lib/active_record/connection_adapters/cockroachdb/column_methods.rb +8 -0
- data/lib/active_record/connection_adapters/cockroachdb/database_statements.rb +0 -83
- data/lib/active_record/connection_adapters/cockroachdb/database_tasks.rb +31 -24
- data/lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb +14 -10
- data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +41 -13
- data/lib/active_record/connection_adapters/cockroachdb/type.rb +3 -2
- data/lib/active_record/connection_adapters/cockroachdb_adapter.rb +86 -148
- data/lib/active_record/relation/query_methods_ext.rb +30 -1
- data/lib/arel/nodes/join_source_ext.rb +28 -0
- data/lib/version.rb +1 -1
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c3f85e82470685a2ece6851d557bcaf03f5a77ce309d76062c3960ca17d1606
|
4
|
+
data.tar.gz: b6ab7bfb9d29feebf217b70b11ccf2f73e6fb0853138cc27f4556f07b6c36dac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96fbb4c5a42c1ab3f2fe590f3c74012b883c818608c5b9d0525f30739f1d4d8733c95cfffd31a8a05350d6210d995f84d915727a494264d1cb1c217d10d200b1
|
7
|
+
data.tar.gz: 12ae86ab8f26eb074eabb60cd3f831eed44729d98f70bdc88d1c0d9c3d5cff5680ae7cf0a9643756395e6cfc1257cb7fc62ee5abab4c37c6f0fe45817fc619a0
|
data/.editorconfig
ADDED
data/.github/workflows/ci.yml
CHANGED
@@ -4,12 +4,9 @@
|
|
4
4
|
name: Test
|
5
5
|
|
6
6
|
on:
|
7
|
-
# Triggers the workflow on push or pull request events.
|
8
7
|
push:
|
9
|
-
|
10
|
-
|
11
|
-
branches:
|
12
|
-
- "*"
|
8
|
+
branches: [master]
|
9
|
+
# Triggers the workflow on pull request events.
|
13
10
|
pull_request:
|
14
11
|
types: [opened, reopened, synchronize]
|
15
12
|
|
@@ -18,27 +15,42 @@ on:
|
|
18
15
|
|
19
16
|
# This allows a subsequently queued workflow run to interrupt previous runs.
|
20
17
|
concurrency:
|
21
|
-
group:
|
18
|
+
group: "${{ github.workflow }} @ ${{ github.ref }}"
|
22
19
|
cancel-in-progress: true
|
23
20
|
|
24
21
|
jobs:
|
22
|
+
# Since the name of the matrix job depends on the version, we define another job with a more stable name.
|
23
|
+
test_results:
|
24
|
+
if: ${{ always() }}
|
25
|
+
runs-on: ubuntu-latest
|
26
|
+
name: Test Results
|
27
|
+
needs: [test]
|
28
|
+
steps:
|
29
|
+
- run: |
|
30
|
+
result="${{ needs.test.result }}"
|
31
|
+
if [[ $result == "success" || $result == "skipped" ]]; then
|
32
|
+
exit 0
|
33
|
+
else
|
34
|
+
exit 1
|
35
|
+
fi
|
36
|
+
|
25
37
|
test:
|
26
38
|
runs-on: ubuntu-latest
|
27
39
|
strategy:
|
28
40
|
matrix:
|
29
|
-
crdb: [v23.1.
|
30
|
-
ruby: [
|
41
|
+
crdb: [v23.1.11]
|
42
|
+
ruby: [head]
|
31
43
|
name: Test (crdb=${{ matrix.crdb }} ruby=${{ matrix.ruby }})
|
32
44
|
steps:
|
33
45
|
- name: Set Up Actions
|
34
|
-
uses: actions/checkout@
|
46
|
+
uses: actions/checkout@v4
|
35
47
|
- name: Install GEOS
|
36
48
|
run: sudo apt-get install libgeos-dev
|
37
49
|
- name: Set Up Ruby
|
38
50
|
uses: ruby/setup-ruby@v1
|
39
51
|
with:
|
40
|
-
|
41
|
-
|
52
|
+
ruby-version: ${{ matrix.ruby }}
|
53
|
+
bundler-cache: true
|
42
54
|
- name: Install and Start Cockroachdb
|
43
55
|
run: |
|
44
56
|
# Download CockroachDB
|
@@ -83,4 +95,4 @@ jobs:
|
|
83
95
|
SET CLUSTER SETTING sql.defaults.experimental_temporary_tables.enabled = 'true';
|
84
96
|
"
|
85
97
|
- name: Test
|
86
|
-
run: bundle exec rake test
|
98
|
+
run: bundle exec rake test TESTOPTS='--profile=3'
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
## Ongoing
|
4
4
|
|
5
|
+
- Add support for [AOST](cockroachlabs.com/docs/stable/as-of-system-time) queries ([#284](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/284))
|
6
|
+
- Dump schema name in foreign keys ([#289](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/289))
|
7
|
+
|
8
|
+
## 7.1.0 - 2024-01-30
|
9
|
+
|
10
|
+
- Add support for Rails 7.1 ([#300](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/300)).
|
11
|
+
|
5
12
|
## 7.0.3 - 2023-08-23
|
6
13
|
|
7
14
|
- Fix Multiple Database connections ([#283](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/)).
|
data/Gemfile
CHANGED
@@ -10,6 +10,11 @@ module RailsTag
|
|
10
10
|
def call
|
11
11
|
req = gemspec_requirement
|
12
12
|
"v" + all_activerecord_versions.find { req.satisfied_by?(_1) }.version
|
13
|
+
rescue => e
|
14
|
+
warn "Unable to determine Rails version. Using last used. Error: #{e.message}"
|
15
|
+
lockfile = File.expand_path("Gemfile.lock", __dir__)
|
16
|
+
File.foreach(lockfile, chomp: true).find { _1[/tag: (.*)$/] }
|
17
|
+
Regexp.last_match(1)
|
13
18
|
end
|
14
19
|
|
15
20
|
def gemspec_requirement
|
@@ -43,9 +48,13 @@ group :development, :test do
|
|
43
48
|
# a specific rails codebase.
|
44
49
|
gem "rails", github: "rails/rails", tag: RailsTag.call
|
45
50
|
|
51
|
+
# Needed for the test suite
|
52
|
+
gem "msgpack", ">= 1.7.0"
|
53
|
+
|
46
54
|
gem "rake"
|
47
|
-
gem "
|
55
|
+
gem "debug"
|
48
56
|
gem "minitest-excludes", "~> 2.0.1"
|
57
|
+
gem "minitest-github_action_reporter", github: "BuonOmo/minitest-github_action_reporter", require: "minitest/github_action_reporter_plugin"
|
49
58
|
|
50
59
|
# Gems used by the ActiveRecord test suite
|
51
60
|
gem "bcrypt", "~> 3.1.18"
|
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.description = "Allows the use of CockroachDB as a backend for ActiveRecord and Rails apps."
|
15
15
|
spec.homepage = "https://github.com/cockroachdb/activerecord-cockroachdb-adapter"
|
16
16
|
|
17
|
-
spec.add_dependency "activerecord", "~> 7.0
|
17
|
+
spec.add_dependency "activerecord", "~> 7.1.0"
|
18
18
|
spec.add_dependency "pg", "~> 1.2"
|
19
19
|
spec.add_dependency "rgeo-activerecord", "~> 7.0.0"
|
20
20
|
|
data/bin/console
CHANGED
@@ -12,39 +12,23 @@ require "active_record"
|
|
12
12
|
# structure_load(Post.connection_db_config, "awesome-file.sql")
|
13
13
|
require "active_record/connection_adapters/cockroachdb/database_tasks"
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
class Post < ActiveRecord::Base
|
35
|
-
end
|
36
|
-
|
37
|
-
unless Post.table_exists?
|
38
|
-
migration = Class.new(ActiveRecord::Migration::Current) do
|
39
|
-
def up
|
40
|
-
create_table("posts") do |t|
|
41
|
-
t.string :title
|
42
|
-
t.text :body
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
migration.migrate(:up)
|
47
|
-
end
|
15
|
+
schema_kind = ENV.fetch("SCHEMA_KIND", "default")
|
16
|
+
|
17
|
+
system("cockroach sql --insecure --host=localhost:26257 --execute='drop database if exists ar_crdb_console'",
|
18
|
+
exception: true)
|
19
|
+
system("cockroach sql --insecure --host=localhost:26257 --execute='create database ar_crdb_console'",
|
20
|
+
exception: true)
|
21
|
+
|
22
|
+
ActiveRecord::Base.establish_connection(
|
23
|
+
#Alternative version: "cockroachdb://root@localhost:26257/ar_crdb_console"
|
24
|
+
adapter: "cockroachdb",
|
25
|
+
host: "localhost",
|
26
|
+
port: 26257,
|
27
|
+
user: "root",
|
28
|
+
database: "ar_crdb_console"
|
29
|
+
)
|
30
|
+
|
31
|
+
load "#{__dir__}/console_schemas/#{schema_kind}.rb"
|
48
32
|
|
49
33
|
require "irb"
|
50
34
|
IRB.start(__FILE__)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Post < ActiveRecord::Base
|
2
|
+
self.table_name = "bar.posts"
|
3
|
+
end
|
4
|
+
|
5
|
+
class Comment < ActiveRecord::Base
|
6
|
+
self.table_name = "foo.comments"
|
7
|
+
end
|
8
|
+
|
9
|
+
ActiveRecord::Schema.define do
|
10
|
+
create_schema("foo")
|
11
|
+
create_schema("bar")
|
12
|
+
create_table("bar.posts") do |t|
|
13
|
+
t.string :title
|
14
|
+
t.text :body
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table("foo.comments") do |t|
|
18
|
+
t.integer :post_id
|
19
|
+
t.text :body
|
20
|
+
end
|
21
|
+
|
22
|
+
add_foreign_key "foo.comments", "bar.posts", column: "post_id"
|
23
|
+
end
|
data/bin/start-cockroachdb
CHANGED
@@ -9,6 +9,7 @@ pid_file="$root_dir/tmp/cockroach.pid"
|
|
9
9
|
log_file="$root_dir/tmp/cockroachdb.log"
|
10
10
|
|
11
11
|
mkdir -p "$root_dir/tmp"
|
12
|
+
[[ -f "$pid_file" ]] && kill -9 $(cat "$pid_file")
|
12
13
|
rm -f "$pid_file"
|
13
14
|
|
14
15
|
if ! (( ${+commands[cockroach]} )); then
|
@@ -20,8 +21,6 @@ cockroach start-single-node \
|
|
20
21
|
--insecure --store=type=mem,size=0.25 --advertise-addr=localhost --pid-file "$pid_file" \
|
21
22
|
&> "$log_file" &
|
22
23
|
|
23
|
-
cockroach_pid=$!
|
24
|
-
|
25
24
|
until [[ -f "$pid_file" ]]; do
|
26
25
|
sleep 1
|
27
26
|
done
|
@@ -41,8 +40,9 @@ ALTER DATABASE system CONFIGURE ZONE USING "gc.ttlseconds" = 600;
|
|
41
40
|
|
42
41
|
CREATE DATABASE activerecord_unittest;
|
43
42
|
CREATE DATABASE activerecord_unittest2;
|
44
|
-
SQL
|
45
43
|
|
46
|
-
|
44
|
+
SET CLUSTER SETTING sql.defaults.experimental_alter_column_type.enabled = 'true';
|
45
|
+
SET CLUSTER SETTING sql.defaults.experimental_temporary_tables.enabled = 'true';
|
46
|
+
SQL
|
47
47
|
|
48
|
-
|
48
|
+
echo "CockroachDB started. PID: $(cat "$pid_file"). log: $log_file"
|
data/build/Dockerfile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# This Dockerfile extends the Examples-ORM testing image in order to
|
2
2
|
# install specific dependencies required for ActiveRecord tests.
|
3
3
|
|
4
|
-
FROM
|
4
|
+
FROM us-east1-docker.pkg.dev/crl-ci-images/cockroach/example-orms-builder:20200413-1918
|
5
5
|
|
6
6
|
# Native dependencies for libxml-ruby and sqlite3.
|
7
7
|
RUN apt-get --allow-releaseinfo-change update -y && apt-get install -y \
|
@@ -22,6 +22,14 @@ module Arel # :nodoc:
|
|
22
22
|
module Visitors # :nodoc:
|
23
23
|
class CockroachDB < PostgreSQL # :nodoc:
|
24
24
|
include RGeo::ActiveRecord::SpatialToSql
|
25
|
+
|
26
|
+
def visit_Arel_Nodes_JoinSource(o, collector)
|
27
|
+
super
|
28
|
+
if o.aost
|
29
|
+
collector << " AS OF SYSTEM TIME '#{o.aost.iso8601}'"
|
30
|
+
end
|
31
|
+
collector
|
32
|
+
end
|
25
33
|
end
|
26
34
|
end
|
27
35
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module ConnectionAdapters
|
3
3
|
module CockroachDB
|
4
|
-
class Column <
|
4
|
+
class Column < PostgreSQL::Column
|
5
5
|
# most functions taken from activerecord-postgis-adapter spatial_column
|
6
6
|
# https://github.com/rgeo/activerecord-postgis-adapter/blob/master/lib/active_record/connection_adapters/postgis/spatial_column.rb
|
7
7
|
def initialize(name, default, sql_type_metadata = nil, null = true,
|
8
|
-
default_function = nil, collation: nil, comment: nil,
|
8
|
+
default_function = nil, collation: nil, comment: nil, identity: nil,
|
9
9
|
serial: nil, spatial: nil, generated: nil, hidden: nil)
|
10
10
|
@sql_type_metadata = sql_type_metadata
|
11
11
|
@geographic = !!(sql_type_metadata.sql_type =~ /geography\(/i)
|
@@ -30,7 +30,7 @@ module ActiveRecord
|
|
30
30
|
build_from_sql_type(sql_type_metadata.sql_type)
|
31
31
|
end
|
32
32
|
super(name, default, sql_type_metadata, null, default_function,
|
33
|
-
collation: collation, comment: comment, serial: serial, generated: generated)
|
33
|
+
collation: collation, comment: comment, serial: serial, generated: generated, identity: identity)
|
34
34
|
if spatial? && @srid
|
35
35
|
@limit = { srid: @srid, type: to_type_name(geometric_type) }
|
36
36
|
@limit[:has_z] = true if @has_z
|
@@ -53,10 +53,6 @@ module ActiveRecord
|
|
53
53
|
spatial? ? @limit : super
|
54
54
|
end
|
55
55
|
|
56
|
-
def virtual?
|
57
|
-
@generated.present?
|
58
|
-
end
|
59
|
-
|
60
56
|
def hidden?
|
61
57
|
@hidden
|
62
58
|
end
|
@@ -45,6 +45,14 @@ module ActiveRecord
|
|
45
45
|
def st_polygon(name, options = {})
|
46
46
|
column(name, :st_polygon, **options)
|
47
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def valid_column_definition_options
|
52
|
+
spatial = [:srid, :has_z, :has_m, :geographic, :spatial_type]
|
53
|
+
crdb = [:hidden]
|
54
|
+
super + spatial + crdb
|
55
|
+
end
|
48
56
|
end
|
49
57
|
end
|
50
58
|
|
@@ -2,21 +2,6 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
module CockroachDB
|
4
4
|
module DatabaseStatements
|
5
|
-
# Since CockroachDB will run all transactions with serializable isolation,
|
6
|
-
# READ UNCOMMITTED, READ COMMITTED, and REPEATABLE READ are all aliases
|
7
|
-
# for SERIALIZABLE. This lets the adapter support all isolation levels,
|
8
|
-
# but READ UNCOMMITTED has been removed from this list because the
|
9
|
-
# ActiveRecord transaction isolation test fails for READ UNCOMMITTED.
|
10
|
-
# See https://www.cockroachlabs.com/docs/v19.2/transactions.html#isolation-levels
|
11
|
-
def transaction_isolation_levels
|
12
|
-
{
|
13
|
-
read_committed: "READ COMMITTED",
|
14
|
-
repeatable_read: "REPEATABLE READ",
|
15
|
-
serializable: "SERIALIZABLE",
|
16
|
-
read_uncommitted: "READ UNCOMMITTED"
|
17
|
-
}
|
18
|
-
end
|
19
|
-
|
20
5
|
# Overridden to avoid using transactions for schema creation.
|
21
6
|
def insert_fixtures_set(fixture_set, tables_to_delete = [])
|
22
7
|
fixture_inserts = build_fixture_statements(fixture_set)
|
@@ -29,74 +14,6 @@ module ActiveRecord
|
|
29
14
|
end
|
30
15
|
end
|
31
16
|
end
|
32
|
-
|
33
|
-
private
|
34
|
-
def execute_batch(statements, name = nil)
|
35
|
-
statements.each do |statement|
|
36
|
-
execute(statement, name)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
|
41
|
-
private_constant :DEFAULT_INSERT_VALUE
|
42
|
-
|
43
|
-
def default_insert_value(column)
|
44
|
-
DEFAULT_INSERT_VALUE
|
45
|
-
end
|
46
|
-
|
47
|
-
def build_fixture_sql(fixtures, table_name)
|
48
|
-
columns = schema_cache.columns_hash(table_name)
|
49
|
-
|
50
|
-
values_list = fixtures.map do |fixture|
|
51
|
-
fixture = fixture.stringify_keys
|
52
|
-
|
53
|
-
unknown_columns = fixture.keys - columns.keys
|
54
|
-
if unknown_columns.any?
|
55
|
-
raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.)
|
56
|
-
end
|
57
|
-
|
58
|
-
columns.map do |name, column|
|
59
|
-
if fixture.key?(name)
|
60
|
-
type = lookup_cast_type_from_column(column)
|
61
|
-
with_yaml_fallback(type.serialize(fixture[name]))
|
62
|
-
else
|
63
|
-
default_insert_value(column)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
table = Arel::Table.new(table_name)
|
69
|
-
manager = Arel::InsertManager.new
|
70
|
-
manager.into(table)
|
71
|
-
|
72
|
-
if values_list.size == 1
|
73
|
-
values = values_list.shift
|
74
|
-
new_values = []
|
75
|
-
columns.each_key.with_index { |column, i|
|
76
|
-
unless values[i].equal?(DEFAULT_INSERT_VALUE)
|
77
|
-
new_values << values[i]
|
78
|
-
manager.columns << table[column]
|
79
|
-
end
|
80
|
-
}
|
81
|
-
values_list << new_values
|
82
|
-
else
|
83
|
-
columns.each_key { |column| manager.columns << table[column] }
|
84
|
-
end
|
85
|
-
|
86
|
-
manager.values = manager.create_values_list(values_list)
|
87
|
-
manager.to_sql
|
88
|
-
end
|
89
|
-
|
90
|
-
def build_fixture_statements(fixture_set)
|
91
|
-
fixture_set.map do |table_name, fixtures|
|
92
|
-
next if fixtures.empty?
|
93
|
-
build_fixture_sql(fixtures, table_name)
|
94
|
-
end.compact
|
95
|
-
end
|
96
|
-
|
97
|
-
def with_multi_statements
|
98
|
-
yield
|
99
|
-
end
|
100
17
|
end
|
101
18
|
end
|
102
19
|
end
|
@@ -10,37 +10,44 @@ module ActiveRecord
|
|
10
10
|
"https://github.com/cockroachdb/activerecord-cockroachdb-adapter/issues/new"
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
13
|
+
# "See https://github.com/cockroachdb/cockroach/issues/26443."
|
14
|
+
search_path =
|
15
|
+
case ActiveRecord.dump_schemas
|
16
|
+
when :schema_search_path
|
17
|
+
configuration_hash[:schema_search_path]
|
18
|
+
when :all
|
19
|
+
nil
|
20
|
+
when String
|
21
|
+
ActiveRecord.dump_schemas
|
21
22
|
end
|
22
|
-
end
|
23
23
|
|
24
24
|
conn = ActiveRecord::Base.connection
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
begin
|
26
|
+
old_search_path = conn.schema_search_path
|
27
|
+
conn.schema_search_path = search_path
|
28
|
+
File.open(filename, "w") do |file|
|
29
|
+
%w(SCHEMAS TYPES).each do |object_kind|
|
30
|
+
ActiveRecord::Base.connection.execute("SHOW CREATE ALL #{object_kind}").each_row { file.puts _1 }
|
31
|
+
end
|
29
32
|
|
30
|
-
|
33
|
+
ignore_tables = ActiveRecord::SchemaDumper.ignore_tables.to_set
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
conn.execute("SHOW CREATE ALL TABLES").each_row do |(sql)|
|
36
|
+
if sql.start_with?("CREATE")
|
37
|
+
table_name = sql[/CREATE TABLE (?:.*?\.)?\"?(.*?)[\" ]/, 1]
|
38
|
+
next if ignore_tables.member?(table_name)
|
39
|
+
elsif sql.start_with?("ALTER")
|
40
|
+
table_name = sql[/ALTER TABLE (?:.*?\.)?\"?(.*?)[\" ]/, 1]
|
41
|
+
ref_table_name = sql[/REFERENCES (?:.*?\.)?\"?(.*?)[\" ]/, 1]
|
42
|
+
next if ignore_tables.member?(table_name) || ignore_tables.member?(ref_table_name)
|
43
|
+
end
|
41
44
|
|
42
|
-
|
45
|
+
file.puts sql
|
46
|
+
end
|
47
|
+
file.puts "SET seach_path TO #{conn.schema_search_path};\n\n"
|
43
48
|
end
|
49
|
+
ensure
|
50
|
+
conn.schema_search_path = old_search_path
|
44
51
|
end
|
45
52
|
end
|
46
53
|
|
@@ -11,6 +11,14 @@ module ActiveRecord
|
|
11
11
|
module ConnectionAdapters
|
12
12
|
module CockroachDB
|
13
13
|
module ReferentialIntegrity
|
14
|
+
# CockroachDB will raise a `PG::ForeignKeyViolation` when re-enabling
|
15
|
+
# referential integrity (e.g: adding a foreign key with invalid data
|
16
|
+
# raises).
|
17
|
+
# So foreign keys should always be valid for that matter.
|
18
|
+
def check_all_foreign_keys_valid!
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
14
22
|
def disable_referential_integrity
|
15
23
|
foreign_keys = tables.map { |table| foreign_keys(table) }.flatten
|
16
24
|
|
@@ -31,16 +39,12 @@ module ActiveRecord
|
|
31
39
|
|
32
40
|
begin
|
33
41
|
foreign_keys.each do |foreign_key|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
else
|
41
|
-
raise error
|
42
|
-
end
|
43
|
-
end
|
42
|
+
# Avoid having PG:DuplicateObject error if a test is ran in transaction.
|
43
|
+
# TODO: verify that there is no cache issue related to running this (e.g: fk
|
44
|
+
# still in cache but not in db)
|
45
|
+
next if foreign_key_exists?(foreign_key.from_table, name: foreign_key.options[:name])
|
46
|
+
|
47
|
+
add_foreign_key(foreign_key.from_table, foreign_key.to_table, **foreign_key.options)
|
44
48
|
end
|
45
49
|
ensure
|
46
50
|
ActiveRecord::Base.table_name_prefix = old_prefix
|
@@ -36,16 +36,25 @@ module ActiveRecord
|
|
36
36
|
# Modified version of the postgresql foreign_keys method.
|
37
37
|
# Replaces t2.oid::regclass::text with t2.relname since this is
|
38
38
|
# more efficient in CockroachDB.
|
39
|
+
# Also, CockroachDB does not append the schema name in relname,
|
40
|
+
# so we append it manually.
|
39
41
|
def foreign_keys(table_name)
|
40
42
|
scope = quoted_scope(table_name)
|
41
43
|
fk_info = exec_query(<<~SQL, "SCHEMA")
|
42
|
-
SELECT
|
44
|
+
SELECT CASE
|
45
|
+
WHEN n2.nspname = current_schema()
|
46
|
+
THEN ''
|
47
|
+
ELSE n2.nspname || '.'
|
48
|
+
END || t2.relname AS to_table,
|
49
|
+
a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred,
|
50
|
+
c.conkey, c.confkey, c.conrelid, c.confrelid
|
43
51
|
FROM pg_constraint c
|
44
52
|
JOIN pg_class t1 ON c.conrelid = t1.oid
|
45
53
|
JOIN pg_class t2 ON c.confrelid = t2.oid
|
46
54
|
JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
|
47
55
|
JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
|
48
56
|
JOIN pg_namespace t3 ON c.connamespace = t3.oid
|
57
|
+
JOIN pg_namespace n2 ON t2.relnamespace = n2.oid
|
49
58
|
WHERE c.contype = 'f'
|
50
59
|
AND t1.relname = #{scope[:name]}
|
51
60
|
AND t3.nspname = #{scope[:schema]}
|
@@ -53,17 +62,31 @@ module ActiveRecord
|
|
53
62
|
SQL
|
54
63
|
|
55
64
|
fk_info.map do |row|
|
65
|
+
to_table = PostgreSQL::Utils.unquote_identifier(row["to_table"])
|
66
|
+
conkey = row["conkey"].scan(/\d+/).map(&:to_i)
|
67
|
+
confkey = row["confkey"].scan(/\d+/).map(&:to_i)
|
68
|
+
|
69
|
+
if conkey.size > 1
|
70
|
+
column = column_names_from_column_numbers(row["conrelid"], conkey)
|
71
|
+
primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
|
72
|
+
else
|
73
|
+
column = PostgreSQL::Utils.unquote_identifier(row["column"])
|
74
|
+
primary_key = row["primary_key"]
|
75
|
+
end
|
76
|
+
|
56
77
|
options = {
|
57
|
-
column:
|
78
|
+
column: column,
|
58
79
|
name: row["name"],
|
59
|
-
primary_key:
|
80
|
+
primary_key: primary_key
|
60
81
|
}
|
61
|
-
|
62
82
|
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
|
63
83
|
options[:on_update] = extract_foreign_key_action(row["on_update"])
|
84
|
+
options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
|
85
|
+
|
64
86
|
options[:validate] = row["valid"]
|
87
|
+
to_table = PostgreSQL::Utils.unquote_identifier(row["to_table"])
|
65
88
|
|
66
|
-
ForeignKeyDefinition.new(table_name,
|
89
|
+
ForeignKeyDefinition.new(table_name, to_table, options)
|
67
90
|
end
|
68
91
|
end
|
69
92
|
|
@@ -76,16 +99,20 @@ module ActiveRecord
|
|
76
99
|
|
77
100
|
# override
|
78
101
|
# https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb#L624
|
79
|
-
def new_column_from_field(table_name, field)
|
80
|
-
column_name, type, default, notnull, oid, fmod, collation, comment,
|
102
|
+
def new_column_from_field(table_name, field, _definition)
|
103
|
+
column_name, type, default, notnull, oid, fmod, collation, comment, identity, attgenerated, hidden = field
|
81
104
|
type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
|
82
105
|
default_value = extract_value_from_default(default)
|
83
|
-
default_function = extract_default_function(default_value, default)
|
84
106
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
107
|
+
if attgenerated.present?
|
108
|
+
default_function = default
|
109
|
+
else
|
110
|
+
default_function = extract_default_function(default_value, default)
|
111
|
+
end
|
112
|
+
|
113
|
+
if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
|
114
|
+
serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
|
115
|
+
end
|
89
116
|
|
90
117
|
# {:dimension=>2, :has_m=>false, :has_z=>false, :name=>"latlon", :srid=>0, :type=>"GEOMETRY"}
|
91
118
|
spatial = spatial_column_info(table_name).get(column_name, type_metadata.sql_type)
|
@@ -99,8 +126,9 @@ module ActiveRecord
|
|
99
126
|
collation: collation,
|
100
127
|
comment: comment.presence,
|
101
128
|
serial: serial,
|
129
|
+
identity: identity.presence,
|
102
130
|
spatial: spatial,
|
103
|
-
generated:
|
131
|
+
generated: attgenerated,
|
104
132
|
hidden: hidden
|
105
133
|
)
|
106
134
|
end
|
@@ -1,15 +1,16 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Type
|
3
|
-
|
3
|
+
module CRDBExt
|
4
4
|
# Return :postgresql instead of :cockroachdb for current_adapter_name so
|
5
5
|
# we can continue using the ActiveRecord::Types defined in
|
6
6
|
# PostgreSQLAdapter.
|
7
7
|
def adapter_name_from(model)
|
8
|
-
name =
|
8
|
+
name = super
|
9
9
|
return :postgresql if name == :cockroachdb
|
10
10
|
|
11
11
|
name
|
12
12
|
end
|
13
13
|
end
|
14
|
+
singleton_class.prepend CRDBExt
|
14
15
|
end
|
15
16
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require "rgeo/active_record"
|
2
2
|
|
3
|
+
require_relative "../../arel/nodes/join_source_ext"
|
3
4
|
require "active_record/connection_adapters/postgresql_adapter"
|
4
5
|
require "active_record/connection_adapters/cockroachdb/attribute_methods"
|
5
6
|
require "active_record/connection_adapters/cockroachdb/column_methods"
|
@@ -160,7 +161,7 @@ module ActiveRecord
|
|
160
161
|
end
|
161
162
|
|
162
163
|
def supports_bulk_alter?
|
163
|
-
|
164
|
+
true
|
164
165
|
end
|
165
166
|
|
166
167
|
def supports_json?
|
@@ -181,7 +182,15 @@ module ActiveRecord
|
|
181
182
|
end
|
182
183
|
|
183
184
|
def supports_partial_index?
|
184
|
-
|
185
|
+
true
|
186
|
+
end
|
187
|
+
|
188
|
+
def supports_index_include?
|
189
|
+
false
|
190
|
+
end
|
191
|
+
|
192
|
+
def supports_exclusion_constraints?
|
193
|
+
false
|
185
194
|
end
|
186
195
|
|
187
196
|
def supports_expression_index?
|
@@ -196,7 +205,7 @@ module ActiveRecord
|
|
196
205
|
end
|
197
206
|
|
198
207
|
def supports_comments?
|
199
|
-
|
208
|
+
true
|
200
209
|
end
|
201
210
|
|
202
211
|
def supports_comments_in_create?
|
@@ -208,11 +217,11 @@ module ActiveRecord
|
|
208
217
|
end
|
209
218
|
|
210
219
|
def supports_virtual_columns?
|
211
|
-
|
220
|
+
true
|
212
221
|
end
|
213
222
|
|
214
223
|
def supports_string_to_array_coercion?
|
215
|
-
|
224
|
+
true
|
216
225
|
end
|
217
226
|
|
218
227
|
def supports_partitioned_indexes?
|
@@ -238,62 +247,30 @@ module ActiveRecord
|
|
238
247
|
alias index_name_length max_identifier_length
|
239
248
|
alias table_alias_length max_identifier_length
|
240
249
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
elsif crdb_version_string.include? "v19.1."
|
266
|
-
version_num = 1910
|
267
|
-
elsif crdb_version_string.include? "v19.2."
|
268
|
-
version_num = 1920
|
269
|
-
elsif crdb_version_string.include? "v20.1."
|
270
|
-
version_num = 2010
|
271
|
-
elsif crdb_version_string.include? "v20.2."
|
272
|
-
version_num = 2020
|
273
|
-
elsif crdb_version_string.include? "v21.1."
|
274
|
-
version_num = 2110
|
275
|
-
else
|
276
|
-
version_num = 2120
|
277
|
-
end
|
278
|
-
end
|
279
|
-
@crdb_version = version_num.to_i
|
280
|
-
|
281
|
-
# NOTE: this is normally in configure_connection, but that is run
|
282
|
-
# before crdb_version is determined. Once all supported versions
|
283
|
-
# of CockroachDB support SET intervalstyle it can safely be moved
|
284
|
-
# back.
|
285
|
-
# Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
|
286
|
-
if @crdb_version >= 2120
|
287
|
-
begin
|
288
|
-
execute("SET intervalstyle_enabled = true", "SCHEMA")
|
289
|
-
execute("SET intervalstyle = iso_8601", "SCHEMA")
|
290
|
-
rescue
|
291
|
-
# Ignore any error. This can happen with a cluster that has
|
292
|
-
# not yet finalized the v21.2 upgrade. v21.2 does not have
|
293
|
-
# a way to tell if the upgrade was finalized (see comment above).
|
294
|
-
end
|
295
|
-
end
|
296
|
-
end
|
250
|
+
# NOTE: This commented bit of code allows to have access to crdb version,
|
251
|
+
# which can be useful for feature detection. However, we currently don't
|
252
|
+
# need, hence we avoid the extra queries.
|
253
|
+
#
|
254
|
+
# def initialize(connection, logger, conn_params, config)
|
255
|
+
# super(connection, logger, conn_params, config)
|
256
|
+
|
257
|
+
# # crdb_version is the version of the binary running on the node. We
|
258
|
+
# # really want to use `SHOW CLUSTER SETTING version` to get the cluster
|
259
|
+
# # version, but that is only available to admins. Instead, we can use
|
260
|
+
# # crdb_internal.is_at_least_version, but that's only available in 22.1.
|
261
|
+
# crdb_version_string = query_value("SHOW crdb_version")
|
262
|
+
# if crdb_version_string.include? "v22.1"
|
263
|
+
# version_num = query_value(<<~SQL, "VERSION")
|
264
|
+
# SELECT
|
265
|
+
# CASE
|
266
|
+
# WHEN crdb_internal.is_at_least_version('22.2') THEN 2220
|
267
|
+
# WHEN crdb_internal.is_at_least_version('22.1') THEN 2210
|
268
|
+
# ELSE 2120
|
269
|
+
# END;
|
270
|
+
# SQL
|
271
|
+
# end
|
272
|
+
# @crdb_version = version_num.to_i
|
273
|
+
# end
|
297
274
|
|
298
275
|
def self.database_exists?(config)
|
299
276
|
!!ActiveRecord::Base.cockroachdb_connection(config)
|
@@ -306,12 +283,12 @@ module ActiveRecord
|
|
306
283
|
# (DO $$) that CockroachDB does not support.
|
307
284
|
#
|
308
285
|
# Given a name and an array of values, creates an enum type.
|
309
|
-
def create_enum(name, values)
|
310
|
-
sql_values = values.map { |s|
|
286
|
+
def create_enum(name, values, **options)
|
287
|
+
sql_values = values.map { |s| quote(s) }.join(", ")
|
311
288
|
query = <<~SQL
|
312
|
-
CREATE TYPE IF NOT EXISTS
|
289
|
+
CREATE TYPE IF NOT EXISTS #{quote_table_name(name)} AS ENUM (#{sql_values});
|
313
290
|
SQL
|
314
|
-
|
291
|
+
internal_exec_query(query).tap { reload_type_map }
|
315
292
|
end
|
316
293
|
|
317
294
|
class << self
|
@@ -369,56 +346,6 @@ module ActiveRecord
|
|
369
346
|
|
370
347
|
private
|
371
348
|
|
372
|
-
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
|
373
|
-
# This is called by #connect and should not be called manually.
|
374
|
-
#
|
375
|
-
# NOTE(joey): This was cradled from postgresql_adapter.rb. This
|
376
|
-
# was due to needing to override configuration statements.
|
377
|
-
def configure_connection
|
378
|
-
if @config[:encoding]
|
379
|
-
@connection.set_client_encoding(@config[:encoding])
|
380
|
-
end
|
381
|
-
self.client_min_messages = @config[:min_messages] || "warning"
|
382
|
-
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
|
383
|
-
|
384
|
-
# Use standard-conforming strings so we don't have to do the E'...' dance.
|
385
|
-
set_standard_conforming_strings
|
386
|
-
|
387
|
-
variables = @config.fetch(:variables, {}).stringify_keys
|
388
|
-
|
389
|
-
# If using Active Record's time zone support configure the connection to return
|
390
|
-
# TIMESTAMP WITH ZONE types in UTC.
|
391
|
-
unless variables["timezone"]
|
392
|
-
if ActiveRecord.default_timezone == :utc
|
393
|
-
variables["timezone"] = "UTC"
|
394
|
-
elsif @local_tz
|
395
|
-
variables["timezone"] = @local_tz
|
396
|
-
end
|
397
|
-
end
|
398
|
-
|
399
|
-
# NOTE(joey): This is a workaround as CockroachDB 1.1.x
|
400
|
-
# supports SET TIME ZONE <...> and SET "time zone" = <...> but
|
401
|
-
# not SET timezone = <...>.
|
402
|
-
if variables.key?("timezone")
|
403
|
-
tz = variables.delete("timezone")
|
404
|
-
execute("SET TIME ZONE #{quote(tz)}", "SCHEMA")
|
405
|
-
end
|
406
|
-
|
407
|
-
# SET statements from :variables config hash
|
408
|
-
# https://www.postgresql.org/docs/current/static/sql-set.html
|
409
|
-
variables.map do |k, v|
|
410
|
-
if v == ":default" || v == :default
|
411
|
-
# Sets the value to the global or compile default
|
412
|
-
|
413
|
-
# NOTE(joey): I am not sure if simply commenting this out
|
414
|
-
# is technically correct.
|
415
|
-
# execute("SET #{k} = DEFAULT", "SCHEMA")
|
416
|
-
elsif !v.nil?
|
417
|
-
execute("SET SESSION #{k} = #{quote(v)}", "SCHEMA")
|
418
|
-
end
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
349
|
# Override extract_value_from_default because the upstream definition
|
423
350
|
# doesn't handle the variations in CockroachDB's behavior.
|
424
351
|
def extract_value_from_default(default)
|
@@ -502,7 +429,8 @@ module ActiveRecord
|
|
502
429
|
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
|
503
430
|
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
|
504
431
|
c.collname, NULL AS comment,
|
505
|
-
|
432
|
+
attidentity,
|
433
|
+
attgenerated,
|
506
434
|
NULL as is_hidden
|
507
435
|
FROM pg_attribute a
|
508
436
|
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
@@ -517,26 +445,31 @@ module ActiveRecord
|
|
517
445
|
|
518
446
|
# Use regex comparison because if a type is an array it will
|
519
447
|
# have [] appended to the end of it.
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
448
|
+
re = /\A(?:geometry|geography|interval|numeric)/
|
449
|
+
|
450
|
+
# 0: attname
|
451
|
+
# 1: type
|
452
|
+
# 2: default
|
453
|
+
# 3: attnotnull
|
454
|
+
# 4: atttypid
|
455
|
+
# 5: atttypmod
|
456
|
+
# 6: collname
|
457
|
+
# 7: comment
|
458
|
+
# 8: attidentity
|
459
|
+
# 9: attgenerated
|
460
|
+
# 10: is_hidden
|
528
461
|
fields.map do |field|
|
529
462
|
dtype = field[1]
|
530
463
|
field[1] = crdb_fields[field[0]][2].downcase if re.match(dtype)
|
531
464
|
field[7] = crdb_fields[field[0]][1]&.gsub!(/^\'|\'?$/, '')
|
532
|
-
field[
|
465
|
+
field[10] = true if crdb_fields[field[0]][3]
|
533
466
|
field
|
534
467
|
end
|
535
468
|
fields.delete_if do |field|
|
536
469
|
# Don't include rowid column if it is hidden and the primary key
|
537
470
|
# is not defined (meaning CRDB implicitly created it).
|
538
471
|
if field[0] == CockroachDBAdapter::DEFAULT_PRIMARY_KEY
|
539
|
-
field[
|
472
|
+
field[10] && !primary_key(table_name)
|
540
473
|
else
|
541
474
|
false # Keep this entry.
|
542
475
|
end
|
@@ -549,11 +482,14 @@ module ActiveRecord
|
|
549
482
|
# precision, and scale information in the type.
|
550
483
|
# Ex. geometry -> geometry(point, 4326)
|
551
484
|
def crdb_column_definitions(table_name)
|
485
|
+
table_name = PostgreSQL::Utils.extract_schema_qualified_name(table_name)
|
486
|
+
table = table_name.identifier
|
487
|
+
with_schema = " AND c.table_schema = #{quote(table_name.schema)}" if table_name.schema
|
552
488
|
fields = \
|
553
489
|
query(<<~SQL, "SCHEMA")
|
554
490
|
SELECT c.column_name, c.column_comment, c.crdb_sql_type, c.is_hidden::BOOLEAN
|
555
|
-
|
556
|
-
WHERE c.table_name = #{quote(
|
491
|
+
FROM information_schema.columns c
|
492
|
+
WHERE c.table_name = #{quote(table)}#{with_schema}
|
557
493
|
SQL
|
558
494
|
|
559
495
|
fields.reduce({}) do |a, e|
|
@@ -594,21 +530,10 @@ module ActiveRecord
|
|
594
530
|
def load_additional_types(oids = nil)
|
595
531
|
if @config[:use_follower_reads_for_type_introspection]
|
596
532
|
initializer = OID::TypeMapInitializer.new(type_map)
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
LEFT JOIN pg_range as r ON oid = rngtypid AS OF SYSTEM TIME '-10s'
|
602
|
-
SQL
|
603
|
-
|
604
|
-
if oids
|
605
|
-
query += "WHERE t.oid IN (%s)" % oids.join(", ")
|
606
|
-
else
|
607
|
-
query += initializer.query_conditions_for_initial_load
|
608
|
-
end
|
609
|
-
|
610
|
-
execute_and_clear(query, "SCHEMA", []) do |records|
|
611
|
-
initializer.run(records)
|
533
|
+
load_types_queries_with_aost(initializer, oids) do |query|
|
534
|
+
execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |records|
|
535
|
+
initializer.run(records)
|
536
|
+
end
|
612
537
|
end
|
613
538
|
else
|
614
539
|
super
|
@@ -619,6 +544,21 @@ module ActiveRecord
|
|
619
544
|
super
|
620
545
|
end
|
621
546
|
|
547
|
+
def load_types_queries_with_aost(initializer, oids)
|
548
|
+
query = <<~SQL
|
549
|
+
SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
|
550
|
+
FROM pg_type as t
|
551
|
+
LEFT JOIN pg_range as r ON oid = rngtypid AS OF SYSTEM TIME '-10s'
|
552
|
+
SQL
|
553
|
+
if oids
|
554
|
+
yield query + "WHERE t.oid IN (%s)" % oids.join(", ")
|
555
|
+
else
|
556
|
+
yield query + initializer.query_conditions_for_known_type_names
|
557
|
+
yield query + initializer.query_conditions_for_known_type_types
|
558
|
+
yield query + initializer.query_conditions_for_array_types
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
622
562
|
# override
|
623
563
|
# This method maps data types to their proper decoder.
|
624
564
|
#
|
@@ -649,15 +589,13 @@ module ActiveRecord
|
|
649
589
|
WHERE t.typname IN (%s)
|
650
590
|
SQL
|
651
591
|
|
652
|
-
coders = execute_and_clear(query, "SCHEMA", []) do |result|
|
653
|
-
result
|
654
|
-
.map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
|
655
|
-
.compact
|
592
|
+
coders = execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
|
593
|
+
result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
|
656
594
|
end
|
657
595
|
|
658
596
|
map = PG::TypeMapByOid.new
|
659
597
|
coders.each { |coder| map.add_coder(coder) }
|
660
|
-
@
|
598
|
+
@raw_connection.type_map_for_results = map
|
661
599
|
|
662
600
|
@type_map_for_results = PG::TypeMapByOid.new
|
663
601
|
@type_map_for_results.default_type_map = map
|
@@ -3,6 +3,23 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
class Relation
|
5
5
|
module QueryMethodsExt
|
6
|
+
def aost!(time) # :nodoc:
|
7
|
+
unless time.nil? || time.is_a?(Time)
|
8
|
+
raise ArgumentError, "Unsupported argument type: #{time} (#{time.class})"
|
9
|
+
end
|
10
|
+
|
11
|
+
@aost = time
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
# Set system time for the current query. Using
|
16
|
+
# `.aost(nil)` resets.
|
17
|
+
#
|
18
|
+
# See cockroachlabs.com/docs/stable/as-of-system-time
|
19
|
+
def aost(time)
|
20
|
+
spawn.aost!(time)
|
21
|
+
end
|
22
|
+
|
6
23
|
def from!(...) # :nodoc:
|
7
24
|
@force_index = nil
|
8
25
|
@index_hint = nil
|
@@ -59,8 +76,20 @@ module ActiveRecord
|
|
59
76
|
self
|
60
77
|
end
|
61
78
|
|
79
|
+
# TODO: reset or no reset?
|
80
|
+
|
81
|
+
def show_create
|
82
|
+
connection.execute("show create table #{connection.quote_table_name self.table_name}").first["create_statement"]
|
83
|
+
end
|
84
|
+
|
62
85
|
private
|
63
86
|
|
87
|
+
def build_arel(...)
|
88
|
+
arel = super
|
89
|
+
arel.aost(@aost) if @aost.present?
|
90
|
+
arel
|
91
|
+
end
|
92
|
+
|
64
93
|
def from_clause_is_a_table_name?
|
65
94
|
# if empty, we are just dealing with the current table.
|
66
95
|
return true if from_clause.empty?
|
@@ -94,5 +123,5 @@ module ActiveRecord
|
|
94
123
|
# as ancestor. That is how active_record is doing is as well.
|
95
124
|
#
|
96
125
|
# @see https://github.com/rails/rails/blob/914130a9f/activerecord/lib/active_record/querying.rb#L23
|
97
|
-
Querying.delegate(:force_index, :index_hint, to: :all)
|
126
|
+
Querying.delegate(:force_index, :index_hint, :aost, :show_create, to: :all)
|
98
127
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Arel
|
2
|
+
module Nodes
|
3
|
+
module JoinSourceExt
|
4
|
+
def initialize(...)
|
5
|
+
super
|
6
|
+
@aost = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def hash
|
10
|
+
[*super, aost].hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def eql?(other)
|
14
|
+
super && aost == other.aost
|
15
|
+
end
|
16
|
+
alias_method :==, :eql?
|
17
|
+
end
|
18
|
+
JoinSource.attr_accessor :aost
|
19
|
+
JoinSource.prepend JoinSourceExt
|
20
|
+
end
|
21
|
+
module SelectManagerExt
|
22
|
+
def aost(time)
|
23
|
+
@ctx.source.aost = time
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
SelectManager.prepend SelectManagerExt
|
28
|
+
end
|
data/lib/version.rb
CHANGED
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: 7.0
|
4
|
+
version: 7.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:
|
11
|
+
date: 2024-01-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 7.0
|
19
|
+
version: 7.1.0
|
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: 7.0
|
26
|
+
version: 7.1.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: pg
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -74,6 +74,7 @@ executables: []
|
|
74
74
|
extensions: []
|
75
75
|
extra_rdoc_files: []
|
76
76
|
files:
|
77
|
+
- ".editorconfig"
|
77
78
|
- ".github/workflows/ci.yml"
|
78
79
|
- ".github/workflows/docker.yml"
|
79
80
|
- ".gitignore"
|
@@ -87,6 +88,8 @@ files:
|
|
87
88
|
- Rakefile
|
88
89
|
- activerecord-cockroachdb-adapter.gemspec
|
89
90
|
- bin/console
|
91
|
+
- bin/console_schemas/default.rb
|
92
|
+
- bin/console_schemas/schemas.rb
|
90
93
|
- bin/setup
|
91
94
|
- bin/start-cockroachdb
|
92
95
|
- build/Dockerfile
|
@@ -118,6 +121,7 @@ files:
|
|
118
121
|
- lib/active_record/migration/cockroachdb/compatibility.rb
|
119
122
|
- lib/active_record/relation/query_methods_ext.rb
|
120
123
|
- lib/activerecord-cockroachdb-adapter.rb
|
124
|
+
- lib/arel/nodes/join_source_ext.rb
|
121
125
|
- lib/version.rb
|
122
126
|
homepage: https://github.com/cockroachdb/activerecord-cockroachdb-adapter
|
123
127
|
licenses:
|
@@ -139,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
143
|
- !ruby/object:Gem::Version
|
140
144
|
version: '0'
|
141
145
|
requirements: []
|
142
|
-
rubygems_version: 3.4.
|
146
|
+
rubygems_version: 3.4.10
|
143
147
|
signing_key:
|
144
148
|
specification_version: 4
|
145
149
|
summary: CockroachDB adapter for ActiveRecord.
|