1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

wasm: eliminated the dependency on the deprecated emcc-provided allocate() function. Adjacent cleanups in blob binding.

FossilOrigin-Name: 140618b212e3aa9ff2df20f22af846a1c4c5ffaeefd84330446f61362b39a8f1
This commit is contained in:
stephan
2022-06-25 06:46:22 +00:00
parent 766ba7917e
commit f170b0868d
5 changed files with 136 additions and 62 deletions

View File

@ -1,12 +1,11 @@
ALLOC_NORMAL
FS FS
UTF8ToString
addFunction addFunction
allocate
allocateUTF8OnStack allocateUTF8OnStack
ccall ccall
cwrap cwrap
getValue getValue
intArrayFromString
lengthBytesUTF8
removeFunction removeFunction
setValue setValue
stackAlloc stackAlloc

View File

@ -122,6 +122,27 @@ Module.postRun.push(function(namespace/*the module object, the target for
const SQM = namespace/*the sqlite module object */; const SQM = namespace/*the sqlite module object */;
/** Throws a new Error, the message of which is the concatenation
all args with a space between each. */
const toss = function(){
throw new Error(Array.prototype.join.call(arguments, ' '));
};
/**
Returns true if v appears to be one of our supported TypedArray types:
Uint8Array or Int8Array.
*/
const isSupportedTypedArray = function(v){
return v && (undefined!==v.byteLength) && (v.byteLength === v.length);
};
/** Returns true if isSupportedTypedArray(v) does, else throws with a message
that v is not a supported TypedArray value. */
const affirmSupportedTypedArray = function(v){
return isSupportedTypedArray(v)
|| toss("Value is not of a supported TypedArray type.");
};
/** /**
Set up the main sqlite3 binding API here, mimicking the C API as Set up the main sqlite3 binding API here, mimicking the C API as
closely as we can. closely as we can.
@ -181,15 +202,38 @@ Module.postRun.push(function(namespace/*the module object, the target for
*/ */
wasm: { wasm: {
/** /**
Proxy for SQM.allocate(). TODO: remove deprecated allocate(), api.wasm._malloc()'s srcTypedArray.byteLength bytes,
use _malloc(). The kicker is that allocate() uses populates them with the values from the source array,
module-init-internal state which isn't normally visible to and returns the pointer to that memory. The pointer
us. must eventually be passed to api.wasm._free() to clean
it up.
As a special case, to avoid further special cases where
this is used, if srcTypedArray.byteLength is 0, it
allocates a single byte and sets it to the value 0.
ACHTUNG: this currently only works for Uint8Array and
Int8Array types.
*/ */
allocate: (slab, allocator=SQM.ALLOC_NORMAL)=>SQM.allocate(slab, allocator), mallocFromTypedArray: function(srcTypedArray){
affirmSupportedTypedArray(srcTypedArray);
const pRet = api.wasm._malloc(srcTypedArray.byteLength || 1);
if(srcTypedArray.byteLength){
api.wasm._malloc.HEAP.set(srcTypedArray, pRet);
/* That unfortunately does not behave intuitively
when copying, e.g., the contents of a
Uint16Array, copying only 1 byte of each
entry instead of blitting the whole array
contents over the destination array. A potential TODO
is handle that copying here so that we can support a wider
array (haha) of bindable-as-blob types. */
}
else api.wasm._malloc.HEAP[pRet] = 0;
return pRet;
},
/** /**
The buffer which holds the heap memory managed by the The TypedArray buffer which holds the heap memory
emscripten-installed allocator. managed by the emscripten-installed _malloc().
*/ */
HEAP8: SQM.HEAP8 HEAP8: SQM.HEAP8
} }
@ -360,12 +404,20 @@ Module.postRun.push(function(namespace/*the module object, the target for
/** Populate api.wasm with several members of the module object... */ /** Populate api.wasm with several members of the module object... */
['getValue','setValue', 'stackSave', 'stackRestore', 'stackAlloc', ['getValue','setValue', 'stackSave', 'stackRestore', 'stackAlloc',
'allocateUTF8OnStack', '_malloc', '_free', 'allocateUTF8OnStack', '_malloc', '_free',
'addFunction', 'removeFunction' 'addFunction', 'removeFunction',
'intArrayFromString'
].forEach(function(m){ ].forEach(function(m){
if(undefined === (api.wasm[m] = SQM[m])){ if(undefined === (api.wasm[m] = SQM[m])){
toss("Internal init error: Module."+m+" not found."); toss("Internal init error: Module."+m+" not found.");
} }
}); });
/**
The array object which holds the raw bytes managed by the
_malloc() binding. Side note: why on earth _malloc() manages
HEAP8 (an Int8Array), rather than HEAPU8 (a Uint8Array), is a
mystery.
*/
api.wasm._malloc.HEAP = api.wasm.HEAP8;
/* What follows is colloquially known as "OO API #1". It is a /* What follows is colloquially known as "OO API #1". It is a
binding of the sqlite3 API which is designed to be run within binding of the sqlite3 API which is designed to be run within
@ -374,12 +426,6 @@ Module.postRun.push(function(namespace/*the module object, the target for
the sqlite3 binding if, e.g., the wrapper is in the main thread the sqlite3 binding if, e.g., the wrapper is in the main thread
and the sqlite3 API is in a worker. */ and the sqlite3 API is in a worker. */
/** Throws a new error, concatenating all args with a space between
each. */
const toss = function(){
throw new Error(Array.prototype.join.call(arguments, ' '));
};
/** /**
The DB class wraps a sqlite3 db handle. The DB class wraps a sqlite3 db handle.
@ -954,10 +1000,9 @@ Module.postRun.push(function(namespace/*the module object, the target for
if(null===val) { if(null===val) {
api.sqlite3_result_null(pCx); api.sqlite3_result_null(pCx);
break; break;
}else if(undefined!==val.length){ }else if(isSupportedTypedArray(val)){
const pBlob = const pBlob = api.wasm.mallocFromTypedArray(val);
api.wasm.allocate(val); api.sqlite3_result_blob(pCx, pBlob, val.byteLength,
api.sqlite3_result_blob(pCx, pBlob, val.length,
api.SQLITE_TRANSIENT); api.SQLITE_TRANSIENT);
api.wasm._free(pBlob); api.wasm._free(pBlob);
break; break;
@ -1068,8 +1113,8 @@ Module.postRun.push(function(namespace/*the module object, the target for
case BindTypes.string: case BindTypes.string:
return t; return t;
default: default:
if(v instanceof Uint8Array) return BindTypes.blob; //console.log("isSupportedBindType",t,v);
return undefined; return isSupportedTypedArray(v) ? BindTypes.blob : undefined;
} }
}; };
@ -1078,7 +1123,8 @@ Module.postRun.push(function(namespace/*the module object, the target for
function returns that value, else it throws. function returns that value, else it throws.
*/ */
const affirmSupportedBindType = function(v){ const affirmSupportedBindType = function(v){
return isSupportedBindType(v) || toss("Unsupport bind() argument type."); //console.log('affirmSupportedBindType',v);
return isSupportedBindType(v) || toss("Unsupported bind() argument type:",typeof v);
}; };
/** /**
@ -1140,11 +1186,12 @@ Module.postRun.push(function(namespace/*the module object, the target for
if(!f._){ if(!f._){
f._ = { f._ = {
string: function(stmt, ndx, val, asBlob){ string: function(stmt, ndx, val, asBlob){
const bytes = intArrayFromString(val,true); const bytes = api.wasm.intArrayFromString(val,true);
const pStr = api.wasm.allocate(bytes); const pStr = api.wasm._malloc(bytes.length || 1);
stmt._allocs.push(pStr); stmt._allocs.push(pStr);
api.wasm._malloc.HEAP.set(bytes, pStr);
const func = asBlob ? api.sqlite3_bind_blob : api.sqlite3_bind_text; const func = asBlob ? api.sqlite3_bind_blob : api.sqlite3_bind_text;
return func(stmt._pStmt, ndx, pStr, bytes.length, 0); return func(stmt._pStmt, ndx, pStr, bytes.length, api.SQLITE_STATIC);
} }
}; };
} }
@ -1175,17 +1222,21 @@ Module.postRun.push(function(namespace/*the module object, the target for
if('string'===typeof val){ if('string'===typeof val){
rc = f._.string(stmt, ndx, val, true); rc = f._.string(stmt, ndx, val, true);
}else{ }else{
const len = val.length; if(!isSupportedTypedArray(val)){
if(undefined===len){
toss("Binding a value as a blob requires", toss("Binding a value as a blob requires",
"that it have a length member."); "that it be a string, Uint8Array, or Int8Array.");
} }
const pBlob = api.wasm.allocate(val); //console.debug("Binding blob",len,val);
const pBlob = api.wasm.mallocFromTypedArray(val);
stmt._allocs.push(pBlob); stmt._allocs.push(pBlob);
rc = api.sqlite3_bind_blob(stmt._pStmt, ndx, pBlob, len, 0); rc = api.sqlite3_bind_blob(stmt._pStmt, ndx, pBlob, val.byteLength,
api.SQLITE_STATIC);
} }
break;
} }
default: toss("Unsupported bind() argument type."); default:
console.warn("Unsupported bind() argument type:",val);
toss("Unsupported bind() argument type.");
} }
if(rc) stmt.db.checkRc(rc); if(rc) stmt.db.checkRc(rc);
return stmt; return stmt;
@ -1286,7 +1337,9 @@ Module.postRun.push(function(namespace/*the module object, the target for
- Strings are bound as strings (use bindAsBlob() to force - Strings are bound as strings (use bindAsBlob() to force
blob binding). blob binding).
- Uint8Array instances are bound as blobs. - Uint8Array and Int8Array instances are bound as blobs.
(TODO: support binding other TypedArray types with larger
int sizes.)
If passed an array, each element of the array is bound at If passed an array, each element of the array is bound at
the parameter index equal to the array index plus 1 the parameter index equal to the array index plus 1
@ -1297,7 +1350,7 @@ Module.postRun.push(function(namespace/*the module object, the target for
bindable parameter names, including any `$`, `@`, or `:` bindable parameter names, including any `$`, `@`, or `:`
prefix. Because `$` is a legal identifier chararacter in prefix. Because `$` is a legal identifier chararacter in
JavaScript, that is the suggested prefix for bindable JavaScript, that is the suggested prefix for bindable
parameters. parameters: `stmt.bind({$a: 1, $b: 2})`.
It returns this object on success and throws on It returns this object on success and throws on
error. Errors include: error. Errors include:
@ -1342,8 +1395,9 @@ Module.postRun.push(function(namespace/*the module object, the target for
arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v)); arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v));
return this; return this;
} }
else if('object'===typeof arg/*null was checked above*/){ else if('object'===typeof arg/*null was checked above*/
/* bind by name */ && !isSupportedTypedArray(arg)){
/* Treat each property of arg as a named bound parameter. */
if(1!==arguments.length){ if(1!==arguments.length){
toss("When binding an object, an index argument is not permitted."); toss("When binding an object, an index argument is not permitted.");
} }
@ -1353,25 +1407,25 @@ Module.postRun.push(function(namespace/*the module object, the target for
arg[k])); arg[k]));
return this; return this;
}else{ }else{
return bindOne(this, ndx, return bindOne(this, ndx, affirmSupportedBindType(arg), arg);
affirmSupportedBindType(arg), arg);
} }
toss("Should not reach this point."); toss("Should not reach this point.");
}, },
/** /**
Special case of bind() which binds the given value Special case of bind() which binds the given value using
using the BLOB binding mechanism instead of the default the BLOB binding mechanism instead of the default selected
selected one for the value. The ndx may be a numbered one for the value. The ndx may be a numbered or named bind
or named bind index. The value must be of type string, index. The value must be of type string, null/undefined
Uint8Array, or null/undefined (both treated as null). (both treated as null), or a TypedArray of a type supported
by the bind() API.
If passed a single argument, a bind index of 1 is assumed. If passed a single argument, a bind index of 1 is assumed.
*/ */
bindAsBlob: function(ndx,arg){ bindAsBlob: function(ndx,arg){
affirmStmtOpen(this); affirmStmtOpen(this);
if(1===arguments.length){ if(1===arguments.length){
arg = ndx;
ndx = 1; ndx = 1;
arg = arguments[0];
} }
const t = affirmSupportedBindType(arg); const t = affirmSupportedBindType(arg);
if(BindTypes.string !== t && BindTypes.blob !== t if(BindTypes.string !== t && BindTypes.blob !== t

View File

@ -77,6 +77,12 @@
}); });
T.assert(2 === list.length); T.assert(2 === list.length);
//log("Exec'd SQL:", list); //log("Exec'd SQL:", list);
let blob = db.selectValue("select b from t where a='blob'");
T.assert(blob instanceof Uint8Array).
assert(0x68===blob[0] && 0x69===blob[1]);
blob = null;
let counter = 0, colNames = []; let counter = 0, colNames = [];
list.length = 0; list.length = 0;
db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{ db.exec(new TextEncoder('utf-8').encode("SELECT a a, b b FROM t"),{
@ -121,9 +127,7 @@
} }
}).createFunction({ }).createFunction({
name: "asis", name: "asis",
callback: function(arg){ callback: (arg)=>arg
return arg;
}
}); });
log("Testing DB::selectValue() w/ UDF..."); log("Testing DB::selectValue() w/ UDF...");
@ -132,11 +136,7 @@
assert(3===db.selectValue("select bar(1,2)")). assert(3===db.selectValue("select bar(1,2)")).
assert(-1===db.selectValue("select bar(1,2,-4)")). assert(-1===db.selectValue("select bar(1,2,-4)")).
assert('hi'===db.selectValue("select asis('hi')")); assert('hi'===db.selectValue("select asis('hi')"));
let blob = db.selectValue("select asis(X'6869')");
T.assert(blob instanceof Uint8Array).
assert(2 === blob.length).
assert(0x68==blob[0] && 0x69==blob[1]);
const eqApprox = function(v1,v2,factor=0.05){ const eqApprox = function(v1,v2,factor=0.05){
//log('eqApprox',v1, v2); //log('eqApprox',v1, v2);
return v1>=(v2-factor) && v1<=(v2+factor); return v1>=(v2-factor) && v1<=(v2+factor);
@ -150,6 +150,27 @@
assert(eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))). assert(eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))).
assert(eqApprox(1.3,db.selectValue("select asis(1 + 0.3)"))) assert(eqApprox(1.3,db.selectValue("select asis(1 + 0.3)")))
; ;
log("Testing binding and UDF propagation of blobs...");
let blobArg = new Uint8Array(2);
blobArg.set([0x68, 0x69], 0);
let blobRc = db.selectValue("select asis(?1)", blobArg);
T.assert(blobRc instanceof Uint8Array).
assert(2 === blobRc.length).
assert(0x68==blobRc[0] && 0x69==blobRc[1]);
blobRc = db.selectValue("select asis(X'6869')");
T.assert(blobRc instanceof Uint8Array).
assert(2 === blobRc.length).
assert(0x68==blobRc[0] && 0x69==blobRc[1]);
blobArg = new Int8Array(2);
blobArg.set([0x68, 0x69]);
console.debug("blobArg=",blobArg);
blobRc = db.selectValue("select asis(?1)", blobArg);
T.assert(blobRc instanceof Uint8Array).
assert(2 === blobRc.length);
console.debug("blobRc=",blobRc);
T.assert(0x68==blobRc[0] && 0x69==blobRc[1]);
}; };
const testAttach = function(db){ const testAttach = function(db){

View File

@ -1,5 +1,5 @@
C wasm:\scorrected\sthe\spropagation\sof\stext/blob\svalues\svia\sUDFs.\sDB.exec()'s\ssql\smay\snow\sbe\san\sarray\sof\sstrings\swhich\sget\sconcatenated\stogether\sbefore\spassing\sit\son\sto\ssqlite3_prepare_v2().\sDB.exec()'s\scallback\snow\sapplies\sto\sthe\sfirst\sstatement\swhich\shas\sresult\scolumns\sinstead\sof\sonly\sthe\sfirst\sstatement.\sFixed\sa\sprecedence\sbut\swhich\scaused\sisInt32()\sto\sreport\sfalse\spositives. C wasm:\seliminated\sthe\sdependency\son\sthe\sdeprecated\semcc-provided\sallocate()\sfunction.\sAdjacent\scleanups\sin\sblob\sbinding.
D 2022-06-25T03:53:43.503 D 2022-06-25T06:46:22.284
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -57,7 +57,7 @@ F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaed
F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72 F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72
F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 7fb73f7150ab79d83bb45a67d257553c905c78cd3d693101699243f36c5ae6c3 F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 7fb73f7150ab79d83bb45a67d257553c905c78cd3d693101699243f36c5ae6c3
F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api 540b9dec63a3a62a256e2f030827848a92e9b9d9b6fa5c0188295a4a1c5382cd F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api 540b9dec63a3a62a256e2f030827848a92e9b9d9b6fa5c0188295a4a1c5382cd
F ext/fiddle/EXPORTED_RUNTIME_METHODS b831017ba67ba993b34a27400cef2f6095bd6789c0fc4eba7e7a251c207be31c F ext/fiddle/EXPORTED_RUNTIME_METHODS e499bbb5201bf671850da8dcd47de31b0db47c1183f0c669016801b4d2035534
F ext/fiddle/Makefile e25d34a0e1324f771d64c09c592601b97219282011587e6ce410fa8acdedb913 F ext/fiddle/Makefile e25d34a0e1324f771d64c09c592601b97219282011587e6ce410fa8acdedb913
F ext/fiddle/SqliteTestUtil.js 559731c3e8e0de330ec7d292e6c1846566408caee6637acc8a119ac338a8781c F ext/fiddle/SqliteTestUtil.js 559731c3e8e0de330ec7d292e6c1846566408caee6637acc8a119ac338a8781c
F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
@ -65,11 +65,11 @@ F ext/fiddle/fiddle-worker.js 88bc2193a6cb6a3f04d8911bed50a4401fe6f277de7a71ba83
F ext/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08 F ext/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08
F ext/fiddle/fiddle.js 812f9954cc7c4b191884ad171f36fcf2d0112d0a7ecfdf6087896833a0c079a8 F ext/fiddle/fiddle.js 812f9954cc7c4b191884ad171f36fcf2d0112d0a7ecfdf6087896833a0c079a8
F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf
F ext/fiddle/sqlite3-api.js b706be1f777a4508f9e3e237301e11c108933ae651b3fa599bf047cac34b5bdb F ext/fiddle/sqlite3-api.js 706bb55b3901a90ec3a0e588f00d4c260d61d5b5ac916b894f48cdd6203ff0ba
F ext/fiddle/sqlite3-worker.js a9c2b614beca187dbdd8c053ec2770cc61ec1ac9c0ec6398ceb49a79f705a421 F ext/fiddle/sqlite3-worker.js a9c2b614beca187dbdd8c053ec2770cc61ec1ac9c0ec6398ceb49a79f705a421
F ext/fiddle/testing.css 750572dded671d2cf142bbcb27af5542522ac08db128245d0b9fe410aa1d7f2a F ext/fiddle/testing.css 750572dded671d2cf142bbcb27af5542522ac08db128245d0b9fe410aa1d7f2a
F ext/fiddle/testing1.html ea1f3be727f78e420007f823912c1a03b337ecbb8e79449abc2244ad4fe15d9a F ext/fiddle/testing1.html ea1f3be727f78e420007f823912c1a03b337ecbb8e79449abc2244ad4fe15d9a
F ext/fiddle/testing1.js 3e5f1fb22764ec735396db518b85ea6b3070f6c2da479cba69c8d8e89f8299f8 F ext/fiddle/testing1.js f3a5bd125154a51dfe20b699cb90c7cf2a23fbb5eaf9129cd4fb06e262de2647
F ext/fiddle/testing2.html 9063b2430ade2fe9da4e711addd1b51a2741cf0c7ebf6926472a5e5dd63c0bc4 F ext/fiddle/testing2.html 9063b2430ade2fe9da4e711addd1b51a2741cf0c7ebf6926472a5e5dd63c0bc4
F ext/fiddle/testing2.js 7b45b4e7fddbd51dbaf89b6722c02758051b34bac5a98c11b569a7e7572f88ee F ext/fiddle/testing2.js 7b45b4e7fddbd51dbaf89b6722c02758051b34bac5a98c11b569a7e7572f88ee
F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
@ -1978,8 +1978,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 42dc500819bfc1308a9542aa2cae4f6dfd98a29237c59cec82e0e6f9e0bf3779 P 37a8fecb56793fbd3763a2240a0bd62b639c811934d3af2ef0e5ff579073d632
R a152bf70e7d16ac14f437dceeb13aaae R 4c023b8516cd5b1336a52ada20a79c7d
U stephan U stephan
Z 385f37f8ee9972f16da6e6bace96df52 Z 05e90a0a470a56e05a8109a17f4f47da
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
37a8fecb56793fbd3763a2240a0bd62b639c811934d3af2ef0e5ff579073d632 140618b212e3aa9ff2df20f22af846a1c4c5ffaeefd84330446f61362b39a8f1