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.
Files changed (241) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/CONTRIBUTING.md +10 -17
  4. data/Gemfile +2 -1
  5. data/Rakefile +0 -19
  6. data/lib/active_record/connection_adapters/cockroachdb/column.rb +69 -24
  7. data/lib/active_record/connection_adapters/cockroachdb/database_statements.rb +13 -3
  8. data/lib/active_record/connection_adapters/cockroachdb/database_tasks.rb +1 -1
  9. data/lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb +34 -19
  10. data/lib/active_record/connection_adapters/cockroachdb/quoting.rb +19 -3
  11. data/lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb +137 -51
  12. data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +181 -102
  13. data/lib/active_record/connection_adapters/cockroachdb_adapter.rb +78 -34
  14. data/lib/version.rb +1 -1
  15. data/test/cases/adapter_test.rb +98 -0
  16. data/test/cases/adapters/cockroachdb/cached_plan_failure_test.rb +106 -0
  17. data/test/cases/adapters/cockroachdb/referential_integrity_test.rb +91 -0
  18. data/test/cases/adapters/postgresql/active_schema_test.rb +71 -0
  19. data/test/cases/adapters/postgresql/change_schema_test.rb +75 -0
  20. data/test/cases/adapters/postgresql/connection_test.rb +46 -0
  21. data/test/cases/adapters/postgresql/ddl_test.rb +326 -0
  22. data/test/cases/adapters/postgresql/interval_test.rb +131 -0
  23. data/test/cases/adapters/postgresql/nested_class_test.rb +22 -0
  24. data/test/cases/adapters/postgresql/numeric_test.rb +28 -0
  25. data/test/cases/adapters/postgresql/postgis_test.rb +199 -0
  26. data/test/cases/adapters/postgresql/postgresql_adapter_test.rb +115 -0
  27. data/test/cases/adapters/postgresql/quoting_test.rb +30 -0
  28. data/test/cases/adapters/postgresql/schema_statements_test.rb +29 -0
  29. data/test/cases/adapters/postgresql/serial_test.rb +199 -0
  30. data/test/cases/adapters/postgresql/spatial_queries_test.rb +118 -0
  31. data/test/cases/adapters/postgresql/spatial_setup_test.rb +18 -0
  32. data/test/cases/adapters/postgresql/spatial_type_test.rb +41 -0
  33. data/test/cases/adapters/postgresql/timestamp_test.rb +58 -0
  34. data/test/cases/adapters/postgresql/virtual_column_test.rb +39 -0
  35. data/test/cases/associations/eager_load_nested_include_test.rb +111 -0
  36. data/test/cases/associations/left_outer_join_association_test.rb +30 -0
  37. data/test/cases/associations_test.rb +108 -0
  38. data/test/cases/base_test.rb +31 -0
  39. data/test/cases/comment_test.rb +75 -0
  40. data/test/cases/connection_adapters/type_test.rb +28 -0
  41. data/test/cases/database_configurations/resolver_test.rb +24 -0
  42. data/test/cases/defaults_test.rb +45 -0
  43. data/test/cases/dirty_test.rb +24 -0
  44. data/test/cases/fixtures_test.rb +541 -0
  45. data/test/cases/helper_cockroachdb.rb +232 -0
  46. data/test/cases/inheritance_test.rb +42 -0
  47. data/test/cases/invertible_migration_test.rb +73 -0
  48. data/test/cases/marshal_serialization_test.rb +45 -0
  49. data/test/cases/migration/change_schema_test.rb +140 -0
  50. data/test/cases/migration/check_constraint_test.rb +125 -0
  51. data/test/cases/migration/columns_test.rb +50 -0
  52. data/test/cases/migration/create_join_table_test.rb +66 -0
  53. data/test/cases/migration/foreign_key_test.rb +390 -0
  54. data/test/cases/migration/hidden_column_test.rb +70 -0
  55. data/test/cases/migration/references_foreign_key_test.rb +124 -0
  56. data/test/cases/migration_test.rb +120 -0
  57. data/test/cases/persistence_test.rb +33 -0
  58. data/test/cases/primary_keys_test.rb +134 -0
  59. data/test/cases/relation/aost_test.rb +57 -0
  60. data/test/cases/relation/or_test.rb +26 -0
  61. data/test/cases/relation/table_hints_test.rb +124 -0
  62. data/test/cases/relation_test.rb +26 -0
  63. data/test/cases/relations_test.rb +29 -0
  64. data/test/cases/schema_dumper_test.rb +221 -0
  65. data/test/cases/show_create_test.rb +14 -0
  66. data/test/cases/strict_loading_test.rb +34 -0
  67. data/test/cases/tasks/cockroachdb_rake_test.rb +113 -0
  68. data/test/cases/test_fixtures_test.rb +57 -0
  69. data/test/cases/transactions_test.rb +51 -0
  70. data/test/cases/unsafe_raw_sql_test.rb +22 -0
  71. data/test/config.yml +23 -0
  72. data/test/excludes/ActiveRecord/AdapterTest.rb +3 -0
  73. data/test/excludes/ActiveRecord/AdapterTestWithoutTransaction.rb +25 -0
  74. data/test/excludes/ActiveRecord/ConnectionAdapters/PoolConfig/ResolverTest.rb +1 -0
  75. data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter/BindParameterTest.rb +15 -0
  76. data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter/QuotingTest.rb +22 -0
  77. data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterPreventWritesLegacyTest.rb +1 -0
  78. data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterPreventWritesTest.rb +1 -0
  79. data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterTest.rb +40 -0
  80. data/test/excludes/ActiveRecord/ConnectionAdapters/RegistrationIsolatedTest.rb +14 -0
  81. data/test/excludes/ActiveRecord/Encryption/EncryptionPerformanceTest.rb +1 -0
  82. data/test/excludes/ActiveRecord/Encryption/EnvelopeEncryptionPerformanceTest.rb +1 -0
  83. data/test/excludes/ActiveRecord/Encryption/ExtendedDeterministicQueriesPerformanceTest.rb +1 -0
  84. data/test/excludes/ActiveRecord/Encryption/StoragePerformanceTest.rb +2 -0
  85. data/test/excludes/ActiveRecord/InstrumentationTest.rb +16 -0
  86. data/test/excludes/ActiveRecord/InvertibleMigrationTest.rb +2 -0
  87. data/test/excludes/ActiveRecord/Migration/ChangeSchemaTest.rb +7 -0
  88. data/test/excludes/ActiveRecord/Migration/CheckConstraintTest.rb +6 -0
  89. data/test/excludes/ActiveRecord/Migration/ColumnsTest.rb +4 -0
  90. data/test/excludes/ActiveRecord/Migration/CompatibilityTest.rb +50 -0
  91. data/test/excludes/ActiveRecord/Migration/CompositeForeignKeyTest.rb +2 -0
  92. data/test/excludes/ActiveRecord/Migration/CreateJoinTableTest.rb +2 -0
  93. data/test/excludes/ActiveRecord/Migration/ForeignKeyInCreateTest.rb +1 -0
  94. data/test/excludes/ActiveRecord/Migration/ForeignKeyTest.rb +35 -0
  95. data/test/excludes/ActiveRecord/Migration/InvalidOptionsTest.rb +14 -0
  96. data/test/excludes/ActiveRecord/Migration/PGChangeSchemaTest.rb +8 -0
  97. data/test/excludes/ActiveRecord/Migration/ReferencesForeignKeyTest.rb +7 -0
  98. data/test/excludes/ActiveRecord/Migration/ReferencesIndexTest.rb +7 -0
  99. data/test/excludes/ActiveRecord/Migration/ReferencesStatementsTest.rb +3 -0
  100. data/test/excludes/ActiveRecord/MysqlDBCreateWithInvalidPermissionsTest.rb +1 -0
  101. data/test/excludes/ActiveRecord/OrTest.rb +1 -0
  102. data/test/excludes/ActiveRecord/PostgreSQLStructureDumpTest.rb +10 -0
  103. data/test/excludes/ActiveRecord/PostgresqlConnectionTest.rb +12 -0
  104. data/test/excludes/ActiveRecord/PostgresqlTransactionNestedTest.rb +14 -0
  105. data/test/excludes/ActiveRecord/PostgresqlTransactionTest.rb +4 -0
  106. data/test/excludes/ActiveRecord/RelationTest.rb +2 -0
  107. data/test/excludes/ActiveRecord/TooManyOrTest.rb +1 -0
  108. data/test/excludes/ActiveSupportSubclassWithFixturesTest.rb +1 -0
  109. data/test/excludes/AssociationDeprecationTest/NotifyModeTest.rb +2 -0
  110. data/test/excludes/AssociationDeprecationTest/RaiseBacktraceModeTest.rb +2 -0
  111. data/test/excludes/AssociationDeprecationTest/RaiseModeTest.rb +2 -0
  112. data/test/excludes/AssociationDeprecationTest/WarnBacktraceModeTest.rb +2 -0
  113. data/test/excludes/AssociationDeprecationTest/WarnModeTest.rb +2 -0
  114. data/test/excludes/AssociationDeprecationTest/fix_backtrace_cleaner.rb +10 -0
  115. data/test/excludes/BasicsTest.rb +1 -0
  116. data/test/excludes/BulkAlterTableMigrationsTest.rb +7 -0
  117. data/test/excludes/CalculationsTest.rb +4 -0
  118. data/test/excludes/CommentTest.rb +2 -0
  119. data/test/excludes/CoreTest.rb +1 -0
  120. data/test/excludes/CreateOrFindByWithinTransactions.rb +3 -0
  121. data/test/excludes/DefaultsUsingMultipleSchemasAndDomainTest.rb +7 -0
  122. data/test/excludes/DirtyTest.rb +3 -0
  123. data/test/excludes/EachTest.rb +8 -0
  124. data/test/excludes/EagerLoadPolyAssocsTest.rb +1 -0
  125. data/test/excludes/ExplicitlyNamedIndexMigrationTest.rb +1 -0
  126. data/test/excludes/FixturesResetPkSequenceTest.rb +3 -0
  127. data/test/excludes/FixturesTest.rb +21 -0
  128. data/test/excludes/FixturesWithForeignKeyViolationsTest.rb +2 -0
  129. data/test/excludes/ForeignTableTest.rb +10 -0
  130. data/test/excludes/InheritanceComputeTypeTest.rb +1 -0
  131. data/test/excludes/LeftOuterJoinAssociationTest.rb +2 -0
  132. data/test/excludes/LegacyPrimaryKeyTest/V4_2.rb +6 -0
  133. data/test/excludes/LegacyPrimaryKeyTest/V5_0.rb +6 -0
  134. data/test/excludes/MarshalSerializationTest.rb +4 -0
  135. data/test/excludes/MaterializedViewTest.rb +13 -0
  136. data/test/excludes/MigrationTest.rb +2 -0
  137. data/test/excludes/MultiDbMigratorTest.rb +2 -0
  138. data/test/excludes/NestedRelationScopingTest.rb +1 -0
  139. data/test/excludes/OrTest.rb +1 -0
  140. data/test/excludes/PersistenceTest.rb +3 -0
  141. data/test/excludes/PessimisticLockingTest.rb +1 -0
  142. data/test/excludes/PostgreSQLExplainTest.rb +7 -0
  143. data/test/excludes/PostgreSQLGeometricLineTest.rb +3 -0
  144. data/test/excludes/PostgreSQLGeometricTypesTest.rb +7 -0
  145. data/test/excludes/PostgreSQLPartitionsTest.rb +1 -0
  146. data/test/excludes/PostgreSQLReferentialIntegrityTest.rb +14 -0
  147. data/test/excludes/PostgresqlArrayTest.rb +21 -0
  148. data/test/excludes/PostgresqlBigSerialTest.rb +7 -0
  149. data/test/excludes/PostgresqlBitStringTest.rb +2 -0
  150. data/test/excludes/PostgresqlByteaTest.rb +1 -0
  151. data/test/excludes/PostgresqlCitextTest.rb +7 -0
  152. data/test/excludes/PostgresqlCollationTest.rb +5 -0
  153. data/test/excludes/PostgresqlCompositeTest.rb +2 -0
  154. data/test/excludes/PostgresqlCompositeWithCustomOIDTest.rb +2 -0
  155. data/test/excludes/PostgresqlDataTypeTest.rb +9 -0
  156. data/test/excludes/PostgresqlDefaultExpressionTest.rb +1 -0
  157. data/test/excludes/PostgresqlDeferredConstraintsTest.rb +3 -0
  158. data/test/excludes/PostgresqlDomainTest.rb +2 -0
  159. data/test/excludes/PostgresqlExtensionMigrationTest.rb +5 -0
  160. data/test/excludes/PostgresqlFullTextTest.rb +3 -0
  161. data/test/excludes/PostgresqlGeometricTest.rb +4 -0
  162. data/test/excludes/PostgresqlHstoreTest.rb +45 -0
  163. data/test/excludes/PostgresqlInfinityTest.rb +5 -0
  164. data/test/excludes/PostgresqlIntervalTest.rb +1 -0
  165. data/test/excludes/PostgresqlJSONBTest.rb +27 -0
  166. data/test/excludes/PostgresqlJSONTest.rb +27 -0
  167. data/test/excludes/PostgresqlLtreeTest.rb +4 -0
  168. data/test/excludes/PostgresqlMoneyTest.rb +12 -0
  169. data/test/excludes/PostgresqlNetworkTest.rb +69 -0
  170. data/test/excludes/PostgresqlNumberTest.rb +1 -0
  171. data/test/excludes/PostgresqlPointTest.rb +15 -0
  172. data/test/excludes/PostgresqlRangeTest.rb +3 -0
  173. data/test/excludes/PostgresqlRenameTableTest.rb +2 -0
  174. data/test/excludes/PostgresqlSerialTest.rb +7 -0
  175. data/test/excludes/PostgresqlTimestampFixtureTest.rb +2 -0
  176. data/test/excludes/PostgresqlTimestampMigrationTest.rb +3 -0
  177. data/test/excludes/PostgresqlTypeLookupTest.rb +2 -0
  178. data/test/excludes/PostgresqlUUIDGenerationTest.rb +7 -0
  179. data/test/excludes/PostgresqlUUIDTest.rb +1 -0
  180. data/test/excludes/PostgresqlVirtualColumnTest.rb +17 -0
  181. data/test/excludes/PostgresqlXMLTest.rb +5 -0
  182. data/test/excludes/PrimaryKeyIntegerNilDefaultTest.rb +1 -0
  183. data/test/excludes/PrimaryKeyIntegerTest.rb +3 -0
  184. data/test/excludes/PrimaryKeysTest.rb +2 -0
  185. data/test/excludes/QueryCacheExpiryTest.rb +10 -0
  186. data/test/excludes/RelationMergingTest.rb +15 -0
  187. data/test/excludes/RelationTest.rb +3 -0
  188. data/test/excludes/ReservedWordsMigrationTest.rb +1 -0
  189. data/test/excludes/SameNameDifferentDatabaseFixturesTest.rb +1 -0
  190. data/test/excludes/SanitizeTest.rb +13 -0
  191. data/test/excludes/SchemaAuthorizationTest.rb +8 -0
  192. data/test/excludes/SchemaCreateTableOptionsTest.rb +2 -0
  193. data/test/excludes/SchemaDumperDefaultsTest.rb +1 -0
  194. data/test/excludes/SchemaDumperTest.rb +11 -0
  195. data/test/excludes/SchemaForeignKeyTest.rb +1 -0
  196. data/test/excludes/SchemaIndexNullsOrderTest.rb +2 -0
  197. data/test/excludes/SchemaIndexOpclassTest.rb +3 -0
  198. data/test/excludes/SchemaTest.rb +49 -0
  199. data/test/excludes/SequenceNameDetectionTestCases/CollidedSequenceNameTest.rb +1 -0
  200. data/test/excludes/SequenceNameDetectionTestCases/LongerSequenceNameDetectionTest.rb +1 -0
  201. data/test/excludes/StrictLoadingFixturesTest.rb +1 -0
  202. data/test/excludes/TestFixturesTest.rb +1 -0
  203. data/test/excludes/TransactionInstrumentationTest.rb +1 -0
  204. data/test/excludes/TransactionIsolationTest.rb +1 -0
  205. data/test/excludes/TypeTest.rb +1 -0
  206. data/test/excludes/UniquenessValidationTest.rb +2 -0
  207. data/test/excludes/UnloggedTablesTest.rb +3 -0
  208. data/test/excludes/UnsafeRawSqlTest.rb +2 -0
  209. data/test/excludes/UpdateableViewTest.rb +5 -0
  210. data/test/excludes/WithAnnotationsTest.rb +6 -0
  211. data/test/models/building.rb +4 -0
  212. data/test/models/spatial_model.rb +5 -0
  213. data/test/schema/cockroachdb_specific_schema.rb +218 -0
  214. data/test/support/copy_cat.rb +83 -0
  215. data/test/support/exclude_from_transactional_tests.rb +33 -0
  216. data/test/support/paths_cockroachdb.rb +46 -0
  217. data/test/support/rake_helpers.rb +37 -0
  218. data/test/support/sql_logger.rb +55 -0
  219. data/test/support/template_creator.rb +114 -0
  220. metadata +422 -30
  221. data/.editorconfig +0 -7
  222. data/.github/issue_template.md +0 -46
  223. data/.github/reproduction_scripts/migrations.rb +0 -60
  224. data/.github/reproduction_scripts/models_and_database.rb +0 -49
  225. data/.github/workflows/ci.yml +0 -96
  226. data/.github/workflows/docker.yml +0 -57
  227. data/.gitignore +0 -18
  228. data/.gitmodules +0 -0
  229. data/activerecord-cockroachdb-adapter.gemspec +0 -38
  230. data/bin/console +0 -36
  231. data/bin/console_schemas/default.rb +0 -11
  232. data/bin/console_schemas/schemas.rb +0 -23
  233. data/bin/setup +0 -8
  234. data/bin/start-cockroachdb +0 -33
  235. data/build/Dockerfile +0 -14
  236. data/build/config.teamcity.yml +0 -31
  237. data/build/local-test.sh +0 -32
  238. data/build/teamcity-test.sh +0 -76
  239. data/docker.sh +0 -35
  240. data/lib/active_record/connection_adapters/cockroachdb/spatial_column_info.rb +0 -60
  241. 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