Skip to content

Commit

Permalink
issues-336 Third try impl postgres-array (#467)
Browse files Browse the repository at this point in the history
* issues-336 Third try impl postgres-array

* issues-336 Fix compile errors

* issues-336 Fix sqlx_postgres.rs

* issues-336 Fix compile error

* issues-336 Fix example

* issues-336 Add small documentation

* Reuse $name ident in type_to_box_value macro for ArrayType variant

* issues-336 Fix after review

* issues-336 Fix macros

* issues-336 Rename function

Co-authored-by: Billy Chan <[email protected]>
  • Loading branch information
ikrivosheev and billy1624 authored Oct 16, 2022
1 parent a3a5241 commit 9c2a6b2
Show file tree
Hide file tree
Showing 14 changed files with 412 additions and 50 deletions.
5 changes: 3 additions & 2 deletions examples/postgres/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime};
use postgres::types::IsNull::No;
use postgres::{Client, NoTls, Row};
use rust_decimal::Decimal;
use sea_query::{ColumnDef, Iden, Order, PostgresQueryBuilder, Query, Table};
use sea_query::{ColumnDef, ColumnType, Iden, Order, PostgresQueryBuilder, Query, Table};
use sea_query_postgres::PostgresBinder;
use time::{
macros::{date, offset, time},
Expand Down Expand Up @@ -34,7 +35,7 @@ fn main() {
.col(ColumnDef::new(Document::Timestamp).timestamp())
.col(ColumnDef::new(Document::TimestampWithTimeZone).timestamp_with_time_zone())
.col(ColumnDef::new(Document::Decimal).decimal())
.col(ColumnDef::new(Document::Array).array("integer".into()))
.col(ColumnDef::new(Document::Array).array(ColumnType::Integer(None)))
.build(PostgresQueryBuilder),
]
.join("; ");
Expand Down
29 changes: 20 additions & 9 deletions sea-query-binder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,33 @@ rust-version = "1.60"
[lib]

[dependencies]
sea-query = { version = "^0", path = ".." }
sea-query = { version = "^0", path = "..", features = ["thread-safe"] }
sqlx = { version = "^0.6.1", optional = true }
serde_json = { version = "^1", optional = true }
chrono = { version = "^0.4", default-features = false, features = ["clock"], optional = true }
postgres-types = { version = "^0", optional = true }
rust_decimal = { version = "^1", optional = true }
bigdecimal = { version = "^0.3", optional = true }
uuid = { version = "^1", optional = true }
proc-macro2 = { version = "1", optional = true }
quote = { version = "^1", optional = true }
time = { version = "^0.3", optional = true, features = ["macros", "formatting"] }
ipnetwork = { version = "^0.19", optional = true }
mac_address = { version = "^1.1", optional = true }

[features]
sqlx-mysql = ["sqlx/mysql"]
sqlx-postgres = ["sqlx/postgres"]
sqlx-sqlite = ["sqlx/sqlite"]
sqlx-any = ["sqlx/any"]
with-chrono = ["sqlx/chrono", "sea-query/with-chrono"]
with-json = ["sqlx/json", "sea-query/with-json"]
with-rust_decimal = ["sqlx/decimal", "sea-query/with-rust_decimal"]
with-bigdecimal = ["sqlx/bigdecimal", "sea-query/with-bigdecimal"]
with-uuid = ["sqlx/uuid", "sea-query/with-uuid"]
with-time = ["sqlx/time", "sea-query/with-time"]
with-ipnetwork = ["sqlx/ipnetwork", "sea-query/with-ipnetwork"]
with-mac_address = ["sqlx/mac_address", "sea-query/with-mac_address"]
with-chrono = ["sqlx/chrono", "sea-query/with-chrono", "chrono"]
with-json = ["sqlx/json", "sea-query/with-json", "serde_json"]
with-rust_decimal = ["sqlx/decimal", "sea-query/with-rust_decimal", "rust_decimal"]
with-bigdecimal = ["sqlx/bigdecimal", "sea-query/with-bigdecimal", "bigdecimal"]
with-uuid = ["sqlx/uuid", "sea-query/with-uuid", "uuid"]
with-time = ["sqlx/time", "sea-query/with-time", "time"]
with-ipnetwork = ["sqlx/ipnetwork", "sea-query/with-ipnetwork", "ipnetwork"]
with-mac_address = ["sqlx/mac_address", "sea-query/with-mac_address", "mac_address"]
postgres-array = ["sea-query/postgres-array"]
runtime-async-std-native-tls = ["sqlx/runtime-async-std-native-tls"]
runtime-async-std-rustls = ["sqlx/runtime-async-std-rustls", ]
Expand Down
2 changes: 1 addition & 1 deletion sea-query-binder/src/sqlx_any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::any::Any> for SqlxValues {
panic!("SeaQuery doesn't support MacAddress arguments for Any");
}
#[cfg(feature = "postgres-array")]
Value::Array(_) => {
Value::Array(_, _) => {
panic!("SeaQuery doesn't support array arguments for Any");
}
}
Expand Down
2 changes: 1 addition & 1 deletion sea-query-binder/src/sqlx_mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::mysql::MySql> for SqlxValues {
args.add(j.as_deref());
}
#[cfg(feature = "postgres-array")]
Value::Array(_) => {
Value::Array(_, _) => {
panic!("Mysql doesn't support array arguments");
}
#[cfg(feature = "with-ipnetwork")]
Expand Down
209 changes: 203 additions & 6 deletions sea-query-binder/src/sqlx_postgres.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
#[cfg(feature = "with-bigdecimal")]
use bigdecimal::BigDecimal;
#[cfg(feature = "with-chrono")]
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc};
#[cfg(feature = "with-ipnetwork")]
use ipnetwork::IpNetwork;
#[cfg(feature = "with-mac_address")]
use mac_address::MacAddress;
#[cfg(feature = "with-rust_decimal")]
use rust_decimal::Decimal;
#[cfg(feature = "with-json")]
use serde_json::Value as Json;
#[cfg(feature = "with-uuid")]
use uuid::Uuid;

use sea_query::{ArrayType, Value};

use crate::SqlxValues;
use sea_query::Value;

impl<'q> sqlx::IntoArguments<'q, sqlx::postgres::Postgres> for SqlxValues {
fn into_arguments(self) -> sqlx::postgres::PgArguments {
Expand Down Expand Up @@ -32,7 +48,7 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::postgres::Postgres> for SqlxValues {
args.add(i.map(|i| i as i64));
}
Value::BigUnsigned(i) => {
args.add(i.map(|i| <i64 as std::convert::TryFrom<u64>>::try_from(i).unwrap()));
args.add(i.map(|i| <i64 as TryFrom<u64>>::try_from(i).unwrap()));
}
Value::Float(f) => {
args.add(f);
Expand Down Expand Up @@ -105,10 +121,6 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::postgres::Postgres> for SqlxValues {
Value::Json(j) => {
args.add(j.as_deref());
}
#[cfg(feature = "postgres-array")]
Value::Array(_) => {
panic!("SeaQuery doesn't support array arguments for Postgresql");
}
#[cfg(feature = "with-ipnetwork")]
Value::IpNetwork(ip) => {
args.add(ip.as_deref());
Expand All @@ -117,6 +129,191 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::postgres::Postgres> for SqlxValues {
Value::MacAddress(mac) => {
args.add(mac.as_deref());
}
#[cfg(feature = "postgres-array")]
Value::Array(ty, v) => match ty {
ArrayType::Bool => {
let value: Option<Vec<bool>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::Bool");
args.add(value)
}
ArrayType::TinyInt => {
let value: Option<Vec<i8>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::TinyInt");
args.add(value)
}
ArrayType::SmallInt => {
let value: Option<Vec<i16>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::SmallInt");
args.add(value)
}
ArrayType::Int => {
let value: Option<Vec<i32>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::Int");
args.add(value)
}
ArrayType::BigInt => {
let value: Option<Vec<i64>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::BigInt");
args.add(value)
}
ArrayType::TinyUnsigned => {
let value: Option<Vec<u8>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::TinyUnsigned");
let value: Option<Vec<i16>> =
value.map(|vec| vec.into_iter().map(|i| i as i16).collect());
args.add(value)
}
ArrayType::SmallUnsigned => {
let value: Option<Vec<u16>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::SmallUnsigned");
let value: Option<Vec<i32>> =
value.map(|vec| vec.into_iter().map(|i| i as i32).collect());
args.add(value)
}
ArrayType::Unsigned => {
let value: Option<Vec<u32>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::Unsigned");
let value: Option<Vec<i64>> =
value.map(|vec| vec.into_iter().map(|i| i as i64).collect());
args.add(value)
}
ArrayType::BigUnsigned => {
let value: Option<Vec<u64>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::BigUnsigned");
let value: Option<Vec<i64>> = value.map(|vec| {
vec.into_iter()
.map(|i| <i64 as TryFrom<u64>>::try_from(i).unwrap())
.collect()
});
args.add(value)
}
ArrayType::Float => {
let value: Option<Vec<f32>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::Float");
args.add(value)
}
ArrayType::Double => {
let value: Option<Vec<f64>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::Double");
args.add(value)
}
ArrayType::String => {
let value: Option<Vec<String>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::String");
args.add(value)
}
ArrayType::Char => {
let value: Option<Vec<char>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::Char");
let value: Option<Vec<String>> =
value.map(|vec| vec.into_iter().map(|c| c.to_string()).collect());
args.add(value)
}
ArrayType::Bytes => {
let value: Option<Vec<Vec<u8>>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::Bytes");
args.add(value)
}
#[cfg(feature = "with-chrono")]
ArrayType::ChronoDate => {
let value: Option<Vec<NaiveDate>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::ChronoDate");
args.add(value);
}
#[cfg(feature = "with-chrono")]
ArrayType::ChronoTime => {
let value: Option<Vec<NaiveTime>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::ChronoTime");
args.add(value);
}
#[cfg(feature = "with-chrono")]
ArrayType::ChronoDateTime => {
let value: Option<Vec<NaiveDateTime>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::ChronoDateTime");
args.add(value);
}
#[cfg(feature = "with-chrono")]
ArrayType::ChronoDateTimeUtc => {
let value: Option<Vec<DateTime<Utc>>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::ChronoDateTimeUtc");
args.add(value);
}
#[cfg(feature = "with-chrono")]
ArrayType::ChronoDateTimeLocal => {
let value: Option<Vec<DateTime<Local>>> = Value::Array(ty, v).expect(
"This Value::Array should consist of Value::ChronoDateTimeLocal",
);
args.add(value);
}
#[cfg(feature = "with-chrono")]
ArrayType::ChronoDateTimeWithTimeZone => {
let value: Option<Vec<DateTime<Local>>> = Value::Array(ty, v).expect(
"This Value::Array should consist of Value::ChronoDateTimeWithTimeZone",
);
args.add(value);
}
#[cfg(feature = "with-time")]
ArrayType::TimeDate => {
let value: Option<Vec<time::Date>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::TimeDate");
args.add(value);
}
#[cfg(feature = "with-time")]
ArrayType::TimeTime => {
let value: Option<Vec<time::Time>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::TimeTime");
args.add(value);
}
#[cfg(feature = "with-time")]
ArrayType::TimeDateTime => {
let value: Option<Vec<time::PrimitiveDateTime>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::TimeDateTime");
args.add(value);
}
#[cfg(feature = "with-time")]
ArrayType::TimeDateTimeWithTimeZone => {
let value: Option<Vec<time::OffsetDateTime>> = Value::Array(ty, v).expect(
"This Value::Array should consist of Value::TimeDateTimeWithTimeZone",
);
args.add(value);
}
#[cfg(feature = "with-uuid")]
ArrayType::Uuid => {
let value: Option<Vec<Uuid>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::Uuid");
args.add(value);
}
#[cfg(feature = "with-rust_decimal")]
ArrayType::Decimal => {
let value: Option<Vec<Decimal>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::Decimal");
args.add(value);
}
#[cfg(feature = "with-bigdecimal")]
ArrayType::BigDecimal => {
let value: Option<Vec<BigDecimal>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::BigDecimal");
args.add(value);
}
#[cfg(feature = "with-json")]
ArrayType::Json => {
let value: Option<Vec<Json>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::Json");
args.add(value);
}
#[cfg(feature = "with-ipnetwork")]
ArrayType::IpNetwork => {
let value: Option<Vec<IpNetwork>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::IpNetwork");
args.add(value);
}
#[cfg(feature = "with-mac_address")]
ArrayType::MacAddress => {
let value: Option<Vec<MacAddress>> = Value::Array(ty, v)
.expect("This Value::Array should consist of Value::MacAddress");
args.add(value);
}
},
}
}
args
Expand Down
2 changes: 1 addition & 1 deletion sea-query-binder/src/sqlx_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::sqlite::Sqlite> for SqlxValues {
panic!("Sqlite doesn't support MacAddress arguments");
}
#[cfg(feature = "postgres-array")]
Value::Array(_) => {
Value::Array(_, _) => {
panic!("Sqlite doesn't support array arguments");
}
}
Expand Down
6 changes: 3 additions & 3 deletions sea-query-postgres/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use postgres_types::{to_sql_checked, IsNull, ToSql, Type};
use sea_query::{query::*, QueryBuilder, Value};

#[derive(Clone, Debug, PartialEq)]
pub struct PostgresValue(pub sea_query::Value);
pub struct PostgresValue(pub Value);
#[derive(Clone, Debug, PartialEq)]
pub struct PostgresValues(pub Vec<PostgresValue>);

Expand Down Expand Up @@ -101,13 +101,13 @@ impl ToSql for PostgresValue {
#[cfg(feature = "with-uuid")]
Value::Uuid(v) => v.as_deref().to_sql(ty, out),
#[cfg(feature = "postgres-array")]
Value::Array(Some(v)) => v
Value::Array(_, Some(v)) => v
.iter()
.map(|v| PostgresValue(v.clone()))
.collect::<Vec<PostgresValue>>()
.to_sql(ty, out),
#[cfg(feature = "postgres-array")]
Value::Array(None) => Ok(IsNull::Yes),
Value::Array(_, None) => Ok(IsNull::Yes),
#[allow(unreachable_patterns)]
_ => unimplemented!(),
}
Expand Down
2 changes: 1 addition & 1 deletion sea-query-rusqlite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl ToSql for RusqliteValue {
panic!("Rusqlite doesn't support MacAddress arguments");
}
#[cfg(feature = "postgres-array")]
Value::Array(_) => {
Value::Array(_, _) => {
panic!("Rusqlite doesn't support Array arguments");
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/backend/postgres/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ impl TableBuilder for PostgresQueryBuilder {
ColumnType::Json => "json".into(),
ColumnType::JsonBinary => "jsonb".into(),
ColumnType::Uuid => "uuid".into(),
ColumnType::Array(elem_type) => format!("{}[]", elem_type.as_ref().unwrap()),
ColumnType::Array(elem_type) => {
let mut sql = String::new();
self.prepare_column_type(elem_type, &mut sql);
format!("{}[]", sql)
}
ColumnType::Custom(iden) => iden.to_string(),
ColumnType::Enum { name, .. } => name.to_string(),
ColumnType::Cidr => "cidr".into(),
Expand Down
4 changes: 2 additions & 2 deletions src/backend/query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -966,7 +966,7 @@ pub trait QueryBuilder: QuotedBuilder + EscapeBuilder + TableRefBuilder {
#[cfg(feature = "with-mac_address")]
Value::MacAddress(None) => write!(s, "NULL").unwrap(),
#[cfg(feature = "postgres-array")]
Value::Array(None) => write!(s, "NULL").unwrap(),
Value::Array(_, None) => write!(s, "NULL").unwrap(),
Value::Bool(Some(b)) => write!(s, "{}", if *b { "TRUE" } else { "FALSE" }).unwrap(),
Value::TinyInt(Some(v)) => write!(s, "{}", v).unwrap(),
Value::SmallInt(Some(v)) => write!(s, "{}", v).unwrap(),
Expand Down Expand Up @@ -1036,7 +1036,7 @@ pub trait QueryBuilder: QuotedBuilder + EscapeBuilder + TableRefBuilder {
#[cfg(feature = "with-uuid")]
Value::Uuid(Some(v)) => write!(s, "'{}'", v).unwrap(),
#[cfg(feature = "postgres-array")]
Value::Array(Some(v)) => write!(
Value::Array(_, Some(v)) => write!(
s,
"'{{{}}}'",
v.iter()
Expand Down
Loading

0 comments on commit 9c2a6b2

Please sign in to comment.