diff --git a/Cargo.toml b/Cargo.toml index 5dfc4ec5..97560e1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,13 @@ authors = ["Maxwell Flitton "] edition = "2018" [dependencies] -surrealdb = { version = "1.2.0", features = ["protocol-ws", "protocol-http", "kv-mem"] } +surrealdb = { version = "2.0.2", features = [ + "protocol-ws", + "protocol-http", + "kv-mem", +] } serde = "^1.0.164" -futures = { version="0.3.30", features = ["executor"] } +futures = { version = "0.3.30", features = ["executor"] } once_cell = "1.19.0" serde_json = "^1.0.97" crossbeam-channel = "^0.5" @@ -20,7 +24,7 @@ futures-util-preview = "0.2.2" [lib] name = "rust_surrealdb" -crate-type=["cdylib"] +crate-type = ["cdylib"] [dependencies.pyo3] version = "0.20.0" diff --git a/src/operations/auth/core.rs b/src/operations/auth/core.rs index 4b508bcf..32929ac0 100644 --- a/src/operations/auth/core.rs +++ b/src/operations/auth/core.rs @@ -1,55 +1,70 @@ //! Defines the core functions for the auth operations against the database. -use surrealdb::opt::auth::Scope; use serde_json::value::Value; +use surrealdb::opt::auth::Record; use super::interface::WrappedJwt; use crate::connection::interface::WrappedConnection; - /// Signs up to a specific authentication scope in an async manner. -/// +/// /// # Arguments /// * `connection` - The connection that will be performing the signup /// * `params` - The auth parameters to be used for the signup such as email and password /// * `namespace` - The namespace to be used for the signup /// * `database` - The database to be used for the signup -/// +/// /// # Returns /// * `Ok(String)` - The token for the signup -pub async fn sign_up(connection: WrappedConnection, params: Value, namespace: String, database: String, scope: String) -> Result { - let token = connection.connection.signup(Scope { - namespace: namespace.as_str(), - database: database.as_str(), - scope: scope.as_str(), - params: params, - }).await.map_err(|e| e.to_string())?; - let token = WrappedJwt {jwt: token}; - return Ok(token) +pub async fn sign_up( + connection: WrappedConnection, + params: Value, + namespace: String, + database: String, + access: String, +) -> Result { + let token = connection + .connection + .signup(Record { + namespace: namespace.as_str(), + database: database.as_str(), + access: access.as_str(), + params: params, + }) + .await + .map_err(|e| e.to_string())?; + let token = WrappedJwt { jwt: token }; + return Ok(token); } - /// Invalidates the authentication for the current connection in an async manner. -/// +/// /// # Arguments /// * `connection` - The connection to be invalidated -/// +/// /// # Returns /// * `Ok(())` - The operation was successful pub async fn invalidate(connection: WrappedConnection) -> Result<(), String> { - connection.connection.invalidate().await.map_err(|e| e.to_string())?; - return Ok(()) + connection + .connection + .invalidate() + .await + .map_err(|e| e.to_string())?; + return Ok(()); } - /// Authenticates the current connection with a JWT token in an async manner. -/// +/// /// # Arguments /// * `connection` - The connection to be authenticated /// * `jwt` - The JWT token to be used for authentication -/// +/// /// # Returns /// * `Ok(())` - The operation was successful pub async fn authenticate(connection: WrappedConnection, jwt: WrappedJwt) -> Result<(), String> { - connection.connection.authenticate(jwt.jwt).await.map_err(|e| e.to_string())?; - return Ok(()) + connection + .connection + .authenticate(jwt.jwt) + .await + .map_err(|e| e.to_string())?; + return Ok(()); } diff --git a/src/operations/create/core.rs b/src/operations/create/core.rs index 39385eb2..ff5a7890 100644 --- a/src/operations/create/core.rs +++ b/src/operations/create/core.rs @@ -1,81 +1,139 @@ //! Defines the core functions for creating records. These functions should not be called directly //! from the Python API but rather from the TCP connection in the runtime module. In this //! module we can do the following: -//! +//! //! * Create a record in the database +use crate::connection::interface::WrappedConnection; use serde_json::value::Value; use surrealdb::opt::Resource; -use surrealdb::sql::Range; -use crate::connection::interface::WrappedConnection; - +use surrealdb::sql::Thing; +use surrealdb::{RecordId, RecordIdKey}; /// Creates a record in the database. -/// +/// /// # Arguments /// * `connection` - The connection performing the operation on the database /// * `table_name` - The name of the table to create the record in -/// +/// /// # Returns /// * `Ok(())` - The record was created successfully -pub async fn create(connection: WrappedConnection, table_name: String, data: Value) -> Result { - let resource = Resource::from(table_name.clone()); - let outcome = connection.connection.create(resource).content(data).await.map_err(|e| e.to_string())?; - let outcome_string = outcome.into_json().to_string(); +pub async fn create( + connection: WrappedConnection, + resource: String, + data: Value, +) -> Result { + let resource = match resource.parse::() { + Ok(rid) => Resource::RecordId(RecordId::from_inner(rid)), + Err(_) => Resource::from(resource), + }; + let outcome = connection + .connection + .create(resource) + .content(data) + .await + .map_err(|e| e.to_string())?; + let outcome_string = outcome.into_inner().into_json().to_string(); Ok(outcome_string) } - /// Delete all records, or a specific record /// /// # Arguments /// * `connection_id` - The connection performing the operation on the database /// * `resource` - The resource to delete (can be a table or a range) -/// +/// /// # Returns -/// +/// pub async fn delete(connection: WrappedConnection, resource: String) -> Result { - let response = match resource.parse::() { - Ok(range) => { - connection.connection.delete(Resource::from(range.tb)).range((range.beg, range.end)) - .await.map_err(|e| e.to_string())? - } - Err(_) => connection.connection.delete(Resource::from(resource)) - .await.map_err(|e| e.to_string())? + let response = match resource.parse::() { + Ok(rid) => match rid.id { + surrealdb::sql::Id::Range(id_range) => connection + .connection + .delete(Resource::from(rid.tb)) + .range(( + id_range.beg.map(RecordIdKey::from_inner), + id_range.end.map(RecordIdKey::from_inner), + )) + .await + .map_err(|e| e.to_string())? + .into_inner() + .into_json(), + _ => connection + .connection + .delete::>(RecordId::from_inner(rid)) + .await + .map_err(|e| e.to_string())? + .unwrap_or_default() + .into_inner() + .into_json(), + }, + Err(_) => connection + .connection + .delete(Resource::from(resource)) + .await + .map_err(|e| e.to_string())? + .into_inner() + .into_json(), }; - Ok(response.into_json().to_string()) + Ok(response.to_string()) } +// fn map_bound() -> Boun #[cfg(test)] mod tests { use super::*; - use crate::operations::query::core::query; use crate::connection::core::make_connection; - use tokio::runtime::Runtime; + use crate::operations::query::core::query; use serde_json::{from_str, Value}; - + use tokio::runtime::Runtime; fn generate_json(name: &str, age: i32) -> Value { - let json_string = format!(r#" + let json_string = format!( + r#" {{ "name": "{}", "age": {} }} - "#, name, age); + "#, + name, age + ); let json_value: Value = from_str(&json_string).unwrap(); json_value } - async fn prime_database(connection: WrappedConnection) { - query(connection.clone(), "CREATE user:1 SET name = 'Tobie', age = 1;".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:2 SET name = 'Jaime', age = 1;".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:3 SET name = 'Dave', age = 2;".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:4 SET name = 'Tom', age = 2;".to_string(), None).await.unwrap(); + query( + connection.clone(), + "CREATE user:1 SET name = 'Tobie', age = 1;".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:2 SET name = 'Jaime', age = 1;".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:3 SET name = 'Dave', age = 2;".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:4 SET name = 'Tom', age = 2;".to_string(), + None, + ) + .await + .unwrap(); } - #[test] fn test_create() { let runtime = Runtime::new().unwrap(); @@ -83,8 +141,12 @@ mod tests { let outcome = runtime.block_on(async { let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); create(connection.clone(), "user".to_string(), json_value).await }); @@ -100,11 +162,17 @@ mod tests { let outcome = runtime.block_on(async { let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); let _ = create(connection.clone(), "user".to_string(), json_value.clone()).await; let _ = create(connection.clone(), "user".to_string(), json_value).await; - query(connection.clone(), "SELECT * FROM user;".to_string(), None).await.unwrap() + query(connection.clone(), "SELECT * FROM user;".to_string(), None) + .await + .unwrap() }); let outcome: Value = from_str(&outcome).unwrap(); assert_eq!(outcome[0].as_array().unwrap().len(), 2); @@ -116,13 +184,25 @@ mod tests { let outcome = runtime.block_on(async { let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); prime_database(connection.clone()).await; - let outcome: Value = from_str(delete(connection.clone(), "user".to_string()).await.unwrap().as_str()).unwrap(); + let outcome: Value = from_str( + delete(connection.clone(), "user".to_string()) + .await + .unwrap() + .as_str(), + ) + .unwrap(); assert_eq!(outcome.as_array().unwrap().len(), 4); - query(connection.clone(), "SELECT * FROM user;".to_string(), None).await.unwrap() + query(connection.clone(), "SELECT * FROM user;".to_string(), None) + .await + .unwrap() }); let outcome: Value = from_str(&outcome).unwrap(); assert_eq!(outcome[0].as_array().unwrap().len(), 0); @@ -134,13 +214,25 @@ mod tests { let outcome = runtime.block_on(async { let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); prime_database(connection.clone()).await; - let outcome: Value = from_str(delete(connection.clone(), "user:2..4".to_string()).await.unwrap().as_str()).unwrap(); + let outcome: Value = from_str( + delete(connection.clone(), "user:2..4".to_string()) + .await + .unwrap() + .as_str(), + ) + .unwrap(); assert_eq!(outcome.as_array().unwrap().len(), 2); - query(connection.clone(), "SELECT * FROM user;".to_string(), None).await.unwrap() + query(connection.clone(), "SELECT * FROM user;".to_string(), None) + .await + .unwrap() }); let outcome: Value = from_str(&outcome).unwrap(); @@ -155,19 +247,30 @@ mod tests { let outcome = runtime.block_on(async { let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); prime_database(connection.clone()).await; - let outcome: Value = from_str(delete(connection.clone(), "user:2".to_string()).await.unwrap().as_str()).unwrap(); + let outcome: Value = from_str( + delete(connection.clone(), "user:2".to_string()) + .await + .unwrap() + .as_str(), + ) + .unwrap(); assert_eq!(outcome["name"], "Jaime"); assert_eq!(outcome["age"], 1); assert_eq!(outcome["id"], "user:2"); - query(connection.clone(), "SELECT * FROM user;".to_string(), None).await.unwrap() + query(connection.clone(), "SELECT * FROM user;".to_string(), None) + .await + .unwrap() }); let outcome: Value = from_str(&outcome).unwrap(); assert_eq!(outcome[0].as_array().unwrap().len(), 3); } - } diff --git a/src/operations/mod.rs b/src/operations/mod.rs index d2a494ae..c8038fd1 100644 --- a/src/operations/mod.rs +++ b/src/operations/mod.rs @@ -1,17 +1,16 @@ -/// Handles operations to the database. -use pyo3::prelude::{PyModule, wrap_pyfunction}; +/// Handles operations to the database. +use pyo3::prelude::{wrap_pyfunction, PyModule}; +pub mod auth; pub mod create; -pub mod set; pub mod query; +pub mod set; pub mod update; -pub mod auth; - /// Adds operations python entry points to a module handling this factory. -/// +/// /// # Arguments /// * `m` - The module to add the entry points to -/// +/// /// # Returns /// * `()` - Nothing pub fn operations_module_factory(m: &PyModule) { diff --git a/src/operations/query/core.rs b/src/operations/query/core.rs index de5f7a36..7f9dce7b 100644 --- a/src/operations/query/core.rs +++ b/src/operations/query/core.rs @@ -1,160 +1,273 @@ //! Defines the core functions for making queries. These functions should not be called directly //! from the Python API but rather from the TCP connection in the runtime module. In this //! module we can do the following: -//! +//! //! * Perform a query on the database -use serde_json::value::Value; use crate::connection::interface::WrappedConnection; -use surrealdb::sql::Value as SurrealValue; +use serde_json::value::Value; use surrealdb::opt::Resource; -use surrealdb::sql::Range; - +use surrealdb::sql; +use surrealdb::sql::Thing; +use surrealdb::RecordId; +use surrealdb::RecordIdKey; /// Performs a query on the database. -/// +/// /// # Arguments /// * `connection` - The connection to perform the query on /// * `sql` - The SQL query to perform /// * `bindings` - The bindings to use for the query -/// +/// /// # Returns /// * `Ok(Value)` - The result of the query -pub async fn query(connection: WrappedConnection, sql: String, bindings: Option) -> Result { - let mut response = match bindings { - Some(bind) => {connection.connection.query(sql).bind(bind).await}, - None => {connection.connection.query(sql).await} - }.map_err(|e| e.to_string())?; - - // extract data needed from the Response struct - let num_statements = response.num_statements(); - let mut output = Vec::::with_capacity(num_statements); - - // converting SurrealValue items into serde_json::Value items - for index in 0..num_statements { - let value: SurrealValue = response.take(index).map_err(|x| x.to_string())?; - output.push(value.into_json()); - } - let json_value: Value = Value::Array(output); - Ok(json_value.to_string()) +pub async fn query( + connection: WrappedConnection, + sql: String, + bindings: Option, +) -> Result { + let mut response = match bindings { + Some(bind) => connection.connection.query(sql).bind(bind).await, + None => connection.connection.query(sql).await, + } + .map_err(|e| e.to_string())?; + + // extract data needed from the Response struct + let num_statements = response.num_statements(); + let mut output = Vec::::with_capacity(num_statements); + + // converting SurrealValue items into serde_json::Value items + for index in 0..num_statements { + let value: surrealdb::Value = response.take(index).map_err(|x| x.to_string())?; + output.push(value.into_inner().into_json()); + } + let json_value: Value = Value::Array(output); + Ok(json_value.to_string()) } /// Performs a select on the database. -/// +/// /// # Arguments /// * `connection` - The connection to perform the select with /// * `resource` - The resource to select (can be a table or a range) -/// +/// /// # Returns /// * `Ok(Value)` - The result of the select pub async fn select(connection: WrappedConnection, resource: String) -> Result { - let response = match resource.parse::() { - Ok(range) => { - connection.connection.select(Resource::from(range.tb)).range((range.beg, range.end)) - .await.map_err(|e| e.to_string())? - } - Err(_) => connection.connection.select(Resource::from(resource)) - .await.map_err(|e| e.to_string())? - }; - Ok(response.into_json().to_string()) + let response = match resource.parse::() { + Ok(rid) => match rid.id { + sql::Id::Range(id_range) => connection + .connection + .select(Resource::from(rid.tb)) + .range(( + id_range.beg.map(RecordIdKey::from_inner), + id_range.end.map(RecordIdKey::from_inner), + )) + .await + .map_err(|e| e.to_string())?, + _ => connection + .connection + .select(Resource::RecordId(RecordId::from_inner(rid))) + .await + .map_err(|e| e.to_string())?, + }, + Err(_) => connection + .connection + .select(Resource::from(resource)) + .await + .map_err(|e| e.to_string())?, + }; + Ok(response.into_inner().into_json().to_string()) } - #[cfg(test)] mod tests { - use super::*; - use crate::connection::core::make_connection; - use tokio::runtime::Runtime; - use serde_json::{from_str, Value}; - - #[test] - fn test_query() { - - let runtime = Runtime::new().unwrap(); - - let outcome = runtime.block_on(async { - let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); - - query(connection.clone(), "CREATE user:tobie SET name = 'Tobie';".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:jaime SET name = 'Jaime';".to_string(), None).await.unwrap(); - - query(connection, "SELECT * FROM user;".to_string(), None).await.unwrap() - }); - - let outcome: Value = from_str(&outcome).unwrap(); - assert_eq!(outcome[0].as_array().unwrap()[0]["name"], "Jaime"); - assert_eq!(outcome[0].as_array().unwrap()[1]["name"], "Tobie"); - } - - - #[test] - fn test_select_all_users() { - let runtime = Runtime::new().unwrap(); - - let outcome = runtime.block_on(async { - let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); - - query(connection.clone(), "CREATE user:1 SET name = 'Tobie';".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:2 SET name = 'Jaime';".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:3 SET name = 'Dave';".to_string(), None).await.unwrap(); - - select(connection, "user".to_string()).await.unwrap() - }); - - let outcome: Value = from_str(&outcome).unwrap(); - assert_eq!(outcome.as_array().unwrap().len(), 3); - assert_eq!(outcome[0]["name"], "Tobie"); - assert_eq!(outcome[1]["name"], "Jaime"); - assert_eq!(outcome[2]["name"], "Dave"); - } - - #[test] - fn test_select_range_users() { - let runtime = Runtime::new().unwrap(); - - let outcome = runtime.block_on(async { - let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); - - query(connection.clone(), "CREATE user:1 SET name = 'Tobie';".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:2 SET name = 'Jaime';".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:3 SET name = 'Dave';".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:4 SET name = 'Tom';".to_string(), None).await.unwrap(); - - select(connection, "user:1..4".to_string()).await.unwrap() - }); - - let outcome: Value = from_str(&outcome).unwrap(); - assert_eq!(outcome.as_array().unwrap().len(), 3); - assert_eq!(outcome[0]["name"], "Tobie"); - assert_eq!(outcome[1]["name"], "Jaime"); - assert_eq!(outcome[2]["name"], "Dave"); - } - - #[test] - fn test_select_particular_user() { - let runtime = Runtime::new().unwrap(); - - let outcome = runtime.block_on(async { - let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); - - query(connection.clone(), "CREATE user:1 SET name = 'Tobie';".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:2 SET name = 'Jaime';".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:3 SET name = 'Dave';".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:4 SET name = 'Tom';".to_string(), None).await.unwrap(); - - select(connection, "user:2".to_string()).await.unwrap() - }); - - let outcome: Value = from_str(&outcome).unwrap(); - assert_eq!(outcome["name"], "Jaime"); - assert_eq!(outcome["id"], "user:2"); - } + use super::*; + use crate::connection::core::make_connection; + use serde_json::{from_str, Value}; + use tokio::runtime::Runtime; + + #[test] + fn test_query() { + let runtime = Runtime::new().unwrap(); + + let outcome = runtime.block_on(async { + let connection = make_connection("memory".to_string()).await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); + + query( + connection.clone(), + "CREATE user:tobie SET name = 'Tobie';".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:jaime SET name = 'Jaime';".to_string(), + None, + ) + .await + .unwrap(); + + query(connection, "SELECT * FROM user;".to_string(), None) + .await + .unwrap() + }); + + let outcome: Value = from_str(&outcome).unwrap(); + assert_eq!(outcome[0].as_array().unwrap()[0]["name"], "Jaime"); + assert_eq!(outcome[0].as_array().unwrap()[1]["name"], "Tobie"); + } + + #[test] + fn test_select_all_users() { + let runtime = Runtime::new().unwrap(); + + let outcome = runtime.block_on(async { + let connection = make_connection("memory".to_string()).await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); + + query( + connection.clone(), + "CREATE user:1 SET name = 'Tobie';".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:2 SET name = 'Jaime';".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:3 SET name = 'Dave';".to_string(), + None, + ) + .await + .unwrap(); + + select(connection, "user".to_string()).await.unwrap() + }); + + let outcome: Value = from_str(&outcome).unwrap(); + assert_eq!(outcome.as_array().unwrap().len(), 3); + assert_eq!(outcome[0]["name"], "Tobie"); + assert_eq!(outcome[1]["name"], "Jaime"); + assert_eq!(outcome[2]["name"], "Dave"); + } + + #[test] + fn test_select_range_users() { + let runtime = Runtime::new().unwrap(); + + let outcome = runtime.block_on(async { + let connection = make_connection("memory".to_string()).await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); + + query( + connection.clone(), + "CREATE user:1 SET name = 'Tobie';".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:2 SET name = 'Jaime';".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:3 SET name = 'Dave';".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:4 SET name = 'Tom';".to_string(), + None, + ) + .await + .unwrap(); + + select(connection, "user:1..4".to_string()).await.unwrap() + }); + + let outcome: Value = from_str(&outcome).unwrap(); + assert_eq!(outcome.as_array().unwrap().len(), 3); + assert_eq!(outcome[0]["name"], "Tobie"); + assert_eq!(outcome[1]["name"], "Jaime"); + assert_eq!(outcome[2]["name"], "Dave"); + } + + #[test] + fn test_select_particular_user() { + let runtime = Runtime::new().unwrap(); + + let outcome = runtime.block_on(async { + let connection = make_connection("memory".to_string()).await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); + + query( + connection.clone(), + "CREATE user:1 SET name = 'Tobie';".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:2 SET name = 'Jaime';".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:3 SET name = 'Dave';".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:4 SET name = 'Tom';".to_string(), + None, + ) + .await + .unwrap(); + + select(connection, "user:2".to_string()).await.unwrap() + }); + let outcome: Value = from_str(&outcome).unwrap(); + assert_eq!(outcome["name"], "Jaime"); + assert_eq!(outcome["id"], "user:2"); + } } diff --git a/src/operations/update/core.rs b/src/operations/update/core.rs index 09647db9..df526398 100644 --- a/src/operations/update/core.rs +++ b/src/operations/update/core.rs @@ -1,13 +1,18 @@ //! Defines the functions that perform update operations on the database. -use serde_json::value::Value; -use surrealdb::sql::Range; -use surrealdb::opt::Resource; -use surrealdb::opt::PatchOp; use crate::connection::interface::WrappedConnection; use serde::Deserialize; -use std::collections::VecDeque; use serde_json::from_str; +use serde_json::value::Value; +use std::collections::VecDeque; use std::fmt; +use surrealdb::engine; +use surrealdb::method::Update; +use surrealdb::opt::PatchOp; +use surrealdb::opt::Resource; +use surrealdb::sql; +use surrealdb::sql::Thing; +use surrealdb::RecordId; +use surrealdb::RecordIdKey; #[derive(Clone, PartialEq)] pub struct Diff { @@ -28,54 +33,54 @@ impl fmt::Debug for Diff { } } - /// Performs an update on the database for a particular resource. -/// +/// /// # Arguments /// * `connection` - The connection to perform the update with /// * `resource` - The resource to update (can be a table or a range) /// * `data` - The data to update the resource with -/// +/// /// # Returns /// * `Ok(Value)` - The result of the update -pub async fn update(connection: WrappedConnection, resource: String, data: Value) -> Result { - let update = match resource.parse::() { - Ok(range) => connection.connection.update(Resource::from(range.tb)).range((range.beg, range.end)), - Err(_) => connection.connection.update(Resource::from(resource)), - }; +pub async fn update( + connection: WrappedConnection, + resource: String, + data: Value, +) -> Result { + let update = update_inner(&connection, resource); let outcome = match data { Value::Object(_) => update.content(data).await, _ => update.await, - }.map_err(|e| e.to_string())?; - Ok(outcome.into_json().to_string()) + } + .map_err(|e| e.to_string())?; + Ok(outcome.into_inner().into_json().to_string()) } - /// Performs a merge on the database for a particular resource. -/// +/// /// # Arguments /// * `connection` - The connection to perform the merge with /// * `resource` - The resource to merge (can be a table or a range) -/// +/// /// # Returns /// * `Ok(Value)` - The result of the merge -pub async fn merge(connection: WrappedConnection, resource: String, data: Value) -> Result { - let update = match resource.parse::() { - Ok(range) => connection.connection.update(Resource::from(range.tb)).range((range.beg, range.end)), - Err(_) => connection.connection.update(Resource::from(resource)), - }; +pub async fn merge( + connection: WrappedConnection, + resource: String, + data: Value, +) -> Result { + let update = update_inner(&connection, resource); let response = update.merge(data).await.map_err(|e| e.to_string())?; - Ok(response.into_json().to_string()) + Ok(response.into_inner().into_json().to_string()) } - /// Performs a patch on the database for a particular resource. -/// +/// /// # Arguments /// * `connection` - The connection to perform the patch with /// * `resource` - The resource to patch (can be a table or a range) /// * `data` - The data to patch the resource with -/// +/// /// # Data Examples /// For instance, if you wanted to update the last name of the user for all users in the `users` table, /// you would do the following: @@ -88,27 +93,20 @@ pub async fn merge(connection: WrappedConnection, resource: String, data: Value) /// ``` /// # Returns /// an array of the results of the patch for each row that was updated with the patch operation. -pub async fn patch(connection: WrappedConnection, resource: String, data: Value) -> Result { - let patch = match resource.parse::() { - Ok(range) => connection.connection.update(Resource::from(range.tb)).range((range.beg, range.end)), - Err(_) => connection.connection.update(Resource::from(resource)) - }; +pub async fn patch( + connection: WrappedConnection, + resource: String, + data: Value, +) -> Result { + let patch = update_inner(&connection, resource); let data_str = serde_json::to_string(&data).map_err(|e| e.to_string())?; let mut patches: VecDeque = from_str(&data_str).map_err(|e| e.to_string())?; let mut patch = match patches.pop_front() { Some(p) => patch.patch(match p { - Patch::Add { - path, - value, - } => PatchOp::add(&path, value), - Patch::Remove { - path, - } => PatchOp::remove(&path), - Patch::Replace { - path, - value, - } => PatchOp::replace(&path, value), + Patch::Add { path, value } => PatchOp::add(&path, value), + Patch::Remove { path } => PatchOp::remove(&path), + Patch::Replace { path, value } => PatchOp::replace(&path, value), // Patch::Change { // path, // diff, @@ -116,22 +114,14 @@ pub async fn patch(connection: WrappedConnection, resource: String, data: Value) }), None => { let response = patch.await.map_err(|e| e.to_string())?; - return Ok(response.into_json().to_string()) + return Ok(response.into_inner().into_json().to_string()); } }; for p in patches { patch = patch.patch(match p { - Patch::Add { - path, - value, - } => PatchOp::add(&path, value), - Patch::Remove { - path, - } => PatchOp::remove(&path), - Patch::Replace { - path, - value, - } => PatchOp::replace(&path, value), + Patch::Add { path, value } => PatchOp::add(&path, value), + Patch::Remove { path } => PatchOp::remove(&path), + Patch::Replace { path, value } => PatchOp::replace(&path, value), // Patch::Change { // path, // diff, @@ -139,62 +129,120 @@ pub async fn patch(connection: WrappedConnection, resource: String, data: Value) }); } let response = patch.await.map_err(|e| e.to_string())?; - Ok(response.into_json().to_string()) + Ok(response.into_inner().into_json().to_string()) } +pub fn update_inner<'a>( + connection: &'a WrappedConnection, + resource: String, +) -> Update<'a, engine::any::Any, surrealdb::Value> { + let update = match resource.parse::() { + Ok(rid) => match rid.id { + sql::Id::Range(id_range) => { + connection.connection.update(Resource::from(rid.tb)).range(( + id_range.beg.map(RecordIdKey::from_inner), + id_range.end.map(RecordIdKey::from_inner), + )) + } + _ => connection + .connection + .update(Resource::RecordId(RecordId::from_inner(rid))), + }, + Err(_) => connection.connection.update(Resource::from(resource)), + }; + update +} #[derive(Deserialize)] #[serde(remote = "Diff")] struct DiffDef { - operation: i32, - text: String, + operation: i32, + text: String, } #[derive(Deserialize)] #[serde(tag = "op")] #[serde(rename_all = "lowercase")] pub enum Patch { - Add { - path: String, - value: Value, - }, - Remove { - path: String, - }, - Replace { - path: String, - value: Value, - }, - // Change { - // path: String, - // // #[serde(with = "DiffDef")] - // diff: Diff, - // }, + Add { path: String, value: Value }, + Remove { path: String }, + Replace { path: String, value: Value }, + // Change { + // path: String, + // // #[serde(with = "DiffDef")] + // diff: Diff, + // }, } - #[cfg(test)] mod tests { use super::*; - use crate::operations::query::core::query; use crate::connection::core::make_connection; - use tokio::runtime::Runtime; + use crate::operations::query::core::query; use serde_json::{from_str, Value}; - + use tokio::runtime::Runtime; async fn prime_database(connection: WrappedConnection) { - query(connection.clone(), "CREATE user:1 SET name = 'Tobie', age = 1;".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:2 SET name = 'Jaime', age = 1;".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:3 SET name = 'Dave', age = 2;".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:4 SET name = 'Tom', age = 2;".to_string(), None).await.unwrap(); + query( + connection.clone(), + "CREATE user:1 SET name = 'Tobie', age = 1;".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:2 SET name = 'Jaime', age = 1;".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:3 SET name = 'Dave', age = 2;".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:4 SET name = 'Tom', age = 2;".to_string(), + None, + ) + .await + .unwrap(); } async fn prime_merge_database(connection: WrappedConnection) { - query(connection.clone(), "CREATE user:1 SET age = 1, name = {first: 'Tobie', last: 'one'};".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:2 SET age = 1, name = {first: 'Jaime', last: 'two'};".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:3 SET age = 2, name = {first: 'Dave', last: 'three'};".to_string(), None).await.unwrap(); - query(connection.clone(), "CREATE user:4 SET age = 2, name = {first: 'Tom', last: 'four'};".to_string(), None).await.unwrap(); + query( + connection.clone(), + "CREATE user:1 SET age = 1, name = {first: 'Tobie', last: 'one'};".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:2 SET age = 1, name = {first: 'Jaime', last: 'two'};".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:3 SET age = 2, name = {first: 'Dave', last: 'three'};".to_string(), + None, + ) + .await + .unwrap(); + query( + connection.clone(), + "CREATE user:4 SET age = 2, name = {first: 'Tom', last: 'four'};".to_string(), + None, + ) + .await + .unwrap(); } fn generate_json() -> Value { @@ -225,17 +273,23 @@ mod tests { let outcome = runtime.block_on(async { let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); prime_database(connection.clone()).await; - update(connection.clone(), "user".to_string(), json_value).await.unwrap() + update(connection.clone(), "user".to_string(), json_value) + .await + .unwrap() }); let outcome: Value = from_str(&outcome).unwrap(); assert_eq!(outcome.as_array().unwrap().len(), 4); assert_eq!(outcome[0]["name"], "John Doe"); - assert_eq!(outcome[1]["name"], "John Doe"); - assert_eq!(outcome[2]["name"], "John Doe"); + assert_eq!(outcome[1]["name"], "John Doe"); + assert_eq!(outcome[2]["name"], "John Doe"); assert_eq!(outcome[3]["name"], "John Doe"); } @@ -246,18 +300,26 @@ mod tests { let outcome = runtime.block_on(async { let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); prime_database(connection.clone()).await; - let _ = update(connection.clone(), "user:2..4".to_string(), json_value).await.unwrap(); - query(connection.clone(), "SELECT * FROM user;".to_string(), None).await.unwrap() + let _ = update(connection.clone(), "user:2..4".to_string(), json_value) + .await + .unwrap(); + query(connection.clone(), "SELECT * FROM user;".to_string(), None) + .await + .unwrap() }); let outcome: Value = from_str(&outcome).unwrap(); assert_eq!(outcome[0].as_array().unwrap().len(), 4); assert_eq!(outcome[0].as_array().unwrap()[0]["name"], "Tobie"); - assert_eq!(outcome[0].as_array().unwrap()[1]["name"], "John Doe"); - assert_eq!(outcome[0].as_array().unwrap()[2]["name"], "John Doe"); + assert_eq!(outcome[0].as_array().unwrap()[1]["name"], "John Doe"); + assert_eq!(outcome[0].as_array().unwrap()[2]["name"], "John Doe"); assert_eq!(outcome[0].as_array().unwrap()[3]["name"], "Tom"); } @@ -268,22 +330,29 @@ mod tests { let outcome = runtime.block_on(async { let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); prime_database(connection.clone()).await; - let _ = update(connection.clone(), "user:2".to_string(), json_value).await.unwrap(); - query(connection.clone(), "SELECT * FROM user;".to_string(), None).await.unwrap() + let _ = update(connection.clone(), "user:2".to_string(), json_value) + .await + .unwrap(); + query(connection.clone(), "SELECT * FROM user;".to_string(), None) + .await + .unwrap() }); let outcome: Value = from_str(&outcome).unwrap(); assert_eq!(outcome[0].as_array().unwrap().len(), 4); assert_eq!(outcome[0].as_array().unwrap()[0]["name"], "Tobie"); - assert_eq!(outcome[0].as_array().unwrap()[1]["name"], "John Doe"); - assert_eq!(outcome[0].as_array().unwrap()[2]["name"], "Dave"); + assert_eq!(outcome[0].as_array().unwrap()[1]["name"], "John Doe"); + assert_eq!(outcome[0].as_array().unwrap()[2]["name"], "Dave"); assert_eq!(outcome[0].as_array().unwrap()[3]["name"], "Tom"); } - #[test] fn test_merge_all_records() { let runtime = Runtime::new().unwrap(); @@ -291,10 +360,16 @@ mod tests { let outcome = runtime.block_on(async { let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); prime_merge_database(connection.clone()).await; - merge(connection.clone(), "user".to_string(), json_value).await.unwrap() + merge(connection.clone(), "user".to_string(), json_value) + .await + .unwrap() }); let outcome: Value = from_str(&outcome).unwrap(); @@ -305,7 +380,6 @@ mod tests { assert_eq!(outcome.as_array().unwrap().len(), 4); } - #[test] fn test_merge_some() { let runtime = Runtime::new().unwrap(); @@ -313,18 +387,38 @@ mod tests { let outcome = runtime.block_on(async { let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); prime_merge_database(connection.clone()).await; - let _ = merge(connection.clone(), "user:2..4".to_string(), json_value).await.unwrap(); - query(connection.clone(), "SELECT * FROM user;".to_string(), None).await.unwrap() + let _ = merge(connection.clone(), "user:2..4".to_string(), json_value) + .await + .unwrap(); + query(connection.clone(), "SELECT * FROM user;".to_string(), None) + .await + .unwrap() }); let outcome: Value = from_str(&outcome).unwrap(); - assert_eq!(outcome[0].as_array().unwrap()[0]["name"]["last"], "one".to_string()); - assert_eq!(outcome[0].as_array().unwrap()[1]["name"]["last"], "Doe".to_string()); - assert_eq!(outcome[0].as_array().unwrap()[2]["name"]["last"], "Doe".to_string()); - assert_eq!(outcome[0].as_array().unwrap()[3]["name"]["last"], "four".to_string()); + assert_eq!( + outcome[0].as_array().unwrap()[0]["name"]["last"], + "one".to_string() + ); + assert_eq!( + outcome[0].as_array().unwrap()[1]["name"]["last"], + "Doe".to_string() + ); + assert_eq!( + outcome[0].as_array().unwrap()[2]["name"]["last"], + "Doe".to_string() + ); + assert_eq!( + outcome[0].as_array().unwrap()[3]["name"]["last"], + "four".to_string() + ); assert_eq!(outcome[0].as_array().unwrap().len(), 4); } @@ -335,25 +429,43 @@ mod tests { let outcome = runtime.block_on(async { let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); prime_merge_database(connection.clone()).await; - let _ = merge(connection.clone(), "user:2".to_string(), json_value).await.unwrap(); - query(connection.clone(), "SELECT * FROM user;".to_string(), None).await.unwrap() + let _ = merge(connection.clone(), "user:2".to_string(), json_value) + .await + .unwrap(); + query(connection.clone(), "SELECT * FROM user;".to_string(), None) + .await + .unwrap() }); let outcome: Value = from_str(&outcome).unwrap(); - assert_eq!(outcome[0].as_array().unwrap()[0]["name"]["last"], "one".to_string()); - assert_eq!(outcome[0].as_array().unwrap()[1]["name"]["last"], "Doe".to_string()); - assert_eq!(outcome[0].as_array().unwrap()[2]["name"]["last"], "three".to_string()); - assert_eq!(outcome[0].as_array().unwrap()[3]["name"]["last"], "four".to_string()); + assert_eq!( + outcome[0].as_array().unwrap()[0]["name"]["last"], + "one".to_string() + ); + assert_eq!( + outcome[0].as_array().unwrap()[1]["name"]["last"], + "Doe".to_string() + ); + assert_eq!( + outcome[0].as_array().unwrap()[2]["name"]["last"], + "three".to_string() + ); + assert_eq!( + outcome[0].as_array().unwrap()[3]["name"]["last"], + "four".to_string() + ); assert_eq!(outcome[0].as_array().unwrap().len(), 4); } - #[test] fn test_patch() { - let json_string = r#" [{ "op": "replace", @@ -367,13 +479,21 @@ mod tests { let outcome = runtime.block_on(async { let connection = make_connection("memory".to_string()).await.unwrap(); - connection.connection.use_ns("test_namespace").await.unwrap(); - connection.connection.use_db("test_database").await.unwrap(); + connection + .connection + .use_ns("test_namespace") + .await + .unwrap(); + connection.connection.use_db("test_database").await.unwrap(); prime_merge_database(connection.clone()).await; - let outcome = patch(connection.clone(), "user".to_string(), json_value).await.unwrap(); + let outcome = patch(connection.clone(), "user".to_string(), json_value) + .await + .unwrap(); println!("{:?}", outcome); - query(connection.clone(), "SELECT * FROM user;".to_string(), None).await.unwrap() + query(connection.clone(), "SELECT * FROM user;".to_string(), None) + .await + .unwrap() }); let outcome: Value = from_str(&outcome).unwrap(); @@ -381,5 +501,4 @@ mod tests { assert_eq!(i["name"]["last"], "Doe".to_string()); } } - } diff --git a/tests/integration/async/test_auth.py b/tests/integration/async/test_auth.py index 1e9ffd0b..4a62f879 100644 --- a/tests/integration/async/test_auth.py +++ b/tests/integration/async/test_auth.py @@ -35,23 +35,17 @@ def test_login_wrong_password(self): with self.assertRaises(RuntimeError) as context: asyncio.run(self.login("root", "wrong")) - if os.environ.get("CONNECTION_PROTOCOL", "http") == "http": - self.assertEqual(True, "(401 Unauthorized)" in str(context.exception)) - else: - self.assertEqual( - '"There was a problem with authentication"', str(context.exception) - ) + self.assertEqual( True, + 'There was a problem with authentication' in str(context.exception) + ) def test_login_wrong_username(self): with self.assertRaises(RuntimeError) as context: asyncio.run(self.login("wrong", "root")) - if os.environ.get("CONNECTION_PROTOCOL", "http") == "http": - self.assertEqual(True, "(401 Unauthorized)" in str(context.exception)) - else: - self.assertEqual( - '"There was a problem with authentication"', str(context.exception) - ) + self.assertEqual( True, + 'There was a problem with authentication' in str(context.exception) + ) if __name__ == "__main__": diff --git a/tests/integration/blocking/test_auth.py b/tests/integration/blocking/test_auth.py index d1081421..aa615a37 100644 --- a/tests/integration/blocking/test_auth.py +++ b/tests/integration/blocking/test_auth.py @@ -32,23 +32,17 @@ def test_login_wrong_password(self): with self.assertRaises(SurrealDbError) as context: self.login("root", "wrong") - if os.environ.get("CONNECTION_PROTOCOL", "http") == "http": - self.assertEqual(True, "(401 Unauthorized)" in str(context.exception)) - else: - self.assertEqual( - '"There was a problem with authentication"', str(context.exception) - ) + self.assertEqual( True, + 'There was a problem with authentication' in str(context.exception) + ) def test_login_wrong_username(self): with self.assertRaises(SurrealDbError) as context: self.login("wrong", "root") - if os.environ.get("CONNECTION_PROTOCOL", "http") == "http": - self.assertEqual(True, "(401 Unauthorized)" in str(context.exception)) - else: - self.assertEqual( - '"There was a problem with authentication"', str(context.exception) - ) + self.assertEqual( True, + 'There was a problem with authentication' in str(context.exception) + ) if __name__ == "__main__": diff --git a/tests/scripts/runner.py b/tests/scripts/runner.py index 4ba2b249..d3d99c87 100644 --- a/tests/scripts/runner.py +++ b/tests/scripts/runner.py @@ -85,13 +85,7 @@ def run_tests(port: int, protocol: str) -> None: if __name__ == "__main__": port = 8000 for version in [ - "v1.2.1", - "v1.2.0", - "v1.0.1", - "v1.1.1", - "v1.1.0", - "v1.0.1", - "1.0.0", + "v2.0.2", ]: container = DbInstance(version=version, port=port) container.start()