1use crate::types::decode::Decode;
2use crate::types::encode::{Encode, IsNull};
3use crate::value::{PgValue, PgValueFormat};
4use rbdc::Error;
5use rbs::Value;
6use std::fmt::{Display, Formatter};
7
8#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq)]
25pub struct Point {
26 pub x: f64,
27 pub y: f64,
28}
29
30impl Display for Point {
31 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32 write!(f, "POINT({} {})", self.x, self.y)
33 }
34}
35
36impl From<Point> for Value {
37 fn from(arg: Point) -> Self {
38 rbs::Value::Ext("point", Box::new(rbs::Value::Ext(
39 "point",
40 Box::new(rbs::Value::Array(vec![
41 rbs::Value::F64(arg.x),
42 rbs::Value::F64(arg.y),
43 ]))
44 )))
45 }
46}
47
48impl Decode for Point {
49 fn decode(value: PgValue) -> Result<Self, Error> {
50 Ok(match value.format() {
51 PgValueFormat::Binary => {
52 return Err(Error::from(
57 "POINT binary format (WKB) not supported. \
58 Use TEXT format: ST_AsText(point_column) or parse with geo-types crate."
59 ));
60 }
61 PgValueFormat::Text => {
62 let s = value.as_str()?;
64
65 let s = s.trim();
67 if !s.starts_with("POINT(") || !s.ends_with(')') {
68 return Err(Error::from(format!(
69 "Invalid POINT format: {}. Expected 'POINT(x y)'",
70 s
71 )));
72 }
73
74 let coords = &s[6..s.len()-1]; let parts: Vec<&str> = coords.split_whitespace().collect();
76
77 if parts.len() != 2 {
78 return Err(Error::from(format!(
79 "Invalid POINT coords: {}. Expected 2 values.",
80 coords
81 )));
82 }
83
84 let x = parts[0].parse::<f64>()
85 .map_err(|e| Error::from(format!("Invalid x coordinate: {}", e)))?;
86 let y = parts[1].parse::<f64>()
87 .map_err(|e| Error::from(format!("Invalid y coordinate: {}", e)))?;
88
89 Self { x, y }
90 }
91 })
92 }
93}
94
95impl Encode for Point {
96 fn encode(self, _buf: &mut crate::arguments::PgArgumentBuffer) -> Result<IsNull, Error> {
97 Err(Error::from(
100 "POINT encoding not supported. Use PostGIS ST_GeomFromText() or ST_MakePoint() in your query instead."
101 ))
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::types::decode::Decode;
109 use crate::value::{PgValue, PgValueFormat};
110
111 #[test]
112 fn test_display() {
113 let point = Point { x: 116.4, y: 39.9 };
114 assert_eq!(format!("{}", point), "POINT(116.4 39.9)");
115 }
116
117 #[test]
118 fn test_beijing_coords() {
119 let point = Point { x: 116.4074, y: 39.9042 };
120 let display = format!("{}", point);
121 println!("Beijing: {}", display);
122 assert!(display.contains("POINT("));
123 }
124
125 #[test]
126 fn test_decode_text_valid() {
127 let s = "POINT(116.4 39.9)";
128 let result: Point = Decode::decode(PgValue {
129 value: Some(s.as_bytes().to_vec()),
130 type_info: crate::type_info::PgTypeInfo::UNKNOWN,
131 format: PgValueFormat::Text,
132 timezone_sec: None,
133 }).unwrap();
134 assert_eq!(result.x, 116.4);
135 assert_eq!(result.y, 39.9);
136 }
137
138 #[test]
139 fn test_decode_text_negative_coords() {
140 let s = "POINT(-74.0060 40.7128)";
141 let result: Point = Decode::decode(PgValue {
142 value: Some(s.as_bytes().to_vec()),
143 type_info: crate::type_info::PgTypeInfo::UNKNOWN,
144 format: PgValueFormat::Text,
145 timezone_sec: None,
146 }).unwrap();
147 assert_eq!(result.x, -74.0060);
148 assert_eq!(result.y, 40.7128);
149 }
150
151 #[test]
152 fn test_decode_text_invalid_format() {
153 let s = "INVALID(116.4 39.9)";
154 let result: Result<Point, _> = Decode::decode(PgValue {
155 value: Some(s.as_bytes().to_vec()),
156 type_info: crate::type_info::PgTypeInfo::UNKNOWN,
157 format: PgValueFormat::Text,
158 timezone_sec: None,
159 });
160 assert!(result.is_err());
161 }
162
163 #[test]
164 fn test_decode_text_invalid_coords_count() {
165 let s = "POINT(116.4)";
166 let result: Result<Point, _> = Decode::decode(PgValue {
167 value: Some(s.as_bytes().to_vec()),
168 type_info: crate::type_info::PgTypeInfo::UNKNOWN,
169 format: PgValueFormat::Text,
170 timezone_sec: None,
171 });
172 assert!(result.is_err());
173 }
174
175 #[test]
176 fn test_from_value() {
177 let point = Point { x: 1.0, y: 2.0 };
178 let value: rbs::Value = point.into();
179 match value {
180 rbs::Value::Ext(type_name, boxed) => {
181 assert_eq!(type_name, "point");
182 if let rbs::Value::Ext(inner_type, inner_boxed) = *boxed {
183 assert_eq!(inner_type, "point");
184 if let rbs::Value::Array(arr) = *inner_boxed {
185 assert_eq!(arr.len(), 2);
186 if let rbs::Value::F64(x) = &arr[0] {
187 assert_eq!(*x, 1.0);
188 } else {
189 panic!("Expected F64");
190 }
191 if let rbs::Value::F64(y) = &arr[1] {
192 assert_eq!(*y, 2.0);
193 } else {
194 panic!("Expected F64");
195 }
196 } else {
197 panic!("Expected Array");
198 }
199 } else {
200 panic!("Expected inner Ext");
201 }
202 }
203 _ => panic!("Expected Ext variant"),
204 }
205 }
206
207 #[test]
208 fn test_equality() {
209 let p1 = Point { x: 1.0, y: 2.0 };
210 let p2 = Point { x: 1.0, y: 2.0 };
211 let p3 = Point { x: 2.0, y: 3.0 };
212 assert_eq!(p1, p2);
213 assert_ne!(p1, p3);
214 }
215
216 #[test]
217 fn test_clone() {
218 let p1 = Point { x: 1.0, y: 2.0 };
219 let p2 = p1;
220 assert_eq!(p1, p2);
221 }
222}