@@ -12,6 +12,7 @@ import (
1212 "github.com/grafana/dskit/user"
1313
1414 "github.com/grafana/loki/v3/pkg/dataobj"
15+ "github.com/grafana/loki/v3/pkg/dataobj/sections/indexpointers"
1516 "github.com/grafana/loki/v3/pkg/dataobj/sections/pointers"
1617)
1718
@@ -332,6 +333,123 @@ func forEachMatchedPointerSectionKey(
332333 return nil
333334}
334335
336+ func forEachIndexPointer (
337+ ctx context.Context ,
338+ object * dataobj.Object ,
339+ sStart , sEnd * scalar.Timestamp ,
340+ f func (pointer indexpointers.IndexPointer ),
341+ ) error {
342+ targetTenant , err := user .ExtractOrgID (ctx )
343+ if err != nil {
344+ return fmt .Errorf ("extracting org ID: %w" , err )
345+ }
346+ var reader indexpointers.Reader
347+ defer reader .Close ()
348+
349+ const batchSize = 1024
350+ buf := make ([]indexpointers.IndexPointer , batchSize )
351+
352+ // iterate over the sections and fill buf column by column
353+ // once the read operation is over invoke client's [f] on every read row (numRows not always the same as len(buf))
354+ for _ , section := range object .Sections ().Filter (indexpointers .CheckSection ) {
355+ if section .Tenant != targetTenant {
356+ continue
357+ }
358+
359+ sec , err := indexpointers .Open (ctx , section )
360+ if err != nil {
361+ return fmt .Errorf ("opening section: %w" , err )
362+ }
363+
364+ var (
365+ colPath * indexpointers.Column
366+ colMinTimestamp * indexpointers.Column
367+ colMaxTimestamp * indexpointers.Column
368+ )
369+
370+ for _ , c := range sec .Columns () {
371+ if c .Type == indexpointers .ColumnTypePath {
372+ colPath = c
373+ }
374+ if c .Type == indexpointers .ColumnTypeMinTimestamp {
375+ colMinTimestamp = c
376+ }
377+ if c .Type == indexpointers .ColumnTypeMaxTimestamp {
378+ colMaxTimestamp = c
379+ }
380+ if colPath != nil && colMinTimestamp != nil && colMaxTimestamp != nil {
381+ break
382+ }
383+ }
384+
385+ if colPath == nil || colMinTimestamp == nil || colMaxTimestamp == nil {
386+ return fmt .Errorf ("one of the mandatory columns is missing: (path=%t, minTimestamp=%t, maxTimestamp=%t)" , colPath == nil , colMinTimestamp == nil , colMaxTimestamp == nil )
387+ }
388+
389+ reader .Reset (indexpointers.ReaderOptions {
390+ Columns : sec .Columns (),
391+ Predicates : []indexpointers.Predicate {
392+ indexpointers .WhereTimeRangeOverlapsWith (colMinTimestamp , colMaxTimestamp , sStart , sEnd ),
393+ },
394+ })
395+
396+ for {
397+ rec , readErr := reader .Read (ctx , batchSize )
398+ if readErr != nil && ! errors .Is (readErr , io .EOF ) {
399+ return fmt .Errorf ("reading recordBatch: %w" , readErr )
400+ }
401+ numRows := int (rec .NumRows ())
402+ if numRows == 0 && errors .Is (readErr , io .EOF ) {
403+ break
404+ }
405+
406+ for colIdx := range int (rec .NumCols ()) {
407+ col := rec .Column (colIdx )
408+ pointerCol := sec .Columns ()[colIdx ]
409+
410+ switch pointerCol .Type {
411+ case indexpointers .ColumnTypePath :
412+ values := col .(* array.String )
413+ for rIdx := range numRows {
414+ if col .IsNull (rIdx ) {
415+ continue
416+ }
417+ buf [rIdx ].Path = values .Value (rIdx )
418+ }
419+ case indexpointers .ColumnTypeMinTimestamp :
420+ values := col .(* array.Timestamp )
421+ for rIdx := range numRows {
422+ if col .IsNull (rIdx ) {
423+ continue
424+ }
425+ buf [rIdx ].StartTs = time .Unix (0 , int64 (values .Value (rIdx )))
426+ }
427+ case indexpointers .ColumnTypeMaxTimestamp :
428+ values := col .(* array.Timestamp )
429+ for rIdx := range numRows {
430+ if col .IsNull (rIdx ) {
431+ continue
432+ }
433+ buf [rIdx ].EndTs = time .Unix (0 , int64 (values .Value (rIdx )))
434+ }
435+ default :
436+ continue
437+ }
438+ }
439+
440+ for rowIdx := range numRows {
441+ f (buf [rowIdx ])
442+ }
443+
444+ if errors .Is (readErr , io .EOF ) {
445+ break
446+ }
447+ }
448+ }
449+
450+ return nil
451+ }
452+
335453func findPointersColumnsByTypes (allColumns []* pointers.Column , columnTypes ... pointers.ColumnType ) ([]* pointers.Column , error ) {
336454 result := make ([]* pointers.Column , 0 , len (columnTypes ))
337455
0 commit comments