1package vfs
2
3import (
4 "context"
5 "net/url"
6
7 "github.com/tetratelabs/wazero/api"
8
9 "github.com/ncruces/go-sqlite3/internal/util"
10)
11
12// Filename is used by SQLite to pass filenames
13// to the Open method of a VFS.
14//
15// https://sqlite.org/c3ref/filename.html
16type Filename struct {
17 ctx context.Context
18 mod api.Module
19 zPath ptr_t
20 flags OpenFlag
21 stack [2]stk_t
22}
23
24// GetFilename is an internal API users should not call directly.
25func GetFilename(ctx context.Context, mod api.Module, id ptr_t, flags OpenFlag) *Filename {
26 if id == 0 {
27 return nil
28 }
29 return &Filename{
30 ctx: ctx,
31 mod: mod,
32 zPath: id,
33 flags: flags,
34 }
35}
36
37// String returns this filename as a string.
38func (n *Filename) String() string {
39 if n == nil || n.zPath == 0 {
40 return ""
41 }
42 return util.ReadString(n.mod, n.zPath, _MAX_PATHNAME)
43}
44
45// Database returns the name of the corresponding database file.
46//
47// https://sqlite.org/c3ref/filename_database.html
48func (n *Filename) Database() string {
49 return n.path("sqlite3_filename_database")
50}
51
52// Journal returns the name of the corresponding rollback journal file.
53//
54// https://sqlite.org/c3ref/filename_database.html
55func (n *Filename) Journal() string {
56 return n.path("sqlite3_filename_journal")
57}
58
59// Journal returns the name of the corresponding WAL file.
60//
61// https://sqlite.org/c3ref/filename_database.html
62func (n *Filename) WAL() string {
63 return n.path("sqlite3_filename_wal")
64}
65
66func (n *Filename) path(method string) string {
67 if n == nil || n.zPath == 0 {
68 return ""
69 }
70 if n.flags&(OPEN_MAIN_DB|OPEN_MAIN_JOURNAL|OPEN_WAL) == 0 {
71 return ""
72 }
73
74 n.stack[0] = stk_t(n.zPath)
75 fn := n.mod.ExportedFunction(method)
76 if err := fn.CallWithStack(n.ctx, n.stack[:]); err != nil {
77 panic(err)
78 }
79 return util.ReadString(n.mod, ptr_t(n.stack[0]), _MAX_PATHNAME)
80}
81
82// DatabaseFile returns the main database [File] corresponding to a journal.
83//
84// https://sqlite.org/c3ref/database_file_object.html
85func (n *Filename) DatabaseFile() File {
86 if n == nil || n.zPath == 0 {
87 return nil
88 }
89 if n.flags&(OPEN_MAIN_DB|OPEN_MAIN_JOURNAL|OPEN_WAL) == 0 {
90 return nil
91 }
92
93 n.stack[0] = stk_t(n.zPath)
94 fn := n.mod.ExportedFunction("sqlite3_database_file_object")
95 if err := fn.CallWithStack(n.ctx, n.stack[:]); err != nil {
96 panic(err)
97 }
98 file, _ := vfsFileGet(n.ctx, n.mod, ptr_t(n.stack[0])).(File)
99 return file
100}
101
102// URIParameter returns the value of a URI parameter.
103//
104// https://sqlite.org/c3ref/uri_boolean.html
105func (n *Filename) URIParameter(key string) string {
106 if n == nil || n.zPath == 0 {
107 return ""
108 }
109
110 uriKey := n.mod.ExportedFunction("sqlite3_uri_key")
111 n.stack[0] = stk_t(n.zPath)
112 n.stack[1] = stk_t(0)
113 if err := uriKey.CallWithStack(n.ctx, n.stack[:]); err != nil {
114 panic(err)
115 }
116
117 ptr := ptr_t(n.stack[0])
118 if ptr == 0 {
119 return ""
120 }
121
122 // Parse the format from:
123 // https://github.com/sqlite/sqlite/blob/b74eb0/src/pager.c#L4797-L4840
124 // This avoids having to alloc/free the key just to find a value.
125 for {
126 k := util.ReadString(n.mod, ptr, _MAX_NAME)
127 if k == "" {
128 return ""
129 }
130 ptr += ptr_t(len(k)) + 1
131
132 v := util.ReadString(n.mod, ptr, _MAX_NAME)
133 if k == key {
134 return v
135 }
136 ptr += ptr_t(len(v)) + 1
137 }
138}
139
140// URIParameters obtains values for URI parameters.
141//
142// https://sqlite.org/c3ref/uri_boolean.html
143func (n *Filename) URIParameters() url.Values {
144 if n == nil || n.zPath == 0 {
145 return nil
146 }
147
148 uriKey := n.mod.ExportedFunction("sqlite3_uri_key")
149 n.stack[0] = stk_t(n.zPath)
150 n.stack[1] = stk_t(0)
151 if err := uriKey.CallWithStack(n.ctx, n.stack[:]); err != nil {
152 panic(err)
153 }
154
155 ptr := ptr_t(n.stack[0])
156 if ptr == 0 {
157 return nil
158 }
159
160 var params url.Values
161
162 // Parse the format from:
163 // https://github.com/sqlite/sqlite/blob/b74eb0/src/pager.c#L4797-L4840
164 // This is the only way to support multiple valued keys.
165 for {
166 k := util.ReadString(n.mod, ptr, _MAX_NAME)
167 if k == "" {
168 return params
169 }
170 ptr += ptr_t(len(k)) + 1
171
172 v := util.ReadString(n.mod, ptr, _MAX_NAME)
173 if params == nil {
174 params = url.Values{}
175 }
176 params.Add(k, v)
177 ptr += ptr_t(len(v)) + 1
178 }
179}