From d7ead5264e245d7fe8138c62dce1e07bb6460bf8 Mon Sep 17 00:00:00 2001 From: Andrew Dunham Date: Wed, 14 Jan 2026 16:25:21 -0500 Subject: [PATCH] cgosqlite: support named parameters with $ prefix This allows using the "$VVV" syntax for named parameters in SQLite, as described in https://sqlite.org/c3ref/bind_blob.html Updates tailscale/corp#35883 Signed-off-by: Andrew Dunham --- cgosqlite/cgosqlite.go | 3 +++ cgosqlite/cgosqlite_test.go | 39 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/cgosqlite/cgosqlite.go b/cgosqlite/cgosqlite.go index 04a1f14..bfcc095 100644 --- a/cgosqlite/cgosqlite.go +++ b/cgosqlite/cgosqlite.go @@ -357,6 +357,9 @@ func (stmt *Stmt) BindParameterIndexSearch(name string) int { if i := stmt.BindParameterIndex("@" + name); i > 0 { return i } + if i := stmt.BindParameterIndex("$" + name); i > 0 { + return i + } return stmt.BindParameterIndex("?" + name) } diff --git a/cgosqlite/cgosqlite_test.go b/cgosqlite/cgosqlite_test.go index 9b4f94f..1048ceb 100644 --- a/cgosqlite/cgosqlite_test.go +++ b/cgosqlite/cgosqlite_test.go @@ -8,6 +8,45 @@ import ( "github.com/tailscale/sqlite/sqliteh" ) +func TestBindParameterIndexSearch(t *testing.T) { + db, err := Open(filepath.Join(t.TempDir(), "test.db"), sqliteh.OpenFlagsDefault, "") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + tests := []struct { + name string + query string + param string + wantOK bool + }{ + {"colon", "SELECT :foo", "foo", true}, + {"at_sybol", "SELECT @foo", "foo", true}, + {"dollar", "SELECT $foo", "foo", true}, + {"question", "SELECT ?123", "123", true}, + {"not_found", "SELECT :bar", "foo", false}, + {"dollar_multiple_params", "SELECT $a, $b, $c", "b", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stmt, _, err := db.Prepare(tt.query, 0) + if err != nil { + t.Fatalf("Prepare %q: %v", tt.query, err) + } + defer stmt.Finalize() + + idx := stmt.BindParameterIndexSearch(tt.param) + gotOK := idx > 0 + if gotOK != tt.wantOK { + t.Errorf("BindParameterIndexSearch(%q) = %d, wantOK=%v gotOK=%v", + tt.param, idx, tt.wantOK, gotOK) + } + }) + } +} + func TestColumnBlob(t *testing.T) { // Run the test with and without the SetAlwaysCopyBlob flag enabled. cases := []struct {