@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,134 @@
|
|
|
1
|
+
// @ts-strict-ignore
|
|
2
|
+
import SQL from 'better-sqlite3';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
|
|
5
|
+
import { readFile, removeFile } from '../fs';
|
|
6
|
+
import { logger } from '../log';
|
|
7
|
+
|
|
8
|
+
import { normalise } from './normalise';
|
|
9
|
+
import { unicodeLike } from './unicodeLike';
|
|
10
|
+
|
|
11
|
+
function verifyParamTypes(sql, arr) {
|
|
12
|
+
arr.forEach(val => {
|
|
13
|
+
if (typeof val !== 'string' && typeof val !== 'number' && val !== null) {
|
|
14
|
+
logger.log(sql, arr);
|
|
15
|
+
throw new Error('Invalid field type ' + val + ' for sql ' + sql);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function init() {
|
|
21
|
+
// No need to initialise on electron
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function prepare(db, sql) {
|
|
25
|
+
return db.prepare(sql);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function runQuery(
|
|
29
|
+
db: SQL.Database,
|
|
30
|
+
sql: string | SQL.Statement,
|
|
31
|
+
params: (string | number)[] = [],
|
|
32
|
+
fetchAll = false,
|
|
33
|
+
) {
|
|
34
|
+
if (params) {
|
|
35
|
+
verifyParamTypes(sql, params);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let stmt: SQL.Statement;
|
|
39
|
+
try {
|
|
40
|
+
stmt = typeof sql === 'string' ? db.prepare(sql) : sql;
|
|
41
|
+
} catch (e) {
|
|
42
|
+
logger.log('error', sql);
|
|
43
|
+
throw e;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (fetchAll) {
|
|
47
|
+
try {
|
|
48
|
+
const result = stmt.all(...params);
|
|
49
|
+
return result;
|
|
50
|
+
} catch (e) {
|
|
51
|
+
logger.log('error', sql);
|
|
52
|
+
throw e;
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
const info = stmt.run(...params);
|
|
56
|
+
return { changes: info.changes, insertId: info.lastInsertRowid };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function execQuery(db: SQL.Database, sql: string) {
|
|
61
|
+
db.exec(sql);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function transaction(db: SQL.Database, fn: () => void) {
|
|
65
|
+
db.transaction(fn)();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// **Important**: this is an unsafe function since sqlite executes
|
|
69
|
+
// executes statements sequentially. It would be easy for other code
|
|
70
|
+
// to run statements in between our transaction and get caught up in
|
|
71
|
+
// it. This is rarely used, and only needed for specific cases (like
|
|
72
|
+
// batch importing a bunch of data). Don't use this.
|
|
73
|
+
let transactionDepth = 0;
|
|
74
|
+
export async function asyncTransaction(
|
|
75
|
+
db: SQL.Database,
|
|
76
|
+
fn: () => Promise<void>,
|
|
77
|
+
) {
|
|
78
|
+
// Support nested transactions by "coalescing" them into the parent
|
|
79
|
+
// one if one is already started
|
|
80
|
+
if (transactionDepth === 0) {
|
|
81
|
+
db.exec('BEGIN TRANSACTION');
|
|
82
|
+
}
|
|
83
|
+
transactionDepth++;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await fn();
|
|
87
|
+
} finally {
|
|
88
|
+
transactionDepth--;
|
|
89
|
+
// We always commit because rollback is more dangerous - any
|
|
90
|
+
// queries that ran *in-between* this async function would be
|
|
91
|
+
// lost. Right now we are only using transactions for speed
|
|
92
|
+
// purposes unfortunately
|
|
93
|
+
if (transactionDepth === 0) {
|
|
94
|
+
db.exec('COMMIT');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function regexp(regex: string, text: string | null) {
|
|
100
|
+
return new RegExp(regex).test(text || '') ? 1 : 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function openDatabase(pathOrBuffer: string | Buffer): SQL.Database {
|
|
104
|
+
const db = new SQL(pathOrBuffer);
|
|
105
|
+
// Define Unicode-aware LOWER, UPPER, and LIKE implementation.
|
|
106
|
+
// This is necessary because better-sqlite3 uses SQLite build without ICU support.
|
|
107
|
+
db.function('UNICODE_LOWER', { deterministic: true }, (arg: string | null) =>
|
|
108
|
+
arg?.toLowerCase(),
|
|
109
|
+
);
|
|
110
|
+
db.function('UNICODE_UPPER', { deterministic: true }, (arg: string | null) =>
|
|
111
|
+
arg?.toUpperCase(),
|
|
112
|
+
);
|
|
113
|
+
db.function('UNICODE_LIKE', { deterministic: true }, unicodeLike);
|
|
114
|
+
db.function('REGEXP', { deterministic: true }, regexp);
|
|
115
|
+
db.function('NORMALISE', { deterministic: true }, normalise);
|
|
116
|
+
return db;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function closeDatabase(db: SQL.Database) {
|
|
120
|
+
db.close();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function exportDatabase(db: SQL.Database) {
|
|
124
|
+
// electron does not support better-sqlite serialize since v21
|
|
125
|
+
// save to file and read in the raw data.
|
|
126
|
+
const name = `${process.env.ACTUAL_DATA_DIR}/backup-for-export-${uuidv4()}.db`;
|
|
127
|
+
|
|
128
|
+
await db.backup(name);
|
|
129
|
+
|
|
130
|
+
const data = await readFile(name, 'binary');
|
|
131
|
+
await removeFile(name);
|
|
132
|
+
|
|
133
|
+
return data;
|
|
134
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// @ts-strict-ignore
|
|
2
|
+
import { patchFetchForSqlJS } from '../../../mocks/util';
|
|
3
|
+
|
|
4
|
+
import { execQuery, init, openDatabase, runQuery, transaction } from './index';
|
|
5
|
+
|
|
6
|
+
beforeAll(async () => {
|
|
7
|
+
const baseURL = `${__dirname}/../../../../../../node_modules/@jlongster/sql.js/dist/`;
|
|
8
|
+
patchFetchForSqlJS(baseURL);
|
|
9
|
+
|
|
10
|
+
return init({ baseURL });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const initSQL = `
|
|
14
|
+
CREATE TABLE numbers (id TEXT PRIMARY KEY, number INTEGER);
|
|
15
|
+
CREATE TABLE textstrings (id TEXT PRIMARY KEY, string TEXT);
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
describe('Web sqlite', () => {
|
|
19
|
+
it('should rollback transactions', async () => {
|
|
20
|
+
const db = await openDatabase();
|
|
21
|
+
execQuery(db, initSQL);
|
|
22
|
+
|
|
23
|
+
runQuery(db, "INSERT INTO numbers (id, number) VALUES ('id1', 4)");
|
|
24
|
+
|
|
25
|
+
let rows = runQuery(db, 'SELECT * FROM numbers', null, true);
|
|
26
|
+
expect(rows.length).toBe(1);
|
|
27
|
+
// @ts-expect-error Property 'number' does not exist on type 'unknown'
|
|
28
|
+
expect(rows[0].number).toBe(4);
|
|
29
|
+
|
|
30
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => null);
|
|
31
|
+
expect(() => {
|
|
32
|
+
transaction(db, () => {
|
|
33
|
+
runQuery(db, "INSERT INTO numbers (id, number) VALUES ('id2', 5)");
|
|
34
|
+
runQuery(db, "INSERT INTO numbers (id, number) VALUES ('id3', 6)");
|
|
35
|
+
// Insert an invalid one that will error
|
|
36
|
+
runQuery(db, "INSERT INTO numbers (id, number) VALUES ('id1', 1)");
|
|
37
|
+
});
|
|
38
|
+
}).toThrow(/constraint failed/);
|
|
39
|
+
consoleSpy.mockRestore();
|
|
40
|
+
|
|
41
|
+
// Nothing should have changed in the db
|
|
42
|
+
rows = runQuery(db, 'SELECT * FROM numbers', null, true);
|
|
43
|
+
expect(rows.length).toBe(1);
|
|
44
|
+
// @ts-expect-error Property 'number' does not exist on type 'unknown'
|
|
45
|
+
expect(rows[0].number).toBe(4);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should support nested transactions', async () => {
|
|
49
|
+
const db = await openDatabase();
|
|
50
|
+
execQuery(db, initSQL);
|
|
51
|
+
|
|
52
|
+
runQuery(db, "INSERT INTO numbers (id, number) VALUES ('id1', 4)");
|
|
53
|
+
|
|
54
|
+
let rows = runQuery(db, 'SELECT * FROM numbers', null, true);
|
|
55
|
+
expect(rows.length).toBe(1);
|
|
56
|
+
// @ts-expect-error Property 'number' does not exist on type 'unknown'
|
|
57
|
+
expect(rows[0].number).toBe(4);
|
|
58
|
+
|
|
59
|
+
transaction(db, () => {
|
|
60
|
+
runQuery(db, "INSERT INTO numbers (id, number) VALUES ('id2', 5)");
|
|
61
|
+
runQuery(db, "INSERT INTO numbers (id, number) VALUES ('id3', 6)");
|
|
62
|
+
|
|
63
|
+
// Only this transaction should fail
|
|
64
|
+
const consoleSpy = vi
|
|
65
|
+
.spyOn(console, 'log')
|
|
66
|
+
.mockImplementation(() => null);
|
|
67
|
+
expect(() => {
|
|
68
|
+
transaction(db, () => {
|
|
69
|
+
runQuery(db, "INSERT INTO numbers (id, number) VALUES ('id4', 7)");
|
|
70
|
+
// Insert an invalid one that will error
|
|
71
|
+
runQuery(db, "INSERT INTO numbers (id, number) VALUES ('id1', 1)");
|
|
72
|
+
});
|
|
73
|
+
}).toThrow(/constraint failed/);
|
|
74
|
+
consoleSpy.mockRestore();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Nothing should have changed in the db
|
|
78
|
+
rows = runQuery(db, 'SELECT * FROM numbers', null, true);
|
|
79
|
+
expect(rows.length).toBe(3);
|
|
80
|
+
// @ts-expect-error Property 'number' does not exist on type 'unknown'
|
|
81
|
+
expect(rows[0].number).toBe(4);
|
|
82
|
+
// @ts-expect-error Property 'number' does not exist on type 'unknown'
|
|
83
|
+
expect(rows[1].number).toBe(5);
|
|
84
|
+
// @ts-expect-error Property 'number' does not exist on type 'unknown'
|
|
85
|
+
expect(rows[2].number).toBe(6);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should match regex on text fields', async () => {
|
|
89
|
+
const db = await openDatabase();
|
|
90
|
+
execQuery(db, initSQL);
|
|
91
|
+
|
|
92
|
+
runQuery(
|
|
93
|
+
db,
|
|
94
|
+
"INSERT INTO textstrings (id, string) VALUES ('id1', 'not empty string')",
|
|
95
|
+
);
|
|
96
|
+
runQuery(db, "INSERT INTO textstrings (id) VALUES ('id2')");
|
|
97
|
+
|
|
98
|
+
const rows = runQuery(
|
|
99
|
+
db,
|
|
100
|
+
'SELECT id FROM textstrings where REGEXP("n.", string)',
|
|
101
|
+
null,
|
|
102
|
+
true,
|
|
103
|
+
);
|
|
104
|
+
expect(rows.length).toBe(1);
|
|
105
|
+
// @ts-expect-error Property 'id' does not exist on type 'unknown'
|
|
106
|
+
expect(rows[0].id).toBe('id1');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// @ts-strict-ignore
|
|
2
|
+
import initSqlJS from '@jlongster/sql.js';
|
|
3
|
+
import type { Database, SqlJsStatic, Statement } from '@jlongster/sql.js';
|
|
4
|
+
|
|
5
|
+
import { logger } from '../log';
|
|
6
|
+
|
|
7
|
+
import { normalise } from './normalise';
|
|
8
|
+
import { unicodeLike } from './unicodeLike';
|
|
9
|
+
|
|
10
|
+
// Types exported from sql.js (and Emscripten) are incomplete, so we need to redefine them here
|
|
11
|
+
type FSStream = (typeof FS)['FSStream'] & {
|
|
12
|
+
node: (typeof FS)['FSNode'] & {
|
|
13
|
+
contents: {
|
|
14
|
+
readIfFallback: () => Promise<unknown>;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
type FS = Omit<typeof FS, 'lookupPath' | 'open' | 'close'> & {
|
|
19
|
+
lookupPath: (
|
|
20
|
+
path: string,
|
|
21
|
+
opts?: { follow?: boolean },
|
|
22
|
+
) => { node: (typeof FS)['FSNode'] & { link?: string } };
|
|
23
|
+
open: (path: string, flags: string, mode?: number) => FSStream;
|
|
24
|
+
close: (stream: FSStream) => void;
|
|
25
|
+
};
|
|
26
|
+
export type SqlJsModule = SqlJsStatic & {
|
|
27
|
+
FS: FS;
|
|
28
|
+
reset_filesystem: () => void;
|
|
29
|
+
register_for_idb: (idb: IDBDatabase) => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let SQL: SqlJsModule | null = null;
|
|
33
|
+
|
|
34
|
+
export async function init({
|
|
35
|
+
baseURL = process.env.PUBLIC_URL,
|
|
36
|
+
}: { baseURL?: string } = {}) {
|
|
37
|
+
// `initSqlJS` doesn't actually return a real promise, so make sure
|
|
38
|
+
// we're returning a real one for correct semantics
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
initSqlJS({
|
|
41
|
+
locateFile: file => baseURL + file,
|
|
42
|
+
}).then(
|
|
43
|
+
sql => {
|
|
44
|
+
SQL = sql as SqlJsModule;
|
|
45
|
+
resolve(undefined);
|
|
46
|
+
},
|
|
47
|
+
err => {
|
|
48
|
+
reject(err);
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function _getModule() {
|
|
55
|
+
if (SQL == null) {
|
|
56
|
+
throw new Error('_getModule: sql.js must be initialized first');
|
|
57
|
+
}
|
|
58
|
+
return SQL;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function verifyParamTypes(
|
|
62
|
+
sql: string | Statement,
|
|
63
|
+
arr: (string | number)[] = [],
|
|
64
|
+
) {
|
|
65
|
+
arr.forEach(val => {
|
|
66
|
+
if (typeof val !== 'string' && typeof val !== 'number' && val !== null) {
|
|
67
|
+
throw new Error('Invalid field type ' + val + ' for sql ' + sql);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function prepare(db: Database, sql: string) {
|
|
73
|
+
return db.prepare(sql);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function runQuery(
|
|
77
|
+
db: Database,
|
|
78
|
+
sql: string | Statement,
|
|
79
|
+
params?: (string | number)[],
|
|
80
|
+
fetchAll?: false,
|
|
81
|
+
): { changes: unknown };
|
|
82
|
+
export function runQuery<T>(
|
|
83
|
+
db: Database,
|
|
84
|
+
sql: string | Statement,
|
|
85
|
+
params: (string | number)[],
|
|
86
|
+
fetchAll: true,
|
|
87
|
+
): T[];
|
|
88
|
+
export function runQuery<T>(
|
|
89
|
+
db: Database,
|
|
90
|
+
sql: string | Statement,
|
|
91
|
+
params: (string | number)[] = [],
|
|
92
|
+
fetchAll = false,
|
|
93
|
+
): T[] | { changes: unknown } {
|
|
94
|
+
if (params) {
|
|
95
|
+
verifyParamTypes(sql, params);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const stmt = typeof sql === 'string' ? db.prepare(sql) : sql;
|
|
99
|
+
|
|
100
|
+
if (fetchAll) {
|
|
101
|
+
try {
|
|
102
|
+
stmt.bind(params);
|
|
103
|
+
const rows = [];
|
|
104
|
+
|
|
105
|
+
while (stmt.step()) {
|
|
106
|
+
rows.push(stmt.getAsObject());
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (typeof sql === 'string') {
|
|
110
|
+
stmt.free();
|
|
111
|
+
} else {
|
|
112
|
+
stmt.reset();
|
|
113
|
+
}
|
|
114
|
+
return rows;
|
|
115
|
+
} catch (e) {
|
|
116
|
+
logger.log(sql);
|
|
117
|
+
throw e;
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
stmt.run(params);
|
|
121
|
+
return { changes: db.getRowsModified() };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function execQuery(db: Database, sql: string) {
|
|
126
|
+
db.exec(sql);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let transactionDepth = 0;
|
|
130
|
+
|
|
131
|
+
export function transaction(db: Database, fn: () => void) {
|
|
132
|
+
let before, after, undo;
|
|
133
|
+
if (transactionDepth > 0) {
|
|
134
|
+
before = 'SAVEPOINT __actual_sp';
|
|
135
|
+
after = 'RELEASE __actual_sp';
|
|
136
|
+
undo = 'ROLLBACK TO __actual_sp';
|
|
137
|
+
} else {
|
|
138
|
+
before = 'BEGIN';
|
|
139
|
+
after = 'COMMIT';
|
|
140
|
+
undo = 'ROLLBACK';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
execQuery(db, before);
|
|
144
|
+
transactionDepth++;
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
fn();
|
|
148
|
+
execQuery(db, after);
|
|
149
|
+
} catch (ex) {
|
|
150
|
+
execQuery(db, undo);
|
|
151
|
+
|
|
152
|
+
if (undo !== 'ROLLBACK') {
|
|
153
|
+
execQuery(db, after);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
throw ex;
|
|
157
|
+
} finally {
|
|
158
|
+
transactionDepth--;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// See the comment about this function in index.electron.js. You
|
|
163
|
+
// shouldn't normally use this. I'd like to get rid of it.
|
|
164
|
+
export async function asyncTransaction(db: Database, fn: () => Promise<void>) {
|
|
165
|
+
// Support nested transactions by "coalescing" them into the parent
|
|
166
|
+
// one if one is already started
|
|
167
|
+
if (transactionDepth === 0) {
|
|
168
|
+
db.exec('BEGIN TRANSACTION');
|
|
169
|
+
}
|
|
170
|
+
transactionDepth++;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
await fn();
|
|
174
|
+
} finally {
|
|
175
|
+
transactionDepth--;
|
|
176
|
+
// We always commit because rollback is more dangerous - any
|
|
177
|
+
// queries that ran *in-between* this async function would be
|
|
178
|
+
// lost. Right now we are only using transactions for speed
|
|
179
|
+
// purposes unfortunately
|
|
180
|
+
if (transactionDepth === 0) {
|
|
181
|
+
db.exec('COMMIT');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function regexp(regex: string, text: string) {
|
|
187
|
+
return new RegExp(regex).test(text || '') ? 1 : 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export async function openDatabase(pathOrBuffer?: string | Uint8Array) {
|
|
191
|
+
let db = null;
|
|
192
|
+
if (pathOrBuffer) {
|
|
193
|
+
if (typeof pathOrBuffer !== 'string') {
|
|
194
|
+
db = new SQL.Database(pathOrBuffer);
|
|
195
|
+
} else {
|
|
196
|
+
const path = pathOrBuffer;
|
|
197
|
+
if (path !== ':memory:') {
|
|
198
|
+
if (typeof SharedArrayBuffer === 'undefined') {
|
|
199
|
+
const stream = SQL.FS.open(SQL.FS.readlink(path), 'a+');
|
|
200
|
+
await stream.node.contents.readIfFallback();
|
|
201
|
+
SQL.FS.close(stream);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
db = new SQL.Database(
|
|
205
|
+
path.includes('/blocked') ? path : SQL.FS.readlink(path),
|
|
206
|
+
// @ts-expect-error 2nd argument missed in sql.js types
|
|
207
|
+
{ filename: true },
|
|
208
|
+
);
|
|
209
|
+
db.exec(`
|
|
210
|
+
PRAGMA journal_mode=MEMORY;
|
|
211
|
+
PRAGMA cache_size=-10000;
|
|
212
|
+
`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (db === null) {
|
|
218
|
+
db = new SQL.Database();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Define Unicode-aware LOWER, UPPER, and LIKE implementation.
|
|
222
|
+
// This is necessary because sql.js uses SQLite build without ICU support.
|
|
223
|
+
//
|
|
224
|
+
// Note that this function should ideally be created with a deterministic flag
|
|
225
|
+
// to allow SQLite to better optimize calls to it by factoring them out of inner loops
|
|
226
|
+
// but SQL.js does not support this: https://github.com/sql-js/sql.js/issues/551
|
|
227
|
+
db.create_function('UNICODE_LOWER', arg => arg?.toLowerCase());
|
|
228
|
+
db.create_function('UNICODE_UPPER', arg => arg?.toUpperCase());
|
|
229
|
+
db.create_function('UNICODE_LIKE', unicodeLike);
|
|
230
|
+
db.create_function('REGEXP', regexp);
|
|
231
|
+
db.create_function('NORMALISE', normalise);
|
|
232
|
+
return db;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function closeDatabase(db: Database) {
|
|
236
|
+
db.close();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function exportDatabase(db: Database) {
|
|
240
|
+
return db.export();
|
|
241
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { unicodeLike } from './unicodeLike';
|
|
2
|
+
|
|
3
|
+
describe('unicode LIKE functionality', () => {
|
|
4
|
+
it('empty pattern should not match to a value', () => {
|
|
5
|
+
const result = unicodeLike(null, 'value');
|
|
6
|
+
|
|
7
|
+
expect(result).toBe(0);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('empty pattern should not match to null', () => {
|
|
11
|
+
const result = unicodeLike(null, null);
|
|
12
|
+
|
|
13
|
+
expect(result).toBe(0);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should match special characters', () => {
|
|
17
|
+
// oxlint-disable-next-line no-template-curly-in-string
|
|
18
|
+
const result = unicodeLike('.*+^${}()|[]\\', '.*+^${}()|[]\\');
|
|
19
|
+
|
|
20
|
+
expect(result).toBe(1);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should use ? as the single character placeholder', () => {
|
|
24
|
+
const result = unicodeLike('t?st', 'test');
|
|
25
|
+
|
|
26
|
+
expect(result).toBe(1);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should use % as the zero-or-more characters placeholder', () => {
|
|
30
|
+
const result = unicodeLike('t%st', 'te123st');
|
|
31
|
+
|
|
32
|
+
expect(result).toBe(1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should ignore case for unicode', () => {
|
|
36
|
+
const result = unicodeLike('á', 'Ábcdefg');
|
|
37
|
+
|
|
38
|
+
expect(result).toBe(1);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should ignore case for ascii', () => {
|
|
42
|
+
const result = unicodeLike('a', 'Abcdefg');
|
|
43
|
+
|
|
44
|
+
expect(result).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should treat null value as empty string', () => {
|
|
48
|
+
const result = unicodeLike('%', null);
|
|
49
|
+
|
|
50
|
+
expect(result).toBe(1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should not match null value to the string "null"', () => {
|
|
54
|
+
const result = unicodeLike('null', null);
|
|
55
|
+
|
|
56
|
+
expect(result).toBe(0);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { LRUCache } from 'lru-cache';
|
|
2
|
+
|
|
3
|
+
const likePatternCache = new LRUCache<string, RegExp>({ max: 500 });
|
|
4
|
+
|
|
5
|
+
export function unicodeLike(
|
|
6
|
+
pattern: string | null,
|
|
7
|
+
value: string | null,
|
|
8
|
+
): number {
|
|
9
|
+
if (!pattern) {
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!value) {
|
|
14
|
+
value = '';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let cachedRegExp = likePatternCache.get(pattern);
|
|
18
|
+
if (!cachedRegExp) {
|
|
19
|
+
// we don't escape ? and % because we don't know
|
|
20
|
+
// whether they originate from the user input or from our query compiler.
|
|
21
|
+
// Maybe improve the query compiler to correctly process these characters?
|
|
22
|
+
const processedPattern = pattern
|
|
23
|
+
.replace(/[.*+^${}()|[\]\\]/g, '\\$&')
|
|
24
|
+
.replaceAll('?', '.')
|
|
25
|
+
.replaceAll('%', '.*');
|
|
26
|
+
cachedRegExp = new RegExp(processedPattern, 'i');
|
|
27
|
+
likePatternCache.set(pattern, cachedRegExp);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return cachedRegExp.test(value) ? 1 : 0;
|
|
31
|
+
}
|