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
@@ -20,6 +20,91 @@ module ActiveRecord
20
20
  module SchemaStatements
21
21
  include ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements
22
22
 
23
+ # OVERRIDE(v8.1.1):
24
+ # - prepend Utils with PostgreSQL::
25
+ # - handle hidden attributes
26
+ # Returns an array of indexes for the given table.
27
+ def indexes(table_name) # :nodoc:
28
+ scope = quoted_scope(table_name)
29
+
30
+ result = query(<<~SQL, "SCHEMA")
31
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid),
32
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment, d.indisvalid,
33
+ ARRAY(
34
+ SELECT pg_get_indexdef(d.indexrelid, k + 1, true)
35
+ FROM generate_subscripts(d.indkey, 1) AS k
36
+ ORDER BY k
37
+ ) AS columns,
38
+ ARRAY(
39
+ SELECT a.attname
40
+ FROM pg_attribute a
41
+ WHERE a.attrelid = d.indexrelid AND a.attishidden
42
+ ) AS hidden_columns
43
+ FROM pg_class t
44
+ INNER JOIN pg_index d ON t.oid = d.indrelid
45
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
46
+ LEFT JOIN pg_namespace n ON n.oid = t.relnamespace
47
+ WHERE i.relkind IN ('i', 'I')
48
+ AND d.indisprimary = 'f'
49
+ AND t.relname = #{scope[:name]}
50
+ AND n.nspname = #{scope[:schema]}
51
+ ORDER BY i.relname
52
+ SQL
53
+
54
+ unquote = -> (column) {
55
+ PostgreSQL::Utils.unquote_identifier(column.strip.gsub('""', '"'))
56
+ }
57
+ result.map do |row|
58
+ index_name = row[0]
59
+ unique = row[1]
60
+ indkey = row[2].split(" ").map(&:to_i)
61
+ inddef = row[3]
62
+ comment = row[4]
63
+ valid = row[5]
64
+ columns = decode_string_array(row[6]).map(&unquote)
65
+ hidden_columns = decode_string_array(row[7]).map(&unquote)
66
+
67
+ using, expressions, include, nulls_not_distinct, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: INCLUDE \((.+?)\))?( NULLS NOT DISTINCT)?(?: WHERE (.+))?\z/m).flatten
68
+
69
+ orders = {}
70
+ opclasses = {}
71
+ include_columns = include ? include.split(",").map(&unquote) : []
72
+
73
+ if indkey.include?(0)
74
+ columns = expressions
75
+ else
76
+ # prevent INCLUDE and hidden columns from being matched
77
+ columns.reject! { |c| include_columns.include?(c) || hidden_columns.include?(c) }
78
+
79
+ # add info on sort order (only desc order is explicitly specified, asc is the default)
80
+ # and non-default opclasses
81
+ expressions.scan(/(?<column>\w+)"?\s?(?<opclass>\w+_ops(_\w+)?)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
82
+ opclasses[column] = opclass.to_sym if opclass
83
+ if nulls
84
+ orders[column] = [desc, nulls].compact.join(" ")
85
+ else
86
+ orders[column] = :desc if desc
87
+ end
88
+ end
89
+ end
90
+
91
+ IndexDefinition.new(
92
+ table_name,
93
+ index_name,
94
+ unique,
95
+ columns,
96
+ orders: orders,
97
+ opclasses: opclasses,
98
+ where: where,
99
+ using: using.to_sym,
100
+ include: include_columns.presence,
101
+ nulls_not_distinct: nulls_not_distinct.present?,
102
+ comment: comment.presence,
103
+ valid: valid
104
+ )
105
+ end
106
+ end
107
+
23
108
  # OVERRIDE: We do not want to see the crdb_internal schema in the names.
24
109
  #
25
110
  # Returns an array of schema names.
@@ -27,16 +112,6 @@ module ActiveRecord
27
112
  super - ["crdb_internal"]
28
113
  end
29
114
 
30
- def add_index(table_name, column_name, **options)
31
- super
32
- rescue ActiveRecord::StatementInvalid => error
33
- if debugging? && error.cause.class == PG::FeatureNotSupported
34
- warn "#{error}\n\nThis error will be ignored and the index will not be created.\n\n"
35
- else
36
- raise error
37
- end
38
- end
39
-
40
115
  # ActiveRecord allows for tables to exist without primary keys.
41
116
  # Databases like PostgreSQL support this behavior, but CockroachDB does
42
117
  # not. If a table is created without a primary key, CockroachDB will add
@@ -55,34 +130,18 @@ module ActiveRecord
55
130
  end
56
131
  end
57
132
 
133
+ # OVERRIDE(v8.1.1): handle hidden attributes
58
134
  def primary_keys(table_name)
59
- return super unless database_version >= 24_02_02
60
-
61
135
  query_values(<<~SQL, "SCHEMA")
62
136
  SELECT a.attname
63
- FROM (
64
- SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
65
- FROM pg_index
66
- WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
67
- AND indisprimary
68
- ) i
69
- JOIN pg_attribute a
70
- ON a.attrelid = i.indrelid
71
- AND a.attnum = i.indkey[i.idx]
72
- AND NOT a.attishidden
73
- ORDER BY i.idx
74
- SQL
75
- end
76
-
77
- def column_names_from_column_numbers(table_oid, column_numbers)
78
- return super unless database_version >= 24_02_02
79
-
80
- Hash[query(<<~SQL, "SCHEMA")].values_at(*column_numbers).compact
81
- SELECT a.attnum, a.attname
82
- FROM pg_attribute a
83
- WHERE a.attrelid = #{table_oid}
84
- AND a.attnum IN (#{column_numbers.join(", ")})
85
- AND NOT a.attishidden
137
+ FROM pg_index i
138
+ JOIN pg_attribute a
139
+ ON a.attrelid = i.indrelid
140
+ AND a.attnum = ANY(i.indkey)
141
+ AND NOT a.attishidden
142
+ WHERE i.indrelid = #{quote(quote_table_name(table_name))}::regclass
143
+ AND i.indisprimary
144
+ ORDER BY array_position(i.indkey, a.attnum)
86
145
  SQL
87
146
  end
88
147
 
@@ -94,7 +153,7 @@ module ActiveRecord
94
153
  options
95
154
  end
96
155
 
97
- # OVERRIDE: Added `unique_rowid` to the last line of the second query.
156
+ # OVERRIDE(v8.1.1): Added `unique_rowid` to the last line of the second query.
98
157
  # This is a CockroachDB-specific function used for primary keys.
99
158
  # Also make sure we don't consider `NOT VISIBLE` columns.
100
159
  #
@@ -108,11 +167,7 @@ module ActiveRecord
108
167
  pg_attribute attr,
109
168
  pg_depend dep,
110
169
  pg_constraint cons,
111
- pg_namespace nsp,
112
- -- TODO: use the pg_catalog.pg_attribute(attishidden) column when
113
- -- it is added instead of joining on crdb_internal.
114
- -- See https://github.com/cockroachdb/cockroach/pull/126397
115
- crdb_internal.table_columns tc
170
+ pg_namespace nsp
116
171
  WHERE seq.oid = dep.objid
117
172
  AND seq.relkind = 'S'
118
173
  AND attr.attrelid = dep.refobjid
@@ -120,12 +175,10 @@ module ActiveRecord
120
175
  AND attr.attrelid = cons.conrelid
121
176
  AND attr.attnum = cons.conkey[1]
122
177
  AND seq.relnamespace = nsp.oid
123
- AND attr.attrelid = tc.descriptor_id
124
- AND attr.attname = tc.column_name
125
- AND tc.hidden = false
126
178
  AND cons.contype = 'p'
127
179
  AND dep.classid = 'pg_class'::regclass
128
180
  AND dep.refobjid = #{quote(quote_table_name(table))}::regclass
181
+ AND not attr.attishidden
129
182
  SQL
130
183
 
131
184
  if result.nil? || result.empty?
@@ -143,12 +196,8 @@ module ActiveRecord
143
196
  JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
144
197
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
145
198
  JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
146
- -- TODO: use the pg_catalog.pg_attribute(attishidden) column when
147
- -- it is added instead of joining on crdb_internal.
148
- -- See https://github.com/cockroachdb/cockroach/pull/126397
149
- JOIN crdb_internal.table_columns tc ON (attr.attrelid = tc.descriptor_id AND attr.attname = tc.column_name)
150
199
  WHERE t.oid = #{quote(quote_table_name(table))}::regclass
151
- AND tc.hidden = false
200
+ AND NOT attr.attishidden
152
201
  AND cons.contype = 'p'
153
202
  AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate|gen_random_uuid|unique_rowid'
154
203
  SQL
@@ -164,53 +213,66 @@ module ActiveRecord
164
213
  nil
165
214
  end
166
215
 
167
- # override
168
- # Modified version of the postgresql foreign_keys method.
169
- # Replaces t2.oid::regclass::text with t2.relname since this is
170
- # more efficient in CockroachDB.
171
- # Also, CockroachDB does not append the schema name in relname,
172
- # so we append it manually.
216
+ # OVERRIDE(v8.1.1):
217
+ # - Replaces t2.oid::regclass::text with t2.relname
218
+ # since this is more efficient in CockroachDB.
219
+ # - prepend schema name to relname (see `AS to_table`)
220
+ # - handle hidden attributes.
221
+ #
222
+ # NOTE: If you edit this method, you'll need to edit
223
+ # the `#all_foreign_keys` method as well.
173
224
  def foreign_keys(table_name)
174
225
  scope = quoted_scope(table_name)
175
- fk_info = internal_exec_query(<<~SQL, "SCHEMA")
226
+ fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
176
227
  SELECT CASE
177
228
  WHEN n2.nspname = current_schema()
178
229
  THEN ''
179
230
  ELSE n2.nspname || '.'
180
231
  END || t2.relname AS to_table,
181
- 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,
182
- c.conkey, c.confkey, c.conrelid, c.confrelid
232
+ 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,
233
+ (
234
+ SELECT array_agg(a.attname ORDER BY idx)
235
+ FROM (
236
+ SELECT idx, c.conkey[idx] AS conkey_elem
237
+ FROM generate_subscripts(c.conkey, 1) AS idx
238
+ ) indexed_conkeys
239
+ JOIN pg_attribute a ON a.attrelid = t1.oid
240
+ AND a.attnum = indexed_conkeys.conkey_elem
241
+ AND NOT a.attishidden
242
+ ) AS conkey_names,
243
+ (
244
+ SELECT array_agg(a.attname ORDER BY idx)
245
+ FROM (
246
+ SELECT idx, c.confkey[idx] AS confkey_elem
247
+ FROM generate_subscripts(c.confkey, 1) AS idx
248
+ ) indexed_confkeys
249
+ JOIN pg_attribute a ON a.attrelid = t2.oid
250
+ AND a.attnum = indexed_confkeys.confkey_elem
251
+ AND NOT a.attishidden
252
+ ) AS confkey_names
183
253
  FROM pg_constraint c
184
254
  JOIN pg_class t1 ON c.conrelid = t1.oid
185
255
  JOIN pg_class t2 ON c.confrelid = t2.oid
186
- JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
187
- JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
188
- JOIN pg_namespace t3 ON c.connamespace = t3.oid
256
+ JOIN pg_namespace n1 ON t1.relnamespace = n1.oid
189
257
  JOIN pg_namespace n2 ON t2.relnamespace = n2.oid
190
258
  WHERE c.contype = 'f'
191
259
  AND t1.relname = #{scope[:name]}
192
- AND t3.nspname = #{scope[:schema]}
260
+ AND n1.nspname = #{scope[:schema]}
193
261
  ORDER BY c.conname
194
262
  SQL
195
263
 
196
264
  fk_info.map do |row|
197
265
  to_table = PostgreSQL::Utils.unquote_identifier(row["to_table"])
198
- conkey = row["conkey"].scan(/\d+/).map(&:to_i)
199
- confkey = row["confkey"].scan(/\d+/).map(&:to_i)
200
266
 
201
- if conkey.size > 1
202
- column = column_names_from_column_numbers(row["conrelid"], conkey)
203
- primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
204
- else
205
- column = PostgreSQL::Utils.unquote_identifier(row["column"])
206
- primary_key = row["primary_key"]
207
- end
267
+ column = decode_string_array(row["conkey_names"])
268
+ primary_key = decode_string_array(row["confkey_names"])
208
269
 
209
270
  options = {
210
- column: column,
271
+ column: column.size == 1 ? column.first : column,
211
272
  name: row["name"],
212
- primary_key: primary_key
273
+ primary_key: primary_key.size == 1 ? primary_key.first : primary_key
213
274
  }
275
+
214
276
  options[:on_delete] = extract_foreign_key_action(row["on_delete"])
215
277
  options[:on_update] = extract_foreign_key_action(row["on_update"])
216
278
  options[:deferrable] = extract_constraint_deferrable(row["deferrable"], row["deferred"])
@@ -228,8 +290,51 @@ module ActiveRecord
228
290
  nil
229
291
  end
230
292
 
231
- # override
232
- # https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb#L624
293
+ # OVERRIDE(v8.1.1): handle hidden attributes
294
+ #
295
+ # Returns an array of unique constraints for the given table.
296
+ # The unique constraints are represented as UniqueConstraintDefinition objects.
297
+ def unique_constraints(table_name)
298
+ scope = quoted_scope(table_name)
299
+
300
+ unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
301
+ SELECT c.conname, c.conrelid, c.condeferrable, c.condeferred, pg_get_constraintdef(c.oid) AS constraintdef,
302
+ (
303
+ SELECT array_agg(a.attname ORDER BY idx)
304
+ FROM (
305
+ SELECT idx, c.conkey[idx] AS conkey_elem
306
+ FROM generate_subscripts(c.conkey, 1) AS idx
307
+ ) indexed_conkeys
308
+ JOIN pg_attribute a ON a.attrelid = t.oid
309
+ AND a.attnum = indexed_conkeys.conkey_elem
310
+ AND NOT a.attishidden
311
+ ) AS conkey_names
312
+ FROM pg_constraint c
313
+ JOIN pg_class t ON c.conrelid = t.oid
314
+ JOIN pg_namespace n ON n.oid = c.connamespace
315
+ WHERE c.contype = 'u'
316
+ AND t.relname = #{scope[:name]}
317
+ AND n.nspname = #{scope[:schema]}
318
+ SQL
319
+
320
+ unique_info.map do |row|
321
+ columns = decode_string_array(row["conkey_names"])
322
+
323
+ nulls_not_distinct = row["constraintdef"].start_with?("UNIQUE NULLS NOT DISTINCT")
324
+ deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
325
+
326
+ options = {
327
+ name: row["conname"],
328
+ nulls_not_distinct: nulls_not_distinct,
329
+ deferrable: deferrable
330
+ }
331
+
332
+ UniqueConstraintDefinition.new(table_name, columns, options)
333
+ end
334
+ end
335
+
336
+ # OVERRIDE(v8.1.1):
337
+ # - Add hidden information
233
338
  def new_column_from_field(table_name, field, _definition)
234
339
  column_name, type, default, notnull, oid, fmod, collation, comment, identity, attgenerated, hidden = field
235
340
  type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
@@ -245,11 +350,9 @@ module ActiveRecord
245
350
  serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]
246
351
  end
247
352
 
248
- # {:dimension=>2, :has_m=>false, :has_z=>false, :name=>"latlon", :srid=>0, :type=>"GEOMETRY"}
249
- spatial = spatial_column_info(table_name).get(column_name, type_metadata.sql_type)
250
-
251
353
  CockroachDB::Column.new(
252
354
  column_name,
355
+ get_oid_type(oid.to_i, fmod.to_i, column_name, type),
253
356
  default_value,
254
357
  type_metadata,
255
358
  !notnull,
@@ -258,7 +361,6 @@ module ActiveRecord
258
361
  comment: comment.presence,
259
362
  serial: serial,
260
363
  identity: identity.presence,
261
- spatial: spatial,
262
364
  generated: attgenerated,
263
365
  hidden: hidden
264
366
  )
@@ -295,34 +397,11 @@ module ActiveRecord
295
397
  sql
296
398
  end
297
399
 
298
- # override
299
- def native_database_types
300
- # Add spatial types
301
- super.merge(
302
- geography: { name: "geography" },
303
- geometry: { name: "geometry" },
304
- geometry_collection: { name: "geometry_collection" },
305
- line_string: { name: "line_string" },
306
- multi_line_string: { name: "multi_line_string" },
307
- multi_point: { name: "multi_point" },
308
- multi_polygon: { name: "multi_polygon" },
309
- spatial: { name: "geometry" },
310
- st_point: { name: "st_point" },
311
- st_polygon: { name: "st_polygon" }
312
- )
313
- end
314
-
315
400
  # override
316
401
  def create_table_definition(*args, **kwargs)
317
402
  CockroachDB::TableDefinition.new(self, *args, **kwargs)
318
403
  end
319
404
 
320
- # memoize hash of column infos for tables
321
- def spatial_column_info(table_name)
322
- @spatial_column_info ||= {}
323
- @spatial_column_info[table_name.to_sym] ||= SpatialColumnInfo.new(self, table_name.to_s)
324
- end
325
-
326
405
  def create_schema_dumper(options)
327
406
  CockroachDB::SchemaDumper.create(self, options)
328
407
  end
@@ -30,7 +30,6 @@ require "active_record/connection_adapters/cockroachdb/table_definition"
30
30
  require "active_record/connection_adapters/cockroachdb/quoting"
31
31
  require "active_record/connection_adapters/cockroachdb/type"
32
32
  require "active_record/connection_adapters/cockroachdb/column"
33
- require "active_record/connection_adapters/cockroachdb/spatial_column_info"
34
33
  require "active_record/connection_adapters/cockroachdb/setup"
35
34
  require "active_record/connection_adapters/cockroachdb/oid/spatial"
36
35
  require "active_record/connection_adapters/cockroachdb/oid/interval"
@@ -111,6 +110,23 @@ module ActiveRecord
111
110
  SPATIAL_COLUMN_OPTIONS[key]
112
111
  end
113
112
 
113
+ def self.native_database_types
114
+ return @native_database_types if defined?(@native_database_types)
115
+ # Add spatial types
116
+ @native_database_types = super.merge(
117
+ geography: { name: "geography" },
118
+ geometry: { name: "geometry" },
119
+ geometry_collection: { name: "geometry_collection" },
120
+ line_string: { name: "line_string" },
121
+ multi_line_string: { name: "multi_line_string" },
122
+ multi_point: { name: "multi_point" },
123
+ multi_polygon: { name: "multi_polygon" },
124
+ spatial: { name: "geometry" },
125
+ st_point: { name: "st_point" },
126
+ st_polygon: { name: "st_polygon" }
127
+ )
128
+ end
129
+
114
130
  def postgis_lib_version
115
131
  @postgis_lib_version ||= select_value("SELECT PostGIS_Lib_Version()")
116
132
  end
@@ -128,10 +144,6 @@ module ActiveRecord
128
144
  }
129
145
  end
130
146
 
131
- def debugging?
132
- !!ENV["DEBUG_COCKROACHDB_ADAPTER"]
133
- end
134
-
135
147
  def max_transaction_retries
136
148
  @max_transaction_retries ||= @config.fetch(:max_transaction_retries, 3)
137
149
  end
@@ -190,6 +202,12 @@ module ActiveRecord
190
202
  false
191
203
  end
192
204
 
205
+ def supports_restart_db_transaction?
206
+ # In PostgreSQL, this would call 'ROLLBACK AND CHAIN'
207
+ # which is not available with CRDB.
208
+ false
209
+ end
210
+
193
211
  # OVERRIDE: UNIQUE CONSTRAINTS will create indexes anyway, so we only consider
194
212
  # then as indexes.
195
213
  # See https://github.com/cockroachdb/activerecord-cockroachdb-adapter/issues/347.
@@ -233,6 +251,10 @@ module ActiveRecord
233
251
  false
234
252
  end
235
253
 
254
+ def supports_close_prepared?
255
+ true
256
+ end
257
+
236
258
  def check_version # :nodoc:
237
259
  # https://www.cockroachlabs.com/docs/releases/release-support-policy
238
260
  if database_version < 23_01_12 # < 23.1.12
@@ -243,6 +265,26 @@ module ActiveRecord
243
265
  def configure_connection(...)
244
266
  super
245
267
 
268
+ # Starting in CockroachDB v26.x, tables are created with the
269
+ # `schema_locked` storage parameter enabled by default (controlled by the
270
+ # `sql.create_table_with_schema_locked.enabled` cluster setting). While it
271
+ # improves changefeed performance, it blocks the transactional DDL that
272
+ # Active Record migrations and the test suite rely on: CockroachDB can
273
+ # only auto-unlock a locked table for single-statement implicit
274
+ # transactions, not for DDL run inside a transaction. Opt out at the
275
+ # session level so Active Record keeps working out of the box; users who
276
+ # want the changefeed benefit can still lock individual tables or set
277
+ # `create_table_with_schema_locked: true` in their connection variables.
278
+ #
279
+ # The session variable was introduced in CockroachDB v25.3, and we let an
280
+ # explicit `:variables` entry take precedence over this default.
281
+ #
282
+ # See https://www.cockroachlabs.com/docs/stable/schema-locked
283
+ variables = @config.fetch(:variables, {}).stringify_keys
284
+ if database_version >= 25_03_00 && !variables.key?("create_table_with_schema_locked")
285
+ internal_execute("SET create_table_with_schema_locked = false", "SCHEMA")
286
+ end
287
+
246
288
  # This rescue flow appears in new_client, but it is needed here as well
247
289
  # since Cockroach will sometimes not raise until a query is made.
248
290
  #
@@ -299,7 +341,7 @@ module ActiveRecord
299
341
  st_polygon
300
342
  ).each do |geo_type|
301
343
  m.register_type(geo_type) do |oid, _, sql_type|
302
- CockroachDB::OID::Spatial.new(oid, sql_type)
344
+ CockroachDB::OID::Spatial.new(oid, sql_type).freeze
303
345
  end
304
346
  end
305
347
 
@@ -378,18 +420,10 @@ module ActiveRecord
378
420
  nil
379
421
  end
380
422
 
381
- # override
382
- # This method makes a query to gather information about columns
383
- # in a table. It returns an array of arrays (one for each col) and
384
- # passes each to the SchemaStatements#new_column_from_field method
385
- # as the field parameter. This data is then used to format the column
386
- # objects for the model and sent to the OID for data casting.
387
- #
388
- # Sometimes there are differences between how data is formatted
389
- # in Postgres and CockroachDB, so additional queries for certain types
390
- # may be necessary to properly form the column definition.
391
- #
392
- # @see: https://github.com/rails/rails/blob/8695b028261bdd244e254993255c6641bdbc17a5/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L829
423
+ # OVERRIDE(v8.1.1):
424
+ # - comment is retrieved differently than PG for performance
425
+ # - gather detailed information about spatial columns. See
426
+ # `#crdb_column_definitions`
393
427
  def column_definitions(table_name)
394
428
  fields = query(<<~SQL, "SCHEMA")
395
429
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
@@ -397,7 +431,7 @@ module ActiveRecord
397
431
  c.collname, NULL AS comment,
398
432
  attidentity,
399
433
  attgenerated,
400
- NULL as is_hidden
434
+ a.attishidden
401
435
  FROM pg_attribute a
402
436
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
403
437
  LEFT JOIN pg_type t ON a.atttypid = t.oid
@@ -428,7 +462,6 @@ module ActiveRecord
428
462
  dtype = field[f_type]
429
463
  field[f_type] = crdb_fields[field[f_attname]][2].downcase if re.match(dtype)
430
464
  field[f_comment] = crdb_fields[field[f_attname]][1]
431
- field[f_is_hidden] = true if crdb_fields[field[f_attname]][3]
432
465
  field
433
466
  end
434
467
  fields.delete_if do |field|
@@ -444,13 +477,24 @@ module ActiveRecord
444
477
  # column_definitions. This will include limit,
445
478
  # precision, and scale information in the type.
446
479
  # Ex. geometry -> geometry(point, 4326)
480
+ #
481
+ # The performance difference to fetch comments is very
482
+ # significant. On a M1 Mac, CockroachDB v25.2.2 and
483
+ # data from the test suite, it is 20x times faster.
484
+ #
485
+ # SELECT col_description(a.attrelid, a.attnum)
486
+ # FROM pg_attribute a; -- 1.052s
487
+ #
488
+ # SELECT c.column_comment
489
+ # FROM information_schema.columns c; -- 50ms
490
+ #
447
491
  def crdb_column_definitions(table_name)
448
492
  table_name = PostgreSQL::Utils.extract_schema_qualified_name(table_name)
449
493
  table = table_name.identifier
450
494
  with_schema = " AND c.table_schema = #{quote(table_name.schema)}" if table_name.schema
451
495
  fields = \
452
496
  query(<<~SQL, "SCHEMA")
453
- SELECT c.column_name, c.column_comment, c.crdb_sql_type, c.is_hidden::BOOLEAN
497
+ SELECT c.column_name, c.column_comment, c.crdb_sql_type
454
498
  FROM information_schema.columns c
455
499
  WHERE c.table_name = #{quote(table)}#{with_schema}
456
500
  SQL
@@ -461,22 +505,22 @@ module ActiveRecord
461
505
  end
462
506
 
463
507
  # override
464
- # This method is used to determine if a
465
- # FEATURE_NOT_SUPPORTED error from the PG gem should
466
- # be an ActiveRecord::PreparedStatementCacheExpired
467
- # error.
468
- #
469
- # ActiveRecord handles this by checking that the sql state matches the
470
- # FEATURE_NOT_SUPPORTED code and that the source function
471
- # is "RevalidateCachedQuery" since that is the only function
472
- # in postgres that will create this error.
508
+ # Classifies a FEATURE_NOT_SUPPORTED error from the PG gem as an
509
+ # ActiveRecord::PreparedStatementCacheExpired.
473
510
  #
474
- # That method will not work for CockroachDB because the error
475
- # originates from the "runExecBuilder" function, so we need
476
- # to modify the original to match the CockroachDB behavior.
511
+ # Upstream matches PG_DIAG_SOURCE_FUNCTION == "RevalidateCachedQuery",
512
+ # the one PostgreSQL C function that raises this error. CockroachDB's
513
+ # raising function is an internal detail that has already moved
514
+ # (runExecBuilder before cockroachdb/cockroach#164406, execBind after),
515
+ # so match the message instead. The message is not formally guaranteed,
516
+ # but it has been unchanged in PostgreSQL since 8.3 and drivers such as
517
+ # pgx assert the exact string. Upstream avoids message matching because
518
+ # PostgreSQL localizes messages via lc_messages (rails/rails@d507ae2a74);
519
+ # CockroachDB does not localize, so that concern does not apply.
520
+ CACHED_PLAN_HEURISTIC = "cached plan must not change result type"
477
521
  def is_cached_plan_failure?(pgerror)
478
522
  pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
479
- pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "runExecBuilder"
523
+ pgerror.result.result_error_field(PG::PG_DIAG_MESSAGE_PRIMARY).include?(CACHED_PLAN_HEURISTIC)
480
524
  rescue
481
525
  false
482
526
  end
data/lib/version.rb CHANGED
@@ -15,5 +15,5 @@
15
15
  # limitations under the License.
16
16
 
17
17
  module ActiveRecord
18
- COCKROACH_DB_ADAPTER_VERSION = "8.0.3"
18
+ COCKROACH_DB_ADAPTER_VERSION = "8.1.1"
19
19
  end