diff --git a/packages/loot-core/src/server/aql/compiler.js b/packages/loot-core/src/server/aql/compiler.js index a5ad99f43f8655f2079824c8217665b1c77f0953..48e076ad6e91762255d3de57ef41cd38a5de54f7 100644 --- a/packages/loot-core/src/server/aql/compiler.js +++ b/packages/loot-core/src/server/aql/compiler.js @@ -927,7 +927,7 @@ function isAggregateFunction(expr) { return true; } - return argExprs.find(ex => isAggregateFunction(ex)); + return !!argExprs.find(ex => isAggregateFunction(ex)); } export function isAggregateQuery(queryState) { @@ -939,7 +939,7 @@ export function isAggregateQuery(queryState) { return true; } - return queryState.selectExpressions.find(expr => { + return !!queryState.selectExpressions.find(expr => { if (typeof expr !== 'string') { let [_, value] = Object.entries(expr)[0]; return isAggregateFunction(value); diff --git a/packages/loot-core/src/server/aql/schema/executors.js b/packages/loot-core/src/server/aql/schema/executors.js index df3c229a98fd4bdec0c5e28ad6ae0f791532a7b1..2be475e7f4d986e99e15a7a815fbf3fa426dc414 100644 --- a/packages/loot-core/src/server/aql/schema/executors.js +++ b/packages/loot-core/src/server/aql/schema/executors.js @@ -85,18 +85,28 @@ async function execTransactionsGrouped( let { withDead } = queryState; let whereDead = withDead ? '' : `AND ${sql.from}.tombstone = 0`; + // Aggregate queries don't make sense for a grouped transactions + // query. We never should include both parent and children + // transactions as it would duplicate amounts and the final number + // would never make sense. In this case, switch back to the "inline" + // type where only non-parent transactions are considered if (isAggregateQuery(queryState)) { - let allSql = ` - SELECT ${sql.select} - FROM ${sql.from} - ${sql.joins} - ${sql.where} AND is_parent = 0 ${whereDead} - ${sql.groupBy} - ${sql.orderBy} - ${sql.limit != null ? `LIMIT ${sql.limit}` : ''} - ${sql.offset != null ? `OFFSET ${sql.offset}` : ''} - `; - return db.all(allSql); + let s = { ...sql }; + + // Modify the where to only include non-parents + s.where = `${s.where} AND ${s.from}.is_parent = 0`; + + // We also want to exclude deleted transactions. Normally we + // handle this manually down below, but now that we are doing a + // normal query we want to rely on the view. Unfortunately, SQL + // has already been generated so we can't easily change the view + // name here; instead, we change it and map it back to the name + // used elsewhere in the query. Ideally we'd improve this + if (!withDead) { + s.from = 'v_transactions_internal_alive v_transactions_internal'; + } + + return execQuery(queryState, state, s, params, outputTypes); } let rows; diff --git a/packages/loot-core/src/server/aql/schema/executors.test.js b/packages/loot-core/src/server/aql/schema/executors.test.js index 65591868092596e0c067da5dfdfee3a07204bcdc..1df6cf60786c83179ceeabb000e4fbeb03235c05 100644 --- a/packages/loot-core/src/server/aql/schema/executors.test.js +++ b/packages/loot-core/src/server/aql/schema/executors.test.js @@ -207,7 +207,7 @@ describe('transaction executors', () => { let { data } = await runQuery(aggQuery.serialize()); - let sum = arr.reduce((sum, trans) => { + let sum = aliveTransactions(arr).reduce((sum, trans) => { let amount = trans.amount || 0; let matched = (amount < -5 || amount > -2) && trans.payee != null; if (!trans.tombstone && !trans.is_parent && matched) {