Skip to content

Commit 78e5fd5

Browse files
leo-aa88cursoragent
andcommitted
fix(sqlite): build valid read-only file URI on Windows paths
OpenReadOnly used net/url which produced file://C:%5C... URIs that modernc sqlite rejects on windows-latest. Detect drive-letter paths and emit file:///C:/...?mode=ro with forward slashes. Fixes TestIntegration_inspectWeb_API_afterRun on Windows CI. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 067fb97 commit 78e5fd5

2 files changed

Lines changed: 53 additions & 5 deletions

File tree

internal/state/sqlite/readonly.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
"context"
55
"database/sql"
66
"fmt"
7-
"net/url"
87
"path/filepath"
8+
"strings"
99
)
1010

1111
// OpenReadOnly opens an existing SQLite database for read-only queries.
@@ -36,12 +36,29 @@ func OpenReadOnly(ctx context.Context, path string) (*Store, error) {
3636
return &Store{db: db}, nil
3737
}
3838

39+
// readOnlyDSN builds a modernc.org/sqlite file URI with mode=ro.
40+
// Windows paths use file:///C:/... (forward slashes); net/url.Path is incorrect for drive letters.
3941
func readOnlyDSN(path string) (string, error) {
40-
abs, err := filepath.Abs(filepath.Clean(path))
42+
clean := filepath.Clean(path)
43+
if win, ok := windowsSQLitePath(clean); ok {
44+
return "file:///" + win + "?mode=ro", nil
45+
}
46+
abs, err := filepath.Abs(clean)
4147
if err != nil {
4248
return "", fmt.Errorf("sqlite read-only path: %w", err)
4349
}
44-
// modernc.org/sqlite file URI: mode=ro prevents writes at the VFS layer.
45-
u := url.URL{Scheme: "file", Path: abs, RawQuery: "mode=ro"}
46-
return u.String(), nil
50+
return "file://" + filepath.ToSlash(abs) + "?mode=ro", nil
51+
}
52+
53+
// windowsSQLitePath reports whether p is a Windows drive path and returns C:/... form.
54+
func windowsSQLitePath(p string) (string, bool) {
55+
if len(p) < 3 || p[1] != ':' {
56+
return "", false
57+
}
58+
if p[2] != '\\' && p[2] != '/' {
59+
return "", false
60+
}
61+
drive := strings.ToUpper(string(p[0])) + ":"
62+
rest := strings.TrimPrefix(strings.ReplaceAll(p[2:], `\`, `/`), "/")
63+
return drive + "/" + rest, true
4764
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package sqlite
2+
3+
import (
4+
"strings"
5+
"testing"
6+
)
7+
8+
func TestReadOnlyDSN_unixStyle(t *testing.T) {
9+
dsn, err := readOnlyDSN("/tmp/state.db")
10+
if err != nil {
11+
t.Fatal(err)
12+
}
13+
if !strings.HasPrefix(dsn, "file://") || !strings.Contains(dsn, "mode=ro") {
14+
t.Fatalf("dsn=%q", dsn)
15+
}
16+
if strings.Contains(dsn, "%5C") {
17+
t.Fatalf("unexpected encoding in unix dsn: %q", dsn)
18+
}
19+
}
20+
21+
func TestReadOnlyDSN_windowsDrive(t *testing.T) {
22+
// Simulate CI path shape without requiring Windows runtime.
23+
dsn, err := readOnlyDSN(`C:\Users\RUNNER~1\AppData\Local\Temp\inspect-web.db`)
24+
if err != nil {
25+
t.Fatal(err)
26+
}
27+
wantPrefix := "file:///C:/Users/RUNNER~1/AppData/Local/Temp/inspect-web.db?mode=ro"
28+
if dsn != wantPrefix {
29+
t.Fatalf("dsn=%q want %q", dsn, wantPrefix)
30+
}
31+
}

0 commit comments

Comments
 (0)