@@ -72,6 +72,9 @@ type WorkflowArchive interface {
7272 // list workflows, with the most recently started workflows at the beginning (i.e. index 0 is the most recent)
7373 ListWorkflows (ctx context.Context , options sutils.ListOptions ) (wfv1.Workflows , error )
7474 CountWorkflows (ctx context.Context , options sutils.ListOptions ) (int64 , error )
75+ // HasMoreWorkflows efficiently checks if there are more workflows beyond the current offset+limit
76+ // This is much faster than counting all workflows for pagination purposes
77+ HasMoreWorkflows (ctx context.Context , options sutils.ListOptions ) (bool , error )
7578 GetWorkflow (ctx context.Context , uid string , namespace string , name string ) (* wfv1.Workflow , error )
7679 GetWorkflowForEstimator (ctx context.Context , namespace string , requirements []labels.Requirement ) (* wfv1.Workflow , error )
7780 DeleteWorkflow (ctx context.Context , uid string ) error
@@ -275,8 +278,47 @@ func (r *workflowArchive) ListWorkflows(ctx context.Context, options sutils.List
275278}
276279
277280func (r * workflowArchive ) CountWorkflows (ctx context.Context , options sutils.ListOptions ) (int64 , error ) {
281+ if options .Limit > 0 && options .Offset > 0 {
282+ return r .countWorkflowsOptimized (options )
283+ }
284+
278285 total := & archivedWorkflowCount {}
279286
287+ if len (options .LabelRequirements ) == 0 {
288+ selector := r .session .SQL ().
289+ Select (db .Raw ("count(*) as total" )).
290+ From (archiveTableName ).
291+ Where (r .clusterManagedNamespaceAndInstanceID ()).
292+ And (namespaceEqual (options .Namespace )).
293+ And (namePrefixClause (options .NamePrefix )).
294+ And (startedAtFromClause (options .MinStartedAt )).
295+ And (startedAtToClause (options .MaxStartedAt )).
296+ And (createdAfterClause (options .CreatedAfter )).
297+ And (finishedBeforeClause (options .FinishedBefore ))
298+
299+ if options .Name != "" {
300+ nameFilter := options .NameFilter
301+ if nameFilter == "" {
302+ nameFilter = "Exact"
303+ }
304+ if nameFilter == "Exact" {
305+ selector = selector .And (nameEqual (options .Name ))
306+ }
307+ if nameFilter == "Contains" {
308+ selector = selector .And (nameContainsClause (options .Name ))
309+ }
310+ if nameFilter == "Prefix" {
311+ selector = selector .And (namePrefixClause (options .Name ))
312+ }
313+ }
314+
315+ err := selector .One (total )
316+ if err != nil {
317+ return 0 , err
318+ }
319+ return int64 (total .Total ), nil
320+ }
321+
280322 selector := r .session .SQL ().
281323 Select (db .Raw ("count(*) as total" )).
282324 From (archiveTableName ).
@@ -294,6 +336,107 @@ func (r *workflowArchive) CountWorkflows(ctx context.Context, options sutils.Lis
294336 return int64 (total .Total ), nil
295337}
296338
339+ func (r * workflowArchive ) countWorkflowsOptimized (options sutils.ListOptions ) (int64 , error ) {
340+ sampleSelector := r .session .SQL ().
341+ Select (db .Raw ("count(*) as total" )).
342+ From (archiveTableName ).
343+ Where (r .clusterManagedNamespaceAndInstanceID ()).
344+ And (namespaceEqual (options .Namespace )).
345+ And (namePrefixClause (options .NamePrefix )).
346+ And (startedAtFromClause (options .MinStartedAt )).
347+ And (startedAtToClause (options .MaxStartedAt )).
348+ And (createdAfterClause (options .CreatedAfter )).
349+ And (finishedBeforeClause (options .FinishedBefore ))
350+
351+ if options .Name != "" {
352+ nameFilter := options .NameFilter
353+ if nameFilter == "" {
354+ nameFilter = "Exact"
355+ }
356+ if nameFilter == "Exact" {
357+ sampleSelector = sampleSelector .And (nameEqual (options .Name ))
358+ }
359+ if nameFilter == "Contains" {
360+ sampleSelector = sampleSelector .And (nameContainsClause (options .Name ))
361+ }
362+ if nameFilter == "Prefix" {
363+ sampleSelector = sampleSelector .And (namePrefixClause (options .Name ))
364+ }
365+ }
366+
367+ if options .Offset < 1000 {
368+ total := & archivedWorkflowCount {}
369+ err := sampleSelector .One (total )
370+ if err != nil {
371+ return 0 , err
372+ }
373+ return int64 (total .Total ), nil
374+ }
375+
376+ sampleSize := 1000
377+ sampleSelector = sampleSelector .Limit (sampleSize )
378+
379+ sampleTotal := & archivedWorkflowCount {}
380+ err := sampleSelector .One (sampleTotal )
381+ if err != nil {
382+ return 0 , err
383+ }
384+
385+ if int64 (sampleTotal .Total ) < int64 (sampleSize ) {
386+ return int64 (sampleTotal .Total ), nil
387+ }
388+
389+ estimatedTotal := int64 (options .Offset ) + int64 (sampleTotal .Total ) + int64 (options .Limit )
390+ return estimatedTotal , nil
391+ }
392+
393+ func (r * workflowArchive ) HasMoreWorkflows (ctx context.Context , options sutils.ListOptions ) (bool , error ) {
394+ selector := r .session .SQL ().
395+ Select ("uid" ).
396+ From (archiveTableName ).
397+ Where (r .clusterManagedNamespaceAndInstanceID ()).
398+ And (namespaceEqual (options .Namespace )).
399+ And (namePrefixClause (options .NamePrefix )).
400+ And (startedAtFromClause (options .MinStartedAt )).
401+ And (startedAtToClause (options .MaxStartedAt )).
402+ And (createdAfterClause (options .CreatedAfter )).
403+ And (finishedBeforeClause (options .FinishedBefore ))
404+
405+ if options .Name != "" {
406+ nameFilter := options .NameFilter
407+ if nameFilter == "" {
408+ nameFilter = "Exact"
409+ }
410+ if nameFilter == "Exact" {
411+ selector = selector .And (nameEqual (options .Name ))
412+ }
413+ if nameFilter == "Contains" {
414+ selector = selector .And (nameContainsClause (options .Name ))
415+ }
416+ if nameFilter == "Prefix" {
417+ selector = selector .And (namePrefixClause (options .Name ))
418+ }
419+ }
420+
421+ if len (options .LabelRequirements ) > 0 {
422+ var err error
423+ selector , err = BuildArchivedWorkflowSelector (selector , archiveTableName , archiveLabelsTableName , r .dbType , options , false )
424+ if err != nil {
425+ return false , err
426+ }
427+ }
428+
429+ selector = selector .Limit (1 ).Offset (options .Offset + options .Limit )
430+
431+ var result []struct { UID string }
432+ err := selector .All (& result )
433+ if err != nil {
434+ return false , err
435+ }
436+
437+ return len (result ) > 0 , nil
438+ }
439+
297440func (r * workflowArchive ) clusterManagedNamespaceAndInstanceID () * db.AndExpr {
298441 return db .And (
299442 db.Cond {"clustername" : r .clusterName },
0 commit comments