#![allow(clippy::result_large_err)]
use crate::catalog::CatalogQuery;
use crate::catalog::error::CatalogError;
use crate::catalog::rows::Row;
use crate::identifier::Identifier;
use crate::ir::subscription::{OriginMode, StreamingMode, Subscription, SubscriptionOptions};
const Q: CatalogQuery = CatalogQuery::Subscriptions;
pub fn decode_subscription_row(row: &Row) -> Result<Subscription, CatalogError> {
let name_str = row.get_text(Q, "name")?;
let owner_str = row.get_text(Q, "owner")?;
let comment_str = row.get_text(Q, "comment")?;
let connection = row.get_text(Q, "connection")?;
let name = Identifier::from_unquoted(&name_str).map_err(|e| CatalogError::BadColumnType {
query: Q,
column: "name".to_string(),
message: format!("invalid subscription name {name_str:?}: {e}"),
})?;
let owner = if owner_str.is_empty() {
None
} else {
Some(
Identifier::from_unquoted(&owner_str).map_err(|e| CatalogError::BadColumnType {
query: Q,
column: "owner".to_string(),
message: format!("invalid owner name {owner_str:?}: {e}"),
})?,
)
};
let comment = if comment_str.is_empty() {
None
} else {
Some(comment_str)
};
let pub_names = row.get_text_array(Q, "publications")?;
let publications = pub_names
.into_iter()
.map(|s| {
Identifier::from_unquoted(&s).map_err(|e| CatalogError::BadColumnType {
query: Q,
column: "publications".to_string(),
message: format!("invalid publication name {s:?}: {e}"),
})
})
.collect::<Result<Vec<Identifier>, CatalogError>>()?;
let options = decode_options(row)?;
Ok(Subscription {
name,
connection,
publications,
options,
owner,
comment,
})
}
fn decode_options(row: &Row) -> Result<SubscriptionOptions, CatalogError> {
let enabled = Some(row.get_bool(Q, "enabled")?);
let slot_name_str = row.get_text(Q, "slot_name")?;
let slot_name = if slot_name_str.is_empty() {
None
} else {
Some(Identifier::from_unquoted(&slot_name_str).map_err(|e| {
CatalogError::BadColumnType {
query: Q,
column: "slot_name".to_string(),
message: format!("invalid slot name {slot_name_str:?}: {e}"),
}
})?)
};
let synchronous_commit_str = row.get_text(Q, "synchronous_commit")?;
let synchronous_commit = if synchronous_commit_str.is_empty() {
None
} else {
Some(synchronous_commit_str)
};
let binary = Some(row.get_bool(Q, "binary")?);
let streaming_str = row.get_text(Q, "streaming")?;
let streaming = Some(decode_streaming(&streaming_str).map_err(|e| match e {
CatalogError::BadColumnType { message, .. } => CatalogError::BadColumnType {
query: Q,
column: "streaming".to_string(),
message,
},
other => other,
})?);
let two_phase = if row.is_null("two_phase_state") {
None
} else {
let two_phase_str = row.get_text(Q, "two_phase_state")?;
Some(decode_two_phase(&two_phase_str).map_err(|e| match e {
CatalogError::BadColumnType { message, .. } => CatalogError::BadColumnType {
query: Q,
column: "two_phase_state".to_string(),
message,
},
other => other,
})?)
};
let disable_on_error = opt_bool(row, "disable_on_error")?;
let password_required = opt_bool(row, "password_required")?;
let run_as_owner = opt_bool(row, "run_as_owner")?;
let failover = opt_bool(row, "failover")?;
let origin = if row.is_null("origin") {
None
} else {
let origin_str = row.get_text(Q, "origin")?;
Some(decode_origin(&origin_str).map_err(|e| match e {
CatalogError::BadColumnType { message, .. } => CatalogError::BadColumnType {
query: Q,
column: "origin".to_string(),
message,
},
other => other,
})?)
};
Ok(SubscriptionOptions {
enabled,
slot_name,
create_slot: None,
copy_data: None,
synchronous_commit,
binary,
streaming,
two_phase,
disable_on_error,
password_required,
run_as_owner,
origin,
failover,
})
}
fn opt_bool(row: &Row, col: &str) -> Result<Option<bool>, CatalogError> {
if row.is_null(col) {
Ok(None)
} else {
Ok(Some(row.get_bool(Q, col)?))
}
}
pub fn decode_streaming(s: &str) -> Result<StreamingMode, CatalogError> {
match s {
"f" => Ok(StreamingMode::Off),
"t" => Ok(StreamingMode::On),
"p" => Ok(StreamingMode::Parallel),
other => Err(CatalogError::BadColumnType {
query: Q,
column: "streaming".to_string(),
message: format!("unknown substream value: {other:?}"),
}),
}
}
pub fn decode_two_phase(s: &str) -> Result<bool, CatalogError> {
match s {
"d" => Ok(false),
"e" | "p" => Ok(true),
other => Err(CatalogError::BadColumnType {
query: Q,
column: "two_phase_state".to_string(),
message: format!("unknown subtwophasestate value: {other:?}"),
}),
}
}
pub fn decode_origin(s: &str) -> Result<OriginMode, CatalogError> {
match s.to_ascii_lowercase().as_str() {
"any" => Ok(OriginMode::Any),
"none" => Ok(OriginMode::None),
other => Err(CatalogError::BadColumnType {
query: Q,
column: "origin".to_string(),
message: format!("unknown suborigin value: {other:?}"),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn streaming_off_decodes() {
assert_eq!(decode_streaming("f").unwrap(), StreamingMode::Off);
}
#[test]
fn streaming_on_decodes() {
assert_eq!(decode_streaming("t").unwrap(), StreamingMode::On);
}
#[test]
fn streaming_parallel_decodes() {
assert_eq!(decode_streaming("p").unwrap(), StreamingMode::Parallel);
}
#[test]
fn streaming_unknown_errors() {
let err = decode_streaming("x").unwrap_err();
assert!(
format!("{err}").contains("unknown substream value"),
"error should mention unknown value: {err}"
);
}
#[test]
fn two_phase_disabled_decodes() {
assert!(!decode_two_phase("d").unwrap());
}
#[test]
fn two_phase_enabled_decodes() {
assert!(decode_two_phase("e").unwrap());
}
#[test]
fn two_phase_pending_decodes_as_true() {
assert!(decode_two_phase("p").unwrap());
}
#[test]
fn two_phase_unknown_errors() {
let err = decode_two_phase("z").unwrap_err();
assert!(
format!("{err}").contains("unknown subtwophasestate value"),
"error should mention unknown value: {err}"
);
}
#[test]
fn origin_any_lowercase_decodes() {
assert_eq!(decode_origin("any").unwrap(), OriginMode::Any);
}
#[test]
fn origin_any_uppercase_decodes() {
assert_eq!(decode_origin("ANY").unwrap(), OriginMode::Any);
}
#[test]
fn origin_none_decodes() {
assert_eq!(decode_origin("none").unwrap(), OriginMode::None);
}
#[test]
fn origin_unknown_errors() {
let err = decode_origin("both").unwrap_err();
assert!(
format!("{err}").contains("unknown suborigin value"),
"error should mention unknown value: {err}"
);
}
}