diff --git a/core/vdbe/execute.rs b/core/vdbe/execute.rs index 1bfc278a9..0902fdf79 100644 --- a/core/vdbe/execute.rs +++ b/core/vdbe/execute.rs @@ -1407,6 +1407,73 @@ pub fn op_vdestroy( Ok(InsnFunctionStepResult::Step) } +pub fn op_vbegin( + program: &Program, + state: &mut ProgramState, + insn: &Insn, + pager: &Arc, + mv_store: Option<&Arc>, +) -> Result { + load_insn!(VBegin { cursor_id }, insn); + let cursor = state.get_cursor(*cursor_id); + let cursor = cursor.as_virtual_mut(); + cursor.begin()?; + state.pc += 1; + Ok(InsnFunctionStepResult::Step) +} + +pub fn op_vcommit( + program: &Program, + state: &mut ProgramState, + insn: &Insn, + pager: &Arc, + mv_store: Option<&Arc>, +) -> Result { + load_insn!(VCommit { cursor_id }, insn); + let cursor = state.get_cursor(*cursor_id); + let cursor = cursor.as_virtual_mut(); + cursor.commit()?; + state.pc += 1; + Ok(InsnFunctionStepResult::Step) +} + +pub fn op_vrollback( + program: &Program, + state: &mut ProgramState, + insn: &Insn, + pager: &Arc, + mv_store: Option<&Arc>, +) -> Result { + load_insn!(VRollback { cursor_id }, insn); + let cursor = state.get_cursor(*cursor_id); + let cursor = cursor.as_virtual_mut(); + cursor.rollback()?; + state.pc += 1; + Ok(InsnFunctionStepResult::Step) +} + +pub fn op_vrename( + program: &Program, + state: &mut ProgramState, + insn: &Insn, + pager: &Arc, + mv_store: Option<&Arc>, +) -> Result { + load_insn!( + VRename { + cursor_id, + new_table_name + }, + insn + ); + let conn = program.connection.clone(); + let cursor = state.get_cursor(*cursor_id); + let cursor = cursor.as_virtual_mut(); + cursor.rename(&new_table_name)?; + state.pc += 1; + Ok(InsnFunctionStepResult::Step) +} + pub fn op_open_pseudo( program: &Program, state: &mut ProgramState, diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index ccaca542d..7da5f8b38 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -488,6 +488,42 @@ pub fn insn_to_row( 0, "".to_string(), ), + Insn::VBegin{cursor_id} => ( + "VBegin", + *cursor_id as i32, + 0, + 0, + Value::build_text(""), + 0, + "".into() + ), + Insn::VRollback{cursor_id} => ( + "VRollback", + *cursor_id as i32, + 0, + 0, + Value::build_text(""), + 0, + "".into(), + ), + Insn::VCommit{cursor_id} => ( + "VCommit", + *cursor_id as i32, + 0, + 0, + Value::build_text(""), + 0, + "".into(), + ), + Insn::VRename{cursor_id, new_table_name} => ( + "VCommit", + *cursor_id as i32, + 0, + 0, + Value::build_text(&new_table_name), + 0, + "".into(), + ), Insn::OpenPseudo { cursor_id, content_reg, diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 98693d718..acde18b5a 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -412,6 +412,24 @@ pub enum Insn { /// The database within which this virtual table needs to be destroyed (P1). db: usize, }, + VBegin { + /// The database within which this virtual table transaction needs to begin (P1). + cursor_id: CursorID, + }, + VRollback { + /// The database within which this virtual table transaction needs to rollback (P1). + cursor_id: CursorID, + }, + VCommit { + /// The database within which this virtual table transaction needs to commit (P1). + cursor_id: CursorID, + }, + VRename { + /// The database within which this virtual table needs to be renamed (P1). + cursor_id: CursorID, + /// New name of the virtual table (P2). + new_table_name: String, + }, /// Open a cursor for a pseudo-table that contains a single row. OpenPseudo { @@ -1383,6 +1401,10 @@ impl InsnVariants { InsnVariants::SequenceTest => execute::op_sequence_test, InsnVariants::FkCounter => execute::op_fk_counter, InsnVariants::FkIfZero => execute::op_fk_if_zero, + InsnVariants::VBegin => execute::op_vbegin, + InsnVariants::VCommit => execute::op_vcommit, + InsnVariants::VRollback => execute::op_vrollback, + InsnVariants::VRename => execute::op_vrename, } } } diff --git a/core/vtab.rs b/core/vtab.rs index 58ac7b4ee..f9df565db 100644 --- a/core/vtab.rs +++ b/core/vtab.rs @@ -225,6 +225,54 @@ impl VirtualTableCursor { VirtualTableCursor::Internal(cursor) => cursor.write().filter(&args, idx_str, idx_num), } } + + pub(crate) fn begin(&self) -> crate::Result<()> { + match self { + VirtualTableCursor::Pragma(_) => Err(LimboError::ExtensionError( + "Pragma virtual tables do not support transactions".to_string(), + )), + VirtualTableCursor::External(cursor) => cursor.begin(), + VirtualTableCursor::Internal(_) => Err(LimboError::ExtensionError( + "Internal virtual tables currently do not support transactions".to_string(), + )), + } + } + + pub(crate) fn rollback(&self) -> crate::Result<()> { + match self { + VirtualTableCursor::Pragma(_) => Err(LimboError::ExtensionError( + "Pragma virtual tables do not support transactions".to_string(), + )), + VirtualTableCursor::External(cursor) => cursor.rollback(), + VirtualTableCursor::Internal(_) => Err(LimboError::ExtensionError( + "Internal virtual tables currently do not support transactions".to_string(), + )), + } + } + + pub(crate) fn commit(&self) -> crate::Result<()> { + match self { + VirtualTableCursor::Pragma(_) => Err(LimboError::ExtensionError( + "Pragma virtual tables do not support transactions".to_string(), + )), + VirtualTableCursor::External(cursor) => cursor.commit(), + VirtualTableCursor::Internal(_) => Err(LimboError::ExtensionError( + "Internal virtual tables currently do not support transactions".to_string(), + )), + } + } + + pub(crate) fn rename(&self, new_name: &str) -> crate::Result<()> { + match self { + VirtualTableCursor::Pragma(_) => Err(LimboError::ExtensionError( + "Pragma virtual tables do not support renaming".to_string(), + )), + VirtualTableCursor::External(cursor) => cursor.rename(new_name), + VirtualTableCursor::Internal(_) => Err(LimboError::ExtensionError( + "Internal virtual tables currently do not support renaming".to_string(), + )), + } + } } #[derive(Debug)] @@ -374,6 +422,39 @@ impl ExtVirtualTableCursor { unsafe { (self.implementation.rowid)(self.cursor.as_ptr()) } } + fn begin(&self) -> crate::Result<()> { + let rc = unsafe { (self.implementation.begin)(self.cursor.as_ptr()) }; + match rc { + ResultCode::OK => Ok(()), + _ => Err(LimboError::ExtensionError("Begin failed".to_string())), + } + } + + fn rollback(&self) -> crate::Result<()> { + let rc = unsafe { (self.implementation.rollback)(self.cursor.as_ptr()) }; + match rc { + ResultCode::OK => Ok(()), + _ => Err(LimboError::ExtensionError("Rollback failed".to_string())), + } + } + + fn commit(&self) -> crate::Result<()> { + let rc = unsafe { (self.implementation.commit)(self.cursor.as_ptr()) }; + match rc { + ResultCode::OK => Ok(()), + _ => Err(LimboError::ExtensionError("Commit failed".to_string())), + } + } + + fn rename(&self, new_name: &str) -> crate::Result<()> { + let c_new_name = std::ffi::CString::new(new_name).unwrap(); + let rc = unsafe { (self.implementation.rename)(self.cursor.as_ptr(), c_new_name.as_ptr()) }; + match rc { + ResultCode::OK => Ok(()), + _ => Err(LimboError::ExtensionError("Rename failed".to_string())), + } + } + #[tracing::instrument(skip(self))] fn filter( &self, diff --git a/extensions/core/src/vtabs.rs b/extensions/core/src/vtabs.rs index 449b26269..e3856c74b 100644 --- a/extensions/core/src/vtabs.rs +++ b/extensions/core/src/vtabs.rs @@ -28,6 +28,10 @@ pub struct VTabModuleImpl { pub rowid: VtabRowIDFn, pub destroy: VtabFnDestroy, pub best_idx: BestIdxFn, + pub begin: VtabBegin, + pub commit: VtabCommit, + pub rollback: VtabRollback, + pub rename: VtabRename, } // SAFETY: VTabModuleImpl contains function pointers and a name pointer that are @@ -108,6 +112,12 @@ pub type VtabFnUpdate = unsafe extern "C" fn( pub type VtabFnDestroy = unsafe extern "C" fn(table: *const c_void) -> ResultCode; +pub type VtabBegin = unsafe extern "C" fn(table: *mut c_void) -> ResultCode; +pub type VtabCommit = unsafe extern "C" fn(table: *mut c_void) -> ResultCode; +pub type VtabRollback = unsafe extern "C" fn(table: *mut c_void) -> ResultCode; +pub type VtabRename = + unsafe extern "C" fn(table: *mut c_void, new_name: *const c_char) -> ResultCode; + pub type BestIdxFn = unsafe extern "C" fn( constraints: *const ConstraintInfo, constraint_len: i32, @@ -140,9 +150,21 @@ pub trait VTable { /// 'conn' is an Option to allow for testing. Otherwise a valid connection to the core database /// that created the virtual table will be available to use in your extension here. fn open(&self, _conn: Option>) -> Result; + fn begin(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + fn commit(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + fn rollback(&mut self) -> Result<(), Self::Error> { + Ok(()) + } fn update(&mut self, _rowid: i64, _args: &[Value]) -> Result<(), Self::Error> { Ok(()) } + fn rename(&mut self, _new_name: &str) -> Result<(), Self::Error> { + Ok(()) + } fn insert(&mut self, _args: &[Value]) -> Result { Ok(0) } diff --git a/macros/src/ext/vtab_derive.rs b/macros/src/ext/vtab_derive.rs index 4ca040617..966b88831 100644 --- a/macros/src/ext/vtab_derive.rs +++ b/macros/src/ext/vtab_derive.rs @@ -18,6 +18,10 @@ pub fn derive_vtab_module(input: TokenStream) -> TokenStream { let rowid_fn_name = format_ident!("rowid_{}", struct_name); let destroy_fn_name = format_ident!("destroy_{}", struct_name); let best_idx_fn_name = format_ident!("best_idx_{}", struct_name); + let begin_fn_name = format_ident!("begin_{}", struct_name); + let rollback_fn_name = format_ident!("rollback_{}", struct_name); + let commit_fn_name = format_ident!("commit_{}", struct_name); + let rename_fn_name = format_ident!("rename_{}", struct_name); let expanded = quote! { impl #struct_name { @@ -227,6 +231,75 @@ pub fn derive_vtab_module(input: TokenStream) -> TokenStream { } } + #[no_mangle] + pub unsafe extern "C" fn #begin_fn_name( + table: *mut ::std::ffi::c_void, + ) -> ::turso_ext::ResultCode { + let table = if table.is_null() { + return ::turso_ext::ResultCode::Error; + } else { + &mut *(table as *mut <#struct_name as ::turso_ext::VTabModule>::Table) + }; + if <#struct_name as ::turso_ext::VTabModule>::Table::begin(table).is_err() { + return ::turso_ext::ResultCode::Error; + } + ::turso_ext::ResultCode::OK + } + + #[no_mangle] + pub unsafe extern "C" fn #rollback_fn_name( + table: *mut ::std::ffi::c_void, + ) -> ::turso_ext::ResultCode { + let table = if table.is_null() { + return ::turso_ext::ResultCode::Error; + } else { + &mut *(table as *mut <#struct_name as ::turso_ext::VTabModule>::Table) + }; + if <#struct_name as ::turso_ext::VTabModule>::Table::rollback(table).is_err() { + return ::turso_ext::ResultCode::Error; + } + ::turso_ext::ResultCode::OK + } + + #[no_mangle] + pub unsafe extern "C" fn #commit_fn_name( + table: *mut ::std::ffi::c_void, + ) -> ::turso_ext::ResultCode { + let table = if table.is_null() { + return ::turso_ext::ResultCode::Error; + } else { + &mut *(table as *mut <#struct_name as ::turso_ext::VTabModule>::Table) + }; + if <#struct_name as ::turso_ext::VTabModule>::Table::commit(table).is_err() { + return ::turso_ext::ResultCode::Error; + } + ::turso_ext::ResultCode::OK + } + + #[no_mangle] + pub unsafe extern "C" fn #rename_fn_name( + table: *mut ::std::ffi::c_void, + name: *const ::std::ffi::c_char, + ) -> ::turso_ext::ResultCode { + let table = if table.is_null() { + return ::turso_ext::ResultCode::Error; + } else { + &mut *(table as *mut <#struct_name as ::turso_ext::VTabModule>::Table) + }; + let name_str = if name.is_null() { + return ::turso_ext::ResultCode::Error; + } else { + match ::std::ffi::CStr::from_ptr(name).to_str() { + Ok(s) => s, + Err(_) => return ::turso_ext::ResultCode::Error, + } + }; + if <#struct_name as ::turso_ext::VTabModule>::Table::rename(table, name_str).is_err() { + return ::turso_ext::ResultCode::Error; + } + ::turso_ext::ResultCode::OK + } + #[no_mangle] pub unsafe extern "C" fn #register_fn_name( api: *const ::turso_ext::ExtensionApi @@ -251,6 +324,10 @@ pub fn derive_vtab_module(input: TokenStream) -> TokenStream { rowid: Self::#rowid_fn_name, destroy: Self::#destroy_fn_name, best_idx: Self::#best_idx_fn_name, + begin: Self::#begin_fn_name, + rollback: Self::#rollback_fn_name, + commit: Self::#commit_fn_name, + rename: Self::#rename_fn_name, }; (api.register_vtab_module)(api.ctx, name_c, module, <#struct_name as ::turso_ext::VTabModule>::VTAB_KIND) }