diff --git a/README.md b/README.md
index cd6edb4f..804740ac 100644
--- a/README.md
+++ b/README.md
@@ -253,6 +253,27 @@ types. The driver currently supports the following conversions:
(optional)
MACADDR |
+
+
+ geo::Point<f64>
+ (optional)
+ |
+ POINT |
+
+
+
+ geo::Bbox<f64>
+ (optional)
+ |
+ BOX |
+
+
+
+ geo::LineString<f64>
+ (optional)
+ |
+ PATH |
@@ -316,3 +337,21 @@ and `FromSql` implementations for `bit-vec`'s `BitVec` type.
[MACADDR](http://www.postgresql.org/docs/9.4/static/datatype-net-types.html#DATATYPE-MACADDR)
support is provided optionally by the `with-eui48` feature, which adds `ToSql`
and `FromSql` implementations for `eui48`'s `MacAddress` type.
+
+### POINT type
+
+[POINT](https://www.postgresql.org/docs/9.4/static/datatype-geometric.html#AEN6799)
+support is provided optionally by the `with-geo` feature, which adds `ToSql` and `FromSql` implementations for `geo`'s `Point` type.
+
+### BOX type
+
+[BOX](https://www.postgresql.org/docs/9.4/static/datatype-geometric.html#AEN6883)
+support is provided optionally by the `with-geo` feature, which adds `ToSql` and `FromSql` implementations for `geo`'s `Bbox` type.
+
+### PATH type
+
+[PATH](https://www.postgresql.org/docs/9.4/static/datatype-geometric.html#AEN6912)
+support is provided optionally by the `with-geo` feature, which adds `ToSql` and `FromSql` implementations for `geo`'s `LineString` type.
+Paths converted from LineString are always treated as "open" paths. Use the
+[pclose](https://www.postgresql.org/docs/8.2/static/functions-geometry.html#FUNCTIONS-GEOMETRY-FUNC-TABLE)
+geometric function to insert a closed path.
diff --git a/postgres-shared/Cargo.toml b/postgres-shared/Cargo.toml
index 8b30e491..e9073cfe 100644
--- a/postgres-shared/Cargo.toml
+++ b/postgres-shared/Cargo.toml
@@ -10,6 +10,7 @@ repository = "https://github.com/sfackler/rust-postgres"
with-bit-vec = ["bit-vec"]
with-chrono = ["chrono"]
with-eui48 = ["eui48"]
+with-geo = ["geo"]
with-rustc-serialize = ["rustc-serialize"]
with-serde_json = ["serde_json"]
with-time = ["time"]
@@ -24,6 +25,7 @@ postgres-protocol = "0.2"
bit-vec = { version = "0.4", optional = true }
chrono = { version = "0.3", optional = true }
eui48 = { version = "0.1", optional = true }
+geo = { version = "0.4", optional = true }
rustc-serialize = { version = "0.3", optional = true }
serde_json = { version = "0.9", optional = true }
time = { version = "0.1.14", optional = true }
diff --git a/postgres-shared/src/types/geo.rs b/postgres-shared/src/types/geo.rs
new file mode 100644
index 00000000..13080495
--- /dev/null
+++ b/postgres-shared/src/types/geo.rs
@@ -0,0 +1,104 @@
+extern crate geo;
+
+use postgres_protocol::types;
+use self::geo::{Bbox, LineString, Point};
+use std::error::Error;
+
+use types::{FromSql, ToSql, IsNull, Type};
+
+impl FromSql for Point {
+ fn from_sql(_: &Type, raw: &[u8]) -> Result> {
+ if raw.len() != 16 {
+ return Err("invalid message length".into());
+ }
+
+ let x = types::float8_from_sql(&raw[0..8])?;
+ let y = types::float8_from_sql(&raw[8..16])?;
+ Ok(Point::new(x, y))
+ }
+
+ accepts!(Type::Point);
+}
+
+impl ToSql for Point {
+ fn to_sql(&self, _: &Type, out: &mut Vec) -> Result> {
+ types::float8_to_sql(self.x(), out);
+ types::float8_to_sql(self.y(), out);
+ Ok(IsNull::No)
+ }
+
+ accepts!(Type::Point);
+ to_sql_checked!();
+}
+
+impl FromSql for Bbox {
+ fn from_sql(_: &Type, raw: &[u8]) -> Result> {
+ if raw.len() != 32 {
+ return Err("invalid message length".into());
+ }
+
+ let xmax = types::float8_from_sql(&raw[0..8])?;
+ let ymax = types::float8_from_sql(&raw[8..16])?;
+ let xmin = types::float8_from_sql(&raw[16..24])?;
+ let ymin = types::float8_from_sql(&raw[24..32])?;
+ Ok(Bbox{xmax: xmax, ymax: ymax, xmin: xmin, ymin: ymin})
+ }
+
+ accepts!(Type::Box);
+}
+
+impl ToSql for Bbox {
+ fn to_sql(&self, _: &Type, out: &mut Vec) -> Result> {
+ types::float8_to_sql(self.xmax, out);
+ types::float8_to_sql(self.ymax, out);
+ types::float8_to_sql(self.xmin, out);
+ types::float8_to_sql(self.ymin, out);
+ Ok(IsNull::No)
+ }
+
+ accepts!(Type::Box);
+ to_sql_checked!();
+}
+
+impl FromSql for LineString {
+ fn from_sql(_: &Type, raw: &[u8]) -> Result> {
+ if raw.len() < 5 {
+ return Err("invalid message length".into());
+ }
+
+ // let _ = types::bool_from_sql(&raw[0..1])?; // is path open or closed
+ let n_points = types::int4_from_sql(&raw[1..5])? as usize;
+ let raw_points = &raw[5..raw.len()];
+ if raw_points.len() != 16 * n_points {
+ return Err("invalid message length".into());
+ }
+
+ let mut offset = 0;
+ let mut points = Vec::with_capacity(n_points);
+ for _ in 0..n_points {
+ let x = types::float8_from_sql(&raw_points[offset..offset+8])?;
+ let y = types::float8_from_sql(&raw_points[offset+8..offset+16])?;
+ points.push(Point::new(x, y));
+ offset += 16;
+ }
+ Ok(LineString(points))
+ }
+
+ accepts!(Type::Path);
+}
+
+impl ToSql for LineString {
+ fn to_sql(&self, _: &Type, out: &mut Vec) -> Result> {
+ let closed = false; // always encode an open path from LineString
+ types::bool_to_sql(closed, out);
+ types::int4_to_sql(self.0.len() as i32, out);
+ for point in &self.0 {
+ types::float8_to_sql(point.x(), out);
+ types::float8_to_sql(point.y(), out);
+ }
+ Ok(IsNull::No)
+ }
+
+ accepts!(Type::Path);
+ to_sql_checked!();
+}
diff --git a/postgres-shared/src/types/mod.rs b/postgres-shared/src/types/mod.rs
index 5a7a8f9c..76ba41fa 100644
--- a/postgres-shared/src/types/mod.rs
+++ b/postgres-shared/src/types/mod.rs
@@ -73,6 +73,8 @@ mod serde_json;
mod chrono;
#[cfg(feature = "with-eui48")]
mod eui48;
+#[cfg(feature = "with-geo")]
+mod geo;
mod special;
mod type_gen;
diff --git a/postgres/Cargo.toml b/postgres/Cargo.toml
index 86ccbeec..47270e2b 100644
--- a/postgres/Cargo.toml
+++ b/postgres/Cargo.toml
@@ -24,6 +24,7 @@ path = "tests/test.rs"
with-bit-vec = ["postgres-shared/with-bit-vec"]
with-chrono = ["postgres-shared/with-chrono"]
with-eui48 = ["postgres-shared/with-eui48"]
+with-geo = ["postgres-shared/with-geo"]
with-rustc-serialize = ["postgres-shared/with-rustc-serialize"]
with-serde_json = ["postgres-shared/with-serde_json"]
with-time = ["postgres-shared/with-time"]
@@ -57,6 +58,7 @@ url = "1.0"
bit-vec = "0.4"
chrono = "0.3"
eui48 = "0.1"
+geo = "0.4"
rustc-serialize = "0.3"
serde_json = "0.9"
time = "0.1.14"
diff --git a/postgres/tests/types/geo.rs b/postgres/tests/types/geo.rs
new file mode 100644
index 00000000..7b6afc0d
--- /dev/null
+++ b/postgres/tests/types/geo.rs
@@ -0,0 +1,28 @@
+extern crate geo;
+
+use self::geo::{Bbox, LineString, Point};
+use types::test_type;
+
+#[test]
+fn test_point_params() {
+ test_type("POINT",
+ &[(Some(Point::new(0.0, 0.0)), "POINT(0, 0)"),
+ (Some(Point::new(-3.14, 1.618)), "POINT(-3.14, 1.618)"),
+ (None, "NULL")]);
+}
+
+#[test]
+fn test_box_params() {
+ test_type("BOX",
+ &[(Some(Bbox{xmax: 160.0, ymax: 69701.5615, xmin: -3.14, ymin: 1.618}),
+ "BOX(POINT(160.0, 69701.5615), POINT(-3.14, 1.618))"),
+ (None, "NULL")]);
+}
+
+#[test]
+fn test_path_params() {
+ let points = vec![Point::new(0.0, 0.0), Point::new(-3.14, 1.618), Point::new(160.0, 69701.5615)];
+ test_type("PATH",
+ &[(Some(LineString(points)),"path '((0, 0), (-3.14, 1.618), (160.0, 69701.5615))'"),
+ (None, "NULL")]);
+}
diff --git a/postgres/tests/types/mod.rs b/postgres/tests/types/mod.rs
index 719f5d39..7634452a 100644
--- a/postgres/tests/types/mod.rs
+++ b/postgres/tests/types/mod.rs
@@ -23,6 +23,8 @@ mod rustc_serialize;
mod serde_json;
#[cfg(feature = "with-chrono")]
mod chrono;
+#[cfg(feature = "with-geo")]
+mod geo;
fn test_type(sql_type: &str, checks: &[(T, S)]) {
let conn = or_panic!(Connection::connect("postgres://postgres@localhost", TlsMode::None));
diff --git a/tokio-postgres/Cargo.toml b/tokio-postgres/Cargo.toml
index 22f9e521..4f5fbb03 100644
--- a/tokio-postgres/Cargo.toml
+++ b/tokio-postgres/Cargo.toml
@@ -13,6 +13,7 @@ keywords = ["database", "postgres", "postgresql", "sql", "async"]
with-bit-vec = ["postgres-shared/with-bit-vec"]
with-chrono = ["postgres-shared/with-chrono"]
with-eui48 = ["postgres-shared/with-eui48"]
+with-geo = ["postgres-shared/with-geo"]
with-rustc-serialize = ["postgres-shared/with-rustc-serialize"]
with-serde_json = ["postgres-shared/with-serde_json"]
with-time = ["postgres-shared/with-time"]