// Tests common to all protocols and storage engines use surrealdb::fflags::FFLAGS; use surrealdb::value; use surrealdb::Response; use surrealdb_core::sql::{Id, Value as CoreValue}; static PERMITS: Semaphore = Semaphore::const_new(1); #[test_log::test(tokio::test)] async fn connect() { let (permit, db) = new_db().await; drop(permit); db.health().await.unwrap(); } #[test_log::test(tokio::test)] async fn yuse() { let (permit, db) = new_db().await; let item = Ulid::new().to_string(); match db.create(Resource::from(item.as_str())).await.unwrap_err() { // Local engines return this error Error::Db(DbError::NsEmpty) => {} // Remote engines return this error Error::Api(ApiError::Query(error)) if error.contains("Specify a namespace to use") => {} error => panic!("{:?}", error), } db.use_ns(NS).await.unwrap(); match db.create(Resource::from(item.as_str())).await.unwrap_err() { // Local engines return this error Error::Db(DbError::DbEmpty) => {} // Remote engines return this error Error::Api(ApiError::Query(error)) if error.contains("Specify a database to use") => {} error => panic!("{:?}", error), } db.use_db(item.as_str()).await.unwrap(); db.create(Resource::from(item)).await.unwrap(); drop(permit); } #[test_log::test(tokio::test)] async fn invalidate() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); db.invalidate().await.unwrap(); let error = db.create::>(("user", "john")).await.unwrap_err(); assert!( error.to_string().contains("Not enough permissions to perform this action"), "Unexpected error: {:?}", error ); } #[test_log::test(tokio::test)] async fn signup_record() { let (permit, db) = new_db().await; let database = Ulid::new().to_string(); db.use_ns(NS).use_db(&database).await.unwrap(); let access = Ulid::new().to_string(); let sql = format!( " DEFINE ACCESS `{access}` ON DB TYPE RECORD SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) ) SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) ) DURATION FOR SESSION 1d FOR TOKEN 15s " ); let response = db.query(sql).await.unwrap(); drop(permit); response.check().unwrap(); db.signup(RecordAccess { namespace: NS, database: &database, access: &access, params: AuthParams { email: "john.doe@example.com", pass: "password123", }, }) .await .unwrap(); } #[test_log::test(tokio::test)] async fn signin_ns() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); let user = Ulid::new().to_string(); let pass = "password123"; let sql = format!("DEFINE USER `{user}` ON NAMESPACE PASSWORD '{pass}'"); let response = db.query(sql).await.unwrap(); drop(permit); response.check().unwrap(); db.signin(Namespace { namespace: NS, username: &user, password: pass, }) .await .unwrap(); } #[test_log::test(tokio::test)] async fn signin_db() { let (permit, db) = new_db().await; let database = Ulid::new().to_string(); db.use_ns(NS).use_db(&database).await.unwrap(); let user = Ulid::new().to_string(); let pass = "password123"; let sql = format!("DEFINE USER `{user}` ON DATABASE PASSWORD '{pass}'"); let response = db.query(sql).await.unwrap(); drop(permit); response.check().unwrap(); db.signin(Database { namespace: NS, database: &database, username: &user, password: pass, }) .await .unwrap(); } #[test_log::test(tokio::test)] async fn signin_record() { let (permit, db) = new_db().await; let database = Ulid::new().to_string(); db.use_ns(NS).use_db(&database).await.unwrap(); let access = Ulid::new().to_string(); let email = format!("{access}@example.com"); let pass = "password123"; let sql = format!( " DEFINE ACCESS `{access}` ON DB TYPE RECORD SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) ) SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) ) DURATION FOR SESSION 1d FOR TOKEN 15s " ); let response = db.query(sql).await.unwrap(); drop(permit); response.check().unwrap(); db.signup(RecordAccess { namespace: NS, database: &database, access: &access, params: AuthParams { pass, email: &email, }, }) .await .unwrap(); db.signin(RecordAccess { namespace: NS, database: &database, access: &access, params: AuthParams { pass, email: &email, }, }) .await .unwrap(); } #[test_log::test(tokio::test)] async fn record_access_throws_error() { let (permit, db) = new_db().await; let database = Ulid::new().to_string(); db.use_ns(NS).use_db(&database).await.unwrap(); let access = Ulid::new().to_string(); let email = format!("{access}@example.com"); let pass = "password123"; let sql = format!( " DEFINE ACCESS `{access}` ON DB TYPE RECORD SIGNUP {{ THROW 'signup_thrown_error' }} SIGNIN {{ THROW 'signin_thrown_error' }} DURATION FOR SESSION 1d FOR TOKEN 15s " ); let response = db.query(sql).await.unwrap(); drop(permit); response.check().unwrap(); match db .signup(RecordAccess { namespace: NS, database: &database, access: &access, params: AuthParams { pass, email: &email, }, }) .await { Err(Error::Db(surrealdb::err::Error::Thrown(e))) => assert_eq!(e, "signup_thrown_error"), Err(Error::Api(surrealdb::error::Api::Query(e))) => assert!(e.contains("signup")), Err(Error::Api(surrealdb::error::Api::Http(e))) => assert_eq!( e, "HTTP status client error (400 Bad Request) for url (http://127.0.0.1:8000/signup)" ), v => panic!("Unexpected response or error: {v:?}"), }; match db .signin(RecordAccess { namespace: NS, database: &database, access: &access, params: AuthParams { pass, email: &email, }, }) .await { Err(Error::Db(surrealdb::err::Error::Thrown(e))) => assert_eq!(e, "signin_thrown_error"), Err(Error::Api(surrealdb::error::Api::Query(e))) => assert!(e.contains("signin")), Err(Error::Api(surrealdb::error::Api::Http(e))) => assert_eq!( e, "HTTP status client error (400 Bad Request) for url (http://127.0.0.1:8000/signin)" ), v => panic!("Unexpected response or error: {v:?}"), }; } #[test_log::test(tokio::test)] async fn record_access_invalid_query() { let (permit, db) = new_db().await; let database = Ulid::new().to_string(); db.use_ns(NS).use_db(&database).await.unwrap(); let access = Ulid::new().to_string(); let email = format!("{access}@example.com"); let pass = "password123"; let sql = format!( " DEFINE ACCESS `{access}` ON DB TYPE RECORD SIGNUP {{ SELECT * FROM ONLY [1, 2] }} SIGNIN {{ SELECT * FROM ONLY [1, 2] }} DURATION FOR SESSION 1d FOR TOKEN 15s " ); let response = db.query(sql).await.unwrap(); drop(permit); response.check().unwrap(); match db .signup(RecordAccess { namespace: NS, database: &database, access: &access, params: AuthParams { pass, email: &email, }, }) .await { Err(Error::Db(surrealdb::err::Error::AccessRecordSignupQueryFailed)) => (), Err(Error::Api(surrealdb::error::Api::Query(e))) => { assert_eq!( e, "There was a problem with the database: The record access signup query failed" ) } Err(Error::Api(surrealdb::error::Api::Http(e))) => assert_eq!( e, "HTTP status client error (400 Bad Request) for url (http://127.0.0.1:8000/signup)" ), v => panic!("Unexpected response or error: {v:?}"), }; match db .signin(RecordAccess { namespace: NS, database: &database, access: &access, params: AuthParams { pass, email: &email, }, }) .await { Err(Error::Db(surrealdb::err::Error::AccessRecordSigninQueryFailed)) => (), Err(Error::Api(surrealdb::error::Api::Query(e))) => { assert_eq!( e, "There was a problem with the database: The record access signin query failed" ) } Err(Error::Api(surrealdb::error::Api::Http(e))) => assert_eq!( e, "HTTP status client error (400 Bad Request) for url (http://127.0.0.1:8000/signin)" ), v => panic!("Unexpected response or error: {v:?}"), }; } #[test_log::test(tokio::test)] async fn authenticate() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); let user = Ulid::new().to_string(); let pass = "password123"; let sql = format!("DEFINE USER `{user}` ON NAMESPACE PASSWORD '{pass}'"); let response = db.query(sql).await.unwrap(); drop(permit); response.check().unwrap(); let token = db .signin(Namespace { namespace: NS, username: &user, password: pass, }) .await .unwrap(); db.authenticate(token).await.unwrap(); } #[test_log::test(tokio::test)] async fn query() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let _ = db .query( " CREATE user:john SET name = 'John Doe' ", ) .await .unwrap() .check() .unwrap(); let mut response = db.query("SELECT name FROM user:john").await.unwrap().check().unwrap(); let Some(name): Option = response.take("name").unwrap() else { panic!("query returned no record"); }; assert_eq!(name, "John Doe"); } #[test_log::test(tokio::test)] async fn query_decimals() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); let sql = " DEFINE TABLE foo; DEFINE FIELD bar ON foo TYPE decimal; CREATE foo CONTENT { bar: 42.69 }; "; let _ = db.query(sql).await.unwrap().check().unwrap(); drop(permit); } #[test_log::test(tokio::test)] async fn query_binds() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let mut response = db.query("CREATE user:john SET name = $name").bind(("name", "John Doe")).await.unwrap(); let Some(record): Option = response.take(0).unwrap() else { panic!("query returned no record"); }; assert_eq!(record.name, "John Doe"); let mut response = db .query("SELECT * FROM $record_id") .bind(("record_id", "user:john".parse::().unwrap())) .await .unwrap(); let Some(record): Option = response.take(0).unwrap() else { panic!("query returned no record"); }; assert_eq!(record.name, "John Doe"); let mut response = db .query("CREATE user SET name = $name") .bind(Record { name: "John Doe".to_owned(), }) .await .unwrap(); let Some(record): Option = response.take(0).unwrap() else { panic!("query returned no record"); }; assert_eq!(record.name, "John Doe"); } #[test_log::test(tokio::test)] async fn query_with_stats() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let sql = "CREATE foo; SELECT * FROM foo"; let mut response = db.query(sql).with_stats().await.unwrap(); // First query statement let (stats, result) = response.take(0).unwrap(); assert!(stats.execution_time > Some(Duration::ZERO)); let _: Value = result.unwrap(); // Second query statement let (stats, result) = response.take(1).unwrap(); assert!(stats.execution_time > Some(Duration::ZERO)); let _: Vec = result.unwrap(); } #[test_log::test(tokio::test)] async fn query_chaining() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let response = db .query(BeginStatement::default()) .query("CREATE account:one SET balance = 135605.16") .query("CREATE account:two SET balance = 91031.31") .query("UPDATE account:one SET balance += 300.00") .query("UPDATE account:two SET balance -= 300.00") .query(CommitStatement::default()) .await .unwrap(); response.check().unwrap(); } #[test_log::test(tokio::test)] async fn mixed_results_query() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let sql = "CREATE bar SET baz = rand('a'); CREATE foo;"; let mut response = db.query(sql).await.unwrap(); response.take::(0).unwrap_err(); let _: Option = response.take(1).unwrap(); } #[test_log::test(tokio::test)] async fn create_record_no_id() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let _: Vec = db.create("user").await.unwrap(); let _: Value = db.create(Resource::from("user")).await.unwrap(); } #[test_log::test(tokio::test)] async fn create_record_with_id() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let _: Option = db.create(("user", "jane")).await.unwrap(); let _: Value = db.create(Resource::from(("user", "john"))).await.unwrap(); let _: Value = db.create(Resource::from("user:doe")).await.unwrap(); } #[test_log::test(tokio::test)] async fn create_record_no_id_with_content() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let _: Vec = db .create("user") .content(Record { name: "John Doe".to_owned(), }) .await .unwrap(); let _: Value = db .create(Resource::from("user")) .content(Record { name: "John Doe".to_owned(), }) .await .unwrap(); } #[test_log::test(tokio::test)] async fn create_record_with_id_with_content() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let record: Option = db .create(("user", "john")) .content(Record { name: "John Doe".to_owned(), }) .await .unwrap(); assert_eq!(record.unwrap().id, "user:john".parse::().unwrap()); let value: Value = db .create(Resource::from("user:jane".parse::().unwrap())) .content(Record { name: "Jane Doe".to_owned(), }) .await .unwrap(); assert_eq!( value.into_inner().record(), Some("user:jane".parse::().unwrap().into_inner()) ); } #[test_log::test(tokio::test)] async fn insert_table() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let table = "user"; let _: Vec = db.insert(table).await.unwrap(); let _: Vec = db.insert(table).content(json!({ "foo": "bar" })).await.unwrap(); let _: Vec = db.insert(table).content(json!([{ "foo": "bar" }])).await.unwrap(); let _: Value = db.insert(Resource::from(table)).await.unwrap(); let _: Value = db.insert(Resource::from(table)).content(json!({ "foo": "bar" })).await.unwrap(); let _: Value = db.insert(Resource::from(table)).content(json!([{ "foo": "bar" }])).await.unwrap(); let users: Vec = db.insert(table).await.unwrap(); assert!(!users.is_empty()); } #[test_log::test(tokio::test)] async fn insert_thing() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let table = "user"; let _: Option = db.insert((table, "user1")).await.unwrap(); let _: Option = db.insert((table, "user1")).content(json!({ "foo": "bar" })).await.unwrap(); let _: Value = db.insert(Resource::from((table, "user2"))).await.unwrap(); let _: Value = db.insert(Resource::from((table, "user2"))).content(json!({ "foo": "bar" })).await.unwrap(); let user: Option = db.insert((table, "user3")).await.unwrap(); assert_eq!( user, Some(ApiRecordId { id: "user:user3".parse().unwrap(), }) ); } #[test_log::test(tokio::test)] async fn insert_unspecified() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let tmp: Result, _> = db.insert(()).await; tmp.unwrap_err(); let tmp: Result, _> = db.insert(()).content(json!({ "foo": "bar" })).await; tmp.unwrap_err(); let tmp: Vec = db .insert(()) .content("{id: user:user1, foo: 'bar'}".parse::().unwrap()) .await .unwrap(); assert_eq!( tmp, vec![ApiRecordId { id: "user:user1".parse::().unwrap(), }] ); let tmp: Result = db.insert(Resource::from(())).await; tmp.unwrap_err(); let tmp: Result = db.insert(Resource::from(())).content(json!({ "foo": "bar" })).await; tmp.unwrap_err(); let tmp: Value = db .insert(Resource::from(())) .content("{id: user:user2, foo: 'bar'}".parse::().unwrap()) .await .unwrap(); let val = "{id: user:user2, foo: 'bar'}".parse::().unwrap(); assert_eq!(tmp, val); } #[test_log::test(tokio::test)] async fn select_table() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let table = "user"; let _: Vec = db.create(table).await.unwrap(); let _: Vec = db.create(table).await.unwrap(); let _: Value = db.create(Resource::from(table)).await.unwrap(); let users: Vec = db.select(table).await.unwrap(); assert_eq!(users.len(), 3); } #[test_log::test(tokio::test)] async fn select_record_id() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let record_id = ("user", "john"); let _: Option = db.create(record_id).await.unwrap(); let Some(record): Option = db.select(record_id).await.unwrap() else { panic!("record not found"); }; assert_eq!(record.id, "user:john".parse().unwrap()); let value: Value = db.select(Resource::from(record_id)).await.unwrap(); assert_eq!( value.into_inner().record(), Some("user:john".parse::().unwrap().into_inner()) ); } #[test_log::test(tokio::test)] async fn select_record_ranges() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let table = "user"; let _: Option = db.create((table, "amos")).await.unwrap(); let _: Option = db.create((table, "jane")).await.unwrap(); let _: Option = db.create((table, "john")).await.unwrap(); let _: Value = db.create(Resource::from((table, "zoey"))).await.unwrap(); let convert = |users: Vec| -> Vec { users .into_iter() .map(|user| { let Id::String(ref x) = user.id.into_inner().id else { panic!() }; x.clone() }) .collect() }; let users: Vec = db.select(table).range(..).await.unwrap(); assert_eq!(convert(users), vec!["amos", "jane", "john", "zoey"]); let users: Vec = db.select(table).range(.."john").await.unwrap(); assert_eq!(convert(users), vec!["amos", "jane"]); let users: Vec = db.select(table).range(..="john").await.unwrap(); assert_eq!(convert(users), vec!["amos", "jane", "john"]); let users: Vec = db.select(table).range("jane"..).await.unwrap(); assert_eq!(convert(users), vec!["jane", "john", "zoey"]); let users: Vec = db.select(table).range("jane".."john").await.unwrap(); assert_eq!(convert(users), vec!["jane"]); let users: Vec = db.select(table).range("jane"..="john").await.unwrap(); assert_eq!(convert(users), vec!["jane", "john"]); let v: Value = db.select(Resource::from(table)).range("jane"..="john").await.unwrap(); let CoreValue::Array(array) = v.into_inner() else { panic!() }; assert_eq!(array.len(), 2); let users: Vec = db.select(table).range((Bound::Excluded("jane"), Bound::Included("john"))).await.unwrap(); assert_eq!(convert(users), vec!["john"]); } #[test_log::test(tokio::test)] async fn select_records_order_by_start_limit() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let sql = " CREATE user:john SET name = 'John'; CREATE user:zoey SET name = 'Zoey'; CREATE user:amos SET name = 'Amos'; CREATE user:jane SET name = 'Jane'; "; db.query(sql).await.unwrap().check().unwrap(); let check_start_limit = |mut response: Response, expected: Vec<&str>| { let users: Vec = response.take(0).unwrap(); let users: Vec = users.into_iter().map(|user| user.name).collect(); assert_eq!(users, expected); }; let response = db.query("SELECT name FROM user ORDER BY name DESC START 1 LIMIT 2").await.unwrap(); check_start_limit(response, vec!["John", "Jane"]); let response = db.query("SELECT name FROM user ORDER BY name DESC START 1").await.unwrap(); check_start_limit(response, vec!["John", "Jane", "Amos"]); let response = db.query("SELECT name FROM user ORDER BY name DESC START 4").await.unwrap(); check_start_limit(response, vec![]); let response = db.query("SELECT name FROM user ORDER BY name DESC LIMIT 2").await.unwrap(); check_start_limit(response, vec!["Zoey", "John"]); let response = db.query("SELECT name FROM user ORDER BY name DESC LIMIT 10").await.unwrap(); check_start_limit(response, vec!["Zoey", "John", "Jane", "Amos"]); } #[test_log::test(tokio::test)] async fn select_records_order_by() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let sql = " CREATE user:john SET name = 'John'; CREATE user:zoey SET name = 'Zoey'; CREATE user:amos SET name = 'Amos'; CREATE user:jane SET name = 'Jane'; "; db.query(sql).await.unwrap().check().unwrap(); let sql = "SELECT name FROM user ORDER BY name DESC"; let mut response = db.query(sql).await.unwrap(); let users: Vec = response.take(0).unwrap(); let convert = |users: Vec| -> Vec { users.into_iter().map(|user| user.name).collect() }; assert_eq!(convert(users), vec!["Zoey", "John", "Jane", "Amos"]); } #[test_log::test(tokio::test)] async fn select_records_fetch() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let sql = " CREATE tag:rs SET name = 'Rust'; CREATE tag:go SET name = 'Golang'; CREATE tag:js SET name = 'JavaScript'; CREATE person:tobie SET tags = [tag:rs, tag:go, tag:js]; CREATE person:jaime SET tags = [tag:js]; "; db.query(sql).await.unwrap().check().unwrap(); let check_fetch = |mut response: Response, expected: &str| { let val: Value = response.take(0).unwrap(); let exp = expected.parse().unwrap(); assert_eq!(val, exp); }; let sql = "SELECT * FROM person LIMIT 1 FETCH tags;"; let response = db.query(sql).await.unwrap(); check_fetch( response, "[ { id: person:jaime, tags: [ { id: tag:js, name: 'JavaScript' } ] } ]", ); let sql = "SELECT * FROM person START 1 LIMIT 1 FETCH tags;"; let response = db.query(sql).await.unwrap(); check_fetch( response, "[ { id: person:tobie, tags: [ { id: tag:rs, name: 'Rust' }, { id: tag:go, name: 'Golang' }, { id: tag:js, name: 'JavaScript' } ] } ]", ); let sql = "SELECT * FROM person ORDER BY id FETCH tags;"; let response = db.query(sql).await.unwrap(); check_fetch( response, "[ { id: person:jaime, tags: [ { id: tag:js, name: 'JavaScript' } ] }, { id: person:tobie, tags: [ { id: tag:rs, name: 'Rust' }, { id: tag:go, name: 'Golang' }, { id: tag:js, name: 'JavaScript' } ] } ]", ); } #[test_log::test(tokio::test)] async fn update_table() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let table = "user"; let _: Vec = db.create(table).await.unwrap(); let _: Vec = db.create(table).await.unwrap(); let _: Value = db.update(Resource::from(table)).await.unwrap(); let users: Vec = db.update(table).await.unwrap(); assert_eq!(users.len(), 2); } #[test_log::test(tokio::test)] async fn update_record_id() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let table = "user"; let _: Option = db.create((table, "john")).await.unwrap(); let _: Option = db.create((table, "jane")).await.unwrap(); let users: Vec = db.update(table).await.unwrap(); assert_eq!(users.len(), 2); } #[test_log::test(tokio::test)] async fn update_table_with_content() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let sql = " CREATE type::thing($table, 'amos') SET name = 'Amos'; CREATE type::thing($table, 'jane') SET name = 'Jane'; CREATE type::thing($table, 'john') SET name = 'John'; CREATE type::thing($table, 'zoey') SET name = 'Zoey'; "; let table = "user"; let response = db.query(sql).bind(("table", table)).await.unwrap(); response.check().unwrap(); let users: Vec = db .update(table) .content(Record { name: "Doe".to_owned(), }) .await .unwrap(); let expected = &[ RecordBuf { id: "user:amos".parse().unwrap(), name: "Doe".to_owned(), }, RecordBuf { id: "user:jane".parse().unwrap(), name: "Doe".to_owned(), }, RecordBuf { id: "user:john".parse().unwrap(), name: "Doe".to_owned(), }, RecordBuf { id: "user:zoey".parse().unwrap(), name: "Doe".to_owned(), }, ]; assert_eq!(users, expected); let users: Vec = db.select(table).await.unwrap(); assert_eq!(users, expected); } #[test_log::test(tokio::test)] async fn update_record_range_with_content() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let sql = " CREATE type::thing($table, 'amos') SET name = 'Amos'; CREATE type::thing($table, 'jane') SET name = 'Jane'; CREATE type::thing($table, 'john') SET name = 'John'; CREATE type::thing($table, 'zoey') SET name = 'Zoey'; "; let table = "user"; let response = db.query(sql).bind(("table", table)).await.unwrap(); response.check().unwrap(); let users: Vec = db .update(table) .range("jane".."zoey") .content(Record { name: "Doe".to_owned(), }) .await .unwrap(); assert_eq!( users, &[ RecordBuf { id: "user:jane".parse().unwrap(), name: "Doe".to_owned(), }, RecordBuf { id: "user:john".parse().unwrap(), name: "Doe".to_owned(), }, ] ); let users: Vec = db.select(table).await.unwrap(); assert_eq!( users, &[ RecordBuf { id: "user:amos".parse().unwrap(), name: "Amos".to_owned(), }, RecordBuf { id: "user:jane".parse().unwrap(), name: "Doe".to_owned(), }, RecordBuf { id: "user:john".parse().unwrap(), name: "Doe".to_owned(), }, RecordBuf { id: "user:zoey".parse().unwrap(), name: "Zoey".to_owned(), }, ] ); } #[test_log::test(tokio::test)] async fn update_record_id_with_content() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let record_id = ("user", "john"); let user: Option = db .create(record_id) .content(Record { name: "Jane Doe".to_owned(), }) .await .unwrap(); assert_eq!(user.unwrap().name, "Jane Doe"); let user: Option = db .update(record_id) .content(Record { name: "John Doe".to_owned(), }) .await .unwrap(); assert_eq!(user.unwrap().name, "John Doe"); let user: Option = db.select(record_id).await.unwrap(); assert_eq!(user.unwrap().name, "John Doe"); } #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd)] struct Name { first: Cow<'static, str>, last: Cow<'static, str>, } #[derive(Debug, Serialize, Deserialize, PartialEq, PartialOrd)] struct Person { #[serde(skip_serializing)] id: Option, title: Cow<'static, str>, name: Name, marketing: bool, } #[test_log::test(tokio::test)] async fn merge_record_id() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let record_id = ("person", "jaime"); let mut jaime: Option = db .create(record_id) .content(Person { id: None, title: "Founder & COO".into(), name: Name { first: "Jaime".into(), last: "Morgan Hitchcock".into(), }, marketing: false, }) .await .unwrap(); assert_eq!(jaime.unwrap().id.unwrap(), "person:jaime".parse().unwrap()); jaime = db.update(record_id).merge(json!({ "marketing": true })).await.unwrap(); assert!(jaime.as_ref().unwrap().marketing); jaime = db.select(record_id).await.unwrap(); assert_eq!( jaime.unwrap(), Person { id: Some("person:jaime".parse().unwrap()), title: "Founder & COO".into(), name: Name { first: "Jaime".into(), last: "Morgan Hitchcock".into(), }, marketing: true, } ); } #[test_log::test(tokio::test)] async fn patch_record_id() { #[derive(Debug, Deserialize, PartialEq)] struct Record { id: RecordId, baz: String, hello: Vec, } let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let id = "john"; let _: Option = db .create(("user", id)) .content(json!({ "baz": "qux", "foo": "bar" })) .await .unwrap(); let _: Option = db .update(("user", id)) .patch(PatchOp::replace("/baz", "boo")) .patch(PatchOp::add("/hello", ["world"])) .patch(PatchOp::remove("/foo")) .await .unwrap(); let value: Option = db.select(("user", id)).await.unwrap(); assert_eq!( value, Some(Record { id: format!("user:{id}").parse().unwrap(), baz: "boo".to_owned(), hello: vec!["world".to_owned()], }) ); } #[test_log::test(tokio::test)] async fn delete_table() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let table = "user"; let _: Vec = db.create(table).await.unwrap(); let _: Vec = db.create(table).await.unwrap(); let _: Vec = db.create(table).await.unwrap(); let users: Vec = db.select(table).await.unwrap(); assert_eq!(users.len(), 3); let users: Vec = db.delete(table).await.unwrap(); assert_eq!(users.len(), 3); let users: Vec = db.select(table).await.unwrap(); assert!(users.is_empty()); } #[test_log::test(tokio::test)] async fn delete_record_id() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let record_id = ("user", "john"); let _: Option = db.create(record_id).await.unwrap(); let _: Option = db.select(record_id).await.unwrap(); let john: Option = db.delete(record_id).await.unwrap(); assert!(john.is_some()); let john: Option = db.select(record_id).await.unwrap(); assert!(john.is_none()); // non-existing user let jane: Option = db.delete(("user", "jane")).await.unwrap(); assert!(jane.is_none()); let value: Value = db.delete(Resource::from(("user", "jane"))).await.unwrap(); assert_eq!(value.into_inner(), CoreValue::None); } #[test_log::test(tokio::test)] async fn delete_record_range() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let sql = " CREATE type::thing($table, 'amos') SET name = 'Amos'; CREATE type::thing($table, 'jane') SET name = 'Jane'; CREATE type::thing($table, 'john') SET name = 'John'; CREATE type::thing($table, 'zoey') SET name = 'Zoey'; "; let table = "user"; let response = db.query(sql).bind(("table", table)).await.unwrap(); response.check().unwrap(); let users: Vec = db.delete(table).range("jane".."zoey").await.unwrap(); assert_eq!( users, &[ RecordBuf { id: "user:jane".parse().unwrap(), name: "Jane".to_owned(), }, RecordBuf { id: "user:john".parse().unwrap(), name: "John".to_owned(), }, ] ); let users: Vec = db.select(table).await.unwrap(); assert_eq!( users, &[ RecordBuf { id: "user:amos".parse().unwrap(), name: "Amos".to_owned(), }, RecordBuf { id: "user:zoey".parse().unwrap(), name: "Zoey".to_owned(), }, ] ); } #[test_log::test(tokio::test)] async fn changefeed() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); // Enable change feeds let sql = " DEFINE TABLE user CHANGEFEED 1h; "; let response = db.query(sql).await.unwrap(); drop(permit); response.check().unwrap(); // Create and update users let sql = " CREATE user:amos SET name = 'Amos'; CREATE user:jane SET name = 'Jane'; UPDATE user:amos SET name = 'AMOS'; "; let table = "user"; let response = db.query(sql).await.unwrap(); response.check().unwrap(); let users: Vec = db .update(table) .content(Record { name: "Doe".to_owned(), }) .await .unwrap(); let expected = &[ RecordBuf { id: "user:amos".parse().unwrap(), name: "Doe".to_owned(), }, RecordBuf { id: "user:jane".parse().unwrap(), name: "Doe".to_owned(), }, ]; assert_eq!(users, expected); let users: Vec = db.select(table).await.unwrap(); assert_eq!(users, expected); let sql = " SHOW CHANGES FOR TABLE user SINCE 0 LIMIT 10; "; let mut response = db.query(sql).await.unwrap(); let v: Value = response.take(0).unwrap(); let CoreValue::Array(array) = v.into_inner() else { panic!() }; assert_eq!(array.len(), 5); // DEFINE TABLE let a = array.first().unwrap(); let CoreValue::Object(a) = a.clone() else { unreachable!() }; let CoreValue::Number(_versionstamp1) = a.get("versionstamp").clone().unwrap() else { unreachable!() }; let changes = a.get("changes").unwrap().clone().to_owned(); assert_eq!( Value::from_inner(changes), "[ { define_table: { name: 'user' } } ]" .parse() .unwrap() ); // UPDATE user:amos let a = array.get(1).unwrap(); let CoreValue::Object(a) = a.clone() else { unreachable!() }; let CoreValue::Number(versionstamp1) = a.get("versionstamp").unwrap() else { unreachable!() }; let changes = a.get("changes").unwrap().to_owned(); match FFLAGS.change_feed_live_queries.enabled() { true => { assert_eq!( Value::from_inner(changes), r#"[ { create: { id: user:amos, name: 'Amos' } } ]"# .parse() .unwrap() ); } false => { assert_eq!( Value::from_inner(changes), r#"[ { update: { id: user:amos, name: 'Amos' } } ]"# .parse() .unwrap() ); } } // UPDATE user:jane let a = array.get(2).unwrap(); let CoreValue::Object(a) = a.clone() else { unreachable!() }; let CoreValue::Number(versionstamp2) = a.get("versionstamp").unwrap().clone() else { unreachable!() }; assert!(*versionstamp1 < versionstamp2); let changes = a.get("changes").unwrap().to_owned(); match FFLAGS.change_feed_live_queries.enabled() { true => { assert_eq!( Value::from_inner(changes), "[ { create: { id: user:jane, name: 'Jane' } } ]" .parse() .unwrap() ); } false => { assert_eq!( Value::from_inner(changes), "[ { update: { id: user:jane, name: 'Jane' } } ]" .parse() .unwrap() ); } } // UPDATE user:amos let a = array.get(3).unwrap(); let CoreValue::Object(a) = a.clone() else { unreachable!() }; let CoreValue::Number(versionstamp3) = a.get("versionstamp").unwrap() else { unreachable!() }; assert!(versionstamp2 < *versionstamp3); let changes = a.get("changes").unwrap().to_owned(); match FFLAGS.change_feed_live_queries.enabled() { true => { assert_eq!( Value::from_inner(changes), "[ { create: { id: user:amos, name: 'AMOS' } } ]" .parse() .unwrap() ); } false => { assert_eq!( Value::from_inner(changes), "[ { update: { id: user:amos, name: 'AMOS' } } ]" .parse() .unwrap() ); } }; // UPDATE table let a = array.get(4).unwrap(); let CoreValue::Object(a) = a.clone() else { unreachable!() }; let CoreValue::Number(versionstamp4) = a.get("versionstamp").unwrap() else { unreachable!() }; assert!(versionstamp3 < versionstamp4); let changes = a.get("changes").unwrap().to_owned(); assert_eq!( Value::from_inner(changes), "[ { update: { id: user:amos, name: 'Doe' } }, { update: { id: user:jane, name: 'Doe' } } ]" .parse() .unwrap() ); } #[test_log::test(tokio::test)] async fn version() { let (permit, db) = new_db().await; drop(permit); db.version().await.unwrap(); } #[test_log::test(tokio::test)] async fn set_unset() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let (key, value) = ("name", "Doe"); let sql = "RETURN $name"; db.set(key, value).await.unwrap(); let mut response = db.query(sql).await.unwrap(); let Some(name): Option = response.take(0).unwrap() else { panic!("record not found"); }; assert_eq!(name, value); // `token` is a reserved variable db.set("token", value).await.unwrap_err(); // make sure we can still run queries after trying to set a protected variable db.query("RETURN true").await.unwrap().check().unwrap(); db.unset(key).await.unwrap(); let mut response = db.query(sql).await.unwrap(); let name: Option = response.take(0).unwrap(); assert!(name.is_none()); } #[test_log::test(tokio::test)] async fn return_bool() { let (permit, db) = new_db().await; let mut response = db.query("RETURN true").await.unwrap(); drop(permit); let Some(boolean): Option = response.take(0).unwrap() else { panic!("record not found"); }; assert!(boolean); let mut response = db.query("RETURN false").await.unwrap(); let value: Value = response.take(0).unwrap(); assert_eq!(value.into_inner(), CoreValue::Bool(false)); } #[test_log::test(tokio::test)] async fn run() { let (permit, db) = new_db().await; db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap(); drop(permit); let sql = " DEFINE FUNCTION fn::foo() { RETURN 42; }; DEFINE FUNCTION fn::bar($val: any) { CREATE foo:1 set val = $val; }; DEFINE FUNCTION fn::baz() { RETURN SELECT VALUE val FROM ONLY foo:1; }; "; let _ = db.query(sql).await; let tmp: i32 = db.run("fn::foo").await.unwrap(); assert_eq!(tmp, 42); let tmp = db.run::("fn::foo").args(7).await.unwrap_err(); println!("fn::foo res: {tmp}"); assert!(tmp.to_string().contains("The function expects 0 arguments.")); let tmp = db.run::<()>("fn::idnotexist").await.unwrap_err(); println!("fn::idontexist res: {tmp}"); assert!(tmp.to_string().contains("The function 'fn::idnotexist' does not exist")); let tmp: usize = db.run("count").args(vec![1, 2, 3]).await.unwrap(); assert_eq!(tmp, 3); let tmp: Option = db.run("fn::bar").args(7).await.unwrap(); assert_eq!(tmp, None); let tmp: i32 = db.run("fn::baz").await.unwrap(); assert_eq!(tmp, 7); }