vldb-sqlite 0.1.5

A Rust gRPC, library, and FFI gateway for SQLite with JSON and Arrow IPC query paths.
Documentation
mod config;
mod db_lock;
mod fts;
mod logging;
#[allow(dead_code)]
mod runtime;
mod sql_exec;
mod service;
mod tokenizer;

pub mod pb {
    tonic::include_proto!("vldb.sqlite.v1");
}

use crate::config::{BoxError, is_special_db_path, load_config};
use crate::db_lock::DatabaseFileLock;
use crate::logging::ServiceLogger;
use crate::pb::sqlite_service_server::SqliteServiceServer;
use crate::service::{SqliteGrpcService, effective_connection_pool_size, open_connection_pool};
use tonic::transport::Server;

#[tokio::main]
async fn main() -> Result<(), BoxError> {
    let loaded = load_config()?;
    let config = loaded.config;

    if let Some(source) = &loaded.source {
        println!("loaded config from {}", source.display());
    } else {
        println!(
            "no config file found; using defaults (or provide -config <path> / place vldb-sqlite.json in the working directory or executable directory)"
        );
    }

    if !is_special_db_path(&config.db_path)
        && let Some(parent) = std::path::Path::new(&config.db_path).parent()
    {
        std::fs::create_dir_all(parent)?;
    }

    let db_file_lock =
        if config.hardening.enforce_db_file_lock && !is_special_db_path(&config.db_path) {
            Some(DatabaseFileLock::acquire(std::path::Path::new(
                &config.db_path,
            ))?)
        } else {
            None
        };

    let logger = ServiceLogger::new("vldb-sqlite", &config.logging)?;
    let connection_pool = open_connection_pool(&config)?;
    let addr = format!("{}:{}", config.host, config.port).parse()?;
    let effective_pool_size = effective_connection_pool_size(&config);

    println!("sqlite database: {}", config.db_path);
    println!(
        "connection_pool_size: {}",
        if effective_pool_size == config.connection_pool_size {
            effective_pool_size.to_string()
        } else {
            format!(
                "{} (effective {}, in-memory SQLite is forced to a single shared connection)",
                config.connection_pool_size, effective_pool_size
            )
        }
    );
    println!(
        "busy_timeout_ms: {} | journal_mode: {} | synchronous: {} | temp_store: {}",
        config.busy_timeout_ms,
        config.pragmas.journal_mode,
        config.pragmas.synchronous,
        config.pragmas.temp_store,
    );
    println!(
        "sqlite hardening: db_file_lock={} read_only={} uri_filenames={} trusted_schema={} defensive={} foreign_keys={} wal_autocheckpoint_pages={} cache_size_kib={} mmap_size_bytes={}",
        if config.hardening.enforce_db_file_lock {
            "enabled"
        } else {
            "disabled"
        },
        if config.hardening.read_only {
            "enabled"
        } else {
            "disabled"
        },
        if config.hardening.allow_uri_filenames {
            "enabled"
        } else {
            "disabled"
        },
        if config.hardening.trusted_schema {
            "enabled"
        } else {
            "disabled"
        },
        if config.hardening.defensive {
            "enabled"
        } else {
            "disabled"
        },
        if config.pragmas.foreign_keys {
            "enabled"
        } else {
            "disabled"
        },
        config.pragmas.wal_autocheckpoint_pages,
        config.pragmas.cache_size_kib,
        config.pragmas.mmap_size_bytes,
    );
    if let Some(db_file_lock) = &db_file_lock {
        println!("database lock file: {}", db_file_lock.path().display());
    } else if config.hardening.enforce_db_file_lock {
        println!("database lock file: skipped for in-memory or URI database path");
    }
    if let Some(log_path) = logger.log_path() {
        println!("request log file: {}", log_path.display());
    } else if config.logging.enabled {
        println!("request log file: disabled");
    }
    println!("gRPC listening on {addr}");

    let svc = SqliteGrpcService::new(connection_pool, logger, config.clone());

    Server::builder()
        .add_service(SqliteServiceServer::new(svc))
        .serve_with_shutdown(addr, shutdown_signal())
        .await?;

    drop(db_file_lock);

    Ok(())
}

async fn shutdown_signal() {
    if tokio::signal::ctrl_c().await.is_ok() {
        println!("shutdown signal received, stopping vldb-sqlite");
    }
}