use super::bounds::{bounded_write_policy_rejection, sql_write_execution_bounds_for_staged_kind};
pub(in crate::db::session::sql) const DEFAULT_PUBLIC_BOUNDED_WRITE_LIMIT: u32 = 100;
pub(in crate::db::session::sql) const DEFAULT_PUBLIC_WRITE_RETURNING_RESPONSE_BYTES: u32 =
1_048_576;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[doc(hidden)]
pub enum SqlWriteWhereProof {
Missing,
PrimaryKeyEquality,
Other,
}
impl SqlWriteWhereProof {
#[must_use]
pub const fn has_where(self) -> bool {
!matches!(self, Self::Missing)
}
#[must_use]
pub const fn is_primary_key_equality(self) -> bool {
matches!(self, Self::PrimaryKeyEquality)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[doc(hidden)]
pub enum SqlWriteOrderProof {
Missing,
CanonicalPrimaryKey,
DescendingPrimaryKey,
Other,
}
impl SqlWriteOrderProof {
#[must_use]
pub const fn is_canonical_primary_key(self) -> bool {
matches!(self, Self::CanonicalPrimaryKey)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[doc(hidden)]
pub enum SqlWriteReturningShape {
None,
NarrowAll,
NarrowFields,
}
impl SqlWriteReturningShape {
#[must_use]
pub const fn is_requested(self) -> bool {
!matches!(self, Self::None)
}
#[must_use]
pub const fn is_narrow(self) -> bool {
matches!(self, Self::NarrowAll | Self::NarrowFields)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::session::sql) enum SqlWriteBoundedPolicyRejection {
MissingCanonicalPrimaryKeyOrder,
DescendingOrder,
MissingLimit,
OffsetUnsupported,
LimitTooHigh,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::session::sql) enum SqlGeneratedWritePolicyKind {
Query,
Ddl,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::session::sql) enum SqlWriteExposureClass {
SessionWriteCurrent,
GeneratedQuery,
GeneratedDdl,
PublicPrimaryKeyOnly,
PublicBoundedDeterministic,
AdminBulk,
}
impl SqlWriteExposureClass {
pub(in crate::db::session::sql) const fn generated_policy_kind(
self,
) -> Option<SqlGeneratedWritePolicyKind> {
match self {
Self::GeneratedQuery => Some(SqlGeneratedWritePolicyKind::Query),
Self::GeneratedDdl => Some(SqlGeneratedWritePolicyKind::Ddl),
Self::SessionWriteCurrent
| Self::PublicPrimaryKeyOnly
| Self::PublicBoundedDeterministic
| Self::AdminBulk => None,
}
}
const fn admission_lane(self) -> Option<SqlWriteAdmissionLane> {
Some(match self {
Self::PublicPrimaryKeyOnly => SqlWriteAdmissionLane::PrimaryKeyOnly,
Self::PublicBoundedDeterministic => SqlWriteAdmissionLane::BoundedDeterministic,
Self::SessionWriteCurrent | Self::AdminBulk => SqlWriteAdmissionLane::Bulk,
Self::GeneratedQuery | Self::GeneratedDdl => return None,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::session::sql) enum SqlWriteShapePolicyRejection {
MissingWhere,
PrimaryKeyProofFailed,
Bounded(SqlWriteBoundedPolicyRejection),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[doc(hidden)]
pub struct SqlWriteReturningBounds {
pub max_rows: Option<u32>,
pub max_response_bytes: Option<u32>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[doc(hidden)]
pub struct SqlWriteExecutionBounds {
pub max_staged_rows: Option<u32>,
pub returning: SqlWriteReturningBounds,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[doc(hidden)]
pub struct SqlWriteStatementShape {
pub where_proof: SqlWriteWhereProof,
pub order_proof: SqlWriteOrderProof,
pub limit: Option<u32>,
pub offset: Option<u32>,
pub returning_shape: SqlWriteReturningShape,
}
impl SqlWriteStatementShape {
#[must_use]
pub const fn is_bounded(&self) -> bool {
matches!(self.limit, Some(limit) if limit > 0)
}
#[must_use]
pub const fn has_explicit_canonical_primary_key_order(&self) -> bool {
self.order_proof.is_canonical_primary_key()
}
const fn bounded_policy_rejection(
&self,
max_limit: u32,
) -> Option<SqlWriteBoundedPolicyRejection> {
bounded_write_policy_rejection(self.offset, self.limit, max_limit, self.order_proof)
}
pub(in crate::db::session::sql) const fn bounded_policy_rejection_for_bounds(
&self,
bounds: SqlWritePolicyBounds,
) -> Option<SqlWriteBoundedPolicyRejection> {
self.bounded_policy_rejection(bounds.public_bounded_limit)
}
pub(in crate::db::session::sql) const fn required_where_rejection(
&self,
) -> Option<SqlWriteShapePolicyRejection> {
if self.where_proof.has_where() {
None
} else {
Some(SqlWriteShapePolicyRejection::MissingWhere)
}
}
pub(in crate::db::session::sql) const fn primary_key_policy_rejection(
&self,
) -> Option<SqlWriteShapePolicyRejection> {
if let Some(rejection) = self.required_where_rejection() {
return Some(rejection);
}
if self.where_proof.is_primary_key_equality() {
None
} else {
Some(SqlWriteShapePolicyRejection::PrimaryKeyProofFailed)
}
}
pub(in crate::db::session::sql) const fn bounded_deterministic_policy_rejection(
&self,
bounds: SqlWritePolicyBounds,
) -> Option<SqlWriteShapePolicyRejection> {
if let Some(rejection) = self.required_where_rejection() {
return Some(rejection);
}
match self.bounded_policy_rejection_for_bounds(bounds) {
Some(rejection) => Some(SqlWriteShapePolicyRejection::Bounded(rejection)),
None => None,
}
}
pub(in crate::db::session::sql) const fn execution_bounds_for_admission_lane(
&self,
admission_lane: SqlWriteAdmissionLane,
bounds: SqlWritePolicyBounds,
) -> SqlWriteExecutionBounds {
sql_write_execution_bounds_for_staged_kind(
admission_lane.staged_row_bound_kind(),
self.limit,
self.returning_shape.is_requested(),
bounds.returning_rows,
bounds.returning_response_bytes,
)
}
pub(in crate::db::session::sql) const fn execution_bounds_for_exposure_class(
&self,
exposure_class: SqlWriteExposureClass,
bounds: SqlWritePolicyBounds,
) -> Option<SqlWriteExecutionBounds> {
match exposure_class.admission_lane() {
Some(admission_lane) => {
Some(self.execution_bounds_for_admission_lane(admission_lane, bounds))
}
None => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::session::sql) struct SqlWritePolicyBounds {
pub(in crate::db::session::sql) public_bounded_limit: u32,
pub(in crate::db::session::sql) returning_rows: Option<u32>,
pub(in crate::db::session::sql) returning_response_bytes: Option<u32>,
}
impl SqlWritePolicyBounds {
pub(in crate::db::session::sql) const fn new(
public_bounded_limit: u32,
returning_rows: Option<u32>,
returning_response_bytes: Option<u32>,
) -> Self {
Self {
public_bounded_limit,
returning_rows,
returning_response_bytes,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::session::sql) struct SqlWritePlanCore<S, C> {
statement: S,
classification: C,
execution_bounds: SqlWriteExecutionBounds,
}
impl<S, C> SqlWritePlanCore<S, C> {
pub(in crate::db::session::sql) const fn new(
statement: S,
classification: C,
execution_bounds: SqlWriteExecutionBounds,
) -> Self {
Self {
statement,
classification,
execution_bounds,
}
}
pub(in crate::db::session::sql) const fn statement(&self) -> &S {
&self.statement
}
pub(in crate::db::session::sql) const fn classification(&self) -> &C {
&self.classification
}
pub(in crate::db::session::sql) const fn execution_bounds(&self) -> SqlWriteExecutionBounds {
self.execution_bounds
}
#[cfg(test)]
pub(in crate::db) const fn set_execution_bounds_for_tests(
&mut self,
execution_bounds: SqlWriteExecutionBounds,
) {
self.execution_bounds = execution_bounds;
}
}
impl<S: Clone, C: Clone> SqlWritePlanCore<S, C> {
pub(in crate::db::session::sql) fn from_borrowed(
statement: &S,
classification: &C,
execution_bounds: SqlWriteExecutionBounds,
) -> Self {
Self::new(statement.clone(), classification.clone(), execution_bounds)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::session::sql) struct SqlWritePrimaryKeyPlanProof {
primary_key_fields: Vec<String>,
}
impl SqlWritePrimaryKeyPlanProof {
pub(in crate::db::session::sql) const fn new(primary_key_fields: Vec<String>) -> Self {
Self { primary_key_fields }
}
pub(in crate::db::session::sql) fn from_field_names(primary_key_fields: &[&str]) -> Self {
Self::new(owned_write_field_names(primary_key_fields))
}
pub(in crate::db::session::sql) const fn primary_key_fields(&self) -> &[String] {
self.primary_key_fields.as_slice()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::session::sql) struct SqlWriteBoundedPlanProof {
limit: u32,
ordered_primary_key_fields: Vec<String>,
}
impl SqlWriteBoundedPlanProof {
pub(in crate::db::session::sql) const fn new(
limit: u32,
ordered_primary_key_fields: Vec<String>,
) -> Self {
Self {
limit,
ordered_primary_key_fields,
}
}
pub(in crate::db::session::sql) const fn limit(&self) -> u32 {
self.limit
}
pub(in crate::db::session::sql) const fn ordered_primary_key_fields(&self) -> &[String] {
self.ordered_primary_key_fields.as_slice()
}
pub(in crate::db::session::sql) fn from_admitted_shape(
shape: &SqlWriteStatementShape,
ordered_primary_key_fields: &[&str],
) -> Option<Self> {
Some(Self::new(
shape.limit?,
owned_write_field_names(ordered_primary_key_fields),
))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::session::sql) enum SqlWriteAdmissionLane {
PrimaryKeyOnly,
BoundedDeterministic,
Bulk,
}
impl SqlWriteAdmissionLane {
pub(super) const fn staged_row_bound_kind(self) -> SqlWriteStagedRowBoundKind {
match self {
Self::PrimaryKeyOnly => SqlWriteStagedRowBoundKind::One,
Self::BoundedDeterministic => SqlWriteStagedRowBoundKind::Limit,
Self::Bulk => SqlWriteStagedRowBoundKind::Unbounded,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) enum SqlWriteStagedRowBoundKind {
One,
Limit,
Unbounded,
}
fn owned_write_field_names(fields: &[&str]) -> Vec<String> {
fields.iter().map(|field| (*field).to_owned()).collect()
}