activerecord-cockroachdb-adapter 0.2.3 → 0.3.0.beta1

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 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.