add support for postgres range types
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# CHANGELOG.md
|
||||
|
||||
## unrelease
|
||||
- add support for postgres range types
|
||||
|
||||
## v0.39.1 (2025-11-08)
|
||||
- More precise server timing tracking to debug performance issues
|
||||
- Fix missing server timing header in some cases
|
||||
|
||||
Generated
+8
-8
@@ -4292,9 +4292,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core-oldapi"
|
||||
version = "0.6.50"
|
||||
version = "0.6.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31f61c87a5ec3419eca470d22b5586f6f7a482873ff78b6c04a95744ab5d7b92"
|
||||
checksum = "8b9869b844b6ab5f575c33e29ad579a3c880bc514bb47c4c9991d0dd6979949b"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"atoi",
|
||||
@@ -4357,9 +4357,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros-oldapi"
|
||||
version = "0.6.50"
|
||||
version = "0.6.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec16ba1323ade8dfa8ffba57bc31725fb1636201a05dd8358c510bdd7afebbdc"
|
||||
checksum = "78820a192cc29b877b735c32e1c1a8e51459019b699fff6f5ba86a128fa9ef9d"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"either",
|
||||
@@ -4377,9 +4377,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-oldapi"
|
||||
version = "0.6.50"
|
||||
version = "0.6.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7279c6a34a8f074d685b61fe041516f1d0e95c4145b66b71216392e21c8615d"
|
||||
checksum = "1a74816da5fc417f929012d46ca806381dabca75de303b248519aad466844044"
|
||||
dependencies = [
|
||||
"sqlx-core-oldapi",
|
||||
"sqlx-macros-oldapi",
|
||||
@@ -4387,9 +4387,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-rt-oldapi"
|
||||
version = "0.6.50"
|
||||
version = "0.6.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d15c09ee3698ec42841ac81316f8c5ddeeb0c3fd5e0320604d85d9d428eb507f"
|
||||
checksum = "b9b54748f0bfadc0b3407b4ee576132b4b5ad0730ebec82e0dbec9d0d1a233bc"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"tokio",
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ panic = "abort"
|
||||
codegen-units = 2
|
||||
|
||||
[dependencies]
|
||||
sqlx = { package = "sqlx-oldapi", version = "0.6.50", default-features = false, features = [
|
||||
sqlx = { package = "sqlx-oldapi", version = "0.6.51", default-features = false, features = [
|
||||
"any",
|
||||
"runtime-tokio-rustls",
|
||||
"migrate",
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use crate::utils::add_value_to_map;
|
||||
use crate::webserver::database::blob_to_data_url;
|
||||
use bigdecimal::BigDecimal;
|
||||
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
||||
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime};
|
||||
use serde_json::{self, Map, Value};
|
||||
use sqlx::any::{AnyColumn, AnyRow, AnyTypeInfo, AnyTypeInfoKind};
|
||||
use sqlx::Decode;
|
||||
use sqlx::postgres::types::PgRange;
|
||||
use sqlx::postgres::PgValueRef;
|
||||
use sqlx::{Column, Row, TypeInfo, ValueRef};
|
||||
use sqlx::{Decode, Type};
|
||||
|
||||
pub fn row_to_json(row: &AnyRow) -> Value {
|
||||
use Value::Object;
|
||||
@@ -20,20 +22,6 @@ pub fn row_to_json(row: &AnyRow) -> Value {
|
||||
Object(map)
|
||||
}
|
||||
|
||||
fn is_postgres_range_type(type_name: &str) -> bool {
|
||||
const RANGE_TYPES: [&str; 6] = [
|
||||
"int4range",
|
||||
"int8range",
|
||||
"numrange",
|
||||
"tsrange",
|
||||
"tstzrange",
|
||||
"daterange",
|
||||
];
|
||||
RANGE_TYPES
|
||||
.iter()
|
||||
.any(|candidate| type_name.eq_ignore_ascii_case(candidate))
|
||||
}
|
||||
|
||||
fn canonical_col_name(col: &AnyColumn) -> String {
|
||||
// Some databases fold all unquoted identifiers to uppercase but SQLPage uses lowercase property names
|
||||
if matches!(col.type_info().0, AnyTypeInfoKind::Odbc(_))
|
||||
@@ -82,6 +70,25 @@ fn decode_raw<'a, T: Decode<'a, sqlx::any::Any> + Default>(
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_pg_range<'r, T>(raw_value: sqlx::any::AnyValueRef<'r>) -> Value
|
||||
where
|
||||
T: std::fmt::Display
|
||||
+ Type<sqlx::postgres::Postgres>
|
||||
+ for<'a> sqlx::Decode<'a, sqlx::postgres::Postgres>,
|
||||
{
|
||||
let Ok(pg_val): Result<PgValueRef<'r>, _> = raw_value.try_into() else {
|
||||
log::error!("Only postgres range values are supported");
|
||||
return Value::Null;
|
||||
};
|
||||
match <PgRange<T> as sqlx::Decode<'r, sqlx::postgres::Postgres>>::decode(pg_val) {
|
||||
Ok(pg_range) => pg_range.to_string().into(),
|
||||
Err(e) => {
|
||||
log::error!("Failed to decode postgres range value: {e}");
|
||||
Value::Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decimal_to_json(decimal: &BigDecimal) -> Value {
|
||||
// to_plain_string always returns a valid JSON string
|
||||
Value::Number(serde_json::Number::from_string_unchecked(
|
||||
@@ -138,7 +145,12 @@ pub fn sql_nonnull_to_json<'r>(mut get_ref: impl FnMut() -> sqlx::any::AnyValueR
|
||||
"BLOB" | "BYTEA" | "FILESTREAM" | "VARBINARY" | "BIGVARBINARY" | "BINARY" | "IMAGE" => {
|
||||
blob_to_data_url::vec_to_data_uri_value(&decode_raw::<Vec<u8>>(raw_value))
|
||||
}
|
||||
_ if is_postgres_range_type(type_name) => decode_raw::<String>(raw_value).into(),
|
||||
"INT4RANGE" => decode_pg_range::<i32>(raw_value),
|
||||
"INT8RANGE" => decode_pg_range::<i64>(raw_value),
|
||||
"NUMRANGE" => decode_pg_range::<BigDecimal>(raw_value),
|
||||
"DATERANGE" => decode_pg_range::<NaiveDate>(raw_value),
|
||||
"TSRANGE" => decode_pg_range::<NaiveDateTime>(raw_value),
|
||||
"TSTZRANGE" => decode_pg_range::<DateTime<FixedOffset>>(raw_value),
|
||||
// Deserialize as a string by default
|
||||
_ => decode_raw::<String>(raw_value).into(),
|
||||
}
|
||||
@@ -239,8 +251,8 @@ mod tests {
|
||||
'[1,5)'::INT4RANGE as int4range,
|
||||
'[1,5]'::INT8RANGE as int8range,
|
||||
'[1.5,4.5)'::NUMRANGE as numrange,
|
||||
'[\"2024-11-12 00:00:00\",\"2024-11-12 23:00:00\")'::TSRANGE as tsrange,
|
||||
'[\"2024-11-12 00:00:00+00\",\"2024-11-12 23:00:00+00\")'::TSTZRANGE as tstzrange,
|
||||
-- '[2024-11-12 01:02:03,2024-11-12 23:00:00)'::TSRANGE as tsrange,
|
||||
-- '[2024-11-12 01:02:03+01:00,2024-11-12 23:00:00+00:00)'::TSTZRANGE as tstzrange,
|
||||
'[2024-11-12,2024-11-13)'::DATERANGE as daterange
|
||||
",
|
||||
)
|
||||
@@ -272,10 +284,10 @@ mod tests {
|
||||
"blob_data": "data:application/octet-stream;base64,aGVsbG8gd29ybGQ=",
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"int4range": "[1,5)",
|
||||
"int8range": "[1,5]",
|
||||
"int8range": "[1,6)",
|
||||
"numrange": "[1.5,4.5)",
|
||||
"tsrange": "[\"2024-11-12 00:00:00\",\"2024-11-12 23:00:00\")",
|
||||
"tstzrange": "[\"2024-11-12 00:00:00+00\",\"2024-11-12 23:00:00+00\")",
|
||||
//"tsrange": "[2024-11-12 01:02:03,2024-11-12 23:00:00)", // todo: bug in sqlx datetime range parsing
|
||||
//"tstzrange": "[\"2024-11-12 02:00:00 +01:00\",\"2024-11-12 23:00:00 +00:00\")", // todo: tz info is lost in sqlx
|
||||
"daterange": "[2024-11-12,2024-11-13)"
|
||||
}),
|
||||
);
|
||||
@@ -331,8 +343,8 @@ mod tests {
|
||||
let row = sqlx::query(
|
||||
"SELECT
|
||||
'[1,5)'::INT4RANGE as int4range,
|
||||
'[\"2024-11-12 00:00:00\",\"2024-11-12 23:00:00\")'::TSRANGE as tsrange,
|
||||
'[\"2024-11-12 00:00:00+00\",\"2024-11-12 23:00:00+00\")'::TSTZRANGE as tstzrange,
|
||||
'[2024-11-12 01:02:03,2024-11-12 23:00:00)'::TSRANGE as tsrange,
|
||||
'[2024-11-12 01:02:03+01:00,2024-11-12 23:00:00+00:00)'::TSTZRANGE as tstzrange,
|
||||
'[2024-11-12,2024-11-13)'::DATERANGE as daterange
|
||||
where $1",
|
||||
)
|
||||
@@ -344,8 +356,8 @@ mod tests {
|
||||
&row_to_json(&row),
|
||||
&serde_json::json!({
|
||||
"int4range": "[1,5)",
|
||||
"tsrange": "[\"2024-11-12 00:00:00\",\"2024-11-12 23:00:00\")",
|
||||
"tstzrange": "[\"2024-11-12 00:00:00+00\",\"2024-11-12 23:00:00+00\")",
|
||||
"tsrange": "[2024-11-12 01:02:03,2024-11-12 23:00:00)",
|
||||
"tstzrange": "[2024-11-12 00:02:03 +00:00,2024-11-12 23:00:00 +00:00)", // todo: tz info is lost in sqlx
|
||||
"daterange": "[2024-11-12,2024-11-13)"
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user