dbschema 0.1.0

Define database schema's as HCL files, and generate idempotent SQL migrations
Documentation
use super::{LintCheck, LintMessage, LintSeverity};
use crate::ir::Config;

pub struct LongIdentifier;

const MAX_IDENTIFIER_LEN: usize = 63;

impl LongIdentifier {
    fn ignored(ignores: &[String], rule: &str) -> bool {
        ignores.iter().any(|i| i == rule)
    }
}

impl LintCheck for LongIdentifier {
    fn name(&self) -> &'static str {
        "long-identifier"
    }

    fn run(&self, cfg: &Config) -> Vec<LintMessage> {
        let mut msgs = Vec::new();
        for table in &cfg.tables {
            if Self::ignored(&table.lint_ignore, self.name()) {
                continue;
            }
            if table.name.len() > MAX_IDENTIFIER_LEN {
                msgs.push(LintMessage {
                    check: self.name(),
                    message: format!(
                        "table '{}' name exceeds {} characters",
                        table.name, MAX_IDENTIFIER_LEN
                    ),
                    severity: LintSeverity::Error,
                });
            }
            for col in &table.columns {
                if Self::ignored(&col.lint_ignore, self.name()) {
                    continue;
                }
                if col.name.len() > MAX_IDENTIFIER_LEN {
                    msgs.push(LintMessage {
                        check: self.name(),
                        message: format!(
                            "column '{}.{}' name exceeds {} characters",
                            table.name, col.name, MAX_IDENTIFIER_LEN
                        ),
                        severity: LintSeverity::Error,
                    });
                }
            }
            for idx in &table.indexes {
                if let Some(name) = &idx.name {
                    if name.len() > MAX_IDENTIFIER_LEN {
                        msgs.push(LintMessage {
                            check: self.name(),
                            message: format!(
                                "index '{}.{}' name exceeds {} characters",
                                table.name, name, MAX_IDENTIFIER_LEN
                            ),
                            severity: LintSeverity::Error,
                        });
                    }
                }
            }
        }
        for idx in &cfg.indexes {
            if idx.name.len() > MAX_IDENTIFIER_LEN {
                msgs.push(LintMessage {
                    check: self.name(),
                    message: format!(
                        "index '{}' name exceeds {} characters",
                        idx.name, MAX_IDENTIFIER_LEN
                    ),
                    severity: LintSeverity::Error,
                });
            }
        }
        msgs
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ir::{ColumnSpec, Config, PrimaryKeySpec, TableSpec};
    use crate::lint::{LintSettings, run_with_checks};

    #[test]
    fn detects_long_name() {
        let long_name = "a".repeat(64);
        let table = TableSpec {
            name: long_name.clone(),
            alt_name: None,
            schema: None,
            if_not_exists: false,
            columns: vec![ColumnSpec {
                name: "id".into(),
                r#type: "int".into(),
                nullable: false,
                default: None,
                db_type: None,
                lint_ignore: vec![],
                comment: None,
                count: 1,
            }],
            primary_key: Some(PrimaryKeySpec {
                name: None,
                columns: vec!["id".into()],
            }),
            indexes: vec![],
            checks: vec![],
            foreign_keys: vec![],
            partition_by: None,
            partitions: vec![],
            back_references: vec![],
            lint_ignore: vec![],
            comment: None,
            map: None,
        };
        let cfg = Config {
            tables: vec![table],
            ..Default::default()
        };
        let msgs = run_with_checks(
            &cfg,
            vec![Box::new(LongIdentifier)],
            &LintSettings::default(),
        );
        assert!(msgs.iter().any(|m| m.check == "long-identifier"));
    }
}