surrealdb-core 3.2.3

A scalable, distributed, collaborative, document-graph database, for the realtime web
Documentation
#![recursion_limit = "256"]

mod helpers;
use std::time::{Duration, Instant};

use anyhow::Result;
use helpers::Test;
use surrealdb_core::dbs::{Capabilities, Session};
#[allow(unused_imports)]
use surrealdb_core::kvs::Datastore;

#[tokio::test]
async fn statement_timeouts() -> Result<()> {
	let sql = "
		CREATE ONLY person:ok CONTENT { test: true };
		CREATE person:test CONTENT { test: true } TIMEOUT 0s;
		UPSERT person:test CONTENT { test: true } TIMEOUT 0s;
		UPDATE person:test CONTENT { test: true } TIMEOUT 0s;
		INSERT { id: person:test, test: true } TIMEOUT 0s;
		RELATE person:test->know->person:ok TIMEOUT 0s;
		LET $temp = SELECT * FROM person TIMEOUT 0s;
		SELECT * FROM person TIMEOUT 0s;
		DELETE person:test TIMEOUT 0s;
	";
	let error = "The query was not executed because it exceeded the timeout: 0ns";
	Test::new(sql)
		.await?
		.expect_val("{ id: person:ok, test: true }")?
		.expect_error(error)?
		.expect_error(error)?
		.expect_error(error)?
		.expect_error(error)?
		.expect_error(error)?
		.expect_error(error)?
		.expect_error(error)?
		.expect_error(error)?;

	Ok(())
}

async fn new_ds_with_timeout(query: Option<Duration>, transaction: Option<Duration>) -> Datastore {
	let ds = Datastore::builder()
		.with_capabilities(Capabilities::all())
		.with_query_timeout(query)
		.with_transaction_timeout(transaction)
		.build_with_path("memory")
		.await
		.expect("success");
	ds.execute("DEFINE NS `test`", &Session::owner(), None).await.expect("success");
	let sess = Session::owner().with_ns("test");
	ds.execute("DEFINE DB `test`", &sess, None).await.expect("success");
	ds
}

#[tokio::test]
async fn query_timeout() -> Result<()> {
	let sql = "
		FOR $i in 0..1000000000{
			FOR $i in 0..1000000000{
				FOR $i in 0..1000000000{
				}
			}
		}
	";
	let ds = new_ds_with_timeout(Some(Duration::from_millis(500)), None).await;
	let session = Session::owner();
	let before = Instant::now();
	let mut res = ds.execute(sql, &session, None).await.unwrap();
	if before.elapsed() > Duration::from_millis(7050) {
		panic!("Query did not properly timeout");
	}
	res.pop().unwrap().result.unwrap_err();

	Ok(())
}

#[tokio::test]
async fn transaction_timeout() -> Result<()> {
	let ds = new_ds_with_timeout(None, Some(Duration::from_millis(500))).await;
	let session = Session::owner().with_ns("test").with_db("test");

	let before = Instant::now();
	let mut res = ds.execute("SLEEP 10s", &session, None).await.unwrap();
	let elapsed = before.elapsed();
	assert!(
		elapsed < Duration::from_secs(5),
		"Transaction timeout was not enforced, took {elapsed:?}"
	);

	let result = res.pop().unwrap().result;
	let err = result.unwrap_err().to_string();
	assert!(err.contains("exceeded the timeout"), "Expected transaction timeout error, got: {err}");

	Ok(())
}

#[tokio::test]
async fn transaction_timeout_begin_commit() -> Result<()> {
	let ds = new_ds_with_timeout(None, Some(Duration::from_millis(500))).await;
	let session = Session::owner().with_ns("test").with_db("test");

	let before = Instant::now();
	let res = ds
		.execute("BEGIN; CREATE person:1 SET name = 'a'; SLEEP 10s; COMMIT;", &session, None)
		.await
		.unwrap();
	let elapsed = before.elapsed();
	assert!(
		elapsed < Duration::from_secs(5),
		"Transaction timeout was not enforced in BEGIN block, took {elapsed:?}"
	);

	let has_timeout_err = res.iter().any(|r| {
		r.result.as_ref().is_err_and(|e| {
			e.to_string().contains("exceeded the timeout") || e.to_string().contains("timed out")
		})
	});
	assert!(has_timeout_err, "Expected transaction timeout error in BEGIN block results: {res:?}");

	Ok(())
}