Repository: dbpunk-labs/db3 (language: rust)

Issue:
Can not use filter to query doc?
## Bug description

<!-- A clear and concise description of what the bug is. -->
 filter  parameter does not work 
- Would you like to work on a fix? [y/n]

## To Reproduce

Steps to reproduce the behavior:


## **Use console client**
**1. Start db3  console client**

`$  ./target/debug/db3 console`

```typescript

██████╗ ██████╗ ██████╗
██╔══██╗██╔══██╗╚════██╗
██║  ██║██████╔╝ █████╔╝
██║  ██║██╔══██╗ ╚═══██╗
██████╔╝██████╔╝██████╔╝
╚═════╝ ╚═════╝ ╚═════╝
@db3.network🚀🚀🚀
db3>-$
```
**2. Create a database**
```shell
db3>-$ new-db
 database address                           | mutation id
--------------------------------------------+----------------------------------------------
 0x59cd014b73b81e36daa90f2dee940709474809ac | au3Q+hMreJe2MGXeIjxtKcUFhwJbPDciVyFOZd2bvgQ=
 ```

```shell
 db3>-$ show-db --addr 0x59cd014b73b81e36daa90f2dee940709474809ac
 # | database address                           | description | sender address                             | related mutations                            | collections
---+--------------------------------------------+-------------+--------------------------------------------+----------------------------------------------+-------------
 1 | 0x59cd014b73b81e36daa90f2dee940709474809ac |             | 0x6922f1bf2cbde237e0bf53dda36186e9ed2e5e08 | au3Q+hMreJe2MGXeIjxtKcUFhwJbPDciVyFOZd2bvgQ= |
 ```

**3. Create a  new collection in the database**
```shell

db3>-$ new-collection  --addr 0x59cd014b73b81e36daa90f2dee940709474809ac --name userinfo  --index '{"id": 1,"name": "idx1","fields": [{"field_path": "name","value_mode": {"Order": 1}},{"field_path": "bio","value_mode": {"Order": 2}}]}'
send add collection done!
 mutation_id
----------------------------------------------
 9Uo1VL1I/sMYHbzY+vHybYMn4Z2KPPrJQ2+sfZvI8sM=
```
```shell
db3>-$ show-collection --addr 0x59cd014b73b81e36daa90f2dee940709474809ac
 name     | index
----------+--------------------------------------------------------------------------------------------------------------------------------
 userinfo | {"name":"idx1","id":0,"fields":[{"field_path":"name","value_mode":{"Order":1}},{"field_path":"bio","value_mode":{"Order":2}}]}
 ```

 **4. Create a  new doc in the collection**
```shell
db3>-$ new-doc  --addr 0x59cd014b73b81e36daa90f2dee940709474809ac --collection-name userinfo   --documents '{"name": "Bob","age": 43,"bio": "a virtual person in Blockchain"}'
send add document done
 mutation id
----------------------------------------------
 ct9xhtOtnrbUj+g6c8cppHaKn9RknQNSF5kx0xxw5NQ=
 ```

```shell
db3>-$ show-doc --addr 0x59cd014b73b81e36daa90f2dee940709474809ac --collection-name userinfo
 id_base64                            | owner                                      | document                                                                                             | mutation_id
--------------------------------------+--------------------------------------------+------------------------------------------------------------------------------------------------------+----------------------------------------------
 AQAAAAAAAFy3AAEAAQAAAAAAAF0bAAEAAA== | 0x6922f1bf2cbde237e0bf53dda36186e9ed2e5e08 | Document({"name": String("Bob"), "age": Int64(43), "bio": String("a virtual person in Blockchain")}) | ct9xhtOtnrbUj+g6c8cppHaKn9RknQNSF5kx0xxw5NQ=
 ```


**5. Query a  doc by  limit**
```shell
db3>-$ show-doc --addr 0x59cd014b73b81e36daa90f2dee940709474809ac --collection-name userinfo  --limit 1
 id_base64                            | owner                                      | document                                                                                             | mutation_id
--------------------------------------+--------------------------------------------+------------------------------------------------------------------------------------------------------+----------------------------------------------
 AQAAAAAAAFy3AAEAAQAAAAAAAF0bAAEAAA== | 0x6922f1bf2cbde237e0bf53dda36186e9ed2e5e08 | Document({"name": String("Bob"), "age": Int64(43), "bio": String("a virtual person in Blockchain")}) | ct9xhtOtnrbUj+g6c8cppHaKn9RknQNSF5kx0xxw5NQ=
 ```
```shell
db3>-$ show-doc --addr 0x59cd014b73b81e36daa90f2dee940709474809ac --collection-name userinfo  --limit 2
 id_base64                            | owner                                      | document                                                                                               | mutation_id
--------------------------------------+--------------------------------------------+--------------------------------------------------------------------------------------------------------+----------------------------------------------
 AQAAAAAAAFy3AAEAAQAAAAAAAF0bAAEAAA== | 0x6922f1bf2cbde237e0bf53dda36186e9ed2e5e08 | Document({"name": String("Bob"), "age": Int64(43), "bio": String("a virtual person in Blockchain")})   | ct9xhtOtnrbUj+g6c8cppHaKn9RknQNSF5kx0xxw5NQ=
 AQAAAAAAAFy3AAEAAQAAAAAAAF8YAAEAAA== | 0x6922f1bf2cbde237e0bf53dda36186e9ed2e5e08 | Document({"name": String("alice"), "age": Int64(43), "bio": String("a virtual person in Blockchain")}) | X1Ml6FfwYm9x2ZWXdLCjWrDDk3YlLaOty/pOz/i/icE=
 ```

**6. Query a  doc by  filter**
```shell
db3>-$ show-doc --addr  0x59cd014b73b81e36daa90f2dee940709474809ac  --collection-name  userinfo --filter '{"field": "name", "value": "Bob", "op": "=="}'
```

**In the Step 6,  i can not query  doc by filter**


<!-- Make sure you are able to reproduce the bug in the main branch, too. -->

## Expected behavior
it can query  doc data
## Screenshots

<!-- A clear and concise description of what you expected to happen. -->
![image](https://user-images.githubusercontent.com/35133768/233599747-f66aa80c-414a-41cc-a6d8-f48d08ae5a57.png)

<!-- If applicable, add screenshots to help explain your problem. -->

## Environment
Mac OS  inter

## Additional context

<!-- Add any other context about the problem here. -->



Discussion / hints:
@jingchen2222 
thx @Rock-liyi 
we currently do not support multi-key index like `"fields":[{"field_path":"name","value_mode":{"Order":1}},{"field_path":"bio","value_mode":{"Order":2}}]}`. 

```
db3>-$ new-collection  --addr 0xe45be847dbc2ab1ec4500a4efe27eaf5f8812adb --name userinfo  --index '{"id": 1,"name": "idx1","fields": [{"field_path": "name","value_mode": {"Order": 1}}]}'
send add collection done!
 mutation_id
----------------------------------------------
 fSO1WJLWf52cq9GVDlx6XG5VCtnqgImqPImb86rmXy0=
db3>-$ show-collection --addr 0xe45be847dbc2ab1ec4500a4efe27eaf5f8812adb
 name     | index
----------+----------------------------------------------------------------------------------
 userinfo | {"name":"idx1","id":0,"fields":[{"field_path":"name","value_mode":{"Order":1}}]}
db3>-$ new-doc  --addr 0xe45be847dbc2ab1ec4500a4efe27eaf5f8812adb --collection-name userinfo   --documents '{"name": "Bob","age": 43,"bio": "a virtual person in Blockchain"}'
send add document done
 mutation id
----------------------------------------------
 F3UOABma1k0ZosJJBoqwbDe3HqdYCc8dCCfZkA0Nxc4=
db3>-$ show-doc --addr  0xe45be847dbc2ab1ec4500a4efe27eaf5f8812adb --collection-name userinfo
filter: None
query: StructuredQuery { select: Some(Projection { fields: [] }), collection_name: "userinfo", r#where: None, limit: None }
 id_base64                            | owner                                      | document                                                                                             | mutation_id
--------------------------------------+--------------------------------------------+------------------------------------------------------------------------------------------------------+----------------------------------------------
 AQAAAAAAAAE2AAEAAQAAAAAAAAFVAAEAAA== | 0x84b0bd55e7ad979b7cb92a56f561190de8f68403 | Document({"name": String("Bob"), "age": Int64(43), "bio": String("a virtual person in Blockchain")}) | F3UOABma1k0ZosJJBoqwbDe3HqdYCc8dCCfZkA0Nxc4=
db3>-$ show-doc --addr  0xe45be847dbc2ab1ec4500a4efe27eaf5f8812adb --collection-name  userinfo --filter '{"field": "name", "value": "Bob", "op": "=="}'
filter: Some(Filter { filter_type: Some(FieldFilter(FieldFilter { field: "name", op: Equal, value: Some(Value { value_type: Some(StringValue("Bob")) }) })) })
query: StructuredQuery { select: Some(Projection { fields: [] }), collection_name: "userinfo", r#where: Some(Filter { filter_type: Some(FieldFilter(FieldFilter { field: "name", op: Equal, value: Some(Value { value_type: Some(StringValue("Bob")) }) })) }), limit: None }
 id_base64                            | owner                                      | document                                                                                             | mutation_id
--------------------------------------+--------------------------------------------+------------------------------------------------------------------------------------------------------+----------------------------------------------
 AQAAAAAAAAE2AAEAAQAAAAAAAAFVAAEAAA== | 0x84b0bd55e7ad979b7cb92a56f561190de8f68403 | Document({"name": String("Bob"), "age": Int64(43), "bio": String("a virtual person in Blockchain")}) | F3UOABma1k0ZosJJBoqwbDe3HqdYCc8dCCfZkA0Nxc4=
```

It's my bad, and I should add logic to disable multi-key index creation from sdk/console.
@imotai @Rock-liyi 
TODO

- [ ] disable multi-key index
- [ ] support multi-key index and support filter with first key equal (e.g `key1=value1`) and combine-key query (e,g `key1=value1 and key2=value2`). https://github.com/dbpunk-labs/db3/issues/423
 



Fix patch:
diff --git a/src/cmd/src/command.rs b/src/cmd/src/command.rs
index f207e562..00597ed6 100644
--- a/src/cmd/src/command.rs
+++ b/src/cmd/src/command.rs
@@ -512,6 +512,12 @@ impl DB3ClientCommand {
                         bson_util::json_str_to_index(index_list[idx].as_str(), idx as u32).unwrap(),
                     );
                 }
+                // we currently only support index filter single field
+                for index in index_vec.iter() {
+                    if index.fields.len() != 1 {
+                        return Err("fail to create collection with multi-key index".to_string());
+                    }
+                }
                 let collection = CollectionMutation {
                     index: index_vec.to_owned(),
                     collection_name: name.to_string(),
diff --git a/src/storage/src/db_store.rs b/src/storage/src/db_store.rs
index 68628522..2d7ad12c 100644
--- a/src/storage/src/db_store.rs
+++ b/src/storage/src/db_store.rs
@@ -168,6 +168,16 @@ impl DbStore {
         );
         let (db, mut ops) =
             Self::new_database(&dbid, sender, tx, mutation, block_id, mutation_id, desc);
+        for c in &db.collections {
+            for index in &c.index_list {
+                if index.fields.len() > 1 {
+                    return Err(DB3Error::ApplyDatabaseError(format!(
+                        "currently not support multi-key index, index fields should be less than 1, but got {}",
+                        index.fields.len()
+                    )));
+                }
+            }
+        }
         let (batches, data_in_bytes) =
             Self::encode_database(dbid, &db, sender, block_id, mutation_id, true)?;
         ops.update_data_size(data_in_bytes as u64);
@@ -1232,6 +1242,43 @@ mod tests {
         println!("{json_data}");
         dm
     }
+    fn build_database_mutation_with_multi_key_index(
+        addr: &DB3Address,
+        collection_name: &str,
+    ) -> DatabaseMutation {
+        let index_field_name = IndexField {
+            field_path: "name".to_string(),
+            value_mode: Some(ValueMode::Order(Order::Ascending as i32)),
+        };
+
+        let index_field_age = IndexField {
+            field_path: "age".to_string(),
+            value_mode: Some(ValueMode::Order(Order::Ascending as i32)),
+        };
+
+        let index_name_age = Index {
+            id: 0,
+            name: "idx1".to_string(),
+            fields: vec![index_field_name, index_field_age],
+        };
+
+        let index_mutation = CollectionMutation {
+            index: vec![index_name_age],
+            collection_name: collection_name.to_string(),
+        };
+
+        let dm = DatabaseMutation {
+            meta: None,
+            collection_mutations: vec![index_mutation],
+            document_mutations: vec![],
+            db_address: addr.to_vec(),
+            action: DatabaseAction::CreateDb.into(),
+            db_desc: "".to_string(),
+        };
+        let json_data = serde_json::to_string(&dm).unwrap();
+        println!("{json_data}");
+        dm
+    }
 
     #[test]
     fn collect_field_index_map_ut() {
@@ -1993,6 +2040,21 @@ mod tests {
             format!("{:?}", res.err().unwrap())
         );
     }
+    #[test]
+    fn db_store_create_database_wrong_path() {
+        let tmp_dir_path = TempDir::new("db_store_create_database_test").expect("create temp dir");
+        let addr = gen_address();
+        let merk = Merk::open(tmp_dir_path).unwrap();
+        let mut db = Box::pin(merk);
+        let collection_name = "db_store_create_database_test".to_string();
+        let db_mutation =
+            build_database_mutation_with_multi_key_index(&addr, collection_name.as_str());
+        let db_m: Pin<&mut Merk> = Pin::as_mut(&mut db);
+
+        // create DB wrong path
+        let result = DbStore::apply_mutation(db_m, &addr, 1, &TxId::zero(), &db_mutation, 1000, 1);
+        assert!(result.is_err());
+    }
 
     #[test]
     fn db_store_smoke_test() {


Test patch: