Skip to content

Proposal for reading SQLite databases embedded in Go executables #968

Description

@GeekStocks

Proposal for reading SQLite databases embedded in Go executables

Purpose

Numerous forums in multiple programming languages are littered with questions similar to this:

"How can I read my preexisting SQLite database in memory mode? Why can't I just pass a memory pointer to my driver's open command?"

The disappointing answer to these questions is simply that the authors of SQLite have not exposed a method to do so. Driver authors / maintainers like @mattn for this package cannot interface to something that doesn't exist. Given this, the "fallback" answer usually wins the day: open SQLite using ":memory:" mode and then Exec a SQL script containing a backup "dump" of the database. Once the overhead of this loading process has occurred, the user has the environment they want.

But is there a better way that could avoid this loading overhead? The purpose of this proposal is to explore and solicit comments for a proposed custom SQLite VFS module that would work in tandem with mattn/go-sqlite3 and Go's new embed package. You are encouraged to comment if you can add to the knowledge presented here and/or see another way to accomplish the goal.

Trying a different approach

Most observed approaches to this problem start with the desired :memory: mode and quickly run into trouble with no pointer parameter available. This proposal avoids SQLite's memory mode altogether and seeks to use the existing SQLite URI mode to open an embedded database. By virtue of being embedded the database is already in memory and accessable via a variable, so opening it is all that remains.

As of Go version 1.16 released in February, 2021 embedding a SQLite database into a Go executable is now trivial:

package main

import "embed"

//go:embed specimen.db
var embeddedDatabase embed.FS

Go's embed package offers string, []byte, and the new embed.FS data types as embedded possibilities. Users of the "fallback" solution can now at least embed their SQL script backup "dumps" as a string. Since this author knows no method to load SQLite databases from []byte, the focus of this proposal is on the new embed.FS option shown above.

It is important to note that embedding digital assets into Go executables is a read-only process:

An FS is a read-only collection of files, usually initialized with a //go:embed directive

This limits the usefulness of this proposal; if a project must have write ability on the SQLite database then the "fallback" method remains the only choice. But as the use cases for read-only databases are legion, we soldier on.

Under the hood SQLite uses a standard Virtual File System (VFS) to interpret the open string and locate files. The VFS chosen depends on the OS of course:

Unix builds come with multiple built-in VFSes. The default VFS for unix is called "unix" and is used in most applications. Windows builds also come with multiple built-in VFSes. The default Windows VFS is called "win32" and is used in most applications.

We are not limited to these bundled VFSes however; we can subclass SQLite's objects to create our own. If appropriate, the majority of the work can even be delegated to the standard VFS to create a shim:

A new VFS is implemented by subclassing three objects:

  • sqlite3_vfs
  • sqlite3_io_methods
  • sqlite3_file

For Your Consideration

The proposed goal is to enable opening an embedded SQLite database from Go's new embed.FS type using a URI and a custom VFS. According to RFC 3986, a URI consists of "...a scheme, an authority, a path, a query string, and a fragment."

Consider the following code which extends the previous example:

db, err := sql.Open("sqlite3", "file://embeddedDatabase/specimen.db")

In this line a standard VFS is being used since no VFS pragma was specified. Accordingly the "embeddedDatabase" authority string above is interpreted as a folder name, and not as our embed.FS variable of the same name. The proposed solution would require the authority string to be interpreted as a Go embed.FS variable name, and the path string to be the path to follow within the embed.FS instance. Thus, the proposed Open statement would conceivably look like:

db, err := sql.Open("sqlite3", "file://embeddedDatabase/specimen.db?vfs=go-embed-unix&mode=ro")
// or
db, err := sql.Open("sqlite3", "file://embeddedDatabase/specimen.db?vfs=go-embed-win32&mode=ro")

If you are knowledgeable and so inclined, please answer or comment on the following questions as you see fit:

  • Does this approach appear sound? Why or why not?
  • Does a method already exist to read embedded SQLite that has been overlooked here?
  • Do you have any custom VFS experience you can share? Are there pitfalls to be aware of?
  • Do you foresee this proposal as a stand-alone module or something to include in mattn/go-sqlite3?

Sources

Go

SQLite

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions