pgx_catalog_test.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. package pgserver
  2. import (
  3. "context"
  4. "errors"
  5. "testing"
  6. "github.com/jackc/pgx/v5/pgconn"
  7. )
  8. // A stock pgx connection introspects the catalog with its normal
  9. // Parse/Bind/Execute path. This covers the real driver reading oid/name/regclass
  10. // columns and issuing SET during a session.
  11. func TestPGXCatalogIntrospection(t *testing.T) {
  12. conn := connectPGX(t)
  13. ctx := context.Background()
  14. for _, sql := range []string{
  15. `CREATE TABLE catalog_parent (id INTEGER PRIMARY KEY, label TEXT NOT NULL UNIQUE)`,
  16. `CREATE TABLE catalog_child (id INTEGER PRIMARY KEY, parent_id INTEGER NOT NULL, note TEXT DEFAULT 'n', CONSTRAINT fk_catalog_child FOREIGN KEY (parent_id) REFERENCES catalog_parent(id))`,
  17. `CREATE INDEX idx_catalog_child_note ON catalog_child (note)`,
  18. } {
  19. if _, err := conn.Exec(ctx, sql); err != nil {
  20. t.Fatalf("%s: %v", sql, err)
  21. }
  22. }
  23. var (
  24. oid uint32
  25. relname string
  26. relkind string
  27. )
  28. err := conn.QueryRow(ctx, `
  29. SELECT c.oid, c.relname, c.relkind
  30. FROM pg_catalog.pg_class c
  31. WHERE c.relname = 'catalog_child'`).Scan(&oid, &relname, &relkind)
  32. if err != nil {
  33. t.Fatalf("pg_class scan: %v", err)
  34. }
  35. if oid == 0 || relname != "catalog_child" || relkind != "r" {
  36. t.Fatalf("pg_class = %d %q %q", oid, relname, relkind)
  37. }
  38. rows, err := conn.Query(ctx, `
  39. SELECT n.nspname, a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod)
  40. FROM pg_catalog.pg_class c
  41. JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
  42. JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
  43. WHERE c.relname = 'catalog_child' AND a.attnum > 0
  44. ORDER BY a.attnum`)
  45. if err != nil {
  46. t.Fatalf("catalog join: %v", err)
  47. }
  48. defer rows.Close()
  49. var columns []string
  50. for rows.Next() {
  51. var schema, column, typ string
  52. if err := rows.Scan(&schema, &column, &typ); err != nil {
  53. t.Fatalf("scan: %v", err)
  54. }
  55. if schema != "public" {
  56. t.Fatalf("schema = %q", schema)
  57. }
  58. columns = append(columns, column+":"+typ)
  59. }
  60. if err := rows.Err(); err != nil {
  61. t.Fatalf("rows: %v", err)
  62. }
  63. want := []string{"id:integer", "parent_id:integer", "note:text"}
  64. if len(columns) != len(want) {
  65. t.Fatalf("columns = %v, want %v", columns, want)
  66. }
  67. for i := range want {
  68. if columns[i] != want[i] {
  69. t.Fatalf("columns = %v, want %v", columns, want)
  70. }
  71. }
  72. var (
  73. tableName string
  74. columnName string
  75. dataType string
  76. isNullable string
  77. )
  78. err = conn.QueryRow(ctx, `
  79. SELECT table_name, column_name, data_type, is_nullable
  80. FROM information_schema.columns
  81. WHERE table_name = 'catalog_child' AND column_name = 'note'`).
  82. Scan(&tableName, &columnName, &dataType, &isNullable)
  83. if err != nil {
  84. t.Fatalf("information_schema scan: %v", err)
  85. }
  86. if tableName != "catalog_child" || columnName != "note" || dataType != "text" || isNullable != "YES" {
  87. t.Fatalf("information_schema row = %q %q %q %q", tableName, columnName, dataType, isNullable)
  88. }
  89. if _, err := conn.Exec(ctx, "SET search_path TO public"); err != nil {
  90. t.Fatalf("SET search_path: %v", err)
  91. }
  92. var searchPath string
  93. if err := conn.QueryRow(ctx, "SHOW search_path").Scan(&searchPath); err != nil {
  94. t.Fatalf("SHOW search_path: %v", err)
  95. }
  96. if searchPath != "public" {
  97. t.Fatalf("search_path = %q", searchPath)
  98. }
  99. var regclassRows int
  100. if err := conn.QueryRow(ctx, `
  101. SELECT count(*) FROM pg_catalog.pg_constraint
  102. WHERE conrelid = 'catalog_child'::regclass AND contype = 'f'`).Scan(&regclassRows); err != nil {
  103. t.Fatalf("regclass constraint query: %v", err)
  104. }
  105. if regclassRows != 1 {
  106. t.Fatalf("foreign key constraint count = %d, want 1", regclassRows)
  107. }
  108. }
  109. // An unknown regclass over the extended protocol is a 42P01 PgError, not an
  110. // XX000 internal error, and to_regclass returns NULL for the same name.
  111. func TestPGXCatalogUndefinedRelationSQLState(t *testing.T) {
  112. conn := connectPGX(t)
  113. ctx := context.Background()
  114. if _, err := conn.Exec(ctx, `CREATE TABLE catalog_state (id INTEGER PRIMARY KEY)`); err != nil {
  115. t.Fatalf("create table: %v", err)
  116. }
  117. for _, sql := range []string{
  118. `SELECT 'missing_relation'::regclass`,
  119. `SELECT 'missing_type'::regtype`,
  120. `SELECT count(*) FROM pg_catalog.pg_class WHERE oid = 'missing_relation'::regclass`,
  121. `SELECT count(*) FROM public.pg_class`,
  122. } {
  123. var n int64
  124. err := conn.QueryRow(ctx, sql).Scan(&n)
  125. var pgErr *pgconn.PgError
  126. if !errors.As(err, &pgErr) {
  127. t.Fatalf("%s: err = %v, want a PgError", sql, err)
  128. }
  129. if pgErr.Code != "42P01" {
  130. t.Fatalf("%s: SQLSTATE = %q, want 42P01 (%s)", sql, pgErr.Code, pgErr.Message)
  131. }
  132. }
  133. var missing, present *string
  134. err := conn.QueryRow(ctx, `SELECT to_regclass('missing_relation'), to_regclass('catalog_state')`).
  135. Scan(&missing, &present)
  136. if err != nil {
  137. t.Fatalf("to_regclass: %v", err)
  138. }
  139. if missing != nil || present == nil || *present != "catalog_state" {
  140. t.Fatalf("to_regclass = %v / %v, want NULL / catalog_state", missing, present)
  141. }
  142. }