2022-05-21 14:19:05 +00:00
/ *
2022 - 05 - 20
The author disclaims copyright to this source code . In place of a
legal notice , here is a blessing :
* May you do good and not evil .
* May you find forgiveness for yourself and forgive others .
* May you share freely , never taking more than you give .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
This is the JS Worker file for the sqlite3 fiddle app . It loads the
sqlite3 wasm module and offers access to the db via the Worker
message - passing interface .
2022-05-22 00:27:19 +00:00
Forewarning : this API is still very much Under Construction and
subject to any number of changes as experience reveals what those
need to be .
2022-05-21 14:19:05 +00:00
Because we can have only a single message handler , as opposed to an
arbitrary number of discrete event listeners like with DOM elements ,
we have to define a lower - level message API . Messages abstractly
look like :
{ type : string , data : type - specific value }
Where 'type' is used for dispatching and 'data' is a
'type' - dependent value .
The 'type' values expected by each side of the main / worker
connection vary . The types are described below but subject to
change at any time as this experiment evolves .
Workers - to - Main types
- stdout , stderr : indicate stdout / stderr output from the wasm
layer . The data property is the string of the output , noting
that the emscripten binding emits these one line at a time . Thus ,
if a C - side puts ( ) emits multiple lines in a single call , the JS
2022-05-23 16:54:18 +00:00
side will see that as multiple calls . Example :
{ type : 'stdout' , data : 'Hi, world.' }
2022-05-21 14:19:05 +00:00
- module : Status text . This is intended to alert the main thread
about module loading status so that , e . g . , the main thread can
update a progress widget and DTRT when the module is finished
2022-05-23 16:54:18 +00:00
loading and available for work . Status messages come in the form
2023-02-10 11:04:39 +00:00
2022-05-23 16:54:18 +00:00
{ type : 'module' , data : {
type : 'status' ,
data : { text : string | null , step : 1 - based - integer }
}
with an incrementing step value for each subsequent message . When
the module loading is complete , a message with a text value of
null is posted .
2022-05-21 14:19:05 +00:00
- working : data = 'start' | 'end' . Indicates that work is about to be
sent to the module or has just completed . This can be used , e . g . ,
to disable UI elements which should not be activated while work
2022-05-23 16:54:18 +00:00
is pending . Example :
{ type : 'working' , data : 'start' }
2022-05-21 14:19:05 +00:00
Main - to - Worker types :
- shellExec : data = text to execute as if it had been entered in the
sqlite3 CLI shell app ( as opposed to sqlite3 _exec ( ) ) . This event
causes the worker to emit a 'working' event ( data = 'start' ) before
it starts and a 'working' event ( data = 'end' ) when it finished . If
called while work is currently being executed it emits stderr
message instead of doing actual work , as the underlying db cannot
2022-05-23 16:54:18 +00:00
handle concurrent tasks . Example :
{ type : 'shellExec' , data : 'select * from sqlite_master' }
2022-05-21 14:19:05 +00:00
- More TBD as the higher - level db layer develops .
* /
/ *
Apparent browser ( s ) bug : console messages emitted may be duplicated
in the console , even though they ' re provably only run once . See :
https : //stackoverflow.com/questions/49659464
Noting that it happens in Firefox as well as Chrome . Harmless but
annoying .
* /
2022-05-22 00:27:19 +00:00
"use strict" ;
2022-05-25 03:08:22 +00:00
( function ( ) {
2022-08-12 17:57:09 +00:00
/ * *
2022-09-24 11:32:00 +00:00
Posts a message in the form { type , data } . If passed more than 2
args , the 3 rd must be an array of "transferable" values to pass
as the 2 nd argument to postMessage ( ) . * /
const wMsg =
( type , data , transferables ) => {
postMessage ( { type , data } , transferables || [ ] ) ;
} ;
2022-09-21 19:51:25 +00:00
const stdout = ( ... args ) => wMsg ( 'stdout' , args ) ;
const stderr = ( ... args ) => wMsg ( 'stderr' , args ) ;
2022-09-26 11:38:58 +00:00
const toss = ( ... args ) => {
throw new Error ( args . join ( ' ' ) ) ;
} ;
2022-10-20 05:14:37 +00:00
const fixmeOPFS = "(FIXME: won't work with OPFS-over-sqlite3_vfs.)" ;
2022-09-26 13:55:10 +00:00
let sqlite3 /* gets assigned when the wasm module is loaded */ ;
2022-08-12 17:57:09 +00:00
self . onerror = function ( /*message, source, lineno, colno, error*/ ) {
const err = arguments [ 4 ] ;
if ( err && 'ExitStatus' == err . name ) {
/ * T h i s i s r e l e v a n t f o r t h e s q l i t e 3 s h e l l b i n d i n g b u t n o t t h e
lower - level binding . * /
fiddleModule . isDead = true ;
stderr ( "FATAL ERROR:" , err . message ) ;
stderr ( "Restarting the app requires reloading the page." ) ;
wMsg ( 'error' , err ) ;
}
console . error ( err ) ;
fiddleModule . setStatus ( 'Exception thrown, see JavaScript console: ' + err ) ;
} ;
const Sqlite3Shell = {
/** Returns the name of the currently-opened db. */
dbFilename : function f ( ) {
2022-10-29 07:54:10 +00:00
if ( ! f . _ ) f . _ = sqlite3 . wasm . xWrap ( 'fiddle_db_filename' , "string" , [ 'string' ] ) ;
2022-09-26 13:55:10 +00:00
return f . _ ( 0 ) ;
} ,
dbHandle : function f ( ) {
2022-10-29 07:54:10 +00:00
if ( ! f . _ ) f . _ = sqlite3 . wasm . xWrap ( "fiddle_db_handle" , "sqlite3*" ) ;
2022-08-12 17:57:09 +00:00
return f . _ ( ) ;
} ,
2022-09-26 13:55:10 +00:00
dbIsOpfs : function f ( ) {
2022-10-20 18:31:32 +00:00
return sqlite3 . opfs && sqlite3 . capi . sqlite3 _js _db _uses _vfs (
2022-09-26 13:55:10 +00:00
this . dbHandle ( ) , "opfs"
) ;
} ,
2022-09-24 07:36:45 +00:00
runMain : function f ( ) {
2022-09-24 10:15:08 +00:00
if ( f . argv ) return 0 === f . argv . rc ;
2022-09-24 07:36:45 +00:00
const dbName = "/fiddle.sqlite3" ;
f . argv = [
'sqlite3-fiddle.wasm' ,
'-bail' , '-safe' ,
dbName
/ * R e m i n d e r : b e c a u s e o f h o w w e r u n f i d d l e , w e h a v e t o e n s u r e
that any argv strings passed to its main ( ) are valid until
the wasm environment shuts down . * /
] ;
2022-10-29 07:54:10 +00:00
const capi = sqlite3 . capi , wasm = sqlite3 . wasm ;
2022-09-24 10:15:08 +00:00
/ * W e n e e d t o c a l l s q l i t e 3 _ s h u t d o w n ( ) i n o r d e r t o a v o i d n u m e r o u s
legitimate warnings from the shell about it being initialized
2022-09-26 11:38:58 +00:00
after sqlite3 _initialize ( ) has been called . This means ,
2022-09-24 10:15:08 +00:00
however , that any initialization done by the JS code may need
to be re - done ( e . g . re - registration of dynamically - loaded
2022-09-26 13:55:10 +00:00
VFSes ) . We need a more generic approach to running such
init - level code . * /
2022-09-29 16:54:23 +00:00
capi . sqlite3 _shutdown ( ) ;
2022-10-29 07:54:10 +00:00
f . argv . pArgv = wasm . allocMainArgv ( f . argv ) ;
f . argv . rc = wasm . exports . fiddle _main (
2022-09-24 07:36:45 +00:00
f . argv . length , f . argv . pArgv
) ;
if ( f . argv . rc ) {
stderr ( "Fatal error initializing sqlite3 shell." ) ;
fiddleModule . isDead = true ;
return false ;
}
2022-11-05 11:04:46 +01:00
stdout ( "libSQL version" , capi . sqlite3 _libversion ( ) ,
2022-09-29 16:54:23 +00:00
capi . sqlite3 _sourceid ( ) . substr ( 0 , 19 ) ) ;
2022-09-24 10:15:08 +00:00
stdout ( 'Welcome to the "fiddle" shell.' ) ;
2022-10-26 15:40:17 +00:00
if ( sqlite3 . opfs ) {
2022-09-24 10:15:08 +00:00
stdout ( "\nOPFS is available. To open a persistent db, use:\n\n" ,
" .open file:name?vfs=opfs\n\nbut note that some" ,
2022-09-26 11:38:58 +00:00
"features (e.g. upload) do not yet work with OPFS." ) ;
2022-10-26 15:40:17 +00:00
sqlite3 . opfs . registerVfs ( ) ;
2022-09-24 10:15:08 +00:00
}
stdout ( '\nEnter ".help" for usage hints.' ) ;
this . exec ( [ // initialization commands...
'.nullvalue NULL' ,
'.headers on'
] . join ( '\n' ) ) ;
2022-09-24 07:36:45 +00:00
return true ;
} ,
2022-05-23 16:54:18 +00:00
/ * *
2022-08-12 17:57:09 +00:00
Runs the given text through the shell as if it had been typed
in by a user . Fires a working / start event before it starts and
working / end event when it finishes .
2022-05-23 16:54:18 +00:00
* /
2022-08-12 17:57:09 +00:00
exec : function f ( sql ) {
2022-09-24 07:36:45 +00:00
if ( ! f . _ ) {
if ( ! this . runMain ( ) ) return ;
2022-10-29 07:54:10 +00:00
f . _ = sqlite3 . wasm . xWrap ( 'fiddle_exec' , null , [ 'string' ] ) ;
2022-09-24 07:36:45 +00:00
}
2022-08-12 17:57:09 +00:00
if ( fiddleModule . isDead ) {
stderr ( "shell module has exit()ed. Cannot run SQL." ) ;
return ;
}
wMsg ( 'working' , 'start' ) ;
try {
if ( f . _running ) {
stderr ( 'Cannot run multiple commands concurrently.' ) ;
2022-09-24 07:36:45 +00:00
} else if ( sql ) {
2022-09-29 16:54:23 +00:00
if ( Array . isArray ( sql ) ) sql = sql . join ( '' ) ;
2022-08-12 17:57:09 +00:00
f . _running = true ;
f . _ ( sql ) ;
2022-05-25 03:08:22 +00:00
}
2022-09-24 07:36:45 +00:00
} finally {
2022-08-12 17:57:09 +00:00
delete f . _running ;
wMsg ( 'working' , 'end' ) ;
}
} ,
resetDb : function f ( ) {
2022-10-29 07:54:10 +00:00
if ( ! f . _ ) f . _ = sqlite3 . wasm . xWrap ( 'fiddle_reset_db' , null ) ;
2022-10-03 11:42:45 +00:00
stdout ( "Resetting database." ) ;
2022-08-12 17:57:09 +00:00
f . _ ( ) ;
stdout ( "Reset" , this . dbFilename ( ) ) ;
} ,
/ * I n t e r r u p t c a n ' t w o r k : t h i s W o r k e r i s t i e d u p w o r k i n g , s o w o n ' t g e t t h e
interrupt event which would be needed to perform the interrupt . * /
interrupt : function f ( ) {
2022-10-29 07:54:10 +00:00
if ( ! f . _ ) f . _ = sqlite3 . wasm . xWrap ( 'fiddle_interrupt' , null ) ;
2022-08-12 17:57:09 +00:00
stdout ( "Requesting interrupt." ) ;
f . _ ( ) ;
}
} ;
2023-08-01 16:38:08 +00:00
2022-08-12 17:57:09 +00:00
self . onmessage = function f ( ev ) {
ev = ev . data ;
if ( ! f . cache ) {
f . cache = {
prevFilename : null
} ;
}
//console.debug("worker: onmessage.data",ev);
switch ( ev . type ) {
case 'shellExec' : Sqlite3Shell . exec ( ev . data ) ; return ;
case 'db-reset' : Sqlite3Shell . resetDb ( ) ; return ;
case 'interrupt' : Sqlite3Shell . interrupt ( ) ; return ;
/ * * T r i g g e r s t h e e x p o r t o f t h e c u r r e n t d b . F i r e s a n
event in the form :
{ type : 'db-export' ,
data : {
filename : name of db ,
buffer : contents of the db file ( Uint8Array ) ,
error : on error , a message string and no buffer property .
2022-05-25 03:08:22 +00:00
}
2022-08-12 17:57:09 +00:00
}
* /
case 'db-export' : {
const fn = Sqlite3Shell . dbFilename ( ) ;
2022-09-26 11:38:58 +00:00
stdout ( "Exporting" , fn + "." ) ;
2022-08-12 17:57:09 +00:00
const fn2 = fn ? fn . split ( /[/\\]/ ) . pop ( ) : null ;
try {
2022-10-01 16:01:41 +00:00
if ( ! fn2 ) toss ( "DB appears to be closed." ) ;
2022-10-20 18:31:32 +00:00
const buffer = sqlite3 . capi . sqlite3 _js _db _export (
2022-10-01 16:01:41 +00:00
Sqlite3Shell . dbHandle ( )
) ;
wMsg ( 'db-export' , { filename : fn2 , buffer : buffer . buffer } , [ buffer . buffer ] ) ;
2022-08-12 17:57:09 +00:00
} catch ( e ) {
2022-10-01 16:01:41 +00:00
console . error ( "Export failed:" , e ) ;
2022-08-12 17:57:09 +00:00
/ * P o s t a f a i l u r e m e s s a g e s o t h a t U I e l e m e n t s d i s a b l e d
during the export can be re - enabled . * /
wMsg ( 'db-export' , {
filename : fn ,
error : e . message
} ) ;
}
return ;
2022-05-25 03:08:22 +00:00
}
2022-08-12 17:57:09 +00:00
case 'open' : {
/ * E x p e c t s : {
2022-09-24 11:32:00 +00:00
buffer : ArrayBuffer | Uint8Array ,
filename : the filename for the db . Any dir part is
stripped .
}
* /
2022-08-12 17:57:09 +00:00
const opt = ev . data ;
let buffer = opt . buffer ;
2022-09-24 10:15:08 +00:00
stderr ( 'open():' , fixmeOPFS ) ;
2022-09-24 11:32:00 +00:00
if ( buffer instanceof ArrayBuffer ) {
2022-08-12 17:57:09 +00:00
buffer = new Uint8Array ( buffer ) ;
2022-09-24 11:32:00 +00:00
} else if ( ! ( buffer instanceof Uint8Array ) ) {
2022-08-12 17:57:09 +00:00
stderr ( "'open' expects {buffer:Uint8Array} containing an uploaded db." ) ;
return ;
}
const fn = (
opt . filename
? opt . filename . split ( /[/\\]/ ) . pop ( ) . replace ( '"' , '_' )
: ( "db-" + ( ( Math . random ( ) * 10000000 ) | 0 ) +
"-" + ( ( Math . random ( ) * 10000000 ) | 0 ) + ".sqlite3" )
) ;
2022-09-24 11:32:00 +00:00
try {
/ * W e c a n n o t d e l e t e t h e e x i s t i n g d b f i l e u n t i l t h e n e w o n e
is installed , which means that we risk overflowing our
quota ( if any ) by having both the previous and current
db briefly installed in the virtual filesystem . * /
const fnAbs = '/' + fn ;
const oldName = Sqlite3Shell . dbFilename ( ) ;
if ( oldName && oldName === fnAbs ) {
/ * W e c a n n o t c r e a t e t h e r e p l a c e m e n t f i l e w h i l e t h e c u r r e n t f i l e
is opened , nor does the shell have a . close command , so we
must temporarily switch to another db ... * /
Sqlite3Shell . exec ( '.open :memory:' ) ;
fiddleModule . FS . unlink ( fnAbs ) ;
}
fiddleModule . FS . createDataFile ( "/" , fn , buffer , true , true ) ;
Sqlite3Shell . exec ( '.open "' + fnAbs + '"' ) ;
if ( oldName && oldName !== fnAbs ) {
try { fiddleModule . fsUnlink ( oldName ) }
catch ( e ) { /*ignored*/ }
}
stdout ( "Replaced DB with" , fn + "." ) ;
} catch ( e ) {
stderr ( "Error installing db" , fn + ":" , e . message ) ;
2022-08-12 17:57:09 +00:00
}
return ;
2022-05-24 22:16:12 +00:00
}
2022-05-25 03:08:22 +00:00
} ;
2022-08-12 17:57:09 +00:00
console . warn ( "Unknown fiddle-worker message type:" , ev ) ;
} ;
/ * *
emscripten module for use with build mode - sMODULARIZE .
* /
const fiddleModule = {
print : stdout ,
printErr : stderr ,
2022-05-25 03:08:22 +00:00
/ * *
2022-08-12 17:57:09 +00:00
Intercepts status updates from the emscripting module init
and fires worker events with a type of 'status' and a
payload of :
{
text : string | null , // null at end of load process
step : integer // starts at 1, increments 1 per call
}
We have no way of knowing in advance how many steps will
be processed / posted , so creating a "percentage done" view is
not really practical . One can be approximated by giving it a
current value of message . step and max value of message . step + 1 ,
though .
When work is finished , a message with a text value of null is
submitted .
After a message with text == null is posted , the module may later
post messages about fatal problems , e . g . an exit ( ) being
triggered , so it is recommended that UI elements for posting
status messages not be outright removed from the DOM when
text == null , and that they instead be hidden until / unless
text != null .
2022-05-25 03:08:22 +00:00
* /
2022-08-12 17:57:09 +00:00
setStatus : function f ( text ) {
if ( ! f . last ) f . last = { step : 0 , text : '' } ;
else if ( text === f . last . text ) return ;
f . last . text = text ;
wMsg ( 'module' , {
type : 'status' ,
data : { step : ++ f . last . step , text : text || null }
} ) ;
}
} ;
2022-09-21 19:51:25 +00:00
importScripts ( 'fiddle-module.js' + self . location . search ) ;
2022-08-12 17:57:09 +00:00
/ * *
initFiddleModule ( ) is installed via fiddle - module . js due to
building with :
emcc ... - sMODULARIZE = 1 - sEXPORT _NAME = initFiddleModule
* /
2022-09-29 16:54:23 +00:00
sqlite3InitModule ( fiddleModule ) . then ( ( _sqlite3 ) => {
sqlite3 = _sqlite3 ;
2023-02-10 11:04:39 +00:00
console . warn ( "Installing sqlite3 module globally (in Worker)" ,
2023-08-01 16:38:08 +00:00
"for use in the dev console." , sqlite3 ) ;
globalThis . sqlite3 = sqlite3 ;
2022-10-29 07:54:10 +00:00
const dbVfs = sqlite3 . wasm . xWrap ( 'fiddle_db_vfs' , "*" , [ 'string' ] ) ;
2022-09-29 16:54:23 +00:00
fiddleModule . fsUnlink = ( fn ) => {
2022-10-29 07:54:10 +00:00
return sqlite3 . wasm . sqlite3 _wasm _vfs _unlink ( dbVfs ( 0 ) , fn ) ;
2022-09-21 19:51:25 +00:00
} ;
2022-09-29 16:54:23 +00:00
wMsg ( 'fiddle-ready' ) ;
2023-08-01 16:38:08 +00:00
} ) . catch ( e => {
console . error ( "Fiddle worker init failed:" , e ) ;
} ) ;
2022-05-25 03:08:22 +00:00
} ) ( ) ;