sqlite pass 1
This commit is contained in:
parent
1867b30d68
commit
8678211369
@ -176,6 +176,7 @@ func (s *Moonshark) initRunner() error {
|
||||
runnerOpts := []runner.RunnerOption{
|
||||
runner.WithPoolSize(s.Config.Runner.PoolSize),
|
||||
runner.WithLibDirs(s.Config.Dirs.Libs...),
|
||||
runner.WithDataDir(s.Config.Dirs.Data),
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -16,12 +16,17 @@ var sandboxLuaCode string
|
||||
//go:embed json.lua
|
||||
var jsonLuaCode string
|
||||
|
||||
//go:embed sqlite.lua
|
||||
var sqliteLuaCode string
|
||||
|
||||
// Global bytecode cache to improve performance
|
||||
var (
|
||||
sandboxBytecode atomic.Pointer[[]byte]
|
||||
jsonBytecode atomic.Pointer[[]byte]
|
||||
sqliteBytecode atomic.Pointer[[]byte]
|
||||
bytecodeOnce sync.Once
|
||||
jsonBytecodeOnce sync.Once
|
||||
sqliteBytecodeOnce sync.Once
|
||||
)
|
||||
|
||||
// precompileSandboxCode compiles the sandbox.lua code to bytecode once
|
||||
@ -68,10 +73,33 @@ func precompileJsonModule() {
|
||||
logger.Debug("Successfully precompiled json.lua to bytecode (%d bytes)", len(code))
|
||||
}
|
||||
|
||||
// precompileSqliteModule compiles the sqlite.lua code to bytecode once
|
||||
func precompileSqliteModule() {
|
||||
tempState := luajit.New()
|
||||
if tempState == nil {
|
||||
logger.Fatal("Failed to create temp Lua state for SQLite module compilation")
|
||||
}
|
||||
defer tempState.Close()
|
||||
defer tempState.Cleanup()
|
||||
|
||||
code, err := tempState.CompileBytecode(sqliteLuaCode, "sqlite.lua")
|
||||
if err != nil {
|
||||
logger.Error("Failed to compile SQLite module: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
bytecode := make([]byte, len(code))
|
||||
copy(bytecode, code)
|
||||
sqliteBytecode.Store(&bytecode)
|
||||
|
||||
logger.Debug("Successfully precompiled sqlite.lua to bytecode (%d bytes)", len(code))
|
||||
}
|
||||
|
||||
// loadSandboxIntoState loads the sandbox code into a Lua state
|
||||
func loadSandboxIntoState(state *luajit.State, verbose bool) error {
|
||||
bytecodeOnce.Do(precompileSandboxCode)
|
||||
jsonBytecodeOnce.Do(precompileJsonModule)
|
||||
sqliteBytecodeOnce.Do(precompileSqliteModule)
|
||||
|
||||
// First load and execute the JSON module
|
||||
jsBytecode := jsonBytecode.Load()
|
||||
@ -80,20 +108,16 @@ func loadSandboxIntoState(state *luajit.State, verbose bool) error {
|
||||
logger.Debug("Loading json.lua from precompiled bytecode")
|
||||
}
|
||||
|
||||
// Execute JSON module bytecode and store result to _G.json
|
||||
if err := state.LoadBytecode(*jsBytecode, "json.lua"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Execute with 1 result and store to _G.json
|
||||
if err := state.RunBytecodeWithResults(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The module table is now on top of stack, set it to _G.json
|
||||
state.SetGlobal("json")
|
||||
} else {
|
||||
// Fallback - compile and execute JSON module directly
|
||||
if verbose {
|
||||
logger.Warning("Using non-precompiled json.lua")
|
||||
}
|
||||
@ -103,7 +127,37 @@ func loadSandboxIntoState(state *luajit.State, verbose bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Then load sandbox
|
||||
// Initialize active connections tracking
|
||||
if err := state.DoString(`__active_sqlite_connections = {}`); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load SQLite module
|
||||
sqlBytecode := sqliteBytecode.Load()
|
||||
if sqlBytecode != nil && len(*sqlBytecode) > 0 {
|
||||
if verbose {
|
||||
logger.Debug("Loading sqlite.lua from precompiled bytecode")
|
||||
}
|
||||
|
||||
if err := state.LoadBytecode(*sqlBytecode, "sqlite.lua"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := state.RunBytecodeWithResults(1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
state.SetGlobal("sqlite")
|
||||
} else {
|
||||
if verbose {
|
||||
logger.Warning("Using non-precompiled sqlite.lua")
|
||||
}
|
||||
|
||||
if err := state.DoString(sqliteLuaCode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bytecode := sandboxBytecode.Load()
|
||||
if bytecode != nil && len(*bytecode) > 0 {
|
||||
if verbose {
|
||||
@ -112,7 +166,6 @@ func loadSandboxIntoState(state *luajit.State, verbose bool) error {
|
||||
return state.LoadAndRunBytecode(*bytecode, "sandbox.lua")
|
||||
}
|
||||
|
||||
// Fallback
|
||||
if verbose {
|
||||
logger.Warning("Using non-precompiled sandbox.lua")
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ type Runner struct {
|
||||
statePool chan int // Pool of available state indexes
|
||||
poolSize int // Size of the state pool
|
||||
moduleLoader *ModuleLoader // Module loader
|
||||
dataDir string // Data directory for SQLite databases
|
||||
isRunning atomic.Bool // Whether the runner is active
|
||||
mu sync.RWMutex // Mutex for thread safety
|
||||
scriptDir string // Current script directory
|
||||
@ -65,11 +66,21 @@ func WithLibDirs(dirs ...string) RunnerOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithDataDir sets the data directory for SQLite databases
|
||||
func WithDataDir(dataDir string) RunnerOption {
|
||||
return func(r *Runner) {
|
||||
if dataDir != "" {
|
||||
r.dataDir = dataDir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewRunner creates a new Runner with a pool of states
|
||||
func NewRunner(options ...RunnerOption) (*Runner, error) {
|
||||
// Default configuration
|
||||
runner := &Runner{
|
||||
poolSize: runtime.GOMAXPROCS(0),
|
||||
dataDir: "data",
|
||||
}
|
||||
|
||||
// Apply options
|
||||
@ -86,12 +97,16 @@ func NewRunner(options ...RunnerOption) (*Runner, error) {
|
||||
runner.moduleLoader = NewModuleLoader(config)
|
||||
}
|
||||
|
||||
// Initialize SQLite
|
||||
InitSQLite(runner.dataDir)
|
||||
|
||||
// Initialize states and pool
|
||||
runner.states = make([]*State, runner.poolSize)
|
||||
runner.statePool = make(chan int, runner.poolSize)
|
||||
|
||||
// Create and initialize all states
|
||||
if err := runner.initializeStates(); err != nil {
|
||||
CleanupSQLite() // Clean up SQLite connections
|
||||
runner.Close() // Clean up already created states
|
||||
return nil, err
|
||||
}
|
||||
@ -258,6 +273,9 @@ cleanup:
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up SQLite
|
||||
CleanupSQLite()
|
||||
|
||||
logger.Debug("Runner closed")
|
||||
return nil
|
||||
}
|
||||
|
@ -101,19 +101,21 @@ func (s *Sandbox) registerCoreFunctions(state *luajit.State) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := RegisterSQLiteFunctions(state); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execute runs a Lua script in the sandbox with the given context
|
||||
func (s *Sandbox) Execute(state *luajit.State, bytecode []byte, ctx *Context) (*Response, error) {
|
||||
// Get the execution function first
|
||||
state.GetGlobal("__execute_script")
|
||||
if !state.IsFunction(-1) {
|
||||
state.Pop(1)
|
||||
return nil, ErrSandboxNotInitialized
|
||||
}
|
||||
|
||||
// Load bytecode
|
||||
if err := state.LoadBytecode(bytecode, "script"); err != nil {
|
||||
state.Pop(1) // Pop the __execute_script function
|
||||
return nil, fmt.Errorf("failed to load script: %w", err)
|
||||
@ -127,10 +129,10 @@ func (s *Sandbox) Execute(state *luajit.State, bytecode []byte, ctx *Context) (*
|
||||
|
||||
// Execute with 2 args, 1 result
|
||||
if err := state.Call(2, 1); err != nil {
|
||||
ReleaseActiveConnections(state)
|
||||
return nil, fmt.Errorf("script execution failed: %w", err)
|
||||
}
|
||||
|
||||
// Get result value
|
||||
body, err := state.ToValue(-1)
|
||||
state.Pop(1)
|
||||
|
||||
@ -141,6 +143,8 @@ func (s *Sandbox) Execute(state *luajit.State, bytecode []byte, ctx *Context) (*
|
||||
|
||||
extractHTTPResponseData(state, response)
|
||||
|
||||
ReleaseActiveConnections(state)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
|
393
core/runner/sqlite.go
Normal file
393
core/runner/sqlite.go
Normal file
@ -0,0 +1,393 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
sqlite "zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
|
||||
"Moonshark/core/utils/logger"
|
||||
|
||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
)
|
||||
|
||||
// SQLiteConnection tracks an active connection
|
||||
type SQLiteConnection struct {
|
||||
DbName string
|
||||
Conn *sqlite.Conn
|
||||
Pool *sqlitex.Pool
|
||||
}
|
||||
|
||||
// SQLiteManager handles database connections
|
||||
type SQLiteManager struct {
|
||||
mu sync.RWMutex
|
||||
pools map[string]*sqlitex.Pool
|
||||
activeConns map[string]*SQLiteConnection
|
||||
dataDir string
|
||||
}
|
||||
|
||||
// Global manager
|
||||
var sqliteManager *SQLiteManager
|
||||
|
||||
// InitSQLite initializes the SQLite manager
|
||||
func InitSQLite(dataDir string) {
|
||||
sqliteManager = &SQLiteManager{
|
||||
pools: make(map[string]*sqlitex.Pool),
|
||||
activeConns: make(map[string]*SQLiteConnection),
|
||||
dataDir: dataDir,
|
||||
}
|
||||
logger.Server("SQLite initialized with data directory: %s", dataDir)
|
||||
}
|
||||
|
||||
// CleanupSQLite closes all database connections
|
||||
func CleanupSQLite() {
|
||||
if sqliteManager == nil {
|
||||
return
|
||||
}
|
||||
|
||||
sqliteManager.mu.Lock()
|
||||
defer sqliteManager.mu.Unlock()
|
||||
|
||||
// Release all active connections
|
||||
for id, conn := range sqliteManager.activeConns {
|
||||
if conn.Pool != nil {
|
||||
conn.Pool.Put(conn.Conn)
|
||||
}
|
||||
delete(sqliteManager.activeConns, id)
|
||||
}
|
||||
|
||||
// Close all pools
|
||||
for name, pool := range sqliteManager.pools {
|
||||
if err := pool.Close(); err != nil {
|
||||
logger.Error("Failed to close database %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
sqliteManager.pools = nil
|
||||
sqliteManager.activeConns = nil
|
||||
logger.Debug("SQLite connections closed")
|
||||
}
|
||||
|
||||
// ReleaseActiveConnections returns all active connections to their pools
|
||||
func ReleaseActiveConnections(state *luajit.State) {
|
||||
if sqliteManager == nil {
|
||||
return
|
||||
}
|
||||
|
||||
sqliteManager.mu.Lock()
|
||||
defer sqliteManager.mu.Unlock()
|
||||
|
||||
// Get active connections table from Lua
|
||||
state.GetGlobal("__active_sqlite_connections")
|
||||
if !state.IsTable(-1) {
|
||||
state.Pop(1)
|
||||
return
|
||||
}
|
||||
|
||||
// Iterate through active connections
|
||||
state.PushNil() // Start iteration
|
||||
for state.Next(-2) {
|
||||
// Stack now has key at -2 and value at -1
|
||||
if state.IsTable(-1) {
|
||||
state.GetField(-1, "id")
|
||||
if state.IsString(-1) {
|
||||
connID := state.ToString(-1)
|
||||
|
||||
// Release connection from Go side
|
||||
if conn, exists := sqliteManager.activeConns[connID]; exists {
|
||||
if conn.Pool != nil {
|
||||
conn.Pool.Put(conn.Conn)
|
||||
}
|
||||
delete(sqliteManager.activeConns, connID)
|
||||
}
|
||||
}
|
||||
state.Pop(1) // Pop connection id
|
||||
}
|
||||
state.Pop(1) // Pop value, leave key for next iteration
|
||||
}
|
||||
|
||||
// Clear the active connections table
|
||||
state.PushNil()
|
||||
state.SetGlobal("__active_sqlite_connections")
|
||||
}
|
||||
|
||||
// getPool returns a connection pool for the specified database
|
||||
func getPool(dbName string) (*sqlitex.Pool, error) {
|
||||
if sqliteManager == nil {
|
||||
return nil, errors.New("SQLite not initialized")
|
||||
}
|
||||
|
||||
// Validate database name
|
||||
dbName = filepath.Base(dbName)
|
||||
if dbName == "" || dbName[0] == '.' {
|
||||
return nil, errors.New("invalid database name")
|
||||
}
|
||||
|
||||
// Check for existing pool
|
||||
sqliteManager.mu.RLock()
|
||||
pool, exists := sqliteManager.pools[dbName]
|
||||
sqliteManager.mu.RUnlock()
|
||||
|
||||
if exists {
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// Create new pool
|
||||
sqliteManager.mu.Lock()
|
||||
defer sqliteManager.mu.Unlock()
|
||||
|
||||
// Double check if another goroutine created it
|
||||
if pool, exists = sqliteManager.pools[dbName]; exists {
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// Create database file path
|
||||
dbPath := filepath.Join(sqliteManager.dataDir, dbName+".db")
|
||||
|
||||
// Create the pool
|
||||
pool, err := sqlitex.NewPool(dbPath, sqlitex.PoolOptions{})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
|
||||
sqliteManager.pools[dbName] = pool
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// getConnection returns a connection from the pool
|
||||
func getConnection(dbName string, connID string) (*sqlite.Conn, *sqlitex.Pool, error) {
|
||||
// Check for existing connection first
|
||||
sqliteManager.mu.RLock()
|
||||
conn, exists := sqliteManager.activeConns[connID]
|
||||
sqliteManager.mu.RUnlock()
|
||||
|
||||
if exists {
|
||||
return conn.Conn, conn.Pool, nil
|
||||
}
|
||||
|
||||
// Get the pool
|
||||
pool, err := getPool(dbName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Get a connection
|
||||
dbConn := pool.Get(nil)
|
||||
if dbConn == nil {
|
||||
return nil, nil, errors.New("failed to get connection from pool")
|
||||
}
|
||||
|
||||
// Store connection
|
||||
sqliteManager.mu.Lock()
|
||||
sqliteManager.activeConns[connID] = &SQLiteConnection{
|
||||
DbName: dbName,
|
||||
Conn: dbConn,
|
||||
Pool: pool,
|
||||
}
|
||||
sqliteManager.mu.Unlock()
|
||||
|
||||
return dbConn, pool, nil
|
||||
}
|
||||
|
||||
// luaSQLQuery executes a SQL query and returns results to Lua
|
||||
func luaSQLQuery(state *luajit.State) int {
|
||||
// Get database name
|
||||
if !state.IsString(1) {
|
||||
state.PushString("sqlite.query: database name must be a string")
|
||||
return -1
|
||||
}
|
||||
dbName := state.ToString(1)
|
||||
|
||||
// Get query
|
||||
if !state.IsString(2) {
|
||||
state.PushString("sqlite.query: query must be a string")
|
||||
return -1
|
||||
}
|
||||
query := state.ToString(2)
|
||||
|
||||
// Get connection ID (optional for compatibility)
|
||||
var connID string
|
||||
if state.GetTop() >= 4 && !state.IsNil(4) && state.IsString(4) {
|
||||
connID = state.ToString(4)
|
||||
} else {
|
||||
// Generate a temporary connection ID
|
||||
connID = fmt.Sprintf("temp_%p", &query)
|
||||
}
|
||||
|
||||
// Get parameters (optional)
|
||||
var params map[string]any
|
||||
if state.GetTop() >= 3 && !state.IsNil(3) && state.IsTable(3) {
|
||||
var err error
|
||||
params, err = state.ToTable(3)
|
||||
if err != nil {
|
||||
state.PushString("sqlite.query: failed to parse parameters: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Get connection
|
||||
conn, pool, err := getConnection(dbName, connID)
|
||||
if err != nil {
|
||||
state.PushString("sqlite.query: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
// For temporary connections, defer release
|
||||
if !strings.HasPrefix(connID, "temp_") {
|
||||
defer pool.Put(conn)
|
||||
|
||||
// Remove from active connections
|
||||
sqliteManager.mu.Lock()
|
||||
delete(sqliteManager.activeConns, connID)
|
||||
sqliteManager.mu.Unlock()
|
||||
}
|
||||
|
||||
// Execute query and collect results
|
||||
var rows []map[string]any
|
||||
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Named: params, // Using Named for named parameters
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
row := make(map[string]any)
|
||||
columnCount := stmt.ColumnCount()
|
||||
|
||||
for i := 0; i < columnCount; i++ {
|
||||
columnName := stmt.ColumnName(i)
|
||||
columnType := stmt.ColumnType(i)
|
||||
|
||||
switch columnType {
|
||||
case sqlite.TypeInteger:
|
||||
row[columnName] = stmt.ColumnInt64(i)
|
||||
case sqlite.TypeFloat:
|
||||
row[columnName] = stmt.ColumnFloat(i)
|
||||
case sqlite.TypeText:
|
||||
row[columnName] = stmt.ColumnText(i)
|
||||
case sqlite.TypeBlob:
|
||||
blobSize := stmt.ColumnLen(i)
|
||||
buf := make([]byte, blobSize)
|
||||
blob := stmt.ColumnBytes(i, buf)
|
||||
row[columnName] = blob
|
||||
case sqlite.TypeNull:
|
||||
row[columnName] = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Add row copy to results
|
||||
rowCopy := make(map[string]any, len(row))
|
||||
for k, v := range row {
|
||||
rowCopy[k] = v
|
||||
}
|
||||
rows = append(rows, rowCopy)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
state.PushString("sqlite.query: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
// Create result table
|
||||
state.NewTable()
|
||||
|
||||
// Add results to the table
|
||||
for i, row := range rows {
|
||||
state.PushNumber(float64(i + 1))
|
||||
if err := state.PushTable(row); err != nil {
|
||||
state.PushString("sqlite.query: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
state.SetTable(-3)
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// luaSQLExec executes a SQL statement without returning results
|
||||
func luaSQLExec(state *luajit.State) int {
|
||||
// Get database name and query
|
||||
if !state.IsString(1) {
|
||||
state.PushString("sqlite.exec: database name must be a string")
|
||||
return -1
|
||||
}
|
||||
dbName := state.ToString(1)
|
||||
|
||||
if !state.IsString(2) {
|
||||
state.PushString("sqlite.exec: query must be a string")
|
||||
return -1
|
||||
}
|
||||
query := state.ToString(2)
|
||||
|
||||
// Get connection ID (optional for compatibility)
|
||||
var connID string
|
||||
if state.GetTop() >= 4 && !state.IsNil(4) && state.IsString(4) {
|
||||
connID = state.ToString(4)
|
||||
} else {
|
||||
// Generate a temporary connection ID
|
||||
connID = fmt.Sprintf("temp_%p", &query)
|
||||
}
|
||||
|
||||
// Get parameters (optional)
|
||||
var params map[string]any
|
||||
if state.GetTop() >= 3 && !state.IsNil(3) && state.IsTable(3) {
|
||||
var err error
|
||||
params, err = state.ToTable(3)
|
||||
if err != nil {
|
||||
state.PushString("sqlite.exec: failed to parse parameters: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// Get connection
|
||||
conn, pool, err := getConnection(dbName, connID)
|
||||
if err != nil {
|
||||
state.PushString("sqlite.exec: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
// For temporary connections, defer release
|
||||
if !strings.HasPrefix(connID, "temp_") {
|
||||
defer pool.Put(conn)
|
||||
|
||||
// Remove from active connections
|
||||
sqliteManager.mu.Lock()
|
||||
delete(sqliteManager.activeConns, connID)
|
||||
sqliteManager.mu.Unlock()
|
||||
}
|
||||
|
||||
// Execute statement
|
||||
if params != nil {
|
||||
err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
|
||||
Named: params, // Using Named for named parameters
|
||||
})
|
||||
} else {
|
||||
err = sqlitex.ExecScript(conn, query)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
state.PushString("sqlite.exec: " + err.Error())
|
||||
return -1
|
||||
}
|
||||
|
||||
// Return number of affected rows
|
||||
state.PushNumber(float64(conn.Changes()))
|
||||
return 1
|
||||
}
|
||||
|
||||
// RegisterSQLiteFunctions registers SQLite functions with the Lua state
|
||||
func RegisterSQLiteFunctions(state *luajit.State) error {
|
||||
if err := state.RegisterGoFunction("__sqlite_query", luaSQLQuery); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := state.RegisterGoFunction("__sqlite_exec", luaSQLExec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
180
core/runner/sqlite.lua
Normal file
180
core/runner/sqlite.lua
Normal file
@ -0,0 +1,180 @@
|
||||
__active_sqlite_connections = {}
|
||||
|
||||
-- Connection metatable
|
||||
local connection_mt = {
|
||||
__index = {
|
||||
-- Execute a query and return results as a table
|
||||
query = function(self, query, params)
|
||||
if type(query) ~= "string" then
|
||||
error("connection:query: query must be a string", 2)
|
||||
end
|
||||
return __sqlite_query(self.db_name, query, params)
|
||||
end,
|
||||
|
||||
-- Execute a statement and return affected rows
|
||||
exec = function(self, query, params)
|
||||
if type(query) ~= "string" then
|
||||
error("connection:exec: query must be a string", 2)
|
||||
end
|
||||
return __sqlite_exec(self.db_name, query, params)
|
||||
end,
|
||||
|
||||
-- Create a new table
|
||||
create_table = function(self, table_name, schema)
|
||||
if type(schema) ~= "table" then
|
||||
error("connection:create_table: schema must be a table", 2)
|
||||
end
|
||||
|
||||
local columns = {}
|
||||
for name, definition in pairs(schema) do
|
||||
table.insert(columns, name .. " " .. definition)
|
||||
end
|
||||
|
||||
local query = string.format("CREATE TABLE IF NOT EXISTS %s (%s)",
|
||||
table_name, table.concat(columns, ", "))
|
||||
|
||||
return self:exec(query)
|
||||
end,
|
||||
|
||||
-- Insert a row or multiple rows
|
||||
insert = function(self, table_name, data)
|
||||
if type(data) ~= "table" then
|
||||
error("connection:insert: data must be a table", 2)
|
||||
end
|
||||
|
||||
-- Single row
|
||||
if data[1] == nil and next(data) ~= nil then
|
||||
local columns = {}
|
||||
local placeholders = {}
|
||||
local params = {}
|
||||
|
||||
for col, val in pairs(data) do
|
||||
table.insert(columns, col)
|
||||
table.insert(placeholders, ":" .. col)
|
||||
params[col] = val
|
||||
end
|
||||
|
||||
local query = string.format(
|
||||
"INSERT INTO %s (%s) VALUES (%s)",
|
||||
table_name,
|
||||
table.concat(columns, ", "),
|
||||
table.concat(placeholders, ", ")
|
||||
)
|
||||
|
||||
return self:exec(query, params)
|
||||
end
|
||||
|
||||
-- Multiple rows
|
||||
if #data > 0 and type(data[1]) == "table" then
|
||||
local affected = 0
|
||||
|
||||
for _, row in ipairs(data) do
|
||||
local result = self:insert(table_name, row)
|
||||
affected = affected + result
|
||||
end
|
||||
|
||||
return affected
|
||||
end
|
||||
|
||||
error("connection:insert: invalid data format", 2)
|
||||
end,
|
||||
|
||||
-- Update rows
|
||||
update = function(self, table_name, data, where, where_params)
|
||||
if type(data) ~= "table" then
|
||||
error("connection:update: data must be a table", 2)
|
||||
end
|
||||
|
||||
local sets = {}
|
||||
local params = {}
|
||||
|
||||
for col, val in pairs(data) do
|
||||
table.insert(sets, col .. " = :" .. col)
|
||||
params[col] = val
|
||||
end
|
||||
|
||||
local query = string.format(
|
||||
"UPDATE %s SET %s",
|
||||
table_name,
|
||||
table.concat(sets, ", ")
|
||||
)
|
||||
|
||||
if where then
|
||||
query = query .. " WHERE " .. where
|
||||
|
||||
if where_params then
|
||||
for k, v in pairs(where_params) do
|
||||
params[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self:exec(query, params)
|
||||
end,
|
||||
|
||||
-- Delete rows
|
||||
delete = function(self, table_name, where, params)
|
||||
local query = "DELETE FROM " .. table_name
|
||||
|
||||
if where then
|
||||
query = query .. " WHERE " .. where
|
||||
end
|
||||
|
||||
return self:exec(query, params)
|
||||
end,
|
||||
|
||||
-- Get one row
|
||||
get_one = function(self, query, params)
|
||||
local results = self:query(query, params)
|
||||
return results[1]
|
||||
end,
|
||||
|
||||
-- Begin transaction
|
||||
begin = function(self)
|
||||
return self:exec("BEGIN TRANSACTION")
|
||||
end,
|
||||
|
||||
-- Commit transaction
|
||||
commit = function(self)
|
||||
return self:exec("COMMIT")
|
||||
end,
|
||||
|
||||
-- Rollback transaction
|
||||
rollback = function(self)
|
||||
return self:exec("ROLLBACK")
|
||||
end,
|
||||
|
||||
-- Transaction wrapper function
|
||||
transaction = function(self, callback)
|
||||
self:begin()
|
||||
|
||||
local success, result = pcall(function()
|
||||
return callback(self)
|
||||
end)
|
||||
|
||||
if success then
|
||||
self:commit()
|
||||
return result
|
||||
else
|
||||
self:rollback()
|
||||
error(result, 2)
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
-- Create sqlite() function that returns a connection object
|
||||
return function(db_name)
|
||||
if type(db_name) ~= "string" then
|
||||
error("sqlite: database name must be a string", 2)
|
||||
end
|
||||
|
||||
local conn = {
|
||||
db_name = db_name,
|
||||
id = tostring({}):match("table: (.*)") -- unique ID based on table address
|
||||
}
|
||||
|
||||
__active_sqlite_connections[conn.id] = conn
|
||||
|
||||
return setmetatable(conn, connection_mt)
|
||||
end
|
10
go.mod
10
go.mod
@ -10,15 +10,25 @@ require (
|
||||
github.com/matoous/go-nanoid/v2 v2.1.0
|
||||
github.com/valyala/bytebufferpool v1.0.0
|
||||
github.com/valyala/fasthttp v1.61.0
|
||||
zombiezen.com/go/sqlite v1.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
modernc.org/libc v1.65.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.10.0 // indirect
|
||||
modernc.org/sqlite v1.37.0 // indirect
|
||||
)
|
||||
|
||||
replace git.sharkk.net/Sky/LuaJIT-to-Go => ./luajit
|
||||
|
56
go.sum
56
go.sum
@ -12,17 +12,29 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deneonet/benc v1.1.7 h1:0XPxTTVJZq/ulxXvMn2Mzjx5XquekVky3wX6eTgA0vA=
|
||||
github.com/deneonet/benc v1.1.7/go.mod h1:UCfkM5Od0B2huwv/ZItvtUb7QnALFt9YXtX8NXX4Lts=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE=
|
||||
github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM=
|
||||
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/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
@ -35,8 +47,52 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
||||
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||
modernc.org/libc v1.65.0 h1:e183gLDnAp9VJh6gWKdTy0CThL9Pt7MfcR/0bgb7Y1Y=
|
||||
modernc.org/libc v1.65.0/go.mod h1:7m9VzGq7APssBTydds2zBcxGREwvIGpuUBaKTXdm2Qs=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4=
|
||||
modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
|
||||
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
|
||||
modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI=
|
||||
modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
zombiezen.com/go/sqlite v1.4.0 h1:N1s3RIljwtp4541Y8rM880qgGIgq3fTD2yks1xftnKU=
|
||||
zombiezen.com/go/sqlite v1.4.0/go.mod h1:0w9F1DN9IZj9AcLS9YDKMboubCACkwYCGkzoy3eG5ik=
|
||||
|
Loading…
x
Reference in New Issue
Block a user