Files
member-console/internal/db/partitions.go
T
cgalo5758 88db730fcc Add dual licensing and SPDX headers
Introduce a commercial license option alongside AGPL-3.0-only, require a
CLA for contributors, and document the terms in COMMERCIAL.md and
NOTICE. Add a script to stamp SPDX headers on Go files and apply it
across the tree.
2026-09-06 02:29:42 -05:00

70 lines
3.2 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
package db
import (
"context"
"database/sql"
"fmt"
"time"
)
// DefaultWebhookPartitionMonthsAhead is the default lookahead window for
// EnsureWebhookEventPartitions: the current month plus this many months
// ahead. One more than the baseline migration's initial 0..2 loop
// (migrations/00001_init.sql:1054), so a fresh install immediately gains
// headroom (design.md D3).
const DefaultWebhookPartitionMonthsAhead = 3
// EnsureWebhookEventPartitions creates the monthly RANGE partitions of
// core.webhook_events covering the UTC month of `now` through `now`'s month
// plus monthsAhead (inclusive) — e.g. monthsAhead=3 ensures four partitions:
// the current month and the next three.
//
// It mirrors the baseline migration's partition-creation DO block
// (migrations/00001_init.sql:1048-1064) exactly: the same
// core.webhook_events_YYYY_MM naming (zero-padded four-digit year, two-digit
// month) and the same month-start/next-month-start boundaries. That parity
// is load-bearing — partitions created here must never overlap the ones the
// migration creates, whichever runs first — so any change to the naming or
// boundary computation here MUST be mirrored back into that DO block, and
// vice versa.
//
// Idempotent via CREATE TABLE IF NOT EXISTS: running it twice, or racing the
// migration's own initial-partition creation, is safe. A concurrent-create
// race between two runners surfaces as an error to whichever loses; the
// caller logs it and the next run (boot pass or scheduled workflow) succeeds
// (design.md D3, Risks).
func EnsureWebhookEventPartitions(ctx context.Context, database *sql.DB, now time.Time, monthsAhead int) error {
monthStart := time.Date(now.UTC().Year(), now.UTC().Month(), 1, 0, 0, 0, 0, time.UTC)
for i := 0; i <= monthsAhead; i++ {
start := monthStart.AddDate(0, i, 0)
end := start.AddDate(0, 1, 0)
// The partition name and the boundary literals are composed entirely
// from computed integers (never user input), so building the DDL
// string with fmt.Sprintf is safe — mirroring the migration's own
// EXECUTE FORMAT('... %s ... %L ... %L', part, m_start, m_end)
// approach exactly, which relies on the same property. Boundaries are
// embedded as literals rather than query parameters deliberately: pgx
// against PG18 rejects placeholders inside this DDL shape ("mismatched
// param and argument count") — CREATE TABLE ... PARTITION OF ...
// FOR VALUES FROM/TO isn't a statement pgx's extended protocol can
// describe placeholders for, the same family of pgx/PG18 DDL
// interaction ConnectPlain's doc comment (database.go) already works
// around for multi-statement migrations.
partition := fmt.Sprintf("core.webhook_events_%04d_%02d", start.Year(), int(start.Month()))
stmt := fmt.Sprintf(
`CREATE TABLE IF NOT EXISTS %s PARTITION OF core.webhook_events FOR VALUES FROM ('%s') TO ('%s')`,
partition, start.Format(time.RFC3339), end.Format(time.RFC3339),
)
if _, err := database.ExecContext(ctx, stmt); err != nil {
return fmt.Errorf("ensure webhook_events partition %s: %w", partition, err)
}
}
return nil
}