Fix failing constraint

This commit is contained in:
2026-01-22 00:48:27 +01:00
parent 8fd9ff9140
commit 15285d3225
2 changed files with 28 additions and 27 deletions

View File

@@ -55,9 +55,10 @@ impl Database {
); );
-- Main table with normalized foreign keys and integer timestamp -- Main table with normalized foreign keys and integer timestamp
-- timestamp_ms stores milliseconds since epoch for uniqueness
CREATE TABLE IF NOT EXISTS signature_entries ( CREATE TABLE IF NOT EXISTS signature_entries (
session_id TEXT NOT NULL, session_id TEXT NOT NULL,
timestamp INTEGER NOT NULL, timestamp_ms INTEGER NOT NULL,
app_id INTEGER NOT NULL REFERENCES apps(id), app_id INTEGER NOT NULL REFERENCES apps(id),
version_id INTEGER NOT NULL REFERENCES versions(id), version_id INTEGER NOT NULL REFERENCES versions(id),
offline_login_usage INTEGER, offline_login_usage INTEGER,
@@ -70,7 +71,7 @@ impl Database {
model_id INTEGER REFERENCES models(id), model_id INTEGER REFERENCES models(id),
device_id INTEGER REFERENCES devices(id), device_id INTEGER REFERENCES devices(id),
password_autofill_usage INTEGER, password_autofill_usage INTEGER,
PRIMARY KEY (session_id, timestamp) PRIMARY KEY (session_id, timestamp_ms)
) WITHOUT ROWID; ) WITHOUT ROWID;
CREATE INDEX IF NOT EXISTS idx_session_id ON signature_entries(session_id); CREATE INDEX IF NOT EXISTS idx_session_id ON signature_entries(session_id);
@@ -96,7 +97,7 @@ impl Database {
let mut insert_stmt = tx.prepare_cached( let mut insert_stmt = tx.prepare_cached(
r#" r#"
INSERT INTO signature_entries ( INSERT INTO signature_entries (
session_id, timestamp, app_id, version_id, session_id, timestamp_ms, app_id, version_id,
offline_login_usage, is_password_autofill_enabled, camera_roll_usage, offline_login_usage, is_password_autofill_enabled, camera_roll_usage,
os_id, app_name_id, touch_id, is_offline_login_enabled, os_id, app_name_id, touch_id, is_offline_login_enabled,
model_id, device_id, password_autofill_usage model_id, device_id, password_autofill_usage
@@ -114,7 +115,7 @@ impl Database {
insert_stmt.execute(params![ insert_stmt.execute(params![
entry.session_id, entry.session_id,
entry.timestamp.and_utc().timestamp(), entry.timestamp_ms,
app_id, app_id,
version_id, version_id,
entry.offline_login_usage, entry.offline_login_usage,

View File

@@ -7,7 +7,8 @@ use std::sync::LazyLock;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct SignatureEntry { pub struct SignatureEntry {
pub session_id: String, pub session_id: String,
pub timestamp: NaiveDateTime, /// Timestamp as milliseconds since Unix epoch (UTC)
pub timestamp_ms: i64,
pub app: String, pub app: String,
pub version: String, pub version: String,
pub offline_login_usage: Option<i64>, pub offline_login_usage: Option<i64>,
@@ -39,7 +40,7 @@ pub enum ParsedMessage {
static SESSION_ID_RE: LazyLock<Regex> = static SESSION_ID_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"sessionId=([^,\s]+)").unwrap()); LazyLock::new(|| Regex::new(r"sessionId=([^,\s]+)").unwrap());
static DATETIME_RE: LazyLock<Regex> = static DATETIME_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r#"dt="(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})"#).unwrap()); LazyLock::new(|| Regex::new(r#"dt="(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})(?:,(\d{3}))?"#).unwrap());
static CORRELATION_ID_RE: LazyLock<Regex> = static CORRELATION_ID_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"correlationId=([^,\s]+)").unwrap()); LazyLock::new(|| Regex::new(r"correlationId=([^,\s]+)").unwrap());
static SIGNATURE_RE: LazyLock<Regex> = static SIGNATURE_RE: LazyLock<Regex> =
@@ -84,15 +85,8 @@ impl SignatureParser {
.map(|m| m.as_str().to_string()) .map(|m| m.as_str().to_string())
.ok_or_else(|| anyhow!("Missing sessionId"))?; .ok_or_else(|| anyhow!("Missing sessionId"))?;
// Extract timestamp // Extract timestamp as milliseconds
let datetime_str = DATETIME_RE let timestamp_ms = extract_timestamp_ms(line)?;
.captures(line)
.and_then(|c| c.get(1))
.map(|m| m.as_str())
.ok_or_else(|| anyhow!("Missing datetime"))?;
let timestamp = NaiveDateTime::parse_from_str(datetime_str, "%Y-%m-%d %H:%M:%S")
.map_err(|e| anyhow!("Invalid datetime format: {}", e))?;
// Extract signature details // Extract signature details
let caps = SIGNATURE_RE let caps = SIGNATURE_RE
@@ -109,7 +103,7 @@ impl SignatureParser {
let entry = SignatureEntry { let entry = SignatureEntry {
session_id, session_id,
timestamp, timestamp_ms,
app, app,
version, version,
offline_login_usage: parse_number(&details, "offlineLoginUsage"), offline_login_usage: parse_number(&details, "offlineLoginUsage"),
@@ -143,7 +137,7 @@ impl MessageParser for MobileClientIosParser {
impl MobileClientIosParser { impl MobileClientIosParser {
fn parse_mobile_ios_line(&self, line: &str) -> Result<ParsedMessage> { fn parse_mobile_ios_line(&self, line: &str) -> Result<ParsedMessage> {
let timestamp = extract_timestamp(line)?; let timestamp_ms = extract_timestamp_ms(line)?;
let session_id = extract_correlation_id(line)?; let session_id = extract_correlation_id(line)?;
let caps = MOBILE_IOS_RE let caps = MOBILE_IOS_RE
@@ -158,7 +152,7 @@ impl MobileClientIosParser {
let entry = SignatureEntry { let entry = SignatureEntry {
session_id, session_id,
timestamp, timestamp_ms,
app, app,
version, version,
offline_login_usage: None, offline_login_usage: None,
@@ -192,7 +186,7 @@ impl MessageParser for MobileClientAndroidParser {
impl MobileClientAndroidParser { impl MobileClientAndroidParser {
fn parse_mobile_android_line(&self, line: &str) -> Result<ParsedMessage> { fn parse_mobile_android_line(&self, line: &str) -> Result<ParsedMessage> {
let timestamp = extract_timestamp(line)?; let timestamp_ms = extract_timestamp_ms(line)?;
let session_id = extract_correlation_id(line)?; let session_id = extract_correlation_id(line)?;
let caps = MOBILE_ANDROID_RE let caps = MOBILE_ANDROID_RE
@@ -207,7 +201,7 @@ impl MobileClientAndroidParser {
let entry = SignatureEntry { let entry = SignatureEntry {
session_id, session_id,
timestamp, timestamp_ms,
app, app,
version, version,
offline_login_usage: None, offline_login_usage: None,
@@ -226,16 +220,22 @@ impl MobileClientAndroidParser {
} }
} }
/// Extract timestamp from log line /// Extract timestamp from log line as milliseconds since Unix epoch
fn extract_timestamp(line: &str) -> Result<NaiveDateTime> { fn extract_timestamp_ms(line: &str) -> Result<i64> {
let datetime_str = DATETIME_RE let caps = DATETIME_RE
.captures(line) .captures(line)
.and_then(|c| c.get(1))
.map(|m| m.as_str())
.ok_or_else(|| anyhow!("Missing datetime"))?; .ok_or_else(|| anyhow!("Missing datetime"))?;
NaiveDateTime::parse_from_str(datetime_str, "%Y-%m-%d %H:%M:%S") let datetime_str = caps.get(1).map(|m| m.as_str()).unwrap();
.map_err(|e| anyhow!("Invalid datetime format: {}", e)) let millis: i64 = caps
.get(2)
.map(|m| m.as_str().parse().unwrap_or(0))
.unwrap_or(0);
let dt = NaiveDateTime::parse_from_str(datetime_str, "%Y-%m-%d %H:%M:%S")
.map_err(|e| anyhow!("Invalid datetime format: {}", e))?;
Ok(dt.and_utc().timestamp_millis() + millis)
} }
/// Extract correlation ID as session ID for mobile client logs /// Extract correlation ID as session ID for mobile client logs