capabilities_test.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package capabilities
  2. import (
  3. "encoding/json"
  4. "strings"
  5. "testing"
  6. "github.com/danfragoso/pizzasql-next/pkg/pgerror"
  7. "github.com/danfragoso/pizzasql-next/pkg/version"
  8. )
  9. func TestCurrentDocumentIsVersionedAndMarshalable(t *testing.T) {
  10. doc := Current()
  11. if doc.SchemaVersion != SchemaVersion {
  12. t.Fatalf("schema version = %d, want %d", doc.SchemaVersion, SchemaVersion)
  13. }
  14. if doc.Engine != "pizzasql" || doc.EngineVersion != version.String() {
  15. t.Fatalf("engine identity = %q/%q", doc.Engine, doc.EngineVersion)
  16. }
  17. body, err := json.Marshal(doc)
  18. if err != nil {
  19. t.Fatalf("marshal: %v", err)
  20. }
  21. var round Document
  22. if err := json.Unmarshal(body, &round); err != nil {
  23. t.Fatalf("unmarshal: %v", err)
  24. }
  25. if round.Constraints.Unique.SQLState != "23505" {
  26. t.Fatalf("unique SQLSTATE = %q, want 23505", round.Constraints.Unique.SQLState)
  27. }
  28. if round.Transactions.SerializationCode != "40001" {
  29. t.Fatalf("serialization SQLSTATE = %q, want 40001", round.Transactions.SerializationCode)
  30. }
  31. if round.ErrorCodes["0A000"] != "feature_not_supported" {
  32. t.Fatalf("0A000 mapping = %q", round.ErrorCodes["0A000"])
  33. }
  34. if !round.Notifications.Listen.Supported || !round.Notifications.Notify.Supported {
  35. t.Fatal("LISTEN/NOTIFY should be advertised as supported")
  36. }
  37. if round.Notifications.PayloadLimit != 8000 || round.Notifications.DurableReplay {
  38. t.Fatalf("unexpected notification limits: %+v", round.Notifications)
  39. }
  40. if !round.DDL.RenameColumnIndexes.Supported {
  41. t.Fatal("RENAME COLUMN must advertise index metadata rewriting")
  42. }
  43. }
  44. func TestJSONArrowOperatorsAreAdvertised(t *testing.T) {
  45. arrow := Current().JSON.ArrowOperators
  46. if !arrow.Supported {
  47. t.Fatal("-> and ->> should be advertised as supported")
  48. }
  49. if arrow.SQLState != "" {
  50. t.Fatalf("arrow operator SQLSTATE = %q, want none", arrow.SQLState)
  51. }
  52. if arrow.Status == "rejected" {
  53. t.Fatalf("arrow operator status = %q, want a supported status", arrow.Status)
  54. }
  55. }
  56. func TestAddColumnBackfillIsAdvertised(t *testing.T) {
  57. doc := Current()
  58. if !doc.DDL.AddColumnBackfill.Supported {
  59. t.Fatalf("add_column_backfill = %+v, want supported", doc.DDL.AddColumnBackfill)
  60. }
  61. if doc.DDL.AddColumnBackfill.Status != "rewrites_rows" {
  62. t.Fatalf("add_column_backfill status = %q, want rewrites_rows", doc.DDL.AddColumnBackfill.Status)
  63. }
  64. if doc.DDL.AddColumn.Status == "metadata_only" {
  65. t.Fatalf("add_column status = %q, but a DEFAULT rewrites rows", doc.DDL.AddColumn.Status)
  66. }
  67. }
  68. // The capability document must describe the current default semantics: constant
  69. // and whitelisted clock defaults are supported, the clock ones are evaluated on
  70. // write, and only the remaining non-constant forms are rejected.
  71. func TestNonConstantDefaultsAreAdvertisedAsEvaluatedAtWriteTime(t *testing.T) {
  72. defaults := Current().Constraints.DefaultValues
  73. if !defaults.Supported {
  74. t.Fatalf("default_values = %+v, want supported", defaults)
  75. }
  76. if defaults.Status != "evaluated_at_write_time" {
  77. t.Fatalf("default_values status = %q, want evaluated_at_write_time", defaults.Status)
  78. }
  79. if defaults.SQLState != "0A000" {
  80. t.Fatalf("default_values SQLSTATE = %q, want 0A000 for the remaining rejected forms", defaults.SQLState)
  81. }
  82. }
  83. func TestCheckConstraintIsAdvertised(t *testing.T) {
  84. doc := Current()
  85. check := doc.Constraints.Check
  86. if !check.Supported {
  87. t.Fatalf("check = %+v, want supported", check)
  88. }
  89. if check.Status != "enforced" {
  90. t.Fatalf("check status = %q, want enforced", check.Status)
  91. }
  92. if check.SQLState != pgerror.CodeCheckViolation {
  93. t.Fatalf("check SQLSTATE = %q, want %q", check.SQLState, pgerror.CodeCheckViolation)
  94. }
  95. if check.Notes == "" {
  96. t.Fatal("check notes must describe the enforcement surface")
  97. }
  98. if got := doc.ErrorCodes[pgerror.CodeCheckViolation]; got != "check_violation" {
  99. t.Fatalf("23514 mapping = %q, want check_violation", got)
  100. }
  101. }
  102. func TestDDLNotesDescribeCheckDependencies(t *testing.T) {
  103. doc := Current()
  104. if !strings.Contains(doc.DDL.DropColumn.Notes, "CHECK") || !strings.Contains(doc.DDL.DropColumn.Notes, "0A000") {
  105. t.Fatalf("drop_column notes = %q, want the persisted CHECK restriction", doc.DDL.DropColumn.Notes)
  106. }
  107. if !strings.Contains(doc.DDL.RenameColumn.Notes, "CHECK") || !strings.Contains(doc.DDL.RenameColumn.Notes, "0A000") {
  108. t.Fatalf("rename_column notes = %q, want the persisted CHECK restriction", doc.DDL.RenameColumn.Notes)
  109. }
  110. if !strings.Contains(doc.DDL.AddColumnBackfill.Notes, "23514") {
  111. t.Fatalf("add_column_backfill notes = %q, want the CHECK backfill failure", doc.DDL.AddColumnBackfill.Notes)
  112. }
  113. }
  114. // Composite primary keys are advertised as supported, with the SQLSTATE callers
  115. // should expect for a duplicate key.
  116. func TestCompositePrimaryKeyIsAdvertised(t *testing.T) {
  117. doc := Current()
  118. composite := doc.Constraints.CompositePK
  119. if !composite.Supported {
  120. t.Fatalf("composite_primary_key = %+v, want supported", composite)
  121. }
  122. if composite.Status == "rejected" {
  123. t.Fatalf("composite_primary_key status = %q, want a supported status", composite.Status)
  124. }
  125. if composite.SQLState != "23505" {
  126. t.Fatalf("composite_primary_key SQLSTATE = %q, want 23505", composite.SQLState)
  127. }
  128. if !doc.Constraints.PrimaryKey.Supported {
  129. t.Fatalf("primary_key = %+v, want supported", doc.Constraints.PrimaryKey)
  130. }
  131. body, err := json.Marshal(doc)
  132. if err != nil {
  133. t.Fatalf("marshal: %v", err)
  134. }
  135. if !strings.Contains(string(body), `"composite_primary_key":{"supported":true`) {
  136. t.Fatalf("document does not advertise composite_primary_key as supported: %s", body)
  137. }
  138. }
  139. // Foreign keys are advertised as enforced, with the SQLSTATE callers should
  140. // expect for a dangling reference, the referential actions that execute
  141. // immediately, and the explicit exclusions for deferred enforcement.
  142. func TestForeignKeyIsAdvertised(t *testing.T) {
  143. doc := Current()
  144. fk := doc.Constraints.ForeignKey
  145. if !fk.Supported {
  146. t.Fatalf("foreign_key = %+v, want supported", fk)
  147. }
  148. if fk.Status != "enforced" {
  149. t.Fatalf("foreign_key status = %q, want enforced", fk.Status)
  150. }
  151. if fk.SQLState != pgerror.CodeForeignKeyViolation {
  152. t.Fatalf("foreign_key SQLSTATE = %q, want %q", fk.SQLState, pgerror.CodeForeignKeyViolation)
  153. }
  154. for _, want := range []string{
  155. "UNIQUE", "COMMIT", "MATCH SIMPLE", "MATCH keyword", "self",
  156. "CASCADE", "SET NULL", "SET DEFAULT", "NOT NULL", "transitively",
  157. "autocommit", "no later than COMMIT", "statement savepoint", "hand-authored catalog",
  158. "O(FK component)", "DEFERRABLE", "0A000",
  159. "PRAGMA foreign_keys", "rejects OFF", "foreign_key_list", "database-wide foreign-key write gate",
  160. "DROP/RENAME COLUMN", "DROP TABLE", "pg_constraint",
  161. } {
  162. if !strings.Contains(fk.Notes, want) {
  163. t.Fatalf("foreign_key notes = %q, want it to mention %q", fk.Notes, want)
  164. }
  165. }
  166. if got := doc.ErrorCodes[pgerror.CodeForeignKeyViolation]; got != "foreign_key_violation" {
  167. t.Fatalf("23503 mapping = %q, want foreign_key_violation", got)
  168. }
  169. }