github.com/ncruces/go-sqlite3@v0.15.1-0.20240520133447-53eef1510ff0/vfs/filename.go (about)

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