pg_catalog_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. package pgserver
  2. import (
  3. "strconv"
  4. "strings"
  5. "testing"
  6. )
  7. func simpleQuery(sql string) *Message {
  8. return &Message{Type: MsgQuery, Data: append([]byte(sql), 0)}
  9. }
  10. // Catalog columns carry PostgreSQL's types over the wire (oid 26, name 19), so
  11. // a driver decodes them without falling back to TEXT.
  12. func TestPgCatalogWireTypes(t *testing.T) {
  13. c, client := newDataConnection(t)
  14. for _, sql := range []string{
  15. "CREATE TABLE wire (id INTEGER PRIMARY KEY, payload JSONB)",
  16. "CREATE INDEX idx_wire_payload ON wire (payload)",
  17. } {
  18. if msgs := runQuery(t, c, client, simpleQuery(sql)); errorCode(msgs[0]) != "" {
  19. t.Fatalf("%s: %s", sql, errorMessage(msgs[0]))
  20. }
  21. }
  22. msgs := runQuery(t, c, client, simpleQuery(`
  23. SELECT c.oid, c.relname, c.relkind
  24. FROM pg_catalog.pg_class c
  25. WHERE c.relname = 'wire'`))
  26. if msg := findError(msgs); msg != nil {
  27. t.Fatalf("catalog query failed: %s", errorMessage(msg))
  28. }
  29. rd := findRowDescription(t, msgs)
  30. if got := rowDescriptionOIDs(t, rd.Data); len(got) != 3 || got[0] != 26 || got[1] != 19 || got[2] != 25 {
  31. t.Fatalf("catalog OIDs = %v, want [26 19 25]", got)
  32. }
  33. rows := catalogRows(t, msgs)
  34. if len(rows) != 1 || rows[0][1] != "wire" || rows[0][2] != "r" {
  35. t.Fatalf("pg_class row = %v", rows)
  36. }
  37. if _, err := strconv.ParseUint(rows[0][0], 10, 32); err != nil {
  38. t.Fatalf("oid %q is not numeric: %v", rows[0][0], err)
  39. }
  40. }
  41. // A join across pg_class/pg_namespace/pg_attribute works over the simple-query
  42. // protocol, which is how migration tools list columns.
  43. func TestPgCatalogJoinOverWire(t *testing.T) {
  44. c, client := newDataConnection(t)
  45. for _, sql := range []string{
  46. "CREATE TABLE wire_join (id INTEGER PRIMARY KEY, label TEXT NOT NULL)",
  47. } {
  48. if msgs := runQuery(t, c, client, simpleQuery(sql)); errorCode(msgs[0]) != "" {
  49. t.Fatalf("%s: %s", sql, errorMessage(msgs[0]))
  50. }
  51. }
  52. msgs := runQuery(t, c, client, simpleQuery(`
  53. SELECT n.nspname, a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod)
  54. FROM pg_catalog.pg_class c
  55. JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
  56. JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
  57. WHERE c.relname = 'wire_join' AND a.attnum > 0
  58. ORDER BY a.attnum`))
  59. if msg := findError(msgs); msg != nil {
  60. t.Fatalf("catalog join failed: %s", errorMessage(msg))
  61. }
  62. rows := catalogRows(t, msgs)
  63. if len(rows) != 2 || rows[0][1] != "id" || rows[0][2] != "integer" || rows[1][1] != "label" {
  64. t.Fatalf("catalog join rows = %v", rows)
  65. }
  66. }
  67. // ORM connections issue SET during startup; the engine acknowledges it and
  68. // SHOW reports the retained search path.
  69. func TestSetAndShowSearchPathOverWire(t *testing.T) {
  70. c, client := newDataConnection(t)
  71. if msgs := runQuery(t, c, client, simpleQuery("SET search_path TO public")); errorCode(msgs[0]) != "" {
  72. t.Fatalf("SET search_path failed: %s", errorMessage(msgs[0]))
  73. }
  74. if msgs := runQuery(t, c, client, simpleQuery("SET TIME ZONE 'UTC'")); errorCode(msgs[0]) != "" {
  75. t.Fatalf("SET TIME ZONE failed: %s", errorMessage(msgs[0]))
  76. }
  77. msgs := runQuery(t, c, client, simpleQuery("SHOW search_path"))
  78. if msg := findError(msgs); msg != nil {
  79. t.Fatalf("SHOW search_path failed: %s", errorMessage(msg))
  80. }
  81. rows := catalogRows(t, msgs)
  82. if len(rows) != 1 || rows[0][0] != "public" {
  83. t.Fatalf("SHOW search_path = %v", rows)
  84. }
  85. msgs = runQuery(t, c, client, simpleQuery("SHOW client_encoding"))
  86. if rows := catalogRows(t, msgs); len(rows) != 1 || !strings.EqualFold(rows[0][0], "UTF8") {
  87. t.Fatalf("SHOW client_encoding = %v", rows)
  88. }
  89. }
  90. // An unknown regclass/regtype reaches the client as SQLSTATE 42P01 over the
  91. // simple-query protocol, both in a projection and inside a WHERE clause, and
  92. // to_regclass returns NULL instead of failing.
  93. func TestPgCatalogUndefinedRelationSQLState(t *testing.T) {
  94. c, client := newDataConnection(t)
  95. for _, sql := range []string{
  96. "CREATE TABLE wire_state (id INTEGER PRIMARY KEY)",
  97. } {
  98. if msgs := runQuery(t, c, client, simpleQuery(sql)); errorCode(msgs[0]) != "" {
  99. t.Fatalf("%s: %s", sql, errorMessage(msgs[0]))
  100. }
  101. }
  102. for _, sql := range []string{
  103. `SELECT 'missing_relation'::regclass`,
  104. `SELECT 'missing_type'::regtype`,
  105. `SELECT count(*) FROM pg_catalog.pg_class WHERE oid = 'missing_relation'::regclass`,
  106. `SELECT count(*) FROM public.pg_class`,
  107. } {
  108. msgs := runQuery(t, c, client, simpleQuery(sql))
  109. msg := findError(msgs)
  110. if msg == nil {
  111. t.Fatalf("%s: expected an error, got %v", sql, msgs)
  112. }
  113. if code := errorCode(msg); code != ErrCodeUndefinedTable {
  114. t.Fatalf("%s: SQLSTATE = %q, want %q", sql, code, ErrCodeUndefinedTable)
  115. }
  116. }
  117. msgs := runQuery(t, c, client, simpleQuery(`SELECT to_regclass('missing_relation'), to_regclass('wire_state')`))
  118. if msg := findError(msgs); msg != nil {
  119. t.Fatalf("to_regclass failed: %s", errorMessage(msg))
  120. }
  121. rd := findRowDescription(t, msgs)
  122. if got := rowDescriptionOIDs(t, rd.Data); len(got) != 2 || got[0] != 2205 || got[1] != 2205 {
  123. t.Fatalf("to_regclass OIDs = %v, want regclass 2205 twice", got)
  124. }
  125. rows := catalogRows(t, msgs)
  126. if len(rows) != 1 || rows[0][0] != "<null>" || rows[0][1] != "wire_state" {
  127. t.Fatalf("to_regclass rows = %v, want NULL then wire_state", rows)
  128. }
  129. }
  130. // pg_type's function attributes are regproc (OID 24) with numeric values, not
  131. // the text "-".
  132. func TestPgCatalogRegprocWireType(t *testing.T) {
  133. c, client := newDataConnection(t)
  134. msgs := runQuery(t, c, client, simpleQuery(`SELECT typinput, typoutput FROM pg_type WHERE typname = 'int4'`))
  135. if msg := findError(msgs); msg != nil {
  136. t.Fatalf("pg_type query failed: %s", errorMessage(msg))
  137. }
  138. rd := findRowDescription(t, msgs)
  139. if got := rowDescriptionOIDs(t, rd.Data); len(got) != 2 || got[0] != 24 || got[1] != 24 {
  140. t.Fatalf("regproc OIDs = %v, want [24 24]", got)
  141. }
  142. rows := catalogRows(t, msgs)
  143. if len(rows) != 1 || rows[0][0] != "0" || rows[0][1] != "0" {
  144. t.Fatalf("pg_type regproc values = %v, want 0/0", rows)
  145. }
  146. }