Skip to content

Commit 5d5c0c6

Browse files
committed
feat(execmode): allow runtime configuration
- introduce `SetExecMode` function to programmatically control execution mode - make `GODBC_EXEC_MODE` environment variable dynamic, read on every call - ensure `SetExecMode` takes precedence over the environment variable - update documentation to reflect new configuration options - add tests for dynamic and overridden execution modes
1 parent 0ee6dc2 commit 5d5c0c6

3 files changed

Lines changed: 91 additions & 6 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,13 @@ GODBC_EXEC_MODE=prepexec ./your-app
349349
GODBC_EXEC_MODE=directW ./your-app
350350
```
351351

352+
The env var is read on every call, so you can also set it from code before
353+
your first query, or override it explicitly with `godbc.SetExecMode`:
354+
355+
```go
356+
godbc.SetExecMode("prepexec") // takes precedence over GODBC_EXEC_MODE
357+
```
358+
352359
This only affects paramless `db.Exec`/`db.Query`/`db.Ping`; parameterized
353360
queries already go through the prepare/execute path.
354361

odbc.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"runtime"
77
"strings"
88
"sync"
9+
"sync/atomic"
910
"unsafe"
1011

1112
"github.com/ebitengine/purego"
@@ -269,10 +270,8 @@ const (
269270
execModePrepExec // SQLPrepare + SQLExecute
270271
)
271272

272-
var currentExecMode = parseExecMode(os.Getenv("GODBC_EXEC_MODE"))
273-
274-
// parseExecMode maps the GODBC_EXEC_MODE env var to an execMode. Unknown or
275-
// empty values fall back to execModeDirect.
273+
// parseExecMode maps a GODBC_EXEC_MODE value to an execMode. Unknown or empty
274+
// values fall back to execModeDirect.
276275
func parseExecMode(s string) execMode {
277276
switch strings.ToLower(strings.TrimSpace(s)) {
278277
case "directa", "direct_a", "a":
@@ -286,15 +285,42 @@ func parseExecMode(s string) execMode {
286285
}
287286
}
288287

288+
// currentExecMode resolves the active execution mode.
289+
//
290+
// The GODBC_EXEC_MODE environment variable is read on every call rather than
291+
// once at init, so a program can set it in code (os.Setenv) before its first
292+
// query and have it take effect. An explicit override set via SetExecMode
293+
// takes precedence over the environment variable.
294+
func currentExecMode() execMode {
295+
if m := execModeOverride.Load(); m != 0 {
296+
return execMode(m - 1) // stored as mode+1 so 0 means "unset"
297+
}
298+
return parseExecMode(os.Getenv("GODBC_EXEC_MODE"))
299+
}
300+
301+
// execModeOverride holds an in-code override of the execution mode. It stores
302+
// mode+1 so that the zero value means "no override, use GODBC_EXEC_MODE".
303+
var execModeOverride atomic.Int32
304+
305+
// SetExecMode overrides the statement execution mode in code, taking precedence
306+
// over the GODBC_EXEC_MODE environment variable. Valid values are the same as
307+
// the env var: "", "direct", "directA", "directW", "prepexec". It is safe to
308+
// call concurrently and affects subsequent ExecDirect calls.
309+
func SetExecMode(mode string) {
310+
execModeOverride.Store(int32(parseExecMode(mode)) + 1)
311+
}
312+
289313
// ExecDirect executes an SQL statement directly.
290314
//
291315
// The statement text length is passed explicitly rather than SQL_NTS, and the
292316
// underlying buffer is NUL-terminated to be robust against Linux drivers that
293317
// strlen the text regardless. See cStmtText.
294318
//
295-
// The ODBC entry point used is selected by GODBC_EXEC_MODE (see execMode).
319+
// The ODBC entry point used is selected by GODBC_EXEC_MODE (or SetExecMode);
320+
// see execMode. The mode is resolved here, on every call, so it can be changed
321+
// at runtime.
296322
func ExecDirect(stmt SQLHSTMT, query string) SQLRETURN {
297-
return execDirectVia(stmt, query, currentExecMode)
323+
return execDirectVia(stmt, query, currentExecMode())
298324
}
299325

300326
// execDirectVia is the testable core of ExecDirect — it dispatches to the

odbc_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package godbc
22

33
import (
4+
"os"
45
"reflect"
56
"testing"
67
"time"
@@ -1772,6 +1773,57 @@ func TestParseExecMode(t *testing.T) {
17721773
}
17731774
}
17741775

1776+
// TestCurrentExecMode verifies the execution mode is resolved at call time:
1777+
// the GODBC_EXEC_MODE env var is honored dynamically, and SetExecMode
1778+
// overrides it.
1779+
func TestCurrentExecMode(t *testing.T) {
1780+
// Save and restore global state.
1781+
origEnv, hadEnv := os.LookupEnv("GODBC_EXEC_MODE")
1782+
origOverride := execModeOverride.Load()
1783+
t.Cleanup(func() {
1784+
if hadEnv {
1785+
os.Setenv("GODBC_EXEC_MODE", origEnv)
1786+
} else {
1787+
os.Unsetenv("GODBC_EXEC_MODE")
1788+
}
1789+
execModeOverride.Store(origOverride)
1790+
})
1791+
1792+
// No env, no override -> default.
1793+
os.Unsetenv("GODBC_EXEC_MODE")
1794+
execModeOverride.Store(0)
1795+
if got := currentExecMode(); got != execModeDirect {
1796+
t.Errorf("default: got %d, want execModeDirect", got)
1797+
}
1798+
1799+
// Env var is read dynamically (not frozen at init).
1800+
os.Setenv("GODBC_EXEC_MODE", "prepexec")
1801+
if got := currentExecMode(); got != execModePrepExec {
1802+
t.Errorf("env prepexec: got %d, want execModePrepExec", got)
1803+
}
1804+
os.Setenv("GODBC_EXEC_MODE", "directW")
1805+
if got := currentExecMode(); got != execModeDirectW {
1806+
t.Errorf("env directW: got %d, want execModeDirectW", got)
1807+
}
1808+
1809+
// SetExecMode overrides the env var.
1810+
SetExecMode("directA")
1811+
if got := currentExecMode(); got != execModeDirectA {
1812+
t.Errorf("override directA over env directW: got %d, want execModeDirectA", got)
1813+
}
1814+
1815+
// Clearing the override falls back to the env var again.
1816+
SetExecMode("")
1817+
if got := currentExecMode(); got != execModeDirect {
1818+
t.Errorf("override cleared to direct: got %d, want execModeDirect", got)
1819+
}
1820+
// "" parses to execModeDirect, so the override is still active (forcing
1821+
// direct) — the env var (directW) is not consulted. Verify that.
1822+
if got := currentExecMode(); got != execModeDirect {
1823+
t.Errorf("empty override still forces direct: got %d, want execModeDirect", got)
1824+
}
1825+
}
1826+
17751827
// TestExecDirectViaDispatch verifies that execDirectVia routes to the correct
17761828
// underlying FFI function pointer based on the mode. The function pointers are
17771829
// stubbed for the test and restored afterwards.

0 commit comments

Comments
 (0)