activerecord-cockroachdb-adapter 0.2.3 → 0.3.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 96a284c08e5798cc8b45332be543b14f60bdb8c0
4
- data.tar.gz: ffa757e8b581788ba8d1880842d37eca7058695e
2
+ SHA256:
3
+ metadata.gz: 48ca5207eae1d4bf1c26da960ad80e78f54099b1eb62466e89ff5186c9d41632
4
+ data.tar.gz: d3d8a5c38a3adda552b283663d4aaf88e05587b3ac9e0da28418fa05d72cfcdd
5
5
  SHA512:
6
- metadata.gz: 456d42bc6a8a973488a6c66a9631d8e78f7e4a35abb379b3958239b6fb474bce9a3503678be6a098d788bb09425c02d30732fb1ee546b3d4c2b8cb21b3a7fe57
7
- data.tar.gz: 40ada5999c613d6d2f953ce7fc99b9fbb3793685d258fe9cef790e2421df96d218ee3684081a499571ed38cc129be6e65de6a87328d57297fdd62323430c61c6
6
+ metadata.gz: 955dca7f67f68b93fd2ebd19cd6027de446598c823abbc108ce5630fbf6d566c14c09e283f37e3d04fb306ff72a68e55f6856daea91a61f9488636f9da957920
7
+ data.tar.gz: 9cb056da8d37cd199427f2ae25d354bcb3fb606fde92e1c01fb2cef7eedd2122a9eccbf0b3693d7d0c7197f8ff8d7d4b1321d16d9212db4331673ddf5669b934
@@ -1,3 +0,0 @@
1
- [submodule "rails"]
2
- path = rails
3
- url = https://github.com/lego/ruby-on-rails.git
@@ -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,30 @@ 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
- # Ensure the rails fork is fetched.
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 ActiveRecord test suite with an active CockroachDB instance:
50
50
 
51
51
  ```bash
52
- cp build/config.teamcity.yml rails/activerecord/test/config.yml
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
- ### Test commands in detail
55
+ To run specific 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
- cp build/config.teamcity.yml rails/activerecord/test/config.yml
58
+ TEST_FILES_AR="test/cases/associations_test.rb,test/cases/ar_schema_test.rb" bundle exec rake test
61
59
  ```
62
60
 
63
- This copies the TeamCity ActiveRecord configuration for the application.
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.)
69
-
70
- ```
71
- (cd rails/activerecord && BUNDLE_GEMFILE=../Gemfile bundle exec rake db:cockroachdb:rebuild)
72
- ```
61
+ By default, tests will be run from the bundled version of Rails. To run against a local copy, set environemnt variable `RAILS_SOURCE`.
73
62
 
74
- This prepares CockroachDB for running tests. It only drops and
75
- re-creates all of the databases needed.
76
-
77
- - This command needs to be run from activerecord folder in order to use
78
- the ActiveRecord `Rakefile`. The `Rakefile` defines scripts (called
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)
63
+ ```bash
64
+ RAILS_SOURCE="path/to/local_copy" bundle exec rake test
89
65
  ```
90
66
 
91
- This executes the CockroachDB tests.
92
-
93
- - Like the previous command, this one uses the Activerecord Rakefile and
94
- the Rails Gemfile. The task code can be found in the Rakefile.
95
- - Running specific test files can be done by appending
96
- `TESTFILES=test/cases/attribute_methods.rb` to the command. Globs are
97
- used. Multiple individual files cannot be specified.
98
-
67
+ `test/config.yml` assumes CockroachDB will be running at localhost:26257 with a root user. Make changes to `test/config.yml` as needed.
99
68
 
100
69
  # Improvements
101
70
 
@@ -143,15 +112,6 @@ A possible way to approach this would be to add a shim to cause any
143
112
  tests that use it to fail, and grep the tests that pass and then skip
144
113
  them.
145
114
 
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
115
  ## Publishing to Rubygems
156
116
 
157
117
  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
- # gem 'activerecord', git: 'https://github.com/lego/ruby-on-rails.git'
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
- git_source(:github) { |repo| "https://github.com/#{repo}.git" }
11
+ gem_version = gemspec.dependencies.
12
+ find { |dep| dep.name == 'activerecord' }.
13
+ requirement.
14
+ requirements.
15
+ first.
16
+ last
6
17
 
7
- # Specify your gem's dependencies in activerecord-cockroachdb-adapter.gemspec
8
- gemspec
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
- # We need a newish Rake since Active Job sets its test tasks' descriptions.
11
- gem "rake", ">= 11.1"
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', '~> 0.2.2'
10
+ gem 'activerecord-cockroachdb-adapter', '~> 0.2'
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
- Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList['test/**/*_test.rb']
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 :default => :test
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.2.3"
7
+ spec.version = "0.3.0.beta1"
8
8
  spec.licenses = ["Apache-2.0"]
9
9
  spec.authors = ["Cockroach Labs"]
10
10
  spec.email = ["cockroach-db@googlegroups.com"]
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = "https://github.com/cockroachdb/activerecord-cockroachdb-adapter"
15
15
 
16
16
  spec.add_dependency "activerecord", "~> 5.2"
17
- spec.add_dependency "pg", ">= 0.20", "< 0.22"
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
@@ -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/postgres-test:20170308-1644
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 \
@@ -2,15 +2,14 @@
2
2
 
3
3
  set -euo pipefail
4
4
 
5
- # Download CockroachDB. NB: currently this uses an alpha, due to feature
6
- # requirements.
7
- VERSION=v2.0-alpha.20171218
5
+ # Download CockroachDB
6
+ VERSION=v20.1.0-rc.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=$(pwd)/cockroach-$VERSION.linux-amd64/:$PATH
12
+ export PATH=./cockroach-$VERSION.linux-amd64/:$PATH
14
13
  readonly urlfile=cockroach-url
15
14
 
16
15
  run_cockroach() {
@@ -32,45 +31,23 @@ 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;'
35
36
  }
36
37
 
37
- # Target the Rails dependency file.
38
- export BUNDLE_GEMFILE=$(pwd)/rails/Gemfile
39
-
40
38
  # Install ruby dependencies.
39
+ gem install bundler:2.1.4
41
40
  bundle install
42
41
 
43
- cp build/config.teamcity.yml rails/activerecord/test/config.yml
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
42
+ run_cockroach
54
43
 
55
- # Get the test files with "# FILE(OK)". These should pass.
56
- TESTS=$(cd rails/activerecord && find test/cases -type f \( -name "*_test.rb" \) -exec grep -l "# FILE(OK)" {} +)
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"
44
+ if ! (bundle exec rake test); then
45
+ echo "Tests failed"
68
46
  HAS_FAILED=1
69
- else
70
- echo "Test PASSED: $TESTFILE"
47
+ else
48
+ echo "Tests passed"
71
49
  HAS_FAILED=0
72
- fi
73
- done
50
+ fi
74
51
 
75
52
  # Attempt a clean shutdown for good measure. We'll force-kill in the
76
53
  # exit trap if this script fails.
@@ -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,21 @@
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
+ end
19
+ end
20
+ end
21
+ 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
- # NOTE(joey): This is cradled from connection_adapters/postgresql/referential_integrity.rb
3
- # It is commonly used for setting up fixtures during tests.
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 # :nodoc:
8
- def disable_referential_integrity # :nodoc:
9
- original_exception = nil
10
- fkeys = nil
13
+ module ReferentialIntegrity
14
+ def disable_referential_integrity
15
+ foreign_keys = tables.map { |table| foreign_keys(table) }.flatten
11
16
 
12
- begin
13
- transaction do
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
- begin
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
- begin
41
- transaction do
42
- if !fkeys.nil?
43
- fkeys.each do |fkey|
44
- add_foreign_key fkey.from_table, fkey.to_table, fkey.options
45
- end
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,57 +1,43 @@
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
- # NOTE(joey): This was ripped from PostgresSQL::SchemaStatements, with a
9
- # slight modification to change setval(string, int, bool) to just
10
- # setval(string, int) for CockroachDB compatbility.
11
- # See https://github.com/cockroachdb/cockroach/issues/19723
12
- #
13
- # Resets the sequence of a table's primary key to the maximum value.
14
- def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
15
- unless pk && sequence
16
- default_pk, default_sequence = pk_and_sequence_for(table)
17
6
 
18
- pk ||= default_pk
19
- sequence ||= default_sequence
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
20
14
  end
15
+ end
21
16
 
22
- if @logger && pk && !sequence
23
- @logger.warn "#{table} has primary key #{pk} with no default sequence."
24
- end
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
25
27
 
26
- if pk && sequence
27
- quoted_sequence = quote_table_name(sequence)
28
- max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
29
- if max_pk.nil?
30
- if postgresql_version >= 100000
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
35
- end
36
- if max_pk
37
- # NOTE(joey): This is done to replace the call:
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")
28
+ if pk == CockroachDBAdapter::DEFAULT_PRIMARY_KEY
29
+ nil
30
+ else
31
+ pk
53
32
  end
54
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
55
41
  end
56
42
  end
57
43
  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
- def begin_transaction(options={})
16
- @connection.lock.synchronize do
17
- # If the transaction nesting is already 2 deep, raise an error.
18
- if @connection.adapter_name == "CockroachDB" && @stack.is_a?(ActiveRecord::ConnectionAdapters::SavepointTransaction)
19
- raise(ArgumentError, "cannot nest more than 1 transaction at a time. this is a CockroachDB limitation")
20
- end
21
- end
22
- super(options)
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
@@ -32,16 +36,25 @@ module ActiveRecord
32
36
  module ConnectionAdapters
33
37
  class CockroachDBAdapter < PostgreSQLAdapter
34
38
  ADAPTER_NAME = "CockroachDB".freeze
39
+ DEFAULT_PRIMARY_KEY = "rowid"
35
40
 
36
41
  include CockroachDB::SchemaStatements
37
42
  include CockroachDB::ReferentialIntegrity
43
+ include CockroachDB::DatabaseStatements
44
+ include CockroachDB::Quoting
38
45
 
46
+ def debugging?
47
+ !!ENV["DEBUG_COCKROACHDB_ADAPTER"]
48
+ end
39
49
 
40
- # Note that in the migration from ActiveRecord 5.0 to 5.1, the
41
- # `extract_schema_qualified_name` method was aliased in the PostgreSQLAdapter.
42
- # To ensure backward compatibility with both <5.1 and 5.1, we rename it here
43
- # to use the same original `Utils` module.
44
- Utils = PostgreSQL::Utils
50
+ def max_transaction_retries
51
+ @max_transaction_retries ||= @config.fetch(:max_transaction_retries, 3)
52
+ end
53
+
54
+ # CockroachDB 20.1 can run queries that work against PostgreSQL 10+.
55
+ def postgresql_version
56
+ 100000
57
+ end
45
58
 
46
59
  def supports_json?
47
60
  # FIXME(joey): Add a version check.
@@ -65,10 +78,6 @@ module ActiveRecord
65
78
  false
66
79
  end
67
80
 
68
- def supports_pg_crypto_uuid?
69
- false
70
- end
71
-
72
81
  def supports_partial_index?
73
82
  # See cockroachdb/cockroach#9683
74
83
  false
@@ -103,116 +112,6 @@ module ActiveRecord
103
112
  false
104
113
  end
105
114
 
106
- def supports_savepoints?
107
- # See cockroachdb/cockroach#10735.
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
214
- end
215
-
216
115
  # This is hardcoded to 63 (as previously was in ActiveRecord 5.0) to aid in
217
116
  # migration from PostgreSQL to CockroachDB. In practice, this limitation
218
117
  # is arbitrary since CockroachDB supports index name lengths and table alias
@@ -295,6 +194,52 @@ module ActiveRecord
295
194
  end
296
195
  end
297
196
 
197
+ # Override extract_value_from_default because the upstream definition
198
+ # doesn't handle the variations in CockroachDB's behavior.
199
+ def extract_value_from_default(default)
200
+ super ||
201
+ extract_escaped_string_from_default(default) ||
202
+ extract_time_from_default(default)
203
+ end
204
+
205
+ # Both PostgreSQL and CockroachDB use C-style string escapes under the
206
+ # covers. PostgreSQL obscures this for us and unescapes the strings, but
207
+ # CockroachDB does not. Here we'll use Ruby to unescape the string.
208
+ # See https://github.com/cockroachdb/cockroach/issues/47497 and
209
+ # https://www.postgresql.org/docs/9.2/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE.
210
+ def extract_escaped_string_from_default(default)
211
+ # Escaped strings start with an e followed by the string in quotes (e'…')
212
+ return unless default =~ /\A[\(B]?e'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
213
+
214
+ # String#undump doesn't account for escaped single quote characters
215
+ "\"#{$1}\"".undump.gsub("\\'".freeze, "'".freeze)
216
+ end
217
+
218
+ # This method exists to extract the correct time and date defaults for a
219
+ # couple of reasons.
220
+ # 1) There's a bug in CockroachDB where the date type is missing from
221
+ # the column info query.
222
+ # https://github.com/cockroachdb/cockroach/issues/47285
223
+ # 2) PostgreSQL's timestamp without time zone type maps to CockroachDB's
224
+ # TIMESTAMP type. TIMESTAMP includes a UTC time zone while timestamp
225
+ # without time zone doesn't.
226
+ # https://www.cockroachlabs.com/docs/v19.2/timestamp.html#variants
227
+ def extract_time_from_default(default)
228
+ return unless default =~ /\A'(.*)'\z/
229
+
230
+ # If default has a UTC time zone, we'll drop the time zone information
231
+ # so it acts like PostgreSQL's timestamp without time zone. Then, try
232
+ # to parse the resulting string to verify if it's a time.
233
+ time = if default =~ /\A'(.*)(\+00:00)'\z/
234
+ $1
235
+ else
236
+ default
237
+ end
238
+
239
+ Time.parse(time).to_s
240
+ rescue
241
+ nil
242
+ end
298
243
 
299
244
  # end private
300
245
  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.2.3
4
+ version: 0.3.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cockroach Labs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-04-19 00:00:00.000000000 Z
11
+ date: 2020-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -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
@@ -131,12 +88,11 @@ 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: '0'
93
+ version: 1.3.1
137
94
  requirements: []
138
- rubyforge_project:
139
- rubygems_version: 2.5.2
95
+ rubygems_version: 3.1.2
140
96
  signing_key:
141
97
  specification_version: 4
142
98
  summary: CockroachDB adapter for ActiveRecord.