foreign_key_concurrency_test.go 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. package executor
  2. import (
  3. "fmt"
  4. "sync"
  5. "testing"
  6. )
  7. // A child insert and a parent delete that run concurrently must never both
  8. // commit, and the table set must never contain a dangling child reference.
  9. // Both statements take the foreign-key write gate, so exactly one wins; if that
  10. // ever regresses, the loser is not a foreign-key violation or the invariant scan
  11. // finds an orphan.
  12. func TestForeignKeyConcurrentChildInsertAndParentDelete(t *testing.T) {
  13. _, schema, table := newTestDB(t)
  14. inserter := newExec(schema, table)
  15. deleter := newExec(schema, table)
  16. execMust(t, inserter, "CREATE TABLE parent (id INTEGER PRIMARY KEY)")
  17. execMust(t, inserter, "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))")
  18. const rounds = 100
  19. for round := 1; round <= rounds; round++ {
  20. execMust(t, inserter, fmt.Sprintf("INSERT INTO parent (id) VALUES (%d)", round))
  21. start := make(chan struct{})
  22. insertDone := make(chan error, 1)
  23. deleteDone := make(chan error, 1)
  24. var wg sync.WaitGroup
  25. wg.Add(2)
  26. go func() {
  27. defer wg.Done()
  28. <-start
  29. _, err := execSQL(inserter, fmt.Sprintf("INSERT INTO child (id, parent_id) VALUES (%d, %d)", round, round))
  30. insertDone <- err
  31. }()
  32. go func() {
  33. defer wg.Done()
  34. <-start
  35. _, err := execSQL(deleter, fmt.Sprintf("DELETE FROM parent WHERE id = %d", round))
  36. deleteDone <- err
  37. }()
  38. close(start)
  39. wg.Wait()
  40. insertErr := <-insertDone
  41. deleteErr := <-deleteDone
  42. switch {
  43. case insertErr == nil && deleteErr == nil:
  44. t.Fatalf("round %d: child insert and parent delete both committed", round)
  45. case insertErr == nil:
  46. requireForeignKeyViolation(t, deleteErr)
  47. case deleteErr == nil:
  48. requireForeignKeyViolation(t, insertErr)
  49. default:
  50. t.Fatalf("round %d: both statements failed: insert=%v delete=%v", round, insertErr, deleteErr)
  51. }
  52. dangling := execMust(t, inserter, "SELECT count(*) FROM child c WHERE NOT EXISTS (SELECT 1 FROM parent p WHERE p.id = c.parent_id)")
  53. if dangling.Rows[0][0] != int64(0) {
  54. t.Fatalf("round %d: %v dangling child rows", round, dangling.Rows[0][0])
  55. }
  56. execMust(t, inserter, "DELETE FROM child")
  57. execMust(t, inserter, fmt.Sprintf("DELETE FROM parent WHERE id = %d", round))
  58. }
  59. }