query()
Runs one or more SurrealQL statements against the database.
Method Syntaxdb.query(query)
Argument | Type | Description | |||
---|---|---|---|---|---|
query | query | Specifies the SurrealQL statements. |
The .query()
method serves as a default way to pass queries into the Rust SDK. The simplest usage of this method is by passing in a &str
and returning a Response
.
use surrealdb::engine::any::connect; use surrealdb::opt::auth::Root; async fn main() -> surrealdb::Result<()> { let db = connect("ws://localhost:8000").await?; db.signin(Root { username: "root", password: "secret", }) .await?; db.use_ns("ns").use_db("db").await?; let query = r#" LET $data = ["J. Jonah Jameson", "James Earl Jones"]; RETURN $data.map(|$name| { LET $names = $name.split(' '); { first_name: $names[0], middle_name: $names[1], last_name: $names[2] } }); "#; let result = db.query(query).await?; println!("Number of statements: {}", result.num_statements()); dbg!(result); Ok(()) }
The .take()
method can be used to pull out one of the responses into a deserialized format. Note that in the next example the LET
statement is the first statement received by the database, and thus .take(1)
is used to grab the output of the second statement to deserialize into a Person
struct.
use serde::Deserialize; use surrealdb::engine::any::connect; use surrealdb::opt::auth::Root; struct Person { first_name: String, middle_name: String, last_name: String, } async fn main() -> surrealdb::Result<()> { let db = connect("ws://localhost:8000").await?; db.signin(Root { username: "root", password: "secret", }) .await?; db.use_ns("ns").use_db("db").await?; let query = r#" LET $data = ["J. Jonah Jameson", "James Earl Jones"]; RETURN $data.map(|$name| { LET $names = $name.split(' '); { first_name: $names[0], middle_name: $names[1], last_name: $names[2] } });"#; let mut result = db.query(query).await?; let people: Vec<Person> = result.take(1)?; dbg!(people); Ok(()) }
The return value from this method is Result<Response, Error>
. A Result::Ok(Response)
only means that the query or queries were successfully executed, but does not mean that each query contained in the Response
was successful.
Take the following code for example which contains one successful query, followed by one with incorrect syntax (an integer where a string is expected).
use surrealdb::engine::any::connect; async fn main() { let db = connect("memory").await.unwrap(); let res = db.query(" LET $x = 9; LET $x: string = 9") // valid SurrealQL but wrong type .await; println!("{res:?}"); }
The .query()
method returns an Ok(Response)
, showing that the database was able to understand and process the queries, even though the latter returned an error.
Ok(Response { results: {0: (Stats { execution_time: Some(197.875µs) }, Ok(None)), 1: (Stats { execution_time: Some(207.625µs) }, Err(Db(SetCheck { value: "9", name: "x", check: "string" })))}, live_queries: {} })
But if the function contains input that the database is unable to parse into a query in the first place, an Err
will be returned for the entire .query()
call. If the string
syntax is changed to something nonsensical like Hi how are you?
, the database is unable to process the query in the first place and .query()
will return an Err
for the whole call.
use surrealdb::engine::any::connect; async fn main() { let db = connect("memory").await.unwrap(); let res = db.query(" LET $x = 9; Hi how are you?;") // invalid SurrealQL .await; println!("{res:?}"); }
Err(Db(InvalidQuery(RenderedError { errors: ["Unexpected token `an identifier`, expected Eof"], snippets: [Snippet { source: "LET $x = 9; Hi how are you?", truncation: None, location: Location { line: 1, column: 16 }, offset: 15, length: 3, label: None, kind: Error }] })))
The Response
struct contains helper metods such as .check()
to check for errors, or .take_errors()
which removes the errors from the main Response
.
use surrealdb::engine::any::connect; async fn main() { let db = connect("memory").await.unwrap(); db.use_ns("ns").use_db("db").await.unwrap(); let mut res = db .query( "LET $x = 9; LET $x: string = 9; LET $x: bool = 9; CREATE person", ) .await .unwrap(); println!("Errors: {:?}\n", res.take_errors()); println!("Successes: {:?}", res); }
Output:
Errors: {1: Db(SetCheck { value: "9", name: "x", check: "string" }), 2: Db(SetCheck { value: "9", name: "x", check: "bool" })} Successes: Response { results: {0: (Stats { execution_time: Some(143.458µs) }, Ok(None)), 3: (Stats { execution_time: Some(1.463583ms) }, Ok(Array(Array([Object(Object({"id": Thing(Thing { tb: "person", id: String("aokn0fp36pmqlxprjhre") })}))]))))}, live_queries: {} }
As the .query()
method can be used to pass any SurrealQL query on to the database, it is an easy go-to when using complex queries. However, be sure to keep the following best practices in mind when doing so.
When using SurrealDB as a traditional backend database, your application will usually build SurrealQL queries that may need to contain some untrusted input, such as that provided by the users of your application. To do so, SurrealDB offers bind as a method to query, which should always be used when including untrusted input into queries. Otherwise, SurrealDB will be unable to separate the actual query syntax from the user input, resulting in the well-known SQL injection vulnerabilities. This practice is known as prepared statements or parameterised queries.
Thus, instead of using user input to directly construct a string:
let bad_sql = format!(" CREATE {user_input}; SELECT * FROM {user_input};");
You can insert a parameter using SurrealQL’s $
parameter syntax,
let sql = " CREATE person; SELECT * FROM type::table($table); ";
And then apply the .bind()
method to pass the parameter in.
use serde::Deserialize; use surrealdb::engine::any::connect; use surrealdb::opt::auth::Root; use surrealdb::RecordId; struct Person { id: RecordId, } async fn main() -> surrealdb::Result<()> { let db = connect("ws://localhost:8000").await?; db.signin(Root { username: "root", password: "secret", }) .await?; db.use_ns("ns").use_db("db").await?; let sql = " CREATE person; SELECT * FROM type::table($table); "; let mut result = db.query(sql).bind(("table", "person")).await?; // Get the first result from the first query let created: Option<Person> = result.take(0)?; dbg!(created); // Get all of the results from the second query let people: Vec<Person> = result.take(1)?; dbg!(people); Ok(()) }