Skip to content

Commit e69225e

Browse files
committed
Added libc support and exposed Rowset class
1 parent 34ca228 commit e69225e

File tree

2 files changed

+199
-35
lines changed

2 files changed

+199
-35
lines changed

src/sqlitejs.c

Lines changed: 195 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <assert.h>
1212
#include "sqlitejs.h"
1313
#include "quickjs.h"
14+
#include "quickjs-libc.h"
1415

1516
#ifndef SQLITE_CORE
1617
SQLITE_EXTENSION_INIT1
@@ -26,6 +27,7 @@ typedef struct {
2627
JSRuntime *runtime;
2728
JSContext *context;
2829
sqlite3 *db;
30+
JSClassID rowSetClassID;
2931
} globaljs_context;
3032

3133
typedef struct {
@@ -46,11 +48,118 @@ typedef struct {
4648
} functionjs_context;
4749

4850
static char *sqlite_strdup (const char *str);
49-
static bool js_global_init (JSContext *ctx);
51+
static bool js_global_init (JSContext *ctx, globaljs_context *js);
52+
static JSValue sqlite_value_to_js (JSContext *ctx, sqlite3_value *value);
5053

5154
#define SQLITEJS_VERSION "1.0.0"
5255
static char gversion[128];
5356

57+
// MARK: - RowSet -
58+
59+
typedef struct {
60+
int ncols;
61+
sqlite3_stmt *vm;
62+
} rowset;
63+
64+
static void js_rowset_finalizer(JSRuntime *rt, JSValue val);
65+
static JSValue js_rowset_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
66+
static JSValue js_rowset_get(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
67+
static JSValue js_rowset_name(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
68+
static JSValue js_rowset_to_array(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
69+
70+
// Define the Rowset class
71+
static const JSClassDef js_rowset_class = {
72+
"Rowset",
73+
.finalizer = js_rowset_finalizer,
74+
};
75+
76+
// Define the Rowset prototype with methods
77+
static const JSCFunctionListEntry js_rowset_proto_funcs[] = {
78+
JS_CFUNC_DEF("next", 0, js_rowset_next),
79+
JS_CFUNC_DEF("get", 1, js_rowset_get),
80+
JS_CFUNC_DEF("name", 1, js_rowset_name),
81+
JS_CFUNC_DEF("toArray", 0, js_rowset_to_array),
82+
};
83+
84+
static JSValue js_rowset_next(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
85+
globaljs_context *js = JS_GetContextOpaque(ctx);
86+
rowset *rs = JS_GetOpaque(this_val, js->rowSetClassID);
87+
if (!rs) return JS_EXCEPTION;
88+
89+
if (sqlite3_step(rs->vm) == SQLITE_ROW) return JS_TRUE;
90+
91+
sqlite3_finalize(rs->vm);
92+
rs->vm = NULL;
93+
return JS_FALSE;
94+
}
95+
96+
static JSValue js_rowset_get(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
97+
globaljs_context *js = JS_GetContextOpaque(ctx);
98+
rowset *rs = JS_GetOpaque(this_val, js->rowSetClassID);
99+
if (!rs) return JS_EXCEPTION;
100+
101+
uint32_t index = 0;
102+
JS_ToUint32(ctx, &index, argv[0]);
103+
if (index >= rs->ncols) return JS_EXCEPTION;
104+
105+
sqlite3_value *value = sqlite3_column_value(rs->vm, (int)index);
106+
return sqlite_value_to_js(ctx, value);
107+
}
108+
109+
static JSValue js_rowset_name(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
110+
globaljs_context *js = JS_GetContextOpaque(ctx);
111+
rowset *rs = JS_GetOpaque(this_val, js->rowSetClassID);
112+
if (!rs) return JS_EXCEPTION;
113+
114+
uint32_t index = 0;
115+
JS_ToUint32(ctx, &index, argv[0]);
116+
if (index >= rs->ncols) return JS_EXCEPTION;
117+
118+
const char *name = sqlite3_column_name(rs->vm, (int)index);
119+
if (!name) return JS_EXCEPTION;
120+
121+
return JS_NewString(ctx, name);
122+
}
123+
124+
static JSValue js_rowset_to_array(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
125+
globaljs_context *js = JS_GetContextOpaque(ctx);
126+
rowset *rs = JS_GetOpaque(this_val, js->rowSetClassID);
127+
if (!rs) return JS_EXCEPTION;
128+
129+
JSValue result = JS_NewArray(ctx);
130+
if (JS_IsException(result)) return JS_EXCEPTION;
131+
132+
int ncols = rs->ncols;
133+
int nrows = 0;
134+
while (true) {
135+
int rc = sqlite3_step(rs->vm);
136+
if (rc != SQLITE_ROW) break;
137+
138+
JSValue row = JS_NewArray(ctx);
139+
if (JS_IsException(row)) return JS_EXCEPTION;
140+
141+
for (int i=0; i<ncols; ++i) {
142+
sqlite3_value *value = sqlite3_column_value(rs->vm, i);
143+
JSValue jvalue = sqlite_value_to_js(ctx, value);
144+
JS_SetPropertyUint32(ctx, row, i, jvalue);
145+
}
146+
147+
JS_SetPropertyUint32(ctx, result, nrows++, row);
148+
}
149+
150+
sqlite3_finalize(rs->vm);
151+
rs->vm = NULL;
152+
return result;
153+
}
154+
155+
static void js_rowset_finalizer(JSRuntime *rt, JSValue val) {
156+
rowset *rs = (rowset *)JS_VALUE_GET_PTR(val);
157+
if (!rs) return;
158+
159+
if (rs->vm) sqlite3_finalize(rs->vm);
160+
sqlite3_free(rs);
161+
}
162+
54163
// MARK: - Initializer -
55164

56165
static globaljs_context *globaljs_init (sqlite3 *db) {
@@ -67,15 +176,19 @@ static globaljs_context *globaljs_init (sqlite3 *db) {
67176
//JS_SetMemoryLimit(rt, 80 * 1024);
68177
//JS_SetMaxStackSize(rt, 10 * 1024);
69178

179+
js_std_init_handlers(rt);
180+
JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
181+
70182
ctx = JS_NewContext(rt);
71183
if (!ctx) goto abort_init;
72184

73-
JS_SetContextOpaque(ctx, db);
74-
js_global_init(ctx);
75-
76185
js->db = db;
77186
js->runtime = rt;
78187
js->context = ctx;
188+
189+
JS_SetContextOpaque(ctx, js);
190+
js_global_init(ctx, js);
191+
79192
return js;
80193

81194
abort_init:
@@ -234,9 +347,48 @@ static void compute_version_string (void) {
234347

235348
// MARK: - Utils -
236349

237-
static JSValue js_sqlite_exec (JSContext *ctx, sqlite3 *db, const char *sql) {
238-
//int rc = sqlite3_prepare_v2(db, sql, -1, sqlite3_stmt **ppStmt, const char **pzTail)
239-
return JS_NewNumber(ctx, 3.1415);
350+
static JSValue js_sqlite_exec (JSContext *ctx, sqlite3 *db, const char *sql, int argc, JSValueConst *argv) {
351+
sqlite3_stmt *vm = NULL;
352+
const char *tail = NULL;
353+
354+
// compile statement
355+
int rc = sqlite3_prepare_v2(db, sql, -1, &vm, &tail);
356+
if (rc != SQLITE_OK) goto abort_with_dberror;
357+
358+
// count if statement contains bindings
359+
int nbind = sqlite3_bind_parameter_count(vm);
360+
if (nbind > 0 && argc > 0) {
361+
// loop to bind
362+
if (nbind > argc) nbind = argc;
363+
for (int i=1; i<=nbind; ++i) {
364+
365+
}
366+
}
367+
368+
// create and initialize internal rowset
369+
rowset *rs = (rowset *)sqlite3_malloc(sizeof(rowset));
370+
if (!rs) goto abort_with_dberror;
371+
rs->vm = vm;
372+
rs->ncols = sqlite3_column_count(vm);
373+
374+
// create Rowset JS object
375+
globaljs_context *js = JS_GetContextOpaque(ctx);
376+
JSValue obj = JS_NewObjectClass(ctx, js->rowSetClassID);
377+
if (JS_IsException(obj)) goto abort_with_jserror;
378+
JS_SetOpaque(obj, rs);
379+
JS_SetPropertyStr(ctx, obj, "columnCount", JS_NewInt32(ctx, rs->ncols));
380+
381+
return obj;
382+
383+
abort_with_dberror:
384+
rs->vm = NULL;
385+
if (vm) sqlite3_finalize(vm);
386+
return JS_ThrowInternalError(ctx, "%s", sqlite3_errmsg(db));
387+
388+
abort_with_jserror:
389+
rs->vm = NULL;
390+
if (vm) sqlite3_finalize(vm);
391+
return obj;
240392
}
241393

242394
static JSValue js_dbfunc_exec (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
@@ -249,28 +401,38 @@ static JSValue js_dbfunc_exec (JSContext *ctx, JSValueConst this_val, int argc,
249401
if (!sql) return JS_EXCEPTION;
250402

251403
// perform statement
252-
sqlite3 *db = (sqlite3 *)JS_GetContextOpaque(ctx);
253-
JSValue value = js_sqlite_exec(ctx, db, sql);
404+
globaljs_context *js = JS_GetContextOpaque(ctx);
405+
JSValue value = js_sqlite_exec(ctx, js->db, sql, argc-1, argv);
254406

255407
// free the string when done
256408
JS_FreeCString(ctx, sql);
257409

258410
return value;
259411
}
260412

261-
static bool js_global_init (JSContext *ctx) {
413+
static bool js_global_init (JSContext *ctx, globaljs_context *js) {
414+
js_std_add_helpers(ctx, 0, NULL);
415+
262416
// add any global objects or functions here
263417
JSValue global_obj = JS_GetGlobalObject(ctx);
264418

265419
// create a new db object
266420
JSValue db_obj = JS_NewObject(ctx);
267-
268-
// add the exec function to the db object
269421
JS_SetPropertyStr(ctx, db_obj, "exec", JS_NewCFunction(ctx, js_dbfunc_exec, "exec", 1));
270-
271-
// set the db object as a property of the global object
272422
JS_SetPropertyStr(ctx, global_obj, "db", db_obj);
273423

424+
// register rowset class
425+
JS_NewClassID(js->runtime, &js->rowSetClassID);
426+
JS_NewClass(js->runtime, js->rowSetClassID, &js_rowset_class);
427+
JSValue proto = JS_NewObject(ctx);
428+
JS_SetPropertyFunctionList(ctx, proto, js_rowset_proto_funcs, sizeof(js_rowset_proto_funcs)/sizeof(js_rowset_proto_funcs[0]));
429+
JS_SetClassProto(ctx, js->rowSetClassID, proto);
430+
431+
// register standard modules
432+
js_init_module_std(ctx, "std");
433+
js_init_module_os(ctx, "os");
434+
js_init_module_bjson(ctx, "bjson");
435+
274436
// release the global object reference
275437
JS_FreeValue(ctx, global_obj);
276438

@@ -340,17 +502,17 @@ static void js_error_to_sqlite (sqlite3_context *context, JSContext *js_ctx, JSV
340502
if (!JS_IsNull(exception)) JS_FreeValue(js_ctx, exception);
341503
}
342504

343-
static void js_value_to_sqlite (sqlite3_context *context, JSContext *js_ctx, JSValue value) {
505+
static bool js_value_to_sqlite (sqlite3_context *context, JSContext *js_ctx, JSValue value) {
344506
// check for exceptions first and convert it to a proper error message (if any)
345507
if (JS_IsException(value)) {
346508
js_error_to_sqlite(context, js_ctx, value, NULL);
347-
return;
509+
return false;
348510
}
349511

350512
// check for null or undefined
351513
if (JS_IsNull(value) || JS_IsUndefined(value)) {
352514
sqlite3_result_null(context);
353-
return;
515+
return false;
354516
}
355517

356518
// handle numbers
@@ -368,13 +530,13 @@ static void js_value_to_sqlite (sqlite3_context *context, JSContext *js_ctx, JSV
368530
// handle BigInt if needed
369531
sqlite3_result_null(context);
370532
}
371-
return;
533+
return false;
372534
}
373535

374536
// handle booleans
375537
if (JS_IsBool(value)) {
376538
sqlite3_result_int(context, JS_ToBool(js_ctx, value));
377-
return;
539+
return false;
378540
}
379541

380542
// handle strings
@@ -387,16 +549,17 @@ static void js_value_to_sqlite (sqlite3_context *context, JSContext *js_ctx, JSV
387549
} else {
388550
sqlite3_result_error(context, "Failed to convert JS string", -1);
389551
}
390-
return;
552+
return false;
391553
}
392554

393555
if (JS_IsObject(value)) {
394-
sqlite3_result_error(context, "Value is an object", -1);
395-
return;
556+
sqlite3_result_null(context);
557+
return true;
396558
}
397559

398560
// fallback for unsupported types
399561
sqlite3_result_error(context, "Unsupported JS value type", -1);
562+
return false;
400563
}
401564

402565
static bool js_setup_aggregate (sqlite3_context *context, globaljs_context *js, functionjs_context *fctx, const char *init_code, const char *step_code, const char *final_code, const char *value_code, const char *inverse_code) {
@@ -409,8 +572,8 @@ static bool js_setup_aggregate (sqlite3_context *context, globaljs_context *js,
409572
}
410573

411574
// setup global object
412-
JS_SetContextOpaque(ctx, sqlite3_context_db_handle(context));
413-
js_global_init(ctx);
575+
JS_SetContextOpaque(ctx, js);
576+
js_global_init(ctx, js);
414577

415578
// init code is optional
416579
if (init_code) {
@@ -537,8 +700,9 @@ static void js_execute_commong (sqlite3_context *context, int nvalues, sqlite3_v
537700
JSValue result = JS_Call(js_context, func, this_obj, (values) ? 1 : 0, (values) ? args_val : NULL);
538701
JS_FreeValue(js_context, args);
539702

540-
if (return_value) js_value_to_sqlite(context, js_context, result);
541-
JS_FreeValue(js_context, result);
703+
bool is_object = false;
704+
if (return_value) is_object = js_value_to_sqlite(context, js_context, result);
705+
if (!is_object) JS_FreeValue(js_context, result);
542706
}
543707

544708
static void js_execute_scalar (sqlite3_context *context, int nvalues, sqlite3_value **values) {
@@ -751,11 +915,11 @@ void js_eval (sqlite3_context *context, int argc, sqlite3_value **argv) {
751915
}
752916

753917
JSValue value = JS_Eval(data->context, code, strlen(code), NULL, JS_EVAL_TYPE_GLOBAL);
754-
js_value_to_sqlite(context, data->context, value);
755-
JS_FreeValue(data->context, value);
918+
bool is_object = js_value_to_sqlite(context, data->context, value);
919+
if (!is_object) JS_FreeValue(data->context, value);
756920
}
757921

758-
static void js_load_file (sqlite3_context *context, int argc, sqlite3_value **argv, bool is_blob) {
922+
static void js_load_fromfile (sqlite3_context *context, int argc, sqlite3_value **argv, bool is_blob) {
759923
const char *path = (const char *)sqlite3_value_text(argv[0]);
760924
if (!path) {
761925
sqlite3_result_error(context, "A parameter of type TEXT is required", -1);
@@ -791,11 +955,11 @@ static void js_load_file (sqlite3_context *context, int argc, sqlite3_value **ar
791955
}
792956

793957
void js_load_text (sqlite3_context *context, int argc, sqlite3_value **argv) {
794-
js_load_file(context, argc, argv, false);
958+
js_load_fromfile(context, argc, argv, false);
795959
}
796960

797961
void js_load_blob (sqlite3_context *context, int argc, sqlite3_value **argv) {
798-
js_load_file(context, argc, argv, true);
962+
js_load_fromfile(context, argc, argv, true);
799963
}
800964

801965
void js_version (sqlite3_context *context, int argc, sqlite3_value **argv) {

test/sqlitejs/main.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,6 @@ int main (void) {
3737

3838
printf("SQLite-JS version: %s\n\n", sqlitejs_version());
3939

40-
// db object
41-
printf("Testing db\n");
42-
rc = db_exec(db, "SELECT js_eval('db.exec(''SELECT 134;'');');");
43-
4440
// context
4541
printf("Testing context\n");
4642
rc = db_exec(db, "SELECT js_eval('x = 100;');");
@@ -70,6 +66,10 @@ int main (void) {
7066
rc = db_exec(db, "INSERT INTO data(val) VALUES (10), (12), (14), (16), (18), (20);");
7167
rc = db_exec(db, "SELECT Median(val) FROM data;");
7268

69+
// db object
70+
printf("\nTesting db.exec\n");
71+
rc = db_exec(db, "SELECT js_eval('let rs = db.exec(''SELECT * FROM data;''); console.log(`rowset = ${rs.toArray()}`);');");
72+
7373
// collation
7474
printf("\nTesting js_create_collation\n");
7575
const char *collation_js_function = "(function(str1,str2){"

0 commit comments

Comments
 (0)