Skip to content

Commit 1a8f57e

Browse files
committed
Add beatiful replica list
1 parent 38573ff commit 1a8f57e

File tree

8 files changed

+154
-33
lines changed

8 files changed

+154
-33
lines changed

BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ rust_binary(
2929
"@crates//:futures",
3030
"@crates//:glob",
3131
"@crates//:home",
32+
"@crates//:itertools",
3233
"@crates//:rpassword",
3334
"@crates//:rustyline",
3435
"@crates//:sentry",

Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ features = {}
4646

4747
[dependencies.typedb-driver]
4848
features = []
49-
rev = "d2ce9ab4f85f8316a9087c130a20c7b1e731e8c4"
49+
rev = "dfbb1a1345c3ba6de5d30ef751ae61af3b9e5a89"
5050
git = "https://github.com/typedb/typedb-driver"
5151
default-features = false
5252

@@ -55,6 +55,11 @@ features = {}
5555
version = "0.3.31"
5656
default-features = false
5757

58+
[dependencies.itertools]
59+
features = ["default", "use_alloc", "use_std"]
60+
version = "0.10.5"
61+
default-features = false
62+
5863
[dependencies.serde_json]
5964
features = ["alloc", "default", "indexmap", "preserve_order", "raw_value", "std"]
6065
version = "1.0.140"

dependencies/typedb/repositories.bzl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ def typedb_dependencies():
1414

1515
def typedb_driver():
1616
# TODO: Return typedb
17+
# native.local_repository(
18+
# name = "typedb_driver",
19+
# path = "../typedb-driver",
20+
# )
1721
git_repository(
1822
name = "typedb_driver",
19-
remote = "https://github.com/farost/typedb-driver",
20-
commit = "d2ce9ab4f85f8316a9087c130a20c7b1e731e8c4", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_driver
23+
remote = "https://github.com/typedb/typedb-driver",
24+
commit = "dfbb1a1345c3ba6de5d30ef751ae61af3b9e5a89", # sync-marker: do not remove this comment, this is used for sync-dependencies by @typedb_driver
2125
)
2226

2327
def typeql():

src/cli.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ pub struct Args {
4747
pub replication_disabled: bool,
4848

4949
// TODO: Add cluster-related retries/attempts flags from Driver Options?
50-
5150
/// Username for authentication
5251
#[arg(long, value_name = USERNAME_VALUE_NAME)]
5352
pub username: Option<String>,

src/main.rs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
use std::{
8+
collections::HashMap,
89
env,
910
env::temp_dir,
1011
error::Error,
@@ -17,7 +18,7 @@ use std::{
1718
rc::Rc,
1819
sync::Arc,
1920
};
20-
use std::collections::HashMap;
21+
2122
use clap::Parser;
2223
use home::home_dir;
2324
use rustyline::error::ReadlineError;
@@ -29,9 +30,9 @@ use crate::{
2930
completions::{database_name_completer_fn, file_completer},
3031
operations::{
3132
database_create, database_delete, database_export, database_import, database_list, database_schema,
32-
transaction_close, transaction_commit, transaction_query, transaction_read, transaction_rollback,
33-
transaction_schema, transaction_source, transaction_write, user_create, user_delete, user_list,
34-
user_update_password,
33+
replica_deregister, replica_list, replica_primary, replica_register, server_version, transaction_close,
34+
transaction_commit, transaction_query, transaction_read, transaction_rollback, transaction_schema,
35+
transaction_source, transaction_write, user_create, user_delete, user_list, user_update_password,
3536
},
3637
repl::{
3738
command::{get_word, parse_one_query, CommandInput, CommandLeaf, Subcommand},
@@ -40,7 +41,6 @@ use crate::{
4041
},
4142
runtime::BackgroundRuntime,
4243
};
43-
use crate::operations::{replica_deregister, replica_list, replica_primary, replica_register, server_version};
4444

4545
mod cli;
4646
mod completions;
@@ -158,7 +158,11 @@ fn main() {
158158
}
159159
let tls_root_ca_path = args.tls_root_ca.as_ref().map(|value| Path::new(value));
160160
let runtime = BackgroundRuntime::new();
161-
let driver_options = DriverOptions::new().use_replication(!args.replication_disabled).tls_enabled(!args.tls_disabled).tls_root_ca(tls_root_ca_path).unwrap();
161+
let driver_options = DriverOptions::new()
162+
.use_replication(!args.replication_disabled)
163+
.tls_enabled(!args.tls_disabled)
164+
.tls_root_ca(tls_root_ca_path)
165+
.unwrap();
162166
let driver = match runtime.run(TypeDBDriver::new(
163167
address_info.addresses,
164168
Credentials::new(&username, args.password.as_ref().unwrap()),
@@ -330,8 +334,8 @@ fn execute_commands(
330334
}
331335

332336
fn entry_repl(driver: Arc<TypeDBDriver>, runtime: BackgroundRuntime) -> Repl<ConsoleContext> {
333-
let server_commands = Subcommand::new("server")
334-
.add(CommandLeaf::new("version", "Retrieve server version.", server_version));
337+
let server_commands =
338+
Subcommand::new("server").add(CommandLeaf::new("version", "Retrieve server version.", server_version));
335339

336340
let replica_commands = Subcommand::new("replica")
337341
.add(CommandLeaf::new("list", "List replicas.", replica_list))
@@ -511,11 +515,14 @@ struct AddressInfo {
511515

512516
fn parse_addresses(args: &Args) -> AddressInfo {
513517
if let Some(address) = &args.address {
514-
AddressInfo {only_https: is_https_address(address), addresses: Addresses::try_from_address_str(address).unwrap() }
518+
AddressInfo {
519+
only_https: is_https_address(address),
520+
addresses: Addresses::try_from_address_str(address).unwrap(),
521+
}
515522
} else if let Some(addresses) = &args.addresses {
516523
let split = addresses.split(',').map(str::to_string).collect::<Vec<_>>();
517524
let only_https = split.iter().all(|address| is_https_address(address));
518-
AddressInfo {only_https, addresses: Addresses::try_from_addresses_str(split).unwrap() }
525+
AddressInfo { only_https, addresses: Addresses::try_from_addresses_str(split).unwrap() }
519526
} else if let Some(translation) = &args.address_translation {
520527
let mut map = HashMap::new();
521528
let mut only_https = true;
@@ -527,7 +534,7 @@ fn parse_addresses(args: &Args) -> AddressInfo {
527534
map.insert(public_address.to_string(), private_address.to_string());
528535
}
529536
println!("Translation map:: {map:?}"); // TODO: Remove
530-
AddressInfo {only_https, addresses: Addresses::try_from_translation_str(map).unwrap() }
537+
AddressInfo { only_https, addresses: Addresses::try_from_translation_str(map).unwrap() }
531538
} else {
532539
panic!("At least one of --address, --addresses, or --address-translation must be provided.");
533540
}

src/operations.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use typedb_driver::{
2020

2121
use crate::{
2222
constants::DEFAULT_TRANSACTION_TIMEOUT,
23-
printer::{print_document, print_row},
23+
printer::{print_document, print_replicas_table, print_row},
2424
repl::command::{parse_one_query, CommandResult, ReplError},
2525
transaction_repl, ConsoleContext,
2626
};
@@ -203,17 +203,17 @@ pub(crate) fn replica_list(context: &mut ConsoleContext, _input: &[String]) -> C
203203
if replicas.is_empty() {
204204
println!("No replicas are present.");
205205
} else {
206-
for replica in replicas {
207-
println!("{}", replica.address());
208-
}
206+
print_replicas_table(replicas);
209207
}
210208
Ok(())
211209
})
212210
}
213211

214212
pub(crate) fn replica_primary(context: &mut ConsoleContext, _input: &[String]) -> CommandResult {
215213
let driver = context.driver.clone();
216-
let primary_replica = driver.primary_replica();
214+
let primary_replica = context
215+
.background_runtime
216+
.run(async move { driver.primary_replica().await.map_err(|err| Box::new(err) as Box<dyn Error + Send>) })?;
217217
if let Some(primary_replica) = primary_replica {
218218
println!("{}", primary_replica.address());
219219
} else {
@@ -224,8 +224,9 @@ pub(crate) fn replica_primary(context: &mut ConsoleContext, _input: &[String]) -
224224

225225
pub(crate) fn replica_register(context: &mut ConsoleContext, input: &[String]) -> CommandResult {
226226
let driver = context.driver.clone();
227-
let replica_id: u64 = input[0].parse().map_err(|_| Box::new(ReplError { message: format!("Replica id '{}' must be a number.", input[0]) })
228-
as Box<dyn Error + Send>)?;
227+
let replica_id: u64 = input[0].parse().map_err(|_| {
228+
Box::new(ReplError { message: format!("Replica id '{}' must be a number.", input[0]) }) as Box<dyn Error + Send>
229+
})?;
229230
let address = input[1].clone();
230231
context
231232
.background_runtime
@@ -237,8 +238,9 @@ pub(crate) fn replica_register(context: &mut ConsoleContext, input: &[String]) -
237238

238239
pub(crate) fn replica_deregister(context: &mut ConsoleContext, input: &[String]) -> CommandResult {
239240
let driver = context.driver.clone();
240-
let replica_id: u64 = input[0].parse().map_err(|_| Box::new(ReplError { message: format!("Replica id '{}' must be a number.", input[0]) })
241-
as Box<dyn Error + Send>)?;
241+
let replica_id: u64 = input[0].parse().map_err(|_| {
242+
Box::new(ReplError { message: format!("Replica id '{}' must be a number.", input[0]) }) as Box<dyn Error + Send>
243+
})?;
242244
context
243245
.background_runtime
244246
.run(async move { driver.deregister_replica(replica_id).await })

src/printer.rs

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,44 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7+
use std::collections::HashSet;
8+
79
use clap::builder::styling::{AnsiColor, Color, Style};
10+
use itertools::Itertools;
811
use typedb_driver::{
912
answer::{ConceptDocument, ConceptRow},
1013
concept::{Concept, Value},
11-
IID,
14+
Replica, ReplicaRole, ServerReplica, IID,
1215
};
1316

1417
const TABLE_INDENT: &'static str = " ";
1518
const CONTENT_INDENT: &'static str = " ";
1619
const TABLE_DASHES: usize = 7;
1720

18-
pub const ERROR_STYLE: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red))).bold();
19-
pub const WARNING_STYLE: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow))).bold();
20-
pub const ARGUMENT_STYLE: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow)));
21+
pub const STYLE_RED: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red)));
22+
pub const STYLE_GREEN: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green)));
23+
pub const STYLE_ERROR: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red))).bold();
24+
pub const STYLE_WARNING: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow))).bold();
25+
pub const STYLE_ARGUMENT: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow)));
2126

2227
#[macro_export]
2328
macro_rules! format_error {
2429
($($arg:tt)*) => {
25-
$crate::format_colored!($crate::printer::ERROR_STYLE, $($arg)*)
30+
$crate::format_colored!($crate::printer::STYLE_ERROR, $($arg)*)
2631
};
2732
}
2833

2934
#[macro_export]
3035
macro_rules! format_warning {
3136
($($arg:tt)*) => {
32-
$crate::format_colored!($crate::printer::WARNING_STYLE, $($arg)*)
37+
$crate::format_colored!($crate::printer::STYLE_WARNING, $($arg)*)
3338
};
3439
}
3540

3641
#[macro_export]
3742
macro_rules! format_argument {
3843
($($arg:tt)*) => {
39-
$crate::format_colored!($crate::printer::ARGUMENT_STYLE, $($arg)*)
44+
$crate::format_colored!($crate::printer::STYLE_ARGUMENT, $($arg)*)
4045
};
4146
}
4247

@@ -67,6 +72,103 @@ fn println(string: &str) {
6772
println!("{}", string)
6873
}
6974

75+
pub(crate) fn print_replicas_table(replicas: HashSet<ServerReplica>) {
76+
const COLUMN_NUM: usize = 5;
77+
#[derive(Debug)]
78+
struct Row {
79+
id: String,
80+
address: String,
81+
role: String,
82+
term: String,
83+
status: (String, Style),
84+
}
85+
86+
let mut rows = Vec::new();
87+
rows.push(Row {
88+
id: "id".to_string(),
89+
address: "address".to_string(),
90+
role: "role".to_string(),
91+
term: "term".to_string(),
92+
status: ("status".to_string(), Style::new()),
93+
});
94+
95+
for replica in replicas.into_iter().sorted_by_key(|replica| replica.id()) {
96+
let role = match replica.role() {
97+
Some(ReplicaRole::Primary) => "primary",
98+
Some(ReplicaRole::Candidate) => "candidate",
99+
Some(ReplicaRole::Secondary) => "secondary",
100+
None => "",
101+
}
102+
.to_string();
103+
104+
let term = replica.term().map(|t| t.to_string()).unwrap_or_default();
105+
106+
let status = match &replica {
107+
ServerReplica::Available(_) => ("available".to_string(), STYLE_GREEN),
108+
ServerReplica::Unavailable { .. } => ("unavailable".to_string(), STYLE_RED),
109+
};
110+
111+
rows.push(Row {
112+
id: replica.id().to_string(),
113+
address: replica.address().map(|address| address.to_string()).unwrap_or_default(),
114+
role,
115+
term,
116+
status,
117+
});
118+
}
119+
120+
// Compute max content length per column (without padding)
121+
let mut width_id = 0usize;
122+
let mut width_address = 0usize;
123+
let mut width_role = 0usize;
124+
let mut width_term = 0usize;
125+
let mut width_status = 0usize;
126+
127+
for r in &rows {
128+
width_id = width_id.max(r.id.len());
129+
width_address = width_address.max(r.address.len());
130+
width_role = width_role.max(r.role.len());
131+
width_term = width_term.max(r.term.len());
132+
width_status = width_status.max(r.status.0.len());
133+
}
134+
135+
// Add 2 spaces per column (one at the beginning and one at the end)
136+
width_id += 2;
137+
width_address += 2;
138+
width_role += 2;
139+
width_term += 2;
140+
width_status += 2;
141+
142+
fn print_cell(content: &str, width: usize, style: Option<Style>) {
143+
// One space on the left, one at the right, rest is extra right padding
144+
let content_len = content.len();
145+
let base_len = content_len + 2; // left + right space
146+
let extra_padding = width.saturating_sub(base_len);
147+
let styled_content = format_colored!(style.unwrap_or_default(), "{content}");
148+
print!(" {}{} ", styled_content, " ".repeat(extra_padding));
149+
}
150+
151+
const PIPES_NUM: usize = COLUMN_NUM - 1;
152+
let total_width = width_id + width_address + width_role + width_term + width_status + PIPES_NUM;
153+
154+
for (row_index, row) in rows.iter().enumerate() {
155+
print_cell(&row.id, width_id, None);
156+
print!("|");
157+
print_cell(&row.address, width_address, None);
158+
print!("|");
159+
print_cell(&row.role, width_role, None);
160+
print!("|");
161+
print_cell(&row.term, width_term, None);
162+
print!("|");
163+
print_cell(&row.status.0, width_status, Some(row.status.1));
164+
println!();
165+
166+
if row_index == 0 {
167+
println!("{}", "-".repeat(total_width));
168+
}
169+
}
170+
}
171+
70172
pub(crate) fn print_document(document: ConceptDocument) {
71173
// Note: inefficient, but easy...
72174
match serde_json::from_str::<serde_json::Value>(&document.into_json().to_string()) {

0 commit comments

Comments
 (0)