From d11571564e5ae522dd5b7a9497b56beea18e4f71 Mon Sep 17 00:00:00 2001 From: julien040 <48369040+julien040@users.noreply.github.com> Date: Sun, 7 Apr 2024 23:07:55 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Create=20func=20signatures=20for?= =?UTF-8?q?=20the=20SQLite=20VTab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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". --- go.mod | 2 + go.sum | 4 ++ plugin/module.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++ plugin/structs.go | 17 ++++++- 4 files changed, 132 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index fd84831..41303db 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 8817c6d..1eefeea 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= diff --git a/plugin/module.go b/plugin/module.go index b0736c3..e0e7bee 100644 --- a/plugin/module.go +++ b/plugin/module.go @@ -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 +} diff --git a/plugin/structs.go b/plugin/structs.go index 0b9a77b..6e59d7a 100644 --- a/plugin/structs.go +++ b/plugin/structs.go @@ -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 } @@ -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 }