use spg_engine::{Engine, QueryResult};
use spg_storage::Value;
fn rows(r: QueryResult) -> Vec<Vec<Value<'static>>> {
match r {
QueryResult::Rows { rows, .. } => rows.into_iter().map(|r| r.values).collect(),
_ => panic!("expected rows"),
}
}
fn seed_scores(e: &mut Engine) {
e.execute("CREATE TABLE t (id INT NOT NULL, score INT NOT NULL)")
.unwrap();
e.execute(
"INSERT INTO t VALUES \
(1, 100), (2, 90), (3, 80), (4, 80), (5, 80), (6, 70), (7, 60)",
)
.unwrap();
}
#[test]
fn fetch_first_3_with_ties_extends_through_ties() {
let mut e = Engine::new();
seed_scores(&mut e);
let mut r = rows(
e.execute("SELECT id FROM t ORDER BY score DESC FETCH FIRST 3 ROWS WITH TIES")
.unwrap(),
);
r.sort_by_key(|row| match row[0] {
Value::Int(n) => n,
_ => panic!(),
});
let ids: Vec<i32> = r
.into_iter()
.map(|row| match row[0] {
Value::Int(n) => n,
_ => panic!(),
})
.collect();
assert_eq!(ids, vec![1, 2, 3, 4, 5]);
}
#[test]
fn fetch_first_3_rows_only_keeps_exactly_n() {
let mut e = Engine::new();
seed_scores(&mut e);
let r = rows(
e.execute("SELECT id FROM t ORDER BY score DESC FETCH FIRST 3 ROWS ONLY")
.unwrap(),
);
assert_eq!(r.len(), 3);
assert_eq!(r[0][0], Value::Int(1));
assert_eq!(r[1][0], Value::Int(2));
match r[2][0] {
Value::Int(n) => assert!((3..=5).contains(&n)),
_ => panic!(),
}
}
#[test]
fn with_ties_without_order_by_errors() {
let mut e = Engine::new();
seed_scores(&mut e);
let err = e
.execute("SELECT id FROM t FETCH FIRST 3 ROWS WITH TIES")
.expect_err("WITH TIES without ORDER BY must error");
let msg = format!("{err:?}");
assert!(msg.to_uppercase().contains("ORDER BY"));
}
#[test]
fn with_ties_no_tie_at_cutoff_keeps_exactly_n() {
let mut e = Engine::new();
seed_scores(&mut e);
let r = rows(
e.execute("SELECT id FROM t ORDER BY score DESC FETCH FIRST 2 ROWS WITH TIES")
.unwrap(),
);
assert_eq!(r.len(), 2);
assert_eq!(r[0][0], Value::Int(1));
assert_eq!(r[1][0], Value::Int(2));
}
#[test]
fn with_ties_and_offset_extends_after_offset_cutoff() {
let mut e = Engine::new();
seed_scores(&mut e);
let mut r = rows(
e.execute(
"SELECT id FROM t ORDER BY score DESC \
OFFSET 2 FETCH FIRST 2 ROWS WITH TIES",
)
.unwrap(),
);
r.sort_by_key(|row| match row[0] {
Value::Int(n) => n,
_ => panic!(),
});
let ids: Vec<i32> = r
.into_iter()
.map(|row| match row[0] {
Value::Int(n) => n,
_ => panic!(),
})
.collect();
assert_eq!(ids, vec![3, 4, 5]);
}
#[test]
fn with_ties_multi_key_order_by_matches_full_tuple() {
let mut e = Engine::new();
seed_scores(&mut e);
let r = rows(
e.execute(
"SELECT id FROM t ORDER BY score DESC, id ASC \
FETCH FIRST 2 ROWS WITH TIES",
)
.unwrap(),
);
assert_eq!(r.len(), 2);
assert_eq!(r[0][0], Value::Int(1));
assert_eq!(r[1][0], Value::Int(2));
}
#[test]
fn with_ties_asc_order_by_extends_at_low_end() {
let mut e = Engine::new();
seed_scores(&mut e);
let mut r = rows(
e.execute("SELECT id FROM t ORDER BY score ASC FETCH FIRST 3 ROWS WITH TIES")
.unwrap(),
);
r.sort_by_key(|row| match row[0] {
Value::Int(n) => n,
_ => panic!(),
});
let ids: Vec<i32> = r
.into_iter()
.map(|row| match row[0] {
Value::Int(n) => n,
_ => panic!(),
})
.collect();
assert_eq!(ids, vec![3, 4, 5, 6, 7]);
}
#[test]
fn fetch_first_larger_than_row_count_returns_all() {
let mut e = Engine::new();
seed_scores(&mut e);
let r = rows(
e.execute("SELECT id FROM t ORDER BY score DESC FETCH FIRST 100 ROWS WITH TIES")
.unwrap(),
);
assert_eq!(r.len(), 7);
}