@@ -31,6 +31,10 @@ func NewReader(w *Writer) *Reader {
3131
3232// Query executes a query on current data
3333func (r * Reader ) Query (whereClause string , limit int ) ([]HEPRecord , error ) {
34+ if err := ValidateWhereClause (whereClause , AllQueryColumns ()); err != nil {
35+ return nil , err
36+ }
37+ limit = ClampLimit (limit , DefaultQueryLimit , MaxQueryLimit )
3438 query := fmt .Sprintf ("SELECT * FROM %s" , r .tableFQN )
3539 if whereClause != "" {
3640 query += " WHERE " + whereClause
@@ -45,6 +49,10 @@ func (r *Reader) Query(whereClause string, limit int) ([]HEPRecord, error) {
4549
4650// QueryAtSnapshot queries data at a specific snapshot
4751func (r * Reader ) QueryAtSnapshot (snapshotID int64 , whereClause string , limit int ) ([]HEPRecord , error ) {
52+ if err := ValidateWhereClause (whereClause , AllQueryColumns ()); err != nil {
53+ return nil , err
54+ }
55+ limit = ClampLimit (limit , DefaultQueryLimit , MaxQueryLimit )
4856 // DuckLake syntax: SELECT * FROM table AT SNAPSHOT snapshot_id
4957 query := fmt .Sprintf ("SELECT * FROM %s AT SNAPSHOT %d" , r .tableFQN , snapshotID )
5058 if whereClause != "" {
@@ -60,6 +68,10 @@ func (r *Reader) QueryAtSnapshot(snapshotID int64, whereClause string, limit int
6068
6169// QueryAtTime queries data as it was at a specific timestamp
6270func (r * Reader ) QueryAtTime (asOf time.Time , whereClause string , limit int ) ([]HEPRecord , error ) {
71+ if err := ValidateWhereClause (whereClause , AllQueryColumns ()); err != nil {
72+ return nil , err
73+ }
74+ limit = ClampLimit (limit , DefaultQueryLimit , MaxQueryLimit )
6375 // DuckLake syntax: SELECT * FROM table AT TIMESTAMP 'timestamp'
6476 timestamp := asOf .Format ("2006-01-02 15:04:05" )
6577 query := fmt .Sprintf ("SELECT * FROM %s AT TIMESTAMP '%s'" , r .tableFQN , timestamp )
@@ -108,9 +120,7 @@ func (r *Reader) executeQuery(query string) ([]HEPRecord, error) {
108120
109121// ListSnapshots returns all available snapshots
110122func (r * Reader ) ListSnapshots (limit int ) ([]Snapshot , error ) {
111- if limit <= 0 {
112- limit = 100
113- }
123+ limit = ClampLimit (limit , DefaultQueryLimit , MaxQueryLimit )
114124
115125 // DuckLake provides snapshot info via system function
116126 query := fmt .Sprintf (`
@@ -259,16 +269,20 @@ func NewMultiTableReader(w *MultiTableWriter) *MultiTableReader {
259269
260270// allTablesForKey returns the DuckLake table and, when search_buffer is enabled,
261271// both in-memory buffer tables for a key.
262- func (r * MultiTableReader ) allTablesForKey (key TableKey ) []string {
263- tables := []string {r .writer .GetTableFQN (key )}
272+ func (r * MultiTableReader ) allTablesForKey (key TableKey ) ([]string , error ) {
273+ fqn , err := ResolveTableFQN (r .writer , key )
274+ if err != nil {
275+ return nil , err
276+ }
277+ tables := []string {fqn }
264278 if r .searchBuffer {
265279 if tw := r .writer .GetTable (key ); tw != nil {
266280 for _ , mem := range tw .MemTableNames () {
267281 tables = append (tables , mem )
268282 }
269283 }
270284 }
271- return tables
285+ return tables , nil
272286}
273287
274288// GetTimeRange returns min/max timestamps across all tables including buffers
@@ -279,7 +293,11 @@ func (r *MultiTableReader) GetTimeRange() (minTs, maxTs int64, err error) {
279293 }
280294
281295 for _ , key := range keys {
282- for _ , tbl := range r .allTablesForKey (key ) {
296+ tbls , tblErr := r .allTablesForKey (key )
297+ if tblErr != nil {
298+ continue
299+ }
300+ for _ , tbl := range tbls {
283301 query := fmt .Sprintf ("SELECT MIN(timestamp), MAX(timestamp) FROM %s" , tbl )
284302 var minNull , maxNull sql.NullInt64
285303 if err := r .db .QueryRow (query ).Scan (& minNull , & maxNull ); err != nil {
@@ -299,7 +317,14 @@ func (r *MultiTableReader) GetTimeRange() (minTs, maxTs int64, err error) {
299317
300318// GetTimeRangeForTableKey returns time range for specific TableKey including buffers
301319func (r * MultiTableReader ) GetTimeRangeForTableKey (key TableKey ) (minTs , maxTs int64 , err error ) {
302- for _ , tbl := range r .allTablesForKey (key ) {
320+ if err := ValidateTableKey (key ); err != nil {
321+ return 0 , 0 , err
322+ }
323+ tbls , err := r .allTablesForKey (key )
324+ if err != nil {
325+ return 0 , 0 , err
326+ }
327+ for _ , tbl := range tbls {
303328 query := fmt .Sprintf ("SELECT MIN(timestamp), MAX(timestamp) FROM %s" , tbl )
304329 var minNull , maxNull sql.NullInt64
305330 if err := r .db .QueryRow (query ).Scan (& minNull , & maxNull ); err != nil {
@@ -338,7 +363,11 @@ func (r *MultiTableReader) GetRowCount() (int64, error) {
338363 var total int64
339364
340365 for _ , key := range keys {
341- for _ , tbl := range r .allTablesForKey (key ) {
366+ tbls , tblErr := r .allTablesForKey (key )
367+ if tblErr != nil {
368+ continue
369+ }
370+ for _ , tbl := range tbls {
342371 var count int64
343372 query := fmt .Sprintf ("SELECT COUNT(*) FROM %s" , tbl )
344373 if err := r .db .QueryRow (query ).Scan (& count ); err != nil {
@@ -353,8 +382,15 @@ func (r *MultiTableReader) GetRowCount() (int64, error) {
353382
354383// GetRowCountForTableKey returns row count for specific TableKey including buffers
355384func (r * MultiTableReader ) GetRowCountForTableKey (key TableKey ) (int64 , error ) {
385+ if err := ValidateTableKey (key ); err != nil {
386+ return 0 , err
387+ }
388+ tbls , err := r .allTablesForKey (key )
389+ if err != nil {
390+ return 0 , err
391+ }
356392 var total int64
357- for _ , tbl := range r . allTablesForKey ( key ) {
393+ for _ , tbl := range tbls {
358394 var count int64
359395 query := fmt .Sprintf ("SELECT COUNT(*) FROM %s" , tbl )
360396 if err := r .db .QueryRow (query ).Scan (& count ); err != nil {
@@ -369,8 +405,12 @@ func (r *MultiTableReader) GetRowCountForTableKey(key TableKey) (int64, error) {
369405// is enabled, it builds a UNION ALL across the DuckLake persistent table and
370406// both in-memory buffer tables so queries see the freshest data even before
371407// flush. When search_buffer is disabled, it queries only the DuckLake table.
372- func (r * MultiTableReader ) buildUnionQuery (key TableKey , whereClause string , limit int ) string {
373- tables := r .allTablesForKey (key )
408+ func (r * MultiTableReader ) buildUnionQuery (key TableKey , whereClause string , limit int ) (string , error ) {
409+ tables , err := r .allTablesForKey (key )
410+ if err != nil {
411+ return "" , err
412+ }
413+ limit = ClampLimit (limit , DefaultQueryLimit , MaxQueryLimit )
374414
375415 if len (tables ) == 1 {
376416 query := fmt .Sprintf ("SELECT * FROM %s" , tables [0 ])
@@ -381,7 +421,7 @@ func (r *MultiTableReader) buildUnionQuery(key TableKey, whereClause string, lim
381421 if limit > 0 {
382422 query += fmt .Sprintf (" LIMIT %d" , limit )
383423 }
384- return query
424+ return query , nil
385425 }
386426
387427 var parts []string
@@ -398,28 +438,51 @@ func (r *MultiTableReader) buildUnionQuery(key TableKey, whereClause string, lim
398438 if limit > 0 {
399439 query += fmt .Sprintf (" LIMIT %d" , limit )
400440 }
401- return query
441+ return query , nil
402442}
403443
404444// Query executes a query on specific table by TableKey.
405445// Includes data from both in-memory buffers via UNION ALL so that
406446// un-flushed records are visible to search.
407447func (r * MultiTableReader ) Query (key TableKey , whereClause string , limit int ) ([]map [string ]interface {}, error ) {
408- query := r .buildUnionQuery (key , whereClause , limit )
448+ if err := ValidateTableKey (key ); err != nil {
449+ return nil , err
450+ }
451+ cols , err := ColumnsForTableKey (key )
452+ if err != nil {
453+ return nil , err
454+ }
455+ if err := ValidateWhereClause (whereClause , cols ); err != nil {
456+ return nil , err
457+ }
458+ query , err := r .buildUnionQuery (key , whereClause , limit )
459+ if err != nil {
460+ return nil , err
461+ }
409462 return r .executeQueryGeneric (query )
410463}
411464
412465// QueryAll executes a query across all tables.
413466// Includes data from both in-memory buffers via UNION ALL.
414467func (r * MultiTableReader ) QueryAll (whereClause string , limit int ) ([]map [string ]interface {}, error ) {
468+ if err := ValidateWhereClause (whereClause , AllQueryColumns ()); err != nil {
469+ return nil , err
470+ }
471+ limit = ClampLimit (limit , DefaultQueryLimit , MaxQueryLimit )
415472 keys := r .writer .ListTableKeys ()
416473 if len (keys ) == 0 {
417474 return nil , nil
418475 }
419476
420477 var allResults []map [string ]interface {}
421478 for _ , key := range keys {
422- query := r .buildUnionQuery (key , whereClause , limit )
479+ if err := ValidateTableKey (key ); err != nil {
480+ continue
481+ }
482+ query , err := r .buildUnionQuery (key , whereClause , limit )
483+ if err != nil {
484+ continue
485+ }
423486 results , err := r .executeQueryGeneric (query )
424487 if err != nil {
425488 continue
@@ -497,12 +560,12 @@ func extractTimestamp(row map[string]interface{}) time.Time {
497560
498561// ListSnapshots returns snapshots for a specific table by TableKey
499562func (r * MultiTableReader ) ListSnapshots (key TableKey , limit int ) ([]Snapshot , error ) {
500- if limit <= 0 {
501- limit = 100
563+ limit = ClampLimit (limit , DefaultQueryLimit , MaxQueryLimit )
564+ tableFQN , err := ResolveTableFQN (r .writer , key )
565+ if err != nil {
566+ return nil , err
502567 }
503568
504- tableFQN := r .writer .GetTableFQN (key )
505-
506569 query := fmt .Sprintf (`
507570 SELECT snapshot_id, snapshot_time, row_count
508571 FROM ducklake_snapshots('%s')
0 commit comments