-
Notifications
You must be signed in to change notification settings - Fork 94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add MaybeTransaction for better writing speed when supported #584
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -179,9 +179,93 @@ impl Dataset { | |
} | ||
} | ||
|
||
/// Represents maybe a transaction on a dataset for speed reasons. | ||
/// | ||
/// It can be committed by calling [`commit`](MaybeTransaction::commit), but unlike [`Transaction`] it can not be rolled back. The transaction part is to make the process speedy when possible. If the [`Dataset`] does not support transaction, it does nothing. | ||
/// | ||
/// If the transaction is not explicitly committed when it is dropped, it is implicitly committed, but you will not know if it fails. | ||
/// | ||
/// The transaction holds a mutable borrow on the `Dataset` that it was created from, so during the | ||
/// lifetime of the transaction you will need to access the dataset by dereferencing the | ||
/// `MaybeTransaction` through its [`Deref`] or [`DerefMut`] implementations. | ||
#[derive(Debug)] | ||
pub struct MaybeTransaction<'a> { | ||
dataset: &'a mut Dataset, | ||
is_transaction: bool, | ||
commit_on_drop: bool, | ||
} | ||
|
||
impl<'a> MaybeTransaction<'a> { | ||
fn new(dataset: &'a mut Dataset, is_transaction: bool) -> Self { | ||
MaybeTransaction { | ||
dataset, | ||
is_transaction, | ||
commit_on_drop: true, | ||
} | ||
} | ||
|
||
pub fn is_transaction(&self) -> bool { | ||
self.is_transaction | ||
} | ||
|
||
/// Commits this transaction. | ||
/// | ||
/// If the commit fails, will return [`OGRErr::OGRERR_FAILURE`]. | ||
/// | ||
/// Depending on drivers, this may or may not abort layer sequential readings that are active. | ||
pub fn commit(mut self) -> Result<()> { | ||
if !self.is_transaction { | ||
return Ok(()); | ||
} | ||
|
||
let rv = unsafe { gdal_sys::GDALDatasetCommitTransaction(self.dataset.c_dataset()) }; | ||
self.commit_on_drop = false; | ||
if rv != OGRErr::OGRERR_NONE { | ||
return Err(GdalError::OgrError { | ||
err: rv, | ||
method_name: "GDALDatasetCommitTransaction", | ||
}); | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl<'a> Deref for MaybeTransaction<'a> { | ||
type Target = Dataset; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
self.dataset | ||
} | ||
} | ||
|
||
impl<'a> DerefMut for MaybeTransaction<'a> { | ||
fn deref_mut(&mut self) -> &mut Self::Target { | ||
self.dataset | ||
} | ||
} | ||
|
||
impl Dataset { | ||
pub fn maybe_start_transaction(&mut self) -> MaybeTransaction<'_> { | ||
let force = 1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should add at some point that parameter for I think it only works for FileGDB, and it's implemented by making a file system-level copy of the whole dataset, and restoring it on rollback. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that won't help with performance, I think if we want transaction for rollback purposes vs want transaction for speed purposes, I think we should have different methods. The That's why I was thinking maybe some other name would be better. The current interpretation of On that note I think we can add the force thing to normal |
||
let rv = unsafe { gdal_sys::GDALDatasetStartTransaction(self.c_dataset(), force) }; | ||
|
||
MaybeTransaction::new(self, rv == OGRErr::OGRERR_NONE) | ||
} | ||
} | ||
|
||
impl<'a> Drop for MaybeTransaction<'a> { | ||
fn drop(&mut self) { | ||
if self.commit_on_drop { | ||
// We silently swallow any errors, because we have no way to report them from a drop | ||
// function apart from panicking. | ||
unsafe { gdal_sys::GDALDatasetCommitTransaction(self.dataset.c_dataset()) }; | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::test_utils::{fixture, open_gpkg_for_update}; | ||
use crate::test_utils::{fixture, open_dataset_for_update, open_gpkg_for_update}; | ||
use crate::vector::{Geometry, LayerAccess}; | ||
use crate::Dataset; | ||
|
||
|
@@ -241,4 +325,54 @@ mod tests { | |
let mut ds = Dataset::open(fixture("roads.geojson")).unwrap(); | ||
assert!(ds.start_transaction().is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_maybe_start_transaction() { | ||
let (_temp_path, mut ds) = open_gpkg_for_update(&fixture("poly.gpkg")); | ||
let txn = ds.maybe_start_transaction(); | ||
assert!(txn.is_transaction()); | ||
let mut ds = Dataset::open(fixture("roads.geojson")).unwrap(); | ||
let txn = ds.maybe_start_transaction(); | ||
assert!(!txn.is_transaction()); | ||
} | ||
|
||
#[test] | ||
fn test_maybe_transaction_commit() { | ||
let (_temp_path, mut ds) = open_gpkg_for_update(&fixture("poly.gpkg")); | ||
let orig_feature_count = ds.layer(0).unwrap().feature_count(); | ||
|
||
let txn = ds.maybe_start_transaction(); | ||
let mut layer = txn.layer(0).unwrap(); | ||
layer.create_feature(polygon()).unwrap(); | ||
assert!(txn.commit().is_ok()); | ||
|
||
assert_eq!(ds.layer(0).unwrap().feature_count(), orig_feature_count + 1); | ||
} | ||
|
||
#[test] | ||
fn test_maybe_transaction_commit_unsupported() { | ||
let (_temp_path, mut ds) = open_dataset_for_update(&fixture("roads.geojson")); | ||
let orig_feature_count = ds.layer(0).unwrap().feature_count(); | ||
|
||
let txn = ds.maybe_start_transaction(); | ||
let mut layer = txn.layer(0).unwrap(); | ||
layer.create_feature(polygon()).unwrap(); | ||
assert!(txn.commit().is_ok()); | ||
|
||
assert_eq!(ds.layer(0).unwrap().feature_count(), orig_feature_count + 1); | ||
} | ||
|
||
#[test] | ||
fn test_maybe_transaction_implicit_commit() { | ||
let (_temp_path, mut ds) = open_gpkg_for_update(&fixture("poly.gpkg")); | ||
let orig_feature_count = ds.layer(0).unwrap().feature_count(); | ||
|
||
{ | ||
let txn = ds.maybe_start_transaction(); | ||
let mut layer = txn.layer(0).unwrap(); | ||
layer.create_feature(polygon()).unwrap(); | ||
} // txn is dropped here. | ||
|
||
assert_eq!(ds.layer(0).unwrap().feature_count(), orig_feature_count + 1); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be reasonable to go with a callback based approach here instead of having that guard object. This should make it much easier to handle errors on committing.
That would give you the following function signature:
For the implementation of that pattern see diesels transaction method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for late response. I made it quickly, just copying the mechanism that is being already used by the transaction. It gives uniformity to the transaction API.