优化和收尾
This commit is contained in:
1 parent
7f5bcc289e
commit
9e8c5f1ef0
33 files changed
+3332
-598
No files matched your search
@@ -153,3 +153,63 @@ fn order_too_large_for_page_errors() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
assert!(DiskBTree::open(dir.path().join("t.idx"), 512, 300, DEFAULT_POOL_SIZE).is_err());
|
||||
}
|
||||
|
||||
/// 删掉键之后查不到,别人不受影响
|
||||
#[test]
|
||||
fn delete_removes_only_that_key() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let mut t = DiskBTree::open(
|
||||
dir.path().join("t.idx"),
|
||||
DEFAULT_PAGE_SIZE,
|
||||
DEFAULT_BTREE_ORDER,
|
||||
DEFAULT_POOL_SIZE,
|
||||
)
|
||||
.unwrap();
|
||||
for i in 0..200 {
|
||||
t.insert(i, i as u64 + 1).unwrap();
|
||||
}
|
||||
|
||||
assert!(t.delete(50).unwrap(), "删一个存在的键应该返回 true");
|
||||
assert_eq!(t.get(50).unwrap(), None);
|
||||
assert_eq!(t.get(49).unwrap(), Some(50));
|
||||
assert_eq!(t.get(51).unwrap(), Some(52));
|
||||
|
||||
// 再删一次:它已经不在了
|
||||
assert!(!t.delete(50).unwrap());
|
||||
|
||||
// 遍历里也不能再有它
|
||||
let all = t.iter_all().unwrap();
|
||||
assert_eq!(all.len(), 199);
|
||||
assert!(all.iter().all(|(k, _)| *k != 50));
|
||||
}
|
||||
|
||||
/// 删过之后树还能继续插(惰性删除会留下稀疏节点,但不能影响正确性)
|
||||
#[test]
|
||||
fn insert_after_delete_still_works() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let mut t = DiskBTree::open(
|
||||
dir.path().join("t.idx"),
|
||||
DEFAULT_PAGE_SIZE,
|
||||
DEFAULT_BTREE_ORDER,
|
||||
DEFAULT_POOL_SIZE,
|
||||
)
|
||||
.unwrap();
|
||||
for i in 0..300 {
|
||||
t.insert(i, i as u64).unwrap();
|
||||
}
|
||||
// 把偶数键全删掉
|
||||
for i in (0..300).step_by(2) {
|
||||
assert!(t.delete(i).unwrap());
|
||||
}
|
||||
// 再插一批新的
|
||||
for i in 1000..1100 {
|
||||
t.insert(i, i as u64).unwrap();
|
||||
}
|
||||
for i in 0..300 {
|
||||
let want = if i % 2 == 0 { None } else { Some(i as u64) };
|
||||
assert_eq!(t.get(i).unwrap(), want, "key {}", i);
|
||||
}
|
||||
for i in 1000..1100 {
|
||||
assert_eq!(t.get(i).unwrap(), Some(i as u64));
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ fn insert_and_scan_one_row() {
|
||||
let path = dir.path().join("t.db");
|
||||
let mut t = HeapTable::open(&path, DEFAULT_PAGE_SIZE, DEFAULT_POOL_SIZE).unwrap();
|
||||
|
||||
t.insert(&row(&[("id", json!(1)), ("name", json!("Alice"))]))
|
||||
t.insert_row(&row(&[("id", json!(1)), ("name", json!("Alice"))]))
|
||||
.unwrap();
|
||||
|
||||
let rows = t.scan().unwrap();
|
||||
@@ -38,7 +38,7 @@ fn insert_many_rows_cross_pages() {
|
||||
let mut t = HeapTable::open(&path, DEFAULT_PAGE_SIZE, DEFAULT_POOL_SIZE).unwrap();
|
||||
|
||||
for i in 0..500 {
|
||||
t.insert(&row(&[("id", json!(i))])).unwrap();
|
||||
t.insert_row(&row(&[("id", json!(i))])).unwrap();
|
||||
}
|
||||
|
||||
let rows = t.scan().unwrap();
|
||||
@@ -56,7 +56,7 @@ fn large_row_still_fits() {
|
||||
let mut t = HeapTable::open(&path, DEFAULT_PAGE_SIZE, DEFAULT_POOL_SIZE).unwrap();
|
||||
|
||||
let big = "x".repeat(2000);
|
||||
t.insert(&row(&[("data", json!(big.clone()))])).unwrap();
|
||||
t.insert_row(&row(&[("data", json!(big.clone()))])).unwrap();
|
||||
|
||||
let rows = t.scan().unwrap();
|
||||
assert_eq!(rows.len(), 1);
|
||||
@@ -71,7 +71,7 @@ fn row_too_large_errors() {
|
||||
let mut t = HeapTable::open(&path, DEFAULT_PAGE_SIZE, DEFAULT_POOL_SIZE).unwrap();
|
||||
|
||||
let huge = "x".repeat(10_000);
|
||||
let err = t.insert(&row(&[("data", json!(huge))]));
|
||||
let err = t.insert_row(&row(&[("data", json!(huge))]));
|
||||
assert!(err.is_err());
|
||||
}
|
||||
|
||||
@@ -83,8 +83,8 @@ fn reopen_and_scan() {
|
||||
|
||||
{
|
||||
let mut t = HeapTable::open(&path, DEFAULT_PAGE_SIZE, DEFAULT_POOL_SIZE).unwrap();
|
||||
t.insert(&row(&[("id", json!(1))])).unwrap();
|
||||
t.insert(&row(&[("id", json!(2))])).unwrap();
|
||||
t.insert_row(&row(&[("id", json!(1))])).unwrap();
|
||||
t.insert_row(&row(&[("id", json!(2))])).unwrap();
|
||||
}
|
||||
|
||||
let mut t = HeapTable::open(&path, DEFAULT_PAGE_SIZE, DEFAULT_POOL_SIZE).unwrap();
|
||||
@@ -102,12 +102,12 @@ fn custom_page_size() {
|
||||
let mut t = HeapTable::open(&path, 512, DEFAULT_POOL_SIZE).unwrap();
|
||||
|
||||
for i in 0..50 {
|
||||
t.insert(&row(&[("id", json!(i))])).unwrap();
|
||||
t.insert_row(&row(&[("id", json!(i))])).unwrap();
|
||||
}
|
||||
assert_eq!(t.scan().unwrap().len(), 50);
|
||||
|
||||
let big = "x".repeat(600);
|
||||
assert!(t.insert(&row(&[("data", json!(big))])).is_err());
|
||||
assert!(t.insert_row(&row(&[("data", json!(big))])).is_err());
|
||||
}
|
||||
|
||||
/// 整表改写后索引重建
|
||||
@@ -123,9 +123,9 @@ fn replace_all_rebuilds_index() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
t.insert_keyed(&row(&[("id", json!(1)), ("name", json!("Alice"))]), 1)
|
||||
t.insert_row(&row(&[("id", json!(1)), ("name", json!("Alice"))]))
|
||||
.unwrap();
|
||||
t.insert_keyed(&row(&[("id", json!(2)), ("name", json!("Bob"))]), 2)
|
||||
t.insert_row(&row(&[("id", json!(2)), ("name", json!("Bob"))]))
|
||||
.unwrap();
|
||||
assert!(t.get_by_key(1).unwrap().is_some());
|
||||
|
||||
@@ -141,3 +141,150 @@ fn replace_all_rebuilds_index() {
|
||||
assert_eq!(got["name"], json!("Zoe"));
|
||||
assert_eq!(t.scan().unwrap().len(), 2);
|
||||
}
|
||||
|
||||
/// 行级删除:行没了、索引条目没了、顺序不乱
|
||||
#[test]
|
||||
fn delete_by_keys_removes_rows() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let mut t = HeapTable::open_indexed(
|
||||
dir.path().join("t.db"),
|
||||
dir.path().join("t.idx"),
|
||||
DEFAULT_PAGE_SIZE,
|
||||
chusql_storage::config::DEFAULT_BTREE_ORDER,
|
||||
DEFAULT_POOL_SIZE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
for i in 1..=5 {
|
||||
t.insert_row(&row(&[("id", json!(i)), ("name", json!(format!("u{}", i)))]))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let deleted = t.delete_by_keys(&[2, 4, 99]).unwrap();
|
||||
assert_eq!(deleted.len(), 2, "只有 2、4 真的删掉了");
|
||||
|
||||
let ids: Vec<i64> = t
|
||||
.scan()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|r| r["id"].as_i64().unwrap())
|
||||
.collect();
|
||||
assert_eq!(ids, vec![1, 3, 5], "剩下的行要保持原顺序");
|
||||
|
||||
assert!(t.get_by_key(2).unwrap().is_none(), "索引条目要跟着删");
|
||||
assert!(t.get_by_key(4).unwrap().is_none());
|
||||
assert!(t.get_by_key(3).unwrap().is_some());
|
||||
|
||||
// 删掉的位置可以再插回来
|
||||
t.insert_row(&row(&[("id", json!(2)), ("name", json!("u2b"))]))
|
||||
.unwrap();
|
||||
assert_eq!(t.get_by_key(2).unwrap().unwrap()["name"], json!("u2b"));
|
||||
}
|
||||
|
||||
/// 批量插入
|
||||
#[test]
|
||||
fn insert_rows_batch() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let mut t = HeapTable::open_indexed(
|
||||
dir.path().join("t.db"),
|
||||
dir.path().join("t.idx"),
|
||||
DEFAULT_PAGE_SIZE,
|
||||
chusql_storage::config::DEFAULT_BTREE_ORDER,
|
||||
DEFAULT_POOL_SIZE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let rows: Vec<Row> = (1..=20)
|
||||
.map(|i| row(&[("id", json!(i)), ("name", json!(format!("u{}", i)))]))
|
||||
.collect();
|
||||
t.insert_rows(&rows).unwrap();
|
||||
|
||||
assert_eq!(t.scan().unwrap().len(), 20);
|
||||
assert_eq!(t.get_by_key(20).unwrap().unwrap()["name"], json!("u20"));
|
||||
|
||||
// 批里出现重复键要报错(整批之前就查出来,不会插一半)
|
||||
let dup = vec![
|
||||
row(&[("id", json!(100))]),
|
||||
row(&[("id", json!(100))]),
|
||||
];
|
||||
assert!(t.insert_rows(&dup).is_err());
|
||||
}
|
||||
|
||||
/// 第二列索引:建索引时要求唯一,建完之后插入/删除都跟着维护
|
||||
#[test]
|
||||
fn secondary_index_is_maintained() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let mut t = HeapTable::open_indexed(
|
||||
dir.path().join("t.db"),
|
||||
dir.path().join("t.idx"),
|
||||
DEFAULT_PAGE_SIZE,
|
||||
chusql_storage::config::DEFAULT_BTREE_ORDER,
|
||||
DEFAULT_POOL_SIZE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
t.insert_rows(&[
|
||||
row(&[("id", json!(1)), ("code", json!(7001))]),
|
||||
row(&[("id", json!(2)), ("code", json!(7002))]),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert!(!t.has_index("code"));
|
||||
t.build_index(
|
||||
"code",
|
||||
&dir.path().join("t.code.idx"),
|
||||
DEFAULT_PAGE_SIZE,
|
||||
chusql_storage::config::DEFAULT_BTREE_ORDER,
|
||||
DEFAULT_POOL_SIZE,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(t.has_index("code"));
|
||||
assert_eq!(t.get_by_column_key("code", 7002).unwrap().unwrap()["id"], json!(2));
|
||||
|
||||
// 建索引之后新插入的行也进索引
|
||||
t.insert_row(&row(&[("id", json!(3)), ("code", json!(7003))]))
|
||||
.unwrap();
|
||||
assert_eq!(t.get_by_column_key("code", 7003).unwrap().unwrap()["id"], json!(3));
|
||||
|
||||
// 删掉一行,索引条目跟着走
|
||||
t.delete_by_keys(&[2]).unwrap();
|
||||
assert!(t.get_by_column_key("code", 7002).unwrap().is_none());
|
||||
|
||||
// code 已经有索引,重复的 code 插不进去(索引列是唯一的)
|
||||
assert!(t.insert_row(&row(&[("id", json!(4)), ("code", json!(7001))])).is_err());
|
||||
let mut t2 = HeapTable::open(&dir.path().join("d.db"), DEFAULT_PAGE_SIZE, DEFAULT_POOL_SIZE).unwrap();
|
||||
t2.insert_rows(&[
|
||||
row(&[("id", json!(1)), ("age", json!(30))]),
|
||||
row(&[("id", json!(2)), ("age", json!(30))]),
|
||||
])
|
||||
.unwrap();
|
||||
let err = t2.build_index(
|
||||
"age",
|
||||
&dir.path().join("d.age.idx"),
|
||||
DEFAULT_PAGE_SIZE,
|
||||
chusql_storage::config::DEFAULT_BTREE_ORDER,
|
||||
DEFAULT_POOL_SIZE,
|
||||
);
|
||||
assert!(err.is_err(), "重复值不给建索引");
|
||||
assert!(!t2.has_index("age"), "失败之后不留半成品");
|
||||
}
|
||||
|
||||
/// 同一个 id 插两次要报错
|
||||
#[test]
|
||||
fn duplicate_key_is_rejected() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let mut t = HeapTable::open_indexed(
|
||||
dir.path().join("t.db"),
|
||||
dir.path().join("t.idx"),
|
||||
DEFAULT_PAGE_SIZE,
|
||||
chusql_storage::config::DEFAULT_BTREE_ORDER,
|
||||
DEFAULT_POOL_SIZE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
t.insert_row(&row(&[("id", json!(1))])).unwrap();
|
||||
let r = t.insert_row(&row(&[("id", json!(1))]));
|
||||
assert!(r.is_err());
|
||||
assert_eq!(t.scan().unwrap().len(), 1, "失败的那一行不应该进堆表");
|
||||
}
|
||||
|
||||
@@ -152,17 +152,17 @@ fn replace_all_replaces_rows() {
|
||||
assert!(!r.contains("Alice"), "old row should be gone: {}", r);
|
||||
}
|
||||
|
||||
/// 带 key 插入后能按下标查
|
||||
/// 按 id 查(列名走默认值 `id`,老客户端不用改)
|
||||
#[test]
|
||||
fn insert_with_key_then_lookup() {
|
||||
fn insert_then_lookup_by_index() {
|
||||
let pipe = unique_pipe_name();
|
||||
let (_srv, _data) = start_server(&pipe);
|
||||
let mut c = connect(&pipe).unwrap();
|
||||
|
||||
for (id, name) in [(1, "Alice"), (2, "Bob"), (3, "Carol")] {
|
||||
let req = format!(
|
||||
r#"{{"method":"insert","table":"idx_users","row":{{"id":{},"name":"{}"}},"key":{}}}"#,
|
||||
id, name, id
|
||||
r#"{{"method":"insert","table":"idx_users","row":{{"id":{},"name":"{}"}}}}"#,
|
||||
id, name
|
||||
);
|
||||
let r = send(&mut c, &req);
|
||||
assert!(r.contains(r#""status":"ok""#), "insert: {}", r);
|
||||
@@ -175,16 +175,170 @@ fn insert_with_key_then_lookup() {
|
||||
assert!(r.contains(r#""rows":[]"#), "miss: {}", r);
|
||||
}
|
||||
|
||||
/// 不带 key 不建索引
|
||||
/// 插入时索引由表自己维护:没给 key,id 也会进索引
|
||||
#[test]
|
||||
fn insert_without_key_does_not_index() {
|
||||
fn insert_indexes_the_id_column_automatically() {
|
||||
let pipe = unique_pipe_name();
|
||||
let (_srv, _data) = start_server(&pipe);
|
||||
let mut c = connect(&pipe).unwrap();
|
||||
|
||||
send(&mut c, r#"{"method":"insert","table":"nokey","row":{"id":1}}"#);
|
||||
let r = send(&mut c, r#"{"method":"lookup_by_index","table":"nokey","key":1}"#);
|
||||
assert!(r.contains(r#""rows":[]"#), "got: {}", r);
|
||||
send(&mut c, r#"{"method":"insert","table":"auto_idx","row":{"id":7,"name":"Zoe"}}"#);
|
||||
let r = send(
|
||||
&mut c,
|
||||
r#"{"method":"lookup_by_index","table":"auto_idx","column":"id","key":7}"#,
|
||||
);
|
||||
assert!(r.contains("Zoe"), "got: {}", r);
|
||||
}
|
||||
|
||||
/// 同一个 id 插两次要报错(索引列是唯一的)
|
||||
#[test]
|
||||
fn duplicate_id_is_rejected() {
|
||||
let pipe = unique_pipe_name();
|
||||
let (_srv, _data) = start_server(&pipe);
|
||||
let mut c = connect(&pipe).unwrap();
|
||||
|
||||
let req = r#"{"method":"insert","table":"dup_id","row":{"id":1,"name":"A"}}"#;
|
||||
assert!(send(&mut c, req).contains(r#""status":"ok""#));
|
||||
|
||||
let r = send(&mut c, r#"{"method":"insert","table":"dup_id","row":{"id":1,"name":"B"}}"#);
|
||||
assert!(r.contains(r#""status":"error""#), "got: {}", r);
|
||||
assert!(r.contains("duplicate key"), "got: {}", r);
|
||||
}
|
||||
|
||||
/// 批量插入一次全进
|
||||
#[test]
|
||||
fn insert_batch_writes_all_rows() {
|
||||
let pipe = unique_pipe_name();
|
||||
let (_srv, _data) = start_server(&pipe);
|
||||
let mut c = connect(&pipe).unwrap();
|
||||
|
||||
let r = send(
|
||||
&mut c,
|
||||
r#"{"method":"insert_batch","table":"batch_t","rows":[{"id":1,"name":"A"},{"id":2,"name":"B"},{"id":3,"name":"C"}]}"#,
|
||||
);
|
||||
assert!(r.contains(r#""status":"ok""#), "batch: {}", r);
|
||||
|
||||
let r = send(&mut c, r#"{"method":"scan","table":"batch_t"}"#);
|
||||
assert!(r.contains("A") && r.contains("B") && r.contains("C"), "scan: {}", r);
|
||||
|
||||
let r = send(&mut c, r#"{"method":"lookup_by_index","table":"batch_t","key":2}"#);
|
||||
assert!(r.contains("B"), "index after batch: {}", r);
|
||||
}
|
||||
|
||||
/// 行级删除:行没了、索引条目也没了、别人不受影响
|
||||
#[test]
|
||||
fn delete_keys_removes_row_and_index_entry() {
|
||||
let pipe = unique_pipe_name();
|
||||
let (_srv, _data) = start_server(&pipe);
|
||||
let mut c = connect(&pipe).unwrap();
|
||||
|
||||
for (id, name) in [(1, "Alice"), (2, "Bob"), (3, "Carol")] {
|
||||
let req = format!(
|
||||
r#"{{"method":"insert","table":"del_t","row":{{"id":{},"name":"{}"}}}}"#,
|
||||
id, name
|
||||
);
|
||||
send(&mut c, &req);
|
||||
}
|
||||
|
||||
let r = send(&mut c, r#"{"method":"delete_keys","table":"del_t","keys":[2]}"#);
|
||||
assert!(r.contains(r#""status":"ok""#), "delete: {}", r);
|
||||
|
||||
let r = send(&mut c, r#"{"method":"scan","table":"del_t"}"#);
|
||||
assert!(!r.contains("Bob"), "row should be gone: {}", r);
|
||||
assert!(r.contains("Alice") && r.contains("Carol"), "others stay: {}", r);
|
||||
|
||||
let r = send(&mut c, r#"{"method":"lookup_by_index","table":"del_t","key":2}"#);
|
||||
assert!(r.contains(r#""rows":[]"#), "index entry should be gone: {}", r);
|
||||
|
||||
let r = send(&mut c, r#"{"method":"insert","table":"del_t","row":{"id":2,"name":"Bob2"}}"#);
|
||||
assert!(r.contains(r#""status":"ok""#), "id 2 应该能再插进来: {}", r);
|
||||
}
|
||||
|
||||
/// 给第二列建索引后能按那一列查
|
||||
#[test]
|
||||
fn create_index_on_secondary_column() {
|
||||
let pipe = unique_pipe_name();
|
||||
let (_srv, _data) = start_server(&pipe);
|
||||
let mut c = connect(&pipe).unwrap();
|
||||
|
||||
for (id, code) in [(1, 5001), (2, 5002), (3, 5003)] {
|
||||
let req = format!(
|
||||
r#"{{"method":"insert","table":"sec_t","row":{{"id":{},"code":{}}}}}"#,
|
||||
id, code
|
||||
);
|
||||
send(&mut c, &req);
|
||||
}
|
||||
|
||||
// 没建之前:明确回 no_index,而不是回空结果
|
||||
let r = send(&mut c, r#"{"method":"lookup_by_index","table":"sec_t","column":"code","key":5002}"#);
|
||||
assert!(r.contains(r#""status":"no_index""#), "got: {}", r);
|
||||
|
||||
let r = send(&mut c, r#"{"method":"create_index","table":"sec_t","column":"code"}"#);
|
||||
assert!(r.contains(r#""status":"ok""#), "create_index: {}", r);
|
||||
|
||||
let r = send(&mut c, r#"{"method":"lookup_by_index","table":"sec_t","column":"code","key":5002}"#);
|
||||
assert!(r.contains(r#""id":2"#), "lookup by code: {}", r);
|
||||
|
||||
// 建完索引之后新插入的行也要进索引
|
||||
send(&mut c, r#"{"method":"insert","table":"sec_t","row":{"id":4,"code":5004}}"#);
|
||||
let r = send(&mut c, r#"{"method":"lookup_by_index","table":"sec_t","column":"code","key":5004}"#);
|
||||
assert!(r.contains(r#""id":4"#), "lookup new row: {}", r);
|
||||
|
||||
// schema 里能看到这条索引
|
||||
let r = send(&mut c, r#"{"method":"describe_table","table":"sec_t"}"#);
|
||||
assert!(r.contains(r#""column":"code""#), "describe: {}", r);
|
||||
|
||||
// 删掉索引后回到 no_index
|
||||
let r = send(&mut c, r#"{"method":"drop_index","table":"sec_t","column":"code"}"#);
|
||||
assert!(r.contains(r#""status":"ok""#), "drop_index: {}", r);
|
||||
let r = send(&mut c, r#"{"method":"lookup_by_index","table":"sec_t","column":"code","key":5002}"#);
|
||||
assert!(r.contains(r#""status":"no_index""#), "after drop: {}", r);
|
||||
}
|
||||
|
||||
/// 有重复值的列不给建索引(不然"走索引"和"全表扫"会给出不同答案)
|
||||
#[test]
|
||||
fn create_index_rejects_duplicate_values() {
|
||||
let pipe = unique_pipe_name();
|
||||
let (_srv, _data) = start_server(&pipe);
|
||||
let mut c = connect(&pipe).unwrap();
|
||||
|
||||
send(&mut c, r#"{"method":"insert","table":"dup_t","row":{"id":1,"age":30}}"#);
|
||||
send(&mut c, r#"{"method":"insert","table":"dup_t","row":{"id":2,"age":30}}"#);
|
||||
|
||||
let r = send(&mut c, r#"{"method":"create_index","table":"dup_t","column":"age"}"#);
|
||||
assert!(r.contains(r#""status":"error""#), "got: {}", r);
|
||||
assert!(r.contains("duplicate value"), "got: {}", r);
|
||||
|
||||
// 失败之后不留半成品:查这一列仍然是 no_index
|
||||
let r = send(&mut c, r#"{"method":"lookup_by_index","table":"dup_t","column":"age","key":30}"#);
|
||||
assert!(r.contains(r#""status":"no_index""#), "got: {}", r);
|
||||
}
|
||||
|
||||
/// 数据字典里的统计:不同值个数
|
||||
#[test]
|
||||
fn stats_report_distinct_values() {
|
||||
let pipe = unique_pipe_name();
|
||||
let (_srv, _data) = start_server(&pipe);
|
||||
let mut c = connect(&pipe).unwrap();
|
||||
|
||||
for (id, age) in [(1, 30), (2, 30), (3, 41)] {
|
||||
let req = format!(
|
||||
r#"{{"method":"insert","table":"stat_t","row":{{"id":{},"age":{}}}}}"#,
|
||||
id, age
|
||||
);
|
||||
send(&mut c, &req);
|
||||
}
|
||||
|
||||
let r = send(&mut c, r#"{"method":"describe_table","table":"stat_t"}"#);
|
||||
assert!(r.contains(r#""row_count":3"#), "row_count: {}", r);
|
||||
assert!(r.contains(r#""name":"age","distinct":2"#), "age 有 2 个不同值: {}", r);
|
||||
assert!(r.contains(r#""name":"id","distinct":3"#), "id 有 3 个不同值: {}", r);
|
||||
|
||||
// 删掉一行,统计跟着掉
|
||||
send(&mut c, r#"{"method":"delete_keys","table":"stat_t","keys":[1]}"#);
|
||||
let r = send(&mut c, r#"{"method":"describe_table","table":"stat_t"}"#);
|
||||
assert!(r.contains(r#""row_count":2"#), "after delete: {}", r);
|
||||
assert!(r.contains(r#""name":"age","distinct":1"#), "after delete: {}", r);
|
||||
}
|
||||
|
||||
/// 重开服务后索引仍在
|
||||
|
||||
@@ -22,7 +22,6 @@ fn append_and_read_insert() {
|
||||
let op = WalOp::Insert {
|
||||
table: "users".into(),
|
||||
row: row(&[("id", json!(1)), ("name", json!("Alice"))]),
|
||||
key: Some(1),
|
||||
};
|
||||
wal.append(&op).unwrap();
|
||||
|
||||
@@ -77,7 +76,6 @@ fn clear_truncates() {
|
||||
wal.append(&WalOp::Insert {
|
||||
table: "t".into(),
|
||||
row: row(&[("id", json!(1))]),
|
||||
key: None,
|
||||
})
|
||||
.unwrap();
|
||||
assert!(wal.read().unwrap().is_some());
|
||||
@@ -97,7 +95,6 @@ fn truncated_tail_is_ignored() {
|
||||
wal.append(&WalOp::Insert {
|
||||
table: "users".into(),
|
||||
row: row(&[("id", json!(1)), ("name", json!("Alice"))]),
|
||||
key: Some(1),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -116,9 +113,9 @@ fn overwrite_on_second_append_is_visible() {
|
||||
wal.append(&WalOp::Insert {
|
||||
table: "t".into(),
|
||||
row: row(&[("id", json!(1))]),
|
||||
key: None,
|
||||
})
|
||||
.unwrap();
|
||||
let first = wal.read().unwrap().unwrap();
|
||||
assert!(matches!(first, WalOp::Insert { .. }));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user