Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ hex = "0.4.3"
http = "0.2.9"
http-body = "0.4.6"
humantime = "2.1.0"
humantime-serde = "1.1.1"
hyper = "0.14"
hyper-rustls = { version = "0.25", features = ["http1", "http2", "ring", "rustls-native-certs"] }
insta = { version = "1.39", features = ["json", "redactions", "yaml"] }
Expand Down
14 changes: 12 additions & 2 deletions influxdb3/src/commands/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ pub struct DatabaseConfig {
#[clap(env = "INFLUXDB3_DATABASE_NAME", required = true)]
pub database_name: String,

#[clap(long = "retention-period")]
/// The retention period for the database as a human-readable duration, e.g., "30d", "24h"
pub retention_period: Option<Duration>,

/// An optional arg to use a custom ca for useful for testing with self signed certs
#[clap(long = "tls-ca", env = "INFLUXDB3_TLS_CA")]
ca_cert: Option<PathBuf>,
Expand Down Expand Up @@ -277,8 +281,14 @@ pub struct TriggerConfig {
pub async fn command(config: Config) -> Result<(), Box<dyn Error>> {
let client = config.get_client()?;
match config.cmd {
SubCommand::Database(DatabaseConfig { database_name, .. }) => {
client.api_v3_configure_db_create(&database_name).await?;
SubCommand::Database(DatabaseConfig {
database_name,
retention_period,
..
}) => {
client
.api_v3_configure_db_create(&database_name, retention_period.map(Into::into))
.await?;

println!("Database {:?} created successfully", &database_name);
}
Expand Down
69 changes: 69 additions & 0 deletions influxdb3/src/commands/update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use super::common::InfluxDb3Config;
use humantime::Duration;
use influxdb3_client::Client;
use secrecy::ExposeSecret;
use std::error::Error;
use std::path::PathBuf;

#[derive(Debug, clap::Parser)]
pub struct Config {
#[clap(subcommand)]
cmd: SubCommand,
}

#[derive(Debug, clap::Subcommand)]
pub enum SubCommand {
/// Update a database
Database(UpdateDatabase),
}

#[derive(Debug, clap::Args)]
pub struct UpdateDatabase {
#[clap(flatten)]
influxdb3_config: InfluxDb3Config,

/// The retention period as a human-readable duration (e.g., "30d", "24h") or "none" to clear
#[clap(long, short = 'r')]
retention_period: Option<String>,

/// An optional arg to use a custom ca for useful for testing with self signed certs
#[clap(long = "tls-ca", env = "INFLUXDB3_TLS_CA")]
ca_cert: Option<PathBuf>,
}

pub async fn command(config: Config) -> Result<(), Box<dyn Error>> {
match config.cmd {
SubCommand::Database(UpdateDatabase {
influxdb3_config:
InfluxDb3Config {
host_url,
auth_token,
database_name,
..
},
retention_period,
ca_cert,
}) => {
let mut client = Client::new(host_url, ca_cert)?;
if let Some(token) = &auth_token {
client = client.with_auth_token(token.expose_secret());
}

if let Some(retention_str) = retention_period {
let retention = if retention_str.to_lowercase() == "none" {
None
} else {
Some(retention_str.parse::<Duration>()?.into())
};
client
.api_v3_configure_db_update(&database_name, retention)
.await?;

println!("Database \"{}\" updated successfully", database_name);
} else {
return Err("--retention-period is required for update database".into());
}
}
}
Ok(())
}
9 changes: 9 additions & 0 deletions influxdb3/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub mod commands {
pub mod serve;
pub mod show;
pub mod test;
pub mod update;
pub mod write;
}

Expand Down Expand Up @@ -105,6 +106,8 @@ enum Command {
/// Test things, such as plugins, work the way you expect
Test(commands::test::Config),

/// Update resources on the InfluxDB 3 Core server
Update(commands::update::Config),
/// Perform a set of writes to a running InfluxDB 3 Core server
Write(commands::write::Config),
}
Expand Down Expand Up @@ -200,6 +203,12 @@ fn main() -> Result<(), std::io::Error> {
std::process::exit(ReturnCode::Failure as _)
}
}
Some(Command::Update(config)) => {
if let Err(e) = commands::update::command(config).await {
eprintln!("Update command failed: {e}");
std::process::exit(ReturnCode::Failure as _)
}
}
Some(Command::Query(config)) => {
if let Err(e) = commands::query::command(config).await {
eprintln!("Query command failed: {e}");
Expand Down
230 changes: 230 additions & 0 deletions influxdb3/tests/cli/db_retention.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
use crate::server::{ConfigProvider, TestServer};
use test_helpers::assert_contains;

#[test_log::test(tokio::test)]
async fn test_create_db_with_retention_period() {
let server = TestServer::configure().with_no_admin_token().spawn().await;
let args = &["--tls-ca", "../testing-certs/rootCA.pem"];
let db_name = "test_db";

// Create database with retention period
let retention_period = "30d";
let result = server
.run(
vec![
"create",
"database",
db_name,
"--retention-period",
retention_period,
],
args,
)
.expect("create database should succeed");

assert_contains!(&result, "Database \"test_db\" created successfully");

let args = &[
"--tls-ca",
"../testing-certs/rootCA.pem",
"--format",
"json",
];

let result = server
.run(
vec![
"query",
"-d",
"_internal",
"SELECT retention_period_ns FROM system.databases WHERE system.databases.database_name='test_db'",
],
args,
)
.expect("create database with retention period should succeed");

assert_eq!(&result, "[{\"retention_period_ns\":2592000000000000}]");
}

#[test_log::test(tokio::test)]
async fn test_create_db_without_retention_period() {
let server = TestServer::configure().with_no_admin_token().spawn().await;
let db_name = "test_db2";
let args = &["--tls-ca", "../testing-certs/rootCA.pem"];

// Create database without retention period
let result = server
.run(vec!["create", "database", db_name], args)
.expect("create database without retention period should succeed");

assert_contains!(&result, "Database \"test_db2\" created successfully");

let args = &[
"--tls-ca",
"../testing-certs/rootCA.pem",
"--format",
"json",
];

let result = server
.run(
vec![
"query",
"-d",
"_internal",
"SELECT retention_period_ns FROM system.databases WHERE system.databases.database_name='test_db2'",
],
args,
)
.expect("create database without retention period should succeed");

assert_eq!(&result, "[{}]");
}

#[test_log::test(tokio::test)]
async fn test_create_db_with_invalid_retention_period() {
let server = TestServer::configure().with_no_admin_token().spawn().await;
let db_name = "test_db3";
let args = &["--tls-ca", "../testing-certs/rootCA.pem"];

// Try to create database with invalid retention period
let result = server.run(
vec![
"create",
"database",
db_name,
"--retention-period",
"invalid",
],
args,
);

assert!(
result.is_err(),
"Creating table with invalid retention period should fail"
);
}

#[test_log::test(tokio::test)]
async fn test_update_db_retention_period() {
let server = TestServer::configure().with_no_admin_token().spawn().await;
let args = &["--tls-ca", "../testing-certs/rootCA.pem"];
let db_name = "test_db_update";

// Create database with retention period
let result = server
.run(
vec!["create", "database", db_name, "--retention-period", "30d"],
args,
)
.expect("create database should succeed");

assert_contains!(
&result,
format!("Database \"{}\" created successfully", db_name)
);

// Update database retention period
let result = server
.run(
vec![
"update",
"database",
"--database",
db_name,
"--retention-period",
"60d",
],
args,
)
.expect("update database retention period should succeed");

assert_contains!(
&result,
format!("Database \"{}\" updated successfully", db_name)
);

// Verify the updated retention period
let args = &[
"--tls-ca",
"../testing-certs/rootCA.pem",
"--format",
"json",
];

let result = server
.run(
vec![
"query",
"-d",
"_internal",
&format!("SELECT retention_period_ns FROM system.databases WHERE system.databases.database_name='{}'", db_name),
],
args,
)
.expect("query should succeed");

assert_eq!(&result, "[{\"retention_period_ns\":5184000000000000}]"); // 60 days in nanoseconds
}

#[test_log::test(tokio::test)]
async fn test_clear_db_retention_period() {
let server = TestServer::configure().with_no_admin_token().spawn().await;
let args = &["--tls-ca", "../testing-certs/rootCA.pem"];
let db_name = "test_db_clear";

// Create database with retention period
let result = server
.run(
vec!["create", "database", db_name, "--retention-period", "30d"],
args,
)
.expect("create database should succeed");

assert_contains!(
&result,
format!("Database \"{}\" created successfully", db_name)
);

// Clear database retention period (set to none)
let result = server
.run(
vec![
"update",
"database",
"--database",
db_name,
"--retention-period",
"none",
],
args,
)
.expect("clear database retention period should succeed");

assert_contains!(
&result,
format!("Database \"{}\" updated successfully", db_name)
);

// Verify the retention period is now none (cleared)
let args = &[
"--tls-ca",
"../testing-certs/rootCA.pem",
"--format",
"json",
];

let result = server
.run(
vec![
"query",
"-d",
"_internal",
&format!("SELECT retention_period_ns FROM system.databases WHERE system.databases.database_name='{}'", db_name),
],
args,
)
.expect("query should succeed");

assert_eq!(&result, "[{}]"); // Empty object for none/cleared retention
}
1 change: 1 addition & 0 deletions influxdb3/tests/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod admin_token;
mod api;
mod db_retention;

use crate::server::{ConfigProvider, TestServer};
use assert_cmd::Command as AssertCmd;
Expand Down
Loading