Skip to main content

drive/query/drive_document_ranked_query/mode_detection/
mod.rs

1//! Request-shape validation for the ranked query, and the versioned
2//! `(select, group_by, order_by, limit, offset)` → [`DocumentRankedMode`]
3//! resolution.
4//!
5//! Pure functions on the request shape — no Drive, no contract, no
6//! indexes. Available under `server` (the dispatcher validates before
7//! executing) and `verify` (the SDK validates the same way before
8//! attempting proof verification), so both sides agree on which requests
9//! are well-formed and on the `(axis, descending, k, offset)` tuple a
10//! well-formed one resolves to. Index-dependent validation ("does an
11//! index actually cover this axis?") needs the document type's index map
12//! and lives in [`super::index_picker`].
13//!
14//! Versioned through
15//! `platform_version.drive.methods.document.query.detect_ranked_mode`,
16//! the same way
17//! [`DriveDocumentCountQuery::detect_mode_versioned`](super::super::drive_document_count_query::DriveDocumentCountQuery::detect_mode_versioned)
18//! routes count's table: the accepted request grammar is a consensus-
19//! adjacent contract on the query surface, so relaxing it later has to
20//! land behind a method-version bump rather than changing what an
21//! already-deployed protocol version accepts.
22
23use super::{
24    DocumentRankedMode, RankedAxis, RankedPaginationInputs, MAX_RANKED_LIMIT,
25    RANKED_COUNT_ORDER_KEY,
26};
27use crate::error::query::QuerySyntaxError;
28use crate::error::Error;
29use crate::query::having::HavingClause;
30use crate::query::projection::{SelectFunction, SelectProjection};
31use crate::query::{OrderClause, WhereClause};
32use dpp::version::PlatformVersion;
33
34/// Versioned entry point. Routes through
35/// `platform_version.drive.methods.document.query.detect_ranked_mode`;
36/// today only `0` is defined and maps to [`detect_ranked_mode_v0`]
37/// verbatim.
38///
39/// # Parameters
40///
41/// * `select`: The selected aggregate (`COUNT(*)`, `SUM(f)` or `AVG(f)`), which sets the axis.
42/// * `group_by`: The `GROUP BY` properties; exactly one is accepted.
43/// * `having`: The `HAVING` clauses; must be empty, since a ranking cannot filter groups.
44/// * `order_by`: The `ORDER BY` clauses; exactly one, naming the selected aggregate.
45/// * `where_clauses`: The `WHERE` clauses pinning the covering index's leading properties.
46/// * `pagination`: The request's limit, offset and whether it carried a start cursor.
47/// * `platform_version`: The platform version.
48///
49/// # Returns
50///
51/// * `Ok(DocumentRankedMode)` with the axis, the direction, the limit, the offset, the group
52///   property, the aggregate field and the prefix pins.
53/// * `Err(Error)` with a query syntax error when the method version is unknown or the request
54///   falls outside the accepted grammar.
55pub fn detect_ranked_mode(
56    select: &SelectProjection,
57    group_by: &[String],
58    having: &[HavingClause],
59    order_by: &[OrderClause],
60    where_clauses: &[WhereClause],
61    pagination: RankedPaginationInputs,
62    platform_version: &PlatformVersion,
63) -> Result<DocumentRankedMode, Error> {
64    match platform_version
65        .drive
66        .methods
67        .document
68        .query
69        .detect_ranked_mode
70    {
71        0 => detect_ranked_mode_v0(
72            select,
73            group_by,
74            having,
75            order_by,
76            where_clauses,
77            pagination,
78        ),
79        version => Err(Error::Query(QuerySyntaxError::Unsupported(format!(
80            "detect_ranked_mode: unknown method version {version}; only 0 is supported"
81        )))),
82    }
83}
84
85/// The `ORDER BY` field name that names a given select's aggregate.
86///
87/// `SUM(f)` / `AVG(f)` are ordered by naming `f` — the same field the
88/// projection aggregates, which is how SQL's `ORDER BY avg(grade)`
89/// reads once the aggregate function is already fixed by the `SELECT`.
90/// `COUNT(*)` has no field, so it is named by the
91/// [`RANKED_COUNT_ORDER_KEY`] sentinel.
92///
93/// Public because request *builders* need it as much as the validator
94/// does: an SDK offering `.order_by_selected_aggregate(…)` has to emit
95/// the same string this function expects to read back, and a second
96/// copy of the sentinel rule is a silent-rejection bug waiting for the
97/// first `COUNT(*)` ranking.
98pub fn ranked_order_key(select: &SelectProjection) -> &str {
99    match select.function {
100        SelectFunction::Count if select.field.is_empty() => RANKED_COUNT_ORDER_KEY,
101        _ => select.field.as_str(),
102    }
103}
104
105mod v0;
106// Re-exported so the dispatcher's callers (`drive_dispatcher`, the
107// test suites) keep addressing the frozen grammar by its old path.
108pub use v0::{detect_ranked_mode_v0, prefix_pins_from_where_clauses};