use std::collections::HashMap;
use glaredb_error::{DbError, Result};
use serde::{Deserialize, Serialize};
use super::{AstParseable, Expr, Ident, ObjectReference};
use crate::keywords::Keyword;
use crate::meta::{AstMeta, Raw};
use crate::parser::Parser;
use crate::tokens::Token;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AttachType {
Database,
Table,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Attach<T: AstMeta> {
pub datasource_name: Ident,
pub attach_type: AttachType,
pub alias: T::ItemReference,
pub options: HashMap<Ident, Expr<T>>,
}
impl AstParseable for Attach<Raw> {
fn parse(parser: &mut Parser) -> Result<Self> {
parser.expect_keyword(Keyword::ATTACH)?;
let datasource_name = Ident::parse(parser)?;
let attach_type = match parser.next_keyword()? {
Keyword::DATABASE => AttachType::Database,
Keyword::TABLE => AttachType::Table,
other => {
return Err(DbError::new(format!(
"Expected DATABASE or TABLE for attach type, got '{other}'"
)));
}
};
parser.expect_keyword(Keyword::AS)?;
let alias = ObjectReference::parse(parser)?;
let mut options = HashMap::new();
if parser.consume_token(&Token::LeftParen) {
loop {
let key = match Ident::parse(parser) {
Ok(ident) => ident,
Err(_) => return Err(DbError::new("Expected identifier for option key")),
};
let val = Expr::parse(parser)?;
options.insert(key, val);
if parser.consume_token(&Token::RightParen) {
break;
}
parser.expect_token(&Token::Comma)?;
}
}
Ok(Attach {
datasource_name,
attach_type,
alias,
options,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Detach<T: AstMeta> {
pub attach_type: AttachType,
pub alias: T::ItemReference,
}
impl AstParseable for Detach<Raw> {
fn parse(parser: &mut Parser) -> Result<Self> {
parser.expect_keyword(Keyword::DETACH)?;
let attach_type = match parser.next_keyword()? {
Keyword::DATABASE => AttachType::Database,
Keyword::TABLE => AttachType::Table,
other => {
return Err(DbError::new(format!(
"Expected DATABASE or TABLE for attach type, got '{other}'"
)));
}
};
let alias = ObjectReference::parse(parser)?;
Ok(Detach { alias, attach_type })
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::ast::Literal;
use crate::ast::testutil::parse_ast;
#[test]
fn attach_pg_database() {
let got = parse_ast::<Attach<_>>("ATTACH POSTGRES DATABASE AS my_pg (CONNECTION_STRING 'postgres://sean:pass@localhost/db')").unwrap();
let expected = Attach {
datasource_name: Ident::new_unquoted("POSTGRES"),
attach_type: AttachType::Database,
alias: ObjectReference::from_strings(["my_pg"]),
options: [(
Ident::new_unquoted("CONNECTION_STRING"),
Expr::Literal(Literal::SingleQuotedString(
"postgres://sean:pass@localhost/db".to_string(),
)),
)]
.into_iter()
.collect(),
};
assert_eq!(expected, got);
}
#[test]
fn attach_pg_table() {
let got = parse_ast::<Attach<_>>("ATTACH POSTGRES TABLE AS my_pg_table (CONNECTION_STRING 'postgres://sean:pass@localhost/db', SCHEMA 'public', TABLE 'users')").unwrap();
let expected = Attach {
datasource_name: Ident::new_unquoted("POSTGRES"),
attach_type: AttachType::Table,
alias: ObjectReference::from_strings(["my_pg_table"]),
options: [
(
Ident::new_unquoted("CONNECTION_STRING"),
Expr::Literal(Literal::SingleQuotedString(
"postgres://sean:pass@localhost/db".to_string(),
)),
),
(
Ident::new_unquoted("SCHEMA"),
Expr::Literal(Literal::SingleQuotedString("public".to_string())),
),
(
Ident::new_unquoted("TABLE"),
Expr::Literal(Literal::SingleQuotedString("users".to_string())),
),
]
.into_iter()
.collect(),
};
assert_eq!(expected, got);
}
#[test]
fn detach_db() {
let got = parse_ast::<Detach<_>>("detach database my_pg").unwrap();
let expected = Detach {
attach_type: AttachType::Database,
alias: ObjectReference::from_strings(["my_pg"]),
};
assert_eq!(expected, got);
}
}