#![allow(dead_code)]
use crate::{
db::{
DbSession, PersistedRow, QueryError,
access::AccessPlan,
executor::{
EntityAuthority, SharedPreparedExecutionPlan,
pipeline::execute_initial_grouped_rows_for_canister,
},
predicate::PreparedSqlScalarPredicateTemplate,
query::plan::expr::PreparedSqlGroupedExprTemplate,
query::plan::{AccessPlannedQuery, LogicalPlan},
session::sql::{
SqlProjectionContract,
projection::{
SqlProjectionPayload, execute_sql_projection_rows_for_canister,
grouped_sql_statement_result_from_page,
},
},
sql::lowering::{
LoweredSqlQuery, PreparedSqlParameterContract, PreparedSqlParameterTypeFamily,
PreparedSqlStatement, lower_sql_command_from_prepared_statement,
prepared_sql_simple_range_slots, sql_expr_is_compound_boolean_shape,
sql_expr_prepared_predicate_template_shape, sql_statement_contains_any_literal,
},
sql::parser::{SqlExpr, SqlStatement},
},
model::{entity::EntityModel, field::FieldKind},
traits::{CanisterKind, EntityValue},
value::Value,
};
#[derive(Clone, Debug)]
pub(in crate::db) struct PreparedSqlQuery {
source_sql: String,
statement: PreparedSqlStatement,
parameter_contracts: Vec<PreparedSqlParameterContract>,
execution_template: Option<PreparedSqlExecutionTemplate>,
}
#[derive(Clone, Debug)]
enum PreparedSqlExecutionTemplate {
SymbolicScalar(PreparedSqlSymbolicScalarTemplate),
SymbolicGrouped(PreparedSqlSymbolicGroupedTemplate),
Legacy(PreparedSqlLegacyExecutionTemplate),
}
#[derive(Clone, Debug)]
struct PreparedSqlLegacyExecutionTemplate {
authority: EntityAuthority,
projection: SqlProjectionContract,
plan: AccessPlannedQuery,
}
#[derive(Clone, Debug)]
struct PreparedSqlSymbolicScalarTemplate {
authority: EntityAuthority,
projection: SqlProjectionContract,
plan: AccessPlannedQuery,
predicate: Option<PreparedSqlScalarPredicateTemplate>,
access: Option<PreparedSqlScalarAccessPathTemplate>,
}
#[derive(Clone, Debug)]
struct PreparedSqlSymbolicGroupedTemplate {
authority: EntityAuthority,
projection: SqlProjectionContract,
plan: AccessPlannedQuery,
predicate: Option<PreparedSqlScalarPredicateTemplate>,
access: Option<PreparedSqlScalarAccessPathTemplate>,
having_expr: Option<PreparedSqlGroupedExprTemplate>,
}
#[derive(Clone, Debug)]
enum PreparedSqlScalarAccessValueTemplate {
Static(Value),
SlotLiteral(usize),
}
#[derive(Clone, Debug)]
enum PreparedSqlScalarAccessPathTemplate {
ByKey {
key: PreparedSqlScalarAccessValueTemplate,
},
KeyRange {
start: PreparedSqlScalarAccessValueTemplate,
end: PreparedSqlScalarAccessValueTemplate,
},
IndexPrefix {
index: crate::model::index::IndexModel,
values: Vec<PreparedSqlScalarAccessValueTemplate>,
},
}
#[cfg(test)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum PreparedSqlExecutionTemplateKind {
SymbolicScalar,
SymbolicGrouped,
Legacy,
}
impl PreparedSqlQuery {
#[must_use]
pub(in crate::db) fn source_sql(&self) -> &str {
&self.source_sql
}
#[must_use]
pub(in crate::db) const fn parameter_contracts(&self) -> &[PreparedSqlParameterContract] {
self.parameter_contracts.as_slice()
}
#[must_use]
pub(in crate::db) const fn parameter_count(&self) -> usize {
self.parameter_contracts.len()
}
#[cfg(test)]
#[must_use]
pub(in crate::db) const fn template_kind_for_test(
&self,
) -> Option<PreparedSqlExecutionTemplateKind> {
match self.execution_template.as_ref() {
Some(PreparedSqlExecutionTemplate::SymbolicScalar(_)) => {
Some(PreparedSqlExecutionTemplateKind::SymbolicScalar)
}
Some(PreparedSqlExecutionTemplate::SymbolicGrouped(_)) => {
Some(PreparedSqlExecutionTemplateKind::SymbolicGrouped)
}
Some(PreparedSqlExecutionTemplate::Legacy(_)) => {
Some(PreparedSqlExecutionTemplateKind::Legacy)
}
None => None,
}
}
}
impl<C: CanisterKind> DbSession<C> {
pub(in crate::db) fn prepare_sql_query<E>(
&self,
sql: &str,
) -> Result<PreparedSqlQuery, QueryError>
where
E: PersistedRow<Canister = C> + EntityValue,
{
let statement = crate::db::session::sql::parse_sql_statement_with_attribution(sql)
.map(|(statement, _)| statement)?;
Self::ensure_sql_query_statement_supported(&statement)?;
let authority = EntityAuthority::for_type::<E>();
let prepared = Self::prepare_sql_statement_for_authority(&statement, authority)?;
let parameter_contracts = prepared
.parameter_contracts(authority.model())
.map_err(QueryError::from_sql_lowering_error)?;
let execution_template =
self.build_prepared_sql_execution_template(&prepared, ¶meter_contracts, authority)?;
Ok(PreparedSqlQuery {
source_sql: sql.to_string(),
statement: prepared,
parameter_contracts,
execution_template,
})
}
pub(in crate::db) fn execute_prepared_sql_query<E>(
&self,
prepared: &PreparedSqlQuery,
bindings: &[Value],
) -> Result<crate::db::session::sql::SqlStatementResult, QueryError>
where
E: PersistedRow<Canister = C> + EntityValue,
{
validate_parameter_bindings(prepared.parameter_contracts(), bindings)?;
if let Some(template) = prepared.execution_template.as_ref()
&& bindings.iter().all(|value| !matches!(value, Value::Null))
{
let bound_plan = match template {
PreparedSqlExecutionTemplate::SymbolicScalar(template) => {
bind_symbolic_scalar_template_plan(
template.plan.clone(),
template.predicate.as_ref(),
template.access.as_ref(),
bindings,
template.authority,
)?
}
PreparedSqlExecutionTemplate::SymbolicGrouped(template) => {
bind_symbolic_grouped_template_plan(
template.plan.clone(),
template.predicate.as_ref(),
template.access.as_ref(),
template.having_expr.as_ref(),
bindings,
template.authority,
)?
}
PreparedSqlExecutionTemplate::Legacy(template) => bind_prepared_template_plan(
template.plan.clone(),
prepared.parameter_contracts(),
bindings,
template.authority,
)?,
};
let prepared_plan =
SharedPreparedExecutionPlan::from_plan(bound_plan.authority(), bound_plan.plan);
let projection = match template {
PreparedSqlExecutionTemplate::SymbolicScalar(template) => &template.projection,
PreparedSqlExecutionTemplate::SymbolicGrouped(template) => &template.projection,
PreparedSqlExecutionTemplate::Legacy(template) => &template.projection,
};
return self.execute_prepared_template_plan(prepared_plan, projection);
}
let bound_statement = prepared.statement.bind_literals(bindings)?;
let authority = EntityAuthority::for_type::<E>();
self.execute_bound_prepared_sql_query_without_caches(&bound_statement, authority)
}
fn build_prepared_sql_execution_template(
&self,
prepared: &PreparedSqlStatement,
parameter_contracts: &[PreparedSqlParameterContract],
authority: EntityAuthority,
) -> Result<Option<PreparedSqlExecutionTemplate>, QueryError> {
if parameter_contracts.is_empty() {
return Ok(None);
}
if prepared.uses_general_template_expr_parameters() {
return Ok(None);
}
if let Some(template) = self.build_symbolic_scalar_prepared_sql_execution_template(
prepared,
parameter_contracts,
authority,
)? {
return Ok(Some(template));
}
if let Some(template) = self.build_symbolic_grouped_prepared_sql_execution_template(
prepared,
parameter_contracts,
authority,
)? {
return Ok(Some(template));
}
let Some(template_bindings) = parameter_contracts
.iter()
.map(|contract| contract.template_binding().cloned())
.collect::<Option<Vec<_>>>()
else {
return Ok(None);
};
if prepared_statement_contains_template_literal_collision(
prepared.statement(),
template_bindings.as_slice(),
) {
return Ok(None);
}
let bound_statement = prepared.bind_literals(template_bindings.as_slice())?;
let normalized_prepared =
Self::prepare_sql_statement_for_authority(&bound_statement, authority)?;
let lowered =
lower_sql_command_from_prepared_statement(normalized_prepared, authority.model())
.map_err(QueryError::from_sql_lowering_error)?;
let Some(LoweredSqlQuery::Select(select)) = lowered.into_query() else {
return Ok(None);
};
let structural = Self::structural_query_from_lowered_select(select, authority)?;
let (_, plan) =
self.build_structural_plan_with_visible_indexes_for_authority(structural, authority)?;
let prepared_plan = SharedPreparedExecutionPlan::from_plan(authority, plan.clone());
let projection = Self::sql_select_projection_contract_from_shared_prepared_plan(
authority,
&prepared_plan,
);
Ok(Some(PreparedSqlExecutionTemplate::Legacy(
PreparedSqlLegacyExecutionTemplate {
authority,
projection,
plan,
},
)))
}
fn build_symbolic_scalar_prepared_sql_execution_template(
&self,
prepared: &PreparedSqlStatement,
parameter_contracts: &[PreparedSqlParameterContract],
authority: EntityAuthority,
) -> Result<Option<PreparedSqlExecutionTemplate>, QueryError> {
if parameter_contracts.is_empty() {
return Ok(None);
}
let SqlStatement::Select(select) = prepared.statement() else {
return Ok(None);
};
let Some(sql_predicate) = select.predicate.as_ref() else {
return Ok(None);
};
let Some(exemplar_bindings) = parameter_contracts
.iter()
.map(prepared_sql_contract_exemplar_binding)
.collect::<Option<Vec<_>>>()
else {
return Ok(None);
};
let mut exemplar_bindings = exemplar_bindings;
apply_sql_range_exemplar_override(
select.predicate.as_ref(),
parameter_contracts,
exemplar_bindings.as_mut_slice(),
authority.model(),
);
let bound_statement = prepared.bind_literals(exemplar_bindings.as_slice())?;
let normalized_prepared =
Self::prepare_sql_statement_for_authority(&bound_statement, authority)?;
let lowered =
lower_sql_command_from_prepared_statement(normalized_prepared, authority.model())
.map_err(QueryError::from_sql_lowering_error)?;
let Some(LoweredSqlQuery::Select(select)) = lowered.into_query() else {
return Ok(None);
};
let structural = Self::structural_query_from_lowered_select(select, authority)?;
let (_, plan) =
self.build_structural_plan_with_visible_indexes_for_authority(structural, authority)?;
let LogicalPlan::Scalar(scalar) = &plan.logical else {
return Ok(None);
};
let predicate_template = match scalar.predicate.as_ref() {
Some(predicate) => {
let Some(predicate_template) =
build_symbolic_scalar_predicate_template(sql_predicate, predicate)
else {
return Ok(None);
};
Some(predicate_template)
}
None => None,
};
let symbolic_value_candidates = parameter_contracts
.iter()
.zip(exemplar_bindings.iter())
.filter(|(contract, _)| contract.type_family() != PreparedSqlParameterTypeFamily::Bool)
.map(|(_, binding)| binding.clone())
.collect::<Vec<_>>();
if predicate_template.is_none()
&& scalar.predicate.as_ref().is_some_and(|predicate| {
predicate.contains_any_runtime_values(symbolic_value_candidates.as_slice())
})
{
return Ok(None);
}
let access_template = build_symbolic_scalar_access_path_template(
&plan.access,
parameter_contracts,
exemplar_bindings.as_slice(),
);
if access_template.is_none()
&& !symbolic_value_candidates.is_empty()
&& plan
.access
.contains_any_runtime_values(symbolic_value_candidates.as_slice())
{
return Ok(None);
}
if access_template.is_some()
&& prepared_statement_contains_template_literal_collision(
prepared.statement(),
exemplar_bindings.as_slice(),
)
{
return Ok(None);
}
if predicate_template.is_none() && access_template.is_none() {
return Ok(None);
}
let prepared_plan = SharedPreparedExecutionPlan::from_plan(authority, plan.clone());
let projection = Self::sql_select_projection_contract_from_shared_prepared_plan(
authority,
&prepared_plan,
);
Ok(Some(PreparedSqlExecutionTemplate::SymbolicScalar(
PreparedSqlSymbolicScalarTemplate {
authority,
projection,
plan,
predicate: predicate_template,
access: access_template,
},
)))
}
#[expect(clippy::too_many_lines)]
fn build_symbolic_grouped_prepared_sql_execution_template(
&self,
prepared: &PreparedSqlStatement,
parameter_contracts: &[PreparedSqlParameterContract],
authority: EntityAuthority,
) -> Result<Option<PreparedSqlExecutionTemplate>, QueryError> {
if parameter_contracts.is_empty() {
return Ok(None);
}
let SqlStatement::Select(statement) = prepared.statement() else {
return Ok(None);
};
let Some(exemplar_bindings) = parameter_contracts
.iter()
.map(prepared_sql_contract_exemplar_binding)
.collect::<Option<Vec<_>>>()
else {
return Ok(None);
};
let mut exemplar_bindings = exemplar_bindings;
apply_sql_range_exemplar_override(
statement.predicate.as_ref(),
parameter_contracts,
exemplar_bindings.as_mut_slice(),
authority.model(),
);
if prepared_statement_contains_template_literal_collision(
prepared.statement(),
exemplar_bindings.as_slice(),
) {
return Ok(None);
}
let bound_statement = prepared.bind_literals(exemplar_bindings.as_slice())?;
let normalized_prepared =
Self::prepare_sql_statement_for_authority(&bound_statement, authority)?;
let lowered =
lower_sql_command_from_prepared_statement(normalized_prepared, authority.model())
.map_err(QueryError::from_sql_lowering_error)?;
let Some(LoweredSqlQuery::Select(select)) = lowered.into_query() else {
return Ok(None);
};
let structural = Self::structural_query_from_lowered_select(select, authority)?;
let (_, plan) =
self.build_structural_plan_with_visible_indexes_for_authority(structural, authority)?;
let LogicalPlan::Grouped(grouped) = &plan.logical else {
return Ok(None);
};
let symbolic_access_candidates = parameter_contracts
.iter()
.zip(exemplar_bindings.iter())
.filter(|(contract, _)| contract.type_family() != PreparedSqlParameterTypeFamily::Bool)
.map(|(_, binding)| binding.clone())
.collect::<Vec<_>>();
let access_template = build_symbolic_scalar_access_path_template(
&plan.access,
parameter_contracts,
exemplar_bindings.as_slice(),
);
let predicate_template = match (
statement.predicate.as_ref(),
grouped.scalar.predicate.as_ref(),
) {
(Some(sql_predicate), Some(predicate)) => {
let Some(predicate_template) =
build_symbolic_scalar_predicate_template(sql_predicate, predicate)
else {
return Ok(None);
};
Some(predicate_template)
}
(Some(_), None) if access_template.is_some() => None,
(None, None) => None,
_ => return Ok(None),
};
if access_template.is_some()
&& statement
.predicate
.as_ref()
.is_some_and(sql_expr_is_compound_boolean_shape)
&& !(predicate_template.is_none()
&& prepared_sql_simple_range_slots(
statement.predicate.as_ref(),
authority.model(),
parameter_contracts,
)
.is_some())
{
return Ok(None);
}
if access_template.is_none()
&& !symbolic_access_candidates.is_empty()
&& plan
.access
.contains_any_runtime_values(symbolic_access_candidates.as_slice())
{
return Ok(None);
}
let having_template = match grouped.having_expr.as_ref() {
Some(having_expr) => {
let Some(having_template) = build_symbolic_grouped_expr_template(
having_expr,
parameter_contracts,
exemplar_bindings.as_slice(),
) else {
return Ok(None);
};
Some(having_template)
}
None => None,
};
if access_template.is_some()
&& prepared_statement_contains_template_literal_collision(
prepared.statement(),
exemplar_bindings.as_slice(),
)
{
return Ok(None);
}
if predicate_template.is_none() && access_template.is_none() && having_template.is_none() {
return Ok(None);
}
let prepared_plan = SharedPreparedExecutionPlan::from_plan(authority, plan.clone());
let projection = Self::sql_select_projection_contract_from_shared_prepared_plan(
authority,
&prepared_plan,
);
Ok(Some(PreparedSqlExecutionTemplate::SymbolicGrouped(
PreparedSqlSymbolicGroupedTemplate {
authority,
projection,
plan,
predicate: predicate_template,
access: access_template,
having_expr: having_template,
},
)))
}
fn execute_prepared_template_plan(
&self,
prepared_plan: SharedPreparedExecutionPlan,
projection: &SqlProjectionContract,
) -> Result<crate::db::session::sql::SqlStatementResult, QueryError> {
if prepared_plan.logical_plan().grouped_plan().is_some() {
let authority = prepared_plan.authority();
let plan = prepared_plan.logical_plan().clone();
let (columns, fixed_scales) = projection.clone().into_parts();
let page =
execute_initial_grouped_rows_for_canister(&self.db, self.debug, authority, plan)
.map_err(QueryError::execute)?;
let statement_result =
grouped_sql_statement_result_from_page(columns, fixed_scales, page)?;
Ok(statement_result)
} else {
let (columns, fixed_scales) = projection.clone().into_parts();
let projected =
execute_sql_projection_rows_for_canister(&self.db, self.debug, prepared_plan)
.map_err(QueryError::execute)?;
let (rows, row_count) = projected.into_parts();
let payload = SqlProjectionPayload::new(columns, fixed_scales, rows, row_count);
Ok(payload.into_statement_result())
}
}
fn execute_bound_prepared_sql_query_without_caches(
&self,
bound_statement: &SqlStatement,
authority: EntityAuthority,
) -> Result<crate::db::session::sql::SqlStatementResult, QueryError> {
let normalized_prepared =
Self::prepare_sql_statement_for_authority(bound_statement, authority)?;
let lowered =
lower_sql_command_from_prepared_statement(normalized_prepared, authority.model())
.map_err(QueryError::from_sql_lowering_error)?;
let Some(LoweredSqlQuery::Select(select)) = lowered.into_query() else {
return Err(QueryError::invariant(
"prepared SQL query fallback must lower to lowered SQL SELECT",
));
};
let structural = Self::structural_query_from_lowered_select(select, authority)?;
let (_, plan) =
self.build_structural_plan_with_visible_indexes_for_authority(structural, authority)?;
let prepared_plan = SharedPreparedExecutionPlan::from_plan(authority, plan);
let projection = Self::sql_select_projection_contract_from_shared_prepared_plan(
authority,
&prepared_plan,
);
self.execute_prepared_template_plan(prepared_plan, &projection)
}
}
#[derive(Clone, Debug)]
struct BoundPreparedTemplatePlan {
authority: EntityAuthority,
plan: AccessPlannedQuery,
}
impl BoundPreparedTemplatePlan {
#[must_use]
const fn new(authority: EntityAuthority, plan: AccessPlannedQuery) -> Self {
Self { authority, plan }
}
#[must_use]
const fn authority(&self) -> EntityAuthority {
self.authority
}
}
fn prepared_statement_contains_template_literal_collision(
statement: &SqlStatement,
template_bindings: &[Value],
) -> bool {
sql_statement_contains_any_literal(statement, template_bindings)
}
fn bind_prepared_template_plan(
mut plan: AccessPlannedQuery,
contracts: &[PreparedSqlParameterContract],
bindings: &[Value],
authority: EntityAuthority,
) -> Result<BoundPreparedTemplatePlan, QueryError> {
let replacements = contracts
.iter()
.filter_map(|contract| {
contract.template_binding().map(|template_binding| {
(
template_binding.clone(),
bindings
.get(contract.index())
.expect("validated binding vector must cover every contract index")
.clone(),
)
})
})
.collect::<Vec<_>>();
match &mut plan.logical {
LogicalPlan::Scalar(scalar) => {
scalar.predicate = scalar
.predicate
.take()
.map(|predicate| predicate.bind_template_values(replacements.as_slice()));
}
LogicalPlan::Grouped(grouped) => {
grouped.scalar.predicate = grouped
.scalar
.predicate
.take()
.map(|predicate| predicate.bind_template_values(replacements.as_slice()));
grouped.having_expr = grouped
.having_expr
.take()
.map(|expr| expr.bind_template_values(replacements.as_slice()));
}
}
plan.access = plan.access.bind_runtime_values(replacements.as_slice());
plan.finalize_planner_route_profile_for_model(authority.model());
plan.finalize_static_planning_shape_for_model(authority.model())
.map_err(QueryError::execute)?;
Ok(BoundPreparedTemplatePlan::new(authority, plan))
}
fn bind_symbolic_scalar_template_plan(
mut plan: AccessPlannedQuery,
predicate_template: Option<&PreparedSqlScalarPredicateTemplate>,
access_template: Option<&PreparedSqlScalarAccessPathTemplate>,
bindings: &[Value],
authority: EntityAuthority,
) -> Result<BoundPreparedTemplatePlan, QueryError> {
let LogicalPlan::Scalar(scalar) = &mut plan.logical else {
return Err(QueryError::unsupported_query(
"symbolic scalar prepared template expected scalar logical plan",
));
};
let bound_components = instantiate_symbolic_scalar_template_components(
predicate_template,
access_template,
bindings,
)?;
if let Some(predicate) = bound_components.predicate {
scalar.predicate = Some(predicate);
scalar.filter_expr = None;
}
if let Some(access) = bound_components.access {
plan.access = access;
}
finalize_bound_symbolic_template_plan(plan, authority)
}
fn bind_symbolic_grouped_template_plan(
mut plan: AccessPlannedQuery,
predicate_template: Option<&PreparedSqlScalarPredicateTemplate>,
access_template: Option<&PreparedSqlScalarAccessPathTemplate>,
having_template: Option<&PreparedSqlGroupedExprTemplate>,
bindings: &[Value],
authority: EntityAuthority,
) -> Result<BoundPreparedTemplatePlan, QueryError> {
let LogicalPlan::Grouped(grouped) = &mut plan.logical else {
return Err(QueryError::unsupported_query(
"symbolic grouped prepared template expected grouped logical plan",
));
};
let bound_components = instantiate_symbolic_scalar_template_components(
predicate_template,
access_template,
bindings,
)?;
if let Some(predicate) = bound_components.predicate {
grouped.scalar.predicate = Some(predicate);
grouped.scalar.filter_expr = None;
}
if let Some(access) = bound_components.access {
plan.access = access;
}
if let Some(having_template) = having_template {
let having_expr = instantiate_symbolic_grouped_expr_template(having_template, bindings)?;
grouped.having_expr = Some(having_expr);
}
finalize_bound_symbolic_template_plan(plan, authority)
}
struct BoundPreparedSqlSymbolicScalarComponents {
predicate: Option<crate::db::predicate::Predicate>,
access: Option<AccessPlan<Value>>,
}
fn instantiate_symbolic_scalar_template_components(
predicate_template: Option<&PreparedSqlScalarPredicateTemplate>,
access_template: Option<&PreparedSqlScalarAccessPathTemplate>,
bindings: &[Value],
) -> Result<BoundPreparedSqlSymbolicScalarComponents, QueryError> {
let predicate = predicate_template
.map(|template| instantiate_symbolic_scalar_predicate_template(template, bindings))
.transpose()?;
let access = access_template
.map(|template| instantiate_symbolic_scalar_access_path_template(template, bindings))
.transpose()?;
Ok(BoundPreparedSqlSymbolicScalarComponents { predicate, access })
}
fn finalize_bound_symbolic_template_plan(
mut plan: AccessPlannedQuery,
authority: EntityAuthority,
) -> Result<BoundPreparedTemplatePlan, QueryError> {
plan.finalize_planner_route_profile_for_model(authority.model());
plan.finalize_static_planning_shape_for_model(authority.model())
.map_err(QueryError::execute)?;
Ok(BoundPreparedTemplatePlan::new(authority, plan))
}
fn prepared_sql_contract_exemplar_binding(
contract: &PreparedSqlParameterContract,
) -> Option<Value> {
match contract.type_family() {
PreparedSqlParameterTypeFamily::Bool => Some(Value::Bool(false)),
PreparedSqlParameterTypeFamily::Numeric | PreparedSqlParameterTypeFamily::Text => {
contract.template_binding().cloned()
}
}
}
fn apply_sql_range_exemplar_override(
predicate: Option<&SqlExpr>,
contracts: &[PreparedSqlParameterContract],
exemplar_bindings: &mut [Value],
model: &'static EntityModel,
) {
let Some((field_kind, lower_slot, upper_slot)) =
prepared_sql_simple_range_slots(predicate, model, contracts)
else {
return;
};
let Some((lower_value, upper_value)) =
ordered_exemplar_range_values_for_field_kind(field_kind, lower_slot, upper_slot)
else {
return;
};
let Some(lower_binding) = exemplar_bindings.get_mut(lower_slot) else {
return;
};
*lower_binding = lower_value;
let Some(upper_binding) = exemplar_bindings.get_mut(upper_slot) else {
return;
};
*upper_binding = upper_value;
}
fn ordered_exemplar_range_values_for_field_kind(
field_kind: FieldKind,
lower_slot: usize,
upper_slot: usize,
) -> Option<(Value, Value)> {
let lower_offset = u64::try_from(lower_slot).ok()?;
let upper_offset = u64::try_from(upper_slot).ok()?;
match field_kind {
FieldKind::Int => {
let lower = i64::try_from(lower_offset).ok()?;
let upper = i64::try_from(upper_offset).ok()?.saturating_add(1);
Some((Value::Int(lower), Value::Int(upper)))
}
FieldKind::Int128 => Some((
Value::Int128((i128::from(lower_offset)).into()),
Value::Int128((i128::from(upper_offset).saturating_add(1)).into()),
)),
FieldKind::IntBig => Some((
Value::IntBig(crate::types::Int::from(i32::try_from(lower_offset).ok()?)),
Value::IntBig(crate::types::Int::from(
i32::try_from(upper_offset.saturating_add(1)).ok()?,
)),
)),
FieldKind::Uint => Some((
Value::Uint(lower_offset),
Value::Uint(upper_offset.saturating_add(1)),
)),
FieldKind::Uint128 => Some((
Value::Uint128((u128::from(lower_offset)).into()),
Value::Uint128((u128::from(upper_offset).saturating_add(1)).into()),
)),
FieldKind::UintBig => Some((
Value::UintBig(lower_offset.into()),
Value::UintBig(upper_offset.saturating_add(1).into()),
)),
FieldKind::Decimal { scale } => Some((
Value::Decimal(crate::types::Decimal::from_i128_with_scale(
i128::from(lower_offset),
scale,
)),
Value::Decimal(crate::types::Decimal::from_i128_with_scale(
i128::from(upper_offset).saturating_add(1),
scale,
)),
)),
FieldKind::Duration => Some((
Value::Duration(crate::types::Duration::from_millis(lower_offset)),
Value::Duration(crate::types::Duration::from_millis(
upper_offset.saturating_add(1),
)),
)),
FieldKind::Timestamp => Some((
Value::Timestamp(crate::types::Timestamp::from_millis(
i64::try_from(lower_offset).ok()?,
)),
Value::Timestamp(crate::types::Timestamp::from_millis(
i64::try_from(upper_offset).ok()?.saturating_add(1),
)),
)),
FieldKind::Text => Some((
Value::Text(format!("__icydb_prepared_range_lower_{lower_slot}__")),
Value::Text(format!("__icydb_prepared_range_upper_{upper_slot}__")),
)),
_ => None,
}
}
fn build_symbolic_scalar_access_path_template(
access: &AccessPlan<Value>,
parameter_contracts: &[PreparedSqlParameterContract],
exemplar_bindings: &[Value],
) -> Option<PreparedSqlScalarAccessPathTemplate> {
if let Some(key) = access.as_by_key_path() {
return Some(PreparedSqlScalarAccessPathTemplate::ByKey {
key: build_symbolic_scalar_access_value_template(
key,
parameter_contracts,
exemplar_bindings,
),
});
}
if let Some((start, end)) = access.as_primary_key_range_path() {
let start = build_symbolic_scalar_access_value_template(
start,
parameter_contracts,
exemplar_bindings,
);
let end = build_symbolic_scalar_access_value_template(
end,
parameter_contracts,
exemplar_bindings,
);
if matches!(start, PreparedSqlScalarAccessValueTemplate::Static(_))
&& matches!(end, PreparedSqlScalarAccessValueTemplate::Static(_))
{
return None;
}
return Some(PreparedSqlScalarAccessPathTemplate::KeyRange { start, end });
}
let (index, values) = access.as_index_prefix_path()?;
let mut had_slot = false;
let mut templates = Vec::with_capacity(values.len());
for value in values {
let template = build_symbolic_scalar_access_value_template(
value,
parameter_contracts,
exemplar_bindings,
);
had_slot |= matches!(
template,
PreparedSqlScalarAccessValueTemplate::SlotLiteral(_)
);
templates.push(template);
}
if !had_slot {
return None;
}
Some(PreparedSqlScalarAccessPathTemplate::IndexPrefix {
index: *index,
values: templates,
})
}
fn build_symbolic_scalar_access_value_template(
value: &Value,
parameter_contracts: &[PreparedSqlParameterContract],
exemplar_bindings: &[Value],
) -> PreparedSqlScalarAccessValueTemplate {
prepared_sql_slot_index_for_exemplar_value(value, parameter_contracts, exemplar_bindings)
.map_or_else(
|| PreparedSqlScalarAccessValueTemplate::Static(value.clone()),
PreparedSqlScalarAccessValueTemplate::SlotLiteral,
)
}
fn prepared_sql_slot_index_for_exemplar_value(
value: &Value,
parameter_contracts: &[PreparedSqlParameterContract],
exemplar_bindings: &[Value],
) -> Option<usize> {
let mut matching_slot = None;
for (contract, exemplar) in parameter_contracts.iter().zip(exemplar_bindings.iter()) {
if contract.type_family() == PreparedSqlParameterTypeFamily::Bool || exemplar != value {
continue;
}
if matching_slot.is_some() {
return None;
}
matching_slot = Some(contract.index());
}
matching_slot
}
fn instantiate_symbolic_scalar_access_path_template(
template: &PreparedSqlScalarAccessPathTemplate,
bindings: &[Value],
) -> Result<AccessPlan<Value>, QueryError> {
match template {
PreparedSqlScalarAccessPathTemplate::ByKey { key } => Ok(AccessPlan::by_key(
instantiate_symbolic_scalar_access_value_template(key, bindings)?,
)),
PreparedSqlScalarAccessPathTemplate::KeyRange { start, end } => Ok(AccessPlan::key_range(
instantiate_symbolic_scalar_access_value_template(start, bindings)?,
instantiate_symbolic_scalar_access_value_template(end, bindings)?,
)),
PreparedSqlScalarAccessPathTemplate::IndexPrefix { index, values } => {
let mut bound_values = Vec::with_capacity(values.len());
for value in values {
bound_values.push(instantiate_symbolic_scalar_access_value_template(
value, bindings,
)?);
}
Ok(AccessPlan::index_prefix(*index, bound_values))
}
}
}
fn instantiate_symbolic_scalar_access_value_template(
template: &PreparedSqlScalarAccessValueTemplate,
bindings: &[Value],
) -> Result<Value, QueryError> {
match template {
PreparedSqlScalarAccessValueTemplate::Static(value) => Ok(value.clone()),
PreparedSqlScalarAccessValueTemplate::SlotLiteral(slot_index) => {
bindings.get(*slot_index).cloned().ok_or_else(|| {
QueryError::unsupported_query(format!(
"missing prepared SQL binding at index={slot_index}",
))
})
}
}
}
fn build_symbolic_scalar_predicate_template(
sql_expr: &SqlExpr,
predicate: &crate::db::predicate::Predicate,
) -> Option<PreparedSqlScalarPredicateTemplate> {
predicate.build_prepared_template(sql_expr_prepared_predicate_template_shape(sql_expr)?)
}
fn instantiate_symbolic_scalar_predicate_template(
template: &PreparedSqlScalarPredicateTemplate,
bindings: &[Value],
) -> Result<crate::db::predicate::Predicate, QueryError> {
template.instantiate(bindings)
}
fn build_symbolic_grouped_expr_template(
expr: &crate::db::query::plan::expr::Expr,
parameter_contracts: &[PreparedSqlParameterContract],
exemplar_bindings: &[Value],
) -> Option<PreparedSqlGroupedExprTemplate> {
expr.build_prepared_grouped_template(&|value| {
prepared_sql_slot_index_for_exemplar_value(value, parameter_contracts, exemplar_bindings)
})
}
fn instantiate_symbolic_grouped_expr_template(
template: &PreparedSqlGroupedExprTemplate,
bindings: &[Value],
) -> Result<crate::db::query::plan::expr::Expr, QueryError> {
template.instantiate(bindings)
}
fn validate_parameter_bindings(
contracts: &[PreparedSqlParameterContract],
bindings: &[Value],
) -> Result<(), QueryError> {
if bindings.len() != contracts.len() {
return Err(QueryError::unsupported_query(format!(
"prepared SQL expected {} bindings, found {}",
contracts.len(),
bindings.len(),
)));
}
for contract in contracts {
let binding = bindings.get(contract.index()).ok_or_else(|| {
QueryError::unsupported_query(format!(
"missing prepared SQL binding at index={}",
contract.index(),
))
})?;
if !binding_matches_contract(binding, contract) {
return Err(QueryError::unsupported_query(format!(
"prepared SQL binding at index={} does not match the required {:?} contract",
contract.index(),
contract.type_family(),
)));
}
}
Ok(())
}
const fn binding_matches_contract(value: &Value, contract: &PreparedSqlParameterContract) -> bool {
if matches!(value, Value::Null) {
return contract.null_allowed();
}
match contract.type_family() {
PreparedSqlParameterTypeFamily::Numeric => matches!(
value,
Value::Int(_)
| Value::Int128(_)
| Value::IntBig(_)
| Value::Uint(_)
| Value::Uint128(_)
| Value::UintBig(_)
| Value::Float32(_)
| Value::Float64(_)
| Value::Decimal(_)
| Value::Duration(_)
| Value::Timestamp(_)
),
PreparedSqlParameterTypeFamily::Text => {
matches!(value, Value::Text(_) | Value::Enum(_))
}
PreparedSqlParameterTypeFamily::Bool => matches!(value, Value::Bool(_)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::db::query::plan::expr::Expr;
#[test]
fn grouped_expr_template_keeps_static_bool_literal_distinct_from_bool_exemplar_slot() {
let expr = Expr::Literal(Value::Bool(false));
let parameter_contracts = vec![PreparedSqlParameterContract::new(
0,
PreparedSqlParameterTypeFamily::Bool,
true,
None,
)];
let exemplar_bindings = vec![Value::Bool(false)];
let template =
build_symbolic_grouped_expr_template(&expr, ¶meter_contracts, &exemplar_bindings)
.expect("grouped literal template should build");
assert!(
matches!(
template,
PreparedSqlGroupedExprTemplate::Static(Expr::Literal(Value::Bool(false)))
),
"grouped static FALSE should stay static instead of rebinding through one unrelated bool exemplar slot",
);
}
}