boxdb 0.3.2

An extended SQLite client with typed columns and readable storage
Documentation
use super::*;
use serde_saphyr::FlowSeq;

#[derive(Default, Serialize)]
pub(super) struct Constraint {
    #[serde(skip_serializing_if = "Option::is_none")]
    name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    primary_key: Option<FlowSeq<Vec<Key>>>,
    #[serde(skip_serializing_if = "std::ops::Not::not")]
    autoincrement: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    unique: Option<FlowSeq<Vec<Key>>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    check: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    foreign_key: Option<FlowSeq<Vec<String>>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    references: Option<Reference>,
    #[serde(skip_serializing_if = "Option::is_none")]
    on_delete: Option<ReferentialAction>,
    #[serde(skip_serializing_if = "Option::is_none")]
    on_update: Option<ReferentialAction>,
    #[serde(rename = "match", skip_serializing_if = "Option::is_none")]
    match_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    deferrable: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    initially: Option<Initially>,
    #[serde(skip_serializing_if = "Option::is_none")]
    on_conflict: Option<Conflict>,
}
#[derive(Serialize)]
struct Reference {
    table: String,
    #[serde(skip_serializing_if = "Vec::is_empty", serialize_with = "flow")]
    columns: Vec<String>,
}
#[derive(Serialize)]
#[serde(untagged)]
enum Key {
    Column(String),
    Ordered(KeyOptions),
}
#[derive(Serialize)]
struct KeyOptions {
    column: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    collate: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    order: Option<Order>,
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
enum Order {
    Asc,
    Desc,
}
fn keys(sql: &mut Sql) -> Result<(Vec<Key>, bool)> {
    let body = sql.group()?;
    let mut autoincrement = false;
    let mut keys = vec![];
    for tokens in split(&body) {
        if autoincrement {
            return Err(invalid("AUTOINCREMENT must follow the last key"));
        }
        let mut sql = Sql {
            tokens: tokens.to_vec(),
            pos: 0,
        };
        let column = sql.name()?;
        let collate = if sql.take("COLLATE") {
            Some(sql.name()?)
        } else {
            None
        };
        let order = if sql.take("ASC") {
            Some(Order::Asc)
        } else if sql.take("DESC") {
            Some(Order::Desc)
        } else {
            None
        };
        autoincrement = sql.take("AUTOINCREMENT");
        if sql.pos != sql.tokens.len() {
            return Err(invalid("unexpected constraint key clause"));
        }
        keys.push(if collate.is_none() && order.is_none() {
            Key::Column(column)
        } else {
            Key::Ordered(KeyOptions {
                column,
                collate,
                order,
            })
        });
    }
    Ok((keys, autoincrement))
}
fn flow<S: Serializer>(columns: &[String], s: S) -> std::result::Result<S::Ok, S::Error> {
    FlowSeq(columns).serialize(s)
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
enum ReferentialAction {
    Cascade,
    Restrict,
    SetNull,
    SetDefault,
    NoAction,
}
impl ReferentialAction {
    fn parse(sql: &mut Sql) -> Result<Self> {
        if sql.take("CASCADE") {
            Ok(Self::Cascade)
        } else if sql.take("RESTRICT") {
            Ok(Self::Restrict)
        } else if sql.take("SET") {
            if sql.take("NULL") {
                Ok(Self::SetNull)
            } else {
                sql.expect("DEFAULT")?;
                Ok(Self::SetDefault)
            }
        } else {
            sql.expect("NO")?;
            sql.expect("ACTION")?;
            Ok(Self::NoAction)
        }
    }
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
enum Initially {
    Deferred,
    Immediate,
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
enum Conflict {
    Rollback,
    Abort,
    Fail,
    Ignore,
    Replace,
}
impl Conflict {
    fn parse(sql: &mut Sql) -> Result<Self> {
        for (word, value) in [
            ("ROLLBACK", Self::Rollback),
            ("ABORT", Self::Abort),
            ("FAIL", Self::Fail),
            ("IGNORE", Self::Ignore),
            ("REPLACE", Self::Replace),
        ] {
            if sql.take(word) {
                return Ok(value);
            }
        }
        Err(invalid("invalid ON CONFLICT action"))
    }
}
fn names(sql: &mut Sql) -> Result<Vec<String>> {
    let body = sql.group()?;
    split(&body)
        .iter()
        .map(|part| {
            if part.len() != 1 {
                return Err(invalid("expected a column name"));
            }
            identifier(&part[0])
        })
        .collect()
}
impl Constraint {
    pub(super) fn parse(tokens: &[Token]) -> Result<Self> {
        let mut sql = Sql {
            tokens: tokens.to_vec(),
            pos: 0,
        };
        let mut value = Self::default();
        if sql.take("CONSTRAINT") {
            value.name = Some(sql.name()?);
        }
        if sql.take("PRIMARY") {
            sql.expect("KEY")?;
            let (keys, autoincrement) = keys(&mut sql)?;
            value.primary_key = Some(FlowSeq(keys));
            value.autoincrement = autoincrement;
        } else if sql.take("UNIQUE") {
            let (keys, autoincrement) = keys(&mut sql)?;
            if autoincrement {
                return Err(invalid("AUTOINCREMENT requires a primary key"));
            }
            value.unique = Some(FlowSeq(keys));
        } else if sql.take("CHECK") {
            value.check = Some(render(&sql.group()?));
        } else {
            sql.expect("FOREIGN")?;
            sql.expect("KEY")?;
            value.foreign_key = Some(FlowSeq(names(&mut sql)?));
            sql.expect("REFERENCES")?;
            value.references = Some(Reference {
                table: sql.name()?,
                columns: if sql.tokens.get(sql.pos) == Some(&Token::LParen) {
                    names(&mut sql)?
                } else {
                    vec![]
                },
            });
        }
        while sql.pos < sql.tokens.len() {
            if sql.take("ON") {
                if sql.take("CONFLICT") {
                    value.on_conflict = Some(Conflict::parse(&mut sql)?);
                } else if sql.take("DELETE") {
                    value.on_delete = Some(ReferentialAction::parse(&mut sql)?);
                } else {
                    sql.expect("UPDATE")?;
                    value.on_update = Some(ReferentialAction::parse(&mut sql)?);
                }
            } else if sql.take("MATCH") {
                value.match_name = Some(sql.name()?);
            } else if sql.take("DEFERRABLE") {
                value.deferrable = Some(true);
            } else if sql.take("NOT") {
                sql.expect("DEFERRABLE")?;
                value.deferrable = Some(false);
            } else if sql.take("INITIALLY") {
                value.initially = Some(if sql.take("DEFERRED") {
                    Initially::Deferred
                } else {
                    sql.expect("IMMEDIATE")?;
                    Initially::Immediate
                });
            } else {
                return Err(invalid("unsupported constraint clause"));
            }
        }
        Ok(value)
    }
}