Merge 'hide dangerous methods behind conn_raw_api feature' from Nikita Sivukhin

WAL API shouldn't be exposed by default because this is relatively
dangerous API which we use internally and ordinary users shouldn't not
be interested in it.

Reviewed-by: Pekka Enberg <penberg@iki.fi>

Closes #2424
This commit is contained in:
Pekka Enberg
2025-08-04 14:52:40 +03:00
committed by GitHub
12 changed files with 43 additions and 28 deletions

View File

@@ -11,6 +11,7 @@ description = "Turso Rust API"
[features]
default = ["experimental_indexes"]
conn_raw_api = ["turso_core/conn_raw_api"]
experimental_indexes = []
antithesis = ["turso_core/antithesis"]

View File

@@ -38,7 +38,8 @@ pub mod transaction;
pub mod value;
use transaction::TransactionBehavior;
use turso_core::types::WalInsertInfo;
#[cfg(feature = "conn_raw_api")]
use turso_core::types::WalFrameInfo;
pub use value::Value;
pub use params::params_from_iter;
@@ -181,6 +182,7 @@ impl Connection {
stmt.execute(params).await
}
#[cfg(feature = "conn_raw_api")]
pub fn wal_frame_count(&self) -> Result<u64> {
let conn = self
.inner
@@ -190,6 +192,7 @@ impl Connection {
.map_err(|e| Error::WalOperationError(format!("wal_insert_begin failed: {e}")))
}
#[cfg(feature = "conn_raw_api")]
pub fn wal_insert_begin(&self) -> Result<()> {
let conn = self
.inner
@@ -199,6 +202,7 @@ impl Connection {
.map_err(|e| Error::WalOperationError(format!("wal_insert_begin failed: {e}")))
}
#[cfg(feature = "conn_raw_api")]
pub fn wal_insert_end(&self) -> Result<()> {
let conn = self
.inner
@@ -208,7 +212,8 @@ impl Connection {
.map_err(|e| Error::WalOperationError(format!("wal_insert_end failed: {e}")))
}
pub fn wal_insert_frame(&self, frame_no: u32, frame: &[u8]) -> Result<WalInsertInfo> {
#[cfg(feature = "conn_raw_api")]
pub fn wal_insert_frame(&self, frame_no: u32, frame: &[u8]) -> Result<WalFrameInfo> {
let conn = self
.inner
.lock()
@@ -217,6 +222,7 @@ impl Connection {
.map_err(|e| Error::WalOperationError(format!("wal_insert_frame failed: {e}")))
}
#[cfg(feature = "conn_raw_api")]
pub fn wal_get_frame(&self, frame_no: u32, frame: &mut [u8]) -> Result<()> {
let conn = self
.inner

View File

@@ -14,8 +14,9 @@ name = "turso_core"
path = "lib.rs"
[features]
antithesis = ["dep:antithesis_sdk"]
default = ["fs", "uuid", "time", "json", "series"]
antithesis = ["dep:antithesis_sdk"]
conn_raw_api = []
fs = ["turso_ext/vfs"]
json = []
uuid = ["dep:uuid"]

View File

@@ -44,8 +44,8 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
use crate::translate::optimizer::optimize_plan;
use crate::translate::pragma::TURSO_CDC_DEFAULT_TABLE_NAME;
#[cfg(feature = "fs")]
use crate::types::WalInsertInfo;
#[cfg(all(feature = "fs", feature = "conn_raw_api"))]
use crate::types::WalFrameInfo;
#[cfg(feature = "fs")]
use crate::util::{OpenMode, OpenOptions};
use crate::vtab::VirtualTable;
@@ -813,6 +813,7 @@ impl Connection {
/// Parse schema from scratch if version of schema for the connection differs from the schema cookie in the root page
/// This function must be called outside of any transaction because internally it will start transaction session by itself
#[allow(dead_code)]
fn maybe_reparse_schema(self: &Arc<Connection>) -> Result<()> {
let pager = self.pager.borrow().clone();
@@ -1140,12 +1141,12 @@ impl Connection {
Ok(())
}
#[cfg(feature = "fs")]
#[cfg(all(feature = "fs", feature = "conn_raw_api"))]
pub fn wal_frame_count(&self) -> Result<u64> {
self.pager.borrow().wal_frame_count()
}
#[cfg(feature = "fs")]
#[cfg(all(feature = "fs", feature = "conn_raw_api"))]
pub fn wal_get_frame(&self, frame_no: u32, frame: &mut [u8]) -> Result<()> {
let c = self.pager.borrow().wal_get_frame(frame_no, frame)?;
self._db.io.wait_for_completion(c)
@@ -1154,13 +1155,13 @@ impl Connection {
/// Insert `frame` (header included) at the position `frame_no` in the WAL
/// If WAL already has frame at that position - turso-db will compare content of the page and either report conflict or return OK
/// If attempt to write frame at the position `frame_no` will create gap in the WAL - method will return error
#[cfg(feature = "fs")]
pub fn wal_insert_frame(&self, frame_no: u32, frame: &[u8]) -> Result<WalInsertInfo> {
#[cfg(all(feature = "fs", feature = "conn_raw_api"))]
pub fn wal_insert_frame(&self, frame_no: u32, frame: &[u8]) -> Result<WalFrameInfo> {
self.pager.borrow().wal_insert_frame(frame_no, frame)
}
/// Start WAL session by initiating read+write transaction for this connection
#[cfg(feature = "fs")]
#[cfg(all(feature = "fs", feature = "conn_raw_api"))]
pub fn wal_insert_begin(&self) -> Result<()> {
let pager = self.pager.borrow();
match pager.begin_read_tx()? {
@@ -1181,7 +1182,7 @@ impl Connection {
/// Finish WAL session by ending read+write transaction taken in the [Self::wal_insert_begin] method
/// All frames written after last commit frame (db_size > 0) within the session will be rolled back
#[cfg(feature = "fs")]
#[cfg(all(feature = "fs", feature = "conn_raw_api"))]
pub fn wal_insert_end(self: &Arc<Connection>) -> Result<()> {
{
let pager = self.pager.borrow();

View File

@@ -6,7 +6,7 @@ use crate::storage::sqlite3_ondisk::{
self, parse_wal_frame_header, DatabaseHeader, PageContent, PageSize, PageType,
};
use crate::storage::wal::{CheckpointResult, Wal};
use crate::types::{IOResult, WalInsertInfo};
use crate::types::{IOResult, WalFrameInfo};
use crate::util::IOExt as _;
use crate::{return_if_io, Completion, TransactionState};
use crate::{turso_assert, Buffer, Connection, LimboError, Result};
@@ -1290,7 +1290,7 @@ impl Pager {
}
#[instrument(skip_all, level = Level::DEBUG)]
pub fn wal_insert_frame(&self, frame_no: u32, frame: &[u8]) -> Result<WalInsertInfo> {
pub fn wal_insert_frame(&self, frame_no: u32, frame: &[u8]) -> Result<WalFrameInfo> {
let Some(wal) = self.wal.as_ref() else {
return Err(LimboError::InternalError(
"wal_insert_frame() called on database without WAL".to_string(),
@@ -1323,9 +1323,9 @@ impl Pager {
}
self.dirty_pages.borrow_mut().clear();
}
Ok(WalInsertInfo {
page_no: header.page_number as usize,
is_commit: header.is_commit_frame(),
Ok(WalFrameInfo {
page_no: header.page_number,
db_size: header.db_size,
})
}

View File

@@ -2590,9 +2590,15 @@ pub struct DatabaseChange {
}
#[derive(Debug)]
pub struct WalInsertInfo {
pub page_no: usize,
pub is_commit: bool,
pub struct WalFrameInfo {
pub page_no: u32,
pub db_size: u32,
}
impl WalFrameInfo {
pub fn is_commit_frame(&self) -> bool {
self.db_size > 0
}
}
#[cfg(test)]

View File

@@ -7,8 +7,8 @@ license.workspace = true
repository.workspace = true
[dependencies]
turso_core = { workspace = true }
turso = { workspace = true }
turso_core = { workspace = true, features = ["conn_raw_api"] }
turso = { workspace = true, features = ["conn_raw_api"] }
thiserror = "2.0.12"
tracing = "0.1.41"
hyper = { version = "1.6.0", features = ["client", "http1"] }

View File

@@ -369,8 +369,8 @@ impl<S: SyncServer, F: Filesystem> DatabaseInner<S, F> {
if !wal_session.in_txn() {
wal_session.begin()?;
}
let wal_insert_info = clean_conn.wal_insert_frame(frame_no as u32, &buffer)?;
if wal_insert_info.is_commit {
let wal_frame_info = clean_conn.wal_insert_frame(frame_no as u32, &buffer)?;
if wal_frame_info.is_commit_frame() {
wal_session.end()?;
// transaction boundary reached - it's safe to commit progress
self.meta = Some(self.write_meta(|m| m.synced_frame_no = frame_no).await?);

View File

@@ -187,7 +187,7 @@ impl SyncServer for TestSyncServer {
let frame = &frames[offset..offset + FRAME_SIZE];
match session.conn.wal_insert_frame(frame_no as u32, frame) {
Ok(info) => {
if info.is_commit {
if info.is_commit_frame() {
if session.in_txn {
session.conn.wal_insert_end()?;
session.in_txn = false;

View File

@@ -24,7 +24,7 @@ crate-type = ["lib", "cdylib", "staticlib"]
[dependencies]
env_logger = { version = "0.11.3", default-features = false }
libc = "0.2.169"
turso_core = { path = "../core" }
turso_core = { path = "../core", features = ["conn_raw_api"] }
tracing = "0.1.41"
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }

View File

@@ -17,8 +17,8 @@ path = "integration/mod.rs"
[dependencies]
anyhow.workspace = true
env_logger = "0.10.1"
turso_core = { path = "../core" }
turso = { path = "../bindings/rust" }
turso_core = { path = "../core", features = ["conn_raw_api"] }
turso = { path = "../bindings/rust", features = ["conn_raw_api"] }
tokio = { version = "1.47", features = ["full"] }
rusqlite = { version = "0.34", features = ["bundled"] }
tempfile = "3.0.7"

View File

@@ -140,7 +140,7 @@ fn test_wal_frame_transfer_schema_changes() {
for frame_id in 1..=conn1.wal_frame_count().unwrap() as u32 {
conn1.wal_get_frame(frame_id, &mut frame).unwrap();
let info = conn2.wal_insert_frame(frame_id, &frame).unwrap();
if info.is_commit {
if info.is_commit_frame() {
commits += 1;
}
}