Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions src/builders/create.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use crate::{
enums::ReturnClause,
internal_macros::push_clause,
types::create::{ContentMode, CreateData, SetField},
};
use std::fmt::Write;

pub struct CreateBuilder {
pub data: CreateData,
}

impl CreateBuilder {
/// Switches the statement from `CREATE ...` to `CREATE ONLY ...`.
///
/// **Note:** SurrealDB expects a single-result `RETURN` when using `ONLY`.
/// The builder does not enforce this — the server will validate it at runtime.
pub fn only(mut self) -> Self {
self.data.only = true;
self
}

/// Sets the data-setting mode to `CONTENT @value`.
///
/// This replaces any previous `CONTENT` or `SET` clause.
///
/// # Example
/// ```
/// # use surrealex::QueryBuilder;
/// let sql = QueryBuilder::create("person")
/// .content("{ name: 'Tobie', company: 'SurrealDB' }")
/// .build();
/// assert_eq!(sql, "CREATE person CONTENT { name: 'Tobie', company: 'SurrealDB' }");
/// ```
pub fn content(mut self, value: &str) -> Self {
self.data.content = Some(ContentMode::Content(value.to_string()));
self
}

/// Adds a `SET field = value` assignment.
///
/// Multiple calls accumulate assignments. If a `CONTENT` clause was previously
/// set, it is replaced by the `SET` clause.
///
/// # Example
/// ```
/// # use surrealex::QueryBuilder;
/// let sql = QueryBuilder::create("person")
/// .set("name", "'Tobie'")
/// .set("company", "'SurrealDB'")
/// .build();
/// assert_eq!(sql, "CREATE person SET name = 'Tobie', company = 'SurrealDB'");
/// ```
pub fn set(mut self, field: &str, value: &str) -> Self {
match &mut self.data.content {
Some(ContentMode::Set(fields)) => {
fields.push(SetField {
field: field.to_string(),
value: value.to_string(),
});
}
_ => {
self.data.content = Some(ContentMode::Set(vec![SetField {
field: field.to_string(),
value: value.to_string(),
}]));
}
}
self
}

/// Sets the RETURN clause to `RETURN NONE`.
pub fn return_none(mut self) -> Self {
self.data.return_clause = Some(ReturnClause::None);
self
}

/// Sets the RETURN clause to `RETURN BEFORE`.
pub fn return_before(mut self) -> Self {
self.data.return_clause = Some(ReturnClause::Before);
self
}

/// Sets the RETURN clause to `RETURN AFTER`.
pub fn return_after(mut self) -> Self {
self.data.return_clause = Some(ReturnClause::After);
self
}

/// Sets the RETURN clause to `RETURN DIFF`.
pub fn return_diff(mut self) -> Self {
self.data.return_clause = Some(ReturnClause::Diff);
self
}

/// Sets the RETURN clause to `RETURN <param1>, <param2>, ...`.
///
/// # Example
/// ```
/// # use surrealex::QueryBuilder;
/// let sql = QueryBuilder::create("person")
/// .set("name", "'Tobie'")
/// .return_params(vec!["name", "id"])
/// .build();
/// assert_eq!(sql, "CREATE person SET name = 'Tobie' RETURN name, id");
/// ```
pub fn return_params<S: Into<String>>(mut self, params: Vec<S>) -> Self {
self.data.return_clause = Some(ReturnClause::Params(
params.into_iter().map(|s| s.into()).collect(),
));
self
}

/// Sets the RETURN clause to `RETURN VALUE <field>`.
///
/// # Example
/// ```
/// # use surrealex::QueryBuilder;
/// let sql = QueryBuilder::create("person")
/// .set("name", "'Tobie'")
/// .return_value("name")
/// .build();
/// assert_eq!(sql, "CREATE person SET name = 'Tobie' RETURN VALUE name");
/// ```
pub fn return_value(mut self, field: &str) -> Self {
self.data.return_clause = Some(ReturnClause::Value(field.to_string()));
self
}

/// Sets the TIMEOUT clause with a raw SurrealQL duration string.
///
/// Accepts SurrealQL duration syntax such as `"500ms"`, `"2s"`, `"1m"`.
pub fn timeout(mut self, duration: &str) -> Self {
self.data.timeout = Some(duration.to_string());
self
}

/// Builds the final CREATE query string.
pub fn build(self) -> String {
let mut query = String::with_capacity(128);
let targets = &self.data.targets;

if self.data.only {
push_clause!(query, "CREATE ONLY {targets}");
} else {
push_clause!(query, "CREATE {targets}");
}

if let Some(ref content) = self.data.content {
match content {
ContentMode::Content(value) => {
push_clause!(query, "CONTENT {value}");
}
ContentMode::Set(fields) => {
let assignments: String = fields
.iter()
.map(|f| format!("{} = {}", f.field, f.value))
.collect::<Vec<String>>()
.join(", ");
push_clause!(query, "SET {assignments}");
}
}
}

if let Some(ref rc) = self.data.return_clause {
push_clause!(query, "RETURN {rc}");
}

if let Some(ref duration) = self.data.timeout {
push_clause!(query, "TIMEOUT {duration}");
}

query
}
}
8 changes: 4 additions & 4 deletions src/builders/delete.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
enums::Condition,
enums::{Condition, ExplainClause, ReturnClause},
internal_macros::push_clause,
types::delete::{DeleteData, ExplainMode, ReturnClause},
types::delete::DeleteData,
};
use std::fmt::Write;

Expand Down Expand Up @@ -76,13 +76,13 @@ impl DeleteBuilder {

/// Adds an `EXPLAIN` clause to the statement.
pub fn explain(mut self) -> Self {
self.data.explain = Some(ExplainMode::Simple);
self.data.explain = Some(ExplainClause::Simple);
self
}

/// Adds an `EXPLAIN FULL` clause to the statement.
pub fn explain_full(mut self) -> Self {
self.data.explain = Some(ExplainMode::Full);
self.data.explain = Some(ExplainClause::Full);
self
}

Expand Down
1 change: 1 addition & 0 deletions src/builders/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod create;
pub mod delete;
pub mod select;
16 changes: 15 additions & 1 deletion src/builders/select.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fmt::Write;

use crate::{
enums::{Condition, SelectionFields},
enums::{Condition, ExplainClause, SelectionFields},
internal_macros::push_clause,
traits::ToSelectField,
types::select::{GraphTraversalParams, OrderOptions, OrderTerm, SelectData, SelectField},
Expand Down Expand Up @@ -121,6 +121,16 @@ impl FromReady {
self
}

pub fn explain(mut self) -> Self {
self.data.explain = Some(ExplainClause::Simple);
self
}

pub fn explain_full(mut self) -> Self {
self.data.explain = Some(ExplainClause::Full);
self
}

pub fn build(self) -> String {
let mut query = String::with_capacity(128);
push_clause!(query, "SELECT");
Expand Down Expand Up @@ -176,6 +186,10 @@ impl FromReady {
push_clause!(query, "FETCH {fetch_fields}");
}

if let Some(explain) = self.data.explain {
push_clause!(query, "{explain}");
}

query
}
}
Expand Down
61 changes: 61 additions & 0 deletions src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,67 @@ use crate::{
types::select::{OrderOptions, SelectField},
};

/// Represents the RETURN clause variants shared across statements.
///
/// SurrealQL supports:
/// - `RETURN NONE`
/// - `RETURN BEFORE`
/// - `RETURN AFTER`
/// - `RETURN DIFF`
/// - `RETURN <param1>, <param2>, ...`
/// - `RETURN VALUE <param>`
#[derive(Debug, Clone)]
pub enum ReturnClause {
/// `RETURN NONE`
None,
/// `RETURN BEFORE`
Before,
/// `RETURN AFTER`
After,
/// `RETURN DIFF`
Diff,
/// `RETURN <field1>, <field2>, ...`
Params(Vec<String>),
/// `RETURN VALUE <field>`
Value(String),
}

impl Display for ReturnClause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ReturnClause::None => write!(f, "NONE"),
ReturnClause::Before => write!(f, "BEFORE"),
ReturnClause::After => write!(f, "AFTER"),
ReturnClause::Diff => write!(f, "DIFF"),
ReturnClause::Params(params) => {
let joined = params.join(", ");
write!(f, "{joined}")
}
ReturnClause::Value(field) => write!(f, "VALUE {field}"),
}
}
}

/// Represents EXPLAIN clause modes shared across statements.
///
/// SurrealQL supports: `EXPLAIN` or `EXPLAIN FULL`.
#[derive(Debug, Clone, PartialEq)]
pub enum ExplainClause {
/// `EXPLAIN`
Simple,
/// `EXPLAIN FULL`
Full,
}

impl Display for ExplainClause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ExplainClause::Simple => write!(f, "EXPLAIN"),
ExplainClause::Full => write!(f, "EXPLAIN FULL"),
}
}
}

/// Direction of graph traversal arrows.
#[derive(Debug, Clone)]
pub enum Direction {
Expand Down
12 changes: 10 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ pub mod macros;

pub mod builders;
pub(crate) mod internal_macros;
pub mod structs;
pub mod traits;
pub mod types;

use crate::{
builders::{delete::DeleteBuilder, select::SelectBuilder},
builders::{create::CreateBuilder, delete::DeleteBuilder, select::SelectBuilder},
enums::SelectionFields,
types::{
create::CreateData,
delete::DeleteData,
select::{SelectData, SelectField},
},
Expand Down Expand Up @@ -43,4 +43,12 @@ impl QueryBuilder {
};
DeleteBuilder { data }
}

pub fn create(targets: &str) -> CreateBuilder {
let data = CreateData {
targets: targets.to_string(),
..Default::default()
};
CreateBuilder { data }
}
}
Loading
Loading