perf(postgres): classify result types once per column
This commit is contained in:
parent
7caf9b1feb
commit
1cd201d261
|
|
@ -304,123 +304,195 @@ fn format_pg_timestamptz(value: DateTime<Local>) -> String {
|
|||
value.to_rfc3339()
|
||||
}
|
||||
|
||||
pub(crate) fn pg_value_to_json(row: &Row, idx: usize, type_name: &str) -> serde_json::Value {
|
||||
/// 时间类型解码失败后的回退目标,与原 if 链中时间分支之后的匹配顺序一一对应。
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub(crate) enum PgTemporalFallback {
|
||||
/// 数组类型名(下划线开头)落到通用数组解码。
|
||||
GenericArray,
|
||||
/// `VECTOR(...)` 形式的类型名落到 pgvector 解码。
|
||||
Vector,
|
||||
/// 其余落到通用试探链。
|
||||
Probe,
|
||||
}
|
||||
|
||||
/// 每列一次的类型分类结果,避免在逐单元格路径上重复 `to_uppercase` 与字符串比较链。
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub(crate) enum PgColType {
|
||||
Bytea,
|
||||
Json,
|
||||
Bool,
|
||||
Temporal { fallback: PgTemporalFallback },
|
||||
Numeric,
|
||||
Uuid,
|
||||
Inet { cidr: bool },
|
||||
MacAddr,
|
||||
BitString,
|
||||
TsVector,
|
||||
SystemU32,
|
||||
InetArray { cidr: bool },
|
||||
MacAddrArray,
|
||||
BitStringArray,
|
||||
GenericArray,
|
||||
Vector,
|
||||
Geometry,
|
||||
Other,
|
||||
}
|
||||
|
||||
pub(crate) fn classify_pg_type(type_name: &str) -> PgColType {
|
||||
let upper = type_name.to_uppercase();
|
||||
|
||||
if upper == "BYTEA" {
|
||||
return row
|
||||
.try_get::<_, Vec<u8>>(idx)
|
||||
.map(|bytes| super::binary_value_to_json(&bytes))
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
return PgColType::Bytea;
|
||||
}
|
||||
|
||||
if upper == "JSON" || upper == "JSONB" {
|
||||
if let Ok(v) = row.try_get::<_, serde_json::Value>(idx) {
|
||||
return serde_json::Value::String(v.to_string());
|
||||
}
|
||||
if let Ok(v) = row.try_get::<_, String>(idx) {
|
||||
return serde_json::Value::String(v);
|
||||
}
|
||||
return serde_json::Value::Null;
|
||||
return PgColType::Json;
|
||||
}
|
||||
|
||||
if upper == "BOOL" {
|
||||
return row.try_get::<_, bool>(idx).map(serde_json::Value::Bool).unwrap_or(serde_json::Value::Null);
|
||||
return PgColType::Bool;
|
||||
}
|
||||
|
||||
if upper.contains("TIMESTAMP")
|
||||
|| upper == "DATE"
|
||||
|| upper == "TIME"
|
||||
|| upper == "TIMETZ"
|
||||
|| upper.contains("INTERVAL")
|
||||
{
|
||||
if let Some(v) = pg_temporal_to_json_value(row, idx) {
|
||||
return v;
|
||||
}
|
||||
let fallback = if upper.starts_with('_') {
|
||||
PgTemporalFallback::GenericArray
|
||||
} else if upper.starts_with("VECTOR(") {
|
||||
PgTemporalFallback::Vector
|
||||
} else {
|
||||
PgTemporalFallback::Probe
|
||||
};
|
||||
return PgColType::Temporal { fallback };
|
||||
}
|
||||
|
||||
if upper == "NUMERIC" || upper == "DECIMAL" || upper == "MONEY" {
|
||||
return row
|
||||
return PgColType::Numeric;
|
||||
}
|
||||
if upper == "UUID" {
|
||||
return PgColType::Uuid;
|
||||
}
|
||||
if matches!(upper.as_str(), "INET" | "CIDR") {
|
||||
return PgColType::Inet { cidr: upper == "CIDR" };
|
||||
}
|
||||
if matches!(upper.as_str(), "MACADDR" | "MACADDR8") {
|
||||
return PgColType::MacAddr;
|
||||
}
|
||||
if matches!(upper.as_str(), "BIT" | "VARBIT") {
|
||||
return PgColType::BitString;
|
||||
}
|
||||
if upper == "TSVECTOR" {
|
||||
return PgColType::TsVector;
|
||||
}
|
||||
if matches!(upper.as_str(), "OID" | "XID" | "CID") {
|
||||
return PgColType::SystemU32;
|
||||
}
|
||||
if matches!(upper.as_str(), "_INET" | "_CIDR") {
|
||||
return PgColType::InetArray { cidr: upper == "_CIDR" };
|
||||
}
|
||||
if matches!(upper.as_str(), "_MACADDR" | "_MACADDR8") {
|
||||
return PgColType::MacAddrArray;
|
||||
}
|
||||
if matches!(upper.as_str(), "_BIT" | "_VARBIT") {
|
||||
return PgColType::BitStringArray;
|
||||
}
|
||||
if upper.starts_with('_') {
|
||||
return PgColType::GenericArray;
|
||||
}
|
||||
if upper == "VECTOR" || upper.starts_with("VECTOR(") {
|
||||
return PgColType::Vector;
|
||||
}
|
||||
if upper == "GEOMETRY" || upper == "GEOGRAPHY" {
|
||||
return PgColType::Geometry;
|
||||
}
|
||||
PgColType::Other
|
||||
}
|
||||
|
||||
pub(crate) fn classify_pg_column_types(column_types: &[String]) -> Vec<PgColType> {
|
||||
column_types.iter().map(|type_name| classify_pg_type(type_name)).collect()
|
||||
}
|
||||
|
||||
pub(crate) fn pg_value_to_json_classified(row: &Row, idx: usize, col_type: PgColType) -> serde_json::Value {
|
||||
match col_type {
|
||||
PgColType::Bytea => row
|
||||
.try_get::<_, Vec<u8>>(idx)
|
||||
.map(|bytes| super::binary_value_to_json(&bytes))
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
PgColType::Json => {
|
||||
if let Ok(v) = row.try_get::<_, serde_json::Value>(idx) {
|
||||
return serde_json::Value::String(v.to_string());
|
||||
}
|
||||
if let Ok(v) = row.try_get::<_, String>(idx) {
|
||||
return serde_json::Value::String(v);
|
||||
}
|
||||
serde_json::Value::Null
|
||||
}
|
||||
PgColType::Bool => row.try_get::<_, bool>(idx).map(serde_json::Value::Bool).unwrap_or(serde_json::Value::Null),
|
||||
PgColType::Temporal { fallback } => {
|
||||
if let Some(v) = pg_temporal_to_json_value(row, idx) {
|
||||
return v;
|
||||
}
|
||||
match fallback {
|
||||
PgTemporalFallback::GenericArray => pg_array_to_json_value(row, idx).unwrap_or(serde_json::Value::Null),
|
||||
PgTemporalFallback::Vector => pg_vector_value_to_json(row, idx),
|
||||
PgTemporalFallback::Probe => pg_fallback_value_to_json(row, idx),
|
||||
}
|
||||
}
|
||||
PgColType::Numeric => row
|
||||
.try_get::<_, Decimal>(idx)
|
||||
.map(|v: Decimal| serde_json::Value::String(v.to_string()))
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
if upper == "UUID" {
|
||||
return row
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
PgColType::Uuid => row
|
||||
.try_get::<_, uuid::Uuid>(idx)
|
||||
.map(|v| serde_json::Value::String(v.to_string()))
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
if matches!(upper.as_str(), "INET" | "CIDR") {
|
||||
return pg_network_address_to_json_value(row, idx, upper == "CIDR").unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
if matches!(upper.as_str(), "MACADDR" | "MACADDR8") {
|
||||
return pg_macaddr_to_json_value(row, idx).unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
if matches!(upper.as_str(), "BIT" | "VARBIT") {
|
||||
return pg_bit_string_to_json_value(row, idx).unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
if upper == "TSVECTOR" {
|
||||
return row
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
PgColType::Inet { cidr } => pg_network_address_to_json_value(row, idx, cidr).unwrap_or(serde_json::Value::Null),
|
||||
PgColType::MacAddr => pg_macaddr_to_json_value(row, idx).unwrap_or(serde_json::Value::Null),
|
||||
PgColType::BitString => pg_bit_string_to_json_value(row, idx).unwrap_or(serde_json::Value::Null),
|
||||
PgColType::TsVector => row
|
||||
.try_get::<_, PgRawBytes>(idx)
|
||||
.ok()
|
||||
.and_then(|raw| decode_tsvector_bytes(&raw.0))
|
||||
.map(serde_json::Value::String)
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
if matches!(upper.as_str(), "OID" | "XID" | "CID") {
|
||||
return pg_system_u32_to_json(row, idx).unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
if matches!(upper.as_str(), "_INET" | "_CIDR") {
|
||||
return pg_network_address_array_to_json_value(row, idx, upper == "_CIDR").unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
if matches!(upper.as_str(), "_MACADDR" | "_MACADDR8") {
|
||||
return pg_macaddr_array_to_json_value(row, idx).unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
if matches!(upper.as_str(), "_BIT" | "_VARBIT") {
|
||||
return pg_bit_string_array_to_json_value(row, idx).unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
if upper.starts_with('_') {
|
||||
return pg_array_to_json_value(row, idx).unwrap_or(serde_json::Value::Null);
|
||||
}
|
||||
|
||||
if upper == "VECTOR" || upper.starts_with("VECTOR(") {
|
||||
if let Ok(PgRawBytes(raw)) = row.try_get::<_, PgRawBytes>(idx) {
|
||||
if let Some(floats) = decode_pgvector_bytes(&raw) {
|
||||
return serde_json::Value::Array(
|
||||
floats
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
serde_json::Number::from_f64((v as f64 * 1_000_000.0).round() / 1_000_000.0)
|
||||
.map(serde_json::Value::Number)
|
||||
.unwrap_or(serde_json::Value::Null)
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
PgColType::SystemU32 => pg_system_u32_to_json(row, idx).unwrap_or(serde_json::Value::Null),
|
||||
PgColType::InetArray { cidr } => {
|
||||
pg_network_address_array_to_json_value(row, idx, cidr).unwrap_or(serde_json::Value::Null)
|
||||
}
|
||||
PgColType::MacAddrArray => pg_macaddr_array_to_json_value(row, idx).unwrap_or(serde_json::Value::Null),
|
||||
PgColType::BitStringArray => pg_bit_string_array_to_json_value(row, idx).unwrap_or(serde_json::Value::Null),
|
||||
PgColType::GenericArray => pg_array_to_json_value(row, idx).unwrap_or(serde_json::Value::Null),
|
||||
PgColType::Vector => pg_vector_value_to_json(row, idx),
|
||||
PgColType::Geometry => {
|
||||
if let Ok(PgRawBytes(raw)) = row.try_get::<_, PgRawBytes>(idx) {
|
||||
return super::wkb::wkb_to_wkt(&raw)
|
||||
.map(serde_json::Value::String)
|
||||
.unwrap_or_else(|| super::binary_value_to_json(&raw));
|
||||
}
|
||||
serde_json::Value::Null
|
||||
}
|
||||
return serde_json::Value::Null;
|
||||
PgColType::Other => pg_fallback_value_to_json(row, idx),
|
||||
}
|
||||
}
|
||||
|
||||
if upper == "GEOMETRY" || upper == "GEOGRAPHY" {
|
||||
if let Ok(PgRawBytes(raw)) = row.try_get::<_, PgRawBytes>(idx) {
|
||||
return super::wkb::wkb_to_wkt(&raw)
|
||||
.map(serde_json::Value::String)
|
||||
.unwrap_or_else(|| super::binary_value_to_json(&raw));
|
||||
fn pg_vector_value_to_json(row: &Row, idx: usize) -> serde_json::Value {
|
||||
if let Ok(PgRawBytes(raw)) = row.try_get::<_, PgRawBytes>(idx) {
|
||||
if let Some(floats) = decode_pgvector_bytes(&raw) {
|
||||
return serde_json::Value::Array(
|
||||
floats
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
serde_json::Number::from_f64((v as f64 * 1_000_000.0).round() / 1_000_000.0)
|
||||
.map(serde_json::Value::Number)
|
||||
.unwrap_or(serde_json::Value::Null)
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
return serde_json::Value::Null;
|
||||
}
|
||||
serde_json::Value::Null
|
||||
}
|
||||
|
||||
fn pg_fallback_value_to_json(row: &Row, idx: usize) -> serde_json::Value {
|
||||
row.try_get::<_, String>(idx)
|
||||
.map(serde_json::Value::String)
|
||||
.or_else(|e| pg_system_u32_to_json(row, idx).ok_or(e))
|
||||
|
|
@ -630,6 +702,7 @@ async fn execute_select_prepared(
|
|||
);
|
||||
let columns: Vec<String> = stmt.columns().iter().map(|c| c.name().to_string()).collect();
|
||||
let column_types: Vec<String> = stmt.columns().iter().map(|c| c.type_().name().to_string()).collect();
|
||||
let column_classes = classify_pg_column_types(&column_types);
|
||||
|
||||
let params: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = Vec::new();
|
||||
let query_start = Instant::now();
|
||||
|
|
@ -653,7 +726,9 @@ async fn execute_select_prepared(
|
|||
let row = row_result?;
|
||||
result_rows.push(
|
||||
(0..row.columns().len())
|
||||
.map(|i| pg_value_to_json(&row, i, column_types.get(i).map(String::as_str).unwrap_or("")))
|
||||
.map(|i| {
|
||||
pg_value_to_json_classified(&row, i, column_classes.get(i).copied().unwrap_or(PgColType::Other))
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
|
@ -785,6 +860,7 @@ async fn stream_select_query_prepared(
|
|||
client.prepare_cached(sql).await.map_err(|err| PostgresQueryStreamError::Postgres { err, emitted: false })?;
|
||||
let columns: Vec<String> = stmt.columns().iter().map(|c| c.name().to_string()).collect();
|
||||
let column_types: Vec<String> = stmt.columns().iter().map(|c| c.type_().name().to_string()).collect();
|
||||
let column_classes = classify_pg_column_types(&column_types);
|
||||
|
||||
let params: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = Vec::new();
|
||||
let stream = client
|
||||
|
|
@ -806,7 +882,7 @@ async fn stream_select_query_prepared(
|
|||
columns_emitted = true;
|
||||
}
|
||||
let values = (0..row.columns().len())
|
||||
.map(|i| pg_value_to_json(&row, i, column_types.get(i).map(String::as_str).unwrap_or("")))
|
||||
.map(|i| pg_value_to_json_classified(&row, i, column_classes.get(i).copied().unwrap_or(PgColType::Other)))
|
||||
.collect();
|
||||
on_item(PostgresQueryStreamItem::Row(values)).map_err(PostgresQueryStreamError::Export)?;
|
||||
rows_streamed += 1;
|
||||
|
|
@ -915,6 +991,7 @@ async fn stream_query_rows_on_client(
|
|||
) -> Result<u64, String> {
|
||||
let stmt = client.prepare_cached(sql).await.map_err(pg_error_to_string)?;
|
||||
let column_types: Vec<String> = stmt.columns().iter().map(|c| c.type_().name().to_string()).collect();
|
||||
let column_classes = classify_pg_column_types(&column_types);
|
||||
let params: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = Vec::new();
|
||||
let stream = client.query_raw(&stmt, params).await.map_err(pg_error_to_string)?;
|
||||
tokio::pin!(stream);
|
||||
|
|
@ -930,7 +1007,7 @@ async fn stream_query_rows_on_client(
|
|||
}
|
||||
let row = row_result.map_err(pg_error_to_string)?;
|
||||
let values: Vec<serde_json::Value> = (0..row.columns().len())
|
||||
.map(|i| pg_value_to_json(&row, i, column_types.get(i).map(String::as_str).unwrap_or("")))
|
||||
.map(|i| pg_value_to_json_classified(&row, i, column_classes.get(i).copied().unwrap_or(PgColType::Other)))
|
||||
.collect();
|
||||
on_row(&values)?;
|
||||
rows_exported += 1;
|
||||
|
|
@ -3284,6 +3361,51 @@ mod tests {
|
|||
use std::time::Instant;
|
||||
use tokio_postgres::types::FromSql;
|
||||
|
||||
#[test]
|
||||
fn classify_pg_type_covers_all_dispatch_branches() {
|
||||
assert_eq!(classify_pg_type("bytea"), PgColType::Bytea);
|
||||
assert_eq!(classify_pg_type("json"), PgColType::Json);
|
||||
assert_eq!(classify_pg_type("JSONB"), PgColType::Json);
|
||||
assert_eq!(classify_pg_type("bool"), PgColType::Bool);
|
||||
assert_eq!(classify_pg_type("timestamp"), PgColType::Temporal { fallback: PgTemporalFallback::Probe });
|
||||
assert_eq!(classify_pg_type("timestamptz"), PgColType::Temporal { fallback: PgTemporalFallback::Probe });
|
||||
assert_eq!(classify_pg_type("date"), PgColType::Temporal { fallback: PgTemporalFallback::Probe });
|
||||
assert_eq!(classify_pg_type("time"), PgColType::Temporal { fallback: PgTemporalFallback::Probe });
|
||||
assert_eq!(classify_pg_type("timetz"), PgColType::Temporal { fallback: PgTemporalFallback::Probe });
|
||||
assert_eq!(classify_pg_type("interval"), PgColType::Temporal { fallback: PgTemporalFallback::Probe });
|
||||
// 时间数组类型名在原实现中先进时间分支、解码失败后落到通用数组分支
|
||||
assert_eq!(classify_pg_type("_timestamp"), PgColType::Temporal { fallback: PgTemporalFallback::GenericArray });
|
||||
assert_eq!(classify_pg_type("_interval"), PgColType::Temporal { fallback: PgTemporalFallback::GenericArray });
|
||||
// 同时命中时间关键字与 VECTOR( 前缀的类型名,原实现时间解码失败后走 vector 分支
|
||||
assert_eq!(classify_pg_type("vector(timestamp)"), PgColType::Temporal { fallback: PgTemporalFallback::Vector });
|
||||
assert_eq!(classify_pg_type("numeric"), PgColType::Numeric);
|
||||
assert_eq!(classify_pg_type("money"), PgColType::Numeric);
|
||||
assert_eq!(classify_pg_type("uuid"), PgColType::Uuid);
|
||||
assert_eq!(classify_pg_type("inet"), PgColType::Inet { cidr: false });
|
||||
assert_eq!(classify_pg_type("cidr"), PgColType::Inet { cidr: true });
|
||||
assert_eq!(classify_pg_type("macaddr"), PgColType::MacAddr);
|
||||
assert_eq!(classify_pg_type("macaddr8"), PgColType::MacAddr);
|
||||
assert_eq!(classify_pg_type("bit"), PgColType::BitString);
|
||||
assert_eq!(classify_pg_type("varbit"), PgColType::BitString);
|
||||
assert_eq!(classify_pg_type("tsvector"), PgColType::TsVector);
|
||||
assert_eq!(classify_pg_type("oid"), PgColType::SystemU32);
|
||||
assert_eq!(classify_pg_type("xid"), PgColType::SystemU32);
|
||||
assert_eq!(classify_pg_type("_inet"), PgColType::InetArray { cidr: false });
|
||||
assert_eq!(classify_pg_type("_cidr"), PgColType::InetArray { cidr: true });
|
||||
assert_eq!(classify_pg_type("_macaddr"), PgColType::MacAddrArray);
|
||||
assert_eq!(classify_pg_type("_bit"), PgColType::BitStringArray);
|
||||
assert_eq!(classify_pg_type("_varbit"), PgColType::BitStringArray);
|
||||
assert_eq!(classify_pg_type("_int4"), PgColType::GenericArray);
|
||||
assert_eq!(classify_pg_type("_time"), PgColType::GenericArray);
|
||||
assert_eq!(classify_pg_type("vector"), PgColType::Vector);
|
||||
assert_eq!(classify_pg_type("vector(3)"), PgColType::Vector);
|
||||
assert_eq!(classify_pg_type("geometry"), PgColType::Geometry);
|
||||
assert_eq!(classify_pg_type("geography"), PgColType::Geometry);
|
||||
assert_eq!(classify_pg_type("int4"), PgColType::Other);
|
||||
assert_eq!(classify_pg_type("varchar"), PgColType::Other);
|
||||
assert_eq!(classify_pg_type(""), PgColType::Other);
|
||||
}
|
||||
|
||||
struct DockerPostgres {
|
||||
name: String,
|
||||
port: u16,
|
||||
|
|
|
|||
|
|
@ -3163,6 +3163,7 @@ where
|
|||
Ok(stmt) => {
|
||||
let column_types: Vec<String> =
|
||||
stmt.columns().iter().map(|column| column.type_().name().to_string()).collect();
|
||||
let column_classes = db::postgres::classify_pg_column_types(&column_types);
|
||||
let params: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = Vec::new();
|
||||
match conn.query_raw(&stmt, params).await {
|
||||
Ok(stream) => {
|
||||
|
|
@ -3175,10 +3176,13 @@ where
|
|||
Ok(row) => {
|
||||
let values = (0..row.columns().len())
|
||||
.map(|index| {
|
||||
db::postgres::pg_value_to_json(
|
||||
db::postgres::pg_value_to_json_classified(
|
||||
&row,
|
||||
index,
|
||||
column_types.get(index).map(String::as_str).unwrap_or(""),
|
||||
column_classes
|
||||
.get(index)
|
||||
.copied()
|
||||
.unwrap_or(db::postgres::PgColType::Other),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -3335,6 +3339,7 @@ async fn execute_manual_txn_postgres_statement(
|
|||
let stmt = conn.prepare_cached(sql).await.map_err(|e| format!("Prepare failed: {e}"))?;
|
||||
let columns: Vec<String> = stmt.columns().iter().map(|c| c.name().to_string()).collect();
|
||||
let column_types: Vec<String> = stmt.columns().iter().map(|c| c.type_().name().to_string()).collect();
|
||||
let column_classes = db::postgres::classify_pg_column_types(&column_types);
|
||||
let params: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = Vec::new();
|
||||
let stream = conn.query_raw(&stmt, params).await.map_err(|e| format!("Query failed: {e}"))?;
|
||||
tokio::pin!(stream);
|
||||
|
|
@ -3347,7 +3352,13 @@ async fn execute_manual_txn_postgres_statement(
|
|||
}
|
||||
let row = row_result.map_err(|e| format!("Query failed: {e}"))?;
|
||||
let values: Vec<serde_json::Value> = (0..row.columns().len())
|
||||
.map(|i| db::postgres::pg_value_to_json(&row, i, column_types.get(i).map(String::as_str).unwrap_or("")))
|
||||
.map(|i| {
|
||||
db::postgres::pg_value_to_json_classified(
|
||||
&row,
|
||||
i,
|
||||
column_classes.get(i).copied().unwrap_or(db::postgres::PgColType::Other),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
data.push(values);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue