diff --git a/bindings/lua/sqlite_vec.lua b/bindings/lua/sqlite_vec.lua new file mode 100644 index 00000000..dde78604 --- /dev/null +++ b/bindings/lua/sqlite_vec.lua @@ -0,0 +1,116 @@ +-- sqlite_vec.lua Lua 5.1 compatible version with JSON fallback +local sqlite3 = require("lsqlite3") + +local M = {} + +-- Function to load extension +function M.load(db) + local possible_paths = { + "../../sqlite-vec.so", -- Linux + "../../sqlite-vec.dll", -- Windows + "../../sqlite-vec.dylib", -- macOS + "./sqlite-vec.so", + "./sqlite-vec.dll", + "./sqlite-vec.dylib", + "../sqlite-vec.so", + "../sqlite-vec.dll", + "../sqlite-vec.dylib", + "sqlite-vec", + } + + local entry_point = "sqlite3_vec_init" + + if db.enable_load_extension then + db:enable_load_extension(true) + for _, path in ipairs(possible_paths) do + local ok, result = pcall(function() + return db:load_extension(path, entry_point) + end) + if ok then + db:enable_load_extension(false) + return result + end + end + db:enable_load_extension(false) + error("Failed to load extension from all paths") + else + for _, path in ipairs(possible_paths) do + local ok, result = pcall(function() + return db:load_extension(path, entry_point) + end) + if ok then + return result + else + local ok2, result2 = pcall(function() + return db:load_extension(path) + end) + if ok2 then + return result2 + end + end + end + error("Failed to load extension from all paths") + end +end + +-- Lua 5.1 compatible float to binary conversion function +local function float_to_bytes(f) + if f == 0 then + return string.char(0, 0, 0, 0) + end + + local sign = 0 + if f < 0 then + sign = 1 + f = -f + end + + local mantissa, exponent = math.frexp(f) + exponent = exponent - 1 + + if exponent < -126 then + mantissa = mantissa * 2^(exponent + 126) + exponent = -127 + else + mantissa = (mantissa - 0.5) * 2 + end + + exponent = exponent + 127 + mantissa = math.floor(mantissa * 2^23 + 0.5) + + local bytes = {} + bytes[1] = mantissa % 256; mantissa = math.floor(mantissa / 256) + bytes[2] = mantissa % 256; mantissa = math.floor(mantissa / 256) + bytes[3] = mantissa % 256 + (exponent % 2) * 128; exponent = math.floor(exponent / 2) + bytes[4] = exponent % 128 + sign * 128 + + return string.char(bytes[1], bytes[2], bytes[3], bytes[4]) +end + +-- Helper function: serialize float vector to binary format (Lua 5.1 compatible) +function M.serialize_f32(vector) + local buffer = {} + + if string.pack then + for _, v in ipairs(vector) do + table.insert(buffer, string.pack("f", v)) + end + else + for _, v in ipairs(vector) do + table.insert(buffer, float_to_bytes(v)) + end + end + + return table.concat(buffer) +end + +-- New: JSON format vector serialization (more reliable fallback) +function M.serialize_json(vector) + local values = {} + for _, v in ipairs(vector) do + table.insert(values, tostring(v)) + end + return "[" .. table.concat(values, ",") .. "]" +end + +return M diff --git a/examples/simple-lua/.gitignore b/examples/simple-lua/.gitignore new file mode 100644 index 00000000..7d2345ca --- /dev/null +++ b/examples/simple-lua/.gitignore @@ -0,0 +1,7 @@ +# Lua specific +*.luac + +# SQLite databases (if any test files are created) +*.db +*.sqlite +*.sqlite3 diff --git a/examples/simple-lua/README.md b/examples/simple-lua/README.md new file mode 100644 index 00000000..819df7dd --- /dev/null +++ b/examples/simple-lua/README.md @@ -0,0 +1,62 @@ +# SQLite-Vec Simple Lua Example + +This example demonstrates how to use sqlite-vec with Lua and the lsqlite3 binding. + +## Prerequisites + +1. **Lua** (5.1 or later) - The example is compatible with Lua 5.1+ +2. **lsqlite3** - Lua SQLite3 binding +3. **sqlite-vec extension** - Compiled for your platform + +## Installation + +### Install lsqlite3 + +Using LuaRocks: +```bash +luarocks install lsqlite3 +``` + +Or on Ubuntu/Debian: +```bash +apt-get install lua-sql-sqlite3 +``` + +### Build sqlite-vec + +From the sqlite-vec root directory: +```bash +make +``` + +This will create the appropriate extension file (.so, .dll, or .dylib) for your platform. + +### Setup sqlite_vec.lua + +You have two options: + +1. **Copy the binding file** (recommended): + ```bash + cp ../../bindings/lua/sqlite_vec.lua ./ + ``` + +2. **Modify the require path** in `demo.lua` to point to the bindings directory. + +## Running the Example + +```bash +lua demo.lua +``` + +Expected output: +``` +=== SQLite-Vec Simple Lua Example === +sqlite_version=3.x.x, vec_version=v0.x.x +Inserting vector data... +Executing KNN query... +Results: +rowid=3 distance=0.000000 +rowid=2 distance=0.200000 +rowid=1 distance=0.400000 +✓ Demo completed successfully +``` diff --git a/examples/simple-lua/demo.lua b/examples/simple-lua/demo.lua new file mode 100644 index 00000000..295125e1 --- /dev/null +++ b/examples/simple-lua/demo.lua @@ -0,0 +1,139 @@ +#!/usr/bin/env lua + +-- Simple Lua example demonstrating sqlite-vec usage +-- This example shows how to create vector tables, insert data, and perform KNN queries + +local sqlite3 = require("lsqlite3") + +-- You can either: +-- 1. Copy sqlite_vec.lua from ../../bindings/lua/sqlite_vec.lua to this directory +-- 2. Or modify the path below to point to the bindings directory +local sqlite_vec = require("sqlite_vec") + +function main() + print("=== SQLite-Vec Simple Lua Example ===") + + -- Create in-memory database + local db = sqlite3.open_memory() + if not db then + error("Failed to create database") + end + + -- Load sqlite-vec extension + local load_success, load_error = pcall(function() + sqlite_vec.load(db) + end) + + if not load_success then + error("Failed to load sqlite-vec extension: " .. tostring(load_error)) + end + + -- Check versions - handle the case where vec_version() might not be available + local sqlite_version = nil + local vec_version = nil + + for row in db:nrows("SELECT sqlite_version()") do + sqlite_version = row.sqlite_version + break + end + + -- Try to get vec_version, but don't fail if it's not available + local success, _ = pcall(function() + for row in db:nrows("SELECT vec_version()") do + vec_version = row.vec_version + break + end + end) + + if sqlite_version then + if vec_version then + print(string.format("sqlite_version=%s, vec_version=%s", sqlite_version, vec_version)) + else + print(string.format("sqlite_version=%s, vec_version=unknown", sqlite_version)) + end + end + + -- Verify extension is loaded by checking for vec0 module + local vec0_available = false + for row in db:nrows("SELECT name FROM pragma_module_list() WHERE name='vec0'") do + vec0_available = true + break + end + + if vec0_available then + print("✓ sqlite-vec extension loaded successfully") + else + error("sqlite-vec extension loaded but vec0 module not found") + end + + -- Test data - same as other examples for consistency + local items = { + {1, {0.1, 0.1, 0.1, 0.1}}, + {2, {0.2, 0.2, 0.2, 0.2}}, + {3, {0.3, 0.3, 0.3, 0.3}}, + {4, {0.4, 0.4, 0.4, 0.4}}, + {5, {0.5, 0.5, 0.5, 0.5}}, + } + local query = {0.3, 0.3, 0.3, 0.3} + + -- Create virtual table + local create_result = db:exec("CREATE VIRTUAL TABLE vec_items USING vec0(embedding float[4])") + if create_result ~= sqlite3.OK then + error("Failed to create virtual table: " .. db:errmsg()) + end + + -- Insert data using JSON format (more compatible) + print("Inserting vector data...") + db:exec("BEGIN") + + for _, item in ipairs(items) do + local rowid = math.floor(item[1]) + local vector_json = sqlite_vec.serialize_json(item[2]) + + local sql = string.format("INSERT INTO vec_items(rowid, embedding) VALUES (%d, '%s')", + rowid, vector_json) + local result = db:exec(sql) + if result ~= sqlite3.OK then + error("Failed to insert item: " .. db:errmsg()) + end + end + + db:exec("COMMIT") + + -- Verify data was inserted + local count = 0 + for row in db:nrows("SELECT COUNT(*) as count FROM vec_items") do + count = row.count + break + end + print(string.format("✓ Inserted %d vector records", count)) + + -- Perform KNN query using JSON format + print("Executing KNN query...") + local query_json = sqlite_vec.serialize_json(query) + + local sql = string.format([[ + SELECT + rowid, + distance + FROM vec_items + WHERE embedding MATCH '%s' + ORDER BY distance + LIMIT 3 + ]], query_json) + + print("Results:") + for row in db:nrows(sql) do + print(string.format("rowid=%d distance=%f", row.rowid, row.distance)) + end + + db:close() + print("✓ Demo completed successfully") +end + +-- Run the demo with error handling +local success, error_msg = pcall(main) +if not success then + print("Error: " .. tostring(error_msg)) + os.exit(1) +end diff --git a/examples/simple-lua/run.sh b/examples/simple-lua/run.sh new file mode 100644 index 00000000..ec7d54f3 --- /dev/null +++ b/examples/simple-lua/run.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Simple run script for the Lua example + +echo "=== SQLite-Vec Lua Example Runner ===" + +# Check if Lua is available +if ! command -v lua &> /dev/null; then + echo "Error: Lua is not installed or not in PATH" + exit 1 +fi + +# Check if lsqlite3 is available +lua -e "require('lsqlite3')" 2>/dev/null +if [ $? -ne 0 ]; then + echo "Error: lsqlite3 module is not installed" + echo "Install it with: luarocks install lsqlite3" + exit 1 +fi + +# Check if sqlite-vec extension exists +if [ ! -f "../../sqlite-vec.so" ] && [ ! -f "../../sqlite-vec.dll" ] && [ ! -f "../../sqlite-vec.dylib" ]; then + echo "Error: sqlite-vec extension not found" + echo "Build it with: cd ../.. && make" + exit 1 +fi + +# Run the demo +echo "Running demo..." +lua demo.lua