unique_cache_test.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. package executor
  2. import (
  3. "fmt"
  4. "strings"
  5. "testing"
  6. "github.com/danfragoso/pizzasql-next/pkg/storage"
  7. "github.com/danfragoso/pizzasql-next/pkg/testkv"
  8. )
  9. func TestWarmUniqueValidationDoesNotScan(t *testing.T) {
  10. kv := testkv.New(t)
  11. pool := kv.Pool(8)
  12. defer pool.Close()
  13. schemas := storage.NewSchemaManager(pool, "unique_cache")
  14. tables := storage.NewTableManager(pool, schemas, "unique_cache")
  15. e := newExec(schemas, tables)
  16. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, v INTEGER UNIQUE)")
  17. var values []string
  18. for i := 1; i <= 100; i++ {
  19. values = append(values, fmt.Sprintf("(%d,%d)", i, i))
  20. }
  21. execMust(t, e, "INSERT INTO t VALUES "+strings.Join(values, ","))
  22. for _, sql := range []string{
  23. "INSERT INTO t VALUES (101,101)",
  24. "UPDATE t SET v=102 WHERE id=101",
  25. "DELETE FROM t WHERE id=101",
  26. "INSERT INTO t VALUES (102,102)",
  27. "BEGIN", "INSERT INTO t VALUES (103,103)", "ROLLBACK",
  28. "INSERT INTO t VALUES (103,103)",
  29. "BEGIN", "UPDATE t SET v=501 WHERE id=1", "UPDATE t SET v=1 WHERE id=2", "UPDATE t SET v=2 WHERE id=1", "COMMIT",
  30. "INSERT INTO t VALUES (104,NULL),(105,NULL)",
  31. } {
  32. before := kv.RequestCount(10)
  33. execMust(t, e, sql)
  34. if n := kv.RequestCount(10) - before; n != 0 {
  35. t.Fatalf("%s: unique validation scanned %d pages", sql, n)
  36. }
  37. }
  38. for _, sql := range []string{"INSERT INTO t VALUES (200,2)", "UPDATE t SET v=2 WHERE id=103"} {
  39. if _, err := execSQL(e, sql); err == nil || !strings.Contains(err.Error(), "UNIQUE") {
  40. t.Fatalf("%s: %v", sql, err)
  41. }
  42. }
  43. execMust(t, e, "UPDATE t SET id=1000 WHERE id=1")
  44. if _, err := execSQL(e, "INSERT INTO t VALUES (200,2)"); err == nil {
  45. t.Fatal("PK move released an owned unique value")
  46. }
  47. execMust(t, e, "DELETE FROM t WHERE id=1000")
  48. execMust(t, e, "INSERT INTO t VALUES (200,2)")
  49. // A new manager models restart: durable rows rebuild the cache once, not on
  50. // every mutation. Managers are shared per database in normal operation.
  51. schemas = storage.NewSchemaManager(pool, "unique_cache")
  52. tables = storage.NewTableManager(pool, schemas, "unique_cache")
  53. e = newExec(schemas, tables)
  54. before := kv.RequestCount(10)
  55. execMust(t, e, "INSERT INTO t VALUES (201,201)")
  56. if kv.RequestCount(10) == before {
  57. t.Fatal("cold UNIQUE cache did not read durable state")
  58. }
  59. before = kv.RequestCount(10)
  60. execMust(t, e, "INSERT INTO t VALUES (202,202)")
  61. if kv.RequestCount(10) != before {
  62. t.Fatal("warm UNIQUE cache rebuilt after restart")
  63. }
  64. if _, err := execSQL(e, "INSERT INTO t VALUES (203,201)"); err == nil {
  65. t.Fatal("restarted cache lost an existing value")
  66. }
  67. }
  68. func TestUniqueCacheDropRecreateAndFailedTransaction(t *testing.T) {
  69. _, schemas, tables := newTestDB(t)
  70. e := newExec(schemas, tables)
  71. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, v INTEGER)")
  72. execMust(t, e, "CREATE UNIQUE INDEX MixedCase ON t(v)")
  73. execMust(t, e, "INSERT INTO t VALUES (1,1)")
  74. execMust(t, e, "BEGIN")
  75. execMust(t, e, "INSERT INTO t VALUES (2,2),(3,2)")
  76. if _, err := execSQL(e, "COMMIT"); err == nil {
  77. t.Fatal("duplicate pending values committed")
  78. }
  79. execMust(t, e, "INSERT INTO t VALUES (2,2)")
  80. execMust(t, e, "DROP INDEX MixedCase")
  81. execMust(t, e, "INSERT INTO t VALUES (3,2)")
  82. if _, err := execSQL(e, "CREATE UNIQUE INDEX MixedCase ON t(v)"); err == nil {
  83. t.Fatal("recreated unique index ignored durable duplicate")
  84. }
  85. execMust(t, e, "DELETE FROM t WHERE id=3")
  86. execMust(t, e, "CREATE UNIQUE INDEX MixedCase ON t(v)")
  87. if _, err := execSQL(e, "INSERT INTO t VALUES (3,2)"); err == nil {
  88. t.Fatal("recreated cache lost owner")
  89. }
  90. }
  91. func TestUniqueCacheAmbiguousWriteOutcome(t *testing.T) {
  92. for _, operation := range []string{"transaction", "insert", "update", "delete"} {
  93. t.Run(operation, func(t *testing.T) {
  94. kv := testkv.New(t)
  95. pool := kv.Pool(8)
  96. defer pool.Close()
  97. schemas := storage.NewSchemaManager(pool, "ambiguous")
  98. tables := storage.NewTableManager(pool, schemas, "ambiguous")
  99. e := newExec(schemas, tables)
  100. execMust(t, e, "CREATE TABLE t (id INTEGER PRIMARY KEY, v INTEGER UNIQUE)")
  101. execMust(t, e, "INSERT INTO t VALUES (1,1)")
  102. kv.DropNextCompareReply()
  103. var err error
  104. switch operation {
  105. case "transaction":
  106. _, err = execSQL(e, "INSERT INTO t VALUES (2,2)")
  107. case "insert":
  108. err = tables.Insert("t", storage.Row{"id": int64(2), "v": int64(2)})
  109. case "update":
  110. _, _, err = tables.UpdateByPK("t", "1", func(storage.Row) (storage.Row, error) { return storage.Row{"v": int64(2)}, nil })
  111. case "delete":
  112. _, _, err = tables.DeleteByPK("t", "1")
  113. }
  114. if err == nil {
  115. t.Fatal("dropped reply unexpectedly succeeded")
  116. }
  117. if operation == "delete" {
  118. execMust(t, e, "INSERT INTO t VALUES (3,1)")
  119. } else {
  120. if _, err := execSQL(e, "INSERT INTO t VALUES (3,2)"); err == nil {
  121. t.Fatal("unknown but durable unique owner was ignored")
  122. }
  123. if operation == "update" {
  124. execMust(t, e, "INSERT INTO t VALUES (4,1)")
  125. }
  126. }
  127. })
  128. }
  129. }