Trace Parser Agent: SQL text silently truncated to 4,000 characters during import, hiding query differences in Web Version
Description
When comparing fast and slow traces containing long SQL statements, the agent receives incomplete SQL text and cannot inspect trailing clauses such as JOIN, WHERE, ORDER BY, and query hints.
The observed statements ended at the same point in their shared SELECT list. Their important difference occurred later in the statement:
versus:
ORDER BY T1.CREATEDDATETIME DESC
Full statements were recovered independently from the original ETL files. This demonstrates that the missing text was available in the source traces; it was not necessarily missing from the capture.
An earlier hypothesis attributed the truncation to an ETW capture limitation. Source inspection instead identified explicit truncation in the importer, as detailed below.
Actual behavior
- Long SQL statements lose everything after the first 4,000 characters during import.
- Statements with identical prefixes but different suffixes can appear identical to the agent.
- The agent reports that the SQL is truncated, but may still infer conclusions from other statements rather than retrieving the missing evidence.
- Reimporting after fixing truncation may leave existing truncated records unchanged.
Confirmed code findings
Source inspected September 25, 2026:
TraceParserWeb/TraceParserFunction/SqlImporter.cs
1. The bulk import path explicitly limits statements to 4,000 characters
BulkInsertDimensions() calls:
BulkUpsertHashDim(
conn,
dims.GetAllQueryStatements(),
"QueryStatements",
"QueryStatementHash",
"Statement",
4000,
false);
BulkUpsertHashDim() then truncates each value:
var truncated = text.Length > maxLen ? text[..maxLen] : text;
dt.Rows.Add(hash, truncated);
It also creates a matching fixed-length staging column:
cmd.CommandText =
$"CREATE TABLE #HashStage (HashVal BIGINT PRIMARY KEY, TextVal NVARCHAR({maxLen}))";
For query statements, this becomes NVARCHAR(4000).
2. The single-statement helper also truncates SQL
EnsureQueryStatement() contains:
cmd.Parameters.AddWithValue(
"@s",
stmt.Length > 4000 ? stmt[..4000] : stmt);
Both paths need correction.
3. Existing records are not repaired on reimport
The bulk path uses INSERT ... WHERE NOT EXISTS, while the single-statement helper uses IF NOT EXISTS.
Because hashes are computed before truncation, reimporting the full statement can find the existing hash and skip insertion, leaving the truncated text in place.
4. Original SQL casing is also lost
InMemoryDimensions.EnsureQueryStatement() and the single-statement helper uppercase the entire statement before hashing/storage:
stmt = stmt.ToUpperInvariant();
This also changes string literals and potentially case-sensitive identifiers. Original SQL should be preserved separately from any normalized comparison representation.
Steps to reproduce
- Import two SQL statements longer than 4,000 characters, sharing the same first 4,000 characters but differing in their final
ORDER BY clause.
- Confirm that the original input contains both complete statements.
- Inspect the corresponding
QueryStatements.Statement values and their lengths.
- Retrieve them through the MCP tools and ask the agent to compare them.
- Observe that the distinguishing suffixes are missing.
Use synthetic statements or sanitized traces for a publicly shareable reproduction.
Proposed fix
- Preserve full SQL throughout ingestion. Remove statement slicing in both import paths. Use
NVARCHAR(MAX) for query-statement staging and ensure the destination column supports full text. Use an explicit SqlDbType.NVarChar parameter with size -1 for the single-statement path. Review schema dependencies before migration.
- Preserve original text. Keep normalization separate from the raw statement. Plan hash compatibility and migration carefully so existing trace references remain valid.
- Provide a repair/backfill path. Recover existing truncated values from original traces and update the matching records with validated complete text. Increasing column size alone cannot restore lost suffixes.
- Make completeness explicit. Expose captured length, stored length, and known truncation status. Length equal to 4,000 should indicate suspected legacy truncation, not prove truncation.
- Support efficient full-text retrieval. Return compact previews for discovery, with a dedicated full-statement or chunked retrieval tool. Include length and integrity metadata so clients can detect incomplete responses.
- Prevent unsupported agent conclusions. If either statement is incomplete, the agent should state that comparison is inconclusive and retrieve the full text before claiming equivalence or identifying clause differences.
Acceptance criteria
Trace Parser Agent: SQL text silently truncated to 4,000 characters during import, hiding query differences in Web Version
Description
When comparing fast and slow traces containing long SQL statements, the agent receives incomplete SQL text and cannot inspect trailing clauses such as
JOIN,WHERE,ORDER BY, and query hints.The observed statements ended at the same point in their shared SELECT list. Their important difference occurred later in the statement:
versus:
Full statements were recovered independently from the original ETL files. This demonstrates that the missing text was available in the source traces; it was not necessarily missing from the capture.
An earlier hypothesis attributed the truncation to an ETW capture limitation. Source inspection instead identified explicit truncation in the importer, as detailed below.
Actual behavior
Confirmed code findings
Source inspected September 25, 2026:
TraceParserWeb/TraceParserFunction/SqlImporter.cs1. The bulk import path explicitly limits statements to 4,000 characters
BulkInsertDimensions()calls:BulkUpsertHashDim()then truncates each value:It also creates a matching fixed-length staging column:
For query statements, this becomes
NVARCHAR(4000).2. The single-statement helper also truncates SQL
EnsureQueryStatement()contains:Both paths need correction.
3. Existing records are not repaired on reimport
The bulk path uses
INSERT ... WHERE NOT EXISTS, while the single-statement helper usesIF NOT EXISTS.Because hashes are computed before truncation, reimporting the full statement can find the existing hash and skip insertion, leaving the truncated text in place.
4. Original SQL casing is also lost
InMemoryDimensions.EnsureQueryStatement()and the single-statement helper uppercase the entire statement before hashing/storage:This also changes string literals and potentially case-sensitive identifiers. Original SQL should be preserved separately from any normalized comparison representation.
Steps to reproduce
ORDER BYclause.QueryStatements.Statementvalues and their lengths.Use synthetic statements or sanitized traces for a publicly shareable reproduction.
Proposed fix
NVARCHAR(MAX)for query-statement staging and ensure the destination column supports full text. Use an explicitSqlDbType.NVarCharparameter with size-1for the single-statement path. Review schema dependencies before migration.Acceptance criteria
ORDER BYdifference located beyond character 4,000.