activerecord-cockroachdb-adapter 0.2.3 → 6.0.0beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.gitmodules +0 -3
- data/CONTRIBUTING.md +25 -53
- data/Gemfile +58 -6
- data/README.md +2 -2
- data/Rakefile +17 -5
- data/activerecord-cockroachdb-adapter.gemspec +3 -7
- data/build/Dockerfile +1 -1
- data/build/teamcity-test.sh +17 -37
- data/docker.sh +1 -1
- data/lib/active_record/connection_adapters/cockroachdb/attribute_methods.rb +28 -0
- data/lib/active_record/connection_adapters/cockroachdb/column.rb +15 -0
- data/lib/active_record/connection_adapters/cockroachdb/database_statements.rb +102 -0
- data/lib/active_record/connection_adapters/cockroachdb/quoting.rb +28 -0
- data/lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb +23 -38
- data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +64 -30
- data/lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb +14 -16
- data/lib/active_record/connection_adapters/cockroachdb/type.rb +14 -0
- data/lib/active_record/connection_adapters/cockroachdb_adapter.rb +110 -128
- metadata +15 -59
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a580dde40d074b1982c6642310513d79bbcb710032a476a64bb9fcb99bec72bb
|
4
|
+
data.tar.gz: 91b11b334235484e298704ae514d435a61451a02f3f759881141fd6354549ee4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77b98dff7d2069ba0cdcd78bccf5aedf9ac31192c0813d7893a5a6f1084537d9d0b03039be58fe83a5a0e0cd58d5d42ba567e43beee62507d8212242722fdb13
|
7
|
+
data.tar.gz: f1bf6e2fa1925d94133a98dcd03690a24da2631134cc6a5eb6dd860253392f398db88572942f409f13dec4d3d7aeb34f18317a5b8cd4b30c70e4d75d13599b47
|
data/.gitignore
CHANGED
data/.gitmodules
CHANGED
data/CONTRIBUTING.md
CHANGED
@@ -11,15 +11,19 @@ CockroachDBAdapter to initialize ActiveRecord for their projects.
|
|
11
11
|
This adapter extends the PostgreSQL ActiveRecord adapter in order to
|
12
12
|
override and monkey-patch functionality.
|
13
13
|
|
14
|
-
The other repository is a fork of [Rails]. The tests have been modified
|
15
|
-
for the purposes of testing our CockroachDB adapter.
|
16
|
-
|
17
14
|
[activerecord-cockroachdb-adapter]: https://github.com/cockroachdb/activerecord-cockroachdb-adapter/
|
18
|
-
[Rails]: https://github.com/lego/ruby-on-rails
|
19
|
-
|
20
15
|
|
21
16
|
## Setup and running tests
|
22
17
|
|
18
|
+
In CockroachDB, create two databases to be used by the ActiveRecord test suite:
|
19
|
+
activerecord_unittest and activerecord_unittest2.
|
20
|
+
|
21
|
+
```sql
|
22
|
+
CREATE DATABASE activerecord_unittest;
|
23
|
+
|
24
|
+
CREATE DATABASE activerecord_unittest2;
|
25
|
+
```
|
26
|
+
|
23
27
|
It is best to have a Ruby environment manager installed, such as
|
24
28
|
[rvm](https://rvm.io/), as Rails has varying Ruby version requirements.
|
25
29
|
If you are using rvm, you then install and use the required Ruby
|
@@ -37,65 +41,42 @@ rvm use 2.2.5
|
|
37
41
|
```
|
38
42
|
|
39
43
|
Using [bundler](http://bundler.io/), install the dependancies of Rails.
|
40
|
-
Additionally, make sure the Rails git submodule is loaded.
|
41
44
|
|
42
45
|
```bash
|
43
|
-
|
44
|
-
git submodule update
|
45
|
-
# Install rails dependancies.
|
46
|
-
(cd rails && bundle install)
|
46
|
+
bundle install
|
47
47
|
```
|
48
48
|
|
49
|
-
Then, to run the test with an active CockroachDB instance:
|
49
|
+
Then, to run the full test suite with an active CockroachDB instance:
|
50
50
|
|
51
51
|
```bash
|
52
|
-
|
53
|
-
(cd rails/activerecord && BUNDLE_GEMFILE=../Gemfile bundle exec rake db:cockroachdb:rebuild)
|
54
|
-
(cd rails/activerecord && BUNDLE_GEMFILE=../Gemfile bundle exec rake test:cockroachdb)
|
52
|
+
bundle exec rake test
|
55
53
|
```
|
56
54
|
|
57
|
-
|
55
|
+
To run specific ActiveRecord tests, set environemnt variable `TEST_FILES_AR`. For example, to run ActiveRecord tests `test/cases/associations_test.rb` and `test/cases/ar_schema_test.rb.rb`
|
58
56
|
|
59
57
|
```bash
|
60
|
-
|
58
|
+
TEST_FILES_AR="test/cases/associations_test.rb,test/cases/ar_schema_test.rb" bundle exec rake test
|
61
59
|
```
|
62
60
|
|
63
|
-
|
64
|
-
This configuration specifies:
|
65
|
-
|
66
|
-
- CockroachDB port and host the test suite uses.
|
67
|
-
- Database names used for the different test connections. (ActiveRecord
|
68
|
-
uses two separate connections for some tests.)
|
61
|
+
To run specific CockroachDB Adapter tests, set environemnt variable `TEST_FILES`. For example, to run CockroachDB Adpater tests `test/cases/adapter_test.rb` and `test/cases/associations/left_outer_join_association_test.rb`
|
69
62
|
|
70
|
-
```
|
71
|
-
|
63
|
+
```bash
|
64
|
+
TEST_FILES="test/cases/adapter_test.rb,test/cases/associations/left_outer_join_association_test.rb" bundle exec rake test
|
72
65
|
```
|
73
66
|
|
74
|
-
|
75
|
-
re-creates all of the databases needed.
|
67
|
+
To run a specific test case, use minitest's `-n` option to run tests that match a given pattern. All minitest options are set via the `TESTOPTS` environemnt variable. For example, to run `test_indexes` from CockroachDB's `test/cases/adapter_test.rb` file
|
76
68
|
|
77
|
-
|
78
|
-
|
79
|
-
tasks) such as executing tests.
|
80
|
-
- `BUNDLE_GEMFILE=../Gemfile` tells `bundle` to use the dependancies for
|
81
|
-
Rails that were previously installed.
|
82
|
-
- `bundle exec rake` uses `bundle` to execute the Ruby package `rake`.
|
83
|
-
- `rake db:cockroachdb:rebuild` runs the specified Rake task. All tasks
|
84
|
-
can be found in `rails/activerecord/Rakefile`.
|
85
|
-
|
86
|
-
|
87
|
-
```
|
88
|
-
(cd rails/activerecord && BUNDLE_GEMFILE=../Gemfile bundle exec rake test:cockroachdb)
|
69
|
+
```bash
|
70
|
+
TEST_FILES="test/cases/adapter_test.rb" TESTOPTS=`-n=/test_indexes/` bundle exec rake test
|
89
71
|
```
|
90
72
|
|
91
|
-
|
73
|
+
By default, tests will be run from the bundled version of Rails. To run against a local copy, set environemnt variable `RAILS_SOURCE`. Running against a local copy of Rails can be helpful when try to debug issues.
|
92
74
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
`TESTFILES=test/cases/attribute_methods.rb` to the command. Globs are
|
97
|
-
used. Multiple individual files cannot be specified.
|
75
|
+
```bash
|
76
|
+
RAILS_SOURCE="path/to/local_copy" bundle exec rake test
|
77
|
+
```
|
98
78
|
|
79
|
+
`test/config.yml` assumes CockroachDB will be running at localhost:26257 with a root user. Make changes to `test/config.yml` as needed.
|
99
80
|
|
100
81
|
# Improvements
|
101
82
|
|
@@ -143,15 +124,6 @@ A possible way to approach this would be to add a shim to cause any
|
|
143
124
|
tests that use it to fail, and grep the tests that pass and then skip
|
144
125
|
them.
|
145
126
|
|
146
|
-
# Cleanup
|
147
|
-
|
148
|
-
One of the earlier commits to the Rails repo did a big grep of
|
149
|
-
`PostgreSQLAdapter` -> `CockroachDBAdapter`. In order to better support
|
150
|
-
changes upstream, this modification should be changed to instead only
|
151
|
-
add `CockroachDBAdapter` alongside any `PostgreSQLAdapter`. The later
|
152
|
-
test cleanup commit will conflict on any further changes (like adding
|
153
|
-
back PostgreSQL, or removing CockroachDB for PostgreSQL).
|
154
|
-
|
155
127
|
## Publishing to Rubygems
|
156
128
|
|
157
129
|
TODO: Expand on this. Jordan is likely the only person with publishing
|
data/Gemfile
CHANGED
@@ -1,11 +1,63 @@
|
|
1
|
+
require 'openssl'
|
1
2
|
source 'https://rubygems.org'
|
3
|
+
gemspec
|
2
4
|
|
3
|
-
|
5
|
+
if ENV['RAILS_SOURCE']
|
6
|
+
gemspec path: ENV['RAILS_SOURCE']
|
7
|
+
else
|
8
|
+
def get_version_from_gemspec
|
9
|
+
gemspec = eval(File.read('activerecord-cockroachdb-adapter.gemspec'))
|
4
10
|
|
5
|
-
|
11
|
+
gem_version = gemspec.dependencies.
|
12
|
+
find { |dep| dep.name == 'activerecord' }.
|
13
|
+
requirement.
|
14
|
+
requirements.
|
15
|
+
first.
|
16
|
+
last
|
6
17
|
|
7
|
-
|
8
|
-
|
18
|
+
major, minor, tiny, pre = gem_version.segments
|
19
|
+
|
20
|
+
if pre
|
21
|
+
gem_version.to_s
|
22
|
+
else
|
23
|
+
find_latest_matching_version(major, minor)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_latest_matching_version(gemspec_major, gemspec_minor)
|
28
|
+
all_activerecord_versions.
|
29
|
+
reject { |version| version["prerelease"] }.
|
30
|
+
map { |version| version["number"].split(".").map(&:to_i) }.
|
31
|
+
find { |major, minor|
|
32
|
+
major == gemspec_major && (minor == gemspec_minor || gemspec_minor.nil?)
|
33
|
+
}.join(".")
|
34
|
+
end
|
35
|
+
|
36
|
+
def all_activerecord_versions
|
37
|
+
require 'net/http'
|
38
|
+
require 'yaml'
|
39
|
+
|
40
|
+
uri = URI.parse "https://rubygems.org/api/v1/versions/activerecord.yaml"
|
41
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
42
|
+
http.use_ssl = true
|
43
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
44
|
+
|
45
|
+
YAML.load(
|
46
|
+
http.request(Net::HTTP::Get.new(uri.request_uri)).body
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get Rails from source beacause the gem doesn't include tests
|
51
|
+
version = ENV['RAILS_VERSION'] || get_version_from_gemspec
|
52
|
+
gem 'rails', git: "https://github.com/rails/rails.git", tag: "v#{version}"
|
53
|
+
end
|
54
|
+
|
55
|
+
group :development do
|
56
|
+
gem "byebug"
|
57
|
+
gem "minitest-excludes"
|
9
58
|
|
10
|
-
#
|
11
|
-
gem "
|
59
|
+
# Gems used by the ActiveRecord test suite
|
60
|
+
gem "bcrypt"
|
61
|
+
gem "mocha"
|
62
|
+
gem "sqlite3"
|
63
|
+
end
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ CockroachDB adapter for ActiveRecord 4 and 5. This is a lightweight extension of
|
|
7
7
|
Add this line to your project's Gemfile:
|
8
8
|
|
9
9
|
```ruby
|
10
|
-
gem 'activerecord-cockroachdb-adapter', '~>
|
10
|
+
gem 'activerecord-cockroachdb-adapter', '~> 5.2.0'
|
11
11
|
```
|
12
12
|
|
13
13
|
If you're using Rails 4.x, use the `0.1.x` versions of this gem.
|
@@ -26,4 +26,4 @@ development:
|
|
26
26
|
## Modifying the adapter?
|
27
27
|
|
28
28
|
See [CONTRIBUTING.md](/CONTRIBUTING.md) for more details on setting up
|
29
|
-
the environment and making modifications.
|
29
|
+
the environment and making modifications.
|
data/Rakefile
CHANGED
@@ -1,10 +1,22 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rake/testtask"
|
3
|
+
require_relative 'test/support/paths_cockroachdb'
|
4
|
+
require_relative 'test/support/rake_helpers'
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
6
|
+
task test: ["test:cockroachdb"]
|
7
|
+
task default: [:test]
|
8
|
+
|
9
|
+
namespace :test do
|
10
|
+
Rake::TestTask.new("cockroachdb") do |t|
|
11
|
+
t.libs = ARTest::CockroachDB.test_load_paths
|
12
|
+
t.test_files = test_files
|
13
|
+
t.warning = !!ENV["WARNING"]
|
14
|
+
t.verbose = false
|
15
|
+
end
|
16
|
+
|
17
|
+
task "cockroachdb:env" do
|
18
|
+
ENV["ARCONN"] = "cockroachdb"
|
19
|
+
end
|
8
20
|
end
|
9
21
|
|
10
|
-
task :
|
22
|
+
task 'test:cockroachdb' => 'test:cockroachdb:env'
|
@@ -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 = "0.
|
7
|
+
spec.version = "6.0.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", "~>
|
17
|
-
spec.add_dependency "pg", ">= 0.20"
|
16
|
+
spec.add_dependency "activerecord", "~> 6.0.3"
|
17
|
+
spec.add_dependency "pg", ">= 0.20"
|
18
18
|
|
19
19
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
20
20
|
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
@@ -31,8 +31,4 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.bindir = "exe"
|
32
32
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
33
33
|
spec.require_paths = ["lib"]
|
34
|
-
|
35
|
-
spec.add_development_dependency "bundler", "~> 1.14"
|
36
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
37
|
-
spec.add_development_dependency "minitest", "~> 5.0"
|
38
34
|
end
|
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 cockroachdb/
|
4
|
+
FROM cockroachdb/example-orms-builder:20200413-1918
|
5
5
|
|
6
6
|
# Native dependencies for libxml-ruby and sqlite3.
|
7
7
|
RUN apt-get update -y && apt-get install -y \
|
data/build/teamcity-test.sh
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
#!/usr/bin/env bash
|
2
2
|
|
3
|
-
set -
|
3
|
+
set -euox pipefail
|
4
4
|
|
5
|
-
# Download CockroachDB
|
6
|
-
|
7
|
-
VERSION=v2.0-alpha.20171218
|
5
|
+
# Download CockroachDB
|
6
|
+
VERSION=v20.2.1
|
8
7
|
wget -qO- https://binaries.cockroachdb.com/cockroach-$VERSION.linux-amd64.tgz | tar xvz
|
9
8
|
readonly COCKROACH=./cockroach-$VERSION.linux-amd64/cockroach
|
10
9
|
|
11
10
|
# Make sure cockroach can be found on the path. This is required for the
|
12
11
|
# ActiveRecord Rakefile that rebuilds the test database.
|
13
|
-
export PATH
|
12
|
+
export PATH=./cockroach-$VERSION.linux-amd64/:$PATH
|
14
13
|
readonly urlfile=cockroach-url
|
15
14
|
|
16
15
|
run_cockroach() {
|
@@ -22,7 +21,7 @@ run_cockroach() {
|
|
22
21
|
cockroach quit --insecure || true
|
23
22
|
rm -rf cockroach-data
|
24
23
|
# Start CockroachDB.
|
25
|
-
cockroach start --insecure --host=localhost --listening-url-file="$urlfile" >/dev/null 2>&1 &
|
24
|
+
cockroach start-single-node --max-sql-memory=25% --cache=25% --insecure --host=localhost --listening-url-file="$urlfile" >/dev/null 2>&1 &
|
26
25
|
# Ensure CockroachDB is stopped on script exit.
|
27
26
|
trap "echo 'Exit routine: Killing CockroachDB.' && kill -9 $! &> /dev/null" EXIT
|
28
27
|
# Wait until CockroachDB has started.
|
@@ -32,45 +31,26 @@ run_cockroach() {
|
|
32
31
|
echo "server not yet available; sleeping for $backoff seconds"
|
33
32
|
sleep $backoff
|
34
33
|
done
|
34
|
+
cockroach sql --insecure -e 'CREATE DATABASE activerecord_unittest;'
|
35
|
+
cockroach sql --insecure -e 'CREATE DATABASE activerecord_unittest2;'
|
36
|
+
cockroach sql --insecure -e 'SET CLUSTER SETTING sql.stats.automatic_collection.enabled = false;'
|
37
|
+
cockroach sql --insecure -e 'SET CLUSTER SETTING sql.stats.histogram_collection.enabled = false;'
|
38
|
+
cockroach sql --insecure -e "SET CLUSTER SETTING jobs.retention_time = '180s';"
|
35
39
|
}
|
36
40
|
|
37
|
-
# Target the Rails dependency file.
|
38
|
-
export BUNDLE_GEMFILE=$(pwd)/rails/Gemfile
|
39
|
-
|
40
41
|
# Install ruby dependencies.
|
42
|
+
gem install bundler:2.1.4
|
41
43
|
bundle install
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
# 'Install' our adapter. This involves symlinking it inside of
|
46
|
-
# ActiveRecord. Normally the adapter will transitively install
|
47
|
-
# ActiveRecord, but we need to execute tests from inside the Rails
|
48
|
-
# context so we cannot rely on that. We also need previous links to make
|
49
|
-
# tests idempotent.
|
50
|
-
rm -f rails/activerecord/lib/active_record/connection_adapters/cockroachdb_adapter.rb
|
51
|
-
ln -s $(pwd)/lib/active_record/connection_adapters/cockroachdb_adapter.rb rails/activerecord/lib/active_record/connection_adapters/cockroachdb_adapter.rb
|
52
|
-
rm -rf rails/activerecord/lib/active_record/connection_adapters/cockroachdb
|
53
|
-
ln -s $(pwd)/lib/active_record/connection_adapters/cockroachdb rails/activerecord/lib/active_record/connection_adapters/cockroachdb
|
45
|
+
run_cockroach
|
54
46
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
for TESTFILE in ${TESTS}; do
|
59
|
-
# Start CockroachDB
|
60
|
-
run_cockroach
|
61
|
-
# Run the tests.
|
62
|
-
echo "Rebuilding database"
|
63
|
-
(cd rails/activerecord && bundle exec rake db:cockroachdb:rebuild)
|
64
|
-
echo "Running test: $TESTFILE"
|
65
|
-
# Run the test. Continue testing even if this file fails.
|
66
|
-
if ! (cd rails/activerecord && bundle exec rake test:cockroachdb TESTFILES=$TESTFILE); then
|
67
|
-
echo "Test FAILED: $TESTFILE"
|
47
|
+
if ! (RUBYOPT="-W0" TESTOPTS="-v" bundle exec rake test); then
|
48
|
+
echo "Tests failed"
|
68
49
|
HAS_FAILED=1
|
69
|
-
|
70
|
-
echo "
|
50
|
+
else
|
51
|
+
echo "Tests passed"
|
71
52
|
HAS_FAILED=0
|
72
|
-
|
73
|
-
done
|
53
|
+
fi
|
74
54
|
|
75
55
|
# Attempt a clean shutdown for good measure. We'll force-kill in the
|
76
56
|
# exit trap if this script fails.
|
data/docker.sh
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module CockroachDB
|
3
|
+
module AttributeMethodsMonkeyPatch
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
# Filter out rowid so it doesn't get inserted by ActiveRecord. rowid is a
|
8
|
+
# column added by CockroachDB for tables that don't define primary keys.
|
9
|
+
# CockroachDB will automatically insert rowid values. See
|
10
|
+
# https://www.cockroachlabs.com/docs/v19.2/create-table.html#create-a-table.
|
11
|
+
def attributes_for_create(attribute_names)
|
12
|
+
super.reject { |name| name == ConnectionAdapters::CockroachDBAdapter::DEFAULT_PRIMARY_KEY }
|
13
|
+
end
|
14
|
+
|
15
|
+
# Filter out rowid so it doesn't get updated by ActiveRecord. rowid is a
|
16
|
+
# column added by CockroachDB for tables that don't define primary keys.
|
17
|
+
# CockroachDB will automatically insert rowid values. See
|
18
|
+
# https://www.cockroachlabs.com/docs/v19.2/create-table.html#create-a-table.
|
19
|
+
def attributes_for_update(attribute_names)
|
20
|
+
super.reject { |name| name == ConnectionAdapters::CockroachDBAdapter::DEFAULT_PRIMARY_KEY }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Base
|
26
|
+
prepend CockroachDB::AttributeMethodsMonkeyPatch
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module CockroachDB
|
4
|
+
module PostgreSQLColumnMonkeyPatch
|
5
|
+
def serial?
|
6
|
+
default_function == "unique_rowid()"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class PostgreSQLColumn
|
12
|
+
prepend CockroachDB::PostgreSQLColumnMonkeyPatch
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module CockroachDB
|
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
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Overridden to avoid using transactions for schema creation.
|
20
|
+
def insert_fixtures_set(fixture_set, tables_to_delete = [])
|
21
|
+
fixture_inserts = build_fixture_statements(fixture_set)
|
22
|
+
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
|
23
|
+
statements = table_deletes + fixture_inserts
|
24
|
+
|
25
|
+
with_multi_statements do
|
26
|
+
disable_referential_integrity do
|
27
|
+
execute_batch(statements, "Fixtures Load")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def execute_batch(statements, name = nil)
|
34
|
+
statements.each do |statement|
|
35
|
+
execute(statement, name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
|
40
|
+
private_constant :DEFAULT_INSERT_VALUE
|
41
|
+
|
42
|
+
def default_insert_value(column)
|
43
|
+
DEFAULT_INSERT_VALUE
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_fixture_sql(fixtures, table_name)
|
47
|
+
columns = schema_cache.columns_hash(table_name)
|
48
|
+
|
49
|
+
values_list = fixtures.map do |fixture|
|
50
|
+
fixture = fixture.stringify_keys
|
51
|
+
|
52
|
+
unknown_columns = fixture.keys - columns.keys
|
53
|
+
if unknown_columns.any?
|
54
|
+
raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.)
|
55
|
+
end
|
56
|
+
|
57
|
+
columns.map do |name, column|
|
58
|
+
if fixture.key?(name)
|
59
|
+
type = lookup_cast_type_from_column(column)
|
60
|
+
with_yaml_fallback(type.serialize(fixture[name]))
|
61
|
+
else
|
62
|
+
default_insert_value(column)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
table = Arel::Table.new(table_name)
|
68
|
+
manager = Arel::InsertManager.new
|
69
|
+
manager.into(table)
|
70
|
+
|
71
|
+
if values_list.size == 1
|
72
|
+
values = values_list.shift
|
73
|
+
new_values = []
|
74
|
+
columns.each_key.with_index { |column, i|
|
75
|
+
unless values[i].equal?(DEFAULT_INSERT_VALUE)
|
76
|
+
new_values << values[i]
|
77
|
+
manager.columns << table[column]
|
78
|
+
end
|
79
|
+
}
|
80
|
+
values_list << new_values
|
81
|
+
else
|
82
|
+
columns.each_key { |column| manager.columns << table[column] }
|
83
|
+
end
|
84
|
+
|
85
|
+
manager.values = manager.create_values_list(values_list)
|
86
|
+
manager.to_sql
|
87
|
+
end
|
88
|
+
|
89
|
+
def build_fixture_statements(fixture_set)
|
90
|
+
fixture_set.map do |table_name, fixtures|
|
91
|
+
next if fixtures.empty?
|
92
|
+
build_fixture_sql(fixtures, table_name)
|
93
|
+
end.compact
|
94
|
+
end
|
95
|
+
|
96
|
+
def with_multi_statements
|
97
|
+
yield
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module CockroachDB
|
4
|
+
module Quoting
|
5
|
+
private
|
6
|
+
|
7
|
+
# CockroachDB does not allow inserting integer values into string
|
8
|
+
# columns, but ActiveRecord expects this to work. CockroachDB will
|
9
|
+
# however allow inserting string values into integer columns. It will
|
10
|
+
# try to parse string values and convert them to integers so they can be
|
11
|
+
# inserted in integer columns.
|
12
|
+
#
|
13
|
+
# We take advantage of this behavior here by forcing numeric values to
|
14
|
+
# always be strings. Then, we won't have to make any additional changes
|
15
|
+
# to ActiveRecord to support inserting integer values into string
|
16
|
+
# columns.
|
17
|
+
def _quote(value)
|
18
|
+
case value
|
19
|
+
when Numeric
|
20
|
+
"'#{quote_string(value.to_s)}'"
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,51 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
2
|
+
|
3
|
+
# The PostgresSQL Adapter's ReferentialIntegrity module can disable and
|
4
|
+
# re-enable foreign key constraints by disabling all table triggers. Since
|
5
|
+
# triggers are not available in CockroachDB, we have to remove foreign keys and
|
6
|
+
# re-add them via the ActiveRecord API.
|
7
|
+
#
|
8
|
+
# This module is commonly used to load test fixture data without having to worry
|
9
|
+
# about the order in which that data is loaded.
|
4
10
|
module ActiveRecord
|
5
11
|
module ConnectionAdapters
|
6
12
|
module CockroachDB
|
7
|
-
module ReferentialIntegrity
|
8
|
-
def disable_referential_integrity
|
9
|
-
|
10
|
-
fkeys = nil
|
13
|
+
module ReferentialIntegrity
|
14
|
+
def disable_referential_integrity
|
15
|
+
foreign_keys = tables.map { |table| foreign_keys(table) }.flatten
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
tables.each do |table_name|
|
15
|
-
fkeys = foreign_keys(table_name)
|
16
|
-
fkeys.each do |fkey|
|
17
|
-
remove_foreign_key table_name, name: fkey.options[:name]
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
rescue ActiveRecord::ActiveRecordError => e
|
22
|
-
original_exception = e
|
17
|
+
foreign_keys.each do |foreign_key|
|
18
|
+
remove_foreign_key(foreign_key.from_table, name: foreign_key.options[:name])
|
23
19
|
end
|
24
20
|
|
25
|
-
|
26
|
-
yield
|
27
|
-
rescue ActiveRecord::InvalidForeignKey => e
|
28
|
-
warn <<-WARNING
|
29
|
-
WARNING: Rails was not able to disable referential integrity.
|
30
|
-
|
31
|
-
Please go to https://github.com/cockroachdb/activerecord-cockroachdb-adapter
|
32
|
-
and report this issue.
|
33
|
-
|
34
|
-
cause: #{original_exception.try(:message)}
|
35
|
-
|
36
|
-
WARNING
|
37
|
-
raise e
|
38
|
-
end
|
21
|
+
yield
|
39
22
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
23
|
+
foreign_keys.each do |foreign_key|
|
24
|
+
begin
|
25
|
+
add_foreign_key(foreign_key.from_table, foreign_key.to_table, foreign_key.options)
|
26
|
+
rescue ActiveRecord::StatementInvalid => error
|
27
|
+
if error.cause.class == PG::DuplicateObject
|
28
|
+
# This error is safe to ignore because the yielded caller
|
29
|
+
# already re-added the foreign key constraint.
|
30
|
+
else
|
31
|
+
raise error
|
46
32
|
end
|
47
33
|
end
|
48
|
-
rescue ActiveRecord::ActiveRecordError
|
49
34
|
end
|
50
35
|
end
|
51
36
|
end
|
@@ -1,17 +1,70 @@
|
|
1
|
-
require 'active_record/connection_adapters/postgresql/schema_statements'
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
module ConnectionAdapters
|
5
3
|
module CockroachDB
|
6
4
|
module SchemaStatements
|
7
5
|
include ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
|
7
|
+
def add_index(table_name, column_name, options = {})
|
8
|
+
super
|
9
|
+
rescue ActiveRecord::StatementInvalid => error
|
10
|
+
if debugging? && error.cause.class == PG::FeatureNotSupported
|
11
|
+
warn "#{error}\n\nThis error will be ignored and the index will not be created.\n\n"
|
12
|
+
else
|
13
|
+
raise error
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# ActiveRecord allows for tables to exist without primary keys.
|
18
|
+
# Databases like PostgreSQL support this behavior, but CockroachDB does
|
19
|
+
# not. If a table is created without a primary key, CockroachDB will add
|
20
|
+
# a rowid column to serve as its primary key. This breaks a lot of
|
21
|
+
# ActiveRecord's assumptions so we'll treat tables with rowid primary
|
22
|
+
# keys as if they didn't have primary keys at all.
|
23
|
+
# https://www.cockroachlabs.com/docs/v19.2/create-table.html#create-a-table
|
24
|
+
# https://api.rubyonrails.org/v5.2.4/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-create_table
|
25
|
+
def primary_key(table_name)
|
26
|
+
pk = super
|
27
|
+
|
28
|
+
if pk == CockroachDBAdapter::DEFAULT_PRIMARY_KEY
|
29
|
+
nil
|
30
|
+
else
|
31
|
+
pk
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# CockroachDB uses unique_rowid() for primary keys, not sequences. It's
|
36
|
+
# possible to force a table to use sequences, but since it's not the
|
37
|
+
# default behavior we'll always return nil for default_sequence_name.
|
38
|
+
def default_sequence_name(table_name, pk = "id")
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# CockroachDB will use INT8 if the SQL type is INTEGER, so we make it use
|
43
|
+
# INT4 explicitly when needed.
|
44
|
+
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
|
45
|
+
sql = \
|
46
|
+
case type.to_s
|
47
|
+
when "integer"
|
48
|
+
case limit
|
49
|
+
when nil; "int"
|
50
|
+
when 1, 2; "int2"
|
51
|
+
when 3, 4; "int4"
|
52
|
+
when 5..8; "int8"
|
53
|
+
else super
|
54
|
+
end
|
55
|
+
else
|
56
|
+
super
|
57
|
+
end
|
58
|
+
# The call to super might have appeneded [] already.
|
59
|
+
if array && type != :primary_key && !sql.end_with?("[]")
|
60
|
+
sql = "#{sql}[]"
|
61
|
+
end
|
62
|
+
sql
|
63
|
+
end
|
64
|
+
|
65
|
+
# This overrides the method from PostegreSQL adapter
|
13
66
|
# Resets the sequence of a table's primary key to the maximum value.
|
14
|
-
def reset_pk_sequence!(table, pk = nil, sequence = nil)
|
67
|
+
def reset_pk_sequence!(table, pk = nil, sequence = nil)
|
15
68
|
unless pk && sequence
|
16
69
|
default_pk, default_sequence = pk_and_sequence_for(table)
|
17
70
|
|
@@ -27,29 +80,10 @@ module ActiveRecord
|
|
27
80
|
quoted_sequence = quote_table_name(sequence)
|
28
81
|
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
|
29
82
|
if max_pk.nil?
|
30
|
-
|
31
|
-
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
32
|
-
else
|
33
|
-
minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA")
|
34
|
-
end
|
83
|
+
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
35
84
|
end
|
36
|
-
|
37
|
-
|
38
|
-
#
|
39
|
-
# SELECT setval(..., max_pk, false)
|
40
|
-
#
|
41
|
-
# with
|
42
|
-
#
|
43
|
-
# SELECT setval(..., max_pk-1)
|
44
|
-
#
|
45
|
-
# These two statements are semantically equivilant, but
|
46
|
-
# setval(string, int, bool) is not supported by CockroachDB.
|
47
|
-
#
|
48
|
-
# FIXME(joey): This is incorrect if the sequence is not 1
|
49
|
-
# incremented. We would need to pull out the custom increment value.
|
50
|
-
max_pk - 1
|
51
|
-
end
|
52
|
-
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue})", "SCHEMA")
|
85
|
+
|
86
|
+
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
53
87
|
end
|
54
88
|
end
|
55
89
|
end
|
@@ -1,25 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'active_record/connection_adapters/abstract/transaction'
|
4
|
-
|
5
3
|
module ActiveRecord
|
6
4
|
module ConnectionAdapters
|
7
|
-
|
8
|
-
# NOTE(joey): This is a very sad monkey patch. Unfortunately, it is
|
9
|
-
# required in order to prevent doing more than 2 nested transactions
|
10
|
-
# while still allowing a single nested transaction. This is because
|
11
|
-
# CockroachDB only supports a single savepoint at the beginning of a
|
12
|
-
# transaction. Allowing this works for the common case of testing.
|
13
5
|
module CockroachDB
|
14
6
|
module TransactionManagerMonkeyPatch
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
7
|
+
# Capture ActiveRecord::SerializationFailure errors caused by
|
8
|
+
# transactions that fail due to serialization errors. Failed
|
9
|
+
# transactions will be retried until they pass or the max retry limit is
|
10
|
+
# exceeded.
|
11
|
+
def within_new_transaction(options = {})
|
12
|
+
attempts = options.fetch(:attempts, 0)
|
13
|
+
super
|
14
|
+
rescue ActiveRecord::SerializationFailure => error
|
15
|
+
raise if attempts >= @connection.max_transaction_retries
|
16
|
+
|
17
|
+
attempts += 1
|
18
|
+
sleep_seconds = (2 ** attempts + rand) / 10
|
19
|
+
sleep(sleep_seconds)
|
20
|
+
within_new_transaction(options.merge(attempts: attempts)) { yield }
|
23
21
|
end
|
24
22
|
end
|
25
23
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Type
|
3
|
+
class << self
|
4
|
+
private
|
5
|
+
|
6
|
+
# Return :postgresql instead of :cockroachdb for current_adapter_name so
|
7
|
+
# we can continue using the ActiveRecord::Types defined in
|
8
|
+
# PostgreSQLAdapter.
|
9
|
+
def current_adapter_name
|
10
|
+
:postgresql
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -1,8 +1,12 @@
|
|
1
1
|
require 'active_record/connection_adapters/postgresql_adapter'
|
2
|
-
require "active_record/connection_adapters/postgresql/schema_statements"
|
3
2
|
require "active_record/connection_adapters/cockroachdb/schema_statements"
|
4
3
|
require "active_record/connection_adapters/cockroachdb/referential_integrity"
|
5
4
|
require "active_record/connection_adapters/cockroachdb/transaction_manager"
|
5
|
+
require "active_record/connection_adapters/cockroachdb/column"
|
6
|
+
require "active_record/connection_adapters/cockroachdb/database_statements"
|
7
|
+
require "active_record/connection_adapters/cockroachdb/quoting"
|
8
|
+
require "active_record/connection_adapters/cockroachdb/type"
|
9
|
+
require "active_record/connection_adapters/cockroachdb/attribute_methods"
|
6
10
|
|
7
11
|
module ActiveRecord
|
8
12
|
module ConnectionHandling
|
@@ -23,7 +27,14 @@ module ActiveRecord
|
|
23
27
|
# The postgres drivers don't allow the creation of an unconnected
|
24
28
|
# PG::Connection object, so just pass a nil connection object for the
|
25
29
|
# time being.
|
26
|
-
|
30
|
+
conn = PG.connect(conn_params)
|
31
|
+
ConnectionAdapters::CockroachDBAdapter.new(conn, logger, conn_params, config)
|
32
|
+
rescue ::PG::Error => error
|
33
|
+
if error.message.include?("does not exist")
|
34
|
+
raise ActiveRecord::NoDatabaseError
|
35
|
+
else
|
36
|
+
raise
|
37
|
+
end
|
27
38
|
end
|
28
39
|
end
|
29
40
|
end
|
@@ -32,16 +43,29 @@ module ActiveRecord
|
|
32
43
|
module ConnectionAdapters
|
33
44
|
class CockroachDBAdapter < PostgreSQLAdapter
|
34
45
|
ADAPTER_NAME = "CockroachDB".freeze
|
46
|
+
DEFAULT_PRIMARY_KEY = "rowid"
|
35
47
|
|
36
48
|
include CockroachDB::SchemaStatements
|
37
49
|
include CockroachDB::ReferentialIntegrity
|
50
|
+
include CockroachDB::DatabaseStatements
|
51
|
+
include CockroachDB::Quoting
|
52
|
+
|
53
|
+
def debugging?
|
54
|
+
!!ENV["DEBUG_COCKROACHDB_ADAPTER"]
|
55
|
+
end
|
38
56
|
|
57
|
+
def max_transaction_retries
|
58
|
+
@max_transaction_retries ||= @config.fetch(:max_transaction_retries, 3)
|
59
|
+
end
|
60
|
+
|
61
|
+
# CockroachDB 20.1 can run queries that work against PostgreSQL 10+.
|
62
|
+
def postgresql_version
|
63
|
+
100000
|
64
|
+
end
|
39
65
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
# to use the same original `Utils` module.
|
44
|
-
Utils = PostgreSQL::Utils
|
66
|
+
def supports_bulk_alter?
|
67
|
+
false
|
68
|
+
end
|
45
69
|
|
46
70
|
def supports_json?
|
47
71
|
# FIXME(joey): Add a version check.
|
@@ -56,22 +80,12 @@ module ActiveRecord
|
|
56
80
|
false
|
57
81
|
end
|
58
82
|
|
59
|
-
def supports_ranges?
|
60
|
-
# See cockroachdb/cockroach#17022
|
61
|
-
false
|
62
|
-
end
|
63
|
-
|
64
83
|
def supports_materialized_views?
|
65
84
|
false
|
66
85
|
end
|
67
86
|
|
68
|
-
def supports_pg_crypto_uuid?
|
69
|
-
false
|
70
|
-
end
|
71
|
-
|
72
87
|
def supports_partial_index?
|
73
|
-
|
74
|
-
false
|
88
|
+
@crdb_version >= 202
|
75
89
|
end
|
76
90
|
|
77
91
|
def supports_expression_index?
|
@@ -94,8 +108,7 @@ module ActiveRecord
|
|
94
108
|
end
|
95
109
|
|
96
110
|
def supports_advisory_locks?
|
97
|
-
|
98
|
-
true
|
111
|
+
false
|
99
112
|
end
|
100
113
|
|
101
114
|
def supports_virtual_columns?
|
@@ -103,114 +116,8 @@ module ActiveRecord
|
|
103
116
|
false
|
104
117
|
end
|
105
118
|
|
106
|
-
def
|
107
|
-
|
108
|
-
false
|
109
|
-
end
|
110
|
-
|
111
|
-
def transaction_isolation_levels
|
112
|
-
{
|
113
|
-
# Explicitly prevent READ UNCOMMITTED from being used. This
|
114
|
-
# was due to the READ UNCOMMITTED test failing.
|
115
|
-
# read_uncommitted: "READ UNCOMMITTED",
|
116
|
-
read_committed: "READ COMMITTED",
|
117
|
-
repeatable_read: "REPEATABLE READ",
|
118
|
-
serializable: "SERIALIZABLE"
|
119
|
-
}
|
120
|
-
end
|
121
|
-
|
122
|
-
|
123
|
-
# Sadly, we can only do savepoints at the beginning of
|
124
|
-
# transactions. This means that we cannot use them for most cases
|
125
|
-
# of transaction, so we just pretend they're usable.
|
126
|
-
def create_savepoint(name = "COCKROACH_RESTART"); end
|
127
|
-
|
128
|
-
def exec_rollback_to_savepoint(name = "COCKROACH_RESTART"); end
|
129
|
-
|
130
|
-
def release_savepoint(name = "COCKROACH_RESTART"); end
|
131
|
-
|
132
|
-
def indexes(table_name, name = nil) # :nodoc:
|
133
|
-
# The PostgreSQL adapter uses a correlated subquery in the following query,
|
134
|
-
# which CockroachDB does not yet support. That portion of the query fetches
|
135
|
-
# any non-standard opclasses that each index uses. CockroachDB also doesn't
|
136
|
-
# support opclasses at this time, so the query is modified to just remove
|
137
|
-
# the section about opclasses entirely.
|
138
|
-
if name
|
139
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
140
|
-
Passing name to #indexes is deprecated without replacement.
|
141
|
-
MSG
|
142
|
-
end
|
143
|
-
|
144
|
-
table = Utils.extract_schema_qualified_name(table_name.to_s)
|
145
|
-
|
146
|
-
result = query(<<-SQL, "SCHEMA")
|
147
|
-
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
|
148
|
-
pg_catalog.obj_description(i.oid, 'pg_class') AS comment
|
149
|
-
FROM pg_class t
|
150
|
-
INNER JOIN pg_index d ON t.oid = d.indrelid
|
151
|
-
INNER JOIN pg_class i ON d.indexrelid = i.oid
|
152
|
-
LEFT JOIN pg_namespace n ON n.oid = i.relnamespace
|
153
|
-
WHERE i.relkind = 'i'
|
154
|
-
AND d.indisprimary = 'f'
|
155
|
-
AND t.relname = '#{table.identifier}'
|
156
|
-
AND n.nspname = #{table.schema ? "'#{table.schema}'" : 'ANY (current_schemas(false))'}
|
157
|
-
ORDER BY i.relname
|
158
|
-
SQL
|
159
|
-
|
160
|
-
result.map do |row|
|
161
|
-
index_name = row[0]
|
162
|
-
unique = row[1]
|
163
|
-
indkey = row[2].split(" ").map(&:to_i)
|
164
|
-
inddef = row[3]
|
165
|
-
oid = row[4]
|
166
|
-
comment = row[5]
|
167
|
-
|
168
|
-
expressions, where = inddef.scan(/\((.+?)\)(?: WHERE (.+))?\z/).flatten
|
169
|
-
|
170
|
-
if indkey.include?(0)
|
171
|
-
columns = expressions
|
172
|
-
else
|
173
|
-
columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
|
174
|
-
SELECT a.attnum, a.attname
|
175
|
-
FROM pg_attribute a
|
176
|
-
WHERE a.attrelid = #{oid}
|
177
|
-
AND a.attnum IN (#{indkey.join(",")})
|
178
|
-
SQL
|
179
|
-
|
180
|
-
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
181
|
-
orders = Hash[
|
182
|
-
expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
|
183
|
-
]
|
184
|
-
end
|
185
|
-
|
186
|
-
# FIXME(joey): This may be specific to ActiveRecord 5.2.
|
187
|
-
IndexDefinition.new(
|
188
|
-
table_name,
|
189
|
-
index_name,
|
190
|
-
unique,
|
191
|
-
columns,
|
192
|
-
orders: orders,
|
193
|
-
where: where,
|
194
|
-
comment: comment.presence
|
195
|
-
)
|
196
|
-
end.compact
|
197
|
-
end
|
198
|
-
|
199
|
-
|
200
|
-
def primary_keys(table_name)
|
201
|
-
name = Utils.extract_schema_qualified_name(table_name.to_s)
|
202
|
-
select_values(<<-SQL.strip_heredoc, "SCHEMA")
|
203
|
-
SELECT column_name
|
204
|
-
FROM information_schema.key_column_usage kcu
|
205
|
-
JOIN information_schema.table_constraints tc
|
206
|
-
ON kcu.table_name = tc.table_name
|
207
|
-
AND kcu.table_schema = tc.table_schema
|
208
|
-
AND kcu.constraint_name = tc.constraint_name
|
209
|
-
WHERE constraint_type = 'PRIMARY KEY'
|
210
|
-
AND kcu.table_name = #{quote(name.identifier)}
|
211
|
-
AND kcu.table_schema = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
|
212
|
-
ORDER BY kcu.ordinal_position
|
213
|
-
SQL
|
119
|
+
def supports_string_to_array_coercion?
|
120
|
+
@crdb_version >= 202
|
214
121
|
end
|
215
122
|
|
216
123
|
# This is hardcoded to 63 (as previously was in ActiveRecord 5.0) to aid in
|
@@ -228,6 +135,25 @@ module ActiveRecord
|
|
228
135
|
alias index_name_length max_identifier_length
|
229
136
|
alias table_alias_length max_identifier_length
|
230
137
|
|
138
|
+
def initialize(connection, logger, conn_params, config)
|
139
|
+
super(connection, logger, conn_params, config)
|
140
|
+
crdb_version_string = query_value("SHOW crdb_version")
|
141
|
+
if crdb_version_string.include? "v1."
|
142
|
+
version_num = 1
|
143
|
+
elsif crdb_version_string.include? "v2."
|
144
|
+
version_num 2
|
145
|
+
elsif crdb_version_string.include? "v19.1."
|
146
|
+
version_num = 191
|
147
|
+
elsif crdb_version_string.include? "v19.2."
|
148
|
+
version_num = 192
|
149
|
+
elsif crdb_version_string.include? "v20.1."
|
150
|
+
version_num = 201
|
151
|
+
else
|
152
|
+
version_num = 202
|
153
|
+
end
|
154
|
+
@crdb_version = version_num
|
155
|
+
end
|
156
|
+
|
231
157
|
private
|
232
158
|
|
233
159
|
def initialize_type_map(m = type_map)
|
@@ -295,6 +221,62 @@ module ActiveRecord
|
|
295
221
|
end
|
296
222
|
end
|
297
223
|
|
224
|
+
# Override extract_value_from_default because the upstream definition
|
225
|
+
# doesn't handle the variations in CockroachDB's behavior.
|
226
|
+
def extract_value_from_default(default)
|
227
|
+
super ||
|
228
|
+
extract_escaped_string_from_default(default) ||
|
229
|
+
extract_time_from_default(default) ||
|
230
|
+
extract_empty_array_from_default(default)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Both PostgreSQL and CockroachDB use C-style string escapes under the
|
234
|
+
# covers. PostgreSQL obscures this for us and unescapes the strings, but
|
235
|
+
# CockroachDB does not. Here we'll use Ruby to unescape the string.
|
236
|
+
# See https://github.com/cockroachdb/cockroach/issues/47497 and
|
237
|
+
# https://www.postgresql.org/docs/9.2/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE.
|
238
|
+
def extract_escaped_string_from_default(default)
|
239
|
+
# Escaped strings start with an e followed by the string in quotes (e'…')
|
240
|
+
return unless default =~ /\A[\(B]?e'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
|
241
|
+
|
242
|
+
# String#undump doesn't account for escaped single quote characters
|
243
|
+
"\"#{$1}\"".undump.gsub("\\'".freeze, "'".freeze)
|
244
|
+
end
|
245
|
+
|
246
|
+
# This method exists to extract the correct time and date defaults for a
|
247
|
+
# couple of reasons.
|
248
|
+
# 1) There's a bug in CockroachDB where the date type is missing from
|
249
|
+
# the column info query.
|
250
|
+
# https://github.com/cockroachdb/cockroach/issues/47285
|
251
|
+
# 2) PostgreSQL's timestamp without time zone type maps to CockroachDB's
|
252
|
+
# TIMESTAMP type. TIMESTAMP includes a UTC time zone while timestamp
|
253
|
+
# without time zone doesn't.
|
254
|
+
# https://www.cockroachlabs.com/docs/v19.2/timestamp.html#variants
|
255
|
+
def extract_time_from_default(default)
|
256
|
+
return unless default =~ /\A'(.*)'\z/
|
257
|
+
|
258
|
+
# If default has a UTC time zone, we'll drop the time zone information
|
259
|
+
# so it acts like PostgreSQL's timestamp without time zone. Then, try
|
260
|
+
# to parse the resulting string to verify if it's a time.
|
261
|
+
time = if default =~ /\A'(.*)(\+00:00)'\z/
|
262
|
+
$1
|
263
|
+
else
|
264
|
+
default
|
265
|
+
end
|
266
|
+
|
267
|
+
Time.parse(time).to_s
|
268
|
+
rescue
|
269
|
+
nil
|
270
|
+
end
|
271
|
+
|
272
|
+
# CockroachDB stores default values for arrays in the `ARRAY[...]` format.
|
273
|
+
# In general, it is hard to parse that, but it is easy to handle the common
|
274
|
+
# case of an empty array.
|
275
|
+
def extract_empty_array_from_default(default)
|
276
|
+
return unless supports_string_to_array_coercion?
|
277
|
+
return unless default =~ /\AARRAY\[\]\z/
|
278
|
+
return "{}"
|
279
|
+
end
|
298
280
|
|
299
281
|
# end private
|
300
282
|
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: 0.
|
4
|
+
version: 6.0.0beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cockroach Labs
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-17 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:
|
19
|
+
version: 6.0.3
|
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:
|
26
|
+
version: 6.0.3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: pg
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -31,9 +31,6 @@ dependencies:
|
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0.20'
|
34
|
-
- - "<"
|
35
|
-
- !ruby/object:Gem::Version
|
36
|
-
version: '0.22'
|
37
34
|
type: :runtime
|
38
35
|
prerelease: false
|
39
36
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -41,51 +38,6 @@ dependencies:
|
|
41
38
|
- - ">="
|
42
39
|
- !ruby/object:Gem::Version
|
43
40
|
version: '0.20'
|
44
|
-
- - "<"
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '0.22'
|
47
|
-
- !ruby/object:Gem::Dependency
|
48
|
-
name: bundler
|
49
|
-
requirement: !ruby/object:Gem::Requirement
|
50
|
-
requirements:
|
51
|
-
- - "~>"
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: '1.14'
|
54
|
-
type: :development
|
55
|
-
prerelease: false
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
requirements:
|
58
|
-
- - "~>"
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
version: '1.14'
|
61
|
-
- !ruby/object:Gem::Dependency
|
62
|
-
name: rake
|
63
|
-
requirement: !ruby/object:Gem::Requirement
|
64
|
-
requirements:
|
65
|
-
- - "~>"
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version: '10.0'
|
68
|
-
type: :development
|
69
|
-
prerelease: false
|
70
|
-
version_requirements: !ruby/object:Gem::Requirement
|
71
|
-
requirements:
|
72
|
-
- - "~>"
|
73
|
-
- !ruby/object:Gem::Version
|
74
|
-
version: '10.0'
|
75
|
-
- !ruby/object:Gem::Dependency
|
76
|
-
name: minitest
|
77
|
-
requirement: !ruby/object:Gem::Requirement
|
78
|
-
requirements:
|
79
|
-
- - "~>"
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
version: '5.0'
|
82
|
-
type: :development
|
83
|
-
prerelease: false
|
84
|
-
version_requirements: !ruby/object:Gem::Requirement
|
85
|
-
requirements:
|
86
|
-
- - "~>"
|
87
|
-
- !ruby/object:Gem::Version
|
88
|
-
version: '5.0'
|
89
41
|
description: Allows the use of CockroachDB as a backend for ActiveRecord and Rails
|
90
42
|
apps.
|
91
43
|
email:
|
@@ -109,10 +61,15 @@ files:
|
|
109
61
|
- build/local-test.sh
|
110
62
|
- build/teamcity-test.sh
|
111
63
|
- docker.sh
|
64
|
+
- lib/active_record/connection_adapters/cockroachdb/attribute_methods.rb
|
65
|
+
- lib/active_record/connection_adapters/cockroachdb/column.rb
|
66
|
+
- lib/active_record/connection_adapters/cockroachdb/database_statements.rb
|
112
67
|
- lib/active_record/connection_adapters/cockroachdb/database_tasks.rb
|
68
|
+
- lib/active_record/connection_adapters/cockroachdb/quoting.rb
|
113
69
|
- lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb
|
114
70
|
- lib/active_record/connection_adapters/cockroachdb/schema_statements.rb
|
115
71
|
- lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb
|
72
|
+
- lib/active_record/connection_adapters/cockroachdb/type.rb
|
116
73
|
- lib/active_record/connection_adapters/cockroachdb_adapter.rb
|
117
74
|
- lib/activerecord-cockroachdb-adapter.rb
|
118
75
|
homepage: https://github.com/cockroachdb/activerecord-cockroachdb-adapter
|
@@ -120,7 +77,7 @@ licenses:
|
|
120
77
|
- Apache-2.0
|
121
78
|
metadata:
|
122
79
|
allowed_push_host: https://rubygems.org
|
123
|
-
post_install_message:
|
80
|
+
post_install_message:
|
124
81
|
rdoc_options: []
|
125
82
|
require_paths:
|
126
83
|
- lib
|
@@ -131,13 +88,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
131
88
|
version: '0'
|
132
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
90
|
requirements:
|
134
|
-
- - "
|
91
|
+
- - ">"
|
135
92
|
- !ruby/object:Gem::Version
|
136
|
-
version:
|
93
|
+
version: 1.3.1
|
137
94
|
requirements: []
|
138
|
-
|
139
|
-
|
140
|
-
signing_key:
|
95
|
+
rubygems_version: 3.1.4
|
96
|
+
signing_key:
|
141
97
|
specification_version: 4
|
142
98
|
summary: CockroachDB adapter for ActiveRecord.
|
143
99
|
test_files: []
|