activerecord-cockroachdb-adapter 8.0.3 → 8.1.0

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 (239) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -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 +66 -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 +87 -55
  12. data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +181 -102
  13. data/lib/active_record/connection_adapters/cockroachdb_adapter.rb +39 -21
  14. data/lib/version.rb +1 -1
  15. data/test/cases/adapter_test.rb +98 -0
  16. data/test/cases/adapters/cockroachdb/referential_integrity_test.rb +51 -0
  17. data/test/cases/adapters/postgresql/active_schema_test.rb +71 -0
  18. data/test/cases/adapters/postgresql/change_schema_test.rb +75 -0
  19. data/test/cases/adapters/postgresql/connection_test.rb +46 -0
  20. data/test/cases/adapters/postgresql/ddl_test.rb +326 -0
  21. data/test/cases/adapters/postgresql/interval_test.rb +131 -0
  22. data/test/cases/adapters/postgresql/nested_class_test.rb +22 -0
  23. data/test/cases/adapters/postgresql/numeric_test.rb +28 -0
  24. data/test/cases/adapters/postgresql/postgis_test.rb +185 -0
  25. data/test/cases/adapters/postgresql/postgresql_adapter_test.rb +115 -0
  26. data/test/cases/adapters/postgresql/quoting_test.rb +30 -0
  27. data/test/cases/adapters/postgresql/schema_statements_test.rb +29 -0
  28. data/test/cases/adapters/postgresql/serial_test.rb +199 -0
  29. data/test/cases/adapters/postgresql/spatial_queries_test.rb +118 -0
  30. data/test/cases/adapters/postgresql/spatial_setup_test.rb +18 -0
  31. data/test/cases/adapters/postgresql/spatial_type_test.rb +41 -0
  32. data/test/cases/adapters/postgresql/timestamp_test.rb +58 -0
  33. data/test/cases/adapters/postgresql/virtual_column_test.rb +39 -0
  34. data/test/cases/associations/eager_load_nested_include_test.rb +111 -0
  35. data/test/cases/associations/left_outer_join_association_test.rb +30 -0
  36. data/test/cases/associations_test.rb +108 -0
  37. data/test/cases/base_test.rb +31 -0
  38. data/test/cases/comment_test.rb +75 -0
  39. data/test/cases/connection_adapters/type_test.rb +28 -0
  40. data/test/cases/database_configurations/resolver_test.rb +24 -0
  41. data/test/cases/defaults_test.rb +45 -0
  42. data/test/cases/dirty_test.rb +24 -0
  43. data/test/cases/fixtures_test.rb +541 -0
  44. data/test/cases/helper_cockroachdb.rb +232 -0
  45. data/test/cases/inheritance_test.rb +42 -0
  46. data/test/cases/invertible_migration_test.rb +73 -0
  47. data/test/cases/marshal_serialization_test.rb +45 -0
  48. data/test/cases/migration/change_schema_test.rb +140 -0
  49. data/test/cases/migration/check_constraint_test.rb +125 -0
  50. data/test/cases/migration/columns_test.rb +50 -0
  51. data/test/cases/migration/create_join_table_test.rb +66 -0
  52. data/test/cases/migration/foreign_key_test.rb +390 -0
  53. data/test/cases/migration/hidden_column_test.rb +70 -0
  54. data/test/cases/migration/references_foreign_key_test.rb +124 -0
  55. data/test/cases/migration_test.rb +120 -0
  56. data/test/cases/persistence_test.rb +33 -0
  57. data/test/cases/primary_keys_test.rb +134 -0
  58. data/test/cases/relation/aost_test.rb +57 -0
  59. data/test/cases/relation/or_test.rb +26 -0
  60. data/test/cases/relation/table_hints_test.rb +124 -0
  61. data/test/cases/relation_test.rb +26 -0
  62. data/test/cases/relations_test.rb +29 -0
  63. data/test/cases/schema_dumper_test.rb +221 -0
  64. data/test/cases/show_create_test.rb +14 -0
  65. data/test/cases/strict_loading_test.rb +34 -0
  66. data/test/cases/tasks/cockroachdb_rake_test.rb +113 -0
  67. data/test/cases/test_fixtures_test.rb +57 -0
  68. data/test/cases/transactions_test.rb +51 -0
  69. data/test/cases/unsafe_raw_sql_test.rb +22 -0
  70. data/test/config.yml +23 -0
  71. data/test/excludes/ActiveRecord/AdapterTest.rb +3 -0
  72. data/test/excludes/ActiveRecord/AdapterTestWithoutTransaction.rb +25 -0
  73. data/test/excludes/ActiveRecord/ConnectionAdapters/PoolConfig/ResolverTest.rb +1 -0
  74. data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter/BindParameterTest.rb +15 -0
  75. data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter/QuotingTest.rb +22 -0
  76. data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterPreventWritesLegacyTest.rb +1 -0
  77. data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterPreventWritesTest.rb +1 -0
  78. data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterTest.rb +40 -0
  79. data/test/excludes/ActiveRecord/ConnectionAdapters/RegistrationIsolatedTest.rb +14 -0
  80. data/test/excludes/ActiveRecord/Encryption/EncryptionPerformanceTest.rb +1 -0
  81. data/test/excludes/ActiveRecord/Encryption/EnvelopeEncryptionPerformanceTest.rb +1 -0
  82. data/test/excludes/ActiveRecord/Encryption/ExtendedDeterministicQueriesPerformanceTest.rb +1 -0
  83. data/test/excludes/ActiveRecord/Encryption/StoragePerformanceTest.rb +2 -0
  84. data/test/excludes/ActiveRecord/InstrumentationTest.rb +16 -0
  85. data/test/excludes/ActiveRecord/InvertibleMigrationTest.rb +2 -0
  86. data/test/excludes/ActiveRecord/Migration/ChangeSchemaTest.rb +7 -0
  87. data/test/excludes/ActiveRecord/Migration/CheckConstraintTest.rb +6 -0
  88. data/test/excludes/ActiveRecord/Migration/ColumnsTest.rb +4 -0
  89. data/test/excludes/ActiveRecord/Migration/CompatibilityTest.rb +50 -0
  90. data/test/excludes/ActiveRecord/Migration/CompositeForeignKeyTest.rb +2 -0
  91. data/test/excludes/ActiveRecord/Migration/CreateJoinTableTest.rb +2 -0
  92. data/test/excludes/ActiveRecord/Migration/ForeignKeyInCreateTest.rb +1 -0
  93. data/test/excludes/ActiveRecord/Migration/ForeignKeyTest.rb +35 -0
  94. data/test/excludes/ActiveRecord/Migration/InvalidOptionsTest.rb +14 -0
  95. data/test/excludes/ActiveRecord/Migration/PGChangeSchemaTest.rb +8 -0
  96. data/test/excludes/ActiveRecord/Migration/ReferencesForeignKeyTest.rb +7 -0
  97. data/test/excludes/ActiveRecord/Migration/ReferencesIndexTest.rb +7 -0
  98. data/test/excludes/ActiveRecord/Migration/ReferencesStatementsTest.rb +3 -0
  99. data/test/excludes/ActiveRecord/MysqlDBCreateWithInvalidPermissionsTest.rb +1 -0
  100. data/test/excludes/ActiveRecord/OrTest.rb +1 -0
  101. data/test/excludes/ActiveRecord/PostgreSQLStructureDumpTest.rb +10 -0
  102. data/test/excludes/ActiveRecord/PostgresqlConnectionTest.rb +12 -0
  103. data/test/excludes/ActiveRecord/PostgresqlTransactionNestedTest.rb +14 -0
  104. data/test/excludes/ActiveRecord/PostgresqlTransactionTest.rb +4 -0
  105. data/test/excludes/ActiveRecord/RelationTest.rb +2 -0
  106. data/test/excludes/ActiveRecord/TooManyOrTest.rb +1 -0
  107. data/test/excludes/ActiveSupportSubclassWithFixturesTest.rb +1 -0
  108. data/test/excludes/AssociationDeprecationTest/NotifyModeTest.rb +2 -0
  109. data/test/excludes/AssociationDeprecationTest/RaiseBacktraceModeTest.rb +2 -0
  110. data/test/excludes/AssociationDeprecationTest/RaiseModeTest.rb +2 -0
  111. data/test/excludes/AssociationDeprecationTest/WarnBacktraceModeTest.rb +2 -0
  112. data/test/excludes/AssociationDeprecationTest/WarnModeTest.rb +2 -0
  113. data/test/excludes/AssociationDeprecationTest/fix_backtrace_cleaner.rb +10 -0
  114. data/test/excludes/BasicsTest.rb +1 -0
  115. data/test/excludes/BulkAlterTableMigrationsTest.rb +7 -0
  116. data/test/excludes/CalculationsTest.rb +4 -0
  117. data/test/excludes/CommentTest.rb +2 -0
  118. data/test/excludes/CoreTest.rb +1 -0
  119. data/test/excludes/CreateOrFindByWithinTransactions.rb +3 -0
  120. data/test/excludes/DefaultsUsingMultipleSchemasAndDomainTest.rb +7 -0
  121. data/test/excludes/DirtyTest.rb +3 -0
  122. data/test/excludes/EachTest.rb +8 -0
  123. data/test/excludes/EagerLoadPolyAssocsTest.rb +1 -0
  124. data/test/excludes/ExplicitlyNamedIndexMigrationTest.rb +1 -0
  125. data/test/excludes/FixturesResetPkSequenceTest.rb +3 -0
  126. data/test/excludes/FixturesTest.rb +21 -0
  127. data/test/excludes/FixturesWithForeignKeyViolationsTest.rb +2 -0
  128. data/test/excludes/ForeignTableTest.rb +10 -0
  129. data/test/excludes/InheritanceComputeTypeTest.rb +1 -0
  130. data/test/excludes/LeftOuterJoinAssociationTest.rb +2 -0
  131. data/test/excludes/LegacyPrimaryKeyTest/V4_2.rb +6 -0
  132. data/test/excludes/LegacyPrimaryKeyTest/V5_0.rb +6 -0
  133. data/test/excludes/MarshalSerializationTest.rb +4 -0
  134. data/test/excludes/MaterializedViewTest.rb +13 -0
  135. data/test/excludes/MigrationTest.rb +2 -0
  136. data/test/excludes/MultiDbMigratorTest.rb +2 -0
  137. data/test/excludes/NestedRelationScopingTest.rb +1 -0
  138. data/test/excludes/OrTest.rb +1 -0
  139. data/test/excludes/PersistenceTest.rb +3 -0
  140. data/test/excludes/PessimisticLockingTest.rb +1 -0
  141. data/test/excludes/PostgreSQLExplainTest.rb +7 -0
  142. data/test/excludes/PostgreSQLGeometricLineTest.rb +3 -0
  143. data/test/excludes/PostgreSQLGeometricTypesTest.rb +7 -0
  144. data/test/excludes/PostgreSQLPartitionsTest.rb +1 -0
  145. data/test/excludes/PostgreSQLReferentialIntegrityTest.rb +14 -0
  146. data/test/excludes/PostgresqlArrayTest.rb +21 -0
  147. data/test/excludes/PostgresqlBigSerialTest.rb +7 -0
  148. data/test/excludes/PostgresqlBitStringTest.rb +2 -0
  149. data/test/excludes/PostgresqlByteaTest.rb +1 -0
  150. data/test/excludes/PostgresqlCitextTest.rb +7 -0
  151. data/test/excludes/PostgresqlCollationTest.rb +5 -0
  152. data/test/excludes/PostgresqlCompositeTest.rb +2 -0
  153. data/test/excludes/PostgresqlCompositeWithCustomOIDTest.rb +2 -0
  154. data/test/excludes/PostgresqlDataTypeTest.rb +9 -0
  155. data/test/excludes/PostgresqlDefaultExpressionTest.rb +1 -0
  156. data/test/excludes/PostgresqlDeferredConstraintsTest.rb +3 -0
  157. data/test/excludes/PostgresqlDomainTest.rb +2 -0
  158. data/test/excludes/PostgresqlExtensionMigrationTest.rb +5 -0
  159. data/test/excludes/PostgresqlFullTextTest.rb +3 -0
  160. data/test/excludes/PostgresqlGeometricTest.rb +4 -0
  161. data/test/excludes/PostgresqlHstoreTest.rb +45 -0
  162. data/test/excludes/PostgresqlInfinityTest.rb +5 -0
  163. data/test/excludes/PostgresqlIntervalTest.rb +1 -0
  164. data/test/excludes/PostgresqlJSONBTest.rb +27 -0
  165. data/test/excludes/PostgresqlJSONTest.rb +27 -0
  166. data/test/excludes/PostgresqlLtreeTest.rb +4 -0
  167. data/test/excludes/PostgresqlMoneyTest.rb +12 -0
  168. data/test/excludes/PostgresqlNetworkTest.rb +69 -0
  169. data/test/excludes/PostgresqlNumberTest.rb +1 -0
  170. data/test/excludes/PostgresqlPointTest.rb +15 -0
  171. data/test/excludes/PostgresqlRangeTest.rb +3 -0
  172. data/test/excludes/PostgresqlRenameTableTest.rb +2 -0
  173. data/test/excludes/PostgresqlSerialTest.rb +7 -0
  174. data/test/excludes/PostgresqlTimestampFixtureTest.rb +2 -0
  175. data/test/excludes/PostgresqlTimestampMigrationTest.rb +3 -0
  176. data/test/excludes/PostgresqlTypeLookupTest.rb +2 -0
  177. data/test/excludes/PostgresqlUUIDGenerationTest.rb +7 -0
  178. data/test/excludes/PostgresqlUUIDTest.rb +1 -0
  179. data/test/excludes/PostgresqlVirtualColumnTest.rb +17 -0
  180. data/test/excludes/PostgresqlXMLTest.rb +5 -0
  181. data/test/excludes/PrimaryKeyIntegerNilDefaultTest.rb +1 -0
  182. data/test/excludes/PrimaryKeyIntegerTest.rb +3 -0
  183. data/test/excludes/PrimaryKeysTest.rb +2 -0
  184. data/test/excludes/RelationMergingTest.rb +15 -0
  185. data/test/excludes/RelationTest.rb +3 -0
  186. data/test/excludes/ReservedWordsMigrationTest.rb +1 -0
  187. data/test/excludes/SameNameDifferentDatabaseFixturesTest.rb +1 -0
  188. data/test/excludes/SanitizeTest.rb +13 -0
  189. data/test/excludes/SchemaAuthorizationTest.rb +8 -0
  190. data/test/excludes/SchemaCreateTableOptionsTest.rb +2 -0
  191. data/test/excludes/SchemaDumperDefaultsTest.rb +1 -0
  192. data/test/excludes/SchemaDumperTest.rb +11 -0
  193. data/test/excludes/SchemaForeignKeyTest.rb +1 -0
  194. data/test/excludes/SchemaIndexNullsOrderTest.rb +2 -0
  195. data/test/excludes/SchemaIndexOpclassTest.rb +3 -0
  196. data/test/excludes/SchemaTest.rb +49 -0
  197. data/test/excludes/SequenceNameDetectionTestCases/CollidedSequenceNameTest.rb +1 -0
  198. data/test/excludes/SequenceNameDetectionTestCases/LongerSequenceNameDetectionTest.rb +1 -0
  199. data/test/excludes/StrictLoadingFixturesTest.rb +1 -0
  200. data/test/excludes/TestFixturesTest.rb +1 -0
  201. data/test/excludes/TransactionInstrumentationTest.rb +1 -0
  202. data/test/excludes/TransactionIsolationTest.rb +1 -0
  203. data/test/excludes/TypeTest.rb +1 -0
  204. data/test/excludes/UniquenessValidationTest.rb +2 -0
  205. data/test/excludes/UnloggedTablesTest.rb +3 -0
  206. data/test/excludes/UnsafeRawSqlTest.rb +2 -0
  207. data/test/excludes/UpdateableViewTest.rb +5 -0
  208. data/test/excludes/WithAnnotationsTest.rb +6 -0
  209. data/test/models/building.rb +4 -0
  210. data/test/models/spatial_model.rb +5 -0
  211. data/test/schema/cockroachdb_specific_schema.rb +218 -0
  212. data/test/support/copy_cat.rb +83 -0
  213. data/test/support/exclude_from_transactional_tests.rb +33 -0
  214. data/test/support/paths_cockroachdb.rb +46 -0
  215. data/test/support/rake_helpers.rb +37 -0
  216. data/test/support/sql_logger.rb +55 -0
  217. data/test/support/template_creator.rb +114 -0
  218. metadata +422 -34
  219. data/.editorconfig +0 -7
  220. data/.github/issue_template.md +0 -46
  221. data/.github/reproduction_scripts/migrations.rb +0 -60
  222. data/.github/reproduction_scripts/models_and_database.rb +0 -49
  223. data/.github/workflows/ci.yml +0 -96
  224. data/.github/workflows/docker.yml +0 -57
  225. data/.gitignore +0 -18
  226. data/.gitmodules +0 -0
  227. data/activerecord-cockroachdb-adapter.gemspec +0 -38
  228. data/bin/console +0 -36
  229. data/bin/console_schemas/default.rb +0 -11
  230. data/bin/console_schemas/schemas.rb +0 -23
  231. data/bin/setup +0 -8
  232. data/bin/start-cockroachdb +0 -33
  233. data/build/Dockerfile +0 -14
  234. data/build/config.teamcity.yml +0 -31
  235. data/build/local-test.sh +0 -32
  236. data/build/teamcity-test.sh +0 -76
  237. data/docker.sh +0 -35
  238. data/lib/active_record/connection_adapters/cockroachdb/spatial_column_info.rb +0 -60
  239. 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
@@ -233,6 +245,10 @@ module ActiveRecord
233
245
  false
234
246
  end
235
247
 
248
+ def supports_close_prepared?
249
+ true
250
+ end
251
+
236
252
  def check_version # :nodoc:
237
253
  # https://www.cockroachlabs.com/docs/releases/release-support-policy
238
254
  if database_version < 23_01_12 # < 23.1.12
@@ -299,7 +315,7 @@ module ActiveRecord
299
315
  st_polygon
300
316
  ).each do |geo_type|
301
317
  m.register_type(geo_type) do |oid, _, sql_type|
302
- CockroachDB::OID::Spatial.new(oid, sql_type)
318
+ CockroachDB::OID::Spatial.new(oid, sql_type).freeze
303
319
  end
304
320
  end
305
321
 
@@ -378,18 +394,10 @@ module ActiveRecord
378
394
  nil
379
395
  end
380
396
 
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
397
+ # OVERRIDE(v8.1.1):
398
+ # - comment is retrieved differently than PG for performance
399
+ # - gather detailed information about spatial columns. See
400
+ # `#crdb_column_definitions`
393
401
  def column_definitions(table_name)
394
402
  fields = query(<<~SQL, "SCHEMA")
395
403
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
@@ -397,7 +405,7 @@ module ActiveRecord
397
405
  c.collname, NULL AS comment,
398
406
  attidentity,
399
407
  attgenerated,
400
- NULL as is_hidden
408
+ a.attishidden
401
409
  FROM pg_attribute a
402
410
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
403
411
  LEFT JOIN pg_type t ON a.atttypid = t.oid
@@ -428,7 +436,6 @@ module ActiveRecord
428
436
  dtype = field[f_type]
429
437
  field[f_type] = crdb_fields[field[f_attname]][2].downcase if re.match(dtype)
430
438
  field[f_comment] = crdb_fields[field[f_attname]][1]
431
- field[f_is_hidden] = true if crdb_fields[field[f_attname]][3]
432
439
  field
433
440
  end
434
441
  fields.delete_if do |field|
@@ -444,13 +451,24 @@ module ActiveRecord
444
451
  # column_definitions. This will include limit,
445
452
  # precision, and scale information in the type.
446
453
  # Ex. geometry -> geometry(point, 4326)
454
+ #
455
+ # The performance difference to fetch comments is very
456
+ # significant. On a M1 Mac, CockroachDB v25.2.2 and
457
+ # data from the test suite, it is 20x times faster.
458
+ #
459
+ # SELECT col_description(a.attrelid, a.attnum)
460
+ # FROM pg_attribute a; -- 1.052s
461
+ #
462
+ # SELECT c.column_comment
463
+ # FROM information_schema.columns c; -- 50ms
464
+ #
447
465
  def crdb_column_definitions(table_name)
448
466
  table_name = PostgreSQL::Utils.extract_schema_qualified_name(table_name)
449
467
  table = table_name.identifier
450
468
  with_schema = " AND c.table_schema = #{quote(table_name.schema)}" if table_name.schema
451
469
  fields = \
452
470
  query(<<~SQL, "SCHEMA")
453
- SELECT c.column_name, c.column_comment, c.crdb_sql_type, c.is_hidden::BOOLEAN
471
+ SELECT c.column_name, c.column_comment, c.crdb_sql_type
454
472
  FROM information_schema.columns c
455
473
  WHERE c.table_name = #{quote(table)}#{with_schema}
456
474
  SQL
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.0"
19
19
  end
@@ -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