Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build sqlitelua for scalar, aggregate & table-valued UDFs in Lua #118

Merged
merged 3 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile.test
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# docker build -t sqliteviz/test -f Dockerfile.test .
#

FROM node:12
FROM node:12.22-buster

RUN set -ex; \
apt update; \
Expand Down
3 changes: 3 additions & 0 deletions lib/sql-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ SQLite 3rd party extensions included:
1. [pivot_vtab][5] -- a pivot virtual table
2. `pearson` correlation coefficient function extension from [sqlean][21]
(which is part of [squib][20])
3. [sqlitelua][22] -- a virtual table `luafunctions` which allows to define custom scalar,
aggregate and table-valued functions in Lua

To ease the step to have working clone locally, the build is committed into
the repository.
Expand Down Expand Up @@ -103,3 +105,4 @@ described in [this message from SQLite Forum][12]:
[19]: https://github.com/lana-k/sqliteviz/blob/master/tests/lib/database/sqliteExtensions.spec.js
[20]: https://github.com/mrwilson/squib/blob/master/pearson.c
[21]: https://github.com/nalgeon/sqlean/blob/incubator/src/pearson.c
[22]: https://github.com/kev82/sqlitelua
6 changes: 2 additions & 4 deletions lib/sql-js/benchmark/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
FROM node:14-bullseye
FROM node:20.14-bookworm

RUN set -ex; \
echo 'deb http://deb.debian.org/debian unstable main' \
> /etc/apt/sources.list.d/unstable.list; \
apt-get update; \
apt-get install -y -t unstable firefox; \
apt-get install -y firefox-esr; \
apt-get install -y chromium

WORKDIR /tmp/build
Expand Down
15 changes: 14 additions & 1 deletion lib/sql-js/benchmark/result-analysis.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@
],
"metadata": {}
},
{
"cell_type": "code",
"source": [
"!du -b lib | head -n 2"
],
"outputs": [],
"execution_count": null,
"metadata": {
"collapsed": false,
"outputHidden": false,
"inputHidden": true
}
},
{
"cell_type": "code",
"source": [
Expand Down Expand Up @@ -176,7 +189,7 @@
},
"language_info": {
"name": "python",
"version": "3.10.7",
"version": "3.10.14",
"mimetype": "text/x-python",
"codemirror_mode": {
"name": "ipython",
Expand Down
11 changes: 11 additions & 0 deletions lib/sql-js/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
# Compile-time optimisation
'-Os', # reduces the code size about in half comparing to -O2
'-flto',
'-Isrc', '-Isrc/lua',
)
emflags = (
# Base
Expand Down Expand Up @@ -61,13 +62,23 @@ def build(src: Path, dst: Path):
'-c', src / 'extension-functions.c',
'-o', out / 'extension-functions.o',
])
logging.info('Building LLVM bitcode for SQLite Lua extension')
subprocess.check_call([
'emcc',
*cflags,
'-shared',
*(src / 'lua').glob('*.c'),
*(src / 'sqlitelua').glob('*.c'),
'-o', out / 'sqlitelua.o',
])

logging.info('Building WASM from bitcode')
subprocess.check_call([
'emcc',
*emflags,
out / 'sqlite3.o',
out / 'extension-functions.o',
out / 'sqlitelua.o',
'-o', out / 'sql-wasm.js',
])

Expand Down
42 changes: 42 additions & 0 deletions lib/sql-js/configure.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import logging
import re
import shutil
import subprocess
import sys
import tarfile
import zipfile
from io import BytesIO
from pathlib import Path
Expand Down Expand Up @@ -30,8 +32,14 @@
# =====================
('https://github.com/jakethaw/pivot_vtab/raw/9323ef93/pivot_vtab.c', 'sqlite3_pivotvtab_init'),
('https://github.com/nalgeon/sqlean/raw/95e8d21a/src/pearson.c', 'sqlite3_pearson_init'),
# Third-party extension with own dependencies
# ===========================================
('https://github.com/kev82/sqlitelua/raw/db479510/src/main.c', 'sqlite3_luafunctions_init'),
)

lua_url = 'http://www.lua.org/ftp/lua-5.3.5.tar.gz'
sqlitelua_url = 'https://github.com/kev82/sqlitelua/archive/db479510.zip'

sqljs_url = 'https://github.com/sql-js/sql.js/archive/refs/tags/v1.7.0.zip'


Expand Down Expand Up @@ -59,6 +67,38 @@ def _get_amalgamation(tgt: Path):
shutil.copyfileobj(fr, fw)


def _get_lua(tgt: Path):
# Library definitions from lua/Makefile
lib_str = '''
CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o \
lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o \
ltm.o lundump.o lvm.o lzio.o
LIB_O= lauxlib.o lbaselib.o lbitlib.o lcorolib.o ldblib.o liolib.o \
lmathlib.o loslib.o lstrlib.o ltablib.o lutf8lib.o loadlib.o linit.o
LUA_O= lua.o
'''
header_only_files = {'lprefix', 'luaconf', 'llimits', 'lualib'}
lib_names = set(re.findall(r'(\w+)\.o', lib_str)) | header_only_files

logging.info('Downloading and extracting Lua %s', lua_url)
archive = tarfile.open(fileobj=BytesIO(request.urlopen(lua_url).read()))
(tgt / 'lua').mkdir()
for tarinfo in archive:
tarpath = Path(tarinfo.name)
if tarpath.match('src/*') and tarpath.stem in lib_names:
with (tgt / 'lua' / tarpath.name).open('wb') as fw:
shutil.copyfileobj(archive.extractfile(tarinfo), fw)

logging.info('Downloading and extracting SQLite Lua extension %s', sqlitelua_url)
archive = zipfile.ZipFile(BytesIO(request.urlopen(sqlitelua_url).read()))
archive_root_dir = zipfile.Path(archive, archive.namelist()[0])
(tgt / 'sqlitelua').mkdir()
for zpath in (archive_root_dir / 'src').iterdir():
if zpath.name != 'main.c':
with zpath.open() as fr, (tgt / 'sqlitelua' / zpath.name).open('wb') as fw:
shutil.copyfileobj(fr, fw)


def _get_contrib_functions(tgt: Path):
request.urlretrieve(contrib_functions_url, tgt / 'extension-functions.c')

Expand All @@ -70,6 +110,7 @@ def _get_extensions(tgt: Path):
for url, init_fn in extension_urls:
logging.info('Downloading and appending to amalgamation %s', url)
with request.urlopen(url) as resp:
f.write(b'\n')
shutil.copyfileobj(resp, f)
init_functions.append(init_fn)

Expand All @@ -90,6 +131,7 @@ def _get_sqljs(tgt: Path):
def configure(tgt: Path):
_get_amalgamation(tgt)
_get_contrib_functions(tgt)
_get_lua(tgt)
_get_extensions(tgt)
_get_sqljs(tgt)

Expand Down
2 changes: 1 addition & 1 deletion lib/sql-js/dist/sql-wasm.js

Large diffs are not rendered by default.

Binary file modified lib/sql-js/dist/sql-wasm.wasm
Binary file not shown.
81 changes: 81 additions & 0 deletions tests/lib/database/sqliteExtensions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -455,4 +455,85 @@ describe('SQLite extensions', function () {
xx: [1], xy: [0], xz: [1], yx: [0], yy: [1], yz: [1], zx: [1], zy: [1], zz: [1]
})
})

it('supports simple Lua functions', async function () {
const actual = await db.execute(`
INSERT INTO
luafunctions(name, src)
VALUES
('lua_inline', 'return {"arg"}, {"rv"}, "simple", function(arg) return arg + 1 end'),
('lua_full', '
local input = {"arg"}
local output = {"rv"}

local function func(x)
return math.sin(math.pi) + x
end

return input, output, "simple", func
');

SELECT lua_inline(1), lua_full(1) - 1 < 0.000001;
`)
expect(actual.values).to.eql({ 'lua_inline(1)': [2], 'lua_full(1) - 1 < 0.000001': [1] })
})

it('supports aggregate Lua functions', async function () {
const actual = await db.execute(`
INSERT INTO
luafunctions(name, src)
VALUES
('lua_sum', '
local inputs = {"item"}
local outputs = {"sum"}

local function func(item)
if aggregate_now(item) then
return item
end

local sum = 0
while true do
if aggregate_now(item) then
break
end
sum = sum + item
item = coroutine.yield()
end

return sum
end

return inputs, outputs, "aggregate", func
');

SELECT SUM(value), lua_sum(value) FROM generate_series(1, 10);
`)
expect(actual.values).to.eql({ 'SUM(value)': [55], 'lua_sum(value)': [55] })
})

it('supports table-valued Lua functions', async function () {
const actual = await db.execute(`
INSERT INTO
luafunctions(name, src)
VALUES
('lua_match', '
local inputs = {"pattern", "s"}
local outputs = {"idx", "elm"}

local function func(pattern, s)
local i = 1
for k in s:gmatch(pattern) do
coroutine.yield(i, k)
i = i + 1
end
end

return inputs, outputs, "table", func
');

SELECT * FROM lua_match('%w+', 'hello world from Lua');
`)
expect(actual.values).to.eql({ idx: [1, 2, 3, 4], elm: ['hello', 'world', 'from', 'Lua'] })
})
})
8 changes: 4 additions & 4 deletions tests/lib/utils/time.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ describe('time.js', () => {
})

it('sleep resolves after n ms', async () => {
let before = Date.now()
let before = performance.now()
await time.sleep(10)
expect(Date.now() - before).to.be.least(10)
expect(performance.now() - before).to.be.least(10)

before = Date.now()
before = performance.now()
await time.sleep(30)
expect(Date.now() - before).to.be.least(30)
expect(performance.now() - before).to.be.least(30)
})
})
Loading