Skip to content

Commit be413bf

Browse files
authored
Merge pull request #137 from cockroachdb/feature/rails-6-update
Rails 6 update
2 parents 078d4fd + 57ca51a commit be413bf

25 files changed

+818
-15
lines changed

activerecord-cockroachdb-adapter.gemspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
44

55
Gem::Specification.new do |spec|
66
spec.name = "activerecord-cockroachdb-adapter"
7-
spec.version = "5.2.1"
7+
spec.version = "6.0.0beta1"
88
spec.licenses = ["Apache-2.0"]
99
spec.authors = ["Cockroach Labs"]
1010
spec.email = ["[email protected]"]
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
1313
spec.description = "Allows the use of CockroachDB as a backend for ActiveRecord and Rails apps."
1414
spec.homepage = "https://github.com/cockroachdb/activerecord-cockroachdb-adapter"
1515

16-
spec.add_dependency "activerecord", "~> 5.2"
16+
spec.add_dependency "activerecord", "~> 6.0.3"
1717
spec.add_dependency "pg", ">= 0.20"
1818

1919
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'

lib/active_record/connection_adapters/cockroachdb/schema_statements.rb

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,35 @@ def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) #
5757
end
5858
# The call to super might have appeneded [] already.
5959
if array && type != :primary_key && !sql.end_with?("[]")
60-
sql = "#{sql}[]"
60+
sql = "#{sql}[]"
6161
end
6262
sql
6363
end
64+
65+
# This overrides the method from PostegreSQL adapter
66+
# Resets the sequence of a table's primary key to the maximum value.
67+
def reset_pk_sequence!(table, pk = nil, sequence = nil)
68+
unless pk && sequence
69+
default_pk, default_sequence = pk_and_sequence_for(table)
70+
71+
pk ||= default_pk
72+
sequence ||= default_sequence
73+
end
74+
75+
if @logger && pk && !sequence
76+
@logger.warn "#{table} has primary key #{pk} with no default sequence."
77+
end
78+
79+
if pk && sequence
80+
quoted_sequence = quote_table_name(sequence)
81+
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
82+
if max_pk.nil?
83+
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
84+
end
85+
86+
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
87+
end
88+
end
6489
end
6590
end
6691
end

lib/active_record/connection_adapters/cockroachdb_adapter.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,14 @@ def cockroachdb_connection(config)
2727
# The postgres drivers don't allow the creation of an unconnected
2828
# PG::Connection object, so just pass a nil connection object for the
2929
# time being.
30-
ConnectionAdapters::CockroachDBAdapter.new(nil, logger, conn_params, config)
30+
conn = PG.connect(conn_params)
31+
ConnectionAdapters::CockroachDBAdapter.new(conn, logger, conn_params, config)
32+
rescue ::PG::Error => error
33+
if error.message.include?("does not exist")
34+
raise ActiveRecord::NoDatabaseError
35+
else
36+
raise
37+
end
3138
end
3239
end
3340
end
@@ -73,11 +80,6 @@ def supports_extensions?
7380
false
7481
end
7582

76-
def supports_ranges?
77-
# See cockroachdb/cockroach#17022
78-
false
79-
end
80-
8183
def supports_materialized_views?
8284
false
8385
end
@@ -151,7 +153,7 @@ def initialize(connection, logger, conn_params, config)
151153
end
152154
@crdb_version = version_num
153155
end
154-
156+
155157
private
156158

157159
def initialize_type_map(m = type_map)

test/cases/adapter_test.rb

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
require "cases/helper_cockroachdb"
2+
require "models/binary"
3+
require "models/developer"
4+
require "models/post"
5+
require "models/author"
26

37
module CockroachDB
48
class AdapterTest < ActiveRecord::TestCase
@@ -44,9 +48,86 @@ def test_remove_index_when_name_and_wrong_column_name_specified
4448
end
4549
end
4650

51+
class AdapterForeignKeyTest < ActiveRecord::TestCase
52+
self.use_transactional_tests = false
53+
54+
fixtures :fk_test_has_pk
55+
56+
def before_setup
57+
conn = ActiveRecord::Base.connection
58+
59+
conn.drop_table :fk_test_has_fk, if_exists: true
60+
conn.drop_table :fk_test_has_pk, if_exists: true
61+
62+
conn.create_table :fk_test_has_pk, primary_key: "pk_id", force: :cascade do |t|
63+
end
64+
65+
conn.create_table :fk_test_has_fk, force: true do |t|
66+
t.references :fk, null: false
67+
t.foreign_key :fk_test_has_pk, column: "fk_id", name: "fk_name", primary_key: "pk_id"
68+
end
69+
70+
conn.execute "INSERT INTO fk_test_has_pk (pk_id) VALUES (1)"
71+
end
72+
73+
def setup
74+
@connection = ActiveRecord::Base.connection
75+
end
76+
77+
def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false
78+
klass_has_fk = Class.new(ActiveRecord::Base) do
79+
self.table_name = "fk_test_has_fk"
80+
end
81+
82+
error = assert_raises(ActiveRecord::InvalidForeignKey) do
83+
has_fk = klass_has_fk.new
84+
has_fk.fk_id = 1231231231
85+
has_fk.save(validate: false)
86+
end
87+
88+
assert_not_nil error.cause
89+
end
90+
91+
# This is override to prevent an intermittent error
92+
# Table fk_test_has_pk has constrain droped and not created back
93+
def test_foreign_key_violations_on_insert_are_translated_to_specific_exception
94+
error = assert_raises(ActiveRecord::InvalidForeignKey) do
95+
insert_into_fk_test_has_fk
96+
end
97+
98+
assert_not_nil error.cause
99+
end
100+
101+
# This is override to prevent an intermittent error
102+
# Table fk_test_has_pk has constrain droped and not created back
103+
def test_foreign_key_violations_on_delete_are_translated_to_specific_exception
104+
insert_into_fk_test_has_fk fk_id: 1
105+
106+
error = assert_raises(ActiveRecord::InvalidForeignKey) do
107+
@connection.execute "DELETE FROM fk_test_has_pk WHERE pk_id = 1"
108+
end
109+
110+
assert_not_nil error.cause
111+
end
112+
113+
private
114+
115+
def insert_into_fk_test_has_fk(fk_id: 0)
116+
# Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
117+
if @connection.prefetch_primary_key?
118+
id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
119+
@connection.execute "INSERT INTO fk_test_has_fk (id,fk_id) VALUES (#{id_value},#{fk_id})"
120+
else
121+
@connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (#{fk_id})"
122+
end
123+
end
124+
end
125+
47126
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
48127
self.use_transactional_tests = false
49128

129+
fixtures :posts, :authors, :author_addresses
130+
50131
class Widget < ActiveRecord::Base
51132
self.primary_key = "widgetid"
52133
end
@@ -58,6 +139,7 @@ def setup
58139
teardown do
59140
@connection.drop_table :widgets, if_exists: true
60141
@connection.exec_query("DROP SEQUENCE IF EXISTS widget_seq")
142+
@connection.exec_query("DROP SEQUENCE IF EXISTS widgets_seq")
61143
end
62144

63145
# This test replaces the excluded test_reset_empty_table_with_custom_pk. We
@@ -74,5 +156,46 @@ def test_reset_empty_table_with_custom_pk_sequence
74156
")
75157
assert_equal 1, Widget.create(name: "weather").id
76158
end
159+
160+
def test_truncate_tables
161+
assert_operator Post.count, :>, 0
162+
assert_operator Author.count, :>, 0
163+
assert_operator AuthorAddress.count, :>, 0
164+
165+
@connection.truncate_tables("author_addresses", "authors", "posts")
166+
167+
assert_equal 0, Post.count
168+
assert_equal 0, Author.count
169+
assert_equal 0, AuthorAddress.count
170+
ensure
171+
reset_fixtures("author_addresses", "authors", "posts")
172+
end
173+
174+
def test_truncate_tables_with_query_cache
175+
@connection.enable_query_cache!
176+
177+
assert_operator Post.count, :>, 0
178+
assert_operator Author.count, :>, 0
179+
assert_operator AuthorAddress.count, :>, 0
180+
181+
@connection.truncate_tables("author_addresses", "authors", "posts")
182+
183+
assert_equal 0, Post.count
184+
assert_equal 0, Author.count
185+
assert_equal 0, AuthorAddress.count
186+
ensure
187+
reset_fixtures("author_addresses", "authors", "posts")
188+
@connection.disable_query_cache!
189+
end
190+
191+
private
192+
193+
def reset_fixtures(*fixture_names)
194+
ActiveRecord::FixtureSet.reset_cache
195+
196+
fixture_names.each do |fixture_name|
197+
ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, fixture_name)
198+
end
199+
end
77200
end
78201
end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require "cases/helper_cockroachdb"
2+
require "cases/helper"
3+
require "support/ddl_helper"
4+
require "support/connection_helper"
5+
6+
module CockroachDB
7+
module ConnectionAdapters
8+
class PostgreSQLAdapterTest < ActiveRecord::PostgreSQLTestCase
9+
self.use_transactional_tests = false
10+
include DdlHelper
11+
include ConnectionHelper
12+
13+
def setup
14+
@connection = ActiveRecord::Base.connection
15+
@connection_handler = ActiveRecord::Base.connection_handler
16+
end
17+
18+
def test_database_exists_returns_false_when_the_database_does_not_exist
19+
config = { database: "non_extant_database", adapter: "cockroachdb" }
20+
assert_not ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.database_exists?(config),
21+
"expected database #{config[:database]} to not exist"
22+
end
23+
end
24+
end
25+
end

test/cases/associations/left_outer_join_association_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
module CockroachDB
88
class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
9-
fixtures :authors, :posts
9+
fixtures :author_addresses, :authors, :posts
1010

1111
# This replaces the same test that's been excluded from
1212
# LeftOuterJoinAssociationTest. The query has been updated to guarantee the

test/cases/associations_test.rb

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
require "cases/helper_cockroachdb"
2+
3+
require "support/connection_helper"
4+
5+
require "models/bird"
6+
require "models/parrot"
7+
require "models/pirate"
8+
require "models/price_estimate"
9+
require "models/ship"
10+
require "models/treasure"
11+
12+
module CockroachDB
13+
class WithAnnotationsTest < ActiveRecord::TestCase
14+
self.use_transactional_tests = false
15+
16+
fixtures :pirates, :treasures, :parrots
17+
18+
def test_belongs_to_with_annotation_includes_a_query_comment
19+
pirate = SpacePirate.where.not(parrot_id: nil).first
20+
assert pirate, "should have a Pirate record"
21+
22+
log = capture_sql do
23+
pirate.parrot
24+
end
25+
assert_not_predicate log, :empty?
26+
assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
27+
28+
assert_sql(%r{/\* that tells jokes \*/}) do
29+
pirate.parrot_with_annotation
30+
end
31+
end
32+
33+
def test_has_and_belongs_to_many_with_annotation_includes_a_query_comment
34+
pirate = SpacePirate.first
35+
assert pirate, "should have a Pirate record"
36+
37+
log = capture_sql do
38+
pirate.parrots.first
39+
end
40+
assert_not_predicate log, :empty?
41+
assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
42+
43+
assert_sql(%r{/\* that are very colorful \*/}) do
44+
pirate.parrots_with_annotation.first
45+
end
46+
end
47+
48+
def test_has_one_with_annotation_includes_a_query_comment
49+
pirate = SpacePirate.first
50+
assert pirate, "should have a Pirate record"
51+
52+
log = capture_sql do
53+
pirate.ship
54+
end
55+
assert_not_predicate log, :empty?
56+
assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
57+
58+
assert_sql(%r{/\* that is a rocket \*/}) do
59+
pirate.ship_with_annotation
60+
end
61+
end
62+
63+
def test_has_many_with_annotation_includes_a_query_comment
64+
pirate = SpacePirate.first
65+
assert pirate, "should have a Pirate record"
66+
67+
log = capture_sql do
68+
pirate.birds.first
69+
end
70+
assert_not_predicate log, :empty?
71+
assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
72+
73+
assert_sql(%r{/\* that are also parrots \*/}) do
74+
pirate.birds_with_annotation.first
75+
end
76+
end
77+
78+
def test_has_many_through_with_annotation_includes_a_query_comment
79+
pirate = SpacePirate.first
80+
assert pirate, "should have a Pirate record"
81+
82+
log = capture_sql do
83+
pirate.treasure_estimates.first
84+
end
85+
assert_not_predicate log, :empty?
86+
assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
87+
88+
assert_sql(%r{/\* yarrr \*/}) do
89+
pirate.treasure_estimates_with_annotation.first
90+
end
91+
end
92+
93+
def test_has_many_through_with_annotation_includes_a_query_comment_when_eager_loading
94+
pirate = SpacePirate.first
95+
assert pirate, "should have a Pirate record"
96+
97+
log = capture_sql do
98+
pirate.treasure_estimates.first
99+
end
100+
assert_not_predicate log, :empty?
101+
assert_predicate log.select { |query| query.match?(%r{/\*}) }, :empty?
102+
103+
assert_sql(%r{/\* yarrr \*/}) do
104+
SpacePirate.includes(:treasure_estimates_with_annotation, :treasures).first
105+
end
106+
end
107+
end
108+
end

0 commit comments

Comments
 (0)