Advanced Querying and Filtering
Flyte uses a structured filtering system in its repository layer to perform complex searches across runs, actions, and events. This system abstracts SQL generation, allowing you to compose queries using logical operators while protecting against SQL injection through field validation.
Building Basic Filters
The runs/repository/impl package provides several factory functions to create Filter objects for standard field comparisons. These filters map internal field names to SQL expressions.
import (
"github.com/flyteorg/flyte/v2/runs/repository/impl"
"github.com/flyteorg/flyte/v2/runs/repository/interfaces"
)
// Create a simple equality filter
projectFilter := impl.NewEqualFilter("project", "flytesnacks")
// Create a filter for multiple values (SQL IN)
statusFilter := impl.NewEqualFilter("phase", []int32{2, 3}) // Uses FilterExpressionValueIn internally
Available factory functions in runs/repository/impl/filters.go include:
NewEqualFilter(field, value): Matches exact values or a set of values.NewNotEqualFilter(field, value): Matches values not equal to the input.NewProjectIdFilter(projectId): Matches bothprojectanddomain.NewTaskNameFilter(taskName): Matchesproject,domain, andnamefor tasks.NewIsRootActionFilter(): Specifically filters for root actions (runs) whereparent_action_nameis NULL.
Composing Complex Queries
Filters can be combined using .And() and .Or() methods defined in the interfaces.Filter interface. This creates a compositeFilter that recursively generates the appropriate SQL with parentheses.
// Combine multiple criteria with AND
filter := impl.NewIsRootActionFilter().
And(impl.NewEqualFilter("project", "proj1")).
And(impl.NewEqualFilter("domain", "development"))
// Use OR for alternative criteria
filter = impl.NewEqualFilter("phase", 4).
Or(impl.NewEqualFilter("phase", 5))
Executing Queries with Pagination
To execute a query, wrap your filter in a interfaces.ListResourceInput struct and pass it to a repository method like ListActions. Flyte supports both offset-based and keyset-based pagination.
Keyset Pagination (Recommended)
Keyset pagination uses a CursorToken (an RFC3339Nano timestamp) to fetch results strictly after a specific creation time.
input := interfaces.ListResourceInput{
Filter: impl.NewIsRootActionFilter(),
Limit: 50,
}
// Execute the query
runs, err := actionRepo.ListActions(ctx, input)
// To get the next page, use the CreatedAt of the last item as the cursor
if len(runs) > 0 {
nextPageInput := interfaces.ListResourceInput{
Filter: impl.NewIsRootActionFilter(),
Limit: 50,
CursorToken: runs[len(runs)-1].CreatedAt.UTC().Format(time.RFC3339Nano),
}
}
Sorting Results
You can specify sort order using NewSortParameter. If no sort parameters are provided, ListActions defaults to phase ASC, created_at DESC.
input := interfaces.ListResourceInput{
Filter: impl.NewEqualFilter("project", "my-project"),
SortParameters: []interfaces.SortParameter{
impl.NewSortParameter("created_at", interfaces.SortOrderDescending),
},
}
Security and Validation
When converting external API requests (Protobuf) into repository filters, Flyte uses ConvertProtoFilters to validate field names against a set of allowed columns. This prevents SQL injection by ensuring only known database columns can be used in filter expressions.
// Example of validating and converting proto filters
allowedColumns := sets.NewString("project", "domain", "phase", "created_at")
filter, err := impl.ConvertProtoFilters(request.GetFilters(), allowedColumns)
if err != nil {
// Handle invalid filter field error
}
The basicFilter.QueryExpression method handles the actual SQL generation, using placeholders (?) for values to ensure they are safely handled by the underlying database driver. For example, FilterExpressionContains automatically wraps the value in % wildcards and uses the LIKE operator.
// Internal implementation snippet from runs/repository/impl/filters.go
case interfaces.FilterExpressionContains:
query = fmt.Sprintf("%s LIKE ?", column)
f.value = fmt.Sprintf("%%%v%%", f.value)