backend requirements onthe xustomer app
This commit is contained in:
174
scratch/cx_readonly_probe.go
Normal file
174
scratch/cx_readonly_probe.go
Normal file
@@ -0,0 +1,174 @@
|
||||
//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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user