snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
This commit is contained in:
182
internal/sqlcdebug/sqlcdebug.go
Normal file
182
internal/sqlcdebug/sqlcdebug.go
Normal file
@@ -0,0 +1,182 @@
|
||||
// Package sqlcdebug parses the SQLCDEBUG environment variable and exposes
|
||||
// its key=value settings to the rest of the codebase.
|
||||
//
|
||||
// The SQLCDEBUG variable is a comma-separated list of name=value pairs:
|
||||
//
|
||||
// SQLCDEBUG=dumpast=1,trace=trace.out
|
||||
//
|
||||
// Settings are looked up at the call site by declaring a package-level
|
||||
// variable:
|
||||
//
|
||||
// var dumpAST = sqlcdebug.New("dumpast")
|
||||
//
|
||||
// func parse() {
|
||||
// if dumpAST.Value() == "1" { ... }
|
||||
// }
|
||||
//
|
||||
// New panics if name is not registered in [Settings] below. Adding a new
|
||||
// setting therefore requires extending the [Settings] table so that all
|
||||
// known keys are documented in one place.
|
||||
//
|
||||
// This package is modeled after Go's internal/godebug. Unlike Go, sqlc is
|
||||
// short-lived, so settings are parsed once at process startup; the
|
||||
// [Update] hook exists for tests that need to reparse mid-run.
|
||||
package sqlcdebug
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Info documents a single SQLCDEBUG setting.
|
||||
type Info struct {
|
||||
// Name is the SQLCDEBUG key, e.g. "dumpast".
|
||||
Name string
|
||||
// Description is a short human-readable description.
|
||||
Description string
|
||||
// Default is the value returned by [Setting.Value] when the key is
|
||||
// not present in SQLCDEBUG.
|
||||
Default string
|
||||
}
|
||||
|
||||
// Settings is the master table of all known SQLCDEBUG keys. New keys must
|
||||
// be added here before they can be looked up via [New].
|
||||
var Settings = []Info{
|
||||
{Name: "dumpast", Description: "print the AST of every SQL statement"},
|
||||
{Name: "dumpcatalog", Description: "print the parsed database schema"},
|
||||
{Name: "trace", Description: "write a runtime trace to the named file (1 means trace.out)"},
|
||||
{Name: "processplugins", Description: "set to 0 to disable process-based plugins", Default: "1"},
|
||||
{Name: "databases", Description: "set to 'managed' to disable database connections via URI"},
|
||||
{Name: "dumpvetenv", Description: "print the variables available to a vet rule during evaluation"},
|
||||
{Name: "dumpexplain", Description: "print the JSON-formatted output from EXPLAIN during vet evaluation"},
|
||||
}
|
||||
|
||||
func info(name string) (Info, bool) {
|
||||
for _, s := range Settings {
|
||||
if s.Name == name {
|
||||
return s, true
|
||||
}
|
||||
}
|
||||
return Info{}, false
|
||||
}
|
||||
|
||||
// Setting is a single SQLCDEBUG key. Obtain one with [New]; reading it
|
||||
// with [Setting.Value] returns the value parsed from the SQLCDEBUG
|
||||
// environment variable, or the registered default.
|
||||
type Setting struct {
|
||||
info Info
|
||||
set atomic.Bool
|
||||
value atomic.Pointer[string]
|
||||
}
|
||||
|
||||
var (
|
||||
registryMu sync.Mutex
|
||||
registry = map[string]*Setting{}
|
||||
)
|
||||
|
||||
// New returns the Setting for name. The same pointer is returned for
|
||||
// repeated calls. New panics if name is not present in [Settings].
|
||||
func New(name string) *Setting {
|
||||
registryMu.Lock()
|
||||
defer registryMu.Unlock()
|
||||
if s, ok := registry[name]; ok {
|
||||
return s
|
||||
}
|
||||
i, ok := info(name)
|
||||
if !ok {
|
||||
panic("sqlcdebug: unknown setting " + name)
|
||||
}
|
||||
s := &Setting{info: i}
|
||||
registry[name] = s
|
||||
apply(s, parsedEnv())
|
||||
return s
|
||||
}
|
||||
|
||||
// Name returns the setting's key.
|
||||
func (s *Setting) Name() string { return s.info.Name }
|
||||
|
||||
// Default returns the value used when the key is absent from SQLCDEBUG.
|
||||
func (s *Setting) Default() string { return s.info.Default }
|
||||
|
||||
// Value returns the parsed value of the SQLCDEBUG setting, falling back
|
||||
// to the registered default.
|
||||
func (s *Setting) Value() string {
|
||||
if v := s.value.Load(); v != nil {
|
||||
return *v
|
||||
}
|
||||
return s.info.Default
|
||||
}
|
||||
|
||||
// IsSet reports whether the key was present in SQLCDEBUG.
|
||||
func (s *Setting) IsSet() bool { return s.set.Load() }
|
||||
|
||||
// Any reports whether SQLCDEBUG contained any recognized key=value pair.
|
||||
// It mirrors the legacy "is debug active?" check.
|
||||
func Any() bool {
|
||||
for _, s := range registry {
|
||||
if s.IsSet() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return parsedEnvHasAny()
|
||||
}
|
||||
|
||||
var (
|
||||
envOnce sync.Once
|
||||
envMap map[string]string
|
||||
)
|
||||
|
||||
func parsedEnv() map[string]string {
|
||||
envOnce.Do(func() {
|
||||
envMap = parse(os.Getenv("SQLCDEBUG"))
|
||||
})
|
||||
return envMap
|
||||
}
|
||||
|
||||
func parsedEnvHasAny() bool {
|
||||
return len(parsedEnv()) > 0
|
||||
}
|
||||
|
||||
func parse(raw string) map[string]string {
|
||||
out := map[string]string{}
|
||||
if raw == "" {
|
||||
return out
|
||||
}
|
||||
for pair := range strings.SplitSeq(raw, ",") {
|
||||
pair = strings.TrimSpace(pair)
|
||||
if pair == "" {
|
||||
continue
|
||||
}
|
||||
k, v, ok := strings.Cut(pair, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func apply(s *Setting, env map[string]string) {
|
||||
if v, ok := env[s.info.Name]; ok {
|
||||
s.value.Store(&v)
|
||||
s.set.Store(true)
|
||||
} else {
|
||||
s.value.Store(nil)
|
||||
s.set.Store(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Update reparses the given SQLCDEBUG-formatted string and refreshes
|
||||
// every registered setting. Intended for tests; production code should
|
||||
// rely on the value parsed at startup.
|
||||
func Update(raw string) {
|
||||
registryMu.Lock()
|
||||
defer registryMu.Unlock()
|
||||
envMap = parse(raw)
|
||||
envOnce.Do(func() {}) // mark as parsed so future New() calls see envMap.
|
||||
for _, s := range registry {
|
||||
apply(s, envMap)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user