Repository: PoloDB/PoloDB (Rust)

Issue:
Query on Nested Document
While it is possible to insert a document containing nested documents into a collection, it doesn't appear possible to use them in queries. Meaning it's impossible to filter documents by a nested value. I initially encountered this when trying to filter by an enum containing variants with fields (as Bson serializes them as nested documents).

Here I have written a minimal example of what I mean, using just a struct inside another struct:
```Rust
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Example {
    inner: Inner,
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Inner {
    data: String,
}

let example = Example { inner: Inner { data: "foobar".into() } };

let db = Database::open_memory().unwrap();
let col: Collection<Example> = db.collection("test");

col.insert_one(&example).unwrap();

let found1 = col.find_one(None).unwrap().unwrap();
assert_eq!(example, found1);

// create query document using the data we just read from the database
let query = polodb_core::bson::to_document(&found1).unwrap();

let found2 = col.find_one(query).unwrap(); // panics, find_one() returns Err
assert_eq!(Some(found1), found2);
```
For reference, the Bson-serialized `example` data looks like this:
```Bson
Document({
    "inner": Document({
        "data": String("foobar")
    })
})
```
The error returned from the last `.find_one()` operation:
```
InvalidField(InvalidFieldStruct { field_type: "query", field_name: "data", path: Some("/inner/data") })
```
_MongoDB reference [Query on Embedded/Nested Documents](https://www.mongodb.com/docs/manual/tutorial/query-embedded-documents/)_

---
Additionally, if I instead use mongodb's [dot notation](https://www.mongodb.com/docs/manual/core/document/#dot-notation) for the query:
```Rust
let query = doc! {
    "inner.data": "foobar"
};
let found2 = col.find_one(query).unwrap();
assert_eq!(Some(found1), found2); // panics, found2 is None
```
This `find_one()` operation no longer returns an error, but it also doesn't return any data.



Discussion:
I did figure out a temporary workaround though.. You can retrieve the entire collection as a `Vec<Document>`, manually filter, and get the document(s)'s ObjectId. Then use that to perform whatever update/delete operations you want. Not ideal, but it works.
```Rust
let matches: Vec<(ObjectId, Example)> = db.collection::<Document>("test")
    .find_many(None)
    .unwrap()
    .into_iter()
    .filter_map(|doc| {
        let id = doc.get_object_id("_id").unwrap();
        
        from_document::<Example>(doc)
            .ok()
            .map(|entry| (id, entry))
    })
    .filter(|(_, entry)| entry.inner.data == "foobar") // example filtering with nested data
    .collect();

let (id, example) = matches[0].clone();
assert_eq!(
    Some(example),
    db.collection::<Example>("test")
        .find_one(doc! {
            "_id": id
        }).unwrap()
);
```
Sure, nested queries should be supported. I will start thinking about how to implement them.
`4.3.1` supports querying embedded documents now. Try it if you are still interested.

The `.` form is the right way to use it:

```rust
let query = doc! {
    "inner.data": "foobar"
};
```



Patch:
diff --git a/src/polodb_core/vm/subprogram.rs b/src/polodb_core/vm/subprogram.rs
index fd896355..55f5563a 100644
--- a/src/polodb_core/vm/subprogram.rs
+++ b/src/polodb_core/vm/subprogram.rs
@@ -582,6 +582,56 @@ mod tests {
         assert_eq!(expect, actual)
     }
 
+    #[test]
+    fn print_query_embedded_document() {
+        let query_doc = doc! {
+            "info.color": "yellow",
+        };
+        let col_spec = new_spec("test");
+        let program = SubProgram::compile_query(&col_spec, &query_doc, false).unwrap();
+        let actual = format!("Program:\n\n{}", program);
+
+        let expect = r#"Program:
+
+0: OpenRead("test")
+5: Rewind(30)
+10: Goto(73)
+
+15: Label(1)
+20: Next(73)
+
+25: Label(5, "Close")
+30: Close
+31: Halt
+
+32: Label(4, "Not this item")
+37: RecoverStackPos
+38: Pop
+39: Goto(20)
+
+44: Label(3, "Get field failed")
+49: RecoverStackPos
+50: Pop
+51: Goto(20)
+
+56: Label(2, "Result")
+61: ResultRow
+62: Pop
+63: Goto(20)
+
+68: Label(0, "Compare")
+73: SaveStackPos
+74: GetField("info.color", 49)
+83: PushValue("yellow")
+88: Equal
+89: FalseJump(37)
+94: Pop
+95: Pop
+96: Goto(61)
+"#;
+        assert_eq!(expect, actual)
+    }
+
     #[test]
     fn print_query_by_primary_key() {
         let col_spec = new_spec("test");
diff --git a/src/polodb_core/vm/vm.rs b/src/polodb_core/vm/vm.rs
index dd7a7192..f8c48202 100644
--- a/src/polodb_core/vm/vm.rs
+++ b/src/polodb_core/vm/vm.rs
@@ -582,9 +582,9 @@ impl VM {
                             }
                         };
 
-                        match doc.get(key_name) {
+                        match crate::utils::bson::try_get_document_value(doc, key_name) {
                             Some(val) => {
-                                self.stack.push(val.clone());
+                                self.stack.push(val);
                                 self.pc = self.pc.add(9);
                             }
 


Test patch:
diff --git a/src/polodb_core/tests/test_collection.rs b/src/polodb_core/tests/test_collection.rs
index 4ff7095c..a9605975 100644
--- a/src/polodb_core/tests/test_collection.rs
+++ b/src/polodb_core/tests/test_collection.rs
@@ -134,3 +134,48 @@ fn test_create_collection_and_find_by_pkey() {
         assert_eq!(result.len(), 1);
     });
 }
+
+#[test]
+fn test_query_embedded_document() {
+    vec![
+        create_file_and_return_db_with_items("test-embedded-document", 10),
+        create_memory_and_return_db_with_items(10),
+    ].iter().for_each(|db| {
+        let collection = db.collection::<Document>("test");
+
+        collection.insert_one(doc! {
+            "name": "Apple",
+            "price": 100,
+            "info": {
+                "description": "This is an apple",
+                "color": "red",
+            }
+        }).unwrap();
+
+        collection.insert_one(doc! {
+            "name": "Banana",
+            "price": 200,
+            "info": {
+                "description": "This is a banana",
+                "color": "yellow",
+            }
+        }).unwrap();
+
+        collection.insert_one(doc! {
+            "name": "Orange",
+            "price": 300,
+            "info": {
+                "description": "This is an orange",
+                "color": "orange",
+            }
+        }).unwrap();
+
+        // query a fruit with color yellow
+        let result = collection.find(doc! {
+            "info.color": "yellow",
+        }).unwrap().collect::<Result<Vec<Document>>>().unwrap();
+
+        assert_eq!(result.len(), 1);
+    });
+
+}