Switch to uuidv7 (#864)

* Add uuid_v7

* Actually use the new uuid

* Add a test to ensure all uuids are unique, even in a tight loop

* Make clippy happy
This commit is contained in:
Ellie Huxtable 2023-04-11 16:26:16 +01:00 committed by GitHub
parent 301296fae5
commit 03dd3ddf8b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 70 additions and 7 deletions

1
Cargo.lock generated
View file

@ -152,6 +152,7 @@ name = "atuin-common"
version = "14.0.0" version = "14.0.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"rand",
"serde", "serde",
"uuid", "uuid",
] ]

View file

@ -3,7 +3,7 @@ use std::env;
use chrono::Utc; use chrono::Utc;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use atuin_common::utils::uuid_v4; use atuin_common::utils::uuid_v7;
// Any new fields MUST be Optional<>! // Any new fields MUST be Optional<>!
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, sqlx::FromRow)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, sqlx::FromRow)]
@ -48,12 +48,12 @@ impl History {
) -> Self { ) -> Self {
let session = session let session = session
.or_else(|| env::var("ATUIN_SESSION").ok()) .or_else(|| env::var("ATUIN_SESSION").ok())
.unwrap_or_else(uuid_v4); .unwrap_or_else(|| uuid_v7().as_simple().to_string());
let hostname = let hostname =
hostname.unwrap_or_else(|| format!("{}:{}", whoami::hostname(), whoami::username())); hostname.unwrap_or_else(|| format!("{}:{}", whoami::hostname(), whoami::username()));
Self { Self {
id: uuid_v4(), id: uuid_v7().as_simple().to_string(),
timestamp, timestamp,
command, command,
cwd, cwd,

View file

@ -6,7 +6,7 @@ use directories::UserDirs;
use eyre::{eyre, Result}; use eyre::{eyre, Result};
use serde::Deserialize; use serde::Deserialize;
use atuin_common::utils::uuid_v4; use atuin_common::utils::uuid_v7;
use super::{get_histpath, unix_byte_lines, Importer, Loader}; use super::{get_histpath, unix_byte_lines, Importer, Loader};
use crate::history::History; use crate::history::History;
@ -123,13 +123,13 @@ impl Importer for Resh {
}; };
h.push(History { h.push(History {
id: uuid_v4(), id: uuid_v7().as_simple().to_string(),
timestamp, timestamp,
duration, duration,
exit: entry.exit_code, exit: entry.exit_code,
command: entry.cmd_line, command: entry.cmd_line,
cwd: entry.pwd, cwd: entry.pwd,
session: uuid_v4(), session: uuid_v7().as_simple().to_string(),
hostname: entry.host, hostname: entry.host,
deleted_at: None, deleted_at: None,
}) })

View file

@ -0,0 +1,5 @@
pub struct Message {
pub id: Uuid,
pub type: String,
}

View file

@ -14,3 +14,4 @@ repository = "https://github.com/ellie/atuin"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1.0.145", features = ["derive"] } serde = { version = "1.0.145", features = ["derive"] }
uuid = { version = "1.2", features = ["v4"] } uuid = { version = "1.2", features = ["v4"] }
rand = { version = "0.8.5", features = ["std"] }

View file

@ -2,8 +2,46 @@ use std::env;
use std::path::PathBuf; use std::path::PathBuf;
use chrono::{Months, NaiveDate}; use chrono::{Months, NaiveDate};
use rand::RngCore;
use uuid::Uuid; use uuid::Uuid;
pub fn random_bytes<const N: usize>() -> [u8; N] {
let mut ret = [0u8; N];
rand::thread_rng().fill_bytes(&mut ret);
ret
}
// basically just ripped from the uuid crate. they have it as unstable, but we can use it fine.
const fn encode_unix_timestamp_millis(millis: u64, random_bytes: &[u8; 10]) -> Uuid {
let millis_high = ((millis >> 16) & 0xFFFF_FFFF) as u32;
let millis_low = (millis & 0xFFFF) as u16;
let random_and_version =
(random_bytes[0] as u16 | ((random_bytes[1] as u16) << 8) & 0x0FFF) | (0x7 << 12);
let mut d4 = [0; 8];
d4[0] = (random_bytes[2] & 0x3F) | 0x80;
d4[1] = random_bytes[3];
d4[2] = random_bytes[4];
d4[3] = random_bytes[5];
d4[4] = random_bytes[6];
d4[5] = random_bytes[7];
d4[6] = random_bytes[8];
d4[7] = random_bytes[9];
Uuid::from_fields(millis_high, millis_low, random_and_version, &d4)
}
pub fn uuid_v7() -> Uuid {
let bytes = random_bytes();
let now: u64 = chrono::Utc::now().timestamp_millis() as u64;
encode_unix_timestamp_millis(now, &bytes)
}
pub fn uuid_v4() -> String { pub fn uuid_v4() -> String {
Uuid::new_v4().as_simple().to_string() Uuid::new_v4().as_simple().to_string()
} }
@ -59,6 +97,8 @@ mod tests {
use super::*; use super::*;
use std::env; use std::env;
use std::collections::HashSet;
#[test] #[test]
fn test_dirs() { fn test_dirs() {
// these tests need to be run sequentially to prevent race condition // these tests need to be run sequentially to prevent race condition
@ -117,4 +157,20 @@ mod tests {
// leap years // leap years
assert_eq!(get_days_from_month(2024, 2), 29); assert_eq!(get_days_from_month(2024, 2), 29);
} }
#[test]
fn uuid_is_unique() {
let how_many: usize = 1000000;
// for peace of mind
let mut uuids: HashSet<Uuid> = HashSet::with_capacity(how_many);
// there will be many in the same millisecond
for _ in 0..how_many {
let uuid = uuid_v7();
uuids.insert(uuid);
}
assert_eq!(uuids.len(), how_many);
}
} }

View file

@ -60,7 +60,7 @@ impl AtuinCmd {
Ok(()) Ok(())
} }
Self::Uuid => { Self::Uuid => {
println!("{}", atuin_common::utils::uuid_v4()); println!("{}", atuin_common::utils::uuid_v7().as_simple());
Ok(()) Ok(())
} }
Self::GenCompletions { shell, out_dir } => { Self::GenCompletions { shell, out_dir } => {