Skip to content

fix: escape LIKE wildcards in NodeQuery.fileFilter and nameLike#446

Merged
carlos-alm merged 22 commits intomainfrom
fix/greptile-444
Mar 16, 2026
Merged

fix: escape LIKE wildcards in NodeQuery.fileFilter and nameLike#446
carlos-alm merged 22 commits intomainfrom
fix/greptile-444

Conversation

@carlos-alm
Copy link
Contributor

@carlos-alm carlos-alm commented Mar 16, 2026

Summary

Found during

Dogfooding v3.1.4 — native binary pin issue found as #454

Test plan

  • New fileFilter() and nameLike() escaping tests in query-builder.test.js verify _ is treated as literal
  • New findNodesWithFanIn parity test in repository-parity.test.js verifies both implementations reject _ as wildcard
  • All 1862 tests pass
  • Lint passes

…pyramid (3.13)

Introduce a Repository base class, InMemoryRepository (Maps-backed, no
SQLite), and SqliteRepository (delegates to existing fn(db,...) functions).
Includes fluent TestRepoBuilder fixture factory and 105 new tests
(51 unit + 54 parity) verifying behavioral equivalence between both
implementations. Existing code and tests remain unchanged.

Impact: 139 functions changed, 6 affected
- Add kind/role validation to InMemoryRepository.findNodesForTriage
  matching SqliteRepository behavior (throws ConfigError)
- Optimize findIntraFileCallEdges sort with pre-built lookup map
  instead of spreading nodes Map per comparison
- Remove dead #nameIndex field from TestRepoBuilder
- Add validation parity tests for invalid kind/role

Impact: 5 functions changed, 0 affected
Impact: 2 functions changed, 0 affected
Impact: 2 functions changed, 0 affected
Move the duplicate-name guard before repo.addNode() so the repo
is not mutated when a duplicate is detected.

Impact: 2 functions changed, 0 affected
findNodesByScope, findNodeByQualifiedName, findNodesWithFanIn,
listFunctionNodes, and findNodesForTriage all passed user-provided
strings directly into likeToRegex without escaping % and _ chars.
This caused divergence from SQLite which uses escapeLike + ESCAPE '\'.

Also adds parity tests for findNodesByScope, findNodeByQualifiedName,
findNodesWithFanIn, findFileNodes, findNodeChildren, import queries,
getCallableNodes, getFileNodesAll, getImportEdges, and hasCfgTables.

Impact: 7 functions changed, 5 affected
The in-memory noTests filter only checked .test. and .spec. patterns,
missing __test__, __tests__, and .stories. that the SQLite query-builder
excludes. Add the missing patterns to both findNodesForTriage and
iterateFunctionNodes. Extend parity test fixtures with __tests__ and
.stories. nodes so the divergence is caught automatically.

Impact: 5 functions changed, 0 affected
fileFilter() and nameLike() in query-builder.js passed user input
directly into LIKE patterns without escaping, so _ and % acted as
wildcards instead of literals. This caused a parity gap where the
SQLite findNodesWithFanIn treated _ as a wildcard but the in-memory
version (which already called escapeLike) treated it as literal.

Fix: add escapeLike() to query-builder.js (exported), use it in
fileFilter() and nameLike() with ESCAPE '\', and deduplicate the
copy in nodes.js by importing from query-builder.js.
@carlos-alm
Copy link
Contributor Author

@greptileai

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 16, 2026

Greptile Summary

This PR fixes a correctness bug where LIKE wildcards (_, %, \) in user-supplied search terms were not escaped before being interpolated into SQL LIKE patterns in NodeQuery.fileFilter() and nameLike(), causing characters like _ to act as single-character wildcards instead of literals. The fix centralises a canonical escapeLike helper in query-builder.js, updates both builder methods to use it with an ESCAPE '\\' clause, and eliminates duplicate private copies of the same function that previously existed independently in nodes.js and in-memory-repository.js.

  • escapeLike centralised — exported from query-builder.js; private copies removed from nodes.js and in-memory-repository.js, both of which now import the canonical version
  • fileFilter() and nameLike() now call escapeLike(value) before wrapping with %, and append ESCAPE '\\' to the SQL fragment so SQLite honours the backslash escape sequences
  • Parity gap closed — the in-memory findNodesWithFanIn already escaped opts.file via its private copy; the SQLite path now does the same via the updated fileFilter(), making both implementations consistent
  • Tests addedquery-builder.test.js verifies _ is treated as a literal by both fileFilter() and nameLike(); repository-parity.test.js confirms the fix holds across both the SQLite and in-memory repositories
  • Native binary pins corrected — all six optionalDependencies bumped from 3.1.3 to 3.1.4 in package.json and package-lock.json, resolving bug: native binary optionalDependencies pinned to 3.1.3 after v3.1.4 release #454 found during dogfooding

Confidence Score: 5/5

  • Safe to merge — the fix is correct, well-tested, and introduces no regressions.
  • The escapeLike implementation correctly escapes %, _, and \; the ESCAPE '\\' clause in JS produces the single-backslash SQL string ESCAPE '\' that SQLite expects; likeToRegex in the in-memory path correctly interprets the same backslash-escape sequences; and the two new test suites verify both the SQL builder and the cross-implementation parity. The package-version bump is mechanical and matches the lock file. No unrelated files are touched and all 1862 tests pass per the PR description.
  • No files require special attention.

Important Files Changed

Filename Overview
src/db/query-builder.js Canonical escapeLike export added; fileFilter() and nameLike() now escape wildcards and include ESCAPE '\\' clause — implementation is correct and consistent.
src/db/repository/nodes.js Private escapeLike removed; now imports canonical version from query-builder.js. findNodesWithFanIn delegates opts.file to fileFilter() which now escapes correctly.
src/db/repository/in-memory-repository.js Private escapeLike removed; imports from query-builder.js. findNodesWithFanIn correctly uses escapeLike(opts.file) before likeToRegex, achieving parity with the SQLite implementation.
tests/unit/query-builder.test.js New tests verify fileFilter() and nameLike() treat _ as literal; existing chain test updated to assert the ESCAPE '\\' clause is present in built SQL.
tests/unit/repository-parity.test.js New parity test confirms both SQLite and in-memory findNodesWithFanIn treat _ as a literal character in opts.file, closing the previously-reported parity gap.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["User input\n(e.g. '_oo.js')"] --> B["escapeLike()\nquery-builder.js\nexport"]

    B --> C["'_oo.js' → '\\_oo.js'"]

    C --> D1["NodeQuery.fileFilter()\n'%\\_oo.js%'"]
    C --> D2["NodeQuery.nameLike()\n'%\\_oo.js%'"]
    C --> D3["InMemoryRepository\nfindNodesWithFanIn()\n likeToRegex('%\\_oo.js%')"]

    D1 --> E1["SQL: n.file LIKE ? ESCAPE '\\'"]
    D2 --> E2["SQL: n.name LIKE ? ESCAPE '\\'"]
    D3 --> E3["RegExp: /^.*_oo\\.js.*$/i\n(via likeToRegex)"]

    E1 --> F["SQLite: '_' treated as literal ✓"]
    E2 --> F
    E3 --> G["In-memory: '_' treated as literal ✓"]

    style B fill:#4caf50,color:#fff
    style F fill:#2196f3,color:#fff
    style G fill:#2196f3,color:#fff
Loading

Last reviewed commit: 2cf64b5

Comment on lines +6 to +13
* Escape LIKE special characters so they are treated as literals.
* Mirrors the `escapeLike` function in `nodes.js`.
* @param {string} s
* @returns {string}
*/
function escapeLike(s) {
return s.replace(/[%_\\]/g, '\\$&');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Stale JSDoc and duplicate escapeLike

The JSDoc says "Mirrors the escapeLike function in nodes.js", but nodes.js no longer defines its own escapeLike — this PR moved the canonical copy to query-builder.js. The comment is now stale.

More broadly, the PR description states it "Deduplicates escapeLike" by having nodes.js import from query-builder.js, yet this file defines a third private copy. Consider importing the canonical export instead to complete the deduplication:

Suggested change
* Escape LIKE special characters so they are treated as literals.
* Mirrors the `escapeLike` function in `nodes.js`.
* @param {string} s
* @returns {string}
*/
function escapeLike(s) {
return s.replace(/[%_\\]/g, '\\$&');
}
import { escapeLike } from '../query-builder.js';

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed — removed the private escapeLike copy and now importing it from query-builder.js. All 183 related tests pass.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Already addressed in the current state of the PR: in-memory-repository.js imports escapeLike from the canonical ../query-builder.js (line 3) — there is no duplicate definition and no stale JSDoc referencing nodes.js. The deduplication is complete.

@carlos-alm
Copy link
Contributor Author

@greptileai

@carlos-alm
Copy link
Contributor Author

@greptileai

@carlos-alm carlos-alm merged commit 2fa6e1f into main Mar 16, 2026
13 checks passed
@carlos-alm carlos-alm deleted the fix/greptile-444 branch March 16, 2026 07:40
@github-actions github-actions bot locked and limited conversation to collaborators Mar 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: native binary optionalDependencies pinned to 3.1.3 after v3.1.4 release

1 participant