activerecord-cockroachdb-adapter 8.0.3 → 8.1.1
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/CONTRIBUTING.md +10 -17
- data/Gemfile +2 -1
- data/Rakefile +0 -19
- data/lib/active_record/connection_adapters/cockroachdb/column.rb +69 -24
- data/lib/active_record/connection_adapters/cockroachdb/database_statements.rb +13 -3
- data/lib/active_record/connection_adapters/cockroachdb/database_tasks.rb +1 -1
- data/lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb +34 -19
- data/lib/active_record/connection_adapters/cockroachdb/quoting.rb +19 -3
- data/lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb +137 -51
- data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +181 -102
- data/lib/active_record/connection_adapters/cockroachdb_adapter.rb +78 -34
- data/lib/version.rb +1 -1
- data/test/cases/adapter_test.rb +98 -0
- data/test/cases/adapters/cockroachdb/cached_plan_failure_test.rb +106 -0
- data/test/cases/adapters/cockroachdb/referential_integrity_test.rb +91 -0
- data/test/cases/adapters/postgresql/active_schema_test.rb +71 -0
- data/test/cases/adapters/postgresql/change_schema_test.rb +75 -0
- data/test/cases/adapters/postgresql/connection_test.rb +46 -0
- data/test/cases/adapters/postgresql/ddl_test.rb +326 -0
- data/test/cases/adapters/postgresql/interval_test.rb +131 -0
- data/test/cases/adapters/postgresql/nested_class_test.rb +22 -0
- data/test/cases/adapters/postgresql/numeric_test.rb +28 -0
- data/test/cases/adapters/postgresql/postgis_test.rb +199 -0
- data/test/cases/adapters/postgresql/postgresql_adapter_test.rb +115 -0
- data/test/cases/adapters/postgresql/quoting_test.rb +30 -0
- data/test/cases/adapters/postgresql/schema_statements_test.rb +29 -0
- data/test/cases/adapters/postgresql/serial_test.rb +199 -0
- data/test/cases/adapters/postgresql/spatial_queries_test.rb +118 -0
- data/test/cases/adapters/postgresql/spatial_setup_test.rb +18 -0
- data/test/cases/adapters/postgresql/spatial_type_test.rb +41 -0
- data/test/cases/adapters/postgresql/timestamp_test.rb +58 -0
- data/test/cases/adapters/postgresql/virtual_column_test.rb +39 -0
- data/test/cases/associations/eager_load_nested_include_test.rb +111 -0
- data/test/cases/associations/left_outer_join_association_test.rb +30 -0
- data/test/cases/associations_test.rb +108 -0
- data/test/cases/base_test.rb +31 -0
- data/test/cases/comment_test.rb +75 -0
- data/test/cases/connection_adapters/type_test.rb +28 -0
- data/test/cases/database_configurations/resolver_test.rb +24 -0
- data/test/cases/defaults_test.rb +45 -0
- data/test/cases/dirty_test.rb +24 -0
- data/test/cases/fixtures_test.rb +541 -0
- data/test/cases/helper_cockroachdb.rb +232 -0
- data/test/cases/inheritance_test.rb +42 -0
- data/test/cases/invertible_migration_test.rb +73 -0
- data/test/cases/marshal_serialization_test.rb +45 -0
- data/test/cases/migration/change_schema_test.rb +140 -0
- data/test/cases/migration/check_constraint_test.rb +125 -0
- data/test/cases/migration/columns_test.rb +50 -0
- data/test/cases/migration/create_join_table_test.rb +66 -0
- data/test/cases/migration/foreign_key_test.rb +390 -0
- data/test/cases/migration/hidden_column_test.rb +70 -0
- data/test/cases/migration/references_foreign_key_test.rb +124 -0
- data/test/cases/migration_test.rb +120 -0
- data/test/cases/persistence_test.rb +33 -0
- data/test/cases/primary_keys_test.rb +134 -0
- data/test/cases/relation/aost_test.rb +57 -0
- data/test/cases/relation/or_test.rb +26 -0
- data/test/cases/relation/table_hints_test.rb +124 -0
- data/test/cases/relation_test.rb +26 -0
- data/test/cases/relations_test.rb +29 -0
- data/test/cases/schema_dumper_test.rb +221 -0
- data/test/cases/show_create_test.rb +14 -0
- data/test/cases/strict_loading_test.rb +34 -0
- data/test/cases/tasks/cockroachdb_rake_test.rb +113 -0
- data/test/cases/test_fixtures_test.rb +57 -0
- data/test/cases/transactions_test.rb +51 -0
- data/test/cases/unsafe_raw_sql_test.rb +22 -0
- data/test/config.yml +23 -0
- data/test/excludes/ActiveRecord/AdapterTest.rb +3 -0
- data/test/excludes/ActiveRecord/AdapterTestWithoutTransaction.rb +25 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/PoolConfig/ResolverTest.rb +1 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter/BindParameterTest.rb +15 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter/QuotingTest.rb +22 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterPreventWritesLegacyTest.rb +1 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterPreventWritesTest.rb +1 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterTest.rb +40 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/RegistrationIsolatedTest.rb +14 -0
- data/test/excludes/ActiveRecord/Encryption/EncryptionPerformanceTest.rb +1 -0
- data/test/excludes/ActiveRecord/Encryption/EnvelopeEncryptionPerformanceTest.rb +1 -0
- data/test/excludes/ActiveRecord/Encryption/ExtendedDeterministicQueriesPerformanceTest.rb +1 -0
- data/test/excludes/ActiveRecord/Encryption/StoragePerformanceTest.rb +2 -0
- data/test/excludes/ActiveRecord/InstrumentationTest.rb +16 -0
- data/test/excludes/ActiveRecord/InvertibleMigrationTest.rb +2 -0
- data/test/excludes/ActiveRecord/Migration/ChangeSchemaTest.rb +7 -0
- data/test/excludes/ActiveRecord/Migration/CheckConstraintTest.rb +6 -0
- data/test/excludes/ActiveRecord/Migration/ColumnsTest.rb +4 -0
- data/test/excludes/ActiveRecord/Migration/CompatibilityTest.rb +50 -0
- data/test/excludes/ActiveRecord/Migration/CompositeForeignKeyTest.rb +2 -0
- data/test/excludes/ActiveRecord/Migration/CreateJoinTableTest.rb +2 -0
- data/test/excludes/ActiveRecord/Migration/ForeignKeyInCreateTest.rb +1 -0
- data/test/excludes/ActiveRecord/Migration/ForeignKeyTest.rb +35 -0
- data/test/excludes/ActiveRecord/Migration/InvalidOptionsTest.rb +14 -0
- data/test/excludes/ActiveRecord/Migration/PGChangeSchemaTest.rb +8 -0
- data/test/excludes/ActiveRecord/Migration/ReferencesForeignKeyTest.rb +7 -0
- data/test/excludes/ActiveRecord/Migration/ReferencesIndexTest.rb +7 -0
- data/test/excludes/ActiveRecord/Migration/ReferencesStatementsTest.rb +3 -0
- data/test/excludes/ActiveRecord/MysqlDBCreateWithInvalidPermissionsTest.rb +1 -0
- data/test/excludes/ActiveRecord/OrTest.rb +1 -0
- data/test/excludes/ActiveRecord/PostgreSQLStructureDumpTest.rb +10 -0
- data/test/excludes/ActiveRecord/PostgresqlConnectionTest.rb +12 -0
- data/test/excludes/ActiveRecord/PostgresqlTransactionNestedTest.rb +14 -0
- data/test/excludes/ActiveRecord/PostgresqlTransactionTest.rb +4 -0
- data/test/excludes/ActiveRecord/RelationTest.rb +2 -0
- data/test/excludes/ActiveRecord/TooManyOrTest.rb +1 -0
- data/test/excludes/ActiveSupportSubclassWithFixturesTest.rb +1 -0
- data/test/excludes/AssociationDeprecationTest/NotifyModeTest.rb +2 -0
- data/test/excludes/AssociationDeprecationTest/RaiseBacktraceModeTest.rb +2 -0
- data/test/excludes/AssociationDeprecationTest/RaiseModeTest.rb +2 -0
- data/test/excludes/AssociationDeprecationTest/WarnBacktraceModeTest.rb +2 -0
- data/test/excludes/AssociationDeprecationTest/WarnModeTest.rb +2 -0
- data/test/excludes/AssociationDeprecationTest/fix_backtrace_cleaner.rb +10 -0
- data/test/excludes/BasicsTest.rb +1 -0
- data/test/excludes/BulkAlterTableMigrationsTest.rb +7 -0
- data/test/excludes/CalculationsTest.rb +4 -0
- data/test/excludes/CommentTest.rb +2 -0
- data/test/excludes/CoreTest.rb +1 -0
- data/test/excludes/CreateOrFindByWithinTransactions.rb +3 -0
- data/test/excludes/DefaultsUsingMultipleSchemasAndDomainTest.rb +7 -0
- data/test/excludes/DirtyTest.rb +3 -0
- data/test/excludes/EachTest.rb +8 -0
- data/test/excludes/EagerLoadPolyAssocsTest.rb +1 -0
- data/test/excludes/ExplicitlyNamedIndexMigrationTest.rb +1 -0
- data/test/excludes/FixturesResetPkSequenceTest.rb +3 -0
- data/test/excludes/FixturesTest.rb +21 -0
- data/test/excludes/FixturesWithForeignKeyViolationsTest.rb +2 -0
- data/test/excludes/ForeignTableTest.rb +10 -0
- data/test/excludes/InheritanceComputeTypeTest.rb +1 -0
- data/test/excludes/LeftOuterJoinAssociationTest.rb +2 -0
- data/test/excludes/LegacyPrimaryKeyTest/V4_2.rb +6 -0
- data/test/excludes/LegacyPrimaryKeyTest/V5_0.rb +6 -0
- data/test/excludes/MarshalSerializationTest.rb +4 -0
- data/test/excludes/MaterializedViewTest.rb +13 -0
- data/test/excludes/MigrationTest.rb +2 -0
- data/test/excludes/MultiDbMigratorTest.rb +2 -0
- data/test/excludes/NestedRelationScopingTest.rb +1 -0
- data/test/excludes/OrTest.rb +1 -0
- data/test/excludes/PersistenceTest.rb +3 -0
- data/test/excludes/PessimisticLockingTest.rb +1 -0
- data/test/excludes/PostgreSQLExplainTest.rb +7 -0
- data/test/excludes/PostgreSQLGeometricLineTest.rb +3 -0
- data/test/excludes/PostgreSQLGeometricTypesTest.rb +7 -0
- data/test/excludes/PostgreSQLPartitionsTest.rb +1 -0
- data/test/excludes/PostgreSQLReferentialIntegrityTest.rb +14 -0
- data/test/excludes/PostgresqlArrayTest.rb +21 -0
- data/test/excludes/PostgresqlBigSerialTest.rb +7 -0
- data/test/excludes/PostgresqlBitStringTest.rb +2 -0
- data/test/excludes/PostgresqlByteaTest.rb +1 -0
- data/test/excludes/PostgresqlCitextTest.rb +7 -0
- data/test/excludes/PostgresqlCollationTest.rb +5 -0
- data/test/excludes/PostgresqlCompositeTest.rb +2 -0
- data/test/excludes/PostgresqlCompositeWithCustomOIDTest.rb +2 -0
- data/test/excludes/PostgresqlDataTypeTest.rb +9 -0
- data/test/excludes/PostgresqlDefaultExpressionTest.rb +1 -0
- data/test/excludes/PostgresqlDeferredConstraintsTest.rb +3 -0
- data/test/excludes/PostgresqlDomainTest.rb +2 -0
- data/test/excludes/PostgresqlExtensionMigrationTest.rb +5 -0
- data/test/excludes/PostgresqlFullTextTest.rb +3 -0
- data/test/excludes/PostgresqlGeometricTest.rb +4 -0
- data/test/excludes/PostgresqlHstoreTest.rb +45 -0
- data/test/excludes/PostgresqlInfinityTest.rb +5 -0
- data/test/excludes/PostgresqlIntervalTest.rb +1 -0
- data/test/excludes/PostgresqlJSONBTest.rb +27 -0
- data/test/excludes/PostgresqlJSONTest.rb +27 -0
- data/test/excludes/PostgresqlLtreeTest.rb +4 -0
- data/test/excludes/PostgresqlMoneyTest.rb +12 -0
- data/test/excludes/PostgresqlNetworkTest.rb +69 -0
- data/test/excludes/PostgresqlNumberTest.rb +1 -0
- data/test/excludes/PostgresqlPointTest.rb +15 -0
- data/test/excludes/PostgresqlRangeTest.rb +3 -0
- data/test/excludes/PostgresqlRenameTableTest.rb +2 -0
- data/test/excludes/PostgresqlSerialTest.rb +7 -0
- data/test/excludes/PostgresqlTimestampFixtureTest.rb +2 -0
- data/test/excludes/PostgresqlTimestampMigrationTest.rb +3 -0
- data/test/excludes/PostgresqlTypeLookupTest.rb +2 -0
- data/test/excludes/PostgresqlUUIDGenerationTest.rb +7 -0
- data/test/excludes/PostgresqlUUIDTest.rb +1 -0
- data/test/excludes/PostgresqlVirtualColumnTest.rb +17 -0
- data/test/excludes/PostgresqlXMLTest.rb +5 -0
- data/test/excludes/PrimaryKeyIntegerNilDefaultTest.rb +1 -0
- data/test/excludes/PrimaryKeyIntegerTest.rb +3 -0
- data/test/excludes/PrimaryKeysTest.rb +2 -0
- data/test/excludes/QueryCacheExpiryTest.rb +10 -0
- data/test/excludes/RelationMergingTest.rb +15 -0
- data/test/excludes/RelationTest.rb +3 -0
- data/test/excludes/ReservedWordsMigrationTest.rb +1 -0
- data/test/excludes/SameNameDifferentDatabaseFixturesTest.rb +1 -0
- data/test/excludes/SanitizeTest.rb +13 -0
- data/test/excludes/SchemaAuthorizationTest.rb +8 -0
- data/test/excludes/SchemaCreateTableOptionsTest.rb +2 -0
- data/test/excludes/SchemaDumperDefaultsTest.rb +1 -0
- data/test/excludes/SchemaDumperTest.rb +11 -0
- data/test/excludes/SchemaForeignKeyTest.rb +1 -0
- data/test/excludes/SchemaIndexNullsOrderTest.rb +2 -0
- data/test/excludes/SchemaIndexOpclassTest.rb +3 -0
- data/test/excludes/SchemaTest.rb +49 -0
- data/test/excludes/SequenceNameDetectionTestCases/CollidedSequenceNameTest.rb +1 -0
- data/test/excludes/SequenceNameDetectionTestCases/LongerSequenceNameDetectionTest.rb +1 -0
- data/test/excludes/StrictLoadingFixturesTest.rb +1 -0
- data/test/excludes/TestFixturesTest.rb +1 -0
- data/test/excludes/TransactionInstrumentationTest.rb +1 -0
- data/test/excludes/TransactionIsolationTest.rb +1 -0
- data/test/excludes/TypeTest.rb +1 -0
- data/test/excludes/UniquenessValidationTest.rb +2 -0
- data/test/excludes/UnloggedTablesTest.rb +3 -0
- data/test/excludes/UnsafeRawSqlTest.rb +2 -0
- data/test/excludes/UpdateableViewTest.rb +5 -0
- data/test/excludes/WithAnnotationsTest.rb +6 -0
- data/test/models/building.rb +4 -0
- data/test/models/spatial_model.rb +5 -0
- data/test/schema/cockroachdb_specific_schema.rb +218 -0
- data/test/support/copy_cat.rb +83 -0
- data/test/support/exclude_from_transactional_tests.rb +33 -0
- data/test/support/paths_cockroachdb.rb +46 -0
- data/test/support/rake_helpers.rb +37 -0
- data/test/support/sql_logger.rb +55 -0
- data/test/support/template_creator.rb +114 -0
- metadata +422 -30
- data/.editorconfig +0 -7
- data/.github/issue_template.md +0 -46
- data/.github/reproduction_scripts/migrations.rb +0 -60
- data/.github/reproduction_scripts/models_and_database.rb +0 -49
- data/.github/workflows/ci.yml +0 -96
- data/.github/workflows/docker.yml +0 -57
- data/.gitignore +0 -18
- data/.gitmodules +0 -0
- data/activerecord-cockroachdb-adapter.gemspec +0 -38
- data/bin/console +0 -36
- data/bin/console_schemas/default.rb +0 -11
- data/bin/console_schemas/schemas.rb +0 -23
- data/bin/setup +0 -8
- data/bin/start-cockroachdb +0 -33
- data/build/Dockerfile +0 -14
- data/build/config.teamcity.yml +0 -31
- data/build/local-test.sh +0 -32
- data/build/teamcity-test.sh +0 -76
- data/docker.sh +0 -35
- data/lib/active_record/connection_adapters/cockroachdb/spatial_column_info.rb +0 -60
- data/setup.sql +0 -18
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
require "cases/helper_cockroachdb"
|
|
2
|
+
require "models/binary"
|
|
3
|
+
require "models/developer"
|
|
4
|
+
require "models/post"
|
|
5
|
+
require "models/author"
|
|
6
|
+
|
|
7
|
+
module CockroachDB
|
|
8
|
+
class AdapterTest < ActiveRecord::TestCase
|
|
9
|
+
self.use_transactional_tests = false
|
|
10
|
+
|
|
11
|
+
def setup
|
|
12
|
+
@connection = ActiveRecord::Base.lease_connection
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# This replaces the same test that's been excluded from
|
|
16
|
+
# ActiveRecord::AdapterTest. We can run it here with
|
|
17
|
+
# use_transactional_tests set to false.
|
|
18
|
+
# See test/excludes/ActiveRecord/AdapterTest.rb.
|
|
19
|
+
def test_indexes
|
|
20
|
+
idx_name = "accounts_idx"
|
|
21
|
+
|
|
22
|
+
indexes = @connection.indexes("accounts")
|
|
23
|
+
assert_empty indexes
|
|
24
|
+
|
|
25
|
+
@connection.add_index :accounts, :firm_id, name: idx_name
|
|
26
|
+
indexes = @connection.indexes("accounts")
|
|
27
|
+
assert_equal "accounts", indexes.first.table
|
|
28
|
+
assert_equal idx_name, indexes.first.name
|
|
29
|
+
assert !indexes.first.unique
|
|
30
|
+
assert_equal ["firm_id"], indexes.first.columns
|
|
31
|
+
ensure
|
|
32
|
+
@connection.remove_index(:accounts, name: idx_name, if_exists: true)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# This replaces the same test that's been excluded from
|
|
36
|
+
# ActiveRecord::AdapterTest. We can run it here with
|
|
37
|
+
# use_transactional_tests set to false.
|
|
38
|
+
# See test/excludes/ActiveRecord/AdapterTest.rb.
|
|
39
|
+
def test_remove_index_when_name_and_wrong_column_name_specified
|
|
40
|
+
index_name = "accounts_idx"
|
|
41
|
+
|
|
42
|
+
@connection.add_index :accounts, :firm_id, name: index_name
|
|
43
|
+
assert_raises ArgumentError do
|
|
44
|
+
@connection.remove_index :accounts, name: index_name, column: :wrong_column_name
|
|
45
|
+
end
|
|
46
|
+
ensure
|
|
47
|
+
@connection.remove_index(:accounts, name: index_name)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# This replaces the same test that's been excluded from
|
|
51
|
+
# ActiveRecord::AdapterTest. We can run it here with
|
|
52
|
+
# use_transactional_tests set to false.
|
|
53
|
+
# See test/excludes/ActiveRecord/AdapterTest.rb.
|
|
54
|
+
def test_remove_index_when_name_and_wrong_column_name_specified_positional_argument
|
|
55
|
+
index_name = "accounts_idx"
|
|
56
|
+
|
|
57
|
+
@connection.add_index :accounts, :firm_id, name: index_name
|
|
58
|
+
assert_raises ArgumentError do
|
|
59
|
+
@connection.remove_index :accounts, :wrong_column_name, name: index_name
|
|
60
|
+
end
|
|
61
|
+
ensure
|
|
62
|
+
@connection.remove_index(:accounts, name: index_name)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
|
|
68
|
+
self.use_transactional_tests = false
|
|
69
|
+
|
|
70
|
+
class Widget < ActiveRecord::Base
|
|
71
|
+
self.primary_key = "widgetid"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def setup
|
|
75
|
+
@connection = ActiveRecord::Base.lease_connection
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
teardown do
|
|
79
|
+
@connection.drop_table :widgets, if_exists: true
|
|
80
|
+
@connection.exec_query("DROP SEQUENCE IF EXISTS widgets_seq")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# This test replaces the excluded test_reset_empty_table_with_custom_pk. We
|
|
84
|
+
# can run the same assertions, but we have to manually create the table so
|
|
85
|
+
# it has a primary key sequence.
|
|
86
|
+
# See test/excludes/ActiveRecord/AdapterTestWithoutTransaction.rb.
|
|
87
|
+
def test_reset_empty_table_with_custom_pk_sequence
|
|
88
|
+
@connection.exec_query("CREATE SEQUENCE widgets_seq")
|
|
89
|
+
@connection.exec_query("
|
|
90
|
+
CREATE TABLE widgets (
|
|
91
|
+
widgetid INT PRIMARY KEY DEFAULT nextval('widgets_seq'),
|
|
92
|
+
name string
|
|
93
|
+
)
|
|
94
|
+
")
|
|
95
|
+
assert_equal 1, Widget.create(name: "weather").id
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_cockroachdb"
|
|
4
|
+
|
|
5
|
+
class CockroachDBCachedPlanFailureTest < ActiveRecord::PostgreSQLTestCase
|
|
6
|
+
FakeResult = Struct.new(:fields) do
|
|
7
|
+
def result_error_field(code)
|
|
8
|
+
fields[code]
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
FakeError = Struct.new(:result)
|
|
12
|
+
|
|
13
|
+
# Reference the adapter's constant so the tests can't drift from the
|
|
14
|
+
# heuristic they are meant to verify.
|
|
15
|
+
CACHED_PLAN_MESSAGE =
|
|
16
|
+
ActiveRecord::ConnectionAdapters::CockroachDBAdapter::CACHED_PLAN_HEURISTIC
|
|
17
|
+
|
|
18
|
+
def setup
|
|
19
|
+
@connection = ActiveRecord::Base.lease_connection
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# CockroachDB has raised this error from "runExecBuilder" (Execute
|
|
23
|
+
# phase) and, since cockroachdb/cockroach#164406, from "execBind"
|
|
24
|
+
# (Bind phase). Detection must not depend on the source function.
|
|
25
|
+
def test_detects_cached_plan_failure_from_any_source_function
|
|
26
|
+
%w[runExecBuilder execBind].each do |source_function|
|
|
27
|
+
pgerror = FakeError.new(FakeResult.new({
|
|
28
|
+
PG::PG_DIAG_SQLSTATE => "0A000",
|
|
29
|
+
PG::PG_DIAG_MESSAGE_PRIMARY => CACHED_PLAN_MESSAGE,
|
|
30
|
+
PG::PG_DIAG_SOURCE_FUNCTION => source_function
|
|
31
|
+
}))
|
|
32
|
+
assert @connection.send(:is_cached_plan_failure?, pgerror),
|
|
33
|
+
"expected cached plan failure to be detected when raised from #{source_function}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_detects_cached_plan_failure_with_wrapped_message
|
|
38
|
+
pgerror = FakeError.new(FakeResult.new({
|
|
39
|
+
PG::PG_DIAG_SQLSTATE => "0A000",
|
|
40
|
+
PG::PG_DIAG_MESSAGE_PRIMARY => "portal \"p1\": #{CACHED_PLAN_MESSAGE}"
|
|
41
|
+
}))
|
|
42
|
+
assert @connection.send(:is_cached_plan_failure?, pgerror)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_ignores_other_feature_not_supported_errors
|
|
46
|
+
pgerror = FakeError.new(FakeResult.new({
|
|
47
|
+
PG::PG_DIAG_SQLSTATE => "0A000",
|
|
48
|
+
PG::PG_DIAG_MESSAGE_PRIMARY => "unimplemented: something else"
|
|
49
|
+
}))
|
|
50
|
+
assert_not @connection.send(:is_cached_plan_failure?, pgerror)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_ignores_cached_plan_message_with_other_sqlstate
|
|
54
|
+
pgerror = FakeError.new(FakeResult.new({
|
|
55
|
+
PG::PG_DIAG_SQLSTATE => "XX000",
|
|
56
|
+
PG::PG_DIAG_MESSAGE_PRIMARY => CACHED_PLAN_MESSAGE
|
|
57
|
+
}))
|
|
58
|
+
assert_not @connection.send(:is_cached_plan_failure?, pgerror)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_returns_false_when_error_fields_unavailable
|
|
62
|
+
assert_not @connection.send(:is_cached_plan_failure?, Object.new)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# End-to-end check that a real cached-plan failure is caught and recovered
|
|
66
|
+
# from against a live server, rather than only exercising the classifier
|
|
67
|
+
# with fabricated errors. Adding a column changes the result type of a
|
|
68
|
+
# cached `SELECT *`, which makes CockroachDB raise FEATURE_NOT_SUPPORTED
|
|
69
|
+
# ("cached plan must not change result type") the next time the prepared
|
|
70
|
+
# statement runs. The adapter must evict the stale statement and retry.
|
|
71
|
+
#
|
|
72
|
+
# Recovery only happens outside a transaction (inside one, the adapter can
|
|
73
|
+
# only raise PreparedStatementCacheExpired), so this test must not run in
|
|
74
|
+
# the suite's wrapping transaction.
|
|
75
|
+
exclude_from_transactional_tests :test_recovers_from_real_cached_plan_failure
|
|
76
|
+
|
|
77
|
+
def test_recovers_from_real_cached_plan_failure
|
|
78
|
+
@connection.execute("DROP TABLE IF EXISTS cached_plan_things")
|
|
79
|
+
@connection.execute("CREATE TABLE cached_plan_things (id INT PRIMARY KEY, a INT)")
|
|
80
|
+
@connection.execute("INSERT INTO cached_plan_things (id, a) VALUES (1, 10)")
|
|
81
|
+
|
|
82
|
+
sql = "SELECT * FROM cached_plan_things WHERE id = $1"
|
|
83
|
+
bind = ActiveRecord::Relation::QueryAttribute.new(
|
|
84
|
+
"id", 1, ActiveRecord::Type::Integer.new
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Prime the server-side prepared statement cache. `prepare: true` forces
|
|
88
|
+
# the prepared-statement code path that contains the recovery logic.
|
|
89
|
+
first = @connection.exec_query(sql, "SQL", [bind], prepare: true)
|
|
90
|
+
assert_equal ["id", "a"], first.columns
|
|
91
|
+
|
|
92
|
+
# Invalidate the cached plan by changing the result type.
|
|
93
|
+
@connection.execute("ALTER TABLE cached_plan_things ADD COLUMN b INT")
|
|
94
|
+
|
|
95
|
+
# Re-running the same prepared statement would raise without recovery;
|
|
96
|
+
# the adapter should transparently flush the stale statement and retry.
|
|
97
|
+
second = assert_nothing_raised do
|
|
98
|
+
@connection.exec_query(sql, "SQL", [bind], prepare: true)
|
|
99
|
+
end
|
|
100
|
+
assert_equal ["id", "a", "b"], second.columns
|
|
101
|
+
assert_equal 1, second.rows.length
|
|
102
|
+
ensure
|
|
103
|
+
@connection.execute("DROP TABLE IF EXISTS cached_plan_things")
|
|
104
|
+
@connection.clear_cache!
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_cockroachdb"
|
|
4
|
+
require "support/connection_helper" # for #reset_connection
|
|
5
|
+
require "support/copy_cat"
|
|
6
|
+
|
|
7
|
+
class CockroachDBReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase
|
|
8
|
+
include ConnectionHelper
|
|
9
|
+
|
|
10
|
+
module ProgrammerMistake
|
|
11
|
+
def execute_batch(sql, name = nil)
|
|
12
|
+
raise ArgumentError, "something is not right." if name.match?(/referential integrity/)
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def setup
|
|
18
|
+
@connection = ActiveRecord::Base.lease_connection
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def teardown
|
|
22
|
+
reset_connection
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
exclude_from_transactional_tests :test_only_catch_active_record_errors_others_bubble_up
|
|
26
|
+
CopyCat.copy_methods(self, ::PostgreSQLReferentialIntegrityTest, :test_only_catch_active_record_errors_others_bubble_up)
|
|
27
|
+
|
|
28
|
+
def test_should_reraise_invalid_foreign_key_exception_and_show_warning
|
|
29
|
+
warning = capture(:stderr) do
|
|
30
|
+
e = assert_raises(ActiveRecord::InvalidForeignKey) do
|
|
31
|
+
@connection.disable_referential_integrity do
|
|
32
|
+
@connection.execute("INSERT INTO authors (name, author_address_id) VALUES ('Mona Chollet', 42)")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
assert_match (/Key \(author_address_id\)=\(42\) is not present in table/), e.message
|
|
36
|
+
end
|
|
37
|
+
assert_match (/WARNING: Rails was not able to disable referential integrity/), warning
|
|
38
|
+
assert_match (/autocommit_before_ddl/), warning
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_no_warning_nor_error_with_autocommit_before_ddl
|
|
42
|
+
@connection.execute("SET SESSION autocommit_before_ddl = 'on'")
|
|
43
|
+
warning = capture(:stderr) do
|
|
44
|
+
@connection.disable_referential_integrity do
|
|
45
|
+
@connection.execute("INSERT INTO authors (name, author_address_id) VALUES ('Mona Chollet', 42)")
|
|
46
|
+
@connection.truncate(:authors)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
assert_predicate warning, :blank?, "expected no warnings but got:\n#{warning}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# `#disable_referential_integrity` drops and re-adds every foreign key using
|
|
53
|
+
# batched DDL. CockroachDB cannot auto-unlock a `schema_locked` table for a
|
|
54
|
+
# multi-statement batch, so the adapter must unlock the affected tables (both
|
|
55
|
+
# the referencing and referenced ones) around the batch and restore their
|
|
56
|
+
# locked state. This must run outside a transaction: the batched DDL only
|
|
57
|
+
# takes the unlocking path when no transaction is open, and toggling
|
|
58
|
+
# `schema_locked` is only allowed in single-statement implicit transactions.
|
|
59
|
+
exclude_from_transactional_tests :test_disable_referential_integrity_unlocks_schema_locked_tables
|
|
60
|
+
def test_disable_referential_integrity_unlocks_schema_locked_tables
|
|
61
|
+
skip "schema_locked requires CockroachDB v25.3+" if @connection.database_version < 25_03_00
|
|
62
|
+
|
|
63
|
+
# `authors.author_address_id` references `author_addresses`, so this covers
|
|
64
|
+
# both the referencing and referenced sides of a foreign key.
|
|
65
|
+
begin
|
|
66
|
+
@connection.execute("ALTER TABLE authors SET (schema_locked = true)")
|
|
67
|
+
@connection.execute("ALTER TABLE author_addresses SET (schema_locked = true)")
|
|
68
|
+
assert schema_locked?(:authors), "precondition: authors should be schema_locked"
|
|
69
|
+
assert schema_locked?(:author_addresses), "precondition: author_addresses should be schema_locked"
|
|
70
|
+
|
|
71
|
+
assert_nothing_raised do
|
|
72
|
+
@connection.disable_referential_integrity { }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
assert schema_locked?(:authors), "authors should be re-locked afterwards"
|
|
76
|
+
assert schema_locked?(:author_addresses), "author_addresses should be re-locked afterwards"
|
|
77
|
+
ensure
|
|
78
|
+
@connection.execute("ALTER TABLE authors SET (schema_locked = false)")
|
|
79
|
+
@connection.execute("ALTER TABLE author_addresses SET (schema_locked = false)")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def schema_locked?(table)
|
|
86
|
+
reloptions = @connection.query_value(<<~SQL)
|
|
87
|
+
SELECT array_to_string(reloptions, ',') FROM pg_class WHERE relname = #{@connection.quote(table.to_s)}
|
|
88
|
+
SQL
|
|
89
|
+
reloptions.to_s.include?("schema_locked=true")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require "cases/helper_cockroachdb"
|
|
2
|
+
|
|
3
|
+
module CockroachDB
|
|
4
|
+
class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
|
|
5
|
+
def setup
|
|
6
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
|
7
|
+
def execute(sql, name = nil) sql end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
teardown do
|
|
12
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
|
13
|
+
remove_method :execute
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# This replaces the same test that's been excluded from
|
|
18
|
+
# PostgresqlActiveSchemaTest. It is almost exactly the same, but it excludes
|
|
19
|
+
# assertions against partial indexes because they're not supported in
|
|
20
|
+
# CockroachDB.
|
|
21
|
+
# See test/excludes/PostgresqlActiveSchemaTest.rb
|
|
22
|
+
def test_add_index
|
|
23
|
+
# add_index calls index_name_exists? which can't work since execute is stubbed
|
|
24
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) { |*| false }
|
|
25
|
+
|
|
26
|
+
expected = %(CREATE UNIQUE INDEX "index_people_on_lower_last_name" ON "people" (lower(last_name)))
|
|
27
|
+
assert_equal expected, add_index(:people, "lower(last_name)", unique: true)
|
|
28
|
+
|
|
29
|
+
expected = %(CREATE UNIQUE INDEX "index_people_on_last_name_varchar_pattern_ops" ON "people" (last_name varchar_pattern_ops))
|
|
30
|
+
assert_equal expected, add_index(:people, "last_name varchar_pattern_ops", unique: true)
|
|
31
|
+
|
|
32
|
+
expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" ("last_name"))
|
|
33
|
+
assert_equal expected, add_index(:people, :last_name, algorithm: :concurrently)
|
|
34
|
+
|
|
35
|
+
expected = %(CREATE INDEX "index_people_on_last_name_and_first_name" ON "people" ("last_name" DESC, "first_name" ASC))
|
|
36
|
+
assert_equal expected, add_index(:people, [:last_name, :first_name], order: { last_name: :desc, first_name: :asc })
|
|
37
|
+
assert_equal expected, add_index(:people, ["last_name", :first_name], order: { last_name: :desc, "first_name" => :asc })
|
|
38
|
+
|
|
39
|
+
%w(gin gist hash btree).each do |type|
|
|
40
|
+
expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
|
|
41
|
+
assert_equal expected, add_index(:people, :last_name, using: type)
|
|
42
|
+
|
|
43
|
+
expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
|
|
44
|
+
assert_equal expected, add_index(:people, :last_name, using: type, algorithm: :concurrently)
|
|
45
|
+
|
|
46
|
+
expected = %(CREATE UNIQUE INDEX "index_people_on_lower_last_name" ON "people" USING #{type} (lower(last_name)))
|
|
47
|
+
assert_equal expected, add_index(:people, "lower(last_name)", using: type, unique: true)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name" bpchar_pattern_ops))
|
|
51
|
+
assert_equal expected, add_index(:people, :last_name, using: :gist, opclass: { last_name: :bpchar_pattern_ops })
|
|
52
|
+
|
|
53
|
+
expected = %(CREATE INDEX "index_people_on_last_name_and_first_name" ON "people" ("last_name" DESC NULLS LAST, "first_name" ASC))
|
|
54
|
+
assert_equal expected, add_index(:people, [:last_name, :first_name], order: { last_name: "DESC NULLS LAST", first_name: :asc })
|
|
55
|
+
|
|
56
|
+
expected = %(CREATE INDEX "index_people_on_last_name" ON "people" ("last_name" NULLS FIRST))
|
|
57
|
+
assert_equal expected, add_index(:people, :last_name, order: "NULLS FIRST")
|
|
58
|
+
|
|
59
|
+
assert_raise ArgumentError do
|
|
60
|
+
add_index(:people, :last_name, algorithm: :copy)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_exists?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
def method_missing(...)
|
|
68
|
+
ActiveRecord::Base.lease_connection.send(...)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper"
|
|
4
|
+
require "cases/helper_cockroachdb"
|
|
5
|
+
|
|
6
|
+
module ActiveRecord
|
|
7
|
+
module CockroachDB
|
|
8
|
+
class Migration
|
|
9
|
+
class PGChangeSchemaTest < ActiveRecord::PostgreSQLTestCase
|
|
10
|
+
self.use_transactional_tests = false
|
|
11
|
+
attr_reader :connection
|
|
12
|
+
|
|
13
|
+
def setup
|
|
14
|
+
super
|
|
15
|
+
@connection = ActiveRecord::Base.lease_connection
|
|
16
|
+
connection.create_table(:strings) do |t|
|
|
17
|
+
t.string :somedate
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def teardown
|
|
22
|
+
connection.drop_table :strings
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_change_string_to_date
|
|
26
|
+
connection.change_column :strings, :somedate, :timestamp, using: 'CAST("somedate" AS timestamp)'
|
|
27
|
+
assert_equal :datetime, connection.columns(:strings).find { |c| c.name == "somedate" }.type
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_change_type_with_symbol
|
|
31
|
+
connection.change_column :strings, :somedate, :timestamp, cast_as: :timestamp
|
|
32
|
+
assert_equal :datetime, connection.columns(:strings).find { |c| c.name == "somedate" }.type
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_change_type_with_symbol_with_timestamptz
|
|
36
|
+
connection.change_column :strings, :somedate, :timestamptz, cast_as: :timestamptz
|
|
37
|
+
assert_equal :timestamptz, connection.columns(:strings).find { |c| c.name == "somedate" }.type
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_change_type_with_symbol_using_datetime
|
|
41
|
+
connection.change_column :strings, :somedate, :datetime, cast_as: :datetime
|
|
42
|
+
assert_equal :datetime, connection.columns(:strings).find { |c| c.name == "somedate" }.type
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_change_type_with_symbol_using_timestamp_with_timestamptz_as_default
|
|
46
|
+
with_cockroachdb_datetime_type(:timestamptz) do
|
|
47
|
+
connection.change_column :strings, :somedate, :timestamp, cast_as: :timestamp
|
|
48
|
+
assert_equal :timestamp, connection.columns(:strings).find { |c| c.name == "somedate" }.type
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_change_type_with_symbol_with_timestamptz_as_default
|
|
53
|
+
with_cockroachdb_datetime_type(:timestamptz) do
|
|
54
|
+
connection.change_column :strings, :somedate, :timestamptz, cast_as: :timestamptz
|
|
55
|
+
assert_equal :datetime, connection.columns(:strings).find { |c| c.name == "somedate" }.type
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_change_type_with_symbol_using_datetime_with_timestamptz_as_default
|
|
60
|
+
with_cockroachdb_datetime_type(:timestamptz) do
|
|
61
|
+
connection.change_column :strings, :somedate, :datetime, cast_as: :datetime
|
|
62
|
+
assert_equal :datetime, connection.columns(:strings).find { |c| c.name == "somedate" }.type
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_change_type_with_array
|
|
67
|
+
connection.change_column :strings, :somedate, :timestamp, array: true, cast_as: :timestamp
|
|
68
|
+
column = connection.columns(:strings).find { |c| c.name == "somedate" }
|
|
69
|
+
assert_equal :datetime, column.type
|
|
70
|
+
assert_predicate column, :array?
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_cockroachdb"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module ActiveRecord
|
|
7
|
+
module CockroachDB
|
|
8
|
+
class PostgresqlConnectionTest < ActiveRecord::PostgreSQLTestCase
|
|
9
|
+
include ConnectionHelper
|
|
10
|
+
|
|
11
|
+
def test_set_session_variable_true
|
|
12
|
+
run_without_connection do |orig_connection|
|
|
13
|
+
ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { null_ordered_last: true }))
|
|
14
|
+
set_true = ActiveRecord::Base.lease_connection.exec_query "SHOW NULL_ORDERED_LAST"
|
|
15
|
+
assert_equal [["on"]], set_true.rows
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_set_session_variable_false
|
|
20
|
+
run_without_connection do |orig_connection|
|
|
21
|
+
ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { null_ordered_last: false }))
|
|
22
|
+
set_false = ActiveRecord::Base.lease_connection.exec_query "SHOW NULL_ORDERED_LAST"
|
|
23
|
+
assert_equal [["off"]], set_false.rows
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_set_session_variable_nil
|
|
28
|
+
run_without_connection do |orig_connection|
|
|
29
|
+
# This should be a no-op that does not raise an error
|
|
30
|
+
assert_nothing_raised do
|
|
31
|
+
ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { null_ordered_last: nil }))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_set_session_variable_default
|
|
37
|
+
run_without_connection do |orig_connection|
|
|
38
|
+
# This should execute a query that does not raise an error
|
|
39
|
+
assert_nothing_raised do
|
|
40
|
+
ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { null_ordered_last: :default }))
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|