123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196 |
- //#ifnot omit-oo1
- /*
- 2022-07-22
- 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 file contains the so-called OO #1 API wrapper for the sqlite3
- WASM build. It requires that sqlite3-api-glue.js has already run
- and it installs its deliverable as globalThis.sqlite3.oo1.
- */
- globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
- const toss3 = (...args)=>{throw new sqlite3.SQLite3Error(...args)};
- const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util;
- /* What follows is colloquially known as "OO API #1". It is a
- binding of the sqlite3 API which is designed to be run within
- the same thread (main or worker) as the one in which the
- sqlite3 WASM binding was initialized. This wrapper cannot use
- the sqlite3 binding if, e.g., the wrapper is in the main thread
- and the sqlite3 API is in a worker. */
- /**
- In order to keep clients from manipulating, perhaps
- inadvertently, the underlying pointer values of DB and Stmt
- instances, we'll gate access to them via the `pointer` property
- accessor and store their real values in this map. Keys = DB/Stmt
- objects, values = pointer values. This also unifies how those are
- accessed, for potential use downstream via custom
- wasm.xWrap() function signatures which know how to extract
- it.
- */
- const __ptrMap = new WeakMap();
- /**
- Map of DB instances to objects, each object being a map of Stmt
- wasm pointers to Stmt objects.
- */
- const __stmtMap = new WeakMap();
- /** If object opts has _its own_ property named p then that
- property's value is returned, else dflt is returned. */
- const getOwnOption = (opts, p, dflt)=>{
- const d = Object.getOwnPropertyDescriptor(opts,p);
- return d ? d.value : dflt;
- };
- // Documented in DB.checkRc()
- const checkSqlite3Rc = function(dbPtr, sqliteResultCode){
- if(sqliteResultCode){
- if(dbPtr instanceof DB) dbPtr = dbPtr.pointer;
- toss3(
- sqliteResultCode,
- "sqlite3 result code",sqliteResultCode+":",
- (dbPtr
- ? capi.sqlite3_errmsg(dbPtr)
- : capi.sqlite3_errstr(sqliteResultCode))
- );
- }
- return arguments[0];
- };
- /**
- sqlite3_trace_v2() callback which gets installed by the DB ctor
- if its open-flags contain "t".
- */
- const __dbTraceToConsole =
- wasm.installFunction('i(ippp)', function(t,c,p,x){
- if(capi.SQLITE_TRACE_STMT===t){
- // x == SQL, p == sqlite3_stmt*
- console.log("SQL TRACE #"+(++this.counter)+' via sqlite3@'+c+':',
- wasm.cstrToJs(x));
- }
- }.bind({counter: 0}));
- /**
- A map of sqlite3_vfs pointers to SQL code or a callback function
- to run when the DB constructor opens a database with the given
- VFS. In the latter case, the call signature is
- (theDbObject,sqlite3Namespace) and the callback is expected to
- throw on error.
- */
- const __vfsPostOpenCallback = Object.create(null);
- //#if enable-see
- /**
- Converts ArrayBuffer or Uint8Array ba into a string of hex
- digits.
- */
- const byteArrayToHex = function(ba){
- if( ba instanceof ArrayBuffer ){
- ba = new Uint8Array(ba);
- }
- const li = [];
- const digits = "0123456789abcdef";
- for( const d of ba ){
- li.push( digits[(d & 0xf0) >> 4], digits[d & 0x0f] );
- }
- return li.join('');
- };
- /**
- Internal helper to apply an SEE key to a just-opened
- database. Requires that db be-a DB object which has just been
- opened, opt be the options object processed by its ctor, and opt
- must have either the key, hexkey, or textkey properties, either
- as a string, an ArrayBuffer, or a Uint8Array.
- This is a no-op in non-SEE builds. It throws on error and returns
- without side effects if none of the key/textkey/hexkey options
- are set. It throws if more than one is set or if any are set to
- values of an invalid type.
- Returns true if it applies the key, else an unspecified falsy
- value. Note that applying the key does not imply that the key is
- correct, only that it was passed on to the db.
- */
- const dbCtorApplySEEKey = function(db,opt){
- if( !capi.sqlite3_key_v2 ) return;
- let keytype;
- let key;
- const check = (opt.key ? 1 : 0) + (opt.hexkey ? 1 : 0) + (opt.textkey ? 1 : 0);
- if( !check ) return;
- else if( check>1 ){
- toss3(capi.SQLITE_MISUSE,
- "Only ONE of (key, hexkey, textkey) may be provided.");
- }
- if( opt.key ){
- /* It is not legal to bind an argument to PRAGMA key=?, so we
- convert it to a hexkey... */
- keytype = 'key';
- key = opt.key;
- if('string'===typeof key){
- key = new TextEncoder('utf-8').encode(key);
- }
- if((key instanceof ArrayBuffer) || (key instanceof Uint8Array)){
- key = byteArrayToHex(key);
- keytype = 'hexkey';
- }else{
- toss3(capi.SQLITE_MISUSE,
- "Invalid value for the 'key' option. Expecting a string,",
- "ArrayBuffer, or Uint8Array.");
- return;
- }
- }else if( opt.textkey ){
- /* For textkey we need it to be in string form, so convert it to
- a string if it's a byte array... */
- keytype = 'textkey';
- key = opt.textkey;
- if(key instanceof ArrayBuffer){
- key = new Uint8Array(key);
- }
- if(key instanceof Uint8Array){
- key = new TextDecoder('utf-8').decode(key);
- }else if('string'!==typeof key){
- toss3(capi.SQLITE_MISUSE,
- "Invalid value for the 'textkey' option. Expecting a string,",
- "ArrayBuffer, or Uint8Array.");
- }
- }else if( opt.hexkey ){
- keytype = 'hexkey';
- key = opt.hexkey;
- if((key instanceof ArrayBuffer) || (key instanceof Uint8Array)){
- key = byteArrayToHex(key);
- }else if('string'!==typeof key){
- toss3(capi.SQLITE_MISUSE,
- "Invalid value for the 'hexkey' option. Expecting a string,",
- "ArrayBuffer, or Uint8Array.");
- }
- /* else assume it's valid hex codes */
- }else{
- return;
- }
- let stmt;
- try{
- stmt = db.prepare("PRAGMA "+keytype+"="+util.sqlite3__wasm_qfmt_token(key, 1));
- stmt.step();
- return true;
- }finally{
- if(stmt) stmt.finalize();
- }
- };
- //#endif enable-see
- /**
- A proxy for DB class constructors. It must be called with the
- being-construct DB object as its "this". See the DB constructor
- for the argument docs. This is split into a separate function
- in order to enable simple creation of special-case DB constructors,
- e.g. JsStorageDb and OpfsDb.
- Expects to be passed a configuration object with the following
- properties:
- - `.filename`: the db filename. It may be a special name like ":memory:"
- or "".
- - `.flags`: as documented in the DB constructor.
- - `.vfs`: as documented in the DB constructor.
- It also accepts those as the first 3 arguments.
- */
- const dbCtorHelper = function ctor(...args){
- if(!ctor._name2vfs){
- /**
- Map special filenames which we handle here (instead of in C)
- to some helpful metadata...
- As of 2022-09-20, the C API supports the names :localStorage:
- and :sessionStorage: for kvvfs. However, C code cannot
- determine (without embedded JS code, e.g. via Emscripten's
- EM_JS()) whether the kvvfs is legal in the current browser
- context (namely the main UI thread). In order to help client
- code fail early on, instead of it being delayed until they
- try to read or write a kvvfs-backed db, we'll check for those
- names here and throw if they're not legal in the current
- context.
- */
- ctor._name2vfs = Object.create(null);
- const isWorkerThread = ('function'===typeof importScripts/*===running in worker thread*/)
- ? (n)=>toss3("The VFS for",n,"is only available in the main window thread.")
- : false;
- ctor._name2vfs[':localStorage:'] = {
- vfs: 'kvvfs', filename: isWorkerThread || (()=>'local')
- };
- ctor._name2vfs[':sessionStorage:'] = {
- vfs: 'kvvfs', filename: isWorkerThread || (()=>'session')
- };
- }
- const opt = ctor.normalizeArgs(...args);
- let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags;
- if(('string'!==typeof fn && 'number'!==typeof fn)
- || 'string'!==typeof flagsStr
- || (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){
- sqlite3.config.error("Invalid DB ctor args",opt,arguments);
- toss3("Invalid arguments for DB constructor.");
- }
- let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn;
- const vfsCheck = ctor._name2vfs[fnJs];
- if(vfsCheck){
- vfsName = vfsCheck.vfs;
- fn = fnJs = vfsCheck.filename(fnJs);
- }
- let pDb, oflags = 0;
- if( flagsStr.indexOf('c')>=0 ){
- oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
- }
- if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
- if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
- oflags |= capi.SQLITE_OPEN_EXRESCODE;
- const stack = wasm.pstack.pointer;
- try {
- const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
- let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0);
- pDb = wasm.peekPtr(pPtr);
- checkSqlite3Rc(pDb, rc);
- capi.sqlite3_extended_result_codes(pDb, 1);
- if(flagsStr.indexOf('t')>=0){
- capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT,
- __dbTraceToConsole, pDb);
- }
- }catch( e ){
- if( pDb ) capi.sqlite3_close_v2(pDb);
- throw e;
- }finally{
- wasm.pstack.restore(stack);
- }
- this.filename = fnJs;
- __ptrMap.set(this, pDb);
- __stmtMap.set(this, Object.create(null));
- try{
- //#if enable-see
- dbCtorApplySEEKey(this,opt);
- //#endif
- // Check for per-VFS post-open SQL/callback...
- const pVfs = capi.sqlite3_js_db_vfs(pDb)
- || toss3("Internal error: cannot get VFS for new db handle.");
- const postInitSql = __vfsPostOpenCallback[pVfs];
- if(postInitSql){
- /**
- Reminder: if this db is encrypted and the client did _not_ pass
- in the key, any init code will fail, causing the ctor to throw.
- We don't actually know whether the db is encrypted, so we cannot
- sensibly apply any heuristics which skip the init code only for
- encrypted databases for which no key has yet been supplied.
- */
- if(postInitSql instanceof Function){
- postInitSql(this, sqlite3);
- }else{
- checkSqlite3Rc(
- pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0)
- );
- }
- }
- }catch(e){
- this.close();
- throw e;
- }
- };
- /**
- Sets a callback which should be called after a db is opened with
- the given sqlite3_vfs pointer. The 2nd argument must be a
- function, which gets called with
- (theOo1DbObject,sqlite3Namespace) at the end of the DB()
- constructor. The function must throw on error, in which case the
- db is closed and the exception is propagated. This function is
- intended only for use by DB subclasses or sqlite3_vfs
- implementations.
- Prior to 2024-07-22, it was legal to pass SQL code as the second
- argument, but that can interfere with a client's ability to run
- pragmas which must be run before anything else, namely (pragma
- locking_mode=exclusive) for use with WAL mode. That capability
- had only ever been used as an internal detail of the two OPFS
- VFSes, and they no longer use it that way.
- */
- dbCtorHelper.setVfsPostOpenCallback = function(pVfs, callback){
- if( !(callback instanceof Function)){
- toss3("dbCtorHelper.setVfsPostOpenCallback() should not be used with "+
- "a non-function argument.",arguments);
- }
- __vfsPostOpenCallback[pVfs] = callback;
- };
- /**
- A helper for DB constructors. It accepts either a single
- config-style object or up to 3 arguments (filename, dbOpenFlags,
- dbVfsName). It returns a new object containing:
- { filename: ..., flags: ..., vfs: ... }
- If passed an object, any additional properties it has are copied
- as-is into the new object.
- */
- dbCtorHelper.normalizeArgs = function(filename=':memory:',flags = 'c',vfs = null){
- const arg = {};
- if(1===arguments.length && arguments[0] && 'object'===typeof arguments[0]){
- Object.assign(arg, arguments[0]);
- if(undefined===arg.flags) arg.flags = 'c';
- if(undefined===arg.vfs) arg.vfs = null;
- if(undefined===arg.filename) arg.filename = ':memory:';
- }else{
- arg.filename = filename;
- arg.flags = flags;
- arg.vfs = vfs;
- }
- return arg;
- };
- /**
- The DB class provides a high-level OO wrapper around an sqlite3
- db handle.
- The given db filename must be resolvable using whatever
- filesystem layer (virtual or otherwise) is set up for the default
- sqlite3 VFS.
- Note that the special sqlite3 db names ":memory:" and ""
- (temporary db) have their normal special meanings here and need
- not resolve to real filenames, but "" uses an on-storage
- temporary database and requires that the VFS support that.
- The second argument specifies the open/create mode for the
- database. It must be string containing a sequence of letters (in
- any order, but case sensitive) specifying the mode:
- - "c": create if it does not exist, else fail if it does not
- exist. Implies the "w" flag.
- - "w": write. Implies "r": a db cannot be write-only.
- - "r": read-only if neither "w" nor "c" are provided, else it
- is ignored.
- - "t": enable tracing of SQL executed on this database handle,
- sending it to `console.log()`. To disable it later, call
- `sqlite3.capi.sqlite3_trace_v2(thisDb.pointer, 0, 0, 0)`.
- If "w" is not provided, the db is implicitly read-only, noting
- that "rc" is meaningless
- Any other letters are currently ignored. The default is
- "c". These modes are ignored for the special ":memory:" and ""
- names and _may_ be ignored altogether for certain VFSes.
- The final argument is analogous to the final argument of
- sqlite3_open_v2(): the name of an sqlite3 VFS. Pass a falsy value,
- or none at all, to use the default. If passed a value, it must
- be the string name of a VFS.
- The constructor optionally (and preferably) takes its arguments
- in the form of a single configuration object with the following
- properties:
- - `filename`: database file name
- - `flags`: open-mode flags
- - `vfs`: the VFS fname
- //#if enable-see
- SEE-capable builds optionally support ONE of the following
- additional options:
- - `key`, `hexkey`, or `textkey`: encryption key as a string,
- ArrayBuffer, or Uint8Array. These flags function as documented
- for the SEE pragmas of the same names. Using a byte array for
- `hexkey` is equivalent to the same series of hex codes in
- string form, so `'666f6f'` is equivalent to
- `Uint8Array([0x66,0x6f,0x6f])`. A `textkey` byte array is
- assumed to be UTF-8. A `key` string is transformed into a UTF-8
- byte array, and a `key` byte array is transformed into a
- `hexkey` with the same bytes.
- In non-SEE builds, these options are ignored. In SEE builds,
- `PRAGMA key/textkey/hexkey=X` is executed immediately after
- opening the db. If more than one of the options is provided,
- or any option has an invalid argument type, an exception is
- thrown.
- Note that some DB subclasses may run post-initialization SQL
- code, e.g. to set a busy-handler timeout or tweak the page cache
- size. Such code is run _after_ the SEE key is applied. If no key
- is supplied and the database is encrypted, execution of the
- post-initialization SQL will fail, causing the constructor to
- throw.
- //#endif enable-see
- The `filename` and `vfs` arguments may be either JS strings or
- C-strings allocated via WASM. `flags` is required to be a JS
- string (because it's specific to this API, which is specific
- to JS).
- For purposes of passing a DB instance to C-style sqlite3
- functions, the DB object's read-only `pointer` property holds its
- `sqlite3*` pointer value. That property can also be used to check
- whether this DB instance is still open: it will evaluate to
- `undefined` after the DB object's close() method is called.
- In the main window thread, the filenames `":localStorage:"` and
- `":sessionStorage:"` are special: they cause the db to use either
- localStorage or sessionStorage for storing the database using
- the kvvfs. If one of these names are used, they trump
- any vfs name set in the arguments.
- */
- const DB = function(...args){
- dbCtorHelper.apply(this, args);
- };
- DB.dbCtorHelper = dbCtorHelper;
- /**
- Internal-use enum for mapping JS types to DB-bindable types.
- These do not (and need not) line up with the SQLITE_type
- values. All values in this enum must be truthy and distinct
- but they need not be numbers.
- */
- const BindTypes = {
- null: 1,
- number: 2,
- string: 3,
- boolean: 4,
- blob: 5
- };
- BindTypes['undefined'] == BindTypes.null;
- if(wasm.bigIntEnabled){
- BindTypes.bigint = BindTypes.number;
- }
- /**
- This class wraps sqlite3_stmt. Calling this constructor
- directly will trigger an exception. Use DB.prepare() to create
- new instances.
- For purposes of passing a Stmt instance to C-style sqlite3
- functions, its read-only `pointer` property holds its `sqlite3_stmt*`
- pointer value.
- Other non-function properties include:
- - `db`: the DB object which created the statement.
- - `columnCount`: the number of result columns in the query, or 0
- for queries which cannot return results. This property is a proxy
- for sqlite3_column_count() and its use in loops should be avoided
- because of the call overhead associated with that. The
- `columnCount` is not cached when the Stmt is created because a
- schema change made via a separate db connection between this
- statement's preparation and when it is stepped may invalidate it.
- - `parameterCount`: the number of bindable parameters in the query.
- As a general rule, most methods of this class will throw if
- called on an instance which has been finalized. For brevity's
- sake, the method docs do not all repeat this warning.
- */
- const Stmt = function(){
- if(BindTypes!==arguments[2]){
- toss3(capi.SQLITE_MISUSE, "Do not call the Stmt constructor directly. Use DB.prepare().");
- }
- this.db = arguments[0];
- __ptrMap.set(this, arguments[1]);
- this.parameterCount = capi.sqlite3_bind_parameter_count(this.pointer);
- };
- /** Throws if the given DB has been closed, else it is returned. */
- const affirmDbOpen = function(db){
- if(!db.pointer) toss3("DB has been closed.");
- return db;
- };
- /** Throws if ndx is not an integer or if it is out of range
- for stmt.columnCount, else returns stmt.
- Reminder: this will also fail after the statement is finalized
- but the resulting error will be about an out-of-bounds column
- index rather than a statement-is-finalized error.
- */
- const affirmColIndex = function(stmt,ndx){
- if((ndx !== (ndx|0)) || ndx<0 || ndx>=stmt.columnCount){
- toss3("Column index",ndx,"is out of range.");
- }
- return stmt;
- };
- /**
- Expects to be passed the `arguments` object from DB.exec(). Does
- the argument processing/validation, throws on error, and returns
- a new object on success:
- { sql: the SQL, opt: optionsObj, cbArg: function}
- The opt object is a normalized copy of any passed to this
- function. The sql will be converted to a string if it is provided
- in one of the supported non-string formats.
- cbArg is only set if the opt.callback or opt.resultRows are set,
- in which case it's a function which expects to be passed the
- current Stmt and returns the callback argument of the type
- indicated by the input arguments.
- */
- const parseExecArgs = function(db, args){
- const out = Object.create(null);
- out.opt = Object.create(null);
- switch(args.length){
- case 1:
- if('string'===typeof args[0] || util.isSQLableTypedArray(args[0])){
- out.sql = args[0];
- }else if(Array.isArray(args[0])){
- out.sql = args[0];
- }else if(args[0] && 'object'===typeof args[0]){
- out.opt = args[0];
- out.sql = out.opt.sql;
- }
- break;
- case 2:
- out.sql = args[0];
- out.opt = args[1];
- break;
- default: toss3("Invalid argument count for exec().");
- };
- out.sql = util.flexibleString(out.sql);
- if('string'!==typeof out.sql){
- toss3("Missing SQL argument or unsupported SQL value type.");
- }
- const opt = out.opt;
- switch(opt.returnValue){
- case 'resultRows':
- if(!opt.resultRows) opt.resultRows = [];
- out.returnVal = ()=>opt.resultRows;
- break;
- case 'saveSql':
- if(!opt.saveSql) opt.saveSql = [];
- out.returnVal = ()=>opt.saveSql;
- break;
- case undefined:
- case 'this':
- out.returnVal = ()=>db;
- break;
- default:
- toss3("Invalid returnValue value:",opt.returnValue);
- }
- if(!opt.callback && !opt.returnValue && undefined!==opt.rowMode){
- if(!opt.resultRows) opt.resultRows = [];
- out.returnVal = ()=>opt.resultRows;
- }
- if(opt.callback || opt.resultRows){
- switch((undefined===opt.rowMode) ? 'array' : opt.rowMode) {
- case 'object':
- out.cbArg = (stmt,cache)=>{
- if( !cache.columnNames ) cache.columnNames = stmt.getColumnNames([]);
- /* https://sqlite.org/forum/forumpost/3632183d2470617d:
- conversion of rows to objects (key/val pairs) is
- somewhat expensive for large data sets because of the
- native-to-JS conversion of the column names. If we
- instead cache the names and build objects from that
- list of strings, it can run twice as fast. The
- difference is not noticeable for small data sets but
- becomes human-perceivable when enough rows are
- involved. */
- const row = stmt.get([]);
- const rv = Object.create(null);
- for( const i in cache.columnNames ) rv[cache.columnNames[i]] = row[i];
- return rv;
- };
- break;
- case 'array': out.cbArg = (stmt)=>stmt.get([]); break;
- case 'stmt':
- if(Array.isArray(opt.resultRows)){
- toss3("exec(): invalid rowMode for a resultRows array: must",
- "be one of 'array', 'object',",
- "a result column number, or column name reference.");
- }
- out.cbArg = (stmt)=>stmt;
- break;
- default:
- if(util.isInt32(opt.rowMode)){
- out.cbArg = (stmt)=>stmt.get(opt.rowMode);
- break;
- }else if('string'===typeof opt.rowMode
- && opt.rowMode.length>1
- && '$'===opt.rowMode[0]){
- /* "$X": fetch column named "X" (case-sensitive!). Prior
- to 2022-12-14 ":X" and "@X" were also permitted, but
- having so many options is unnecessary and likely to
- cause confusion. */
- const $colName = opt.rowMode.substr(1);
- out.cbArg = (stmt)=>{
- const rc = stmt.get(Object.create(null))[$colName];
- return (undefined===rc)
- ? toss3(capi.SQLITE_NOTFOUND,
- "exec(): unknown result column:",$colName)
- : rc;
- };
- break;
- }
- toss3("Invalid rowMode:",opt.rowMode);
- }
- }
- return out;
- };
- /**
- Internal impl of the DB.selectValue(), selectArray(), and
- selectObject() methods.
- */
- const __selectFirstRow = (db, sql, bind, ...getArgs)=>{
- const stmt = db.prepare(sql);
- try {
- const rc = stmt.bind(bind).step() ? stmt.get(...getArgs) : undefined;
- stmt.reset(/*for INSERT...RETURNING locking case*/);
- return rc;
- }finally{
- stmt.finalize();
- }
- };
- /**
- Internal impl of the DB.selectArrays() and selectObjects()
- methods.
- */
- const __selectAll =
- (db, sql, bind, rowMode)=>db.exec({
- sql, bind, rowMode, returnValue: 'resultRows'
- });
- /**
- Expects to be given a DB instance or an `sqlite3*` pointer (may
- be null) and an sqlite3 API result code. If the result code is
- not falsy, this function throws an SQLite3Error with an error
- message from sqlite3_errmsg(), using db (or, if db is-a DB,
- db.pointer) as the db handle, or sqlite3_errstr() if db is
- falsy. Note that if it's passed a non-error code like SQLITE_ROW
- or SQLITE_DONE, it will still throw but the error string might be
- "Not an error." The various non-0 non-error codes need to be
- checked for in client code where they are expected.
- The thrown exception's `resultCode` property will be the value of
- the second argument to this function.
- If it does not throw, it returns its first argument.
- */
- DB.checkRc = (db,resultCode)=>checkSqlite3Rc(db,resultCode);
- DB.prototype = {
- /** Returns true if this db handle is open, else false. */
- isOpen: function(){
- return !!this.pointer;
- },
- /** Throws if this given DB has been closed, else returns `this`. */
- affirmOpen: function(){
- return affirmDbOpen(this);
- },
- /**
- Finalizes all open statements and closes this database
- connection. This is a no-op if the db has already been
- closed. After calling close(), `this.pointer` will resolve to
- `undefined`, so that can be used to check whether the db
- instance is still opened.
- If this.onclose.before is a function then it is called before
- any close-related cleanup.
- If this.onclose.after is a function then it is called after the
- db is closed but before auxiliary state like this.filename is
- cleared.
- Both onclose handlers are passed this object, with the onclose
- object as their "this," noting that the db will have been
- closed when onclose.after is called. If this db is not opened
- when close() is called, neither of the handlers are called. Any
- exceptions the handlers throw are ignored because "destructors
- must not throw."
- Note that garbage collection of a db handle, if it happens at
- all, will never trigger close(), so onclose handlers are not a
- reliable way to implement close-time cleanup or maintenance of
- a db.
- */
- close: function(){
- if(this.pointer){
- if(this.onclose && (this.onclose.before instanceof Function)){
- try{this.onclose.before(this)}
- catch(e){/*ignore*/}
- }
- const pDb = this.pointer;
- Object.keys(__stmtMap.get(this)).forEach((k,s)=>{
- if(s && s.pointer){
- try{s.finalize()}
- catch(e){/*ignore*/}
- }
- });
- __ptrMap.delete(this);
- __stmtMap.delete(this);
- capi.sqlite3_close_v2(pDb);
- if(this.onclose && (this.onclose.after instanceof Function)){
- try{this.onclose.after(this)}
- catch(e){/*ignore*/}
- }
- delete this.filename;
- }
- },
- /**
- Returns the number of changes, as per sqlite3_changes()
- (if the first argument is false) or sqlite3_total_changes()
- (if it's true). If the 2nd argument is true, it uses
- sqlite3_changes64() or sqlite3_total_changes64(), which
- will trigger an exception if this build does not have
- BigInt support enabled.
- */
- changes: function(total=false,sixtyFour=false){
- const p = affirmDbOpen(this).pointer;
- if(total){
- return sixtyFour
- ? capi.sqlite3_total_changes64(p)
- : capi.sqlite3_total_changes(p);
- }else{
- return sixtyFour
- ? capi.sqlite3_changes64(p)
- : capi.sqlite3_changes(p);
- }
- },
- /**
- Similar to the this.filename but returns the
- sqlite3_db_filename() value for the given database name,
- defaulting to "main". The argument may be either a JS string
- or a pointer to a WASM-allocated C-string.
- */
- dbFilename: function(dbName='main'){
- return capi.sqlite3_db_filename(affirmDbOpen(this).pointer, dbName);
- },
- /**
- Returns the name of the given 0-based db number, as documented
- for sqlite3_db_name().
- */
- dbName: function(dbNumber=0){
- return capi.sqlite3_db_name(affirmDbOpen(this).pointer, dbNumber);
- },
- /**
- Returns the name of the sqlite3_vfs used by the given database
- of this connection (defaulting to 'main'). The argument may be
- either a JS string or a WASM C-string. Returns undefined if the
- given db name is invalid. Throws if this object has been
- close()d.
- */
- dbVfsName: function(dbName=0){
- let rc;
- const pVfs = capi.sqlite3_js_db_vfs(
- affirmDbOpen(this).pointer, dbName
- );
- if(pVfs){
- const v = new capi.sqlite3_vfs(pVfs);
- try{ rc = wasm.cstrToJs(v.$zName) }
- finally { v.dispose() }
- }
- return rc;
- },
- /**
- Compiles the given SQL and returns a prepared Stmt. This is
- the only way to create new Stmt objects. Throws on error.
- The given SQL must be a string, a Uint8Array holding SQL, a
- WASM pointer to memory holding the NUL-terminated SQL string,
- or an array of strings. In the latter case, the array is
- concatenated together, with no separators, to form the SQL
- string (arrays are often a convenient way to formulate long
- statements). If the SQL contains no statements, an
- SQLite3Error is thrown.
- Design note: the C API permits empty SQL, reporting it as a 0
- result code and a NULL stmt pointer. Supporting that case here
- would cause extra work for all clients: any use of the Stmt API
- on such a statement will necessarily throw, so clients would be
- required to check `stmt.pointer` after calling `prepare()` in
- order to determine whether the Stmt instance is empty or not.
- Long-time practice (with other sqlite3 script bindings)
- suggests that the empty-prepare case is sufficiently rare that
- supporting it here would simply hurt overall usability.
- */
- prepare: function(sql){
- affirmDbOpen(this);
- const stack = wasm.pstack.pointer;
- let ppStmt, pStmt;
- try{
- ppStmt = wasm.pstack.alloc(8)/* output (sqlite3_stmt**) arg */;
- DB.checkRc(this, capi.sqlite3_prepare_v2(this.pointer, sql, -1, ppStmt, null));
- pStmt = wasm.peekPtr(ppStmt);
- }
- finally {
- wasm.pstack.restore(stack);
- }
- if(!pStmt) toss3("Cannot prepare empty SQL.");
- const stmt = new Stmt(this, pStmt, BindTypes);
- __stmtMap.get(this)[pStmt] = stmt;
- return stmt;
- },
- /**
- Executes one or more SQL statements in the form of a single
- string. Its arguments must be either (sql,optionsObject) or
- (optionsObject). In the latter case, optionsObject.sql must
- contain the SQL to execute. By default it returns this object
- but that can be changed via the `returnValue` option as
- described below. Throws on error.
- If no SQL is provided, or a non-string is provided, an
- exception is triggered. Empty SQL, on the other hand, is
- simply a no-op.
- The optional options object may contain any of the following
- properties:
- - `sql` = the SQL to run (unless it's provided as the first
- argument). This must be of type string, Uint8Array, or an array
- of strings. In the latter case they're concatenated together
- as-is, _with no separator_ between elements, before evaluation.
- The array form is often simpler for long hand-written queries.
- - `bind` = a single value valid as an argument for
- Stmt.bind(). This is _only_ applied to the _first_ non-empty
- statement in the SQL which has any bindable parameters. (Empty
- statements are skipped entirely.)
- - `saveSql` = an optional array. If set, the SQL of each
- executed statement is appended to this array before the
- statement is executed (but after it is prepared - we don't have
- the string until after that). Empty SQL statements are elided
- but can have odd effects in the output. e.g. SQL of: `"select
- 1; -- empty\n; select 2"` will result in an array containing
- `["select 1;", "--empty \n; select 2"]`. That's simply how
- sqlite3 records the SQL for the 2nd statement.
- ==================================================================
- The following options apply _only_ to the _first_ statement
- which has a non-zero result column count, regardless of whether
- the statement actually produces any result rows.
- ==================================================================
- - `columnNames`: if this is an array, the column names of the
- result set are stored in this array before the callback (if
- any) is triggered (regardless of whether the query produces any
- result rows). If no statement has result columns, this value is
- unchanged. Achtung: an SQL result may have multiple columns
- with identical names.
- - `callback` = a function which gets called for each row of the
- result set, but only if that statement has any result rows. The
- callback's "this" is the options object, noting that this
- function synthesizes one if the caller does not pass one to
- exec(). The second argument passed to the callback is always
- the current Stmt object, as it's needed if the caller wants to
- fetch the column names or some such (noting that they could
- also be fetched via `this.columnNames`, if the client provides
- the `columnNames` option). If the callback returns a literal
- `false` (as opposed to any other falsy value, e.g. an implicit
- `undefined` return), any ongoing statement-`step()` iteration
- stops without an error. The return value of the callback is
- otherwise ignored.
- ACHTUNG: The callback MUST NOT modify the Stmt object. Calling
- any of the Stmt.get() variants, Stmt.getColumnName(), or
- similar, is legal, but calling step() or finalize() is
- not. Member methods which are illegal in this context will
- trigger an exception, but clients must also refrain from using
- any lower-level (C-style) APIs which might modify the
- statement.
- The first argument passed to the callback defaults to an array of
- values from the current result row but may be changed with ...
- - `rowMode` = specifies the type of he callback's first argument.
- It may be any of...
- A) A string describing what type of argument should be passed
- as the first argument to the callback:
- A.1) `'array'` (the default) causes the results of
- `stmt.get([])` to be passed to the `callback` and/or appended
- to `resultRows`.
- A.2) `'object'` causes the results of
- `stmt.get(Object.create(null))` to be passed to the
- `callback` and/or appended to `resultRows`. Achtung: an SQL
- result may have multiple columns with identical names. In
- that case, the right-most column will be the one set in this
- object!
- A.3) `'stmt'` causes the current Stmt to be passed to the
- callback, but this mode will trigger an exception if
- `resultRows` is an array because appending the transient
- statement to the array would be downright unhelpful.
- B) An integer, indicating a zero-based column in the result
- row. Only that one single value will be passed on.
- C) A string with a minimum length of 2 and leading character of
- '$' will fetch the row as an object, extract that one field,
- and pass that field's value to the callback. Note that these
- keys are case-sensitive so must match the case used in the
- SQL. e.g. `"select a A from t"` with a `rowMode` of `'$A'`
- would work but `'$a'` would not. A reference to a column not in
- the result set will trigger an exception on the first row (as
- the check is not performed until rows are fetched). Note also
- that `$` is a legal identifier character in JS so need not be
- quoted.
- Any other `rowMode` value triggers an exception.
- - `resultRows`: if this is an array, it functions similarly to
- the `callback` option: each row of the result set (if any),
- with the exception that the `rowMode` 'stmt' is not legal. It
- is legal to use both `resultRows` and `callback`, but
- `resultRows` is likely much simpler to use for small data sets
- and can be used over a WebWorker-style message interface.
- exec() throws if `resultRows` is set and `rowMode` is 'stmt'.
- - `returnValue`: is a string specifying what this function
- should return:
- A) The default value is (usually) `"this"`, meaning that the
- DB object itself should be returned. The exception is if
- the caller passes neither of `callback` nor `returnValue`
- but does pass an explicit `rowMode` then the default
- `returnValue` is `"resultRows"`, described below.
- B) `"resultRows"` means to return the value of the
- `resultRows` option. If `resultRows` is not set, this
- function behaves as if it were set to an empty array.
- C) `"saveSql"` means to return the value of the
- `saveSql` option. If `saveSql` is not set, this
- function behaves as if it were set to an empty array.
- Potential TODOs:
- - `bind`: permit an array of arrays/objects to bind. The first
- sub-array would act on the first statement which has bindable
- parameters (as it does now). The 2nd would act on the next such
- statement, etc.
- - `callback` and `resultRows`: permit an array entries with
- semantics similar to those described for `bind` above.
- */
- exec: function(/*(sql [,obj]) || (obj)*/){
- affirmDbOpen(this);
- const arg = parseExecArgs(this, arguments);
- if(!arg.sql){
- return toss3("exec() requires an SQL string.");
- }
- const opt = arg.opt;
- const callback = opt.callback;
- const resultRows =
- Array.isArray(opt.resultRows) ? opt.resultRows : undefined;
- let stmt;
- let bind = opt.bind;
- let evalFirstResult = !!(
- arg.cbArg || opt.columnNames || resultRows
- ) /* true to step through the first result-returning statement */;
- const stack = wasm.scopedAllocPush();
- const saveSql = Array.isArray(opt.saveSql) ? opt.saveSql : undefined;
- try{
- const isTA = util.isSQLableTypedArray(arg.sql)
- /* Optimization: if the SQL is a TypedArray we can save some string
- conversion costs. */;
- /* Allocate the two output pointers (ppStmt, pzTail) and heap
- space for the SQL (pSql). When prepare_v2() returns, pzTail
- will point to somewhere in pSql. */
- let sqlByteLen = isTA ? arg.sql.byteLength : wasm.jstrlen(arg.sql);
- const ppStmt = wasm.scopedAlloc(
- /* output (sqlite3_stmt**) arg and pzTail */
- (2 * wasm.ptrSizeof) + (sqlByteLen + 1/* SQL + NUL */)
- );
- const pzTail = ppStmt + wasm.ptrSizeof /* final arg to sqlite3_prepare_v2() */;
- let pSql = pzTail + wasm.ptrSizeof;
- const pSqlEnd = pSql + sqlByteLen;
- if(isTA) wasm.heap8().set(arg.sql, pSql);
- else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false);
- wasm.poke(pSql + sqlByteLen, 0/*NUL terminator*/);
- while(pSql && wasm.peek(pSql, 'i8')
- /* Maintenance reminder:^^^ _must_ be 'i8' or else we
- will very likely cause an endless loop. What that's
- doing is checking for a terminating NUL byte. If we
- use i32 or similar then we read 4 bytes, read stuff
- around the NUL terminator, and get stuck in and
- endless loop at the end of the SQL, endlessly
- re-preparing an empty statement. */ ){
- wasm.pokePtr([ppStmt, pzTail], 0);
- DB.checkRc(this, capi.sqlite3_prepare_v3(
- this.pointer, pSql, sqlByteLen, 0, ppStmt, pzTail
- ));
- const pStmt = wasm.peekPtr(ppStmt);
- pSql = wasm.peekPtr(pzTail);
- sqlByteLen = pSqlEnd - pSql;
- if(!pStmt) continue;
- if(saveSql) saveSql.push(capi.sqlite3_sql(pStmt).trim());
- stmt = new Stmt(this, pStmt, BindTypes);
- if(bind && stmt.parameterCount){
- stmt.bind(bind);
- bind = null;
- }
- if(evalFirstResult && stmt.columnCount){
- /* Only forward SELECT-style results for the FIRST query
- in the SQL which potentially has them. */
- let gotColNames = Array.isArray(
- opt.columnNames
- /* As reported in
- https://sqlite.org/forum/forumpost/7774b773937cbe0a
- we need to delay fetching of the column names until
- after the first step() (if we step() at all) because
- a schema change between the prepare() and step(), via
- another connection, may invalidate the column count
- and names. */) ? 0 : 1;
- evalFirstResult = false;
- if(arg.cbArg || resultRows){
- const cbArgCache = Object.create(null)
- /* 2nd arg for arg.cbArg, used by (at least) row-to-object
- converter */;
- for( ; stmt.step(); __execLock.delete(stmt) ){
- if(0===gotColNames++){
- stmt.getColumnNames(cbArgCache.columnNames = (opt.columnNames || []));
- }
- __execLock.add(stmt);
- const row = arg.cbArg(stmt,cbArgCache);
- if(resultRows) resultRows.push(row);
- if(callback && false === callback.call(opt, row, stmt)){
- break;
- }
- }
- __execLock.delete(stmt);
- }
- if(0===gotColNames){
- /* opt.columnNames was provided but we visited no result rows */
- stmt.getColumnNames(opt.columnNames);
- }
- }else{
- stmt.step();
- }
- stmt.reset(
- /* In order to trigger an exception in the
- INSERT...RETURNING locking scenario:
- https://sqlite.org/forum/forumpost/36f7a2e7494897df
- */).finalize();
- stmt = null;
- }/*prepare() loop*/
- }/*catch(e){
- sqlite3.config.warn("DB.exec() is propagating exception",opt,e);
- throw e;
- }*/finally{
- wasm.scopedAllocPop(stack);
- if(stmt){
- __execLock.delete(stmt);
- stmt.finalize();
- }
- }
- return arg.returnVal();
- }/*exec()*/,
- /**
- Creates a new UDF (User-Defined Function) which is accessible
- via SQL code. This function may be called in any of the
- following forms:
- - (name, function)
- - (name, function, optionsObject)
- - (name, optionsObject)
- - (optionsObject)
- In the final two cases, the function must be defined as the
- `callback` property of the options object (optionally called
- `xFunc` to align with the C API documentation). In the final
- case, the function's name must be the 'name' property.
- The first two call forms can only be used for creating scalar
- functions. Creating an aggregate or window function requires
- the options-object form (see below for details).
- UDFs can be removed as documented for
- sqlite3_create_function_v2() and
- sqlite3_create_window_function(), but doing so will "leak" the
- JS-created WASM binding of those functions (meaning that their
- entries in the WASM indirect function table still
- exist). Eliminating that potential leak is a pending TODO.
- On success, returns this object. Throws on error.
- When called from SQL arguments to the UDF, and its result,
- will be converted between JS and SQL with as much fidelity as
- is feasible, triggering an exception if a type conversion
- cannot be determined. The docs for sqlite3_create_function_v2()
- describe the conversions in more detail.
- The values set in the options object differ for scalar and
- aggregate functions:
- - Scalar: set the `xFunc` function-type property to the UDF
- function.
- - Aggregate: set the `xStep` and `xFinal` function-type
- properties to the "step" and "final" callbacks for the
- aggregate. Do not set the `xFunc` property.
- - Window: set the `xStep`, `xFinal`, `xValue`, and `xInverse`
- function-type properties. Do not set the `xFunc` property.
- The options object may optionally have an `xDestroy`
- function-type property, as per sqlite3_create_function_v2().
- Its argument will be the WASM-pointer-type value of the `pApp`
- property, and this function will throw if `pApp` is defined but
- is not null, undefined, or a numeric (WASM pointer)
- value. i.e. `pApp`, if set, must be value suitable for use as a
- WASM pointer argument, noting that `null` or `undefined` will
- translate to 0 for that purpose.
- The options object may contain flags to modify how
- the function is defined:
- - `arity`: the number of arguments which SQL calls to this
- function expect or require. The default value is `xFunc.length`
- or `xStep.length` (i.e. the number of declared parameters it
- has) **MINUS 1** (see below for why). As a special case, if the
- `length` is 0, its arity is also 0 instead of -1. A negative
- arity value means that the function is variadic and may accept
- any number of arguments, up to sqlite3's compile-time
- limits. sqlite3 will enforce the argument count if is zero or
- greater. The callback always receives a pointer to an
- `sqlite3_context` object as its first argument. Any arguments
- after that are from SQL code. The leading context argument does
- _not_ count towards the function's arity. See the docs for
- sqlite3.capi.sqlite3_create_function_v2() for why that argument
- is needed in the interface.
- The following options-object properties correspond to flags
- documented at:
- https://sqlite.org/c3ref/create_function.html
- - `deterministic` = sqlite3.capi.SQLITE_DETERMINISTIC
- - `directOnly` = sqlite3.capi.SQLITE_DIRECTONLY
- - `innocuous` = sqlite3.capi.SQLITE_INNOCUOUS
- Sidebar: the ability to add new WASM-accessible functions to
- the runtime requires that the WASM build is compiled with the
- equivalent functionality as that provided by Emscripten's
- `-sALLOW_TABLE_GROWTH` flag.
- */
- createFunction: function f(name, xFunc, opt){
- const isFunc = (f)=>(f instanceof Function);
- switch(arguments.length){
- case 1: /* (optionsObject) */
- opt = name;
- name = opt.name;
- xFunc = opt.xFunc || 0;
- break;
- case 2: /* (name, callback|optionsObject) */
- if(!isFunc(xFunc)){
- opt = xFunc;
- xFunc = opt.xFunc || 0;
- }
- break;
- case 3: /* name, xFunc, opt */
- break;
- default: break;
- }
- if(!opt) opt = {};
- if('string' !== typeof name){
- toss3("Invalid arguments: missing function name.");
- }
- let xStep = opt.xStep || 0;
- let xFinal = opt.xFinal || 0;
- const xValue = opt.xValue || 0;
- const xInverse = opt.xInverse || 0;
- let isWindow = undefined;
- if(isFunc(xFunc)){
- isWindow = false;
- if(isFunc(xStep) || isFunc(xFinal)){
- toss3("Ambiguous arguments: scalar or aggregate?");
- }
- xStep = xFinal = null;
- }else if(isFunc(xStep)){
- if(!isFunc(xFinal)){
- toss3("Missing xFinal() callback for aggregate or window UDF.");
- }
- xFunc = null;
- }else if(isFunc(xFinal)){
- toss3("Missing xStep() callback for aggregate or window UDF.");
- }else{
- toss3("Missing function-type properties.");
- }
- if(false === isWindow){
- if(isFunc(xValue) || isFunc(xInverse)){
- toss3("xValue and xInverse are not permitted for non-window UDFs.");
- }
- }else if(isFunc(xValue)){
- if(!isFunc(xInverse)){
- toss3("xInverse must be provided if xValue is.");
- }
- isWindow = true;
- }else if(isFunc(xInverse)){
- toss3("xValue must be provided if xInverse is.");
- }
- const pApp = opt.pApp;
- if(undefined!==pApp &&
- null!==pApp &&
- (('number'!==typeof pApp) || !util.isInt32(pApp))){
- toss3("Invalid value for pApp property. Must be a legal WASM pointer value.");
- }
- const xDestroy = opt.xDestroy || 0;
- if(xDestroy && !isFunc(xDestroy)){
- toss3("xDestroy property must be a function.");
- }
- let fFlags = 0 /*flags for sqlite3_create_function_v2()*/;
- if(getOwnOption(opt, 'deterministic')) fFlags |= capi.SQLITE_DETERMINISTIC;
- if(getOwnOption(opt, 'directOnly')) fFlags |= capi.SQLITE_DIRECTONLY;
- if(getOwnOption(opt, 'innocuous')) fFlags |= capi.SQLITE_INNOCUOUS;
- name = name.toLowerCase();
- const xArity = xFunc || xStep;
- const arity = getOwnOption(opt, 'arity');
- const arityArg = ('number'===typeof arity
- ? arity
- : (xArity.length ? xArity.length-1/*for pCtx arg*/ : 0));
- let rc;
- if( isWindow ){
- rc = capi.sqlite3_create_window_function(
- this.pointer, name, arityArg,
- capi.SQLITE_UTF8 | fFlags, pApp || 0,
- xStep, xFinal, xValue, xInverse, xDestroy);
- }else{
- rc = capi.sqlite3_create_function_v2(
- this.pointer, name, arityArg,
- capi.SQLITE_UTF8 | fFlags, pApp || 0,
- xFunc, xStep, xFinal, xDestroy);
- }
- DB.checkRc(this, rc);
- return this;
- }/*createFunction()*/,
- /**
- Prepares the given SQL, step()s it one time, and returns
- the value of the first result column. If it has no results,
- undefined is returned.
- If passed a second argument, it is treated like an argument
- to Stmt.bind(), so may be any type supported by that
- function. Passing the undefined value is the same as passing
- no value, which is useful when...
- If passed a 3rd argument, it is expected to be one of the
- SQLITE_{typename} constants. Passing the undefined value is
- the same as not passing a value.
- Throws on error (e.g. malformed SQL).
- */
- selectValue: function(sql,bind,asType){
- return __selectFirstRow(this, sql, bind, 0, asType);
- },
- /**
- Runs the given query and returns an array of the values from
- the first result column of each row of the result set. The 2nd
- argument is an optional value for use in a single-argument call
- to Stmt.bind(). The 3rd argument may be any value suitable for
- use as the 2nd argument to Stmt.get(). If a 3rd argument is
- desired but no bind data are needed, pass `undefined` for the 2nd
- argument.
- If there are no result rows, an empty array is returned.
- */
- selectValues: function(sql,bind,asType){
- const stmt = this.prepare(sql), rc = [];
- try {
- stmt.bind(bind);
- while(stmt.step()) rc.push(stmt.get(0,asType));
- stmt.reset(/*for INSERT...RETURNING locking case*/);
- }finally{
- stmt.finalize();
- }
- return rc;
- },
- /**
- Prepares the given SQL, step()s it one time, and returns an
- array containing the values of the first result row. If it has
- no results, `undefined` is returned.
- If passed a second argument other than `undefined`, it is
- treated like an argument to Stmt.bind(), so may be any type
- supported by that function.
- Throws on error (e.g. malformed SQL).
- */
- selectArray: function(sql,bind){
- return __selectFirstRow(this, sql, bind, []);
- },
- /**
- Prepares the given SQL, step()s it one time, and returns an
- object containing the key/value pairs of the first result
- row. If it has no results, `undefined` is returned.
- Note that the order of returned object's keys is not guaranteed
- to be the same as the order of the fields in the query string.
- If passed a second argument other than `undefined`, it is
- treated like an argument to Stmt.bind(), so may be any type
- supported by that function.
- Throws on error (e.g. malformed SQL).
- */
- selectObject: function(sql,bind){
- return __selectFirstRow(this, sql, bind, {});
- },
- /**
- Runs the given SQL and returns an array of all results, with
- each row represented as an array, as per the 'array' `rowMode`
- option to `exec()`. An empty result set resolves
- to an empty array. The second argument, if any, is treated as
- the 'bind' option to a call to exec().
- */
- selectArrays: function(sql,bind){
- return __selectAll(this, sql, bind, 'array');
- },
- /**
- Works identically to selectArrays() except that each value
- in the returned array is an object, as per the 'object' `rowMode`
- option to `exec()`.
- */
- selectObjects: function(sql,bind){
- return __selectAll(this, sql, bind, 'object');
- },
- /**
- Returns the number of currently-opened Stmt handles for this db
- handle, or 0 if this DB instance is closed. Note that only
- handles prepared via this.prepare() are counted, and not
- handles prepared using capi.sqlite3_prepare_v3() (or
- equivalent).
- */
- openStatementCount: function(){
- return this.pointer ? Object.keys(__stmtMap.get(this)).length : 0;
- },
- /**
- Starts a transaction, calls the given callback, and then either
- rolls back or commits the transaction, depending on whether the
- callback throws. The callback is passed this db object as its
- only argument. On success, returns the result of the
- callback. Throws on error.
- Note that transactions may not be nested, so this will throw if
- it is called recursively. For nested transactions, use the
- savepoint() method or manually manage SAVEPOINTs using exec().
- If called with 2 arguments, the first must be a keyword which
- is legal immediately after a BEGIN statement, e.g. one of
- "DEFERRED", "IMMEDIATE", or "EXCLUSIVE". Though the exact list
- of supported keywords is not hard-coded here, in order to be
- future-compatible, if the argument does not look like a single
- keyword then an exception is triggered with a description of
- the problem.
- */
- transaction: function(/* [beginQualifier,] */callback){
- let opener = 'BEGIN';
- if(arguments.length>1){
- if(/[^a-zA-Z]/.test(arguments[0])){
- toss3(capi.SQLITE_MISUSE, "Invalid argument for BEGIN qualifier.");
- }
- opener += ' '+arguments[0];
- callback = arguments[1];
- }
- affirmDbOpen(this).exec(opener);
- try {
- const rc = callback(this);
- this.exec("COMMIT");
- return rc;
- }catch(e){
- this.exec("ROLLBACK");
- throw e;
- }
- },
- /**
- This works similarly to transaction() but uses sqlite3's SAVEPOINT
- feature. This function starts a savepoint (with an unspecified name)
- and calls the given callback function, passing it this db object.
- If the callback returns, the savepoint is released (committed). If
- the callback throws, the savepoint is rolled back. If it does not
- throw, it returns the result of the callback.
- */
- savepoint: function(callback){
- affirmDbOpen(this).exec("SAVEPOINT oo1");
- try {
- const rc = callback(this);
- this.exec("RELEASE oo1");
- return rc;
- }catch(e){
- this.exec("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1");
- throw e;
- }
- },
- /**
- A convenience form of DB.checkRc(this,resultCode). If it does
- not throw, it returns this object.
- */
- checkRc: function(resultCode){
- return checkSqlite3Rc(this, resultCode);
- }
- }/*DB.prototype*/;
- /** Throws if the given Stmt has been finalized, else stmt is
- returned. */
- const affirmStmtOpen = function(stmt){
- if(!stmt.pointer) toss3("Stmt has been closed.");
- return stmt;
- };
- /** Returns an opaque truthy value from the BindTypes
- enum if v's type is a valid bindable type, else
- returns a falsy value. As a special case, a value of
- undefined is treated as a bind type of null. */
- const isSupportedBindType = function(v){
- let t = BindTypes[(null===v||undefined===v) ? 'null' : typeof v];
- switch(t){
- case BindTypes.boolean:
- case BindTypes.null:
- case BindTypes.number:
- case BindTypes.string:
- return t;
- case BindTypes.bigint:
- if(wasm.bigIntEnabled) return t;
- /* else fall through */
- default:
- return util.isBindableTypedArray(v) ? BindTypes.blob : undefined;
- }
- };
- /**
- If isSupportedBindType(v) returns a truthy value, this
- function returns that value, else it throws.
- */
- const affirmSupportedBindType = function(v){
- //sqlite3.config.log('affirmSupportedBindType',v);
- return isSupportedBindType(v) || toss3("Unsupported bind() argument type:",typeof v);
- };
- /**
- If key is a number and within range of stmt's bound parameter
- count, key is returned.
- If key is not a number then it is checked against named
- parameters. If a match is found, its index is returned.
- Else it throws.
- */
- const affirmParamIndex = function(stmt,key){
- const n = ('number'===typeof key)
- ? key : capi.sqlite3_bind_parameter_index(stmt.pointer, key);
- if(0===n || !util.isInt32(n)){
- toss3("Invalid bind() parameter name: "+key);
- }
- else if(n<1 || n>stmt.parameterCount) toss3("Bind index",key,"is out of range.");
- return n;
- };
- /**
- Each Stmt object which is "locked" by DB.exec() gets an entry
- here to note that "lock".
- The reason this is in place is because exec({callback:...})'s
- callback gets access to the Stmt objects created internally by
- exec() but it must not use certain Stmt APIs.
- */
- const __execLock = new Set();
- /**
- This is a Stmt.get() counterpart of __execLock. Each time
- Stmt.step() returns true, the statement is added to this set,
- indicating that Stmt.get() is legal. Stmt APIs which invalidate
- that status remove the Stmt object from this set, which will
- cause Stmt.get() to throw with a descriptive error message
- instead of a more generic "API misuse" if we were to allow that
- call to reach the C API.
- */
- const __stmtMayGet = new Set();
- /**
- Stmt APIs which are prohibited on locked objects must call
- affirmNotLockedByExec() before doing any work.
- If __execLock.has(stmt) is truthy, this throws an exception
- complaining that the 2nd argument (an operation name,
- e.g. "bind()") is not legal while the statement is "locked".
- Locking happens before an exec()-like callback is passed a
- statement, to ensure that the callback does not mutate or
- finalize the statement. If it does not throw, it returns stmt.
- */
- const affirmNotLockedByExec = function(stmt,currentOpName){
- if(__execLock.has(stmt)){
- toss3("Operation is illegal when statement is locked:",currentOpName);
- }
- return stmt;
- };
- /**
- Binds a single bound parameter value on the given stmt at the
- given index (numeric or named) using the given bindType (see
- the BindTypes enum) and value. Throws on error. Returns stmt on
- success.
- */
- const bindOne = function f(stmt,ndx,bindType,val){
- affirmNotLockedByExec(affirmStmtOpen(stmt), 'bind()');
- if(!f._){
- f._tooBigInt = (v)=>toss3(
- "BigInt value is too big to store without precision loss:", v
- );
- f._ = {
- string: function(stmt, ndx, val, asBlob){
- const [pStr, n] = wasm.allocCString(val, true);
- const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text;
- return f(stmt.pointer, ndx, pStr, n, capi.SQLITE_WASM_DEALLOC);
- }
- };
- }/* static init */
- affirmSupportedBindType(val);
- ndx = affirmParamIndex(stmt,ndx);
- let rc = 0;
- switch((null===val || undefined===val) ? BindTypes.null : bindType){
- case BindTypes.null:
- rc = capi.sqlite3_bind_null(stmt.pointer, ndx);
- break;
- case BindTypes.string:
- rc = f._.string(stmt, ndx, val, false);
- break;
- case BindTypes.number: {
- let m;
- if(util.isInt32(val)) m = capi.sqlite3_bind_int;
- else if('bigint'===typeof val){
- if(!util.bigIntFits64(val)){
- f._tooBigInt(val);
- }else if(wasm.bigIntEnabled){
- m = capi.sqlite3_bind_int64;
- }else if(util.bigIntFitsDouble(val)){
- val = Number(val);
- m = capi.sqlite3_bind_double;
- }else{
- f._tooBigInt(val);
- }
- }else{ // !int32, !bigint
- val = Number(val);
- if(wasm.bigIntEnabled && Number.isInteger(val)){
- m = capi.sqlite3_bind_int64;
- }else{
- m = capi.sqlite3_bind_double;
- }
- }
- rc = m(stmt.pointer, ndx, val);
- break;
- }
- case BindTypes.boolean:
- rc = capi.sqlite3_bind_int(stmt.pointer, ndx, val ? 1 : 0);
- break;
- case BindTypes.blob: {
- if('string'===typeof val){
- rc = f._.string(stmt, ndx, val, true);
- break;
- }else if(val instanceof ArrayBuffer){
- val = new Uint8Array(val);
- }else if(!util.isBindableTypedArray(val)){
- toss3("Binding a value as a blob requires",
- "that it be a string, Uint8Array, Int8Array, or ArrayBuffer.");
- }
- const pBlob = wasm.alloc(val.byteLength || 1);
- wasm.heap8().set(val.byteLength ? val : [0], pBlob)
- rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength,
- capi.SQLITE_WASM_DEALLOC);
- break;
- }
- default:
- sqlite3.config.warn("Unsupported bind() argument type:",val);
- toss3("Unsupported bind() argument type: "+(typeof val));
- }
- if(rc) DB.checkRc(stmt.db.pointer, rc);
- return stmt;
- };
- Stmt.prototype = {
- /**
- "Finalizes" this statement. This is a no-op if the statement
- has already been finalized. Returns the result of
- sqlite3_finalize() (0 on success, non-0 on error), or the
- undefined value if the statement has already been
- finalized. Regardless of success or failure, most methods in
- this class will throw if called after this is.
- This method always throws if called when it is illegal to do
- so. Namely, when triggered via a per-row callback handler of a
- DB.exec() call.
- */
- finalize: function(){
- if(this.pointer){
- affirmNotLockedByExec(this,'finalize()');
- const rc = capi.sqlite3_finalize(this.pointer);
- delete __stmtMap.get(this.db)[this.pointer];
- __ptrMap.delete(this);
- __execLock.delete(this);
- __stmtMayGet.delete(this);
- delete this.parameterCount;
- delete this.db;
- return rc;
- }
- },
- /**
- Clears all bound values. Returns this object. Throws if this
- statement has been finalized or if modification of the
- statement is currently illegal (e.g. in the per-row callback of
- a DB.exec() call).
- */
- clearBindings: function(){
- affirmNotLockedByExec(affirmStmtOpen(this), 'clearBindings()')
- capi.sqlite3_clear_bindings(this.pointer);
- __stmtMayGet.delete(this);
- return this;
- },
- /**
- Resets this statement so that it may be step()ed again from the
- beginning. Returns this object. Throws if this statement has
- been finalized, if it may not legally be reset because it is
- currently being used from a DB.exec() callback, or if the
- underlying call to sqlite3_reset() returns non-0.
- If passed a truthy argument then this.clearBindings() is
- also called, otherwise any existing bindings, along with
- any memory allocated for them, are retained.
- In versions 3.42.0 and earlier, this function did not throw if
- sqlite3_reset() returns non-0, but it was discovered that
- throwing (or significant extra client-side code) is necessary
- in order to avoid certain silent failure scenarios, as
- discussed at:
- https://sqlite.org/forum/forumpost/36f7a2e7494897df
- */
- reset: function(alsoClearBinds){
- affirmNotLockedByExec(this,'reset()');
- if(alsoClearBinds) this.clearBindings();
- const rc = capi.sqlite3_reset(affirmStmtOpen(this).pointer);
- __stmtMayGet.delete(this);
- checkSqlite3Rc(this.db, rc);
- return this;
- },
- /**
- Binds one or more values to its bindable parameters. It
- accepts 1 or 2 arguments:
- If passed a single argument, it must be either an array, an
- object, or a value of a bindable type (see below).
- If passed 2 arguments, the first one is the 1-based bind
- index or bindable parameter name and the second one must be
- a value of a bindable type.
- Bindable value types:
- - null is bound as NULL.
- - undefined as a standalone value is a no-op intended to
- simplify certain client-side use cases: passing undefined as
- a value to this function will not actually bind anything and
- this function will skip confirmation that binding is even
- legal. (Those semantics simplify certain client-side uses.)
- Conversely, a value of undefined as an array or object
- property when binding an array/object (see below) is treated
- the same as null.
- - Numbers are bound as either doubles or integers: doubles if
- they are larger than 32 bits, else double or int32, depending
- on whether they have a fractional part. Booleans are bound as
- integer 0 or 1. It is not expected the distinction of binding
- doubles which have no fractional parts and integers is
- significant for the majority of clients due to sqlite3's data
- typing model. If BigInt support is enabled then this routine
- will bind BigInt values as 64-bit integers if they'll fit in
- 64 bits. If that support disabled, it will store the BigInt
- as an int32 or a double if it can do so without loss of
- precision. If the BigInt is _too BigInt_ then it will throw.
- - Strings are bound as strings (use bindAsBlob() to force
- blob binding).
- - Uint8Array, Int8Array, and ArrayBuffer instances are bound as
- blobs.
- If passed an array, each element of the array is bound at
- the parameter index equal to the array index plus 1
- (because arrays are 0-based but binding is 1-based).
- If passed an object, each object key is treated as a
- bindable parameter name. The object keys _must_ match any
- bindable parameter names, including any `$`, `@`, or `:`
- prefix. Because `$` is a legal identifier chararacter in
- JavaScript, that is the suggested prefix for bindable
- parameters: `stmt.bind({$a: 1, $b: 2})`.
- It returns this object on success and throws on
- error. Errors include:
- - Any bind index is out of range, a named bind parameter
- does not match, or this statement has no bindable
- parameters.
- - Any value to bind is of an unsupported type.
- - Passed no arguments or more than two.
- - The statement has been finalized.
- */
- bind: function(/*[ndx,] arg*/){
- affirmStmtOpen(this);
- let ndx, arg;
- switch(arguments.length){
- case 1: ndx = 1; arg = arguments[0]; break;
- case 2: ndx = arguments[0]; arg = arguments[1]; break;
- default: toss3("Invalid bind() arguments.");
- }
- if(undefined===arg){
- /* It might seem intuitive to bind undefined as NULL
- but this approach simplifies certain client-side
- uses when passing on arguments between 2+ levels of
- functions. */
- return this;
- }else if(!this.parameterCount){
- toss3("This statement has no bindable parameters.");
- }
- __stmtMayGet.delete(this);
- if(null===arg){
- /* bind NULL */
- return bindOne(this, ndx, BindTypes.null, arg);
- }
- else if(Array.isArray(arg)){
- /* bind each entry by index */
- if(1!==arguments.length){
- toss3("When binding an array, an index argument is not permitted.");
- }
- arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v));
- return this;
- }else if(arg instanceof ArrayBuffer){
- arg = new Uint8Array(arg);
- }
- if('object'===typeof arg/*null was checked above*/
- && !util.isBindableTypedArray(arg)){
- /* Treat each property of arg as a named bound parameter. */
- if(1!==arguments.length){
- toss3("When binding an object, an index argument is not permitted.");
- }
- Object.keys(arg)
- .forEach(k=>bindOne(this, k,
- affirmSupportedBindType(arg[k]),
- arg[k]));
- return this;
- }else{
- return bindOne(this, ndx, affirmSupportedBindType(arg), arg);
- }
- toss3("Should not reach this point.");
- },
- /**
- Special case of bind() which binds the given value using the
- BLOB binding mechanism instead of the default selected one for
- the value. The ndx may be a numbered or named bind index. The
- value must be of type string, null/undefined (both get treated
- as null), or a TypedArray of a type supported by the bind()
- API. This API cannot bind numbers as blobs.
- If passed a single argument, a bind index of 1 is assumed and
- the first argument is the value.
- */
- bindAsBlob: function(ndx,arg){
- affirmStmtOpen(this);
- if(1===arguments.length){
- arg = ndx;
- ndx = 1;
- }
- const t = affirmSupportedBindType(arg);
- if(BindTypes.string !== t && BindTypes.blob !== t
- && BindTypes.null !== t){
- toss3("Invalid value type for bindAsBlob()");
- }
- return bindOne(this, ndx, BindTypes.blob, arg);
- },
- /**
- Steps the statement one time. If the result indicates that a
- row of data is available, a truthy value is returned. If no
- row of data is available, a falsy value is returned. Throws on
- error.
- */
- step: function(){
- affirmNotLockedByExec(this, 'step()');
- const rc = capi.sqlite3_step(affirmStmtOpen(this).pointer);
- switch(rc){
- case capi.SQLITE_DONE:
- __stmtMayGet.delete(this);
- return false;
- case capi.SQLITE_ROW:
- __stmtMayGet.add(this);
- return true;
- default:
- __stmtMayGet.delete(this);
- sqlite3.config.warn("sqlite3_step() rc=",rc,
- capi.sqlite3_js_rc_str(rc),
- "SQL =", capi.sqlite3_sql(this.pointer));
- DB.checkRc(this.db.pointer, rc);
- }
- },
- /**
- Functions exactly like step() except that...
- 1) On success, it calls this.reset() and returns this object.
- 2) On error, it throws and does not call reset().
- This is intended to simplify constructs like:
- ```
- for(...) {
- stmt.bind(...).stepReset();
- }
- ```
- Note that the reset() call makes it illegal to call this.get()
- after the step.
- */
- stepReset: function(){
- this.step();
- return this.reset();
- },
- /**
- Functions like step() except that it calls finalize() on this
- statement immediately after stepping, even if the step() call
- throws.
- On success, it returns true if the step indicated that a row of
- data was available, else it returns a falsy value.
- This is intended to simplify use cases such as:
- ```
- aDb.prepare("insert into foo(a) values(?)").bind(123).stepFinalize();
- ```
- */
- stepFinalize: function(){
- try{
- const rc = this.step();
- this.reset(/*for INSERT...RETURNING locking case*/);
- return rc;
- }finally{
- try{this.finalize()}
- catch(e){/*ignored*/}
- }
- },
- /**
- Fetches the value from the given 0-based column index of
- the current data row, throwing if index is out of range.
- Requires that step() has just returned a truthy value, else
- an exception is thrown.
- By default it will determine the data type of the result
- automatically. If passed a second argument, it must be one
- of the enumeration values for sqlite3 types, which are
- defined as members of the sqlite3 module: SQLITE_INTEGER,
- SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB. Any other value,
- except for undefined, will trigger an exception. Passing
- undefined is the same as not passing a value. It is legal
- to, e.g., fetch an integer value as a string, in which case
- sqlite3 will convert the value to a string.
- If ndx is an array, this function behaves a differently: it
- assigns the indexes of the array, from 0 to the number of
- result columns, to the values of the corresponding column,
- and returns that array.
- If ndx is a plain object, this function behaves even
- differentlier: it assigns the properties of the object to
- the values of their corresponding result columns and returns
- that object.
- Blobs are returned as Uint8Array instances.
- Potential TODO: add type ID SQLITE_JSON, which fetches the
- result as a string and passes it (if it's not null) to
- JSON.parse(), returning the result of that. Until then,
- getJSON() can be used for that.
- */
- get: function(ndx,asType){
- if(!__stmtMayGet.has(affirmStmtOpen(this))){
- toss3("Stmt.step() has not (recently) returned true.");
- }
- if(Array.isArray(ndx)){
- let i = 0;
- const n = this.columnCount;
- while(i<n){
- ndx[i] = this.get(i++);
- }
- return ndx;
- }else if(ndx && 'object'===typeof ndx){
- let i = 0;
- const n = this.columnCount;
- while(i<n){
- ndx[capi.sqlite3_column_name(this.pointer,i)] = this.get(i++);
- }
- return ndx;
- }
- affirmColIndex(this, ndx);
- switch(undefined===asType
- ? capi.sqlite3_column_type(this.pointer, ndx)
- : asType){
- case capi.SQLITE_NULL: return null;
- case capi.SQLITE_INTEGER:{
- if(wasm.bigIntEnabled){
- const rc = capi.sqlite3_column_int64(this.pointer, ndx);
- if(rc>=Number.MIN_SAFE_INTEGER && rc<=Number.MAX_SAFE_INTEGER){
- /* Coerce "normal" number ranges to normal number values,
- and only return BigInt-type values for numbers out of this
- range. */
- return Number(rc).valueOf();
- }
- return rc;
- }else{
- const rc = capi.sqlite3_column_double(this.pointer, ndx);
- if(rc>Number.MAX_SAFE_INTEGER || rc<Number.MIN_SAFE_INTEGER){
- /* Throwing here is arguable but, since we're explicitly
- extracting an SQLITE_INTEGER-type value, it seems fair to throw
- if the extracted number is out of range for that type.
- This policy may be laxened to simply pass on the number and
- hope for the best, as the C API would do. */
- toss3("Integer is out of range for JS integer range: "+rc);
- }
- //sqlite3.config.log("get integer rc=",rc,isInt32(rc));
- return util.isInt32(rc) ? (rc | 0) : rc;
- }
- }
- case capi.SQLITE_FLOAT:
- return capi.sqlite3_column_double(this.pointer, ndx);
- case capi.SQLITE_TEXT:
- return capi.sqlite3_column_text(this.pointer, ndx);
- case capi.SQLITE_BLOB: {
- const n = capi.sqlite3_column_bytes(this.pointer, ndx),
- ptr = capi.sqlite3_column_blob(this.pointer, ndx),
- rc = new Uint8Array(n);
- //heap = n ? wasm.heap8() : false;
- if(n) rc.set(wasm.heap8u().slice(ptr, ptr+n), 0);
- //for(let i = 0; i < n; ++i) rc[i] = heap[ptr + i];
- if(n && this.db._blobXfer instanceof Array){
- /* This is an optimization soley for the
- Worker-based API. These values will be
- transfered to the main thread directly
- instead of being copied. */
- this.db._blobXfer.push(rc.buffer);
- }
- return rc;
- }
- default: toss3("Don't know how to translate",
- "type of result column #"+ndx+".");
- }
- toss3("Not reached.");
- },
- /** Equivalent to get(ndx) but coerces the result to an
- integer. */
- getInt: function(ndx){return this.get(ndx,capi.SQLITE_INTEGER)},
- /** Equivalent to get(ndx) but coerces the result to a
- float. */
- getFloat: function(ndx){return this.get(ndx,capi.SQLITE_FLOAT)},
- /** Equivalent to get(ndx) but coerces the result to a
- string. */
- getString: function(ndx){return this.get(ndx,capi.SQLITE_TEXT)},
- /** Equivalent to get(ndx) but coerces the result to a
- Uint8Array. */
- getBlob: function(ndx){return this.get(ndx,capi.SQLITE_BLOB)},
- /**
- A convenience wrapper around get() which fetches the value
- as a string and then, if it is not null, passes it to
- JSON.parse(), returning that result. Throws if parsing
- fails. If the result is null, null is returned. An empty
- string, on the other hand, will trigger an exception.
- */
- getJSON: function(ndx){
- const s = this.get(ndx, capi.SQLITE_STRING);
- return null===s ? s : JSON.parse(s);
- },
- // Design note: the only reason most of these getters have a 'get'
- // prefix is for consistency with getVALUE_TYPE(). The latter
- // arguably really need that prefix for API readability and the
- // rest arguably don't, but consistency is a powerful thing.
- /**
- Returns the result column name of the given index, or
- throws if index is out of bounds or this statement has been
- finalized. This can be used without having run step()
- first.
- */
- getColumnName: function(ndx){
- return capi.sqlite3_column_name(
- affirmColIndex(affirmStmtOpen(this),ndx).pointer, ndx
- );
- },
- /**
- If this statement potentially has result columns, this function
- returns an array of all such names. If passed an array, it is
- used as the target and all names are appended to it. Returns
- the target array. Throws if this statement cannot have result
- columns. This object's columnCount property holds the number of
- columns.
- */
- getColumnNames: function(tgt=[]){
- affirmColIndex(affirmStmtOpen(this),0);
- const n = this.columnCount;
- for(let i = 0; i < n; ++i){
- tgt.push(capi.sqlite3_column_name(this.pointer, i));
- }
- return tgt;
- },
- /**
- If this statement has named bindable parameters and the
- given name matches one, its 1-based bind index is
- returned. If no match is found, 0 is returned. If it has no
- bindable parameters, the undefined value is returned.
- */
- getParamIndex: function(name){
- return (affirmStmtOpen(this).parameterCount
- ? capi.sqlite3_bind_parameter_index(this.pointer, name)
- : undefined);
- },
- /**
- If this statement has named bindable parameters and the given
- index refers to one, its name is returned, else null is
- returned. If this statement has no bound parameters, undefined
- is returned.
- Added in 3.47.
- */
- getParamName: function(ndx){
- return (affirmStmtOpen(this).parameterCount
- ? capi.sqlite3_bind_parameter_name(this.pointer, ndx)
- : undefined);
- },
- /**
- Behaves like sqlite3_stmt_busy() but throws if this statement
- is closed and returns a value of type boolean instead of integer.
- Added in 3.47.
- */
- isBusy: function(){
- return 0!==capi.sqlite3_stmt_busy(affirmStmtOpen(this));
- },
- /**
- Behaves like sqlite3_stmt_readonly() but throws if this statement
- is closed and returns a value of type boolean instead of integer.
- Added in 3.47.
- */
- isReadOnly: function(){
- return 0!==capi.sqlite3_stmt_readonly(affirmStmtOpen(this));
- }
- }/*Stmt.prototype*/;
- {/* Add the `pointer` property to DB and Stmt. */
- const prop = {
- enumerable: true,
- get: function(){return __ptrMap.get(this)},
- set: ()=>toss3("The pointer property is read-only.")
- }
- Object.defineProperty(Stmt.prototype, 'pointer', prop);
- Object.defineProperty(DB.prototype, 'pointer', prop);
- }
- /**
- Stmt.columnCount is an interceptor for sqlite3_column_count().
- This requires an unfortunate performance hit compared to caching
- columnCount when the Stmt is created/prepared (as was done in
- SQLite <=3.42.0), but is necessary in order to handle certain
- corner cases, as described in
- https://sqlite.org/forum/forumpost/7774b773937cbe0a.
- */
- Object.defineProperty(Stmt.prototype, 'columnCount', {
- enumerable: false,
- get: function(){return capi.sqlite3_column_count(this.pointer)},
- set: ()=>toss3("The columnCount property is read-only.")
- });
- /** The OO API's public namespace. */
- sqlite3.oo1 = {
- DB,
- Stmt
- }/*oo1 object*/;
- if(util.isUIThread()){
- /**
- Functionally equivalent to DB(storageName,'c','kvvfs') except
- that it throws if the given storage name is not one of 'local'
- or 'session'.
- As of version 3.46, the argument may optionally be an options
- object in the form:
- {
- filename: 'session'|'local',
- ... etc. (all options supported by the DB ctor)
- }
- noting that the 'vfs' option supported by main DB
- constructor is ignored here: the vfs is always 'kvvfs'.
- */
- sqlite3.oo1.JsStorageDb = function(storageName='session'){
- const opt = dbCtorHelper.normalizeArgs(...arguments);
- storageName = opt.filename;
- if('session'!==storageName && 'local'!==storageName){
- toss3("JsStorageDb db name must be one of 'session' or 'local'.");
- }
- opt.vfs = 'kvvfs';
- dbCtorHelper.call(this, opt);
- };
- const jdb = sqlite3.oo1.JsStorageDb;
- jdb.prototype = Object.create(DB.prototype);
- /** Equivalent to sqlite3_js_kvvfs_clear(). */
- jdb.clearStorage = capi.sqlite3_js_kvvfs_clear;
- /**
- Clears this database instance's storage or throws if this
- instance has been closed. Returns the number of
- database blocks which were cleaned up.
- */
- jdb.prototype.clearStorage = function(){
- return jdb.clearStorage(affirmDbOpen(this).filename);
- };
- /** Equivalent to sqlite3_js_kvvfs_size(). */
- jdb.storageSize = capi.sqlite3_js_kvvfs_size;
- /**
- Returns the _approximate_ number of bytes this database takes
- up in its storage or throws if this instance has been closed.
- */
- jdb.prototype.storageSize = function(){
- return jdb.storageSize(affirmDbOpen(this).filename);
- };
- }/*main-window-only bits*/
- });
- //#else
- /* Built with the omit-oo1 flag. */
- //#endif ifnot omit-oo1
|