Skip to content

fix: match the endpoint method filter case-insensitively - #345

Open
FrameAutomata wants to merge 1 commit into
mainfrom
fix/321-method-filter-case-insensitive
Open

fix: match the endpoint method filter case-insensitively#345
FrameAutomata wants to merge 1 commit into
mainfrom
fix/321-method-filter-case-insensitive

Conversation

@FrameAutomata

Copy link
Copy Markdown
Collaborator

Closes #321. Implements option 2 from the issue discussion — "I'd honestly do case insensitive querying".

The problem

getHTTPEndpoint (trace_converter.go) concatenates http.request.method verbatim with no ToUpper, so get /api/lowercase rows genuinely exist. The dropdown only ever offers the 7 canonical uppercase methods, and normalizeMethodFilter upper-cases whatever it is given — so no dropdown selection could ever reach such a row, on any backend. They appeared in the unfiltered list, counted toward the total, and vanished the moment a method was picked.

The fix

Upper-case the column, not the caller's argument, so the result can't depend on how the request cased its input:

backend before after
SQLite / DuckDB SUBSTR(endpoint, 1, N) = :method UPPER(SUBSTR(endpoint, 1, N)) = :method
ClickHouse startsWith(endpoint, ?) startsWith(upper(endpoint), ?)

Two deliberate choices:

  • SUBSTR stays, LIKE is still avoided. methodFilter reaches SQL as a value, and LIKE would read a % or _ in it as a wildcard. Keeping SUBSTR keeps that property while adding the case fold.
  • upper(), not upperUTF8(), on ClickHouse. SQLite's UPPER is ASCII-only, and ASCII is the entire range RFC 9110 allows in a method token — so all three backends answer identically rather than diverging on non-ASCII input.

This reverses a deliberate decision, not a bug

c7a5dcae shipped TestEndpointRepository_FindGroupedByEndpoint_MethodFilter asserting get /api/lowercase is excluded, as part of aligning the embedded backends onto ClickHouse's long-standing case sensitivity. That alignment was right; the reachability hole it left is what #321 is about. Worth flagging explicitly so the changed assertion doesn't read as an accident.

What stays asserted: GETAWAY /api/cars must still not match GET. The comparison is a whole space-terminated token, not a string prefix.

A second test covers the argument side — GET, get and GeT all return the same three rows.

Verification

Unit tests pass on both embedded backends (default build and -tags telemetry_duckdb); the ClickHouse variant builds and vets under -tags 'transactional_pg telemetry_ch' (no CH server here to run it against).

Then against a running backend on a scratch SQLite DB, with GET /api/users, get /api/lowercase, Get /api/mixed, GETAWAY /api/cars and POST /api/users seeded:

POST /api/endpoints/grouped
  methodFilter="GET"   -> 3  ["GET /api/users","Get /api/mixed","get /api/lowercase"]
  methodFilter="get"   -> 3  ["GET /api/users","Get /api/mixed","get /api/lowercase"]
  methodFilter="GeT"   -> 3  ["GET /api/users","Get /api/mixed","get /api/lowercase"]
  methodFilter="POST"  -> 1  ["POST /api/users"]
  methodFilter=""      -> 5  (all, GETAWAY included)

POST /api/endpoints/chart
  methodFilter="GET"   -> ["Get /api/mixed","get /api/lowercase","GET /api/users"]
  methodFilter="get"   -> ["Get /api/mixed","get /api/lowercase","GET /api/users"]

Left alone on purpose

Option 3 from the issue — ToUpper at ingest in getHTTPEndpoint — is not done here. It would not reach rows already stored (which is the actual complaint), and it merges two endpoint groups that are currently distinct, which is a data-shape change rather than a filter fix. Still open if you want canonical storage going forward; it composes with this rather than replacing it.

Also unchanged: the list still groups get /x and GET /x as two rows. Only the filter folds case; the grouping is what ingest decided.

🤖 Generated with Claude Code

getHTTPEndpoint concatenates http.request.method verbatim, so rows like
"get /api/lowercase" genuinely exist. The dropdown only ever offers the
7 canonical uppercase methods and normalizeMethodFilter upper-cases what
it is given, so no selection could reach those rows on any backend:
they were in the list, counted in the unfiltered total, and invisible the
moment a method was picked.

Upper-cases the column rather than trusting the value's case, so the
result cannot depend on how the caller cased its argument:

  embedded   SUBSTR(endpoint, 1, N) = :method
          -> UPPER(SUBSTR(endpoint, 1, N)) = :method
  ClickHouse startsWith(endpoint, ?)
          -> startsWith(upper(endpoint), ?)

SUBSTR stays rather than LIKE: methodFilter reaches SQL as a value, and
LIKE would read % and _ in it as wildcards. upper() rather than
upperUTF8() on ClickHouse because SQLite's UPPER is ASCII-only, and ASCII
is the whole of the method-token range RFC 9110 allows -- so all three
backends answer identically.

This replaces the exclusion TestEndpointRepository_FindGroupedByEndpoint_
MethodFilter asserted. That assertion was deliberate when written
(c7a5dca aligned the embedded backends onto ClickHouse's long-standing
case sensitivity), so it is worth being explicit that this is a decision
reversed, not a bug: the reachability problem it left is #321. "GETAWAY
/api/cars" still must not match -- the comparison is a whole
space-terminated token, not a string prefix -- and that stays asserted.

Verified against a running backend with GET/get/Get/GETAWAY/POST rows
seeded: /api/endpoints/grouped and /api/endpoints/chart both return the
same three GET-family rows for "GET", "get" and "GeT", POST returns one,
and GETAWAY is excluded from all of them.

Closes #321.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lowercase-stored HTTP methods are unreachable from the endpoint method filter

1 participant