// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. package pgserver import ( "context" "fmt" "os" "strconv" "five/hbrt" ) // init wires the PG-server entry points into the runtime as // HB_FUNCs. Importing this package (e.g. via _ "five/hbrtl/pgserver" // from hbrtl's bootstrap) is enough; PRG code then sees: // // pg_server_start( nPort | cAddr [, cAuthMode ] ) // → starts server, blocks // (call inside SPAWN to keep // the calling thread free) // pg_server_stop() → closes the active server // // Embedded callers compose this with their own DBF setup: // // #include "FiveSqlDef.ch" // PROCEDURE Main() // USE customers SHARED // USE orders SHARED NEW // pg_server_start( 5432 ) /* blocks; psql can now connect */ // RETURN func init() { hbrt.HB_FUNC("PG_SERVER_START", pgServerStart) hbrt.HB_FUNC("PG_SERVER_STOP", pgServerStop) hbrt.HB_FUNC("PG_ADD_ROLE", pgAddRole) hbrt.HB_FUNC("PG_REMOVE_ROLE", pgRemoveRole) } // pgAddRole registers a (username, password) pair with the // pgserver auth subsystem. Idempotent — re-adding the same name // replaces the prior credential. PRG signature: // // pg_add_role( cName, cPassword ) -> NIL // // Bootstrap pattern (combine with pg_server_start): // // pg_add_role( "alice", "swordfish" ) // pg_add_role( "bob", "hunter2" ) // pg_server_start( 5432, "md5" ) func pgAddRole(ctx *hbrt.HBContext) { if ctx.PCount() < 2 || !ctx.IsChar(1) || !ctx.IsChar(2) { ctx.RetNil() return } AddRole(ctx.ParC(1), ctx.ParC(2)) ctx.RetNil() } // pgRemoveRole drops a previously-added role. PRG signature: // // pg_remove_role( cName ) -> NIL func pgRemoveRole(ctx *hbrt.HBContext) { if ctx.PCount() < 1 || !ctx.IsChar(1) { ctx.RetNil() return } RemoveRole(ctx.ParC(1)) ctx.RetNil() } func pgServerStart(ctx *hbrt.HBContext) { listen := ":5432" if ctx.PCount() >= 1 { if ctx.IsNumeric(1) { listen = ":" + strconv.Itoa(ctx.ParNI(1)) } else if ctx.IsChar(1) { listen = ctx.ParC(1) } } cfg := Config{Listen: listen} if ctx.PCount() >= 2 && ctx.IsChar(2) { cfg.AuthMode = ctx.ParC(2) } srv := NewServer(ctx.T.VM(), cfg) setActiveServer(srv) fmt.Fprintf(os.Stderr, "pgserver: listening on %s (auth=%s)\n", cfg.listenAddr(), defaultStr(cfg.AuthMode, "trust")) if err := srv.Serve(context.Background()); err != nil { fmt.Fprintf(os.Stderr, "pgserver: %v\n", err) } ctx.RetNil() } func pgServerStop(ctx *hbrt.HBContext) { if srv := takeActiveServer(); srv != nil { _ = srv.Close() } ctx.RetNil() } func defaultStr(s, fallback string) string { if s == "" { return fallback } return s } // activeServer tracks the most recently started server so // pg_server_stop() can find it without the PRG layer needing to // hold a handle. v1.0 is single-server-per-process; a future // upgrade can swap this for a slice. var activeServerSlot *Server func setActiveServer(s *Server) { activeServerSlot = s } func takeActiveServer() *Server { s := activeServerSlot activeServerSlot = nil return s }