use crate::{
expr::{Column, DynExpr, Order, Path, Projection, RecordLink, SurrealQL},
types::{SurrealEdge, SurrealRecord, Thing},
};
use std::collections::BTreeMap;
#[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),
}
}
fn render_params(&self, buf: &mut String, params: &mut BTreeMap<String, serde_json::Value>) {
match self {
Target::Table(t) => buf.push_str(t),
Target::Record(r) => r.render_dyn_params(buf, params),
}
}
}
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 project_path(self, path: Path, alias: &'static str) -> Select<T> {
let mut s = Select::bare();
s.projections = vec![Projection::aliased(path, alias)];
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![],
returning: Returning::None,
}
}
pub fn create(self) -> Create<T> {
Create::for_table()
}
pub fn update(self) -> Update<T> {
Update::for_table()
}
pub fn upsert(self) -> Update<T> {
Update::for_upsert()
}
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>,
value: bool,
omit: Vec<String>,
with: Option<String>,
filter: Option<Box<dyn DynExpr>>,
split: Vec<String>,
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>,
timeout: Option<String>,
explain: Option<bool>,
from_sub: Option<Box<Select<T>>>,
}
impl<T: SurrealRecord> Select<T> {
fn bare() -> Self {
Select {
_marker: std::marker::PhantomData,
projections: Vec::new(),
value: false,
omit: Vec::new(),
with: None,
filter: None,
split: Vec::new(),
order: Vec::new(),
limit: None,
start: 0,
fetch: Vec::new(),
group_by: Vec::new(),
group_all: false,
count: false,
count_alias: None,
timeout: None,
explain: None,
from_sub: None,
}
}
pub fn filter(mut self, expr: impl DynExpr + 'static) -> Self {
self.filter = Some(Box::new(expr));
self
}
pub fn with_path(mut self, path: Path, alias: &'static str) -> Self {
if self.projections.is_empty() {
self.projections
.push(Projection::new(crate::expr::Raw("*".to_string())));
}
self.projections.push(Projection::aliased(path, alias));
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 value(mut self) -> Self {
self.value = true;
self
}
pub fn omit(mut self, field: &str) -> Self {
self.omit.push(field.to_string());
self
}
pub fn split(mut self, field: &str) -> Self {
self.split.push(field.to_string());
self
}
pub fn with_index<I, S>(mut self, indexes: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
let list = indexes
.into_iter()
.map(|s| s.as_ref().to_string())
.collect::<Vec<_>>()
.join(", ");
self.with = Some(format!("WITH INDEX {list}"));
self
}
pub fn with_no_index(mut self) -> Self {
self.with = Some("WITH NOINDEX".to_string());
self
}
pub fn timeout(mut self, duration: impl Into<String>) -> Self {
self.timeout = Some(duration.into());
self
}
pub fn from_subquery(mut self, sub: Select<T>) -> Self {
self.from_sub = Some(Box::new(sub));
self
}
pub fn explain(mut self) -> Self {
self.explain = Some(false);
self
}
pub fn explain_full(mut self) -> Self {
self.explain = Some(true);
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);
}
}
}
fn render_select_list_params(
&self,
q: &mut String,
params: &mut BTreeMap<String, serde_json::Value>,
) {
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_params(q, params);
}
}
}
fn render(
&self,
q: &mut String,
params: &mut BTreeMap<String, serde_json::Value>,
param_mode: bool,
) {
q.push_str("SELECT ");
if self.value {
q.push_str("VALUE ");
}
if param_mode {
self.render_select_list_params(q, params);
} else {
self.render_select_list(q);
}
if !self.omit.is_empty() {
q.push_str(" OMIT ");
q.push_str(&self.omit.join(", "));
}
q.push_str(" FROM ");
match &self.from_sub {
Some(sub) => {
q.push('(');
sub.render(q, params, param_mode);
q.push(')');
}
None => q.push_str(T::table_name()),
}
if let Some(w) = &self.with {
q.push(' ');
q.push_str(w);
}
if let Some(ref f) = self.filter {
q.push_str(" WHERE ");
if param_mode {
f.render_dyn_params(q, params);
} else {
f.render_dyn(q);
}
}
for (i, s) in self.split.iter().enumerate() {
q.push_str(if i == 0 { " SPLIT " } else { ", " });
q.push_str(s);
}
for (i, (col, dir)) in self.order.iter().enumerate() {
q.push_str(if i == 0 { " ORDER BY " } else { ", " });
q.push_str(&format!("{col} {dir}"));
}
for (i, g) in self.group_by.iter().enumerate() {
q.push_str(if i == 0 { " GROUP BY " } else { ", " });
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}"));
}
if let Some(t) = &self.timeout {
q.push_str(" TIMEOUT ");
q.push_str(t);
}
match self.explain {
Some(true) => q.push_str(" EXPLAIN FULL"),
Some(false) => q.push_str(" EXPLAIN"),
None => {}
}
}
pub fn to_surrealql(&self) -> String {
let mut q = String::new();
let mut sink = BTreeMap::new();
self.render(&mut q, &mut sink, false);
q
}
pub fn to_surrealql_with_params(&self) -> (String, BTreeMap<String, serde_json::Value>) {
let mut params = BTreeMap::new();
let mut q = String::new();
self.render(&mut q, &mut params, true);
(q, params)
}
}
impl<T: SurrealRecord> std::fmt::Debug for Select<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Select")
.field("sql", &self.to_surrealql())
.finish()
}
}
impl<T: SurrealRecord> DynExpr for Select<T> {
fn render_dyn(&self, buf: &mut String) {
let mut sink = BTreeMap::new();
buf.push('(');
self.render(buf, &mut sink, false);
buf.push(')');
}
fn render_dyn_params(
&self,
buf: &mut String,
params: &mut BTreeMap<String, serde_json::Value>,
) {
buf.push('(');
self.render(buf, params, true);
buf.push(')');
}
}
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>,
returning: Returning,
}
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 returning(mut self, r: Returning) -> Self {
self.returning = r;
self
}
pub fn data(&self) -> &[T] {
&self.data
}
pub fn to_surrealql(&self) -> String
where
T: serde::Serialize,
{
let body = match self.data.as_slice() {
[] => "[]".to_string(),
[one] => serde_json::to_string(one).unwrap_or_else(|_| "{}".to_string()),
many => serde_json::to_string(many).unwrap_or_else(|_| "[]".to_string()),
};
let mut q = format!("INSERT INTO {} {}", T::table_name(), body);
if !self.return_fields.is_empty() {
q.push_str(" RETURN ");
q.push_str(&self.return_fields.join(", "));
} else {
self.returning.render(&mut q);
}
q
}
}
enum SetVal {
Assign(String, Box<dyn DynExpr>),
Merge(Box<dyn DynExpr>),
Content(Box<dyn DynExpr>),
}
impl SetVal {
fn render(&self, buf: &mut String, set_pairs: &mut Vec<String>) {
match self {
SetVal::Assign(k, v) => {
let mut val_buf = String::new();
v.render_dyn(&mut val_buf);
set_pairs.push(format!("{k} = {val_buf}"));
}
SetVal::Merge(v) => {
let mut val_buf = String::new();
v.render_dyn(&mut val_buf);
buf.push_str(" MERGE ");
buf.push_str(&val_buf);
}
SetVal::Content(v) => {
let mut val_buf = String::new();
v.render_dyn(&mut val_buf);
buf.push_str(" CONTENT ");
buf.push_str(&val_buf);
}
}
}
fn render_params(
&self,
buf: &mut String,
set_pairs: &mut Vec<String>,
params: &mut BTreeMap<String, serde_json::Value>,
) {
match self {
SetVal::Assign(k, v) => {
let mut val_buf = String::new();
v.render_dyn_params(&mut val_buf, params);
set_pairs.push(format!("{k} = {val_buf}"));
}
SetVal::Merge(v) => {
let mut val_buf = String::new();
v.render_dyn_params(&mut val_buf, params);
buf.push_str(" MERGE ");
buf.push_str(&val_buf);
}
SetVal::Content(v) => {
let mut val_buf = String::new();
v.render_dyn_params(&mut val_buf, params);
buf.push_str(" CONTENT ");
buf.push_str(&val_buf);
}
}
}
}
pub struct Update<T: SurrealRecord> {
_marker: std::marker::PhantomData<T>,
verb: &'static str,
target: Target,
filter: Option<Box<dyn DynExpr>>,
sets: Vec<SetVal>,
returning: Returning,
}
impl<T: SurrealRecord> Update<T> {
pub(crate) fn for_table() -> Self {
Self::with_verb("UPDATE")
}
pub(crate) fn for_upsert() -> Self {
Self::with_verb("UPSERT")
}
fn with_verb(verb: &'static str) -> Self {
Self {
_marker: std::marker::PhantomData,
verb,
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 {
self.sets.push(SetVal::Assign(
col.name.to_string(),
Box::new(crate::expr::Literal(value)),
));
self
}
pub fn set_lit<C: SurrealQL>(mut self, col: &str, value: C) -> Self {
self.sets.push(SetVal::Assign(
col.to_string(),
Box::new(crate::expr::Literal(value)),
));
self
}
pub fn set_expr(mut self, col: &str, expr: impl DynExpr + 'static) -> Self {
self.sets
.push(SetVal::Assign(col.to_string(), Box::new(expr)));
self
}
pub fn set_raw(mut self, col: &str, raw: impl Into<String>) -> Self {
self.sets.push(SetVal::Assign(
col.to_string(),
Box::new(crate::expr::Raw(raw.into())),
));
self
}
pub fn merge(mut self, expr: impl DynExpr + 'static) -> Self {
self.sets.push(SetVal::Merge(Box::new(expr)));
self
}
pub fn content(mut self, expr: impl DynExpr + 'static) -> Self {
self.sets.push(SetVal::Content(Box::new(expr)));
self
}
pub fn returning(mut self, r: Returning) -> Self {
self.returning = r;
self
}
pub fn then_select(self, select: Select<T>) -> String {
format!("{};\n{}", self.to_surrealql(), select.to_surrealql())
}
pub fn then_select_params(
self,
select: Select<T>,
) -> (String, BTreeMap<String, serde_json::Value>) {
let (mut_q, mut params) = self.to_surrealql_with_params();
let (sel_q, sel_params) = select.to_surrealql_with_params();
params.extend(sel_params);
(format!("{mut_q};\n{sel_q}"), params)
}
pub fn to_surrealql(&self) -> String {
let mut q = String::from(self.verb);
q.push(' ');
self.target.render(&mut q);
let mut set_pairs = Vec::new();
let mut trait_buf = String::new();
for s in &self.sets {
s.render(&mut trait_buf, &mut set_pairs);
}
if !trait_buf.is_empty() {
q.push_str(&trait_buf);
} 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
}
pub fn to_surrealql_with_params(&self) -> (String, BTreeMap<String, serde_json::Value>) {
let mut params = BTreeMap::new();
let mut q = String::from(self.verb);
q.push(' ');
self.target.render_params(&mut q, &mut params);
let mut set_pairs = Vec::new();
let mut trait_buf = String::new();
for s in &self.sets {
s.render_params(&mut trait_buf, &mut set_pairs, &mut params);
}
if !trait_buf.is_empty() {
q.push_str(&trait_buf);
} 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_params(&mut q, &mut params);
}
self.returning.render(&mut q);
(q, params)
}
}
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(Box<dyn DynExpr>),
Set(Vec<(String, Box<dyn DynExpr>)>),
}
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 + 'static) -> Self {
self.body = CreateBody::Content(Box::new(expr));
self
}
pub fn set_lit<C: SurrealQL>(mut self, col: &str, value: C) -> Self {
self.push_set(col, Box::new(crate::expr::Literal(value)));
self
}
pub fn set_expr(mut self, col: &str, expr: impl DynExpr + 'static) -> Self {
self.push_set(col, Box::new(expr));
self
}
pub fn set_raw(mut self, col: &str, raw: impl Into<String>) -> Self {
self.push_set(col, Box::new(crate::expr::Raw(raw.into())));
self
}
fn push_set(&mut self, col: &str, expr: Box<dyn DynExpr>) {
match &mut self.body {
CreateBody::Set(v) => v.push((col.to_string(), expr)),
CreateBody::Content(_) => {
self.body = CreateBody::Set(vec![(col.to_string(), expr)]);
}
}
}
pub fn returning(mut self, r: Returning) -> Self {
self.returning = r;
self
}
pub fn then_select(self, select: Select<T>) -> String {
format!("{};\n{}", self.to_surrealql(), select.to_surrealql())
}
pub fn then_select_params(
self,
select: Select<T>,
) -> (String, BTreeMap<String, serde_json::Value>) {
let (mut_q, mut params) = self.to_surrealql_with_params();
let (sel_q, sel_params) = select.to_surrealql_with_params();
params.extend(sel_params);
(format!("{mut_q};\n{sel_q}"), params)
}
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 ");
c.render_dyn(&mut q);
}
CreateBody::Set(pairs) if !pairs.is_empty() => {
q.push_str(" SET ");
q.push_str(
&pairs
.iter()
.map(|(k, v)| {
let mut val = String::new();
v.render_dyn(&mut val);
format!("{k} = {val}")
})
.collect::<Vec<_>>()
.join(", "),
);
}
CreateBody::Set(_) => {}
}
self.returning.render(&mut q);
q
}
pub fn to_surrealql_with_params(&self) -> (String, BTreeMap<String, serde_json::Value>) {
let mut params = BTreeMap::new();
let mut q = String::from("CREATE ");
self.target.render_params(&mut q, &mut params);
match &self.body {
CreateBody::Content(c) => {
q.push_str(" CONTENT ");
c.render_dyn_params(&mut q, &mut params);
}
CreateBody::Set(pairs) if !pairs.is_empty() => {
q.push_str(" SET ");
q.push_str(
&pairs
.iter()
.map(|(k, v)| {
let mut val = String::new();
v.render_dyn_params(&mut val, &mut params);
format!("{k} = {val}")
})
.collect::<Vec<_>>()
.join(", "),
);
}
CreateBody::Set(_) => {}
}
self.returning.render(&mut q);
(q, params)
}
}
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 then_select(self, select: Select<T>) -> String {
format!("{};\n{}", self.to_surrealql(), select.to_surrealql())
}
pub fn then_select_params(
self,
select: Select<T>,
) -> (String, BTreeMap<String, serde_json::Value>) {
let (mut_q, mut params) = self.to_surrealql_with_params();
let (sel_q, sel_params) = select.to_surrealql_with_params();
params.extend(sel_params);
(format!("{mut_q};\n{sel_q}"), params)
}
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
}
pub fn to_surrealql_with_params(&self) -> (String, BTreeMap<String, serde_json::Value>) {
let mut params = BTreeMap::new();
let mut q = String::from("DELETE ");
self.target.render_params(&mut q, &mut params);
if let Some(ref f) = self.filter {
q.push_str(" WHERE ");
f.render_dyn_params(&mut q, &mut params);
}
self.returning.render(&mut q);
(q, params)
}
}
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())
}
}
#[derive(Default)]
pub struct Transaction {
statements: Vec<String>,
cancel: bool,
}
impl Transaction {
pub fn new() -> Self {
Self::default()
}
pub fn push(mut self, stmt: impl ToString) -> Self {
self.statements.push(stmt.to_string());
self
}
pub fn cancel(mut self) -> Self {
self.cancel = true;
self
}
pub fn to_surrealql(&self) -> String {
let mut out = String::from("BEGIN TRANSACTION;\n");
for s in &self.statements {
out.push_str(s);
if !s.trim_end().ends_with(';') {
out.push(';');
}
out.push('\n');
}
out.push_str(if self.cancel {
"CANCEL TRANSACTION;"
} else {
"COMMIT TRANSACTION;"
});
out
}
pub fn len(&self) -> usize {
self.statements.len()
}
pub fn is_empty(&self) -> bool {
self.statements.is_empty()
}
}
impl std::fmt::Display for Transaction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}
fn record_id(thing: &Thing<impl SurrealRecord>, buf: &mut String) {
buf.push_str(thing.table());
buf.push(':');
thing.key.render_id(buf);
}
fn record_id_string(thing: &Thing<impl SurrealRecord>) -> String {
let mut s = String::new();
record_id(thing, &mut s);
s
}
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 {
let mut q = String::from("RELATE ");
record_id(from, &mut q);
q.push_str(" -> ");
q.push_str(E::edge_name());
q.push_str(" -> ");
record_id(to, &mut q);
q
}
}
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>,
return_fields: Vec<&'static str>,
returning: Returning,
}
impl<E: SurrealEdge> RelateEdge<E> {
pub fn from(from: &Thing<impl SurrealRecord>) -> Self {
Self {
_marker: std::marker::PhantomData,
from_label: record_id_string(from),
to_label: String::new(),
content_json: None,
return_fields: Vec::new(),
returning: Returning::None,
}
}
pub fn to(mut self, to: &Thing<impl SurrealRecord>) -> Self {
self.to_label = record_id_string(to);
self
}
pub fn content(mut self, edge: &impl serde::Serialize) -> Self {
self.content_json = serde_json::to_value(edge).ok();
self
}
pub fn return_field(mut self, field: &'static str) -> Self {
self.return_fields.push(field);
self
}
pub fn returning(mut self, r: Returning) -> Self {
self.returning = r;
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()
));
}
if !self.return_fields.is_empty() {
q.push_str(" RETURN ");
q.push_str(&self.return_fields.join(", "));
} else {
self.returning.render(&mut q);
}
q
}
}
pub struct LetVar {
name: String,
value: Box<dyn DynExpr>,
}
impl LetVar {
pub fn new(name: impl Into<String>, value: impl DynExpr + 'static) -> Self {
Self {
name: name.into(),
value: Box::new(value),
}
}
pub fn literal<V: SurrealQL>(name: impl Into<String>, value: V) -> Self {
Self {
name: name.into(),
value: Box::new(crate::expr::Literal(value)),
}
}
pub fn to_surrealql(&self) -> String {
let mut q = format!("LET ${} = ", self.name);
self.value.render_dyn(&mut q);
q
}
pub fn to_surrealql_with_params(&self) -> (String, BTreeMap<String, serde_json::Value>) {
let mut params = BTreeMap::new();
let mut q = format!("LET ${} = ", self.name);
self.value.render_dyn_params(&mut q, &mut params);
(q, params)
}
}
impl std::fmt::Display for LetVar {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}
pub struct For {
var: String,
array: Box<dyn DynExpr>,
body: Vec<String>,
}
impl For {
pub fn new(var: impl Into<String>, array: impl DynExpr + 'static) -> Self {
Self {
var: var.into(),
array: Box::new(array),
body: Vec::new(),
}
}
pub fn push(mut self, stmt: impl Into<String>) -> Self {
self.body.push(stmt.into());
self
}
pub fn to_surrealql(&self) -> String {
let mut q = format!("FOR ${} IN ", self.var);
self.array.render_dyn(&mut q);
q.push_str(" { ");
for s in &self.body {
q.push_str(s);
if !s.trim_end().ends_with(';') {
q.push(';');
}
q.push(' ');
}
q.push('}');
q
}
}
impl std::fmt::Display for For {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}
enum IndexKind {
Plain,
Unique,
Raw(String),
}
pub struct DefineIndex {
name: String,
table: String,
fields: Vec<String>,
kind: IndexKind,
if_not_exists: bool,
comment: Option<String>,
concurrently: bool,
}
impl DefineIndex {
pub fn new(name: impl Into<String>, table: impl Into<String>) -> Self {
Self {
name: name.into(),
table: table.into(),
fields: Vec::new(),
kind: IndexKind::Plain,
if_not_exists: true,
comment: None,
concurrently: false,
}
}
pub fn field(mut self, name: impl Into<String>) -> Self {
self.fields.push(name.into());
self
}
pub fn fields<I, S>(mut self, names: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.fields.extend(names.into_iter().map(Into::into));
self
}
pub fn unique(mut self) -> Self {
self.kind = IndexKind::Unique;
self
}
pub fn search(mut self, analyzer: &str) -> Self {
self.kind = IndexKind::Raw(format!("SEARCH ANALYZER {analyzer}"));
self
}
pub fn hnsw(mut self, dimension: u32, dist: &str) -> Self {
self.kind = IndexKind::Raw(format!("HNSW DIMENSION {dimension} DIST {dist}"));
self
}
pub fn mtree(mut self, dimension: u32, dist: &str) -> Self {
self.kind = IndexKind::Raw(format!("MTREE DIMENSION {dimension} DIST {dist}"));
self
}
pub fn raw(mut self, tail: impl Into<String>) -> Self {
self.kind = IndexKind::Raw(tail.into());
self
}
pub fn overwrite(mut self) -> Self {
self.if_not_exists = false;
self
}
pub fn comment(mut self, text: impl Into<String>) -> Self {
self.comment = Some(text.into());
self
}
pub fn concurrently(mut self) -> Self {
self.concurrently = true;
self
}
pub fn to_surrealql(&self) -> String {
let guard = if self.if_not_exists {
"IF NOT EXISTS "
} else {
""
};
let mut q = format!(
"DEFINE INDEX {guard}{} ON TABLE {} FIELDS {}",
self.name,
self.table,
self.fields.join(", "),
);
match &self.kind {
IndexKind::Plain => {}
IndexKind::Unique => q.push_str(" UNIQUE"),
IndexKind::Raw(tail) => {
q.push(' ');
q.push_str(tail);
}
}
if let Some(c) = &self.comment {
let escaped = c.replace('\\', "\\\\").replace('\'', "\\'");
q.push_str(&format!(" COMMENT '{escaped}'"));
}
if self.concurrently {
q.push_str(" CONCURRENTLY");
}
q
}
pub fn remove(name: &str, table: &str) -> String {
format!("REMOVE INDEX IF EXISTS {name} ON TABLE {table}")
}
}
impl std::fmt::Display for DefineIndex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}
fn guard(if_not_exists: bool) -> &'static str {
if if_not_exists {
"IF NOT EXISTS "
} else {
""
}
}
pub struct DefineEvent {
name: String,
table: String,
when: String,
then: String,
if_not_exists: bool,
}
impl DefineEvent {
pub fn new(name: impl Into<String>, table: impl Into<String>) -> Self {
Self {
name: name.into(),
table: table.into(),
when: String::new(),
then: String::new(),
if_not_exists: true,
}
}
pub fn when(mut self, cond: impl Into<String>) -> Self {
self.when = cond.into();
self
}
pub fn then(mut self, block: impl Into<String>) -> Self {
self.then = block.into();
self
}
pub fn overwrite(mut self) -> Self {
self.if_not_exists = false;
self
}
pub fn to_surrealql(&self) -> String {
format!(
"DEFINE EVENT {}{} ON TABLE {} WHEN {} THEN {}",
guard(self.if_not_exists),
self.name,
self.table,
self.when,
self.then
)
}
pub fn remove(name: &str, table: &str) -> String {
format!("REMOVE EVENT IF EXISTS {name} ON TABLE {table}")
}
}
impl std::fmt::Display for DefineEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}
pub struct DefineFunction {
name: String,
args: Vec<(String, String)>,
returns: Option<String>,
body: String,
if_not_exists: bool,
}
impl DefineFunction {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
args: Vec::new(),
returns: None,
body: String::new(),
if_not_exists: true,
}
}
pub fn arg(mut self, name: impl Into<String>, ty: impl Into<String>) -> Self {
self.args.push((name.into(), ty.into()));
self
}
pub fn returns(mut self, ty: impl Into<String>) -> Self {
self.returns = Some(ty.into());
self
}
pub fn body(mut self, body: impl Into<String>) -> Self {
self.body = body.into();
self
}
pub fn overwrite(mut self) -> Self {
self.if_not_exists = false;
self
}
pub fn to_surrealql(&self) -> String {
let args = self
.args
.iter()
.map(|(n, t)| format!("${n}: {t}"))
.collect::<Vec<_>>()
.join(", ");
let ret = self
.returns
.as_ref()
.map(|r| format!(" -> {r}"))
.unwrap_or_default();
format!(
"DEFINE FUNCTION {}fn::{}({}){} {{ {} }}",
guard(self.if_not_exists),
self.name,
args,
ret,
self.body
)
}
pub fn remove(name: &str) -> String {
format!("REMOVE FUNCTION IF EXISTS fn::{name}")
}
}
impl std::fmt::Display for DefineFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}
pub struct DefineAnalyzer {
name: String,
tokenizers: Vec<String>,
filters: Vec<String>,
if_not_exists: bool,
}
impl DefineAnalyzer {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
tokenizers: Vec::new(),
filters: Vec::new(),
if_not_exists: true,
}
}
pub fn tokenizers<I, S>(mut self, toks: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.tokenizers = toks.into_iter().map(Into::into).collect();
self
}
pub fn filters<I, S>(mut self, filters: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.filters = filters.into_iter().map(Into::into).collect();
self
}
pub fn overwrite(mut self) -> Self {
self.if_not_exists = false;
self
}
pub fn to_surrealql(&self) -> String {
let mut q = format!("DEFINE ANALYZER {}{}", guard(self.if_not_exists), self.name);
if !self.tokenizers.is_empty() {
q.push_str(" TOKENIZERS ");
q.push_str(&self.tokenizers.join(", "));
}
if !self.filters.is_empty() {
q.push_str(" FILTERS ");
q.push_str(&self.filters.join(", "));
}
q
}
pub fn remove(name: &str) -> String {
format!("REMOVE ANALYZER IF EXISTS {name}")
}
}
impl std::fmt::Display for DefineAnalyzer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}
pub struct DefineParam {
name: String,
value: String,
if_not_exists: bool,
}
impl DefineParam {
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
Self {
name: name.into(),
value: value.into(),
if_not_exists: true,
}
}
pub fn value_lit<V: SurrealQL>(mut self, value: V) -> Self {
let mut buf = String::new();
V::render_literal(&value, &mut buf);
self.value = buf;
self
}
pub fn overwrite(mut self) -> Self {
self.if_not_exists = false;
self
}
pub fn to_surrealql(&self) -> String {
format!(
"DEFINE PARAM {}${} VALUE {}",
guard(self.if_not_exists),
self.name,
self.value
)
}
pub fn remove(name: &str) -> String {
format!("REMOVE PARAM IF EXISTS ${name}")
}
}
impl std::fmt::Display for DefineParam {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_surrealql())
}
}