feat: membrane remaining wasm host surfaces
This commit is contained in:
+309
-17
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user