diff --git a/Cargo.toml b/Cargo.toml index f750dc5..c50e9e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "diesel_geometry" -version = "1.4.0" +version = "2.0.0" authors = ["YetAnotherMinion "] license = "MIT OR Apache-2.0" description = "Adds support for geometric types and functions to Diesel." @@ -12,7 +12,7 @@ categories = ["database"] [dependencies] byteorder = "1.0" -diesel = { version = ">=1.2, <1.5", features = ["postgres"] } +diesel = { version = "2", features = ["postgres"] } serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] diff --git a/src/doctest_setup.rs b/src/doctest_setup.rs index eea0f20..f7b7133 100644 --- a/src/doctest_setup.rs +++ b/src/doctest_setup.rs @@ -2,7 +2,7 @@ extern crate cfg_if; extern crate dotenv; -use diesel::prelude::*; +use diesel::{prelude::*, connection::SimpleConnection}; use diesel_geometry::prelude::*; use self::dotenv::dotenv; @@ -17,30 +17,30 @@ cfg_if! { } fn connection_no_data() -> PgConnection { - let connection = connection_no_transaction(); + let mut connection = connection_no_transaction(); connection.begin_test_transaction().unwrap(); - connection.execute("DROP TABLE IF EXISTS drawings CASCADE").unwrap(); - connection.execute("DROP TABLE IF EXISTS shapes CASCADE").unwrap(); + connection.batch_execute("DROP TABLE IF EXISTS drawings CASCADE").unwrap(); + connection.batch_execute("DROP TABLE IF EXISTS shapes CASCADE").unwrap(); connection } #[allow(dead_code)] fn establish_connection() -> PgConnection { - let connection = connection_no_data(); + let mut connection = connection_no_data(); - connection.execute("CREATE TABLE drawings ( + connection.batch_execute("CREATE TABLE drawings ( id SERIAL PRIMARY KEY, title VARCHAR NOT NULL )").unwrap(); - connection.execute("INSERT INTO drawings (title) VALUES ('Cubism'), ('Airplanes')").unwrap(); + connection.batch_execute("INSERT INTO drawings (title) VALUES ('Cubism'), ('Airplanes')").unwrap(); - connection.execute("CREATE TABLE shapes ( + connection.batch_execute("CREATE TABLE shapes ( id SERIAL PRIMARY KEY, drawing_id INTEGER NOT NULL, centroid POINT )").unwrap(); - connection.execute("INSERT INTO shapes (drawing_id, centroid) VALUES + connection.batch_execute("INSERT INTO shapes (drawing_id, centroid) VALUES (1, point '(0, 0)'), (2, point '(1,2)')").unwrap(); diff --git a/src/pg/expression/expression_methods.rs b/src/pg/expression/expression_methods.rs index 3fb73b1..22e7a09 100644 --- a/src/pg/expression/expression_methods.rs +++ b/src/pg/expression/expression_methods.rs @@ -1,10 +1,9 @@ use diesel::expression::{AsExpression, Expression}; -use diesel::pg::expression::operators::IsContainedBy; -use super::operators::SameAs; +use super::operators::{IsContainedBy, SameAs}; use sql_types::{self, Circle, Point}; -pub trait PgSameAsExpressionMethods: Expression + Sized { +pub trait PgSameAsExpressionMethods: Expression + Sized { /// Creates a PostgresSQL `~=` expression. /// /// The "same as" operator, ~=, represents the usual notion of equality for the `point`, `box`, @@ -22,14 +21,14 @@ pub trait PgSameAsExpressionMethods: Expression + Sized { /// # /// # fn main() { /// # use schema::shapes::dsl::*; - /// # let connection = establish_connection(); + /// # let mut connection = establish_connection(); /// let found_drawing_id = shapes /// .select(drawing_id) /// .filter(centroid.same_as(PgPoint(1.0, 2.0))) - /// .first(&connection); + /// .first(&mut connection); /// assert_eq!(Ok(2), found_drawing_id); /// # } - fn same_as(self: Self, other: T) -> SameAs + fn same_as(self, other: T) -> SameAs where T: AsExpression, { @@ -41,7 +40,7 @@ impl> PgSameAsExpressionMethods for T {} impl> PgSameAsExpressionMethods for T {} impl> PgSameAsExpressionMethods for T {} -pub trait PgIsContainedByExpressionMethods: Expression + Sized { +pub trait PgIsContainedByExpressionMethods: Expression + Sized { /// Creates a PostgresSQL `<@` expression. /// /// For geometric types. @@ -57,7 +56,7 @@ pub trait PgIsContainedByExpressionMethods: Expression + Sized { /// # /// # fn main() { /// # use schema::shapes::dsl::*; - /// # let connection = establish_connection(); + /// # let mut connection = establish_connection(); /// // Looking for point at (1,2) /// let found_drawing_id = shapes /// .select(drawing_id) @@ -66,10 +65,11 @@ pub trait PgIsContainedByExpressionMethods: Expression + Sized { /// PgBox(PgPoint(0.5, 1.5), PgPoint(3.0,5.0)).into_sql::() /// ) /// ) - /// .first(&connection); + /// .first(&mut connection); /// assert_eq!(Ok(2), found_drawing_id); /// # } - fn is_contained_by(self: Self, other: T) -> IsContainedBy + #[allow(clippy::wrong_self_convention)] + fn is_contained_by(self, other: T) -> IsContainedBy where T: AsExpression, { diff --git a/src/pg/expression/operators.rs b/src/pg/expression/operators.rs index 30caf70..c444036 100644 --- a/src/pg/expression/operators.rs +++ b/src/pg/expression/operators.rs @@ -1,3 +1,4 @@ use diesel::pg::Pg; -diesel_infix_operator!(SameAs, " ~= ", backend: Pg); +infix_operator!(SameAs, " ~= ", backend: Pg); +infix_operator!(IsContainedBy, " <@ ", backend: Pg); diff --git a/src/pg/types/geometric.rs b/src/pg/types/geometric.rs index a577c0c..0b60395 100644 --- a/src/pg/types/geometric.rs +++ b/src/pg/types/geometric.rs @@ -1,26 +1,32 @@ //! Support for Geometric types under PostgreSQL. use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt}; -use std::io::prelude::*; +use diesel::backend::RawValue; use diesel::deserialize::{self, FromSql}; -use diesel::expression::bound::Bound; use diesel::expression::AsExpression; use diesel::pg::Pg; use diesel::serialize::{self, IsNull, Output, ToSql}; -use diesel::sql_types::Nullable; use sql_types::{self, Circle, Point}; /// Point is represented in Postgres as a tuple of 64 bit floating point values (x, y). This /// struct is a dumb wrapper type, meant only to indicate the tuple's meaning. #[derive(Debug, Clone, PartialEq, Copy, FromSqlRow, AsExpression)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[sql_type = "Point"] +#[diesel(sql_type = Point)] pub struct PgPoint(pub f64, pub f64); +impl PgPoint { + fn from_sql_bytes(mut bytes: &[u8]) -> deserialize::Result { + let x = bytes.read_f64::()?; + let y = bytes.read_f64::()?; + Ok(PgPoint(x, y)) + } +} + impl FromSql for PgPoint { - fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result { - let mut bytes = not_none!(bytes); + fn from_sql(bytes: RawValue<'_, Pg>) -> deserialize::Result { + let mut bytes = bytes.as_bytes(); let x = bytes.read_f64::()?; let y = bytes.read_f64::()?; Ok(PgPoint(x, y)) @@ -28,7 +34,7 @@ impl FromSql for PgPoint { } impl ToSql for PgPoint { - fn to_sql(&self, out: &mut Output) -> serialize::Result { + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result { out.write_f64::(self.0)?; out.write_f64::(self.1)?; Ok(IsNull::No) @@ -40,7 +46,7 @@ impl ToSql for PgPoint { #[derive(Debug, Clone, PartialEq, Copy, FromSqlRow)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(AsExpression)] -#[sql_type = "sql_types::Box"] +#[diesel(sql_type = sql_types::Box)] pub struct PgBox(pub PgPoint, pub PgPoint); // We must manually derive AsExpression because sql_types::Box would conflict with the builtin Box @@ -81,19 +87,19 @@ pub struct PgBox(pub PgPoint, pub PgPoint); // https://github.com/postgres/postgres/blob/9d4649ca49416111aee2c84b7e4441a0b7aa2fac/src/backend/utils/adt/geo_ops.c impl FromSql for PgBox { - fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result { - let bytes = not_none!(bytes); + fn from_sql(value: RawValue<'_, Pg>) -> deserialize::Result { + let bytes = value.as_bytes(); let (upper_bytes, lower_bytes) = bytes.split_at(16); // By convention the box is written as (lower left, upper right) and is stored as [ high.x, // high,y, low.x, low.y ]. - let upper = PgPoint::from_sql(Some(upper_bytes))?; - let lower = PgPoint::from_sql(Some(lower_bytes))?; + let upper = PgPoint::from_sql_bytes(upper_bytes)?; + let lower = PgPoint::from_sql_bytes(lower_bytes)?; Ok(PgBox(lower, upper)) } } impl ToSql for PgBox { - fn to_sql(&self, out: &mut Output) -> serialize::Result { + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result { // By convention the box is written as (lower left, upper right) // and is stored as [ high.x, high,y, low.x, low.y ]. Postgres will reorder the corners if // necessary. We write the points assuming the Box is following convention. @@ -114,21 +120,21 @@ impl ToSql for PgBox { /// This struct is a dumb wrapper type, meant only to indicate the tuple's meaning. #[derive(Debug, Clone, PartialEq, Copy, FromSqlRow, AsExpression)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[sql_type = "Circle"] +#[diesel(sql_type = Circle)] pub struct PgCircle(pub PgPoint, pub f64); impl FromSql for PgCircle { - fn from_sql(bytes: Option<&[u8]>) -> deserialize::Result { - let bytes = not_none!(bytes); + fn from_sql(value: RawValue<'_, Pg>) -> deserialize::Result { + let bytes = value.as_bytes(); let (center_bytes, mut radius_bytes) = bytes.split_at(16); - let center = PgPoint::from_sql(Some(center_bytes))?; + let center = PgPoint::from_sql_bytes(center_bytes)?; let radius = radius_bytes.read_f64::()?; Ok(PgCircle(center, radius)) } } impl ToSql for PgCircle { - fn to_sql(&self, out: &mut Output) -> serialize::Result { + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result { >::to_sql(&self.0, out)?; out.write_f64::(self.1)?; @@ -139,42 +145,22 @@ impl ToSql for PgCircle { #[cfg(test)] mod tests { use diesel; - use diesel::deserialize::FromSql; + use diesel::connection::SimpleConnection; use diesel::dsl::sql; - use diesel::pg::Pg; use diesel::prelude::*; use diesel::select; - use diesel::serialize::ToSql; use expression_methods::*; use pg::types::geometric::{PgBox, PgCircle, PgPoint}; use sql_types::{self, Circle, Point}; - use test_helpers::{connection, create_testing_output}; - - #[test] - fn point_roundtrip() { - let mut bytes = create_testing_output(); - let input_point = PgPoint(4.5, 3439.1); - ToSql::::to_sql(&input_point, &mut bytes).unwrap(); - let output_point: PgPoint = FromSql::from_sql(Some(bytes.as_ref())).unwrap(); - assert_eq!(input_point, output_point); - } - - #[test] - fn no_point_from_sql() { - let uuid: Result = FromSql::::from_sql(None); - assert_eq!( - uuid.unwrap_err().description(), - "Unexpected null for non-null column" - ); - } + use test_helpers::{connection}; #[test] fn point_encodes_correctly() { - let connection = connection(); + let mut connection = connection(); let point = PgPoint(3.0, 4.0); let query = select(sql::("point '(3, 4)'").same_as(point)); - assert!(query.get_result::(&connection).unwrap()); + assert!(query.get_result::(&mut connection).unwrap()); } mod schema { @@ -211,7 +197,7 @@ mod tests { // Compile check that PgPoint can be used in insertable context, use self::schema::items; #[derive(Debug, Clone, Copy, Insertable)] - #[table_name = "items"] + #[diesel(table_name = items)] struct NewItem { name: &'static str, location: ::pg::types::geometric::PgPoint, @@ -226,9 +212,10 @@ mod tests { #[test] fn point_is_queryable() { - let connection = connection(); + let mut connection = connection(); // Compile check that PgPoint can be used in queryable context, #[derive(Debug, Clone, Queryable)] + #[allow(dead_code)] struct Item { id: i32, name: String, @@ -238,14 +225,15 @@ mod tests { let _query_row = items .filter(id.eq(1)) .filter(location.same_as(PgPoint(3.1, 9.4))) - .get_result::(&connection); + .get_result::(&mut connection); } #[test] fn box_roundtrip() { - let connection = connection(); + let mut connection = connection(); + connection - .execute( + .batch_execute( "CREATE TABLE box_roundtrip ( id SERIAL PRIMARY KEY, boxes BOX @@ -253,7 +241,7 @@ mod tests { ).unwrap(); use self::schema::box_roundtrip; #[derive(Debug, PartialEq, Insertable, Queryable)] - #[table_name = "box_roundtrip"] + #[diesel(table_name = box_roundtrip)] struct Roundtrip { id: i32, boxes: Option<::pg::types::geometric::PgBox>, @@ -264,9 +252,9 @@ mod tests { }; diesel::insert_into(box_roundtrip::table) .values(&data) - .execute(&connection) + .execute(&mut connection) .unwrap(); - let x = box_roundtrip::table.first::(&connection); + let x = box_roundtrip::table.first::(&mut connection); match x { Ok(record) => assert_eq!(data, record), Err(_) => panic!(), @@ -277,7 +265,7 @@ mod tests { #[test] fn point_contained_queries() { - let connection = connection(); + let mut connection = connection(); let point = PgPoint(1., 1.); let bounding_box = PgBox(PgPoint(0., 0.), PgPoint(2., 2.)); let bounding_circle = PgCircle(PgPoint(0., 0.), 3.0); @@ -285,22 +273,22 @@ mod tests { point .into_sql::() .is_contained_by(bounding_circle.into_sql::()), - ).get_result::(&connection) + ).get_result::(&mut connection) .unwrap(); assert!(is_contained); let is_contained = diesel::select( AsExpression::::as_expression(point) .is_contained_by(bounding_box.into_sql::()), - ).get_result::(&connection) + ).get_result::(&mut connection) .unwrap(); assert!(is_contained); } #[test] fn circle_roundtrip() { - let connection = connection(); + let mut connection = connection(); connection - .execute( + .batch_execute( "CREATE TABLE circle_roundtrip ( id SERIAL PRIMARY KEY, circles CIRCLE @@ -308,7 +296,7 @@ mod tests { ).unwrap(); use self::schema::circle_roundtrip; #[derive(Debug, PartialEq, Insertable, Queryable)] - #[table_name = "circle_roundtrip"] + #[diesel(table_name = circle_roundtrip)] struct Roundtrip { id: i32, circles: Option<::pg::types::geometric::PgCircle>, @@ -319,9 +307,9 @@ mod tests { }; diesel::insert_into(circle_roundtrip::table) .values(&data) - .execute(&connection) + .execute(&mut connection) .unwrap(); - let x = circle_roundtrip::table.first::(&connection); + let x = circle_roundtrip::table.first::(&mut connection); match x { Ok(record) => assert_eq!(data, record), Err(_) => panic!(), diff --git a/src/pg/types/mod.rs b/src/pg/types/mod.rs index 685d5a0..e99c094 100644 --- a/src/pg/types/mod.rs +++ b/src/pg/types/mod.rs @@ -37,8 +37,8 @@ pub mod sql_types { /// # fn main() { /// # use diesel::insert_into; /// # use items::dsl::*; - /// # let connection = connection_no_data(); - /// # connection.execute("CREATE TABLE items ( + /// # let mut connection = connection_no_data(); + /// # connection.batch_execute("CREATE TABLE items ( /// # id SERIAL PRIMARY KEY, /// # name VARCHAR NOT NULL, /// # location POINT NOT NULL @@ -46,12 +46,12 @@ pub mod sql_types { /// let inserted_location = insert_into(items) /// .values((name.eq("Shiny Thing"), location.eq(PgPoint(3.1, 9.4)))) /// .returning(location) - /// .get_result(&connection); + /// .get_result(&mut connection); /// assert_eq!(Ok(PgPoint(3.1, 9.4)), inserted_location); /// # } /// ``` #[derive(Debug, Clone, Copy, Default, QueryId, SqlType)] - #[postgres(oid = "600", array_oid = "1017")] + #[diesel(postgres_type(oid = 600, array_oid = 1017))] pub struct Point; /// The PostgreSQL [Box](https://www.postgresql.org/docs/current/static/datatype-geometric.html) type. @@ -90,8 +90,8 @@ pub mod sql_types { /// # use diesel::insert_into; /// # use diesel_geometry::prelude::*; /// # use items::dsl::*; - /// # let connection = connection_no_data(); - /// # connection.execute("CREATE TABLE items ( + /// # let mut connection = connection_no_data(); + /// # connection.batch_execute("CREATE TABLE items ( /// # id SERIAL PRIMARY KEY, /// # name VARCHAR NOT NULL, /// # location POINT NOT NULL @@ -99,19 +99,19 @@ pub mod sql_types { /// insert_into(items) /// .values((name.eq("Shiny Thing"), location.eq(PgPoint(3.1, 9.4)))) /// .returning(location) - /// .execute(&connection) + /// .execute(&mut connection) /// .unwrap(); /// let inserted_location = items /// .select(location) /// .filter(location.is_contained_by( /// PgBox(PgPoint(0.,0.), PgPoint(10.,10.)).into_sql::() /// )) - /// .first(&connection); + /// .first(&mut connection); /// assert_eq!(Ok(PgPoint(3.1, 9.4)), inserted_location); /// # } /// ``` #[derive(Debug, Clone, Copy, Default, QueryId, SqlType)] - #[postgres(oid = "603", array_oid = "1020")] + #[diesel(postgres_type(oid = 603, array_oid = 1020))] pub struct Box; /// The PostgreSQL [Circle](https://www.postgresql.org/docs/current/static/datatype-geometric.html) type. @@ -149,8 +149,8 @@ pub mod sql_types { /// # fn main() { /// # use diesel::insert_into; /// # use items::dsl::*; - /// # let connection = connection_no_data(); - /// # connection.execute("CREATE TABLE items ( + /// # let mut connection = connection_no_data(); + /// # connection.batch_execute("CREATE TABLE items ( /// # id SERIAL PRIMARY KEY, /// # name VARCHAR NOT NULL, /// # location CIRCLE NOT NULL @@ -158,11 +158,11 @@ pub mod sql_types { /// let inserted_location = insert_into(items) /// .values((name.eq("Shiny Thing"), location.eq(PgCircle(PgPoint(3.1, 6.6), 9.4)))) /// .returning(location) - /// .get_result(&connection); + /// .get_result(&mut connection); /// assert_eq!(Ok(PgCircle(PgPoint(3.1, 6.6), 9.4)), inserted_location); /// # } /// ``` #[derive(Debug, Clone, Copy, Default, QueryId, SqlType)] - #[postgres(oid = "718", array_oid = "719")] + #[diesel(postgres_type(oid = 718, array_oid = 719))] pub struct Circle; } diff --git a/src/test_helpers.rs b/src/test_helpers.rs index 48ef929..0051408 100644 --- a/src/test_helpers.rs +++ b/src/test_helpers.rs @@ -7,7 +7,7 @@ cfg_if! { pub type TestConnection = PgConnection; pub fn connection() -> TestConnection { - let conn = PgConnection::establish(&database_url()).unwrap(); + let mut conn = PgConnection::establish(&database_url()).unwrap(); conn.begin_test_transaction().unwrap(); conn } @@ -18,15 +18,6 @@ cfg_if! { .expect("DATABASE_URL must be set in order to run tests") } - use diesel::serialize::Output; - use diesel::sql_types::TypeMetadata; - /// Returns a `Output` suitable for testing `ToSql` implementations. - /// Unsafe to use for testing types which perform dynamic metadata lookup. - pub fn create_testing_output() -> Output<'static, Vec, DB> { - use std::mem; - #[cfg_attr(feature = "clippy", allow(invalid_ref))] - Output::new(Vec::new(), unsafe { mem::uninitialized() }) - } } else { compile_error!( "At least one backend must be used to test this crate.\n \