SQLTester.mjs 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348
  1. /*
  2. ** 2023-08-29
  3. **
  4. ** The author disclaims copyright to this source code. In place of
  5. ** a legal notice, here is a blessing:
  6. **
  7. ** May you do good and not evil.
  8. ** May you find forgiveness for yourself and forgive others.
  9. ** May you share freely, never taking more than you give.
  10. **
  11. *************************************************************************
  12. ** This file contains the main application entry pointer for the JS
  13. ** implementation of the SQLTester framework.
  14. **
  15. ** This version is not well-documented because it's a direct port of
  16. ** the Java implementation, which is documented: in the main SQLite3
  17. ** source tree, see ext/jni/src/org/sqlite/jni/capi/SQLTester.java.
  18. */
  19. import sqlite3ApiInit from '/jswasm/sqlite3.mjs';
  20. const sqlite3 = await sqlite3ApiInit();
  21. const log = (...args)=>{
  22. console.log('SQLTester:',...args);
  23. };
  24. /**
  25. Try to install vfsName as the new default VFS. Once this succeeds
  26. (returns true) then it becomes a no-op on future calls. Throws if
  27. VFS registration as the default VFS fails but has no side effects
  28. if vfsName is not currently registered.
  29. */
  30. const tryInstallVfs = function f(vfsName){
  31. if(f.vfsName) return false;
  32. const pVfs = sqlite3.capi.sqlite3_vfs_find(vfsName);
  33. if(pVfs){
  34. log("Installing",'"'+vfsName+'"',"as default VFS.");
  35. const rc = sqlite3.capi.sqlite3_vfs_register(pVfs, 1);
  36. if(rc){
  37. sqlite3.SQLite3Error.toss(rc,"While trying to register",vfsName,"vfs.");
  38. }
  39. f.vfsName = vfsName;
  40. }
  41. return !!pVfs;
  42. };
  43. tryInstallVfs.vfsName = undefined;
  44. if( 0 && globalThis.WorkerGlobalScope ){
  45. // Try OPFS storage, if available...
  46. if( 1 && sqlite3.oo1.OpfsDb ){
  47. /* Really slow with these tests */
  48. tryInstallVfs("opfs");
  49. }else if( sqlite3.installOpfsSAHPoolVfs ){
  50. await sqlite3.installOpfsSAHPoolVfs({
  51. clearOnInit: true,
  52. initialCapacity: 15,
  53. name: 'opfs-SQLTester'
  54. }).then(pool=>{
  55. tryInstallVfs(pool.vfsName);
  56. }).catch(e=>{
  57. log("OpfsSAHPool could not load:",e);
  58. });
  59. }
  60. }
  61. const wPost = (function(){
  62. return (('undefined'===typeof WorkerGlobalScope)
  63. ? ()=>{}
  64. : (type, payload)=>{
  65. postMessage({type, payload});
  66. });
  67. })();
  68. //log("WorkerGlobalScope",globalThis.WorkerGlobalScope);
  69. // Return a new enum entry value
  70. const newE = ()=>Object.create(null);
  71. const newObj = (props)=>Object.assign(newE(), props);
  72. /**
  73. Modes for how to escape (or not) column values and names from
  74. SQLTester.execSql() to the result buffer output.
  75. */
  76. const ResultBufferMode = Object.assign(Object.create(null),{
  77. //! Do not append to result buffer
  78. NONE: newE(),
  79. //! Append output escaped.
  80. ESCAPED: newE(),
  81. //! Append output as-is
  82. ASIS: newE()
  83. });
  84. /**
  85. Modes to specify how to emit multi-row output from
  86. SQLTester.execSql() to the result buffer.
  87. */
  88. const ResultRowMode = newObj({
  89. //! Keep all result rows on one line, space-separated.
  90. ONLINE: newE(),
  91. //! Add a newline between each result row.
  92. NEWLINE: newE()
  93. });
  94. class SQLTesterException extends globalThis.Error {
  95. constructor(testScript, ...args){
  96. if(testScript){
  97. super( [testScript.getOutputPrefix()+": ", ...args].join('') );
  98. }else{
  99. super( args.join('') );
  100. }
  101. this.name = 'SQLTesterException';
  102. }
  103. isFatal() { return false; }
  104. }
  105. SQLTesterException.toss = (...args)=>{
  106. throw new SQLTesterException(...args);
  107. }
  108. class DbException extends SQLTesterException {
  109. constructor(testScript, pDb, rc, closeDb=false){
  110. super(testScript, "DB error #"+rc+": "+sqlite3.capi.sqlite3_errmsg(pDb));
  111. this.name = 'DbException';
  112. if( closeDb ) sqlite3.capi.sqlite3_close_v2(pDb);
  113. }
  114. isFatal() { return true; }
  115. }
  116. class TestScriptFailed extends SQLTesterException {
  117. constructor(testScript, ...args){
  118. super(testScript,...args);
  119. this.name = 'TestScriptFailed';
  120. }
  121. isFatal() { return true; }
  122. }
  123. class UnknownCommand extends SQLTesterException {
  124. constructor(testScript, cmdName){
  125. super(testScript, cmdName);
  126. this.name = 'UnknownCommand';
  127. }
  128. isFatal() { return true; }
  129. }
  130. class IncompatibleDirective extends SQLTesterException {
  131. constructor(testScript, ...args){
  132. super(testScript,...args);
  133. this.name = 'IncompatibleDirective';
  134. }
  135. }
  136. //! For throwing where an expression is required.
  137. const toss = (errType, ...args)=>{
  138. throw new errType(...args);
  139. };
  140. const __utf8Decoder = new TextDecoder();
  141. const __utf8Encoder = new TextEncoder('utf-8');
  142. //! Workaround for Util.utf8Decode()
  143. const __SAB = ('undefined'===typeof globalThis.SharedArrayBuffer)
  144. ? function(){} : globalThis.SharedArrayBuffer;
  145. /* Frequently-reused regexes. */
  146. const Rx = newObj({
  147. requiredProperties: / REQUIRED_PROPERTIES:[ \t]*(\S.*)\s*$/,
  148. scriptModuleName: / SCRIPT_MODULE_NAME:[ \t]*(\S+)\s*$/,
  149. mixedModuleName: / ((MIXED_)?MODULE_NAME):[ \t]*(\S+)\s*$/,
  150. command: /^--(([a-z-]+)( .*)?)$/,
  151. //! "Special" characters - we have to escape output if it contains any.
  152. special: /[\x00-\x20\x22\x5c\x7b\x7d]/,
  153. squiggly: /[{}]/
  154. });
  155. const Util = newObj({
  156. toss,
  157. unlink: function f(fn){
  158. if(!f.unlink){
  159. f.unlink = sqlite3.wasm.xWrap('sqlite3__wasm_vfs_unlink','int',
  160. ['*','string']);
  161. }
  162. return 0==f.unlink(0,fn);
  163. },
  164. argvToString: (list)=>{
  165. const m = [...list];
  166. m.shift() /* strip command name */;
  167. return m.join(" ")
  168. },
  169. utf8Decode: function(arrayBuffer, begin, end){
  170. return __utf8Decoder.decode(
  171. (arrayBuffer.buffer instanceof __SAB)
  172. ? arrayBuffer.slice(begin, end)
  173. : arrayBuffer.subarray(begin, end)
  174. );
  175. },
  176. utf8Encode: (str)=>__utf8Encoder.encode(str),
  177. strglob: sqlite3.wasm.xWrap('sqlite3__wasm_SQLTester_strglob','int',
  178. ['string','string'])
  179. })/*Util*/;
  180. /**
  181. Output logger utility.
  182. */
  183. class Outer {
  184. #lnBuf = [];
  185. #verbosity = 0;
  186. #logger = console.log.bind(console);
  187. constructor(func){
  188. if(func) this.setFunc(func);
  189. }
  190. logger(...args){
  191. if(args.length){
  192. this.#logger = args[0];
  193. return this;
  194. }
  195. return this.#logger;
  196. }
  197. out(...args){
  198. if( this.getOutputPrefix && !this.#lnBuf.length ){
  199. this.#lnBuf.push(this.getOutputPrefix());
  200. }
  201. this.#lnBuf.push(...args);
  202. return this;
  203. }
  204. #outlnImpl(vLevel, ...args){
  205. if( this.getOutputPrefix && !this.#lnBuf.length ){
  206. this.#lnBuf.push(this.getOutputPrefix());
  207. }
  208. this.#lnBuf.push(...args,'\n');
  209. const msg = this.#lnBuf.join('');
  210. this.#lnBuf.length = 0;
  211. this.#logger(msg);
  212. return this;
  213. }
  214. outln(...args){
  215. return this.#outlnImpl(0,...args);
  216. }
  217. outputPrefix(){
  218. if( 0==arguments.length ){
  219. return (this.getOutputPrefix
  220. ? (this.getOutputPrefix() ?? '') : '');
  221. }else{
  222. this.getOutputPrefix = arguments[0];
  223. return this;
  224. }
  225. }
  226. static #verboseLabel = ["🔈",/*"🔉",*/"🔊","📢"];
  227. verboseN(lvl, args){
  228. if( this.#verbosity>=lvl ){
  229. this.#outlnImpl(lvl, Outer.#verboseLabel[lvl-1],': ',...args);
  230. }
  231. }
  232. verbose1(...args){ return this.verboseN(1,args); }
  233. verbose2(...args){ return this.verboseN(2,args); }
  234. verbose3(...args){ return this.verboseN(3,args); }
  235. verbosity(){
  236. const rc = this.#verbosity;
  237. if(arguments.length) this.#verbosity = +arguments[0];
  238. return rc;
  239. }
  240. }/*Outer*/
  241. class SQLTester {
  242. //! Console output utility.
  243. #outer = new Outer().outputPrefix( ()=>'SQLTester: ' );
  244. //! List of input scripts.
  245. #aScripts = [];
  246. //! Test input buffer.
  247. #inputBuffer = [];
  248. //! Test result buffer.
  249. #resultBuffer = [];
  250. //! Output representation of SQL NULL.
  251. #nullView;
  252. metrics = newObj({
  253. //! Total tests run
  254. nTotalTest: 0,
  255. //! Total test script files run
  256. nTestFile: 0,
  257. //! Test-case count for to the current TestScript
  258. nTest: 0,
  259. //! Names of scripts which were aborted.
  260. failedScripts: []
  261. });
  262. #emitColNames = false;
  263. //! True to keep going regardless of how a test fails.
  264. #keepGoing = false;
  265. #db = newObj({
  266. //! The list of available db handles.
  267. list: new Array(7),
  268. //! Index into this.list of the current db.
  269. iCurrentDb: 0,
  270. //! Name of the default db, re-created for each script.
  271. initialDbName: "test.db",
  272. //! Buffer for REQUIRED_PROPERTIES pragmas.
  273. initSql: ['select 1;'],
  274. //! (sqlite3*) to the current db.
  275. currentDb: function(){
  276. return this.list[this.iCurrentDb];
  277. }
  278. });
  279. constructor(){
  280. this.reset();
  281. }
  282. outln(...args){ return this.#outer.outln(...args); }
  283. out(...args){ return this.#outer.out(...args); }
  284. outer(...args){
  285. if(args.length){
  286. this.#outer = args[0];
  287. return this;
  288. }
  289. return this.#outer;
  290. }
  291. verbose1(...args){ return this.#outer.verboseN(1,args); }
  292. verbose2(...args){ return this.#outer.verboseN(2,args); }
  293. verbose3(...args){ return this.#outer.verboseN(3,args); }
  294. verbosity(...args){
  295. const rc = this.#outer.verbosity(...args);
  296. return args.length ? this : rc;
  297. }
  298. setLogger(func){
  299. this.#outer.logger(func);
  300. return this;
  301. }
  302. incrementTestCounter(){
  303. ++this.metrics.nTotalTest;
  304. ++this.metrics.nTest;
  305. }
  306. reset(){
  307. this.clearInputBuffer();
  308. this.clearResultBuffer();
  309. this.#clearBuffer(this.#db.initSql);
  310. this.closeAllDbs();
  311. this.metrics.nTest = 0;
  312. this.#nullView = "nil";
  313. this.#emitColNames = false;
  314. this.#db.iCurrentDb = 0;
  315. //this.#db.initSql.push("SELECT 1;");
  316. }
  317. appendInput(line, addNL){
  318. this.#inputBuffer.push(line);
  319. if( addNL ) this.#inputBuffer.push('\n');
  320. }
  321. appendResult(line, addNL){
  322. this.#resultBuffer.push(line);
  323. if( addNL ) this.#resultBuffer.push('\n');
  324. }
  325. appendDbInitSql(sql){
  326. this.#db.initSql.push(sql);
  327. if( this.currentDb() ){
  328. this.execSql(null, true, ResultBufferMode.NONE, null, sql);
  329. }
  330. }
  331. #runInitSql(pDb){
  332. let rc = 0;
  333. for(const sql of this.#db.initSql){
  334. this.#outer.verbose2("RUNNING DB INIT CODE: ",sql);
  335. rc = this.execSql(pDb, false, ResultBufferMode.NONE, null, sql);
  336. if( rc ) break;
  337. }
  338. return rc;
  339. }
  340. #clearBuffer(buffer){
  341. buffer.length = 0;
  342. return buffer;
  343. }
  344. clearInputBuffer(){ return this.#clearBuffer(this.#inputBuffer); }
  345. clearResultBuffer(){return this.#clearBuffer(this.#resultBuffer); }
  346. getInputText(){ return this.#inputBuffer.join(''); }
  347. getResultText(){ return this.#resultBuffer.join(''); }
  348. #takeBuffer(buffer){
  349. const s = buffer.join('');
  350. buffer.length = 0;
  351. return s;
  352. }
  353. takeInputBuffer(){
  354. return this.#takeBuffer(this.#inputBuffer);
  355. }
  356. takeResultBuffer(){
  357. return this.#takeBuffer(this.#resultBuffer);
  358. }
  359. nullValue(){
  360. return (0==arguments.length)
  361. ? this.#nullView
  362. : (this.#nullView = ''+arguments[0]);
  363. }
  364. outputColumnNames(){
  365. return (0==arguments.length)
  366. ? this.#emitColNames
  367. : (this.#emitColNames = !!arguments[0]);
  368. }
  369. currentDbId(){
  370. return (0==arguments.length)
  371. ? this.#db.iCurrentDb
  372. : (this.#affirmDbId(arguments[0]).#db.iCurrentDb = arguments[0]);
  373. }
  374. #affirmDbId(id){
  375. if(id<0 || id>=this.#db.list.length){
  376. toss(SQLTesterException, "Database index ",id," is out of range.");
  377. }
  378. return this;
  379. }
  380. currentDb(...args){
  381. if( 0!=args.length ){
  382. this.#affirmDbId(id).#db.iCurrentDb = id;
  383. }
  384. return this.#db.currentDb();
  385. }
  386. getDbById(id){
  387. return this.#affirmDbId(id).#db.list[id];
  388. }
  389. getCurrentDb(){ return this.#db.list[this.#db.iCurrentDb]; }
  390. closeDb(id) {
  391. if( 0==arguments.length ){
  392. id = this.#db.iCurrentDb;
  393. }
  394. const pDb = this.#affirmDbId(id).#db.list[id];
  395. if( pDb ){
  396. sqlite3.capi.sqlite3_close_v2(pDb);
  397. this.#db.list[id] = null;
  398. }
  399. }
  400. closeAllDbs(){
  401. for(let i = 0; i<this.#db.list.length; ++i){
  402. if(this.#db.list[i]){
  403. sqlite3.capi.sqlite3_close_v2(this.#db.list[i]);
  404. this.#db.list[i] = null;
  405. }
  406. }
  407. this.#db.iCurrentDb = 0;
  408. }
  409. openDb(name, createIfNeeded){
  410. if( 3===arguments.length ){
  411. const slot = arguments[0];
  412. this.#affirmDbId(slot).#db.iCurrentDb = slot;
  413. name = arguments[1];
  414. createIfNeeded = arguments[2];
  415. }
  416. this.closeDb();
  417. const capi = sqlite3.capi, wasm = sqlite3.wasm;
  418. let pDb = 0;
  419. let flags = capi.SQLITE_OPEN_READWRITE;
  420. if( createIfNeeded ) flags |= capi.SQLITE_OPEN_CREATE;
  421. try{
  422. let rc;
  423. wasm.pstack.call(function(){
  424. let ppOut = wasm.pstack.allocPtr();
  425. rc = sqlite3.capi.sqlite3_open_v2(name, ppOut, flags, null);
  426. pDb = wasm.peekPtr(ppOut);
  427. });
  428. let sql;
  429. if( 0==rc && this.#db.initSql.length > 0){
  430. rc = this.#runInitSql(pDb);
  431. }
  432. if( 0!=rc ){
  433. sqlite3.SQLite3Error.toss(
  434. rc,
  435. "sqlite3 result code",rc+":",
  436. (pDb ? sqlite3.capi.sqlite3_errmsg(pDb)
  437. : sqlite3.capi.sqlite3_errstr(rc))
  438. );
  439. }
  440. return this.#db.list[this.#db.iCurrentDb] = pDb;
  441. }catch(e){
  442. sqlite3.capi.sqlite3_close_v2(pDb);
  443. throw e;
  444. }
  445. }
  446. addTestScript(ts){
  447. if( 2===arguments.length ){
  448. ts = new TestScript(arguments[0], arguments[1]);
  449. }else if(ts instanceof Uint8Array){
  450. ts = new TestScript('<unnamed>', ts);
  451. }else if('string' === typeof arguments[1]){
  452. ts = new TestScript('<unnamed>', Util.utf8Encode(arguments[1]));
  453. }
  454. if( !(ts instanceof TestScript) ){
  455. Util.toss(SQLTesterException, "Invalid argument type for addTestScript()");
  456. }
  457. this.#aScripts.push(ts);
  458. return this;
  459. }
  460. runTests(){
  461. const tStart = (new Date()).getTime();
  462. let isVerbose = this.verbosity();
  463. this.metrics.failedScripts.length = 0;
  464. this.metrics.nTotalTest = 0;
  465. this.metrics.nTestFile = 0;
  466. for(const ts of this.#aScripts){
  467. this.reset();
  468. ++this.metrics.nTestFile;
  469. let threw = false;
  470. const timeStart = (new Date()).getTime();
  471. let msgTail = '';
  472. try{
  473. ts.run(this);
  474. }catch(e){
  475. if(e instanceof SQLTesterException){
  476. threw = true;
  477. this.outln("🔥EXCEPTION: ",e);
  478. this.metrics.failedScripts.push({script: ts.filename(), message:e.toString()});
  479. if( this.#keepGoing ){
  480. this.outln("Continuing anyway because of the keep-going option.");
  481. }else if( e.isFatal() ){
  482. throw e;
  483. }
  484. }else{
  485. throw e;
  486. }
  487. }finally{
  488. const timeEnd = (new Date()).getTime();
  489. this.out("🏁", (threw ? "❌" : "✅"), " ",
  490. this.metrics.nTest, " test(s) in ",
  491. (timeEnd-timeStart),"ms. ");
  492. const mod = ts.moduleName();
  493. if( mod ){
  494. this.out( "[",mod,"] " );
  495. }
  496. this.outln(ts.filename());
  497. }
  498. }
  499. const tEnd = (new Date()).getTime();
  500. Util.unlink(this.#db.initialDbName);
  501. this.outln("Took ",(tEnd-tStart),"ms. Test count = ",
  502. this.metrics.nTotalTest,", script count = ",
  503. this.#aScripts.length,(
  504. this.metrics.failedScripts.length
  505. ? ", failed scripts = "+this.metrics.failedScripts.length
  506. : ""
  507. )
  508. );
  509. return this;
  510. }
  511. #setupInitialDb(){
  512. if( !this.#db.list[0] ){
  513. Util.unlink(this.#db.initialDbName);
  514. this.openDb(0, this.#db.initialDbName, true);
  515. }else{
  516. this.#outer.outln("WARNING: setupInitialDb() was unexpectedly ",
  517. "triggered while it is opened.");
  518. }
  519. }
  520. #escapeSqlValue(v){
  521. if( !v ) return "{}";
  522. if( !Rx.special.test(v) ){
  523. return v /* no escaping needed */;
  524. }
  525. if( !Rx.squiggly.test(v) ){
  526. return "{"+v+"}";
  527. }
  528. const sb = ["\""];
  529. const n = v.length;
  530. for(let i = 0; i < n; ++i){
  531. const ch = v.charAt(i);
  532. switch(ch){
  533. case '\\': sb.push("\\\\"); break;
  534. case '"': sb.push("\\\""); break;
  535. default:{
  536. //verbose("CHAR ",(int)ch," ",ch," octal=",String.format("\\%03o", (int)ch));
  537. const ccode = ch.charCodeAt(i);
  538. if( ccode < 32 ) sb.push('\\',ccode.toString(8),'o');
  539. else sb.push(ch);
  540. break;
  541. }
  542. }
  543. }
  544. sb.push("\"");
  545. return sb.join('');
  546. }
  547. #appendDbErr(pDb, sb, rc){
  548. sb.push(sqlite3.capi.sqlite3_js_rc_str(rc), ' ');
  549. const msg = this.#escapeSqlValue(sqlite3.capi.sqlite3_errmsg(pDb));
  550. if( '{' === msg.charAt(0) ){
  551. sb.push(msg);
  552. }else{
  553. sb.push('{', msg, '}');
  554. }
  555. }
  556. #checkDbRc(pDb,rc){
  557. sqlite3.oo1.DB.checkRc(pDb, rc);
  558. }
  559. execSql(pDb, throwOnError, appendMode, rowMode, sql){
  560. if( !pDb && !this.#db.list[0] ){
  561. this.#setupInitialDb();
  562. }
  563. if( !pDb ) pDb = this.#db.currentDb();
  564. const wasm = sqlite3.wasm, capi = sqlite3.capi;
  565. sql = (sql instanceof Uint8Array)
  566. ? sql
  567. : Util.utf8Encode(capi.sqlite3_js_sql_to_string(sql));
  568. const self = this;
  569. const sb = (ResultBufferMode.NONE===appendMode) ? null : this.#resultBuffer;
  570. let rc = 0;
  571. wasm.scopedAllocCall(function(){
  572. let sqlByteLen = sql.byteLength;
  573. const ppStmt = wasm.scopedAlloc(
  574. /* output (sqlite3_stmt**) arg and pzTail */
  575. (2 * wasm.ptrSizeof) + (sqlByteLen + 1/* SQL + NUL */)
  576. );
  577. const pzTail = ppStmt + wasm.ptrSizeof /* final arg to sqlite3_prepare_v2() */;
  578. let pSql = pzTail + wasm.ptrSizeof;
  579. const pSqlEnd = pSql + sqlByteLen;
  580. wasm.heap8().set(sql, pSql);
  581. wasm.poke8(pSql + sqlByteLen, 0/*NUL terminator*/);
  582. let pos = 0, n = 1, spacing = 0;
  583. while( pSql && wasm.peek8(pSql) ){
  584. wasm.pokePtr([ppStmt, pzTail], 0);
  585. rc = capi.sqlite3_prepare_v3(
  586. pDb, pSql, sqlByteLen, 0, ppStmt, pzTail
  587. );
  588. if( 0!==rc ){
  589. if(throwOnError){
  590. throw new DbException(self, pDb, rc);
  591. }else if( sb ){
  592. self.#appendDbErr(pDb, sb, rc);
  593. }
  594. break;
  595. }
  596. const pStmt = wasm.peekPtr(ppStmt);
  597. pSql = wasm.peekPtr(pzTail);
  598. sqlByteLen = pSqlEnd - pSql;
  599. if(!pStmt) continue /* only whitespace or comments */;
  600. if( sb ){
  601. const nCol = capi.sqlite3_column_count(pStmt);
  602. let colName, val;
  603. while( capi.SQLITE_ROW === (rc = capi.sqlite3_step(pStmt)) ) {
  604. for( let i=0; i < nCol; ++i ){
  605. if( spacing++ > 0 ) sb.push(' ');
  606. if( self.#emitColNames ){
  607. colName = capi.sqlite3_column_name(pStmt, i);
  608. switch(appendMode){
  609. case ResultBufferMode.ASIS: sb.push( colName ); break;
  610. case ResultBufferMode.ESCAPED:
  611. sb.push( self.#escapeSqlValue(colName) );
  612. break;
  613. default:
  614. self.toss("Unhandled ResultBufferMode.");
  615. }
  616. sb.push(' ');
  617. }
  618. val = capi.sqlite3_column_text(pStmt, i);
  619. if( null===val ){
  620. sb.push( self.#nullView );
  621. continue;
  622. }
  623. switch(appendMode){
  624. case ResultBufferMode.ASIS: sb.push( val ); break;
  625. case ResultBufferMode.ESCAPED:
  626. sb.push( self.#escapeSqlValue(val) );
  627. break;
  628. }
  629. }/* column loop */
  630. if( ResultRowMode.NEWLINE === rowMode ){
  631. spacing = 0;
  632. sb.push('\n');
  633. }
  634. }/* row loop */
  635. }else{ // no output but possibly other side effects
  636. while( capi.SQLITE_ROW === (rc = capi.sqlite3_step(pStmt)) ) {}
  637. }
  638. capi.sqlite3_finalize(pStmt);
  639. if( capi.SQLITE_ROW===rc || capi.SQLITE_DONE===rc) rc = 0;
  640. else if( rc!=0 ){
  641. if( sb ){
  642. self.#appendDbErr(pDb, sb, rc);
  643. }
  644. break;
  645. }
  646. }/* SQL script loop */;
  647. })/*scopedAllocCall()*/;
  648. return rc;
  649. }
  650. }/*SQLTester*/
  651. class Command {
  652. constructor(){
  653. }
  654. process(sqlTester,testScript,argv){
  655. SQLTesterException.toss("process() must be overridden");
  656. }
  657. argcCheck(testScript,argv,min,max){
  658. const argc = argv.length-1;
  659. if(argc<min || (max>=0 && argc>max)){
  660. if( min==max ){
  661. testScript.toss(argv[0]," requires exactly ",min," argument(s)");
  662. }else if(max>0){
  663. testScript.toss(argv[0]," requires ",min,"-",max," arguments.");
  664. }else{
  665. testScript.toss(argv[0]," requires at least ",min," arguments.");
  666. }
  667. }
  668. }
  669. }
  670. class Cursor {
  671. src;
  672. sb = [];
  673. pos = 0;
  674. //! Current line number. Starts at 0 for internal reasons and will
  675. // line up with 1-based reality once parsing starts.
  676. lineNo = 0 /* yes, zero */;
  677. //! Putback value for this.pos.
  678. putbackPos = 0;
  679. //! Putback line number
  680. putbackLineNo = 0;
  681. //! Peeked-to pos, used by peekLine() and consumePeeked().
  682. peekedPos = 0;
  683. //! Peeked-to line number.
  684. peekedLineNo = 0;
  685. constructor(){
  686. }
  687. //! Restore parsing state to the start of the stream.
  688. rewind(){
  689. this.sb.length = this.pos = this.lineNo
  690. = this.putbackPos = this.putbackLineNo
  691. = this.peekedPos = this.peekedLineNo = 0;
  692. }
  693. }
  694. class TestScript {
  695. #cursor = new Cursor();
  696. #moduleName = null;
  697. #filename = null;
  698. #testCaseName = null;
  699. #outer = new Outer().outputPrefix( ()=>this.getOutputPrefix()+': ' );
  700. constructor(...args){
  701. let content, filename;
  702. if( 2 == args.length ){
  703. filename = args[0];
  704. content = args[1];
  705. }else if( 1 == args.length ){
  706. if(args[0] instanceof Object){
  707. const o = args[0];
  708. filename = o.name;
  709. content = o.content;
  710. }else{
  711. content = args[0];
  712. }
  713. }
  714. if(!(content instanceof Uint8Array)){
  715. if('string' === typeof content){
  716. content = Util.utf8Encode(content);
  717. }else if((content instanceof ArrayBuffer)
  718. ||(content instanceof Array)){
  719. content = new Uint8Array(content);
  720. }else{
  721. toss(Error, "Invalid content type for TestScript constructor.");
  722. }
  723. }
  724. this.#filename = filename;
  725. this.#cursor.src = content;
  726. }
  727. moduleName(){
  728. return (0==arguments.length)
  729. ? this.#moduleName : (this.#moduleName = arguments[0]);
  730. }
  731. testCaseName(){
  732. return (0==arguments.length)
  733. ? this.#testCaseName : (this.#testCaseName = arguments[0]);
  734. }
  735. filename(){
  736. return (0==arguments.length)
  737. ? this.#filename : (this.#filename = arguments[0]);
  738. }
  739. getOutputPrefix() {
  740. let rc = "["+(this.#moduleName || '<unnamed>')+"]";
  741. if( this.#testCaseName ) rc += "["+this.#testCaseName+"]";
  742. if( this.#filename ) rc += '['+this.#filename+']';
  743. return rc + " line "+ this.#cursor.lineNo;
  744. }
  745. reset(){
  746. this.#testCaseName = null;
  747. this.#cursor.rewind();
  748. return this;
  749. }
  750. toss(...args){
  751. throw new TestScriptFailed(this,...args);
  752. }
  753. verbose1(...args){ return this.#outer.verboseN(1,args); }
  754. verbose2(...args){ return this.#outer.verboseN(2,args); }
  755. verbose3(...args){ return this.#outer.verboseN(3,args); }
  756. verbosity(...args){
  757. const rc = this.#outer.verbosity(...args);
  758. return args.length ? this : rc;
  759. }
  760. #checkRequiredProperties(tester, props){
  761. if(true) return false;
  762. let nOk = 0;
  763. for(const rp of props){
  764. this.verbose2("REQUIRED_PROPERTIES: ",rp);
  765. switch(rp){
  766. case "RECURSIVE_TRIGGERS":
  767. tester.appendDbInitSql("pragma recursive_triggers=on;");
  768. ++nOk;
  769. break;
  770. case "TEMPSTORE_FILE":
  771. /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,
  772. which we just happen to know is the case */
  773. tester.appendDbInitSql("pragma temp_store=1;");
  774. ++nOk;
  775. break;
  776. case "TEMPSTORE_MEM":
  777. /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2,
  778. which we just happen to know is the case */
  779. tester.appendDbInitSql("pragma temp_store=0;");
  780. ++nOk;
  781. break;
  782. case "AUTOVACUUM":
  783. tester.appendDbInitSql("pragma auto_vacuum=full;");
  784. ++nOk;
  785. break;
  786. case "INCRVACUUM":
  787. tester.appendDbInitSql("pragma auto_vacuum=incremental;");
  788. ++nOk;
  789. default:
  790. break;
  791. }
  792. }
  793. return props.length == nOk;
  794. }
  795. #checkForDirective(tester,line){
  796. if(line.startsWith("#")){
  797. throw new IncompatibleDirective(this, "C-preprocessor input: "+line);
  798. }else if(line.startsWith("---")){
  799. throw new IncompatibleDirective(this, "triple-dash: ",line);
  800. }
  801. let m = Rx.scriptModuleName.exec(line);
  802. if( m ){
  803. this.#moduleName = m[1];
  804. return;
  805. }
  806. m = Rx.requiredProperties.exec(line);
  807. if( m ){
  808. const rp = m[1];
  809. if( !this.#checkRequiredProperties( tester, rp.split(/\s+/).filter(v=>!!v) ) ){
  810. throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp);
  811. }
  812. }
  813. m = Rx.mixedModuleName.exec(line);
  814. if( m ){
  815. throw new IncompatibleDirective(this, m[1]+": "+m[3]);
  816. }
  817. if( line.indexOf("\n|")>=0 ){
  818. throw new IncompatibleDirective(this, "newline-pipe combination.");
  819. }
  820. }
  821. #getCommandArgv(line){
  822. const m = Rx.command.exec(line);
  823. return m ? m[1].trim().split(/\s+/) : null;
  824. }
  825. #isCommandLine(line, checkForImpl){
  826. let m = Rx.command.exec(line);
  827. if( m && checkForImpl ){
  828. m = !!CommandDispatcher.getCommandByName(m[2]);
  829. }
  830. return !!m;
  831. }
  832. fetchCommandBody(tester){
  833. const sb = [];
  834. let line;
  835. while( (null !== (line = this.peekLine())) ){
  836. this.#checkForDirective(tester, line);
  837. if( this.#isCommandLine(line, true) ) break;
  838. sb.push(line,"\n");
  839. this.consumePeeked();
  840. }
  841. line = sb.join('');
  842. return !!line.trim() ? line : null;
  843. }
  844. run(tester){
  845. this.reset();
  846. this.#outer.verbosity( tester.verbosity() );
  847. this.#outer.logger( tester.outer().logger() );
  848. let line, directive, argv = [];
  849. while( null != (line = this.getLine()) ){
  850. this.verbose3("run() input line: ",line);
  851. this.#checkForDirective(tester, line);
  852. argv = this.#getCommandArgv(line);
  853. if( argv ){
  854. this.#processCommand(tester, argv);
  855. continue;
  856. }
  857. tester.appendInput(line,true);
  858. }
  859. return true;
  860. }
  861. #processCommand(tester, argv){
  862. this.verbose2("processCommand(): ",argv[0], " ", Util.argvToString(argv));
  863. if(this.#outer.verbosity()>1){
  864. const input = tester.getInputText();
  865. this.verbose3("processCommand() input buffer = ",input);
  866. }
  867. CommandDispatcher.dispatch(tester, this, argv);
  868. }
  869. getLine(){
  870. const cur = this.#cursor;
  871. if( cur.pos==cur.src.byteLength ){
  872. return null/*EOF*/;
  873. }
  874. cur.putbackPos = cur.pos;
  875. cur.putbackLineNo = cur.lineNo;
  876. cur.sb.length = 0;
  877. let b = 0, prevB = 0, i = cur.pos;
  878. let doBreak = false;
  879. let nChar = 0 /* number of bytes in the aChar char */;
  880. const end = cur.src.byteLength;
  881. for(; i < end && !doBreak; ++i){
  882. b = cur.src[i];
  883. switch( b ){
  884. case 13/*CR*/: continue;
  885. case 10/*NL*/:
  886. ++cur.lineNo;
  887. if(cur.sb.length>0) doBreak = true;
  888. // Else it's an empty string
  889. break;
  890. default:{
  891. /* Multi-byte chars need to be gathered up and appended at
  892. one time so that we can get them as string objects. */
  893. nChar = 1;
  894. switch( b & 0xF0 ){
  895. case 0xC0: nChar = 2; break;
  896. case 0xE0: nChar = 3; break;
  897. case 0xF0: nChar = 4; break;
  898. default:
  899. if( b > 127 ) this.toss("Invalid character (#"+b+").");
  900. break;
  901. }
  902. if( 1==nChar ){
  903. cur.sb.push(String.fromCharCode(b));
  904. }else{
  905. const aChar = [] /* multi-byte char buffer */;
  906. for(let x = 0; (x < nChar) && (i+x < end); ++x) aChar[x] = cur.src[i+x];
  907. cur.sb.push(
  908. Util.utf8Decode( new Uint8Array(aChar) )
  909. );
  910. i += nChar-1;
  911. }
  912. break;
  913. }
  914. }
  915. }
  916. cur.pos = i;
  917. const rv = cur.sb.join('');
  918. if( i==cur.src.byteLength && 0==rv.length ){
  919. return null /* EOF */;
  920. }
  921. return rv;
  922. }/*getLine()*/
  923. /**
  924. Fetches the next line then resets the cursor to its pre-call
  925. state. consumePeeked() can be used to consume this peeked line
  926. without having to re-parse it.
  927. */
  928. peekLine(){
  929. const cur = this.#cursor;
  930. const oldPos = cur.pos;
  931. const oldPB = cur.putbackPos;
  932. const oldPBL = cur.putbackLineNo;
  933. const oldLine = cur.lineNo;
  934. try {
  935. return this.getLine();
  936. }finally{
  937. cur.peekedPos = cur.pos;
  938. cur.peekedLineNo = cur.lineNo;
  939. cur.pos = oldPos;
  940. cur.lineNo = oldLine;
  941. cur.putbackPos = oldPB;
  942. cur.putbackLineNo = oldPBL;
  943. }
  944. }
  945. /**
  946. Only valid after calling peekLine() and before calling getLine().
  947. This places the cursor to the position it would have been at had
  948. the peekLine() had been fetched with getLine().
  949. */
  950. consumePeeked(){
  951. const cur = this.#cursor;
  952. cur.pos = cur.peekedPos;
  953. cur.lineNo = cur.peekedLineNo;
  954. }
  955. /**
  956. Restores the cursor to the position it had before the previous
  957. call to getLine().
  958. */
  959. putbackLine(){
  960. const cur = this.#cursor;
  961. cur.pos = cur.putbackPos;
  962. cur.lineNo = cur.putbackLineNo;
  963. }
  964. }/*TestScript*/;
  965. //! --close command
  966. class CloseDbCommand extends Command {
  967. process(t, ts, argv){
  968. this.argcCheck(ts,argv,0,1);
  969. let id;
  970. if(argv.length>1){
  971. const arg = argv[1];
  972. if( "all" === arg ){
  973. t.closeAllDbs();
  974. return;
  975. }
  976. else{
  977. id = parseInt(arg);
  978. }
  979. }else{
  980. id = t.currentDbId();
  981. }
  982. t.closeDb(id);
  983. }
  984. }
  985. //! --column-names command
  986. class ColumnNamesCommand extends Command {
  987. process( st, ts, argv ){
  988. this.argcCheck(ts,argv,1);
  989. st.outputColumnNames( !!parseInt(argv[1]) );
  990. }
  991. }
  992. //! --db command
  993. class DbCommand extends Command {
  994. process(t, ts, argv){
  995. this.argcCheck(ts,argv,1);
  996. t.currentDbId( parseInt(argv[1]) );
  997. }
  998. }
  999. //! --glob command
  1000. class GlobCommand extends Command {
  1001. #negate = false;
  1002. constructor(negate=false){
  1003. super();
  1004. this.#negate = negate;
  1005. }
  1006. process(t, ts, argv){
  1007. this.argcCheck(ts,argv,1,-1);
  1008. t.incrementTestCounter();
  1009. const sql = t.takeInputBuffer();
  1010. let rc = t.execSql(null, true, ResultBufferMode.ESCAPED,
  1011. ResultRowMode.ONELINE, sql);
  1012. const result = t.getResultText();
  1013. const sArgs = Util.argvToString(argv);
  1014. //t2.verbose2(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs);
  1015. const glob = Util.argvToString(argv);
  1016. rc = Util.strglob(glob, result);
  1017. if( (this.#negate && 0===rc) || (!this.#negate && 0!==rc) ){
  1018. ts.toss(argv[0], " mismatch: ", glob," vs input: ",result);
  1019. }
  1020. }
  1021. }
  1022. //! --notglob command
  1023. class NotGlobCommand extends GlobCommand {
  1024. constructor(){super(true);}
  1025. }
  1026. //! --open command
  1027. class OpenDbCommand extends Command {
  1028. #createIfNeeded = false;
  1029. constructor(createIfNeeded=false){
  1030. super();
  1031. this.#createIfNeeded = createIfNeeded;
  1032. }
  1033. process(t, ts, argv){
  1034. this.argcCheck(ts,argv,1);
  1035. t.openDb(argv[1], this.#createIfNeeded);
  1036. }
  1037. }
  1038. //! --new command
  1039. class NewDbCommand extends OpenDbCommand {
  1040. constructor(){ super(true); }
  1041. }
  1042. //! Placeholder dummy/no-op commands
  1043. class NoopCommand extends Command {
  1044. process(t, ts, argv){}
  1045. }
  1046. //! --null command
  1047. class NullCommand extends Command {
  1048. process(st, ts, argv){
  1049. this.argcCheck(ts,argv,1);
  1050. st.nullValue( argv[1] );
  1051. }
  1052. }
  1053. //! --print command
  1054. class PrintCommand extends Command {
  1055. process(st, ts, argv){
  1056. st.out(ts.getOutputPrefix(),': ');
  1057. if( 1==argv.length ){
  1058. st.out( st.getInputText() );
  1059. }else{
  1060. st.outln( Util.argvToString(argv) );
  1061. }
  1062. }
  1063. }
  1064. //! --result command
  1065. class ResultCommand extends Command {
  1066. #bufferMode;
  1067. constructor(resultBufferMode = ResultBufferMode.ESCAPED){
  1068. super();
  1069. this.#bufferMode = resultBufferMode;
  1070. }
  1071. process(t, ts, argv){
  1072. this.argcCheck(ts,argv,0,-1);
  1073. t.incrementTestCounter();
  1074. const sql = t.takeInputBuffer();
  1075. //ts.verbose2(argv[0]," SQL =\n",sql);
  1076. t.execSql(null, false, this.#bufferMode, ResultRowMode.ONELINE, sql);
  1077. const result = t.getResultText().trim();
  1078. const sArgs = argv.length>1 ? Util.argvToString(argv) : "";
  1079. if( result !== sArgs ){
  1080. t.outln(argv[0]," FAILED comparison. Result buffer:\n",
  1081. result,"\nExpected result:\n",sArgs);
  1082. ts.toss(argv[0]+" comparison failed.");
  1083. }
  1084. }
  1085. }
  1086. //! --json command
  1087. class JsonCommand extends ResultCommand {
  1088. constructor(){ super(ResultBufferMode.ASIS); }
  1089. }
  1090. //! --run command
  1091. class RunCommand extends Command {
  1092. process(t, ts, argv){
  1093. this.argcCheck(ts,argv,0,1);
  1094. const pDb = (1==argv.length)
  1095. ? t.currentDb() : t.getDbById( parseInt(argv[1]) );
  1096. const sql = t.takeInputBuffer();
  1097. const rc = t.execSql(pDb, false, ResultBufferMode.NONE,
  1098. ResultRowMode.ONELINE, sql);
  1099. if( 0!==rc && t.verbosity()>0 ){
  1100. const msg = sqlite3.capi.sqlite3_errmsg(pDb);
  1101. ts.verbose2(argv[0]," non-fatal command error #",rc,": ",
  1102. msg,"\nfor SQL:\n",sql);
  1103. }
  1104. }
  1105. }
  1106. //! --tableresult command
  1107. class TableResultCommand extends Command {
  1108. #jsonMode;
  1109. constructor(jsonMode=false){
  1110. super();
  1111. this.#jsonMode = jsonMode;
  1112. }
  1113. process(t, ts, argv){
  1114. this.argcCheck(ts,argv,0);
  1115. t.incrementTestCounter();
  1116. let body = ts.fetchCommandBody(t);
  1117. if( null===body ) ts.toss("Missing ",argv[0]," body.");
  1118. body = body.trim();
  1119. if( !body.endsWith("\n--end") ){
  1120. ts.toss(argv[0], " must be terminated with --end\\n");
  1121. }else{
  1122. body = body.substring(0, body.length-6);
  1123. }
  1124. const globs = body.split(/\s*\n\s*/);
  1125. if( globs.length < 1 ){
  1126. ts.toss(argv[0], " requires 1 or more ",
  1127. (this.#jsonMode ? "json snippets" : "globs"),".");
  1128. }
  1129. const sql = t.takeInputBuffer();
  1130. t.execSql(null, true,
  1131. this.#jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED,
  1132. ResultRowMode.NEWLINE, sql);
  1133. const rbuf = t.getResultText().trim();
  1134. const res = rbuf.split(/\r?\n/);
  1135. if( res.length !== globs.length ){
  1136. ts.toss(argv[0], " failure: input has ", res.length,
  1137. " row(s) but expecting ",globs.length);
  1138. }
  1139. for(let i = 0; i < res.length; ++i){
  1140. const glob = globs[i].replaceAll(/\s+/g," ").trim();
  1141. //ts.verbose2(argv[0]," <<",glob,">> vs <<",res[i],">>");
  1142. if( this.#jsonMode ){
  1143. if( glob!==res[i] ){
  1144. ts.toss(argv[0], " json <<",glob, ">> does not match: <<",
  1145. res[i],">>");
  1146. }
  1147. }else if( 0!=Util.strglob(glob, res[i]) ){
  1148. ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>");
  1149. }
  1150. }
  1151. }
  1152. }
  1153. //! --json-block command
  1154. class JsonBlockCommand extends TableResultCommand {
  1155. constructor(){ super(true); }
  1156. }
  1157. //! --testcase command
  1158. class TestCaseCommand extends Command {
  1159. process(tester, script, argv){
  1160. this.argcCheck(script, argv,1);
  1161. script.testCaseName(argv[1]);
  1162. tester.clearResultBuffer();
  1163. tester.clearInputBuffer();
  1164. }
  1165. }
  1166. //! --verbosity command
  1167. class VerbosityCommand extends Command {
  1168. process(t, ts, argv){
  1169. this.argcCheck(ts,argv,1);
  1170. ts.verbosity( parseInt(argv[1]) );
  1171. }
  1172. }
  1173. class CommandDispatcher {
  1174. static map = newObj();
  1175. static getCommandByName(name){
  1176. let rv = CommandDispatcher.map[name];
  1177. if( rv ) return rv;
  1178. switch(name){
  1179. case "close": rv = new CloseDbCommand(); break;
  1180. case "column-names": rv = new ColumnNamesCommand(); break;
  1181. case "db": rv = new DbCommand(); break;
  1182. case "glob": rv = new GlobCommand(); break;
  1183. case "json": rv = new JsonCommand(); break;
  1184. case "json-block": rv = new JsonBlockCommand(); break;
  1185. case "new": rv = new NewDbCommand(); break;
  1186. case "notglob": rv = new NotGlobCommand(); break;
  1187. case "null": rv = new NullCommand(); break;
  1188. case "oom": rv = new NoopCommand(); break;
  1189. case "open": rv = new OpenDbCommand(); break;
  1190. case "print": rv = new PrintCommand(); break;
  1191. case "result": rv = new ResultCommand(); break;
  1192. case "run": rv = new RunCommand(); break;
  1193. case "tableresult": rv = new TableResultCommand(); break;
  1194. case "testcase": rv = new TestCaseCommand(); break;
  1195. case "verbosity": rv = new VerbosityCommand(); break;
  1196. }
  1197. if( rv ){
  1198. CommandDispatcher.map[name] = rv;
  1199. }
  1200. return rv;
  1201. }
  1202. static dispatch(tester, testScript, argv){
  1203. const cmd = CommandDispatcher.getCommandByName(argv[0]);
  1204. if( !cmd ){
  1205. toss(UnknownCommand,testScript,argv[0]);
  1206. }
  1207. cmd.process(tester, testScript, argv);
  1208. }
  1209. }/*CommandDispatcher*/
  1210. const namespace = newObj({
  1211. Command,
  1212. DbException,
  1213. IncompatibleDirective,
  1214. Outer,
  1215. SQLTester,
  1216. SQLTesterException,
  1217. TestScript,
  1218. TestScriptFailed,
  1219. UnknownCommand,
  1220. Util,
  1221. sqlite3
  1222. });
  1223. export {namespace as default};