@@ -14,6 +14,7 @@ import (
1414
1515 "github.com/grafana/loki/v3/pkg/dataobj"
1616 "github.com/grafana/loki/v3/pkg/dataobj/sections/logs"
17+ "github.com/grafana/loki/v3/pkg/dataobj/sections/pointers"
1718 "github.com/grafana/loki/v3/pkg/dataobj/sections/streams"
1819 "github.com/grafana/loki/v3/pkg/engine/internal/planner/physical"
1920 "github.com/grafana/loki/v3/pkg/xcap"
@@ -95,6 +96,10 @@ func (c *Context) execute(ctx context.Context, node physical.Node) Pipeline {
9596 return newObservedPipeline (c .executeDataObjScan (ctx , n , nodeRegion ))
9697 }, inputs )
9798
99+ case * physical.PointersScan :
100+ return newLazyPipeline (func (ctx context.Context , _ []Pipeline ) Pipeline {
101+ return newObservedPipeline (c .executePointersScan (ctx , n , nodeRegion ))
102+ }, inputs )
98103 case * physical.TopK :
99104 return newObservedPipeline (c .executeTopK (ctx , n , inputs , nodeRegion ))
100105 case * physical.Limit :
@@ -208,6 +213,73 @@ func (c *Context) executeDataObjScan(ctx context.Context, node *physical.DataObj
208213 return pipeline
209214}
210215
216+ func (c * Context ) executePointersScan (ctx context.Context , node * physical.PointersScan , region * xcap.Region ) Pipeline {
217+ if c .bucket == nil {
218+ return errorPipeline (ctx , errors .New ("no object store bucket configured" ))
219+ }
220+
221+ obj , err := dataobj .FromBucket (ctx , c .bucket , string (node .Location ))
222+ if err != nil {
223+ return errorPipeline (ctx , fmt .Errorf ("creating data object: %w" , err ))
224+ }
225+ region .AddEvent ("opened dataobj" )
226+
227+ var (
228+ streamsSection * streams.Section
229+ pointersSections []* pointers.Section
230+ )
231+
232+ tenant , err := user .ExtractOrgID (ctx )
233+ if err != nil {
234+ return errorPipeline (ctx , fmt .Errorf ("missing org ID: %w" , err ))
235+ }
236+
237+ for _ , sec := range obj .Sections ().Filter (streams .CheckSection ) {
238+ if sec .Tenant != tenant {
239+ continue
240+ }
241+
242+ if streamsSection != nil {
243+ return errorPipeline (ctx , fmt .Errorf ("multiple streams sections found in data object %q" , node .Location ))
244+ }
245+
246+ var err error
247+ streamsSection , err = streams .Open (ctx , sec )
248+ if err != nil {
249+ return errorPipeline (ctx , fmt .Errorf ("opening streams section %q: %w" , sec .Type , err ))
250+ }
251+ region .AddEvent ("opened streams section" )
252+ }
253+ if streamsSection == nil {
254+ return errorPipeline (ctx , fmt .Errorf ("streams section not found in data object %q" , node .Location ))
255+ }
256+
257+ for _ , sec := range obj .Sections ().Filter (pointers .CheckSection ) {
258+ if sec .Tenant != tenant {
259+ continue
260+ }
261+
262+ s , err := pointers .Open (ctx , sec )
263+ if err != nil {
264+ return errorPipeline (ctx , fmt .Errorf ("pointers logs section %q: %w" , sec .Type , err ))
265+ }
266+ pointersSections = append (pointersSections , s )
267+ region .AddEvent ("opened pointers section" )
268+ break
269+ }
270+
271+ var pipeline Pipeline = newPointersScanPipeline (pointersScanOptions {
272+ StreamsSection : streamsSection ,
273+ PointersSections : pointersSections ,
274+ Selector : node .Selector ,
275+ BatchSize : c .batchSize ,
276+ Start : node .Start ,
277+ End : node .End ,
278+ }, log .With (c .logger , "location" , string (node .Location )), region )
279+
280+ return pipeline
281+ }
282+
211283func (c * Context ) executeTopK (ctx context.Context , topK * physical.TopK , inputs []Pipeline , region * xcap.Region ) Pipeline {
212284 if len (inputs ) == 0 {
213285 return emptyPipeline ()
@@ -336,7 +408,7 @@ func (c *Context) executeScanSet(ctx context.Context, set *physical.ScanSet, _ *
336408 // ScanSet typically gets partitioned by the scheduler into multiple scan
337409 // nodes.
338410 //
339- // However, for locally testing unpartitioned pipelines, we still supprt
411+ // However, for locally testing unpartitioned pipelines, we still support
340412 // running a ScanSet. In this case, we treat internally execute it as a
341413 // Merge on top of multiple sequential scans.
342414 ctx , mergeRegion := xcap .StartRegion (ctx , physical .NodeTypeMerge .String ())
@@ -356,6 +428,16 @@ func (c *Context) executeScanSet(ctx context.Context, set *physical.ScanSet, _ *
356428 targets = append (targets , newLazyPipeline (func (_ context.Context , _ []Pipeline ) Pipeline {
357429 return newObservedPipeline (c .executeDataObjScan (nodeCtx , partition , partitionRegion ))
358430 }, nil ))
431+ case physical .ScanTypePointers :
432+ // Make sure projections and predicates get passed down to the
433+ // individual scan.
434+ partition := target .PointersScan
435+
436+ nodeCtx , partitionRegion := startRegionForNode (ctx , partition )
437+
438+ targets = append (targets , newLazyPipeline (func (_ context.Context , _ []Pipeline ) Pipeline {
439+ return newObservedPipeline (c .executePointersScan (nodeCtx , partition , partitionRegion ))
440+ }, nil ))
359441 default :
360442 return errorPipeline (ctx , fmt .Errorf ("unrecognized ScanSet target %s" , target .Type ))
361443 }
@@ -452,6 +534,10 @@ func startRegionForNode(ctx context.Context, n physical.Node) (context.Context,
452534 attribute .Int ("num_predicates" , len (n .Predicates )),
453535 attribute .Int ("num_projections" , len (n .Projections )),
454536 )
537+
538+ case * physical.PointersScan :
539+ attributes = append (attributes ,
540+ attribute .String ("location" , string (n .Location )))
455541 default :
456542 // do nothing.
457543 }
0 commit comments