Skip to content

Commit ee11929

Browse files
committed
feat: Implement DDL for collections
This commit introduces DDL statements for managing collections in ArgusDB. The following statements are now supported: - CREATE COLLECTION <collection_name> - DROP COLLECTION <collection_name> - SHOW COLLECTIONS The changes are implemented in the following files: - : Updated the query language specification to include the new DDL statements. - : Updated the parser to recognize the new DDL statements. - : Added the new DDL statements to the enum. - : Implemented the logic for creating, dropping, and showing collections. - : Updated the main loop to handle the new DDL statements.
1 parent 75e7d85 commit ee11929

5 files changed

Lines changed: 322 additions & 97 deletions

File tree

specs/query-language.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,36 @@ The database consists of collections of JSON documents. Each document is a semi-
88

99
## Statements
1010

11+
### CREATE COLLECTION
12+
13+
Creates a new, empty collection in the database.
14+
15+
**Syntax:**
16+
17+
```sql
18+
CREATE COLLECTION <collection_name>
19+
```
20+
21+
### DROP COLLECTION
22+
23+
Removes an entire collection, including all of its documents and associated data.
24+
25+
**Syntax:**
26+
27+
```sql
28+
DROP COLLECTION <collection_name>
29+
```
30+
31+
### SHOW COLLECTIONS
32+
33+
Lists all available collections in the database.
34+
35+
**Syntax:**
36+
37+
```sql
38+
SHOW COLLECTIONS
39+
```
40+
1141
### INSERT
1242

1343
The `INSERT` statement is used to add new documents to a collection.

src/db.rs

Lines changed: 161 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -245,83 +245,74 @@ impl<'a> Iterator for MergedIterator<'a> {
245245
impl DB {
246246
pub fn new(root_dir: &str, memtable_threshold: usize, jstable_threshold: u64) -> Self {
247247
fs::create_dir_all(root_dir).unwrap();
248-
let mut collections = HashMap::new();
249-
250-
if let Ok(entries) = fs::read_dir(root_dir) {
251-
for entry in entries {
252-
if let Ok(entry) = entry {
253-
if entry.path().is_dir() {
254-
let dir_path = entry.path();
255-
256-
// Try to find collection name from JSTable-0
257-
let jstable_path = dir_path.join("jstable-0");
258-
let col_name = if jstable_path.exists() {
259-
if let Ok(iter) =
260-
jstable::JSTableIterator::new(jstable_path.to_str().unwrap())
261-
{
262-
Some(iter.collection)
263-
} else {
264-
None
265-
}
266-
} else {
267-
// Fallback to directory name (sanitized) if no jstable
268-
entry.file_name().to_str().map(|s| s.to_string())
269-
};
270-
271-
if let Some(name) = col_name {
272-
let collection = Collection::new(
273-
name.clone(),
274-
dir_path,
275-
memtable_threshold,
276-
jstable_threshold,
277-
);
278-
collections.insert(name, collection);
279-
}
280-
}
281-
}
282-
}
283-
}
284-
285248
DB {
286249
root_dir: PathBuf::from(root_dir),
287-
collections,
250+
collections: HashMap::new(),
288251
memtable_threshold,
289252
jstable_threshold,
290253
}
291254
}
292255

293-
fn get_collection(&mut self, name: &str) -> &mut Collection {
294-
self.collections.entry(name.to_string()).or_insert_with(|| {
295-
let safe_name = sanitize_filename(name);
296-
let col_dir = self.root_dir.join(safe_name);
297-
Collection::new(
298-
name.to_string(),
299-
col_dir,
300-
self.memtable_threshold,
301-
self.jstable_threshold,
302-
)
303-
})
256+
fn get_collection_mut(&mut self, name: &str) -> Result<&mut Collection, String> {
257+
self.collections
258+
.get_mut(name)
259+
.ok_or_else(|| format!("Collection '{}' not found", name))
304260
}
305261

306-
pub fn insert(&mut self, collection: &str, doc: Value) -> String {
307-
self.get_collection(collection).insert(doc)
262+
fn get_collection(&self, name: &str) -> Result<&Collection, String> {
263+
self.collections
264+
.get(name)
265+
.ok_or_else(|| format!("Collection '{}' not found", name))
308266
}
309267

310-
pub fn delete(&mut self, collection: &str, id: &str) {
311-
self.get_collection(collection).delete(id);
312-
}
313-
314-
pub fn update(&mut self, collection: &str, id: &str, doc: Value) {
315-
self.get_collection(collection).update(id, doc);
268+
pub fn create_collection(&mut self, name: &str) -> Result<(), String> {
269+
if self.collections.contains_key(name) {
270+
return Err(format!("Collection '{}' already exists", name));
271+
}
272+
let safe_name = sanitize_filename(name);
273+
let col_dir = self.root_dir.join(safe_name);
274+
let collection = Collection::new(
275+
name.to_string(),
276+
col_dir,
277+
self.memtable_threshold,
278+
self.jstable_threshold,
279+
);
280+
self.collections.insert(name.to_string(), collection);
281+
Ok(())
316282
}
317283

318-
pub fn scan(&self, collection: &str) -> Box<dyn Iterator<Item = (String, Value)> + '_> {
319-
if let Some(col) = self.collections.get(collection) {
320-
Box::new(col.scan())
284+
pub fn drop_collection(&mut self, name: &str) -> Result<(), String> {
285+
if let Some(collection) = self.collections.remove(name) {
286+
fs::remove_dir_all(collection.dir).map_err(|e| e.to_string())
321287
} else {
322-
Box::new(std::iter::empty())
288+
Err(format!("Collection '{}' not found", name))
323289
}
324290
}
291+
292+
pub fn show_collections(&self) -> Vec<String> {
293+
self.collections.keys().cloned().collect()
294+
}
295+
296+
pub fn insert(&mut self, collection: &str, doc: Value) -> Result<String, String> {
297+
self.get_collection_mut(collection).map(|c| c.insert(doc))
298+
}
299+
300+
pub fn delete(&mut self, collection: &str, id: &str) -> Result<(), String> {
301+
self.get_collection_mut(collection).map(|c| c.delete(id))
302+
}
303+
304+
pub fn update(&mut self, collection: &str, id: &str, doc: Value) -> Result<(), String> {
305+
self.get_collection_mut(collection)
306+
.map(|c| c.update(id, doc))
307+
}
308+
309+
pub fn scan(
310+
&self,
311+
collection: &str,
312+
) -> Result<Box<dyn Iterator<Item = (String, Value)> + '_>, String> {
313+
self.get_collection(collection)
314+
.map(|c| Box::new(c.scan()) as Box<dyn Iterator<Item = (String, Value)> + '_>)
315+
}
325316
}
326317

327318
#[cfg(test)]
@@ -341,15 +332,16 @@ mod tests {
341332
MEMTABLE_THRESHOLD,
342333
JSTABLE_THRESHOLD,
343334
);
335+
db.create_collection("test").unwrap();
344336

345337
for i in 0..MEMTABLE_THRESHOLD {
346-
db.insert("test", json!({ "a": i }));
338+
db.insert("test", json!({ "a": i })).unwrap();
347339
}
348340
let col = db.collections.get("test").unwrap();
349341
assert_eq!(col.memtable.len(), MEMTABLE_THRESHOLD);
350342
assert_eq!(col.jstable_count, 0);
351343

352-
db.insert("test", json!({"a": MEMTABLE_THRESHOLD}));
344+
db.insert("test", json!({"a": MEMTABLE_THRESHOLD})).unwrap();
353345
let col = db.collections.get("test").unwrap();
354346
assert_eq!(col.memtable.len(), 1);
355347
assert_eq!(col.jstable_count, 1);
@@ -368,13 +360,14 @@ mod tests {
368360
MEMTABLE_THRESHOLD,
369361
JSTABLE_THRESHOLD,
370362
);
363+
db.create_collection("test").unwrap();
371364
let doc1 = json!({"a": 1});
372-
let id1 = db.insert("test", doc1.clone());
365+
let id1 = db.insert("test", doc1.clone()).unwrap();
373366

374367
let doc2 = json!({"b": "hello"});
375-
db.update("test", &id1, doc2.clone());
368+
db.update("test", &id1, doc2.clone()).unwrap();
376369

377-
db.delete("test", &id1);
370+
db.delete("test", &id1).unwrap();
378371

379372
let col = db.collections.get("test").unwrap();
380373
let log_path = col.dir.join("argus.log");
@@ -414,20 +407,22 @@ mod tests {
414407
MEMTABLE_THRESHOLD,
415408
JSTABLE_THRESHOLD,
416409
);
410+
db.create_collection("test").unwrap();
417411
let doc1 = json!({"a": 1});
418-
let id1 = db.insert("test", doc1.clone());
412+
let id1 = db.insert("test", doc1.clone()).unwrap();
419413

420414
let doc2 = json!({"b": "hello"});
421-
let id2 = db.insert("test", doc2.clone());
415+
let id2 = db.insert("test", doc2.clone()).unwrap();
422416

423-
db.delete("test", &id1);
417+
db.delete("test", &id1).unwrap();
424418

425419
// Recover by creating new DB instance pointed to same dir
426-
let db2 = DB::new(
420+
let mut db2 = DB::new(
427421
dir.path().to_str().unwrap(),
428422
MEMTABLE_THRESHOLD,
429423
JSTABLE_THRESHOLD,
430424
);
425+
db2.create_collection("test").unwrap();
431426
// "test" should be loaded if it persisted JSTable or fallback to dir name
432427
let col = db2.collections.get("test").unwrap();
433428

@@ -444,14 +439,15 @@ mod tests {
444439
MEMTABLE_THRESHOLD,
445440
JSTABLE_THRESHOLD,
446441
);
442+
db.create_collection("test").unwrap();
447443

448444
for i in 0..(MEMTABLE_THRESHOLD * JSTABLE_THRESHOLD as usize) {
449-
db.insert("test", json!({ "a": i }));
445+
db.insert("test", json!({ "a": i })).unwrap();
450446
}
451447

452448
let col = db.collections.get("test").unwrap();
453449
assert_eq!(col.jstable_count, JSTABLE_THRESHOLD - 1);
454-
db.insert("test", json!({ "a": 999 }));
450+
db.insert("test", json!({ "a": 999 })).unwrap();
455451

456452
let col = db.collections.get("test").unwrap();
457453
assert_eq!(col.jstable_count, 1);
@@ -465,32 +461,34 @@ mod tests {
465461
MEMTABLE_THRESHOLD,
466462
JSTABLE_THRESHOLD,
467463
);
464+
db.create_collection("test").unwrap();
468465

469-
let id_to_delete = db.insert("test", json!({ "a": 100 }));
466+
let id_to_delete = db.insert("test", json!({ "a": 100 })).unwrap();
470467

471468
for i in 0..9 {
472-
db.insert("test", json!({ "fill": i }));
469+
db.insert("test", json!({ "fill": i })).unwrap();
473470
}
474-
db.insert("test", json!({ "trigger_1": 1 }));
471+
db.insert("test", json!({ "trigger_1": 1 })).unwrap();
475472

476473
let col = db.collections.get("test").unwrap();
477474
assert_eq!(col.jstable_count, 1);
478475

479-
db.delete("test", &id_to_delete);
476+
db.delete("test", &id_to_delete).unwrap();
480477

481478
for i in 0..8 {
482-
db.insert("test", json!({ "fill_2": i }));
479+
db.insert("test", json!({ "fill_2": i })).unwrap();
483480
}
484-
db.insert("test", json!({ "trigger_2": 1 }));
481+
db.insert("test", json!({ "trigger_2": 1 })).unwrap();
485482

486483
let col = db.collections.get("test").unwrap();
487484
assert_eq!(col.jstable_count, 2);
488485

489486
for t in 0..3 {
490487
for i in 0..9 {
491-
db.insert("test", json!({ "fill_more": t, "i": i }));
488+
db.insert("test", json!({ "fill_more": t, "i": i }))
489+
.unwrap();
492490
}
493-
db.insert("test", json!({ "trigger_more": t }));
491+
db.insert("test", json!({ "trigger_more": t })).unwrap();
494492
}
495493

496494
let col = db.collections.get("test").unwrap();
@@ -510,13 +508,14 @@ mod tests {
510508
MEMTABLE_THRESHOLD,
511509
JSTABLE_THRESHOLD,
512510
);
511+
db.create_collection("test").unwrap();
513512

514513
for i in 0..MEMTABLE_THRESHOLD {
515-
db.insert("test", json!({"val": i}));
514+
db.insert("test", json!({"val": i})).unwrap();
516515
}
517-
db.insert("test", json!({"val": 10}));
516+
db.insert("test", json!({"val": 10})).unwrap();
518517

519-
let results: HashMap<String, Value> = db.scan("test").collect();
518+
let results: HashMap<String, Value> = db.scan("test").unwrap().collect();
520519
assert_eq!(results.len(), 11);
521520
}
522521

@@ -526,4 +525,83 @@ mod tests {
526525
assert_eq!(sanitize_filename("foo/bar"), "foo_2fbar");
527526
assert_eq!(sanitize_filename("test.1"), "test_2e1");
528527
}
528+
529+
#[test]
530+
fn test_create_collection() {
531+
let dir = tempdir().unwrap();
532+
let mut db = DB::new(
533+
dir.path().to_str().unwrap(),
534+
MEMTABLE_THRESHOLD,
535+
JSTABLE_THRESHOLD,
536+
);
537+
db.create_collection("test").unwrap();
538+
assert!(db.collections.contains_key("test"));
539+
}
540+
541+
#[test]
542+
fn test_create_collection_already_exists() {
543+
let dir = tempdir().unwrap();
544+
let mut db = DB::new(
545+
dir.path().to_str().unwrap(),
546+
MEMTABLE_THRESHOLD,
547+
JSTABLE_THRESHOLD,
548+
);
549+
db.create_collection("test").unwrap();
550+
let res = db.create_collection("test");
551+
assert!(res.is_err());
552+
}
553+
554+
#[test]
555+
fn test_drop_collection() {
556+
let dir = tempdir().unwrap();
557+
let mut db = DB::new(
558+
dir.path().to_str().unwrap(),
559+
MEMTABLE_THRESHOLD,
560+
JSTABLE_THRESHOLD,
561+
);
562+
db.create_collection("test").unwrap();
563+
assert!(db.collections.contains_key("test"));
564+
db.drop_collection("test").unwrap();
565+
assert!(!db.collections.contains_key("test"));
566+
}
567+
568+
#[test]
569+
fn test_drop_collection_not_found() {
570+
let dir = tempdir().unwrap();
571+
let mut db = DB::new(
572+
dir.path().to_str().unwrap(),
573+
MEMTABLE_THRESHOLD,
574+
JSTABLE_THRESHOLD,
575+
);
576+
let res = db.drop_collection("test");
577+
assert!(res.is_err());
578+
}
579+
580+
#[test]
581+
fn test_show_collections() {
582+
let dir = tempdir().unwrap();
583+
let mut db = DB::new(
584+
dir.path().to_str().unwrap(),
585+
MEMTABLE_THRESHOLD,
586+
JSTABLE_THRESHOLD,
587+
);
588+
db.create_collection("test1").unwrap();
589+
db.create_collection("test2").unwrap();
590+
let collections = db.show_collections();
591+
assert_eq!(collections.len(), 2);
592+
assert!(collections.contains(&"test1".to_string()));
593+
assert!(collections.contains(&"test2".to_string()));
594+
}
595+
596+
#[test]
597+
fn test_insert_into_non_existent_collection() {
598+
let dir = tempdir().unwrap();
599+
let mut db = DB::new(
600+
dir.path().to_str().unwrap(),
601+
MEMTABLE_THRESHOLD,
602+
JSTABLE_THRESHOLD,
603+
);
604+
let res = db.insert("test", json!({ "a": 1 }));
605+
assert!(res.is_err());
606+
}
529607
}

0 commit comments

Comments
 (0)