@@ -11,6 +11,7 @@ import { mergeQueryResults } from "./merge.js";
1111import { coalesceRanges , fetchBounded , withRetry , withTimeout } from "./coalesce.js" ;
1212import { VipCache } from "./vip-cache.js" ;
1313import { parseLanceV2Columns } from "./lance-v2.js" ;
14+ import { parseAndValidateQuery } from "./query-schema.js" ;
1415import wasmModule from "./wasm-module.js" ;
1516
1617const FRAGMENT_POOL_MAX = 20 ; // Max Fragment DO slots per datacenter (idle slots cost nothing)
@@ -246,11 +247,13 @@ export class QueryDO implements DurableObject {
246247
247248 private async handleQuery ( request : Request ) : Promise < Response > {
248249 const requestId = request . headers . get ( "x-querymode-request-id" ) ?? crypto . randomUUID ( ) ;
249- const body = await request . json ( ) as Record < string , unknown > ;
250- if ( ! body . table || typeof body . table !== "string" ) {
251- return this . json ( { error : "Missing or invalid 'table' field" } , 400 ) ;
250+ const body = await request . json ( ) ;
251+ let query : QueryDescriptor ;
252+ try {
253+ query = this . parseQuery ( body ) ;
254+ } catch ( err ) {
255+ return this . json ( { error : ( err as Error ) . message } , 400 ) ;
252256 }
253- const query = this . parseQuery ( body ) ;
254257 try {
255258 const result = await this . executeQuery ( query ) ;
256259 result . requestId = requestId ;
@@ -291,25 +294,14 @@ export class QueryDO implements DurableObject {
291294 return `qr:${ query . table } :${ ( h >>> 0 ) . toString ( 36 ) } ` ;
292295 }
293296
294- private parseQuery ( request_body : Record < string , unknown > ) : QueryDescriptor {
295- return {
296- table : request_body . table as string ,
297- filters : ( request_body . filters ?? [ ] ) as QueryDescriptor [ "filters" ] ,
298- projections : ( request_body . projections ?? request_body . select ?? [ ] ) as string [ ] ,
299- sortColumn : request_body . sortColumn as string | undefined ,
300- sortDirection : request_body . sortDirection as "asc" | "desc" | undefined ,
301- limit : request_body . limit as number | undefined ,
302- vectorSearch : request_body . vectorSearch as QueryDescriptor [ "vectorSearch" ] ,
303- aggregates : request_body . aggregates as QueryDescriptor [ "aggregates" ] ,
304- groupBy : request_body . groupBy as string [ ] | undefined ,
305- cacheTTL : request_body . cacheTTL as number | undefined ,
306- } ;
297+ private parseQuery ( body : unknown ) : QueryDescriptor {
298+ return parseAndValidateQuery ( body ) as QueryDescriptor ;
307299 }
308300
309301 private async handleCount ( request : Request ) : Promise < Response > {
310- const body = await request . json ( ) as Record < string , unknown > ;
311- if ( ! body . table || typeof body . table !== "string" ) return this . json ( { error : "Missing 'table'" } , 400 ) ;
312- const query = this . parseQuery ( body ) ;
302+ const body = await request . json ( ) ;
303+ let query : QueryDescriptor ;
304+ try { query = this . parseQuery ( body ) ; } catch ( err ) { return this . json ( { error : ( err as Error ) . message } , 400 ) ; }
313305 try {
314306 // Fast path: no filters — sum page rowCounts from cached metadata
315307 if ( query . filters . length === 0 ) {
@@ -331,9 +323,9 @@ export class QueryDO implements DurableObject {
331323 }
332324
333325 private async handleExists ( request : Request ) : Promise < Response > {
334- const body = await request . json ( ) as Record < string , unknown > ;
335- if ( ! body . table || typeof body . table !== "string" ) return this . json ( { error : "Missing 'table'" } , 400 ) ;
336- const query = this . parseQuery ( body ) ;
326+ const body = await request . json ( ) ;
327+ let query : QueryDescriptor ;
328+ try { query = this . parseQuery ( body ) ; } catch ( err ) { return this . json ( { error : ( err as Error ) . message } , 400 ) ; }
337329 query . limit = 1 ;
338330 try {
339331 const result = await this . executeQuery ( query ) ;
@@ -344,9 +336,9 @@ export class QueryDO implements DurableObject {
344336 }
345337
346338 private async handleFirst ( request : Request ) : Promise < Response > {
347- const body = await request . json ( ) as Record < string , unknown > ;
348- if ( ! body . table || typeof body . table !== "string" ) return this . json ( { error : "Missing 'table'" } , 400 ) ;
349- const query = this . parseQuery ( body ) ;
339+ const body = await request . json ( ) ;
340+ let query : QueryDescriptor ;
341+ try { query = this . parseQuery ( body ) ; } catch ( err ) { return this . json ( { error : ( err as Error ) . message } , 400 ) ; }
350342 query . limit = 1 ;
351343 try {
352344 const result = await this . executeQuery ( query ) ;
@@ -357,9 +349,9 @@ export class QueryDO implements DurableObject {
357349 }
358350
359351 private async handleExplain ( request : Request ) : Promise < Response > {
360- const body = await request . json ( ) as Record < string , unknown > ;
361- if ( ! body . table || typeof body . table !== "string" ) return this . json ( { error : "Missing 'table'" } , 400 ) ;
362- const query = this . parseQuery ( body ) ;
352+ const body = await request . json ( ) ;
353+ let query : QueryDescriptor ;
354+ try { query = this . parseQuery ( body ) ; } catch ( err ) { return this . json ( { error : ( err as Error ) . message } , 400 ) ; }
363355 try {
364356 let meta : TableMeta | undefined = this . footerCache . get ( query . table ) ;
365357 const metaCached = ! ! meta ;
@@ -1358,7 +1350,9 @@ export class QueryDO implements DurableObject {
13581350
13591351 /** Stream query results as NDJSON. */
13601352 private async handleQueryStream ( request : Request ) : Promise < Response > {
1361- const query = ( await request . json ( ) ) as QueryDescriptor ;
1353+ const body = await request . json ( ) ;
1354+ let query : QueryDescriptor ;
1355+ try { query = this . parseQuery ( body ) ; } catch ( err ) { return this . json ( { error : ( err as Error ) . message } , 400 ) ; }
13621356 const result = await this . executeQuery ( query ) ;
13631357
13641358 const { readable, writable } = new TransformStream < Uint8Array > ( ) ;
0 commit comments