Skip to content

Fix/5840 escape search wildcards#7270

Merged
matt-fidd merged 14 commits intoactualbudget:masterfrom
nathaliaacouto:fix/5840-escape-search-wildcards
Apr 8, 2026
Merged

Fix/5840 escape search wildcards#7270
matt-fidd merged 14 commits intoactualbudget:masterfrom
nathaliaacouto:fix/5840-escape-search-wildcards

Conversation

@eduardopio03
Copy link
Copy Markdown
Contributor

@eduardopio03 eduardopio03 commented Mar 23, 2026

Description

When a user types special characters such as ? or % into the transaction quick search bar, all transactions were returned instead of only those matching the literal character. This happened because these characters were being passed unescaped into $like query patterns, where they acted as regex wildcards inside the custom UNICODE_LIKE function.

The fix has two parts:

  • In transactionsSearch (index.ts), the search string is now escaped before being used in $like patterns, so ? and % are prefixed with \ to signal they should be treated as literals.
  • In unicodeLike.ts, the pattern-to-regex converter is updated to recognise these escape sequences and convert them to properly escaped regex literals instead of wildcards. This directly addresses the TODO comment already present in that file.

Related issue(s)

Fixes #5840

Testing

  1. Create a transaction with ? or % in the notes or payee name field.
  2. Open the transaction view and type ? or % in the quick search bar.
  3. Before fix: all transactions are shown.
  4. After fix: only transactions containing the literal ? or % are shown.
  5. Verify that normal searches (e.g. plain text, amounts, dates) still work correctly.

Checklist

  • Release notes added (see link above)
  • No obvious regressions in affected areas
  • Self-review has been performed - I understand what each change in the code does and why it is needed

Bundle Stats

Bundle Files count Total bundle size % Changed
desktop-client 27 12.41 MB → 12.41 MB (+93 B) +0.00%
loot-core 1 4.84 MB → 4.84 MB (+466 B) +0.01%
api 1 3.83 MB → 3.84 MB (+446 B) +0.01%
cli 1 7.89 MB 0%
View detailed bundle stats

desktop-client

Total

Files count Total bundle size % Changed
27 12.41 MB → 12.41 MB (+93 B) +0.00%
Changeset
File Δ Size
src/queries/index.ts 📈 +93 B (+4.26%) 2.13 kB → 2.22 kB
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger

Asset File Size % Changed
static/js/useTransactionBatchActions.js 4.33 MB → 4.33 MB (+93 B) +0.00%

Smaller
No assets were smaller

Unchanged

Asset File Size % Changed
static/js/index.js 3.31 MB 0%
static/js/BackgroundImage.js 121.09 kB 0%
static/js/FormulaEditor.js 852.77 kB 0%
static/js/ReportRouter.js 1.17 MB 0%
static/js/TransactionList.js 82.49 kB 0%
static/js/ca.js 189.75 kB 0%
static/js/da.js 104.66 kB 0%
static/js/de.js 174.38 kB 0%
static/js/en-GB.js 8.2 kB 0%
static/js/en.js 175.65 kB 0%
static/js/es.js 181.8 kB 0%
static/js/fr.js 177.08 kB 0%
static/js/indexeddb-main-thread-worker-e59fee74.js 13.46 kB 0%
static/js/it.js 165.87 kB 0%
static/js/narrow.js 363.02 kB 0%
static/js/nb-NO.js 151.85 kB 0%
static/js/nl.js 108.93 kB 0%
static/js/pl.js 88.34 kB 0%
static/js/pt-BR.js 177.44 kB 0%
static/js/resize-observer.js 18.06 kB 0%
static/js/th.js 179.3 kB 0%
static/js/theme.js 30.79 kB 0%
static/js/uk.js 212.6 kB 0%
static/js/wide.js 295 B 0%
static/js/workbox-window.prod.es5.js 7.33 kB 0%
static/js/zh-Hans.js 94.19 kB 0%

loot-core

Total

Files count Total bundle size % Changed
1 4.84 MB → 4.84 MB (+466 B) +0.01%
Changeset
File Δ Size
home/runner/work/actual/actual/packages/loot-core/src/platform/server/sqlite/unicodeLike.ts 📈 +466 B (+84.88%) 549 B → 1015 B
View detailed bundle breakdown

Added

Asset File Size % Changed
kcab.worker.BNveTyqm.js 0 B → 4.84 MB (+4.84 MB) -

Removed

Asset File Size % Changed
kcab.worker.B7SvglID.js 4.84 MB → 0 B (-4.84 MB) -100%

Bigger
No assets were bigger

Smaller
No assets were smaller

Unchanged
No assets were unchanged


api

Total

Files count Total bundle size % Changed
1 3.83 MB → 3.84 MB (+446 B) +0.01%
Changeset
File Δ Size
home/runner/work/actual/actual/packages/loot-core/src/platform/server/sqlite/unicodeLike.ts 📈 +446 B (+81.54%) 547 B → 993 B
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger

Asset File Size % Changed
index.js 3.83 MB → 3.84 MB (+446 B) +0.01%

Smaller
No assets were smaller

Unchanged
No assets were unchanged


cli

Total

Files count Total bundle size % Changed
1 7.89 MB 0%
View detailed bundle breakdown

Added
No assets were added

Removed
No assets were removed

Bigger
No assets were bigger

Smaller
No assets were smaller

Unchanged

Asset File Size % Changed
cli.js 7.89 MB 0%

@actual-github-bot actual-github-bot bot changed the title Fix/5840 escape search wildcards [WIP] Fix/5840 escape search wildcards Mar 23, 2026
@netlify
Copy link
Copy Markdown

netlify bot commented Mar 23, 2026

Deploy Preview for actualbudget ready!

Name Link
🔨 Latest commit d6717fe
🔍 Latest deploy log https://app.netlify.com/projects/actualbudget/deploys/69d670f71dab5a0007a22b7c
😎 Deploy Preview https://deploy-preview-7270.demo.actualbudget.org
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions
Copy link
Copy Markdown
Contributor

👋 Hello contributor!

We would love to review your PR! Before we can do that, please make sure:

  • ✅ All CI checks pass
  • ✅ The PR is moved from draft to open (if applicable)
  • ✅ The "[WIP]" prefix is removed from the PR title
  • ✅ All CodeRabbit code review comments are resolved (if you disagree with anything - reply to the bot with your reasoning so we can read through it). The bot will eventually approve the PR.

We do this to reduce the TOIL the core contributor team has to go through for each PR and to allow for speedy reviews and merges.

For more information, please see our Contributing Guide.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Escapes SQL/LIKE wildcard characters in transaction quick search and updates LIKE→regex translation to respect backslash-escaped %, ?, and \. Adds unit tests for escaped sequences and a release note documenting the bugfix.

Changes

Cohort / File(s) Summary
Search Query Escaping
packages/desktop-client/src/queries/index.ts
transactionsSearch now computes an escaped search string (escaping \, %, _, ?) and uses it for $like matches on payee.name, payee.transfer_acct.name, notes, category.name, and account.name. Date and amount filters unchanged.
LIKE Pattern Handling & Tests
packages/loot-core/src/platform/server/sqlite/unicodeLike.ts, packages/loot-core/src/platform/server/sqlite/unicodeLike.test.ts
Added internal likePatternToRegex(pattern: string): RegExp and updated unicodeLike to build a case-insensitive regex that treats unescaped %.*, unescaped ?., and respects backslash-escaped %, ?, and \. Added tests ensuring \%, \?, and \\ match literal characters.
Release Notes
upcoming-release-notes/7270.md
New bugfix entry documenting that quick search previously treated ? and % as wildcards and now treats them as literals when escaped.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through queries, small and bright,
Found % and ? misbehaving in the light.
I wrapped them in backslashes, tidy and neat,
Now literals and searches finally meet.
A nibble, a hop — the results feel right. 🥕

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: escaping wildcard characters in search patterns to fix issue #5840.
Linked Issues check ✅ Passed The PR successfully addresses issue #5840 by escaping wildcard characters (? and %) in search patterns, implementing both the query-side escaping and pattern-to-regex conversion to treat them as literals.
Out of Scope Changes check ✅ Passed All code changes directly support fixing the wildcard escaping issue: transactionsSearch escapes input, unicodeLike handles escaped patterns, tests validate escaping, and release notes document the fix.
Description check ✅ Passed The PR description clearly explains the bug (special characters treated as wildcards in search), identifies the two-part fix (escaping in transactionsSearch and pattern recognition in unicodeLike), references the related issue (#5840), and provides testing steps.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/desktop-client/src/queries/index.ts (1)

87-87: ⚠️ Potential issue | 🟠 Major

Incomplete escaping: backslashes in user input are not escaped.

The CodeQL warning is valid. If a user searches for a literal backslash (e.g., a\b), the backslash passes through unescaped and will be interpreted as an escape character by unicodeLike, potentially causing unexpected behavior.

The backslash must be escaped first, before escaping the wildcards:

🐛 Proposed fix to escape backslashes
-  const escapedSearch = search.replace(/[%_?]/g, '\\$&');
+  const escapedSearch = search.replace(/\\/g, '\\\\').replace(/[%_?]/g, '\\$&');

Alternatively, escape all special characters in a single pass with proper ordering:

const escapedSearch = search.replace(/[\\%_?]/g, '\\$&');

Note: The single-pass version works because $& inserts the matched character, so \ becomes \\, % becomes \%, etc.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/desktop-client/src/queries/index.ts` at line 87, The current
escapedSearch calculation in queries/index.ts misses escaping backslashes, so
update the replace to escape backslashes as well (either escape backslashes
first or use a single-pass regex that includes backslash) so that inputs like
"a\b" don't misbehave when passed to unicodeLike; modify the code that defines
escapedSearch (and any callers that pass it to unicodeLike) to use a pattern
that replaces backslash, percent, underscore, and question-mark (e.g., include
'\\' in the character class) ensuring backslashes become '\\\\' in the resulting
string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/loot-core/src/platform/server/sqlite/unicodeLike.ts`:
- Around line 5-29: In likePatternToRegex, update the escape handling in the
likePatternToRegex function so it only treats '%' and '?' as escapable wildcards
(remove '_' from the condition that checks next === '%' || next === '_' || next
=== '?'); adjust the conditional to next === '%' || next === '?' and keep the
existing logic that escapes the literal character and advances i by 2, ensuring
plain '_' continues to be handled as a normal literal via the default branch.

---

Duplicate comments:
In `@packages/desktop-client/src/queries/index.ts`:
- Line 87: The current escapedSearch calculation in queries/index.ts misses
escaping backslashes, so update the replace to escape backslashes as well
(either escape backslashes first or use a single-pass regex that includes
backslash) so that inputs like "a\b" don't misbehave when passed to unicodeLike;
modify the code that defines escapedSearch (and any callers that pass it to
unicodeLike) to use a pattern that replaces backslash, percent, underscore, and
question-mark (e.g., include '\\' in the character class) ensuring backslashes
become '\\\\' in the resulting string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e4bc05e-f038-4fdb-964a-b2888a1e798f

📥 Commits

Reviewing files that changed from the base of the PR and between 9232e0d and d8a2843.

📒 Files selected for processing (3)
  • packages/desktop-client/src/queries/index.ts
  • packages/loot-core/src/platform/server/sqlite/unicodeLike.ts
  • upcoming-release-notes/7270.md

@eduardopio03 eduardopio03 marked this pull request as draft March 23, 2026 20:49
@eduardopio03 eduardopio03 marked this pull request as ready for review March 23, 2026 21:31
@actual-github-bot actual-github-bot bot changed the title [WIP] Fix/5840 escape search wildcards Fix/5840 escape search wildcards Mar 23, 2026
@coderabbitai coderabbitai bot removed the size small label Mar 23, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/loot-core/src/platform/server/sqlite/unicodeLike.ts`:
- Around line 10-17: The escape handling currently only treats "\%" and "\?" as
escaped wildcards; update the logic in unicodeLike
(packages/loot-core/src/platform/server/sqlite/unicodeLike.ts) so that "\_" is
also recognized as an escaped wildcard: extend the condition that checks next
(currently next === '%' || next === '?') to include next === '_' and ensure the
replacement/escaping logic that appends to regexStr still treats the underscore
correctly (add '_' to the list if you maintain a custom escape character class,
or note that '_' is not a special regex char so simply allowing it through is
fine); adjust the variables pattern, next, and regexStr handling accordingly so
"\_" becomes a literal underscore in the generated regex.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 430f0d74-56d1-4c2c-98e1-9198edd315e9

📥 Commits

Reviewing files that changed from the base of the PR and between d8a2843 and e01706d.

📒 Files selected for processing (2)
  • packages/desktop-client/src/queries/index.ts
  • packages/loot-core/src/platform/server/sqlite/unicodeLike.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/desktop-client/src/queries/index.ts

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 23, 2026
Copy link
Copy Markdown
Member

@matt-fidd matt-fidd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a look at this!

Could you have a look at my comments, and add some tests in for this new behaviour please?

@eduardopio03
Copy link
Copy Markdown
Contributor Author

@matt-fidd Already did you requests :) Hope everything it's right

Thank you!

Looks like there's just one comment left, and that's to remove the handling in the escapedSearch regex, because it shouldn't be treated as a wildcard.

Removed _ from the escapedSearch regex in index.ts! Everything should be good to go.

@StephenBrown2
Copy link
Copy Markdown
Contributor

StephenBrown2 commented Mar 25, 2026

Wait, I considered this a feature, not a bug. How often do payees actually contain special characters?

@eduardopio03
Copy link
Copy Markdown
Contributor Author

Wait, I considered this a feature, not a bug. How often do payees actually contain special characters?

I guess, in the "notes" you can put wathever you want.

@StephenBrown2
Copy link
Copy Markdown
Contributor

This is true, but even more of a reason to include wildcard characters. In that case, can you make it escape only for the notes field? https://github.com/actualbudget/actual/pull/7270/changes#diff-d8fa4e0507344fcd8454e77d6dec4adb052acaea5abb19682dc861998cb2f4c5R103

@eduardopio03
Copy link
Copy Markdown
Contributor Author

This is true, but even more of a reason to include wildcard characters. In that case, can you make it escape only for the notes field? https://github.com/actualbudget/actual/pull/7270/changes#diff-d8fa4e0507344fcd8454e77d6dec4adb052acaea5abb19682dc861998cb2f4c5R103

@StephenBrown2 I understand the value of having wildcards for advanced searches! However, the quick search uses an $or condition across all fields. If we leave the notes field unescaped, typing a ? in the search bar will still return all transactions (because the unescaped notes field will match everything). This would defeat the purpose of fixing the bug reported in #5840.

@matt-fidd since you reviewed the initial fix, what is your take on this? I'm happy to adjust the code, but I want to make sure we don't accidentally reintroduce the bug for the global search. Let me know how you'd like to proceed!

@eduardopio03
Copy link
Copy Markdown
Contributor Author

Hi @matt-fidd! I would love to see this closed. Can you take a look?

@matt-fidd matt-fidd force-pushed the master branch 2 times, most recently from 5c7c70d to d262f7d Compare April 5, 2026 17:13
@matt-fidd
Copy link
Copy Markdown
Member

matt-fidd commented Apr 8, 2026

Hi @matt-fidd! I would love to see this closed. Can you take a look?

Hey, thanks for your work on this. I understand Stephen's points here, and also what you've said about the or operator making it return everything regardless. I've been tossing this one about in my head, and i think that most users will expect it to be escaped and won't be using the wildcards, so we can go ahead with this.

The search is pretty rudimentary at the moment, some sort of fuzzy search algorithm would be great down the road.

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 8, 2026

Deploy Preview for actualbudget-storybook ready!

Name Link
🔨 Latest commit d6717fe
🔍 Latest deploy log https://app.netlify.com/projects/actualbudget-storybook/deploys/69d670f7e8aba00008a8ba35
😎 Deploy Preview https://deploy-preview-7270--actualbudget-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 8, 2026

Deploy Preview for actualbudget-website ready!

Name Link
🔨 Latest commit d6717fe
🔍 Latest deploy log https://app.netlify.com/projects/actualbudget-website/deploys/69d670f7c9d8ca000804fd3e
😎 Deploy Preview https://deploy-preview-7270.www.actualbudget.org
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Copy Markdown
Member

@matt-fidd matt-fidd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brilliant, thank you! And sorry for the delay getting this in

@matt-fidd matt-fidd added this pull request to the merge queue Apr 8, 2026
Merged via the queue into actualbudget:master with commit 092b85e Apr 8, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Transaction view - quick search with "?" finds everything

4 participants