1111#include <assert.h>
1212#include "sqlitejs.h"
1313#include "quickjs.h"
14+ #include "quickjs-libc.h"
1415
1516#ifndef SQLITE_CORE
1617SQLITE_EXTENSION_INIT1
@@ -26,6 +27,7 @@ typedef struct {
2627 JSRuntime * runtime ;
2728 JSContext * context ;
2829 sqlite3 * db ;
30+ JSClassID rowSetClassID ;
2931} globaljs_context ;
3032
3133typedef struct {
@@ -46,11 +48,118 @@ typedef struct {
4648} functionjs_context ;
4749
4850static 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"
5255static 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
56165static 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
81194abort_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
242394static 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
402565static 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
544708static 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
793957void 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
797961void 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
801965void js_version (sqlite3_context * context , int argc , sqlite3_value * * argv ) {
0 commit comments