|
157 | 157 | stmt.match?(/\AALTER\s+INDEX\s+"?IX_DBMS_META_INV_UNIQ"?\s+INVISIBLE\s*\z/i) |
158 | 158 | end |
159 | 159 | expect(alter_stmt).not_to be_nil |
160 | | - |
161 | | - # And the standalone CREATE INDEX should NOT be emitted (the |
162 | | - # constraint inline already created the index). |
163 | | - standalone = dump.split("\n\n/\n\n").select do |stmt| |
164 | | - stmt.match?(/\ACREATE\s+(?:UNIQUE\s+)?INDEX\s+"?IX_DBMS_META_INV_UNIQ"?/i) |
165 | | - end |
166 | | - expect(standalone).to be_empty |
167 | 160 | ensure |
168 | 161 | schema_define { drop_table :test_dbms_meta_inv_uniq, if_exists: true } |
169 | 162 | end |
|
232 | 225 | expect(dump).not_to match(/PCTFREE\s+/i) |
233 | 226 | end |
234 | 227 |
|
235 | | - it "does not emit a standalone CREATE INDEX for a UNIQUE constraint's backing index" do |
| 228 | + it "splits a UNIQUE-constraint-with-named-backing-index into separate CREATE INDEX and ADD CONSTRAINT statements" do |
236 | 229 | schema_define do |
237 | 230 | create_table :test_dbms_metadata_posts, force: true do |t| |
238 | 231 | t.string :email |
239 | 232 | end |
240 | 233 | add_index :test_dbms_metadata_posts, :email, unique: true, name: "ix_test_dbms_metadata_email" |
241 | 234 | end |
242 | 235 | dump = @conn.structure_dump |
243 | | - # `CONSTRAINTS=TRUE` already inlines the unique constraint (and its |
244 | | - # backing index) into the CREATE TABLE DDL. Emitting a separate |
245 | | - # `CREATE UNIQUE INDEX` for the same name would duplicate it. |
246 | | - standalone_index_stmts = dump.split("\n\n/\n\n").select do |stmt| |
| 236 | + stmts = dump.split("\n\n/\n\n") |
| 237 | + |
| 238 | + # `GET_DDL('TABLE', ...)` returns CREATE TABLE, CREATE UNIQUE INDEX, |
| 239 | + # and ALTER TABLE ... ADD CONSTRAINT ... USING INDEX as one CLOB |
| 240 | + # when the UNIQUE constraint references a named backing index. |
| 241 | + # `SQLTERMINATOR=TRUE` plus `split_dbms_metadata_sql_ddl` splits |
| 242 | + # them so each one is a separate dump entry; `structure_load` |
| 243 | + # would otherwise feed the whole CLOB as one statement and Oracle |
| 244 | + # would reject it with ORA-00922. |
| 245 | + create_index_stmts = stmts.select do |stmt| |
247 | 246 | stmt.match?(/\ACREATE\s+UNIQUE\s+INDEX\s+"?IX_TEST_DBMS_METADATA_EMAIL"?/i) |
248 | 247 | end |
249 | | - expect(standalone_index_stmts).to be_empty |
| 248 | + expect(create_index_stmts.size).to eq(1) |
| 249 | + |
| 250 | + add_constraint_stmts = stmts.select do |stmt| |
| 251 | + stmt.match?(/\AALTER\s+TABLE\s+"?TEST_DBMS_METADATA_POSTS"?\s+ADD\s+CONSTRAINT\s+"?IX_TEST_DBMS_METADATA_EMAIL"?\s+UNIQUE/i) |
| 252 | + end |
| 253 | + expect(add_constraint_stmts.size).to eq(1) |
| 254 | + end |
| 255 | + |
| 256 | + # Round-trip regression for the same table shape: dump → drop → load |
| 257 | + # used to fail with ORA-00922 because the multi-statement `GET_DDL` |
| 258 | + # CLOB was sent to Oracle as a single statement. |
| 259 | + it "round-trips a UNIQUE-with-named-backing-index table through structure_dump/structure_load" do |
| 260 | + require "tempfile" |
| 261 | + schema_define do |
| 262 | + create_table :test_dbms_metadata_posts, force: true do |t| |
| 263 | + t.string :email |
| 264 | + end |
| 265 | + add_index :test_dbms_metadata_posts, :email, unique: true, name: "ix_test_dbms_metadata_email" |
| 266 | + end |
| 267 | + |
| 268 | + temp_file = Tempfile.create(["oracle_enhanced_roundtrip", ".sql"]).path |
| 269 | + config = ActiveRecord::Base.connection_db_config.configuration_hash |
| 270 | + |
| 271 | + begin |
| 272 | + ActiveRecord::Tasks::DatabaseTasks.structure_dump(config, temp_file) |
| 273 | + ActiveRecord::Tasks::DatabaseTasks.drop(config) |
| 274 | + ActiveRecord::Tasks::DatabaseTasks.structure_load(config, temp_file) |
| 275 | + expect(@conn.table_exists?(:test_dbms_metadata_posts)).to be(true) |
| 276 | + ensure |
| 277 | + File.unlink(temp_file) if File.exist?(temp_file) |
| 278 | + end |
| 279 | + end |
| 280 | + |
| 281 | + # Round-trip regression for foreign keys: with SQLTERMINATOR=TRUE |
| 282 | + # the dependent FK DDL also ends with ';', so the dump must run it |
| 283 | + # through `split_dbms_metadata_sql_ddl` to strip the trailing |
| 284 | + # terminator. Otherwise the load raises ORA-00911 on the trailing ';'. |
| 285 | + it "round-trips foreign-key constraints through structure_dump/structure_load" do |
| 286 | + require "tempfile" |
| 287 | + schema_define do |
| 288 | + create_table :test_dbms_metadata_posts, force: true do |t| |
| 289 | + t.string :title |
| 290 | + end |
| 291 | + create_table :test_dbms_metadata_children, force: true do |t| |
| 292 | + t.integer :test_dbms_metadata_post_id |
| 293 | + end |
| 294 | + add_foreign_key :test_dbms_metadata_children, :test_dbms_metadata_posts, |
| 295 | + column: :test_dbms_metadata_post_id, name: "fk_dbms_meta_rt" |
| 296 | + end |
| 297 | + |
| 298 | + temp_file = Tempfile.create(["oracle_enhanced_fk_rt", ".sql"]).path |
| 299 | + config = ActiveRecord::Base.connection_db_config.configuration_hash |
| 300 | + |
| 301 | + begin |
| 302 | + ActiveRecord::Tasks::DatabaseTasks.structure_dump(config, temp_file) |
| 303 | + ActiveRecord::Tasks::DatabaseTasks.drop(config) |
| 304 | + ActiveRecord::Tasks::DatabaseTasks.structure_load(config, temp_file) |
| 305 | + expect(@conn.table_exists?(:test_dbms_metadata_children)).to be(true) |
| 306 | + fk = @conn.foreign_keys(:test_dbms_metadata_children).find { |f| f.name == "fk_dbms_meta_rt" } |
| 307 | + expect(fk).not_to be_nil |
| 308 | + ensure |
| 309 | + File.unlink(temp_file) if File.exist?(temp_file) |
| 310 | + end |
250 | 311 | end |
251 | 312 | end |
252 | 313 |
|
|
0 commit comments