@@ -130,53 +130,80 @@ function columnNames(table: KustoTable): string[] {
130130 return ( table . Columns ?? [ ] ) . map ( ( column ) => column . ColumnName ?? '' )
131131}
132132
133+ interface TableOfContents {
134+ primaryOrdinal : number | null
135+ statusOrdinal : number | null
136+ }
137+
133138/**
134- * Picks the table holding the query's own results.
135- *
136- * A v1 query response carries several tables plus a trailing table of contents
137- * that maps each ordinal to a kind; the first `QueryResult` ordinal is the
138- * primary result. Management commands return a single table with no table of
139- * contents, so the first table is the answer.
139+ * Reads the trailing table of contents, which maps each ordinal in the response
140+ * to a kind. It is the only thing that identifies which table holds the query's
141+ * results and which holds its status — a management command has no table of
142+ * contents, and returns `null` here.
140143 */
141- function selectPrimaryTable ( tables : KustoTable [ ] ) : KustoTable | null {
144+ function readTableOfContents ( tables : KustoTable [ ] ) : TableOfContents | null {
142145 if ( tables . length === 0 ) return null
143146
144147 const contents = tables [ tables . length - 1 ]
145148 const names = columnNames ( contents )
146149 const ordinalIndex = names . indexOf ( 'Ordinal' )
147150 const kindIndex = names . indexOf ( 'Kind' )
148-
149- if ( ordinalIndex >= 0 && kindIndex >= 0 ) {
150- for ( const row of contents . Rows ?? [ ] ) {
151- if ( row [ kindIndex ] !== 'QueryResult' ) continue
152- const ordinal = Number ( row [ ordinalIndex ] )
153- if ( Number . isInteger ( ordinal ) && tables [ ordinal ] ) return tables [ ordinal ]
154- }
151+ if ( ordinalIndex < 0 || kindIndex < 0 ) return null
152+
153+ let primaryOrdinal : number | null = null
154+ let statusOrdinal : number | null = null
155+ for ( const row of contents . Rows ?? [ ] ) {
156+ const ordinal = Number ( row [ ordinalIndex ] )
157+ if ( ! Number . isInteger ( ordinal ) || ! tables [ ordinal ] ) continue
158+ if ( row [ kindIndex ] === 'QueryResult' && primaryOrdinal === null ) primaryOrdinal = ordinal
159+ if ( row [ kindIndex ] === 'QueryStatus' && statusOrdinal === null ) statusOrdinal = ordinal
155160 }
161+ return { primaryOrdinal, statusOrdinal }
162+ }
156163
164+ /**
165+ * Picks the table holding the query's own results — the first `QueryResult`
166+ * ordinal the table of contents names. A management command returns a single
167+ * table with no table of contents, so the first table is the answer.
168+ */
169+ function selectPrimaryTable (
170+ tables : KustoTable [ ] ,
171+ contents : TableOfContents | null
172+ ) : KustoTable | null {
173+ if ( tables . length === 0 ) return null
174+ if ( contents ?. primaryOrdinal !== null && contents !== null ) {
175+ return tables [ contents . primaryOrdinal ] ?? tables [ 0 ]
176+ }
157177 return tables [ 0 ]
158178}
159179
160180/**
161181 * Finds a partial query failure. Kusto answers 200 as soon as it starts
162- * processing, then reports later failures through a QueryStatus table where a
182+ * processing, then reports later failures through the QueryStatus table, where a
163183 * severity of 2 or lower means the request did not succeed.
184+ *
185+ * Only the table the table of contents names as `QueryStatus` is inspected.
186+ * Scanning every table for `Severity`/`StatusDescription` columns would
187+ * misread an ordinary log query that happens to select columns of those names
188+ * as a failed request.
164189 */
165- function findQueryFailure ( tables : KustoTable [ ] ) : string | null {
166- for ( const table of tables ) {
167- const names = columnNames ( table )
168- const severityIndex = names . indexOf ( 'Severity' )
169- const descriptionIndex = names . indexOf ( 'StatusDescription' )
170- if ( severityIndex < 0 || descriptionIndex < 0 ) continue
171-
172- for ( const row of table . Rows ?? [ ] ) {
173- const severity = Number ( row [ severityIndex ] )
174- if ( ! Number . isFinite ( severity ) || severity > 2 ) continue
175- const description = row [ descriptionIndex ]
176- return typeof description === 'string' && description . length > 0
177- ? description
178- : 'Kusto reported a query failure'
179- }
190+ function findQueryFailure ( tables : KustoTable [ ] , contents : TableOfContents | null ) : string | null {
191+ if ( contents ?. statusOrdinal == null ) return null
192+ const table = tables [ contents . statusOrdinal ]
193+ if ( ! table ) return null
194+
195+ const names = columnNames ( table )
196+ const severityIndex = names . indexOf ( 'Severity' )
197+ const descriptionIndex = names . indexOf ( 'StatusDescription' )
198+ if ( severityIndex < 0 || descriptionIndex < 0 ) return null
199+
200+ for ( const row of table . Rows ?? [ ] ) {
201+ const severity = Number ( row [ severityIndex ] )
202+ if ( ! Number . isFinite ( severity ) || severity > 2 ) continue
203+ const description = row [ descriptionIndex ]
204+ return typeof description === 'string' && description . length > 0
205+ ? description
206+ : 'Kusto reported a query failure'
180207 }
181208 return null
182209}
@@ -339,7 +366,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
339366 ? ( ( body as { Tables : KustoTable [ ] } ) . Tables ?? [ ] )
340367 : [ ]
341368
342- const failure = findQueryFailure ( tables )
369+ const contents = readTableOfContents ( tables )
370+
371+ const failure = findQueryFailure ( tables , contents )
343372 if ( failure ) {
344373 logger . warn ( `[${ requestId } ] Azure Data Explorer partial query failure: ${ failure } ` )
345374 return NextResponse . json (
@@ -350,7 +379,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
350379
351380 return NextResponse . json ( {
352381 success : true ,
353- output : projectTable ( selectPrimaryTable ( tables ) ) ,
382+ output : projectTable ( selectPrimaryTable ( tables , contents ) ) ,
354383 } )
355384 } catch ( error ) {
356385 if ( isPayloadSizeLimitError ( error ) ) {
0 commit comments