@actual-app/core 26.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.swcrc +11 -0
- package/bin/build-browser +40 -0
- package/bin/copy-migrations +9 -0
- package/db.sqlite +0 -0
- package/default-db.sqlite +0 -0
- package/migrations/.force-copy-windows +0 -0
- package/migrations/1548957970627_remove-db-version.sql +5 -0
- package/migrations/1550601598648_payees.sql +23 -0
- package/migrations/1555786194328_remove_category_group_unique.sql +25 -0
- package/migrations/1561751833510_indexes.sql +7 -0
- package/migrations/1567699552727_budget.sql +38 -0
- package/migrations/1582384163573_cleared.sql +6 -0
- package/migrations/1597756566448_rules.sql +10 -0
- package/migrations/1608652596043_parent_field.sql +13 -0
- package/migrations/1608652596044_trans_views.sql +56 -0
- package/migrations/1612625548236_optimize.sql +7 -0
- package/migrations/1614782639336_trans_views2.sql +33 -0
- package/migrations/1615745967948_meta.sql +10 -0
- package/migrations/1616167010796_accounts_order.sql +5 -0
- package/migrations/1618975177358_schedules.sql +28 -0
- package/migrations/1632571489012_remove_cache.js +136 -0
- package/migrations/1679728867040_rules_conditions.sql +5 -0
- package/migrations/1681115033845_add_schedule_name.sql +5 -0
- package/migrations/1682974838138_remove_payee_rules.sql +5 -0
- package/migrations/1685007876842_add_category_hidden.sql +6 -0
- package/migrations/1686139660866_remove_account_type.sql +5 -0
- package/migrations/1688749527273_transaction_filters.sql +10 -0
- package/migrations/1688841238000_add_account_type.sql +5 -0
- package/migrations/1691233396000_add_schedule_next_date_tombstone.sql +5 -0
- package/migrations/1694438752000_add_goal_targets.sql +7 -0
- package/migrations/1697046240000_add_reconciled.sql +5 -0
- package/migrations/1704572023730_add_account_sync_source.sql +5 -0
- package/migrations/1704572023731_add_missing_goCardless_sync_source.sql +9 -0
- package/migrations/1707267033000_reports.sql +28 -0
- package/migrations/1712784523000_unhide_input_group.sql +8 -0
- package/migrations/1716359441000_include_current.sql +5 -0
- package/migrations/1720310586000_link_transfer_schedules.sql +19 -0
- package/migrations/1720664867241_add_payee_favorite.sql +5 -0
- package/migrations/1720665000000_goal_context.sql +6 -0
- package/migrations/1722717601000_reports_move_selected_categories.js +55 -0
- package/migrations/1722804019000_create_dashboard_table.js +69 -0
- package/migrations/1723665565000_prefs.js +59 -0
- package/migrations/1730744182000_fix_dashboard_table.sql +7 -0
- package/migrations/1736640000000_custom_report_sorting.sql +7 -0
- package/migrations/1737158400000_add_learn_categories_to_payees.sql +5 -0
- package/migrations/1738491452000_sorting_rename.sql +13 -0
- package/migrations/1739139550000_bank_sync_page.sql +7 -0
- package/migrations/1740506588539_add_last_reconciled_at.sql +5 -0
- package/migrations/1745425408000_update_budgetType_pref.sql +7 -0
- package/migrations/1749799110000_add_tags.sql +10 -0
- package/migrations/1749799110001_tags_tombstone.sql +5 -0
- package/migrations/1754611200000_add_category_template_settings.sql +5 -0
- package/migrations/1759260219000_add_trim_interval_report_setting.sql +6 -0
- package/migrations/1759842823172_add_isGlobal_to_preferences.sql +1 -0
- package/migrations/1762178745667_rename_csv_skip_lines_pref.sql +8 -0
- package/migrations/1765518577215_multiple_dashboards.js +30 -0
- package/migrations/1768872504000_add_payee_locations.sql +21 -0
- package/package.json +128 -0
- package/src/mocks/arbitrary-schema.ts +162 -0
- package/src/mocks/budget.ts +901 -0
- package/src/mocks/files/8859-1.qfx +63 -0
- package/src/mocks/files/best.data-ever$.QFX +124 -0
- package/src/mocks/files/big.data.QiF +91 -0
- package/src/mocks/files/budgets/.commit-to-git +0 -0
- package/src/mocks/files/camt/camt.053.payee-memo.xml +127 -0
- package/src/mocks/files/camt/camt.053.xml +463 -0
- package/src/mocks/files/credit-card.ofx +11 -0
- package/src/mocks/files/data-multi-decimal.ofx +64 -0
- package/src/mocks/files/data-payee-memo.ofx +75 -0
- package/src/mocks/files/data-payee-memo.qif +17 -0
- package/src/mocks/files/data.ofx +124 -0
- package/src/mocks/files/data.qfx +124 -0
- package/src/mocks/files/data.qif +91 -0
- package/src/mocks/files/default-budget-template/db.sqlite +0 -0
- package/src/mocks/files/default-budget-template/metadata.json +6 -0
- package/src/mocks/files/html-vals.qfx +17 -0
- package/src/mocks/index.ts +221 -0
- package/src/mocks/migrations/1508717984291_up_add-poop.sql +13 -0
- package/src/mocks/migrations/1508718036311_up_modify-poop.sql +2 -0
- package/src/mocks/migrations/1508727787513_remove-is_income.sql +15 -0
- package/src/mocks/random.ts +16 -0
- package/src/mocks/setup.ts +180 -0
- package/src/mocks/spreadsheet.ts +101 -0
- package/src/mocks/util.ts +82 -0
- package/src/platform/client/connection/README.md +3 -0
- package/src/platform/client/connection/__mocks__/index.ts +67 -0
- package/src/platform/client/connection/index-types.ts +95 -0
- package/src/platform/client/connection/index.browser.ts +213 -0
- package/src/platform/client/connection/index.ts +155 -0
- package/src/platform/client/undo/index.ts +59 -0
- package/src/platform/exceptions/__mocks__/index.ts +7 -0
- package/src/platform/exceptions/index.ts +9 -0
- package/src/platform/server/asyncStorage/__mocks__/index.ts +50 -0
- package/src/platform/server/asyncStorage/index-types.ts +35 -0
- package/src/platform/server/asyncStorage/index.api.ts +2 -0
- package/src/platform/server/asyncStorage/index.electron.ts +88 -0
- package/src/platform/server/asyncStorage/index.ts +126 -0
- package/src/platform/server/connection/README.md +3 -0
- package/src/platform/server/connection/__mocks__/index.ts +15 -0
- package/src/platform/server/connection/index-types.ts +20 -0
- package/src/platform/server/connection/index.api.ts +13 -0
- package/src/platform/server/connection/index.electron.ts +102 -0
- package/src/platform/server/connection/index.ts +154 -0
- package/src/platform/server/fetch/__mocks__/index.ts +3 -0
- package/src/platform/server/fetch/index.api.ts +1 -0
- package/src/platform/server/fetch/index.electron.ts +18 -0
- package/src/platform/server/fetch/index.ts +20 -0
- package/src/platform/server/fs/index.api.ts +198 -0
- package/src/platform/server/fs/index.electron.ts +208 -0
- package/src/platform/server/fs/index.test.ts +117 -0
- package/src/platform/server/fs/index.ts +416 -0
- package/src/platform/server/fs/path-join.api.ts +1 -0
- package/src/platform/server/fs/path-join.electron.ts +1 -0
- package/src/platform/server/fs/path-join.ts +97 -0
- package/src/platform/server/fs/shared.ts +33 -0
- package/src/platform/server/indexeddb/index.ts +115 -0
- package/src/platform/server/log/index.ts +43 -0
- package/src/platform/server/sqlite/index.api.ts +2 -0
- package/src/platform/server/sqlite/index.electron.ts +134 -0
- package/src/platform/server/sqlite/index.test.ts +108 -0
- package/src/platform/server/sqlite/index.ts +241 -0
- package/src/platform/server/sqlite/normalise.ts +9 -0
- package/src/platform/server/sqlite/unicodeLike.test.ts +58 -0
- package/src/platform/server/sqlite/unicodeLike.ts +31 -0
- package/src/server/__mocks__/post.ts +9 -0
- package/src/server/__snapshots__/main.test.ts.snap +199 -0
- package/src/server/__snapshots__/sheet.test.ts.snap +9 -0
- package/src/server/accounts/__snapshots__/sync.test.ts.snap +136 -0
- package/src/server/accounts/app-bank-sync.test.ts +136 -0
- package/src/server/accounts/app.ts +1294 -0
- package/src/server/accounts/link.ts +25 -0
- package/src/server/accounts/payees.ts +36 -0
- package/src/server/accounts/sync.test.ts +679 -0
- package/src/server/accounts/sync.ts +1168 -0
- package/src/server/accounts/title/index.ts +60 -0
- package/src/server/accounts/title/lower-case.ts +93 -0
- package/src/server/accounts/title/specials.ts +21 -0
- package/src/server/admin/app.ts +241 -0
- package/src/server/api-models.ts +244 -0
- package/src/server/api.test.ts +36 -0
- package/src/server/api.ts +1030 -0
- package/src/server/app.ts +91 -0
- package/src/server/aql/compiler.test.ts +966 -0
- package/src/server/aql/compiler.ts +1222 -0
- package/src/server/aql/exec.test.ts +289 -0
- package/src/server/aql/exec.ts +128 -0
- package/src/server/aql/index.ts +41 -0
- package/src/server/aql/schema/executors.test.ts +420 -0
- package/src/server/aql/schema/executors.ts +345 -0
- package/src/server/aql/schema/index.test.ts +67 -0
- package/src/server/aql/schema/index.ts +409 -0
- package/src/server/aql/schema-helpers.test.ts +242 -0
- package/src/server/aql/schema-helpers.ts +208 -0
- package/src/server/aql/views.test.ts +62 -0
- package/src/server/aql/views.ts +57 -0
- package/src/server/auth/app.ts +387 -0
- package/src/server/bench.ts +29 -0
- package/src/server/budget/actions.ts +686 -0
- package/src/server/budget/app.ts +469 -0
- package/src/server/budget/base.test.ts +340 -0
- package/src/server/budget/base.ts +339 -0
- package/src/server/budget/category-template-context.test.ts +1658 -0
- package/src/server/budget/category-template-context.ts +862 -0
- package/src/server/budget/cleanup-template.pegjs +27 -0
- package/src/server/budget/cleanup-template.ts +408 -0
- package/src/server/budget/envelope.ts +403 -0
- package/src/server/budget/goal-template.pegjs +110 -0
- package/src/server/budget/goal-template.ts +309 -0
- package/src/server/budget/report.ts +308 -0
- package/src/server/budget/schedule-template.test.ts +184 -0
- package/src/server/budget/schedule-template.ts +351 -0
- package/src/server/budget/statements.ts +60 -0
- package/src/server/budget/template-notes.test.ts +393 -0
- package/src/server/budget/template-notes.ts +323 -0
- package/src/server/budget/util.ts +25 -0
- package/src/server/budgetfiles/__snapshots__/backups.test.ts.snap +101 -0
- package/src/server/budgetfiles/app.ts +672 -0
- package/src/server/budgetfiles/backups.test.ts +79 -0
- package/src/server/budgetfiles/backups.ts +251 -0
- package/src/server/cloud-storage.ts +467 -0
- package/src/server/dashboard/app.ts +373 -0
- package/src/server/db/__snapshots__/index.test.ts.snap +271 -0
- package/src/server/db/index.test.ts +300 -0
- package/src/server/db/index.ts +855 -0
- package/src/server/db/mappings.ts +59 -0
- package/src/server/db/sort.ts +58 -0
- package/src/server/db/types/index.ts +342 -0
- package/src/server/db/util.ts +36 -0
- package/src/server/encryption/app.ts +133 -0
- package/src/server/encryption/encryption-internals.api.ts +2 -0
- package/src/server/encryption/encryption-internals.electron.ts +89 -0
- package/src/server/encryption/encryption-internals.ts +109 -0
- package/src/server/encryption/encryption.test.ts +19 -0
- package/src/server/encryption/index.test.ts +19 -0
- package/src/server/encryption/index.ts +89 -0
- package/src/server/errors.ts +110 -0
- package/src/server/filters/app.ts +191 -0
- package/src/server/importers/actual.ts +49 -0
- package/src/server/importers/index.ts +58 -0
- package/src/server/importers/ynab4-types.ts +163 -0
- package/src/server/importers/ynab4.ts +470 -0
- package/src/server/importers/ynab5-types.ts +290 -0
- package/src/server/importers/ynab5.ts +1193 -0
- package/src/server/main-app.ts +25 -0
- package/src/server/main.test.ts +392 -0
- package/src/server/main.ts +336 -0
- package/src/server/migrate/__snapshots__/migrations.test.ts.snap +17 -0
- package/src/server/migrate/cli.ts +100 -0
- package/src/server/migrate/migrations.test.ts +81 -0
- package/src/server/migrate/migrations.ts +192 -0
- package/src/server/models.ts +184 -0
- package/src/server/mutators.ts +139 -0
- package/src/server/notes/app.ts +18 -0
- package/src/server/payees/app.ts +351 -0
- package/src/server/polyfills.ts +26 -0
- package/src/server/post.ts +219 -0
- package/src/server/preferences/app.ts +249 -0
- package/src/server/prefs.ts +91 -0
- package/src/server/reports/app.ts +187 -0
- package/src/server/rules/action.ts +344 -0
- package/src/server/rules/app.ts +193 -0
- package/src/server/rules/condition.ts +436 -0
- package/src/server/rules/customFunctions.ts +61 -0
- package/src/server/rules/formula-action.test.ts +175 -0
- package/src/server/rules/handlebars-helpers.ts +131 -0
- package/src/server/rules/index.test.ts +1095 -0
- package/src/server/rules/index.ts +22 -0
- package/src/server/rules/rule-indexer.ts +89 -0
- package/src/server/rules/rule-utils.ts +274 -0
- package/src/server/rules/rule.ts +193 -0
- package/src/server/schedules/app.test.ts +502 -0
- package/src/server/schedules/app.ts +644 -0
- package/src/server/schedules/find-schedules.ts +391 -0
- package/src/server/server-config.ts +59 -0
- package/src/server/sheet.test.ts +101 -0
- package/src/server/sheet.ts +280 -0
- package/src/server/spreadsheet/__snapshots__/spreadsheet.test.ts.snap +5 -0
- package/src/server/spreadsheet/app.ts +54 -0
- package/src/server/spreadsheet/globals.ts +13 -0
- package/src/server/spreadsheet/graph-data-structure.ts +165 -0
- package/src/server/spreadsheet/scratch +60 -0
- package/src/server/spreadsheet/spreadsheet.test.ts +191 -0
- package/src/server/spreadsheet/spreadsheet.ts +523 -0
- package/src/server/spreadsheet/util.ts +15 -0
- package/src/server/sql/init.sql +88 -0
- package/src/server/sync/__snapshots__/sync.test.ts.snap +31 -0
- package/src/server/sync/app.ts +29 -0
- package/src/server/sync/encoder.ts +129 -0
- package/src/server/sync/index.ts +820 -0
- package/src/server/sync/make-test-message.ts +19 -0
- package/src/server/sync/migrate.test.ts +169 -0
- package/src/server/sync/migrate.ts +48 -0
- package/src/server/sync/repair.ts +39 -0
- package/src/server/sync/reset.ts +91 -0
- package/src/server/sync/sync.property.test.ts +385 -0
- package/src/server/sync/sync.test.ts +349 -0
- package/src/server/sync/utils.ts +3 -0
- package/src/server/tags/app.ts +101 -0
- package/src/server/tests/mockData.json +9352 -0
- package/src/server/tests/mockSyncServer.ts +119 -0
- package/src/server/tools/app.ts +152 -0
- package/src/server/transactions/__snapshots__/transaction-rules.test.ts.snap +173 -0
- package/src/server/transactions/__snapshots__/transfer.test.ts.snap +655 -0
- package/src/server/transactions/app.ts +136 -0
- package/src/server/transactions/export/export-to-csv.ts +132 -0
- package/src/server/transactions/import/__snapshots__/parse-file.test.ts.snap +1582 -0
- package/src/server/transactions/import/ofx2json.test.ts +33 -0
- package/src/server/transactions/import/ofx2json.ts +157 -0
- package/src/server/transactions/import/parse-file.test.ts +224 -0
- package/src/server/transactions/import/parse-file.ts +286 -0
- package/src/server/transactions/import/qif2json.ts +110 -0
- package/src/server/transactions/import/xmlcamt2json.ts +168 -0
- package/src/server/transactions/index.ts +196 -0
- package/src/server/transactions/merge.test.ts +370 -0
- package/src/server/transactions/merge.ts +139 -0
- package/src/server/transactions/transaction-rules.test.ts +994 -0
- package/src/server/transactions/transaction-rules.ts +1038 -0
- package/src/server/transactions/transfer.test.ts +221 -0
- package/src/server/transactions/transfer.ts +173 -0
- package/src/server/undo.ts +271 -0
- package/src/server/update.ts +37 -0
- package/src/server/util/budget-name.ts +61 -0
- package/src/server/util/custom-sync-mapping.ts +48 -0
- package/src/server/util/rschedule.ts +9 -0
- package/src/shared/__mocks__/platform.ts +7 -0
- package/src/shared/__snapshots__/months.test.ts.snap +21 -0
- package/src/shared/arithmetic.test.ts +112 -0
- package/src/shared/arithmetic.ts +170 -0
- package/src/shared/async.test.ts +135 -0
- package/src/shared/async.ts +76 -0
- package/src/shared/constants.ts +5 -0
- package/src/shared/currencies.ts +70 -0
- package/src/shared/dashboard.ts +260 -0
- package/src/shared/environment.ts +18 -0
- package/src/shared/errors.ts +195 -0
- package/src/shared/locale.ts +27 -0
- package/src/shared/location-utils.test.ts +69 -0
- package/src/shared/location-utils.ts +49 -0
- package/src/shared/months.test.ts +5 -0
- package/src/shared/months.ts +485 -0
- package/src/shared/normalisation.ts +6 -0
- package/src/shared/platform.electron.ts +21 -0
- package/src/shared/platform.ts +20 -0
- package/src/shared/query.ts +176 -0
- package/src/shared/rules.test.ts +56 -0
- package/src/shared/rules.ts +371 -0
- package/src/shared/schedules.test.ts +570 -0
- package/src/shared/schedules.ts +560 -0
- package/src/shared/test-helpers.ts +156 -0
- package/src/shared/transactions.test.ts +275 -0
- package/src/shared/transactions.ts +433 -0
- package/src/shared/transfer.test.ts +75 -0
- package/src/shared/transfer.ts +16 -0
- package/src/shared/user.ts +4 -0
- package/src/shared/util.test.ts +240 -0
- package/src/shared/util.ts +633 -0
- package/src/types/api-handlers.ts +287 -0
- package/src/types/budget.ts +8 -0
- package/src/types/file.ts +47 -0
- package/src/types/handlers.ts +46 -0
- package/src/types/models/account.ts +24 -0
- package/src/types/models/bank-sync.ts +23 -0
- package/src/types/models/bank.ts +6 -0
- package/src/types/models/category-group.ts +11 -0
- package/src/types/models/category.ts +13 -0
- package/src/types/models/dashboard.ts +199 -0
- package/src/types/models/gocardless.ts +84 -0
- package/src/types/models/import-transaction.ts +56 -0
- package/src/types/models/index.ts +23 -0
- package/src/types/models/nearby-payee.ts +7 -0
- package/src/types/models/note.ts +4 -0
- package/src/types/models/openid.ts +8 -0
- package/src/types/models/payee-location.ts +8 -0
- package/src/types/models/payee.ts +10 -0
- package/src/types/models/pluggyai.ts +19 -0
- package/src/types/models/reports.ts +144 -0
- package/src/types/models/rule.ts +174 -0
- package/src/types/models/schedule.ts +49 -0
- package/src/types/models/simplefin.ts +28 -0
- package/src/types/models/tags.ts +6 -0
- package/src/types/models/templates.ts +135 -0
- package/src/types/models/transaction-filter.ts +9 -0
- package/src/types/models/transaction.ts +39 -0
- package/src/types/models/user-access.ts +10 -0
- package/src/types/models/user.ts +25 -0
- package/src/types/prefs.ts +167 -0
- package/src/types/server-events.ts +86 -0
- package/src/types/server-handlers.ts +27 -0
- package/src/types/util.ts +26 -0
- package/tsconfig.json +34 -0
- package/typings/pegjs.ts +1 -0
- package/typings/process-worker.ts +12 -0
- package/typings/vite-plugin-peggy-loader.ts +1 -0
- package/typings/window.ts +62 -0
- package/vite.config.ts +109 -0
- package/vite.desktop.config.ts +59 -0
- package/vitest.config.ts +43 -0
- package/vitest.web.config.ts +38 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// @ts-strict-ignore
|
|
2
|
+
|
|
3
|
+
import { aqlQuery } from '..';
|
|
4
|
+
import { q } from '../../../shared/query';
|
|
5
|
+
import type { QueryState } from '../../../shared/query';
|
|
6
|
+
import type { CategoryEntity } from '../../../types/models';
|
|
7
|
+
import * as db from '../../db';
|
|
8
|
+
import { whereIn } from '../../db/util';
|
|
9
|
+
import { isAggregateQuery } from '../compiler';
|
|
10
|
+
import type { CompilerState, OutputTypes, SqlPieces } from '../compiler';
|
|
11
|
+
import { execQuery } from '../exec';
|
|
12
|
+
import type { AqlQueryExecutor } from '../exec';
|
|
13
|
+
import { convertOutputType } from '../schema-helpers';
|
|
14
|
+
|
|
15
|
+
// Transactions executor
|
|
16
|
+
|
|
17
|
+
type SplitsOption = 'all' | 'inline' | 'none' | 'grouped';
|
|
18
|
+
|
|
19
|
+
function toGroup(parents, children, mapper = x => x) {
|
|
20
|
+
return parents.reduce((list, parent) => {
|
|
21
|
+
const childs = children.get(parent.id) || [];
|
|
22
|
+
list.push({
|
|
23
|
+
...mapper(parent),
|
|
24
|
+
subtransactions: childs.map(mapper),
|
|
25
|
+
});
|
|
26
|
+
return list;
|
|
27
|
+
}, []);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// These two queries will return very different things:
|
|
31
|
+
//
|
|
32
|
+
// q('transactions').select({ $count: 'id' })
|
|
33
|
+
// q('transactions', { splits: "grouped" }).select({ $count: 'id' })
|
|
34
|
+
//
|
|
35
|
+
// The first will return the count of non-split and child
|
|
36
|
+
// transactions, and the second will return the count of all parent
|
|
37
|
+
// (or non-split) transactions
|
|
38
|
+
|
|
39
|
+
function execTransactions(
|
|
40
|
+
compilerState: CompilerState,
|
|
41
|
+
queryState: QueryState,
|
|
42
|
+
sqlPieces: SqlPieces,
|
|
43
|
+
params: (string | number)[],
|
|
44
|
+
outputTypes: OutputTypes,
|
|
45
|
+
) {
|
|
46
|
+
const tableOptions = queryState.tableOptions || {};
|
|
47
|
+
const splitType = tableOptions.splits
|
|
48
|
+
? (tableOptions.splits as string)
|
|
49
|
+
: 'inline';
|
|
50
|
+
if (!isValidSplitsOption(splitType)) {
|
|
51
|
+
throw new Error(`Invalid "splits" option for transactions: "${splitType}"`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (splitType === 'all' || splitType === 'inline' || splitType === 'none') {
|
|
55
|
+
return execTransactionsBasic(
|
|
56
|
+
compilerState,
|
|
57
|
+
queryState,
|
|
58
|
+
sqlPieces,
|
|
59
|
+
params,
|
|
60
|
+
splitType,
|
|
61
|
+
outputTypes,
|
|
62
|
+
);
|
|
63
|
+
} else if (splitType === 'grouped') {
|
|
64
|
+
return execTransactionsGrouped(
|
|
65
|
+
compilerState,
|
|
66
|
+
queryState,
|
|
67
|
+
sqlPieces,
|
|
68
|
+
params,
|
|
69
|
+
outputTypes,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _isUnhappy(filter) {
|
|
75
|
+
// These fields can be filtered - all split transactions will
|
|
76
|
+
// still be returned regardless
|
|
77
|
+
for (const key of Object.keys(filter)) {
|
|
78
|
+
if (key === '$or' || key === '$and') {
|
|
79
|
+
if (filter[key] && _isUnhappy(filter[key])) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
} else if (!(key.indexOf('account') === 0 || key === 'date')) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function isHappyPathQuery(queryState) {
|
|
90
|
+
return queryState.filterExpressions.find(_isUnhappy) == null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function execTransactionsGrouped(
|
|
94
|
+
compilerState: CompilerState,
|
|
95
|
+
queryState: QueryState,
|
|
96
|
+
sqlPieces: SqlPieces,
|
|
97
|
+
params: (string | number)[],
|
|
98
|
+
outputTypes: OutputTypes,
|
|
99
|
+
) {
|
|
100
|
+
const { withDead } = queryState;
|
|
101
|
+
const whereDead = withDead ? '' : `AND ${sqlPieces.from}.tombstone = 0`;
|
|
102
|
+
|
|
103
|
+
// Aggregate queries don't make sense for a grouped transactions
|
|
104
|
+
// query. We never should include both parent and children
|
|
105
|
+
// transactions as it would duplicate amounts and the final number
|
|
106
|
+
// would never make sense. In this case, switch back to the "inline"
|
|
107
|
+
// type where only non-parent transactions are considered
|
|
108
|
+
if (isAggregateQuery(queryState)) {
|
|
109
|
+
const s = { ...sqlPieces };
|
|
110
|
+
|
|
111
|
+
// Modify the where to only include non-parents
|
|
112
|
+
s.where = `${s.where} AND ${s.from}.is_parent = 0`;
|
|
113
|
+
|
|
114
|
+
// We also want to exclude deleted transactions. Normally we
|
|
115
|
+
// handle this manually down below, but now that we are doing a
|
|
116
|
+
// normal query we want to rely on the view. Unfortunately, SQL
|
|
117
|
+
// has already been generated so we can't easily change the view
|
|
118
|
+
// name here; instead, we change it and map it back to the name
|
|
119
|
+
// used elsewhere in the query. Ideally we'd improve this
|
|
120
|
+
if (!withDead) {
|
|
121
|
+
s.from = 'v_transactions_internal_alive v_transactions_internal';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return execQuery(queryState, compilerState, s, params, outputTypes);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let rows;
|
|
128
|
+
let matched = null;
|
|
129
|
+
|
|
130
|
+
if (isHappyPathQuery(queryState)) {
|
|
131
|
+
// This is just an optimization - we can just filter out children
|
|
132
|
+
// directly and only list parents
|
|
133
|
+
const rowSql = `
|
|
134
|
+
SELECT ${sqlPieces.from}.id as group_id
|
|
135
|
+
FROM ${sqlPieces.from}
|
|
136
|
+
${sqlPieces.joins}
|
|
137
|
+
${sqlPieces.where} AND is_child = 0 ${whereDead}
|
|
138
|
+
${sqlPieces.orderBy}
|
|
139
|
+
${sqlPieces.limit != null ? `LIMIT ${sqlPieces.limit}` : ''}
|
|
140
|
+
${sqlPieces.offset != null ? `OFFSET ${sqlPieces.offset}` : ''}
|
|
141
|
+
`;
|
|
142
|
+
rows = await db.all<db.DbViewTransactionInternal>(rowSql, params);
|
|
143
|
+
} else {
|
|
144
|
+
// TODO: phew, what a doozy. write docs why it works this way
|
|
145
|
+
const rowSql = `
|
|
146
|
+
SELECT group_id, matched FROM (
|
|
147
|
+
SELECT
|
|
148
|
+
group_id,
|
|
149
|
+
GROUP_CONCAT(id) as matched
|
|
150
|
+
FROM (
|
|
151
|
+
SELECT ${sqlPieces.from}.id, IFNULL(${sqlPieces.from}.parent_id, ${sqlPieces.from}.id) as group_id
|
|
152
|
+
FROM ${sqlPieces.from}
|
|
153
|
+
LEFT JOIN transactions _t2 ON ${sqlPieces.from}.is_child = 1 AND _t2.id = ${sqlPieces.from}.parent_id
|
|
154
|
+
${sqlPieces.joins}
|
|
155
|
+
${sqlPieces.where} AND ${sqlPieces.from}.tombstone = 0 AND IFNULL(_t2.tombstone, 0) = 0
|
|
156
|
+
)
|
|
157
|
+
GROUP BY group_id
|
|
158
|
+
)
|
|
159
|
+
LEFT JOIN ${sqlPieces.from} ON ${sqlPieces.from}.id = group_id
|
|
160
|
+
${sqlPieces.joins}
|
|
161
|
+
${sqlPieces.orderBy}
|
|
162
|
+
${sqlPieces.limit != null ? `LIMIT ${sqlPieces.limit}` : ''}
|
|
163
|
+
${sqlPieces.offset != null ? `OFFSET ${sqlPieces.offset}` : ''}
|
|
164
|
+
`;
|
|
165
|
+
|
|
166
|
+
rows = await db.all<db.DbViewTransactionInternal>(rowSql, params);
|
|
167
|
+
matched = new Set(
|
|
168
|
+
[].concat.apply(
|
|
169
|
+
[],
|
|
170
|
+
rows.map(row => row.matched.split(',')),
|
|
171
|
+
),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const where = whereIn(
|
|
176
|
+
rows.map(row => row.group_id),
|
|
177
|
+
`IFNULL(${sqlPieces.from}.parent_id, ${sqlPieces.from}.id)`,
|
|
178
|
+
);
|
|
179
|
+
const finalSql = `
|
|
180
|
+
SELECT ${sqlPieces.select}, parent_id AS _parent_id FROM ${sqlPieces.from}
|
|
181
|
+
${sqlPieces.joins}
|
|
182
|
+
WHERE ${where} ${whereDead}
|
|
183
|
+
${sqlPieces.orderBy}
|
|
184
|
+
`;
|
|
185
|
+
|
|
186
|
+
const allRows = await db.all<
|
|
187
|
+
db.DbViewTransactionInternal & {
|
|
188
|
+
_parent_id: db.DbViewTransactionInternal['parent_id'];
|
|
189
|
+
}
|
|
190
|
+
>(finalSql);
|
|
191
|
+
|
|
192
|
+
// Group the parents and children up
|
|
193
|
+
const { parents, children } = allRows.reduce(
|
|
194
|
+
(acc, trans) => {
|
|
195
|
+
const pid = trans._parent_id;
|
|
196
|
+
delete trans._parent_id;
|
|
197
|
+
|
|
198
|
+
if (pid == null) {
|
|
199
|
+
acc.parents.push(trans);
|
|
200
|
+
} else {
|
|
201
|
+
const arr = acc.children.get(pid) || [];
|
|
202
|
+
arr.push(trans);
|
|
203
|
+
acc.children.set(pid, arr);
|
|
204
|
+
}
|
|
205
|
+
return acc;
|
|
206
|
+
},
|
|
207
|
+
{ parents: [], children: new Map() },
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const mapper = trans => {
|
|
211
|
+
Object.keys(trans).forEach(name => {
|
|
212
|
+
trans[name] = convertOutputType(trans[name], outputTypes.get(name));
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (matched && !matched.has(trans.id)) {
|
|
216
|
+
trans._unmatched = true;
|
|
217
|
+
}
|
|
218
|
+
return trans;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return toGroup(parents, children, mapper);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function execTransactionsBasic(
|
|
225
|
+
compilerState: CompilerState,
|
|
226
|
+
queryState: QueryState,
|
|
227
|
+
sqlPieces: SqlPieces,
|
|
228
|
+
params: (string | number)[],
|
|
229
|
+
splitType: SplitsOption,
|
|
230
|
+
outputTypes: OutputTypes,
|
|
231
|
+
) {
|
|
232
|
+
const s = { ...sqlPieces };
|
|
233
|
+
|
|
234
|
+
if (splitType !== 'all') {
|
|
235
|
+
if (splitType === 'none') {
|
|
236
|
+
s.where = `${s.where} AND ${s.from}.parent_id IS NULL`;
|
|
237
|
+
} else {
|
|
238
|
+
s.where = `${s.where} AND ${s.from}.is_parent = 0`;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return execQuery(queryState, compilerState, s, params, outputTypes);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function isValidSplitsOption(splits: string): splits is SplitsOption {
|
|
246
|
+
return ['all', 'inline', 'none', 'grouped'].includes(splits);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Category groups executor
|
|
250
|
+
|
|
251
|
+
type CategoriesOption = 'all' | 'none';
|
|
252
|
+
|
|
253
|
+
async function execCategoryGroups(
|
|
254
|
+
compilerState: CompilerState,
|
|
255
|
+
queryState: QueryState,
|
|
256
|
+
sqlPieces: SqlPieces,
|
|
257
|
+
params: (string | number)[],
|
|
258
|
+
outputTypes: OutputTypes,
|
|
259
|
+
) {
|
|
260
|
+
const tableOptions = queryState.tableOptions || {};
|
|
261
|
+
const categoriesOption = tableOptions.categories
|
|
262
|
+
? (tableOptions.categories as string)
|
|
263
|
+
: 'all';
|
|
264
|
+
if (!isValidCategoriesOption(categoriesOption)) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`Invalid "categories" option for category_groups: "${categoriesOption}"`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (categoriesOption !== 'none') {
|
|
271
|
+
return execCategoryGroupsWithCategories(
|
|
272
|
+
compilerState,
|
|
273
|
+
queryState,
|
|
274
|
+
sqlPieces,
|
|
275
|
+
params,
|
|
276
|
+
categoriesOption,
|
|
277
|
+
outputTypes,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
return execCategoryGroupsBasic(
|
|
281
|
+
compilerState,
|
|
282
|
+
queryState,
|
|
283
|
+
sqlPieces,
|
|
284
|
+
params,
|
|
285
|
+
outputTypes,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function execCategoryGroupsWithCategories(
|
|
290
|
+
compilerState: CompilerState,
|
|
291
|
+
queryState: QueryState,
|
|
292
|
+
sqlPieces: SqlPieces,
|
|
293
|
+
params: (string | number)[],
|
|
294
|
+
categoriesOption: CategoriesOption,
|
|
295
|
+
outputTypes: OutputTypes,
|
|
296
|
+
) {
|
|
297
|
+
const categoryGroups = await execCategoryGroupsBasic(
|
|
298
|
+
compilerState,
|
|
299
|
+
queryState,
|
|
300
|
+
sqlPieces,
|
|
301
|
+
params,
|
|
302
|
+
outputTypes,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
if (categoriesOption === 'none') {
|
|
306
|
+
return categoryGroups;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const { data: categories }: { data: CategoryEntity[] } = await aqlQuery(
|
|
310
|
+
q('categories')
|
|
311
|
+
.filter({
|
|
312
|
+
group: { $oneof: categoryGroups.map(cg => cg.id) },
|
|
313
|
+
})
|
|
314
|
+
.select('*'),
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
return categoryGroups.map(group => {
|
|
318
|
+
const cats = categories.filter(cat => cat.group === group.id);
|
|
319
|
+
return {
|
|
320
|
+
...group,
|
|
321
|
+
categories: cats,
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function execCategoryGroupsBasic(
|
|
327
|
+
compilerState: CompilerState,
|
|
328
|
+
queryState: QueryState,
|
|
329
|
+
sqlPieces: SqlPieces,
|
|
330
|
+
params: (string | number)[],
|
|
331
|
+
outputTypes: OutputTypes,
|
|
332
|
+
) {
|
|
333
|
+
return execQuery(queryState, compilerState, sqlPieces, params, outputTypes);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function isValidCategoriesOption(
|
|
337
|
+
categories: string,
|
|
338
|
+
): categories is CategoriesOption {
|
|
339
|
+
return ['all', 'none'].includes(categories);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export const schemaExecutors: Record<string, AqlQueryExecutor> = {
|
|
343
|
+
transactions: execTransactions,
|
|
344
|
+
category_groups: execCategoryGroups,
|
|
345
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// @ts-strict-ignore
|
|
2
|
+
import * as db from '../../db';
|
|
3
|
+
|
|
4
|
+
// This file doesn't test the schema code directly, it tests that
|
|
5
|
+
// views return data as expected and various constraints on the
|
|
6
|
+
// sqlite3 schema itself.
|
|
7
|
+
|
|
8
|
+
beforeEach(global.emptyDatabase());
|
|
9
|
+
|
|
10
|
+
describe('schema', () => {
|
|
11
|
+
test('never returns transactions without a date', async () => {
|
|
12
|
+
expect(
|
|
13
|
+
(await db.all<db.DbTransaction>('SELECT * FROM transactions')).length,
|
|
14
|
+
).toBe(0);
|
|
15
|
+
expect(
|
|
16
|
+
(await db.all<db.DbViewTransaction>('SELECT * FROM v_transactions'))
|
|
17
|
+
.length,
|
|
18
|
+
).toBe(0);
|
|
19
|
+
db.runQuery('INSERT INTO transactions (acct) VALUES (?)', ['foo']);
|
|
20
|
+
expect(
|
|
21
|
+
(await db.all<db.DbTransaction>('SELECT * FROM transactions')).length,
|
|
22
|
+
).toBe(1);
|
|
23
|
+
expect(
|
|
24
|
+
(await db.all<db.DbViewTransaction>('SELECT * FROM v_transactions'))
|
|
25
|
+
.length,
|
|
26
|
+
).toBe(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('never returns transactions without an account', async () => {
|
|
30
|
+
expect(
|
|
31
|
+
(await db.all<db.DbTransaction>('SELECT * FROM transactions')).length,
|
|
32
|
+
).toBe(0);
|
|
33
|
+
expect(
|
|
34
|
+
(await db.all<db.DbViewTransaction>('SELECT * FROM v_transactions'))
|
|
35
|
+
.length,
|
|
36
|
+
).toBe(0);
|
|
37
|
+
db.runQuery('INSERT INTO transactions (date) VALUES (?)', [20200101]);
|
|
38
|
+
expect(
|
|
39
|
+
(await db.all<db.DbTransaction>('SELECT * FROM transactions')).length,
|
|
40
|
+
).toBe(1);
|
|
41
|
+
expect(
|
|
42
|
+
(await db.all<db.DbViewTransaction>('SELECT * FROM v_transactions'))
|
|
43
|
+
.length,
|
|
44
|
+
).toBe(0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('never returns child transactions without a parent', async () => {
|
|
48
|
+
expect(
|
|
49
|
+
(await db.all<db.DbTransaction>('SELECT * FROM transactions')).length,
|
|
50
|
+
).toBe(0);
|
|
51
|
+
expect(
|
|
52
|
+
(await db.all<db.DbViewTransaction>('SELECT * FROM v_transactions'))
|
|
53
|
+
.length,
|
|
54
|
+
).toBe(0);
|
|
55
|
+
db.runQuery(
|
|
56
|
+
'INSERT INTO transactions (date, acct, isChild) VALUES (?, ?, ?)',
|
|
57
|
+
[20200101, 'foo', 1],
|
|
58
|
+
);
|
|
59
|
+
expect(
|
|
60
|
+
(await db.all<db.DbTransaction>('SELECT * FROM transactions')).length,
|
|
61
|
+
).toBe(1);
|
|
62
|
+
expect(
|
|
63
|
+
(await db.all<db.DbViewTransaction>('SELECT * FROM v_transactions'))
|
|
64
|
+
.length,
|
|
65
|
+
).toBe(0);
|
|
66
|
+
});
|
|
67
|
+
});
|