use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::fmt;
use crate::ast::{
ArchetypePredicate, LikeOperand, NodeNameConstraint, NodePredicate, PathPredicateOperand,
Primitive, SelectQuery, Terminal, ValueListItem,
};
use crate::lexer::Token;
use crate::visit::{
VisitMut, walk_node_predicate_mut, walk_path_predicate_operand_mut, walk_terminal_mut,
walk_value_list_item_mut,
};
#[derive(Clone, Default)]
pub struct Parameters {
supplied: BTreeMap<String, Supplied>,
}
#[derive(Clone)]
enum Supplied {
Value(Primitive),
Refused(FaultKind),
}
impl Parameters {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, name: &str, value: Primitive) {
self.supply(name, Supplied::Value(value));
}
pub fn insert_not_a_literal(&mut self, name: &str) {
self.supply(name, Supplied::Refused(FaultKind::NotALiteral));
}
pub fn names(&self) -> impl Iterator<Item = &str> {
self.supplied.keys().map(String::as_str)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.supplied.is_empty()
}
fn supply(&mut self, name: &str, entry: Supplied) {
let name = name.strip_prefix('$').unwrap_or(name).to_owned();
match self.supplied.entry(name) {
Entry::Occupied(mut held) => {
held.insert(Supplied::Refused(FaultKind::Duplicate));
}
Entry::Vacant(free) => {
free.insert(entry);
}
}
}
fn get(&self, name: &str) -> Option<&Supplied> {
self.supplied.get(name)
}
}
impl fmt::Debug for Parameters {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_set().entries(self.names()).finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Position {
Value,
Pattern,
NodeName,
ArchetypeId,
NodeId,
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Value => "a literal value",
Self::Pattern => "a LIKE pattern string",
Self::NodeName => "a node name string",
Self::ArchetypeId => "an archetype identifier",
Self::NodeId => "a node code or archetype identifier",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FaultKind {
Unbound,
Unknown,
Duplicate,
NotALiteral,
Unbindable(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Fault {
pub name: String,
pub kind: FaultKind,
}
impl fmt::Display for Fault {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = &self.name;
match self.kind {
FaultKind::Unbound => write!(f, "parameter `${name}` has no value"),
FaultKind::Unknown => write!(f, "parameter `{name}` is not used by the query"),
FaultKind::Duplicate => write!(f, "parameter `{name}` is supplied more than once"),
FaultKind::NotALiteral => {
write!(
f,
"parameter `{name}` is not a string, number, boolean or null"
)
}
FaultKind::Unbindable(position) => {
write!(f, "parameter `${name}` stands where {position} is required")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("{}", faults.iter().map(ToString::to_string).collect::<Vec<_>>().join("; "))]
pub struct BindError {
pub faults: Vec<Fault>,
}
pub fn bind(query: &mut SelectQuery, parameters: &Parameters) -> Result<(), BindError> {
let mut bound = query.clone();
let mut binder = Binder {
parameters,
used: Vec::new(),
faults: Vec::new(),
};
binder.visit_select_query_mut(&mut bound);
let Binder {
used, mut faults, ..
} = binder;
for name in parameters.names() {
if !used.iter().any(|u| u == name) {
push_fault(&mut faults, name, FaultKind::Unknown);
}
}
if faults.is_empty() {
*query = bound;
Ok(())
} else {
Err(BindError { faults })
}
}
struct Binder<'p> {
parameters: &'p Parameters,
used: Vec<String>,
faults: Vec<Fault>,
}
impl Binder<'_> {
fn literal(&mut self, raw: &str, position: Position) -> Option<Primitive> {
let name = raw.strip_prefix('$').unwrap_or(raw);
if !self.used.iter().any(|u| u == name) {
self.used.push(name.to_owned());
}
let value = match self.parameters.get(name) {
None => Err(FaultKind::Unbound),
Some(Supplied::Refused(kind)) => Err(*kind),
Some(Supplied::Value(value)) => fits(value, position)
.then(|| value.clone())
.ok_or(FaultKind::Unbindable(position)),
};
value
.map_err(|kind| push_fault(&mut self.faults, name, kind))
.ok()
}
fn text(&mut self, raw: &str, position: Position) -> Option<String> {
match self.literal(raw, position)? {
Primitive::String(text) => Some(text),
_ => None,
}
}
}
fn fits(value: &Primitive, position: Position) -> bool {
match (position, value) {
(Position::Value, Primitive::Real(real)) => real.is_finite(),
(Position::Value, _) | (Position::Pattern | Position::NodeName, Primitive::String(_)) => {
true
}
(Position::ArchetypeId, Primitive::String(text)) => {
matches!(sole_token(text), Some(Token::ArchetypeHrid(_)))
}
(Position::NodeId, Primitive::String(text)) => matches!(
sole_token(text),
Some(Token::ArchetypeHrid(_) | Token::IdCode(_) | Token::AtCode(_))
),
_ => false,
}
}
fn sole_token(text: &str) -> Option<Token> {
let stream = crate::lexer::lex_spanned(text).ok()?;
match (stream.tokens(), stream.spans()) {
([token], [span]) if *span == (0..text.len()) => Some(token.clone()),
_ => None,
}
}
fn push_fault(faults: &mut Vec<Fault>, name: &str, kind: FaultKind) {
if !faults.iter().any(|f| f.name == name && f.kind == kind) {
faults.push(Fault {
name: name.to_owned(),
kind,
});
}
}
impl VisitMut for Binder<'_> {
fn visit_terminal_mut(&mut self, node: &mut Terminal) {
if let Terminal::Parameter(raw) = node
&& let Some(value) = self.literal(raw, Position::Value)
{
*node = Terminal::Primitive(value);
}
walk_terminal_mut(self, node);
}
fn visit_path_predicate_operand_mut(&mut self, node: &mut PathPredicateOperand) {
if let PathPredicateOperand::Parameter(raw) = node
&& let Some(value) = self.literal(raw, Position::Value)
{
*node = PathPredicateOperand::Primitive(value);
}
walk_path_predicate_operand_mut(self, node);
}
fn visit_value_list_item_mut(&mut self, node: &mut ValueListItem) {
if let ValueListItem::Parameter(raw) = node
&& let Some(value) = self.literal(raw, Position::Value)
{
*node = ValueListItem::Primitive(value);
}
walk_value_list_item_mut(self, node);
}
fn visit_like_operand_mut(&mut self, node: &mut LikeOperand) {
if let LikeOperand::Parameter(raw) = node
&& let Some(text) = self.text(raw, Position::Pattern)
{
*node = LikeOperand::String(text);
}
}
fn visit_node_name_constraint_mut(&mut self, node: &mut NodeNameConstraint) {
if let NodeNameConstraint::Parameter(raw) = node
&& let Some(text) = self.text(raw, Position::NodeName)
{
*node = NodeNameConstraint::String(text);
}
}
fn visit_archetype_predicate_mut(&mut self, node: &mut ArchetypePredicate) {
if let ArchetypePredicate::Parameter(raw) = node
&& let Some(hrid) = self.text(raw, Position::ArchetypeId)
{
*node = ArchetypePredicate::Hrid(hrid);
}
}
fn visit_node_predicate_mut(&mut self, node: &mut NodePredicate) {
if let NodePredicate::Parameter(raw) = node
&& let Some(text) = self.text(raw, Position::NodeId)
{
*node = match sole_token(&text) {
Some(Token::ArchetypeHrid(hrid)) => NodePredicate::Archetype { hrid, name: None },
_ => NodePredicate::Code {
code: text,
name: None,
},
};
}
walk_node_predicate_mut(self, node);
}
}