use bytes::Bytes;
use merutable::types::{
key::InternalKey,
schema::{ColumnDef, ColumnType, TableSchema},
sequence::{OpType, SeqNum, SEQNUM_MAX},
value::FieldValue,
};
use proptest::prelude::*;
fn any_op_type() -> impl Strategy<Value = OpType> {
prop_oneof![Just(OpType::Put), Just(OpType::Delete)]
}
fn any_seq() -> impl Strategy<Value = SeqNum> {
prop_oneof![
Just(SeqNum(0)),
Just(SeqNum(1)),
Just(SeqNum(SEQNUM_MAX.0)),
Just(SeqNum(SEQNUM_MAX.0 - 1)),
(0u64..1_000_000).prop_map(SeqNum),
(0u64..=SEQNUM_MAX.0).prop_map(SeqNum),
]
}
fn any_real_seq() -> impl Strategy<Value = SeqNum> {
prop_oneof![
Just(SeqNum(0)),
Just(SeqNum(1)),
Just(SeqNum(SEQNUM_MAX.0 - 1)),
(0u64..1_000_000).prop_map(SeqNum),
(0u64..SEQNUM_MAX.0).prop_map(SeqNum),
]
}
fn any_bytes() -> impl Strategy<Value = Bytes> {
prop_oneof![
Just(Bytes::new()),
Just(Bytes::from_static(&[0u8])),
Just(Bytes::from_static(&[0u8, 0u8])),
Just(Bytes::from_static(&[0xFFu8])),
Just(Bytes::from_static(&[0u8, 0xFFu8, 0u8])),
Just(Bytes::from_static(&[0u8, 0x01u8])),
Just(Bytes::from_static(&[0x01u8, 0u8])),
prop::collection::vec(any::<u8>(), 0..32).prop_map(Bytes::from),
]
}
fn any_schema_and_two_pks() -> impl Strategy<Value = (TableSchema, Vec<FieldValue>, Vec<FieldValue>)>
{
prop_oneof![
(any::<i64>(), any::<i64>()).prop_map(|(a, b)| {
let s = TableSchema {
table_name: "t".into(),
columns: vec![ColumnDef {
name: "id".into(),
col_type: ColumnType::Int64,
nullable: false,
..Default::default()
}],
primary_key: vec![0],
..Default::default()
};
(s, vec![FieldValue::Int64(a)], vec![FieldValue::Int64(b)])
}),
(any_bytes(), any_bytes()).prop_map(|(a, b)| {
let s = TableSchema {
table_name: "t".into(),
columns: vec![ColumnDef {
name: "k".into(),
col_type: ColumnType::ByteArray,
nullable: false,
..Default::default()
}],
primary_key: vec![0],
..Default::default()
};
(s, vec![FieldValue::Bytes(a)], vec![FieldValue::Bytes(b)])
}),
(any::<i64>(), any_bytes(), any::<i64>(), any_bytes()).prop_map(|(a1, a2, b1, b2)| {
let s = TableSchema {
table_name: "t".into(),
columns: vec![
ColumnDef {
name: "a".into(),
col_type: ColumnType::Int64,
nullable: false,
..Default::default()
},
ColumnDef {
name: "b".into(),
col_type: ColumnType::ByteArray,
nullable: false,
..Default::default()
},
],
primary_key: vec![0, 1],
..Default::default()
};
(
s,
vec![FieldValue::Int64(a1), FieldValue::Bytes(a2)],
vec![FieldValue::Int64(b1), FieldValue::Bytes(b2)],
)
}),
]
}
fn any_schema_and_pk() -> impl Strategy<Value = (TableSchema, Vec<FieldValue>)> {
any_schema_and_two_pks().prop_map(|(s, a, _)| (s, a))
}
proptest! {
#[test]
fn roundtrip(
(schema, pk) in any_schema_and_pk(),
seq in any_seq(),
op in any_op_type(),
) {
let k = InternalKey::encode(&pk, seq, op, &schema).unwrap();
let decoded = InternalKey::decode(k.as_bytes(), &schema).unwrap();
prop_assert_eq!(decoded.seq, seq);
prop_assert_eq!(decoded.op_type, op);
prop_assert_eq!(decoded.pk_values(), pk.as_slice());
}
}
proptest! {
#[test]
fn memcmp_matches_ord(
(schema, pk_a, pk_b) in any_schema_and_two_pks(),
seq_a in any_seq(),
seq_b in any_seq(),
op_a in any_op_type(),
op_b in any_op_type(),
) {
let ka = InternalKey::encode(&pk_a, seq_a, op_a, &schema).unwrap();
let kb = InternalKey::encode(&pk_b, seq_b, op_b, &schema).unwrap();
prop_assert_eq!(ka.as_bytes().cmp(kb.as_bytes()), ka.cmp(&kb));
}
}
proptest! {
#[test]
fn bytearray_prefix_always_sorts_first(
prefix in prop::collection::vec(any::<u8>(), 0..16),
extra in prop::collection::vec(any::<u8>(), 1..16),
seq_a in any_seq(),
seq_b in any_seq(),
op_a in any_op_type(),
op_b in any_op_type(),
) {
let schema = TableSchema {
table_name: "t".into(),
columns: vec![ColumnDef {
name: "k".into(),
col_type: ColumnType::ByteArray,
nullable: false,
..Default::default()
}],
primary_key: vec![0],
..Default::default()
};
let a_bytes = Bytes::from(prefix.clone());
let mut b_raw = prefix;
b_raw.extend_from_slice(&extra);
let b_bytes = Bytes::from(b_raw);
let ka = InternalKey::encode(
&[FieldValue::Bytes(a_bytes)],
seq_a,
op_a,
&schema,
).unwrap();
let kb = InternalKey::encode(
&[FieldValue::Bytes(b_bytes)],
seq_b,
op_b,
&schema,
).unwrap();
prop_assert!(
ka < kb,
"prefix A must sort before B regardless of tag: ka={:?} kb={:?}",
ka.as_bytes(),
kb.as_bytes(),
);
}
}
proptest! {
#[test]
fn seek_latest_is_lower_bound(
(schema, pk) in any_schema_and_pk(),
seq in any_real_seq(),
op in any_op_type(),
) {
let seek = InternalKey::seek_latest(&pk, &schema).unwrap();
let real = InternalKey::encode(&pk, seq, op, &schema).unwrap();
prop_assert!(
seek <= real,
"seek_latest must be <= every real entry; seek={:?} real={:?} seq={} op={:?}",
seek.as_bytes(),
real.as_bytes(),
seq.0,
op,
);
}
}