Skip to content

Commit b233c3f

Browse files
committed
Add Dataset::maybe_batch for faster dataset write when possible
1 parent 9692dae commit b233c3f

File tree

3 files changed

+106
-1
lines changed

3 files changed

+106
-1
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
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))
2626
- Add `Dataset::has_capability` for dataset capability check ([#581](https://github.com/georust/gdal/pull/585))
27+
- Add `Dataset::maybe_batch` which always succeeds to use transaction when possible for speed reasons -- rollbacks if operation fails under transaction ([#584](https://github.com/georust/gdal/pull/584))
2728

2829
### Fixed
2930

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

+70-1
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,54 @@ 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. If the
185+
/// closure results in error, and it is a transaction, it is
186+
/// rolled back.
187+
pub fn maybe_batch(&mut self, func: impl Fn(&Dataset) -> Result<()>) -> Result<()> {
188+
let force = 0; // since this is for speed
189+
let rv = unsafe { gdal_sys::GDALDatasetStartTransaction(self.c_dataset(), force) };
190+
if rv == OGRErr::OGRERR_UNSUPPORTED_OPERATION {
191+
func(self)
192+
} else if rv == OGRErr::OGRERR_NONE {
193+
let res = func(self);
194+
if res.is_ok() {
195+
let rv = unsafe { gdal_sys::GDALDatasetCommitTransaction(self.c_dataset()) };
196+
if rv != OGRErr::OGRERR_NONE {
197+
Err(GdalError::OgrError {
198+
err: rv,
199+
method_name: "GDALDatasetCommitTransaction",
200+
})
201+
} else {
202+
res
203+
}
204+
} else {
205+
let rv = unsafe { gdal_sys::GDALDatasetRollbackTransaction(self.c_dataset()) };
206+
if rv != OGRErr::OGRERR_NONE {
207+
Err(GdalError::OgrError {
208+
err: rv,
209+
method_name: "GDALDatasetRollbackTransaction",
210+
})
211+
} else {
212+
res
213+
}
214+
}
215+
} else {
216+
// transaction supported but failed
217+
Err(GdalError::OgrError {
218+
err: rv,
219+
method_name: "GDALDatasetStartTransaction",
220+
})
221+
}
222+
}
180223
}
181224

182225
#[cfg(test)]
183226
mod tests {
184-
use crate::test_utils::{fixture, open_gpkg_for_update};
227+
use crate::test_utils::{fixture, open_dataset_for_update, open_gpkg_for_update};
185228
use crate::vector::{Geometry, LayerAccess};
186229
use crate::Dataset;
187230

@@ -241,4 +284,30 @@ mod tests {
241284
let mut ds = Dataset::open(fixture("roads.geojson")).unwrap();
242285
assert!(ds.start_transaction().is_err());
243286
}
287+
288+
#[test]
289+
fn test_maybe_batch() {
290+
let (_temp_path, mut ds) = open_gpkg_for_update(&fixture("poly.gpkg"));
291+
let orig_feature_count = ds.layer(0).unwrap().feature_count();
292+
293+
let res = ds.maybe_batch(|d| {
294+
let mut layer = d.layer(0).unwrap();
295+
layer.create_feature(polygon())
296+
});
297+
assert!(res.is_ok());
298+
assert_eq!(ds.layer(0).unwrap().feature_count(), orig_feature_count + 1);
299+
}
300+
301+
#[test]
302+
fn test_maybe_transaction_unsupported() {
303+
let (_temp_path, mut ds) = open_dataset_for_update(&fixture("roads.geojson"));
304+
let orig_feature_count = ds.layer(0).unwrap().feature_count();
305+
306+
let res = ds.maybe_batch(|d| {
307+
let mut layer = d.layer(0).unwrap();
308+
layer.create_feature(polygon())
309+
});
310+
assert!(res.is_ok());
311+
assert_eq!(ds.layer(0).unwrap().feature_count(), orig_feature_count + 1);
312+
}
244313
}

0 commit comments

Comments
 (0)