Skip to content

Commit 1994f72

Browse files
committed
Add Dataset::maybe_transaction for faster database write
1 parent b448b08 commit 1994f72

File tree

3 files changed

+83
-1
lines changed

3 files changed

+83
-1
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
- Add methods `alternative_name`, `is_nullable`, `is_unique`, `default_value` to `Field` ([#561](https://github.com/georust/gdal/pull/561))
2424
- Add `Defn::geometry_type` ([#562](https://github.com/georust/gdal/pull/562))
2525
- Add `Defn::field_index` and `Feature::field_index` ([#581](https://github.com/georust/gdal/pull/581))
26+
- Add `Dataset::maybe_batch` which always succeeds to use transaction when possible for speed reasons -- no rollback ([#584](https://github.com/georust/gdal/pull/584))
2627

2728
### Fixed
2829

src/test_utils.rs

+35
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,41 @@ pub fn open_gpkg_for_update(path: &Path) -> (TempPath, Dataset) {
136136
(temp_path, ds)
137137
}
138138

139+
/// Copies the given file to a temporary file and opens it for writing. When the returned
140+
/// `TempPath` is dropped, the file is deleted.
141+
pub fn open_dataset_for_update(path: &Path) -> (TempPath, Dataset) {
142+
use std::fs;
143+
use std::io::Write;
144+
145+
let input_data = fs::read(path).unwrap();
146+
let (mut file, temp_path) = tempfile::Builder::new()
147+
// using the whole filename as suffix should be fine (can't
148+
// use .extension() for .shp.zip and such)
149+
.suffix(
150+
path.file_name()
151+
.unwrap_or_default()
152+
.to_string_lossy()
153+
.as_ref(),
154+
)
155+
.tempfile()
156+
.unwrap()
157+
.into_parts();
158+
file.write_all(&input_data).unwrap();
159+
// Close the temporary file so that Dataset can open it safely even if the filesystem uses
160+
// exclusive locking (Windows?).
161+
drop(file);
162+
163+
let ds = Dataset::open_ex(
164+
&temp_path,
165+
DatasetOptions {
166+
open_flags: GDALAccess::GA_Update.into(),
167+
..DatasetOptions::default()
168+
},
169+
)
170+
.unwrap();
171+
(temp_path, ds)
172+
}
173+
139174
/// Assert numerical difference between two expressions is less than
140175
/// 64-bit machine epsilon or a specified epsilon.
141176
///

src/vector/transaction.rs

+47-1
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,31 @@ impl Dataset {
177177
}
178178
Ok(Transaction::new(self))
179179
}
180+
181+
/// Optionally start a transaction before running `func` for performance
182+
///
183+
/// This uses transaction if the dataset supports it, otherwise it
184+
/// runs the `func` function as it is on the dataset.
185+
pub fn maybe_batch(&mut self, func: impl Fn(&Dataset) -> Result<()>) -> Result<()> {
186+
let force = 0; // since this is for speed
187+
let rv = unsafe { gdal_sys::GDALDatasetStartTransaction(self.c_dataset(), force) };
188+
let res = func(self);
189+
if rv == OGRErr::OGRERR_NONE {
190+
let rv = unsafe { gdal_sys::GDALDatasetCommitTransaction(self.c_dataset()) };
191+
if rv != OGRErr::OGRERR_NONE {
192+
return Err(GdalError::OgrError {
193+
err: rv,
194+
method_name: "GDALDatasetCommitTransaction",
195+
});
196+
}
197+
}
198+
res
199+
}
180200
}
181201

182202
#[cfg(test)]
183203
mod tests {
184-
use crate::test_utils::{fixture, open_gpkg_for_update};
204+
use crate::test_utils::{fixture, open_dataset_for_update, open_gpkg_for_update};
185205
use crate::vector::{Geometry, LayerAccess};
186206
use crate::Dataset;
187207

@@ -241,4 +261,30 @@ mod tests {
241261
let mut ds = Dataset::open(fixture("roads.geojson")).unwrap();
242262
assert!(ds.start_transaction().is_err());
243263
}
264+
265+
#[test]
266+
fn test_maybe_transaction() {
267+
let (_temp_path, mut ds) = open_gpkg_for_update(&fixture("poly.gpkg"));
268+
let orig_feature_count = ds.layer(0).unwrap().feature_count();
269+
270+
let res = ds.maybe_transaction(|d| {
271+
let mut layer = d.layer(0).unwrap();
272+
layer.create_feature(polygon())
273+
});
274+
assert!(res.is_ok());
275+
assert_eq!(ds.layer(0).unwrap().feature_count(), orig_feature_count + 1);
276+
}
277+
278+
#[test]
279+
fn test_maybe_transaction_unsupported() {
280+
let (_temp_path, mut ds) = open_dataset_for_update(&fixture("roads.geojson"));
281+
let orig_feature_count = ds.layer(0).unwrap().feature_count();
282+
283+
let res = ds.maybe_transaction(|d| {
284+
let mut layer = d.layer(0).unwrap();
285+
layer.create_feature(polygon())
286+
});
287+
assert!(res.is_ok());
288+
assert_eq!(ds.layer(0).unwrap().feature_count(), orig_feature_count + 1);
289+
}
244290
}

0 commit comments

Comments
 (0)