Runs one or more SurrealQL statements against the database.
db.query(query)Arguments
Argument | Type | Description |
|---|---|---|
query | query | Specifies the SurrealQL statements. |
Example usage
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 an IndexedResults.
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
#[tokio::main]
async fn main() -> surrealdb::Result<()> {
let db = connect("ws://localhost:8000").await?;
db.signin(Root {
username: "root".to_string(),
password: "secret".to_string(),
})
.await?;
db.use_ns("main").use_db("main").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 surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb_types::SurrealValue;
#[derive(Debug, SurrealValue)]
struct Person {
first_name: String,
middle_name: String,
last_name: String,
}
#[tokio::main]
async fn main() -> surrealdb::Result<()> {
let db = connect("ws://localhost:8000").await?;
db.signin(Root {
username: "root".to_string(),
password: "secret".to_string(),
})
.await?;
db.use_ns("main").use_db("main").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`. 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;
#[tokio::main]
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(IndexedResults), showing that the database was able to understand and process the queries, even though the latter returned an error.
Ok(IndexedResults { results: {0: (DbResultStats { execution_time: Some(392.875µs), query_type: Some(Other) }, Ok(None)), 1: (DbResultStats { execution_time: Some(426.042µs), query_type: Some(Other) }, Err(InternalError("Tried to set `$x`, but couldn't coerce value: Expected `string` but found `9`")))}, 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;
#[tokio::main]
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(ParseError("Parse error: Unexpected token `an identifier`, expected Eof\n --> [3:12]\n |\n3 | Hi how are you?;\n | ^^^\n"))The IndexedResults struct contains helper metods such as .check() to check for errors, or .take_errors() which removes the errors from the main IndexedResults.
use surrealdb::engine::any::connect;
#[tokio::main]
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: {2: InternalError("Tried to set `$x`, but couldn't coerce value: Expected `bool` but found `9`"), 1: InternalError("Tried to set `$x`, but couldn't coerce value: Expected `string` but found `9`")}
Successes: IndexedResults { results: {0: (DbResultStats { execution_time: Some(301.375µs), query_type: Some(Other) }, Ok(None)), 3: (DbResultStats { execution_time: Some(2.278083ms), query_type: Some(Other) }, Ok(Array(Array([Object(Object({"id": RecordId(RecordId { table: Table("person"), key: String("yq7gxgm3ffkr4kibumeb") })}))]))))}, live_queries: {} } Per-statement stats (.with_stats())
The [`.with_stats()`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Query.html#method.with_stats) method can be used on the query builder before awaiting the future. The awaited value is [`WithStats`](https://docs.rs/surrealdb/latest/surrealdb/method/query/struct.WithStats.html), which wraps the usual [`IndexedResults`](https://docs.rs/surrealdb/latest/surrealdb/method/query/struct.IndexedResults.html) so each `.take(...)` can return both [`Stats`](https://docs.rs/surrealdb/latest/surrealdb/method/struct.Stats.html) (including execution time) and the deserialized statement result.
use surrealdb::engine::any::connect;
use surrealdb::types::Value;
#[tokio::main]
async fn main() -> surrealdb::Result<()> {
let db = connect("memory").await?;
db.use_ns("ns").use_db("db").await?;
let mut response = db
.query("CREATE person:ada SET name = 'Ada'; SELECT * FROM person;")
.with_stats()
.await?;
if let Some((stats, res)) = response.take(1) {
let records: Vec<Value> = res?;
println!("time = {:?}, records = {:?}", stats.execution_time, records);
}
Ok(())
}See WithStats::take on Docs.rs for the supported .take shapes (statement index, nested paths, and tuples).
Stream LIVE SELECT output (.stream())
After awaiting .query(...), IndexedResults::stream turns the live-query slot at a given statement index into a QueryStream. Pass a statement index (0, 1, …), or pass () to merge every LIVE SELECT in that response. The stream yields Notification values (or raw Value) and implements futures::Stream. This can be polled with the StreamExt trait from the futures crate.
If you prefer not to embed LIVE SELECT in SurrealQL, the same live subscription can be started with db.select(resource).live() on top of select(); both approaches yield a stream of notifications.
use futures::StreamExt;
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::types::Value;
#[tokio::main]
async fn main() -> surrealdb::Result<()> {
let db = connect("ws://localhost:8000").await?;
db.use_ns("main").use_db("main").await?;
db.signin(Root {
username: "root".into(),
password: "secret".into(),
})
.await?;
// Use 2 or 3 instead of () to only LIVE SELECT
// either person or cat
let mut response = db
.query(
"DEFINE TABLE IF NOT EXISTS person SCHEMALESS;
DEFINE TABLE IF NOT EXISTS cat SCHEMALESS;
LIVE SELECT * FROM person;
LIVE SELECT * FROM cat;",
)
.await?;
let mut stream = response.stream::<Value>(())?;
while let Some(item) = stream.next().await {
let notification = item?;
println!("{:?}", notification);
}
Ok(())
}To test the live stream, either log in using Surrealist or the CLI using the surreal sql --user root --pass secret command in another terminal window. You should see notifications similar to the following whenever a new record is created from the person or cat table, but not for others.
Notification { query_id: Uuid(3bad02bb-fd1e-402b-9a43-5b3eae88f279), action: Create, data: Object(Object({"id": RecordId(RecordId { table: Table("person"), key: String("zhby5ibqh8b2hfyyao30") })})) }
Notification { query_id: Uuid(829d7ec7-d67c-47ef-bd7c-8b3e10b8d149), action: Create, data: Object(Object({"id": RecordId(RecordId { table: Table("cat"), key: String("cgu921pkfco7uk7ajeym") })})) }Security when using the .query() method
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.
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 surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb_types::{RecordId, SurrealValue};
#[derive(Debug, SurrealValue)]
struct Person {
id: RecordId,
}
#[tokio::main]
async fn main() -> surrealdb::Result<()> {
let db = connect("ws://localhost:8000").await?;
db.signin(Root {
username: "root".to_string(),
password: "secret".to_string(),
})
.await?;
db.use_ns("main").use_db("main").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(())
}See also
Live queries via
select().live()(alternative toLIVE SELECTin this page)