Support tables + geometries in CBOR (#3604)

Co-authored-by: Gerard Guillemas Martos <gerard.guillemas@surrealdb.com>
This commit is contained in:
Micha de Vries 2024-03-05 16:19:44 +01:00 committed by GitHub
parent 4eba4a61ab
commit e5c63234ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 255 additions and 13 deletions

70
Cargo.lock generated
View file

@ -2171,7 +2171,7 @@ dependencies = [
"log",
"num-traits",
"robust",
"rstar",
"rstar 0.11.0",
"serde",
]
@ -2188,21 +2188,39 @@ dependencies = [
"log",
"num-traits",
"robust",
"rstar",
"rstar 0.11.0",
"serde",
"spade",
]
[[package]]
name = "geo-types"
version = "0.7.12"
name = "geo"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567495020b114f1ce9bed679b29975aa0bfae06ac22beacd5cfde5dabe7b05d6"
checksum = "f811f663912a69249fa620dcd2a005db7254529da2d8a0b23942e81f47084501"
dependencies = [
"earcutr",
"float_next_after",
"geo-types",
"geographiclib-rs",
"log",
"num-traits",
"robust",
"rstar 0.12.0",
"spade",
]
[[package]]
name = "geo-types"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff16065e5720f376fbced200a5ae0f47ace85fd70b7e54269790281353b6d61"
dependencies = [
"approx",
"arbitrary",
"num-traits",
"rstar",
"rstar 0.11.0",
"rstar 0.12.0",
"serde",
]
@ -2332,6 +2350,15 @@ dependencies = [
"byteorder",
]
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -2382,12 +2409,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32",
"hash32 0.2.1",
"rustc_version",
"spin 0.9.8",
"stable_deref_trait",
]
[[package]]
name = "heapless"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"hash32 0.3.1",
"stable_deref_trait",
]
[[package]]
name = "heck"
version = "0.4.1"
@ -4040,7 +4077,7 @@ checksum = "8bdf592881d821b83d471f8af290226c8d51402259e9bb5be7f9f8bdebbb11ac"
dependencies = [
"bytes",
"heck",
"itertools 0.10.5",
"itertools 0.11.0",
"log",
"multimap",
"once_cell",
@ -4074,7 +4111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
dependencies = [
"anyhow",
"itertools 0.10.5",
"itertools 0.11.0",
"proc-macro2",
"quote",
"syn 2.0.48",
@ -4699,7 +4736,18 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73111312eb7a2287d229f06c00ff35b51ddee180f017ab6dec1f69d62ac098d6"
dependencies = [
"heapless",
"heapless 0.7.17",
"num-traits",
"smallvec",
]
[[package]]
name = "rstar"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133315eb94c7b1e8d0cb097e5a710d850263372fd028fff18969de708afc7008"
dependencies = [
"heapless 0.8.0",
"num-traits",
"smallvec",
]
@ -5371,6 +5419,8 @@ dependencies = [
"env_logger",
"futures",
"futures-util",
"geo 0.28.0",
"geo-types",
"glob",
"http 0.2.11",
"http-body 0.4.6",

View file

@ -53,6 +53,8 @@ clap = { version = "4.4.11", features = [
] }
futures = "0.3.29"
futures-util = "0.3.29"
geo = "0.28.0"
geo-types = "0.7.13"
glob = "0.3.1"
http = "0.2.11"
http-body = "0.4.5"

View file

@ -732,6 +732,10 @@ allow_unsafe = true
[pkg.heapless]
allow_unsafe = true
build.allow_apis = [
"fs",
"process",
]
[pkg.vcpkg]
from.build.allow_apis = [

View file

@ -1,7 +1,12 @@
use ciborium::Value as Data;
use geo::{LineString, Point, Polygon};
use geo_types::{MultiLineString, MultiPoint, MultiPolygon};
use std::collections::BTreeMap;
use std::iter::once;
use std::ops::Deref;
use surrealdb::sql::Datetime;
use surrealdb::sql::Duration;
use surrealdb::sql::Geometry;
use surrealdb::sql::Id;
use surrealdb::sql::Number;
use surrealdb::sql::Thing;
@ -14,6 +19,14 @@ const TAG_UUID: u64 = 7;
const TAG_DECIMAL: u64 = 8;
const TAG_DURATION: u64 = 9;
const TAG_RECORDID: u64 = 10;
const TAG_TABLE: u64 = 11;
const TAG_GEOMETRY_POINT: u64 = 88;
const TAG_GEOMETRY_LINE: u64 = 89;
const TAG_GEOMETRY_POLYGON: u64 = 90;
const TAG_GEOMETRY_MULTIPOINT: u64 = 91;
const TAG_GEOMETRY_MULTILINE: u64 = 92;
const TAG_GEOMETRY_MULTIPOLYGON: u64 = 93;
const TAG_GEOMETRY_COLLECTION: u64 = 94;
#[derive(Debug)]
pub struct Cbor(pub Data);
@ -113,6 +126,114 @@ impl TryFrom<Cbor> for Value {
},
_ => Err("Expected a CBOR text data type, or a CBOR array with 2 elements"),
},
// A literal table
TAG_TABLE => match *v {
Data::Text(v) => Ok(Value::Table(v.into())),
_ => Err("Expected a CBOR text data type"),
},
TAG_GEOMETRY_POINT => match v.deref() {
Data::Array(v) if v.len() == 2 => {
let x = Value::try_from(Cbor(v.clone().remove(0)))?;
let y = Value::try_from(Cbor(v.clone().remove(0)))?;
match (x, y) {
(Value::Number(x), Value::Number(y)) => {
Ok(Value::Geometry(Geometry::Point((x.as_float(), y.as_float()).into())))
},
_ => Err("Expected a CBOR array with 2 decimal values"),
}
},
_ => Err("Expected a CBOR array with 2 decimal values"),
},
TAG_GEOMETRY_LINE => match v.deref() {
Data::Array(v) => {
let points = v
.iter()
.map(|v| match Value::try_from(Cbor(v.clone()))? {
Value::Geometry(Geometry::Point(v)) => Ok(v),
_ => Err("Expected a CBOR array with Geometry Point values")
})
.collect::<Result<Vec<Point>, &str>>()?;
Ok(Value::Geometry(Geometry::Line(LineString::from(points))))
},
_ => Err("Expected a CBOR array with Geometry Point values"),
},
TAG_GEOMETRY_POLYGON => match v.deref() {
Data::Array(v) if v.len() >= 2 => {
let lines = v
.iter()
.map(|v| match Value::try_from(Cbor(v.clone()))? {
Value::Geometry(Geometry::Line(v)) => Ok(v),
_ => Err("Expected a CBOR array with Geometry Line values")
})
.collect::<Result<Vec<LineString>, &str>>()?;
let first = match lines.first() {
Some(v) => v,
_ => return Err("Expected a CBOR array with at least two Geometry Line values")
};
Ok(Value::Geometry(Geometry::Polygon(Polygon::new(first.clone(), Vec::from(&lines[1..])))))
},
_ => Err("Expected a CBOR array with at least two Geometry Line values"),
},
TAG_GEOMETRY_MULTIPOINT => match v.deref() {
Data::Array(v) => {
let points = v
.iter()
.map(|v| match Value::try_from(Cbor(v.clone()))? {
Value::Geometry(Geometry::Point(v)) => Ok(v),
_ => Err("Expected a CBOR array with Geometry Point values")
})
.collect::<Result<Vec<Point>, &str>>()?;
Ok(Value::Geometry(Geometry::MultiPoint(MultiPoint::from(points))))
},
_ => Err("Expected a CBOR array with Geometry Point values"),
},
TAG_GEOMETRY_MULTILINE => match v.deref() {
Data::Array(v) => {
let lines = v
.iter()
.map(|v| match Value::try_from(Cbor(v.clone()))? {
Value::Geometry(Geometry::Line(v)) => Ok(v),
_ => Err("Expected a CBOR array with Geometry Line values")
})
.collect::<Result<Vec<LineString>, &str>>()?;
Ok(Value::Geometry(Geometry::MultiLine(MultiLineString::new(lines))))
},
_ => Err("Expected a CBOR array with Geometry Point values"),
},
TAG_GEOMETRY_MULTIPOLYGON => match v.deref() {
Data::Array(v) => {
let polygons = v
.iter()
.map(|v| match Value::try_from(Cbor(v.clone()))? {
Value::Geometry(Geometry::Polygon(v)) => Ok(v),
_ => Err("Expected a CBOR array with Geometry Polygon values")
})
.collect::<Result<Vec<Polygon>, &str>>()?;
Ok(Value::Geometry(Geometry::MultiPolygon(MultiPolygon::from(polygons))))
},
_ => Err("Expected a CBOR array with Geometry Polygon values"),
},
TAG_GEOMETRY_COLLECTION => match v.deref() {
Data::Array(v) => {
let geometries = v
.iter()
.map(|v| match Value::try_from(Cbor(v.clone()))? {
Value::Geometry(v) => Ok(v),
_ => Err("Expected a CBOR array with Geometry values")
})
.collect::<Result<Vec<Geometry>, &str>>()?;
Ok(Value::Geometry(Geometry::Collection(geometries)))
},
_ => Err("Expected a CBOR array with Geometry values"),
},
// An unknown tag
_ => Err("Encountered an unknown CBOR tag"),
}
@ -171,12 +292,61 @@ impl TryFrom<Value> for Cbor {
Id::String(v) => Data::Text(v),
Id::Array(v) => Cbor::try_from(Value::from(v))?.0,
Id::Object(v) => Cbor::try_from(Value::from(v))?.0,
Id::Generate(_) => unreachable!(),
Id::Generate(_) => {
return Err("Cannot encode an ungenerated Record ID into CBOR")
}
},
])),
))),
Value::Table(v) => Ok(Cbor(Data::Tag(TAG_TABLE, Box::new(Data::Text(v.0))))),
Value::Geometry(v) => Ok(Cbor(encode_geometry(v))),
// We shouldn't reach here
_ => unreachable!(),
_ => Err("Found unsupported SurrealQL value being encoded into a CBOR value"),
}
}
}
fn encode_geometry(v: Geometry) -> Data {
match v {
Geometry::Point(v) => Data::Tag(
TAG_GEOMETRY_POINT,
Box::new(Data::Array(vec![
Data::Tag(TAG_DECIMAL, Box::new(Data::Text(v.x().to_string()))),
Data::Tag(TAG_DECIMAL, Box::new(Data::Text(v.y().to_string()))),
])),
),
Geometry::Line(v) => {
let data = v.points().map(|v| encode_geometry(v.into())).collect::<Vec<Data>>();
Data::Tag(TAG_GEOMETRY_LINE, Box::new(Data::Array(data)))
}
Geometry::Polygon(v) => {
let data = once(v.exterior())
.chain(v.interiors())
.map(|v| encode_geometry(v.clone().into()))
.collect::<Vec<Data>>();
Data::Tag(TAG_GEOMETRY_POLYGON, Box::new(Data::Array(data)))
}
Geometry::MultiPoint(v) => {
let data = v.iter().map(|v| encode_geometry((*v).into())).collect::<Vec<Data>>();
Data::Tag(TAG_GEOMETRY_MULTIPOINT, Box::new(Data::Array(data)))
}
Geometry::MultiLine(v) => {
let data = v.iter().map(|v| encode_geometry(v.clone().into())).collect::<Vec<Data>>();
Data::Tag(TAG_GEOMETRY_MULTILINE, Box::new(Data::Array(data)))
}
Geometry::MultiPolygon(v) => {
let data = v.iter().map(|v| encode_geometry(v.clone().into())).collect::<Vec<Data>>();
Data::Tag(TAG_GEOMETRY_MULTIPOLYGON, Box::new(Data::Array(data)))
}
Geometry::Collection(v) => {
let data = v.iter().map(|v| encode_geometry(v.clone())).collect::<Vec<Data>>();
Data::Tag(TAG_GEOMETRY_COLLECTION, Box::new(Data::Array(data)))
}
}
}

View file

@ -663,8 +663,12 @@ criteria = "safe-to-deploy"
version = "0.27.0"
criteria = "safe-to-deploy"
[[exemptions.geo]]
version = "0.28.0"
criteria = "safe-to-deploy"
[[exemptions.geo-types]]
version = "0.7.12"
version = "0.7.13"
criteria = "safe-to-deploy"
[[exemptions.geographiclib-rs]]
@ -707,6 +711,10 @@ criteria = "safe-to-deploy"
version = "0.2.1"
criteria = "safe-to-deploy"
[[exemptions.hash32]]
version = "0.3.1"
criteria = "safe-to-deploy"
[[exemptions.hashbrown]]
version = "0.14.3"
criteria = "safe-to-deploy"
@ -719,6 +727,10 @@ criteria = "safe-to-deploy"
version = "0.7.17"
criteria = "safe-to-deploy"
[[exemptions.heapless]]
version = "0.8.0"
criteria = "safe-to-deploy"
[[exemptions.hermit-abi]]
version = "0.3.5"
criteria = "safe-to-deploy"
@ -1415,6 +1427,10 @@ criteria = "safe-to-deploy"
version = "0.11.0"
criteria = "safe-to-deploy"
[[exemptions.rstar]]
version = "0.12.0"
criteria = "safe-to-deploy"
[[exemptions.rust-stemmers]]
version = "1.2.0"
criteria = "safe-to-deploy"