105 lines
2.6 KiB
Go
105 lines
2.6 KiB
Go
//go:build ignore
|
|
|
|
// Read-only: does the console's page-by-page drain actually see every booking?
|
|
//
|
|
// GetAdminBookings runs Offset/Limit with NO ORDER BY. In Postgres that makes
|
|
// the row order across pages unspecified, so a paged drain can legally return
|
|
// the same row twice and never return another. This replays the exact 6 pages
|
|
// the console fetches and compares the union against the table.
|
|
//
|
|
// go run scratch/check_booking_paging.go
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"sort"
|
|
|
|
"doormile/config"
|
|
"doormile/db"
|
|
|
|
"github.com/joho/godotenv"
|
|
)
|
|
|
|
const pageSize = 100 // what the server actually returns: min(100, requested)
|
|
|
|
type row struct{ Bookingid int }
|
|
|
|
func main() {
|
|
_ = godotenv.Load()
|
|
cfg := config.Load()
|
|
db.Connect(cfg)
|
|
if db.DB == nil {
|
|
log.Fatal("no DB connection")
|
|
}
|
|
|
|
var total int64
|
|
db.DB.Raw(`SELECT count(*) FROM pickupbookings`).Scan(&total)
|
|
pages := int((total + pageSize - 1) / pageSize)
|
|
fmt.Printf("bookings: %d, pages of %d: %d\n\n", total, pageSize, pages)
|
|
|
|
// The truth: every id that exists.
|
|
var all []row
|
|
db.DB.Raw(`SELECT bookingid FROM pickupbookings`).Scan(&all)
|
|
truth := map[int]bool{}
|
|
for _, r := range all {
|
|
truth[r.Bookingid] = true
|
|
}
|
|
|
|
// The drain, exactly as the console runs it — unordered Offset/Limit.
|
|
seen := map[int]int{}
|
|
for p := 0; p < pages; p++ {
|
|
var page []row
|
|
db.DB.Raw(`SELECT bookingid FROM pickupbookings LIMIT ? OFFSET ?`, pageSize, p*pageSize).Scan(&page)
|
|
for _, r := range page {
|
|
seen[r.Bookingid]++
|
|
}
|
|
fmt.Printf(" page %d: %d rows\n", p+1, len(page))
|
|
}
|
|
|
|
missed := []int{}
|
|
for id := range truth {
|
|
if seen[id] == 0 {
|
|
missed = append(missed, id)
|
|
}
|
|
}
|
|
dupes := []int{}
|
|
for id, n := range seen {
|
|
if n > 1 {
|
|
dupes = append(dupes, id)
|
|
}
|
|
}
|
|
sort.Sort(sort.Reverse(sort.IntSlice(missed)))
|
|
sort.Sort(sort.Reverse(sort.IntSlice(dupes)))
|
|
|
|
fmt.Printf("\nunordered drain saw %d distinct of %d\n", len(seen), len(truth))
|
|
fmt.Printf(" MISSED %d: %v\n", len(missed), head(missed, 20))
|
|
fmt.Printf(" DUPLICATED %d: %v\n", len(dupes), head(dupes, 20))
|
|
|
|
// The same drain with a deterministic order — the proposed fix.
|
|
seenOrdered := map[int]int{}
|
|
for p := 0; p < pages; p++ {
|
|
var page []row
|
|
db.DB.Raw(`SELECT bookingid FROM pickupbookings ORDER BY bookingid DESC LIMIT ? OFFSET ?`,
|
|
pageSize, p*pageSize).Scan(&page)
|
|
for _, r := range page {
|
|
seenOrdered[r.Bookingid]++
|
|
}
|
|
}
|
|
missedOrdered := 0
|
|
for id := range truth {
|
|
if seenOrdered[id] == 0 {
|
|
missedOrdered++
|
|
}
|
|
}
|
|
fmt.Printf("\nwith ORDER BY bookingid DESC: saw %d distinct, missed %d\n",
|
|
len(seenOrdered), missedOrdered)
|
|
}
|
|
|
|
func head(xs []int, n int) []int {
|
|
if len(xs) > n {
|
|
return xs[:n]
|
|
}
|
|
return xs
|
|
}
|