Skip to content

Commit c5b368a

Browse files
Merge pull request #10 from atopio/feat/create-statement
Add Create Statement
2 parents 43404c4 + 5ec4900 commit c5b368a

File tree

15 files changed

+861
-814
lines changed

15 files changed

+861
-814
lines changed

src/builders/create.rs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
use crate::{
2+
enums::ReturnClause,
3+
internal_macros::push_clause,
4+
types::create::{ContentMode, CreateData, SetField},
5+
};
6+
use std::fmt::Write;
7+
8+
pub struct CreateBuilder {
9+
pub data: CreateData,
10+
}
11+
12+
impl CreateBuilder {
13+
/// Switches the statement from `CREATE ...` to `CREATE ONLY ...`.
14+
///
15+
/// **Note:** SurrealDB expects a single-result `RETURN` when using `ONLY`.
16+
/// The builder does not enforce this — the server will validate it at runtime.
17+
pub fn only(mut self) -> Self {
18+
self.data.only = true;
19+
self
20+
}
21+
22+
/// Sets the data-setting mode to `CONTENT @value`.
23+
///
24+
/// This replaces any previous `CONTENT` or `SET` clause.
25+
///
26+
/// # Example
27+
/// ```
28+
/// # use surrealex::QueryBuilder;
29+
/// let sql = QueryBuilder::create("person")
30+
/// .content("{ name: 'Tobie', company: 'SurrealDB' }")
31+
/// .build();
32+
/// assert_eq!(sql, "CREATE person CONTENT { name: 'Tobie', company: 'SurrealDB' }");
33+
/// ```
34+
pub fn content(mut self, value: &str) -> Self {
35+
self.data.content = Some(ContentMode::Content(value.to_string()));
36+
self
37+
}
38+
39+
/// Adds a `SET field = value` assignment.
40+
///
41+
/// Multiple calls accumulate assignments. If a `CONTENT` clause was previously
42+
/// set, it is replaced by the `SET` clause.
43+
///
44+
/// # Example
45+
/// ```
46+
/// # use surrealex::QueryBuilder;
47+
/// let sql = QueryBuilder::create("person")
48+
/// .set("name", "'Tobie'")
49+
/// .set("company", "'SurrealDB'")
50+
/// .build();
51+
/// assert_eq!(sql, "CREATE person SET name = 'Tobie', company = 'SurrealDB'");
52+
/// ```
53+
pub fn set(mut self, field: &str, value: &str) -> Self {
54+
match &mut self.data.content {
55+
Some(ContentMode::Set(fields)) => {
56+
fields.push(SetField {
57+
field: field.to_string(),
58+
value: value.to_string(),
59+
});
60+
}
61+
_ => {
62+
self.data.content = Some(ContentMode::Set(vec![SetField {
63+
field: field.to_string(),
64+
value: value.to_string(),
65+
}]));
66+
}
67+
}
68+
self
69+
}
70+
71+
/// Sets the RETURN clause to `RETURN NONE`.
72+
pub fn return_none(mut self) -> Self {
73+
self.data.return_clause = Some(ReturnClause::None);
74+
self
75+
}
76+
77+
/// Sets the RETURN clause to `RETURN BEFORE`.
78+
pub fn return_before(mut self) -> Self {
79+
self.data.return_clause = Some(ReturnClause::Before);
80+
self
81+
}
82+
83+
/// Sets the RETURN clause to `RETURN AFTER`.
84+
pub fn return_after(mut self) -> Self {
85+
self.data.return_clause = Some(ReturnClause::After);
86+
self
87+
}
88+
89+
/// Sets the RETURN clause to `RETURN DIFF`.
90+
pub fn return_diff(mut self) -> Self {
91+
self.data.return_clause = Some(ReturnClause::Diff);
92+
self
93+
}
94+
95+
/// Sets the RETURN clause to `RETURN <param1>, <param2>, ...`.
96+
///
97+
/// # Example
98+
/// ```
99+
/// # use surrealex::QueryBuilder;
100+
/// let sql = QueryBuilder::create("person")
101+
/// .set("name", "'Tobie'")
102+
/// .return_params(vec!["name", "id"])
103+
/// .build();
104+
/// assert_eq!(sql, "CREATE person SET name = 'Tobie' RETURN name, id");
105+
/// ```
106+
pub fn return_params<S: Into<String>>(mut self, params: Vec<S>) -> Self {
107+
self.data.return_clause = Some(ReturnClause::Params(
108+
params.into_iter().map(|s| s.into()).collect(),
109+
));
110+
self
111+
}
112+
113+
/// Sets the RETURN clause to `RETURN VALUE <field>`.
114+
///
115+
/// # Example
116+
/// ```
117+
/// # use surrealex::QueryBuilder;
118+
/// let sql = QueryBuilder::create("person")
119+
/// .set("name", "'Tobie'")
120+
/// .return_value("name")
121+
/// .build();
122+
/// assert_eq!(sql, "CREATE person SET name = 'Tobie' RETURN VALUE name");
123+
/// ```
124+
pub fn return_value(mut self, field: &str) -> Self {
125+
self.data.return_clause = Some(ReturnClause::Value(field.to_string()));
126+
self
127+
}
128+
129+
/// Sets the TIMEOUT clause with a raw SurrealQL duration string.
130+
///
131+
/// Accepts SurrealQL duration syntax such as `"500ms"`, `"2s"`, `"1m"`.
132+
pub fn timeout(mut self, duration: &str) -> Self {
133+
self.data.timeout = Some(duration.to_string());
134+
self
135+
}
136+
137+
/// Builds the final CREATE query string.
138+
pub fn build(self) -> String {
139+
let mut query = String::with_capacity(128);
140+
let targets = &self.data.targets;
141+
142+
if self.data.only {
143+
push_clause!(query, "CREATE ONLY {targets}");
144+
} else {
145+
push_clause!(query, "CREATE {targets}");
146+
}
147+
148+
if let Some(ref content) = self.data.content {
149+
match content {
150+
ContentMode::Content(value) => {
151+
push_clause!(query, "CONTENT {value}");
152+
}
153+
ContentMode::Set(fields) => {
154+
let assignments: String = fields
155+
.iter()
156+
.map(|f| format!("{} = {}", f.field, f.value))
157+
.collect::<Vec<String>>()
158+
.join(", ");
159+
push_clause!(query, "SET {assignments}");
160+
}
161+
}
162+
}
163+
164+
if let Some(ref rc) = self.data.return_clause {
165+
push_clause!(query, "RETURN {rc}");
166+
}
167+
168+
if let Some(ref duration) = self.data.timeout {
169+
push_clause!(query, "TIMEOUT {duration}");
170+
}
171+
172+
query
173+
}
174+
}

src/builders/delete.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{
2-
enums::Condition,
2+
enums::{Condition, ExplainClause, ReturnClause},
33
internal_macros::push_clause,
4-
types::delete::{DeleteData, ExplainMode, ReturnClause},
4+
types::delete::DeleteData,
55
};
66
use std::fmt::Write;
77

@@ -76,13 +76,13 @@ impl DeleteBuilder {
7676

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

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

src/builders/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
pub mod create;
12
pub mod delete;
23
pub mod select;

src/builders/select.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fmt::Write;
22

33
use crate::{
4-
enums::{Condition, SelectionFields},
4+
enums::{Condition, ExplainClause, SelectionFields},
55
internal_macros::push_clause,
66
traits::ToSelectField,
77
types::select::{GraphTraversalParams, OrderOptions, OrderTerm, SelectData, SelectField},
@@ -121,6 +121,16 @@ impl FromReady {
121121
self
122122
}
123123

124+
pub fn explain(mut self) -> Self {
125+
self.data.explain = Some(ExplainClause::Simple);
126+
self
127+
}
128+
129+
pub fn explain_full(mut self) -> Self {
130+
self.data.explain = Some(ExplainClause::Full);
131+
self
132+
}
133+
124134
pub fn build(self) -> String {
125135
let mut query = String::with_capacity(128);
126136
push_clause!(query, "SELECT");
@@ -176,6 +186,10 @@ impl FromReady {
176186
push_clause!(query, "FETCH {fetch_fields}");
177187
}
178188

189+
if let Some(explain) = self.data.explain {
190+
push_clause!(query, "{explain}");
191+
}
192+
179193
query
180194
}
181195
}

src/enums.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,67 @@ use crate::{
55
types::select::{OrderOptions, SelectField},
66
};
77

8+
/// Represents the RETURN clause variants shared across statements.
9+
///
10+
/// SurrealQL supports:
11+
/// - `RETURN NONE`
12+
/// - `RETURN BEFORE`
13+
/// - `RETURN AFTER`
14+
/// - `RETURN DIFF`
15+
/// - `RETURN <param1>, <param2>, ...`
16+
/// - `RETURN VALUE <param>`
17+
#[derive(Debug, Clone)]
18+
pub enum ReturnClause {
19+
/// `RETURN NONE`
20+
None,
21+
/// `RETURN BEFORE`
22+
Before,
23+
/// `RETURN AFTER`
24+
After,
25+
/// `RETURN DIFF`
26+
Diff,
27+
/// `RETURN <field1>, <field2>, ...`
28+
Params(Vec<String>),
29+
/// `RETURN VALUE <field>`
30+
Value(String),
31+
}
32+
33+
impl Display for ReturnClause {
34+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35+
match self {
36+
ReturnClause::None => write!(f, "NONE"),
37+
ReturnClause::Before => write!(f, "BEFORE"),
38+
ReturnClause::After => write!(f, "AFTER"),
39+
ReturnClause::Diff => write!(f, "DIFF"),
40+
ReturnClause::Params(params) => {
41+
let joined = params.join(", ");
42+
write!(f, "{joined}")
43+
}
44+
ReturnClause::Value(field) => write!(f, "VALUE {field}"),
45+
}
46+
}
47+
}
48+
49+
/// Represents EXPLAIN clause modes shared across statements.
50+
///
51+
/// SurrealQL supports: `EXPLAIN` or `EXPLAIN FULL`.
52+
#[derive(Debug, Clone, PartialEq)]
53+
pub enum ExplainClause {
54+
/// `EXPLAIN`
55+
Simple,
56+
/// `EXPLAIN FULL`
57+
Full,
58+
}
59+
60+
impl Display for ExplainClause {
61+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62+
match self {
63+
ExplainClause::Simple => write!(f, "EXPLAIN"),
64+
ExplainClause::Full => write!(f, "EXPLAIN FULL"),
65+
}
66+
}
67+
}
68+
869
/// Direction of graph traversal arrows.
970
#[derive(Debug, Clone)]
1071
pub enum Direction {

src/lib.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ pub mod macros;
55

66
pub mod builders;
77
pub(crate) mod internal_macros;
8-
pub mod structs;
98
pub mod traits;
109
pub mod types;
1110

1211
use crate::{
13-
builders::{delete::DeleteBuilder, select::SelectBuilder},
12+
builders::{create::CreateBuilder, delete::DeleteBuilder, select::SelectBuilder},
1413
enums::SelectionFields,
1514
types::{
15+
create::CreateData,
1616
delete::DeleteData,
1717
select::{SelectData, SelectField},
1818
},
@@ -43,4 +43,12 @@ impl QueryBuilder {
4343
};
4444
DeleteBuilder { data }
4545
}
46+
47+
pub fn create(targets: &str) -> CreateBuilder {
48+
let data = CreateData {
49+
targets: targets.to_string(),
50+
..Default::default()
51+
};
52+
CreateBuilder { data }
53+
}
4654
}

0 commit comments

Comments
 (0)