mod execute;
mod explain;
mod projection;
use crate::{
db::{
DbSession, GroupedRow, PersistedRow, QueryError,
executor::EntityAuthority,
query::{
intent::StructuralQuery,
plan::{AccessPlannedQuery, VisibleIndexes},
},
sql::parser::{SqlStatement, parse_sql},
},
traits::{CanisterKind, EntityValue},
};
#[cfg(test)]
use crate::db::{
MissingRowPolicy, PagedGroupedExecutionWithTrace,
sql::lowering::{
LoweredSelectQueryShape, bind_lowered_sql_query, lower_sql_command_from_prepared_statement,
prepare_sql_statement,
},
};
#[cfg(feature = "structural-read-metrics")]
pub use crate::db::session::sql::projection::{
SqlProjectionMaterializationMetrics, with_sql_projection_materialization_metrics,
};
#[cfg(feature = "perf-attribution")]
pub use crate::db::{
session::sql::execute::LoweredSqlStatementExecutorAttribution,
session::sql::projection::SqlProjectionTextExecutorAttribution,
};
#[derive(Debug)]
pub enum SqlStatementResult {
Count {
row_count: u32,
},
Projection {
columns: Vec<String>,
rows: Vec<Vec<crate::value::Value>>,
row_count: u32,
},
ProjectionText {
columns: Vec<String>,
rows: Vec<Vec<String>>,
row_count: u32,
},
Grouped {
columns: Vec<String>,
rows: Vec<GroupedRow>,
row_count: u32,
next_cursor: Option<String>,
},
Explain(String),
Describe(crate::db::EntitySchemaDescription),
ShowIndexes(Vec<String>),
ShowColumns(Vec<crate::db::EntityFieldDescription>),
ShowEntities(Vec<String>),
}
pub(in crate::db) fn parse_sql_statement(sql: &str) -> Result<SqlStatement, QueryError> {
parse_sql(sql).map_err(QueryError::from_sql_parse_error)
}
impl<C: CanisterKind> DbSession<C> {
pub(in crate::db::session::sql) fn build_structural_plan_with_visible_indexes_for_authority(
&self,
query: StructuralQuery,
authority: EntityAuthority,
) -> Result<(VisibleIndexes<'_>, AccessPlannedQuery), QueryError> {
let visible_indexes =
self.visible_indexes_for_store_model(authority.store_path(), authority.model())?;
let plan = query.build_plan_with_visible_indexes(&visible_indexes)?;
Ok((visible_indexes, plan))
}
fn ensure_sql_query_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
match statement {
SqlStatement::Select(_)
| SqlStatement::Explain(_)
| SqlStatement::Describe(_)
| SqlStatement::ShowIndexes(_)
| SqlStatement::ShowColumns(_)
| SqlStatement::ShowEntities(_) => Ok(()),
SqlStatement::Insert(_) => Err(QueryError::unsupported_query(
"execute_sql_query rejects INSERT; use execute_sql_update::<E>()",
)),
SqlStatement::Update(_) => Err(QueryError::unsupported_query(
"execute_sql_query rejects UPDATE; use execute_sql_update::<E>()",
)),
SqlStatement::Delete(_) => Err(QueryError::unsupported_query(
"execute_sql_query rejects DELETE; use execute_sql_update::<E>()",
)),
}
}
fn ensure_sql_update_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
match statement {
SqlStatement::Insert(_) | SqlStatement::Update(_) | SqlStatement::Delete(_) => Ok(()),
SqlStatement::Select(_) => Err(QueryError::unsupported_query(
"execute_sql_update rejects SELECT; use execute_sql_query::<E>()",
)),
SqlStatement::Explain(_) => Err(QueryError::unsupported_query(
"execute_sql_update rejects EXPLAIN; use execute_sql_query::<E>()",
)),
SqlStatement::Describe(_) => Err(QueryError::unsupported_query(
"execute_sql_update rejects DESCRIBE; use execute_sql_query::<E>()",
)),
SqlStatement::ShowIndexes(_) => Err(QueryError::unsupported_query(
"execute_sql_update rejects SHOW INDEXES; use execute_sql_query::<E>()",
)),
SqlStatement::ShowColumns(_) => Err(QueryError::unsupported_query(
"execute_sql_update rejects SHOW COLUMNS; use execute_sql_query::<E>()",
)),
SqlStatement::ShowEntities(_) => Err(QueryError::unsupported_query(
"execute_sql_update rejects SHOW ENTITIES; use execute_sql_query::<E>()",
)),
}
}
pub fn execute_sql_query<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
where
E: PersistedRow<Canister = C> + EntityValue,
{
let parsed = parse_sql_statement(sql)?;
Self::ensure_sql_query_statement_supported(&parsed)?;
self.execute_sql_statement_inner::<E>(&parsed)
}
pub fn execute_sql_update<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
where
E: PersistedRow<Canister = C> + EntityValue,
{
let parsed = parse_sql_statement(sql)?;
Self::ensure_sql_update_statement_supported(&parsed)?;
self.execute_sql_statement_inner::<E>(&parsed)
}
#[cfg(test)]
pub(in crate::db) fn execute_grouped_sql_query_for_tests<E>(
&self,
sql: &str,
cursor_token: Option<&str>,
) -> Result<PagedGroupedExecutionWithTrace, QueryError>
where
E: PersistedRow<Canister = C> + EntityValue,
{
let parsed = parse_sql_statement(sql)?;
let lowered = lower_sql_command_from_prepared_statement(
prepare_sql_statement(parsed, E::MODEL.name())
.map_err(QueryError::from_sql_lowering_error)?,
E::MODEL.primary_key.name,
)
.map_err(QueryError::from_sql_lowering_error)?;
let Some(query) = lowered.query().cloned() else {
return Err(QueryError::unsupported_query(
"grouped SELECT helper requires grouped SELECT",
));
};
if query.select_shape() != Some(LoweredSelectQueryShape::Grouped) {
return Err(QueryError::unsupported_query(
"grouped SELECT helper requires grouped SELECT",
));
}
let query = bind_lowered_sql_query::<E>(query, MissingRowPolicy::Ignore)
.map_err(QueryError::from_sql_lowering_error)?;
self.execute_grouped(&query, cursor_token)
}
}