sqlitegraph 3.9.0

Embedded graph database with explicit SQLite, native-v3, and combined backend modes; HNSW vector search; and graph algorithms
Documentation
use super::V3Backend;
use crate::SqliteGraphError;
use rusqlite::Connection;

impl V3Backend {
    pub(super) fn init_sqlite_schema(conn: &Connection) -> Result<(), SqliteGraphError> {
        conn.execute(
            "CREATE TABLE IF NOT EXISTS node_properties (
                node_id INTEGER PRIMARY KEY,
                kind TEXT NOT NULL,
                name TEXT,
                data TEXT,
                created_at INTEGER NOT NULL,
                created_version INTEGER NOT NULL DEFAULT 1,
                updated_version INTEGER,
                deleted_version INTEGER
            )",
            [],
        )
        .map_err(|e| {
            SqliteGraphError::connection(format!("Failed to create node_properties table: {}", e))
        })?;

        conn.execute(
            "CREATE INDEX IF NOT EXISTS node_props_kind ON node_properties(kind)",
            [],
        )
        .map_err(|e| SqliteGraphError::connection(format!("Failed to create kind index: {}", e)))?;

        conn.execute(
            "CREATE TABLE IF NOT EXISTS edge_attributes (
                src INTEGER NOT NULL,
                dst INTEGER NOT NULL,
                attr_name TEXT NOT NULL,
                attr_value TEXT,
                created_version INTEGER NOT NULL DEFAULT 1,
                deleted_version INTEGER,
                PRIMARY KEY (src, dst, attr_name)
            )",
            [],
        )
        .map_err(|e| {
            SqliteGraphError::connection(format!("Failed to create edge_attributes table: {}", e))
        })?;

        conn.execute(
            "CREATE TABLE IF NOT EXISTS layout_graph (
                table_name TEXT NOT NULL,
                column_name TEXT NOT NULL,
                rowgroup_id INTEGER,
                page_id INTEGER,
                parent_page_id INTEGER,
                PRIMARY KEY (table_name, column_name, rowgroup_id, page_id)
            )",
            [],
        )
        .map_err(|e| {
            SqliteGraphError::connection(format!("Failed to create layout_graph table: {}", e))
        })?;

        conn.execute(
            "CREATE INDEX IF NOT EXISTS layout_parent ON layout_graph(parent_page_id)",
            [],
        )
        .map_err(|e| {
            SqliteGraphError::connection(format!("Failed to create parent index: {}", e))
        })?;

        Self::ensure_sqlite_column(
            conn,
            "node_properties",
            "created_version",
            "INTEGER NOT NULL DEFAULT 1",
        )?;
        Self::ensure_sqlite_column(conn, "node_properties", "updated_version", "INTEGER")?;
        Self::ensure_sqlite_column(conn, "node_properties", "deleted_version", "INTEGER")?;
        Self::ensure_sqlite_column(
            conn,
            "edge_attributes",
            "created_version",
            "INTEGER NOT NULL DEFAULT 1",
        )?;
        Self::ensure_sqlite_column(conn, "edge_attributes", "deleted_version", "INTEGER")?;

        conn.execute(
            "UPDATE node_properties SET created_version = 1 WHERE created_version IS NULL",
            [],
        )
        .map_err(|e| {
            SqliteGraphError::connection(format!("Failed to backfill node created_version: {}", e))
        })?;
        conn.execute(
            "UPDATE edge_attributes SET created_version = 1 WHERE created_version IS NULL",
            [],
        )
        .map_err(|e| {
            SqliteGraphError::connection(format!("Failed to backfill edge created_version: {}", e))
        })?;

        Ok(())
    }

    pub(super) fn ensure_sqlite_column(
        conn: &Connection,
        table: &str,
        column: &str,
        definition: &str,
    ) -> Result<(), SqliteGraphError> {
        let pragma = format!("PRAGMA table_info({})", table);
        let mut stmt = conn.prepare(&pragma).map_err(|e| {
            SqliteGraphError::connection(format!("Failed to inspect table {}: {}", table, e))
        })?;

        let mut rows = stmt.query([]).map_err(|e| {
            SqliteGraphError::connection(format!("Failed to read table info for {}: {}", table, e))
        })?;

        while let Some(row) = rows.next().map_err(|e| {
            SqliteGraphError::connection(format!(
                "Failed to iterate table info for {}: {}",
                table, e
            ))
        })? {
            let existing: String = row.get(1).map_err(|e| {
                SqliteGraphError::connection(format!(
                    "Failed to decode column info for {}: {}",
                    table, e
                ))
            })?;
            if existing == column {
                return Ok(());
            }
        }

        let alter = format!("ALTER TABLE {} ADD COLUMN {} {}", table, column, definition);
        conn.execute(&alter, []).map_err(|e| {
            SqliteGraphError::connection(format!("Failed to migrate {}.{}: {}", table, column, e))
        })?;
        Ok(())
    }
}