@@ -225,6 +225,14 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
225
225
"" ,
226
226
[ "number" , "string" , "number" ]
227
227
) ;
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
+ ) ;
228
236
var registerExtensionFunctions = cwrap (
229
237
"RegisterExtensionFunctions" ,
230
238
"number" ,
@@ -1131,81 +1139,90 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
1131
1139
return sqlite3_changes ( this . db ) ;
1132
1140
} ;
1133
1141
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
+ } ;
1138
1151
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
+ */
1144
1212
Database . prototype [ "create_function" ] = function create_function (
1145
1213
name ,
1146
1214
func
1147
1215
) {
1148
1216
function wrapped_func ( cx , argc , argv ) {
1217
+ var args = parseFunctionArguments ( argc , argv ) ;
1149
1218
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
- }
1176
1219
try {
1177
1220
result = func . apply ( null , args ) ;
1178
1221
} catch ( error ) {
1179
1222
sqlite3_result_error ( cx , error , - 1 ) ;
1180
1223
return ;
1181
1224
}
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 ) ;
1209
1226
}
1210
1227
if ( Object . prototype . hasOwnProperty . call ( this . functions , name ) ) {
1211
1228
removeFunction ( this . functions [ name ] ) ;
@@ -1229,6 +1246,137 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() {
1229
1246
return this ;
1230
1247
} ;
1231
1248
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
+
1232
1380
// export Database to Module
1233
1381
Module . Database = Database ;
1234
1382
} ;
0 commit comments