use super::*;
#[test]
fn array_literal_rejects_postgresql_unrepresentable_upper_bound() {
let error = parse_pg_array_literal("[2147483647:2147483647]={1}").unwrap_err();
assert_eq!(error.sqlstate(), Some("54000"));
assert_eq!(
error.to_string(),
"array upper bound is too large: 2147483647"
);
assert!(parse_pg_array_literal("[2147483646:2147483646]={1}").is_ok());
}
use uqa_core::DecimalValue;
#[test]
fn temporal_input_range_errors_name_the_postgresql_input_type() {
for (target, input, name) in [
("date", "5874898-01-01", "date"),
("timestamp", "294277-01-01", "timestamp"),
("timestamptz", "294277-01-01", "timestamp"),
] {
let error = cast_value(&Value::Str(input.into()), target).unwrap_err();
assert_eq!(error.sqlstate(), Some("22008"));
assert_eq!(
error.to_string(),
format!("{name} out of range: \"{input}\"")
);
}
}
#[test]
fn void_casts_are_limited_to_postgresql_string_categories() {
assert_eq!(
cast_value_from(&Value::Str("ignored".into()), "void", Some("text")).unwrap(),
Value::Void
);
assert_eq!(
cast_value_from(&Value::Void, "character varying", Some("void")).unwrap(),
Value::Str(String::new())
);
assert_eq!(
cast_value_from(&Value::Null, "void", Some("text")).unwrap(),
Value::Null
);
for (value, target, source, message) in [
(
Value::Int(1),
"void",
"integer",
"cannot cast type integer to void",
),
(
Value::Void,
"integer",
"void",
"cannot cast type void to integer",
),
] {
let error = cast_value_from(&value, target, Some(source)).unwrap_err();
assert_eq!(error.sqlstate(), Some("42846"));
assert_eq!(error.to_string(), message);
}
let error = cast_value(&Value::Null, "void[]").unwrap_err();
assert_eq!(error.sqlstate(), Some("42704"));
assert_eq!(error.to_string(), "type \"void[]\" does not exist");
}
#[test]
fn temporal_cross_casts_convert_the_carrier_kind() {
let date = Value::Temporal(TemporalValue::parse_date("2020-01-02").unwrap());
assert_eq!(
cast_value(&date, "timestamp").unwrap(),
Value::Temporal(TemporalValue::parse_timestamp("2020-01-02 00:00:00").unwrap())
);
let timestamp = Value::Temporal(TemporalValue::parse_timestamp("2020-01-02 03:04:05").unwrap());
assert_eq!(
cast_value(×tamp, "date").unwrap(),
Value::Temporal(TemporalValue::parse_date("2020-01-02").unwrap())
);
assert_eq!(
cast_value(×tamp, "time").unwrap(),
Value::Temporal(TemporalValue::parse_time("03:04:05").unwrap())
);
let interval = Value::Temporal(TemporalValue::parse_interval("1 day 25:02:03").unwrap());
assert_eq!(
cast_value(&interval, "time").unwrap(),
Value::Temporal(TemporalValue::parse_time("01:02:03").unwrap())
);
}
#[test]
fn uuid_cast_matches_postgresql_input_and_canonical_output() {
for input in [
"A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11",
"a0eebc999c0b4ef8bb6d6bb9bd380a11",
"{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}",
"a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11",
] {
assert_eq!(
cast_value(&Value::Str(input.into()), "uuid").unwrap(),
Value::Str("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11".into())
);
}
}
#[test]
fn uuid_cast_rejects_postgresql_invalid_forms() {
for input in [
" a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 ",
"a0e-ebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
"not-a-uuid",
] {
let error = cast_value(&Value::Str(input.into()), "uuid").unwrap_err();
assert_eq!(error.sqlstate(), Some("22P02"));
}
}
#[test]
fn oid_cast_preserves_postgresql_source_type_rules() {
assert_eq!(
cast_value_from(&Value::Int(-1), "oid", Some("smallint")).unwrap(),
Value::Int(i64::from(u32::MAX))
);
assert_eq!(
cast_value_from(&Value::Int(-1), "oid", Some("integer")).unwrap(),
Value::Int(i64::from(u32::MAX))
);
assert_eq!(
cast_value_from(&Value::Int(i64::from(u32::MAX)), "oid", Some("bigint")).unwrap(),
Value::Int(i64::from(u32::MAX))
);
let error = cast_value_from(&Value::Int(-1), "oid", Some("bigint")).unwrap_err();
assert_eq!(error.sqlstate(), Some("22003"));
assert_eq!(error.to_string(), "OID out of range");
for source in ["boolean", "numeric", "double precision"] {
let value = match source {
"boolean" => Value::Bool(true),
"numeric" => Value::Decimal(DecimalValue::from_i64(1)),
_ => Value::Float(1.0),
};
let error = cast_value_from(&value, "oid", Some(source)).unwrap_err();
assert_eq!(error.sqlstate(), Some("42846"));
}
}
#[test]
fn regclass_cast_preserves_bound_relation_names_and_oid_carriers() {
assert_eq!(
cast_value_from(
&Value::Str("app.items".into()),
"pg_catalog.regclass",
Some("unknown")
)
.unwrap(),
Value::Str("app.items".into())
);
assert_eq!(
cast_value_from(&Value::Int(2205), "regclass", Some("oid")).unwrap(),
Value::Int(2205)
);
}
#[test]
fn regtype_zero_uses_postgresql_dash_text_output() {
for source in [
"regproc",
"regprocedure",
"regclass",
"regnamespace",
"regtype",
] {
assert_eq!(
cast_value_from(&Value::Int(0), "text", Some(source)).unwrap(),
Value::Str("-".into()),
"{source}"
);
}
assert_eq!(
cast_value_from(&Value::Int(42), "text", Some("regproc")).unwrap(),
Value::Str("42".into())
);
}
#[test]
fn oid_and_xid_text_input_use_postgresql_uint32_syntax() {
for target in ["oid", "xid"] {
assert_eq!(
cast_value(&Value::Str("-1".into()), target).unwrap(),
Value::Int(i64::from(u32::MAX))
);
assert_eq!(
cast_value(&Value::Str(i32::MIN.to_string()), target).unwrap(),
Value::Int(i64::from(i32::MIN as u32))
);
assert_eq!(
cast_value(&Value::Str(u32::MAX.to_string()), target).unwrap(),
Value::Int(i64::from(u32::MAX))
);
for input in ["-2147483649", "4294967296"] {
let error = cast_value(&Value::Str(input.into()), target).unwrap_err();
assert_eq!(error.sqlstate(), Some("22003"));
}
let error = cast_value(&Value::Str("1.0".into()), target).unwrap_err();
assert_eq!(error.sqlstate(), Some("22P02"));
}
}
#[test]
fn xid_rejects_integer_and_oid_cast_sources() {
for source in ["smallint", "integer", "bigint", "oid"] {
let error = cast_value_from(&Value::Int(1), "xid", Some(source)).unwrap_err();
assert_eq!(error.sqlstate(), Some("42846"));
}
}
#[test]
fn legacy_vector_text_casts_use_postgresql_space_separation() {
let vector = Value::List(vec![Value::Int(23), Value::Int(25)]);
assert_eq!(
cast_value_from(&vector, "text", Some("oidvector")).unwrap(),
Value::Str("23 25".into())
);
let stored = Value::Array(ArrayValue::try_new(vec![Value::Int(1), Value::Int(3)]).unwrap());
assert_eq!(
cast_value_from(&stored, "text", Some("int2vector")).unwrap(),
Value::Str("1 3".into())
);
assert_eq!(
cast_value_from(
&Value::List(Vec::new()),
"text",
Some("pg_catalog.int2vector")
)
.unwrap(),
Value::Str(String::new())
);
}
#[test]
fn bytea_cast_preserves_postgresql_source_type_and_input_rules() {
assert_eq!(
cast_value_from(&Value::Int(-1), "bytea", Some("smallint")).unwrap(),
Value::Bytes(vec![0xff, 0xff])
);
assert_eq!(
cast_value_from(&Value::Int(-1), "bytea", Some("integer")).unwrap(),
Value::Bytes(vec![0xff; 4])
);
assert_eq!(
cast_value_from(&Value::Int(-1), "bytea", Some("bigint")).unwrap(),
Value::Bytes(vec![0xff; 8])
);
assert_eq!(
cast_value_from(&Value::Str("\\x6162".into()), "bytea", Some("text")).unwrap(),
Value::Bytes(b"ab".to_vec())
);
assert_eq!(
cast_value_from(&Value::Str("a\\\\b\\141".into()), "bytea", Some("text")).unwrap(),
Value::Bytes(b"a\\ba".to_vec())
);
for (value, source) in [
(Value::Bool(true), "boolean"),
(Value::Decimal(DecimalValue::from_i64(1)), "numeric"),
(Value::Float(1.0), "double precision"),
] {
let error = cast_value_from(&value, "bytea", Some(source)).unwrap_err();
assert_eq!(error.sqlstate(), Some("42846"));
}
for (input, expected) in [
("\\x01 02\t0a\r\n", vec![0x01, 0x02, 0x0a]),
("\\xE282ac", vec![0xe2, 0x82, 0xac]),
("\\x", Vec::new()),
("\u{3ba}", "\u{3ba}".as_bytes().to_vec()),
] {
assert_eq!(
cast_value(&Value::Str(input.into()), "bytea").unwrap(),
Value::Bytes(expected),
"{input:?}"
);
}
for (input, sqlstate, message) in [
(
"\\x1",
"22023",
"invalid hexadecimal data: odd number of digits",
),
(
"\\x01 0",
"22023",
"invalid hexadecimal data: odd number of digits",
),
("\\xzz", "22023", "invalid hexadecimal digit: \"z\""),
("\\x0 1", "22023", "invalid hexadecimal digit: \" \""),
(
"\\x\u{e9}",
"22023",
"invalid hexadecimal digit: \"\u{e9}\"",
),
("\\9", "22P02", "invalid input syntax for type bytea"),
("a\\", "22P02", "invalid input syntax for type bytea"),
("\\X0102", "22P02", "invalid input syntax for type bytea"),
("\\08", "22P02", "invalid input syntax for type bytea"),
] {
let error = cast_value(&Value::Str(input.into()), "bytea").unwrap_err();
assert_eq!(
(error.sqlstate(), error.to_string().as_str()),
(Some(sqlstate), message),
"{input:?}"
);
}
}
#[test]
fn unary_minus_preserves_integer_width_and_overflow() {
for (source, input, expected) in [
("smallint", 1_i64, -1_i64),
("integer", 1_i64, -1_i64),
("bigint", 1_i64, -1_i64),
] {
assert_eq!(
negate_value(&Value::Int(input), Some(source)).unwrap(),
Value::Int(expected)
);
}
for (source, minimum) in [
("smallint", i64::from(i16::MIN)),
("integer", i64::from(i32::MIN)),
("bigint", i64::MIN),
] {
let error = negate_value(&Value::Int(minimum), Some(source)).unwrap_err();
assert_eq!(error.sqlstate(), Some("22003"));
}
}
#[test]
fn unary_minus_preserves_interval_fields() {
assert_eq!(
negate_value(
&Value::Temporal(TemporalValue::Interval {
months: 2,
days: -3,
micros: 4,
}),
Some("interval"),
)
.unwrap(),
Value::Temporal(TemporalValue::Interval {
months: -2,
days: 3,
micros: -4,
})
);
}
#[test]
fn integer_text_input_reads_what_postgresql_reads() {
for (text, target, expected) in [
("0x10", "bigint", 16),
(" 1_000 ", "integer", 1000),
("0o17", "smallint", 15),
("-0b101", "integer", -5),
("+7", "bigint", 7),
("-32768", "smallint", -32768),
] {
assert_eq!(
cast_value(&Value::Str(text.into()), target).unwrap(),
Value::Int(expected),
"{text} as {target}"
);
}
for (text, target, sqlstate, message) in [
(
"99999999999999999999",
"bigint",
"22003",
"value \"99999999999999999999\" is out of range for type bigint",
),
(
"40000",
"smallint",
"22003",
"value \"40000\" is out of range for type smallint",
),
(
"2147483648",
"integer",
"22003",
"value \"2147483648\" is out of range for type integer",
),
(
"1__0",
"integer",
"22P02",
"invalid input syntax for type integer: \"1__0\"",
),
(
"1.5",
"integer",
"22P02",
"invalid input syntax for type integer: \"1.5\"",
),
] {
let error = cast_value(&Value::Str(text.into()), target).unwrap_err();
assert_eq!(
(error.sqlstate(), error.to_string().as_str()),
(Some(sqlstate), message),
"{text} as {target}"
);
}
}