use alloc::string::ToString;
use alloc::vec::Vec;
use super::decoder::{
BoolDecoder, DateVerbatimDecoder, DecimalTextDecoder, Decoder, Int64OverflowToTextDecoder,
IntDecoder, IntervalVerbatimDecoder, JsonCanonicalDecoder, JsonVerbatimDecoder,
MySqlBinaryDecoder, NullDecoder, PgByteaBinaryDecoder, PgByteaTextModeDecoder, RealDecoder,
TextDecoder, TimeVerbatimDecoder, TimestampTzVerbatimDecoder, TimestampVerbatimDecoder,
UuidBlob16Decoder, UuidText36Decoder,
};
use super::error::DecodeError;
use super::scalar_helpers::{
decode_pg_bool_binary, decode_pg_int_binary, decode_pg_real_binary, normalize_real,
};
use super::type_map::{TypeMap, TypeMapDefaults};
use super::wire_type::WireType;
use crate::encoding::Value;
use crate::pg_walstream::{ColumnValue, PgWalstream, PgWalstreamColumn};
impl<S, B> Decoder<PgWalstream, S, B> for NullDecoder {
fn decode(&self, _payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
Ok(Value::Null)
}
}
impl<S, B> Decoder<PgWalstream, S, B> for BoolDecoder {
fn decode(&self, payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
match payload.data {
ColumnValue::Null => Ok(Value::Null),
ColumnValue::Text(_) => match payload.data.as_str() {
Some("t") => Ok(Value::Integer(1)),
Some("f") => Ok(Value::Integer(0)),
other => Err(DecodeError::WrongPayloadKind {
column: payload.column_name.to_string(),
expected: "\"t\" or \"f\"",
actual: match other {
Some(_) => "arbitrary text",
None => "non-utf8 bytes",
},
}),
},
ColumnValue::Binary(b) => decode_pg_bool_binary(payload.column_name, b.as_ref()),
}
}
}
impl<S, B> Decoder<PgWalstream, S, B> for IntDecoder {
fn decode(&self, payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
match payload.data {
ColumnValue::Null => Ok(Value::Null),
ColumnValue::Text(_) => {
let s = payload
.data
.as_str()
.ok_or_else(|| DecodeError::InvalidUtf8 {
column: payload.column_name.to_string(),
})?;
match s.parse::<i64>() {
Ok(i) => Ok(Value::Integer(i)),
Err(_)
if s.trim_start_matches('-')
.chars()
.all(|c| c.is_ascii_digit()) =>
{
Err(DecodeError::IntegerOverflow {
column: payload.column_name.to_string(),
digits: s.to_string(),
})
}
Err(_) => Err(DecodeError::WrongPayloadKind {
column: payload.column_name.to_string(),
expected: "base-10 signed integer",
actual: "non-numeric text",
}),
}
}
ColumnValue::Binary(b) => decode_pg_int_binary(payload.column_name, b.as_ref()),
}
}
}
impl<S, B> Decoder<PgWalstream, S, B> for Int64OverflowToTextDecoder
where
S: From<alloc::string::String>,
{
fn decode(&self, payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
match payload.data {
ColumnValue::Null => Ok(Value::Null),
ColumnValue::Text(_) => {
let s = payload
.data
.as_str()
.ok_or_else(|| DecodeError::InvalidUtf8 {
column: payload.column_name.to_string(),
})?;
match s.parse::<i64>() {
Ok(i) => Ok(Value::Integer(i)),
Err(_)
if s.trim_start_matches('-')
.chars()
.all(|c| c.is_ascii_digit()) =>
{
Ok(Value::Text(S::from(s.to_string())))
}
Err(_) => Err(DecodeError::WrongPayloadKind {
column: payload.column_name.to_string(),
expected: "base-10 integer text",
actual: "non-numeric text",
}),
}
}
ColumnValue::Binary(_) => Err(DecodeError::WrongPayloadKind {
column: payload.column_name.to_string(),
expected: "text-mode integer",
actual: "binary payload",
}),
}
}
}
impl<S, B> Decoder<PgWalstream, S, B> for RealDecoder {
fn decode(&self, payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
match payload.data {
ColumnValue::Null => Ok(Value::Null),
ColumnValue::Text(_) => {
let s = payload
.data
.as_str()
.ok_or_else(|| DecodeError::InvalidUtf8 {
column: payload.column_name.to_string(),
})?;
match s.parse::<f64>() {
Ok(f) => Ok(normalize_real(f)),
Err(_) => Err(DecodeError::WrongPayloadKind {
column: payload.column_name.to_string(),
expected: "IEEE 754 float text",
actual: "non-numeric text",
}),
}
}
ColumnValue::Binary(b) => decode_pg_real_binary(payload.column_name, b.as_ref()),
}
}
}
impl<S, B> Decoder<PgWalstream, S, B> for TextDecoder
where
S: From<alloc::string::String>,
{
fn decode(&self, payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
match payload.data {
ColumnValue::Null => Ok(Value::Null),
ColumnValue::Text(_) => {
let s = payload
.data
.as_str()
.ok_or_else(|| DecodeError::InvalidUtf8 {
column: payload.column_name.to_string(),
})?;
Ok(Value::Text(S::from(s.to_string())))
}
ColumnValue::Binary(_) => Err(DecodeError::WrongPayloadKind {
column: payload.column_name.to_string(),
expected: "UTF-8 text",
actual: "binary payload",
}),
}
}
}
impl<S, B> Decoder<PgWalstream, S, B> for PgByteaBinaryDecoder
where
B: From<Vec<u8>>,
{
fn decode(&self, payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
match payload.data {
ColumnValue::Null => Ok(Value::Null),
ColumnValue::Binary(b) => Ok(Value::Blob(B::from(b.to_vec()))),
ColumnValue::Text(_) => {
let s = payload
.data
.as_str()
.ok_or_else(|| DecodeError::InvalidUtf8 {
column: payload.column_name.to_string(),
})?;
match super::bytes_helpers::decode_pg_hex_escape(s) {
Ok(bytes) => Ok(Value::Blob(B::from(bytes))),
Err(at) => Err(DecodeError::InvalidHexEscape {
column: payload.column_name.to_string(),
at,
}),
}
}
}
}
}
impl<S, B> Decoder<PgWalstream, S, B> for UuidBlob16Decoder
where
B: From<Vec<u8>>,
{
fn decode(&self, payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
decode_pg_uuid_to_blob(payload)
}
}
impl<S, B> Decoder<PgWalstream, S, B> for UuidText36Decoder
where
S: From<alloc::string::String>,
{
fn decode(&self, payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
decode_pg_uuid_to_text(payload)
}
}
fn decode_pg_uuid_to_blob<S, B>(payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError>
where
B: From<Vec<u8>>,
{
match payload.data {
ColumnValue::Null => Ok(Value::Null),
ColumnValue::Text(_) => {
let s = payload
.data
.as_str()
.ok_or_else(|| DecodeError::InvalidUtf8 {
column: payload.column_name.to_string(),
})?;
match super::uuid_helpers::parse_uuid(s) {
Ok(bytes) => Ok(Value::Blob(B::from(bytes.to_vec()))),
Err(source_len) => Err(DecodeError::InvalidUuid {
column: payload.column_name.to_string(),
source_len,
}),
}
}
ColumnValue::Binary(_) => Err(DecodeError::WrongPayloadKind {
column: payload.column_name.to_string(),
expected: "UUID text form",
actual: "binary payload",
}),
}
}
fn decode_pg_uuid_to_text<S, B>(payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError>
where
S: From<alloc::string::String>,
{
match payload.data {
ColumnValue::Null => Ok(Value::Null),
ColumnValue::Text(_) => {
let s = payload
.data
.as_str()
.ok_or_else(|| DecodeError::InvalidUtf8 {
column: payload.column_name.to_string(),
})?;
match super::uuid_helpers::preserve_or_canonicalize_uuid_text(s) {
Ok(canonical) => Ok(Value::Text(S::from(canonical))),
Err(source_len) => Err(DecodeError::InvalidUuid {
column: payload.column_name.to_string(),
source_len,
}),
}
}
ColumnValue::Binary(_) => Err(DecodeError::WrongPayloadKind {
column: payload.column_name.to_string(),
expected: "UUID text form",
actual: "binary payload",
}),
}
}
impl<S, B> Decoder<PgWalstream, S, B> for DecimalTextDecoder
where
S: From<alloc::string::String>,
{
fn decode(&self, payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
match payload.data {
ColumnValue::Null => Ok(Value::Null),
ColumnValue::Text(_) => {
let s = payload
.data
.as_str()
.ok_or_else(|| DecodeError::InvalidUtf8 {
column: payload.column_name.to_string(),
})?;
Ok(Value::Text(S::from(s.to_string())))
}
ColumnValue::Binary(_) => Err(DecodeError::WrongPayloadKind {
column: payload.column_name.to_string(),
expected: "text-mode numeric",
actual: "binary payload",
}),
}
}
}
macro_rules! not_yet_impl {
($decoder:ty) => {
impl<S, B> Decoder<PgWalstream, S, B> for $decoder {
fn decode(&self, _payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
Err(DecodeError::NotYetImplemented {
decoder: stringify!($decoder),
})
}
}
};
}
fn decode_pg_text_verbatim<S, B>(payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError>
where
S: From<alloc::string::String>,
{
match payload.data {
ColumnValue::Null => Ok(Value::Null),
ColumnValue::Text(_) => {
let s = payload
.data
.as_str()
.ok_or_else(|| DecodeError::InvalidUtf8 {
column: payload.column_name.to_string(),
})?;
Ok(Value::Text(S::from(s.to_string())))
}
ColumnValue::Binary(_) => Err(DecodeError::WrongPayloadKind {
column: payload.column_name.to_string(),
expected: "text form",
actual: "binary payload",
}),
}
}
macro_rules! verbatim_impl {
($decoder:ty) => {
impl<S, B> Decoder<PgWalstream, S, B> for $decoder
where
S: From<alloc::string::String>,
{
fn decode(&self, payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
decode_pg_text_verbatim(payload)
}
}
};
}
verbatim_impl!(TimestampVerbatimDecoder);
verbatim_impl!(TimestampTzVerbatimDecoder);
verbatim_impl!(DateVerbatimDecoder);
verbatim_impl!(TimeVerbatimDecoder);
verbatim_impl!(IntervalVerbatimDecoder);
verbatim_impl!(JsonVerbatimDecoder);
impl<S, B> Decoder<PgWalstream, S, B> for JsonCanonicalDecoder
where
S: From<alloc::string::String>,
{
fn decode(&self, payload: PgWalstreamColumn<'_>) -> Result<Value<S, B>, DecodeError> {
match payload.data {
ColumnValue::Null => Ok(Value::Null),
ColumnValue::Text(_) => {
let s = payload
.data
.as_str()
.ok_or_else(|| DecodeError::InvalidUtf8 {
column: payload.column_name.to_string(),
})?;
let canon = crate::wire::json_helpers::canonicalize_string(s);
Ok(Value::Text(S::from(canon)))
}
ColumnValue::Binary(_) => Err(DecodeError::WrongPayloadKind {
column: payload.column_name.to_string(),
expected: "text-mode JSON",
actual: "binary payload",
}),
}
}
}
not_yet_impl!(PgByteaTextModeDecoder);
not_yet_impl!(MySqlBinaryDecoder);
impl<S, B> TypeMapDefaults<S, B> for PgWalstream
where
S: From<alloc::string::String>,
B: From<Vec<u8>>,
{
fn defaults() -> TypeMap<Self, S, B> {
TypeMap::new()
.with(WireType::Bool, BoolDecoder)
.with(WireType::Int, IntDecoder)
.with(WireType::Real, RealDecoder)
.with(WireType::Text, TextDecoder)
.with(WireType::Bytes, PgByteaBinaryDecoder)
.with(WireType::Uuid, UuidText36Decoder)
.with(WireType::Decimal, DecimalTextDecoder)
.with(WireType::Timestamp, TimestampVerbatimDecoder)
.with(WireType::TimestampTz, TimestampTzVerbatimDecoder)
.with(WireType::Date, DateVerbatimDecoder)
.with(WireType::Time, TimeVerbatimDecoder)
.with(WireType::Interval, IntervalVerbatimDecoder)
.with(WireType::Json, JsonVerbatimDecoder)
.with(WireType::Jsonb, JsonVerbatimDecoder)
}
}