diff --git a/lib/src/dbs/session.rs b/lib/src/dbs/session.rs index dc9470e6..b012c441 100644 --- a/lib/src/dbs/session.rs +++ b/lib/src/dbs/session.rs @@ -70,6 +70,16 @@ impl Session { ..Session::default() } } + /// Set the selected namespace for the session + pub fn with_ns(mut self, ns: &str) -> Session { + self.ns = Some(ns.to_owned()); + self + } + /// Set the selected database for the session + pub fn with_db(mut self, db: &str) -> Session { + self.db = Some(db.to_owned()); + self + } /// Retrieves the selected namespace pub(crate) fn ns(&self) -> Option> { self.ns.to_owned().map(Arc::new) diff --git a/lib/tests/field.rs b/lib/tests/field.rs new file mode 100644 index 00000000..2229ebe1 --- /dev/null +++ b/lib/tests/field.rs @@ -0,0 +1,83 @@ +mod parse; +use parse::Parse; +use surrealdb::sql::Value; +use surrealdb::Datastore; +use surrealdb::Error; +use surrealdb::Session; + +#[tokio::test] +async fn field_definition_value_assert_failure() -> Result<(), Error> { + let sql = " + DEFINE TABLE person SCHEMAFULL; + DEFINE FIELD age ON person TYPE number ASSERT $value > 0; + DEFINE FIELD email ON person TYPE string ASSERT is::email($value); + DEFINE FIELD name ON person TYPE string VALUE $value OR 'No name'; + CREATE person:test SET email = 'info@surrealdb.com', other = 'ignore'; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 5); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(matches!( + tmp.err(), + Some(e) if e.to_string() == "Found '0' for field 'age' but field must conform to: $value > 0" + )); + // + Ok(()) +} + +#[tokio::test] +async fn field_definition_value_assert_success() -> Result<(), Error> { + let sql = " + DEFINE TABLE person SCHEMAFULL; + DEFINE FIELD age ON person TYPE number ASSERT $value > 0; + DEFINE FIELD email ON person TYPE string ASSERT is::email($value); + DEFINE FIELD name ON person TYPE string VALUE $value OR 'No name'; + CREATE person:test SET email = 'info@surrealdb.com', other = 'ignore', age = 22; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 5); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + id: person:test, + email: 'info@surrealdb.com', + age: 22, + name: 'No name', + } + ]", + ); + assert_eq!(tmp, val); + // + Ok(()) +} diff --git a/lib/tests/insert.rs b/lib/tests/insert.rs new file mode 100644 index 00000000..5d68ac29 --- /dev/null +++ b/lib/tests/insert.rs @@ -0,0 +1,138 @@ +mod parse; +use parse::Parse; +use surrealdb::sql::Value; +use surrealdb::Datastore; +use surrealdb::Error; +use surrealdb::Session; + +#[tokio::test] +async fn insert_statement_object_single() -> Result<(), Error> { + let sql = " + INSERT INTO test { + id: 'tester', + test: true, + something: 'other', + }; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[{ id: test:tester, test: true, something: 'other' }]"); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn insert_statement_object_multiple() -> Result<(), Error> { + let sql = " + INSERT INTO test [ + { + id: 1, + test: true, + something: 'other', + }, + { + id: 2, + test: false, + something: 'else', + }, + ]; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { id: test:1, test: true, something: 'other' }, + { id: test:2, test: false, something: 'else' } + ]", + ); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn insert_statement_values_single() -> Result<(), Error> { + let sql = " + INSERT INTO test (id, test, something) VALUES ('tester', true, 'other'); + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[{ id: test:tester, test: true, something: 'other' }]"); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn insert_statement_values_multiple() -> Result<(), Error> { + let sql = " + INSERT INTO test (id, test, something) VALUES (1, true, 'other'), (2, false, 'else'); + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { id: test:1, test: true, something: 'other' }, + { id: test:2, test: false, something: 'else' } + ]", + ); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn insert_statement_on_duplicate_key() -> Result<(), Error> { + let sql = " + INSERT INTO test (id, test, something) VALUES ('tester', true, 'other'); + INSERT INTO test (id, test, something) VALUES ('tester', true, 'other') ON DUPLICATE KEY UPDATE something = 'else'; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 2); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[{ id: test:tester, test: true, something: 'other' }]"); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[{ id: test:tester, test: true, something: 'else' }]"); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn insert_statement_output() -> Result<(), Error> { + let sql = " + INSERT INTO test (id, test, something) VALUES ('tester', true, 'other') RETURN something; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[{ something: 'other' }]"); + assert_eq!(tmp, val); + // + Ok(()) +} diff --git a/lib/tests/parse.rs b/lib/tests/parse.rs new file mode 100644 index 00000000..f5b34622 --- /dev/null +++ b/lib/tests/parse.rs @@ -0,0 +1,20 @@ +use surrealdb::sql::json; +use surrealdb::sql::thing; +use surrealdb::sql::Thing; +use surrealdb::sql::Value; + +pub trait Parse { + fn parse(val: &str) -> T; +} + +impl Parse for Value { + fn parse(val: &str) -> Value { + json(val).unwrap() + } +} + +impl Parse for Thing { + fn parse(val: &str) -> Thing { + thing(val).unwrap() + } +} diff --git a/lib/tests/script.rs b/lib/tests/script.rs new file mode 100644 index 00000000..0b7a9971 --- /dev/null +++ b/lib/tests/script.rs @@ -0,0 +1,160 @@ +mod parse; +use parse::Parse; +use surrealdb::sql::Value; +use surrealdb::Datastore; +use surrealdb::Error; +use surrealdb::Session; + +#[tokio::test] +async fn script_function_simple() -> Result<(), Error> { + let sql = " + CREATE person:test SET scores = function() { + return [6.6, 8.4, 7.3].map(v => v * 10); + }; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[{ id: person:test, scores: [66, 84, 73] }]"); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn script_function_context() -> Result<(), Error> { + let sql = " + CREATE film:test SET + ratings = [ + { rating: 6.3 }, + { rating: 8.7 }, + ], + display = function() { + return this.ratings.filter(r => { + return r.rating >= 7; + }).map(r => { + return { ...r, rating: Math.round(r.rating * 10) }; + }); + } + ; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + id: film:test, + ratings: [ + { rating: 6.3 }, + { rating: 8.7 }, + ], + display: [ + { rating: 87 }, + ] + } + ]", + ); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn script_function_arguments() -> Result<(), Error> { + let sql = " + LET $value = 'SurrealDB'; + LET $words = ['awesome', 'advanced', 'cool']; + CREATE article:test SET summary = function($value, $words) { + return `${arguments[0]} is ${arguments[1].join(', ')}`; + }; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 3); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + id: article:test, + summary: 'SurrealDB is awesome, advanced, cool', + } + ]", + ); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn script_function_types() -> Result<(), Error> { + let sql = " + CREATE article:test SET + created_at = function() { + return new Date('1995-12-17T03:24:00'); + }, + next_signin = function() { + return new Duration('1w2d6h'); + }, + manager = function() { + return new Record('user', 'joanna'); + }, + identifier = function() { + return new Uuid('03412258-988f-47cd-82db-549902cdaffe'); + } + ; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + id: article:test, + created_at: '1995-12-17T03:24:00Z', + next_signin: 1w2d6h, + manager: user:joanna, + identifier: '03412258-988f-47cd-82db-549902cdaffe', + } + ]", + ); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn script_function_module_os() -> Result<(), Error> { + let sql = " + CREATE platform:test SET version = function() { + const { release } = await import('os'); + return release(); + }; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + Ok(()) +} diff --git a/lib/tests/strict.rs b/lib/tests/strict.rs new file mode 100644 index 00000000..73a0864b --- /dev/null +++ b/lib/tests/strict.rs @@ -0,0 +1,191 @@ +mod parse; +use parse::Parse; +use surrealdb::sql::Value; +use surrealdb::Datastore; +use surrealdb::Error; +use surrealdb::Session; + +#[tokio::test] +async fn strict_mode_no_namespace() -> Result<(), Error> { + let sql = " + -- DEFINE NAMESPACE test; + DEFINE DATABASE test; + DEFINE TABLE test; + DEFINE FIELD extra ON test VALUE true; + CREATE test:tester; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, true).await?; + assert_eq!(res.len(), 4); + // + let tmp = res.remove(0).result; + assert!(matches!(tmp.err(), Some(Error::NsNotFound))); + // + let tmp = res.remove(0).result; + assert!(matches!(tmp.err(), Some(Error::NsNotFound))); + // + let tmp = res.remove(0).result; + assert!(matches!(tmp.err(), Some(Error::NsNotFound))); + // + let tmp = res.remove(0).result; + assert!(matches!(tmp.err(), Some(Error::NsNotFound))); + // + Ok(()) +} + +#[tokio::test] +async fn strict_mode_no_database() -> Result<(), Error> { + let sql = " + DEFINE NAMESPACE test; + -- DEFINE DATABASE test; + DEFINE TABLE test; + DEFINE FIELD extra ON test VALUE true; + CREATE test:tester; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, true).await?; + assert_eq!(res.len(), 4); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(matches!(tmp.err(), Some(Error::DbNotFound))); + // + let tmp = res.remove(0).result; + assert!(matches!(tmp.err(), Some(Error::DbNotFound))); + // + let tmp = res.remove(0).result; + assert!(matches!(tmp.err(), Some(Error::DbNotFound))); + // + Ok(()) +} + +#[tokio::test] +async fn strict_mode_no_table() -> Result<(), Error> { + let sql = " + DEFINE NAMESPACE test; + DEFINE DATABASE test; + -- DEFINE TABLE test; + DEFINE FIELD extra ON test VALUE true; + CREATE test:tester; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, true).await?; + assert_eq!(res.len(), 4); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(matches!(tmp.err(), Some(Error::TbNotFound))); + // + let tmp = res.remove(0).result; + assert!(matches!(tmp.err(), Some(Error::TbNotFound))); + // + Ok(()) +} + +#[tokio::test] +async fn strict_mode_all_ok() -> Result<(), Error> { + let sql = " + DEFINE NAMESPACE test; + DEFINE DATABASE test; + DEFINE TABLE test; + DEFINE FIELD extra ON test VALUE true; + CREATE test:tester; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, true).await?; + assert_eq!(res.len(), 5); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[{ id: test:tester, extra: true }]"); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn loose_mode_all_ok() -> Result<(), Error> { + let sql = " + DEFINE FIELD extra ON test VALUE true; + CREATE test:tester; + INFO FOR KV; + INFO FOR NS; + INFO FOR DB; + INFO FOR TABLE test; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 6); + // + let tmp = res.remove(0).result; + assert!(tmp.is_ok()); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[{ id: test:tester, extra: true }]"); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + ns: { test: 'DEFINE NAMESPACE test' }, + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + db: { test: 'DEFINE DATABASE test' }, + nl: {}, + nt: {}, + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + dl: {}, + dt: {}, + sc: {}, + tb: { test: 'DEFINE TABLE test SCHEMALESS PERMISSIONS NONE' }, + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + ev: {}, + fd: { extra: 'DEFINE FIELD extra ON test VALUE true' }, + ft: {}, + ix: {}, + }", + ); + assert_eq!(tmp, val); + // + Ok(()) +}