WASM: added exec(), execMulti(), and several getters. Various touchups and fixes.

FossilOrigin-Name: b790c91b85e9cf8eecce86ac1717e8ccd2c3b6b98a1ad6a5d64eefc94ee86f9d
This commit is contained in:
stephan 2022-05-22 22:00:39 +00:00
parent f6868562ce
commit 744a65cf7d
5 changed files with 402 additions and 86 deletions

View File

@ -40,6 +40,7 @@ _sqlite3_result_int
_sqlite3_result_null
_sqlite3_result_text
_sqlite3_sourceid
_sqlite3_sql
_sqlite3_step
_sqlite3_value_blob
_sqlite3_value_bytes

View File

@ -42,6 +42,22 @@
This file installs namespace.sqlite3, where namespace is `self`,
meaning either the global window or worker, depending on where this
is loaded from.
# Goals and Non-goals of this API
Goals:
- Except where noted in the non-goals, provide a more-or-less
complete wrapper to the sqlite3 C API, insofar as WASM feature
parity with C allows for.
Non-goals:
- As WASM is a web-based technology and UTF-8 is the King of
Encodings in that realm, there are no plans to support the
UTF16-related APIs will not be. They would add a complication to
the bindings for no appreciable benefit.
*/
(function(namespace){
/* For reference: sql.js does essentially everything we want and
@ -184,13 +200,13 @@
["sqlite3_result_null",null,["number"]],
["sqlite3_result_text",null,["number", "string", "number", "number"]],
["sqlite3_sourceid", "string", []],
["sqlite3_sql", "string", ["number"]],
["sqlite3_step", "number", ["number"]],
["sqlite3_value_blob", "number", ["number"]],
["sqlite3_value_bytes","number",["number"]],
["sqlite3_value_double","number",["number"]],
["sqlite3_value_text", "string", ["number"]],
["sqlite3_value_type", "number", ["number"]]
//["sqlite3_sql", "string", ["number"]],
//["sqlite3_normalized_sql", "string", ["number"]]
].forEach(function(a){
const k = (4==a.length) ? a.shift() : a[0];
@ -201,10 +217,11 @@
/* 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
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. */
/* memory for use in some pointer-passing routines */
/** Memory for use in some pointer-to-pointer-passing routines. */
const pPtrArg = stackAlloc(4);
/** Throws a new error, concatenating all args with a space between
each. */
@ -212,8 +229,8 @@
throw new Error(Array.prototype.join.call(arguments, ' '));
};
const sqlite3/*canonical name*/ = S/*convenience alias*/ = api;
const S/*convenience alias*/ = api;
/**
The DB class wraps a sqlite3 db handle.
*/
@ -222,6 +239,7 @@
else if('string'!==typeof name){
toss("TODO: support blob image of db here.");
}
setValue(pPtrArg, 0, "i32");
this.checkRc(S.sqlite3_open(name, pPtrArg));
this._pDb = getValue(pPtrArg, "i32");
this.filename = name;
@ -249,7 +267,7 @@
new instances.
*/
const Stmt = function(){
if(BindTypes!=arguments[2]){
if(BindTypes!==arguments[2]){
toss("Do not call the Stmt constructor directly. Use DB.prepare().");
}
this.db = arguments[0];
@ -265,16 +283,64 @@
return db;
};
/**
Expects to be passed (arguments) from DB.exec() and
DB.execMulti(). Does the argument processing/validation, throws
on error, and returns a new object on success:
{ sql: the SQL, obt: optionsObj, cbArg: function}
cbArg is only set if the opt.callback is 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(args){
const out = {};
switch(args.length){
case 1:
if('string'===typeof args[0]){
out.sql = args[0];
out.opt = {};
}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: toss("Invalid argument count for exec().");
};
if('string'!==typeof out.sql) toss("Missing SQL argument.");
if(out.opt.callback){
switch((undefined===out.opt.rowMode)
? 'stmt' : out.opt.rowMode) {
case 'object': out.cbArg = (stmt)=>stmt.get({}); break;
case 'array': out.cbArg = (stmt)=>stmt.get([]); break;
case 'stmt': out.cbArg = (stmt)=>stmt; break;
default: toss("Invalid rowMode:",out.opt.rowMode);
}
}
return out;
};
DB.prototype = {
/**
Expects to be given an sqlite3 API result code. If it is
falsy, this function returns this object, else it throws an
exception with an error message from sqlite3_errmsg(),
using this object's db handle.
using this object's db handle. 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.
*/
checkRc: function(sqliteResultCode){
if(!sqliteResultCode) return this;
toss(S.sqlite3_errmsg(this._pDb) || "Unknown db error.");
toss("sqlite result code",sqliteResultCode+":",
S.sqlite3_errmsg(this._pDb) || "Unknown db error.");
},
/**
Finalizes all open statements and closes this database
@ -317,7 +383,168 @@
const stmt = new Stmt(this, pStmt, BindTypes);
this._statements[pStmt] = stmt;
return stmt;
}
},
/**
This function works like execMulti(), and takes the same
arguments, but is more efficient (performs much less work)
when the input SQL is only a single statement. If passed a
multi-statement SQL, it only processes the first one.
This function supports one additional option not used by
execMulti():
- .multi: if true, this function acts as a proxy for
execMulti().
*/
exec: function(/*(sql [,optionsObj]) or (optionsObj)*/){
affirmDbOpen(this);
const arg = parseExecArgs(arguments);
if(!arg.sql) return this;
else if(arg.multi){
return this.execMulti(arg, undefined, BindTypes);
}
const opt = arg.opt;
let stmt;
try {
stmt = this.prepare(arg.sql);
if(opt.bind) stmt.bind(bind);
if(opt.callback){
while(stmt.step()){
stmt._isLocked = true;
opt.callback(arg.cbArg(stmt), stmt);
stmt._isLocked = false;
}
}else{
stmt.step();
}
}finally{
if(stmt){
delete stmt._isLocked;
stmt.finalize();
}
}
return this;
}/*exec()*/,
/**
Executes one or more SQL statements. Its arguments
must be either (sql,optionsObject) or (optionsObject).
In the latter case, optionsObject.sql must contain the
SQL to execute. Returns this object. 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).
- .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.)
- .callback = a function which gets called for each row of
the FIRST statement in the SQL (if it has any result
rows). The second argument passed to the callback is
always the current Stmt object (so that the caller
may collect column names, or similar). The first
argument passed to the callback defaults to the current
Stmt object but may be changed with ...
- .rowMode = a string describing what type of argument
should be passed as the first argument to the callback. A
value of 'object' causes the results of `stmt.get({})` to
be passed to the object. A value of 'array' causes the
results of `stmt.get([])` to be passed to the callback.
A value of 'stmt' is equivalent to the default, passing
the current Stmt to the callback (noting that it's always
passed as the 2nd argument). Any other value triggers an
exception.
- 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.
ACHTUNG #1: The callback MUST NOT modify the Stmt
object. Calling any of the Stmt.get() variants,
Stmt.getColumnName(), or simililar, is legal, but calling
step() or finalize() is not. Routines which are illegal
in this context will trigger an exception.
ACHTUNG #2: The semantics of the `bind` and `callback`
options may well change or those options may be removed
altogether for this function (but retained for exec()).
*/
execMulti: function(/*(sql [,obj]) || (obj)*/){
affirmDbOpen(this);
const arg = (BindTypes===arguments[2]
/* ^^^ Being passed on from exec() */
? arguments[0] : parseExecArgs(arguments));
if(!arg.sql) return this;
const opt = arg.opt;
const stack = stackSave();
let stmt;
let bind = opt.bind;
let rowMode = (
(opt.callback && opt.rowMode)
? opt.rowMode : false);
try{
let pSql = allocateUTF8OnStack(arg.sql)
const pzTail = stackAlloc(4);
while(getValue(pSql, "i8")){
setValue(pPtrArg, 0, "i32");
setValue(pzTail, 0, "i32");
this.checkRc(S.sqlite3_prepare_v2_sqlptr(
this._pDb, pSql, -1, pPtrArg, pzTail
));
const pStmt = getValue(pPtrArg, "i32");
pSql = getValue(pzTail, "i32");
if(!pStmt) continue;
if(opt.saveSql){
opt.saveSql.push(S.sqlite3_sql(pStmt).trim());
}
stmt = new Stmt(this, pStmt, BindTypes);
if(bind && stmt.parameterCount){
stmt.bind(bind);
bind = null;
}
if(opt.callback && null!==rowMode){
while(stmt.step()){
stmt._isLocked = true;
callback(arg.cbArg(stmt), stmt);
stmt._isLocked = false;
}
rowMode = null;
}else{
// Do we need to while(stmt.step()){} here?
stmt.step();
}
stmt.finalize();
stmt = null;
}
}finally{
if(stmt){
delete stmt._isLocked;
stmt.finalize();
}
stackRestore(stack);
}
return this;
}/*execMulti()*/
}/*DB.prototype*/;
/** Throws if the given Stmt has been finalized, else stmt is
returned. */
const affirmStmtOpen = function(stmt){
if(!stmt._pStmt) toss("Stmt has been closed.");
return stmt;
};
/** Returns an opaque truthy value from the BindTypes
@ -360,12 +587,17 @@
if(0===n || (n===key && (n!==(n|0)/*floating point*/))){
toss("Invalid bind() parameter name: "+key);
}
else if(n<1 || n>=stmt.parameterCount) toss("Bind index",key,"is out of range.");
else if(n<1 || n>stmt.parameterCount) toss("Bind index",key,"is out of range.");
return n;
};
/** Throws if ndx is not an integer or if it is out of range
for stmt.columnCount, else returns stmt. */
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.
*/
const affirmColIndex = function(stmt,ndx){
if((ndx !== (ndx|0)) || ndx<0 || ndx>=stmt.columnCount){
toss("Column index",ndx,"is out of range.");
@ -380,7 +612,22 @@
}
return stmt;
};
/**
If stmt._isLocked 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 affirmUnlocked = function(stmt,currentOpName){
if(stmt._isLocked){
toss("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
@ -388,6 +635,7 @@
success.
*/
const bindOne = function f(stmt,ndx,bindType,val){
affirmUnlocked(stmt, 'bind()');
if(!f._){
f._ = {
string: function(stmt, ndx, val, asBlob){
@ -403,14 +651,14 @@
ndx = affirmParamIndex(stmt,ndx);
let rc = 0;
switch((null===val || undefined===val) ? BindTypes.null : bindType){
case BindType.null:
case BindTypes.null:
rc = S.sqlite3_bind_null(stmt._pStmt, ndx);
break;
case BindType.string:{
case BindTypes.string:{
rc = f._.string(stmt, ndx, val, false);
break;
}
case BindType.number: {
case BindTypes.number: {
const m = ((val === (val|0))
? ((val & 0x00000000/*>32 bits*/)
? S.sqlite3_bind_int64
@ -419,10 +667,10 @@
rc = m(stmt._pStmt, ndx, val);
break;
}
case BindType.boolean:
case BindTypes.boolean:
rc = S.sqlite3_bind_int(stmt._pStmt, ndx, val ? 1 : 0);
break;
case BindType.blob: {
case BindTypes.blob: {
if('string'===typeof val){
rc = f._.string(stmt, ndx, val, true);
}else{
@ -442,13 +690,6 @@
return stmt;
};
/** Throws if the given Stmt has been finalized, else
it is returned. */
const affirmStmtOpen = function(stmt){
if(!stmt._pStmt) toss("Stmt has been closed.");
return stmt;
};
/** Frees any memory explicitly allocated for the given
Stmt object. Returns stmt. */
const freeBindMemory = function(stmt){
@ -468,6 +709,7 @@
*/
finalize: function(){
if(this._pStmt){
affirmUnlocked(this,'finalize()');
freeBindMemory(this);
delete this.db._statements[this._pStmt];
S.sqlite3_finalize(this._pStmt);
@ -475,12 +717,15 @@
delete this.parameterCount;
delete this._pStmt;
delete this.db;
delete this._isLocked;
}
},
/** Clears all bound values. Returns this object.
Throws if this statement has been finalized. */
clearBindings: function(){
freeBindMemory(affirmStmtOpen(this));
freeBindMemory(
affirmUnlocked(affirmStmtOpen(this), 'clearBindings()')
);
S.sqlite3_clear_bindings(this._pStmt);
this._mayGet = false;
return this;
@ -495,6 +740,7 @@
any memory allocated for them, are retained.
*/
reset: function(alsoClearBinds){
affirmUnlocked(this,'reset()');
if(alsoClearBinds) this.clearBindings();
S.sqlite3_reset(affirmStmtOpen(this)._pStmt);
this._mayGet = false;
@ -565,7 +811,7 @@
}
if(null===arg || undefined===arg){
/* bind NULL */
return bindOne(this, ndx, BindType.null, arg);
return bindOne(this, ndx, BindTypes.null, arg);
}
else if(Array.isArray(arg)){
/* bind each entry by index */
@ -611,7 +857,7 @@
&& BindTypes.null !== t){
toss("Invalid value type for bindAsBlob()");
}
return bindOne(this, ndx, BindType.blob, arg);
return bindOne(this, ndx, BindTypes.blob, arg);
},
/**
Steps the statement one time. If the result indicates that
@ -619,12 +865,16 @@
data is available, false is returned. Throws on error.
*/
step: function(){
affirmUnlocked(this, 'step()');
const rc = S.sqlite3_step(affirmStmtOpen(this)._pStmt);
this._mayGet = false;
switch(rc){
case S.SQLITE_DONE: return false;
case S.SQLITE_ROW: return this._mayGet = true;
default: this.db.checkRc(rc);
default:
console.warn("sqlite3_step() rc=",rc,"SQL =",
S.sqlite3_sql(this._pStmt));
this.db.checkRc(rc);
};
},
/**
@ -713,7 +963,8 @@
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.
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, S.SQLITE_STRING);
@ -725,26 +976,56 @@
finalized.
*/
getColumnName: function(ndx){
return S.sqlite3_column_name(affirmColIndex(this,ndx)._pStmt, ndx);
return S.sqlite3_column_name(
affirmColIndex(affirmStmtOpen(this),ndx)._pStmt, 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 member
holds the number of columns.
*/
getColumnNames: function(tgt){
affirmColIndex(affirmStmtOpen(this),0);
if(!tgt) tgt = [];
for(let i = 0; i < this.columnCount; ++i){
tgt.push(S.sqlite3_column_name(this._pStmt, 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
? S.sqlite3_bind_parameter_index(this._pStmt, name)
: undefined);
}
};
}/*Stmt.prototype*/;
/** OO binding's namespace. */
const SQLite3 = {
version: {
lib: sqlite3.sqlite3_libversion(),
lib: S.sqlite3_libversion(),
ooApi: "0.0.1"
},
DB,
Stmt,
/**
Reports whether a given compile-time option, named by the
given argument.
given argument. It has several distinct uses:
If optName is an array then it is expected to be a list of
compilation options and this function returns an object
which maps each such option to true or false. That object
is returned.
which maps each such option to true or false, indicating
whether or not the given option was included in this
build. That object is returned.
If optName is an object, its keys are expected to be
compilation options and this function sets each entry to
@ -752,10 +1033,10 @@
If passed no arguments then it returns an object mapping
all known compilation options to their compile-time values,
or true if the are defined with no value.
or boolean true if they are defined with no value.
In all other cases it returns true if the option was active
when when compiling the sqlite3 module, else false.
In all other cases it returns true if the given option was
active when when compiling the sqlite3 module, else false.
Compile-time option names may optionally include their
"SQLITE_" prefix. When it returns an object of all options,
@ -798,9 +1079,9 @@
) ? !!S.sqlite3_compileoption_used(optName) : false;
}
};
namespace.sqlite3 = {
api:sqlite3,
api: api,
SQLite3
};
})(self/*worker or window*/);

View File

@ -20,43 +20,77 @@ const mainTest1 = function(namespace){
console.log("Loaded module:",S.sqlite3_libversion(),
S.sqlite3_sourceid());
const db = new oo.DB();
const log = console.log.bind(console);
T.assert(db._pDb);
log("DB:",db.filename);
log("Build options:",oo.compileOptionUsed());
let st = db.prepare("select 3 as a");
log("statement =",st);
T.assert(st._pStmt)
.assert(!st._mayGet)
.assert('a' === st.getColumnName(0))
.assert(st === db._statements[st._pStmt])
.assert(1===st.columnCount)
.assert(0===st.parameterCount)
.mustThrow(()=>st.bind(1,null))
.assert(true===st.step())
.assert(3 === st.get(0))
.mustThrow(()=>st.get(1))
.mustThrow(()=>st.get(0,~S.SQLITE_INTEGER))
.assert(3 === st.get(0,S.SQLITE_INTEGER))
.assert(3 === st.getInt(0))
.assert('3' === st.get(0,S.SQLITE_TEXT))
.assert('3' === st.getString(0))
.assert(3.0 === st.get(0,S.SQLITE_FLOAT))
.assert(3.0 === st.getFloat(0))
.assert(st.get(0,S.SQLITE_BLOB) instanceof Uint8Array)
.assert(st.getBlob(0) instanceof Uint8Array)
.assert(3 === st.get([])[0])
.assert(3 === st.get({}).a)
.assert(3 === st.getJSON(0))
.assert(st._mayGet)
.assert(false===st.step())
.assert(!st._mayGet)
;
let pId = st._pStmt;
st.finalize();
T.assert(!st._pStmt)
.assert(!db._statements[pId]);
log("Test count:",T.counter);
try {
const log = console.log.bind(console);
T.assert(db._pDb);
log("DB:",db.filename);
log("Build options:",oo.compileOptionUsed());
let st = db.prepare("select 3 as a");
log("statement =",st);
T.assert(st._pStmt)
.assert(!st._mayGet)
.assert('a' === st.getColumnName(0))
.assert(st === db._statements[st._pStmt])
.assert(1===st.columnCount)
.assert(0===st.parameterCount)
.mustThrow(()=>st.bind(1,null))
.assert(true===st.step())
.assert(3 === st.get(0))
.mustThrow(()=>st.get(1))
.mustThrow(()=>st.get(0,~S.SQLITE_INTEGER))
.assert(3 === st.get(0,S.SQLITE_INTEGER))
.assert(3 === st.getInt(0))
.assert('3' === st.get(0,S.SQLITE_TEXT))
.assert('3' === st.getString(0))
.assert(3.0 === st.get(0,S.SQLITE_FLOAT))
.assert(3.0 === st.getFloat(0))
.assert(st.get(0,S.SQLITE_BLOB) instanceof Uint8Array)
.assert(st.getBlob(0) instanceof Uint8Array)
.assert(3 === st.get([])[0])
.assert(3 === st.get({}).a)
.assert(3 === st.getJSON(0))
.assert(st._mayGet)
.assert(false===st.step())
.assert(!st._mayGet)
;
let pId = st._pStmt;
st.finalize();
T.assert(!st._pStmt)
.assert(!db._statements[pId]);
let list = [];
db.execMulti({
sql:`CREATE TABLE t(a,b);
INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`,
saveSql: list,
bind: [5,6]
});
log("Exec'd SQL:", list);
let counter = 0, colNames = [];
db.exec("SELECT a a, b b FROM t",{
rowMode: 'object',
callback: function(row,stmt){
if(!counter) stmt.getColumnNames(colNames);
++counter;
T.assert(row.a%2 && row.a<6);
}
});
assert(2 === colNames.length);
assert('a' === colNames[0]);
T.assert(3 === counter);
db.exec("SELECT a a, b b FROM t",{
rowMode: 'array',
callback: function(row,stmt){
++counter;
assert(Array.isArray(row));
T.assert(0===row[1]%2 && row[1]<7);
}
});
T.assert(6 === counter);
log("Test count:",T.counter);
}finally{
db.close();
}
};
self/*window or worker*/.Module.onRuntimeInitialized = function(){

View File

@ -1,5 +1,5 @@
C Implemented\sStmt.get()\sand\sfriends\sfor\sWASM\sOO\s#1\swrapper.\sAdded\sbasic\stests\sfor\sprepare/step/get.\sRestructured\smodule\sinit\soutput\sto\sintroduce\sonly\s1\sglobal-scope\ssymbol\sinstead\sof\s2.
D 2022-05-22T19:09:59.198
C WASM:\sadded\sexec(),\sexecMulti(),\sand\sseveral\sgetters.\sVarious\stouchups\sand\sfixes.
D 2022-05-22T22:00:39.271
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -56,7 +56,7 @@ F ext/expert/sqlite3expert.c 6ca30d73b9ed75bd56d6e0d7f2c962d2affaa72c505458619d0
F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b
F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72
F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 487fc7c83d45c48326f731c89162ed17ab15767e5efede8999d7d6c6e2d04c0f
F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 bba07bb2b81ce667df7916d4c942f0bfe6de6c77f5fe769d479f01250f92ca01
F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 5816adc4d4715b410a9df971c70f55fca610d3a240bd85d2ec34e75483cb54bb
F ext/fiddle/EXPORTED_RUNTIME_METHODS 91d5dcb0168ee056fa1a340cb8ab3c23d922622f8dad39d28919dd8af2b3ade0
F ext/fiddle/Makefile 9277c73e208b9c8093659256c9f07409c877e366480c7c22ec545ee345451d95
F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
@ -64,10 +64,10 @@ F ext/fiddle/fiddle-worker.js c22557b641b47fa1473d3465a4e69fe06b8b09b924955805a4
F ext/fiddle/fiddle.html 657c6c3f860c322fba3c69fa4f7a1209e2d2ce44b4bc65a3e154e3a97c047a7c
F ext/fiddle/fiddle.js f9c79164428e96a5909532f18a8bc8f8c8ec4f738bfc09ad3d2a532c2400f9f0
F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf
F ext/fiddle/sqlite3-api.js e205ccb758678bab7261f184e816d809a5031e1b4babd7738bed98de534787dd
F ext/fiddle/sqlite3-api.js 6d088e075fa2910dcad2584d2a518de33c7dd5ce4d94408691c0cdb1c9c4394b
F ext/fiddle/testing-common.js 53284264504821314f052017b54fa75ab065dcd9cbb754cc8060930498faeee8
F ext/fiddle/testing1.html 68cec1b1c8646a071717e5979f22e4268e6d36d96ba13ad68333351acdbcf1d1
F ext/fiddle/testing1.js 8849eaee6d7b31a195b29f9532c16a87a03e1be780a48cbdec54760c39ebf66c
F ext/fiddle/testing1.js 5e46c8850f826821cb24b13a21e4dabee8dac9ce76845149dac599ab643784ab
F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b
F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea
@ -1967,8 +1967,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P f3bc0328c87cac7d50513b0f13576d8fe7b411396f19c08fbe7e7c657b33cfbf
R e0afacc1df156a218ff63194b2d94770
P 601dc3fa29c2ce2ede5a8320c79050305f3774b6d7bc759247c5021f3b74aaec
R e2842421067cb498614f974216c18719
U stephan
Z 8276de9ecbbfd692683c1010f2d924a3
Z 9b9e03e3e05307544edc47fc1b8505b5
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
601dc3fa29c2ce2ede5a8320c79050305f3774b6d7bc759247c5021f3b74aaec
b790c91b85e9cf8eecce86ac1717e8ccd2c3b6b98a1ad6a5d64eefc94ee86f9d