use crate::{
expr::{Column, DynExpr, Order, Projection, RecordLink, SurrealQL},
types::{SurrealEdge, SurrealRecord, Thing},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Returning {
None,
Nothing,
Before,
After,
Diff,
}
impl Returning {
fn render(self, buf: &mut String) {
match self {
Returning::None => {}
Returning::Nothing => buf.push_str(" RETURN NONE"),
Returning::Before => buf.push_str(" RETURN BEFORE"),
Returning::After => buf.push_str(" RETURN AFTER"),
Returning::Diff => buf.push_str(" RETURN DIFF"),
}
}
}
enum Target {
Table(&'static str),
Record(RecordLink),
}
impl Target {
fn render(&self, buf: &mut String) {
match self {
Target::Table(t) => buf.push_str(t),
Target::Record(r) => r.render_dyn(buf),
}
}
}
pub struct Table<T: SurrealRecord> {
_marker: std::marker::PhantomData<T>,
}
impl<T: SurrealRecord> Table<T> {
pub fn new() -> Self {
Self {
_marker: std::marker::PhantomData,
}
}
pub fn select(self, _cols: crate::expr::ColumnSet<T>) -> Select<T> {
Select::bare()
}
pub fn project(self, fields: Vec<Projection>) -> Select<T> {
let mut s = Select::bare();
s.projections = fields;
s
}
pub fn count(self, _field: &str) -> Select<T> {
let mut s = Select::bare();
s.count = true;
s.group_all = true;
s
}
pub fn insert(self) -> Insert<T> {
Insert {
data: Vec::new(),
return_fields: vec![],
}
}
pub fn create(self) -> Create<T> {
Create::for_table()
}
pub fn update(self) -> Update<T> {
Update::for_table()
}
pub fn delete(self) -> Delete<T> {
Delete::for_table()
}
}
impl<T: SurrealRecord> Default for Table<T> {
fn default() -> Self {
Self::new()
}
}
pub struct Select<T: SurrealRecord> {
_marker: std::marker::PhantomData<T>,
projections: Vec<Projection>,
filter: Option<Box<dyn DynExpr>>,
order: Vec<(String, Order)>,
limit: Option<u32>,
start: u32,
fetch: Vec<String>,
group_by: Vec<String>,
group_all: bool,
count: bool,
count_alias: Option<&'static str>,
}
impl<T: SurrealRecord> Select<T> {
fn bare() -> Self {
Select {
_marker: std::marker::PhantomData,
projections: Vec::new(),
filter: None,
order: Vec::new(),
limit: None,
start: 0,
fetch: Vec::new(),
group_by: Vec::new(),
group_all: false,
count: false,
count_alias: None,
}
}
pub fn filter(mut self, expr: impl DynExpr + 'static) -> Self {
self.filter = Some(Box::new(expr));
self
}
pub fn limit(mut self, n: u32) -> Self {
self.limit = Some(n);
self
}
pub fn start(mut self, n: u32) -> Self {
self.start = n;
self
}
pub fn fetch(mut self, field: &str) -> Self {
self.fetch.push(field.to_string());
self
}
pub fn group_by<C: DynExpr>(mut self, col: C) -> Self {
let mut buf = String::new();
col.render_dyn(&mut buf);
self.group_by.push(buf);
self
}
pub fn group_all(mut self) -> Self {
self.group_all = true;
self
}
pub fn count_as(mut self, alias: &'static str) -> Self {
self.count = true;
self.count_alias = Some(alias);
self
}
pub fn order_by<C: DynExpr>(mut self, col: C, dir: Order) -> Self {
let mut buf = String::new();
col.render_dyn(&mut buf);
self.order.push((buf, dir));
self
}
pub fn order_asc<C: DynExpr>(self, col: C) -> Self {
self.order_by(col, Order::Asc)
}
pub fn order_desc<C: DynExpr>(self, col: C) -> Self {
self.order_by(col, Order::Desc)
}
fn render_select_list(&self, q: &mut String) {
if self.count {
q.push_str("count()");
if let Some(a) = self.count_alias {
q.push_str(" AS ");
q.push_str(a);
}
} else if self.projections.is_empty() {
q.push('*');
} else {
for (i, p) in self.projections.iter().enumerate() {
if i > 0 {
q.push_str(", ");
}
p.render(q);
}
}
}
pub fn to_surrealql(&self) -> String {
let mut q = String::from("SELECT ");
self.render_select_list(&mut q);
q.push_str(" FROM ");
q.push_str(T::table_name());
if let Some(ref f) = self.filter {
q.push_str(" WHERE ");
f.render_dyn(&mut q);
}
for (i, (col, dir)) in self.order.iter().enumerate() {
if i == 0 {
q.push_str(" ORDER BY ");
} else {
q.push_str(", ");
}
q.push_str(&format!("{col} {dir}"));
}
for (i, g) in self.group_by.iter().enumerate() {
if i == 0 {
q.push_str(" GROUP BY ");
} else {
q.push_str(", ");
}
q.push_str(g);
}
if self.group_all {
q.push_str(" GROUP ALL");
}
if self.start > 0 {
q.push_str(&format!(" START {}", self.start));
}
if let Some(n) = self.limit {
q.push_str(&format!(" LIMIT {n}"));
}
for f in &self.fetch {
q.push_str(&format!(" FETCH {f}"));
}
q
}
}
impl<T: SurrealRecord> std::fmt::Display for Select<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}
pub struct Insert<T: SurrealRecord> {
data: Vec<T>,
return_fields: Vec<&'static str>,
}
impl<T: SurrealRecord> Insert<T> {
pub fn content(mut self, record: T) -> Self {
self.data.push(record);
self
}
pub fn return_field(mut self, field: &'static str) -> Self {
self.return_fields.push(field);
self
}
pub fn data(&self) -> &[T] {
&self.data
}
pub fn to_surrealql(&self) -> String {
let returning = if self.return_fields.is_empty() {
""
} else {
" RETURN AFTER"
};
format!("INSERT INTO {} $data{}", T::table_name(), returning)
}
}
enum SetVal {
Assign(String, String),
Merge(String),
Content(String),
}
pub struct Update<T: SurrealRecord> {
_marker: std::marker::PhantomData<T>,
target: Target,
filter: Option<Box<dyn DynExpr>>,
sets: Vec<SetVal>,
returning: Returning,
}
impl<T: SurrealRecord> Update<T> {
pub(crate) fn for_table() -> Self {
Self {
_marker: std::marker::PhantomData,
target: Target::Table(T::table_name()),
filter: None,
sets: Vec::new(),
returning: Returning::None,
}
}
pub fn record<V: SurrealQL>(mut self, id: V) -> Self {
self.target = Target::Record(RecordLink::new(T::table_name(), id));
self
}
pub fn filter(mut self, expr: impl DynExpr + 'static) -> Self {
self.filter = Some(Box::new(expr));
self
}
pub fn set<C: SurrealQL>(mut self, col: Column<T, C>, value: C) -> Self {
let mut buf = String::new();
C::render_literal(&value, &mut buf);
self.sets.push(SetVal::Assign(col.name.to_string(), buf));
self
}
pub fn set_lit<C: SurrealQL>(mut self, col: &str, value: C) -> Self {
let mut buf = String::new();
C::render_literal(&value, &mut buf);
self.sets.push(SetVal::Assign(col.to_string(), buf));
self
}
pub fn set_expr(mut self, col: &str, expr: impl DynExpr) -> Self {
let mut buf = String::new();
expr.render_dyn(&mut buf);
self.sets.push(SetVal::Assign(col.to_string(), buf));
self
}
pub fn set_raw(mut self, col: &str, raw: impl Into<String>) -> Self {
self.sets.push(SetVal::Assign(col.to_string(), raw.into()));
self
}
pub fn merge(mut self, expr: impl DynExpr) -> Self {
let mut buf = String::new();
expr.render_dyn(&mut buf);
self.sets.push(SetVal::Merge(buf));
self
}
pub fn content(mut self, expr: impl DynExpr) -> Self {
let mut buf = String::new();
expr.render_dyn(&mut buf);
self.sets.push(SetVal::Content(buf));
self
}
pub fn returning(mut self, r: Returning) -> Self {
self.returning = r;
self
}
pub fn to_surrealql(&self) -> String {
let mut q = String::from("UPDATE ");
self.target.render(&mut q);
let mut set_pairs = Vec::new();
let mut merge_clause = None;
let mut content_clause = None;
for s in &self.sets {
match s {
SetVal::Assign(k, v) => set_pairs.push(format!("{k} = {v}")),
SetVal::Merge(v) => merge_clause = Some(v.clone()),
SetVal::Content(v) => content_clause = Some(v.clone()),
}
}
if let Some(c) = content_clause {
q.push_str(" CONTENT ");
q.push_str(&c);
} else if let Some(m) = merge_clause {
q.push_str(" MERGE ");
q.push_str(&m);
} else if !set_pairs.is_empty() {
q.push_str(" SET ");
q.push_str(&set_pairs.join(", "));
}
if let Some(ref f) = self.filter {
q.push_str(" WHERE ");
f.render_dyn(&mut q);
}
self.returning.render(&mut q);
q
}
}
impl<T: SurrealRecord> std::fmt::Display for Update<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}
enum CreateBody {
Content(String),
Set(Vec<(String, String)>),
}
pub struct Create<T: SurrealRecord> {
_marker: std::marker::PhantomData<T>,
target: Target,
body: CreateBody,
returning: Returning,
}
impl<T: SurrealRecord> Create<T> {
pub(crate) fn for_table() -> Self {
Self {
_marker: std::marker::PhantomData,
target: Target::Table(T::table_name()),
body: CreateBody::Set(Vec::new()),
returning: Returning::None,
}
}
pub fn record<V: SurrealQL>(mut self, id: V) -> Self {
self.target = Target::Record(RecordLink::new(T::table_name(), id));
self
}
pub fn content(mut self, expr: impl DynExpr) -> Self {
let mut buf = String::new();
expr.render_dyn(&mut buf);
self.body = CreateBody::Content(buf);
self
}
pub fn set_lit<C: SurrealQL>(mut self, col: &str, value: C) -> Self {
let mut buf = String::new();
C::render_literal(&value, &mut buf);
self.push_set(col, buf);
self
}
pub fn set_expr(mut self, col: &str, expr: impl DynExpr) -> Self {
let mut buf = String::new();
expr.render_dyn(&mut buf);
self.push_set(col, buf);
self
}
pub fn set_raw(mut self, col: &str, raw: impl Into<String>) -> Self {
self.push_set(col, raw.into());
self
}
fn push_set(&mut self, col: &str, rendered: String) {
match &mut self.body {
CreateBody::Set(v) => v.push((col.to_string(), rendered)),
CreateBody::Content(_) => {
self.body = CreateBody::Set(vec![(col.to_string(), rendered)]);
}
}
}
pub fn returning(mut self, r: Returning) -> Self {
self.returning = r;
self
}
pub fn to_surrealql(&self) -> String {
let mut q = String::from("CREATE ");
self.target.render(&mut q);
match &self.body {
CreateBody::Content(c) => {
q.push_str(" CONTENT ");
q.push_str(c);
}
CreateBody::Set(pairs) if !pairs.is_empty() => {
q.push_str(" SET ");
q.push_str(
&pairs
.iter()
.map(|(k, v)| format!("{k} = {v}"))
.collect::<Vec<_>>()
.join(", "),
);
}
CreateBody::Set(_) => {}
}
self.returning.render(&mut q);
q
}
}
impl<T: SurrealRecord> std::fmt::Display for Create<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}
pub struct Delete<T: SurrealRecord> {
_marker: std::marker::PhantomData<T>,
target: Target,
filter: Option<Box<dyn DynExpr>>,
returning: Returning,
}
impl<T: SurrealRecord> Delete<T> {
pub(crate) fn for_table() -> Self {
Self {
_marker: std::marker::PhantomData,
target: Target::Table(T::table_name()),
filter: None,
returning: Returning::None,
}
}
pub fn record<V: SurrealQL>(mut self, id: V) -> Self {
self.target = Target::Record(RecordLink::new(T::table_name(), id));
self
}
pub fn filter(mut self, expr: impl DynExpr + 'static) -> Self {
self.filter = Some(Box::new(expr));
self
}
pub fn returning(mut self, r: Returning) -> Self {
self.returning = r;
self
}
pub fn to_surrealql(&self) -> String {
let mut q = String::from("DELETE ");
self.target.render(&mut q);
if let Some(ref f) = self.filter {
q.push_str(" WHERE ");
f.render_dyn(&mut q);
}
self.returning.render(&mut q);
q
}
}
impl<T: SurrealRecord> std::fmt::Display for Delete<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}
#[derive(Default)]
pub struct Batch {
statements: Vec<String>,
}
impl Batch {
pub fn new() -> Self {
Self {
statements: Vec::new(),
}
}
pub fn push(mut self, stmt: impl ToString) -> Self {
self.statements.push(stmt.to_string());
self
}
pub fn to_surrealql(&self) -> String {
self.statements.join(";\n")
}
pub fn len(&self) -> usize {
self.statements.len()
}
pub fn is_empty(&self) -> bool {
self.statements.is_empty()
}
}
impl std::fmt::Display for Batch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}
pub struct Relate<E: SurrealEdge> {
_marker: std::marker::PhantomData<E>,
}
impl<E: SurrealEdge> Relate<E> {
pub fn new() -> Self {
Self {
_marker: std::marker::PhantomData,
}
}
pub fn to_surrealql(
from: &Thing<impl SurrealRecord>,
to: &Thing<impl SurrealRecord>,
) -> String {
format!(
"RELATE {}:{} -> {} -> {}:{}",
from.table(),
from.key,
E::edge_name(),
to.table(),
to.key
)
}
}
impl<E: SurrealEdge> Default for Relate<E> {
fn default() -> Self {
Self::new()
}
}
pub struct RelateEdge<E: SurrealEdge> {
_marker: std::marker::PhantomData<E>,
from_label: String,
to_label: String,
content_json: Option<serde_json::Value>,
}
impl<E: SurrealEdge> RelateEdge<E> {
pub fn from(from: &Thing<impl SurrealRecord>) -> Self {
Self {
_marker: std::marker::PhantomData,
from_label: format!("{}:{}", from.table(), from.key),
to_label: String::new(),
content_json: None,
}
}
pub fn to(mut self, to: &Thing<impl SurrealRecord>) -> Self {
self.to_label = format!("{}:{}", to.table(), to.key);
self
}
pub fn content(mut self, edge: &impl serde::Serialize) -> Self {
self.content_json = serde_json::to_value(edge).ok();
self
}
pub fn build(&self) -> String {
let mut q = format!(
"RELATE {} -> {} -> {}",
self.from_label,
E::edge_name(),
self.to_label
);
if let Some(ref c) = self.content_json {
q.push_str(&format!(
" CONTENT {}",
serde_json::to_string(c).unwrap_or_default()
));
}
q
}
}