codebase-graph 1.5.1

Native codebaseGraph CLI and MCP server for local code knowledge graphs.
#[derive(Debug, Default, PartialEq, Eq)]
pub(super) struct DatabaseStatementPhases<'a> {
    pub(super) pre_copy: Vec<&'a str>,
    pub(super) post_copy: Vec<&'a str>,
}

pub(super) fn statement_phases<'a>(
    include_fts: bool,
    provided: &'a [String],
) -> DatabaseStatementPhases<'a> {
    let mut phases = DatabaseStatementPhases::default();
    if provided.is_empty() {
        phases.pre_copy.extend(["INSTALL json", "LOAD json"]);
        if include_fts {
            phases.pre_copy.extend(["INSTALL fts", "LOAD fts"]);
        }
        return phases;
    }

    phases.pre_copy.reserve(provided.len());
    for statement in provided {
        if !include_fts && is_fts_statement(statement) {
            continue;
        }
        if is_post_copy_statement(statement) {
            phases.post_copy.push(statement);
        } else {
            phases.pre_copy.push(statement);
        }
    }
    phases
}

fn is_fts_statement(statement: &str) -> bool {
    let statement = statement.trim().trim_end_matches(';').trim_end();
    statement.eq_ignore_ascii_case("INSTALL fts")
        || statement.eq_ignore_ascii_case("LOAD fts")
        || is_post_copy_statement(statement)
}

fn is_post_copy_statement(statement: &str) -> bool {
    statement
        .trim_start()
        .get(.."CALL CREATE_FTS_INDEX".len())
        .is_some_and(|prefix| prefix.eq_ignore_ascii_case("CALL CREATE_FTS_INDEX"))
}

#[cfg(test)]
mod tests {
    use super::{statement_phases, DatabaseStatementPhases};

    #[test]
    fn classifies_fts_indexes_after_copy_without_reordering_each_phase() {
        let statements = vec![
            "INSTALL json".to_string(),
            "  call create_fts_index('File', 'files', ['label'])".to_string(),
            "LOAD json".to_string(),
            "CREATE NODE TABLE `File` (`id` STRING PRIMARY KEY)".to_string(),
            "CALL CREATE_FTS_INDEX('Symbol', 'symbols', ['label'])".to_string(),
        ];

        assert_eq!(
            statement_phases(true, &statements),
            DatabaseStatementPhases {
                pre_copy: vec![
                    "INSTALL json",
                    "LOAD json",
                    "CREATE NODE TABLE `File` (`id` STRING PRIMARY KEY)",
                ],
                post_copy: vec![
                    "  call create_fts_index('File', 'files', ['label'])",
                    "CALL CREATE_FTS_INDEX('Symbol', 'symbols', ['label'])",
                ],
            }
        );
    }

    #[test]
    fn extension_setup_is_always_pre_copy() {
        assert_eq!(
            statement_phases(true, &[]),
            DatabaseStatementPhases {
                pre_copy: vec!["INSTALL json", "LOAD json", "INSTALL fts", "LOAD fts"],
                post_copy: Vec::new(),
            }
        );
    }

    #[test]
    fn disabled_fts_filters_provided_extension_and_index_statements() {
        let statements = vec![
            "INSTALL json".to_string(),
            " INSTALL fts; ".to_string(),
            "LOAD fts;".to_string(),
            "CREATE NODE TABLE `File` (`id` STRING PRIMARY KEY)".to_string(),
            "CALL CREATE_FTS_INDEX('File', 'files', ['label'])".to_string(),
        ];

        assert_eq!(
            statement_phases(false, &statements),
            DatabaseStatementPhases {
                pre_copy: vec![
                    "INSTALL json",
                    "CREATE NODE TABLE `File` (`id` STRING PRIMARY KEY)",
                ],
                post_copy: Vec::new(),
            }
        );
    }
}