Skip to content

Commit

Permalink
🎨 Create func signatures for the SQLite VTab
Browse files Browse the repository at this point in the history
plugin/module.go now has the functions signatures to create a virtual table in SQLite.
The remaining work is now to implement the function body.

**Explanation of the code**
As specified in the SQLite documentation (https://www.sqlite.org/vtab.html),
a VTab (Virtual Table) is implemented with three structs:
	- the module struct => Infer the schema and create the connection
	- the table struct => Find the best querying method and open the cursor struct
	- the cursor struct => query and update the rows
Each of these structs have several methods attached to them. You can refer to the source code to find their exact "raison d'être".
  • Loading branch information
julien040 committed Apr 7, 2024
1 parent edd7028 commit d115715
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 2 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ go 1.22.1

require (
github.com/fatih/color v1.16.0 // indirect
github.com/gammazero/deque v0.2.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/oklog/run v1.1.0 // indirect
golang.org/x/net v0.24.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
Expand All @@ -20,6 +22,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
Expand Down
111 changes: 111 additions & 0 deletions plugin/module.go
Original file line number Diff line number Diff line change
@@ -1 +1,112 @@
package plugin

import (
"github.com/gammazero/deque"
"github.com/mattn/go-sqlite3"
)

// This file links the plugin to the SQLite Virtual Table interface

// SQLiteModule is a struct that holds the information about the SQLite module
//
// For each table that the plugin provides and for each profile, a new SQLiteModule
// should be created and registered in the main program
type SQLiteModule struct {
PluginPath string
PluginManifest PluginManifest
TableIndex int
client *InternalClient
}

// SQLiteTable that holds the information needed for the BestIndex and Open methods
type SQLiteTable struct {
nextCursor int
tableIndex int
schema DatabaseSchema
client *InternalClient
}

// SQLiteCursor holds the information needed for the Column, Filter, EOF and Next methods
type SQLiteCursor struct {
tableIndex int
cursorIndex int
schema DatabaseSchema
client *InternalClient
noMoreRows bool
rows *deque.Deque[[]interface{}] // A ring buffer to store the rows before sending them to SQLite
nextCursor *int
}

// EponymousOnlyModule is a method that is used to mark the table as eponymous-only
//
// See https://www.sqlite.org/vtab.html#eponymous_virtual_tables for more information
func (m *SQLiteModule) EponymousOnlyModule() {}

// Create is called when the virtual table is created e.g. CREATE VIRTUAL TABLE or SELECT...FROM(epo_table)
//
// Its main job is to create a new RPC client and return the needed information
// for the SQLite virtual table methods
func (m *SQLiteModule) Create(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTab, error) {
return nil, nil
}

// Connect is called when the virtual table is connected
//
// Because it's an eponymous-only module, the method must be identical to Create
func (m *SQLiteModule) Connect(c *sqlite3.SQLiteConn, args []string) (sqlite3.VTab, error) {
return m.Create(c, args)
}

// BestIndex is called when the virtual table is queried
// to figure out the best way to query the table
//
// However, we don't use it that way but only to serialize the constraints
// for the Filter method
func (t *SQLiteTable) BestIndex(cst []sqlite3.InfoConstraint, ob []sqlite3.InfoOrderBy) (*sqlite3.IndexResult, error) {
return nil, nil
}

// Open is called when a new cursor is opened
//
// It should return a new cursor
func (t *SQLiteTable) Open() (sqlite3.VTabCursor, error) {
return nil, nil
}

// Close is called when the cursor is no longer needed
func (c *SQLiteCursor) Close() error { return nil }

// These methods are not used in this plugin
func (v *SQLiteTable) Disconnect() error { return nil }
func (v *SQLiteTable) Destroy() error { return nil }
func (v *SQLiteModule) DestroyModule() {}

// Column is called when a column is queried
//
// It should return the value of the column
func (c *SQLiteCursor) Column(cst int) (interface{}, error) { return nil, nil }

// EOF is called after each row is queried to check if there are more rows
func (c *SQLiteCursor) EOF() bool { return false }

// Next is called to move the cursor to the next row
//
// If noMoreRows is set to false, and the cursor is at the end of the rows,
// Next will ask the plugin for more rows
//
// If noMoreRows is set to true, Next will set EOF to true
func (c *SQLiteCursor) Next() error { return nil }

// RowID is called to get the row ID of the current row
func (c *SQLiteCursor) RowID() (int64, error) { return 0, nil }

func (c *SQLiteCursor) Filter(idxNum int, idxStr string, vals []interface{}) error {
// Filter can be called several times with the same cursor
// Each time, it is supposed to reset the cursor to the beginning
// Therefore, it should wipe out all the cursor fields
//
// Moreover, for the sake of simplicity, we will create a new cursor on the plugin side,
// which means the cursorIndex must be incremented while not yelding any conflict
// How to fix this? We must have access to the parent struct (SQLiteTable).
return nil
}
17 changes: 15 additions & 2 deletions plugin/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ type PluginManifest struct {
Version string
Author string
Description string
// A list of tables that the plugin will provide
Tables []string

UserConfig []PluginConfigField
}
Expand All @@ -61,8 +63,19 @@ type PluginConfigField struct {
// you can generate a unique key. The primary key must be unique for each row.
// It is used to update and delete rows.
type DatabaseSchema struct {
// ... other fields
HandleLimit bool
// The columns of the table
Columns []DatabaseSchemaColumn
// The primary key is the index of the column that is the primary key (starting from 0)
PrimaryKey int

// The following fields are used to optimize the queries

// HandleLimit is a boolean that specifies whether the plugin can handle the LIMIT clause.
// If so, the plugin should return only the specified number of rows.
HandleLimit bool

// HandleOffset is a boolean that specifies whether the plugin can handle the OFFSET clause.
// If not, the main program will skip the n offseted rows.
HandleOffset bool
}

Expand Down

0 comments on commit d115715

Please sign in to comment.