Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions lib/model/common/src/drivers/pframe/query/query_common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,56 @@ export interface ExprIsIn<I, T extends string | number> {
set: T[];
}

// ============ Ranking Expression ============

/**
* Ranking function kind.
*
* - `rank` - Standard rank: 1,2,2,4 (gaps after ties)
* - `denseRank` - Dense rank: 1,2,2,3 (no gaps)
* - `rowNumber` - Row number: 1,2,3,4 (no ties)
*/
export type RankingKind = "rank" | "denseRank" | "rowNumber";

/**
* Ranking expression (window function).
*
* Assigns ranks to records within partitions based on explicit ordering.
* Each unique combination of axis values is a record.
* Records are grouped by `partitionBy` values, then ordered by `orderBy` expression
* within each partition. Ties are broken deterministically by axis values.
*
* **Output**: Numeric rank value.
*
* @template I - The expression type (for recursion)
* @template A - Axis selector type
* @template C - Column selector type
*
* @example
* // Rank rows by score descending
* { type: 'ranking', kind: 'rank', orderBy: scoreRef, ascending: false }
*
* // Dense rank within partitions
* {
* type: 'ranking',
* kind: 'denseRank',
* orderBy: valueRef,
* ascending: true,
* partitionBy: [{ type: 'axis', id: 'sample' }]
* }
*/
export interface ExprRanking<I, A, C> {
type: "ranking";
/** Ranking function kind */
kind: RankingKind;
/** Expression to order by */
orderBy: I;
/** Ascending or descending order */
ascending?: boolean;
Comment on lines +524 to +525
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For consistency with other parts of the query model like QuerySort (where ascending is a required property), and to avoid ambiguity about default behavior, it would be better to make the ascending property required here as well. This makes the contract of the ExprRanking interface more explicit. The current implementation for topN/bottomN already provides this property, so it would be a non-breaking change for that use case.

Suggested change
/** Ascending or descending order */
ascending?: boolean;
/** If true, order ascending (A-Z, 0-9); if false, descending */
ascending: boolean;

/** Partition specification — ranking is computed independently within each partition */
partitionBy?: (QueryAxisSelector<A> | QueryColumnSelector<C>)[];
}

// ============ Reference Expression Types ============

/**
Expand Down
4 changes: 3 additions & 1 deletion lib/model/common/src/drivers/pframe/query/query_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
ExprStringEquals,
ExprStringRegex,
ExprNumericUnary,
ExprRanking,
QueryAxisSelector,
QueryColumn,
QuerySparseToDenseColumn,
Expand Down Expand Up @@ -118,6 +119,7 @@ export type DataQueryExpression =
| ExprLogicalUnary<DataQueryExpression>
| ExprLogicalVariadic<DataQueryExpression>
| ExprIsIn<DataQueryExpression, string>
| ExprIsIn<DataQueryExpression, number>;
| ExprIsIn<DataQueryExpression, number>
| ExprRanking<DataQueryExpression, number, number>;

export type DataQueryBooleanExpression = InferBooleanExpressionUnion<DataQueryExpression>;
4 changes: 3 additions & 1 deletion lib/model/common/src/drivers/pframe/query/query_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
ExprStringEquals,
ExprStringRegex,
ExprNumericUnary,
ExprRanking,
QueryAxisSelector,
QueryColumn,
QuerySparseToDenseColumn,
Expand Down Expand Up @@ -137,6 +138,7 @@ export type SpecQueryExpression =
| ExprLogicalUnary<SpecQueryExpression>
| ExprLogicalVariadic<SpecQueryExpression>
| ExprIsIn<SpecQueryExpression, string>
| ExprIsIn<SpecQueryExpression, number>;
| ExprIsIn<SpecQueryExpression, number>
| ExprRanking<SpecQueryExpression, SingleAxisSelector, PObjectId>;

export type SpecQueryBooleanExpression = InferBooleanExpressionUnion<SpecQueryExpression>;
23 changes: 22 additions & 1 deletion sdk/model/src/filters/converters/filterToQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,29 @@ function leafToSpecQueryExpr<Leaf extends FilterSpecLeaf<string>>(
};

case "topN":
return {
type: "numericComparison",
operand: "le",
left: {
type: "ranking",
kind: "rank",
orderBy: resolveColumnRef(filter.column),
ascending: false,
},
right: { type: "constant", value: filter.n },
};
case "bottomN":
throw new Error(`Filter type "${filter.type}" is not supported in query expressions`);
return {
type: "numericComparison",
operand: "le",
left: {
type: "ranking",
kind: "rank",
orderBy: resolveColumnRef(filter.column),
ascending: true,
},
right: { type: "constant", value: filter.n },
};

case undefined:
throw new Error("Filter type is undefined");
Expand Down
5 changes: 5 additions & 0 deletions sdk/ui-vue/src/components/PlTableFilters/PlTableFiltersV2.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import type { PlAdvancedFilterColumnId } from "../PlAdvancedFilter/types";
import type { Nil } from "@milaboratories/helpers";
import { isNil } from "es-toolkit";
import { useFeatureFlags } from "../../defineApp";

const model = defineModel<PlDataTableFiltersWithMeta>({ required: true });
const props = defineProps<{
Expand Down Expand Up @@ -89,6 +90,10 @@ const supportedFilters = [
"notEqual",
] as (typeof PlAdvancedFilterSupportedFilters)[number][];

if (useFeatureFlags().supportsPframeQueryRanking) {
supportedFilters.push("topN", "bottomN");
}

// getSuggestOptions - provide discrete values from column annotations
function handleSuggestOptions(params: {
columnId: PlAdvancedFilterColumnId;
Expand Down
Loading