use super::*;
use gnitz_core::key_reply;
use gnitz_expr::SchemaFacts;
use gnitz_expr::{CmpOp, ExprBuilder, LogicalInstr as L, Sink};
fn gt_predicate(col: usize, threshold: i64) -> Vec<u8> {
let mut b = ExprBuilder::new();
let c = b.emit(L::LoadCol { col: col as u32 });
let k = b.emit(L::LoadConst { val: threshold, unsigned: false });
let cond = b.emit(L::Cmp { op: CmpOp::Gt, a: c, b: k });
b.build(vec![Sink::Reg(cond)])
.expect("a well-formed program")
.to_blob_bytes()
}
fn read_keys(client: &mut GnitzClient, tid: u64, schema: &Schema, predicate: Vec<u8>) -> (Arc<Schema>, ZSetBatch) {
let (reply_schema, sink) = key_reply(schema);
assert_eq!(reply_schema.num_payload_cols(), 0, "the reply is nothing but the key");
assert_eq!(reply_schema.pk_stride(), schema.pk_stride());
let spec = ReadSpec { bound: ReadBound::None, predicate, sink };
let reply = block_on(client.scan_spec(tid, &spec, &reply_schema))
.expect("a PK-only reply must not be rejected")
.batch;
assert!(reply.payload.is_empty() && reply.blob.is_empty());
reply
.validate(&reply_schema)
.expect("the reply validates under its schema");
assert!(reply.weights.iter().all(|&w| w == 1), "per-row weights preserved");
(reply_schema, reply)
}
#[test]
fn a_pk_only_reply_returns_exactly_the_matching_keys() {
let srv = ServerHandle::start_n(4);
let mut client = GnitzClient::connect(srv.sock_path()).unwrap();
let (_, tid, schema) = create_table(
&mut client,
schema_of(&[("id", TypeCode::U64), ("v", TypeCode::I64), ("s", TypeCode::String)]),
);
let mut batch = ZSetBatch::new(&schema);
let mut app = BatchAppender::new(&mut batch);
for i in 1u64..=200 {
app.add_row(i as u128, 1).i64_val(i as i64).str_val(&"x".repeat(300));
}
block_on(client.push(tid, &schema, &batch, WireConflictMode::Update)).unwrap();
let (_, reply) = read_keys(&mut client, tid, &schema, gt_predicate(1, 150));
let mut got: Vec<u64> = (0..reply.len()).map(|i| reply.pks.get(i) as u64).collect();
got.sort_unstable();
assert_eq!(got, (151u64..=200).collect::<Vec<_>>());
}
#[test]
fn a_permuted_compound_pk_round_trips_verbatim() {
let srv = ServerHandle::start_n(4);
let mut client = GnitzClient::connect(srv.sock_path()).unwrap();
let mut schema = schema_of(&[
("c0", TypeCode::U32),
("c1", TypeCode::String),
("c2", TypeCode::I64),
("c3", TypeCode::I64),
]);
schema.pk_cols = vec![3, 0];
let (_, tid, schema) = create_table(&mut client, schema);
assert_eq!(schema.pk_stride(), 12, "I64 then U32, tightly packed");
let rows: Vec<(i64, u32, i64)> = vec![(-9_000_000_000, 7, 10), (-1, 4_294_967_295, 20), (0, 0, 30), (5, 1, 40)];
let mut batch = ZSetBatch::new(&schema);
let mut app = BatchAppender::new(&mut batch);
for &(c3, c0, c2) in &rows {
app.add_row_natives(&[c3 as u128, c0 as u128], 1)
.str_val("payload")
.i64_val(c2);
}
block_on(client.push(tid, &schema, &batch, WireConflictMode::Update)).unwrap();
let (_, reply) = read_keys(&mut client, tid, &schema, gt_predicate(2, 15));
let mut got: Vec<Vec<u8>> = (0..reply.len()).map(|i| reply.pks.get_bytes(i).to_vec()).collect();
let mut want: Vec<Vec<u8>> = rows[1..]
.iter()
.map(|&(c3, c0, _)| [((c3 as u64) ^ (1u64 << 63)).to_be_bytes().as_slice(), &c0.to_be_bytes()].concat())
.collect();
got.sort();
want.sort();
assert_eq!(got, want, "both PK columns round-trip verbatim, sign flip included");
}