-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathsqlcdebug.go
More file actions
182 lines (164 loc) · 4.77 KB
/
sqlcdebug.go
File metadata and controls
182 lines (164 loc) · 4.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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)
}
}