@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,1222 @@
|
|
|
1
|
+
import { getNormalisedString } from '../../shared/normalisation';
|
|
2
|
+
import type { QueryState } from '../../shared/query';
|
|
3
|
+
|
|
4
|
+
// @ts-strict-ignore
|
|
5
|
+
let _uid = 0;
|
|
6
|
+
function resetUid() {
|
|
7
|
+
_uid = 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function uid(tableName) {
|
|
11
|
+
_uid++;
|
|
12
|
+
return tableName + _uid;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class CompileError extends Error {}
|
|
16
|
+
|
|
17
|
+
function nativeDateToInt(date) {
|
|
18
|
+
const pad = x => (x < 10 ? '0' : '') + x;
|
|
19
|
+
return date.getFullYear() + pad(date.getMonth() + 1) + pad(date.getDate());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function dateToInt(date) {
|
|
23
|
+
return parseInt(date.replace(/-/g, ''));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function addTombstone(schema, tableName, tableId, whereStr) {
|
|
27
|
+
const hasTombstone = schema[tableName].tombstone != null;
|
|
28
|
+
return hasTombstone ? `${whereStr} AND ${tableId}.tombstone = 0` : whereStr;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function popPath(path) {
|
|
32
|
+
const parts = path.split('.');
|
|
33
|
+
return { path: parts.slice(0, -1).join('.'), field: parts[parts.length - 1] };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isKeyword(str) {
|
|
37
|
+
return str === 'group';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function quoteAlias(alias) {
|
|
41
|
+
return alias.indexOf('.') === -1 && !isKeyword(alias) ? alias : `"${alias}"`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function typed(value, type, { literal = false } = {}) {
|
|
45
|
+
return { value, type, literal };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getFieldDescription(schema, tableName, field) {
|
|
49
|
+
if (schema[tableName] == null) {
|
|
50
|
+
throw new CompileError(`Table "${tableName}" does not exist in the schema`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const fieldDesc = schema[tableName][field];
|
|
54
|
+
if (fieldDesc == null) {
|
|
55
|
+
throw new CompileError(
|
|
56
|
+
`Field "${field}" does not exist in table "${tableName}"`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return fieldDesc;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function makePath(state, path) {
|
|
63
|
+
const { schema, paths } = state;
|
|
64
|
+
|
|
65
|
+
const parts = path.split('.');
|
|
66
|
+
if (parts.length < 2) {
|
|
67
|
+
throw new CompileError('Invalid path: ' + path);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const initialTable = parts[0];
|
|
71
|
+
|
|
72
|
+
const tableName = parts.slice(1).reduce((tableName, field) => {
|
|
73
|
+
const table = schema[tableName];
|
|
74
|
+
|
|
75
|
+
if (table == null) {
|
|
76
|
+
throw new CompileError(`Path error: ${tableName} table does not exist`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!table[field] || table[field].ref == null) {
|
|
80
|
+
throw new CompileError(
|
|
81
|
+
`Field not joinable on table ${tableName}: "${field}"`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return table[field].ref;
|
|
86
|
+
}, initialTable);
|
|
87
|
+
|
|
88
|
+
let joinTable;
|
|
89
|
+
const parentParts = parts.slice(0, -1);
|
|
90
|
+
if (parentParts.length === 1) {
|
|
91
|
+
joinTable = parentParts[0];
|
|
92
|
+
} else {
|
|
93
|
+
const parentPath = parentParts.join('.');
|
|
94
|
+
const parentDesc = paths.get(parentPath);
|
|
95
|
+
if (!parentDesc) {
|
|
96
|
+
throw new CompileError('Path does not exist: ' + parentPath);
|
|
97
|
+
}
|
|
98
|
+
joinTable = parentDesc.tableId;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
tableName,
|
|
103
|
+
tableId: uid(tableName),
|
|
104
|
+
joinField: parts[parts.length - 1],
|
|
105
|
+
joinTable,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function resolvePath(state, path) {
|
|
110
|
+
let paths = path.split('.');
|
|
111
|
+
|
|
112
|
+
paths = paths.reduce(
|
|
113
|
+
(acc, name) => {
|
|
114
|
+
const fullName = acc.context + '.' + name;
|
|
115
|
+
return {
|
|
116
|
+
context: fullName,
|
|
117
|
+
path: [...acc.path, fullName],
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
{ context: state.implicitTableName, path: [] },
|
|
121
|
+
).path;
|
|
122
|
+
|
|
123
|
+
paths.forEach(path => {
|
|
124
|
+
if (!state.paths.get(path)) {
|
|
125
|
+
state.paths.set(path, makePath(state, path));
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const pathInfo = state.paths.get(paths[paths.length - 1]);
|
|
130
|
+
return pathInfo;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function transformField(state, name) {
|
|
134
|
+
if (typeof name !== 'string') {
|
|
135
|
+
throw new CompileError('Invalid field name, must be a string');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const { path, field: originalField } = popPath(name);
|
|
139
|
+
|
|
140
|
+
let field = originalField;
|
|
141
|
+
let pathInfo;
|
|
142
|
+
if (path === '') {
|
|
143
|
+
pathInfo = {
|
|
144
|
+
tableName: state.implicitTableName,
|
|
145
|
+
tableId: state.implicitTableId,
|
|
146
|
+
};
|
|
147
|
+
} else {
|
|
148
|
+
pathInfo = resolvePath(state, path);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const fieldDesc = getFieldDescription(
|
|
152
|
+
state.schema,
|
|
153
|
+
pathInfo.tableName,
|
|
154
|
+
field,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// If this is a field that references an item in another table, that
|
|
158
|
+
// item could have been deleted. If that's the case, we want to
|
|
159
|
+
// return `null` instead of an id pointing to a deleted item. This
|
|
160
|
+
// converts an id reference into a path that pulls the id through a
|
|
161
|
+
// table join which will filter out dead items, resulting in a
|
|
162
|
+
// `null` id if the item is deleted
|
|
163
|
+
if (
|
|
164
|
+
state.validateRefs &&
|
|
165
|
+
fieldDesc.ref &&
|
|
166
|
+
fieldDesc.type === 'id' &&
|
|
167
|
+
field !== 'id'
|
|
168
|
+
) {
|
|
169
|
+
const refPath = state.implicitTableName + '.' + name;
|
|
170
|
+
let refPathInfo = state.paths.get(refPath);
|
|
171
|
+
|
|
172
|
+
if (!refPathInfo) {
|
|
173
|
+
refPathInfo = makePath(state, refPath);
|
|
174
|
+
state.paths.set(refPath, refPathInfo);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
field = 'id';
|
|
178
|
+
pathInfo = refPathInfo;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const fieldStr = pathInfo.tableId + '.' + field;
|
|
182
|
+
return typed(fieldStr, fieldDesc.type);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function parseDate(str) {
|
|
186
|
+
const m = str.match(/^(\d{4}-\d{2}-\d{2})$/);
|
|
187
|
+
if (m) {
|
|
188
|
+
return typed(dateToInt(m[1]), 'date', { literal: true });
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function parseMonth(str) {
|
|
194
|
+
const m = str.match(/^(\d{4}-\d{2})$/);
|
|
195
|
+
if (m) {
|
|
196
|
+
return typed(dateToInt(m[1]), 'date', { literal: true });
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function parseYear(str) {
|
|
202
|
+
const m = str.match(/^(\d{4})$/);
|
|
203
|
+
if (m) {
|
|
204
|
+
return typed(dateToInt(m[1]), 'date', { literal: true });
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function badDateFormat(str, type) {
|
|
210
|
+
throw new CompileError(`Bad ${type} format: ${str}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function inferParam(param, type) {
|
|
214
|
+
const existingType = param.paramType;
|
|
215
|
+
if (existingType) {
|
|
216
|
+
const casts = {
|
|
217
|
+
date: ['string'],
|
|
218
|
+
'date-month': ['date'],
|
|
219
|
+
'date-year': ['date', 'date-month'],
|
|
220
|
+
id: ['string'],
|
|
221
|
+
float: ['integer'],
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if (
|
|
225
|
+
existingType !== type &&
|
|
226
|
+
(!casts[type] || !casts[type].includes(existingType))
|
|
227
|
+
) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`Parameter "${param.paramName}" can't convert to ${type} (already inferred as ${existingType})`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
param.paramType = type;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function castInput(state, expr, type) {
|
|
238
|
+
if (expr.type === type) {
|
|
239
|
+
return expr;
|
|
240
|
+
} else if (expr.type === 'param') {
|
|
241
|
+
inferParam(expr, type);
|
|
242
|
+
return typed(expr.value, type);
|
|
243
|
+
} else if (expr.type === 'null') {
|
|
244
|
+
if (!expr.literal) {
|
|
245
|
+
throw new CompileError("A non-literal null doesn't make sense");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (type === 'boolean') {
|
|
249
|
+
return typed(0, 'boolean', { literal: true });
|
|
250
|
+
}
|
|
251
|
+
return expr;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// These are all things that can be safely casted automatically
|
|
255
|
+
if (type === 'date') {
|
|
256
|
+
if (expr.type === 'string') {
|
|
257
|
+
if (expr.literal) {
|
|
258
|
+
return parseDate(expr.value) || badDateFormat(expr.value, 'date');
|
|
259
|
+
} else {
|
|
260
|
+
throw new CompileError(
|
|
261
|
+
'Casting string fields to dates is not supported',
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
throw new CompileError(`Can't cast ${expr.type} to date`);
|
|
267
|
+
} else if (type === 'date-month') {
|
|
268
|
+
let expr2;
|
|
269
|
+
if (expr.type === 'date') {
|
|
270
|
+
expr2 = expr;
|
|
271
|
+
} else if (expr.type === 'string' || expr.type === 'any') {
|
|
272
|
+
expr2 =
|
|
273
|
+
parseMonth(expr.value) ||
|
|
274
|
+
parseDate(expr.value) ||
|
|
275
|
+
badDateFormat(expr.value, 'date-month');
|
|
276
|
+
} else {
|
|
277
|
+
throw new CompileError(`Can't cast ${expr.type} to date-month`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (expr2.literal) {
|
|
281
|
+
return typed(
|
|
282
|
+
dateToInt(expr2.value.toString().slice(0, 6)),
|
|
283
|
+
'date-month',
|
|
284
|
+
{ literal: true },
|
|
285
|
+
);
|
|
286
|
+
} else {
|
|
287
|
+
return typed(
|
|
288
|
+
`CAST(SUBSTR(${expr2.value}, 1, 6) AS integer)`,
|
|
289
|
+
'date-month',
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
} else if (type === 'date-year') {
|
|
293
|
+
let expr2;
|
|
294
|
+
if (expr.type === 'date' || expr.type === 'date-month') {
|
|
295
|
+
expr2 = expr;
|
|
296
|
+
} else if (expr.type === 'string') {
|
|
297
|
+
expr2 =
|
|
298
|
+
parseYear(expr.value) ||
|
|
299
|
+
parseMonth(expr.value) ||
|
|
300
|
+
parseDate(expr.value) ||
|
|
301
|
+
badDateFormat(expr.value, 'date-year');
|
|
302
|
+
} else {
|
|
303
|
+
throw new CompileError(`Can't cast ${expr.type} to date-year`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (expr2.literal) {
|
|
307
|
+
return typed(dateToInt(expr2.value.toString().slice(0, 4)), 'date-year', {
|
|
308
|
+
literal: true,
|
|
309
|
+
});
|
|
310
|
+
} else {
|
|
311
|
+
return typed(
|
|
312
|
+
`CAST(SUBSTR(${expr2.value}, 1, 4) AS integer)`,
|
|
313
|
+
'date-year',
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
} else if (type === 'id') {
|
|
317
|
+
if (expr.type === 'string') {
|
|
318
|
+
return typed(expr.value, 'id', { literal: expr.literal });
|
|
319
|
+
}
|
|
320
|
+
} else if (type === 'float') {
|
|
321
|
+
if (expr.type === 'integer') {
|
|
322
|
+
return typed(expr.value, 'float', { literal: expr.literal });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (expr.type === 'any') {
|
|
327
|
+
return typed(expr.value, type, { literal: expr.literal });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
throw new CompileError(`Can't convert ${expr.type} to ${type}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// TODO: remove state from these functions
|
|
334
|
+
function val(state, expr, type?: string) {
|
|
335
|
+
let castedExpr = expr;
|
|
336
|
+
|
|
337
|
+
// Cast the type if necessary
|
|
338
|
+
if (type) {
|
|
339
|
+
castedExpr = castInput(state, expr, type);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (castedExpr.literal) {
|
|
343
|
+
if (castedExpr.type === 'id') {
|
|
344
|
+
return `'${castedExpr.value}'`;
|
|
345
|
+
} else if (castedExpr.type === 'string') {
|
|
346
|
+
// Escape quotes
|
|
347
|
+
const value = castedExpr.value.replace(/'/g, "''");
|
|
348
|
+
return `'${value}'`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return castedExpr.value;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function valArray(state, arr: unknown[], types?: string[]) {
|
|
356
|
+
return arr.map((value, idx) => val(state, value, types ? types[idx] : null));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function validateArgLength(arr: unknown[], min: number, max?: number) {
|
|
360
|
+
if (max == null) {
|
|
361
|
+
max = min;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (min != null && arr.length < min) {
|
|
365
|
+
throw new CompileError('Too few arguments');
|
|
366
|
+
}
|
|
367
|
+
if (max != null && arr.length > max) {
|
|
368
|
+
throw new CompileError('Too many arguments');
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
//// Nice errors
|
|
373
|
+
|
|
374
|
+
function saveStack(type, func) {
|
|
375
|
+
return (state, ...args) => {
|
|
376
|
+
if (state == null || state.compileStack == null) {
|
|
377
|
+
throw new CompileError(
|
|
378
|
+
'This function cannot track error data. ' +
|
|
379
|
+
'It needs to accept the compiler state as the first argument.',
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
state.compileStack.push({ type, args });
|
|
384
|
+
const ret = func(state, ...args);
|
|
385
|
+
state.compileStack.pop();
|
|
386
|
+
return ret;
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function prettyValue(value) {
|
|
391
|
+
if (typeof value === 'string') {
|
|
392
|
+
return value;
|
|
393
|
+
} else if (value === undefined) {
|
|
394
|
+
return 'undefined';
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const str = JSON.stringify(value);
|
|
398
|
+
if (str.length > 70) {
|
|
399
|
+
const expanded = JSON.stringify(value, null, 2);
|
|
400
|
+
return expanded.split('\n').join('\n ');
|
|
401
|
+
}
|
|
402
|
+
return str;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function getCompileError(error, stack) {
|
|
406
|
+
if (stack.length === 0) {
|
|
407
|
+
return error;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
let stackStr = stack
|
|
411
|
+
.slice(1)
|
|
412
|
+
.reverse()
|
|
413
|
+
.map(entry => {
|
|
414
|
+
switch (entry.type) {
|
|
415
|
+
case 'expr':
|
|
416
|
+
case 'function':
|
|
417
|
+
return prettyValue(entry.args[0]);
|
|
418
|
+
case 'op': {
|
|
419
|
+
const [fieldRef, opData] = entry.args;
|
|
420
|
+
return prettyValue({ [fieldRef]: opData });
|
|
421
|
+
}
|
|
422
|
+
case 'value':
|
|
423
|
+
return prettyValue(entry.value);
|
|
424
|
+
default:
|
|
425
|
+
return '';
|
|
426
|
+
}
|
|
427
|
+
})
|
|
428
|
+
.map(str => '\n ' + str)
|
|
429
|
+
.join('');
|
|
430
|
+
|
|
431
|
+
const rootMethod = stack[0].type;
|
|
432
|
+
const methodArgs = stack[0].args[0];
|
|
433
|
+
stackStr += `\n ${rootMethod}(${prettyValue(
|
|
434
|
+
methodArgs.length === 1 ? methodArgs[0] : methodArgs,
|
|
435
|
+
)})`;
|
|
436
|
+
|
|
437
|
+
// In production, hide internal stack traces
|
|
438
|
+
if (process.env.NODE_ENV === 'production') {
|
|
439
|
+
const err = new CompileError();
|
|
440
|
+
err.message = `${error.message}\n\nExpression stack:` + stackStr;
|
|
441
|
+
err.stack = null;
|
|
442
|
+
return err;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
error.message = `${error.message}\n\nExpression stack:` + stackStr;
|
|
446
|
+
return error;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
//// Compiler
|
|
450
|
+
|
|
451
|
+
function compileLiteral(value) {
|
|
452
|
+
if (value === undefined) {
|
|
453
|
+
throw new CompileError('`undefined` is not a valid query value');
|
|
454
|
+
} else if (value === null) {
|
|
455
|
+
return typed('NULL', 'null', { literal: true });
|
|
456
|
+
} else if (value instanceof Date) {
|
|
457
|
+
return typed(nativeDateToInt(value), 'date', { literal: true });
|
|
458
|
+
} else if (typeof value === 'string') {
|
|
459
|
+
// Allow user to escape $, and quote the string to make it a
|
|
460
|
+
// string literal in the output
|
|
461
|
+
value = value.replace(/\\\$/g, '$');
|
|
462
|
+
return typed(value, 'string', { literal: true });
|
|
463
|
+
} else if (typeof value === 'boolean') {
|
|
464
|
+
return typed(value ? 1 : 0, 'boolean', { literal: true });
|
|
465
|
+
} else if (typeof value === 'number') {
|
|
466
|
+
return typed(value, Number.isInteger(value) ? 'integer' : 'float', {
|
|
467
|
+
literal: true,
|
|
468
|
+
});
|
|
469
|
+
} else if (Array.isArray(value)) {
|
|
470
|
+
return typed(value, 'array', { literal: true });
|
|
471
|
+
} else {
|
|
472
|
+
throw new CompileError(
|
|
473
|
+
'Unsupported type of expression: ' + JSON.stringify(value),
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const compileExpr = saveStack('expr', (state, expr) => {
|
|
479
|
+
if (typeof expr === 'string') {
|
|
480
|
+
// Field reference
|
|
481
|
+
if (expr[0] === '$') {
|
|
482
|
+
const fieldRef = expr === '$' ? state.implicitField : expr.slice(1);
|
|
483
|
+
|
|
484
|
+
if (fieldRef == null || fieldRef === '') {
|
|
485
|
+
throw new CompileError('Invalid field reference: ' + expr);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return transformField(state, fieldRef);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Named parameter
|
|
492
|
+
if (expr[0] === ':') {
|
|
493
|
+
const param = { value: '?', type: 'param', paramName: expr.slice(1) };
|
|
494
|
+
state.namedParameters.push(param);
|
|
495
|
+
return param;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (expr !== null) {
|
|
500
|
+
if (Array.isArray(expr)) {
|
|
501
|
+
return compileLiteral(expr);
|
|
502
|
+
} else if (
|
|
503
|
+
typeof expr === 'object' &&
|
|
504
|
+
Object.keys(expr).find(k => k[0] === '$')
|
|
505
|
+
) {
|
|
506
|
+
// It's a function call
|
|
507
|
+
return compileFunction(state, expr);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return compileLiteral(expr);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const compileFunction = saveStack('function', (state, func) => {
|
|
515
|
+
const [name] = Object.keys(func);
|
|
516
|
+
let argExprs = func[name];
|
|
517
|
+
if (!Array.isArray(argExprs)) {
|
|
518
|
+
argExprs = [argExprs];
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (name[0] !== '$') {
|
|
522
|
+
throw new CompileError(
|
|
523
|
+
`Unknown property "${name}." Did you mean to call a function? Try prefixing it with $`,
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
let args = argExprs;
|
|
528
|
+
// `$condition` is a special-case where it will be evaluated later
|
|
529
|
+
if (name !== '$condition') {
|
|
530
|
+
args = argExprs.map(arg => compileExpr(state, arg));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
switch (name) {
|
|
534
|
+
// aggregate functions
|
|
535
|
+
case '$sum': {
|
|
536
|
+
validateArgLength(args, 1);
|
|
537
|
+
const [arg1] = valArray(state, args, ['float']);
|
|
538
|
+
return typed(`SUM(${arg1})`, args[0].type);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
case '$sumOver': {
|
|
542
|
+
const [arg1] = valArray(state, args, ['float']);
|
|
543
|
+
const order = state.orders
|
|
544
|
+
? 'ORDER BY ' + compileOrderBy(state, state.orders)
|
|
545
|
+
: '';
|
|
546
|
+
|
|
547
|
+
return typed(
|
|
548
|
+
`(SUM(${arg1}) OVER (${order} ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING))`,
|
|
549
|
+
args[0].type,
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
case '$count': {
|
|
554
|
+
validateArgLength(args, 1);
|
|
555
|
+
const [arg1] = valArray(state, args);
|
|
556
|
+
return typed(`COUNT(${arg1})`, 'integer');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// string functions
|
|
560
|
+
case '$substr': {
|
|
561
|
+
validateArgLength(args, 2, 3);
|
|
562
|
+
const [arg1, arg2, arg3] = valArray(state, args, [
|
|
563
|
+
'string',
|
|
564
|
+
'integer',
|
|
565
|
+
'integer',
|
|
566
|
+
]);
|
|
567
|
+
return typed(`SUBSTR(${arg1}, ${arg2}, ${arg3})`, 'string');
|
|
568
|
+
}
|
|
569
|
+
case '$lower': {
|
|
570
|
+
validateArgLength(args, 1);
|
|
571
|
+
const [arg1] = valArray(state, args, ['string']);
|
|
572
|
+
return typed(`UNICODE_LOWER(${arg1})`, 'string');
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// integer/float functions
|
|
576
|
+
case '$neg': {
|
|
577
|
+
validateArgLength(args, 1);
|
|
578
|
+
valArray(state, args, ['float']);
|
|
579
|
+
return typed(`(-${val(state, args[0])})`, args[0].type);
|
|
580
|
+
}
|
|
581
|
+
case '$abs': {
|
|
582
|
+
validateArgLength(args, 1);
|
|
583
|
+
valArray(state, args, ['float']);
|
|
584
|
+
return typed(`ABS(${val(state, args[0])})`, args[0].type);
|
|
585
|
+
}
|
|
586
|
+
case '$idiv': {
|
|
587
|
+
validateArgLength(args, 2);
|
|
588
|
+
valArray(state, args, ['integer', 'integer']);
|
|
589
|
+
return typed(
|
|
590
|
+
`(${val(state, args[0])} / ${val(state, args[1])})`,
|
|
591
|
+
args[0].type,
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// id functions
|
|
596
|
+
case '$id': {
|
|
597
|
+
validateArgLength(args, 1);
|
|
598
|
+
return typed(val(state, args[0]), args[0].type);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// date functions
|
|
602
|
+
case '$day': {
|
|
603
|
+
validateArgLength(args, 1);
|
|
604
|
+
return castInput(state, args[0], 'date');
|
|
605
|
+
}
|
|
606
|
+
case '$month': {
|
|
607
|
+
validateArgLength(args, 1);
|
|
608
|
+
return castInput(state, args[0], 'date-month');
|
|
609
|
+
}
|
|
610
|
+
case '$year': {
|
|
611
|
+
validateArgLength(args, 1);
|
|
612
|
+
return castInput(state, args[0], 'date-year');
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// various functions
|
|
616
|
+
case '$condition':
|
|
617
|
+
validateArgLength(args, 1);
|
|
618
|
+
const conds = compileConditions(state, args[0]);
|
|
619
|
+
return typed(conds.join(' AND '), 'boolean');
|
|
620
|
+
|
|
621
|
+
case '$nocase':
|
|
622
|
+
validateArgLength(args, 1);
|
|
623
|
+
const [arg1] = valArray(state, args, ['string']);
|
|
624
|
+
return typed(`${arg1} COLLATE NOCASE`, args[0].type);
|
|
625
|
+
|
|
626
|
+
case '$literal': {
|
|
627
|
+
validateArgLength(args, 1);
|
|
628
|
+
if (!args[0].literal) {
|
|
629
|
+
throw new CompileError('Literal not passed to $literal');
|
|
630
|
+
}
|
|
631
|
+
return args[0];
|
|
632
|
+
}
|
|
633
|
+
default:
|
|
634
|
+
throw new CompileError(`Unknown function: ${name}`);
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
const compileOp = saveStack('op', (state, fieldRef, opData) => {
|
|
639
|
+
const { $transform, ...opExpr } = opData;
|
|
640
|
+
const [op] = Object.keys(opExpr);
|
|
641
|
+
|
|
642
|
+
const rhs = compileExpr(state, opData[op]);
|
|
643
|
+
|
|
644
|
+
let lhs;
|
|
645
|
+
if ($transform) {
|
|
646
|
+
lhs = compileFunction(
|
|
647
|
+
{ ...state, implicitField: fieldRef },
|
|
648
|
+
typeof $transform === 'string' ? { [$transform]: '$' } : $transform,
|
|
649
|
+
);
|
|
650
|
+
} else {
|
|
651
|
+
lhs = compileExpr(state, '$' + fieldRef);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
switch (op) {
|
|
655
|
+
case '$gte': {
|
|
656
|
+
const [left, right] = valArray(state, [lhs, rhs], [null, lhs.type]);
|
|
657
|
+
return `${left} >= ${right}`;
|
|
658
|
+
}
|
|
659
|
+
case '$lte': {
|
|
660
|
+
const [left, right] = valArray(state, [lhs, rhs], [null, lhs.type]);
|
|
661
|
+
return `${left} <= ${right}`;
|
|
662
|
+
}
|
|
663
|
+
case '$gt': {
|
|
664
|
+
const [left, right] = valArray(state, [lhs, rhs], [null, lhs.type]);
|
|
665
|
+
return `${left} > ${right}`;
|
|
666
|
+
}
|
|
667
|
+
case '$lt': {
|
|
668
|
+
const [left, right] = valArray(state, [lhs, rhs], [null, lhs.type]);
|
|
669
|
+
return `${left} < ${right}`;
|
|
670
|
+
}
|
|
671
|
+
case '$eq': {
|
|
672
|
+
if (castInput(state, rhs, lhs.type).type === 'null') {
|
|
673
|
+
return `${val(state, lhs)} IS NULL`;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const [left, right] = valArray(state, [lhs, rhs], [null, lhs.type]);
|
|
677
|
+
|
|
678
|
+
if (rhs.type === 'param') {
|
|
679
|
+
const orders = state.namedParameters.map(param => {
|
|
680
|
+
return param === rhs || param === lhs ? [param, { ...param }] : param;
|
|
681
|
+
});
|
|
682
|
+
state.namedParameters = [].concat.apply([], orders);
|
|
683
|
+
|
|
684
|
+
return `CASE
|
|
685
|
+
WHEN ${left} IS NULL THEN ${right} IS NULL
|
|
686
|
+
ELSE ${left} = ${right}
|
|
687
|
+
END`;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return `${left} = ${right}`;
|
|
691
|
+
}
|
|
692
|
+
case '$ne': {
|
|
693
|
+
if (castInput(state, rhs, lhs.type).type === 'null') {
|
|
694
|
+
return `${val(state, lhs)} IS NOT NULL`;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const [left, right] = valArray(state, [lhs, rhs], [null, lhs.type]);
|
|
698
|
+
|
|
699
|
+
if (rhs.type === 'param') {
|
|
700
|
+
const orders = state.namedParameters.map(param => {
|
|
701
|
+
return param === rhs || param === lhs ? [param, { ...param }] : param;
|
|
702
|
+
});
|
|
703
|
+
state.namedParameters = [].concat.apply([], orders);
|
|
704
|
+
|
|
705
|
+
return `CASE
|
|
706
|
+
WHEN ${left} IS NULL THEN ${right} IS NOT NULL
|
|
707
|
+
ELSE ${left} IS NOT ${right}
|
|
708
|
+
END`;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return `(${left} != ${right} OR ${left} IS NULL)`;
|
|
712
|
+
}
|
|
713
|
+
case '$oneof': {
|
|
714
|
+
const [left, right] = valArray(state, [lhs, rhs], [null, 'array']);
|
|
715
|
+
// Dedupe the ids
|
|
716
|
+
const ids = [...new Set(right)];
|
|
717
|
+
|
|
718
|
+
return (
|
|
719
|
+
`${String(left)} IN (` +
|
|
720
|
+
ids.map(id => `'${String(id)}'`).join(',') +
|
|
721
|
+
')'
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
case '$like': {
|
|
725
|
+
const [left, right] = valArray(state, [lhs, rhs], ['string', 'string']);
|
|
726
|
+
return `UNICODE_LIKE(${getNormalisedString(right)}, NORMALISE(${left}))`;
|
|
727
|
+
}
|
|
728
|
+
case '$regexp': {
|
|
729
|
+
const [left, right] = valArray(state, [lhs, rhs], ['string', 'string']);
|
|
730
|
+
return `REGEXP(${right}, ${left})`;
|
|
731
|
+
}
|
|
732
|
+
case '$notlike': {
|
|
733
|
+
const [left, right] = valArray(state, [lhs, rhs], ['string', 'string']);
|
|
734
|
+
return `(NOT UNICODE_LIKE(${getNormalisedString(right)}, NORMALISE(${left}))\n OR ${left} IS NULL)`;
|
|
735
|
+
}
|
|
736
|
+
default:
|
|
737
|
+
throw new CompileError(`Unknown operator: ${op}`);
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
function compileConditions(state, conds) {
|
|
742
|
+
if (!Array.isArray(conds)) {
|
|
743
|
+
// Convert the object form `{foo: 1, bar:2}` into the array form
|
|
744
|
+
// `[{foo: 1}, {bar:2}]`
|
|
745
|
+
conds = Object.entries(conds).map(cond => {
|
|
746
|
+
return { [cond[0]]: cond[1] };
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return conds.filter(Boolean).reduce((res, condsObj) => {
|
|
751
|
+
const compiled = Object.entries(condsObj)
|
|
752
|
+
.map(([field, cond]) => {
|
|
753
|
+
// Allow a falsy value in the lhs of $and and $or to allow for
|
|
754
|
+
// quick forms like `$or: amount != 0 && ...`
|
|
755
|
+
if (field === '$and') {
|
|
756
|
+
if (!cond) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
return compileAnd(state, cond);
|
|
760
|
+
} else if (field === '$or') {
|
|
761
|
+
if (!cond || (Array.isArray(cond) && cond.length === 0)) {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
return compileOr(state, cond);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (
|
|
768
|
+
typeof cond === 'string' ||
|
|
769
|
+
typeof cond === 'number' ||
|
|
770
|
+
typeof cond === 'boolean' ||
|
|
771
|
+
cond instanceof Date ||
|
|
772
|
+
cond == null
|
|
773
|
+
) {
|
|
774
|
+
return compileOp(state, field, { $eq: cond });
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (Array.isArray(cond)) {
|
|
778
|
+
// An array of conditions for a field is implicitly an `and`
|
|
779
|
+
return cond.map(c => compileOp(state, field, c)).join(' AND ');
|
|
780
|
+
}
|
|
781
|
+
return compileOp(state, field, cond);
|
|
782
|
+
})
|
|
783
|
+
.filter(Boolean);
|
|
784
|
+
|
|
785
|
+
return [...res, ...compiled];
|
|
786
|
+
}, []);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function compileOr(state, conds) {
|
|
790
|
+
// Same as above
|
|
791
|
+
if (!conds) {
|
|
792
|
+
return '0';
|
|
793
|
+
}
|
|
794
|
+
const res = compileConditions(state, conds);
|
|
795
|
+
if (res.length === 0) {
|
|
796
|
+
return '0';
|
|
797
|
+
}
|
|
798
|
+
return '(' + res.join('\n OR ') + ')';
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function compileAnd(state, conds) {
|
|
802
|
+
// Same as above
|
|
803
|
+
if (!conds) {
|
|
804
|
+
return '1';
|
|
805
|
+
}
|
|
806
|
+
const res = compileConditions(state, conds);
|
|
807
|
+
if (res.length === 0) {
|
|
808
|
+
return '1';
|
|
809
|
+
}
|
|
810
|
+
return '(' + res.join('\n AND ') + ')';
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const compileWhere = saveStack('filter', (state, conds) => {
|
|
814
|
+
return compileAnd(state, conds);
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
function compileJoins(state, tableRef, internalTableFilters) {
|
|
818
|
+
const joins = [];
|
|
819
|
+
state.paths.forEach((desc, path) => {
|
|
820
|
+
const { tableName, tableId, joinField, joinTable } = state.paths.get(path);
|
|
821
|
+
|
|
822
|
+
let on = `${tableId}.id = ${tableRef(joinTable)}.${quoteAlias(joinField)}`;
|
|
823
|
+
|
|
824
|
+
const filters = internalTableFilters(tableName);
|
|
825
|
+
if (filters.length > 0) {
|
|
826
|
+
on +=
|
|
827
|
+
' AND ' +
|
|
828
|
+
compileAnd(
|
|
829
|
+
{ ...state, implicitTableName: tableName, implicitTableId: tableId },
|
|
830
|
+
filters,
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
joins.push(
|
|
835
|
+
`LEFT JOIN ${tableRef(
|
|
836
|
+
tableName,
|
|
837
|
+
true,
|
|
838
|
+
)} ${tableId} ON ${addTombstone(state.schema, tableName, tableId, on)}`,
|
|
839
|
+
);
|
|
840
|
+
|
|
841
|
+
if (state.dependencies.indexOf(tableName) === -1) {
|
|
842
|
+
state.dependencies.push(tableName);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
return joins.join('\n');
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function expandStar(state, expr) {
|
|
849
|
+
let path;
|
|
850
|
+
let pathInfo;
|
|
851
|
+
if (expr === '*') {
|
|
852
|
+
pathInfo = {
|
|
853
|
+
tableName: state.implicitTableName,
|
|
854
|
+
tableId: state.implicitTableId,
|
|
855
|
+
};
|
|
856
|
+
} else if (expr.match(/\.\*$/)) {
|
|
857
|
+
const result = popPath(expr);
|
|
858
|
+
path = result.path;
|
|
859
|
+
pathInfo = resolvePath(state, result.path);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const table = state.schema[pathInfo.tableName];
|
|
863
|
+
if (table == null) {
|
|
864
|
+
throw new Error(`Table "${pathInfo.tableName}" does not exist`);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return Object.keys(table).map(field => (path ? `${path}.${field}` : field));
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const compileSelect = saveStack(
|
|
871
|
+
'select',
|
|
872
|
+
(state, exprs, isAggregate, orders) => {
|
|
873
|
+
// Always include the id if it's not an aggregate
|
|
874
|
+
if (!isAggregate && !exprs.includes('id') && !exprs.includes('*')) {
|
|
875
|
+
exprs = exprs.concat(['id']);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const select = exprs.map(expr => {
|
|
879
|
+
if (typeof expr === 'string') {
|
|
880
|
+
if (expr.indexOf('*') !== -1) {
|
|
881
|
+
const fields = expandStar(state, expr);
|
|
882
|
+
|
|
883
|
+
return fields
|
|
884
|
+
.map(field => {
|
|
885
|
+
const compiled = compileExpr(state, '$' + field);
|
|
886
|
+
state.outputTypes.set(field, compiled.type);
|
|
887
|
+
return compiled.value + ' AS ' + quoteAlias(field);
|
|
888
|
+
})
|
|
889
|
+
.join(', ');
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const compiled = compileExpr(state, '$' + expr);
|
|
893
|
+
state.outputTypes.set(expr, compiled.type);
|
|
894
|
+
return compiled.value + ' AS ' + quoteAlias(expr);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const [name, value] = Object.entries(expr)[0];
|
|
898
|
+
if (name[0] === '$') {
|
|
899
|
+
state.compileStack.push({ type: 'value', value: expr });
|
|
900
|
+
throw new CompileError(
|
|
901
|
+
`Invalid field "${name}", are you trying to select a function? You need to name the expression`,
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
if (typeof value === 'string') {
|
|
906
|
+
const compiled = compileExpr(state, '$' + value);
|
|
907
|
+
state.outputTypes.set(name, compiled.type);
|
|
908
|
+
return `${compiled.value} AS ${quoteAlias(name)}`;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const compiled = compileFunction({ ...state, orders }, value);
|
|
912
|
+
state.outputTypes.set(name, compiled.type);
|
|
913
|
+
return compiled.value + ` AS ${quoteAlias(name)}`;
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
return select.join(', ');
|
|
917
|
+
},
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
const compileGroupBy = saveStack('groupBy', (state, exprs) => {
|
|
921
|
+
const groupBy = exprs.map(expr => {
|
|
922
|
+
if (typeof expr === 'string') {
|
|
923
|
+
return compileExpr(state, '$' + expr).value;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
return compileFunction(state, expr).value;
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
return groupBy.join(', ');
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
const compileOrderBy = saveStack('orderBy', (state, exprs) => {
|
|
933
|
+
const orderBy = exprs.map(expr => {
|
|
934
|
+
let compiled;
|
|
935
|
+
let dir = null;
|
|
936
|
+
|
|
937
|
+
if (typeof expr === 'string') {
|
|
938
|
+
compiled = compileExpr(state, '$' + expr).value;
|
|
939
|
+
} else {
|
|
940
|
+
const entries = Object.entries(expr);
|
|
941
|
+
const entry = entries[0];
|
|
942
|
+
|
|
943
|
+
// Check if this is a field reference
|
|
944
|
+
if (entries.length === 1 && entry[0][0] !== '$') {
|
|
945
|
+
dir = entry[1];
|
|
946
|
+
compiled = compileExpr(state, '$' + entry[0]).value;
|
|
947
|
+
} else {
|
|
948
|
+
// Otherwise it's a function
|
|
949
|
+
const { $dir, ...func } = expr;
|
|
950
|
+
dir = $dir;
|
|
951
|
+
compiled = compileFunction(state, func).value;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
if (dir != null) {
|
|
956
|
+
if (dir !== 'desc' && dir !== 'asc') {
|
|
957
|
+
throw new CompileError('Invalid order direction: ' + dir);
|
|
958
|
+
}
|
|
959
|
+
return `${compiled} ${dir}`;
|
|
960
|
+
}
|
|
961
|
+
return compiled;
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
return orderBy.join(', ');
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
const AGGREGATE_FUNCTIONS = ['$sum', '$count'];
|
|
968
|
+
function isAggregateFunction(expr) {
|
|
969
|
+
if (typeof expr !== 'object' || Array.isArray(expr)) {
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const [name, originalArgExprs] = Object.entries(expr)[0];
|
|
974
|
+
let argExprs = originalArgExprs;
|
|
975
|
+
if (!Array.isArray(argExprs)) {
|
|
976
|
+
argExprs = [argExprs];
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
if (AGGREGATE_FUNCTIONS.indexOf(name) !== -1) {
|
|
980
|
+
return true;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
return !!(argExprs as unknown[]).find(ex => isAggregateFunction(ex));
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
export function isAggregateQuery(queryState) {
|
|
987
|
+
// it's aggregate if:
|
|
988
|
+
// either an aggregate function is used in `select`
|
|
989
|
+
// or a `groupBy` exists
|
|
990
|
+
|
|
991
|
+
if (queryState.groupExpressions.length > 0) {
|
|
992
|
+
return true;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
return !!queryState.selectExpressions.find(expr => {
|
|
996
|
+
if (typeof expr !== 'string') {
|
|
997
|
+
const [_, value] = Object.entries(expr)[0];
|
|
998
|
+
return isAggregateFunction(value);
|
|
999
|
+
}
|
|
1000
|
+
return false;
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// TODO: Type this based on schema/index
|
|
1005
|
+
type Schema = unknown;
|
|
1006
|
+
|
|
1007
|
+
export type SchemaConfig = {
|
|
1008
|
+
tableViews?:
|
|
1009
|
+
| Record<string, string>
|
|
1010
|
+
| ((name: string, config: { withDead; isJoin; tableOptions }) => string);
|
|
1011
|
+
tableFilters?: (name: string) => unknown[];
|
|
1012
|
+
customizeQuery?: (queryState: QueryState) => QueryState;
|
|
1013
|
+
views?: Record<
|
|
1014
|
+
string,
|
|
1015
|
+
{
|
|
1016
|
+
fields?: Record<string, string>;
|
|
1017
|
+
[key: `v_${string}`]: string | ((internalFields, publicFields) => string);
|
|
1018
|
+
}
|
|
1019
|
+
>;
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
// Types per field. Should be based on the schema.
|
|
1023
|
+
export type OutputTypes = Map<string, string | number | null>;
|
|
1024
|
+
|
|
1025
|
+
type NamedParameter = {
|
|
1026
|
+
type: string;
|
|
1027
|
+
paramName: string;
|
|
1028
|
+
paramType?: string;
|
|
1029
|
+
value: string;
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
// TODO: Type this
|
|
1033
|
+
type CompileStack = unknown[];
|
|
1034
|
+
|
|
1035
|
+
export type CompilerState = {
|
|
1036
|
+
schema: Schema;
|
|
1037
|
+
implicitTableName: string;
|
|
1038
|
+
implicitTableId: string;
|
|
1039
|
+
paths: Map<string, unknown>;
|
|
1040
|
+
dependencies: string[];
|
|
1041
|
+
compileStack: CompileStack;
|
|
1042
|
+
outputTypes: OutputTypes;
|
|
1043
|
+
validateRefs: boolean;
|
|
1044
|
+
namedParameters: NamedParameter[];
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
export type SqlPieces = {
|
|
1048
|
+
select: string;
|
|
1049
|
+
from: string;
|
|
1050
|
+
joins: string;
|
|
1051
|
+
where: string;
|
|
1052
|
+
groupBy: string;
|
|
1053
|
+
orderBy: string;
|
|
1054
|
+
limit: number | null;
|
|
1055
|
+
offset: number | null;
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
export function compileQuery(
|
|
1059
|
+
queryState: QueryState,
|
|
1060
|
+
schema: Schema,
|
|
1061
|
+
schemaConfig: SchemaConfig = {},
|
|
1062
|
+
) {
|
|
1063
|
+
const { withDead, validateRefs = true, tableOptions, rawMode } = queryState;
|
|
1064
|
+
|
|
1065
|
+
const {
|
|
1066
|
+
tableViews = {},
|
|
1067
|
+
tableFilters = () => [],
|
|
1068
|
+
customizeQuery = queryState => queryState,
|
|
1069
|
+
} = schemaConfig;
|
|
1070
|
+
|
|
1071
|
+
const internalTableFilters = name => {
|
|
1072
|
+
const filters = tableFilters(name);
|
|
1073
|
+
// These filters cannot join tables and must be simple strings
|
|
1074
|
+
for (const filter of filters) {
|
|
1075
|
+
if (Array.isArray(filter)) {
|
|
1076
|
+
throw new CompileError(
|
|
1077
|
+
'Invalid internal table filter: only object filters are supported',
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
if (Object.keys(filter)[0].indexOf('.') !== -1) {
|
|
1081
|
+
throw new CompileError(
|
|
1082
|
+
'Invalid internal table filter: field names cannot contain paths',
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
return filters;
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
const tableRef = (name: string, isJoin?: boolean) => {
|
|
1090
|
+
const view =
|
|
1091
|
+
typeof tableViews === 'function'
|
|
1092
|
+
? tableViews(name, { withDead, isJoin, tableOptions })
|
|
1093
|
+
: tableViews[name];
|
|
1094
|
+
return view || name;
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
const tableName = queryState.table;
|
|
1098
|
+
|
|
1099
|
+
const {
|
|
1100
|
+
filterExpressions,
|
|
1101
|
+
selectExpressions,
|
|
1102
|
+
groupExpressions,
|
|
1103
|
+
orderExpressions,
|
|
1104
|
+
limit,
|
|
1105
|
+
offset,
|
|
1106
|
+
} = customizeQuery(queryState);
|
|
1107
|
+
|
|
1108
|
+
let select = '';
|
|
1109
|
+
let where = '';
|
|
1110
|
+
let joins = '';
|
|
1111
|
+
let groupBy = '';
|
|
1112
|
+
let orderBy = '';
|
|
1113
|
+
const state: CompilerState = {
|
|
1114
|
+
schema,
|
|
1115
|
+
implicitTableName: tableName,
|
|
1116
|
+
implicitTableId: tableRef(tableName),
|
|
1117
|
+
paths: new Map(),
|
|
1118
|
+
dependencies: [tableName],
|
|
1119
|
+
compileStack: [],
|
|
1120
|
+
outputTypes: new Map(),
|
|
1121
|
+
validateRefs,
|
|
1122
|
+
namedParameters: [],
|
|
1123
|
+
};
|
|
1124
|
+
|
|
1125
|
+
resetUid();
|
|
1126
|
+
|
|
1127
|
+
try {
|
|
1128
|
+
select = compileSelect(
|
|
1129
|
+
state,
|
|
1130
|
+
selectExpressions,
|
|
1131
|
+
isAggregateQuery(queryState),
|
|
1132
|
+
orderExpressions,
|
|
1133
|
+
);
|
|
1134
|
+
|
|
1135
|
+
if (filterExpressions.length > 0) {
|
|
1136
|
+
const result = compileWhere(state, filterExpressions);
|
|
1137
|
+
where = 'WHERE ' + result;
|
|
1138
|
+
} else {
|
|
1139
|
+
where = 'WHERE 1';
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
if (!rawMode) {
|
|
1143
|
+
const filters = internalTableFilters(tableName);
|
|
1144
|
+
if (filters.length > 0) {
|
|
1145
|
+
where += ' AND ' + compileAnd(state, filters);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
if (groupExpressions.length > 0) {
|
|
1150
|
+
const result = compileGroupBy(state, groupExpressions);
|
|
1151
|
+
groupBy = 'GROUP BY ' + result;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// Orders don't matter if doing a single calculation
|
|
1155
|
+
if (orderExpressions.length > 0) {
|
|
1156
|
+
const result = compileOrderBy(state, orderExpressions);
|
|
1157
|
+
orderBy = 'ORDER BY ' + result;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
if (state.paths.size > 0) {
|
|
1161
|
+
joins = compileJoins(state, tableRef, internalTableFilters);
|
|
1162
|
+
}
|
|
1163
|
+
} catch (e) {
|
|
1164
|
+
if (e instanceof CompileError) {
|
|
1165
|
+
throw getCompileError(e, state.compileStack);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
throw e;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const sqlPieces: SqlPieces = {
|
|
1172
|
+
select,
|
|
1173
|
+
from: tableRef(tableName),
|
|
1174
|
+
joins,
|
|
1175
|
+
where,
|
|
1176
|
+
groupBy,
|
|
1177
|
+
orderBy,
|
|
1178
|
+
limit,
|
|
1179
|
+
offset,
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
return {
|
|
1183
|
+
sqlPieces,
|
|
1184
|
+
state,
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
export function defaultConstructQuery(
|
|
1189
|
+
queryState: QueryState,
|
|
1190
|
+
compilerState: CompilerState,
|
|
1191
|
+
sqlPieces: SqlPieces,
|
|
1192
|
+
) {
|
|
1193
|
+
const s = sqlPieces;
|
|
1194
|
+
|
|
1195
|
+
const where = queryState.withDead
|
|
1196
|
+
? s.where
|
|
1197
|
+
: addTombstone(
|
|
1198
|
+
compilerState.schema,
|
|
1199
|
+
compilerState.implicitTableName,
|
|
1200
|
+
compilerState.implicitTableId,
|
|
1201
|
+
s.where,
|
|
1202
|
+
);
|
|
1203
|
+
|
|
1204
|
+
return `
|
|
1205
|
+
SELECT ${s.select} FROM ${s.from}
|
|
1206
|
+
${s.joins}
|
|
1207
|
+
${where}
|
|
1208
|
+
${s.groupBy}
|
|
1209
|
+
${s.orderBy}
|
|
1210
|
+
${s.limit != null ? `LIMIT ${s.limit}` : ''}
|
|
1211
|
+
${s.offset != null ? `OFFSET ${s.offset}` : ''}
|
|
1212
|
+
`;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
export function generateSQLWithState(
|
|
1216
|
+
queryState: QueryState,
|
|
1217
|
+
schema?: Schema,
|
|
1218
|
+
schemaConfig?: SchemaConfig,
|
|
1219
|
+
) {
|
|
1220
|
+
const { sqlPieces, state } = compileQuery(queryState, schema, schemaConfig);
|
|
1221
|
+
return { sql: defaultConstructQuery(queryState, state, sqlPieces), state };
|
|
1222
|
+
}
|