From 0c056c97d590be3a84dd2b634cd50a92b707018f Mon Sep 17 00:00:00 2001 From: Sergey Kiselev Date: Sun, 3 Jan 2021 11:35:00 +0300 Subject: [PATCH 01/14] Add methods for accessing rasterband color interpretations --- src/raster/mod.rs | 4 +++- src/raster/rasterband.rs | 27 +++++++++++++++++++++++++-- src/raster/tests.rs | 40 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/raster/mod.rs b/src/raster/mod.rs index 6f1579be..9bca98c7 100644 --- a/src/raster/mod.rs +++ b/src/raster/mod.rs @@ -4,7 +4,9 @@ mod rasterband; mod types; mod warp; -pub use rasterband::{Buffer, ByteBuffer, RasterBand}; +pub use rasterband::{ + get_color_interpretation_by_name, get_color_interpretation_name, Buffer, ByteBuffer, RasterBand, +}; pub use types::{GDALDataType, GdalType}; pub use warp::reproject; diff --git a/src/raster/rasterband.rs b/src/raster/rasterband.rs index a05b3210..49f4ea86 100644 --- a/src/raster/rasterband.rs +++ b/src/raster/rasterband.rs @@ -4,9 +4,10 @@ use crate::dataset::Dataset; use crate::gdal_major_object::MajorObject; use crate::metadata::Metadata; use crate::raster::{GDALDataType, GdalType}; -use crate::utils::_last_cpl_err; -use gdal_sys::{self, CPLErr, GDALMajorObjectH, GDALRWFlag, GDALRasterBandH}; +use crate::utils::{_last_cpl_err, _string}; +use gdal_sys::{self, CPLErr, GDALColorInterp, GDALMajorObjectH, GDALRWFlag, GDALRasterBandH}; use libc::c_int; +use std::ffi::CString; #[cfg(feature = "ndarray")] use ndarray::Array2; @@ -260,6 +261,18 @@ impl<'a> RasterBand<'a> { Ok(()) } + pub fn get_color_interpretation(&self) -> GDALColorInterp::Type { + unsafe { gdal_sys::GDALGetRasterColorInterpretation(self.c_rasterband) } + } + + pub fn set_color_interpretation(&mut self, interp: GDALColorInterp::Type) -> Result<()> { + let rv = unsafe { gdal_sys::GDALSetRasterColorInterpretation(self.c_rasterband, interp) }; + if rv != CPLErr::CE_None { + return Err(_last_cpl_err(rv)); + } + Ok(()) + } + pub fn scale(&self) -> Option { let mut pb_success = 1; let scale = unsafe { gdal_sys::GDALGetRasterScale(self.c_rasterband, &mut pb_success) }; @@ -320,3 +333,13 @@ impl Buffer { } pub type ByteBuffer = Buffer; + +pub fn get_color_interpretation_name(color_interp: GDALColorInterp::Type) -> String { + let rv = unsafe { gdal_sys::GDALGetColorInterpretationName(color_interp) }; + _string(rv) +} + +pub fn get_color_interpretation_by_name(interp_name: &str) -> Result { + let c_str_interp_name = CString::new(interp_name)?; + Ok(unsafe { gdal_sys::GDALGetColorInterpretationByName(c_str_interp_name.as_ptr()) }) +} diff --git a/src/raster/tests.rs b/src/raster/tests.rs index 99873b55..07207d1c 100644 --- a/src/raster/tests.rs +++ b/src/raster/tests.rs @@ -1,8 +1,8 @@ use crate::dataset::Dataset; use crate::metadata::Metadata; -use crate::raster::ByteBuffer; +use crate::raster::{get_color_interpretation_by_name, get_color_interpretation_name, ByteBuffer}; use crate::Driver; -use gdal_sys::GDALDataType; +use gdal_sys::{GDALColorInterp, GDALDataType}; use std::path::Path; #[cfg(feature = "ndarray")] @@ -441,3 +441,39 @@ fn test_get_rasterband_actual_block_size() { let size = rasterband.actual_block_size((0, 40)); assert_eq!(size.unwrap(), (100, 1)); } + +#[test] +fn test_get_rasterband_color_interp() { + let dataset = Dataset::open(fixture!("tinymarble.png")).unwrap(); + let rasterband = dataset.rasterband(1).unwrap(); + let band_interp = rasterband.get_color_interpretation(); + assert_eq!(band_interp, GDALColorInterp::GCI_RedBand); +} + +#[test] +fn test_set_rasterband_color_interp() { + let driver = Driver::get("MEM").unwrap(); + let dataset = driver.create("", 1, 1, 1).unwrap(); + let mut rasterband = dataset.rasterband(1).unwrap(); + rasterband + .set_color_interpretation(GDALColorInterp::GCI_AlphaBand) + .unwrap(); + let band_interp = rasterband.get_color_interpretation(); + assert_eq!(band_interp, GDALColorInterp::GCI_AlphaBand); +} + +#[test] +fn test_color_interp_names() { + assert_eq!( + get_color_interpretation_name(GDALColorInterp::GCI_AlphaBand), + "Alpha" + ); + assert_eq!( + get_color_interpretation_by_name("Alpha").unwrap(), + GDALColorInterp::GCI_AlphaBand + ); + assert_eq!( + get_color_interpretation_by_name("not a valid name").unwrap(), + GDALColorInterp::GCI_Undefined + ); +} From b18bbfe2894acbb0e6a6f963bd68ec7cdd16005a Mon Sep 17 00:00:00 2001 From: Sergey Kiselev Date: Thu, 7 Jan 2021 17:40:13 +0300 Subject: [PATCH 02/14] Add CHANGES entry --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 89ed4266..54c16977 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ # Changes +## Unreleased +* Add support for reading and setting rasterband colour interpretations + ## 0.6.0 - 0.7.0 * Dataset layer iteration and FieldValue types * https://github.com/georust/gdal/pull/126 From 118f224d0a043637c23a2e5e0881b7d8a8c7240f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Feb 2021 07:06:46 +0000 Subject: [PATCH 03/14] Update bindgen requirement from 0.56 to 0.57 Updates the requirements on [bindgen](https://github.com/rust-lang/rust-bindgen) to permit the latest version. - [Release notes](https://github.com/rust-lang/rust-bindgen/releases) - [Changelog](https://github.com/rust-lang/rust-bindgen/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/rust-bindgen/compare/v0.56.0...v0.57.0) Signed-off-by: dependabot[bot] --- gdal-sys/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdal-sys/Cargo.toml b/gdal-sys/Cargo.toml index 7f1b35ff..3134e533 100644 --- a/gdal-sys/Cargo.toml +++ b/gdal-sys/Cargo.toml @@ -11,6 +11,6 @@ edition = "2018" libc = "0.2" [build-dependencies] -bindgen = { version = "0.56", optional = true } +bindgen = { version = "0.57", optional = true } pkg-config = "0.3" semver = "0.11" From 0ec08f62b35f666ee6e6b5edfac975ade0192a22 Mon Sep 17 00:00:00 2001 From: David Marteau Date: Thu, 4 Feb 2021 11:38:36 +0100 Subject: [PATCH 04/14] Implement wrapper for OGR_L_TestCapability --- CHANGES.md | 2 + src/vector/layer.rs | 73 ++++++++++++++++++++++++++++++++++ src/vector/mod.rs | 2 +- src/vector/vector_tests/mod.rs | 15 ++++++- 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e24c393b..ce43f715 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased +* Implement wrapper for `OGR_L_TestCapability` + * * **Breaking**: Use `DatasetOptions` to pass as `Dataset::open_ex` parameters and add support for extended open flags. diff --git a/src/vector/layer.rs b/src/vector/layer.rs index ad32664d..34ef5bfe 100644 --- a/src/vector/layer.rs +++ b/src/vector/layer.rs @@ -13,6 +13,73 @@ use std::{convert::TryInto, ffi::CString, marker::PhantomData}; use crate::errors::*; +/// Layer capabilities +pub enum LayerCaps { + /// Layer capability for random read + OLCRandomRead, + /// Layer capability for sequential write + OLCSequentialWrite, + /// Layer capability for random write + OLCRandomWrite, + /// Layer capability for fast spatial filter + OLCFastSpatialFilter, + /// Layer capability for fast feature count retrieval + OLCFastFeatureCount, + /// Layer capability for fast extent retrieval + OLCFastGetExtent, + /// Layer capability for field creation + OLCCreateField, + /// Layer capability for field deletion + OLCDeleteField, + /// Layer capability for field reordering + OLCReorderFields, + /// Layer capability for field alteration + OLCAlterFieldDefn, + /// Layer capability for transactions + OLCTransactions, + /// Layer capability for feature deletiond + OLCDeleteFeature, + /// Layer capability for setting next feature index + OLCFastSetNextByIndex, + /// Layer capability for strings returned with UTF-8 encoding + OLCStringsAsUTF8, + /// Layer capability for field ignoring + OLCIgnoreFields, + /// Layer capability for geometry field creation + OLCCreateGeomField, + /// Layer capability for curve geometries support + OLCCurveGeometries, + /// Layer capability for measured geometries support + OLCMeasuredGeometries, +} + +// Manage conversion to Gdal values +impl LayerCaps { + fn into_cstring(self) -> CString { + CString::new(match self { + Self::OLCRandomRead => "RandomRead", + Self::OLCSequentialWrite => "SequentialWrite", + Self::OLCRandomWrite => "RandomWrite", + Self::OLCFastSpatialFilter => "FastSpatialFilter", + Self::OLCFastFeatureCount => "FastFeatureCount", + Self::OLCFastGetExtent => "FastGetExtent", + Self::OLCCreateField => "CreateField", + Self::OLCDeleteField => "DeleteField", + Self::OLCReorderFields => "ReorderFields", + Self::OLCAlterFieldDefn => "AlterFieldDefn", + Self::OLCTransactions => "Transactions", + Self::OLCDeleteFeature => "DeleteFeature", + Self::OLCFastSetNextByIndex => "FastSetNextByIndex", + Self::OLCStringsAsUTF8 => "StringsAsUTF8", + Self::OLCIgnoreFields => "IgnoreFields", + Self::OLCCreateGeomField => "CreateGeomField", + Self::OLCCurveGeometries => "CurveGeometries", + Self::OLCMeasuredGeometries => "MeasuredGeometries", + }) + .unwrap() + } +} + /// Layer in a vector dataset /// /// ``` @@ -101,6 +168,12 @@ impl<'a> Layer<'a> { _string(rv) } + pub fn has_capability(&self, capability: LayerCaps) -> bool { + unsafe { + gdal_sys::OGR_L_TestCapability(self.c_layer, capability.into_cstring().as_ptr()) == 1 + } + } + pub fn defn(&self) -> &Defn { &self.defn } diff --git a/src/vector/mod.rs b/src/vector/mod.rs index 4c6b9e90..651b2f6c 100644 --- a/src/vector/mod.rs +++ b/src/vector/mod.rs @@ -27,7 +27,7 @@ pub use defn::{Defn, Field, FieldIterator}; pub use feature::{Feature, FieldValue, FieldValueIterator}; pub use gdal_sys::{OGRFieldType, OGRwkbGeometryType}; pub use geometry::Geometry; -pub use layer::{FeatureIterator, FieldDefn, Layer}; +pub use layer::{FeatureIterator, FieldDefn, Layer, LayerCaps}; pub use ops::GeometryIntersection; use crate::errors::Result; diff --git a/src/vector/vector_tests/mod.rs b/src/vector/vector_tests/mod.rs index 07f35fd0..01c3f765 100644 --- a/src/vector/vector_tests/mod.rs +++ b/src/vector/vector_tests/mod.rs @@ -1,5 +1,6 @@ use super::{ - Feature, FeatureIterator, FieldValue, Geometry, Layer, OGRFieldType, OGRwkbGeometryType, + Feature, FeatureIterator, FieldValue, Geometry, Layer, LayerCaps::*, OGRFieldType, + OGRwkbGeometryType, }; use crate::spatial_ref::SpatialRef; use crate::{assert_almost_eq, Dataset, Driver}; @@ -63,6 +64,18 @@ fn test_layer_spatial_ref() { assert_eq!(srs.auth_code().unwrap(), 4326); } +#[test] +fn test_layer_capabilities() { + let mut ds = Dataset::open(fixture!("roads.geojson")).unwrap(); + let layer = ds.layer(0).unwrap(); + + assert_eq!(layer.has_capability(OLCFastSpatialFilter), false); + assert_eq!(layer.has_capability(OLCFastFeatureCount), true); + assert_eq!(layer.has_capability(OLCFastGetExtent), false); + assert_eq!(layer.has_capability(OLCRandomRead), true); + assert_eq!(layer.has_capability(OLCStringsAsUTF8), true); +} + fn ds_with_layer(ds_name: &str, layer_name: &str, f: F) where F: Fn(Layer), From b69423dc9f20be57f9895bc66abe004baab5e4d8 Mon Sep 17 00:00:00 2001 From: Sergey Kiselev Date: Sun, 7 Feb 2021 14:13:38 +0300 Subject: [PATCH 05/14] Add an enum for color interpretation --- src/raster/mod.rs | 4 +- src/raster/rasterband.rs | 117 +++++++++++++++++++++++++++++++++++---- src/raster/tests.rs | 25 ++++----- 3 files changed, 119 insertions(+), 27 deletions(-) diff --git a/src/raster/mod.rs b/src/raster/mod.rs index 9bca98c7..8eba798c 100644 --- a/src/raster/mod.rs +++ b/src/raster/mod.rs @@ -4,9 +4,7 @@ mod rasterband; mod types; mod warp; -pub use rasterband::{ - get_color_interpretation_by_name, get_color_interpretation_name, Buffer, ByteBuffer, RasterBand, -}; +pub use rasterband::{Buffer, ByteBuffer, ColorInterpretation, RasterBand}; pub use types::{GDALDataType, GdalType}; pub use warp::reproject; diff --git a/src/raster/rasterband.rs b/src/raster/rasterband.rs index 49f4ea86..9798bd5d 100644 --- a/src/raster/rasterband.rs +++ b/src/raster/rasterband.rs @@ -261,12 +261,17 @@ impl<'a> RasterBand<'a> { Ok(()) } - pub fn get_color_interpretation(&self) -> GDALColorInterp::Type { - unsafe { gdal_sys::GDALGetRasterColorInterpretation(self.c_rasterband) } + /// Returns the color interpretation of this band. + pub fn color_interpretation(&self) -> ColorInterpretation { + let interp_index = unsafe { gdal_sys::GDALGetRasterColorInterpretation(self.c_rasterband) }; + ColorInterpretation::from_c_int(interp_index).unwrap() } - pub fn set_color_interpretation(&mut self, interp: GDALColorInterp::Type) -> Result<()> { - let rv = unsafe { gdal_sys::GDALSetRasterColorInterpretation(self.c_rasterband, interp) }; + /// Set the color interpretation for this band. + pub fn set_color_interpretation(&mut self, interp: ColorInterpretation) -> Result<()> { + let interp_index = interp.c_int(); + let rv = + unsafe { gdal_sys::GDALSetRasterColorInterpretation(self.c_rasterband, interp_index) }; if rv != CPLErr::CE_None { return Err(_last_cpl_err(rv)); } @@ -334,12 +339,104 @@ impl Buffer { pub type ByteBuffer = Buffer; -pub fn get_color_interpretation_name(color_interp: GDALColorInterp::Type) -> String { - let rv = unsafe { gdal_sys::GDALGetColorInterpretationName(color_interp) }; - _string(rv) +/// Represents a color interpretation of a RasterBand +#[derive(Debug, PartialEq)] +pub enum ColorInterpretation { + /// Undefined + Undefined, + /// Grayscale + GrayIndex, + /// Paletted (see associated color table) + PaletteIndex, + /// Red band of RGBA image + RedBand, + /// Green band of RGBA image + GreenBand, + /// Blue band of RGBA image + BlueBand, + /// Alpha (0=transparent, 255=opaque) + AlphaBand, + /// Hue band of HLS image + HueBand, + /// Saturation band of HLS image + SaturationBand, + /// Lightness band of HLS image + LightnessBand, + /// Cyan band of CMYK image + CyanBand, + /// Magenta band of CMYK image + MagentaBand, + /// Yellow band of CMYK image + YellowBand, + /// Black band of CMYK image + BlackBand, + /// Y Luminance + YCbCrSpaceYBand, + /// Cb Chroma + YCbCrSpaceCbBand, + /// Cr Chroma + YCbCrSpaceCrBand, } -pub fn get_color_interpretation_by_name(interp_name: &str) -> Result { - let c_str_interp_name = CString::new(interp_name)?; - Ok(unsafe { gdal_sys::GDALGetColorInterpretationByName(c_str_interp_name.as_ptr()) }) +impl ColorInterpretation { + /// Creates a color interpretation from its C API int value. + pub fn from_c_int(color_interpretation: GDALColorInterp::Type) -> Option { + match color_interpretation { + GDALColorInterp::GCI_Undefined => Some(Self::Undefined), + GDALColorInterp::GCI_GrayIndex => Some(Self::GrayIndex), + GDALColorInterp::GCI_PaletteIndex => Some(Self::PaletteIndex), + GDALColorInterp::GCI_RedBand => Some(Self::RedBand), + GDALColorInterp::GCI_GreenBand => Some(Self::GreenBand), + GDALColorInterp::GCI_BlueBand => Some(Self::BlueBand), + GDALColorInterp::GCI_AlphaBand => Some(Self::AlphaBand), + GDALColorInterp::GCI_HueBand => Some(Self::HueBand), + GDALColorInterp::GCI_SaturationBand => Some(Self::SaturationBand), + GDALColorInterp::GCI_LightnessBand => Some(Self::LightnessBand), + GDALColorInterp::GCI_CyanBand => Some(Self::CyanBand), + GDALColorInterp::GCI_MagentaBand => Some(Self::MagentaBand), + GDALColorInterp::GCI_YellowBand => Some(Self::YellowBand), + GDALColorInterp::GCI_BlackBand => Some(Self::BlackBand), + GDALColorInterp::GCI_YCbCr_YBand => Some(Self::YCbCrSpaceYBand), + GDALColorInterp::GCI_YCbCr_CbBand => Some(Self::YCbCrSpaceCbBand), + GDALColorInterp::GCI_YCbCr_CrBand => Some(Self::YCbCrSpaceCrBand), + _ => None, + } + } + + /// Returns the C API int value of this color interpretation. + pub fn c_int(&self) -> GDALColorInterp::Type { + match self { + Self::Undefined => GDALColorInterp::GCI_Undefined, + Self::GrayIndex => GDALColorInterp::GCI_GrayIndex, + Self::PaletteIndex => GDALColorInterp::GCI_PaletteIndex, + Self::RedBand => GDALColorInterp::GCI_RedBand, + Self::GreenBand => GDALColorInterp::GCI_GreenBand, + Self::BlueBand => GDALColorInterp::GCI_BlueBand, + Self::AlphaBand => GDALColorInterp::GCI_AlphaBand, + Self::HueBand => GDALColorInterp::GCI_HueBand, + Self::SaturationBand => GDALColorInterp::GCI_SaturationBand, + Self::LightnessBand => GDALColorInterp::GCI_LightnessBand, + Self::CyanBand => GDALColorInterp::GCI_CyanBand, + Self::MagentaBand => GDALColorInterp::GCI_MagentaBand, + Self::YellowBand => GDALColorInterp::GCI_YellowBand, + Self::BlackBand => GDALColorInterp::GCI_BlackBand, + Self::YCbCrSpaceYBand => GDALColorInterp::GCI_YCbCr_YBand, + Self::YCbCrSpaceCbBand => GDALColorInterp::GCI_YCbCr_CbBand, + Self::YCbCrSpaceCrBand => GDALColorInterp::GCI_YCbCr_CrBand, + } + } + + /// Creates a color interpretation from its name. + pub fn from_name(name: &str) -> Result { + let c_str_interp_name = CString::new(name)?; + let interp_index = + unsafe { gdal_sys::GDALGetColorInterpretationByName(c_str_interp_name.as_ptr()) }; + Ok(Self::from_c_int(interp_index).unwrap()) + } + + /// Returns the name of this color interpretation. + pub fn name(&self) -> String { + let rv = unsafe { gdal_sys::GDALGetColorInterpretationName(self.c_int()) }; + _string(rv) + } } diff --git a/src/raster/tests.rs b/src/raster/tests.rs index 07207d1c..c0a60e9c 100644 --- a/src/raster/tests.rs +++ b/src/raster/tests.rs @@ -1,6 +1,6 @@ use crate::dataset::Dataset; use crate::metadata::Metadata; -use crate::raster::{get_color_interpretation_by_name, get_color_interpretation_name, ByteBuffer}; +use crate::raster::{ByteBuffer, ColorInterpretation}; use crate::Driver; use gdal_sys::{GDALColorInterp, GDALDataType}; use std::path::Path; @@ -446,8 +446,8 @@ fn test_get_rasterband_actual_block_size() { fn test_get_rasterband_color_interp() { let dataset = Dataset::open(fixture!("tinymarble.png")).unwrap(); let rasterband = dataset.rasterband(1).unwrap(); - let band_interp = rasterband.get_color_interpretation(); - assert_eq!(band_interp, GDALColorInterp::GCI_RedBand); + let band_interp = rasterband.color_interpretation(); + assert_eq!(band_interp, ColorInterpretation::RedBand); } #[test] @@ -456,24 +456,21 @@ fn test_set_rasterband_color_interp() { let dataset = driver.create("", 1, 1, 1).unwrap(); let mut rasterband = dataset.rasterband(1).unwrap(); rasterband - .set_color_interpretation(GDALColorInterp::GCI_AlphaBand) + .set_color_interpretation(ColorInterpretation::AlphaBand) .unwrap(); - let band_interp = rasterband.get_color_interpretation(); - assert_eq!(band_interp, GDALColorInterp::GCI_AlphaBand); + let band_interp = rasterband.color_interpretation(); + assert_eq!(band_interp, ColorInterpretation::AlphaBand); } #[test] fn test_color_interp_names() { + assert_eq!(ColorInterpretation::AlphaBand.name(), "Alpha"); assert_eq!( - get_color_interpretation_name(GDALColorInterp::GCI_AlphaBand), - "Alpha" + ColorInterpretation::from_name("Alpha").unwrap(), + ColorInterpretation::AlphaBand ); assert_eq!( - get_color_interpretation_by_name("Alpha").unwrap(), - GDALColorInterp::GCI_AlphaBand - ); - assert_eq!( - get_color_interpretation_by_name("not a valid name").unwrap(), - GDALColorInterp::GCI_Undefined + ColorInterpretation::from_name("not a valid name").unwrap(), + ColorInterpretation::Undefined ); } From 2af0cf36d13b472bb9127fa4c9727cd5f7ed55fa Mon Sep 17 00:00:00 2001 From: Sergey Kiselev Date: Sun, 7 Feb 2021 14:24:52 +0300 Subject: [PATCH 06/14] Fix issues after merge --- src/raster/rasterband.rs | 2 +- src/raster/tests.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/raster/rasterband.rs b/src/raster/rasterband.rs index b92e0a41..c227810d 100644 --- a/src/raster/rasterband.rs +++ b/src/raster/rasterband.rs @@ -3,7 +3,7 @@ use crate::gdal_major_object::MajorObject; use crate::metadata::Metadata; use crate::raster::{GDALDataType, GdalType}; use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string}; -use gdal_sys::{self, CPLErr, GDALColorInterp, GDALMajorObjectH, GDALRWFlag, GDALRasterBandH};] +use gdal_sys::{self, CPLErr, GDALColorInterp, GDALMajorObjectH, GDALRWFlag, GDALRasterBandH}; use libc::c_int; use std::ffi::CString; diff --git a/src/raster/tests.rs b/src/raster/tests.rs index 59348882..0ade16c9 100644 --- a/src/raster/tests.rs +++ b/src/raster/tests.rs @@ -2,7 +2,7 @@ use crate::dataset::Dataset; use crate::metadata::Metadata; use crate::raster::{ByteBuffer, ColorInterpretation}; use crate::Driver; -use gdal_sys::{GDALColorInterp, GDALDataType}; +use gdal_sys::GDALDataType; use std::path::Path; #[cfg(feature = "ndarray")] From 3a92302d4d84d564ec88cd2ffb2f3b14e7abff79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Wed, 17 Feb 2021 18:41:21 +0200 Subject: [PATCH 07/14] Add Dataset::build_overviews --- CHANGES.md | 6 ++++-- src/dataset.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e24c393b..cc0d52f0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,7 +9,7 @@ let dataset = Dataset::open_ex( "roads.geojson", - DatasetOptions { + DatasetOptions { open_flags: GdalOpenFlags::GDAL_OF_UPDATE|GdalOpenFlags::GDAL_OF_VECTOR, ..DatasetOptions::default() } @@ -31,7 +31,7 @@ ``` * Add more functions to SpatialRef implementation - * + * * **Breaking**: Change `Feature::field` return type from `Result` to `Result>`. Fields can be null. Before this change, if a field was null, the value @@ -64,6 +64,8 @@ ``` * * Add basic support to read overviews +* Added a `Dataset::build_overviews` method + * * BREAKING: update geo-types to 0.7.0. geo-types Coordinate now implement `Debug` * * Deprecated `SpatialRef::get_axis_mapping_strategy` - migrate to diff --git a/src/dataset.rs b/src/dataset.rs index 08afc3d6..dce4b13e 100644 --- a/src/dataset.rs +++ b/src/dataset.rs @@ -315,6 +315,37 @@ impl Dataset { } } + /// Builds overviews for the current `Dataset`. + /// + /// # Arguments + /// * resampling - resampling method, as accepted by GDAL, e.g. `"CUBIC"` + /// * overviews - list of overview decimation factors, e.g. `&[2, 4, 8, 16, 32]` + /// * bands - list of bands to build the overviews for, or empty for all bands + pub fn build_overviews( + &self, + resampling: &str, + overviews: &[i32], + bands: &[i32], + ) -> Result<()> { + let c_resampling = CString::new(resampling)?; + let rv = unsafe { + gdal_sys::GDALBuildOverviews( + self.c_dataset, + c_resampling.as_ptr(), + overviews.len() as i32, + overviews.as_ptr() as *mut i32, + bands.len() as i32, + bands.as_ptr() as *mut i32, + None, + null_mut(), + ) + }; + if rv != CPLErr::CE_None { + return Err(_last_cpl_err(rv)); + } + Ok(()) + } + fn child_layer(&self, c_layer: OGRLayerH) -> Layer { unsafe { Layer::from_c_layer(self, c_layer) } } From 9530b1117bcc7842fe73b742e85df8c77f25f339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Wed, 17 Feb 2021 18:55:43 +0200 Subject: [PATCH 08/14] Add doxygen links --- src/dataset.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/dataset.rs b/src/dataset.rs index dce4b13e..e29a2d50 100644 --- a/src/dataset.rs +++ b/src/dataset.rs @@ -146,7 +146,9 @@ impl Dataset { } /// Open a dataset with extended options. See - /// [GDALOpenEx]. + /// [`GDALOpenEx`]. + /// + /// [`GDALOpenEx`]: https://gdal.org/doxygen/gdal_8h.html#a9cb8585d0b3c16726b08e25bcc94274a pub fn open_ex(path: &Path, options: DatasetOptions) -> Result { _register_drivers(); let filename = path.to_string_lossy(); @@ -315,12 +317,14 @@ impl Dataset { } } - /// Builds overviews for the current `Dataset`. + /// Builds overviews for the current `Dataset`. See [`GDALBuildOverviews`]. /// /// # Arguments - /// * resampling - resampling method, as accepted by GDAL, e.g. `"CUBIC"` - /// * overviews - list of overview decimation factors, e.g. `&[2, 4, 8, 16, 32]` - /// * bands - list of bands to build the overviews for, or empty for all bands + /// * `resampling` - resampling method, as accepted by GDAL, e.g. `"CUBIC"` + /// * `overviews` - list of overview decimation factors, e.g. `&[2, 4, 8, 16, 32]` + /// * `bands` - list of bands to build the overviews for, or empty for all bands + /// + /// [`GDALBuildOverviews`]: https://gdal.org/doxygen/gdal_8h.html#a767f4456a6249594ee18ea53f68b7e80 pub fn build_overviews( &self, resampling: &str, From d25e770cd61188e14eaefb10994d877523e27421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 22 Feb 2021 21:13:16 +0200 Subject: [PATCH 09/14] Take self by mut ref --- src/dataset.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dataset.rs b/src/dataset.rs index e29a2d50..7934c2f5 100644 --- a/src/dataset.rs +++ b/src/dataset.rs @@ -326,7 +326,7 @@ impl Dataset { /// /// [`GDALBuildOverviews`]: https://gdal.org/doxygen/gdal_8h.html#a767f4456a6249594ee18ea53f68b7e80 pub fn build_overviews( - &self, + &mut self, resampling: &str, overviews: &[i32], bands: &[i32], From a31b1a554603d76bee94dab43265cc8b0f40eb3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 22 Feb 2021 21:13:34 +0200 Subject: [PATCH 10/14] Fix warning in nightly --- build.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.rs b/build.rs index 9b3f04c6..93da50e5 100644 --- a/build.rs +++ b/build.rs @@ -26,10 +26,10 @@ fn main() { let detected_version = Version::parse(semver_substring).expect("Could not parse gdal version!"); if detected_version.major < 2 { - panic!(format!( + panic!( "The GDAL crate requires a GDAL version >= 2.0.0. Found {}", detected_version.to_string() - )); + ); } println!("cargo:rustc-cfg=gdal_{}", detected_version.major); From 37657367902b3e53359b48aed97c220bfddb12b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 22 Feb 2021 21:21:01 +0200 Subject: [PATCH 11/14] Make clippy happy --- examples/read_write_ogr_datetime.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/read_write_ogr_datetime.rs b/examples/read_write_ogr_datetime.rs index c6bbd486..9bcb212f 100644 --- a/examples/read_write_ogr_datetime.rs +++ b/examples/read_write_ogr_datetime.rs @@ -1,7 +1,5 @@ -use gdal::errors::Result; - #[cfg(feature = "datetime")] -fn run() -> Result<()> { +fn run() -> gdal::errors::Result<()> { use chrono::Duration; use gdal::vector::{Defn, Feature, FieldDefn, FieldValue}; use gdal::{Dataset, Driver}; @@ -60,11 +58,13 @@ fn run() -> Result<()> { } #[cfg(not(feature = "datetime"))] -fn run() -> Result<()> { +fn run() { println!("gdal crate was build without datetime support"); - Ok(()) } fn main() { + #[cfg(feature = "datetime")] run().unwrap(); + #[cfg(not(feature = "datetime"))] + run(); } From b0bafff56fdb32298bf5f3f56fd3dbb7baf1d2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Tue, 23 Feb 2021 09:45:00 +0200 Subject: [PATCH 12/14] Avoid calling GDALAllRegister twice --- src/dataset.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/dataset.rs b/src/dataset.rs index 7934c2f5..86c0e7b5 100644 --- a/src/dataset.rs +++ b/src/dataset.rs @@ -1,4 +1,4 @@ -use std::{ffi::CString, ffi::NulError, path::Path, ptr, sync::Once}; +use std::{ffi::CString, ffi::NulError, path::Path, ptr}; use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string}; use crate::{ @@ -21,7 +21,6 @@ use bitflags::bitflags; /// /// [GDALGetGeoTransform]: https://gdal.org/api/gdaldataset_cpp.html#classGDALDataset_1a5101119705f5fa2bc1344ab26f66fd1d pub type GeoTransform = [c_double; 6]; -static START: Once = Once::new(); /// Wrapper around a [`GDALDataset`][GDALDataset] object. /// @@ -38,14 +37,6 @@ pub struct Dataset { c_dataset: GDALDatasetH, } -pub fn _register_drivers() { - unsafe { - START.call_once(|| { - gdal_sys::GDALAllRegister(); - }); - } -} - // These are skipped by bindgen and manually updated. #[cfg(major_ge_2)] bitflags! { @@ -150,7 +141,8 @@ impl Dataset { /// /// [`GDALOpenEx`]: https://gdal.org/doxygen/gdal_8h.html#a9cb8585d0b3c16726b08e25bcc94274a pub fn open_ex(path: &Path, options: DatasetOptions) -> Result { - _register_drivers(); + crate::driver::_register_drivers(); + let filename = path.to_string_lossy(); let c_filename = CString::new(filename.as_ref())?; let c_open_flags = options.open_flags.bits; From 3952c80711df89be27f90fce1bd1d301ca9f1753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Tue, 23 Feb 2021 09:46:14 +0200 Subject: [PATCH 13/14] Update changelog --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index cc0d52f0..99b934af 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -63,6 +63,8 @@ .unwrap(); ``` * +* Fixed potential race condition wrt. GDAL driver initialization + * * Add basic support to read overviews * Added a `Dataset::build_overviews` method * From 926ccff8a9ed70259665eb9c447725097c45a165 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Wed, 27 Jan 2021 00:33:48 -0500 Subject: [PATCH 14/14] implement sql queries --- src/dataset.rs | 118 +++++++++++++++++++++++++++-- src/errors.rs | 2 + src/vector/defn.rs | 1 + src/vector/feature.rs | 11 ++- src/vector/layer.rs | 1 + src/vector/mod.rs | 1 + src/vector/sql.rs | 46 ++++++++++++ src/vector/vector_tests/mod.rs | 5 +- src/vector/vector_tests/sql.rs | 133 +++++++++++++++++++++++++++++++++ 9 files changed, 308 insertions(+), 10 deletions(-) create mode 100644 src/vector/sql.rs create mode 100644 src/vector/vector_tests/sql.rs diff --git a/src/dataset.rs b/src/dataset.rs index 86c0e7b5..46d2abdd 100644 --- a/src/dataset.rs +++ b/src/dataset.rs @@ -1,18 +1,25 @@ -use std::{ffi::CString, ffi::NulError, path::Path, ptr}; +use ptr::null_mut; +use std::convert::TryInto; +use std::{ + ffi::NulError, + ffi::{CStr, CString}, + path::Path, + ptr, +}; +use crate::errors::*; use crate::utils::{_last_cpl_err, _last_null_pointer_err, _string}; +use crate::vector::sql; +use crate::vector::Geometry; use crate::{ gdal_major_object::MajorObject, raster::RasterBand, spatial_ref::SpatialRef, vector::Layer, Driver, Metadata, }; +use gdal_sys::OGRGeometryH; use gdal_sys::{ self, CPLErr, GDALAccess, GDALDatasetH, GDALMajorObjectH, OGRErr, OGRLayerH, OGRwkbGeometryType, }; use libc::{c_double, c_int, c_uint}; -use ptr::null_mut; - -use crate::errors::*; -use std::convert::TryInto; use bitflags::bitflags; @@ -535,6 +542,107 @@ impl Dataset { } Ok(Transaction::new(self)) } + + /// Execute a SQL query against the Dataset. It is equivalent to calling + /// [`GDALDatasetExecuteSQL`](https://gdal.org/api/raster_c_api.html#_CPPv421GDALDatasetExecuteSQL12GDALDatasetHPKc12OGRGeometryHPKc). + /// Returns a [`sql::ResultSet`], which can be treated just as any other [`Layer`]. + /// + /// Queries such as `ALTER TABLE`, `CREATE INDEX`, etc. have no [`sql::ResultSet`], and return + /// `None`, which is distinct from an empty [`sql::ResultSet`]. + /// + /// # Arguments + /// + /// * `query`: The SQL query + /// * `spatial_filter`: Limit results of the query to features that intersect the given + /// [`Geometry`] + /// * `dialect`: The dialect of SQL to use. See + /// + /// + /// # Example + /// + /// ``` + /// # use gdal::Dataset; + /// # use std::path::Path; + /// use gdal::vector::sql; + /// + /// let ds = Dataset::open(Path::new("fixtures/roads.geojson")).unwrap(); + /// let query = "SELECT kind, is_bridge, highway FROM roads WHERE highway = 'pedestrian'"; + /// let result_set = ds.execute_sql(query, None, sql::Dialect::DEFAULT).unwrap().unwrap(); + /// + /// assert_eq!(10, result_set.feature_count()); + /// + /// for feature in result_set.features() { + /// let highway = feature + /// .field("highway") + /// .unwrap() + /// .unwrap() + /// .into_string() + /// .unwrap(); + /// + /// assert_eq!("pedestrian", highway); + /// } + /// ``` + pub fn execute_sql>( + &self, + query: S, + spatial_filter: Option<&Geometry>, + dialect: sql::Dialect, + ) -> Result> { + let query = CString::new(query.as_ref())?; + + let dialect_c_str = match dialect { + sql::Dialect::DEFAULT => None, + sql::Dialect::OGR => Some(unsafe { CStr::from_bytes_with_nul_unchecked(sql::OGRSQL) }), + sql::Dialect::SQLITE => { + Some(unsafe { CStr::from_bytes_with_nul_unchecked(sql::SQLITE) }) + } + }; + + self._execute_sql(query, spatial_filter, dialect_c_str) + } + + fn _execute_sql( + &self, + query: CString, + spatial_filter: Option<&Geometry>, + dialect_c_str: Option<&CStr>, + ) -> Result> { + let mut filter_geom: OGRGeometryH = std::ptr::null_mut(); + + let dialect_ptr = match dialect_c_str { + None => std::ptr::null(), + Some(ref d) => d.as_ptr(), + }; + + if let Some(spatial_filter) = spatial_filter { + filter_geom = unsafe { spatial_filter.c_geometry() }; + } + + let c_dataset = unsafe { self.c_dataset() }; + + unsafe { gdal_sys::CPLErrorReset() }; + + let c_layer = unsafe { + gdal_sys::GDALDatasetExecuteSQL(c_dataset, query.as_ptr(), filter_geom, dialect_ptr) + }; + + let cpl_err = unsafe { gdal_sys::CPLGetLastErrorType() }; + + if cpl_err != CPLErr::CE_None { + return Err(_last_cpl_err(cpl_err)); + } + + if c_layer.is_null() { + return Ok(None); + } + + let layer = unsafe { Layer::from_c_layer(self, c_layer) }; + + Ok(Some(sql::ResultSet { + layer, + dataset: c_dataset, + })) + } } pub struct LayerIterator<'a> { diff --git a/src/errors.rs b/src/errors.rs index 157bc50b..d8a2e916 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -9,6 +9,8 @@ pub type Result = std::result::Result; pub enum GdalError { #[error("FfiNulError")] FfiNulError(#[from] std::ffi::NulError), + #[error("FfiIntoStringError")] + FfiIntoStringError(#[from] std::ffi::IntoStringError), #[error("StrUtf8Error")] StrUtf8Error(#[from] std::str::Utf8Error), #[cfg(feature = "ndarray")] diff --git a/src/vector/defn.rs b/src/vector/defn.rs index b460c630..797e86d4 100644 --- a/src/vector/defn.rs +++ b/src/vector/defn.rs @@ -11,6 +11,7 @@ use crate::errors::*; /// Layer definition /// /// Defines the fields available for features in a layer. +#[derive(Debug)] pub struct Defn { c_defn: OGRFeatureDefnH, } diff --git a/src/vector/feature.rs b/src/vector/feature.rs index d6d5f7ff..d29209e3 100644 --- a/src/vector/feature.rs +++ b/src/vector/feature.rs @@ -15,6 +15,7 @@ use crate::errors::*; use std::slice; /// OGR Feature +#[derive(Debug)] pub struct Feature<'a> { _defn: &'a Defn, c_feature: OGRFeatureH, @@ -72,12 +73,16 @@ impl<'a> Feature<'a> { /// If the field has an unsupported type, returns a [`GdalError::UnhandledFieldType`]. /// /// If the field is null, returns `None`. - pub fn field(&self, name: &str) -> Result> { - let c_name = CString::new(name)?; + pub fn field>(&self, name: S) -> Result> { + let c_name = CString::new(name.as_ref())?; + self._field(c_name) + } + + fn _field(&self, c_name: CString) -> Result> { let field_id = unsafe { gdal_sys::OGR_F_GetFieldIndex(self.c_feature, c_name.as_ptr()) }; if field_id == -1 { Err(GdalError::InvalidFieldName { - field_name: name.to_string(), + field_name: c_name.into_string()?, method_name: "OGR_F_GetFieldIndex", }) } else { diff --git a/src/vector/layer.rs b/src/vector/layer.rs index ad32664d..6fbb00b7 100644 --- a/src/vector/layer.rs +++ b/src/vector/layer.rs @@ -25,6 +25,7 @@ use crate::errors::*; /// // do something with each feature /// } /// ``` +#[derive(Debug)] pub struct Layer<'a> { c_layer: OGRLayerH, defn: Defn, diff --git a/src/vector/mod.rs b/src/vector/mod.rs index 4c6b9e90..48892ce3 100644 --- a/src/vector/mod.rs +++ b/src/vector/mod.rs @@ -22,6 +22,7 @@ mod geo_to_gdal; mod geometry; mod layer; mod ops; +pub mod sql; pub use defn::{Defn, Field, FieldIterator}; pub use feature::{Feature, FieldValue, FieldValueIterator}; diff --git a/src/vector/sql.rs b/src/vector/sql.rs new file mode 100644 index 00000000..67fcc602 --- /dev/null +++ b/src/vector/sql.rs @@ -0,0 +1,46 @@ +use std::ops::Deref; + +use gdal_sys::GDALDatasetH; + +use crate::vector::Layer; + +/// The result of a SQL query executed by +/// [`Dataset::execute_sql()`](crate::Dataset::execute_sql()). It is just a thin wrapper around a +/// [`Layer`], and you can treat it as such. +#[derive(Debug)] +pub struct ResultSet<'a> { + pub(crate) layer: Layer<'a>, + pub(crate) dataset: GDALDatasetH, +} + +impl<'a> Deref for ResultSet<'a> { + type Target = Layer<'a>; + + fn deref(&self) -> &Self::Target { + &self.layer + } +} + +impl<'a> Drop for ResultSet<'a> { + fn drop(&mut self) { + unsafe { gdal_sys::GDALDatasetReleaseResultSet(self.dataset, self.layer.c_layer()) }; + } +} + +/// Represents valid SQL dialects to use in SQL queries. See +/// +pub enum Dialect { + /// Use the default dialect. This is OGR SQL unless the underlying driver has a native dialect, + /// such as MySQL, Postgres, Oracle, etc. + DEFAULT, + + /// Explicitly choose OGR SQL regardless of if the underlying driver has a native dialect. + OGR, + + /// SQLite dialect. If the data set is not actually a SQLite database, then a virtual SQLite + /// table is created to execute the query. + SQLITE, +} + +pub(crate) const OGRSQL: &[u8] = b"OGRSQL\0"; +pub(crate) const SQLITE: &[u8] = b"SQLITE\0"; diff --git a/src/vector/vector_tests/mod.rs b/src/vector/vector_tests/mod.rs index 07f35fd0..04d66ab7 100644 --- a/src/vector/vector_tests/mod.rs +++ b/src/vector/vector_tests/mod.rs @@ -3,13 +3,14 @@ use super::{ }; use crate::spatial_ref::SpatialRef; use crate::{assert_almost_eq, Dataset, Driver}; -use std::path::Path; mod convert_geo; +mod sql; +#[macro_export] macro_rules! fixture { ($name:expr) => { - Path::new(file!()) + std::path::Path::new(file!()) .parent() .unwrap() .parent() diff --git a/src/vector/vector_tests/sql.rs b/src/vector/vector_tests/sql.rs new file mode 100644 index 00000000..82fffc51 --- /dev/null +++ b/src/vector/vector_tests/sql.rs @@ -0,0 +1,133 @@ +use std::collections::HashSet; + +use crate::{ + fixture, + vector::{sql, Geometry}, + Dataset, +}; + +#[test] +fn test_sql() { + let ds = Dataset::open(fixture!("roads.geojson")).unwrap(); + let query = "SELECT kind, is_bridge, highway FROM roads WHERE highway = 'pedestrian'"; + let result_set = ds + .execute_sql(query, None, sql::Dialect::DEFAULT) + .unwrap() + .unwrap(); + + let field_names: HashSet<_> = result_set + .defn() + .fields() + .map(|field| field.name()) + .collect(); + + let mut correct_field_names = HashSet::new(); + correct_field_names.insert("kind".into()); + correct_field_names.insert("is_bridge".into()); + correct_field_names.insert("highway".into()); + + assert_eq!(correct_field_names, field_names); + assert_eq!(10, result_set.feature_count()); + + for feature in result_set.features() { + let highway = feature + .field("highway") + .unwrap() + .unwrap() + .into_string() + .unwrap(); + + assert_eq!("pedestrian", highway); + } +} + +#[test] +fn test_sql_with_spatial_filter() { + let query = "SELECT * FROM roads WHERE highway = 'pedestrian'"; + let ds = Dataset::open(fixture!("roads.geojson")).unwrap(); + let bbox = Geometry::bbox(26.1017, 44.4297, 26.1025, 44.4303).unwrap(); + let result_set = ds + .execute_sql(query, Some(&bbox), sql::Dialect::DEFAULT) + .unwrap() + .unwrap(); + + assert_eq!(2, result_set.feature_count()); + let mut correct_fids = HashSet::new(); + correct_fids.insert(252725993); + correct_fids.insert(23489656); + + let mut fids = HashSet::new(); + for feature in result_set.features() { + let highway = feature + .field("highway") + .unwrap() + .unwrap() + .into_string() + .unwrap(); + + assert_eq!("pedestrian", highway); + fids.insert(feature.fid().unwrap()); + } + + assert_eq!(correct_fids, fids); +} + +#[test] +fn test_sql_with_dialect() { + let query = "SELECT * FROM roads WHERE highway = 'pedestrian' and NumPoints(GEOMETRY) = 3"; + let ds = Dataset::open(fixture!("roads.geojson")).unwrap(); + let bbox = Geometry::bbox(26.1017, 44.4297, 26.1025, 44.4303).unwrap(); + let result_set = ds + .execute_sql(query, Some(&bbox), sql::Dialect::SQLITE) + .unwrap() + .unwrap(); + + assert_eq!(1, result_set.feature_count()); + let mut features: Vec<_> = result_set.features().collect(); + let feature = features.pop().unwrap(); + let highway = feature + .field("highway") + .unwrap() + .unwrap() + .into_string() + .unwrap(); + + assert_eq!("pedestrian", highway); +} + +#[test] +fn test_sql_empty_result() { + let ds = Dataset::open(fixture!("roads.geojson")).unwrap(); + let query = "SELECT kind, is_bridge, highway FROM roads WHERE highway = 'jazz hands 👐'"; + let result_set = ds + .execute_sql(query, None, sql::Dialect::DEFAULT) + .unwrap() + .unwrap(); + assert_eq!(0, result_set.feature_count()); + assert_eq!(0, result_set.features().count()); +} + +#[test] +fn test_sql_no_result() { + let ds = Dataset::open(fixture!("roads.geojson")).unwrap(); + let query = "ALTER TABLE roads ADD COLUMN fun integer"; + let result_set = ds.execute_sql(query, None, sql::Dialect::DEFAULT).unwrap(); + assert!(result_set.is_none()); +} + +#[test] +fn test_sql_bad_query() { + let ds = Dataset::open(fixture!("roads.geojson")).unwrap(); + + let query = "SELECT nope FROM roads"; + let result_set = ds.execute_sql(query, None, sql::Dialect::DEFAULT); + assert!(result_set.is_err()); + + let query = "SELECT nope FROM"; + let result_set = ds.execute_sql(query, None, sql::Dialect::DEFAULT); + assert!(result_set.is_err()); + + let query = "SELECT ninetynineredballoons(highway) FROM roads"; + let result_set = ds.execute_sql(query, None, sql::Dialect::DEFAULT); + assert!(result_set.is_err()); +}