Skip to content

Commit 60f2c6f

Browse files
committed
Implement internal query algebra operators (Scan, Filter, Project, Limit, Offset)
1 parent 2fe8092 commit 60f2c6f

4 files changed

Lines changed: 465 additions & 2 deletions

File tree

ROADMAP.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ This stage will implement the compaction algorithm for the LSM tree.
3939
# Stage 6 - Internal query API
4040

4141
- [x] Define the operators and structures of the internal query algebra in specs/query-algebra.md
42-
- [] Implement selection of attributes from document collections
43-
- [] Implement filtering based on constraints on attributes
42+
- [x] Implement selection of attributes from document collections
43+
- [x] Implement filtering based on constraints on attributes
4444

4545
# Stage 7 - SQL Language
4646

src/db.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::storage::MemTable;
44
use serde_json::Value;
55
use std::fs;
66
use uuid::Uuid;
7+
use std::iter::Peekable;
78

89
const MEMTABLE_THRESHOLD: usize = 10;
910
const JSTABLE_THRESHOLD: u64 = 5;
@@ -15,6 +16,63 @@ pub struct DB {
1516
logger: Logger,
1617
}
1718

19+
struct MergedIterator<'a> {
20+
sources: Vec<Peekable<Box<dyn Iterator<Item = (String, Value)> + 'a>>>,
21+
}
22+
23+
impl<'a> Iterator for MergedIterator<'a> {
24+
type Item = (String, Value);
25+
26+
fn next(&mut self) -> Option<Self::Item> {
27+
loop {
28+
// Find min_id
29+
let mut min_id: Option<String> = None;
30+
31+
for source in &mut self.sources {
32+
if let Some((id, _)) = source.peek() {
33+
match &min_id {
34+
None => min_id = Some(id.clone()),
35+
Some(current_min) => {
36+
if id < current_min {
37+
min_id = Some(id.clone());
38+
}
39+
}
40+
}
41+
}
42+
}
43+
44+
let min_id = min_id?; // If None, all exhausted
45+
46+
// Consume from sources
47+
let mut result: Option<Value> = None;
48+
49+
for source in &mut self.sources {
50+
let is_match = if let Some((id, _)) = source.peek() {
51+
id == &min_id
52+
} else {
53+
false
54+
};
55+
56+
if is_match {
57+
let (_, doc) = source.next().unwrap();
58+
if result.is_none() {
59+
// First match (highest priority)
60+
result = Some(doc);
61+
}
62+
// Else: ignored (shadowed)
63+
}
64+
}
65+
66+
if let Some(doc) = result {
67+
if !doc.is_null() {
68+
return Some((min_id, doc));
69+
}
70+
// If null (tombstone), loop again
71+
}
72+
}
73+
}
74+
}
75+
1876
impl DB {
1977
pub fn new(jstable_dir: &str) -> Self {
2078
fs::create_dir_all(jstable_dir).unwrap();
@@ -49,6 +107,26 @@ impl DB {
49107
}
50108
}
51109

110+
pub fn scan(&self) -> impl Iterator<Item = (String, Value)> + '_ {
111+
let mut sources: Vec<Peekable<Box<dyn Iterator<Item = (String, Value)>>>> = Vec::new();
112+
113+
// 1. MemTable Iterator (Priority 0 - Highest)
114+
let mem_iter = self.memtable.documents.iter().map(|(k, v)| (k.clone(), v.clone()));
115+
sources.push((Box::new(mem_iter) as Box<dyn Iterator<Item = (String, Value)>>).peekable());
116+
117+
// 2. JSTable Iterators (Newer to Older)
118+
for i in (0..self.jstable_count).rev() {
119+
let path = format!("{}/jstable-{}", self.jstable_dir, i);
120+
if let Ok(iter) = jstable::JSTableIterator::new(&path) {
121+
// Map Result to Value, unwrapping errors for now
122+
let iter = iter.map(|r| r.unwrap());
123+
sources.push((Box::new(iter) as Box<dyn Iterator<Item = (String, Value)>>).peekable());
124+
}
125+
}
126+
127+
MergedIterator { sources }
128+
}
129+
52130
pub fn insert(&mut self, doc: Value) -> String {
53131
if self.memtable.len() >= MEMTABLE_THRESHOLD {
54132
self.flush();
@@ -281,4 +359,48 @@ mod tests {
281359
// Verify other documents exist (e.g. from flush 1)
282360
assert!(table.documents.len() > 40);
283361
}
362+
363+
#[test]
364+
fn test_db_scan() {
365+
let dir = tempdir().unwrap();
366+
let mut db = DB::new(dir.path().to_str().unwrap());
367+
368+
// 1. Insert into JSTable (flush)
369+
// 0..9
370+
let mut ids = Vec::new();
371+
for i in 0..MEMTABLE_THRESHOLD {
372+
ids.push(db.insert(json!({"val": i})));
373+
}
374+
// This filled memtable (10 items). Next insert triggers flush.
375+
376+
// 2. Insert into MemTable (triggers flush of 0..9 to jstable-0)
377+
let id_val_10 = db.insert(json!({"val": 10}));
378+
// Memtable has 1 item (val 10). jstable-0 has 10 items.
379+
380+
// 3. Shadowing: Update an item from jstable-0
381+
let id_to_shadow = ids[0].clone(); // val: 0
382+
db.update(&id_to_shadow, json!({"val": 999}));
383+
// Memtable has: val: 10, val: 999 (id_to_shadow).
384+
385+
// 4. Deletion: Delete an item from jstable-0
386+
let id_to_delete = ids[1].clone(); // val: 1
387+
db.delete(&id_to_delete);
388+
// Memtable has: val: 10, val: 999, tombstone(id_to_delete).
389+
390+
// Scan
391+
let results: std::collections::HashMap<String, Value> = db.scan().collect();
392+
393+
// Check shadowing
394+
assert_eq!(results.get(&id_to_shadow).unwrap(), &json!({"val": 999}));
395+
396+
// Check deletion
397+
assert!(!results.contains_key(&id_to_delete));
398+
399+
// Check preservation of older jstable item
400+
let id_preserved = ids[2].clone(); // val: 2
401+
assert_eq!(results.get(&id_preserved).unwrap(), &json!({"val": 2}));
402+
403+
// Check memtable item
404+
assert_eq!(results.get(&id_val_10).unwrap(), &json!({"val": 10}));
405+
}
284406
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod storage;
33
pub mod log;
44
pub mod db;
55
pub mod jstable;
6+
pub mod query;
67

78
fn main() {
89
println!("Hello, world!");

0 commit comments

Comments
 (0)