-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
sqlite: fix aggregate step function undefined handling #58780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
When an aggregate step function returns undefined, preserve the accumulator value instead of resetting it to null. This allows step functions to conditionally skip processing certain values while maintaining the current aggregate state. Before this fix, returning undefined would incorrectly set the accumulator to null, breaking the aggregation logic. Fixes the step function behavior to match expected semantics where undefined return values preserve state and only explicit null returns store null values.
Review requested:
|
Fix trailing whitespace and arrow function formatting to pass Node.js JavaScript linting requirements.
Here's a simple test harness to run to study the issue, if it helps: // test-aggregate-undefined.js
// Run with: node --experimental-sqlite test-aggregate-undefined.js
// Test with node:sqlite (shows the bug)
const { DatabaseSync } = require("node:sqlite");
console.log("Testing aggregate function with undefined return value...\n");
// Create an in-memory database
const db = new DatabaseSync(":memory:");
// Create test data
db.exec("CREATE TABLE test (value INTEGER)");
db.exec("INSERT INTO test VALUES (1), (2), (3), (4), (5)");
// Register an aggregate function that returns undefined for value 3
db.aggregate("test_sum", {
start: 0,
step: (accumulator, value) => {
console.log(`step called: accumulator=${accumulator}, value=${value}`);
if (value === 3) {
console.log(
" -> returning undefined (should keep accumulator unchanged)",
);
return undefined;
}
const result = accumulator + value;
console.log(` -> returning ${result}`);
return result;
},
});
// Execute the aggregate
console.log("\nRunning aggregate query...");
const result = db.prepare("SELECT test_sum(value) as sum FROM test").get();
console.log("\nFinal result:", result);
// Expected behavior (like better-sqlite3):
// - accumulator=0, value=1 -> returns 1
// - accumulator=1, value=2 -> returns 3
// - accumulator=3, value=3 -> returns undefined (keeps 3)
// - accumulator=3, value=4 -> returns 7
// - accumulator=7, value=5 -> returns 12
// Final result: { sum: 12 }
// Actual behavior with node:sqlite bug:
// - accumulator=0, value=1 -> returns 1
// - accumulator=1, value=2 -> returns 3
// - accumulator=3, value=3 -> returns undefined (stores undefined!)
// - accumulator=undefined, value=4 -> returns NaN
// - accumulator=NaN, value=5 -> returns NaN
// Final result: { sum: null }
db.close();
// If you have better-sqlite3 installed, you can compare:
console.log("\n---\nFor comparison with better-sqlite3 (if installed):");
try {
const Database = require("better-sqlite3");
const db2 = new Database(":memory:");
db2.exec("CREATE TABLE test (value INTEGER)");
db2.exec("INSERT INTO test VALUES (1), (2), (3), (4), (5)");
db2.aggregate("test_sum", {
start: 0,
step: (accumulator, value) => {
if (value === 3) return undefined;
return accumulator + value;
},
});
const result2 = db2.prepare("SELECT test_sum(value) as sum FROM test").get();
console.log("better-sqlite3 result:", result2); // { sum: 12 }
db2.close();
} catch (e) {
console.log("(better-sqlite3 not installed for comparison)");
} |
Thanks for that, @mceachen . We should also note this in the documentation. |
Would it be helpful for me to do that? Should I do that in another PR, or amend this one? |
Can do in this same PR. |
|
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #58780 +/- ##
==========================================
- Coverage 90.13% 90.07% -0.06%
==========================================
Files 640 640
Lines 188294 188289 -5
Branches 36923 36917 -6
==========================================
- Hits 169712 169598 -114
- Misses 11304 11405 +101
- Partials 7278 7286 +8
🚀 New features to boost your workflow:
|
First off: kudos to the team for
node:sqlite
, it's a great thing for node.js to have built-in! (I'm a maintainer of better-sqlite3)I was working on a bridge facade from better-sqlite to node:sqlite and found an edge case in the aggregate function callback.
When an aggregate step function returns undefined (not NULL), we should preserve the accumulator value instead of resetting it to null. This allows step functions to conditionally skip processing certain values while maintaining the current aggregate state.
Before this fix, returning undefined would incorrectly set the accumulator to null, breaking the aggregation logic.
This patch makes the step function behavior match (presumably) expected semantics, where undefined return values preserve state and only explicit null returns store null values.
This patch includes a bunch of new test coverage for:
Here are the failing tests without the patch applied to node_sqlite.cc:
Cheers!