filename.go

  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}