use pest::iterators::Pair;
use selene_core::feature_register::FeatureId;
use crate::{
ast::{SessionResetTarget, SessionSetGraphTarget, Statement},
error::ParserError,
};
use super::{Rule, db_string_param, expr, first_child, span, unexpected_pair, unsupported_feature};
pub(super) fn build_session_command(pair: Pair<'_, Rule>) -> Result<Statement, ParserError> {
debug_assert_eq!(pair.as_rule(), Rule::session_command);
let inner = first_child(pair)?;
match inner.as_rule() {
Rule::session_set => build_session_set(inner),
Rule::session_reset => build_session_reset(inner),
Rule::session_close => Ok(Statement::SessionClose { span: span(&inner) }),
_ => Err(unexpected_pair(inner, "expected session-control statement")),
}
}
fn build_session_set(pair: Pair<'_, Rule>) -> Result<Statement, ParserError> {
let source_span = span(&pair);
let inner = pair
.into_inner()
.find(|child| {
matches!(
child.as_rule(),
Rule::session_set_binding_table_parameter
| Rule::session_set_graph_parameter
| Rule::session_set_graph
| Rule::session_set_time_zone
| Rule::session_set_value
)
})
.ok_or_else(|| ParserError::syntax("SESSION SET is missing a target", source_span, None))?;
match inner.as_rule() {
Rule::session_set_binding_table_parameter => {
build_session_set_binding_table_parameter(inner)
}
Rule::session_set_graph_parameter => build_session_set_graph_parameter(inner),
Rule::session_set_graph => build_session_set_graph(inner),
Rule::session_set_time_zone => build_session_set_time_zone(inner),
Rule::session_set_value => build_session_set_value(inner),
_ => Err(unexpected_pair(inner, "expected SESSION SET target")),
}
}
fn build_session_set_binding_table_parameter(
pair: Pair<'_, Rule>,
) -> Result<Statement, ParserError> {
let mut param_refs = 0;
let mut has_subquery = false;
for child in pair.clone().into_inner() {
match child.as_rule() {
Rule::param_ref => param_refs += 1,
Rule::value_subquery_expr => has_subquery = true,
_ => {}
}
}
let feature_id = if has_subquery {
FeatureId::GS10
} else if param_refs > 1 {
FeatureId::GS13
} else {
FeatureId::GS02
};
Err(unsupported_feature(
&pair,
feature_id,
"SESSION SET binding-table parameters are outside the current D1 claim",
))
}
fn build_session_set_graph_parameter(pair: Pair<'_, Rule>) -> Result<Statement, ParserError> {
let feature_id = if pair
.clone()
.into_inner()
.any(|child| child.as_rule() == Rule::session_current_graph)
{
FeatureId::GS01
} else {
FeatureId::GS12
};
Err(unsupported_feature(
&pair,
feature_id,
"SESSION SET graph parameters are outside the current D1 graph claim",
))
}
fn build_session_set_graph(pair: Pair<'_, Rule>) -> Result<Statement, ParserError> {
let source_span = span(&pair);
let target_pair = pair
.into_inner()
.find(|child| child.as_rule() == Rule::session_current_graph)
.ok_or_else(|| {
ParserError::syntax(
"SESSION SET GRAPH is missing a current graph expression",
source_span,
None,
)
})?;
let target = if target_pair
.as_str()
.eq_ignore_ascii_case("CURRENT_PROPERTY_GRAPH")
{
SessionSetGraphTarget::CurrentPropertyGraph
} else {
SessionSetGraphTarget::CurrentGraph
};
Ok(Statement::SessionSetGraph {
target,
span: source_span,
})
}
fn build_session_set_time_zone(pair: Pair<'_, Rule>) -> Result<Statement, ParserError> {
let source_span = span(&pair);
let string_pair = pair
.into_inner()
.find(|child| child.as_rule() == Rule::string_lit)
.ok_or_else(|| {
ParserError::syntax(
"SESSION SET TIME ZONE is missing a string",
source_span,
None,
)
})?;
let (zone, zone_source_kind) = expr::decode_string_text_with_kind(&string_pair)?;
Ok(Statement::SessionSetTimeZone {
zone,
zone_source_kind,
span: source_span,
})
}
fn build_session_set_value(pair: Pair<'_, Rule>) -> Result<Statement, ParserError> {
let source_span = span(&pair);
let mut if_not_exists = false;
let mut param = None;
let mut declared_type = None;
let mut value = None;
for child in pair.into_inner() {
match child.as_rule() {
Rule::session_value_kw => {}
Rule::if_not_exists => if_not_exists = true,
Rule::param_ref => param = Some(db_string_param(child)?),
Rule::session_value_declared_type => {
let type_pair = child
.into_inner()
.find(|inner| inner.as_rule() == Rule::type_name)
.ok_or_else(|| {
ParserError::syntax(
"SESSION SET VALUE declared type is missing type name",
source_span,
None,
)
})?;
declared_type = Some(expr::build_type_name(type_pair)?);
}
Rule::session_value_spec => {
value = Some(expr::build_value_expr(first_child(child)?)?);
}
Rule::session_value_subquery_expr => {
return Err(unsupported_feature(
&child,
FeatureId::GS11,
"SESSION SET VALUE subquery initializers are outside the current D1 claim",
));
}
Rule::session_value_simple_expr => {
return Err(unsupported_feature(
&child,
FeatureId::GS14,
"SESSION SET VALUE simple-expression initializers are outside the current D1 claim",
));
}
_ => return Err(unexpected_pair(child, "unexpected SESSION SET VALUE child")),
}
}
let param = param.ok_or_else(|| {
ParserError::syntax(
"SESSION SET VALUE is missing a parameter name",
source_span,
None,
)
})?;
let value = value.ok_or_else(|| {
ParserError::syntax(
"SESSION SET VALUE is missing a value expression",
source_span,
None,
)
})?;
Ok(Statement::SessionSetValue {
param,
declared_type,
value: Box::new(value),
if_not_exists,
span: source_span,
})
}
fn build_session_reset(pair: Pair<'_, Rule>) -> Result<Statement, ParserError> {
let source_span = span(&pair);
let Some(args) = pair
.into_inner()
.find(|child| child.as_rule() == Rule::session_reset_args)
else {
return Ok(Statement::SessionReset {
target: SessionResetTarget::AllCharacteristics,
span: source_span,
});
};
let inner = first_child(args)?;
let target = match inner.as_rule() {
Rule::session_reset_schema => {
return Err(unsupported_feature(
&inner,
FeatureId::GS05,
"SESSION RESET SCHEMA is outside the current catalog claim",
));
}
Rule::session_reset_graph => {
return Err(unsupported_feature(
&inner,
FeatureId::GS06,
"SESSION RESET GRAPH is outside the current D1 graph claim",
));
}
Rule::session_reset_time_zone => SessionResetTarget::TimeZone,
Rule::session_reset_all => reset_all_target(&inner),
Rule::session_reset_parameter => {
let param_pair = inner
.into_inner()
.find(|child| child.as_rule() == Rule::param_ref)
.ok_or_else(|| {
ParserError::syntax(
"SESSION RESET PARAMETER is missing a parameter name",
source_span,
None,
)
})?;
SessionResetTarget::Parameter(db_string_param(param_pair)?)
}
_ => return Err(unexpected_pair(inner, "unexpected SESSION RESET argument")),
};
Ok(Statement::SessionReset {
target,
span: source_span,
})
}
fn reset_all_target(pair: &Pair<'_, Rule>) -> SessionResetTarget {
if pair.as_str().to_ascii_uppercase().contains("PARAMETERS") {
SessionResetTarget::Parameters
} else {
SessionResetTarget::AllCharacteristics
}
}