Skip to content

Commit 856e180

Browse files
committed
draft feat: wasm network layer
1 parent a87b266 commit 856e180

File tree

4 files changed

+207
-10
lines changed

4 files changed

+207
-10
lines changed

.github/workflows/main.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,6 @@ jobs:
234234
uses: actions/[email protected]
235235

236236
- name: release tag version from cloudsync.h
237-
if: false
238237
id: tag
239238
run: |
240239
VERSION=$(make version)
@@ -252,7 +251,7 @@ jobs:
252251
253252
- name: publish sqlite-wasm to npm
254253
#if: steps.tag.outputs.version != ''
255-
run: cd artifacts/cloudsync-wasm/sqlite-wasm && ls -lah #npm publish ./artifacts/your-package-1.0.0.tgz
254+
run: #npm publish ./artifacts/cloudsync-wasm/sqlite-wasm/sqliteai-cloudsync-wasm-*+cloudsync-*.tgz
256255
env:
257256
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
258257

@@ -270,7 +269,8 @@ jobs:
270269
done
271270
272271
- uses: softprops/[email protected]
273-
if: steps.tag.outputs.version != ''
272+
if: false
273+
#if: steps.tag.outputs.version != ''
274274
with:
275275
generate_release_notes: true
276276
tag_name: ${{ steps.tag.outputs.version }}

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ $(SQLITE_SRC): $(EMSDK)
166166
cd $(EMSDK) && . ./emsdk_env.sh && cd ../sqlite && ./configure --enable-all
167167

168168
$(TARGET): $(SQLITE_SRC) $(SRC_FILES)
169-
cd $(SQLITE_SRC)/ext/wasm && $(MAKE) dist sqlite3_wasm_extra_init.c=../../../../../src/wasm.c
169+
cd $(SQLITE_SRC)/ext/wasm && $(MAKE) dist sqlite3_wasm_extra_init.c=../../../../../src/wasm.c emcc.jsflags+="-sFETCH -pthread"
170170
mv $(SQLITE_SRC)/ext/wasm/sqlite-wasm-*.zip $(TARGET)
171171
endif
172172

src/network.c

Lines changed: 203 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,15 @@
1414
#include "cloudsync_private.h"
1515
#include "netword_private.h"
1616

17+
#ifndef SQLITE_WASM_EXTRA_INIT
1718
#ifndef CLOUDSYNC_OMIT_CURL
1819
#include "curl/curl.h"
1920
#endif
21+
#else
22+
#include <stdlib.h>
23+
#include <emscripten/fetch.h>
24+
#include <emscripten/emscripten.h>
25+
#endif
2026

2127
#ifdef __ANDROID__
2228
#include "cacert.h"
@@ -127,6 +133,98 @@ static size_t network_receive_callback (void *ptr, size_t size, size_t nmemb, vo
127133
return (size * nmemb);
128134
}
129135

136+
#ifdef SQLITE_WASM_EXTRA_INIT
137+
NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint, const char *authentication, bool zero_terminated, bool is_post_request, char *json_payload, const char *custom_header) {
138+
139+
emscripten_fetch_attr_t attr;
140+
emscripten_fetch_attr_init(&attr);
141+
142+
// Set method
143+
if (json_payload || is_post_request) {
144+
strcpy(attr.requestMethod, "POST");
145+
} else {
146+
strcpy(attr.requestMethod, "GET");
147+
}
148+
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_SYNCHRONOUS;
149+
150+
// Prepare header array (alternating key, value, NULL-terminated)
151+
const char *headers[11];
152+
int h = 0;
153+
154+
// Custom header (must be "Key: Value", split at ':')
155+
char *custom_key = NULL;
156+
if (custom_header) {
157+
const char *colon = strchr(custom_header, ':');
158+
if (colon) {
159+
size_t klen = colon - custom_header;
160+
custom_key = (char *)malloc(klen + 1);
161+
strncpy(custom_key, custom_header, klen);
162+
custom_key[klen] = 0;
163+
const char *custom_val = colon + 1;
164+
while (*custom_val == ' ') custom_val++;
165+
headers[h++] = custom_key;
166+
headers[h++] = custom_val;
167+
}
168+
}
169+
170+
// Authorization
171+
char auth_header[256];
172+
if (authentication) {
173+
snprintf(auth_header, sizeof(auth_header), "Bearer %s", authentication);
174+
headers[h++] = "Authorization";
175+
headers[h++] = auth_header;
176+
}
177+
178+
// Content-Type for JSON
179+
if (json_payload) {
180+
headers[h++] = "Content-Type";
181+
headers[h++] = "application/json";
182+
}
183+
184+
// Accept (always)
185+
headers[h++] = "Accept";
186+
headers[h++] = "application/octet-stream";
187+
headers[h] = 0;
188+
attr.requestHeaders = headers;
189+
190+
// Body
191+
if (json_payload) {
192+
attr.requestData = json_payload;
193+
attr.requestDataSize = strlen(json_payload);
194+
} else if (is_post_request) {
195+
attr.requestData = "";
196+
attr.requestDataSize = 0;
197+
}
198+
199+
emscripten_fetch_t *fetch = emscripten_fetch(&attr, endpoint); // Blocks here until the operation is complete.
200+
NETWORK_RESULT result = {0, NULL, 0, NULL, NULL};
201+
if (fetch->status == 200) {
202+
result.code = (fetch->numBytes > 0) ? CLOUDSYNC_NETWORK_BUFFER : CLOUDSYNC_NETWORK_OK;
203+
result.buffer = (char *)malloc(fetch->numBytes + 1);
204+
if (result.buffer && fetch->numBytes > 0) {
205+
memcpy(result.buffer, fetch->data, fetch->numBytes);
206+
result.buffer[fetch->numBytes] = 0;
207+
result.blen = fetch->numBytes;
208+
} else if (result.buffer) {
209+
result.buffer[0] = 0;
210+
result.blen = 0;
211+
}
212+
} else {
213+
result.code = CLOUDSYNC_NETWORK_ERROR;
214+
if (fetch->statusText && fetch->statusText[0])
215+
result.buffer = strdup(fetch->statusText);
216+
else
217+
result.buffer = strdup("Network error");
218+
result.blen = 0;
219+
}
220+
221+
// cleanup
222+
emscripten_fetch_close(fetch);
223+
if (custom_key) free(custom_key);
224+
225+
return result;
226+
}
227+
#else
130228
NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint, const char *authentication, bool zero_terminated, bool is_post_request, char *json_payload, const char *custom_header) {
131229
char *buffer = NULL;
132230
size_t blen = 0;
@@ -205,6 +303,7 @@ NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint,
205303

206304
return result;
207305
}
306+
#endif
208307

209308
static size_t network_read_callback(char *buffer, size_t size, size_t nitems, void *userdata) {
210309
network_read_data *rd = (network_read_data *)userdata;
@@ -220,7 +319,45 @@ static size_t network_read_callback(char *buffer, size_t size, size_t nitems, vo
220319
return to_copy;
221320
}
222321

223-
bool network_send_buffer (network_data *data, const char *endpoint, const char *authentication, const void *blob, int blob_size) {
322+
#ifdef SQLITE_WASM_EXTRA_INIT
323+
bool network_send_buffer(network_data *data, const char *endpoint, const char *authentication, const void *blob, int blob_size) {
324+
325+
bool result = false;
326+
emscripten_fetch_attr_t attr;
327+
emscripten_fetch_attr_init(&attr);
328+
strcpy(attr.requestMethod, "PUT");
329+
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_SYNCHRONOUS;
330+
331+
// Prepare headers (alternating key, value, NULL-terminated)
332+
// Max 3 headers: Accept, (optional Auth), Content-Type
333+
const char *headers[7];
334+
int h = 0;
335+
headers[h++] = "Accept";
336+
headers[h++] = "text/plain";
337+
char auth_header[256];
338+
if (authentication) {
339+
snprintf(auth_header, sizeof(auth_header), "Bearer %s", authentication);
340+
headers[h++] = "Authorization";
341+
headers[h++] = auth_header;
342+
}
343+
headers[h++] = "Content-Type";
344+
headers[h++] = "application/octet-stream";
345+
headers[h] = 0;
346+
attr.requestHeaders = headers;
347+
348+
// Set request body
349+
attr.requestData = (const char *)blob;
350+
attr.requestDataSize = blob_size;
351+
352+
emscripten_fetch_t *fetch = emscripten_fetch(&attr, endpoint); // Blocks here until the operation is complete.
353+
if (fetch->status == 200) result = true;
354+
355+
emscripten_fetch_close(fetch);
356+
357+
return result;
358+
}
359+
#else
360+
bool network_send_buffer(network_data *data, const char *endpoint, const char *authentication, const void *blob, int blob_size) {
224361
struct curl_slist *headers = NULL;
225362
curl_mime *mime = NULL;
226363
bool result = false;
@@ -292,6 +429,7 @@ bool network_send_buffer (network_data *data, const char *endpoint, const char *
292429
return result;
293430
}
294431
#endif
432+
#endif
295433

296434
int network_set_sqlite_result (sqlite3_context *context, NETWORK_RESULT *result) {
297435
int rc = 0;
@@ -360,6 +498,9 @@ int network_extract_query_param(const char *query, const char *key, char *output
360498

361499
size_t key_len = strlen(key);
362500
const char *p = query;
501+
#ifdef SQLITE_WASM_EXTRA_INIT
502+
if (*p == '?') p++;
503+
#endif
363504

364505
while (p && *p) {
365506
// Find the start of a key=value pair
@@ -394,6 +535,19 @@ int network_extract_query_param(const char *query, const char *key, char *output
394535
}
395536

396537
#ifndef CLOUDSYNC_OMIT_CURL
538+
539+
#ifdef SQLITE_WASM_EXTRA_INIT
540+
static char *substr(const char *start, const char *end) {
541+
size_t len = end - start;
542+
char *out = (char *)malloc(len + 1);
543+
if (out) {
544+
memcpy(out, start, len);
545+
out[len] = 0;
546+
}
547+
return out;
548+
}
549+
#endif
550+
397551
bool network_compute_endpoints (sqlite3_context *context, network_data *data, const char *conn_string) {
398552
// compute endpoints
399553
bool result = false;
@@ -410,12 +564,15 @@ bool network_compute_endpoints (sqlite3_context *context, network_data *data, co
410564

411565
char *conn_string_https = NULL;
412566

567+
#ifndef SQLITE_WASM_EXTRA_INIT
413568
CURLUcode rc = CURLUE_OUT_OF_MEMORY;
414569
CURLU *url = curl_url();
415570
if (!url) goto finalize;
571+
#endif
416572

417573
conn_string_https = cloudsync_string_replace_prefix(conn_string, "sqlitecloud://", "https://");
418574

575+
#ifndef SQLITE_WASM_EXTRA_INIT
419576
// set URL: https://UUID.g5.sqlite.cloud:443/chinook.sqlite?apikey=hWDanFolRT9WDK0p54lufNrIyfgLZgtMw6tb6fbPmpo
420577
rc = curl_url_set(url, CURLUPART_URL, conn_string_https, 0);
421578
if (rc != CURLE_OK) goto finalize;
@@ -440,6 +597,38 @@ bool network_compute_endpoints (sqlite3_context *context, network_data *data, co
440597
// apikey=hWDanFolRT9WDK0p54lufNrIyfgLZgtMw6tb6fbPmpo (OPTIONAL)
441598
rc = curl_url_get(url, CURLUPART_QUERY, &query, 0);
442599
if (rc != CURLE_OK && rc != CURLUE_NO_QUERY) goto finalize;
600+
#else
601+
// Parse: scheme://host[:port]/path?query
602+
const char *p = strstr(conn_string_https, "://");
603+
if (!p) goto finalize;
604+
scheme = substr(conn_string_https, p);
605+
p += 3;
606+
const char *host_start = p;
607+
const char *host_end = strpbrk(host_start, ":/?");
608+
if (!host_end) goto finalize;
609+
host = substr(host_start, host_end);
610+
p = host_end;
611+
if (*p == ':') {
612+
++p;
613+
const char *port_end = strpbrk(p, "/?");
614+
if (!port_end) goto finalize;
615+
port = substr(p, port_end);
616+
p = port_end;
617+
}
618+
if (*p == '/') {
619+
const char *path_start = p;
620+
const char *path_end = strchr(path_start, '?');
621+
if (!path_end) path_end = path_start + strlen(path_start);
622+
database = substr(path_start, path_end);
623+
p = path_end;
624+
}
625+
if (*p == '?') {
626+
query = strdup(p);
627+
}
628+
if (!scheme || !host || !database) goto finalize;
629+
if (!port) port = strdup(CLOUDSYNC_DEFAULT_ENDPOINT_PORT);
630+
#define port_or_default port
631+
#endif
443632
if (query != NULL) {
444633
char value[MAX_QUERY_VALUE_LEN];
445634
if (!authentication && network_extract_query_param(query, "apikey", value, sizeof(value)) == 0) {
@@ -463,8 +652,13 @@ bool network_compute_endpoints (sqlite3_context *context, network_data *data, co
463652
finalize:
464653
if (result == false) {
465654
// store proper result code/message
655+
#ifndef SQLITE_WASM_EXTRA_INIT
466656
if (rc != CURLE_OK) sqlite3_result_error(context, curl_url_strerror(rc), -1);
467657
sqlite3_result_error_code(context, (rc != CURLE_OK) ? SQLITE_ERROR : SQLITE_NOMEM);
658+
#else
659+
sqlite3_result_error(context, "URL parse error", -1);
660+
sqlite3_result_error_code(context, SQLITE_ERROR);
661+
#endif
468662

469663
// cleanup memory managed by the extension
470664
if (authentication) cloudsync_memory_free(authentication);
@@ -485,13 +679,17 @@ bool network_compute_endpoints (sqlite3_context *context, network_data *data, co
485679
data->upload_endpoint = upload_endpoint;
486680
}
487681

488-
// cleanup memory managed by libcurl
682+
// cleanup memory
683+
#ifndef SQLITE_WASM_EXTRA_INIT
684+
if (url) curl_url_cleanup(url);
685+
#else
686+
#define curl_free(x) free(x)
687+
#endif
489688
if (scheme) curl_free(scheme);
490689
if (host) curl_free(host);
491690
if (port) curl_free(port);
492691
if (database) curl_free(database);
493692
if (query) curl_free(query);
494-
if (url) curl_url_cleanup(url);
495693
if (conn_string_https && conn_string_https != conn_string) cloudsync_memory_free(conn_string_https);
496694

497695
return result;
@@ -518,7 +716,7 @@ network_data *cloudsync_network_data(sqlite3_context *context) {
518716
void cloudsync_network_init (sqlite3_context *context, int argc, sqlite3_value **argv) {
519717
DEBUG_FUNCTION("cloudsync_network_init");
520718

521-
#ifndef CLOUDSYNC_OMIT_CURL
719+
#if !defined(CLOUDSYNC_OMIT_CURL) && !defined(SQLITE_WASM_EXTRA_INIT)
522720
curl_global_init(CURL_GLOBAL_ALL);
523721
#endif
524722

@@ -583,7 +781,7 @@ void cloudsync_network_cleanup (sqlite3_context *context, int argc, sqlite3_valu
583781

584782
sqlite3_result_int(context, SQLITE_OK);
585783

586-
#ifndef CLOUDSYNC_OMIT_CURL
784+
#if !defined(CLOUDSYNC_OMIT_CURL) && !defined(SQLITE_WASM_EXTRA_INIT)
587785
curl_global_cleanup();
588786
#endif
589787
}

src/wasm.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#include "sqlite3.h"
44
#include <stdio.h>
55

6-
#define CLOUDSYNC_OMIT_NETWORK
76
#include "utils.c"
87
#include "network.c"
98
#include "dbutils.c"

0 commit comments

Comments
 (0)