activerecord-cockroachdb-adapter 0.2.3 → 6.0.0beta1
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 +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: []
|