Repository navigation
ADFA-6597: Find a tooltip through Tooltips' (categoryId, tag) index - #2160
davidschachterADFA wants to merge 2 commits into
Conversation
Written as a join of Tooltips and TooltipCategories, the tooltip query made SQLite scan all of Tooltips - 46,128 rows, 13 MB - for every tooltip shown, because documentation.db carries no ANALYZE statistics (EXPLAIN QUERY PLAN: "SCAN T"). Looking the category's id up first lets the UNIQUE (categoryId, tag) index find the row: 6.2 ms -> 0.004 ms per lookup on a desktop against the real database, same rows. IN rather than =, so two categories sharing a name still come back as duplicates, which getTooltip reports as before. The buttons query still scans TooltipButtons (5.4 MB); that needs an index in the database, which this change cannot add. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018CMZQuXT3GT7nEct2w4tg3
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
…nds them From review of PR #2160: - TooltipQueryTest bound the query's arguments itself, so it would pass whatever order getTooltip used, and this PR reversed that order. The SQL and its binding now live in queryTooltip, in TooltipQuery.kt, which getTooltip calls with named arguments and the test calls too. Swapping the binding fails two of the three tests. - QUERY_TOOLTIP is marked @VisibleForTesting: only the plan test reads it. It is still a public static field in bytecode, as it was inside TooltipManager, and so still in the plugin-api jar. - Drops @ConscryptMode(OFF). The Conscrypt leak it guarded against broke a TLS test in common; idetooltips has none. A file of its own rather than top-level code in ToolTipManager.kt: ktlint leaves ToolTipManager.kt unformatted while its only top-level declaration, object TooltipManager, does not match the file name. A second declaration there makes Spotless reformat all of the file's ~380 changed lines, burying this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018CMZQuXT3GT7nEct2w4tg3
davidschachterADFA
left a comment
There was a problem hiding this comment.
Independent review
An independent Claude Code review covered this PR. It has no critical, high or medium findings.
| ID | Sev | Finding | Status |
|---|---|---|---|
| R2160-01 | low | Nothing tested the argument order at the call site: swapping it back to (tag, category) passed all 7 tests, yet no tooltip would ever be found |
Fixed in e5f44a8: getTooltip and the test both call queryTooltip, and swapping its arguments fails 2 tests |
| R2160-02 | nit | internal const compiles to a public static final field, which the plugin-api fat jar exposes to Java plugins |
Partly fixed in e5f44a8: marked @VisibleForTesting. Still public in bytecode, because the plan test needs the SQL |
| R2160-03 | nit | @ConscryptMode(OFF) was unexplained, and this module doesn't need it |
Fixed in e5f44a8: removed |
Checked and found correct
- Same rows: the old and new queries agree on all 46,128
(category, tag)pairs in the real database, and on 604 edge variants. - Older SQLite: the plan test holds on SQLite 3.8.7 and 3.32.2.
- Cause: the database has no
ANALYZEstatistics.
Outside this PR
- Locally, Robolectric runs these tests on the nix JDK's SQLite 3.51.2, not Android's. The 3.8.7 and 3.32.2 checks cover older versions.
- The CI job that runs unit tests ignores their failures, a project-wide setting.
- ktlint leaves
ToolTipManager.ktunformatted while its only top-level declaration,object TooltipManager, doesn't match the file name, andbuild.gradle.ktssuppresses thestandard:filenamelint. Renaming the object to match, or adding a second top-level declaration, makes Spotless reformat about 380 lines. That is why the helper is in a file of its own. Other files with the same mismatch were not checked.
🤖 Generated with Claude Code
Independent review of the fix commitsAn independent Claude Code review covered e5f44a8, which answers the earlier independent review (R2160-01 to R2160-03). It has 1 nit. The fix does what it claims: the binding order is now pinned by tests, and every mutation check in the PR body reproduces. The nit is about how the PR body describes the bytecode visibility. All are open.
R2160-04.
Earlier findings
Checked and found correct
Not verified:
🤖 Generated with Claude Code |
Response to the review of the fix commits
The call to 🤖 Generated with Claude Code |
ADFA-6597
Showing a tooltip no longer scans the whole Tooltips table.
Cause
QUERY_TOOLTIPjoined Tooltips and TooltipCategories.documentation.dbhas noANALYZEstatistics, so SQLite's planner chose to scan all of Tooltips (46,128 rows, 13 MB) and look up each row's category.EXPLAIN QUERY PLANshowedSCAN T.Change
The query looks the category's id up first:
SEARCH T USING INDEX sqlite_autoindex_Tooltips_1 (categoryId=? AND tag=?), using the table's existingUNIQUE (categoryId, tag)index.IN, not=: if two categories ever shared a name, both rows would still come back, sogetTooltipkeeps reporting the duplicate as before.(category, tag), to match the new order. The query and its binding live inqueryTooltip, in a newTooltipQuery.kt;getTooltipcalls it with named arguments, and the test calls it too, so the test covers the order the app binds.ToolTipManager.ktunformatted while its only top-level declaration,object TooltipManager, doesn't match the file name. Top-level code added there makes Spotless reformat the whole file, about 380 lines.QUERY_TOOLTIPandqueryTooltipareinternal, andQUERY_TOOLTIPis@VisibleForTesting, since only the plan test reads it. In bytecode both are public static members of a newTooltipQueryKtclass (on stage the field was private inTooltipManager), so this PR adds that class to the plugin-api fat jar, which takes every:idetooltipsclass.Not in this PR: the buttons query still scans TooltipButtons (54,799 rows, 5.4 MB), because that table has no index on
tooltipId. The index has to be built into the database, which appdevforall/OfflineDocumentationTools#39 does. Measured: 2.4 ms -> 0.004 ms, for 696,320 bytes installed and 220,515 bytes of.br. Each PR helps on its own.Testing
TooltipQueryTest(Robolectric, documentation.db's own Tooltips schema), three cases: the right row for a category and tag; two same-named categories still return both rows; the query plan uses the(categoryId, tag)index and doesn't scan Tooltips.[SCAN T, SEARCH TC USING INTEGER PRIMARY KEY (rowid=?)]),=instead ofINfails the duplicate test, and swapping the arguments inqueryTooltipfails both row tests (0 rows found).:idetooltips:testV8DebugUnitTestpasses: 7 tests.idetooltipsgainstestImplementation(libs.tests.robolectric), the catalog entry other modules use.docs/documentation-database.mdis updated.🤖 Generated with Claude Code
https://claude.ai/code/session_018CMZQuXT3GT7nEct2w4tg3