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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb3247022d4dd7a36aaf82f09bfe1502c9106f570b14e6d09c9cd1076c2b8dc6
4
- data.tar.gz: 0a8deb6f3b4741296899a08c1fc29df3afd0dcaf946538600e4e39e7f5e98170
3
+ metadata.gz: 3f8176b5dc25df91e28dc937133083fc0640d7485059e3a098300032e0cf7610
4
+ data.tar.gz: 10448b0a9437ca1b28a8ef104e5e838e2484d3a085668f3c5ef7630463af0b6b
5
5
  SHA512:
6
- metadata.gz: 48c6242571df5e27fa2bc30f5a699c64bbe4d3f37c47d3ab3deedbc39050470636a664df51c1c47b74e970b6950b995746ee715e44818c91bcd1d832b400bb8f
7
- data.tar.gz: 82169f6e8946a5c6ecbd82001b7fa2e163bc11f739946c184ca4aedbdb95a700e5522fa304ed302050d70e26fb61a65162cfdc8f4be934f96c31c386b8bca5d5
6
+ metadata.gz: 613aaf5e74fd644104aaf407894ab006ae2f46d0684920407820c83b3a7402879e90c907fd06a8d158204d66d3c7af5100e266cb2ff17979553c5fd94935c5d2
7
+ data.tar.gz: 96d18f7427ecec5820c579bf579bc8567178bc876c459822d8d68ce9d31dbabfd82a5a5f18b54dab79f2402ff15ea507182706eb62337d82f486c3886f81dc66
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Ongoing
4
4
 
5
+ ## 8.1.1 - 2026-08-18
6
+
7
+ - Disabled `schema_locked` by default on connect for CockroachDB v25.3+ and unlocked tables around batched foreign key changes ([#404](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/404))
8
+ - Improved error classification by detecting cached plan failures from the error message instead of the source function ([#403](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/403))
9
+ - Stopped advertising support for restarting database transactions ([#398](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/398))
10
+ - Fixed enum columns being misdetected as spatial columns ([#396](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/396))
11
+
12
+ ## 8.1.0 - 2025-11-25
13
+ - Add support for Rails 8.1 ([#386](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/386))
14
+
5
15
  ## 8.0.3 - 2025-08-19
6
16
 
7
17
  - Fixed the handling of column comments that end in a single quote ([#382](https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/382))
data/CONTRIBUTING.md CHANGED
@@ -61,23 +61,16 @@ Only do it if you know the schema was left in a correct state.
61
61
 
62
62
  ### Run Tests from a Backup
63
63
 
64
- Loading the full test schema every time a test runs can take a while, so for cases where loading the schema sequentially is unimportant, it is possible to use a backup to set up the database. This is significantly faster than the standard method and is provided to run individual tests faster, but should not be used to validate a build.
65
-
66
- First create the template database.
67
-
68
- ```bash
69
- bundle exec rake db:create_test_template
70
- ```
71
-
72
- This will create a template database for the current version (ex. `activerecord_test_template611` for version 6.1.1) and create a `BACKUP` in the `nodelocal://self/activerecord-crdb-adapter/#{activerecord_version}` directory.
73
-
74
- To load from the template, use the `COCKROACH_LOAD_FROM_TEMPLATE` flag.
75
-
76
- ```bash
77
- COCKROACH_LOAD_FROM_TEMPLATE=1 TEST_FILES="test/cases/adapters/postgresql/ddl_test.rb" bundle exec rake test
78
- ```
79
-
80
- And the `activerecord_unittest` database will use the `RESTORE` command to load the schema from the template database.
64
+ Loading the full test schema every time a test runs can take
65
+ a while, so for cases where loading the schema sequentially
66
+ is unimportant, it is possible to use a backup to set up the
67
+ database. This is significantly faster than the standard
68
+ method and is provided to run individual tests faster, but
69
+ should not be used to validate a build.
70
+
71
+ To do so, just set the env variable `COCKROACH_LOAD_FROM_TEMPLATE`.
72
+ First run will generate and cache a template, latter runs will use
73
+ it.
81
74
 
82
75
  # Improvements
83
76
 
data/Gemfile CHANGED
@@ -52,10 +52,11 @@ group :development, :test do
52
52
  gem "msgpack", ">= 1.7.0"
53
53
  gem "mutex_m", "~> 0.2.0"
54
54
 
55
+ gem "tracer"
55
56
  gem "rake"
56
57
  gem "debug"
58
+ gem "minitest-bisect", github: "BuonOmo/minitest-bisect", branch: "main"
57
59
  gem "minitest-excludes", "~> 2.0.1"
58
- gem "minitest-github_action_reporter", github: "BuonOmo/minitest-github_action_reporter", require: "minitest/github_action_reporter_plugin"
59
60
  gem "ostruct", "~> 0.6"
60
61
 
61
62
  # Gems used for tests meta-programming.
data/Rakefile CHANGED
@@ -2,28 +2,9 @@ require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
  require_relative 'test/support/paths_cockroachdb'
4
4
  require_relative 'test/support/rake_helpers'
5
- require_relative 'test/support/template_creator'
6
5
 
7
6
  task default: [:test]
8
7
 
9
- namespace :db do
10
- task "create_test_template" do
11
- ENV['DEBUG_COCKROACHDB_ADAPTER'] = "1"
12
- ENV['COCKROACH_SKIP_LOAD_SCHEMA'] = "1"
13
-
14
- TemplateCreator.connect
15
- require_relative 'test/cases/helper'
16
-
17
- # TODO: look into this more, but for some reason the blob alias
18
- # is not defined while running this task.
19
- ActiveRecord::ConnectionAdapters::CockroachDB::TableDefinition.class_eval do
20
- alias :blob :binary
21
- end
22
-
23
- TemplateCreator.create_test_template
24
- end
25
- end
26
-
27
8
  Rake::TestTask.new do |t|
28
9
  t.libs = ARTest::CockroachDB.test_load_paths
29
10
  t.test_files = RakeHelpers.test_files
@@ -20,33 +20,32 @@ module ActiveRecord
20
20
  class Column < PostgreSQL::Column
21
21
  # most functions taken from activerecord-postgis-adapter spatial_column
22
22
  # https://github.com/rgeo/activerecord-postgis-adapter/blob/master/lib/active_record/connection_adapters/postgis/spatial_column.rb
23
- def initialize(name, default, sql_type_metadata = nil, null = true,
23
+ def initialize(name, cast_type, default, sql_type_metadata = nil, null = true,
24
24
  default_function = nil, collation: nil, comment: nil, identity: nil,
25
- serial: nil, spatial: nil, generated: nil, hidden: nil)
26
- @sql_type_metadata = sql_type_metadata
27
- @geographic = !!(sql_type_metadata.sql_type =~ /geography\(/i)
25
+ serial: nil, generated: nil, hidden: nil)
26
+
27
+ super(name, cast_type, default, sql_type_metadata, null, default_function,
28
+ collation: collation, comment: comment, serial: serial, generated: generated, identity: identity)
29
+
28
30
  @hidden = hidden
29
31
 
30
- if spatial
31
- # This case comes from an entry in the geometry_columns table
32
- set_geometric_type_from_name(spatial[:type])
33
- @srid = spatial[:srid].to_i
34
- @has_z = !!spatial[:has_z]
35
- @has_m = !!spatial[:has_m]
36
- elsif @geographic
32
+ return unless spatial?
33
+
34
+ @geographic = sql_type_metadata.sql_type.match?(/geography\(/i)
35
+
36
+ if @geographic
37
37
  # Geographic type information is embedded in the SQL type
38
38
  @srid = 4326
39
39
  @has_z = @has_m = false
40
40
  build_from_sql_type(sql_type_metadata.sql_type)
41
- elsif sql_type =~ /geography|geometry|point|linestring|polygon/i
41
+ elsif sql_type.match?(/geography|geometry|point|linestring|polygon/i)
42
42
  build_from_sql_type(sql_type_metadata.sql_type)
43
- elsif sql_type_metadata.sql_type =~ /geography|geometry|point|linestring|polygon/i
43
+ elsif sql_type_metadata.sql_type.match?(/geography|geometry|point|linestring|polygon/i)
44
44
  # A geometry column with no geometry_columns entry.
45
45
  # @geometric_type = geo_type_from_sql_type(sql_type)
46
46
  build_from_sql_type(sql_type_metadata.sql_type)
47
47
  end
48
- super(name, default, sql_type_metadata, null, default_function,
49
- collation: collation, comment: comment, serial: serial, generated: generated, identity: identity)
48
+
50
49
  if spatial? && @srid
51
50
  @limit = { srid: @srid, type: to_type_name(geometric_type) }
52
51
  @limit[:has_z] = true if @has_z
@@ -59,20 +58,18 @@ module ActiveRecord
59
58
  :geometric_type,
60
59
  :has_m,
61
60
  :has_z,
62
- :srid
61
+ :srid,
62
+ :hidden
63
63
 
64
64
  alias geographic? geographic
65
65
  alias has_z? has_z
66
66
  alias has_m? has_m
67
+ alias hidden? hidden
67
68
 
68
69
  def limit
69
70
  spatial? ? @limit : super
70
71
  end
71
72
 
72
- def hidden?
73
- @hidden
74
- end
75
-
76
73
  def spatial?
77
74
  %i[geometry geography].include?(@sql_type_metadata.type)
78
75
  end
@@ -81,15 +78,63 @@ module ActiveRecord
81
78
  default_function == 'unique_rowid()'
82
79
  end
83
80
 
84
- private
81
+ # TODO: add tests (see #390)
82
+ def init_with(coder)
83
+ @geographic = coder["geographic"]
84
+ @geometric_type = coder["geometric_type"]
85
+ @has_m = coder["has_m"]
86
+ @has_z = coder["has_z"]
87
+ @srid = coder["srid"]
88
+ @hidden = coder["hidden"]
89
+ @limit = coder["limit"]
90
+ super
91
+ end
92
+
93
+ # TODO: add tests (see #390)
94
+ def encode_with(coder)
95
+ coder["geographic"] = @geographic
96
+ coder["geometric_type"] = @geometric_type
97
+ coder["has_m"] = @has_m
98
+ coder["has_z"] = @has_z
99
+ coder["srid"] = @srid
100
+ coder["hidden"] = @hidden
101
+ coder["limit"] = @limit
102
+ super
103
+ end
104
+
105
+ # TODO: add tests (see #390)
106
+ def ==(other)
107
+ other.is_a?(Column) &&
108
+ super &&
109
+ other.geographic == geographic &&
110
+ other.geometric_type == geometric_type &&
111
+ other.has_m == has_m &&
112
+ other.has_z == has_z &&
113
+ other.srid == srid &&
114
+ other.hidden == hidden &&
115
+ other.limit == limit
85
116
 
86
- def set_geometric_type_from_name(name)
87
- @geometric_type = RGeo::ActiveRecord.geometric_type_from_name(name) || RGeo::Feature::Geometry
88
117
  end
118
+ alias :eql? :==
119
+
120
+ # TODO: add tests (see #390)
121
+ def hash
122
+ Column.hash ^
123
+ super.hash ^
124
+ geographic.hash ^
125
+ geometric_type.hash ^
126
+ has_m.hash ^
127
+ has_z.hash ^
128
+ srid.hash ^
129
+ hidden.hash ^
130
+ limit.hash
131
+ end
132
+
133
+ private
89
134
 
90
135
  def build_from_sql_type(sql_type)
91
136
  geo_type, @srid, @has_z, @has_m = OID::Spatial.parse_sql_type(sql_type)
92
- set_geometric_type_from_name(geo_type)
137
+ @geometric_type = RGeo::ActiveRecord.geometric_type_from_name(geo_type) || RGeo::Feature::Geometry
93
138
  end
94
139
 
95
140
  def to_type_name(geometric_type)
@@ -22,15 +22,25 @@ module ActiveRecord
22
22
  def insert_fixtures_set(fixture_set, tables_to_delete = [])
23
23
  fixture_inserts = build_fixture_statements(fixture_set)
24
24
  table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
25
- statements = table_deletes + fixture_inserts
25
+ statements = (table_deletes + fixture_inserts).join(";")
26
26
 
27
+ # Since [rails pull request #52428][1], `#execute_batch` does not
28
+ # trigger a cache clear anymore. However, `#insert_fixtures_set`
29
+ # relies on that clear to ensure consistency. In the postgresql
30
+ # adapter, this is ensured by a call to `#execute` rather than
31
+ # `#execute_batch` in `#disable_referential_integrity`. Since
32
+ # we are not always calling `#disable_referential_integrity`,
33
+ # we need to ensure that the cache is cleared when running
34
+ # our statements by calling `#execute` instead of `#execute_batch`.
35
+ #
36
+ # [1]: https://github.com/rails/rails/pull/52428
27
37
  begin # much faster without disabling referential integrity, worth trying.
28
38
  transaction(requires_new: true) do
29
- execute_batch(statements, "Fixtures Load")
39
+ execute(statements, "Fixtures Load")
30
40
  end
31
41
  rescue
32
42
  disable_referential_integrity do
33
- execute_batch(statements, "Fixtures Load")
43
+ execute(statements, "Fixtures Load")
34
44
  end
35
45
  end
36
46
  end
@@ -74,7 +74,7 @@ module ActiveRecord
74
74
  "https://github.com/cockroachdb/activerecord-cockroachdb-adapter/issues/new"
75
75
  end
76
76
 
77
- run_cmd("cockroach", ["sql", "--set", "errexit=false", "--file", filename], "loading")
77
+ run_cmd("cockroach", "sql", "--set", "errexit=false", "--file", filename)
78
78
  end
79
79
 
80
80
  private
@@ -26,9 +26,22 @@ module ActiveRecord
26
26
  # "geometry(Polygon,4326) NOT NULL"
27
27
  # "geometry(Geography,4326)"
28
28
  def initialize(oid, sql_type)
29
- @sql_type = sql_type
30
- @geo_type, @srid, @has_z, @has_m = self.class.parse_sql_type(sql_type)
29
+ super()
30
+ @sql_type = sql_type.freeze
31
+ @factory_attrs = self.class
32
+ .parse_sql_type(sql_type)
33
+ .then { |geo_type, srid, has_z, has_m|
34
+ {
35
+ geo_type: geo_type.underscore.freeze,
36
+ srid: srid.freeze,
37
+ has_z: has_z.freeze,
38
+ has_m: has_m.freeze,
39
+ sql_type: type.to_s.freeze
40
+ }
41
+ }
42
+ .freeze
31
43
  end
44
+ protected attr_reader :sql_type, :factory_attrs
32
45
 
33
46
  # sql_type: geometry, geometry(Point), geometry(Point,4326), ...
34
47
  #
@@ -59,15 +72,8 @@ module ActiveRecord
59
72
  [geo_type, srid, has_z, has_m]
60
73
  end
61
74
 
62
- def spatial_factory
63
- @spatial_factory ||=
64
- RGeo::ActiveRecord::SpatialFactoryStore.instance.factory(
65
- factory_attrs
66
- )
67
- end
68
-
69
75
  def geographic?
70
- @sql_type =~ /geography/
76
+ @sql_type.start_with?("geography")
71
77
  end
72
78
 
73
79
  def spatial?
@@ -92,6 +98,19 @@ module ActiveRecord
92
98
  .generate(geo_value)
93
99
  end
94
100
 
101
+ # TODO: add tests (see #390)
102
+ def ==(other)
103
+ super &&
104
+ @sql_type == other.sql_type &&
105
+ @factory_attrs == other.factory_attrs
106
+ end
107
+ alias eql? ==
108
+
109
+ # TODO: add tests (see #390)
110
+ def hash
111
+ super ^ [@sql_type, @factory_attrs].hash
112
+ end
113
+
95
114
  private
96
115
 
97
116
  def cast_value(value)
@@ -108,7 +127,7 @@ module ActiveRecord
108
127
  end
109
128
 
110
129
  def binary_string?(string)
111
- string[0] == "\x00" || string[0] == "\x01" || string[0, 4] =~ /[0-9a-fA-F]{4}/
130
+ string[0] == "\x00" || string[0] == "\x01" || string[0, 4].match?(/[0-9a-fA-F]{4}/)
112
131
  end
113
132
 
114
133
  def wkt_parser(string)
@@ -119,14 +138,10 @@ module ActiveRecord
119
138
  end
120
139
  end
121
140
 
122
- def factory_attrs
123
- {
124
- geo_type: @geo_type.underscore,
125
- has_m: @has_m,
126
- has_z: @has_z,
127
- srid: @srid,
128
- sql_type: type.to_s
129
- }
141
+ def spatial_factory
142
+ RGeo::ActiveRecord::SpatialFactoryStore.instance.factory(
143
+ factory_attrs
144
+ )
130
145
  end
131
146
  end
132
147
  end
@@ -34,7 +34,8 @@ module ActiveRecord
34
34
  # but when creating objects, using RGeo features is more convenient than
35
35
  # converting to WKB, so this does it automatically.
36
36
  def quote(value)
37
- if value.is_a?(Numeric)
37
+ case value
38
+ when Numeric
38
39
  # NOTE: The fact that integers are quoted is important and helps
39
40
  # mitigate a potential vulnerability.
40
41
  #
@@ -42,9 +43,9 @@ module ActiveRecord
42
43
  # - https://nvd.nist.gov/vuln/detail/CVE-2022-44566
43
44
  # - https://github.com/cockroachdb/activerecord-cockroachdb-adapter/pull/280#discussion_r1288692977
44
45
  "'#{quote_string(value.to_s)}'"
45
- elsif RGeo::Feature::Geometry.check_type(value)
46
+ when RGeo::Feature::Geometry
46
47
  "'#{RGeo::WKRep::WKBGenerator.new(hex_format: true, type_format: :ewkb, emit_ewkb_srid: true).generate(value)}'"
47
- elsif value.is_a?(RGeo::Cartesian::BoundingBox)
48
+ when RGeo::Cartesian::BoundingBox
48
49
  "'#{value.min_x},#{value.min_y},#{value.max_x},#{value.max_y}'::box"
49
50
  else
50
51
  super
@@ -58,6 +59,21 @@ module ActiveRecord
58
59
  # This is tested by `BasicsTest#test_default_in_local_time`.
59
60
  super + value.strftime("%z")
60
61
  end
62
+
63
+ # NOTE: This method should be private in future rails versions.
64
+ # Hence we should also make it private then.
65
+ #
66
+ # See https://github.com/rails/rails/blob/v8.1.1/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb#L190
67
+ def lookup_cast_type(sql_type)
68
+ type_map.lookup(
69
+ # oid
70
+ query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i,
71
+ # fmod, not needed.
72
+ nil,
73
+ # details needed for `..::CockroachDB::OID::Spatial` (e.g. `geometry(point,3857)`)
74
+ sql_type
75
+ )
76
+ end
61
77
  end
62
78
  end
63
79
  end
@@ -34,8 +34,47 @@ module ActiveRecord
34
34
  end
35
35
 
36
36
  def disable_referential_integrity
37
- foreign_keys = all_foreign_keys
37
+ if transaction_open? && query_value("SHOW autocommit_before_ddl") == "off"
38
+ begin
39
+ yield
40
+ rescue ActiveRecord::InvalidForeignKey => e
41
+ warn <<-WARNING
42
+ WARNING: Rails was not able to disable referential integrity.
43
+
44
+ This is due to CockroachDB's need of committing transactions
45
+ before a schema change occurs. To bypass this, you can set
46
+ `autocommit_before_ddl: "on"` in your database configuration.
47
+ WARNING
48
+ raise e
49
+ end
50
+ else
51
+ foreign_keys = all_foreign_keys
52
+
53
+ remove_foreign_keys(foreign_keys)
54
+
55
+ # Prefixes and suffixes are added in add_foreign_key
56
+ # in AR7+ so we need to temporarily disable them here,
57
+ # otherwise prefixes/suffixes will be erroneously added.
58
+ old_prefix = ActiveRecord::Base.table_name_prefix
59
+ old_suffix = ActiveRecord::Base.table_name_suffix
60
+
61
+ begin
62
+ yield
63
+ ensure
64
+ ActiveRecord::Base.table_name_prefix = ""
65
+ ActiveRecord::Base.table_name_suffix = ""
38
66
 
67
+ add_foreign_keys(foreign_keys) # Never raises.
68
+
69
+ ActiveRecord::Base.table_name_prefix = old_prefix if defined?(old_prefix)
70
+ ActiveRecord::Base.table_name_suffix = old_suffix if defined?(old_suffix)
71
+ end
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def remove_foreign_keys(foreign_keys)
39
78
  statements = foreign_keys.map do |foreign_key|
40
79
  # We do not use the `#remove_foreign_key` method here because it
41
80
  # checks for foreign keys existance in the schema cache. This method
@@ -45,48 +84,85 @@ module ActiveRecord
45
84
 
46
85
  schema_creation.accept(at)
47
86
  end
48
- execute_batch(statements, "Disable referential integrity -> remove foreign keys")
49
-
50
- yield
87
+ with_schema_unlocked(foreign_keys.flat_map { |fk| [fk.from_table, fk.to_table] }) do
88
+ execute_batch(statements, "Disable referential integrity -> remove foreign keys")
89
+ end
90
+ end
51
91
 
52
- # Prefixes and suffixes are added in add_foreign_key
53
- # in AR7+ so we need to temporarily disable them here,
54
- # otherwise prefixes/suffixes will be erroneously added.
55
- old_prefix = ActiveRecord::Base.table_name_prefix
56
- old_suffix = ActiveRecord::Base.table_name_suffix
92
+ # NOTE: This method should never raise, otherwise we risk polluting table name
93
+ # prefixes and suffixes. The good thing is: if this happens, tests will crash
94
+ # hard, no way we miss it.
95
+ def add_foreign_keys(foreign_keys)
96
+ # We avoid using `foreign_key_exists?` here because it checks the schema cache
97
+ # for every key. This method is performance critical for the test suite, hence
98
+ # we use the `#all_foreign_keys` method that only make one query to the database.
99
+ already_inserted_foreign_keys = all_foreign_keys
100
+ foreign_keys_to_add = foreign_keys.reject do |foreign_key|
101
+ already_inserted_foreign_keys.any? { |fk| fk.from_table == foreign_key.from_table && fk.options[:name] == foreign_key.options[:name] }
102
+ end
103
+ statements = foreign_keys_to_add.map do |foreign_key|
104
+ options = foreign_key_options(foreign_key.from_table, foreign_key.to_table, foreign_key.options)
105
+ at = create_alter_table foreign_key.from_table
106
+ at.add_foreign_key foreign_key.to_table, options
57
107
 
58
- ActiveRecord::Base.table_name_prefix = ""
59
- ActiveRecord::Base.table_name_suffix = ""
108
+ schema_creation.accept(at)
109
+ end
110
+ with_schema_unlocked(foreign_keys_to_add.flat_map { |fk| [fk.from_table, fk.to_table] }) do
111
+ execute_batch(statements, "Disable referential integrity -> add foreign keys")
112
+ end
113
+ end
60
114
 
115
+ # Starting in CockroachDB v26.x, tables are created with the
116
+ # `schema_locked` storage parameter enabled by default (it improves
117
+ # changefeed performance). CockroachDB transparently unlocks a table to
118
+ # run a single-statement DDL, but it cannot do so for the multi-statement
119
+ # batches used to drop and re-add foreign keys above: it raises instead
120
+ # of unlocking automatically. So we unlock the affected tables ourselves,
121
+ # run the batch, then restore their locked state. Adding a foreign key
122
+ # also writes a back-reference into the referenced table, so callers must
123
+ # pass both the referencing and referenced tables.
124
+ #
125
+ # See https://www.cockroachlabs.com/docs/stable/schema-locked
126
+ def with_schema_unlocked(tables)
127
+ locked = schema_locked_tables(tables.uniq)
128
+ return yield if locked.empty?
129
+
130
+ set_schema_locked(locked, false)
61
131
  begin
62
- # Avoid having PG:DuplicateObject error if a test is ran in transaction.
63
- # TODO: verify that there is no cache issue related to running this (e.g: fk
64
- # still in cache but not in db)
65
- #
66
- # We avoid using `foreign_key_exists?` here because it checks the schema cache
67
- # for every key. This method is performance critical for the test suite, hence
68
- # we use the `#all_foreign_keys` method that only make one query to the database.
69
- already_inserted_foreign_keys = all_foreign_keys
70
- statements = foreign_keys.map do |foreign_key|
71
- next if already_inserted_foreign_keys.any? { |fk| fk.from_table == foreign_key.from_table && fk.options[:name] == foreign_key.options[:name] }
72
-
73
- options = foreign_key_options(foreign_key.from_table, foreign_key.to_table, foreign_key.options)
74
- at = create_alter_table foreign_key.from_table
75
- at.add_foreign_key foreign_key.to_table, options
76
-
77
- schema_creation.accept(at)
78
- end
79
- execute_batch(statements.compact, "Disable referential integrity -> add foreign keys")
132
+ yield
80
133
  ensure
81
- ActiveRecord::Base.table_name_prefix = old_prefix
82
- ActiveRecord::Base.table_name_suffix = old_suffix
134
+ set_schema_locked(locked, true)
83
135
  end
84
136
  end
85
137
 
86
- private
138
+ # Returns the subset of +tables+ whose `schema_locked` storage parameter
139
+ # is currently enabled. CockroachDB exposes storage parameters through
140
+ # `pg_class.reloptions`. On versions (or configurations) that do not lock
141
+ # tables this returns an empty array, so callers stay on the fast path.
142
+ def schema_locked_tables(tables)
143
+ return [] if tables.empty?
144
+
145
+ locked = query_values(<<~SQL, "SCHEMA")
146
+ SELECT (CASE WHEN n.nspname = current_schema() THEN '' ELSE n.nspname || '.' END) || c.relname
147
+ FROM pg_class c
148
+ JOIN pg_namespace n ON n.oid = c.relnamespace
149
+ WHERE c.relkind = 'r' AND 'schema_locked=true' = ANY (c.reloptions)
150
+ SQL
151
+ tables & locked
152
+ end
153
+
154
+ # Toggles the `schema_locked` storage parameter for the given tables.
155
+ # CockroachDB only allows changing `schema_locked` in a single-statement
156
+ # implicit transaction, so unlike the foreign key statements above these
157
+ # cannot be batched together.
158
+ def set_schema_locked(tables, value)
159
+ tables.each do |table|
160
+ execute("ALTER TABLE #{quote_table_name(table)} SET (schema_locked = #{value})", "Toggle schema_locked")
161
+ end
162
+ end
87
163
 
88
- # Copy/paste of the `#foreign_keys(table)` method adapted to return every single
89
- # foreign key in the database.
164
+ # NOTE: Copy/paste of the `#foreign_keys(table)` method adapted
165
+ # to return every single foreign key in the database.
90
166
  def all_foreign_keys
91
167
  fk_info = internal_exec_query(<<~SQL, "SCHEMA")
92
168
  SELECT CASE
@@ -99,14 +175,30 @@ module ActiveRecord
99
175
  THEN ''
100
176
  ELSE n2.nspname || '.'
101
177
  END || t2.relname AS to_table,
102
- a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred,
103
- c.conkey, c.confkey, c.conrelid, c.confrelid
178
+ c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conrelid, c.confrelid,
179
+ (
180
+ SELECT array_agg(a.attname ORDER BY idx)
181
+ FROM (
182
+ SELECT idx, c.conkey[idx] AS conkey_elem
183
+ FROM generate_subscripts(c.conkey, 1) AS idx
184
+ ) indexed_conkeys
185
+ JOIN pg_attribute a ON a.attrelid = t1.oid
186
+ AND a.attnum = indexed_conkeys.conkey_elem
187
+ AND NOT a.attishidden
188
+ ) AS conkey_names,
189
+ (
190
+ SELECT array_agg(a.attname ORDER BY idx)
191
+ FROM (
192
+ SELECT idx, c.confkey[idx] AS confkey_elem
193
+ FROM generate_subscripts(c.confkey, 1) AS idx
194
+ ) indexed_confkeys
195
+ JOIN pg_attribute a ON a.attrelid = t2.oid
196
+ AND a.attnum = indexed_confkeys.confkey_elem
197
+ AND NOT a.attishidden
198
+ ) AS confkey_names
104
199
  FROM pg_constraint c
105
200
  JOIN pg_class t1 ON c.conrelid = t1.oid
106
201
  JOIN pg_class t2 ON c.confrelid = t2.oid
107
- JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
108
- JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
109
- JOIN pg_namespace t3 ON c.connamespace = t3.oid
110
202
  JOIN pg_namespace n1 ON t1.relnamespace = n1.oid
111
203
  JOIN pg_namespace n2 ON t2.relnamespace = n2.oid
112
204
  WHERE c.contype = 'f'
@@ -116,22 +208,16 @@ module ActiveRecord
116
208
  fk_info.map do |row|
117
209
  from_table = PostgreSQL::Utils.unquote_identifier(row["from_table"])
118
210
  to_table = PostgreSQL::Utils.unquote_identifier(row["to_table"])
119
- conkey = row["conkey"].scan(/\d+/).map(&:to_i)
120
- confkey = row["confkey"].scan(/\d+/).map(&:to_i)
121
-
122
- if conkey.size > 1
123
- column = column_names_from_column_numbers(row["conrelid"], conkey)
124
- primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
125
- else
126
- column = PostgreSQL::Utils.unquote_identifier(row["column"])
127
- primary_key = row["primary_key"]
128
- end
211
+
212
+ column = decode_string_array(row["conkey_names"])
213
+ primary_key = decode_string_array(row["confkey_names"])
129
214
 
130
215
  options = {
131
- column: column,
216
+ column: column.size == 1 ? column.first : column,
132
217
  name: row["name"],
133
- primary_key: primary_key
218
+ primary_key: primary_key.size == 1 ? primary_key.first : primary_key
134
219
  }
220
+
135
221
  options[:on_delete] = extract_foreign_key_action(row["on_delete"])
136
222
  options[:on_update] = extract_foreign_key_action(row["on_update"])
137
223
  options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])