From 56712206d175fe3414c0eb01fba6782bf6e214fb Mon Sep 17 00:00:00 2001 From: hanez Date: Thu, 27 Nov 2025 00:42:47 +0100 Subject: [PATCH] Added support for libSQL as an compatible alternative for SQLite. (0.33.0) --- .gitignore | 2 +- CMakeLists.txt | 44 ++++++++++++++++- README.md | 1 + docs/handbook.md | 24 +++++++++- examples/data/{todo.sql => database.sql} | 0 examples/libsql_example.fun | 59 +++++++++++++++++++++++ examples/sqlite_example.fun | 15 +++++- src/bytecode.c | 4 ++ src/bytecode.h | 6 +++ src/parser.c | 37 +++++++++++++++ src/vm.c | 10 ++++ src/vm.h | 1 + src/vm/libsql/close.c | 32 +++++++++++++ src/vm/libsql/common.c | 55 ++++++++++++++++++++++ src/vm/libsql/exec.c | 36 ++++++++++++++ src/vm/libsql/open.c | 37 +++++++++++++++ src/vm/libsql/query.c | 60 ++++++++++++++++++++++++ 17 files changed, 418 insertions(+), 5 deletions(-) rename examples/data/{todo.sql => database.sql} (100%) create mode 100644 examples/libsql_example.fun create mode 100644 src/vm/libsql/close.c create mode 100644 src/vm/libsql/common.c create mode 100644 src/vm/libsql/exec.c create mode 100644 src/vm/libsql/open.c create mode 100644 src/vm/libsql/query.c diff --git a/.gitignore b/.gitignore index 6e70192..5a53300 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ CMakeCache.txt .venv build* cmake* +database.sqlite demo_* dist/ downloaded.png @@ -13,4 +14,3 @@ out/ src/*.o *.swp tmp* -todo.sqlite diff --git a/CMakeLists.txt b/CMakeLists.txt index c8c1698..eb060e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.32.0 LANGUAGES C) +project(fun VERSION 0.33.0 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) @@ -90,6 +90,40 @@ if(FUN_WITH_JSON) endif() endif() +# Optional libsql support (independent from SQLite) +option(FUN_WITH_LIBSQL "Enable libsql (Turso) client support" OFF) +set(LIBSQL_INCLUDE_DIRS "") +set(LIBSQL_LINK_LIBS "") +if(FUN_WITH_LIBSQL) + message(STATUS "Building with libsql support") + add_definitions(-DFUN_WITH_LIBSQL) + # Try pkg-config for libsql first; some systems expose libsql-client + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(LIBSQL QUIET libsql) + if(NOT LIBSQL_FOUND) + pkg_check_modules(LIBSQL_CLIENT QUIET libsql-client) + if(LIBSQL_CLIENT_FOUND) + set(LIBSQL_FOUND TRUE) + set(LIBSQL_INCLUDE_DIRS ${LIBSQL_CLIENT_INCLUDE_DIRS}) + set(LIBSQL_LINK_LIBS ${LIBSQL_CLIENT_LIBRARIES}) + endif() + endif() + endif() + if(LIBSQL_FOUND) + include_directories(${LIBSQL_INCLUDE_DIRS}) + else() + # Fallback: many libsql deployments provide a sqlite3-compatible client lib + # so try linking against sqlite3 as a compatibility layer. + find_library(LIBSQL_LIB sqlite3) + if(LIBSQL_LIB) + list(APPEND LIBSQL_LINK_LIBS ${LIBSQL_LIB}) + else() + message(FATAL_ERROR "libsql not found. Install libsql (or compatible sqlite3 client) or disable FUN_WITH_LIBSQL.") + endif() + endif() +endif() + # Optional PCRE2 support option(FUN_WITH_PCRE2 "Enable PCRE2 (Perl Compatible Regular Expressions) support" OFF) set(PCRE2_INCLUDE_DIRS "") @@ -201,6 +235,14 @@ if(SQLITE3_LINK_LIBS) target_link_libraries(fun_core PUBLIC ${SQLITE3_LINK_LIBS}) endif() +# libsql include and link (if enabled) +if(LIBSQL_INCLUDE_DIRS) + target_include_directories(fun_core PRIVATE ${LIBSQL_INCLUDE_DIRS}) +endif() +if(LIBSQL_LINK_LIBS) + target_link_libraries(fun_core PUBLIC ${LIBSQL_LINK_LIBS}) +endif() + # Link threads if available on UNIX if(Threads_FOUND) target_link_libraries(fun_core PUBLIC Threads::Threads) diff --git a/README.md b/README.md index 61b74f4..bec9a75 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Fun is and will ever be 100% free under the terms of the [Apache-2.0 License](ht - [cURL](https://curl.se/) support builtin using [libcurl](https://curl.se/libcurl/) (optional) ☑ - [JSON](https://www.json.org/) support builtin using [json-c](https://github.com/json-c/json-c) (optional) ☑ +- [libSQL](https://github.com/tursodatabase/libsql) support builtin (optional) ☑ - [PCSC](https://pcscworkgroup.com/) smart card support builtin using [PCSC lite](https://pcsclite.apdu.fr/) (optional) ☑ - [PCRE2](https://pcre2project.github.io/pcre2/) support builtin for Perl-Compatible Regular Expressions (optional) ☑ - [SQLite](https://sqlite.org/) support builtin (optional) ☑ diff --git a/docs/handbook.md b/docs/handbook.md index 5b55c28..2f8ca49 100644 --- a/docs/handbook.md +++ b/docs/handbook.md @@ -90,6 +90,7 @@ Pass all options as -DNAME=VALUE. The most relevant toggles are: - FUN_DEBUG=ON|OFF — verbose VM debug logging (default OFF) - FUN_WITH_CURL=ON|OFF — enable CURL support via libcurl (default OFF) - FUN_WITH_JSON=ON|OFF — enable JSON support via json-c (default OFF) +- FUN_WITH_LIBSQL=ON|OFF — enable libSQL (Turso) client support (default OFF) - FUN_WITH_PCRE2=ON|OFF — enable PCRE2 (Perl-Compatible Regular Expressions) (default OFF) - FUN_WITH_PCSC=ON|OFF — enable PC/SC smart card support (default OFF) - FUN_WITH_REPL=ON|OFF — enable the interactive REPL (default OFF) @@ -117,12 +118,33 @@ cmake -S . -B build -DFUN_WITH_SQLITE=ON cmake --build build --target fun # Create the sample database (requires the sqlite3 CLI): -sqlite3 ./todo.sqlite < ./examples/data/todo.sql +sqlite3 ./database.sqlite < ./examples/data/database.sql # Run the example FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlite_example.fun ``` +#### libSQL example (optional feature) + +libSQL support is optional and disabled by default. It is implemented as an independent extension and can coexist with SQLite. To build with it and run the example: + +``` +cmake -S . -B build -DFUN_WITH_LIBSQL=ON +cmake --build build --target fun + +# Create the sample database using the sqlite3 CLI (libSQL implements the sqlite C API) +sqlite3 ./database.sqlite < ./examples/data/database.sql + +# Run the libSQL example +FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/libsql_example.fun +``` + +Available builtins when built with -DFUN_WITH_LIBSQL=ON: +- libsql_open(path_or_url) -> handle (>0) or 0 on error +- libsql_close(handle) -> Nil +- libsql_exec(handle, sql) -> rc (0 on success) +- libsql_query(handle, sql) -> array of map rows + ### Install Fun to the OS (optional) Not recommended during early development, but supported: diff --git a/examples/data/todo.sql b/examples/data/database.sql similarity index 100% rename from examples/data/todo.sql rename to examples/data/database.sql diff --git a/examples/libsql_example.fun b/examples/libsql_example.fun new file mode 100644 index 0000000..1bfc430 --- /dev/null +++ b/examples/libsql_example.fun @@ -0,0 +1,59 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-26 + */ + +// Demonstrates the optional libSQL extension +// Build with: cmake -S . -B build -DFUN_WITH_LIBSQL=ON && cmake --build build + +// Prepare sample DB from SQL if needed (requires sqlite3 CLI installed) +// Create it once with: +// sqlite3 ./database.sqlite < ./examples/data/database.sql + +number h = libsql_open("./database.sqlite") +if h == 0 + print("Failed to open libSQL database") +else + libsql_exec(h, "CREATE TABLE IF NOT EXISTS todos(id INTEGER PRIMARY KEY, title TEXT, done INT)") + libsql_exec(h, "DELETE FROM todos") + libsql_exec(h, "INSERT INTO todos(title, done) VALUES('Buy milk', 0)") + libsql_exec(h, "INSERT INTO todos(title, done) VALUES('Write code', 1)") + + rows = libsql_query(h, "SELECT id, title, done FROM todos ORDER BY id") + for row in rows + print(to_string(row["id"]) + ": " + to_string(row["title"]) + " (done="+to_string(row["done"]) + ")") + + rows = libsql_query(h, "SELECT id, title, done, created_at FROM tasks ORDER BY id;") + print("Tasks (" + to_string(len(rows)) + "):") + for row in rows + string status = "✘" + if row["done"] == 1 + status = "✔" + print("- [" + status + "] (#" + to_string(row["id"]) + ") " + to_string(row["title"]) + " — " + to_string(row["created_at"])) + + number rc = libsql_exec(h, "INSERT INTO tasks (title, done) VALUES ('Try Fun + SQLite', 0);") + print("Insert rc=" + to_string(rc)) + + rows2 = libsql_query(h, "SELECT count(*) AS cnt FROM tasks;") + print("Total tasks now: " + to_string(rows2[0]["cnt"])) + + libsql_close(h) + +/* Example output: +1: Buy milk (done=0) +2: Write code (done=1) +Tasks (3): +- [✔] (#1) Write Fun + SQLite example — 2025-11-26 23:20:41 +- [✘] (#2) Ship optional feature flag — 2025-11-26 23:20:41 +- [✘] (#3) Celebrate with coffee — 2025-11-26 23:20:41 +Insert rc=0 +Total tasks now: 4 +*/ diff --git a/examples/sqlite_example.fun b/examples/sqlite_example.fun index 0662685..3eb2803 100644 --- a/examples/sqlite_example.fun +++ b/examples/sqlite_example.fun @@ -1,3 +1,5 @@ +#!/usr/bin/env fun + /* * This file is part of the Fun programming language. * https://fun-lang.xyz/ @@ -11,9 +13,9 @@ // Prepare sample DB from SQL if needed (requires sqlite3 CLI installed) // Create it once with: -// sqlite3 todo.sqlite < ./examples/data/todo.sql +// sqlite3 ./database.sqlite < ./examples/data/database.sql -string db_path = "./todo.sqlite" +string db_path = "./database.sqlite" number h = sqlite_open(db_path) if h == 0 @@ -35,3 +37,12 @@ rows2 = sqlite_query(h, "SELECT count(*) AS cnt FROM tasks;") print("Total tasks now: " + to_string(rows2[0]["cnt"])) sqlite_close(h) + +/* Example output: +Tasks (3): +- [✔] (#1) Write Fun + SQLite example — 2025-11-26 23:22:04 +- [✘] (#2) Ship optional feature flag — 2025-11-26 23:22:04 +- [✘] (#3) Celebrate with coffee — 2025-11-26 23:22:04 +Insert rc=0 +Total tasks now: 4 +*/ diff --git a/src/bytecode.c b/src/bytecode.c index 2ca8a5a..fdf63b1 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -152,6 +152,10 @@ static const char *opcode_name(OpCode op) { case OP_SQLITE_CLOSE: return "SQLITE_CLOSE"; case OP_SQLITE_EXEC: return "SQLITE_EXEC"; case OP_SQLITE_QUERY: return "SQLITE_QUERY"; + case OP_LIBSQL_OPEN: return "LIBSQL_OPEN"; + case OP_LIBSQL_CLOSE: return "LIBSQL_CLOSE"; + case OP_LIBSQL_EXEC: return "LIBSQL_EXEC"; + case OP_LIBSQL_QUERY: return "LIBSQL_QUERY"; case OP_PCSC_ESTABLISH: return "PCSC_ESTABLISH"; case OP_PCSC_RELEASE: return "PCSC_RELEASE"; case OP_PCSC_LIST_READERS: return "PCSC_LIST_READERS"; diff --git a/src/bytecode.h b/src/bytecode.h index 08125e3..908abb3 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -158,6 +158,12 @@ typedef enum { OP_SQLITE_EXEC, // pops sql, handle; pushes sqlite rc (0=OK) OP_SQLITE_QUERY, // pops sql, handle; pushes array + // libsql (optional, independent) + OP_LIBSQL_OPEN, // pops url/path; pushes handle (>0) or 0 + OP_LIBSQL_CLOSE, // pops handle; pushes Nil + OP_LIBSQL_EXEC, // pops sql, handle; pushes rc (0=OK) + OP_LIBSQL_QUERY, // pops sql, handle; pushes array + // PCSC (smart card) opcodes OP_PCSC_ESTABLISH, // returns context id (>0) or 0 OP_PCSC_RELEASE, // pops ctx id; returns 1/0 diff --git a/src/parser.c b/src/parser.c index 3e5c033..ab47be0 100644 --- a/src/parser.c +++ b/src/parser.c @@ -824,6 +824,43 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + /* libsql builtins (independent extension) */ + if (strcmp(name, "libsql_open") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_open expects (url_or_path)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libsql_open arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_LIBSQL_OPEN, 0); + free(name); + return 1; + } + if (strcmp(name, "libsql_close") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_close expects (handle)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libsql_close arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_LIBSQL_CLOSE, 0); + free(name); + return 1; + } + if (strcmp(name, "libsql_exec") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_exec expects (handle, sql)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "libsql_exec expects (handle, sql)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_exec expects (handle, sql)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libsql_exec args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_LIBSQL_EXEC, 0); + free(name); + return 1; + } + if (strcmp(name, "libsql_query") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_query expects (handle, sql)"); free(name); return 0; } + if (!consume_char(src, len, pos, ',')) { parser_fail(*pos, "libsql_query expects (handle, sql)"); free(name); return 0; } + if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "libsql_query expects (handle, sql)"); free(name); return 0; } + if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after libsql_query args"); free(name); return 0; } + bytecode_add_instruction(bc, OP_LIBSQL_QUERY, 0); + free(name); + return 1; + } if (strcmp(name, "curl_post") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "curl_post expects (url, body)"); free(name); return 0; } diff --git a/src/vm.c b/src/vm.c index 6a5bbde..e8a8635 100644 --- a/src/vm.c +++ b/src/vm.c @@ -17,6 +17,10 @@ #include #include "vm/sqlite/common.c" #endif +#ifdef FUN_WITH_LIBSQL +#include /* libsql exposes sqlite3-compatible C API */ +#include "vm/libsql/common.c" +#endif #include "vm.h" #include "value.h" #include @@ -713,6 +717,12 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/sqlite/exec.c" #include "vm/sqlite/query.c" + /* libsql ops (independent) */ + #include "vm/libsql/open.c" + #include "vm/libsql/close.c" + #include "vm/libsql/exec.c" + #include "vm/libsql/query.c" + /* PCRE2 ops */ #include "vm/pcre2/test.c" #include "vm/pcre2/match.c" diff --git a/src/vm.h b/src/vm.h index f72ff63..2591da4 100644 --- a/src/vm.h +++ b/src/vm.h @@ -41,6 +41,7 @@ static const char *opcode_names[] = { "JSON_PARSE","JSON_STRINGIFY","JSON_FROM_FILE","JSON_TO_FILE", "CURL_GET","CURL_POST","CURL_DOWNLOAD", "SQLITE_OPEN","SQLITE_CLOSE","SQLITE_EXEC","SQLITE_QUERY", + "LIBSQL_OPEN","LIBSQL_CLOSE","LIBSQL_EXEC","LIBSQL_QUERY", "PCSC_ESTABLISH","PCSC_RELEASE","PCSC_LIST_READERS","PCSC_CONNECT","PCSC_DISCONNECT","PCSC_TRANSMIT", "PCRE2_TEST","PCRE2_MATCH","PCRE2_FINDALL", "SOCK_TCP_LISTEN","SOCK_TCP_ACCEPT","SOCK_TCP_CONNECT","SOCK_SEND","SOCK_RECV","SOCK_CLOSE","SOCK_UNIX_LISTEN","SOCK_UNIX_CONNECT", diff --git a/src/vm/libsql/close.c b/src/vm/libsql/close.c new file mode 100644 index 0000000..0a4e566 --- /dev/null +++ b/src/vm/libsql/close.c @@ -0,0 +1,32 @@ +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-26 + */ + +/** + * OP_LIBSQL_CLOSE: (handle:int) -> Nil + */ +case OP_LIBSQL_CLOSE: { +#ifdef FUN_WITH_LIBSQL + Value vh = pop_value(vm); + int hid = (int)vh.i; + free_value(vh); + LibSqlHandle *h = libsql_reg_get(hid); + if (h && h->db) { + sqlite3_close(h->db); + h->db = NULL; + libsql_reg_del(hid); + } + push_value(vm, make_nil()); +#else + Value v = pop_value(vm); free_value(v); + push_value(vm, make_nil()); +#endif + break; +} diff --git a/src/vm/libsql/common.c b/src/vm/libsql/common.c new file mode 100644 index 0000000..e69f5bb --- /dev/null +++ b/src/vm/libsql/common.c @@ -0,0 +1,55 @@ +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-26 + */ + +#ifdef FUN_WITH_LIBSQL +#include +#include +#include +#include /* libsql provides a sqlite3-compatible C API */ + +typedef struct LibSqlHandle { + int id; + sqlite3 *db; + struct LibSqlHandle *next; +} LibSqlHandle; + +static LibSqlHandle *g_libsql_handles = NULL; +static int g_libsql_next_id = 1; + +static LibSqlHandle *libsql_reg_add(sqlite3 *db) { + LibSqlHandle *h = (LibSqlHandle*)malloc(sizeof(LibSqlHandle)); + if (!h) return NULL; + h->id = g_libsql_next_id++; + h->db = db; + h->next = g_libsql_handles; + g_libsql_handles = h; + return h; +} + +static LibSqlHandle *libsql_reg_get(int id) { + LibSqlHandle *p = g_libsql_handles; + while (p) { if (p->id == id) return p; p = p->next; } + return NULL; +} + +static void libsql_reg_del(int id) { + LibSqlHandle **pp = &g_libsql_handles; + while (*pp) { + if ((*pp)->id == id) { + LibSqlHandle *dead = *pp; + *pp = (*pp)->next; + free(dead); + return; + } + pp = &((*pp)->next); + } +} +#endif /* FUN_WITH_LIBSQL */ diff --git a/src/vm/libsql/exec.c b/src/vm/libsql/exec.c new file mode 100644 index 0000000..0326ae9 --- /dev/null +++ b/src/vm/libsql/exec.c @@ -0,0 +1,36 @@ +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-26 + */ + +/** + * OP_LIBSQL_EXEC: (handle:int, sql:string) -> int rc (0=OK) + */ +case OP_LIBSQL_EXEC: { +#ifdef FUN_WITH_LIBSQL + Value vsql = pop_value(vm); + Value vh = pop_value(vm); + int hid = (int)vh.i; + char *sql = value_to_string_alloc(&vsql); + free_value(vh); + free_value(vsql); + LibSqlHandle *h = libsql_reg_get(hid); + if (!h || !h->db || !sql) { if (sql) free(sql); push_value(vm, make_int(SQLITE_MISUSE)); break; } + char *errmsg = NULL; + int rc = sqlite3_exec(h->db, sql, NULL, NULL, &errmsg); + if (errmsg) sqlite3_free(errmsg); + free(sql); + push_value(vm, make_int(rc)); +#else + Value v1 = pop_value(vm); free_value(v1); + Value v2 = pop_value(vm); free_value(v2); + push_value(vm, make_int(-1)); +#endif + break; +} diff --git a/src/vm/libsql/open.c b/src/vm/libsql/open.c new file mode 100644 index 0000000..7b3f177 --- /dev/null +++ b/src/vm/libsql/open.c @@ -0,0 +1,37 @@ +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-26 + */ + +/** + * OP_LIBSQL_OPEN: (url_or_path:string) -> handle:int (>0) or 0 on error + */ +case OP_LIBSQL_OPEN: { +#ifdef FUN_WITH_LIBSQL + Value vpath = pop_value(vm); + char *path = value_to_string_alloc(&vpath); + free_value(vpath); + if (!path) { push_value(vm, make_int(0)); break; } + sqlite3 *db = NULL; + int rc = sqlite3_open(path, &db); + free(path); + if (rc != SQLITE_OK || !db) { + if (db) sqlite3_close(db); + push_value(vm, make_int(0)); + break; + } + LibSqlHandle *h = libsql_reg_add(db); + if (!h) { sqlite3_close(db); push_value(vm, make_int(0)); break; } + push_value(vm, make_int(h->id)); +#else + Value v = pop_value(vm); free_value(v); + push_value(vm, make_int(0)); +#endif + break; +} diff --git a/src/vm/libsql/query.c b/src/vm/libsql/query.c new file mode 100644 index 0000000..559c6f4 --- /dev/null +++ b/src/vm/libsql/query.c @@ -0,0 +1,60 @@ +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-11-26 + */ + +/** + * OP_LIBSQL_QUERY: (handle:int, sql:string) -> array> + */ +case OP_LIBSQL_QUERY: { +#ifdef FUN_WITH_LIBSQL + Value vsql = pop_value(vm); + Value vh = pop_value(vm); + int hid = (int)vh.i; + char *sql = value_to_string_alloc(&vsql); + free_value(vh); + free_value(vsql); + LibSqlHandle *h = libsql_reg_get(hid); + if (!h || !h->db || !sql) { if (sql) free(sql); push_value(vm, make_array_from_values(NULL, 0)); break; } + sqlite3_stmt *stmt = NULL; + if (sqlite3_prepare_v2(h->db, sql, -1, &stmt, NULL) != SQLITE_OK) { + free(sql); + push_value(vm, make_array_from_values(NULL, 0)); + break; + } + free(sql); + Value rows = make_array_from_values(NULL, 0); + int ncols = sqlite3_column_count(stmt); + while (sqlite3_step(stmt) == SQLITE_ROW) { + Value row = make_map_empty(); + for (int i = 0; i < ncols; i++) { + const char *name = sqlite3_column_name(stmt, i); + int type = sqlite3_column_type(stmt, i); + Value kv; + switch (type) { + case SQLITE_INTEGER: kv = make_int((int64_t)sqlite3_column_int64(stmt, i)); break; + case SQLITE_FLOAT: kv = make_float(sqlite3_column_double(stmt, i)); break; + case SQLITE_TEXT: kv = make_string((const char*)sqlite3_column_text(stmt, i)); break; + case SQLITE_NULL: kv = make_nil(); break; + default: kv = make_nil(); break; /* ignore blobs for now */ + } + (void)map_set(&row, name ? name : "", kv); + } + (void)array_push(&rows, row); + /* Do NOT free 'row' here; owned by rows array. */ + } + sqlite3_finalize(stmt); + push_value(vm, rows); +#else + Value v1 = pop_value(vm); free_value(v1); + Value v2 = pop_value(vm); free_value(v2); + push_value(vm, make_array_from_values(NULL, 0)); +#endif + break; +}