Documentation
¶
Overview ¶
Package triage classifies SQLancer soak-log output (and the wadjet server logs captured alongside it) the way wadjet#289's harness actually needs, which is not the way its README long documented.
The README's triage grep was `"counts mismatch" / "mismatch:"`. That matches NoREC's own assertion text, but NOT the TLP family's: ComparatorHelper.assumeResultSetsAreEqual (shared by TLP-WHERE, TLP-HAVING, and QUERY_PARTITIONING's composite) throws "The size of the result sets mismatch (%d and %d)!" or "The content of the result sets mismatch!" — no colon after "mismatch", and not "counts". TLP-Aggregate throws its own "the results mismatch!". A soak triaged with the old grep could not have reported a genuine TLP violation even if one occurred — see wadjet#289's 2026-08-25 methodology finding.
PQS is harder still: PivotedQuerySynthesisBase.reportMissingPivotRow throws `new AssertionError(query)`, where query is a Query whose toString() is just the raw SQL string — byte-for-byte the same shape as the ordinary "wadjet rejected this SQL with a message Postgres's ExpectedErrors list doesn't recognize" AssertionError that SQLQueryAdapter.checkException throws everywhere else in the harness. The only way to tell them apart is the stack trace: a genuine PQS violation's frames include sqlancer.common.oracle.PivotedQuerySynthesisBase.reportMissingPivotRow.
CERT (CERTOracle.check, reachable here because WadjetProvider extends PostgresProvider and doesn't remove it from PostgresOracleFactory) has its own distinct message, "Inconsistent result for query: ...". The README advises against running CERT at all — it compares EXPLAIN plan text wadjet's EXPLAIN doesn't produce in the same shape, so a CERT finding is expected to lean heavily toward false positives — but if one is generated (or an old soak ran it despite the advisory), it must not be filed away as ordinary noise either.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Category ¶
type Category int
Category is the bucket one classified block of log output falls into.
const ( // CategoryOther is ordinary log content — nothing a triage pass needs // to look at twice. CategoryOther Category = iota // CategoryTLPResultSet is a genuine TLP-WHERE or TLP-HAVING violation: // ComparatorHelper.assumeResultSetsAreEqual threw. The two share this // category because they share the identical assertion text; a // Finding's OracleCheck field (when populated) disambiguates which one // actually threw for a given occurrence. CategoryTLPResultSet // CategoryTLPAggregate is a genuine TLP-Aggregate violation (the // "aggregateCheck" arm QUERY_PARTITIONING composes in). CategoryTLPAggregate // CategoryNoREC is a genuine NoREC violation (NoRECOracle.check). CategoryNoREC // CategoryPQS is a genuine PQS violation // (PivotedQuerySynthesisBase.reportMissingPivotRow). CategoryPQS // CategoryCERT is a genuine CERT violation (CERTOracle.check). See the // package doc for why this needs its own arm rather than falling into // CategoryUnexpectedError, and why it still needs corroborating before // it's treated as a real defect. CategoryCERT // CategoryCrashEcho is a wadjet process crash: either observed // directly (a Go "panic:"/"fatal error:" line from a wadjet server // log) or its symptom on the SQLancer/JDBC side (connection // refused/reset, an I/O error sending to the backend) — plus the // harness's own JVM heap exhaustion, which reads the same way at // triage time (the run is dead, not a wadjet answer). A raw line // count here is NOT a crash-event count: one dead wadjet process // produces a flood of near-identical connection-refused lines from // every query attempted until the session's timeout, so this // category exists to keep that flood out of CategoryUnexpectedError, // not to size it. The soak supervisor's own restart count (its // crashes.log / summary.txt) is the authoritative crash-event count. CategoryCrashEcho // CategoryUnexpectedError is an AssertionError SQLancer raised because // wadjet's response to a generated statement didn't match anything in // the Postgres dialect's ExpectedErrors list — a SQL-surface gap or a // by-design loud rejection, not an oracle-detected wrong answer. CategoryUnexpectedError )
func (Category) IsGenuineViolation ¶
IsGenuineViolation reports whether c is one of the oracle-detected wrong-answer categories, as opposed to noise or a crash echo.
type Finding ¶
type Finding struct {
Category Category
Source string // file path this was found in
Line int // 1-based line number of the header line
Header string // the AssertionError/panic header line itself
Detail string // header plus any message-continuation lines, before the stack trace
Queries []string
// OracleCheck names the specific oracle method whose stack frame
// produced this finding (e.g. "TLP-WHERE", "TLP-HAVING",
// "TLP-AGGREGATE", "NoREC", "PQS", "CERT"), when one of the frames
// scanned matched a known oracle class. It exists mainly to
// disambiguate CategoryTLPResultSet, which TLP-WHERE and TLP-HAVING
// both report under identically — see wadjet#289: a dedicated WHERE
// soak reporting 0 violations does not mean TLP-WHERE is clean if its
// oracle checks never got to run (e.g. starved by a crash-restart
// loop); this field is what lets a re-triage tell "0 because clean"
// from "0 because it barely ran" apart from QUERY_PARTITIONING's own
// mixed results. Empty when no known frame was found.
OracleCheck string
}
Finding is one classified block of log output worth keeping around — every genuine violation, plus the Go-side panic/fatal-error lines from a crash echo (the connection-refused/reset flood a dead server produces on the SQLancer side is tallied in Counts but not retained; there is nothing more to learn from the 4000th "Connection refused" than the 1st).
type Report ¶
Report accumulates classification results across one or more sources.
func NewReport ¶
func NewReport() *Report
NewReport returns an empty Report ready for Classify/ClassifyFile calls. It is not the only valid way to obtain a usable *Report, though: a bare &Report{} works too — see (*Report).incr.
func (*Report) Classify ¶
Classify reads every line from rd and classifies it into r, attributing findings to source (typically a file path, or "" for an ad hoc snippet).
If rd's underlying reader errors partway through (including a line exceeding maxLineBytes), every line successfully read up to that point is still classified — a soak log with one pathological line should not lose every genuine violation that came before it — and the error is returned so the caller knows this source's counts may be incomplete.
func (*Report) ClassifyFile ¶
ClassifyFile opens path (transparently gunzipping a ".gz" suffix) and classifies its lines into r. On error, whatever lines were successfully read before the error are still classified into r (see Classify) — the returned error signals that this file's counts are a floor, not a complete answer, not that nothing was learned from it.
func (*Report) Print ¶
Print writes a human-readable summary of r to w: counts per category, followed by every retained finding (every genuine violation, plus any captured panic/fatal-error lines) with its source location and either its best-effort minimized query (when one was extracted) or its raw captured detail (a crash-echo panic/fatal-error line never has a "query" to extract, so it renders its Header/Detail text instead — omitting that would leave every crash Finding printed with nothing under its header line at all).