From 7dced71f04219613073f30e37d12733ba24edac5 Mon Sep 17 00:00:00 2001 From: "Ruokun (Tommy) Niu" Date: Tue, 9 Apr 2024 14:05:15 -0700 Subject: [PATCH 1/3] Feature: Enable query state API (#116) * init work Signed-off-by: Ruokun Niu * added examples Signed-off-by: Ruokun Niu * completed examples Signed-off-by: Ruokun Niu * wip Signed-off-by: Ruokun Niu * wip Signed-off-by: Ruokun Niu * updated dataset Signed-off-by: Ruokun Niu * add: validation of examples and cleanup Signed-off-by: mikeee * chore: tidy Signed-off-by: mikeee * chore: add query_state validation to the workflow Signed-off-by: mikeee * fix: examples Signed-off-by: mikeee --------- Signed-off-by: Ruokun Niu Signed-off-by: Mike Nguyen Signed-off-by: mikeee Co-authored-by: Mike Nguyen --- .github/workflows/validate-examples.yml | 2 +- Cargo.toml | 8 ++ examples/pubsub/subscriber.rs | 4 +- examples/query_state/README.md | 133 +++++++++++++++++++ examples/query_state/query1.rs | 49 +++++++ examples/query_state/query2.rs | 43 ++++++ examples/query_state/statestore/dataset.json | 112 ++++++++++++++++ examples/query_state/statestore/mongodb.yml | 10 ++ src/client.rs | 50 +++++++ 9 files changed, 408 insertions(+), 3 deletions(-) create mode 100644 examples/query_state/README.md create mode 100644 examples/query_state/query1.rs create mode 100644 examples/query_state/query2.rs create mode 100644 examples/query_state/statestore/dataset.json create mode 100644 examples/query_state/statestore/mongodb.yml diff --git a/.github/workflows/validate-examples.yml b/.github/workflows/validate-examples.yml index e6e461ff..1e278c6a 100644 --- a/.github/workflows/validate-examples.yml +++ b/.github/workflows/validate-examples.yml @@ -144,7 +144,7 @@ jobs: fail-fast: false matrix: examples: - [ "actors", "client", "configuration", "crypto", "invoke/grpc", "invoke/grpc-proxying", "pubsub", "secrets-bulk" ] + [ "actors", "client", "configuration", "crypto", "invoke/grpc", "invoke/grpc-proxying", "pubsub", "query_state", "secrets-bulk" ] steps: - name: Check out code uses: actions/checkout@v4 diff --git a/Cargo.toml b/Cargo.toml index c871b3e7..adb21d55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,14 @@ path = "examples/pubsub/publisher.rs" name = "subscriber" path = "examples/pubsub/subscriber.rs" +[[example]] +name = "query_state_q1" +path = "examples/query_state/query1.rs" + +[[example]] +name = "query_state_q2" +path = "examples/query_state/query2.rs" + [[example]] name = "secrets-bulk" path = "examples/secrets-bulk/app.rs" diff --git a/examples/pubsub/subscriber.rs b/examples/pubsub/subscriber.rs index 57c30324..a149e5c5 100644 --- a/examples/pubsub/subscriber.rs +++ b/examples/pubsub/subscriber.rs @@ -35,9 +35,9 @@ async fn main() -> Result<(), Box> { let mut callback_service = AppCallbackService::new(); - callback_service.add_handler(HandleAEvent::default().get_handler()); + callback_service.add_handler(HandleAEvent.get_handler()); - callback_service.add_handler(HandleBEvent::default().get_handler()); + callback_service.add_handler(HandleBEvent.get_handler()); println!("AppCallback server listening on: {}", addr); diff --git a/examples/query_state/README.md b/examples/query_state/README.md new file mode 100644 index 00000000..a7348de5 --- /dev/null +++ b/examples/query_state/README.md @@ -0,0 +1,133 @@ +# Query state Example +To run this example, the default local redis state store will not work as it does not support redis-json server. You will encounter the following error +``` + GrpcError(GrpcError { _status: Status { code: Internal, message: "failed query in state store statestore: redis-json server support is required for query capability", metadata: MetadataMap { headers: {"content-type": "application/grpc", "grpc-trace-bin": "AABniqIo9TrSF6TepfB0yzgNAZzAwpG45zK0AgE"} }, source: None } }) +``` + +See [Querying JSON objects(optional)](https://docs.dapr.io/reference/components-reference/supported-state-stores/setup-redis/#querying-json-objects-optional) for creation of a redis instance that supports querying json objects. + +For this example, we will be following the query state example in the [Dapr docs](https://docs.dapr.io/developing-applications/building-blocks/state-management/howto-state-query-api/#example-data-and-query) and will be using mongo instead. + +To setup MongoDB, execute the following command: + +```bash +docker run -d --rm -p 27017:27017 --name mongodb mongo:5 +``` + + +You can then apply the statestore configuration using the `statestore/mongodb.yaml` file. + +Then, execute the following commands to populate the state data in the statestore: + + +```bash +dapr run --app-id demo --dapr-http-port 3500 --resources-path statestore/ +``` + + +In a new terminal, apply the test data: + + +```bash +curl -X POST -H "Content-Type: application/json" http://localhost:3500/v1.0/state/statestore -d @./statestore/dataset.json +`````` + + +1. To run the example we need to first build the examples using the following command: + +```bash +cargo build --examples +``` + +2. Executing the first query +Query: +```json +{ + "filter": { + "EQ": { "state": "CA" } + }, + "sort": [ + { + "key": "person.id", + "order": "DESC" + } + ] +} + +``` +Execute the first state query using the following command: + + +```bash +dapr run --app-id=rustapp --dapr-grpc-port 3501 --resources-path statestore/ cargo run -- --example query_state_q1 +``` + + +Expected result: +``` +Query results: [Object {"id": String("3"), "value": String("{\"city\":\"Sacramento\",\"state\":\"CA\",\"person\":{\"org\":\"Finance\",\"id\":1071.0}}")}, +Object {"id": String("7"), "value": String("{\"person\":{\"org\":\"Dev Ops\",\"id\":1015.0},\"city\":\"San Francisco\",\"state\":\"CA\"}")}, +Object {"id": String("5"), "value": String("{\"person\":{\"org\":\"Hardware\",\"id\":1007.0},\"city\":\"Los Angeles\",\"state\":\"CA\"}")}, + Object {"id": String("9"), "value": String("{\"person\":{\"org\":\"Finance\",\"id\":1002.0},\"city\":\"San Diego\",\"state\":\"CA\"}")}] +``` + +3. Executing the second query +Query: +```json +{ + "filter": { + "IN": { "person.org": [ "Dev Ops", "Hardware" ] } + } +} +``` +Execute the second state query using the following command: + + +```bash +dapr run --app-id=rustapp --dapr-grpc-port 3501 --resources-path statestore/ cargo run -- --example query_state_q2 +``` + + +Expected result: +``` +Query results: [Object {"id": String("5"), "value": String("{\"person\":{\"org\":\"Hardware\",\"id\":1007.0},\"city\":\"Los Angeles\",\"state\":\"CA\"}")}, +Object {"id": String("2"), "value": String("{\"person\":{\"id\":1028.0,\"org\":\"Hardware\"},\"city\":\"Portland\",\"state\":\"OR\"}")}, +Object {"id": String("4"), "value": String("{\"person\":{\"org\":\"Dev Ops\",\"id\":1042.0},\"city\":\"Spokane\",\"state\":\"WA\"}")}, +Object {"id": String("7"), "value": String("{\"person\":{\"org\":\"Dev Ops\",\"id\":1015.0},\"city\":\"San Francisco\",\"state\":\"CA\"}")}, +Object {"id": String("8"), "value": String("{\"city\":\"Redmond\",\"state\":\"WA\",\"person\":{\"id\":1077.0,\"org\":\"Hardware\"}}")}, +Object {"id": String("10"), "value": String("{\"person\":{\"org\":\"Dev Ops\",\"id\":1054.0},\"city\":\"New York\",\"state\":\"NY\"}")}, +Object {"id": String("1"), "value": String("{\"person\":{\"org\":\"Dev Ops\",\"id\":1036.0},\"city\":\"Seattle\",\"state\":\"WA\"}")}] +``` diff --git a/examples/query_state/query1.rs b/examples/query_state/query1.rs new file mode 100644 index 00000000..dc6859f8 --- /dev/null +++ b/examples/query_state/query1.rs @@ -0,0 +1,49 @@ +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Introduce delay so that dapr grpc port is assigned before app tries to connect + std::thread::sleep(std::time::Duration::new(5, 0)); + + // Set the Dapr address and create a connection + let addr = "https://127.0.0.1".to_string(); + + // Create the client + let mut client = dapr::Client::::connect(addr).await?; + + let query_condition = json!({ + "filter": { + "EQ": { "state": "CA" } + }, + "sort": [ + { + "key": "person.id", + "order": "DESC" + } + ] + }); + + let response = match client + .query_state_alpha1("statestore", query_condition, None) + .await + { + Ok(response) => response.results, + Err(e) => { + println!("Error: {:?}", e); + return Ok(()); + } + }; + + let mut results = Vec::new(); + for item in response { + let value = String::from_utf8(item.data).unwrap(); + //push id and value as json + results.push(json!({ + "id": item.key, + "value": value + })); + } + println!("Query results: {:?}", results); + + Ok(()) +} diff --git a/examples/query_state/query2.rs b/examples/query_state/query2.rs new file mode 100644 index 00000000..e8e0c7cb --- /dev/null +++ b/examples/query_state/query2.rs @@ -0,0 +1,43 @@ +use serde_json::json; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Introduce delay so that dapr grpc port is assigned before app tries to connect + std::thread::sleep(std::time::Duration::new(5, 0)); + + // Set the Dapr address and create a connection + let addr = "https://127.0.0.1".to_string(); + + // Create the client + let mut client = dapr::Client::::connect(addr).await?; + + let query_condition = json!({ + "filter": { + "IN": { "person.org": [ "Dev Ops", "Hardware" ] } + }, + }); + + let response = match client + .query_state_alpha1("statestore", query_condition, None) + .await + { + Ok(response) => response.results, + Err(e) => { + println!("Error: {:?}", e); + return Ok(()); + } + }; + + let mut results = Vec::new(); + for item in response { + let value = String::from_utf8(item.data).unwrap(); + //push id and value as json + results.push(json!({ + "id": item.key, + "value": value + })); + } + println!("Query results: {:?}", results); + + Ok(()) +} diff --git a/examples/query_state/statestore/dataset.json b/examples/query_state/statestore/dataset.json new file mode 100644 index 00000000..6ae17014 --- /dev/null +++ b/examples/query_state/statestore/dataset.json @@ -0,0 +1,112 @@ +[ + { + "key": "1", + "value": { + "person": { + "org": "Dev Ops", + "id": 1036 + }, + "city": "Seattle", + "state": "WA" + } + }, + { + "key": "2", + "value": { + "person": { + "org": "Hardware", + "id": 1028 + }, + "city": "Portland", + "state": "OR" + } + }, + { + "key": "3", + "value": { + "person": { + "org": "Finance", + "id": 1071 + }, + "city": "Sacramento", + "state": "CA" + } + }, + { + "key": "4", + "value": { + "person": { + "org": "Dev Ops", + "id": 1042 + }, + "city": "Spokane", + "state": "WA" + } + }, + { + "key": "5", + "value": { + "person": { + "org": "Hardware", + "id": 1007 + }, + "city": "Los Angeles", + "state": "CA" + } + }, + { + "key": "6", + "value": { + "person": { + "org": "Finance", + "id": 1094 + }, + "city": "Eugene", + "state": "OR" + } + }, + { + "key": "7", + "value": { + "person": { + "org": "Dev Ops", + "id": 1015 + }, + "city": "San Francisco", + "state": "CA" + } + }, + { + "key": "8", + "value": { + "person": { + "org": "Hardware", + "id": 1077 + }, + "city": "Redmond", + "state": "WA" + } + }, + { + "key": "9", + "value": { + "person": { + "org": "Finance", + "id": 1002 + }, + "city": "San Diego", + "state": "CA" + } + }, + { + "key": "10", + "value": { + "person": { + "org": "Dev Ops", + "id": 1054 + }, + "city": "New York", + "state": "NY" + } + } +] \ No newline at end of file diff --git a/examples/query_state/statestore/mongodb.yml b/examples/query_state/statestore/mongodb.yml new file mode 100644 index 00000000..82c96d18 --- /dev/null +++ b/examples/query_state/statestore/mongodb.yml @@ -0,0 +1,10 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.mongodb + version: v1 + metadata: + - name: host + value: localhost:27017 diff --git a/src/client.rs b/src/client.rs index e9c62628..b58d08f9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,4 @@ +use serde_json::Value; use std::collections::HashMap; use async_trait::async_trait; @@ -206,6 +207,35 @@ impl Client { .await } + /// Query state objects based on specific query conditions + /// + /// # Arguments + /// + /// * `store_name` - The name of state store. + /// * `query` - The query request (json) + pub async fn query_state_alpha1( + &mut self, + store_name: S, + query: Value, + metadata: Option>, + ) -> Result + where + S: Into, + { + let mut mdata = HashMap::::new(); + if let Some(m) = metadata { + mdata = m; + } + + self.0 + .query_state_alpha1(QueryStateRequest { + store_name: store_name.into(), + query: serde_json::to_string(&query).unwrap(), + metadata: mdata, + }) + .await + } + /// Delete an array of state objects. /// /// # Arguments @@ -480,6 +510,10 @@ pub trait DaprInterface: Sized { ) -> Result; async fn get_state(&mut self, request: GetStateRequest) -> Result; async fn save_state(&mut self, request: SaveStateRequest) -> Result<(), Error>; + async fn query_state_alpha1( + &mut self, + request: QueryStateRequest, + ) -> Result; async fn delete_state(&mut self, request: DeleteStateRequest) -> Result<(), Error>; async fn delete_bulk_state(&mut self, request: DeleteBulkStateRequest) -> Result<(), Error>; async fn set_metadata(&mut self, request: SetMetadataRequest) -> Result<(), Error>; @@ -558,6 +592,16 @@ impl DaprInterface for dapr_v1::dapr_client::DaprClient { Ok(self.get_state(Request::new(request)).await?.into_inner()) } + async fn query_state_alpha1( + &mut self, + request: QueryStateRequest, + ) -> Result { + Ok(self + .query_state_alpha1(Request::new(request)) + .await? + .into_inner()) + } + async fn save_state(&mut self, request: SaveStateRequest) -> Result<(), Error> { self.save_state(Request::new(request)).await?.into_inner(); Ok(()) @@ -691,6 +735,12 @@ pub type GetStateResponse = dapr_v1::GetStateResponse; /// A request for saving state pub type SaveStateRequest = dapr_v1::SaveStateRequest; +/// A request for querying state +pub type QueryStateRequest = dapr_v1::QueryStateRequest; + +/// A response from querying state +pub type QueryStateResponse = dapr_v1::QueryStateResponse; + /// A request for deleting state pub type DeleteStateRequest = dapr_v1::DeleteStateRequest; From 223fadac5b51f1d212cad6b0568e8d0d0e1b7799 Mon Sep 17 00:00:00 2001 From: "Kent (Chia-Hao), Hsu" Date: Thu, 11 Apr 2024 15:10:54 +0800 Subject: [PATCH 2/3] update holopin badge (#165) Signed-off-by: KentHsu --- .github/holopin.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/holopin.yml b/.github/holopin.yml index 44a7f0c8..7cb490cf 100644 --- a/.github/holopin.yml +++ b/.github/holopin.yml @@ -1,6 +1,6 @@ organization: dapr -defaultSticker: clmjkxscc122740fl0mkmb7egi +defaultSticker: clrqfdv4x24910fl5n4iwu5oa stickers: - - id: clmjkxscc122740fl0mkmb7egi - alias: ghc2023 + id: clrqfdv4x24910fl5n4iwu5oa + alias: sdk-badge From 396f571d966c0143f7475e0ccfbbb5a3ad2177bc Mon Sep 17 00:00:00 2001 From: Mike Nguyen Date: Thu, 11 Apr 2024 09:20:28 +0100 Subject: [PATCH 3/3] fix: holopin formatting (#166) Signed-off-by: Mike Nguyen --- .github/holopin.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/holopin.yml b/.github/holopin.yml index 7cb490cf..9493a22f 100644 --- a/.github/holopin.yml +++ b/.github/holopin.yml @@ -1,6 +1,5 @@ organization: dapr defaultSticker: clrqfdv4x24910fl5n4iwu5oa stickers: - - - id: clrqfdv4x24910fl5n4iwu5oa + - id: clrqfdv4x24910fl5n4iwu5oa alias: sdk-badge