fix(bootstrap): 迁库比对行数的 SQL 不再对常量排序报错,宿主机库能真正迁入 felis-postgres
This commit is contained in:
2 files changed
+110
-3
No files matched your search
+5
-3
@@ -448,9 +448,11 @@ PG_LOCKOUT_LINE="# The felis database moved into k3s (felis-postgres); nothing e
|
|||||||
# The units that write to the database from the host; the move stops them and starts
|
# The units that write to the database from the host; the move stops them and starts
|
||||||
# again the ones that were running.
|
# again the ones that were running.
|
||||||
PG_MOVE_TIMERS=(felis-db-backup felis-offsite felis-update-check felis-watchdog)
|
PG_MOVE_TIMERS=(felis-db-backup felis-offsite felis-update-check felis-watchdog)
|
||||||
# Each public table with its exact row count, one per line, in a collation-independent
|
# Each public table with its exact row count, one per line, in byte order of the table names
|
||||||
# order, so the two servers' answers compare byte for byte.
|
# whatever the servers' collations (relname is a name, which sorts in collation "C" since
|
||||||
PG_TABLE_COUNTS="SELECT c.relname || ' ' || (xpath('/row/n/text()', query_to_xml(format('SELECT count(*) AS n FROM public.%I', c.relname), false, true, '')))[1]::text FROM pg_class c JOIN pg_namespace s ON s.oid = c.relnamespace WHERE s.nspname = 'public' AND c.relkind IN ('r', 'p') ORDER BY 1 COLLATE \"C\""
|
# PostgreSQL 12), so the two servers' answers compare byte for byte. internal/pgint runs it
|
||||||
|
# on the oldest and newest server the installer meets; the shell tests stub psql.
|
||||||
|
PG_TABLE_COUNTS="SELECT c.relname || ' ' || (xpath('/row/n/text()', query_to_xml(format('SELECT count(*) AS n FROM public.%I', c.relname), false, true, '')))[1]::text FROM pg_class c JOIN pg_namespace s ON s.oid = c.relnamespace WHERE s.nspname = 'public' AND c.relkind IN ('r', 'p') ORDER BY c.relname"
|
||||||
PG_MOVE_STAGE=""
|
PG_MOVE_STAGE=""
|
||||||
PG_MOVE_HBA=""
|
PG_MOVE_HBA=""
|
||||||
PG_MOVE_UNITS=()
|
PG_MOVE_UNITS=()
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
//go:build pgint
|
||||||
|
|
||||||
|
package pgint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The row-count query deploy/bootstrap.sh runs on the host PostgreSQL and on felis-postgres
|
||||||
|
// when it moves the database into k3s, comparing the two answers byte for byte: every public
|
||||||
|
// table with its exact row count, one per line, in byte order of the table names. The shell
|
||||||
|
// tests stub psql, so this is the only place the SQL itself runs before a real host does.
|
||||||
|
func TestInstallerTableCounts(t *testing.T) {
|
||||||
|
// bash reads the assignment the way the installer does, quoting and escapes included.
|
||||||
|
out, err := exec.Command("bash", "-c", `eval "$(grep '^PG_TABLE_COUNTS=' "$1")" && printf %s "$PG_TABLE_COUNTS"`,
|
||||||
|
"bash", "../../deploy/bootstrap.sh").Output()
|
||||||
|
if err != nil || len(out) == 0 {
|
||||||
|
t.Fatalf("reading PG_TABLE_COUNTS from deploy/bootstrap.sh: %v (%q)", err, out)
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
// Tables the schema does not have yet but a migration may add: a name needing quotes,
|
||||||
|
// which byte order puts before every lowercase one and a locale's order does not, and a
|
||||||
|
// partitioned table.
|
||||||
|
for _, stmt := range []string{
|
||||||
|
`CREATE TABLE public."Pgint_Counts" (id int)`,
|
||||||
|
`INSERT INTO public."Pgint_Counts" VALUES (1), (2), (3)`,
|
||||||
|
`CREATE TABLE public.pgint_counts_parted (id int) PARTITION BY RANGE (id)`,
|
||||||
|
`CREATE TABLE public.pgint_counts_parted_all PARTITION OF public.pgint_counts_parted FOR VALUES FROM (0) TO (100)`,
|
||||||
|
`INSERT INTO public.pgint_counts_parted VALUES (1), (2)`,
|
||||||
|
} {
|
||||||
|
if _, err := db.ExecContext(ctx, stmt); err != nil {
|
||||||
|
t.Fatalf("%s: %v", stmt, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
_, _ = db.ExecContext(context.Background(), `DROP TABLE IF EXISTS public."Pgint_Counts", public.pgint_counts_parted`)
|
||||||
|
})
|
||||||
|
// One snapshot for the query and the counts it is checked against.
|
||||||
|
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelRepeatableRead, ReadOnly: true})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() { _ = tx.Rollback() }()
|
||||||
|
|
||||||
|
rows, err := tx.QueryContext(ctx, string(out))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("PG_TABLE_COUNTS: %v", err)
|
||||||
|
}
|
||||||
|
var got []string
|
||||||
|
for rows.Next() {
|
||||||
|
var line string
|
||||||
|
if err := rows.Scan(&line); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
got = append(got, line)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
t.Fatalf("PG_TABLE_COUNTS: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := publicTables(ctx, tx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sort.Strings(names) // byte order, what COLLATE "C" sorts by
|
||||||
|
var want []string
|
||||||
|
for _, name := range names {
|
||||||
|
var n int64
|
||||||
|
q := `SELECT count(*) FROM public."` + strings.ReplaceAll(name, `"`, `""`) + `"`
|
||||||
|
if err := tx.QueryRowContext(ctx, q).Scan(&n); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
want = append(want, fmt.Sprintf("%s %d", name, n))
|
||||||
|
}
|
||||||
|
if !slices.Contains(want, "Pgint_Counts 3") || !slices.Contains(want, "pgint_counts_parted 2") {
|
||||||
|
t.Fatalf("the tables this test made are not what it counts: %q", want)
|
||||||
|
}
|
||||||
|
if !slices.Equal(got, want) {
|
||||||
|
t.Fatalf("PG_TABLE_COUNTS returned\n%s\nwant\n%s", strings.Join(got, "\n"), strings.Join(want, "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func publicTables(ctx context.Context, tx *sql.Tx) ([]string, error) {
|
||||||
|
rows, err := tx.QueryContext(ctx, "SELECT tablename FROM pg_tables WHERE schemaname = 'public'")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var names []string
|
||||||
|
for rows.Next() {
|
||||||
|
var name string
|
||||||
|
if err := rows.Scan(&name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names, rows.Err()
|
||||||
|
}
|
||||||
Reference in new issue
Block a user