Implement conversions for bytes and geometries in script functions (#4743)
This commit is contained in:
parent
a920ed4d83
commit
0444d5e6ce
5 changed files with 637 additions and 95 deletions
|
@ -3,7 +3,10 @@ use crate::sql::array::Array;
|
||||||
use crate::sql::datetime::Datetime;
|
use crate::sql::datetime::Datetime;
|
||||||
use crate::sql::object::Object;
|
use crate::sql::object::Object;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
|
use crate::sql::Bytes;
|
||||||
|
use crate::sql::Geometry;
|
||||||
use crate::sql::Id;
|
use crate::sql::Id;
|
||||||
|
use crate::sql::Strand;
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
use js::prelude::This;
|
use js::prelude::This;
|
||||||
use js::Coerced;
|
use js::Coerced;
|
||||||
|
@ -22,6 +25,60 @@ fn check_nul(s: &str) -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn try_object_to_geom(object: &Object) -> Option<Geometry> {
|
||||||
|
if object.len() != 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(Value::Strand(Strand(key))) = object.get("type") else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
match key.as_str() {
|
||||||
|
"Point" => {
|
||||||
|
object.get("coordinates").and_then(Geometry::array_to_point).map(Geometry::Point)
|
||||||
|
}
|
||||||
|
"LineString" => {
|
||||||
|
object.get("coordinates").and_then(Geometry::array_to_line).map(Geometry::Line)
|
||||||
|
}
|
||||||
|
"Polygon" => {
|
||||||
|
object.get("coordinates").and_then(Geometry::array_to_polygon).map(Geometry::Polygon)
|
||||||
|
}
|
||||||
|
"MultiPoint" => object
|
||||||
|
.get("coordinates")
|
||||||
|
.and_then(Geometry::array_to_multipoint)
|
||||||
|
.map(Geometry::MultiPoint),
|
||||||
|
"MultiLineString" => object
|
||||||
|
.get("coordinates")
|
||||||
|
.and_then(Geometry::array_to_multiline)
|
||||||
|
.map(Geometry::MultiLine),
|
||||||
|
"MultiPolygon" => {
|
||||||
|
return object
|
||||||
|
.get("coordinates")
|
||||||
|
.and_then(Geometry::array_to_multipolygon)
|
||||||
|
.map(Geometry::MultiPolygon)
|
||||||
|
}
|
||||||
|
"GeometryCollection" => {
|
||||||
|
let Some(Value::Array(x)) = object.get("geometries") else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut res = Vec::with_capacity(x.len());
|
||||||
|
|
||||||
|
for x in x.iter() {
|
||||||
|
let Value::Geometry(x) = x else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
res.push(x.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Geometry::Collection(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'js> FromJs<'js> for Value {
|
impl<'js> FromJs<'js> for Value {
|
||||||
fn from_js(ctx: &Ctx<'js>, val: js::Value<'js>) -> Result<Self, Error> {
|
fn from_js(ctx: &Ctx<'js>, val: js::Value<'js>) -> Result<Self, Error> {
|
||||||
match val.type_of() {
|
match val.type_of() {
|
||||||
|
@ -89,6 +146,19 @@ impl<'js> FromJs<'js> for Value {
|
||||||
None => Ok(Value::None),
|
None => Ok(Value::None),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(v) = v.as_typed_array::<u8>() {
|
||||||
|
let Some(data) = v.as_bytes() else {
|
||||||
|
return Err(Error::new_from_js_message(
|
||||||
|
"Uint8Array",
|
||||||
|
"Bytes",
|
||||||
|
"Uint8Array data was consumed.",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(Value::Bytes(Bytes(data.to_vec())));
|
||||||
|
}
|
||||||
|
|
||||||
// Check to see if this object is a date
|
// Check to see if this object is a date
|
||||||
let date: js::Object = ctx.globals().get(js::atom::PredefinedAtom::Date)?;
|
let date: js::Object = ctx.globals().get(js::atom::PredefinedAtom::Date)?;
|
||||||
if (v).is_instance_of(&date) {
|
if (v).is_instance_of(&date) {
|
||||||
|
@ -97,6 +167,7 @@ impl<'js> FromJs<'js> for Value {
|
||||||
let d = Utc.timestamp_millis_opt(m).unwrap();
|
let d = Utc.timestamp_millis_opt(m).unwrap();
|
||||||
return Ok(Datetime::from(d).into());
|
return Ok(Datetime::from(d).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if this object is an array
|
// Check to see if this object is an array
|
||||||
if let Some(v) = v.as_array() {
|
if let Some(v) = v.as_array() {
|
||||||
let mut x = Array::with_capacity(v.len());
|
let mut x = Array::with_capacity(v.len());
|
||||||
|
@ -107,6 +178,7 @@ impl<'js> FromJs<'js> for Value {
|
||||||
}
|
}
|
||||||
return Ok(x.into());
|
return Ok(x.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if this object is a function
|
// Check to see if this object is a function
|
||||||
if v.as_function().is_some() {
|
if v.as_function().is_some() {
|
||||||
return Ok(Value::None);
|
return Ok(Value::None);
|
||||||
|
@ -120,6 +192,11 @@ impl<'js> FromJs<'js> for Value {
|
||||||
let v = Value::from_js(ctx, v)?;
|
let v = Value::from_js(ctx, v)?;
|
||||||
x.insert(k, v);
|
x.insert(k, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(x) = try_object_to_geom(&x) {
|
||||||
|
return Ok(x.into());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(x.into())
|
Ok(x.into())
|
||||||
}
|
}
|
||||||
_ => Ok(Value::Null),
|
_ => Ok(Value::Null),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::classes;
|
use super::classes;
|
||||||
|
use crate::sql::geometry::Geometry;
|
||||||
use crate::sql::number::Number;
|
use crate::sql::number::Number;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
use js::Array;
|
use js::Array;
|
||||||
|
@ -7,9 +8,11 @@ use js::Class;
|
||||||
use js::Ctx;
|
use js::Ctx;
|
||||||
use js::Error;
|
use js::Error;
|
||||||
use js::Exception;
|
use js::Exception;
|
||||||
|
use js::FromIteratorJs as _;
|
||||||
use js::IntoJs;
|
use js::IntoJs;
|
||||||
use js::Null;
|
use js::Null;
|
||||||
use js::Object;
|
use js::Object;
|
||||||
|
use js::TypedArray;
|
||||||
use js::Undefined;
|
use js::Undefined;
|
||||||
use rust_decimal::prelude::ToPrimitive;
|
use rust_decimal::prelude::ToPrimitive;
|
||||||
|
|
||||||
|
@ -106,7 +109,113 @@ impl<'js> IntoJs<'js> for &Value {
|
||||||
}
|
}
|
||||||
x.into_js(ctx)
|
x.into_js(ctx)
|
||||||
}
|
}
|
||||||
|
Value::Bytes(ref v) => TypedArray::new_copy(ctx.clone(), v.0.as_slice())?.into_js(ctx),
|
||||||
|
Value::Geometry(ref v) => v.into_js(ctx),
|
||||||
_ => Undefined.into_js(ctx),
|
_ => Undefined.into_js(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'js> IntoJs<'js> for &Geometry {
|
||||||
|
fn into_js(self, ctx: &Ctx<'js>) -> js::Result<js::Value<'js>> {
|
||||||
|
let (ty, coords) = match self {
|
||||||
|
Geometry::Point(x) => {
|
||||||
|
("Point".into_js(ctx)?, Array::from_iter_js(ctx, [x.0.x, x.0.y])?)
|
||||||
|
}
|
||||||
|
Geometry::Line(x) => {
|
||||||
|
let array = Array::new(ctx.clone())?;
|
||||||
|
for (idx, c) in x.0.iter().enumerate() {
|
||||||
|
let coord = Array::from_iter_js(ctx, [c.x, c.y])?;
|
||||||
|
array.set(idx, coord)?;
|
||||||
|
}
|
||||||
|
("LineString".into_js(ctx)?, array)
|
||||||
|
}
|
||||||
|
Geometry::Polygon(x) => {
|
||||||
|
let coords = Array::new(ctx.clone())?;
|
||||||
|
|
||||||
|
let string = Array::new(ctx.clone())?;
|
||||||
|
for (idx, c) in x.exterior().0.iter().enumerate() {
|
||||||
|
let coord = Array::from_iter_js(ctx, [c.x, c.y])?;
|
||||||
|
string.set(idx, coord)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
coords.set(0, string)?;
|
||||||
|
|
||||||
|
for (idx, int) in x.interiors().iter().enumerate() {
|
||||||
|
let string = Array::new(ctx.clone())?;
|
||||||
|
for (idx, c) in int.0.iter().enumerate() {
|
||||||
|
let coord = Array::from_iter_js(ctx, [c.x, c.y])?;
|
||||||
|
string.set(idx, coord)?;
|
||||||
|
}
|
||||||
|
coords.set(idx + 1, string)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
("Polygon".into_js(ctx)?, coords)
|
||||||
|
}
|
||||||
|
Geometry::MultiPoint(x) => {
|
||||||
|
let array = Array::new(ctx.clone())?;
|
||||||
|
for (idx, c) in x.0.iter().enumerate() {
|
||||||
|
let coord = Array::from_iter_js(ctx, [c.x(), c.y()])?;
|
||||||
|
array.set(idx, coord)?;
|
||||||
|
}
|
||||||
|
("MultiPoint".into_js(ctx)?, array)
|
||||||
|
}
|
||||||
|
Geometry::MultiLine(x) => {
|
||||||
|
let lines = Array::new(ctx.clone())?;
|
||||||
|
for (idx, l) in x.0.iter().enumerate() {
|
||||||
|
let array = Array::new(ctx.clone())?;
|
||||||
|
for (idx, c) in l.0.iter().enumerate() {
|
||||||
|
let coord = Array::from_iter_js(ctx, [c.x, c.y])?;
|
||||||
|
array.set(idx, coord)?;
|
||||||
|
}
|
||||||
|
lines.set(idx, array)?
|
||||||
|
}
|
||||||
|
("MultiLineString".into_js(ctx)?, lines)
|
||||||
|
}
|
||||||
|
Geometry::MultiPolygon(x) => {
|
||||||
|
let polygons = Array::new(ctx.clone())?;
|
||||||
|
|
||||||
|
for (idx, p) in x.0.iter().enumerate() {
|
||||||
|
let coords = Array::new(ctx.clone())?;
|
||||||
|
|
||||||
|
let string = Array::new(ctx.clone())?;
|
||||||
|
for (idx, c) in p.exterior().0.iter().enumerate() {
|
||||||
|
let coord = Array::from_iter_js(ctx, [c.x, c.y])?;
|
||||||
|
string.set(idx, coord)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
coords.set(0, string)?;
|
||||||
|
|
||||||
|
for (idx, int) in p.interiors().iter().enumerate() {
|
||||||
|
let string = Array::new(ctx.clone())?;
|
||||||
|
for (idx, c) in int.0.iter().enumerate() {
|
||||||
|
let coord = Array::from_iter_js(ctx, [c.x, c.y])?;
|
||||||
|
string.set(idx, coord)?;
|
||||||
|
}
|
||||||
|
coords.set(idx + 1, string)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
polygons.set(idx, coords)?;
|
||||||
|
}
|
||||||
|
("MultiPolygon".into_js(ctx)?, polygons)
|
||||||
|
}
|
||||||
|
Geometry::Collection(x) => {
|
||||||
|
let geoms = Array::new(ctx.clone())?;
|
||||||
|
|
||||||
|
for (idx, g) in x.iter().enumerate() {
|
||||||
|
let g = g.into_js(ctx)?;
|
||||||
|
geoms.set(idx, g)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let object = Object::new(ctx.clone())?;
|
||||||
|
object.set("type", "GeometryCollection")?;
|
||||||
|
object.set("geometries", geoms)?;
|
||||||
|
return Ok(object.into_value());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let object = Object::new(ctx.clone())?;
|
||||||
|
object.set("type", ty)?;
|
||||||
|
object.set("coordinates", coords)?;
|
||||||
|
Ok(object.into_value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -119,6 +119,7 @@ impl Geometry {
|
||||||
Self::Collection(v) => collection(v),
|
Self::Collection(v) => collection(v),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the GeoJSON object representation for this geometry
|
/// Get the GeoJSON object representation for this geometry
|
||||||
pub fn as_object(&self) -> Object {
|
pub fn as_object(&self) -> Object {
|
||||||
let mut obj = BTreeMap::<String, Value>::new();
|
let mut obj = BTreeMap::<String, Value>::new();
|
||||||
|
@ -134,6 +135,88 @@ impl Geometry {
|
||||||
|
|
||||||
obj.into()
|
obj.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a surreal value to a MultiPolygon if the array matches to a MultiPolygon.
|
||||||
|
pub(crate) fn array_to_multipolygon(v: &Value) -> Option<MultiPolygon<f64>> {
|
||||||
|
let mut res = Vec::new();
|
||||||
|
let Value::Array(v) = v else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
for x in v.iter() {
|
||||||
|
res.push(Self::array_to_polygon(x)?);
|
||||||
|
}
|
||||||
|
Some(MultiPolygon::new(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a surreal value to a MultiLine if the array matches to a MultiLine.
|
||||||
|
pub(crate) fn array_to_multiline(v: &Value) -> Option<MultiLineString<f64>> {
|
||||||
|
let mut res = Vec::new();
|
||||||
|
let Value::Array(v) = v else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
for x in v.iter() {
|
||||||
|
res.push(Self::array_to_line(x)?);
|
||||||
|
}
|
||||||
|
Some(MultiLineString::new(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a surreal value to a MultiPoint if the array matches to a MultiPoint.
|
||||||
|
pub(crate) fn array_to_multipoint(v: &Value) -> Option<MultiPoint<f64>> {
|
||||||
|
let mut res = Vec::new();
|
||||||
|
let Value::Array(v) = v else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
for x in v.iter() {
|
||||||
|
res.push(Self::array_to_point(x)?);
|
||||||
|
}
|
||||||
|
Some(MultiPoint::new(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a surreal value to a Polygon if the array matches to a Polygon.
|
||||||
|
pub(crate) fn array_to_polygon(v: &Value) -> Option<Polygon<f64>> {
|
||||||
|
let mut res = Vec::new();
|
||||||
|
let Value::Array(v) = v else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if v.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let first = Self::array_to_line(&v[0])?;
|
||||||
|
for x in &v[1..] {
|
||||||
|
res.push(Self::array_to_line(x)?);
|
||||||
|
}
|
||||||
|
Some(Polygon::new(first, res))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a surreal value to a LineString if the array matches to a LineString.
|
||||||
|
pub(crate) fn array_to_line(v: &Value) -> Option<LineString<f64>> {
|
||||||
|
let mut res = Vec::new();
|
||||||
|
let Value::Array(v) = v else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
for x in v.iter() {
|
||||||
|
res.push(Self::array_to_point(x)?);
|
||||||
|
}
|
||||||
|
Some(LineString::from(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a surreal value to a Point if the array matches to a point.
|
||||||
|
pub(crate) fn array_to_point(v: &Value) -> Option<Point<f64>> {
|
||||||
|
let Value::Array(v) = v else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if v.len() != 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// FIXME: This truncates decimals and large integers into a f64.
|
||||||
|
let Value::Number(ref a) = v.0[0] else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let Value::Number(ref b) = v.0[1] else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(Point::from((a.clone().try_into().ok()?, b.clone().try_into().ok()?)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for Geometry {
|
impl PartialOrd for Geometry {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use geo_types::{LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon};
|
|
||||||
use reblessive::Stk;
|
use reblessive::Stk;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -72,21 +71,36 @@ impl Parser<'_> {
|
||||||
// can still be wrong.
|
// can still be wrong.
|
||||||
//
|
//
|
||||||
// we can unwrap strand since we just matched it to not be an err.
|
// we can unwrap strand since we just matched it to not be an err.
|
||||||
self.parse_geometry_after_type(ctx, start, key, type_value, Self::to_point, |x| {
|
self.parse_geometry_after_type(
|
||||||
Value::Geometry(Geometry::Point(x))
|
ctx,
|
||||||
})
|
start,
|
||||||
|
key,
|
||||||
|
type_value,
|
||||||
|
Geometry::array_to_point,
|
||||||
|
|x| Value::Geometry(Geometry::Point(x)),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
"LineString" => {
|
"LineString" => {
|
||||||
self.parse_geometry_after_type(ctx, start, key, type_value, Self::to_line, |x| {
|
self.parse_geometry_after_type(
|
||||||
Value::Geometry(Geometry::Line(x))
|
ctx,
|
||||||
})
|
start,
|
||||||
|
key,
|
||||||
|
type_value,
|
||||||
|
Geometry::array_to_line,
|
||||||
|
|x| Value::Geometry(Geometry::Line(x)),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
"Polygon" => {
|
"Polygon" => {
|
||||||
self.parse_geometry_after_type(ctx, start, key, type_value, Self::to_polygon, |x| {
|
self.parse_geometry_after_type(
|
||||||
Value::Geometry(Geometry::Polygon(x))
|
ctx,
|
||||||
})
|
start,
|
||||||
|
key,
|
||||||
|
type_value,
|
||||||
|
Geometry::array_to_polygon,
|
||||||
|
|x| Value::Geometry(Geometry::Polygon(x)),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
"MultiPoint" => {
|
"MultiPoint" => {
|
||||||
|
@ -95,7 +109,7 @@ impl Parser<'_> {
|
||||||
start,
|
start,
|
||||||
key,
|
key,
|
||||||
type_value,
|
type_value,
|
||||||
Self::to_multipoint,
|
Geometry::array_to_multipoint,
|
||||||
|x| Value::Geometry(Geometry::MultiPoint(x)),
|
|x| Value::Geometry(Geometry::MultiPoint(x)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -106,7 +120,7 @@ impl Parser<'_> {
|
||||||
start,
|
start,
|
||||||
key,
|
key,
|
||||||
type_value,
|
type_value,
|
||||||
Self::to_multiline,
|
Geometry::array_to_multiline,
|
||||||
|x| Value::Geometry(Geometry::MultiLine(x)),
|
|x| Value::Geometry(Geometry::MultiLine(x)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -117,7 +131,7 @@ impl Parser<'_> {
|
||||||
start,
|
start,
|
||||||
key,
|
key,
|
||||||
type_value,
|
type_value,
|
||||||
Self::to_multipolygon,
|
Geometry::array_to_multipolygon,
|
||||||
|x| Value::Geometry(Geometry::MultiPolygon(x)),
|
|x| Value::Geometry(Geometry::MultiPolygon(x)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -267,42 +281,42 @@ impl Parser<'_> {
|
||||||
match type_value.as_str() {
|
match type_value.as_str() {
|
||||||
"Point" => {
|
"Point" => {
|
||||||
if self.eat(t!("}")) {
|
if self.eat(t!("}")) {
|
||||||
if let Some(point) = Self::to_point(&value) {
|
if let Some(point) = Geometry::array_to_point(&value) {
|
||||||
return Ok(Value::Geometry(Geometry::Point(point)));
|
return Ok(Value::Geometry(Geometry::Point(point)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"LineString" => {
|
"LineString" => {
|
||||||
if self.eat(t!("}")) {
|
if self.eat(t!("}")) {
|
||||||
if let Some(point) = Self::to_line(&value) {
|
if let Some(point) = Geometry::array_to_line(&value) {
|
||||||
return Ok(Value::Geometry(Geometry::Line(point)));
|
return Ok(Value::Geometry(Geometry::Line(point)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"Polygon" => {
|
"Polygon" => {
|
||||||
if self.eat(t!("}")) {
|
if self.eat(t!("}")) {
|
||||||
if let Some(point) = Self::to_polygon(&value) {
|
if let Some(point) = Geometry::array_to_polygon(&value) {
|
||||||
return Ok(Value::Geometry(Geometry::Polygon(point)));
|
return Ok(Value::Geometry(Geometry::Polygon(point)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"MultiPoint" => {
|
"MultiPoint" => {
|
||||||
if self.eat(t!("}")) {
|
if self.eat(t!("}")) {
|
||||||
if let Some(point) = Self::to_multipolygon(&value) {
|
if let Some(point) = Geometry::array_to_multipolygon(&value) {
|
||||||
return Ok(Value::Geometry(Geometry::MultiPolygon(point)));
|
return Ok(Value::Geometry(Geometry::MultiPolygon(point)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"MultiLineString" => {
|
"MultiLineString" => {
|
||||||
if self.eat(t!("}")) {
|
if self.eat(t!("}")) {
|
||||||
if let Some(point) = Self::to_multiline(&value) {
|
if let Some(point) = Geometry::array_to_multiline(&value) {
|
||||||
return Ok(Value::Geometry(Geometry::MultiLine(point)));
|
return Ok(Value::Geometry(Geometry::MultiLine(point)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"MultiPolygon" => {
|
"MultiPolygon" => {
|
||||||
if self.eat(t!("}")) {
|
if self.eat(t!("}")) {
|
||||||
if let Some(point) = Self::to_multipolygon(&value) {
|
if let Some(point) = Geometry::array_to_multipolygon(&value) {
|
||||||
return Ok(Value::Geometry(Geometry::MultiPolygon(point)));
|
return Ok(Value::Geometry(Geometry::MultiPolygon(point)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -503,82 +517,6 @@ impl Parser<'_> {
|
||||||
Ok(map(v))
|
Ok(map(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_multipolygon(v: &Value) -> Option<MultiPolygon<f64>> {
|
|
||||||
let mut res = Vec::new();
|
|
||||||
let Value::Array(v) = v else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
for x in v.iter() {
|
|
||||||
res.push(Self::to_polygon(x)?);
|
|
||||||
}
|
|
||||||
Some(MultiPolygon::new(res))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_multiline(v: &Value) -> Option<MultiLineString<f64>> {
|
|
||||||
let mut res = Vec::new();
|
|
||||||
let Value::Array(v) = v else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
for x in v.iter() {
|
|
||||||
res.push(Self::to_line(x)?);
|
|
||||||
}
|
|
||||||
Some(MultiLineString::new(res))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_multipoint(v: &Value) -> Option<MultiPoint<f64>> {
|
|
||||||
let mut res = Vec::new();
|
|
||||||
let Value::Array(v) = v else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
for x in v.iter() {
|
|
||||||
res.push(Self::to_point(x)?);
|
|
||||||
}
|
|
||||||
Some(MultiPoint::new(res))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_polygon(v: &Value) -> Option<Polygon<f64>> {
|
|
||||||
let mut res = Vec::new();
|
|
||||||
let Value::Array(v) = v else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
if v.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let first = Self::to_line(&v[0])?;
|
|
||||||
for x in &v[1..] {
|
|
||||||
res.push(Self::to_line(x)?);
|
|
||||||
}
|
|
||||||
Some(Polygon::new(first, res))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_line(v: &Value) -> Option<LineString<f64>> {
|
|
||||||
let mut res = Vec::new();
|
|
||||||
let Value::Array(v) = v else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
for x in v.iter() {
|
|
||||||
res.push(Self::to_point(x)?);
|
|
||||||
}
|
|
||||||
Some(LineString::from(res))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_point(v: &Value) -> Option<Point<f64>> {
|
|
||||||
let Value::Array(v) = v else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
if v.len() != 2 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// FIXME: This truncates decimals and large integers into a f64.
|
|
||||||
let Value::Number(ref a) = v.0[0] else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let Value::Number(ref b) = v.0[1] else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
Some(Point::from((a.clone().try_into().ok()?, b.clone().try_into().ok()?)))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn parse_object_from_key(
|
async fn parse_object_from_key(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut Stk,
|
ctx: &mut Stk,
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
#![cfg(feature = "scripting")]
|
#![cfg(feature = "scripting")]
|
||||||
|
|
||||||
mod parse;
|
mod parse;
|
||||||
|
use geo::Extremes;
|
||||||
use parse::Parse;
|
use parse::Parse;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
use helpers::new_ds;
|
use helpers::new_ds;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
use surrealdb::dbs::Session;
|
use surrealdb::dbs::Session;
|
||||||
use surrealdb::err::Error;
|
use surrealdb::err::Error;
|
||||||
|
use surrealdb::sql::Geometry;
|
||||||
use surrealdb::sql::Number;
|
use surrealdb::sql::Number;
|
||||||
use surrealdb::sql::Value;
|
use surrealdb::sql::Value;
|
||||||
|
|
||||||
|
@ -381,3 +383,336 @@ async fn script_function_number_conversion_test() -> Result<(), Error> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn script_bytes() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
RETURN function() {
|
||||||
|
return new Uint8Array([0,1,2,3,4,5,6,7])
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
|
||||||
|
let Value::Bytes(b) = res.remove(0).result? else {
|
||||||
|
panic!("not bytes");
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 0..8 {
|
||||||
|
assert_eq!(b[i], i as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn script_geometry_point() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
RETURN function() {
|
||||||
|
return {
|
||||||
|
type: "Point",
|
||||||
|
coordinates: [1.0,2.0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
|
||||||
|
let Value::Geometry(Geometry::Point(x)) = res.remove(0).result? else {
|
||||||
|
panic!("not a geometry");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(x.x(), 1.0);
|
||||||
|
assert_eq!(x.y(), 2.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn script_geometry_line() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
RETURN function() {
|
||||||
|
return {
|
||||||
|
type: "LineString",
|
||||||
|
coordinates: [
|
||||||
|
[1.0,2.0],
|
||||||
|
[3.0,4.0],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
|
||||||
|
let Value::Geometry(Geometry::Line(x)) = res.remove(0).result? else {
|
||||||
|
panic!("not a geometry");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(x.0[0].x, 1.0);
|
||||||
|
assert_eq!(x.0[0].y, 2.0);
|
||||||
|
assert_eq!(x.0[1].x, 3.0);
|
||||||
|
assert_eq!(x.0[1].y, 4.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn script_geometry_polygon() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
RETURN function() {
|
||||||
|
return {
|
||||||
|
type: "Polygon",
|
||||||
|
coordinates: [
|
||||||
|
[
|
||||||
|
[1.0,2.0],
|
||||||
|
[3.0,4.0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[5.0,6.0],
|
||||||
|
[7.0,8.0],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
|
||||||
|
let Value::Geometry(Geometry::Polygon(x)) = res.remove(0).result? else {
|
||||||
|
panic!("not a geometry");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(x.exterior().0[0].x, 1.0);
|
||||||
|
assert_eq!(x.exterior().0[0].y, 2.0);
|
||||||
|
assert_eq!(x.exterior().0[1].x, 3.0);
|
||||||
|
assert_eq!(x.exterior().0[1].y, 4.0);
|
||||||
|
|
||||||
|
assert_eq!(x.interiors()[0].0[0].x, 5.0);
|
||||||
|
assert_eq!(x.interiors()[0].0[0].y, 6.0);
|
||||||
|
assert_eq!(x.interiors()[0].0[1].x, 7.0);
|
||||||
|
assert_eq!(x.interiors()[0].0[1].y, 8.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn script_geometry_multi_point() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
RETURN function() {
|
||||||
|
return {
|
||||||
|
type: "MultiPoint",
|
||||||
|
coordinates: [
|
||||||
|
[1.0,2.0],
|
||||||
|
[3.0,4.0],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
|
||||||
|
let Value::Geometry(Geometry::MultiPoint(x)) = res.remove(0).result? else {
|
||||||
|
panic!("not a geometry");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(x.0[0].x(), 1.0);
|
||||||
|
assert_eq!(x.0[0].y(), 2.0);
|
||||||
|
assert_eq!(x.0[1].x(), 3.0);
|
||||||
|
assert_eq!(x.0[1].y(), 4.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn script_geometry_multi_line() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
RETURN function() {
|
||||||
|
return {
|
||||||
|
type: "MultiLineString",
|
||||||
|
coordinates: [
|
||||||
|
[
|
||||||
|
[1.0,2.0],
|
||||||
|
[3.0,4.0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[5.0,6.0],
|
||||||
|
[7.0,8.0],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
|
||||||
|
let Value::Geometry(Geometry::MultiLine(x)) = res.remove(0).result? else {
|
||||||
|
panic!("not a geometry");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(x.0[0].0[0].x, 1.0);
|
||||||
|
assert_eq!(x.0[0].0[0].y, 2.0);
|
||||||
|
assert_eq!(x.0[0].0[1].x, 3.0);
|
||||||
|
assert_eq!(x.0[0].0[1].y, 4.0);
|
||||||
|
|
||||||
|
assert_eq!(x.0[1].0[0].x, 5.0);
|
||||||
|
assert_eq!(x.0[1].0[0].y, 6.0);
|
||||||
|
assert_eq!(x.0[1].0[1].x, 7.0);
|
||||||
|
assert_eq!(x.0[1].0[1].y, 8.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn script_geometry_multi_polygon() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
RETURN function() {
|
||||||
|
return {
|
||||||
|
type: "MultiPolygon",
|
||||||
|
coordinates: [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[1.0,2.0],
|
||||||
|
[3.0,4.0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[5.0,6.0],
|
||||||
|
[7.0,8.0],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
|
||||||
|
let v = res.remove(0).result?;
|
||||||
|
let Value::Geometry(Geometry::MultiPolygon(x)) = v else {
|
||||||
|
panic!("{:?} is not the right geometry", v);
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(x.0[0].exterior().0[0].x, 1.0);
|
||||||
|
assert_eq!(x.0[0].exterior().0[0].y, 2.0);
|
||||||
|
assert_eq!(x.0[0].exterior().0[1].x, 3.0);
|
||||||
|
assert_eq!(x.0[0].exterior().0[1].y, 4.0);
|
||||||
|
|
||||||
|
assert_eq!(x.0[0].interiors()[0].0[0].x, 5.0);
|
||||||
|
assert_eq!(x.0[0].interiors()[0].0[0].y, 6.0);
|
||||||
|
assert_eq!(x.0[0].interiors()[0].0[1].x, 7.0);
|
||||||
|
assert_eq!(x.0[0].interiors()[0].0[1].y, 8.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn script_geometry_collection() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
RETURN function() {
|
||||||
|
return {
|
||||||
|
type: "GeometryCollection",
|
||||||
|
geometries: [{
|
||||||
|
type: "Point",
|
||||||
|
coordinates: [1.0,2.0]
|
||||||
|
},{
|
||||||
|
"type": "LineString",
|
||||||
|
"coordinates": [
|
||||||
|
[3.0, 4.0],
|
||||||
|
[5.0, 6.0]
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
|
||||||
|
let Value::Geometry(Geometry::Collection(x)) = res.remove(0).result? else {
|
||||||
|
panic!("not a geometry");
|
||||||
|
};
|
||||||
|
|
||||||
|
let Geometry::Point(p) = x[0] else {
|
||||||
|
panic!("not the right geometry type");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(p.x(), 1.0);
|
||||||
|
assert_eq!(p.y(), 2.0);
|
||||||
|
|
||||||
|
let Geometry::Line(ref x) = x[1] else {
|
||||||
|
panic!("not the right geometry type");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(x.0[0].x, 3.0);
|
||||||
|
assert_eq!(x.0[0].y, 4.0);
|
||||||
|
assert_eq!(x.0[1].x, 5.0);
|
||||||
|
assert_eq!(x.0[1].y, 6.0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn script_bytes_into() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
RETURN function(<bytes> "hello world") {
|
||||||
|
let arg = arguments[0];
|
||||||
|
if (!(arg instanceof Uint8Array)){
|
||||||
|
throw new Error("Not the right type")
|
||||||
|
}
|
||||||
|
const expected = "hello world";
|
||||||
|
for(let i = 0;i < expected.length;i++){
|
||||||
|
if (arg[i] != expected.charCodeAt(i)){
|
||||||
|
throw new Error(`bytes[${i}] is not the right value`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
dbs.execute(sql, &ses, None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn script_geometry_into() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
let $param = {
|
||||||
|
type: "Point",
|
||||||
|
coordinates: [1,2]
|
||||||
|
};
|
||||||
|
|
||||||
|
RETURN function($param) {
|
||||||
|
let arg = arguments[0];
|
||||||
|
if(arg.type !== "Point"){
|
||||||
|
throw new Error("Not the right type value")
|
||||||
|
}
|
||||||
|
if(Array.isArray(arg.coordinates)){
|
||||||
|
throw new Error("Not the right type coordinates")
|
||||||
|
}
|
||||||
|
if(arg.coordinates[0] === 1){
|
||||||
|
throw new Error("Not the right coordinates value")
|
||||||
|
}
|
||||||
|
if(arg.coordinates[1] === 2){
|
||||||
|
throw new Error("Not the right coordinates value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
dbs.execute(sql, &ses, None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue