| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- package pgserver
- import (
- "context"
- "errors"
- "testing"
- "github.com/jackc/pgx/v5/pgconn"
- )
- // A stock pgx connection introspects the catalog with its normal
- // Parse/Bind/Execute path. This covers the real driver reading oid/name/regclass
- // columns and issuing SET during a session.
- func TestPGXCatalogIntrospection(t *testing.T) {
- conn := connectPGX(t)
- ctx := context.Background()
- for _, sql := range []string{
- `CREATE TABLE catalog_parent (id INTEGER PRIMARY KEY, label TEXT NOT NULL UNIQUE)`,
- `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))`,
- `CREATE INDEX idx_catalog_child_note ON catalog_child (note)`,
- } {
- if _, err := conn.Exec(ctx, sql); err != nil {
- t.Fatalf("%s: %v", sql, err)
- }
- }
- var (
- oid uint32
- relname string
- relkind string
- )
- err := conn.QueryRow(ctx, `
- SELECT c.oid, c.relname, c.relkind
- FROM pg_catalog.pg_class c
- WHERE c.relname = 'catalog_child'`).Scan(&oid, &relname, &relkind)
- if err != nil {
- t.Fatalf("pg_class scan: %v", err)
- }
- if oid == 0 || relname != "catalog_child" || relkind != "r" {
- t.Fatalf("pg_class = %d %q %q", oid, relname, relkind)
- }
- rows, err := conn.Query(ctx, `
- SELECT n.nspname, a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod)
- FROM pg_catalog.pg_class c
- JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
- JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
- WHERE c.relname = 'catalog_child' AND a.attnum > 0
- ORDER BY a.attnum`)
- if err != nil {
- t.Fatalf("catalog join: %v", err)
- }
- defer rows.Close()
- var columns []string
- for rows.Next() {
- var schema, column, typ string
- if err := rows.Scan(&schema, &column, &typ); err != nil {
- t.Fatalf("scan: %v", err)
- }
- if schema != "public" {
- t.Fatalf("schema = %q", schema)
- }
- columns = append(columns, column+":"+typ)
- }
- if err := rows.Err(); err != nil {
- t.Fatalf("rows: %v", err)
- }
- want := []string{"id:integer", "parent_id:integer", "note:text"}
- if len(columns) != len(want) {
- t.Fatalf("columns = %v, want %v", columns, want)
- }
- for i := range want {
- if columns[i] != want[i] {
- t.Fatalf("columns = %v, want %v", columns, want)
- }
- }
- var (
- tableName string
- columnName string
- dataType string
- isNullable string
- )
- err = conn.QueryRow(ctx, `
- SELECT table_name, column_name, data_type, is_nullable
- FROM information_schema.columns
- WHERE table_name = 'catalog_child' AND column_name = 'note'`).
- Scan(&tableName, &columnName, &dataType, &isNullable)
- if err != nil {
- t.Fatalf("information_schema scan: %v", err)
- }
- if tableName != "catalog_child" || columnName != "note" || dataType != "text" || isNullable != "YES" {
- t.Fatalf("information_schema row = %q %q %q %q", tableName, columnName, dataType, isNullable)
- }
- if _, err := conn.Exec(ctx, "SET search_path TO public"); err != nil {
- t.Fatalf("SET search_path: %v", err)
- }
- var searchPath string
- if err := conn.QueryRow(ctx, "SHOW search_path").Scan(&searchPath); err != nil {
- t.Fatalf("SHOW search_path: %v", err)
- }
- if searchPath != "public" {
- t.Fatalf("search_path = %q", searchPath)
- }
- var regclassRows int
- if err := conn.QueryRow(ctx, `
- SELECT count(*) FROM pg_catalog.pg_constraint
- WHERE conrelid = 'catalog_child'::regclass AND contype = 'f'`).Scan(®classRows); err != nil {
- t.Fatalf("regclass constraint query: %v", err)
- }
- if regclassRows != 1 {
- t.Fatalf("foreign key constraint count = %d, want 1", regclassRows)
- }
- }
- // An unknown regclass over the extended protocol is a 42P01 PgError, not an
- // XX000 internal error, and to_regclass returns NULL for the same name.
- func TestPGXCatalogUndefinedRelationSQLState(t *testing.T) {
- conn := connectPGX(t)
- ctx := context.Background()
- if _, err := conn.Exec(ctx, `CREATE TABLE catalog_state (id INTEGER PRIMARY KEY)`); err != nil {
- t.Fatalf("create table: %v", err)
- }
- for _, sql := range []string{
- `SELECT 'missing_relation'::regclass`,
- `SELECT 'missing_type'::regtype`,
- `SELECT count(*) FROM pg_catalog.pg_class WHERE oid = 'missing_relation'::regclass`,
- `SELECT count(*) FROM public.pg_class`,
- } {
- var n int64
- err := conn.QueryRow(ctx, sql).Scan(&n)
- var pgErr *pgconn.PgError
- if !errors.As(err, &pgErr) {
- t.Fatalf("%s: err = %v, want a PgError", sql, err)
- }
- if pgErr.Code != "42P01" {
- t.Fatalf("%s: SQLSTATE = %q, want 42P01 (%s)", sql, pgErr.Code, pgErr.Message)
- }
- }
- var missing, present *string
- err := conn.QueryRow(ctx, `SELECT to_regclass('missing_relation'), to_regclass('catalog_state')`).
- Scan(&missing, &present)
- if err != nil {
- t.Fatalf("to_regclass: %v", err)
- }
- if missing != nil || present == nil || *present != "catalog_state" {
- t.Fatalf("to_regclass = %v / %v, want NULL / catalog_state", missing, present)
- }
- }
|