v26.10.0-pre
This commit is contained in:
1 parent
59027684e6
commit
fd5f320360
13 files changed
+149
-68
No files matched your search
@@ -182,6 +182,11 @@ impl HeapPage {
|
||||
|
||||
/// 读槽位里的行字节
|
||||
pub fn get_tuple(page: &Page, slot: u16) -> Option<Vec<u8>> {
|
||||
Self::tuple_bytes(page, slot).map(<[u8]>::to_vec)
|
||||
}
|
||||
|
||||
/// 借用槽位里的行字节
|
||||
fn tuple_bytes(page: &Page, slot: u16) -> Option<&[u8]> {
|
||||
let n = Self::slot_count(page);
|
||||
if slot >= n {
|
||||
return None;
|
||||
@@ -192,7 +197,7 @@ impl HeapPage {
|
||||
if len == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(page.data[off..off + len].to_vec())
|
||||
Some(&page.data[off..off + len])
|
||||
}
|
||||
|
||||
/// 所有槽位号
|
||||
@@ -593,14 +598,12 @@ impl HeapTable {
|
||||
|
||||
/// 按位置读一行
|
||||
pub fn read_at(&mut self, page_id: PageId, slot: u16) -> io::Result<Option<Row>> {
|
||||
let bytes = self.file.with_page(page_id, |p| HeapPage::get_tuple(p, slot))?;
|
||||
match bytes {
|
||||
None => Ok(None),
|
||||
Some(bytes) => {
|
||||
let row = decode_row(&bytes, None, &self.hidden_columns)?;
|
||||
Ok(Some(row))
|
||||
}
|
||||
}
|
||||
let hidden = &self.hidden_columns;
|
||||
self.file.with_page(page_id, |p| {
|
||||
HeapPage::tuple_bytes(p, slot)
|
||||
.map(|bytes| decode_row(bytes, None, hidden))
|
||||
.transpose()
|
||||
})?
|
||||
}
|
||||
|
||||
/// 按位置删一行并返回它
|
||||
@@ -631,13 +634,9 @@ impl HeapTable {
|
||||
pub fn delete_by_keys(&mut self, keys: &[i64]) -> io::Result<Vec<Row>> {
|
||||
let mut positions: Vec<(PageId, u16)> = Vec::new();
|
||||
|
||||
if self.has_index("id") {
|
||||
if let Some(idx) = self.indexes.iter_mut().find(|i| i.column == "id") {
|
||||
for &k in keys {
|
||||
let pos = self
|
||||
.indexes
|
||||
.iter_mut()
|
||||
.find(|i| i.column == "id")
|
||||
.and_then(|i| i.tree.get(&key_of(k)).ok().flatten());
|
||||
let pos = idx.tree.get(&key_of(k))?;
|
||||
if let Some(p) = pos {
|
||||
positions.push(unpack_position(p));
|
||||
}
|
||||
@@ -714,20 +713,16 @@ impl HeapTable {
|
||||
) -> io::Result<Vec<(PageId, u16, Row)>> {
|
||||
let n = self.file.num_pages()?;
|
||||
let mut out = Vec::new();
|
||||
let hidden = &self.hidden_columns;
|
||||
for i in from..to.min(n) {
|
||||
let tuples = self.file.with_page(i, |p| {
|
||||
let mut v = Vec::new();
|
||||
for slot in HeapPage::iter_slots(p) {
|
||||
if let Some(bytes) = HeapPage::get_tuple(p, slot) {
|
||||
v.push((slot, bytes));
|
||||
self.file.with_page(i, |p| -> io::Result<()> {
|
||||
for slot in 0..HeapPage::slot_count(p) {
|
||||
if let Some(bytes) = HeapPage::tuple_bytes(p, slot) {
|
||||
out.push((i, slot, decode_row(bytes, columns, hidden)?));
|
||||
}
|
||||
}
|
||||
v
|
||||
})?;
|
||||
for (slot, bytes) in tuples {
|
||||
let row = decode_row(&bytes, columns, &self.hidden_columns)?;
|
||||
out.push((i, slot, row));
|
||||
}
|
||||
Ok(())
|
||||
})??;
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,62 @@ fn row(pairs: &[(&str, serde_json::Value)]) -> Row {
|
||||
m
|
||||
}
|
||||
|
||||
/// 索引读失败时删除报错且保留数据
|
||||
#[test]
|
||||
fn delete_propagates_index_read_errors() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let data = dir.path().join("t.db");
|
||||
let index = dir.path().join("t.idx");
|
||||
let mut t = HeapTable::open_indexed(&data, &index, DEFAULT_PAGE_SIZE, 4, 1).unwrap();
|
||||
let original = row(&[("id", json!(1))]);
|
||||
t.insert_row(&original).unwrap();
|
||||
t.flush().unwrap();
|
||||
let file = std::fs::OpenOptions::new().write(true).open(&index).unwrap();
|
||||
file.set_len(DEFAULT_PAGE_SIZE as u64).unwrap();
|
||||
let err = t.delete_by_keys(&[1]).unwrap_err();
|
||||
assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof);
|
||||
assert_eq!(t.scan().unwrap(), vec![original]);
|
||||
}
|
||||
|
||||
/// 跨页投影与分片保持行序及隐藏列
|
||||
#[test]
|
||||
fn projected_shards_preserve_order_and_hidden_columns() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let mut t = HeapTable::open(dir.path().join("t.db"), 256, 1).unwrap();
|
||||
for i in 0..40 {
|
||||
t.insert_row(&row(&[("id", json!(i)), ("name", json!("alice")), ("secret", json!(i))])).unwrap();
|
||||
}
|
||||
t.set_hidden_columns(["secret".to_string()].into_iter().collect());
|
||||
let columns = vec!["id".to_string(), "secret".to_string()];
|
||||
let expected: Vec<Row> = (0..40).map(|i| row(&[("id", json!(i))])).collect();
|
||||
assert_eq!(t.scan_columns(Some(&columns)).unwrap(), expected);
|
||||
let pages = t.num_pages().unwrap();
|
||||
assert!(pages > 1);
|
||||
let mut shards = t.scan_columns_pages(Some(&columns), 0, pages / 2).unwrap();
|
||||
shards.extend(t.scan_columns_pages(Some(&columns), pages / 2, pages + 1).unwrap());
|
||||
assert_eq!(shards, expected);
|
||||
assert_eq!(t.read_at(0, 0).unwrap().unwrap(), row(&[("id", json!(0)), ("name", json!("alice"))]));
|
||||
}
|
||||
|
||||
/// 扫描与点读均返回行解码错误
|
||||
#[test]
|
||||
fn malformed_rows_return_decode_errors() {
|
||||
use chusql_core_storage::heap::HeapPage;
|
||||
use chusql_core_storage::page::{Page, PageFile};
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("t.db");
|
||||
let mut file = PageFile::open(&path, DEFAULT_PAGE_SIZE).unwrap();
|
||||
let mut page = Page::new(0, DEFAULT_PAGE_SIZE);
|
||||
HeapPage::insert_tuple(&mut page, b"{\"id\":1} trailing").unwrap();
|
||||
file.write_page(&page).unwrap();
|
||||
file.flush().unwrap();
|
||||
drop(file);
|
||||
let mut t = HeapTable::open(&path, DEFAULT_PAGE_SIZE, 1).unwrap();
|
||||
assert!(t.scan().is_err());
|
||||
assert!(t.scan_columns(Some(&[])).is_err());
|
||||
assert!(t.read_at(0, 0).is_err());
|
||||
}
|
||||
|
||||
/// 索引文件被清空(强杀或截断)后打开,会按堆数据补齐
|
||||
#[test]
|
||||
fn empty_index_file_is_rebuilt_from_rows() {
|
||||
|
||||
Reference in new issue
Block a user