From cc187639440fdd767c84db87975e1b176c16e995 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Mon, 9 Mar 2026 09:02:16 -0600 Subject: [PATCH] stead from pg --- ctx.go | 24 ++++++++++++++++++ every.go | 34 ++++++++++++++++++++++++++ go.mod | 18 ++++++++++++++ go.sum | 55 ++++++++++++++++++++++++++++++++++++++++++ kv.go | 46 +++++++++++++++++++++++++++++++++++ kv_test.go | 44 +++++++++++++++++++++++++++++++++ pg.go | 12 +++++++++ sql.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ sqlite.go | 14 +++++++++++ 9 files changed, 318 insertions(+) create mode 100644 ctx.go create mode 100644 every.go create mode 100644 go.sum create mode 100644 kv.go create mode 100644 kv_test.go create mode 100644 pg.go create mode 100644 sql.go create mode 100644 sqlite.go diff --git a/ctx.go b/ctx.go new file mode 100644 index 0000000..1bedbdd --- /dev/null +++ b/ctx.go @@ -0,0 +1,24 @@ +package with + +import ( + "context" + "os/signal" + "syscall" + "time" +) + +func Context(foo func(context.Context) error) error { + ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT) + defer can() + + if err := foo(ctx); err != nil && ctx.Err() == nil { + return err + } + return nil +} + +func Timeout(ctx context.Context, d time.Duration, foo func(context.Context) error) error { + ctx, can := context.WithTimeout(ctx, d) + defer can() + return foo(ctx) +} diff --git a/every.go b/every.go new file mode 100644 index 0000000..25bce14 --- /dev/null +++ b/every.go @@ -0,0 +1,34 @@ +package with + +import ( + "context" + "time" +) + +func GoEvery(ctx context.Context, d time.Duration, foo func()) { + every(ctx, d, foo, true) +} + +func Every(ctx context.Context, d time.Duration, foo func()) { + every(ctx, d, foo, false) +} + +func every(ctx context.Context, d time.Duration, foo func(), async bool) { + ticker := time.NewTicker(d) + defer ticker.Stop() + for ctx.Err() == nil { + everyTry(ctx, foo, async) + select { + case <-ctx.Done(): + case <-ticker.C: + } + } +} + +func everyTry(ctx context.Context, foo func(), async bool) { + if async { + go foo() + } else { + foo() + } +} diff --git a/go.mod b/go.mod index 3e6647f..ed9b8b0 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,21 @@ module gitea.bel.blue/bel/with go 1.24.2 + +require ( + github.com/lib/pq v1.11.2 + modernc.org/sqlite v1.46.1 +) + +require ( + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect + golang.org/x/sys v0.37.0 // indirect + modernc.org/libc v1.67.6 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..09e46e4 --- /dev/null +++ b/go.sum @@ -0,0 +1,55 @@ +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= +github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= +golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= +modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= +modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= +modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= +modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= +modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= +modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= +modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/kv.go b/kv.go new file mode 100644 index 0000000..31f0c6c --- /dev/null +++ b/kv.go @@ -0,0 +1,46 @@ +package with + +import ( + "context" + "database/sql" +) + +type SQLKV struct { + db *sql.DB +} + +func KV(ctx context.Context, db *sql.DB, foo func(SQLKV) error) error { + if _, err := db.ExecContext(ctx, ` + CREATE TABLE IF NOT EXISTS with_kv( + k TEXT PRIMARY KEY + , v TEXT + ) + `); err != nil { + return err + } + + return foo(SQLKV{db: db}) +} + +func (kv SQLKV) Get(ctx context.Context, k string) ([]byte, error) { + row := kv.db.QueryRowContext(ctx, ` + SELECT v FROM with_kv WHERE k=$1 + `, k) + + var v []byte + if err := row.Scan(&v); err != nil { + return nil, err + } + + return v, row.Err() +} + +func (kv SQLKV) Set(ctx context.Context, k string, v []byte) error { + _, err := kv.db.ExecContext(ctx, ` + INSERT INTO with_kv + (k, v) VALUES ($1, $2) + ON CONFLICT DO UPDATE + SET v=$2 WHERE k=$1 + `, k, v) + return err +} diff --git a/kv_test.go b/kv_test.go new file mode 100644 index 0000000..9eee6f2 --- /dev/null +++ b/kv_test.go @@ -0,0 +1,44 @@ +package with_test + +import ( + "context" + "database/sql" + "testing" + + "gitea.bel.blue/bel/with" +) + +func TestKV(t *testing.T) { + ctx := context.Background() + if err := with.Sqlite(ctx, ":memory:", func(db *sql.DB) error { + return with.KV(ctx, db, func(kv with.SQLKV) error { + if _, err := kv.Get(ctx, "k"); err == nil { + t.Error("404 no err") + } + + if err := kv.Set(ctx, "k", []byte("v")); err != nil { + t.Error("err on insert", err) + } + + if v, err := kv.Get(ctx, "k"); err != nil { + t.Error("err on get", err) + } else if string(v) != "v" { + t.Errorf("expected 'v' but got %q", v) + } + + if err := kv.Set(ctx, "k", []byte("v2")); err != nil { + t.Error("err on update", err) + } + + if v, err := kv.Get(ctx, "k"); err != nil { + t.Error("err on get updated", err) + } else if string(v) != "v2" { + t.Errorf("expected 'v2' but got %q", v) + } + + return nil + }) + }); err != nil { + t.Fatal(err) + } +} diff --git a/pg.go b/pg.go new file mode 100644 index 0000000..7ea096c --- /dev/null +++ b/pg.go @@ -0,0 +1,12 @@ +package with + +import ( + "context" + "database/sql" + + _ "github.com/lib/pq" +) + +func PSQL(ctx context.Context, conn string, foo func(db *sql.DB) error) error { + return _sql(ctx, "postgres", conn, foo) +} diff --git a/sql.go b/sql.go new file mode 100644 index 0000000..7914010 --- /dev/null +++ b/sql.go @@ -0,0 +1,71 @@ +package with + +import ( + "context" + "database/sql" + "fmt" + "log" + "net/url" + "time" + + _ "modernc.org/sqlite" +) + +func SQL(ctx context.Context, conn string, foo func(*sql.DB) error) error { + u, err := url.Parse(conn) + if err != nil { + return err + } + switch u.Scheme { + case "sqlite": + return Sqlite(ctx, conn, foo) + case "postgres", "postgresql": + return PSQL(ctx, conn, foo) + } + return fmt.Errorf("unknown sql scheme %q", u.Scheme) +} + +func _sql(ctx context.Context, engine, conn string, foo func(db *sql.DB) error) error { + log.Printf("opening %s %s...", engine, conn) + db, err := sql.Open(engine, conn) + if err != nil { + return err + } + defer func() { + log.Println("closed:", db.Close()) + }() + + func() { + pinged := make(chan bool) + defer close(pinged) + for { + log.Println("pinging...") + go func() { + err := db.PingContext(ctx) + if err != nil { + log.Println("!", err) + } + select { + case pinged <- err == nil: + case <-ctx.Done(): + case <-time.After(time.Second * 5): + } + }() + select { + case <-ctx.Done(): + return + case ok := <-pinged: + if ok { + return + } + } + select { + case <-ctx.Done(): + case <-time.After(time.Second): + } + } + }() + log.Println("connected") + + return foo(db) +} diff --git a/sqlite.go b/sqlite.go new file mode 100644 index 0000000..7a4fcef --- /dev/null +++ b/sqlite.go @@ -0,0 +1,14 @@ +package with + +import ( + "context" + "database/sql" + "strings" + + _ "modernc.org/sqlite" +) + +func Sqlite(ctx context.Context, conn string, foo func(db *sql.DB) error) error { + conn = strings.TrimPrefix(conn, "sqlite://") + return _sql(ctx, "sqlite", conn, foo) +}