Skip to content

Commit 9f2d706

Browse files
committed
feat: add ranking expression support for topN and bottomN filters
1 parent cc8d1e6 commit 9f2d706

File tree

5 files changed

+80
-3
lines changed

5 files changed

+80
-3
lines changed

lib/model/common/src/drivers/pframe/query/query_common.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,56 @@ export interface ExprIsIn<I, T extends string | number> {
477477
set: T[];
478478
}
479479

480+
// ============ Ranking Expression ============
481+
482+
/**
483+
* Ranking function kind.
484+
*
485+
* - `rank` - Standard rank: 1,2,2,4 (gaps after ties)
486+
* - `denseRank` - Dense rank: 1,2,2,3 (no gaps)
487+
* - `rowNumber` - Row number: 1,2,3,4 (no ties)
488+
*/
489+
export type RankingKind = "rank" | "denseRank" | "rowNumber";
490+
491+
/**
492+
* Ranking expression (window function).
493+
*
494+
* Assigns ranks to records within partitions based on explicit ordering.
495+
* Each unique combination of axis values is a record.
496+
* Records are grouped by `partitionBy` values, then ordered by `orderBy` expression
497+
* within each partition. Ties are broken deterministically by axis values.
498+
*
499+
* **Output**: Numeric rank value.
500+
*
501+
* @template I - The expression type (for recursion)
502+
* @template A - Axis selector type
503+
* @template C - Column selector type
504+
*
505+
* @example
506+
* // Rank rows by score descending
507+
* { type: 'ranking', kind: 'rank', orderBy: scoreRef, ascending: false }
508+
*
509+
* // Dense rank within partitions
510+
* {
511+
* type: 'ranking',
512+
* kind: 'denseRank',
513+
* orderBy: valueRef,
514+
* ascending: true,
515+
* partitionBy: [{ type: 'axis', id: 'sample' }]
516+
* }
517+
*/
518+
export interface ExprRanking<I, A, C> {
519+
type: "ranking";
520+
/** Ranking function kind */
521+
kind: RankingKind;
522+
/** Expression to order by */
523+
orderBy: I;
524+
/** Ascending or descending order */
525+
ascending?: boolean;
526+
/** Partition specification — ranking is computed independently within each partition */
527+
partitionBy?: (QueryAxisSelector<A> | QueryColumnSelector<C>)[];
528+
}
529+
480530
// ============ Reference Expression Types ============
481531

482532
/**

lib/model/common/src/drivers/pframe/query/query_data.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
ExprStringEquals,
1616
ExprStringRegex,
1717
ExprNumericUnary,
18+
ExprRanking,
1819
QueryAxisSelector,
1920
QueryColumn,
2021
QuerySparseToDenseColumn,
@@ -118,6 +119,7 @@ export type DataQueryExpression =
118119
| ExprLogicalUnary<DataQueryExpression>
119120
| ExprLogicalVariadic<DataQueryExpression>
120121
| ExprIsIn<DataQueryExpression, string>
121-
| ExprIsIn<DataQueryExpression, number>;
122+
| ExprIsIn<DataQueryExpression, number>
123+
| ExprRanking<DataQueryExpression, number, number>;
122124

123125
export type DataQueryBooleanExpression = InferBooleanExpressionUnion<DataQueryExpression>;

lib/model/common/src/drivers/pframe/query/query_spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
ExprStringEquals,
1616
ExprStringRegex,
1717
ExprNumericUnary,
18+
ExprRanking,
1819
QueryAxisSelector,
1920
QueryColumn,
2021
QuerySparseToDenseColumn,
@@ -137,6 +138,7 @@ export type SpecQueryExpression =
137138
| ExprLogicalUnary<SpecQueryExpression>
138139
| ExprLogicalVariadic<SpecQueryExpression>
139140
| ExprIsIn<SpecQueryExpression, string>
140-
| ExprIsIn<SpecQueryExpression, number>;
141+
| ExprIsIn<SpecQueryExpression, number>
142+
| ExprRanking<SpecQueryExpression, SingleAxisSelector, PObjectId>;
141143

142144
export type SpecQueryBooleanExpression = InferBooleanExpressionUnion<SpecQueryExpression>;

sdk/model/src/filters/converters/filterToQuery.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,29 @@ function leafToSpecQueryExpr<Leaf extends FilterSpecLeaf<string>>(
254254
};
255255

256256
case "topN":
257+
return {
258+
type: "numericComparison",
259+
operand: "le",
260+
left: {
261+
type: "ranking",
262+
kind: "rank",
263+
orderBy: resolveColumnRef(filter.column),
264+
ascending: false,
265+
},
266+
right: { type: "constant", value: filter.n },
267+
};
257268
case "bottomN":
258-
throw new Error(`Filter type "${filter.type}" is not supported in query expressions`);
269+
return {
270+
type: "numericComparison",
271+
operand: "le",
272+
left: {
273+
type: "ranking",
274+
kind: "rank",
275+
orderBy: resolveColumnRef(filter.column),
276+
ascending: true,
277+
},
278+
right: { type: "constant", value: filter.n },
279+
};
259280

260281
case undefined:
261282
throw new Error("Filter type is undefined");

sdk/ui-vue/src/components/PlTableFilters/PlTableFiltersV2.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ const supportedFilters = [
8484
"patternFuzzyContainSubsequence",
8585
"equal",
8686
"notEqual",
87+
"topN",
88+
"bottomN",
8789
] as (typeof PlAdvancedFilterSupportedFilters)[number][];
8890
8991
// getSuggestOptions - provide discrete values from column annotations

0 commit comments

Comments
 (0)