From 29b92a15181aaac8e8e974d088d5f39033553c33 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Thu, 10 Jul 2025 12:53:16 +0200 Subject: [PATCH 01/18] Add dummy migration module --- rust/rsmgp-mysql/Cargo.toml | 15 +++++++ rust/rsmgp-mysql/src/lib.rs | 85 +++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 rust/rsmgp-mysql/Cargo.toml create mode 100644 rust/rsmgp-mysql/src/lib.rs diff --git a/rust/rsmgp-mysql/Cargo.toml b/rust/rsmgp-mysql/Cargo.toml new file mode 100644 index 000000000..9e969dabd --- /dev/null +++ b/rust/rsmgp-mysql/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rsmgp-mysql" +version = "0.1.0" +authors = ["Your Name "] +edition = "2018" + +[dependencies] +mysql = "24" +c_str_macro = "1.0.2" +rsmgp-sys = { path = "../rsmgp-sys" } +hex = "0.4.3" + +[lib] +name = "rsmgp_mysql" +crate-type = ["cdylib"] diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs new file mode 100644 index 000000000..44d34a25f --- /dev/null +++ b/rust/rsmgp-mysql/src/lib.rs @@ -0,0 +1,85 @@ +use c_str_macro::c_str; +use mysql::*; +use mysql::prelude::*; +use rsmgp_sys::map::*; +use rsmgp_sys::memgraph::*; +use rsmgp_sys::mgp::*; +use rsmgp_sys::result::Result; +use rsmgp_sys::rsmgp::*; +use rsmgp_sys::value::Value; +use rsmgp_sys::{close_module, define_procedure, define_type, init_module}; +use std::ffi::CString; +use std::os::raw::c_int; +use std::panic; + +const AURORA_HOST: &str = "localhost"; +const AURORA_PORT: u16 = 3306; +const AURORA_USER: &str = "testuser"; +const AURORA_PASSWORD: &str = "testpass"; +const AURORA_DATABASE: &str = "testdb"; + +fn get_aurora_url() -> String { + format!( + "mysql://{user}:{pass}@{host}:{port}/{db}", + user = AURORA_USER, + pass = AURORA_PASSWORD, + host = AURORA_HOST, + port = AURORA_PORT, + db = AURORA_DATABASE + ) +} + +init_module!(|memgraph: &Memgraph| -> Result<()> { + memgraph.add_read_procedure( + migrate, + c_str!("migrate"), + &[define_type!("table_or_sql", Type::String), + define_type!("config", Type::Map)], + &[], + &[define_type!("row", Type::Map),], + )?; + + Ok(()) +}); + +define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { + let args = memgraph.args()?; + let sql_or_table = args.value_at(0)?; + let config = args.value_at(1)?; + + let url = get_aurora_url(); + let opts = Opts::from_url(&url).expect("Invalid MySQL URL"); + let pool = Pool::new(opts).expect("Failed to create pool"); + let mut conn = pool.get_conn().expect("Failed to get connection"); + let query_result: Vec = conn.query("SELECT * FROM `Table`").expect("Query failed"); + for row in query_result.iter() { + let result = memgraph.result_record()?; + let mut row_map = Map::make_empty(&memgraph)?; + let columns = row.columns_ref(); + for (i, col) in columns.iter().enumerate() { + let col_name = CString::new(col.name_str().as_bytes()).unwrap(); + let val = row.as_ref(i).unwrap_or(&mysql::Value::NULL); + let mg_val = match val { + mysql::Value::NULL => Value::Null, + mysql::Value::Int(i) => Value::Int(*i), + mysql::Value::UInt(u) => Value::Int(*u as i64), + mysql::Value::Float(f) => Value::Float(*f as f64), + mysql::Value::Double(d) => Value::Float(*d), + mysql::Value::Bytes(b) => { + // Convert bytes to CString safely, fallback to hex if not valid UTF-8 + match CString::new(b.clone()) { + Ok(s) => Value::String(s), + Err(_) => Value::String(CString::new(hex::encode(b)).unwrap()), + } + }, + _ => Value::Null, + }; + row_map.insert(col_name.as_c_str(), &mg_val)?; + } + result.insert_map(c_str!("row"), &row_map)?; + } + + Ok(()) +}); + +close_module!(|| -> Result<()> { Ok(()) }); From 89cb3a82688264ff0d492ac63923974157c9a534 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Thu, 10 Jul 2025 16:48:51 +0200 Subject: [PATCH 02/18] Add non-batched execution with arbitrary query --- rust/rsmgp-mysql/src/lib.rs | 119 ++++++++++++++++++++++++------- rust/rsmgp-sys/src/result/mod.rs | 3 + 2 files changed, 96 insertions(+), 26 deletions(-) diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index 44d34a25f..150cabfd8 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -5,6 +5,7 @@ use rsmgp_sys::map::*; use rsmgp_sys::memgraph::*; use rsmgp_sys::mgp::*; use rsmgp_sys::result::Result; +use rsmgp_sys::result::Error; use rsmgp_sys::rsmgp::*; use rsmgp_sys::value::Value; use rsmgp_sys::{close_module, define_procedure, define_type, init_module}; @@ -13,22 +14,51 @@ use std::os::raw::c_int; use std::panic; const AURORA_HOST: &str = "localhost"; -const AURORA_PORT: u16 = 3306; -const AURORA_USER: &str = "testuser"; -const AURORA_PASSWORD: &str = "testpass"; -const AURORA_DATABASE: &str = "testdb"; +const AURORA_PORT: i64 = 3306; +const AURORA_USER: &str = "username"; +const AURORA_PASSWORD: &str = "password"; +const AURORA_DATABASE: &str = "database"; + +fn get_aurora_url(config: &Map) -> String { + use std::ffi::CString; + + // Helper to extract a string from the map, or fallback + fn get_str(config: &Map, key: &str, default: &str) -> String { + let ckey = CString::new(key).unwrap(); + match config.at(&ckey) { + Ok(Value::String(s)) => s.to_str().ok().map(|s| s.to_string()).unwrap_or_else(|| default.to_string()), + _ => default.to_string(), + } + } + // Helper to extract a u16 from the map, or fallback + fn get_port(config: &Map, key: &str, default: i64) -> i64 { + let ckey = CString::new(key).unwrap(); + match config.at(&ckey) { + Ok(Value::Int(i)) => i, + _ => default, + } + } + + let user = get_str(config, "user", AURORA_USER); + let pass = get_str(config, "password", AURORA_PASSWORD); + let host = get_str(config, "host", AURORA_HOST); + let db = get_str(config, "database", AURORA_DATABASE); + let port = get_port(config, "port", AURORA_PORT); -fn get_aurora_url() -> String { format!( "mysql://{user}:{pass}@{host}:{port}/{db}", - user = AURORA_USER, - pass = AURORA_PASSWORD, - host = AURORA_HOST, - port = AURORA_PORT, - db = AURORA_DATABASE + user = user, + pass = pass, + host = host, + port = port, + db = db ) } +fn query_is_table(table_or_sql: &str) -> bool { + table_or_sql.split_whitespace().count() == 1 +} + init_module!(|memgraph: &Memgraph| -> Result<()> { memgraph.add_read_procedure( migrate, @@ -44,35 +74,72 @@ init_module!(|memgraph: &Memgraph| -> Result<()> { define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { let args = memgraph.args()?; - let sql_or_table = args.value_at(0)?; - let config = args.value_at(1)?; + let query = match args.value_at(0)? { + Value::String(ref cstr) => { + let s = cstr.to_str().expect("CString is not valid UTF-8"); + if query_is_table(s) { + format!("SELECT * FROM `{}`;", s) + } else { + s.to_string() + } + } + _ => panic!("Expected String value in place of sql_or_table parameter!"), + }; + println!("query: {}", query); - let url = get_aurora_url(); + let config_arg = args.value_at(1)?; + let config = match config_arg { + Value::Map(ref map) => map, + _ => panic!("Expected Map value in place of config parameter!"), + }; + + let url = get_aurora_url(config); + println!("url: {}", url); let opts = Opts::from_url(&url).expect("Invalid MySQL URL"); let pool = Pool::new(opts).expect("Failed to create pool"); let mut conn = pool.get_conn().expect("Failed to get connection"); - let query_result: Vec = conn.query("SELECT * FROM `Table`").expect("Query failed"); + let query_result: Vec = match conn.query(query) { + Ok(rows) => rows, + Err(e) => { + println!("Error: {}", e); + return Err(Error::UnableToExecuteMySQLQuery); + } + }; + for row in query_result.iter() { let result = memgraph.result_record()?; - let mut row_map = Map::make_empty(&memgraph)?; - let columns = row.columns_ref(); - for (i, col) in columns.iter().enumerate() { - let col_name = CString::new(col.name_str().as_bytes()).unwrap(); - let val = row.as_ref(i).unwrap_or(&mysql::Value::NULL); - let mg_val = match val { + let row_map = Map::make_empty(&memgraph)?; + for column in row.columns_ref() { + let col_name = CString::new(column.name_str().as_bytes()).unwrap(); + let column_value = &row[column.name_str().as_ref()]; + let mg_val = match column_value { mysql::Value::NULL => Value::Null, mysql::Value::Int(i) => Value::Int(*i), - mysql::Value::UInt(u) => Value::Int(*u as i64), + mysql::Value::UInt(u) => { + if *u <= i64::MAX as u64 { + Value::Int(*u as i64) + } else { + eprintln!("Warning: MySQL unsigned integer {} exceeds i64::MAX, storing as string", u); + Value::String(CString::new(u.to_string()).unwrap()) + } + }, mysql::Value::Float(f) => Value::Float(*f as f64), mysql::Value::Double(d) => Value::Float(*d), mysql::Value::Bytes(b) => { - // Convert bytes to CString safely, fallback to hex if not valid UTF-8 - match CString::new(b.clone()) { - Ok(s) => Value::String(s), - Err(_) => Value::String(CString::new(hex::encode(b)).unwrap()), + // Try to interpret as UTF-8 string, fallback to hex if not valid + match String::from_utf8(b.clone()) { + Ok(s) => Value::String(CString::new(s).unwrap()), + Err(_) => { + eprintln!("Warning: MySQL bytes column is not valid UTF-8, storing as hex string"); + Value::String(CString::new(hex::encode(b)).unwrap()) + } } }, - _ => Value::Null, + // Optionally handle more types (date/time/decimal) here if needed + other => { + eprintln!("Unhandled MySQL value type: {:?}, mapping to Null", other); + Value::Null + } }; row_map.insert(col_name.as_c_str(), &mg_val)?; } diff --git a/rust/rsmgp-sys/src/result/mod.rs b/rust/rsmgp-sys/src/result/mod.rs index 740e22342..3b2a6ec80 100644 --- a/rust/rsmgp-sys/src/result/mod.rs +++ b/rust/rsmgp-sys/src/result/mod.rs @@ -320,6 +320,9 @@ pub enum Error { #[snafu(display("Unable to check if vertex has a label."))] UnableToCheckVertexHasLabel, + + #[snafu(display("Unable to execute MySQL query."))] + UnableToExecuteMySQLQuery, } /// A result type holding [Error] by default. From 5d1be2b40b093b0214a4d89cd8c1e9723ee669f7 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Fri, 11 Jul 2025 10:15:20 +0200 Subject: [PATCH 03/18] Add optional types to the signature --- rust/rsmgp-mysql/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index 150cabfd8..a1382843a 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -7,8 +7,9 @@ use rsmgp_sys::mgp::*; use rsmgp_sys::result::Result; use rsmgp_sys::result::Error; use rsmgp_sys::rsmgp::*; +use rsmgp_sys::value::MgpValue; use rsmgp_sys::value::Value; -use rsmgp_sys::{close_module, define_procedure, define_type, init_module}; +use rsmgp_sys::{close_module, define_procedure, define_type, define_optional_type, init_module}; use std::ffi::CString; use std::os::raw::c_int; use std::panic; @@ -63,9 +64,8 @@ init_module!(|memgraph: &Memgraph| -> Result<()> { memgraph.add_read_procedure( migrate, c_str!("migrate"), - &[define_type!("table_or_sql", Type::String), - define_type!("config", Type::Map)], - &[], + &[define_type!("table_or_sql", Type::String),], + &[define_optional_type!("config", &MgpValue::make_map(&Map::make_empty(&memgraph)?, &memgraph)?, Type::Map), define_optional_type!("config_path", &MgpValue::make_string(c_str!(""), &memgraph)?, Type::String)], &[define_type!("row", Type::Map),], )?; From 9ff95420dcdb42c722a2bab4428f2da682c8c7d3 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Fri, 11 Jul 2025 10:19:00 +0200 Subject: [PATCH 04/18] added optional arguments --- rust/rsmgp-mysql/src/lib.rs | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index a1382843a..d8f2581c2 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -1,15 +1,15 @@ use c_str_macro::c_str; -use mysql::*; use mysql::prelude::*; +use mysql::*; use rsmgp_sys::map::*; use rsmgp_sys::memgraph::*; use rsmgp_sys::mgp::*; -use rsmgp_sys::result::Result; use rsmgp_sys::result::Error; +use rsmgp_sys::result::Result; use rsmgp_sys::rsmgp::*; use rsmgp_sys::value::MgpValue; use rsmgp_sys::value::Value; -use rsmgp_sys::{close_module, define_procedure, define_type, define_optional_type, init_module}; +use rsmgp_sys::{close_module, define_optional_type, define_procedure, define_type, init_module}; use std::ffi::CString; use std::os::raw::c_int; use std::panic; @@ -27,7 +27,11 @@ fn get_aurora_url(config: &Map) -> String { fn get_str(config: &Map, key: &str, default: &str) -> String { let ckey = CString::new(key).unwrap(); match config.at(&ckey) { - Ok(Value::String(s)) => s.to_str().ok().map(|s| s.to_string()).unwrap_or_else(|| default.to_string()), + Ok(Value::String(s)) => s + .to_str() + .ok() + .map(|s| s.to_string()) + .unwrap_or_else(|| default.to_string()), _ => default.to_string(), } } @@ -64,9 +68,20 @@ init_module!(|memgraph: &Memgraph| -> Result<()> { memgraph.add_read_procedure( migrate, c_str!("migrate"), - &[define_type!("table_or_sql", Type::String),], - &[define_optional_type!("config", &MgpValue::make_map(&Map::make_empty(&memgraph)?, &memgraph)?, Type::Map), define_optional_type!("config_path", &MgpValue::make_string(c_str!(""), &memgraph)?, Type::String)], - &[define_type!("row", Type::Map),], + &[define_type!("table_or_sql", Type::String)], + &[ + define_optional_type!( + "config", + &MgpValue::make_map(&Map::make_empty(&memgraph)?, &memgraph)?, + Type::Map + ), + define_optional_type!( + "config_path", + &MgpValue::make_string(c_str!(""), &memgraph)?, + Type::String + ), + ], + &[define_type!("row", Type::Map)], )?; Ok(()) @@ -122,7 +137,7 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { eprintln!("Warning: MySQL unsigned integer {} exceeds i64::MAX, storing as string", u); Value::String(CString::new(u.to_string()).unwrap()) } - }, + } mysql::Value::Float(f) => Value::Float(*f as f64), mysql::Value::Double(d) => Value::Float(*d), mysql::Value::Bytes(b) => { @@ -134,7 +149,7 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { Value::String(CString::new(hex::encode(b)).unwrap()) } } - }, + } // Optionally handle more types (date/time/decimal) here if needed other => { eprintln!("Unhandled MySQL value type: {:?}, mapping to Null", other); From 8d64461d2976f03376cc1f32121b9bc955f019e0 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Fri, 11 Jul 2025 10:30:10 +0200 Subject: [PATCH 05/18] Add error handling --- rust/rsmgp-mysql/src/lib.rs | 27 ++++++++++++++++++++++----- rust/rsmgp-sys/src/result/mod.rs | 9 +++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index d8f2581c2..3f61b916b 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -100,7 +100,6 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { } _ => panic!("Expected String value in place of sql_or_table parameter!"), }; - println!("query: {}", query); let config_arg = args.value_at(1)?; let config = match config_arg { @@ -109,10 +108,28 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { }; let url = get_aurora_url(config); - println!("url: {}", url); - let opts = Opts::from_url(&url).expect("Invalid MySQL URL"); - let pool = Pool::new(opts).expect("Failed to create pool"); - let mut conn = pool.get_conn().expect("Failed to get connection"); + let opts: Opts = match Opts::from_url(&url) { + Ok(opts) => opts, + Err(e) => { + println!("Error: {}", e); + return Err(Error::InvalidMySQLURL); + } + }; + let pool: Pool = match Pool::new(opts) { + Ok(pool) => pool, + Err(e) => { + println!("Error: {}", e); + return Err(Error::UnableToCreateMySQLPool); + } + }; + + let mut conn: PooledConn = match pool.get_conn() { + Ok(conn) => conn, + Err(e) => { + println!("Error: {}", e); + return Err(Error::UnableToGetMySQLConnection); + } + }; let query_result: Vec = match conn.query(query) { Ok(rows) => rows, Err(e) => { diff --git a/rust/rsmgp-sys/src/result/mod.rs b/rust/rsmgp-sys/src/result/mod.rs index 3b2a6ec80..07fc9331f 100644 --- a/rust/rsmgp-sys/src/result/mod.rs +++ b/rust/rsmgp-sys/src/result/mod.rs @@ -321,6 +321,15 @@ pub enum Error { #[snafu(display("Unable to check if vertex has a label."))] UnableToCheckVertexHasLabel, + #[snafu(display("Invalid MySQL URL."))] + InvalidMySQLURL, + + #[snafu(display("Unable to create MySQL pool."))] + UnableToCreateMySQLPool, + + #[snafu(display("Unable to get MySQL connection."))] + UnableToGetMySQLConnection, + #[snafu(display("Unable to execute MySQL query."))] UnableToExecuteMySQLQuery, } From 87f671c06d4379a1efd97604607dea77a0c80851 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Fri, 11 Jul 2025 11:52:06 +0200 Subject: [PATCH 06/18] Add new C API --- rust/rsmgp-mysql/src/lib.rs | 1 + rust/rsmgp-sys/mgp/mg_procedure.h | 795 +++++++++++++++++++++-------- rust/rsmgp-sys/src/memgraph/mod.rs | 4 + 3 files changed, 600 insertions(+), 200 deletions(-) diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index 3f61b916b..07ff78bd7 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -144,6 +144,7 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { for column in row.columns_ref() { let col_name = CString::new(column.name_str().as_bytes()).unwrap(); let column_value = &row[column.name_str().as_ref()]; + println!("column_value: {:?}", column_value); let mg_val = match column_value { mysql::Value::NULL => Value::Null, mysql::Value::Int(i) => Value::Int(*i), diff --git a/rust/rsmgp-sys/mgp/mg_procedure.h b/rust/rsmgp-sys/mgp/mg_procedure.h index b1d4e323a..fe72519cb 100644 --- a/rust/rsmgp-sys/mgp/mg_procedure.h +++ b/rust/rsmgp-sys/mgp/mg_procedure.h @@ -1,4 +1,4 @@ -// Copyright 2021 Memgraph Ltd. +// Copyright 2025 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -16,6 +16,9 @@ #ifdef __cplusplus extern "C" { +#define MGP_ENUM_CLASS enum class +#else +#define MGP_ENUM_CLASS enum #endif #if __cplusplus >= 201703L @@ -33,22 +36,36 @@ extern "C" { /// All functions return an error code that can be used to figure out whether the API call was successful or not. In /// case of failure, the specific error code can be used to identify the reason of the failure. -enum MGP_NODISCARD mgp_error { - MGP_ERROR_NO_ERROR = 0, - MGP_ERROR_UNKNOWN_ERROR, - MGP_ERROR_UNABLE_TO_ALLOCATE, - MGP_ERROR_INSUFFICIENT_BUFFER, - MGP_ERROR_OUT_OF_RANGE, - MGP_ERROR_LOGIC_ERROR, - MGP_ERROR_DELETED_OBJECT, - MGP_ERROR_INVALID_ARGUMENT, - MGP_ERROR_KEY_ALREADY_EXISTS, - MGP_ERROR_IMMUTABLE_OBJECT, - MGP_ERROR_VALUE_CONVERSION, - MGP_ERROR_SERIALIZATION_ERROR, +MGP_ENUM_CLASS MGP_NODISCARD mgp_error{ + MGP_ERROR_NO_ERROR, + MGP_ERROR_UNKNOWN_ERROR, + MGP_ERROR_UNABLE_TO_ALLOCATE, + MGP_ERROR_INSUFFICIENT_BUFFER, + MGP_ERROR_OUT_OF_RANGE, + MGP_ERROR_LOGIC_ERROR, + MGP_ERROR_DELETED_OBJECT, + MGP_ERROR_INVALID_ARGUMENT, + MGP_ERROR_KEY_ALREADY_EXISTS, + MGP_ERROR_IMMUTABLE_OBJECT, + MGP_ERROR_VALUE_CONVERSION, + MGP_ERROR_SERIALIZATION_ERROR, + MGP_ERROR_AUTHORIZATION_ERROR, + MGP_ERROR_NOT_YET_IMPLEMENTED, }; ///@} +/// @name License check +/// +///@{ + +/// Functions used for checking validity of the enterprise license + +/// Doesn't allocate anything and can't fail therefore doesn't use +/// enum mgp_error +int mgp_is_enterprise_valid(); + +///@} + /// @name Memory Allocation /// /// These should be preferred compared to plain malloc calls as Memgraph's @@ -70,7 +87,7 @@ struct mgp_memory; /// Unlike malloc, this function is not thread-safe. /// `size_in_bytes` must be greater than 0. /// The resulting pointer must be freed with mgp_free. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to serve the requested allocation. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to serve the requested allocation. enum mgp_error mgp_alloc(struct mgp_memory *memory, size_t size_in_bytes, void **result); /// Allocate an aligned block of memory with given size in bytes. @@ -78,7 +95,7 @@ enum mgp_error mgp_alloc(struct mgp_memory *memory, size_t size_in_bytes, void * /// `size_in_bytes` must be greater than 0. /// `alignment` must be a power of 2 value. /// The resulting pointer must be freed with mgp_free. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to serve the requested allocation. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to serve the requested allocation. enum mgp_error mgp_aligned_alloc(struct mgp_memory *memory, size_t size_in_bytes, size_t alignment, void **result); /// Deallocate an allocation from mgp_alloc or mgp_aligned_alloc. @@ -92,14 +109,14 @@ void mgp_free(struct mgp_memory *memory, void *ptr); /// This function can be used to allocate global memory that persists /// beyond a single invocation of mgp_main. /// The resulting pointer must be freed with mgp_global_free. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to serve the requested allocation. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to serve the requested allocation. enum mgp_error mgp_global_alloc(size_t size_in_bytes, void **result); /// Allocate an aligned global block of memory with given size in bytes. /// This function can be used to allocate global memory that persists /// beyond a single invocation of mgp_main. /// The resulting pointer must be freed with mgp_global_free. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to serve the requested allocation. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to serve the requested allocation. enum mgp_error mgp_global_aligned_alloc(size_t size_in_bytes, size_t alignment, void **result); /// Deallocate an allocation from mgp_global_alloc or mgp_global_aligned_alloc. @@ -107,6 +124,22 @@ enum mgp_error mgp_global_aligned_alloc(size_t size_in_bytes, size_t alignment, /// The behavior is undefined if `ptr` is not a value returned from a prior /// mgp_global_alloc() or mgp_global_aligned_alloc(). void mgp_global_free(void *p); + +/// State of the graph database. +struct mgp_graph; + +/// Allocations are tracked only for master thread. If new threads are spawned +/// inside procedure, by calling following function +/// you can start tracking allocations for current thread too. This +/// is important if you need query memory limit to work +/// for given procedure or per procedure memory limit. +enum mgp_error mgp_track_current_thread_allocations(struct mgp_graph *graph); + +/// Once allocations are tracked for current thread, you need to stop tracking allocations +/// for given thread, before thread finishes with execution, or is detached. +/// Otherwise it might result in slowdown of system due to unnecessary tracking of +/// allocations. +enum mgp_error mgp_untrack_current_thread_allocations(struct mgp_graph *graph); ///@} /// @name Operations on mgp_value @@ -167,33 +200,35 @@ enum mgp_value_type { MGP_VALUE_TYPE_DURATION, }; +enum mgp_error mgp_value_copy(struct mgp_value *val, struct mgp_memory *memory, struct mgp_value **result); + /// Free the memory used by the given mgp_value instance. void mgp_value_destroy(struct mgp_value *val); /// Construct a value representing `null` in openCypher. /// You need to free the instance through mgp_value_destroy. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_null(struct mgp_memory *memory, struct mgp_value **result); /// Construct a boolean value. /// Non-zero values represent `true`, while zero represents `false`. /// You need to free the instance through mgp_value_destroy. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_bool(int val, struct mgp_memory *memory, struct mgp_value **result); /// Construct an integer value. /// You need to free the instance through mgp_value_destroy. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_int(int64_t val, struct mgp_memory *memory, struct mgp_value **result); /// Construct a double floating point value. /// You need to free the instance through mgp_value_destroy. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_double(double val, struct mgp_memory *memory, struct mgp_value **result); /// Construct a character string value from a NULL terminated string. /// You need to free the instance through mgp_value_destroy. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_string(const char *val, struct mgp_memory *memory, struct mgp_value **result); /// Create a mgp_value storing a mgp_list. @@ -201,7 +236,7 @@ enum mgp_error mgp_value_make_string(const char *val, struct mgp_memory *memory, /// the list is given to the created mgp_value and destroying the mgp_value will /// destroy the mgp_list. Therefore, if a mgp_value is successfully created /// you must not call mgp_list_destroy on the given list. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_list(struct mgp_list *val, struct mgp_value **result); /// Create a mgp_value storing a mgp_map. @@ -209,7 +244,7 @@ enum mgp_error mgp_value_make_list(struct mgp_list *val, struct mgp_value **resu /// the map is given to the created mgp_value and destroying the mgp_value will /// destroy the mgp_map. Therefore, if a mgp_value is successfully created /// you must not call mgp_map_destroy on the given map. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_map(struct mgp_map *val, struct mgp_value **result); /// Create a mgp_value storing a mgp_vertex. @@ -217,7 +252,7 @@ enum mgp_error mgp_value_make_map(struct mgp_map *val, struct mgp_value **result /// the vertex is given to the created mgp_value and destroying the mgp_value /// will destroy the mgp_vertex. Therefore, if a mgp_value is successfully /// created you must not call mgp_vertex_destroy on the given vertex. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_vertex(struct mgp_vertex *val, struct mgp_value **result); /// Create a mgp_value storing a mgp_edge. @@ -225,7 +260,7 @@ enum mgp_error mgp_value_make_vertex(struct mgp_vertex *val, struct mgp_value ** /// the edge is given to the created mgp_value and destroying the mgp_value will /// destroy the mgp_edge. Therefore, if a mgp_value is successfully created you /// must not call mgp_edge_destroy on the given edge. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_edge(struct mgp_edge *val, struct mgp_value **result); /// Create a mgp_value storing a mgp_path. @@ -233,7 +268,7 @@ enum mgp_error mgp_value_make_edge(struct mgp_edge *val, struct mgp_value **resu /// the path is given to the created mgp_value and destroying the mgp_value will /// destroy the mgp_path. Therefore, if a mgp_value is successfully created you /// must not call mgp_path_destroy on the given path. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_path(struct mgp_path *val, struct mgp_value **result); /// Create a mgp_value storing a mgp_date. @@ -241,7 +276,7 @@ enum mgp_error mgp_value_make_path(struct mgp_path *val, struct mgp_value **resu /// the date is transferred to the created mgp_value and destroying the mgp_value will /// destroy the mgp_date. Therefore, if a mgp_value is successfully created you /// must not call mgp_date_destroy on the given date. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_date(struct mgp_date *val, struct mgp_value **result); /// Create a mgp_value storing a mgp_local_time. @@ -249,7 +284,7 @@ enum mgp_error mgp_value_make_date(struct mgp_date *val, struct mgp_value **resu /// the local time is transferred to the created mgp_value and destroying the mgp_value will /// destroy the mgp_local_time. Therefore, if a mgp_value is successfully created you /// must not call mgp_local_time_destroy on the given local time. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_local_time(struct mgp_local_time *val, struct mgp_value **result); /// Create a mgp_value storing a mgp_local_date_time. @@ -257,7 +292,7 @@ enum mgp_error mgp_value_make_local_time(struct mgp_local_time *val, struct mgp_ /// the local date-time is transferred to the created mgp_value and destroying the mgp_value will /// destroy the mgp_local_date_time. Therefore, if a mgp_value is successfully created you /// must not call mgp_local_date_time_destroy on the given local date-time. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_local_date_time(struct mgp_local_date_time *val, struct mgp_value **result); /// Create a mgp_value storing a mgp_duration. @@ -265,7 +300,7 @@ enum mgp_error mgp_value_make_local_date_time(struct mgp_local_date_time *val, s /// the duration is transferred to the created mgp_value and destroying the mgp_value will /// destroy the mgp_duration. Therefore, if a mgp_value is successfully created you /// must not call mgp_duration_destroy on the given duration. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_value. enum mgp_error mgp_value_make_duration(struct mgp_duration *val, struct mgp_value **result); /// Get the type of the value contained in mgp_value. @@ -399,18 +434,23 @@ enum mgp_error mgp_value_get_duration(struct mgp_value *val, struct mgp_duration /// The created list will have allocated enough memory for `capacity` elements /// of mgp_value, but it will not contain any elements. Therefore, /// mgp_list_size will return 0. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_list. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_list. enum mgp_error mgp_list_make_empty(size_t capacity, struct mgp_memory *memory, struct mgp_list **result); +enum mgp_error mgp_list_copy(struct mgp_list *list, struct mgp_memory *memory, struct mgp_list **result); + /// Free the memory used by the given mgp_list and contained elements. void mgp_list_destroy(struct mgp_list *list); +/// Return whether the given mgp_list contains any deleted values. +enum mgp_error mgp_list_contains_deleted(struct mgp_list *list, int *result); + /// Append a copy of mgp_value to mgp_list if capacity allows. /// The list copies the given value and therefore does not take ownership of the /// original value. You still need to call mgp_value_destroy to free the /// original value. -/// Return MGP_ERROR_INSUFFICIENT_BUFFER if there's no capacity. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value. +/// Return mgp_error::MGP_ERROR_INSUFFICIENT_BUFFER if there's no capacity. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value. enum mgp_error mgp_list_append(struct mgp_list *list, struct mgp_value *val); /// Append a copy of mgp_value to mgp_list increasing capacity if needed. @@ -419,9 +459,13 @@ enum mgp_error mgp_list_append(struct mgp_list *list, struct mgp_value *val); /// original value. /// In case of a capacity change, the previously contained elements will move in /// memory and any references to them will be invalid. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value. enum mgp_error mgp_list_append_extend(struct mgp_list *list, struct mgp_value *val); +/// Ensure the underlying capacity of the mgp_list is at least n. +/// Current implementation always returns without errors. +enum mgp_error mgp_list_reserve(struct mgp_list *list, size_t n); + /// Get the number of elements stored in mgp_list. /// Current implementation always returns without errors. enum mgp_error mgp_list_size(struct mgp_list *list, size_t *result); @@ -432,26 +476,43 @@ enum mgp_error mgp_list_size(struct mgp_list *list, size_t *result); enum mgp_error mgp_list_capacity(struct mgp_list *list, size_t *result); /// Get the element in mgp_list at given position. -/// MGP_ERROR_OUT_OF_RANGE is returned if the index is not within mgp_list_size. +/// mgp_error::MGP_ERROR_OUT_OF_RANGE is returned if the index is not within mgp_list_size. enum mgp_error mgp_list_at(struct mgp_list *list, size_t index, struct mgp_value **result); /// Create an empty map of character strings to mgp_value instances. /// You need to free the created instance with mgp_map_destroy. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_map. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_map. enum mgp_error mgp_map_make_empty(struct mgp_memory *memory, struct mgp_map **result); +enum mgp_error mgp_map_copy(struct mgp_map *map, struct mgp_memory *memory, struct mgp_map **result); + /// Free the memory used by the given mgp_map and contained items. void mgp_map_destroy(struct mgp_map *map); +/// Return whether the given mgp_map contains any deleted values. +enum mgp_error mgp_map_contains_deleted(struct mgp_map *map, int *result); + /// Insert a new mapping from a NULL terminated character string to a value. /// If a mapping with the same key already exists, it is *not* replaced. /// In case of insertion, both the string and the value are copied into the map. /// Therefore, the map does not take ownership of the original key nor value, so /// you still need to free their memory explicitly. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate for insertion. -/// Return MGP_ERROR_KEY_ALREADY_EXISTS if a previous mapping already exists. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate for insertion. +/// Return mgp_error::MGP_ERROR_KEY_ALREADY_EXISTS if a previous mapping already exists. enum mgp_error mgp_map_insert(struct mgp_map *map, const char *key, struct mgp_value *value); +/// Insert a mapping from a NULL terminated character string to a value. +/// If a mapping with the same key already exists, it is replaced. +/// In case of update, both the string and the value are copied into the map. +/// Therefore, the map does not take ownership of the original key nor value, so +/// you still need to free their memory explicitly. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate for insertion. +enum mgp_error mgp_map_update(struct mgp_map *map, const char *key, struct mgp_value *value); + +// Erase a mapping by key. +// If the key doesn't exist in the map nothing happens +enum mgp_error mgp_map_erase(struct mgp_map *map, const char *key); + /// Get the number of items stored in mgp_map. /// Current implementation always returns without errors. enum mgp_error mgp_map_size(struct mgp_map *map, size_t *result); @@ -460,6 +521,9 @@ enum mgp_error mgp_map_size(struct mgp_map *map, size_t *result); /// Result is NULL if no mapping exists. enum mgp_error mgp_map_at(struct mgp_map *map, const char *key, struct mgp_value **result); +/// Returns true if key in map. +enum mgp_error mgp_key_exists(struct mgp_map *map, const char *key, int *result); + /// An item in the mgp_map. struct mgp_map_item; @@ -475,7 +539,7 @@ struct mgp_map_items_iterator; /// Start iterating over items contained in the given map. /// The resulting mgp_map_items_iterator needs to be deallocated with /// mgp_map_items_iterator_destroy. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_map_items_iterator. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_map_items_iterator. enum mgp_error mgp_map_iter_items(struct mgp_map *map, struct mgp_memory *memory, struct mgp_map_items_iterator **result); @@ -500,39 +564,46 @@ enum mgp_error mgp_map_items_iterator_next(struct mgp_map_items_iterator *it, st /// Create a path with the copy of the given starting vertex. /// You need to free the created instance with mgp_path_destroy. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_path. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_path. enum mgp_error mgp_path_make_with_start(struct mgp_vertex *vertex, struct mgp_memory *memory, struct mgp_path **result); /// Copy a mgp_path. /// Returned pointer must be freed with mgp_path_destroy. -/// MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_path. +/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_path. enum mgp_error mgp_path_copy(struct mgp_path *path, struct mgp_memory *memory, struct mgp_path **result); /// Free the memory used by the given mgp_path and contained vertices and edges. void mgp_path_destroy(struct mgp_path *path); +/// Return whether the given mgp_path contains any deleted values. +enum mgp_error mgp_path_contains_deleted(struct mgp_path *path, int *result); + /// Append an edge continuing from the last vertex on the path. /// The edge is copied into the path. Therefore, the path does not take /// ownership of the original edge, so you still need to free the edge memory /// explicitly. /// The last vertex on the path will become the other endpoint of the given /// edge, as continued from the current last vertex. -/// Return MGP_ERROR_LOGIC_ERROR if the current last vertex in the path is not part of the given edge. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for path extension. +/// Return mgp_error::MGP_ERROR_LOGIC_ERROR if the current last vertex in the path is not part of the given edge. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for path extension. enum mgp_error mgp_path_expand(struct mgp_path *path, struct mgp_edge *edge); +/// Remove the last node and the last relationship from the path. +/// Return mgp_error::MGP_ERROR_OUT_OF_RANGE if the path contains no relationships. +enum mgp_error mgp_path_pop(struct mgp_path *path); + /// Get the number of edges in a mgp_path. /// Current implementation always returns without errors. enum mgp_error mgp_path_size(struct mgp_path *path, size_t *result); /// Get the vertex from a path at given index. /// The valid index range is [0, mgp_path_size]. -/// MGP_ERROR_OUT_OF_RANGE is returned if index is out of range. +/// mgp_error::MGP_ERROR_OUT_OF_RANGE is returned if index is out of range. enum mgp_error mgp_path_vertex_at(struct mgp_path *path, size_t index, struct mgp_vertex **result); /// Get the edge from a path at given index. /// The valid index range is [0, mgp_path_size - 1]. -/// MGP_ERROR_OUT_OF_RANGE is returned if index is out of range. +/// mgp_error::MGP_ERROR_OUT_OF_RANGE is returned if index is out of range. enum mgp_error mgp_path_edge_at(struct mgp_path *path, size_t index, struct mgp_edge **result); /// Result is non-zero if given paths are equal, otherwise 0. @@ -549,20 +620,26 @@ enum mgp_error mgp_path_equal(struct mgp_path *p1, struct mgp_path *p2, int *res struct mgp_result; /// Represents a record of resulting field values. struct mgp_result_record; +/// Represents a return type for magic functions +struct mgp_func_result; /// Set the error as the result of the procedure. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE ff there's no memory for copying the error message. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE ff there's no memory for copying the error message. enum mgp_error mgp_result_set_error_msg(struct mgp_result *res, const char *error_msg); /// Create a new record for results. /// The previously obtained mgp_result_record pointer is no longer valid, and you must not use it. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_result_record. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_result_record. enum mgp_error mgp_result_new_record(struct mgp_result *res, struct mgp_result_record **result); +/// Reserve memory for n records in the result. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for the records. +enum mgp_error mgp_result_reserve(struct mgp_result *res, size_t n); + /// Assign a value to a field in the given record. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory to copy the mgp_value to mgp_result_record. -/// Return MGP_ERROR_OUT_OF_RANGE if there is no field named `field_name`. -/// Return MGP_ERROR_LOGIC_ERROR `val` does not satisfy the type of the field name `field_name`. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory to copy the mgp_value to +/// mgp_result_record. Return mgp_error::MGP_ERROR_OUT_OF_RANGE if there is no field named `field_name`. Return +/// mgp_error::MGP_ERROR_LOGIC_ERROR `val` does not satisfy the type of the field name `field_name`. enum mgp_error mgp_result_record_insert(struct mgp_result_record *record, const char *field_name, struct mgp_value *val); ///@} @@ -606,7 +683,7 @@ enum mgp_error mgp_properties_iterator_get(struct mgp_properties_iterator *it, s /// The previous mgp_property obtained through mgp_properties_iterator_get /// will be invalidated, and you must not use its value. /// Result is NULL if the end of the iteration has been reached. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_property. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_property. enum mgp_error mgp_properties_iterator_next(struct mgp_properties_iterator *it, struct mgp_property **result); /// Iterator over edges of a vertex. @@ -623,6 +700,12 @@ struct mgp_vertex_id { /// Get the ID of given vertex. enum mgp_error mgp_vertex_get_id(struct mgp_vertex *v, struct mgp_vertex_id *result); +/// Get the in degree of given vertex. +enum mgp_error mgp_vertex_get_in_degree(struct mgp_vertex *v, size_t *result); + +/// Get the out degree of given vertex. +enum mgp_error mgp_vertex_get_out_degree(struct mgp_vertex *v, size_t *result); + /// Result is non-zero if the vertex can be modified. /// The mutability of the vertex is the same as the graph which it is part of. If a vertex is immutable, then edges /// cannot be created or deleted, properties and labels cannot be set or removed and all of the returned edges will be @@ -632,61 +715,73 @@ enum mgp_error mgp_vertex_underlying_graph_is_mutable(struct mgp_vertex *v, int /// Set the value of a property on a vertex. /// When the value is `null`, then the property is removed from the vertex. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the property. -/// Return MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable. -/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted. -/// Return MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction. -/// Return MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the property. +/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction. +/// Return mgp_error::MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path. enum mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_name, struct mgp_value *property_value); +/// Set the value of properties on a vertex. +/// When the value is `null`, then the property is removed from the vertex. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the property. +/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction. +/// Return mgp_error::MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path. +enum mgp_error mgp_vertex_set_properties(struct mgp_vertex *v, struct mgp_map *properties); + /// Add the label to the vertex. /// If the vertex already has the label, this function does nothing. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the label. -/// Return MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable. -/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted. -/// Return MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the label. +/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction. enum mgp_error mgp_vertex_add_label(struct mgp_vertex *v, struct mgp_label label); /// Remove the label from the vertex. /// If the vertex doesn't have the label, this function does nothing. -/// Return MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable. -/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted. -/// Return MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction. +/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction. enum mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, struct mgp_label label); /// Copy a mgp_vertex. /// Resulting pointer must be freed with mgp_vertex_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertex. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertex. enum mgp_error mgp_vertex_copy(struct mgp_vertex *v, struct mgp_memory *memory, struct mgp_vertex **result); /// Free the memory used by a mgp_vertex. void mgp_vertex_destroy(struct mgp_vertex *v); +/// Return whether the given mgp_vertex is deleted. +enum mgp_error mgp_vertex_is_deleted(struct mgp_vertex *v, int *result); + /// Result is non-zero if given vertices are equal, otherwise 0. enum mgp_error mgp_vertex_equal(struct mgp_vertex *v1, struct mgp_vertex *v2, int *result); /// Get the number of labels a given vertex has. -/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. enum mgp_error mgp_vertex_labels_count(struct mgp_vertex *v, size_t *result); /// Get mgp_label in mgp_vertex at given index. -/// Return MGP_ERROR_OUT_OF_RANGE if the index is out of range. -/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_OUT_OF_RANGE if the index is out of range. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. enum mgp_error mgp_vertex_label_at(struct mgp_vertex *v, size_t index, struct mgp_label *result); /// Result is non-zero if the given vertex has the given label. -/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. enum mgp_error mgp_vertex_has_label(struct mgp_vertex *v, struct mgp_label label, int *result); /// Result is non-zero if the given vertex has a label with given name. -/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. enum mgp_error mgp_vertex_has_label_named(struct mgp_vertex *v, const char *label_name, int *result); /// Get a copy of a vertex property mapped to a given name. /// Resulting value must be freed with mgp_value_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value. -/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. enum mgp_error mgp_vertex_get_property(struct mgp_vertex *v, const char *property_name, struct mgp_memory *memory, struct mgp_value **result); @@ -694,8 +789,8 @@ enum mgp_error mgp_vertex_get_property(struct mgp_vertex *v, const char *propert /// The properties of the vertex are copied when the iterator is created, therefore later changes won't affect them. /// The resulting mgp_properties_iterator needs to be deallocated with /// mgp_properties_iterator_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_properties_iterator. -/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_properties_iterator. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. enum mgp_error mgp_vertex_iter_properties(struct mgp_vertex *v, struct mgp_memory *memory, struct mgp_properties_iterator **result); @@ -703,8 +798,8 @@ enum mgp_error mgp_vertex_iter_properties(struct mgp_vertex *v, struct mgp_memor /// The connection information of the vertex is copied when the iterator is created, therefore later creation or /// deletion of edges won't affect the iterated edges, however the property changes on the edges will be visible. /// The resulting mgp_edges_iterator needs to be deallocated with mgp_edges_iterator_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edges_iterator. -/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edges_iterator. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. enum mgp_error mgp_vertex_iter_in_edges(struct mgp_vertex *v, struct mgp_memory *memory, struct mgp_edges_iterator **result); @@ -712,8 +807,8 @@ enum mgp_error mgp_vertex_iter_in_edges(struct mgp_vertex *v, struct mgp_memory /// The connection information of the vertex is copied when the iterator is created, therefore later creation or /// deletion of edges won't affect the iterated edges, however the property changes on the edges will be visible. /// The resulting mgp_edges_iterator needs to be deallocated with mgp_edges_iterator_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edges_iterator. -/// Return MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edges_iterator. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. enum mgp_error mgp_vertex_iter_out_edges(struct mgp_vertex *v, struct mgp_memory *memory, struct mgp_edges_iterator **result); @@ -732,7 +827,7 @@ enum mgp_error mgp_edges_iterator_get(struct mgp_edges_iterator *it, struct mgp_ /// The previous mgp_edge obtained through mgp_edges_iterator_get /// will be invalidated, and you must not use its value. /// Result is NULL if the end of the iteration has been reached. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edge. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edge. enum mgp_error mgp_edges_iterator_next(struct mgp_edges_iterator *it, struct mgp_edge **result); /// ID of an edge; valid during a single query execution. @@ -751,12 +846,15 @@ enum mgp_error mgp_edge_underlying_graph_is_mutable(struct mgp_edge *e, int *res /// Copy a mgp_edge. /// Resulting pointer must be freed with mgp_edge_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edge. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edge. enum mgp_error mgp_edge_copy(struct mgp_edge *e, struct mgp_memory *memory, struct mgp_edge **result); /// Free the memory used by a mgp_edge. void mgp_edge_destroy(struct mgp_edge *e); +/// Return whether the given mgp_edge is deleted. +enum mgp_error mgp_edge_is_deleted(struct mgp_edge *e, int *result); + /// Result is non-zero if given edges are equal, otherwise 0. enum mgp_error mgp_edge_equal(struct mgp_edge *e1, struct mgp_edge *e2, int *result); @@ -775,39 +873,143 @@ enum mgp_error mgp_edge_get_to(struct mgp_edge *e, struct mgp_vertex **result); /// Get a copy of a edge property mapped to a given name. /// Resulting value must be freed with mgp_value_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value. -/// Return MGP_ERROR_DELETED_OBJECT if `e` has been deleted. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_value. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `e` has been deleted. enum mgp_error mgp_edge_get_property(struct mgp_edge *e, const char *property_name, struct mgp_memory *memory, struct mgp_value **result); /// Set the value of a property on an edge. /// When the value is `null`, then the property is removed from the edge. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the property. -/// Return MGP_ERROR_IMMUTABLE_OBJECT if `e` is immutable. -/// Return MGP_ERROR_DELETED_OBJECT if `e` has been deleted. -/// Return MGP_ERROR_LOGIC_ERROR if properties on edges are disabled. -/// Return MGP_ERROR_SERIALIZATION_ERROR if `e` has been modified by another transaction. -/// Return MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the property. +/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `e` is immutable. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `e` has been deleted. +/// Return mgp_error::MGP_ERROR_LOGIC_ERROR if properties on edges are disabled. +/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `e` has been modified by another transaction. +/// Return mgp_error::MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path. enum mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, struct mgp_value *property_value); +/// Set the value of properties on a vertex. +/// When the value is `null`, then the property is removed from the vertex. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for storing the property. +/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `v` is immutable. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `v` has been deleted. +/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `v` has been modified by another transaction. +/// Return mgp_error::MGP_ERROR_VALUE_CONVERSION if `property_value` is vertex, edge or path. +enum mgp_error mgp_edge_set_properties(struct mgp_edge *e, struct mgp_map *properties); + /// Start iterating over properties stored in the given edge. /// The properties of the edge are copied when the iterator is created, therefore later changes won't affect them. /// Resulting mgp_properties_iterator needs to be deallocated with /// mgp_properties_iterator_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_properties_iterator. -/// Return MGP_ERROR_DELETED_OBJECT if `e` has been deleted. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_properties_iterator. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `e` has been deleted. enum mgp_error mgp_edge_iter_properties(struct mgp_edge *e, struct mgp_memory *memory, struct mgp_properties_iterator **result); -/// State of the graph database. -struct mgp_graph; - /// Get the vertex corresponding to given ID, or NULL if no such vertex exists. /// Resulting vertex must be freed using mgp_vertex_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the vertex. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the vertex. enum mgp_error mgp_graph_get_vertex_by_id(struct mgp_graph *g, struct mgp_vertex_id id, struct mgp_memory *memory, struct mgp_vertex **result); +/// Result is non-zero if the index with the given name exists. +/// The current implementation always returns without errors. +enum mgp_error mgp_graph_has_text_index(struct mgp_graph *graph, const char *index_name, int *result); + +/// Available modes of searching text indices. +MGP_ENUM_CLASS text_search_mode{ + SPECIFIED_PROPERTIES, + REGEX, + ALL_PROPERTIES, +}; + +/// Search the named text index for the given query. The result is a map with the "search_results" and "error_msg" keys. +/// The "search_results" key contains the vertices whose text-indexed properties match the given query. +/// In case of a Tantivy error, the "search_results" key is absent, and "error_msg" contains the error message. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if there’s an allocation error while constructing the results map. +/// Return mgp_error::MGP_ERROR_KEY_ALREADY_EXISTS if the same key is being created in the results map more than once. +enum mgp_error mgp_graph_search_text_index(struct mgp_graph *graph, const char *index_name, const char *search_query, + enum text_search_mode search_mode, struct mgp_memory *memory, + struct mgp_map **result); + +/// Aggregate over the results of a search over the named text index. The result is a map with the "aggregation_results" +/// and "error_msg" keys. +/// The "aggregation_results" key contains the vertices whose text-indexed properties match the given query. +/// In case of a Tantivy error, the "aggregation_results" key is absent, and "error_msg" contains the error message. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if there’s an allocation error while constructing the results map. +/// Return mgp_error::MGP_ERROR_KEY_ALREADY_EXISTS if the same key is being created in the results map more than once. +enum mgp_error mgp_graph_aggregate_over_text_index(struct mgp_graph *graph, const char *index_name, + const char *search_query, const char *aggregation_query, + struct mgp_memory *memory, struct mgp_map **result); + +enum mgp_error mgp_graph_search_vector_index(struct mgp_graph *graph, const char *index_name, struct mgp_list *query, + int result_size, struct mgp_memory *memory, struct mgp_map **result); + +enum mgp_error mgp_graph_search_vector_index_on_edges(struct mgp_graph *graph, const char *index_name, + struct mgp_list *query, int result_size, + struct mgp_memory *memory, struct mgp_map **result); + +enum mgp_error mgp_graph_show_index_info(struct mgp_graph *graph, struct mgp_memory *memory, struct mgp_map **result); + +/// Creates label index for given label. +/// mgp_error::MGP_ERROR_NO_ERROR is always returned. +/// if label index already exists, result will be 0, otherwise 1. +enum mgp_error mgp_create_label_index(struct mgp_graph *graph, const char *label, int *result); + +/// Drop label index. +enum mgp_error mgp_drop_label_index(struct mgp_graph *graph, const char *label, int *result); + +/// List all label indices. +enum mgp_error mgp_list_all_label_indices(struct mgp_graph *graph, struct mgp_memory *memory, struct mgp_list **result); + +/// Creates label-property index for given label and propery. +/// mgp_error::MGP_ERROR_NO_ERROR is always returned. +/// if label property index already exists, result will be 0, otherwise 1. +enum mgp_error mgp_create_label_property_index(struct mgp_graph *graph, const char *label, const char *property, + int *result); + +/// Drops label-property index for given label and propery. +/// mgp_error::MGP_ERROR_NO_ERROR is always returned. +/// if dropping label property index failed, result will be 0, otherwise 1. +enum mgp_error mgp_drop_label_property_index(struct mgp_graph *graph, const char *label, const char *property, + int *result); + +/// List all label+property indices. +enum mgp_error mgp_list_all_label_property_indices(struct mgp_graph *graph, struct mgp_memory *memory, + struct mgp_list **result); + +/// Creates existence constraint for given label and property. +/// mgp_error::MGP_ERROR_NO_ERROR is always returned. +/// if creating existence constraint failed, result will be 0, otherwise 1. +enum mgp_error mgp_create_existence_constraint(struct mgp_graph *graph, const char *label, const char *property, + int *result); + +/// Drops existence constraint for given label and property. +/// mgp_error::MGP_ERROR_NO_ERROR is always returned. +/// if dropping existence constraint failed, result will be 0, otherwise 1. +enum mgp_error mgp_drop_existence_constraint(struct mgp_graph *graph, const char *label, const char *property, + int *result); + +/// List all existence constraints. +enum mgp_error mgp_list_all_existence_constraints(struct mgp_graph *graph, struct mgp_memory *memory, + struct mgp_list **result); + +/// Creates unique constraint for given label and properties. +/// mgp_error::MGP_ERROR_NO_ERROR is always returned. +/// if creating unique constraint failed, result will be 0, otherwise 1. +enum mgp_error mgp_create_unique_constraint(struct mgp_graph *graph, const char *label, struct mgp_list *properties, + int *result); + +/// Drops unique constraint for given label and properties. +/// mgp_error::MGP_ERROR_NO_ERROR is always returned. +/// if dropping unique constraint failed, result will be 0, otherwise 1. +enum mgp_error mgp_drop_unique_constraint(struct mgp_graph *graph, const char *label, struct mgp_list *properties, + int *result); + +/// List all unique constraints +enum mgp_error mgp_list_all_unique_constraints(struct mgp_graph *graph, struct mgp_memory *memory, + struct mgp_list **result); + /// Result is non-zero if the graph can be modified. /// If a graph is immutable, then vertices cannot be created or deleted, and all of the returned vertices will be /// immutable also. The same applies for edges. @@ -822,34 +1024,34 @@ enum mgp_error mgp_graph_is_transactional(struct mgp_graph *graph, int *result); /// Add a new vertex to the graph. /// Resulting vertex must be freed using mgp_vertex_destroy. -/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertex. +/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertex. enum mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, struct mgp_memory *memory, struct mgp_vertex **result); /// Delete a vertex from the graph. -/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. -/// Return MGP_ERROR_LOGIC_ERROR if `vertex` has edges. -/// Return MGP_ERROR_SERIALIZATION_ERROR if `vertex` has been modified by another transaction. +/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. +/// Return mgp_error::MGP_ERROR_LOGIC_ERROR if `vertex` has edges. +/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `vertex` has been modified by another transaction. enum mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, struct mgp_vertex *vertex); /// Delete a vertex and all of its edges from the graph. -/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. -/// Return MGP_ERROR_SERIALIZATION_ERROR if `vertex` has been modified by another transaction. +/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. +/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `vertex` has been modified by another transaction. enum mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, struct mgp_vertex *vertex); /// Add a new directed edge between the two vertices with the specified label. /// Resulting edge must be freed using mgp_edge_destroy. -/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edge. -/// Return MGP_ERROR_DELETED_OBJECT if `from` or `to` has been deleted. -/// Return MGP_ERROR_SERIALIZATION_ERROR if `from` or `to` has been modified by another transaction. +/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_edge. +/// Return mgp_error::MGP_ERROR_DELETED_OBJECT if `from` or `to` has been deleted. +/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `from` or `to` has been modified by another transaction. enum mgp_error mgp_graph_create_edge(struct mgp_graph *graph, struct mgp_vertex *from, struct mgp_vertex *to, struct mgp_edge_type type, struct mgp_memory *memory, struct mgp_edge **result); /// Delete an edge from the graph. -/// Return MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. -/// Return MGP_ERROR_SERIALIZATION_ERROR if `edge`, its source or destination vertex has been modified by another -/// transaction. +/// Return mgp_error::MGP_ERROR_IMMUTABLE_OBJECT if `graph` is immutable. +/// Return mgp_error::MGP_ERROR_SERIALIZATION_ERROR if `edge`, its source or destination vertex has been modified by +/// another transaction. enum mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, struct mgp_edge *edge); /// Iterator over vertices. @@ -860,7 +1062,7 @@ void mgp_vertices_iterator_destroy(struct mgp_vertices_iterator *it); /// Start iterating over vertices of the given graph. /// Resulting mgp_vertices_iterator needs to be deallocated with mgp_vertices_iterator_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertices_iterator. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertices_iterator. enum mgp_error mgp_graph_iter_vertices(struct mgp_graph *g, struct mgp_memory *memory, struct mgp_vertices_iterator **result); @@ -875,6 +1077,12 @@ enum mgp_error mgp_vertices_iterator_underlying_graph_is_mutable(struct mgp_vert /// Result is NULL if the end of the iteration has been reached. enum mgp_error mgp_vertices_iterator_get(struct mgp_vertices_iterator *it, struct mgp_vertex **result); +/// Gets the approximate number of vertices in the graph. +enum mgp_error mgp_graph_approximate_vertex_count(struct mgp_graph *graph, size_t *result); + +/// Gets the approximate number of edges in the graph. +enum mgp_error mgp_graph_approximate_edge_count(struct mgp_graph *graph, size_t *result); + /// @name Temporal Types /// ///@{ @@ -887,20 +1095,20 @@ struct mgp_date_parameters { /// Create a date from a string following the ISO 8601 format. /// Resulting date must be freed with mgp_date_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the string cannot be parsed correctly. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the string cannot be parsed correctly. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_date_from_string(const char *string, struct mgp_memory *memory, struct mgp_date **date); /// Create a date from mgp_date_parameter. /// Resulting date must be freed with mgp_date_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the parameters cannot be parsed correctly. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the parameters cannot be parsed correctly. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_date_from_parameters(struct mgp_date_parameters *parameters, struct mgp_memory *memory, struct mgp_date **date); /// Copy a mgp_date. /// Resulting pointer must be freed with mgp_date_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_date_copy(struct mgp_date *date, struct mgp_memory *memory, struct mgp_date **result); /// Free the memory used by a mgp_date. @@ -923,26 +1131,26 @@ enum mgp_error mgp_date_timestamp(struct mgp_date *date, int64_t *timestamp); /// Get the date representing current date. /// Resulting date must be freed with mgp_date_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_date_now(struct mgp_memory *memory, struct mgp_date **date); /// Add a duration to the date. /// Resulting date must be freed with mgp_date_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid date. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid date. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_date_add_duration(struct mgp_date *date, struct mgp_duration *dur, struct mgp_memory *memory, struct mgp_date **result); /// Subtract a duration from the date. /// Resulting date must be freed with mgp_date_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid date. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid date. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_date_sub_duration(struct mgp_date *date, struct mgp_duration *dur, struct mgp_memory *memory, struct mgp_date **result); /// Get a duration between two dates. /// Resulting duration must be freed with mgp_duration_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_date_diff(struct mgp_date *first, struct mgp_date *second, struct mgp_memory *memory, struct mgp_duration **result); @@ -956,21 +1164,21 @@ struct mgp_local_time_parameters { /// Create a local time from a string following the ISO 8601 format. /// Resulting local time must be freed with mgp_local_time_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the string cannot be parsed correctly. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the string cannot be parsed correctly. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_local_time_from_string(const char *string, struct mgp_memory *memory, struct mgp_local_time **local_time); /// Create a local time from mgp_local_time_parameters. /// Resulting local time must be freed with mgp_local_time_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the parameters cannot be parsed correctly. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the parameters cannot be parsed correctly. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_local_time_from_parameters(struct mgp_local_time_parameters *parameters, struct mgp_memory *memory, struct mgp_local_time **local_time); /// Copy a mgp_local_time. /// Resulting pointer must be freed with mgp_local_time_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_local_time_copy(struct mgp_local_time *local_time, struct mgp_memory *memory, struct mgp_local_time **result); @@ -1000,26 +1208,26 @@ enum mgp_error mgp_local_time_timestamp(struct mgp_local_time *local_time, int64 /// Get the local time representing current time. /// Resulting pointer must be freed with mgp_local_time_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_local_time_now(struct mgp_memory *memory, struct mgp_local_time **local_time); /// Add a duration to the local time. /// Resulting pointer must be freed with mgp_local_time_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid local time. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid local time. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_local_time_add_duration(struct mgp_local_time *local_time, struct mgp_duration *dur, struct mgp_memory *memory, struct mgp_local_time **result); /// Subtract a duration from the local time. /// Resulting pointer must be freed with mgp_local_time_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid local time. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid local time. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_local_time_sub_duration(struct mgp_local_time *local_time, struct mgp_duration *dur, struct mgp_memory *memory, struct mgp_local_time **result); /// Get a duration between two local times. /// Resulting pointer must be freed with mgp_duration_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_date. enum mgp_error mgp_local_time_diff(struct mgp_local_time *first, struct mgp_local_time *second, struct mgp_memory *memory, struct mgp_duration **result); @@ -1030,22 +1238,22 @@ struct mgp_local_date_time_parameters { /// Create a local date-time from a string following the ISO 8601 format. /// Resulting local date-time must be freed with mgp_local_date_time_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the string cannot be parsed correctly. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the string cannot be parsed correctly. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. enum mgp_error mgp_local_date_time_from_string(const char *string, struct mgp_memory *memory, struct mgp_local_date_time **local_date_time); /// Create a local date-time from mgp_local_date_time_parameters. /// Resulting local date-time must be freed with mgp_local_date_time_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the parameters cannot be parsed correctly. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the parameters cannot be parsed correctly. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. enum mgp_error mgp_local_date_time_from_parameters(struct mgp_local_date_time_parameters *parameters, struct mgp_memory *memory, struct mgp_local_date_time **local_date_time); /// Copy a mgp_local_date_time. /// Resulting pointer must be freed with mgp_local_date_time_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. enum mgp_error mgp_local_date_time_copy(struct mgp_local_date_time *local_date_time, struct mgp_memory *memory, struct mgp_local_date_time **result); @@ -1085,26 +1293,26 @@ enum mgp_error mgp_local_date_time_timestamp(struct mgp_local_date_time *local_d /// Get the local date-time representing current date and time. /// Resulting local date-time must be freed with mgp_local_date_time_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. enum mgp_error mgp_local_date_time_now(struct mgp_memory *memory, struct mgp_local_date_time **local_date_time); /// Add a duration to the local date-time. /// Resulting local date-time must be freed with mgp_local_date_time_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid local date-time. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid local date-time. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. enum mgp_error mgp_local_date_time_add_duration(struct mgp_local_date_time *local_date_time, struct mgp_duration *dur, struct mgp_memory *memory, struct mgp_local_date_time **result); /// Subtract a duration from the local date-time. /// Resulting local date-time must be freed with mgp_local_date_time_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid local date-time. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid local date-time. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. enum mgp_error mgp_local_date_time_sub_duration(struct mgp_local_date_time *local_date_time, struct mgp_duration *dur, struct mgp_memory *memory, struct mgp_local_date_time **result); /// Get a duration between two local date-times. /// Resulting duration must be freed with mgp_duration_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_local_date_time. enum mgp_error mgp_local_date_time_diff(struct mgp_local_date_time *first, struct mgp_local_date_time *second, struct mgp_memory *memory, struct mgp_duration **result); @@ -1119,26 +1327,26 @@ struct mgp_duration_parameters { /// Create a duration from a string following the ISO 8601 format. /// Resulting duration must be freed with mgp_duration_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the string cannot be parsed correctly. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the string cannot be parsed correctly. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. enum mgp_error mgp_duration_from_string(const char *string, struct mgp_memory *memory, struct mgp_duration **duration); /// Create a duration from mgp_duration_parameters. /// Resulting duration must be freed with mgp_duration_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the parameters cannot be parsed correctly. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the parameters cannot be parsed correctly. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. enum mgp_error mgp_duration_from_parameters(struct mgp_duration_parameters *parameters, struct mgp_memory *memory, struct mgp_duration **duration); /// Create a duration from microseconds. /// Resulting duration must be freed with mgp_duration_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. enum mgp_error mgp_duration_from_microseconds(int64_t microseconds, struct mgp_memory *memory, struct mgp_duration **duration); /// Copy a mgp_duration. /// Resulting pointer must be freed with mgp_duration_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. enum mgp_error mgp_duration_copy(struct mgp_duration *duration, struct mgp_memory *memory, struct mgp_duration **result); @@ -1153,20 +1361,20 @@ enum mgp_error mgp_duration_get_microseconds(struct mgp_duration *duration, int6 /// Apply unary minus operator to the duration. /// Resulting pointer must be freed with mgp_duration_destroy. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. enum mgp_error mgp_duration_neg(struct mgp_duration *dur, struct mgp_memory *memory, struct mgp_duration **result); /// Add two durations. /// Resulting pointer must be freed with mgp_duration_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid duration. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid duration. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. enum mgp_error mgp_duration_add(struct mgp_duration *first, struct mgp_duration *second, struct mgp_memory *memory, struct mgp_duration **result); /// Subtract two durations. /// Resulting pointer must be freed with mgp_duration_destroy. -/// Return MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid duration. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the operation results in an invalid duration. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_duration. enum mgp_error mgp_duration_sub(struct mgp_duration *first, struct mgp_duration *second, struct mgp_memory *memory, struct mgp_duration **result); ///@} @@ -1175,7 +1383,7 @@ enum mgp_error mgp_duration_sub(struct mgp_duration *first, struct mgp_duration /// The previous mgp_vertex obtained through mgp_vertices_iterator_get /// will be invalidated, and you must not use its value. /// Result is NULL if the end of the iteration has been reached. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertex. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate a mgp_vertex. enum mgp_error mgp_vertices_iterator_next(struct mgp_vertices_iterator *it, struct mgp_vertex **result); ///@} @@ -1194,29 +1402,29 @@ struct mgp_type; /// Get the type representing any value that isn't `null`. /// /// The ANY type is the parent type of all types. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_any(struct mgp_type **result); /// Get the type representing boolean values. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_bool(struct mgp_type **result); /// Get the type representing character string values. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_string(struct mgp_type **result); /// Get the type representing integer values. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_int(struct mgp_type **result); /// Get the type representing floating-point values. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_float(struct mgp_type **result); /// Get the type representing any number value. /// /// This is the parent type for numeric types, i.e. INTEGER and FLOAT. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_number(struct mgp_type **result); /// Get the type representing map values. @@ -1228,51 +1436,51 @@ enum mgp_error mgp_type_number(struct mgp_type **result); /// /// @sa mgp_type_node /// @sa mgp_type_relationship -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_map(struct mgp_type **result); /// Get the type representing graph node values. /// /// Since a node contains a map of properties, the node itself is also of MAP /// type. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_node(struct mgp_type **result); /// Get the type representing graph relationship values. /// /// Since a relationship contains a map of properties, the relationship itself /// is also of MAP type. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_relationship(struct mgp_type **result); /// Get the type representing a graph path (walk) from one node to another. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_path(struct mgp_type **result); /// Build a type representing a list of values of given `element_type`. /// -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_list(struct mgp_type *element_type, struct mgp_type **result); /// Get the type representing a date. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_date(struct mgp_type **result); /// Get the type representing a local time. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_local_time(struct mgp_type **result); /// Get the type representing a local date-time. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_local_date_time(struct mgp_type **result); /// Get the type representing a duration. -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_duration(struct mgp_type **result); /// Build a type representing either a `null` value or a value of given `type`. /// -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate the new type. enum mgp_error mgp_type_nullable(struct mgp_type *type, struct mgp_type **result); ///@} @@ -1296,6 +1504,15 @@ struct mgp_module; /// Describes a procedure of a query module. struct mgp_proc; +/// Describes a Memgraph magic function. +struct mgp_func; + +/// All available log levels that can be used in mgp_log function +MGP_ENUM_CLASS mgp_log_level{ + MGP_LOG_LEVEL_TRACE, MGP_LOG_LEVEL_DEBUG, MGP_LOG_LEVEL_INFO, + MGP_LOG_LEVEL_WARN, MGP_LOG_LEVEL_ERROR, MGP_LOG_LEVEL_CRITICAL, +}; + /// Entry-point for a query module read procedure, invoked through openCypher. /// /// Passed in arguments will not live longer than the callback's execution. @@ -1303,6 +1520,13 @@ struct mgp_proc; /// to allocate global resources. typedef void (*mgp_proc_cb)(struct mgp_list *, struct mgp_graph *, struct mgp_result *, struct mgp_memory *); +/// Cleanup for a query module read procedure. Can't be invoked through OpenCypher. Cleans batched stream. +typedef void (*mgp_proc_cleanup)(); + +/// Initializer for a query module batched read procedure. Can't be invoked through OpenCypher. Initializes batched +/// stream. +typedef void (*mgp_proc_initializer)(struct mgp_list *, struct mgp_graph *, struct mgp_memory *); + /// Register a read-only procedure to a module. /// /// The `name` must be a sequence of digits, underscores, lowercase and @@ -1310,9 +1534,9 @@ typedef void (*mgp_proc_cb)(struct mgp_list *, struct mgp_graph *, struct mgp_re /// Note that Unicode characters are not allowed. Additionally, names are /// case-sensitive. /// -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for mgp_proc. -/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid procedure name. -/// RETURN MGP_ERROR_LOGIC_ERROR if a procedure with the same name was already registered. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for mgp_proc. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid procedure name. +/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if a procedure with the same name was already registered. enum mgp_error mgp_module_add_read_procedure(struct mgp_module *module, const char *name, mgp_proc_cb cb, struct mgp_proc **result); @@ -1321,12 +1545,36 @@ enum mgp_error mgp_module_add_read_procedure(struct mgp_module *module, const ch /// The `name` must be a valid identifier, following the same rules as the /// procedure`name` in mgp_module_add_read_procedure. /// -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for mgp_proc. -/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid procedure name. -/// RETURN MGP_ERROR_LOGIC_ERROR if a procedure with the same name was already registered. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for mgp_proc. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid procedure name. +/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if a procedure with the same name was already registered. enum mgp_error mgp_module_add_write_procedure(struct mgp_module *module, const char *name, mgp_proc_cb cb, struct mgp_proc **result); +/// Register a readable batched procedure to a module. +/// +/// The `name` must be a valid identifier, following the same rules as the +/// procedure`name` in mgp_module_add_read_procedure. +/// +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for mgp_proc. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid procedure name. +/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if a procedure with the same name was already registered. +enum mgp_error mgp_module_add_batch_read_procedure(struct mgp_module *module, const char *name, mgp_proc_cb cb, + mgp_proc_initializer initializer, mgp_proc_cleanup cleanup, + struct mgp_proc **result); + +/// Register a writeable batched procedure to a module. +/// +/// The `name` must be a valid identifier, following the same rules as the +/// procedure`name` in mgp_module_add_read_procedure. +/// +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for mgp_proc. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid procedure name. +/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if a procedure with the same name was already registered. +enum mgp_error mgp_module_add_batch_write_procedure(struct mgp_module *module, const char *name, mgp_proc_cb cb, + mgp_proc_initializer initializer, mgp_proc_cleanup cleanup, + struct mgp_proc **result); + /// Add a required argument to a procedure. /// /// The order of adding arguments will correspond to the order the procedure @@ -1338,9 +1586,9 @@ enum mgp_error mgp_module_add_write_procedure(struct mgp_module *module, const c /// /// Passed in `type` describes what kind of values can be used as the argument. /// -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. -/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid argument name. -/// RETURN MGP_ERROR_LOGIC_ERROR if the procedure already has any optional argument. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid argument name. +/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if the procedure already has any optional argument. enum mgp_error mgp_proc_add_arg(struct mgp_proc *proc, const char *name, struct mgp_type *type); /// Add an optional argument with a default value to a procedure. @@ -1361,10 +1609,10 @@ enum mgp_error mgp_proc_add_arg(struct mgp_proc *proc, const char *name, struct /// a graph element (node, relationship, path) and it must satisfy the given /// `type`. /// -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. -/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid argument name. -/// RETURN MGP_ERROR_VALUE_CONVERSION if `default_value` is a graph element (vertex, edge or path). -/// RETURN MGP_ERROR_LOGIC_ERROR if `default_value` does not satisfy `type`. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid argument name. +/// RETURN mgp_error::MGP_ERROR_VALUE_CONVERSION if `default_value` is a graph element (vertex, edge or path). +/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if `default_value` does not satisfy `type`. enum mgp_error mgp_proc_add_opt_arg(struct mgp_proc *proc, const char *name, struct mgp_type *type, struct mgp_value *default_value); @@ -1376,9 +1624,9 @@ enum mgp_error mgp_proc_add_opt_arg(struct mgp_proc *proc, const char *name, str /// Passed in `type` describes what kind of values can be returned through the /// result field. /// -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. -/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid result name. -/// RETURN MGP_ERROR_LOGIC_ERROR if a result field with the same name was already added. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid result name. +/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if a result field with the same name was already added. enum mgp_error mgp_proc_add_result(struct mgp_proc *proc, const char *name, struct mgp_type *type); /// Add a result field to a procedure and mark it as deprecated. @@ -1386,10 +1634,13 @@ enum mgp_error mgp_proc_add_result(struct mgp_proc *proc, const char *name, stru /// This is the same as mgp_proc_add_result, but the result field will be marked /// as deprecated. /// -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. -/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid result name. -/// RETURN MGP_ERROR_LOGIC_ERROR if a result field with the same name was already added. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid result name. +/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if a result field with the same name was already added. enum mgp_error mgp_proc_add_deprecated_result(struct mgp_proc *proc, const char *name, struct mgp_type *type); + +/// Log a message on a certain level. +enum mgp_error mgp_log(enum mgp_log_level log_level, const char *output); ///@} /// @name Execution @@ -1399,7 +1650,10 @@ enum mgp_error mgp_proc_add_deprecated_result(struct mgp_proc *proc, const char /// @{ /// Return non-zero if the currently executing procedure should abort as soon as -/// possible. +/// possible. If non-zero the reasons are: +/// (1) The transaction was requested to be terminated +/// (2) The server is gracefully shutting down +/// (3) The transaction has hit its timeout threshold /// /// Procedures which perform heavyweight processing run the risk of running too /// long and going over the query execution time limit. To prevent this, such @@ -1411,38 +1665,76 @@ int mgp_must_abort(struct mgp_graph *graph); /// @} -/// @name Kafka message API -/// Currently the API below is for kafka only but in the future -/// mgp_message and mgp_messages might be generic to support -/// other streaming systems. +/// @name Stream Source message API +/// API for accessing specific data contained in a mgp_message +/// used for defining transformation procedures. +/// Not all methods are available for all stream sources +/// so make sure that your transformation procedure can be used +/// for a specific source, i.e. only valid methods are used. ///@{ -/// A single Kafka message +/// A single Stream source message struct mgp_message; -/// A list of Kafka messages +/// A list of Stream source messages struct mgp_messages; +/// Stream source type. +enum mgp_source_type { + KAFKA, + PULSAR, +}; + +/// Get the type of the stream source that produced the message. +enum mgp_error mgp_message_source_type(struct mgp_message *message, enum mgp_source_type *result); + /// Payload is not null terminated and not a string but rather a byte array. /// You need to call mgp_message_payload_size() first, to read the size of /// the payload. +/// Supported stream sources: +/// - Kafka +/// - Pulsar +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source. enum mgp_error mgp_message_payload(struct mgp_message *message, const char **result); /// Get the payload size +/// Supported stream sources: +/// - Kafka +/// - Pulsar +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source. enum mgp_error mgp_message_payload_size(struct mgp_message *message, size_t *result); /// Get the name of topic +/// Supported stream sources: +/// - Kafka +/// - Pulsar +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source. enum mgp_error mgp_message_topic_name(struct mgp_message *message, const char **result); /// Get the key of mgp_message as a byte array +/// Supported stream sources: +/// - Kafka +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source. enum mgp_error mgp_message_key(struct mgp_message *message, const char **result); /// Get the key size of mgp_message +/// Supported stream sources: +/// - Kafka +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source. enum mgp_error mgp_message_key_size(struct mgp_message *message, size_t *result); /// Get the timestamp of mgp_message as a byte array +/// Supported stream sources: +/// - Kafka +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source. enum mgp_error mgp_message_timestamp(struct mgp_message *message, int64_t *result); +/// Get the message offset from a message. +/// Supported stream sources: +/// - Kafka +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source. +enum mgp_error mgp_message_offset(struct mgp_message *message, int64_t *result); + /// Get the number of messages contained in the mgp_messages list /// Current implementation always returns without errors. enum mgp_error mgp_messages_size(struct mgp_messages *message, size_t *result); @@ -1464,12 +1756,115 @@ typedef void (*mgp_trans_cb)(struct mgp_messages *, struct mgp_graph *, struct m /// Note that Unicode characters are not allowed. Additionally, names are /// case-sensitive. /// -/// Return MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for transformation. -/// Return MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid transformation name. -/// RETURN MGP_ERROR_LOGIC_ERROR if a transformation with the same name was already registered. +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for transformation. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid transformation name. +/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if a transformation with the same name was already registered. enum mgp_error mgp_module_add_transformation(struct mgp_module *module, const char *name, mgp_trans_cb cb); /// @} +/// @name Memgraph Magic Functions API +/// +/// API for creating the Memgraph magic functions. It is used to create external-source stateless methods which can +/// be called by using openCypher query language. These methods should not modify the original graph and should use only +/// the values provided as arguments to the method. +/// +///@{ + +/// State of the database that is exposed to magic functions. Currently it is unused, but it enables extending the +/// functionalities of magic functions in future without breaking the API. +struct mgp_func_context; + +/// Add a required argument to a function. +/// +/// The order of the added arguments corresponds to the signature of the openCypher function. +/// Note, that required arguments are followed by optional arguments. +/// +/// The `name` must be a valid identifier, following the same rules as the +/// function `name` in mgp_module_add_function. +/// +/// Passed in `type` describes what kind of values can be used as the argument. +/// +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid argument name. +/// Return mgp_error::MGP_ERROR_LOGIC_ERROR if the function already has any optional argument. +enum mgp_error mgp_func_add_arg(struct mgp_func *func, const char *name, struct mgp_type *type); + +/// Add an optional argument with a default value to a function. +/// +/// The order of the added arguments corresponds to the signature of the openCypher function. +/// Note, that required arguments are followed by optional arguments. +/// +/// The `name` must be a valid identifier, following the same rules as the +/// function `name` in mgp_module_add_function. +/// +/// Passed in `type` describes what kind of values can be used as the argument. +/// +/// `default_value` is copied and set as the default value for the argument. +/// Don't forget to call mgp_value_destroy when you are done using +/// `default_value`. When the function is called, if this argument is not +/// provided, `default_value` will be used instead. `default_value` must not be +/// a graph element (node, relationship, path) and it must satisfy the given +/// `type`. +/// +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for an argument. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid argument name. +/// Return mgp_error::MGP_ERROR_VALUE_CONVERSION if `default_value` is a graph element (vertex, edge or path). +/// Return mgp_error::MGP_ERROR_LOGIC_ERROR if `default_value` does not satisfy `type`. +enum mgp_error mgp_func_add_opt_arg(struct mgp_func *func, const char *name, struct mgp_type *type, + struct mgp_value *default_value); + +/// Entry-point for a custom Memgraph awesome function. +/// +/// Passed in arguments will not live longer than the callback's execution. +/// Therefore, you must not store them globally or use the passed in mgp_memory +/// to allocate global resources. +typedef void (*mgp_func_cb)(struct mgp_list *, struct mgp_func_context *, struct mgp_func_result *, + struct mgp_memory *); + +/// Register a Memgraph magic function. +/// +/// The `name` must be a sequence of digits, underscores, lowercase and +/// uppercase Latin letters. The name must begin with a non-digit character. +/// Note that Unicode characters are not allowed. +/// +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for mgp_func. +/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid function name. +/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if a function with the same name was already registered. +enum mgp_error mgp_module_add_function(struct mgp_module *module, const char *name, mgp_func_cb cb, + struct mgp_func **result); + +/// Set an error message as an output to the Magic function +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if there's no memory for copying the error message. +enum mgp_error mgp_func_result_set_error_msg(struct mgp_func_result *result, const char *error_msg, + struct mgp_memory *memory); + +/// Set an output value for the Magic function +/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory to copy the mgp_value to +/// mgp_func_result. +enum mgp_error mgp_func_result_set_value(struct mgp_func_result *result, struct mgp_value *value, + struct mgp_memory *memory); + +struct mgp_execution_headers; + +enum mgp_error mgp_execution_headers_at(struct mgp_execution_headers *headers, size_t index, const char **result); + +enum mgp_error mgp_execution_headers_size(struct mgp_execution_headers *headers, size_t *result); + +struct mgp_execution_result; + +enum mgp_error mgp_execute_query(struct mgp_graph *graph, struct mgp_memory *memory, const char *query, + struct mgp_map *params, struct mgp_execution_result **result); + +enum mgp_error mgp_fetch_execution_headers(struct mgp_execution_result *exec_result, + struct mgp_execution_headers **result); + +enum mgp_error mgp_pull_one(struct mgp_execution_result *exec_result, struct mgp_graph *graph, + struct mgp_memory *memory, struct mgp_map **result); + +void mgp_execution_result_destroy(struct mgp_execution_result *exec_result); + +/// @} + #ifdef __cplusplus } // extern "C" #endif diff --git a/rust/rsmgp-sys/src/memgraph/mod.rs b/rust/rsmgp-sys/src/memgraph/mod.rs index 3b4b0bb0d..ee133e7e4 100644 --- a/rust/rsmgp-sys/src/memgraph/mod.rs +++ b/rust/rsmgp-sys/src/memgraph/mod.rs @@ -39,6 +39,8 @@ pub enum MgpError { ImmutableObject, ValueConversion, SerializationError, + AuthorizationError, + NotYetImplemented, } pub(crate) trait MgpDefault { @@ -156,6 +158,8 @@ pub(crate) fn to_rust_mgp_error(error: mgp_error) -> Option { mgp_error::MGP_ERROR_IMMUTABLE_OBJECT => Some(MgpError::ImmutableObject), mgp_error::MGP_ERROR_VALUE_CONVERSION => Some(MgpError::ValueConversion), mgp_error::MGP_ERROR_SERIALIZATION_ERROR => Some(MgpError::SerializationError), + mgp_error::MGP_ERROR_AUTHORIZATION_ERROR => Some(MgpError::AuthorizationError), + mgp_error::MGP_ERROR_NOT_YET_IMPLEMENTED => Some(MgpError::NotYetImplemented), } } From 5d58248f1ef9e4a87aba594e136c258a94741152 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Fri, 11 Jul 2025 17:35:20 +0200 Subject: [PATCH 07/18] Add type conversio --- rust/rsmgp-mysql/Cargo.toml | 7 ++++--- rust/rsmgp-mysql/src/lib.rs | 29 +++++++++++++++++++---------- rust/rsmgp-sys/src/result/mod.rs | 3 +++ 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/rust/rsmgp-mysql/Cargo.toml b/rust/rsmgp-mysql/Cargo.toml index 9e969dabd..c5749fcf3 100644 --- a/rust/rsmgp-mysql/Cargo.toml +++ b/rust/rsmgp-mysql/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "rsmgp-mysql" version = "0.1.0" -authors = ["Your Name "] -edition = "2018" +authors = ["Josip Mrden "] +edition = "2024" [dependencies] -mysql = "24" +mysql = "26" c_str_macro = "1.0.2" rsmgp-sys = { path = "../rsmgp-sys" } hex = "0.4.3" +chrono = { version = "0.4.41", features = ["serde"] } [lib] name = "rsmgp_mysql" diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index 07ff78bd7..c2e08fbeb 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -1,3 +1,4 @@ +use chrono::{NaiveDate, NaiveDateTime}; use c_str_macro::c_str; use mysql::prelude::*; use mysql::*; @@ -130,7 +131,14 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { return Err(Error::UnableToGetMySQLConnection); } }; - let query_result: Vec = match conn.query(query) { + let prepared_statement = match conn.prep(&query) { + Ok(stmt) => stmt, + Err(e) => { + println!("Error: {}", e); + return Err(Error::UnableToPrepareMySQLStatement); + } + }; + let query_result: Vec = match conn.exec(prepared_statement, ()) { Ok(rows) => rows, Err(e) => { println!("Error: {}", e); @@ -144,7 +152,6 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { for column in row.columns_ref() { let col_name = CString::new(column.name_str().as_bytes()).unwrap(); let column_value = &row[column.name_str().as_ref()]; - println!("column_value: {:?}", column_value); let mg_val = match column_value { mysql::Value::NULL => Value::Null, mysql::Value::Int(i) => Value::Int(*i), @@ -152,25 +159,27 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { if *u <= i64::MAX as u64 { Value::Int(*u as i64) } else { - eprintln!("Warning: MySQL unsigned integer {} exceeds i64::MAX, storing as string", u); Value::String(CString::new(u.to_string()).unwrap()) } } mysql::Value::Float(f) => Value::Float(*f as f64), - mysql::Value::Double(d) => Value::Float(*d), + mysql::Value::Double(d) => Value::Float(*d as f64), mysql::Value::Bytes(b) => { // Try to interpret as UTF-8 string, fallback to hex if not valid match String::from_utf8(b.clone()) { - Ok(s) => Value::String(CString::new(s).unwrap()), + Ok(s) => { + if let Ok(d) = s.parse::() { + Value::Float(d as f64) + } else { + Value::String(CString::new(s).unwrap()) + } + } Err(_) => { - eprintln!("Warning: MySQL bytes column is not valid UTF-8, storing as hex string"); Value::String(CString::new(hex::encode(b)).unwrap()) } } - } - // Optionally handle more types (date/time/decimal) here if needed - other => { - eprintln!("Unhandled MySQL value type: {:?}, mapping to Null", other); + }, + _ => { Value::Null } }; diff --git a/rust/rsmgp-sys/src/result/mod.rs b/rust/rsmgp-sys/src/result/mod.rs index 07fc9331f..915711dff 100644 --- a/rust/rsmgp-sys/src/result/mod.rs +++ b/rust/rsmgp-sys/src/result/mod.rs @@ -330,6 +330,9 @@ pub enum Error { #[snafu(display("Unable to get MySQL connection."))] UnableToGetMySQLConnection, + #[snafu(display("Unable to prepare MySQL statement."))] + UnableToPrepareMySQLStatement, + #[snafu(display("Unable to execute MySQL query."))] UnableToExecuteMySQLQuery, } From 4c1f817ffd4add1567d0256ad2e0a4b79c094bd8 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Fri, 11 Jul 2025 17:35:32 +0200 Subject: [PATCH 08/18] Add type conversio --- rust/rsmgp-mysql/src/lib.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index c2e08fbeb..e008522cd 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -1,5 +1,5 @@ -use chrono::{NaiveDate, NaiveDateTime}; use c_str_macro::c_str; +use chrono::{NaiveDate, NaiveDateTime}; use mysql::prelude::*; use mysql::*; use rsmgp_sys::map::*; @@ -174,14 +174,10 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { Value::String(CString::new(s).unwrap()) } } - Err(_) => { - Value::String(CString::new(hex::encode(b)).unwrap()) - } + Err(_) => Value::String(CString::new(hex::encode(b)).unwrap()), } - }, - _ => { - Value::Null } + _ => Value::Null, }; row_map.insert(col_name.as_c_str(), &mg_val)?; } From 36f00f82f6d19443091a20a08074749795c85b20 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Fri, 11 Jul 2025 19:18:30 +0200 Subject: [PATCH 09/18] Add support for batch read procedure --- rust/rsmgp-batch-example/.gitignore | 2 + rust/rsmgp-batch-example/Cargo.toml | 14 +++ rust/rsmgp-batch-example/src/lib.rs | 56 +++++++++++ rust/rsmgp-sys/src/memgraph/mod.rs | 70 +++++++++++++ rust/rsmgp-sys/src/rsmgp.rs | 146 ++++++++++++++++++++++++++++ 5 files changed, 288 insertions(+) create mode 100644 rust/rsmgp-batch-example/.gitignore create mode 100644 rust/rsmgp-batch-example/Cargo.toml create mode 100644 rust/rsmgp-batch-example/src/lib.rs diff --git a/rust/rsmgp-batch-example/.gitignore b/rust/rsmgp-batch-example/.gitignore new file mode 100644 index 000000000..48317902f --- /dev/null +++ b/rust/rsmgp-batch-example/.gitignore @@ -0,0 +1,2 @@ +target/ +tarpaulin-report.html diff --git a/rust/rsmgp-batch-example/Cargo.toml b/rust/rsmgp-batch-example/Cargo.toml new file mode 100644 index 000000000..3f0ec1b1a --- /dev/null +++ b/rust/rsmgp-batch-example/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "rsmgp-batch-example" +version = "0.1.0" +authors = ["Josip Mrden "] +edition = "2024" + +[dependencies] +c_str_macro = "1.0.2" +rand = "0.9.1" +rsmgp-sys = { path = "../rsmgp-sys" } + +[lib] +name = "rust_batch_example" +crate-type = ["cdylib"] diff --git a/rust/rsmgp-batch-example/src/lib.rs b/rust/rsmgp-batch-example/src/lib.rs new file mode 100644 index 000000000..a569f79d2 --- /dev/null +++ b/rust/rsmgp-batch-example/src/lib.rs @@ -0,0 +1,56 @@ +use c_str_macro::c_str; +use rand::Rng; +use rsmgp_sys::memgraph::*; +use rsmgp_sys::mgp::*; +use rsmgp_sys::result::*; +use rsmgp_sys::rsmgp::*; +use rsmgp_sys::value::*; +use rsmgp_sys::{ + close_module, define_batch_procedure_cleanup, define_batch_procedure_init, define_procedure, + define_type, init_module, +}; +use std::ffi::CString; +use std::os::raw::c_int; +use std::panic; + +init_module!(|memgraph: &Memgraph| -> Result<()> { + memgraph.add_batch_read_procedure( + test_procedure, + c_str!("test_procedure"), + init_procedure, + cleanup_procedure, + &[], + &[], + &[define_type!("value", Type::Int)], + )?; + + Ok(()) +}); + +define_procedure!(test_procedure, |memgraph: &Memgraph| -> Result<()> { + println!("Entered test procedure!"); + let mut rng = rand::rng(); + let n = rng.random_range(1..=5); + println!("n: {}", n); + if n != 2 { + let result = memgraph.result_record()?; + result.insert_mgp_value(c_str!("value"), &MgpValue::make_int(1, memgraph)?)?; + } + std::thread::sleep(std::time::Duration::from_secs(5)); + println!("Exiting test procedure!"); + Ok(()) +}); + +define_batch_procedure_init!(init_procedure, |memgraph: &Memgraph| -> Result<()> { + println!("Entered init procedure!"); + println!("Exiting init procedure!"); + Ok(()) +}); + +define_batch_procedure_cleanup!(cleanup_procedure, |memgraph: &Memgraph| -> Result<()> { + println!("Entered cleanup procedure!"); + println!("Exiting cleanup procedure!"); + Ok(()) +}); + +close_module!(|| -> Result<()> { Ok(()) }); diff --git a/rust/rsmgp-sys/src/memgraph/mod.rs b/rust/rsmgp-sys/src/memgraph/mod.rs index ee133e7e4..e3379160d 100644 --- a/rust/rsmgp-sys/src/memgraph/mod.rs +++ b/rust/rsmgp-sys/src/memgraph/mod.rs @@ -444,6 +444,76 @@ impl Memgraph { } } + pub fn add_batch_read_procedure( + &self, + proc_ptr: extern "C" fn(*mut mgp_list, *mut mgp_graph, *mut mgp_result, *mut mgp_memory), + name: &CStr, + init_ptr: extern "C" fn(*mut mgp_list, *mut mgp_graph, *mut mgp_memory), + cleanup_ptr: extern "C" fn(), + required_arg_types: &[NamedType], + optional_arg_types: &[OptionalNamedType], + result_field_types: &[NamedType], + ) -> Result<()> { + unsafe { + let maybe_procedure = invoke_mgp_func!( + *mut mgp_proc, + ffi::mgp_module_add_batch_read_procedure, + self.module_ptr(), + name.as_ptr(), + Some(proc_ptr), + Some(init_ptr), + Some(cleanup_ptr) + ); + if maybe_procedure.is_err() { + return Err(Error::UnableToRegisterReadProcedure); + } + let procedure = maybe_procedure.unwrap(); + + for required_type in required_arg_types { + let mgp_type = resolve_mgp_type(&required_type.types); + if ffi::mgp_proc_add_arg(procedure, required_type.name.as_ptr(), mgp_type) + != mgp_error::MGP_ERROR_NO_ERROR + { + return Err(Error::UnableToAddRequiredArguments); + } + } + + for optional_input in optional_arg_types { + let mgp_type = resolve_mgp_type(&optional_input.types); + + if ffi::mgp_proc_add_opt_arg( + procedure, + optional_input.name.as_ptr(), + mgp_type, + optional_input.default.mgp_ptr(), + ) != mgp_error::MGP_ERROR_NO_ERROR + { + return Err(Error::UnableToAddOptionalArguments); + } + } + + for result_field in result_field_types { + let mgp_type = resolve_mgp_type(&result_field.types); + if result_field.deprecated { + if ffi::mgp_proc_add_deprecated_result( + procedure, + result_field.name.as_ptr(), + mgp_type, + ) != mgp_error::MGP_ERROR_NO_ERROR + { + return Err(Error::UnableToAddDeprecatedReturnType); + } + } else if ffi::mgp_proc_add_result(procedure, result_field.name.as_ptr(), mgp_type) + != mgp_error::MGP_ERROR_NO_ERROR + { + return Err(Error::UnableToAddReturnType); + } + } + + Ok(()) + } + } + /// Return `true` if the currently executing procedure should abort as soon as possible. /// /// Procedures which perform heavyweight processing run the risk of running too long and going diff --git a/rust/rsmgp-sys/src/rsmgp.rs b/rust/rsmgp-sys/src/rsmgp.rs index f5da120b0..645ca45cb 100644 --- a/rust/rsmgp-sys/src/rsmgp.rs +++ b/rust/rsmgp-sys/src/rsmgp.rs @@ -99,6 +99,152 @@ macro_rules! define_procedure { }; } +/// Defines a new procedure callable by Memgraph engine. +/// +/// Wraps call to the Rust function provided as a second argument. In addition to the function +/// call, it also sets the catch_unwind hook and properly handles execution errors. The first macro +/// argument is the exact name of the C procedure Memgraph will be able to run (the exact same name +/// has to be registered inside [init_module] phase. The second argument has to be a function +/// accepting reference [Memgraph]. The [Memgraph] object could be used to interact with Memgraph +/// instance. +/// +/// Example +/// +/// ```no run +/// define_procedure!(procedure_name, |memgraph: &Memgraph| -> Result<()> { +/// // Implementation +/// } +/// ``` +#[macro_export] +macro_rules! define_batch_procedure_init { + ($c_name:ident, $rs_func:expr) => { + #[no_mangle] + extern "C" fn $c_name(args: *mut mgp_list, graph: *mut mgp_graph, memory: *mut mgp_memory) { + let prev_hook = panic::take_hook(); + panic::set_hook(Box::new(|_| { /* Do nothing. */ })); + + let procedure_result = panic::catch_unwind(|| { + let memgraph = Memgraph::new( + args, + graph, + std::ptr::null_mut(), + memory, + std::ptr::null_mut(), + ); + match $rs_func(&memgraph) { + Ok(_) => (), + Err(e) => { + let msg = e.to_string(); + println!("{}", msg); + let c_msg = + CString::new(msg).expect("Unable to create Memgraph error message!"); + set_memgraph_error_msg(&c_msg, &memgraph); + } + } + }); + + panic::set_hook(prev_hook); + match procedure_result { + Ok(_) => {} + Err(e) => { + println!("Procedure panic!"); + let memgraph = Memgraph::new( + args, + graph, + std::ptr::null_mut(), + memory, + std::ptr::null_mut(), + ); + match e.downcast::<&str>() { + Ok(msg) => { + println!("{}", msg); + let c_msg = CString::new(msg.as_bytes()) + .expect("Unable to create Memgraph PANIC error message!"); + set_memgraph_error_msg(&c_msg, &memgraph); + } + Err(_) => { + println!("Unknown type of panic!."); + } + } + } + } + } + }; +} + +/// Defines a new procedure callable by Memgraph engine. +/// +/// Wraps call to the Rust function provided as a second argument. In addition to the function +/// call, it also sets the catch_unwind hook and properly handles execution errors. The first macro +/// argument is the exact name of the C procedure Memgraph will be able to run (the exact same name +/// has to be registered inside [init_module] phase. The second argument has to be a function +/// accepting reference [Memgraph]. The [Memgraph] object could be used to interact with Memgraph +/// instance. +/// +/// Example +/// +/// ```no run +/// define_procedure!(procedure_name, |memgraph: &Memgraph| -> Result<()> { +/// // Implementation +/// } +/// ``` +#[macro_export] +macro_rules! define_batch_procedure_cleanup { + ($c_name:ident, $rs_func:expr) => { + #[no_mangle] + extern "C" fn $c_name() { + let prev_hook = panic::take_hook(); + panic::set_hook(Box::new(|_| { /* Do nothing. */ })); + + let procedure_result = panic::catch_unwind(|| { + let memgraph = Memgraph::new( + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + ); + match $rs_func(&memgraph) { + Ok(_) => (), + Err(e) => { + let msg = e.to_string(); + println!("{}", msg); + let c_msg = + CString::new(msg).expect("Unable to create Memgraph error message!"); + set_memgraph_error_msg(&c_msg, &memgraph); + } + } + }); + + panic::set_hook(prev_hook); + match procedure_result { + Ok(_) => {} + Err(e) => { + println!("Procedure panic!"); + let memgraph = Memgraph::new( + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + ); + match e.downcast::<&str>() { + Ok(msg) => { + println!("{}", msg); + let c_msg = CString::new(msg.as_bytes()) + .expect("Unable to create Memgraph PANIC error message!"); + set_memgraph_error_msg(&c_msg, &memgraph); + } + Err(_) => { + println!("Unknown type of panic!."); + } + } + } + } + } + }; +} + /// Initializes Memgraph query module. /// /// Example From 9ca8fb727f25a01f3a4d426c1449d94a4796560b Mon Sep 17 00:00:00 2001 From: Memgraph Date: Sun, 13 Jul 2025 11:16:20 +0200 Subject: [PATCH 10/18] Add thread local to basic batched procedure --- rust/rsmgp-batch-example/src/lib.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/rust/rsmgp-batch-example/src/lib.rs b/rust/rsmgp-batch-example/src/lib.rs index a569f79d2..f5b4d95eb 100644 --- a/rust/rsmgp-batch-example/src/lib.rs +++ b/rust/rsmgp-batch-example/src/lib.rs @@ -1,5 +1,4 @@ use c_str_macro::c_str; -use rand::Rng; use rsmgp_sys::memgraph::*; use rsmgp_sys::mgp::*; use rsmgp_sys::result::*; @@ -12,6 +11,11 @@ use rsmgp_sys::{ use std::ffi::CString; use std::os::raw::c_int; use std::panic; +use std::cell::RefCell; + +thread_local! { + static COUNTER: RefCell = RefCell::new(0); +} init_module!(|memgraph: &Memgraph| -> Result<()> { memgraph.add_batch_read_procedure( @@ -29,15 +33,19 @@ init_module!(|memgraph: &Memgraph| -> Result<()> { define_procedure!(test_procedure, |memgraph: &Memgraph| -> Result<()> { println!("Entered test procedure!"); - let mut rng = rand::rng(); - let n = rng.random_range(1..=5); - println!("n: {}", n); - if n != 2 { + let value: i32 = COUNTER.with(|counter| { + let mut val = counter.borrow_mut(); + *val += 1; + *val + }); + + if value != 10 { let result = memgraph.result_record()?; - result.insert_mgp_value(c_str!("value"), &MgpValue::make_int(1, memgraph)?)?; + result.insert_mgp_value(c_str!("value"), &MgpValue::make_int(value as i64, memgraph)?)?; + std::thread::sleep(std::time::Duration::from_secs(1)); } - std::thread::sleep(std::time::Duration::from_secs(5)); - println!("Exiting test procedure!"); + + println!("Exiting test procedure! Value: {}", value); Ok(()) }); From 4988197ca571e22235a6fd7011048f9746a8e532 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Sun, 13 Jul 2025 11:29:00 +0200 Subject: [PATCH 11/18] Add thread local for batch example and change cursor to iterator --- rust/rsmgp-batch-example/src/lib.rs | 7 +++++-- rust/rsmgp-mysql/src/lib.rs | 32 ++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/rust/rsmgp-batch-example/src/lib.rs b/rust/rsmgp-batch-example/src/lib.rs index f5b4d95eb..228ca88e9 100644 --- a/rust/rsmgp-batch-example/src/lib.rs +++ b/rust/rsmgp-batch-example/src/lib.rs @@ -8,10 +8,10 @@ use rsmgp_sys::{ close_module, define_batch_procedure_cleanup, define_batch_procedure_init, define_procedure, define_type, init_module, }; +use std::cell::RefCell; use std::ffi::CString; use std::os::raw::c_int; use std::panic; -use std::cell::RefCell; thread_local! { static COUNTER: RefCell = RefCell::new(0); @@ -41,7 +41,10 @@ define_procedure!(test_procedure, |memgraph: &Memgraph| -> Result<()> { if value != 10 { let result = memgraph.result_record()?; - result.insert_mgp_value(c_str!("value"), &MgpValue::make_int(value as i64, memgraph)?)?; + result.insert_mgp_value( + c_str!("value"), + &MgpValue::make_int(value as i64, memgraph)?, + )?; std::thread::sleep(std::time::Duration::from_secs(1)); } diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index e008522cd..99181014f 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -1,5 +1,4 @@ use c_str_macro::c_str; -use chrono::{NaiveDate, NaiveDateTime}; use mysql::prelude::*; use mysql::*; use rsmgp_sys::map::*; @@ -10,7 +9,10 @@ use rsmgp_sys::result::Result; use rsmgp_sys::rsmgp::*; use rsmgp_sys::value::MgpValue; use rsmgp_sys::value::Value; -use rsmgp_sys::{close_module, define_optional_type, define_procedure, define_type, init_module}; +use rsmgp_sys::{ + close_module, define_batch_procedure_cleanup, define_batch_procedure_init, + define_optional_type, define_procedure, define_type, init_module, +}; use std::ffi::CString; use std::os::raw::c_int; use std::panic; @@ -66,9 +68,11 @@ fn query_is_table(table_or_sql: &str) -> bool { } init_module!(|memgraph: &Memgraph| -> Result<()> { - memgraph.add_read_procedure( + memgraph.add_batch_read_procedure( migrate, c_str!("migrate"), + init_procedure, + cleanup_procedure, &[define_type!("table_or_sql", Type::String)], &[ define_optional_type!( @@ -138,15 +142,22 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { return Err(Error::UnableToPrepareMySQLStatement); } }; - let query_result: Vec = match conn.exec(prepared_statement, ()) { - Ok(rows) => rows, + let result = match conn.exec_iter(prepared_statement, ()) { + Ok(result) => result, Err(e) => { println!("Error: {}", e); return Err(Error::UnableToExecuteMySQLQuery); } }; - for row in query_result.iter() { + for row_result in result { + let row = match row_result { + Ok(row) => row, + Err(e) => { + println!("Error fetching row: {}", e); + continue; // or return Err(Error::UnableToExecuteMySQLQuery); + } + }; let result = memgraph.result_record()?; let row_map = Map::make_empty(&memgraph)?; for column in row.columns_ref() { @@ -165,7 +176,6 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { mysql::Value::Float(f) => Value::Float(*f as f64), mysql::Value::Double(d) => Value::Float(*d as f64), mysql::Value::Bytes(b) => { - // Try to interpret as UTF-8 string, fallback to hex if not valid match String::from_utf8(b.clone()) { Ok(s) => { if let Ok(d) = s.parse::() { @@ -187,4 +197,12 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { Ok(()) }); +define_batch_procedure_init!(init_procedure, |_memgraph: &Memgraph| -> Result<()> { + Ok(()) +}); + +define_batch_procedure_cleanup!(cleanup_procedure, |_memgraph: &Memgraph| -> Result<()> { + Ok(()) +}); + close_module!(|| -> Result<()> { Ok(()) }); From cccb7849ae47d12f45f88d21505aaf6199cda398 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Sun, 13 Jul 2025 11:29:16 +0200 Subject: [PATCH 12/18] Change cursor to iterator --- rust/rsmgp-mysql/src/lib.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index 99181014f..d15f7811c 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -175,18 +175,16 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { } mysql::Value::Float(f) => Value::Float(*f as f64), mysql::Value::Double(d) => Value::Float(*d as f64), - mysql::Value::Bytes(b) => { - match String::from_utf8(b.clone()) { - Ok(s) => { - if let Ok(d) = s.parse::() { - Value::Float(d as f64) - } else { - Value::String(CString::new(s).unwrap()) - } + mysql::Value::Bytes(b) => match String::from_utf8(b.clone()) { + Ok(s) => { + if let Ok(d) = s.parse::() { + Value::Float(d as f64) + } else { + Value::String(CString::new(s).unwrap()) } - Err(_) => Value::String(CString::new(hex::encode(b)).unwrap()), } - } + Err(_) => Value::String(CString::new(hex::encode(b)).unwrap()), + }, _ => Value::Null, }; row_map.insert(col_name.as_c_str(), &mg_val)?; From f38da547a2630a5297a94e9a1c78b2c1e43aa5a3 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Sun, 13 Jul 2025 12:01:04 +0200 Subject: [PATCH 13/18] Add batched execution for mysql --- rust/rsmgp-mysql/src/lib.rs | 216 ++++++++++++++++++++++-------------- 1 file changed, 133 insertions(+), 83 deletions(-) diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index d15f7811c..d71254488 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -13,16 +13,48 @@ use rsmgp_sys::{ close_module, define_batch_procedure_cleanup, define_batch_procedure_init, define_optional_type, define_procedure, define_type, init_module, }; +use std::cell::RefCell; use std::ffi::CString; use std::os::raw::c_int; use std::panic; +thread_local! { + static MYSQL_BASE_QUERY: RefCell = RefCell::new("".to_string()); + static MYSQL_CONN: RefCell> = RefCell::new(None); + static MYSQL_OFFSET: RefCell = RefCell::new(0); +} + const AURORA_HOST: &str = "localhost"; const AURORA_PORT: i64 = 3306; const AURORA_USER: &str = "username"; const AURORA_PASSWORD: &str = "password"; const AURORA_DATABASE: &str = "database"; +init_module!(|memgraph: &Memgraph| -> Result<()> { + memgraph.add_batch_read_procedure( + migrate, + c_str!("migrate"), + init_migrate, + cleanup_migrate, + &[define_type!("table_or_sql", Type::String)], + &[ + define_optional_type!( + "config", + &MgpValue::make_map(&Map::make_empty(&memgraph)?, &memgraph)?, + Type::Map + ), + define_optional_type!( + "config_path", + &MgpValue::make_string(c_str!(""), &memgraph)?, + Type::String + ), + ], + &[define_type!("row", Type::Map)], + )?; + + Ok(()) +}); + fn get_aurora_url(config: &Map) -> String { use std::ffi::CString; @@ -67,32 +99,88 @@ fn query_is_table(table_or_sql: &str) -> bool { table_or_sql.split_whitespace().count() == 1 } -init_module!(|memgraph: &Memgraph| -> Result<()> { - memgraph.add_batch_read_procedure( - migrate, - c_str!("migrate"), - init_procedure, - cleanup_procedure, - &[define_type!("table_or_sql", Type::String)], - &[ - define_optional_type!( - "config", - &MgpValue::make_map(&Map::make_empty(&memgraph)?, &memgraph)?, - Type::Map - ), - define_optional_type!( - "config_path", - &MgpValue::make_string(c_str!(""), &memgraph)?, - Type::String - ), - ], - &[define_type!("row", Type::Map)], - )?; +define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { + let base_query = MYSQL_BASE_QUERY.with(|cell| cell.borrow().clone()); + let offset = MYSQL_OFFSET.with(|cell| *cell.borrow()); + let batch_size = 100_000; + let query = format!("{} LIMIT {} OFFSET {}", base_query, batch_size, offset); + + MYSQL_CONN.with(|conn_cell| { + let mut conn_opt = conn_cell.borrow_mut(); + let conn = match conn_opt.as_mut() { + Some(c) => c, + None => { + println!("No MySQL connection available"); + return Err(Error::UnableToGetMySQLConnection); + } + }; + let prepared_statement = match conn.prep(&query) { + Ok(stmt) => stmt, + Err(e) => { + println!("Error: {}", e); + return Err(Error::UnableToPrepareMySQLStatement); + } + }; + let result = match conn.exec_iter(prepared_statement, ()) { + Ok(result) => result, + Err(e) => { + println!("Error: {}", e); + return Err(Error::UnableToExecuteMySQLQuery); + } + }; + + for row_result in result { + let row = match row_result { + Ok(row) => row, + Err(e) => { + println!("Error fetching row: {}", e); + continue; + } + }; + let result = memgraph.result_record()?; + let row_map = Map::make_empty(&memgraph)?; + for column in row.columns_ref() { + let col_name = CString::new(column.name_str().as_bytes()).unwrap(); + let column_value = &row[column.name_str().as_ref()]; + let mg_val = match column_value { + mysql::Value::NULL => Value::Null, + mysql::Value::Int(i) => Value::Int(*i), + mysql::Value::UInt(u) => { + if *u <= i64::MAX as u64 { + Value::Int(*u as i64) + } else { + Value::String(CString::new(u.to_string()).unwrap()) + } + } + mysql::Value::Float(f) => Value::Float(*f as f64), + mysql::Value::Double(d) => Value::Float(*d as f64), + mysql::Value::Bytes(b) => match String::from_utf8(b.clone()) { + Ok(s) => { + if let Ok(d) = s.parse::() { + Value::Float(d as f64) + } else { + Value::String(CString::new(s).unwrap()) + } + } + Err(_) => Value::String(CString::new(hex::encode(b)).unwrap()), + }, + _ => Value::Null, + }; + row_map.insert(col_name.as_c_str(), &mg_val)?; + } + result.insert_map(c_str!("row"), &row_map)?; + } + Ok(()) + })?; + + MYSQL_OFFSET.with(|cell| { + *cell.borrow_mut() += batch_size; + }); Ok(()) }); -define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { +define_batch_procedure_init!(init_migrate, |memgraph: &Memgraph| -> Result<()> { let args = memgraph.args()?; let query = match args.value_at(0)? { Value::String(ref cstr) => { @@ -127,79 +215,41 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { return Err(Error::UnableToCreateMySQLPool); } }; - - let mut conn: PooledConn = match pool.get_conn() { + let conn: PooledConn = match pool.get_conn() { Ok(conn) => conn, Err(e) => { println!("Error: {}", e); return Err(Error::UnableToGetMySQLConnection); } }; - let prepared_statement = match conn.prep(&query) { - Ok(stmt) => stmt, - Err(e) => { - println!("Error: {}", e); - return Err(Error::UnableToPrepareMySQLStatement); - } - }; - let result = match conn.exec_iter(prepared_statement, ()) { - Ok(result) => result, - Err(e) => { - println!("Error: {}", e); - return Err(Error::UnableToExecuteMySQLQuery); - } - }; - for row_result in result { - let row = match row_result { - Ok(row) => row, - Err(e) => { - println!("Error fetching row: {}", e); - continue; // or return Err(Error::UnableToExecuteMySQLQuery); - } - }; - let result = memgraph.result_record()?; - let row_map = Map::make_empty(&memgraph)?; - for column in row.columns_ref() { - let col_name = CString::new(column.name_str().as_bytes()).unwrap(); - let column_value = &row[column.name_str().as_ref()]; - let mg_val = match column_value { - mysql::Value::NULL => Value::Null, - mysql::Value::Int(i) => Value::Int(*i), - mysql::Value::UInt(u) => { - if *u <= i64::MAX as u64 { - Value::Int(*u as i64) - } else { - Value::String(CString::new(u.to_string()).unwrap()) - } - } - mysql::Value::Float(f) => Value::Float(*f as f64), - mysql::Value::Double(d) => Value::Float(*d as f64), - mysql::Value::Bytes(b) => match String::from_utf8(b.clone()) { - Ok(s) => { - if let Ok(d) = s.parse::() { - Value::Float(d as f64) - } else { - Value::String(CString::new(s).unwrap()) - } - } - Err(_) => Value::String(CString::new(hex::encode(b)).unwrap()), - }, - _ => Value::Null, - }; - row_map.insert(col_name.as_c_str(), &mg_val)?; - } - result.insert_map(c_str!("row"), &row_map)?; - } + // Store the connection in thread-local + MYSQL_CONN.with(|conn_cell| { + *conn_cell.borrow_mut() = Some(conn); + }); + // Store the base query in thread-local + MYSQL_BASE_QUERY.with(|base_query| { + *base_query.borrow_mut() = query; + }); + // Reset the offset counter to 0 + MYSQL_OFFSET.with(|counter| { + *counter.borrow_mut() = 0; + }); Ok(()) }); -define_batch_procedure_init!(init_procedure, |_memgraph: &Memgraph| -> Result<()> { - Ok(()) -}); +define_batch_procedure_cleanup!(cleanup_migrate, |_memgraph: &Memgraph| -> Result<()> { + MYSQL_CONN.with(|conn_cell| { + *conn_cell.borrow_mut() = None; + }); + MYSQL_BASE_QUERY.with(|base_query| { + *base_query.borrow_mut() = String::new(); + }); + MYSQL_OFFSET.with(|counter| { + *counter.borrow_mut() = 0; + }); -define_batch_procedure_cleanup!(cleanup_procedure, |_memgraph: &Memgraph| -> Result<()> { Ok(()) }); From bd6e239beb64682f1e9d049fa84ab652afc814f9 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Sun, 13 Jul 2025 12:25:08 +0200 Subject: [PATCH 14/18] Add test connection function --- rust/rsmgp-mysql/src/lib.rs | 282 +++++++++++++++++++++--------------- 1 file changed, 168 insertions(+), 114 deletions(-) diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index d71254488..bac0c388b 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -31,6 +31,25 @@ const AURORA_PASSWORD: &str = "password"; const AURORA_DATABASE: &str = "database"; init_module!(|memgraph: &Memgraph| -> Result<()> { + memgraph.add_read_procedure( + migrate, + c_str!("test_connection"), + &[], + &[ + define_optional_type!( + "config", + &MgpValue::make_map(&Map::make_empty(&memgraph)?, &memgraph)?, + Type::Map + ), + define_optional_type!( + "config_path", + &MgpValue::make_string(c_str!(""), &memgraph)?, + Type::String + ), + ], + &[define_type!("success", Type::Bool)], + )?; + memgraph.add_batch_read_procedure( migrate, c_str!("migrate"), @@ -55,49 +74,66 @@ init_module!(|memgraph: &Memgraph| -> Result<()> { Ok(()) }); -fn get_aurora_url(config: &Map) -> String { - use std::ffi::CString; +define_procedure!(test_connection, |memgraph: &Memgraph| -> Result<()> { + let args = memgraph.args()?; + let config_arg = args.value_at(1)?; + let config = match config_arg { + Value::Map(ref map) => map, + _ => panic!("Expected Map value in place of config parameter!"), + }; - // Helper to extract a string from the map, or fallback - fn get_str(config: &Map, key: &str, default: &str) -> String { - let ckey = CString::new(key).unwrap(); - match config.at(&ckey) { - Ok(Value::String(s)) => s - .to_str() - .ok() - .map(|s| s.to_string()) - .unwrap_or_else(|| default.to_string()), - _ => default.to_string(), + let url = get_aurora_url(config); + let mut conn: PooledConn = get_connection(&url)?; + + let _: Vec = match conn.exec("SELECT 1 as result".to_string(), ()) { + Ok(result) => result, + Err(e) => { + println!("Error: {}", e); + return Err(Error::UnableToExecuteMySQLQuery); } - } - // Helper to extract a u16 from the map, or fallback - fn get_port(config: &Map, key: &str, default: i64) -> i64 { - let ckey = CString::new(key).unwrap(); - match config.at(&ckey) { - Ok(Value::Int(i)) => i, - _ => default, + }; + + Ok(()) +}); + +define_batch_procedure_init!(init_migrate, |memgraph: &Memgraph| -> Result<()> { + let args = memgraph.args()?; + let query = match args.value_at(0)? { + Value::String(ref cstr) => { + let s = cstr.to_str().expect("CString is not valid UTF-8"); + if query_is_table(s) { + format!("SELECT * FROM `{}`;", s) + } else { + s.to_string() + } } - } + _ => panic!("Expected String value in place of sql_or_table parameter!"), + }; - let user = get_str(config, "user", AURORA_USER); - let pass = get_str(config, "password", AURORA_PASSWORD); - let host = get_str(config, "host", AURORA_HOST); - let db = get_str(config, "database", AURORA_DATABASE); - let port = get_port(config, "port", AURORA_PORT); + let config_arg = args.value_at(1)?; + let config = match config_arg { + Value::Map(ref map) => map, + _ => panic!("Expected Map value in place of config parameter!"), + }; - format!( - "mysql://{user}:{pass}@{host}:{port}/{db}", - user = user, - pass = pass, - host = host, - port = port, - db = db - ) -} + let url = get_aurora_url(config); + let conn: PooledConn = get_connection(&url)?; -fn query_is_table(table_or_sql: &str) -> bool { - table_or_sql.split_whitespace().count() == 1 -} + // Store the connection in thread-local + MYSQL_CONN.with(|conn_cell| { + *conn_cell.borrow_mut() = Some(conn); + }); + // Store the base query in thread-local + MYSQL_BASE_QUERY.with(|base_query| { + *base_query.borrow_mut() = query; + }); + // Reset the offset counter to 0 + MYSQL_OFFSET.with(|counter| { + *counter.borrow_mut() = 0; + }); + + Ok(()) +}); define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { let base_query = MYSQL_BASE_QUERY.with(|cell| cell.borrow().clone()); @@ -137,38 +173,8 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { continue; } }; - let result = memgraph.result_record()?; - let row_map = Map::make_empty(&memgraph)?; - for column in row.columns_ref() { - let col_name = CString::new(column.name_str().as_bytes()).unwrap(); - let column_value = &row[column.name_str().as_ref()]; - let mg_val = match column_value { - mysql::Value::NULL => Value::Null, - mysql::Value::Int(i) => Value::Int(*i), - mysql::Value::UInt(u) => { - if *u <= i64::MAX as u64 { - Value::Int(*u as i64) - } else { - Value::String(CString::new(u.to_string()).unwrap()) - } - } - mysql::Value::Float(f) => Value::Float(*f as f64), - mysql::Value::Double(d) => Value::Float(*d as f64), - mysql::Value::Bytes(b) => match String::from_utf8(b.clone()) { - Ok(s) => { - if let Ok(d) = s.parse::() { - Value::Float(d as f64) - } else { - Value::String(CString::new(s).unwrap()) - } - } - Err(_) => Value::String(CString::new(hex::encode(b)).unwrap()), - }, - _ => Value::Null, - }; - row_map.insert(col_name.as_c_str(), &mg_val)?; - } - result.insert_map(c_str!("row"), &row_map)?; + + insert_row_into_memgraph(&row, &memgraph)?; } Ok(()) })?; @@ -180,27 +186,63 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { Ok(()) }); -define_batch_procedure_init!(init_migrate, |memgraph: &Memgraph| -> Result<()> { - let args = memgraph.args()?; - let query = match args.value_at(0)? { - Value::String(ref cstr) => { - let s = cstr.to_str().expect("CString is not valid UTF-8"); - if query_is_table(s) { - format!("SELECT * FROM `{}`;", s) - } else { - s.to_string() - } +define_batch_procedure_cleanup!(cleanup_migrate, |_memgraph: &Memgraph| -> Result<()> { + MYSQL_CONN.with(|conn_cell| { + *conn_cell.borrow_mut() = None; + }); + MYSQL_BASE_QUERY.with(|base_query| { + *base_query.borrow_mut() = String::new(); + }); + MYSQL_OFFSET.with(|counter| { + *counter.borrow_mut() = 0; + }); + + Ok(()) +}); + +close_module!(|| -> Result<()> { Ok(()) }); + +fn get_aurora_url(config: &Map) -> String { + use std::ffi::CString; + + // Helper to extract a string from the map, or fallback + fn get_str(config: &Map, key: &str, default: &str) -> String { + let ckey = CString::new(key).unwrap(); + match config.at(&ckey) { + Ok(Value::String(s)) => s + .to_str() + .ok() + .map(|s| s.to_string()) + .unwrap_or_else(|| default.to_string()), + _ => default.to_string(), } - _ => panic!("Expected String value in place of sql_or_table parameter!"), - }; + } + // Helper to extract a u16 from the map, or fallback + fn get_port(config: &Map, key: &str, default: i64) -> i64 { + let ckey = CString::new(key).unwrap(); + match config.at(&ckey) { + Ok(Value::Int(i)) => i, + _ => default, + } + } - let config_arg = args.value_at(1)?; - let config = match config_arg { - Value::Map(ref map) => map, - _ => panic!("Expected Map value in place of config parameter!"), - }; + let user = get_str(config, "user", AURORA_USER); + let pass = get_str(config, "password", AURORA_PASSWORD); + let host = get_str(config, "host", AURORA_HOST); + let db = get_str(config, "database", AURORA_DATABASE); + let port = get_port(config, "port", AURORA_PORT); - let url = get_aurora_url(config); + format!( + "mysql://{user}:{pass}@{host}:{port}/{db}", + user = user, + pass = pass, + host = host, + port = port, + db = db + ) +} + +fn get_connection(url: &str) -> Result { let opts: Opts = match Opts::from_url(&url) { Ok(opts) => opts, Err(e) => { @@ -223,34 +265,46 @@ define_batch_procedure_init!(init_migrate, |memgraph: &Memgraph| -> Result<()> { } }; - // Store the connection in thread-local - MYSQL_CONN.with(|conn_cell| { - *conn_cell.borrow_mut() = Some(conn); - }); - // Store the base query in thread-local - MYSQL_BASE_QUERY.with(|base_query| { - *base_query.borrow_mut() = query; - }); - // Reset the offset counter to 0 - MYSQL_OFFSET.with(|counter| { - *counter.borrow_mut() = 0; - }); + Ok(conn) +} - Ok(()) -}); +fn query_is_table(table_or_sql: &str) -> bool { + table_or_sql.split_whitespace().count() == 1 +} -define_batch_procedure_cleanup!(cleanup_migrate, |_memgraph: &Memgraph| -> Result<()> { - MYSQL_CONN.with(|conn_cell| { - *conn_cell.borrow_mut() = None; - }); - MYSQL_BASE_QUERY.with(|base_query| { - *base_query.borrow_mut() = String::new(); - }); - MYSQL_OFFSET.with(|counter| { - *counter.borrow_mut() = 0; - }); +fn insert_row_into_memgraph(row: &Row, memgraph: &Memgraph) -> Result<()> { + let result = memgraph.result_record()?; + let row_map = Map::make_empty(&memgraph)?; + for column in row.columns_ref() { + let col_name = CString::new(column.name_str().as_bytes()).unwrap(); + let column_value = &row[column.name_str().as_ref()]; + let mg_val = match column_value { + mysql::Value::NULL => Value::Null, + mysql::Value::Int(i) => Value::Int(*i), + mysql::Value::UInt(u) => { + if *u <= i64::MAX as u64 { + Value::Int(*u as i64) + } else { + Value::String(CString::new(u.to_string()).unwrap()) + } + } + mysql::Value::Float(f) => Value::Float(*f as f64), + mysql::Value::Double(d) => Value::Float(*d as f64), + mysql::Value::Bytes(b) => match String::from_utf8(b.clone()) { + Ok(s) => { + if let Ok(d) = s.parse::() { + Value::Float(d as f64) + } else { + Value::String(CString::new(s).unwrap()) + } + } + Err(_) => Value::String(CString::new(hex::encode(b)).unwrap()), + }, + _ => Value::Null, + }; + row_map.insert(col_name.as_c_str(), &mg_val)?; + } + result.insert_map(c_str!("row"), &row_map)?; Ok(()) -}); - -close_module!(|| -> Result<()> { Ok(()) }); +} \ No newline at end of file From a45fa38014c508cf483ba93966a9baa64960ec37 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Sun, 13 Jul 2025 12:25:14 +0200 Subject: [PATCH 15/18] Add test connection function --- rust/rsmgp-mysql/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index bac0c388b..1f18d6d23 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -307,4 +307,4 @@ fn insert_row_into_memgraph(row: &Row, memgraph: &Memgraph) -> Result<()> { result.insert_map(c_str!("row"), &row_map)?; Ok(()) -} \ No newline at end of file +} From 70b4c49ee84674e10971bc65a07fb176479cbf94 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Sun, 13 Jul 2025 12:40:46 +0200 Subject: [PATCH 16/18] Add show tables --- rust/rsmgp-mysql/src/lib.rs | 61 +++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index 1f18d6d23..751d8846e 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -32,7 +32,7 @@ const AURORA_DATABASE: &str = "database"; init_module!(|memgraph: &Memgraph| -> Result<()> { memgraph.add_read_procedure( - migrate, + test_connection, c_str!("test_connection"), &[], &[ @@ -50,6 +50,25 @@ init_module!(|memgraph: &Memgraph| -> Result<()> { &[define_type!("success", Type::Bool)], )?; + memgraph.add_read_procedure( + show_tables, + c_str!("show_tables"), + &[], + &[ + define_optional_type!( + "config", + &MgpValue::make_map(&Map::make_empty(&memgraph)?, &memgraph)?, + Type::Map + ), + define_optional_type!( + "config_path", + &MgpValue::make_string(c_str!(""), &memgraph)?, + Type::String + ), + ], + &[define_type!("table_name", Type::String)], + )?; + memgraph.add_batch_read_procedure( migrate, c_str!("migrate"), @@ -76,7 +95,32 @@ init_module!(|memgraph: &Memgraph| -> Result<()> { define_procedure!(test_connection, |memgraph: &Memgraph| -> Result<()> { let args = memgraph.args()?; - let config_arg = args.value_at(1)?; + let config_arg = args.value_at(0)?; + let config = match config_arg { + Value::Map(ref map) => map, + _ => panic!("Expected Map value in place of config parameter!"), + }; + + let url = get_aurora_url(config); + let mut conn: PooledConn = get_connection(&url)?; + + let _: Vec = match conn.exec("SELECT 1 AS result".to_string(), ()) { + Ok(result) => result, + Err(e) => { + println!("Error: {}", e); + return Err(Error::UnableToExecuteMySQLQuery); + } + }; + + let result = memgraph.result_record()?; + result.insert_bool(c_str!("success"), true)?; + + Ok(()) +}); + +define_procedure!(show_tables, |memgraph: &Memgraph| -> Result<()> { + let args = memgraph.args()?; + let config_arg = args.value_at(0)?; let config = match config_arg { Value::Map(ref map) => map, _ => panic!("Expected Map value in place of config parameter!"), @@ -85,7 +129,7 @@ define_procedure!(test_connection, |memgraph: &Memgraph| -> Result<()> { let url = get_aurora_url(config); let mut conn: PooledConn = get_connection(&url)?; - let _: Vec = match conn.exec("SELECT 1 as result".to_string(), ()) { + let rows: Vec = match conn.exec("SHOW TABLES".to_string(), ()) { Ok(result) => result, Err(e) => { println!("Error: {}", e); @@ -93,6 +137,17 @@ define_procedure!(test_connection, |memgraph: &Memgraph| -> Result<()> { } }; + for row in rows { + // The table name is the first column in the row + let table_name: String = row.get(0).and_then(|val| match val { + mysql::Value::Bytes(bytes) => String::from_utf8(bytes.clone()).ok(), + _ => None, + }).unwrap_or_else(|| "".to_string()); + + let result = memgraph.result_record()?; + result.insert_string(c_str!("table_name"), &CString::new(table_name).unwrap())?; + } + Ok(()) }); From 29c08c270b6edf96e35f2cfb6d161d1e9c90e997 Mon Sep 17 00:00:00 2001 From: Memgraph Date: Sun, 13 Jul 2025 12:40:52 +0200 Subject: [PATCH 17/18] Add show tables --- rust/rsmgp-mysql/src/lib.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index 751d8846e..158507313 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -139,10 +139,13 @@ define_procedure!(show_tables, |memgraph: &Memgraph| -> Result<()> { for row in rows { // The table name is the first column in the row - let table_name: String = row.get(0).and_then(|val| match val { - mysql::Value::Bytes(bytes) => String::from_utf8(bytes.clone()).ok(), - _ => None, - }).unwrap_or_else(|| "".to_string()); + let table_name: String = row + .get(0) + .and_then(|val| match val { + mysql::Value::Bytes(bytes) => String::from_utf8(bytes.clone()).ok(), + _ => None, + }) + .unwrap_or_else(|| "".to_string()); let result = memgraph.result_record()?; result.insert_string(c_str!("table_name"), &CString::new(table_name).unwrap())?; From 53af83fde6d8d0ed35916d4d9aff3a2a9ee57e7c Mon Sep 17 00:00:00 2001 From: Memgraph Date: Sun, 13 Jul 2025 15:55:53 +0200 Subject: [PATCH 18/18] Remove prefix lib --- rust/rsmgp-mysql/Cargo.toml | 3 +- rust/rsmgp-mysql/src/lib.rs | 150 ++++++++++++++++++++++++++++++++++-- 2 files changed, 145 insertions(+), 8 deletions(-) diff --git a/rust/rsmgp-mysql/Cargo.toml b/rust/rsmgp-mysql/Cargo.toml index c5749fcf3..af7024b32 100644 --- a/rust/rsmgp-mysql/Cargo.toml +++ b/rust/rsmgp-mysql/Cargo.toml @@ -12,5 +12,6 @@ hex = "0.4.3" chrono = { version = "0.4.41", features = ["serde"] } [lib] -name = "rsmgp_mysql" +name = "mysql" +libprefix = "" crate-type = ["cdylib"] diff --git a/rust/rsmgp-mysql/src/lib.rs b/rust/rsmgp-mysql/src/lib.rs index 158507313..9172b27c0 100644 --- a/rust/rsmgp-mysql/src/lib.rs +++ b/rust/rsmgp-mysql/src/lib.rs @@ -69,11 +69,49 @@ init_module!(|memgraph: &Memgraph| -> Result<()> { &[define_type!("table_name", Type::String)], )?; + memgraph.add_read_procedure( + describe_table, + c_str!("describe_table"), + &[define_type!("table", Type::String)], + &[ + define_optional_type!( + "config", + &MgpValue::make_map(&Map::make_empty(&memgraph)?, &memgraph)?, + Type::Map + ), + define_optional_type!( + "config_path", + &MgpValue::make_string(c_str!(""), &memgraph)?, + Type::String + ), + ], + &[define_type!("row", Type::Map)], + )?; + + memgraph.add_read_procedure( + execute, + c_str!("execute"), + &[define_type!("table_or_sql", Type::String)], + &[ + define_optional_type!( + "config", + &MgpValue::make_map(&Map::make_empty(&memgraph)?, &memgraph)?, + Type::Map + ), + define_optional_type!( + "config_path", + &MgpValue::make_string(c_str!(""), &memgraph)?, + Type::String + ), + ], + &[define_type!("row", Type::Map)], + )?; + memgraph.add_batch_read_procedure( - migrate, - c_str!("migrate"), - init_migrate, - cleanup_migrate, + batch, + c_str!("batch"), + init_batch, + cleanup_batch, &[define_type!("table_or_sql", Type::String)], &[ define_optional_type!( @@ -154,7 +192,105 @@ define_procedure!(show_tables, |memgraph: &Memgraph| -> Result<()> { Ok(()) }); -define_batch_procedure_init!(init_migrate, |memgraph: &Memgraph| -> Result<()> { +define_procedure!(describe_table, |memgraph: &Memgraph| -> Result<()> { + let args = memgraph.args()?; + let query = match args.value_at(0)? { + Value::String(ref cstr) => format!("DESCRIBE `{}`", cstr.to_str().unwrap()), + _ => panic!("Expected String value in place of table parameter!"), + }; + + let config_arg = args.value_at(1)?; + let config = match config_arg { + Value::Map(ref map) => map, + _ => panic!("Expected Map value in place of config parameter!"), + }; + + let url = get_aurora_url(config); + let mut conn: PooledConn = get_connection(&url)?; + + let prepared_statement = match conn.prep(&query) { + Ok(stmt) => stmt, + Err(e) => { + println!("Error: {}", e); + return Err(Error::UnableToPrepareMySQLStatement); + } + }; + let result = match conn.exec_iter(prepared_statement, ()) { + Ok(result) => result, + Err(e) => { + println!("Error: {}", e); + return Err(Error::UnableToExecuteMySQLQuery); + } + }; + + for row_result in result { + let row = match row_result { + Ok(row) => row, + Err(e) => { + println!("Error fetching row: {}", e); + continue; + } + }; + + insert_row_into_memgraph(&row, &memgraph)?; + } + + Ok(()) +}); + +define_procedure!(execute, |memgraph: &Memgraph| -> Result<()> { + let args = memgraph.args()?; + let query = match args.value_at(0)? { + Value::String(ref cstr) => { + let s = cstr.to_str().expect("CString is not valid UTF-8"); + if query_is_table(s) { + format!("SELECT * FROM `{}`;", s) + } else { + s.to_string() + } + } + _ => panic!("Expected String value in place of sql_or_table parameter!"), + }; + let config_arg = args.value_at(1)?; + let config = match config_arg { + Value::Map(ref map) => map, + _ => panic!("Expected Map value in place of config parameter!"), + }; + + let url = get_aurora_url(config); + let mut conn: PooledConn = get_connection(&url)?; + + let prepared_statement = match conn.prep(&query) { + Ok(stmt) => stmt, + Err(e) => { + println!("Error: {}", e); + return Err(Error::UnableToPrepareMySQLStatement); + } + }; + let result = match conn.exec_iter(prepared_statement, ()) { + Ok(result) => result, + Err(e) => { + println!("Error: {}", e); + return Err(Error::UnableToExecuteMySQLQuery); + } + }; + + for row_result in result { + let row = match row_result { + Ok(row) => row, + Err(e) => { + println!("Error fetching row: {}", e); + continue; + } + }; + + insert_row_into_memgraph(&row, &memgraph)?; + } + + Ok(()) +}); + +define_batch_procedure_init!(init_batch, |memgraph: &Memgraph| -> Result<()> { let args = memgraph.args()?; let query = match args.value_at(0)? { Value::String(ref cstr) => { @@ -193,7 +329,7 @@ define_batch_procedure_init!(init_migrate, |memgraph: &Memgraph| -> Result<()> { Ok(()) }); -define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { +define_procedure!(batch, |memgraph: &Memgraph| -> Result<()> { let base_query = MYSQL_BASE_QUERY.with(|cell| cell.borrow().clone()); let offset = MYSQL_OFFSET.with(|cell| *cell.borrow()); let batch_size = 100_000; @@ -244,7 +380,7 @@ define_procedure!(migrate, |memgraph: &Memgraph| -> Result<()> { Ok(()) }); -define_batch_procedure_cleanup!(cleanup_migrate, |_memgraph: &Memgraph| -> Result<()> { +define_batch_procedure_cleanup!(cleanup_batch, |_memgraph: &Memgraph| -> Result<()> { MYSQL_CONN.with(|conn_cell| { *conn_cell.borrow_mut() = None; });