Skip to content

Commit

Permalink
Feature: Enable query state API (#116)
Browse files Browse the repository at this point in the history
* init work

Signed-off-by: Ruokun Niu <[email protected]>

* added examples

Signed-off-by: Ruokun Niu <[email protected]>

* completed examples

Signed-off-by: Ruokun Niu <[email protected]>

* wip

Signed-off-by: Ruokun Niu <[email protected]>

* wip

Signed-off-by: Ruokun Niu <[email protected]>

* updated dataset

Signed-off-by: Ruokun Niu <[email protected]>

* add: validation of examples and cleanup

Signed-off-by: mikeee <[email protected]>

* chore: tidy

Signed-off-by: mikeee <[email protected]>

* chore: add query_state validation to the workflow

Signed-off-by: mikeee <[email protected]>

* fix: examples

Signed-off-by: mikeee <[email protected]>

---------

Signed-off-by: Ruokun Niu <[email protected]>
Signed-off-by: Mike Nguyen <[email protected]>
Signed-off-by: mikeee <[email protected]>
Co-authored-by: Mike Nguyen <[email protected]>
  • Loading branch information
ruokun-niu and mikeee authored Apr 9, 2024
1 parent 0beb668 commit 7dced71
Show file tree
Hide file tree
Showing 9 changed files with 408 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/validate-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
4 changes: 2 additions & 2 deletions examples/pubsub/subscriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

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);

Expand Down
133 changes: 133 additions & 0 deletions examples/query_state/README.md
Original file line number Diff line number Diff line change
@@ -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:
<!-- STEP
name: Run mongodb instance
background: false
sleep: 10
timeout_seconds: 30
-->
```bash
docker run -d --rm -p 27017:27017 --name mongodb mongo:5
```
<!-- END_STEP -->

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:

<!-- STEP
name: Populate state store step 1/2
background: true
sleep: 5
timeout_seconds: 10
-->
```bash
dapr run --app-id demo --dapr-http-port 3500 --resources-path statestore/
```
<!-- END_STEP -->

In a new terminal, apply the test data:

<!-- STEP
name: Populate state store step 2/2
background: false
sleep: 2
timeout_seconds: 5
-->
```bash
curl -X POST -H "Content-Type: application/json" http://localhost:3500/v1.0/state/statestore -d @./statestore/dataset.json
``````
<!-- END_STEP -->

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:
<!-- STEP
name: Run query_state_q1 example
output_match_mode: substring
match_order: none
expected_stdout_lines:
- 'San Francisco'
background: false
sleep: 15
timeout_seconds: 30
-->
```bash
dapr run --app-id=rustapp --dapr-grpc-port 3501 --resources-path statestore/ cargo run -- --example query_state_q1
```
<!-- END_STEP -->
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:
<!-- STEP
name: Run query_state_q2 example
output_match_mode: substring
match_order: none
expected_stdout_lines:
- 'New York'
background: false
sleep: 15
timeout_seconds: 30
-->
```bash
dapr run --app-id=rustapp --dapr-grpc-port 3501 --resources-path statestore/ cargo run -- --example query_state_q2
```
<!-- END_STEP -->
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\"}")}]
```
49 changes: 49 additions & 0 deletions examples/query_state/query1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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::<dapr::client::TonicClient>::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(())
}
43 changes: 43 additions & 0 deletions examples/query_state/query2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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::<dapr::client::TonicClient>::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(())
}
112 changes: 112 additions & 0 deletions examples/query_state/statestore/dataset.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
]
10 changes: 10 additions & 0 deletions examples/query_state/statestore/mongodb.yml
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 7dced71

Please sign in to comment.