mod plan_summary;
mod policy;
mod render;
use crate::db::query::plan::AccessPlannedQuery;
use icydb_diagnostic_code::QueryReadAdmissionCode;
pub(in crate::db::query) use policy::DEFAULT_BOUNDED_READ_MAX_ROWS;
#[cfg(test)]
pub(in crate::db) use policy::GroupedAdmissionPolicy;
pub(in crate::db) use policy::QueryAdmissionPolicy;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum QueryAdmissionLane {
PublicRead,
DiagnosticExplain,
}
impl QueryAdmissionLane {
#[must_use]
pub(in crate::db) const fn as_str(self) -> &'static str {
match self {
Self::PublicRead => "public_read",
Self::DiagnosticExplain => "diagnostic_explain",
}
}
#[must_use]
pub(in crate::db) const fn executes_rows(self) -> bool {
!matches!(self, Self::DiagnosticExplain)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum QueryBoundKind {
Exact,
ConservativeUpperBound,
EnforcedRuntimeCap,
Unavailable,
}
impl QueryBoundKind {
#[must_use]
pub(in crate::db) const fn as_str(self) -> &'static str {
match self {
Self::Exact => "exact",
Self::ConservativeUpperBound => "conservative_upper_bound",
Self::EnforcedRuntimeCap => "enforced_runtime_cap",
Self::Unavailable => "unavailable",
}
}
#[must_use]
pub(in crate::db) const fn admits_public_read(self) -> bool {
matches!(
self,
Self::Exact | Self::ConservativeUpperBound | Self::EnforcedRuntimeCap
)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum QueryAdmissionDecision {
Admitted,
Rejected,
}
impl QueryAdmissionDecision {
#[must_use]
pub(in crate::db) const fn as_str(self) -> &'static str {
match self {
Self::Admitted => "admitted",
Self::Rejected => "rejected",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum QueryAdmissionAccessKind {
ByKey,
ByKeys,
KeyRange,
IndexPrefix,
IndexMultiLookup,
IndexBranchSet,
IndexRange,
FullScan,
Union,
Intersection,
}
impl QueryAdmissionAccessKind {
#[must_use]
pub(in crate::db) const fn as_str(self) -> &'static str {
match self {
Self::ByKey => "by_key",
Self::ByKeys => "by_keys",
Self::KeyRange => "key_range",
Self::IndexPrefix => "index_prefix",
Self::IndexMultiLookup => "index_multi_lookup",
Self::IndexBranchSet => "index_branch_set",
Self::IndexRange => "index_range",
Self::FullScan => "full_scan",
Self::Union => "union",
Self::Intersection => "intersection",
}
}
#[must_use]
pub(in crate::db) const fn is_secondary_index(self) -> bool {
matches!(
self,
Self::IndexPrefix | Self::IndexMultiLookup | Self::IndexBranchSet | Self::IndexRange
)
}
#[must_use]
pub(in crate::db) const fn is_full_scan(self) -> bool {
matches!(self, Self::FullScan)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum QueryAdmissionPlanShape {
ScalarRead,
GroupedAggregate,
Delete,
}
impl QueryAdmissionPlanShape {
#[must_use]
pub(in crate::db) const fn as_str(self) -> &'static str {
match self {
Self::ScalarRead => "scalar_read",
Self::GroupedAggregate => "grouped_aggregate",
Self::Delete => "delete",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum QueryAdmissionResidualFilter {
Absent,
Predicate,
Expression,
ExpressionAndPredicate,
}
impl QueryAdmissionResidualFilter {
#[must_use]
pub(in crate::db) const fn as_str(self) -> &'static str {
match self {
Self::Absent => "none",
Self::Predicate => "predicate",
Self::Expression => "expression",
Self::ExpressionAndPredicate => "expression_and_predicate",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum QueryAdmissionOrdering {
None,
Requested,
Resolved,
}
impl QueryAdmissionOrdering {
#[must_use]
pub(in crate::db) const fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Requested => "requested",
Self::Resolved => "resolved",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) struct QueryAdmissionGroupedSummary {
group_field_count: u32,
aggregate_count: u32,
distinct_aggregate_count: u32,
max_groups: u64,
max_group_bytes: u64,
having_filter: bool,
}
impl QueryAdmissionGroupedSummary {
#[must_use]
pub(in crate::db) const fn new(
group_field_count: u32,
aggregate_count: u32,
distinct_aggregate_count: u32,
max_groups: u64,
max_group_bytes: u64,
having_filter: bool,
) -> Self {
Self {
group_field_count,
aggregate_count,
distinct_aggregate_count,
max_groups,
max_group_bytes,
having_filter,
}
}
#[must_use]
pub(in crate::db) const fn group_field_count(self) -> u32 {
self.group_field_count
}
#[must_use]
pub(in crate::db) const fn aggregate_count(self) -> u32 {
self.aggregate_count
}
#[must_use]
pub(in crate::db) const fn distinct_aggregate_count(self) -> u32 {
self.distinct_aggregate_count
}
#[must_use]
pub(in crate::db) const fn max_groups(self) -> u64 {
self.max_groups
}
#[must_use]
pub(in crate::db) const fn max_group_bytes(self) -> u64 {
self.max_group_bytes
}
#[must_use]
pub(in crate::db) const fn has_having_filter(self) -> bool {
self.having_filter
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) struct QueryMaterializationSummary {
materialized_sort: bool,
materialized_rows: Option<u32>,
row_bound_kind: QueryBoundKind,
}
impl QueryMaterializationSummary {
#[must_use]
pub(in crate::db) const fn none() -> Self {
Self {
materialized_sort: false,
materialized_rows: None,
row_bound_kind: QueryBoundKind::Unavailable,
}
}
#[must_use]
pub(in crate::db) const fn sort(
materialized_rows: Option<u32>,
row_bound_kind: QueryBoundKind,
) -> Self {
Self {
materialized_sort: true,
materialized_rows,
row_bound_kind,
}
}
#[must_use]
pub(in crate::db) const fn materialized_sort(&self) -> bool {
self.materialized_sort
}
#[must_use]
pub(in crate::db) const fn materialized_rows(&self) -> Option<u32> {
self.materialized_rows
}
#[must_use]
pub(in crate::db) const fn row_bound_kind(&self) -> QueryBoundKind {
self.row_bound_kind
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum QueryAdmissionRejection {
PublicQueryRequiresLimit,
PublicQueryRequiresIndex,
UnboundedFullScanRejected,
SortRequiresMaterialization,
GroupedQueryRequiresLimits,
GroupedQueryExceedsBudget,
DiagnosticLaneDoesNotExecute,
ReturnedRowBoundExceedsPolicy,
PrimaryKeyInputExceedsPolicy,
}
impl QueryAdmissionRejection {
#[must_use]
pub(in crate::db) const fn as_str(self) -> &'static str {
match self {
Self::PublicQueryRequiresLimit => "public_query_requires_limit",
Self::PublicQueryRequiresIndex => "public_query_requires_index",
Self::UnboundedFullScanRejected => "unbounded_full_scan_rejected",
Self::SortRequiresMaterialization => "sort_requires_materialization",
Self::GroupedQueryRequiresLimits => "grouped_query_requires_limits",
Self::GroupedQueryExceedsBudget => "grouped_query_exceeds_budget",
Self::DiagnosticLaneDoesNotExecute => "diagnostic_lane_does_not_execute",
Self::ReturnedRowBoundExceedsPolicy => "returned_row_bound_exceeds_policy",
Self::PrimaryKeyInputExceedsPolicy => "primary_key_input_exceeds_policy",
}
}
#[must_use]
pub(in crate::db) const fn code(self) -> QueryReadAdmissionCode {
match self {
Self::PublicQueryRequiresLimit => QueryReadAdmissionCode::PublicQueryRequiresLimit,
Self::PublicQueryRequiresIndex => QueryReadAdmissionCode::PublicQueryRequiresIndex,
Self::UnboundedFullScanRejected => QueryReadAdmissionCode::UnboundedFullScanRejected,
Self::SortRequiresMaterialization => {
QueryReadAdmissionCode::SortRequiresMaterialization
}
Self::GroupedQueryRequiresLimits => QueryReadAdmissionCode::GroupedQueryRequiresLimits,
Self::GroupedQueryExceedsBudget => QueryReadAdmissionCode::GroupedQueryExceedsBudget,
Self::DiagnosticLaneDoesNotExecute => {
QueryReadAdmissionCode::DiagnosticLaneDoesNotExecute
}
Self::ReturnedRowBoundExceedsPolicy => {
QueryReadAdmissionCode::ReturnedRowBoundExceedsPolicy
}
Self::PrimaryKeyInputExceedsPolicy => {
QueryReadAdmissionCode::PrimaryKeyInputExceedsPolicy
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct QueryAdmissionSummary {
lane: QueryAdmissionLane,
decision: QueryAdmissionDecision,
plan_shape: QueryAdmissionPlanShape,
selected_access: QueryAdmissionAccessKind,
selected_index: Option<String>,
limit: Option<u32>,
offset: Option<u32>,
scan_bound: Option<u64>,
scan_bound_kind: QueryBoundKind,
returned_row_bound: Option<u32>,
returned_row_bound_kind: QueryBoundKind,
primary_key_input_terms: Option<u32>,
primary_key_input_payload_bytes: Option<u32>,
residual_filter: QueryAdmissionResidualFilter,
ordering: QueryAdmissionOrdering,
grouped: Option<QueryAdmissionGroupedSummary>,
materialization: QueryMaterializationSummary,
rejection: Option<QueryAdmissionRejection>,
}
impl QueryAdmissionSummary {
#[must_use]
pub(in crate::db) fn from_plan(lane: QueryAdmissionLane, plan: &AccessPlannedQuery) -> Self {
plan_summary::summary_from_plan(lane, plan)
}
const fn admit(mut self) -> Self {
self.decision = QueryAdmissionDecision::Admitted;
self.rejection = None;
self
}
const fn reject(mut self, rejection: QueryAdmissionRejection) -> Self {
self.decision = QueryAdmissionDecision::Rejected;
self.rejection = Some(rejection);
self
}
#[must_use]
pub(in crate::db) const fn lane(&self) -> QueryAdmissionLane {
self.lane
}
#[must_use]
pub(in crate::db) const fn decision(&self) -> QueryAdmissionDecision {
self.decision
}
#[must_use]
pub(in crate::db) const fn plan_shape(&self) -> QueryAdmissionPlanShape {
self.plan_shape
}
#[must_use]
pub(in crate::db) const fn selected_access(&self) -> QueryAdmissionAccessKind {
self.selected_access
}
#[must_use]
pub(in crate::db) fn selected_index(&self) -> Option<&str> {
self.selected_index.as_deref()
}
#[must_use]
pub(in crate::db) const fn limit(&self) -> Option<u32> {
self.limit
}
#[must_use]
pub(in crate::db) const fn offset(&self) -> Option<u32> {
self.offset
}
#[must_use]
pub(in crate::db) const fn scan_bound(&self) -> Option<u64> {
self.scan_bound
}
#[must_use]
pub(in crate::db) const fn scan_bound_kind(&self) -> QueryBoundKind {
self.scan_bound_kind
}
#[must_use]
pub(in crate::db) const fn returned_row_bound(&self) -> Option<u32> {
self.returned_row_bound
}
#[must_use]
pub(in crate::db) const fn returned_row_bound_kind(&self) -> QueryBoundKind {
self.returned_row_bound_kind
}
#[must_use]
pub(in crate::db) const fn primary_key_input_terms(&self) -> Option<u32> {
self.primary_key_input_terms
}
#[must_use]
pub(in crate::db) const fn primary_key_input_payload_bytes(&self) -> Option<u32> {
self.primary_key_input_payload_bytes
}
#[must_use]
pub(in crate::db) const fn residual_filter(&self) -> QueryAdmissionResidualFilter {
self.residual_filter
}
#[must_use]
pub(in crate::db) const fn ordering(&self) -> QueryAdmissionOrdering {
self.ordering
}
#[must_use]
pub(in crate::db) const fn grouped(&self) -> Option<QueryAdmissionGroupedSummary> {
self.grouped
}
#[must_use]
pub(in crate::db) const fn materialization(&self) -> QueryMaterializationSummary {
self.materialization
}
#[must_use]
#[cfg_attr(not(feature = "sql"), allow(dead_code))]
pub(in crate::db) const fn with_materialization(
mut self,
materialization: QueryMaterializationSummary,
) -> Self {
self.materialization = materialization;
self
}
#[must_use]
pub(in crate::db) const fn rejection(&self) -> Option<QueryAdmissionRejection> {
self.rejection
}
#[must_use]
pub(in crate::db) fn render_text_block(&self) -> String {
render::render_text_block(self)
}
}
#[cfg(test)]
mod tests;