nodedb_sql/planner/
spatial_ctor.rs1use sqlparser::ast;
13
14use super::dml_helpers::expr_to_sql_value;
15use crate::error::{Result, SqlError};
16use crate::parser::normalize::normalize_ident;
17use crate::types::*;
18
19pub(super) fn try_spatial_constructor(func: &ast::Function) -> Option<Result<SqlValue>> {
23 SpatialConstructor::from_function(func).map(|ctor| spatial_constructor_to_value(ctor, func))
24}
25
26#[derive(Copy, Clone)]
31enum SpatialConstructor {
32 Point,
33 MakePoint,
34 GeomFromGeoJson,
35 GeomFromText,
36 GeomFromWkb,
37}
38
39impl SpatialConstructor {
40 fn from_function(func: &ast::Function) -> Option<Self> {
41 let name = func
42 .name
43 .0
44 .iter()
45 .map(|p| match p {
46 ast::ObjectNamePart::Identifier(ident) => normalize_ident(ident),
47 _ => String::new(),
48 })
49 .collect::<Vec<_>>()
50 .join(".")
51 .to_lowercase();
52 match name.as_str() {
53 "st_point" => Some(Self::Point),
54 "st_makepoint" => Some(Self::MakePoint),
55 "st_geomfromgeojson" => Some(Self::GeomFromGeoJson),
56 "st_geomfromtext" => Some(Self::GeomFromText),
57 "st_geomfromwkb" => Some(Self::GeomFromWkb),
58 _ => None,
59 }
60 }
61
62 fn display_name(self) -> &'static str {
63 match self {
64 Self::Point => "ST_Point",
65 Self::MakePoint => "ST_MakePoint",
66 Self::GeomFromGeoJson => "ST_GeomFromGeoJSON",
67 Self::GeomFromText => "ST_GeomFromText",
68 Self::GeomFromWkb => "ST_GeomFromWKB",
69 }
70 }
71}
72
73fn spatial_constructor_to_value(
74 ctor: SpatialConstructor,
75 func: &ast::Function,
76) -> Result<SqlValue> {
77 let args = super::select::extract_func_args(func)?;
78 match ctor {
79 SpatialConstructor::Point => {
80 if args.len() < 2 {
81 return Err(SqlError::InvalidFunction {
82 detail: format!(
83 "{} requires 2 arguments (longitude, latitude), got {}",
84 ctor.display_name(),
85 args.len()
86 ),
87 });
88 }
89 let lon = super::select::extract_float(&args[0])?;
90 let lat = super::select::extract_float(&args[1])?;
91 Ok(SqlValue::String(format!(
92 r#"{{"type":"Point","coordinates":[{lon},{lat}]}}"#
93 )))
94 }
95 SpatialConstructor::MakePoint => {
96 if args.len() < 2 {
97 return Err(SqlError::InvalidFunction {
98 detail: format!(
99 "{} requires at least 2 arguments (x, y[, z]), got {}",
100 ctor.display_name(),
101 args.len()
102 ),
103 });
104 }
105 let x = super::select::extract_float(&args[0])?;
106 let y = super::select::extract_float(&args[1])?;
107 if args.len() >= 3 {
109 let z = super::select::extract_float(&args[2])?;
110 Ok(SqlValue::String(format!(
111 r#"{{"type":"Point","coordinates":[{x},{y},{z}]}}"#
112 )))
113 } else {
114 Ok(SqlValue::String(format!(
115 r#"{{"type":"Point","coordinates":[{x},{y}]}}"#
116 )))
117 }
118 }
119 SpatialConstructor::GeomFromText => {
120 if args.is_empty() {
121 return Err(SqlError::InvalidFunction {
122 detail: format!("{} requires a WKT string argument", ctor.display_name()),
123 });
124 }
125 let wkt = super::select::extract_string_literal(&args[0])?;
127 let geom = nodedb_spatial::geometry_from_wkt(&wkt).ok_or_else(|| {
128 SqlError::InvalidFunction {
129 detail: format!("{}: invalid WKT geometry '{wkt}'", ctor.display_name()),
130 }
131 })?;
132 geometry_to_geojson_value(geom, ctor)
133 }
134 SpatialConstructor::GeomFromWkb => {
135 if args.is_empty() {
136 return Err(SqlError::InvalidFunction {
137 detail: format!("{} requires a WKB bytes argument", ctor.display_name()),
138 });
139 }
140 let bytes = match expr_to_sql_value(&args[0])? {
142 SqlValue::Bytes(b) => b,
143 SqlValue::String(s) => hex_string_to_bytes(&s, ctor)?,
146 other => {
147 return Err(SqlError::InvalidFunction {
148 detail: format!(
149 "{} requires a bytes argument, got {other:?}",
150 ctor.display_name()
151 ),
152 });
153 }
154 };
155 let geom = nodedb_spatial::geometry_from_wkb(&bytes).ok_or_else(|| {
156 SqlError::InvalidFunction {
157 detail: format!("{}: invalid WKB geometry", ctor.display_name()),
158 }
159 })?;
160 geometry_to_geojson_value(geom, ctor)
161 }
162 SpatialConstructor::GeomFromGeoJson => {
163 if args.is_empty() {
164 return Err(SqlError::InvalidFunction {
165 detail: format!(
166 "{} requires 1 argument (GeoJSON string)",
167 ctor.display_name()
168 ),
169 });
170 }
171 let s = super::select::extract_string_literal(&args[0])?;
172 Ok(SqlValue::String(s))
173 }
174 }
175}
176
177fn geometry_to_geojson_value(
182 geom: nodedb_types::geometry::Geometry,
183 ctor: SpatialConstructor,
184) -> Result<SqlValue> {
185 let json = sonic_rs::to_string(&geom).map_err(|e| SqlError::InvalidFunction {
186 detail: format!("{}: failed to serialize geometry: {e}", ctor.display_name()),
187 })?;
188 Ok(SqlValue::String(json))
189}
190
191fn hex_string_to_bytes(s: &str, ctor: SpatialConstructor) -> Result<Vec<u8>> {
196 crate::resolver::expr::value::parse_hex_bytes(s).map_err(|reason| SqlError::InvalidFunction {
197 detail: format!("{}: hex WKB {reason}", ctor.display_name()),
198 })
199}