From 5f6770a2f5e099d39ee402b523a390f768dae5c8 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Fri, 27 Jun 2025 16:07:09 +0100 Subject: [PATCH 1/4] Support cluster-based driver updates --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- dependencies/typedb/repositories.bzl | 10 ++++++++-- src/constants.rs | 2 +- src/main.rs | 6 +++--- src/operations.rs | 4 ++-- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0892c88..c6439f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2456,7 +2456,7 @@ dependencies = [ [[package]] name = "typedb-driver" version = "0.0.0" -source = "git+https://github.com/typedb/typedb-driver?rev=3b171274a42751376b2a480baa40315ebbf80fce#3b171274a42751376b2a480baa40315ebbf80fce" +source = "git+https://github.com/typedb/typedb-driver?rev=59547fa99650030c449697b74fbc30ce63264104#59547fa99650030c449697b74fbc30ce63264104" dependencies = [ "chrono", "chrono-tz", @@ -2478,7 +2478,7 @@ dependencies = [ [[package]] name = "typedb-protocol" version = "0.0.0" -source = "git+https://github.com/typedb/typedb-protocol?rev=38f66a1cc4db3b7a301676f50800e9530ac5c8a3#38f66a1cc4db3b7a301676f50800e9530ac5c8a3" +source = "git+https://github.com/typedb/typedb-protocol?rev=ab1e84f24e861437d00bbb6082ae282e50860763#ab1e84f24e861437d00bbb6082ae282e50860763" dependencies = [ "prost", "tonic", diff --git a/Cargo.toml b/Cargo.toml index 8c5bf21..c57d233 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,8 +46,8 @@ features = {} [dependencies.typedb-driver] features = [] + rev = "59547fa99650030c449697b74fbc30ce63264104" git = "https://github.com/typedb/typedb-driver" - tag = "3.4.0" default-features = false [dependencies.futures] diff --git a/dependencies/typedb/repositories.bzl b/dependencies/typedb/repositories.bzl index c98c0a9..91ec6a7 100644 --- a/dependencies/typedb/repositories.bzl +++ b/dependencies/typedb/repositories.bzl @@ -12,11 +12,17 @@ def typedb_dependencies(): ) def typedb_driver(): + # TODO: Return typedb git_repository( name = "typedb_driver", - remote = "https://github.com/typedb/typedb-driver", - tag = "3.4.0", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_driver + remote = "https://github.com/farost/typedb-driver", + commit = "59547fa99650030c449697b74fbc30ce63264104", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_driver ) +# git_repository( +# name = "typedb_driver", +# remote = "https://github.com/typedb/typedb-driver", +# tag = "3.4.0", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_driver +# ) def typeql(): git_repository( diff --git a/src/constants.rs b/src/constants.rs index 8abbb18..63ecdc4 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -14,4 +14,4 @@ pub mod common { pub const SECONDS_IN_HOUR: u64 = SECONDS_IN_MINUTE * MINUTES_IN_HOUR; } -pub const DEFAULT_TRANSACTION_TIMEOUT: Duration = Duration::from_secs(1 * SECONDS_IN_HOUR); +pub const DEFAULT_TRANSACTION_TIMEOUT: Duration = Duration::from_secs(10); diff --git a/src/main.rs b/src/main.rs index a5ee07b..b221d1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ use clap::Parser; use home::home_dir; use rustyline::error::ReadlineError; use sentry::ClientOptions; -use typedb_driver::{Credentials, DriverOptions, Transaction, TransactionType, TypeDBDriver}; +use typedb_driver::{Addresses, Credentials, DriverOptions, Transaction, TransactionType, TypeDBDriver}; use crate::{ cli::Args, @@ -114,9 +114,9 @@ fn main() { let runtime = BackgroundRuntime::new(); let tls_root_ca_path = args.tls_root_ca.as_ref().map(|value| Path::new(value)); let driver = match runtime.run(TypeDBDriver::new( - args.address, + Addresses::try_from_address_str(args.address).unwrap(), Credentials::new(&args.username, args.password.as_ref().unwrap()), - DriverOptions::new(!args.tls_disabled, tls_root_ca_path).unwrap(), + DriverOptions::new().is_tls_enabled(!args.tls_disabled).tls_root_ca(tls_root_ca_path).unwrap(), )) { Ok(driver) => Arc::new(driver), Err(err) => { diff --git a/src/operations.rs b/src/operations.rs index 3fc9710..cd07f9a 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -117,7 +117,7 @@ pub(crate) fn user_list(context: &mut ConsoleContext, _input: &[String]) -> Comm println!("No users are present."); } else { for user in users { - println!("{}", user.name); + println!("{}", user.name()); } } Ok(()) @@ -175,7 +175,7 @@ pub(crate) fn user_update_password(context: &mut ConsoleContext, input: &[String .expect("Could not fetch currently logged in user."); user.update_password(new_password).await.map_err(|err| Box::new(err) as Box)?; - Ok(current_user.name == username) + Ok(current_user.name() == username) })?; if updated_current_user { println!("Successfully updated current user's password, exiting console. Please log in with the updated credentials."); From f4b59c49746704f3612e7e842682c567720fe7f4 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Fri, 27 Jun 2025 16:19:17 +0100 Subject: [PATCH 2/4] Roll back default tx timeout --- src/constants.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.rs b/src/constants.rs index 63ecdc4..8abbb18 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -14,4 +14,4 @@ pub mod common { pub const SECONDS_IN_HOUR: u64 = SECONDS_IN_MINUTE * MINUTES_IN_HOUR; } -pub const DEFAULT_TRANSACTION_TIMEOUT: Duration = Duration::from_secs(10); +pub const DEFAULT_TRANSACTION_TIMEOUT: Duration = Duration::from_secs(1 * SECONDS_IN_HOUR); From 1c72ac01e0de777b2a49cf09fb8006c2cccf4bdd Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Fri, 4 Jul 2025 11:33:14 +0100 Subject: [PATCH 3/4] Introduce multiple addresses for connection --- Cargo.lock | 4 +-- Cargo.toml | 2 +- dependencies/typedb/repositories.bzl | 6 +++- src/cli.rs | 28 ++++++++++++++--- src/main.rs | 46 +++++++++++++++++++++++++--- 5 files changed, 72 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6439f6..635f091 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2456,7 +2456,7 @@ dependencies = [ [[package]] name = "typedb-driver" version = "0.0.0" -source = "git+https://github.com/typedb/typedb-driver?rev=59547fa99650030c449697b74fbc30ce63264104#59547fa99650030c449697b74fbc30ce63264104" +source = "git+https://github.com/typedb/typedb-driver?rev=f1bf3d9e327344fe7a67084c13da4eb0fb5ca2be#f1bf3d9e327344fe7a67084c13da4eb0fb5ca2be" dependencies = [ "chrono", "chrono-tz", @@ -2478,7 +2478,7 @@ dependencies = [ [[package]] name = "typedb-protocol" version = "0.0.0" -source = "git+https://github.com/typedb/typedb-protocol?rev=ab1e84f24e861437d00bbb6082ae282e50860763#ab1e84f24e861437d00bbb6082ae282e50860763" +source = "git+https://github.com/typedb/typedb-protocol?rev=d1b71067c7e78364c69701625ea3f6d6b1f24a9d#d1b71067c7e78364c69701625ea3f6d6b1f24a9d" dependencies = [ "prost", "tonic", diff --git a/Cargo.toml b/Cargo.toml index c57d233..2aea831 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ features = {} [dependencies.typedb-driver] features = [] - rev = "59547fa99650030c449697b74fbc30ce63264104" + rev = "f1bf3d9e327344fe7a67084c13da4eb0fb5ca2be" git = "https://github.com/typedb/typedb-driver" default-features = false diff --git a/dependencies/typedb/repositories.bzl b/dependencies/typedb/repositories.bzl index 91ec6a7..bcf1c66 100644 --- a/dependencies/typedb/repositories.bzl +++ b/dependencies/typedb/repositories.bzl @@ -13,10 +13,14 @@ def typedb_dependencies(): def typedb_driver(): # TODO: Return typedb +# native.local_repository( +# name = "typedb_driver", +# path = "../typedb-driver", +# ) git_repository( name = "typedb_driver", remote = "https://github.com/farost/typedb-driver", - commit = "59547fa99650030c449697b74fbc30ce63264104", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_driver + commit = "f1bf3d9e327344fe7a67084c13da4eb0fb5ca2be", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_driver ) # git_repository( # name = "typedb_driver", diff --git a/src/cli.rs b/src/cli.rs index ecc2769..7321755 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -21,9 +21,27 @@ pub struct Args { #[arg(long, value_name = "path to script file")] pub script: Vec, - /// TypeDB address to connect to. If using TLS encryption, this must start with "https://" - #[arg(long, value_name = "host:port")] - pub address: String, + /// TypeDB address to connect to (host:port). If using TLS encryption, this must start with "https://". + #[arg(long, value_name = "host:port", conflicts_with_all = ["addresses", "address_translation"])] + pub address: Option, + + /// A comma-separated list of TypeDB replica addresses of a single cluster to connect to. + #[arg(long, value_name = "host1:port1,host2:port2", conflicts_with_all = ["address", "address_translation"])] + pub addresses: Option, + + /// A comma-separated list of public=private address pairs. Public addresses are the user-facing + /// addresses of the replicas, and private addresses are the addresses used for the server-side + /// connection between replicas. + #[arg(long, value_name = "public=private,...", conflicts_with_all = ["address", "addresses"])] + pub address_translation: Option, + + /// If used in a cluster environment, disables attempts to redirect requests to server replicas, + /// limiting Console to communicate only with the single address specified in the `address` + /// argument. + /// Use for administrative / debug purposes to test a specific replica only: this option will + /// lower the success rate of Console's operations in production. + #[arg(long = "replication-disabled", default_value = "false")] + pub replication_disabled: bool, /// Username for authentication #[arg(long, value_name = "username")] @@ -45,8 +63,8 @@ pub struct Args { /// Disable error reporting. Error reporting helps TypeDB improve by reporting /// errors and crashes to the development team. - #[arg(long = "diagnostics-disable", default_value = "false")] - pub diagnostics_disable: bool, + #[arg(long = "diagnostics-disabled", default_value = "false")] + pub diagnostics_disabled: bool, /// Print the Console binary version #[arg(long = "version")] diff --git a/src/main.rs b/src/main.rs index b221d1e..11f4f78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,7 @@ use std::{ rc::Rc, sync::Arc, }; - +use std::collections::HashMap; use clap::Parser; use home::home_dir; use rustyline::error::ReadlineError; @@ -99,10 +99,11 @@ fn main() { if args.password.is_none() { args.password = Some(LineReaderHidden::new().readline(&format!("password for '{}': ", args.username))); } - if !args.diagnostics_disable { + if !args.diagnostics_disabled { init_diagnostics() } - if !args.tls_disabled && !args.address.starts_with("https:") { + let address_info = parse_addresses(&args); + if !args.tls_disabled && !address_info.only_https { println!( "\ TLS connections can only be enabled when connecting to HTTPS endpoints, for example using 'https://:port'. \ @@ -113,10 +114,11 @@ fn main() { } let runtime = BackgroundRuntime::new(); let tls_root_ca_path = args.tls_root_ca.as_ref().map(|value| Path::new(value)); + let driver_options = DriverOptions::new().use_replication(!args.replication_disabled).is_tls_enabled(!args.tls_disabled).tls_root_ca(tls_root_ca_path).unwrap(); let driver = match runtime.run(TypeDBDriver::new( - Addresses::try_from_address_str(args.address).unwrap(), + address_info.addresses, Credentials::new(&args.username, args.password.as_ref().unwrap()), - DriverOptions::new().is_tls_enabled(!args.tls_disabled).tls_root_ca(tls_root_ca_path).unwrap(), + driver_options, )) { Ok(driver) => Arc::new(driver), Err(err) => { @@ -425,6 +427,40 @@ fn transaction_type_str(transaction_type: TransactionType) -> &'static str { } } +struct AddressInfo { + only_https: bool, + addresses: Addresses, +} + +fn parse_addresses(args: &Args) -> AddressInfo { + if let Some(address) = &args.address { + AddressInfo {only_https: is_https_address(address), addresses: Addresses::try_from_address_str(address).unwrap() } + } else if let Some(addresses) = &args.addresses { + let split = addresses.split(',').map(str::to_string).collect::>(); + println!("Split: {split:?}"); + let only_https = split.iter().all(|address| is_https_address(address)); + AddressInfo {only_https, addresses: Addresses::try_from_addresses_str(split).unwrap() } + } else if let Some(translation) = &args.address_translation { + let mut map = HashMap::new(); + let mut only_https = true; + for pair in translation.split(',') { + let (public_address, private_address) = pair + .split_once('=') + .unwrap_or_else(|| panic!("Invalid address pair: {pair}. Must be of form public=private")); + only_https = only_https && is_https_address(public_address); + map.insert(public_address.to_string(), private_address.to_string()); + } + println!("Translation map:: {map:?}"); + AddressInfo {only_https, addresses: Addresses::try_from_translation_str(map).unwrap() } + } else { + panic!("At least one of --address, --addresses, or --address-translation must be provided."); + } +} + +fn is_https_address(address: &str) -> bool { + address.starts_with("https:") +} + fn init_diagnostics() { let _ = sentry::init(( DIAGNOSTICS_REPORTING_URI, From 68ec0834dc8a3b68a63306b09e4689d88814a3a5 Mon Sep 17 00:00:00 2001 From: Georgii Novoselov Date: Tue, 8 Jul 2025 09:43:50 +0100 Subject: [PATCH 4/4] Add replica management --- Cargo.lock | 2 +- Cargo.toml | 2 +- dependencies/typedb/repositories.bzl | 2 +- src/main.rs | 25 ++++++++++++ src/operations.rs | 59 ++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 635f091..bbc6eca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2456,7 +2456,7 @@ dependencies = [ [[package]] name = "typedb-driver" version = "0.0.0" -source = "git+https://github.com/typedb/typedb-driver?rev=f1bf3d9e327344fe7a67084c13da4eb0fb5ca2be#f1bf3d9e327344fe7a67084c13da4eb0fb5ca2be" +source = "git+https://github.com/typedb/typedb-driver?rev=41e42b94ef0bee98d59ae627e46c45acb14a0700#41e42b94ef0bee98d59ae627e46c45acb14a0700" dependencies = [ "chrono", "chrono-tz", diff --git a/Cargo.toml b/Cargo.toml index 2aea831..07fdca5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ features = {} [dependencies.typedb-driver] features = [] - rev = "f1bf3d9e327344fe7a67084c13da4eb0fb5ca2be" + rev = "41e42b94ef0bee98d59ae627e46c45acb14a0700" git = "https://github.com/typedb/typedb-driver" default-features = false diff --git a/dependencies/typedb/repositories.bzl b/dependencies/typedb/repositories.bzl index bcf1c66..8eb4402 100644 --- a/dependencies/typedb/repositories.bzl +++ b/dependencies/typedb/repositories.bzl @@ -20,7 +20,7 @@ def typedb_driver(): git_repository( name = "typedb_driver", remote = "https://github.com/farost/typedb-driver", - commit = "f1bf3d9e327344fe7a67084c13da4eb0fb5ca2be", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_driver + commit = "41e42b94ef0bee98d59ae627e46c45acb14a0700", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_driver ) # git_repository( # name = "typedb_driver", diff --git a/src/main.rs b/src/main.rs index 11f4f78..b2702b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,6 +40,7 @@ use crate::{ }, runtime::BackgroundRuntime, }; +use crate::operations::{replica_deregister, replica_list, replica_primary, replica_register, server_version}; mod cli; mod completions; @@ -277,6 +278,9 @@ fn execute_commands( } fn entry_repl(driver: Arc, runtime: BackgroundRuntime) -> Repl { + let server_commands = Subcommand::new("server") + .add(CommandLeaf::new("version", "Retrieve server version.", server_version)); + let database_commands = Subcommand::new("database") .add(CommandLeaf::new("list", "List databases on the server.", database_list)) .add(CommandLeaf::new_with_input( @@ -350,6 +354,25 @@ fn entry_repl(driver: Arc, runtime: BackgroundRuntime) -> Repl, runtime: BackgroundRuntime) -> Repl CommandResult { + let driver = context.driver.clone(); + let server_version = context + .background_runtime + .run(async move { driver.server_version().await }) + .map_err(|err| Box::new(err) as Box)?; + println!("{}", server_version); + Ok(()) +} + pub(crate) fn database_list(context: &mut ConsoleContext, _input: &[String]) -> CommandResult { let driver = context.driver.clone(); let databases = context @@ -186,6 +196,55 @@ pub(crate) fn user_update_password(context: &mut ConsoleContext, input: &[String Ok(()) } +pub(crate) fn replica_list(context: &mut ConsoleContext, _input: &[String]) -> CommandResult { + let driver = context.driver.clone(); + let replicas = driver.replicas(); + if replicas.is_empty() { + println!("No replicas are present."); + } else { + for replica in replicas { + println!("{}", replica.address()); + } + } + Ok(()) +} + +pub(crate) fn replica_primary(context: &mut ConsoleContext, _input: &[String]) -> CommandResult { + let driver = context.driver.clone(); + let primary_replica = driver.primary_replica(); + if let Some(primary_replica) = primary_replica { + println!("{}", primary_replica.address()); + } else { + println!("No primary replica is present."); + } + Ok(()) +} + +pub(crate) fn replica_register(context: &mut ConsoleContext, input: &[String]) -> CommandResult { + let driver = context.driver.clone(); + let replica_id: u64 = input[0].parse().map_err(|_| Box::new(ReplError { message: format!("Replica id '{}' must be a number.", input[0]) }) + as Box)?; + let address = input[1].clone(); + context + .background_runtime + .run(async move { driver.register_replica(replica_id, address).await }) + .map_err(|err| Box::new(err) as Box)?; + println!("Successfully registered replica."); + Ok(()) +} + +pub(crate) fn replica_deregister(context: &mut ConsoleContext, input: &[String]) -> CommandResult { + let driver = context.driver.clone(); + let replica_id: u64 = input[0].parse().map_err(|_| Box::new(ReplError { message: format!("Replica id '{}' must be a number.", input[0]) }) + as Box)?; + context + .background_runtime + .run(async move { driver.deregister_replica(replica_id).await }) + .map_err(|err| Box::new(err) as Box)?; + println!("Successfully deregistered replica."); + Ok(()) +} + pub(crate) fn transaction_read(context: &mut ConsoleContext, input: &[String]) -> CommandResult { let driver = context.driver.clone(); let db_name = &input[0];