eugene 0.8.3

Careful with That Lock, Eugene
Documentation
use chrono::{DateTime, Utc};
use serde::Serialize;

use crate::hint_data::{hint_url, HintId, StaticHintData};
use crate::hints::HintInfo;
use crate::pg_types::locks::LockableTarget;
use crate::tracing::queries::ColumnMetadata;

#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
pub struct GenericHint {
    pub id: String,
    pub name: String,
    pub condition: String,
    pub effect: String,
    pub workaround: String,
    pub has_lint: bool,
    pub has_trace: bool,
    pub url: String,
}

impl From<&HintInfo> for GenericHint {
    fn from(value: &HintInfo) -> Self {
        GenericHint {
            id: value.code().to_string(),
            name: value.name().to_string(),
            condition: value.condition().to_string(),
            effect: value.effect().to_string(),
            workaround: value.workaround().to_string(),
            has_lint: crate::lints::rules::all_rules().any(|rule| rule.id() == value.code()),
            has_trace: crate::hints::all_hints()
                .iter()
                .any(|hint| hint.code() == value.code()),
            url: value.url(),
        }
    }
}

impl From<&StaticHintData> for GenericHint {
    fn from(value: &StaticHintData) -> Self {
        GenericHint {
            id: value.id.to_string(),
            name: value.name.to_string(),
            condition: value.condition.to_string(),
            effect: value.effect.to_string(),
            workaround: value.workaround.to_string(),
            has_lint: crate::lints::rules::all_rules().any(|rule| rule.id() == value.id),
            has_trace: crate::hints::all_hints()
                .iter()
                .any(|hint| hint.code() == value.id),
            url: value.url(),
        }
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
pub struct DbObject {
    pub schema: String,
    pub object_name: String,
    pub relkind: &'static str,
    pub oid: u32,
}

impl From<&LockableTarget> for DbObject {
    fn from(value: &LockableTarget) -> Self {
        DbObject {
            schema: value.schema.to_string(),
            object_name: value.object_name.to_string(),
            relkind: value.rel_kind.as_str(),
            oid: value.oid,
        }
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
pub struct TracedLock {
    pub schema: String,
    pub object_name: String,
    pub mode: String,
    pub relkind: &'static str,
    pub oid: u32,
    pub maybe_dangerous: bool,
    pub blocked_queries: Vec<&'static str>,
    pub lock_duration_millis: u64,
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
pub struct Column {
    pub schema_name: String,
    pub table_name: String,
    pub column_name: String,
    pub data_type: String,
    pub nullable: bool,
}

impl From<&ColumnMetadata> for Column {
    fn from(meta: &ColumnMetadata) -> Self {
        Column {
            schema_name: meta.schema_name.clone(),
            table_name: meta.table_name.clone(),
            column_name: meta.column_name.clone(),
            data_type: meta.typename.clone(),
            nullable: meta.nullable,
        }
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
pub struct ModifiedColumn {
    pub old: Column,
    pub new: Column,
}

impl From<&crate::tracing::tracer::ModifiedColumn> for ModifiedColumn {
    fn from(meta: &crate::tracing::tracer::ModifiedColumn) -> Self {
        ModifiedColumn {
            old: Column::from(&meta.old),
            new: Column::from(&meta.new),
        }
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
pub struct Constraint {
    pub schema_name: String,
    pub table_name: String,
    pub name: String,
    pub constraint_name: String,
    pub constraint_type: &'static str,
    pub valid: bool,
    pub definition: Option<String>,
}

impl From<&crate::tracing::queries::Constraint> for Constraint {
    fn from(constraint: &crate::tracing::queries::Constraint) -> Self {
        Constraint {
            schema_name: constraint.schema_name.clone(),
            table_name: constraint.table_name.clone(),
            name: constraint.name.clone(),
            constraint_name: constraint.name.clone(),
            constraint_type: constraint.constraint_type.to_display(),
            valid: constraint.valid,
            definition: constraint.expression.clone(),
        }
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
pub struct ModifiedConstraint {
    pub old: Constraint,
    pub new: Constraint,
}

impl From<&crate::tracing::tracer::ModifiedConstraint> for ModifiedConstraint {
    fn from(meta: &crate::tracing::tracer::ModifiedConstraint) -> Self {
        ModifiedConstraint {
            old: Constraint::from(&meta.old),
            new: Constraint::from(&meta.new),
        }
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
pub struct FullSqlStatementLockTrace {
    pub statement_number_in_transaction: usize,
    pub line_number: usize,
    pub sql: String,
    pub duration_millis: u64,
    pub start_time_millis: u64,
    pub locks_at_start: Vec<TracedLock>,
    pub new_locks_taken: Vec<TracedLock>,
    pub new_columns: Vec<Column>,
    pub altered_columns: Vec<ModifiedColumn>,
    pub new_constraints: Vec<Constraint>,
    pub altered_constraints: Vec<ModifiedConstraint>,
    pub new_objects: Vec<DbObject>,
    pub lock_timeout_millis: u64,
    pub triggered_rules: Vec<Hint>,
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
pub struct FullTraceData {
    pub name: Option<String>,
    #[serde(with = "datefmt")]
    pub start_time: DateTime<Utc>,
    pub total_duration_millis: u64,
    pub all_locks_acquired: Vec<TracedLock>,
    pub statements: Vec<FullSqlStatementLockTrace>,
    pub skip_summary: bool,
    pub dangerous_locks_count: usize,
    pub passed_all_checks: bool,
}

mod datefmt {
    use chrono::{DateTime, Utc};

    pub fn serialize<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&date.to_rfc3339())
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Serialize)]
pub struct Hint {
    pub id: String,
    pub name: String,
    pub condition: String,
    pub effect: String,
    pub workaround: String,
    pub help: String,
    pub url: String,
}

impl Hint {
    pub fn new(
        code: &str,
        name: &str,
        condition: &str,
        effect: &str,
        workaround: &str,
        help: String,
    ) -> Self {
        Hint {
            id: code.to_string(),
            name: name.to_string(),
            condition: condition.to_string(),
            effect: effect.to_string(),
            workaround: workaround.to_string(),
            help: help.to_string(),
            url: hint_url(code),
        }
    }
}

#[derive(Debug, Serialize, Clone, Eq, PartialEq)]
pub struct LintedStatement {
    pub statement_number: usize,
    pub line_number: usize,
    pub sql: String,
    pub triggered_rules: Vec<Hint>,
}

#[derive(Debug, Serialize, Clone, Eq, PartialEq)]
pub struct LintReport {
    pub name: Option<String>,
    pub statements: Vec<LintedStatement>,
    pub passed_all_checks: bool,
    pub skip_summary: bool,
}