diff --git a/src/api.js b/src/api.js index 854bf32e..0eef62b6 100644 --- a/src/api.js +++ b/src/api.js @@ -1131,81 +1131,90 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { return sqlite3_changes(this.db); }; - /** Register a custom function with SQLite - @example Register a simple function - db.create_function("addOne", function (x) {return x+1;}) - db.exec("SELECT addOne(1)") // = 2 + var extract_blob = function extract_blob(ptr) { + var size = sqlite3_value_bytes(ptr); + var blob_ptr = sqlite3_value_blob(ptr); + var blob_arg = new Uint8Array(size); + for (var j = 0; j < size; j += 1) { + blob_arg[j] = HEAP8[blob_ptr + j]; + } + return blob_arg; + }; - @param {string} name the name of the function as referenced in - SQL statements. - @param {function} func the actual function to be executed. - @return {Database} The database object. Useful for method chaining - */ + var parseFunctionArguments = function parseFunctionArguments(argc, argv) { + var args = []; + for (var i = 0; i < argc; i += 1) { + var value_ptr = getValue(argv + (4 * i), "i32"); + var value_type = sqlite3_value_type(value_ptr); + var arg; + if ( + value_type === SQLITE_INTEGER + || value_type === SQLITE_FLOAT + ) { + arg = sqlite3_value_double(value_ptr); + } else if (value_type === SQLITE_TEXT) { + arg = sqlite3_value_text(value_ptr); + } else if (value_type === SQLITE_BLOB) { + arg = extract_blob(value_ptr); + } else arg = null; + args.push(arg); + } + return args; + }; + var setFunctionResult = function setFunctionResult(cx, result) { + switch (typeof result) { + case "boolean": + sqlite3_result_int(cx, result ? 1 : 0); + break; + case "number": + sqlite3_result_double(cx, result); + break; + case "string": + sqlite3_result_text(cx, result, -1, -1); + break; + case "object": + if (result === null) { + sqlite3_result_null(cx); + } else if (result.length != null) { + var blobptr = allocate(result, ALLOC_NORMAL); + sqlite3_result_blob(cx, blobptr, result.length, -1); + _free(blobptr); + } else { + sqlite3_result_error(cx, ( + "Wrong API use : tried to return a value " + + "of an unknown type (" + result + ")." + ), -1); + } + break; + default: + sqlite3_result_null(cx); + } + }; + + /** Register a custom function with SQLite + @example Register a simple function + db.create_function("addOne", function (x) {return x+1;}) + db.exec("SELECT addOne(1)") // = 2 + + @param {string} name the name of the function as referenced in + SQL statements. + @param {function} func the actual function to be executed. + @return {Database} The database object. Useful for method chaining + */ Database.prototype["create_function"] = function create_function( name, func ) { function wrapped_func(cx, argc, argv) { + var args = parseFunctionArguments(argc, argv); var result; - function extract_blob(ptr) { - var size = sqlite3_value_bytes(ptr); - var blob_ptr = sqlite3_value_blob(ptr); - var blob_arg = new Uint8Array(size); - for (var j = 0; j < size; j += 1) { - blob_arg[j] = HEAP8[blob_ptr + j]; - } - return blob_arg; - } - var args = []; - for (var i = 0; i < argc; i += 1) { - var value_ptr = getValue(argv + (4 * i), "i32"); - var value_type = sqlite3_value_type(value_ptr); - var arg; - if ( - value_type === SQLITE_INTEGER - || value_type === SQLITE_FLOAT - ) { - arg = sqlite3_value_double(value_ptr); - } else if (value_type === SQLITE_TEXT) { - arg = sqlite3_value_text(value_ptr); - } else if (value_type === SQLITE_BLOB) { - arg = extract_blob(value_ptr); - } else arg = null; - args.push(arg); - } try { result = func.apply(null, args); } catch (error) { sqlite3_result_error(cx, error, -1); return; } - switch (typeof result) { - case "boolean": - sqlite3_result_int(cx, result ? 1 : 0); - break; - case "number": - sqlite3_result_double(cx, result); - break; - case "string": - sqlite3_result_text(cx, result, -1, -1); - break; - case "object": - if (result === null) { - sqlite3_result_null(cx); - } else if (result.length != null) { - var blobptr = allocate(result, ALLOC_NORMAL); - sqlite3_result_blob(cx, blobptr, result.length, -1); - _free(blobptr); - } else { - sqlite3_result_error(cx, ( - "Wrong API use : tried to return a value " - + "of an unknown type (" + result + ")." - ), -1); - } - break; - default: - sqlite3_result_null(cx); - } + setFunctionResult(cx, result); } if (Object.prototype.hasOwnProperty.call(this.functions, name)) { removeFunction(this.functions[name]); @@ -1229,6 +1238,89 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { return this; }; + /** Register a custom aggregate with SQLite + @example Register a aggregate function + db.create_aggregate( + "js_sum", + function () { return { sum: 0 }; }, + function (state, value) { state.sum+=value; }, + function (state) { return state.sum; } + ); + db.exec("CREATE TABLE test (col); INSERT INTO test VALUES (1), (2)"); + db.exec("SELECT js_sum(col) FROM test"); // = 3 + + @param {string} name the name of the aggregate as referenced in + SQL statements. + @param {function} init the actual function to be executed on initialize. + @param {function} step the actual function to be executed on step by step. + @param {function} finalize the actual function to be executed on finalize. + @return {Database} The database object. Useful for method chaining + */ + Database.prototype["create_aggregate"] = function create_aggregate( + name, + init, + step, + finalize + ) { + var state; + function wrapped_step(cx, argc, argv) { + if (!state) { + state = init(); + } + var args = parseFunctionArguments(argc, argv); + var mergedArgs = [state].concat(args); + try { + step.apply(null, mergedArgs); + } catch (error) { + sqlite3_result_error(cx, error, -1); + } + } + function wrapped_finalize(cx) { + var result; + try { + result = finalize.apply(null, [state]); + } catch (error) { + sqlite3_result_error(cx, error, -1); + state = null; + return; + } + setFunctionResult(cx, result); + state = null; + } + + if (Object.prototype.hasOwnProperty.call(this.functions, name)) { + removeFunction(this.functions[name]); + delete this.functions[name]; + } + if (Object.prototype.hasOwnProperty.call( + this.functions, + name + "__finalize" + )) { + removeFunction(this.functions[name + "__finalize"]); + delete this.functions[name + "__finalize"]; + } + // The signature of the wrapped function is : + // void wrapped(sqlite3_context *db, int argc, sqlite3_value **argv) + var step_ptr = addFunction(wrapped_step, "viii"); + // The signature of the wrapped function is : + // void wrapped(sqlite3_context *db) + var finalize_ptr = addFunction(wrapped_finalize, "vi"); + this.functions[name] = step_ptr; + this.functions[name + "__finalize"] = finalize_ptr; + this.handleError(sqlite3_create_function_v2( + this.db, + name, + step.length - 1, + SQLITE_UTF8, + 0, + 0, + step_ptr, + finalize_ptr, + 0 + )); + return this; + }; + // export Database to Module Module.Database = Database; }; diff --git a/test/test_aggregate_functions.js b/test/test_aggregate_functions.js new file mode 100644 index 00000000..5a0ed16e --- /dev/null +++ b/test/test_aggregate_functions.js @@ -0,0 +1,17 @@ +exports.test = function (SQL, assert) { + var db = new SQL.Database(); + + db.create_aggregate( + "js_sum", + function () { return { sum: 0 }; }, + function (state, value) { state.sum += value; }, + function (state) { return state.sum; } + ); + + db.exec("CREATE TABLE test (col);"); + db.exec("INSERT INTO test VALUES (1), (2), (3);"); + var result = db.exec("SELECT js_sum(col) FROM test;"); + assert.equal(result[0].values[0][0], 6, "Simple aggregate function."); + + // TODO: Add test cases.. +} \ No newline at end of file pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy