fix: the Open/Resolved and Chains filters apply an exact rule's slash, case and query options (WR-22)
coveredSQL and chainSQL compared the source literally, so /missing/ or /missing?utm=x stayed open after /missing answered it, and /a -> /b/ was no chain next to /b. Both now use one SQL form of the runtime's exact match (prefiltered on the indexed source_key). Pattern and regex rules remain with the per-row badge, which the list_not_found schema text now says.
This commit is contained in:
@@ -41,9 +41,11 @@ const (
|
||||
// FilterScopes lists the 404 log's filter scopes.
|
||||
func (NotFound) FilterScopes() []string { return []string{FilterNotFoundStatus} }
|
||||
|
||||
// coveredSQL is true for an entry an exact redirect or 410 answers now
|
||||
// (the list filter; the Resolved badge also counts patterns and regex).
|
||||
const coveredSQL = `EXISTS (SELECT 1 FROM golem15_seo_redirects AS r WHERE r.match = 'exact' AND (r.source = golem15_seo_notfound.url OR (r.ignore_case AND lower(r.source) = lower(golem15_seo_notfound.url))))`
|
||||
// coveredSQL is true for an entry an exact redirect or 410 answers now,
|
||||
// with the rule's case, trailing-slash and query options as the runtime
|
||||
// matcher applies them (WR-22). Pattern and regex rules are left to the
|
||||
// Resolved badge, which runs the compiled rule set.
|
||||
var coveredSQL = `EXISTS (SELECT 1 FROM golem15_seo_redirects AS r WHERE ` + coversSQL("r", "golem15_seo_notfound.url") + `)`
|
||||
|
||||
// FilterScope narrows the 404 log to open or resolved entries; an unknown
|
||||
// value matches nothing.
|
||||
|
||||
@@ -224,9 +224,42 @@ func (Redirect) FilterScopes() []string {
|
||||
return []string{FilterRedirectStatus, FilterRedirectMatch, FilterRedirectOrigin, FilterRedirectChain}
|
||||
}
|
||||
|
||||
// chainSQL is true for a redirect whose target is another redirect's
|
||||
// source.
|
||||
const chainSQL = `EXISTS (SELECT 1 FROM golem15_seo_redirects AS next WHERE next.source = golem15_seo_redirects.target AND next.id <> golem15_seo_redirects.id)`
|
||||
// coversSQL is an SQL condition, true when the exact redirect aliased
|
||||
// r matches the address addr (an SQL expression of a path with an optional
|
||||
// query) the way the runtime matcher does (classes.exactMatches): the
|
||||
// source's path with its ignore_case and trailing_slash options, and, when
|
||||
// the source has a query, the address's query exactly (WR-22). The
|
||||
// source_key test (the generated column of classes.exactKey) is a
|
||||
// necessary condition that lets Postgres use its index; the rest is exact.
|
||||
// Pattern and regex rules are not covered: their expressions are RE2 and
|
||||
// only the compiled RedirectSet evaluates them.
|
||||
func coversSQL(r, addr string) string {
|
||||
return strings.NewReplacer("R.", r+".", "ADDR", addr).Replace(exactCoversSQL)
|
||||
}
|
||||
|
||||
// exactCoversSQL is coversSQL with the placeholders R. and ADDR.
|
||||
const exactCoversSQL = `(R.match = 'exact'
|
||||
AND R.source_key IN (
|
||||
lower(CASE WHEN length(split_part(ADDR, '?', 1)) > 1 AND right(split_part(ADDR, '?', 1), 1) = '/' THEN left(split_part(ADDR, '?', 1), -1) ELSE split_part(ADDR, '?', 1) END),
|
||||
lower(CASE WHEN length(split_part(ADDR, '?', 1)) > 1 AND right(split_part(ADDR, '?', 1), 1) = '/' THEN left(split_part(ADDR, '?', 1), -1) ELSE split_part(ADDR, '?', 1) END)
|
||||
|| '?' || (CASE WHEN strpos(ADDR, '?') > 0 THEN substr(ADDR, strpos(ADDR, '?') + 1) ELSE '' END))
|
||||
AND (strpos(R.source, '?') = 0
|
||||
OR substr(R.source, strpos(R.source, '?') + 1) = (CASE WHEN strpos(ADDR, '?') > 0 THEN substr(ADDR, strpos(ADDR, '?') + 1) ELSE '' END))
|
||||
AND (split_part(R.source, '?', 1) = split_part(ADDR, '?', 1)
|
||||
OR (R.ignore_case AND lower(split_part(R.source, '?', 1)) = lower(split_part(ADDR, '?', 1)))
|
||||
OR (R.trailing_slash AND (
|
||||
(CASE WHEN length(split_part(R.source, '?', 1)) > 1 AND right(split_part(R.source, '?', 1), 1) = '/' THEN left(split_part(R.source, '?', 1), -1) ELSE split_part(R.source, '?', 1) END)
|
||||
= (CASE WHEN length(split_part(ADDR, '?', 1)) > 1 AND right(split_part(ADDR, '?', 1), 1) = '/' THEN left(split_part(ADDR, '?', 1), -1) ELSE split_part(ADDR, '?', 1) END)
|
||||
OR (R.ignore_case AND lower(CASE WHEN length(split_part(R.source, '?', 1)) > 1 AND right(split_part(R.source, '?', 1), 1) = '/' THEN left(split_part(R.source, '?', 1), -1) ELSE split_part(R.source, '?', 1) END)
|
||||
= lower(CASE WHEN length(split_part(ADDR, '?', 1)) > 1 AND right(split_part(ADDR, '?', 1), 1) = '/' THEN left(split_part(ADDR, '?', 1), -1) ELSE split_part(ADDR, '?', 1) END))))))`
|
||||
|
||||
// chainSQL is true for a redirect whose target leads into another
|
||||
// redirect: a path target that another rule's source names literally or
|
||||
// an exact rule matches with its options (WR-22). Absolute targets on the
|
||||
// site URL and pattern or regex next hops are only seen by the save-time
|
||||
// check (the chain notice).
|
||||
var chainSQL = `EXISTS (SELECT 1 FROM golem15_seo_redirects AS next WHERE next.id <> golem15_seo_redirects.id AND (next.source = golem15_seo_redirects.target OR (golem15_seo_redirects.target LIKE '/%' AND golem15_seo_redirects.target NOT LIKE '//%' AND ` +
|
||||
coversSQL("next", "golem15_seo_redirects.target") + `)))`
|
||||
|
||||
// FilterScope narrows the redirects list; an unknown value matches
|
||||
// nothing.
|
||||
|
||||
Reference in New Issue
Block a user