//go:build ignore // Read-only probe of the live `logistics` database for the customer-app work. // // STRICTLY READ-ONLY. It runs SELECTs against information_schema and a few // COUNTs, and nothing else — no DDL, no INSERT, no UPDATE. It exists to answer // one question before anybody boots the service against this database: // // is migrations.Migrate() actually additive here, or would it collide with // something that already exists? // // main.go calls Migrate() on startup, so "just run the server and see" is a // schema change to a database the Miler app and both consoles are live against. // This is the safe half of that check. // // go run scratch/cx_readonly_probe.go package main import ( "database/sql" "fmt" "log" "os" "strings" _ "github.com/lib/pq" ) // The tables migrations.Migrate() would create for the customer app. var newTables = []string{ "serviceablestates", "serviceabledistricts", "pickupslottemplates", "customerbookinglimits", "bookingdestinations", "bookingparcelphotos", "bookingstageevents", "customerrefreshtokens", "customerdevices", } // Columns it would add to existing tables. These are the ones that touch rows // the live system already reads. var newColumns = map[string][]string{ "pickupbookings": { "slotid", "customerstage", "customerstatus", "estimateminrupees", "estimatemaxrupees", "routekm", "pickuptitle", "pickupsub", "cancelreason", }, "bookingparcels": {"bookingdestinationid"}, } var newSequences = []string{"cx_booking_reference_seq", "cx_tracking_seq"} func main() { // Credentials come from the environment only. The other probes in this // folder hardcode the production password; that is one more copy of a live // credential in the repository than there needs to be, and this file does // not add another. // // set -a && . ./.env && set +a && go run scratch/cx_readonly_probe.go host := env("DB_HOST", "") port := env("DB_PORT", "5433") user := env("DB_USER", "") pass := env("DB_PASSWORD", "") name := env("DB_NAME", "logistics") if host == "" || user == "" || pass == "" { log.Fatal("set DB_HOST, DB_USER and DB_PASSWORD first — " + "e.g. `set -a && . ./.env && set +a`") } dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", host, user, pass, name, port) db, err := sql.Open("postgres", dsn) if err != nil { log.Fatalf("open: %v", err) } defer db.Close() if err := db.Ping(); err != nil { log.Fatalf("ping: %v", err) } fmt.Printf("connected read-only to %s@%s:%s/%s\n\n", user, host, port, name) var total int must(db.QueryRow(`SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public'`).Scan(&total)) fmt.Printf("existing public tables: %d\n\n", total) fmt.Println("── tables the migration would CREATE ──") existing := 0 for _, t := range newTables { var n int must(db.QueryRow(`SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name = $1`, t).Scan(&n)) if n > 0 { existing++ var rows int if err := db.QueryRow(fmt.Sprintf(`SELECT count(*) FROM %q`, t)).Scan(&rows); err != nil { rows = -1 } fmt.Printf(" %-24s ALREADY EXISTS (%d rows)\n", t, rows) } else { fmt.Printf(" %-24s absent — would be created\n", t) } } fmt.Println("\n── columns the migration would ADD to live tables ──") for table, cols := range newColumns { var tableRows int if err := db.QueryRow(fmt.Sprintf(`SELECT count(*) FROM %q`, table)).Scan(&tableRows); err != nil { fmt.Printf(" %s: could not count rows: %v\n", table, err) continue } fmt.Printf(" %s (%d rows):\n", table, tableRows) for _, col := range cols { var n int must(db.QueryRow(`SELECT count(*) FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1 AND column_name = $2`, table, col).Scan(&n)) state := "absent — would be added" if n > 0 { state = "ALREADY EXISTS" } fmt.Printf(" %-24s %s\n", col, state) } } fmt.Println("\n── sequences ──") for _, s := range newSequences { var n int must(db.QueryRow(`SELECT count(*) FROM information_schema.sequences WHERE sequence_schema = 'public' AND sequence_name = $1`, s).Scan(&n)) state := "absent — would be created" if n > 0 { state = "ALREADY EXISTS" } fmt.Printf(" %-30s %s\n", s, state) } // Anything that would make the migration NOT additive: a name collision on // a table that is not ours, or a column of an incompatible type. fmt.Println("\n── collision check ──") if existing == 0 { fmt.Println(" none — every customer-app table is new to this database") } else { fmt.Printf(" %d of %d target tables already exist; inspect them before migrating\n", existing, len(newTables)) } // How much live data the additive columns would land on. var bookings, consignments int _ = db.QueryRow(`SELECT count(*) FROM pickupbookings`).Scan(&bookings) _ = db.QueryRow(`SELECT count(*) FROM consignments`).Scan(&consignments) fmt.Printf("\nlive rows that would gain nullable columns: pickupbookings=%d consignments=%d\n", bookings, consignments) fmt.Println("\nNOTHING WAS WRITTEN. This probe issues SELECTs only.") } func env(key, fallback string) string { if v := strings.TrimSpace(os.Getenv(key)); v != "" { return v } return fallback } func must(err error) { if err != nil { log.Fatalf("query: %v", err) } }