@@ -225,6 +225,14 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
225225 "" ,
226226 [ "number" , "string" , "number" ]
227227 ) ;
228+
229+ // https://www.sqlite.org/c3ref/aggregate_context.html
230+ // void *sqlite3_aggregate_context(sqlite3_context*, int nBytes)
231+ var sqlite3_aggregate_context = cwrap (
232+ "sqlite3_aggregate_context" ,
233+ "number" ,
234+ [ "number" , "number" ]
235+ ) ;
228236 var registerExtensionFunctions = cwrap (
229237 "RegisterExtensionFunctions" ,
230238 "number" ,
@@ -1131,81 +1139,90 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
11311139 return sqlite3_changes ( this . db ) ;
11321140 } ;
11331141
1134- /** Register a custom function with SQLite
1135- @example Register a simple function
1136- db.create_function("addOne", function (x) {return x+1;})
1137- db.exec("SELECT addOne(1)") // = 2
1142+ var extract_blob = function extract_blob ( ptr ) {
1143+ var size = sqlite3_value_bytes ( ptr ) ;
1144+ var blob_ptr = sqlite3_value_blob ( ptr ) ;
1145+ var blob_arg = new Uint8Array ( size ) ;
1146+ for ( var j = 0 ; j < size ; j += 1 ) {
1147+ blob_arg [ j ] = HEAP8 [ blob_ptr + j ] ;
1148+ }
1149+ return blob_arg ;
1150+ } ;
11381151
1139- @param {string } name the name of the function as referenced in
1140- SQL statements.
1141- @param {function } func the actual function to be executed.
1142- @return {Database } The database object. Useful for method chaining
1143- */
1152+ var parseFunctionArguments = function parseFunctionArguments ( argc , argv ) {
1153+ var args = [ ] ;
1154+ for ( var i = 0 ; i < argc ; i += 1 ) {
1155+ var value_ptr = getValue ( argv + ( 4 * i ) , "i32" ) ;
1156+ var value_type = sqlite3_value_type ( value_ptr ) ;
1157+ var arg ;
1158+ if (
1159+ value_type === SQLITE_INTEGER
1160+ || value_type === SQLITE_FLOAT
1161+ ) {
1162+ arg = sqlite3_value_double ( value_ptr ) ;
1163+ } else if ( value_type === SQLITE_TEXT ) {
1164+ arg = sqlite3_value_text ( value_ptr ) ;
1165+ } else if ( value_type === SQLITE_BLOB ) {
1166+ arg = extract_blob ( value_ptr ) ;
1167+ } else arg = null ;
1168+ args . push ( arg ) ;
1169+ }
1170+ return args ;
1171+ } ;
1172+ var setFunctionResult = function setFunctionResult ( cx , result ) {
1173+ switch ( typeof result ) {
1174+ case "boolean" :
1175+ sqlite3_result_int ( cx , result ? 1 : 0 ) ;
1176+ break ;
1177+ case "number" :
1178+ sqlite3_result_double ( cx , result ) ;
1179+ break ;
1180+ case "string" :
1181+ sqlite3_result_text ( cx , result , - 1 , - 1 ) ;
1182+ break ;
1183+ case "object" :
1184+ if ( result === null ) {
1185+ sqlite3_result_null ( cx ) ;
1186+ } else if ( result . length != null ) {
1187+ var blobptr = allocate ( result , ALLOC_NORMAL ) ;
1188+ sqlite3_result_blob ( cx , blobptr , result . length , - 1 ) ;
1189+ _free ( blobptr ) ;
1190+ } else {
1191+ sqlite3_result_error ( cx , (
1192+ "Wrong API use : tried to return a value "
1193+ + "of an unknown type (" + result + ")."
1194+ ) , - 1 ) ;
1195+ }
1196+ break ;
1197+ default :
1198+ sqlite3_result_null ( cx ) ;
1199+ }
1200+ } ;
1201+
1202+ /** Register a custom function with SQLite
1203+ @example <caption>Register a simple function</caption>
1204+ db.create_function("addOne", function (x) {return x+1;})
1205+ db.exec("SELECT addOne(1)") // = 2
1206+
1207+ @param {string } name the name of the function as referenced in
1208+ SQL statements.
1209+ @param {function } func the actual function to be executed.
1210+ @return {Database } The database object. Useful for method chaining
1211+ */
11441212 Database . prototype [ "create_function" ] = function create_function (
11451213 name ,
11461214 func
11471215 ) {
11481216 function wrapped_func ( cx , argc , argv ) {
1217+ var args = parseFunctionArguments ( argc , argv ) ;
11491218 var result ;
1150- function extract_blob ( ptr ) {
1151- var size = sqlite3_value_bytes ( ptr ) ;
1152- var blob_ptr = sqlite3_value_blob ( ptr ) ;
1153- var blob_arg = new Uint8Array ( size ) ;
1154- for ( var j = 0 ; j < size ; j += 1 ) {
1155- blob_arg [ j ] = HEAP8 [ blob_ptr + j ] ;
1156- }
1157- return blob_arg ;
1158- }
1159- var args = [ ] ;
1160- for ( var i = 0 ; i < argc ; i += 1 ) {
1161- var value_ptr = getValue ( argv + ( 4 * i ) , "i32" ) ;
1162- var value_type = sqlite3_value_type ( value_ptr ) ;
1163- var arg ;
1164- if (
1165- value_type === SQLITE_INTEGER
1166- || value_type === SQLITE_FLOAT
1167- ) {
1168- arg = sqlite3_value_double ( value_ptr ) ;
1169- } else if ( value_type === SQLITE_TEXT ) {
1170- arg = sqlite3_value_text ( value_ptr ) ;
1171- } else if ( value_type === SQLITE_BLOB ) {
1172- arg = extract_blob ( value_ptr ) ;
1173- } else arg = null ;
1174- args . push ( arg ) ;
1175- }
11761219 try {
11771220 result = func . apply ( null , args ) ;
11781221 } catch ( error ) {
11791222 sqlite3_result_error ( cx , error , - 1 ) ;
11801223 return ;
11811224 }
1182- switch ( typeof result ) {
1183- case "boolean" :
1184- sqlite3_result_int ( cx , result ? 1 : 0 ) ;
1185- break ;
1186- case "number" :
1187- sqlite3_result_double ( cx , result ) ;
1188- break ;
1189- case "string" :
1190- sqlite3_result_text ( cx , result , - 1 , - 1 ) ;
1191- break ;
1192- case "object" :
1193- if ( result === null ) {
1194- sqlite3_result_null ( cx ) ;
1195- } else if ( result . length != null ) {
1196- var blobptr = allocate ( result , ALLOC_NORMAL ) ;
1197- sqlite3_result_blob ( cx , blobptr , result . length , - 1 ) ;
1198- _free ( blobptr ) ;
1199- } else {
1200- sqlite3_result_error ( cx , (
1201- "Wrong API use : tried to return a value "
1202- + "of an unknown type (" + result + ")."
1203- ) , - 1 ) ;
1204- }
1205- break ;
1206- default :
1207- sqlite3_result_null ( cx ) ;
1208- }
1225+ setFunctionResult ( cx , result ) ;
12091226 }
12101227 if ( Object . prototype . hasOwnProperty . call ( this . functions , name ) ) {
12111228 removeFunction ( this . functions [ name ] ) ;
@@ -1229,6 +1246,137 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
12291246 return this ;
12301247 } ;
12311248
1249+ /** Register a custom aggregate with SQLite
1250+ @example <caption>Register a custom sum function</caption>
1251+ db.create_aggregate("js_sum", {
1252+ init: () => 0,
1253+ step: (state, value) => state + value,
1254+ finalize: state => state
1255+ });
1256+ db.exec("SELECT js_sum(column1) FROM (VALUES (1), (2))"); // = 3
1257+
1258+ @param {string } name the name of the aggregate as referenced in
1259+ SQL statements.
1260+ @param {object } aggregateFunctions
1261+ object containing at least a step function.
1262+ @param {function(): T } [aggregateFunctions.init = ()=>null]
1263+ a function receiving no arguments and returning an initial
1264+ value for the aggregate function. The initial value will be
1265+ null if this key is omitted.
1266+ @param {function(T, any) : T } aggregateFunctions.step
1267+ a function receiving the current state and a value to aggregate
1268+ and returning a new state.
1269+ Will receive the value from init for the first step.
1270+ @param {function(T): any } [aggregateFunctions.finalize = (state)=>state]
1271+ a function returning the result of the aggregate function
1272+ given its final state.
1273+ If omitted, the value returned by the last step
1274+ will be used as the final value.
1275+ @return {Database } The database object. Useful for method chaining
1276+ @template T
1277+ */
1278+ Database . prototype [ "create_aggregate" ] = function create_aggregate (
1279+ name ,
1280+ aggregateFunctions
1281+ ) {
1282+ // Default initializer and finalizer
1283+ var init = aggregateFunctions [ "init" ]
1284+ || function init ( ) { return null ; } ;
1285+ var finalize = aggregateFunctions [ "finalize" ]
1286+ || function finalize ( state ) { return state ; } ;
1287+ var step = aggregateFunctions [ "step" ] ;
1288+
1289+ if ( ! step ) {
1290+ throw "An aggregate function must have a step function in " + name ;
1291+ }
1292+
1293+ // state is a state object; we'll use the pointer p to serve as the
1294+ // key for where we hold our state so that multiple invocations of
1295+ // this function never step on each other
1296+ var state = { } ;
1297+
1298+ function wrapped_step ( cx , argc , argv ) {
1299+ // > The first time the sqlite3_aggregate_context(C,N) routine is
1300+ // > called for a particular aggregate function, SQLite allocates N
1301+ // > bytes of memory, zeroes out that memory, and returns a pointer
1302+ // > to the new memory.
1303+ //
1304+ // We're going to use that pointer as a key to our state array,
1305+ // since using sqlite3_aggregate_context as it's meant to be used
1306+ // through webassembly seems to be very difficult. Just allocate
1307+ // one byte.
1308+ var p = sqlite3_aggregate_context ( cx , 1 ) ;
1309+
1310+ // If this is the first invocation of wrapped_step, call `init`
1311+ //
1312+ // Make sure that every path through the step and finalize
1313+ // functions deletes the value state[p] when it's done so we don't
1314+ // leak memory and possibly stomp the init value of future calls
1315+ if ( ! Object . hasOwnProperty . call ( state , p ) ) state [ p ] = init ( ) ;
1316+
1317+ var args = parseFunctionArguments ( argc , argv ) ;
1318+ var mergedArgs = [ state [ p ] ] . concat ( args ) ;
1319+ try {
1320+ state [ p ] = step . apply ( null , mergedArgs ) ;
1321+ } catch ( error ) {
1322+ delete state [ p ] ;
1323+ sqlite3_result_error ( cx , error , - 1 ) ;
1324+ }
1325+ }
1326+
1327+ function wrapped_finalize ( cx ) {
1328+ var result ;
1329+ var p = sqlite3_aggregate_context ( cx , 1 ) ;
1330+ try {
1331+ result = finalize ( state [ p ] ) ;
1332+ } catch ( error ) {
1333+ delete state [ p ] ;
1334+ sqlite3_result_error ( cx , error , - 1 ) ;
1335+ return ;
1336+ }
1337+ setFunctionResult ( cx , result ) ;
1338+ delete state [ p ] ;
1339+ }
1340+
1341+ if ( Object . hasOwnProperty . call ( this . functions , name ) ) {
1342+ removeFunction ( this . functions [ name ] ) ;
1343+ delete this . functions [ name ] ;
1344+ }
1345+ var finalize_name = name + "__finalize" ;
1346+ if ( Object . hasOwnProperty . call ( this . functions , finalize_name ) ) {
1347+ removeFunction ( this . functions [ finalize_name ] ) ;
1348+ delete this . functions [ finalize_name ] ;
1349+ }
1350+ // The signature of the wrapped function is :
1351+ // void wrapped(sqlite3_context *db, int argc, sqlite3_value **argv)
1352+ var step_ptr = addFunction ( wrapped_step , "viii" ) ;
1353+
1354+ // The signature of the wrapped function is :
1355+ // void wrapped(sqlite3_context *db)
1356+ var finalize_ptr = addFunction ( wrapped_finalize , "vi" ) ;
1357+ this . functions [ name ] = step_ptr ;
1358+ this . functions [ finalize_name ] = finalize_ptr ;
1359+
1360+ // passing null to the sixth parameter defines this as an aggregate
1361+ // function
1362+ //
1363+ // > An aggregate SQL function requires an implementation of xStep and
1364+ // > xFinal and NULL pointer must be passed for xFunc.
1365+ // - http://www.sqlite.org/c3ref/create_function.html
1366+ this . handleError ( sqlite3_create_function_v2 (
1367+ this . db ,
1368+ name ,
1369+ step . length - 1 ,
1370+ SQLITE_UTF8 ,
1371+ 0 ,
1372+ 0 ,
1373+ step_ptr ,
1374+ finalize_ptr ,
1375+ 0
1376+ ) ) ;
1377+ return this ;
1378+ } ;
1379+
12321380 // export Database to Module
12331381 Module . Database = Database ;
12341382} ;
0 commit comments