feat: membrane remaining wasm host surfaces

This commit is contained in:
root
2026-06-13 20:10:00 +00:00
parent c84fc86e6c
commit fe83c52411
5 changed files with 758 additions and 36 deletions
+309 -17
View File
@@ -10,32 +10,323 @@
#include "../lib/mysql-connector.h"
#include "../lib/sqlite-connector.h"
// ---- W3 connector membrane stubs -------------------------------------------
// Generated units reference the connector class surface through uce_lib.h, so
// the core must define it. Until the real hostcall connectors land (W5 parity
// scope: the starter uses no database), every operation fails cleanly with an
// explanatory error instead of trapping.
// ---- W3 connector membranes -----------------------------------------------
// sqlite/mysql run host-side (the host links the native connectors and owns the
// connections in per-workspace handle tables). UCEB1-marshalled hostcalls carry
// operation requests/responses; `connection` holds the host handle (>0).
static const char* WASM_DB_UNAVAILABLE =
"database connectors are not yet available in the wasm workspace";
"database connector is not available in the wasm workspace";
extern "C" size_t uce_host_mysql(const char* in, size_t in_len, char* out, size_t cap);
extern "C" size_t uce_host_zip(const char* in, size_t in_len, char* out, size_t cap);
extern "C" size_t uce_host_units(const char* in, size_t in_len, char* out, size_t cap);
static DValue wasm_sized_hostcall(DValue request, size_t (*hostcall)(const char*, size_t, char*, size_t))
{
String encoded = ucb_encode(request);
size_t need = hostcall(encoded.data(), encoded.size(), 0, 0);
if(need == 0)
return(DValue());
String buffer(need, 0);
size_t got = hostcall(encoded.data(), encoded.size(), &buffer[0], need);
if(got == 0 || got > need)
return(DValue());
DValue response;
String error;
ucb_decode(String(buffer.data(), got), response, &error);
return(response);
}
static DValue wasm_zip_call(DValue request)
{
DValue response = wasm_sized_hostcall(request, uce_host_zip);
return(response);
}
DValue zip_list(String zip_file_name)
{
DValue request;
request["op"] = "list";
request["path"] = zip_file_name;
DValue response = wasm_zip_call(request);
DValue* result = response.key("result");
return(result ? *result : DValue());
}
String zip_read(String zip_file_name, String entry_name)
{
DValue request;
request["op"] = "read";
request["path"] = zip_file_name;
request["entry"] = entry_name;
DValue response = wasm_zip_call(request);
return(response["result"].to_string());
}
bool zip_create(String zip_file_name, DValue entries)
{
DValue request;
request["op"] = "create";
request["path"] = zip_file_name;
request["entries"] = entries;
DValue response = wasm_zip_call(request);
return(response["ok"].to_bool());
}
bool zip_extract(String zip_file_name, String destination_directory)
{
DValue request;
request["op"] = "extract";
request["path"] = zip_file_name;
request["destination"] = destination_directory;
DValue response = wasm_zip_call(request);
return(response["ok"].to_bool());
}
String gz_compress(String src)
{
DValue request;
request["op"] = "gz_compress";
request["src"] = src;
DValue response = wasm_zip_call(request);
return(response["result"].to_string());
}
String gz_uncompress(String compressed)
{
DValue request;
request["op"] = "gz_uncompress";
request["src"] = compressed;
DValue response = wasm_zip_call(request);
return(response["result"].to_string());
}
static DValue wasm_units_call(DValue request)
{
return(wasm_sized_hostcall(request, uce_host_units));
}
DValue unit_info(String path)
{
DValue request;
request["op"] = "info";
request["path"] = path;
DValue response = wasm_units_call(request);
DValue* result = response.key("result");
return(result ? *result : DValue());
}
StringList units_list()
{
DValue request;
request["op"] = "list";
DValue response = wasm_units_call(request);
StringList result;
DValue* items = response.key("result");
if(items)
items->each([&](const DValue& value, String) { result.push_back(value.to_string()); });
return(result);
}
bool unit_compile(String path)
{
DValue request;
request["op"] = "compile";
request["path"] = path;
DValue response = wasm_units_call(request);
return(response["ok"].to_bool());
}
static DValue wasm_unit_call_result;
DValue* unit_call(String file_name, String function_name, DValue* call_param)
{
DValue request;
request["op"] = "call";
request["file"] = file_name;
request["function"] = function_name;
if(call_param)
request["param"] = *call_param;
DValue response = wasm_units_call(request);
String output = response["output"].to_string();
if(output != "")
print(output);
DValue* result = response.key("result");
if(result)
{
wasm_unit_call_result = *result;
return(&wasm_unit_call_result);
}
return(0);
}
SharedUnit* compiler_load_shared_unit(Request* context, String file_name, String current_path, bool opt_so_optional)
{
(void)context; (void)file_name; (void)current_path; (void)opt_so_optional;
return(0);
}
static DValue wasm_mysql_call(DValue request)
{
return(wasm_sized_hostcall(request, uce_host_mysql));
}
bool MySQL::connect(String host, String username, String password)
{
(void)host; (void)username; (void)password;
connection = 0;
statement_info = WASM_DB_UNAVAILABLE;
DValue request;
request["op"] = "connect";
request["host"] = host;
request["username"] = username;
request["password"] = password;
DValue response = wasm_mysql_call(request);
u64 handle = response["handle"].to_u64();
connection = (void*)(uintptr_t)handle;
_preload_next_error_code = (u32)response["error_code"].to_u64();
statement_info = response["statement_info"].to_string();
return(handle != 0 && _preload_next_error_code == 0);
}
void MySQL::disconnect()
{
if(connection)
{
DValue request;
request["op"] = "disconnect";
request["handle"] = (f64)(uintptr_t)connection;
wasm_mysql_call(request);
connection = 0;
}
}
String MySQL::error()
{
String result = statement_info;
statement_info = "";
_preload_next_error_code = 0;
return(result);
}
String MySQL::escape(String raw, char quote_char) { return(mysql_escape(raw, quote_char)); }
String mysql_escape(String raw, char quote_char)
{
DValue request;
request["op"] = "escape";
request["raw"] = raw;
request["quote_char"] = String(quote_char > 0 ? 1 : 0, quote_char);
DValue response = wasm_mysql_call(request);
DValue* result = response.key("result");
return(result ? result->to_string() : raw);
}
String MySQL::parse_query_parameters(String query, StringMap map)
{
String result;
query.append(1, ' ');
u8 mode = 0;
char quote = 0;
String identifier;
for(u32 i = 0; i < query.length(); i++)
{
char c = query[i];
if(mode == 0)
{
if(c == ':')
{
mode = 1;
identifier = "";
}
else if(c == '"' || c == '\'')
{
result.append(1, c);
mode = 2;
quote = c;
}
else
result.append(1, c);
}
else if(mode == 1)
{
if(isalnum(c))
identifier.append(1, c);
else
{
result.append(escape(map[identifier]));
result.append(1, c);
mode = 0;
}
}
else if(mode == 2)
{
if(c == quote)
mode = 0;
result.append(1, c);
}
}
return(result);
}
static bool wasm_mysql_has_unquoted_positional_placeholder(String query)
{
bool quoted = false;
char quote = 0;
bool escaped = false;
for(u32 i = 0; i < query.length(); i++)
{
char c = query[i];
if(quoted)
{
if(escaped)
{
escaped = false;
continue;
}
if(c == '\\')
{
escaped = true;
continue;
}
if(c == quote)
quoted = false;
continue;
}
if(c == '\'' || c == '"')
{
quoted = true;
quote = c;
continue;
}
if(c == '?')
return(true);
}
return(false);
}
void MySQL::disconnect() { connection = 0; }
String MySQL::error() { return(WASM_DB_UNAVAILABLE); }
String MySQL::escape(String raw, char quote_char) { (void)quote_char; return(raw); }
String MySQL::parse_query_parameters(String query, StringMap m) { (void)m; return(query); }
DValue MySQL::query(String q) { (void)q; statement_info = WASM_DB_UNAVAILABLE; return(DValue()); }
DValue MySQL::query(String q, StringMap params) { (void)q; (void)params; statement_info = WASM_DB_UNAVAILABLE; return(DValue()); }
DValue MySQL::get_pending_result() { return(DValue()); }
DValue MySQL::query(String q)
{
if(wasm_mysql_has_unquoted_positional_placeholder(q))
{
_preload_next_error_code = 2000;
statement_info = "mysql positional ? placeholders are not supported; use named :name placeholders";
return(DValue());
}
DValue request;
request["op"] = "query";
request["handle"] = (f64)(uintptr_t)connection;
request["query"] = q;
DValue response = wasm_mysql_call(request);
insert_id = response["insert_id"].to_u64();
affected_rows = (u32)response["affected"].to_u64();
_preload_next_error_code = (u32)response["error_code"].to_u64();
statement_info = response["statement_info"].to_string();
DValue* result = response.key("result");
return(result ? *result : DValue());
}
String mysql_escape(String raw, char quote_char) { (void)quote_char; return(raw); }
DValue MySQL::query(String q, StringMap params) { return(query(parse_query_parameters(q, params))); }
DValue MySQL::get_pending_result() { return(DValue()); }
// sqlite runs host-side (the host links libsqlite and owns the connections in
// a per-workspace handle table). One UCEB1-marshalled hostcall carries
@@ -129,6 +420,7 @@ DValue sqlite_query(SQLite* db, String q, const StringMap& params) { return(db ?
u64 sqlite_insert_id(SQLite* db) { return(db ? db->insert_id : 0); }
u32 sqlite_affected_rows(SQLite* db) { return(db ? db->affected_rows : 0); }
void cleanup_sqlite_connections() { }
void cleanup_mysql_connections() { }
static ServerState wasm_server;
static Request wasm_request;