cartel-sqlite 0.27.0

SQLite driver for the dope runtime
Documentation
use crate::api::work::guard::{Guard, open};
use crate::{catalogs, database, options, retry, runtime, sys};
use retry::lease;
use runtime::{local, slot};
use std::pin;

pub(super) struct Opening<'local, 'd: 'local, I: catalogs::Catalog> {
    resources: slot::Opening<'local, 'd, I>,
    value_bytes: i32,
    statement_sql_bytes: i32,
    storage: database::Kind,
    options: options::OpenOptions,
    commands: sys::Commands<'d>,
    phase: Phase,
}

enum Phase {
    Configure(Configure),
    Settings(Settings),
    Verify(Verify),
    Commands,
}

#[derive(Clone, Copy)]
enum Configure {
    Limits,
    TemporaryStore,
    MainCache,
    TemporaryCache,
}

#[derive(Clone, Copy)]
enum Settings {
    Journal,
    JournalLimit,
    Mapped,
    Synchronous,
    Checkpoint,
}

#[derive(Clone, Copy)]
enum Verify {
    Mapped,
    TemporaryStore,
    MainCache,
    TemporaryCache,
}

pub(super) enum Progress {
    Pending,
    Ready,
}

impl<'local, 'd: 'local, I: catalogs::Catalog> Opening<'local, 'd, I> {
    pub(super) fn new(
        local: pin::Pin<&'local local::State<'d, I>>,
        report: &'local mut crate::open::Completion,
    ) -> Result<(Self, crate::conn::raw::Vacant<'local, 'd, I>), crate::open::Error> {
        let (database, lease, vacant) = local.try_lease(report)?;
        let (location, options, commands) = database.into_parts();
        let flags = match &location {
            database::Location::File(_) => options.flags(),
            database::Location::Memory => 0,
        };
        let storage = location.kind();
        let db = match &location {
            database::Location::File(path) => sys::Connection::open(&path.0, flags)?,
            database::Location::Memory => sys::Connection::open(
                c":memory:",
                libsqlite3_sys::SQLITE_OPEN_READWRITE | libsqlite3_sys::SQLITE_OPEN_CREATE,
            )?,
        };
        let opened = lease.open(db)?;
        Ok((
            Self {
                resources: opened,
                value_bytes: local.value_bytes(),
                statement_sql_bytes: local.sql_bytes(),
                storage,
                options,
                commands,
                phase: Phase::Configure(Configure::Limits),
            },
            vacant,
        ))
    }

    pub(super) fn retry(
        &mut self,
    ) -> (
        &Guard<'local, open::Open>,
        lease::Lease<'_, 'd, lease::Open>,
    ) {
        self.resources.retry()
    }

    pub(super) fn advance(&mut self) -> Result<Progress, crate::open::Error> {
        match self.phase {
            Phase::Configure(step) => return Ok(self.configure(step)?),
            Phase::Settings(step) => return Ok(self.settings(step)?),
            Phase::Verify(step) => return Ok(self.verify(step)?),
            Phase::Commands => {}
        }
        let Some((sql, tail)) = self.commands.split_first() else {
            return Ok(Progress::Ready);
        };
        self.resources.call().execute(sql)?;
        self.commands = tail;
        Ok(Progress::Pending)
    }

    pub(super) fn prepare(self) -> slot::Prepare<'local, 'd, I> {
        self.resources.prepare()
    }

    fn configure(&mut self, step: Configure) -> Result<Progress, crate::Error> {
        let call = self.resources.call();
        self.phase = Phase::Configure(match step {
            Configure::Limits => {
                self.limits()?;
                Configure::TemporaryStore
            }
            Configure::TemporaryStore => {
                call.exec("PRAGMA temp_store=FILE")?;
                Configure::MainCache
            }
            Configure::MainCache => {
                call.pragma("cache_size", i64::from(-self.resources.config().main_kib()))?;
                Configure::TemporaryCache
            }
            Configure::TemporaryCache => {
                call.pragma(
                    "temp.cache_size",
                    i64::from(-self.resources.config().temp_kib()),
                )?;
                // Native connection and pager creation already enable cache spilling.
                self.phase = Phase::Settings(Settings::Journal);
                return Ok(Progress::Pending);
            }
        });
        Ok(Progress::Pending)
    }

    fn settings(&mut self, step: Settings) -> Result<Progress, crate::Error> {
        let call = self.resources.call();
        self.phase = Phase::Settings(match step {
            Settings::Journal => {
                match self.storage {
                    database::Kind::File => self.options.set_journal(&call)?,
                    database::Kind::Memory => {}
                }
                Settings::JournalLimit
            }
            Settings::JournalLimit => {
                match self.storage {
                    database::Kind::File => self.options.retain(&call)?,
                    database::Kind::Memory => {}
                }
                Settings::Mapped
            }
            Settings::Mapped => {
                self.options.set_mapped(&call, self.storage)?;
                Settings::Synchronous
            }
            Settings::Synchronous => {
                self.options.sync(&call)?;
                Settings::Checkpoint
            }
            Settings::Checkpoint => {
                self.options.checkpoint(&call)?;
                self.phase = Phase::Verify(Verify::Mapped);
                return Ok(Progress::Pending);
            }
        });
        Ok(Progress::Pending)
    }

    fn verify(&mut self, step: Verify) -> Result<Progress, crate::Error> {
        let call = self.resources.call();
        match step {
            Verify::Mapped => {
                let found = match self.storage {
                    database::Kind::File => call.query_i64("PRAGMA mmap_size")?,
                    database::Kind::Memory => 0,
                };
                let expected = self.options.mapped(self.storage)?;
                if found != expected {
                    return Err(crate::Error::Mapped {
                        expected,
                        actual: found,
                    });
                }
                self.phase = Phase::Verify(Verify::TemporaryStore);
            }
            Verify::TemporaryStore => {
                let actual = call.query_i64("PRAGMA temp_store")?;
                if actual != 1 {
                    return Err(crate::Error::TemporaryStore { actual });
                }
                self.phase = Phase::Verify(Verify::MainCache);
            }
            Verify::MainCache => {
                let expected = i64::from(-self.resources.config().main_kib());
                let actual = call.query_i64("PRAGMA cache_size")?;
                if actual != expected {
                    return Err(crate::Error::MainCache { expected, actual });
                }
                self.phase = Phase::Verify(Verify::TemporaryCache);
            }
            Verify::TemporaryCache => {
                let expected = i64::from(-self.resources.config().temp_kib());
                let actual = call.query_i64("PRAGMA temp.cache_size")?;
                if actual != expected {
                    return Err(crate::Error::TemporaryCache { expected, actual });
                }
                self.resources.authorize();
                self.phase = Phase::Commands;
            }
        }
        Ok(Progress::Pending)
    }

    fn limits(&mut self) -> Result<(), crate::Error> {
        self.resources.install()?;
        let max = self
            .resources
            .limit(libsqlite3_sys::SQLITE_LIMIT_SQL_LENGTH, -1);
        if self.statement_sql_bytes > max {
            return Err(crate::Error::StatementSqlLimit {
                requested: self.statement_sql_bytes,
                available: max,
            });
        }
        self.resources
            .limit(libsqlite3_sys::SQLITE_LIMIT_LENGTH, self.value_bytes);
        self.resources
            .limit(libsqlite3_sys::SQLITE_LIMIT_ATTACHED, 0);
        self.resources
            .limit(libsqlite3_sys::SQLITE_LIMIT_WORKER_THREADS, 0);
        self.resources.limit(
            libsqlite3_sys::SQLITE_LIMIT_VDBE_OP,
            self.resources.config().program_ops(),
        );
        Ok(())
    }
}