activerecord-cockroachdb-adapter 0.2.3 → 5.2.2

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.
Files changed (27) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.gitmodules +0 -3
  4. data/CONTRIBUTING.md +25 -53
  5. data/Gemfile +58 -6
  6. data/README.md +293 -2
  7. data/Rakefile +17 -5
  8. data/activerecord-cockroachdb-adapter.gemspec +3 -6
  9. data/build/Dockerfile +1 -1
  10. data/build/teamcity-test.sh +17 -37
  11. data/docker.sh +1 -1
  12. data/lib/active_record/connection_adapters/cockroachdb/arel_tosql.rb +27 -0
  13. data/lib/active_record/connection_adapters/cockroachdb/attribute_methods.rb +28 -0
  14. data/lib/active_record/connection_adapters/cockroachdb/column.rb +94 -0
  15. data/lib/active_record/connection_adapters/cockroachdb/column_methods.rb +53 -0
  16. data/lib/active_record/connection_adapters/cockroachdb/database_statements.rb +102 -0
  17. data/lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb +121 -0
  18. data/lib/active_record/connection_adapters/cockroachdb/quoting.rb +37 -0
  19. data/lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb +23 -38
  20. data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +123 -40
  21. data/lib/active_record/connection_adapters/cockroachdb/setup.rb +19 -0
  22. data/lib/active_record/connection_adapters/cockroachdb/spatial_column_info.rb +44 -0
  23. data/lib/active_record/connection_adapters/cockroachdb/table_definition.rb +56 -0
  24. data/lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb +14 -16
  25. data/lib/active_record/connection_adapters/cockroachdb/type.rb +14 -0
  26. data/lib/active_record/connection_adapters/cockroachdb_adapter.rb +218 -123
  27. metadata +18 -42
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 = "5.2.2"
8
8
  spec.licenses = ["Apache-2.0"]
9
9
  spec.authors = ["Cockroach Labs"]
10
10
  spec.email = ["cockroach-db@googlegroups.com"]
@@ -14,7 +14,8 @@ 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
+ spec.add_dependency "rgeo-activerecord", "~> 7.0.0"
18
19
 
19
20
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
20
21
  # to allow pushing to a single host or delete this section to allow pushing to any host.
@@ -31,8 +32,4 @@ Gem::Specification.new do |spec|
31
32
  spec.bindir = "exe"
32
33
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
34
  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
35
  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 \
@@ -1,16 +1,15 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
- set -euo pipefail
3
+ set -euox 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.2.3
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() {
@@ -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 --spatial-libs=./cockroach-$VERSION.linux-amd64/lib --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
- 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
45
+ run_cockroach
54
46
 
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"
47
+ if ! (RUBYOPT="-W0" TESTOPTS="-v" bundle exec rake test); then
48
+ echo "Tests failed"
68
49
  HAS_FAILED=1
69
- else
70
- echo "Test PASSED: $TESTFILE"
50
+ else
51
+ echo "Tests passed"
71
52
  HAS_FAILED=0
72
- fi
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
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # This file is largely cargo-culted from cockroachdb/cockroach/build/builder.sh.
4
4
 
5
- set -euo pipefail
5
+ set -euox pipefail
6
6
 
7
7
  DOCKER_IMAGE_TAG=activerecord_test_container
8
8
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RGeo
4
+ module ActiveRecord
5
+ ##
6
+ # Extend rgeo-activerecord visitors to use PostGIS specific functionality
7
+ module SpatialToPostGISSql
8
+ def visit_in_spatial_context(node, collector)
9
+ # Use ST_GeomFromEWKT for EWKT geometries
10
+ if node.is_a?(String) && node =~ /SRID=[\d+]{0,};/
11
+ collector << "#{st_func('ST_GeomFromEWKT')}(#{quote(node)})"
12
+ else
13
+ super(node, collector)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ RGeo::ActiveRecord::SpatialToSql.prepend RGeo::ActiveRecord::SpatialToPostGISSql
20
+
21
+ module Arel # :nodoc:
22
+ module Visitors # :nodoc:
23
+ class CockroachDB < PostgreSQL # :nodoc:
24
+ include RGeo::ActiveRecord::SpatialToSql
25
+ end
26
+ end
27
+ end
@@ -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,94 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module CockroachDB
4
+ module PostgreSQLColumnMonkeyPatch
5
+ # most functions taken from activerecord-postgis-adapter spatial_column
6
+ # https://github.com/rgeo/activerecord-postgis-adapter/blob/master/lib/active_record/connection_adapters/postgis/spatial_column.rb
7
+ def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil,
8
+ default_function = nil, collation = nil, comment = nil, cast_type = nil, opts = nil)
9
+ @sql_type_metadata = sql_type_metadata
10
+ @geographic = !!(sql_type_metadata.sql_type =~ /geography\(/i)
11
+
12
+ @cast_type = cast_type
13
+ @geographic = !!(sql_type_metadata.sql_type =~ /geography\(/i)
14
+ if opts
15
+ # This case comes from an entry in the geometry_columns table
16
+ set_geometric_type_from_name(opts[:type])
17
+ @srid = opts[:srid].to_i
18
+ @has_z = !!opts[:has_z]
19
+ @has_m = !!opts[:has_m]
20
+ elsif @geographic
21
+ # Geographic type information is embedded in the SQL type
22
+ @srid = 4326
23
+ @has_z = @has_m = false
24
+ build_from_sql_type(sql_type_metadata.sql_type)
25
+ elsif sql_type =~ /geography|geometry|point|linestring|polygon/i
26
+ build_from_sql_type(sql_type_metadata.sql_type)
27
+ elsif sql_type_metadata.sql_type =~ /geography|geometry|point|linestring|polygon/i
28
+ # A geometry column with no geometry_columns entry.
29
+ # @geometric_type = geo_type_from_sql_type(sql_type)
30
+ build_from_sql_type(sql_type_metadata.sql_type)
31
+ end
32
+ super(name, default, sql_type_metadata, null, table_name, default_function, collation, comment: comment)
33
+ if spatial?
34
+ if @srid
35
+ @limit = { srid: @srid, type: to_type_name(geometric_type) }
36
+ @limit[:has_z] = true if @has_z
37
+ @limit[:has_m] = true if @has_m
38
+ @limit[:geographic] = true if @geographic
39
+ end
40
+ end
41
+ end
42
+
43
+ attr_reader :geographic,
44
+ :geometric_type,
45
+ :has_m,
46
+ :has_z,
47
+ :srid
48
+
49
+ alias geographic? geographic
50
+ alias has_z? has_z
51
+ alias has_m? has_m
52
+
53
+ def limit
54
+ spatial? ? @limit : super
55
+ end
56
+
57
+ def spatial?
58
+ %i[geometry geography].include?(@sql_type_metadata.type)
59
+ end
60
+
61
+ def serial?
62
+ default_function == 'unique_rowid()'
63
+ end
64
+
65
+ private
66
+
67
+ def set_geometric_type_from_name(name)
68
+ @geometric_type = RGeo::ActiveRecord.geometric_type_from_name(name) || RGeo::Feature::Geometry
69
+ end
70
+
71
+ def build_from_sql_type(sql_type)
72
+ geo_type, @srid, @has_z, @has_m = OID::Spatial.parse_sql_type(sql_type)
73
+ set_geometric_type_from_name(geo_type)
74
+ end
75
+
76
+ def to_type_name(geometric_type)
77
+ name = geometric_type.type_name.underscore
78
+ case name
79
+ when 'point'
80
+ 'st_point'
81
+ when 'polygon'
82
+ 'st_polygon'
83
+ else
84
+ name
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ class PostgreSQLColumn
91
+ prepend CockroachDB::PostgreSQLColumnMonkeyPatch
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module CockroachDB
6
+ module ColumnMethods
7
+ def spatial(name, options = {})
8
+ raise "You must set a type. For example: 't.spatial type: :st_point'" unless options[:type]
9
+
10
+ column(name, options[:type], **options)
11
+ end
12
+
13
+ def geography(name, options = {})
14
+ column(name, :geography, **options)
15
+ end
16
+
17
+ def geometry(name, options = {})
18
+ column(name, :geometry, **options)
19
+ end
20
+
21
+ def geometry_collection(name, options = {})
22
+ column(name, :geometry_collection, **options)
23
+ end
24
+
25
+ def line_string(name, options = {})
26
+ column(name, :line_string, **options)
27
+ end
28
+
29
+ def multi_line_string(name, options = {})
30
+ column(name, :multi_line_string, **options)
31
+ end
32
+
33
+ def multi_point(name, options = {})
34
+ column(name, :multi_point, **options)
35
+ end
36
+
37
+ def multi_polygon(name, options = {})
38
+ column(name, :multi_polygon, **options)
39
+ end
40
+
41
+ def st_point(name, options = {})
42
+ column(name, :st_point, **options)
43
+ end
44
+
45
+ def st_polygon(name, options = {})
46
+ column(name, :st_polygon, **options)
47
+ end
48
+ end
49
+ end
50
+
51
+ PostgreSQL::Table.include CockroachDB::ColumnMethods
52
+ end
53
+ 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