From 4f8c5f1484443c7ccfbd8234418907be4ad1acd1 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Fri, 4 Sep 2026 13:12:48 +0100 Subject: [PATCH 1/3] DOC-7032: Add tested client examples for JSONPath filter operators Move the illustrative CLI blocks in "Filter expression operators" into runnable clients-example demos under "Filter examples", covering negation, literal comparison, arithmetic, in/nin, subsetof/anyof/noneof, size/empty, and the ~ get-keys operator. New TCE set json_path_ops, verified against a live Redis 8.10 server across all 11 clients that already cover this page. Also fixes two latent example-test-harness gaps found while testing: pom-lettuce-async.xml/pom-lettuce-reactive.xml were pinning a stale lettuce-core and missing jackson-databind (needed for getJsonParser()), and the portable Rust runners had no json/serde_json support at all, so no Rust JSON example had ever been runnable through the harness before. Co-Authored-By: Claude Sonnet 5 --- .../pom-lettuce-async.xml | 3 +- .../pom-lettuce-reactive.xml | 3 +- build/example-test-harness/run.sh | 8 +- content/develop/data-types/json/path.md | 184 ++++--- .../go-redis/json_path_ops_test.go | 444 +++++++++++++++ .../jedis/JsonPathOpsExample.java | 192 +++++++ .../lettuce-async/JsonPathOpsExample.java | 307 +++++++++++ .../lettuce-reactive/JsonPathOpsExample.java | 333 ++++++++++++ .../json_path_ops/node-redis/json-path-ops.js | 179 ++++++ .../nredisstack/JsonPathOpsExample.cs | 222 ++++++++ .../json_path_ops/predis/JsonPathOpsTest.php | 223 ++++++++ .../json_path_ops/redis-py/json_path_ops.py | 201 +++++++ .../json_path_ops/ruby/json_path_ops.rb | 174 ++++++ .../json_path_ops/rust-async/json_path_ops.rs | 453 ++++++++++++++++ .../json_path_ops/rust-sync/json_path_ops.rs | 513 ++++++++++++++++++ 15 files changed, 3351 insertions(+), 88 deletions(-) create mode 100644 local_examples/json_path_ops/go-redis/json_path_ops_test.go create mode 100644 local_examples/json_path_ops/jedis/JsonPathOpsExample.java create mode 100644 local_examples/json_path_ops/lettuce-async/JsonPathOpsExample.java create mode 100644 local_examples/json_path_ops/lettuce-reactive/JsonPathOpsExample.java create mode 100644 local_examples/json_path_ops/node-redis/json-path-ops.js create mode 100644 local_examples/json_path_ops/nredisstack/JsonPathOpsExample.cs create mode 100644 local_examples/json_path_ops/predis/JsonPathOpsTest.php create mode 100644 local_examples/json_path_ops/redis-py/json_path_ops.py create mode 100644 local_examples/json_path_ops/ruby/json_path_ops.rb create mode 100644 local_examples/json_path_ops/rust-async/json_path_ops.rs create mode 100644 local_examples/json_path_ops/rust-sync/json_path_ops.rs diff --git a/build/example-test-harness/pom-lettuce-async.xml b/build/example-test-harness/pom-lettuce-async.xml index b488bebbf5..b887da8f00 100644 --- a/build/example-test-harness/pom-lettuce-async.xml +++ b/build/example-test-harness/pom-lettuce-async.xml @@ -4,7 +4,8 @@ io.redislettuce-async1.0 17UTF-8 - io.lettucelettuce-core6.5.5.RELEASE + io.lettucelettuce-core7.7.0.RELEASE + com.fasterxml.jackson.corejackson-databind2.17.1 org.junit.jupiterjunit-jupiter5.10.2test org.assertjassertj-core3.25.3test diff --git a/build/example-test-harness/pom-lettuce-reactive.xml b/build/example-test-harness/pom-lettuce-reactive.xml index e3f67e7892..b1fb7d1c25 100644 --- a/build/example-test-harness/pom-lettuce-reactive.xml +++ b/build/example-test-harness/pom-lettuce-reactive.xml @@ -4,7 +4,8 @@ io.redislettuce-reactive1.0 17UTF-8 - io.lettucelettuce-core6.5.5.RELEASE + io.lettucelettuce-core7.7.0.RELEASE + com.fasterxml.jackson.corejackson-databind2.17.1 org.junit.jupiterjunit-jupiter5.10.2test org.assertjassertj-core3.25.3test diff --git a/build/example-test-harness/run.sh b/build/example-test-harness/run.sh index 3adf12f665..3c9cd83544 100755 --- a/build/example-test-harness/run.sh +++ b/build/example-test-harness/run.sh @@ -384,10 +384,12 @@ run_go() { cp "$1" "$d/ex_test.go" (cd "$d" && go mod tidy >/dev/null 2>&1 && go test ./... ) >"$LOG" 2>&1; rc=$? } -run_rust_sync() { rust_run "$WORK/rust-sync" "$1" 'redis = "1.3"' ; } -run_rust_async(){ rust_run "$WORK/rust-async" "$1" 'redis = { version = "1.3", features = ["tokio-comp"] } +run_rust_sync() { rust_run "$WORK/rust-sync" "$1" 'redis = { version = "1.3", features = ["json"] } +serde_json = { version = "1", features = ["preserve_order"] }' ; } +run_rust_async(){ rust_run "$WORK/rust-async" "$1" 'redis = { version = "1.3", features = ["tokio-comp", "json"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } -futures-util = "0.3"' ; } +futures-util = "0.3" +serde_json = { version = "1", features = ["preserve_order"] }' ; } rust_run() { local d="$1" src="$2" deps="$3"; mkdir -p "$d/src" if [ ! -f "$d/Cargo.toml" ]; then diff --git a/content/develop/data-types/json/path.md b/content/develop/data-types/json/path.md index 5133f7f136..b79b168068 100644 --- a/content/develop/data-types/json/path.md +++ b/content/develop/data-types/json/path.md @@ -107,64 +107,20 @@ The following operators can be used within a filter expression (`?()`). The `!` operator negates a filter condition. It binds more tightly than the logical `&&` and `||` operators, so use parentheses to negate a compound condition. A double negation (`!!`) is equivalent to an existence test. -``` -> JSON.SET doc $ '[{"a":1,"b":1},{"b":2},{"a":1},{"c":3}]' -OK -> JSON.GET doc '$[?!@.a]' -"[{\"b\":2},{\"c\":3}]" -> JSON.GET doc '$[?!(@.a==1)]' -"[{\"b\":2},{\"c\":3}]" -> JSON.GET doc '$[?!@.a && @.b]' -"[{\"b\":2}]" -``` - ### Comparing array and object literals The `==` and `!=` operators can compare any JSON literal, including array and object literals, on either side of the operator. Comparison is by structural (deep) equality. A comparison between values of different types is always false. -``` -> JSON.SET doc $ '{"arrs":[[1],[2],[1,2],[1,[2]]],"objs":[{"x":1},{"x":2},{"y":1}]}' -OK -> JSON.GET doc '$.arrs[?(@ == [1])]' -"[[1]]" -> JSON.GET doc '$.arrs[?(@ == [1,[2]])]' -"[[1,[2]]]" -> JSON.GET doc '$.objs[?(@ == {"x":1})]' -"[{\"x\":1}]" -``` - ### Arithmetic operators The binary operators `+`, `-`, `*`, `/`, and `%` and the unary operators `-` and `+` operate on numbers. Precedence, from highest to lowest, is: unary `-`/`+`, then `*`/`/`/`%`, then `+`/`-`. Use parentheses to override precedence. Division always produces a floating-point result. Division or modulo by zero produces *Nothing*, so no element matches. Remember that arithmetic operators must be surrounded by spaces (see [Evaluation semantics](#evaluation-semantics)). -``` -> JSON.SET doc $ '[{"a":2,"b":3},{"a":5,"b":2}]' -OK -> JSON.GET doc '$[?@.a + 1 == 3]' -"[{\"a\":2,\"b\":3}]" -> JSON.GET doc '$[?@.a + @.b * 2 == 8]' -"[{\"a\":2,\"b\":3}]" -> JSON.GET doc '$[?(@.a + @.b) * 2 == 10]' -"[{\"a\":2,\"b\":3}]" -``` - ### Membership: `in` and `nin` `value in array` matches when `value` is a member of `array`; `value nin array` is its strict negation. The right-hand side can be an array literal or a path to an array. Membership uses the same structural comparison and number coercion as `==`. If the right-hand side is not an array, `in` is false (so `nin` is true). -``` -> JSON.SET doc $ '{"a":[1,2,3,4],"allow":[2,3]}' -OK -> JSON.GET doc '$.a[?@ in [2,4]]' -"[2,4]" -> JSON.GET doc '$.a[?@ nin [2,4]]' -"[1,3]" -> JSON.GET doc '$.a[?@ in $.allow]' -"[2,3]" -``` - ### Set relations: `subsetof`, `anyof`, and `noneof` These operators compare two arrays (each side can be an array literal or a path to an array): @@ -173,57 +129,20 @@ These operators compare two arrays (each side can be an array literal or a path - `anyof` matches when the two arrays have a non-empty intersection. An empty array never matches. - `noneof` matches when the two arrays have an empty intersection. An empty array always matches. -``` -> JSON.SET doc $ '{"a":[[1,2],[1,5],[]]}' -OK -> JSON.GET doc '$.a[?@ subsetof [1,2,3]]' -"[[1,2],[]]" -> JSON.SET doc $ '{"a":[[1,9],[8,9],[]]}' -OK -> JSON.GET doc '$.a[?@ anyof [1,2,3]]' -"[[1,9]]" -> JSON.SET doc $ '{"a":[[4,5],[1,9],[]]}' -OK -> JSON.GET doc '$.a[?@ noneof [1,2,3]]' -"[[4,5],[]]" -``` - ### `size` (`sizeof`) and `empty` `left sizeof n` matches when the size of `left` equals the integer `n`, where size is the number of characters in a string, the number of elements in an array, or the number of members in an object. A non-integer `n` is truncated toward zero; a non-numeric `n` never matches. `size` is an alias for `sizeof`. `left empty true` matches an empty string, array, or object; `left empty false` matches a non-empty one. -``` -> JSON.SET doc $ '{"a":[[4,5],[1],[7,8,9]]}' -OK -> JSON.GET doc '$.a[?@ sizeof 2]' -"[[4,5]]" -> JSON.SET doc $ '{"a":[[],[1],"",[2,3],{},{"k":1}]}' -OK -> JSON.GET doc '$.a[?@ empty true]' -"[[],\"\",{}]" -> JSON.GET doc '$.a[?@ empty false]' -"[[1],[2,3],{\"k\":1}]" -``` - ### Get-keys operator `~` The terminal `~` operator returns the member names of an object as a flat list of strings. Applied to the root (`$~`), it returns the top-level keys. Applied to a multiple-match receiver, it flattens the keys of every matched object. A non-object receiver contributes no keys. -``` -> JSON.SET doc $ '{"obj":{"x":1,"y":2},"books":[{"t":"a"},{"t":"b"}]}' -OK -> JSON.GET doc '$.obj~' -"[\"x\",\"y\"]" -> JSON.GET doc '$~' -"[\"obj\",\"books\"]" -> JSON.GET doc '$.books~' -"[]" -``` - Because `~` is terminal, expressions such as `$.obj~.x`, `$.obj~~`, and `$.obj.keys()~` are parse errors. For a composable alternative that can be chained with other functions, use the [`keys()`](#keys) function. +See [Filter examples](#filter-examples) for a runnable example of each operator. + ## Functions Functions can appear inside filter expressions and, when they return a value, as top-level [projection expressions](#projection-expressions). A function can be written in prefix form, `length($.arr)`, or in postfix (method) form, `$.arr.length()`. A path segment immediately followed by `(` is a method call, so `$.arr.length()` is a function call while `$.arr.length` is a reference to a field named `length`. @@ -548,6 +467,105 @@ OK See [Filter expression operators](#filter-expression-operators) for more information. +Beginning with Redis 8.10, the JSONPath filter syntax supports additional operators, described in [Filter expression operators](#filter-expression-operators). The following examples demonstrate each one. + +#### Negation + +{{< clients-example set="json_path_ops" step="filter_negation" description="Filter negation: Use the ! operator to invert a filter condition when you need to select elements that do not match" difficulty="advanced" >}} +> JSON.SET doc $ '[{"a":1,"b":1},{"b":2},{"a":1},{"c":3}]' +OK +> JSON.GET doc '$[?!@.a]' +"[{\"b\":2},{\"c\":3}]" +> JSON.GET doc '$[?!(@.a==1)]' +"[{\"b\":2},{\"c\":3}]" +> JSON.GET doc '$[?!@.a && @.b]' +"[{\"b\":2}]" +{{< /clients-example >}} + +#### Comparing array and object literals + +{{< clients-example set="json_path_ops" step="filter_literal_eq" description="Literal comparison: Use == and != to compare array and object literals by structural equality when you need to match a whole nested value" difficulty="advanced" >}} +> JSON.SET doc $ '{"arrs":[[1],[2],[1,2],[1,[2]]],"objs":[{"x":1},{"x":2},{"y":1}]}' +OK +> JSON.GET doc '$.arrs[?(@ == [1])]' +"[[1]]" +> JSON.GET doc '$.arrs[?(@ == [1,[2]])]' +"[[1,[2]]]" +> JSON.GET doc '$.objs[?(@ == {"x":1})]' +"[{\"x\":1}]" +{{< /clients-example >}} + +#### Arithmetic operators + +{{< clients-example set="json_path_ops" step="filter_arithmetic" description="Arithmetic in filters: Use +, -, *, / and % inside a filter condition when you need to compute a value before comparing it" difficulty="advanced" >}} +> JSON.SET doc $ '[{"a":2,"b":3},{"a":5,"b":2}]' +OK +> JSON.GET doc '$[?@.a + 1 == 3]' +"[{\"a\":2,\"b\":3}]" +> JSON.GET doc '$[?@.a + @.b * 2 == 8]' +"[{\"a\":2,\"b\":3}]" +> JSON.GET doc '$[?(@.a + @.b) * 2 == 10]' +"[{\"a\":2,\"b\":3}]" +{{< /clients-example >}} + +#### Membership: `in` and `nin` + +{{< clients-example set="json_path_ops" step="filter_membership" description="Membership filters: Use in and nin to test whether a value is a member of an array when you need set-style matching" difficulty="advanced" >}} +> JSON.SET doc $ '{"a":[1,2,3,4],"allow":[2,3]}' +OK +> JSON.GET doc '$.a[?@ in [2,4]]' +"[2,4]" +> JSON.GET doc '$.a[?@ nin [2,4]]' +"[1,3]" +> JSON.GET doc '$.a[?@ in $.allow]' +"[2,3]" +{{< /clients-example >}} + +#### Set relations: `subsetof`, `anyof`, and `noneof` + +{{< clients-example set="json_path_ops" step="filter_set_relations" description="Set relation filters: Use subsetof, anyof, and noneof to compare two arrays when you need to match based on their overlap" difficulty="advanced" >}} +> JSON.SET doc $ '{"a":[[1,2],[1,5],[]]}' +OK +> JSON.GET doc '$.a[?@ subsetof [1,2,3]]' +"[[1,2],[]]" +> JSON.SET doc $ '{"a":[[1,9],[8,9],[]]}' +OK +> JSON.GET doc '$.a[?@ anyof [1,2,3]]' +"[[1,9]]" +> JSON.SET doc $ '{"a":[[4,5],[1,9],[]]}' +OK +> JSON.GET doc '$.a[?@ noneof [1,2,3]]' +"[[4,5],[]]" +{{< /clients-example >}} + +#### `size` (`sizeof`) and `empty` + +{{< clients-example set="json_path_ops" step="filter_size_empty" description="Size and emptiness filters: Use sizeof and empty to filter by the size or emptiness of a string, array, or object" difficulty="advanced" >}} +> JSON.SET doc $ '{"a":[[4,5],[1],[7,8,9]]}' +OK +> JSON.GET doc '$.a[?@ sizeof 2]' +"[[4,5]]" +> JSON.SET doc $ '{"a":[[],[1],"",[2,3],{},{"k":1}]}' +OK +> JSON.GET doc '$.a[?@ empty true]' +"[[],\"\",{}]" +> JSON.GET doc '$.a[?@ empty false]' +"[[1],[2,3],{\"k\":1}]" +{{< /clients-example >}} + +#### Get-keys operator `~` + +{{< clients-example set="json_path_ops" step="filter_getkeys" description="Get-keys filter: Use the ~ operator to retrieve an object's member names as a list of strings" difficulty="advanced" >}} +> JSON.SET doc $ '{"obj":{"x":1,"y":2},"books":[{"t":"a"},{"t":"b"}]}' +OK +> JSON.GET doc '$.obj~' +"[\"x\",\"y\"]" +> JSON.GET doc '$~' +"[\"obj\",\"books\"]" +> JSON.GET doc '$.books~' +"[]" +{{< /clients-example >}} + ### Update examples You can also use JSONPath queries when you want to update specific sections of a JSON document. diff --git a/local_examples/json_path_ops/go-redis/json_path_ops_test.go b/local_examples/json_path_ops/go-redis/json_path_ops_test.go new file mode 100644 index 0000000000..658d67f1e6 --- /dev/null +++ b/local_examples/json_path_ops/go-redis/json_path_ops_test.go @@ -0,0 +1,444 @@ +// EXAMPLE: json_path_ops +// HIDE_START +package example_commands_test + +import ( + "context" + "fmt" + + "github.com/redis/go-redis/v9" +) + +// HIDE_END + +func ExampleClient_json_path_ops_filter_negation() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password docs + DB: 0, // use default DB + }) + + // REMOVE_START + rdb.FlushDB(ctx) + rdb.Del(ctx, "doc") + // REMOVE_END + + // STEP_START filter_negation + res1, err := rdb.JSONSet(ctx, "doc", "$", + `[{"a":1,"b":1},{"b":2},{"a":1},{"c":3}]`, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res1) // >>> OK + + res2, err := rdb.JSONGet(ctx, "doc", `$[?!@.a]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res2) // >>> [{"b":2},{"c":3}] + + res3, err := rdb.JSONGet(ctx, "doc", `$[?!(@.a==1)]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res3) // >>> [{"b":2},{"c":3}] + + res4, err := rdb.JSONGet(ctx, "doc", `$[?!@.a && @.b]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res4) // >>> [{"b":2}] + // STEP_END + + // Output: + // OK + // [{"b":2},{"c":3}] + // [{"b":2},{"c":3}] + // [{"b":2}] +} + +func ExampleClient_json_path_ops_filter_literal_eq() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password docs + DB: 0, // use default DB + }) + + // REMOVE_START + rdb.FlushDB(ctx) + rdb.Del(ctx, "doc") + // REMOVE_END + + // STEP_START filter_literal_eq + res1, err := rdb.JSONSet(ctx, "doc", "$", + `{"arrs":[[1],[2],[1,2],[1,[2]]],"objs":[{"x":1},{"x":2},{"y":1}]}`, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res1) // >>> OK + + res2, err := rdb.JSONGet(ctx, "doc", `$.arrs[?(@ == [1])]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res2) // >>> [[1]] + + res3, err := rdb.JSONGet(ctx, "doc", `$.arrs[?(@ == [1,[2]])]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res3) // >>> [[1,[2]]] + + res4, err := rdb.JSONGet(ctx, "doc", `$.objs[?(@ == {"x":1})]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res4) // >>> [{"x":1}] + // STEP_END + + // Output: + // OK + // [[1]] + // [[1,[2]]] + // [{"x":1}] +} + +func ExampleClient_json_path_ops_filter_arithmetic() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password docs + DB: 0, // use default DB + }) + + // REMOVE_START + rdb.FlushDB(ctx) + rdb.Del(ctx, "doc") + // REMOVE_END + + // STEP_START filter_arithmetic + res1, err := rdb.JSONSet(ctx, "doc", "$", + `[{"a":2,"b":3},{"a":5,"b":2}]`, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res1) // >>> OK + + res2, err := rdb.JSONGet(ctx, "doc", `$[?@.a + 1 == 3]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res2) // >>> [{"a":2,"b":3}] + + res3, err := rdb.JSONGet(ctx, "doc", `$[?@.a + @.b * 2 == 8]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res3) // >>> [{"a":2,"b":3}] + + res4, err := rdb.JSONGet(ctx, "doc", `$[?(@.a + @.b) * 2 == 10]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res4) // >>> [{"a":2,"b":3}] + // STEP_END + + // Output: + // OK + // [{"a":2,"b":3}] + // [{"a":2,"b":3}] + // [{"a":2,"b":3}] +} + +func ExampleClient_json_path_ops_filter_membership() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password docs + DB: 0, // use default DB + }) + + // REMOVE_START + rdb.FlushDB(ctx) + rdb.Del(ctx, "doc") + // REMOVE_END + + // STEP_START filter_membership + res1, err := rdb.JSONSet(ctx, "doc", "$", + `{"a":[1,2,3,4],"allow":[2,3]}`, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res1) // >>> OK + + res2, err := rdb.JSONGet(ctx, "doc", `$.a[?@ in [2,4]]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res2) // >>> [2,4] + + res3, err := rdb.JSONGet(ctx, "doc", `$.a[?@ nin [2,4]]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res3) // >>> [1,3] + + res4, err := rdb.JSONGet(ctx, "doc", `$.a[?@ in $.allow]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res4) // >>> [2,3] + // STEP_END + + // Output: + // OK + // [2,4] + // [1,3] + // [2,3] +} + +func ExampleClient_json_path_ops_filter_set_relations() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password docs + DB: 0, // use default DB + }) + + // REMOVE_START + rdb.FlushDB(ctx) + rdb.Del(ctx, "doc") + // REMOVE_END + + // STEP_START filter_set_relations + res1, err := rdb.JSONSet(ctx, "doc", "$", + `{"a":[[1,2],[1,5],[]]}`, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res1) // >>> OK + + res2, err := rdb.JSONGet(ctx, "doc", `$.a[?@ subsetof [1,2,3]]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res2) // >>> [[1,2],[]] + + res3, err := rdb.JSONSet(ctx, "doc", "$", + `{"a":[[1,9],[8,9],[]]}`, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res3) // >>> OK + + res4, err := rdb.JSONGet(ctx, "doc", `$.a[?@ anyof [1,2,3]]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res4) // >>> [[1,9]] + + res5, err := rdb.JSONSet(ctx, "doc", "$", + `{"a":[[4,5],[1,9],[]]}`, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res5) // >>> OK + + res6, err := rdb.JSONGet(ctx, "doc", `$.a[?@ noneof [1,2,3]]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res6) // >>> [[4,5],[]] + // STEP_END + + // Output: + // OK + // [[1,2],[]] + // OK + // [[1,9]] + // OK + // [[4,5],[]] +} + +func ExampleClient_json_path_ops_filter_size_empty() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password docs + DB: 0, // use default DB + }) + + // REMOVE_START + rdb.FlushDB(ctx) + rdb.Del(ctx, "doc") + // REMOVE_END + + // STEP_START filter_size_empty + res1, err := rdb.JSONSet(ctx, "doc", "$", + `{"a":[[4,5],[1],[7,8,9]]}`, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res1) // >>> OK + + res2, err := rdb.JSONGet(ctx, "doc", `$.a[?@ sizeof 2]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res2) // >>> [[4,5]] + + res3, err := rdb.JSONSet(ctx, "doc", "$", + `{"a":[[],[1],"",[2,3],{},{"k":1}]}`, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res3) // >>> OK + + res4, err := rdb.JSONGet(ctx, "doc", `$.a[?@ empty true]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res4) // >>> [[],"",{}] + + res5, err := rdb.JSONGet(ctx, "doc", `$.a[?@ empty false]`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res5) // >>> [[1],[2,3],{"k":1}] + // STEP_END + + // Output: + // OK + // [[4,5]] + // OK + // [[],"",{}] + // [[1],[2,3],{"k":1}] +} + +func ExampleClient_json_path_ops_filter_getkeys() { + ctx := context.Background() + + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password docs + DB: 0, // use default DB + }) + + // REMOVE_START + rdb.FlushDB(ctx) + rdb.Del(ctx, "doc") + // REMOVE_END + + // STEP_START filter_getkeys + res1, err := rdb.JSONSet(ctx, "doc", "$", + `{"obj":{"x":1,"y":2},"books":[{"t":"a"},{"t":"b"}]}`, + ).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res1) // >>> OK + + res2, err := rdb.JSONGet(ctx, "doc", `$.obj~`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res2) // >>> ["x","y"] + + res3, err := rdb.JSONGet(ctx, "doc", `$~`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res3) // >>> ["obj","books"] + + res4, err := rdb.JSONGet(ctx, "doc", `$.books~`).Result() + + if err != nil { + panic(err) + } + + fmt.Println(res4) // >>> [] + // STEP_END + + // Output: + // OK + // ["x","y"] + // ["obj","books"] + // [] +} diff --git a/local_examples/json_path_ops/jedis/JsonPathOpsExample.java b/local_examples/json_path_ops/jedis/JsonPathOpsExample.java new file mode 100644 index 0000000000..d99c364d81 --- /dev/null +++ b/local_examples/json_path_ops/jedis/JsonPathOpsExample.java @@ -0,0 +1,192 @@ +// EXAMPLE: json_path_ops +// REMOVE_START +package io.redis.examples; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +// REMOVE_END + +// HIDE_START +import redis.clients.jedis.RedisClient; +import redis.clients.jedis.json.Path; +import redis.clients.jedis.json.Path2; +import org.json.JSONObject; +// HIDE_END + +// HIDE_START +public class JsonPathOpsExample { + + @Test + public void run() { + RedisClient jedis = RedisClient.create("redis://localhost:6379"); + + // REMOVE_START + jedis.del("doc"); + // REMOVE_END +// HIDE_END + + // STEP_START filter_negation + String res1 = jedis.jsonSet("doc", new Path2("$"), "[{\"a\":1,\"b\":1},{\"b\":2},{\"a\":1},{\"c\":3}]"); + System.out.println(res1); // >>> OK + + Object res2 = jedis.jsonGet("doc", new Path2("$[?!@.a]")); + System.out.println(res2); // >>> [{"b":2},{"c":3}] + + Object res3 = jedis.jsonGet("doc", new Path2("$[?!(@.a==1)]")); + System.out.println(res3); // >>> [{"b":2},{"c":3}] + + Object res4 = jedis.jsonGet("doc", new Path2("$[?!@.a && @.b]")); + System.out.println(res4); // >>> [{"b":2}] + // STEP_END + // REMOVE_START + assertEquals("[{\"b\":2},{\"c\":3}]", res2.toString()); + assertEquals("[{\"b\":2},{\"c\":3}]", res3.toString()); + assertEquals("[{\"b\":2}]", res4.toString()); + jedis.del("doc"); + // REMOVE_END + + // STEP_START filter_literal_eq + String res5 = jedis.jsonSet( + "doc", + new Path2("$"), + "{\"arrs\":[[1],[2],[1,2],[1,[2]]],\"objs\":[{\"x\":1},{\"x\":2},{\"y\":1}]}" + ); + System.out.println(res5); // >>> OK + + Object res6 = jedis.jsonGet("doc", new Path2("$.arrs[?(@ == [1])]")); + System.out.println(res6); // >>> [[1]] + + Object res7 = jedis.jsonGet("doc", new Path2("$.arrs[?(@ == [1,[2]])]")); + System.out.println(res7); // >>> [[1,[2]]] + + Object res8 = jedis.jsonGet("doc", new Path2("$.objs[?(@ == {\"x\":1})]")); + System.out.println(res8); // >>> [{"x":1}] + // STEP_END + // REMOVE_START + assertEquals("[[1]]", res6.toString()); + assertEquals("[[1,[2]]]", res7.toString()); + assertEquals("[{\"x\":1}]", res8.toString()); + jedis.del("doc"); + // REMOVE_END + + // STEP_START filter_arithmetic + String res9 = jedis.jsonSet("doc", new Path2("$"), "[{\"a\":2,\"b\":3},{\"a\":5,\"b\":2}]"); + System.out.println(res9); // >>> OK + + Object res10 = jedis.jsonGet("doc", new Path2("$[?@.a + 1 == 3]")); + System.out.println(res10); // >>> [{"a":2,"b":3}] + + Object res11 = jedis.jsonGet("doc", new Path2("$[?@.a + @.b * 2 == 8]")); + System.out.println(res11); // >>> [{"a":2,"b":3}] + + Object res12 = jedis.jsonGet("doc", new Path2("$[?(@.a + @.b) * 2 == 10]")); + System.out.println(res12); // >>> [{"a":2,"b":3}] + // STEP_END + // REMOVE_START + assertEquals("[{\"a\":2,\"b\":3}]", res10.toString()); + assertEquals("[{\"a\":2,\"b\":3}]", res11.toString()); + assertEquals("[{\"a\":2,\"b\":3}]", res12.toString()); + jedis.del("doc"); + // REMOVE_END + + // STEP_START filter_membership + String res13 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":[1,2,3,4],\"allow\":[2,3]}"); + System.out.println(res13); // >>> OK + + Object res14 = jedis.jsonGet("doc", new Path2("$.a[?@ in [2,4]]")); + System.out.println(res14); // >>> [2,4] + + Object res15 = jedis.jsonGet("doc", new Path2("$.a[?@ nin [2,4]]")); + System.out.println(res15); // >>> [1,3] + + Object res16 = jedis.jsonGet("doc", new Path2("$.a[?@ in $.allow]")); + System.out.println(res16); // >>> [2,3] + // STEP_END + // REMOVE_START + assertEquals("[2,4]", res14.toString()); + assertEquals("[1,3]", res15.toString()); + assertEquals("[2,3]", res16.toString()); + jedis.del("doc"); + // REMOVE_END + + // STEP_START filter_set_relations + String res17 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":[[1,2],[1,5],[]]}"); + System.out.println(res17); // >>> OK + + Object res18 = jedis.jsonGet("doc", new Path2("$.a[?@ subsetof [1,2,3]]")); + System.out.println(res18); // >>> [[1,2],[]] + + String res19 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":[[1,9],[8,9],[]]}"); + System.out.println(res19); // >>> OK + + Object res20 = jedis.jsonGet("doc", new Path2("$.a[?@ anyof [1,2,3]]")); + System.out.println(res20); // >>> [[1,9]] + + String res21 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":[[4,5],[1,9],[]]}"); + System.out.println(res21); // >>> OK + + Object res22 = jedis.jsonGet("doc", new Path2("$.a[?@ noneof [1,2,3]]")); + System.out.println(res22); // >>> [[4,5],[]] + // STEP_END + // REMOVE_START + assertEquals("[[1,2],[]]", res18.toString()); + assertEquals("[[1,9]]", res20.toString()); + assertEquals("[[4,5],[]]", res22.toString()); + jedis.del("doc"); + // REMOVE_END + + // STEP_START filter_size_empty + String res23 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":[[4,5],[1],[7,8,9]]}"); + System.out.println(res23); // >>> OK + + Object res24 = jedis.jsonGet("doc", new Path2("$.a[?@ sizeof 2]")); + System.out.println(res24); // >>> [[4,5]] + + String res25 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":[[],[1],\"\",[2,3],{},{\"k\":1}]}"); + System.out.println(res25); // >>> OK + + Object res26 = jedis.jsonGet("doc", new Path2("$.a[?@ empty true]")); + System.out.println(res26); // >>> [[],"",{}] + + Object res27 = jedis.jsonGet("doc", new Path2("$.a[?@ empty false]")); + System.out.println(res27); // >>> [[1],[2,3],{"k":1}] + // STEP_END + // REMOVE_START + assertEquals("[[4,5]]", res24.toString()); + assertEquals("[[],\"\",{}]", res26.toString()); + assertEquals("[[1],[2,3],{\"k\":1}]", res27.toString()); + jedis.del("doc"); + // REMOVE_END + + // STEP_START filter_getkeys + String res28 = jedis.jsonSet( + "doc", + new Path2("$"), + "{\"obj\":{\"x\":1,\"y\":2},\"books\":[{\"t\":\"a\"},{\"t\":\"b\"}]}" + ); + System.out.println(res28); // >>> OK + + Object res29 = jedis.jsonGet("doc", new Path2("$.obj~")); + System.out.println(res29); // >>> ["x","y"] + + Object res30 = jedis.jsonGet("doc", new Path2("$~")); + System.out.println(res30); // >>> ["obj","books"] + + Object res31 = jedis.jsonGet("doc", new Path2("$.books~")); + System.out.println(res31); // >>> [] + // STEP_END + // REMOVE_START + assertEquals("[\"x\",\"y\"]", res29.toString()); + assertEquals("[\"obj\",\"books\"]", res30.toString()); + assertEquals("[]", res31.toString()); + jedis.del("doc"); + // REMOVE_END + + +// HIDE_START + jedis.close(); + } +} +// HIDE_END diff --git a/local_examples/json_path_ops/lettuce-async/JsonPathOpsExample.java b/local_examples/json_path_ops/lettuce-async/JsonPathOpsExample.java new file mode 100644 index 0000000000..e6655c35f3 --- /dev/null +++ b/local_examples/json_path_ops/lettuce-async/JsonPathOpsExample.java @@ -0,0 +1,307 @@ +// EXAMPLE: json_path_ops +package io.redis.examples.async; + +import io.lettuce.core.*; +import io.lettuce.core.api.async.RedisAsyncCommands; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.json.JsonPath; +import io.lettuce.core.json.JsonParser; +import io.lettuce.core.json.JsonObject; + +import java.util.concurrent.CompletableFuture; +// REMOVE_START +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +// REMOVE_END + +public class JsonPathOpsExample { + + // REMOVE_START + @Test + // REMOVE_END + public void run() { + // REMOVE_START + RedisClient redisClient = RedisClient.create("redis://localhost:6379"); + + try (StatefulRedisConnection connection = redisClient.connect()) { + RedisAsyncCommands asyncCommands = connection.async(); + // REMOVE_END + + JsonParser parser = asyncCommands.getJsonParser(); + + // REMOVE_START + asyncCommands.del("doc").toCompletableFuture().join(); + // REMOVE_END + + // STEP_START filter_negation + CompletableFuture filterNegationExample = asyncCommands + .jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue("[{\"a\":1,\"b\":1},{\"b\":2},{\"a\":1},{\"c\":3}]")) + .thenCompose(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$[?!@.a]")); + }).thenCompose(res2 -> { + System.out.println(res2); // >>> [[{"b":2},{"c":3}]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[{\"b\":2},{\"c\":3}]]"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$[?!(@.a==1)]")); + }).thenCompose(res3 -> { + System.out.println(res3); // >>> [[{"b":2},{"c":3}]] + // REMOVE_START + assertThat(res3.toString()).isEqualTo("[[{\"b\":2},{\"c\":3}]]"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$[?!@.a && @.b]")); + }).thenAccept(res4 -> { + System.out.println(res4); // >>> [[{"b":2}]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[{\"b\":2}]]"); + // REMOVE_END + }).toCompletableFuture(); + // STEP_END + + filterNegationExample.join(); + // REMOVE_START + asyncCommands.del("doc").toCompletableFuture().join(); + // REMOVE_END + + // STEP_START filter_literal_eq + CompletableFuture filterLiteralEqExample = asyncCommands + .jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue( + "{\"arrs\":[[1],[2],[1,2],[1,[2]]],\"objs\":[{\"x\":1},{\"x\":2},{\"y\":1}]}")) + .thenCompose(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.arrs[?(@ == [1])]")); + }).thenCompose(res2 -> { + System.out.println(res2); // >>> [[[1]]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[[1]]]"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.arrs[?(@ == [1,[2]])]")); + }).thenCompose(res3 -> { + System.out.println(res3); // >>> [[[1,[2]]]] + // REMOVE_START + assertThat(res3.toString()).isEqualTo("[[[1,[2]]]]"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.objs[?(@ == {\"x\":1})]")); + }).thenAccept(res4 -> { + System.out.println(res4); // >>> [[{"x":1}]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[{\"x\":1}]]"); + // REMOVE_END + }).toCompletableFuture(); + // STEP_END + + filterLiteralEqExample.join(); + // REMOVE_START + asyncCommands.del("doc").toCompletableFuture().join(); + // REMOVE_END + + // STEP_START filter_arithmetic + CompletableFuture filterArithmeticExample = asyncCommands + .jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue("[{\"a\":2,\"b\":3},{\"a\":5,\"b\":2}]")) + .thenCompose(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$[?@.a + 1 == 3]")); + }).thenCompose(res2 -> { + System.out.println(res2); // >>> [[{"a":2,"b":3}]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[{\"a\":2,\"b\":3}]]"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$[?@.a + @.b * 2 == 8]")); + }).thenCompose(res3 -> { + System.out.println(res3); // >>> [[{"a":2,"b":3}]] + // REMOVE_START + assertThat(res3.toString()).isEqualTo("[[{\"a\":2,\"b\":3}]]"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$[?(@.a + @.b) * 2 == 10]")); + }).thenAccept(res4 -> { + System.out.println(res4); // >>> [[{"a":2,"b":3}]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[{\"a\":2,\"b\":3}]]"); + // REMOVE_END + }).toCompletableFuture(); + // STEP_END + + filterArithmeticExample.join(); + // REMOVE_START + asyncCommands.del("doc").toCompletableFuture().join(); + // REMOVE_END + + // STEP_START filter_membership + CompletableFuture filterMembershipExample = asyncCommands + .jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue("{\"a\":[1,2,3,4],\"allow\":[2,3]}")) + .thenCompose(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ in [2,4]]")); + }).thenCompose(res2 -> { + System.out.println(res2); // >>> [[2,4]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[2,4]]"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ nin [2,4]]")); + }).thenCompose(res3 -> { + System.out.println(res3); // >>> [[1,3]] + // REMOVE_START + assertThat(res3.toString()).isEqualTo("[[1,3]]"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ in $.allow]")); + }).thenAccept(res4 -> { + System.out.println(res4); // >>> [[2,3]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[2,3]]"); + // REMOVE_END + }).toCompletableFuture(); + // STEP_END + + filterMembershipExample.join(); + // REMOVE_START + asyncCommands.del("doc").toCompletableFuture().join(); + // REMOVE_END + + // STEP_START filter_set_relations + CompletableFuture filterSetRelationsExample = asyncCommands + .jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("{\"a\":[[1,2],[1,5],[]]}")) + .thenCompose(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ subsetof [1,2,3]]")); + }).thenCompose(res2 -> { + System.out.println(res2); // >>> [[[1,2],[]]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[[1,2],[]]]"); + // REMOVE_END + return asyncCommands.jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue("{\"a\":[[1,9],[8,9],[]]}")); + }).thenCompose(res3 -> { + System.out.println(res3); // >>> OK + // REMOVE_START + assertThat(res3).isEqualTo("OK"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ anyof [1,2,3]]")); + }).thenCompose(res4 -> { + System.out.println(res4); // >>> [[[1,9]]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[[1,9]]]"); + // REMOVE_END + return asyncCommands.jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue("{\"a\":[[4,5],[1,9],[]]}")); + }).thenCompose(res5 -> { + System.out.println(res5); // >>> OK + // REMOVE_START + assertThat(res5).isEqualTo("OK"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ noneof [1,2,3]]")); + }).thenAccept(res6 -> { + System.out.println(res6); // >>> [[[4,5],[]]] + // REMOVE_START + assertThat(res6.toString()).isEqualTo("[[[4,5],[]]]"); + // REMOVE_END + }).toCompletableFuture(); + // STEP_END + + filterSetRelationsExample.join(); + // REMOVE_START + asyncCommands.del("doc").toCompletableFuture().join(); + // REMOVE_END + + // STEP_START filter_size_empty + CompletableFuture filterSizeEmptyExample = asyncCommands + .jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("{\"a\":[[4,5],[1],[7,8,9]]}")) + .thenCompose(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ sizeof 2]")); + }).thenCompose(res2 -> { + System.out.println(res2); // >>> [[[4,5]]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[[4,5]]]"); + // REMOVE_END + return asyncCommands.jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue("{\"a\":[[],[1],\"\",[2,3],{},{\"k\":1}]}")); + }).thenCompose(res3 -> { + System.out.println(res3); // >>> OK + // REMOVE_START + assertThat(res3).isEqualTo("OK"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ empty true]")); + }).thenCompose(res4 -> { + System.out.println(res4); // >>> [[[],"",{}]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[[],\"\",{}]]"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ empty false]")); + }).thenAccept(res5 -> { + System.out.println(res5); // >>> [[[1],[2,3],{"k":1}]] + // REMOVE_START + assertThat(res5.toString()).isEqualTo("[[[1],[2,3],{\"k\":1}]]"); + // REMOVE_END + }).toCompletableFuture(); + // STEP_END + + filterSizeEmptyExample.join(); + // REMOVE_START + asyncCommands.del("doc").toCompletableFuture().join(); + // REMOVE_END + + // STEP_START filter_getkeys + CompletableFuture filterGetkeysExample = asyncCommands + .jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue( + "{\"obj\":{\"x\":1,\"y\":2},\"books\":[{\"t\":\"a\"},{\"t\":\"b\"}]}")) + .thenCompose(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.obj~")); + }).thenCompose(res2 -> { + System.out.println(res2); // >>> [["x","y"]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[\"x\",\"y\"]]"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$~")); + }).thenCompose(res3 -> { + System.out.println(res3); // >>> [["obj","books"]] + // REMOVE_START + assertThat(res3.toString()).isEqualTo("[[\"obj\",\"books\"]]"); + // REMOVE_END + return asyncCommands.jsonGet("doc", JsonPath.of("$.books~")); + }).thenAccept(res4 -> { + System.out.println(res4); // >>> [[]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[]]"); + // REMOVE_END + }).toCompletableFuture(); + // STEP_END + + filterGetkeysExample.join(); + // REMOVE_START + asyncCommands.del("doc").toCompletableFuture().join(); + // REMOVE_END + + } finally { + redisClient.shutdown(); + } + } + +} diff --git a/local_examples/json_path_ops/lettuce-reactive/JsonPathOpsExample.java b/local_examples/json_path_ops/lettuce-reactive/JsonPathOpsExample.java new file mode 100644 index 0000000000..4c5d74ad52 --- /dev/null +++ b/local_examples/json_path_ops/lettuce-reactive/JsonPathOpsExample.java @@ -0,0 +1,333 @@ +// EXAMPLE: json_path_ops +package io.redis.examples.reactive; + +import io.lettuce.core.*; +import io.lettuce.core.api.reactive.RedisReactiveCommands; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.json.JsonPath; +import io.lettuce.core.json.JsonParser; +import io.lettuce.core.json.JsonObject; +// REMOVE_START +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +// REMOVE_END + +import reactor.core.publisher.Mono; + +public class JsonPathOpsExample { + + // REMOVE_START + @Test + // REMOVE_END + public void run() { + RedisClient redisClient = RedisClient.create("redis://localhost:6379"); + + try (StatefulRedisConnection connection = redisClient.connect()) { + RedisReactiveCommands reactiveCommands = connection.reactive(); + + JsonParser parser = reactiveCommands.getJsonParser(); + + // REMOVE_START + reactiveCommands.del("doc").block(); + // REMOVE_END + + // STEP_START filter_negation + Mono filterNegationExample = reactiveCommands + .jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue("[{\"a\":1,\"b\":1},{\"b\":2},{\"a\":1},{\"c\":3}]")) + .doOnNext(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + }) + .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$[?!@.a]")).collectList()) + .doOnNext(res2 -> { + System.out.println(res2); // >>> [[{"b":2},{"c":3}]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[{\"b\":2},{\"c\":3}]]"); + // REMOVE_END + }) + .flatMap(res2 -> reactiveCommands.jsonGet("doc", JsonPath.of("$[?!(@.a==1)]")).collectList()) + .doOnNext(res3 -> { + System.out.println(res3); // >>> [[{"b":2},{"c":3}]] + // REMOVE_START + assertThat(res3.toString()).isEqualTo("[[{\"b\":2},{\"c\":3}]]"); + // REMOVE_END + }) + .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$[?!@.a && @.b]")).collectList()) + .doOnNext(res4 -> { + System.out.println(res4); // >>> [[{"b":2}]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[{\"b\":2}]]"); + // REMOVE_END + }) + .then(); + // STEP_END + + filterNegationExample.block(); + // REMOVE_START + reactiveCommands.del("doc").block(); + // REMOVE_END + + // STEP_START filter_literal_eq + Mono filterLiteralEqExample = reactiveCommands + .jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue( + "{\"arrs\":[[1],[2],[1,2],[1,[2]]],\"objs\":[{\"x\":1},{\"x\":2},{\"y\":1}]}")) + .doOnNext(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + }) + .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.arrs[?(@ == [1])]")).collectList()) + .doOnNext(res2 -> { + System.out.println(res2); // >>> [[[1]]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[[1]]]"); + // REMOVE_END + }) + .flatMap(res2 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.arrs[?(@ == [1,[2]])]")).collectList()) + .doOnNext(res3 -> { + System.out.println(res3); // >>> [[[1,[2]]]] + // REMOVE_START + assertThat(res3.toString()).isEqualTo("[[[1,[2]]]]"); + // REMOVE_END + }) + .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.objs[?(@ == {\"x\":1})]")).collectList()) + .doOnNext(res4 -> { + System.out.println(res4); // >>> [[{"x":1}]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[{\"x\":1}]]"); + // REMOVE_END + }) + .then(); + // STEP_END + + filterLiteralEqExample.block(); + // REMOVE_START + reactiveCommands.del("doc").block(); + // REMOVE_END + + // STEP_START filter_arithmetic + Mono filterArithmeticExample = reactiveCommands + .jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("[{\"a\":2,\"b\":3},{\"a\":5,\"b\":2}]")) + .doOnNext(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + }) + .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$[?@.a + 1 == 3]")).collectList()) + .doOnNext(res2 -> { + System.out.println(res2); // >>> [[{"a":2,"b":3}]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[{\"a\":2,\"b\":3}]]"); + // REMOVE_END + }) + .flatMap(res2 -> reactiveCommands.jsonGet("doc", JsonPath.of("$[?@.a + @.b * 2 == 8]")).collectList()) + .doOnNext(res3 -> { + System.out.println(res3); // >>> [[{"a":2,"b":3}]] + // REMOVE_START + assertThat(res3.toString()).isEqualTo("[[{\"a\":2,\"b\":3}]]"); + // REMOVE_END + }) + .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$[?(@.a + @.b) * 2 == 10]")).collectList()) + .doOnNext(res4 -> { + System.out.println(res4); // >>> [[{"a":2,"b":3}]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[{\"a\":2,\"b\":3}]]"); + // REMOVE_END + }) + .then(); + // STEP_END + + filterArithmeticExample.block(); + // REMOVE_START + reactiveCommands.del("doc").block(); + // REMOVE_END + + // STEP_START filter_membership + Mono filterMembershipExample = reactiveCommands + .jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("{\"a\":[1,2,3,4],\"allow\":[2,3]}")) + .doOnNext(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + }) + .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ in [2,4]]")).collectList()) + .doOnNext(res2 -> { + System.out.println(res2); // >>> [[2,4]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[2,4]]"); + // REMOVE_END + }) + .flatMap(res2 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ nin [2,4]]")).collectList()) + .doOnNext(res3 -> { + System.out.println(res3); // >>> [[1,3]] + // REMOVE_START + assertThat(res3.toString()).isEqualTo("[[1,3]]"); + // REMOVE_END + }) + .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ in $.allow]")).collectList()) + .doOnNext(res4 -> { + System.out.println(res4); // >>> [[2,3]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[2,3]]"); + // REMOVE_END + }) + .then(); + // STEP_END + + filterMembershipExample.block(); + // REMOVE_START + reactiveCommands.del("doc").block(); + // REMOVE_END + + // STEP_START filter_set_relations + Mono filterSetRelationsExample = reactiveCommands + .jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("{\"a\":[[1,2],[1,5],[]]}")) + .doOnNext(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + }) + .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ subsetof [1,2,3]]")).collectList()) + .doOnNext(res2 -> { + System.out.println(res2); // >>> [[[1,2],[]]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[[1,2],[]]]"); + // REMOVE_END + }) + .flatMap(res2 -> reactiveCommands.jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue("{\"a\":[[1,9],[8,9],[]]}"))) + .doOnNext(res3 -> { + System.out.println(res3); // >>> OK + // REMOVE_START + assertThat(res3).isEqualTo("OK"); + // REMOVE_END + }) + .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ anyof [1,2,3]]")).collectList()) + .doOnNext(res4 -> { + System.out.println(res4); // >>> [[[1,9]]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[[1,9]]]"); + // REMOVE_END + }) + .flatMap(res4 -> reactiveCommands.jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue("{\"a\":[[4,5],[1,9],[]]}"))) + .doOnNext(res5 -> { + System.out.println(res5); // >>> OK + // REMOVE_START + assertThat(res5).isEqualTo("OK"); + // REMOVE_END + }) + .flatMap(res5 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ noneof [1,2,3]]")).collectList()) + .doOnNext(res6 -> { + System.out.println(res6); // >>> [[[4,5],[]]] + // REMOVE_START + assertThat(res6.toString()).isEqualTo("[[[4,5],[]]]"); + // REMOVE_END + }) + .then(); + // STEP_END + + filterSetRelationsExample.block(); + // REMOVE_START + reactiveCommands.del("doc").block(); + // REMOVE_END + + // STEP_START filter_size_empty + Mono filterSizeEmptyExample = reactiveCommands + .jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("{\"a\":[[4,5],[1],[7,8,9]]}")) + .doOnNext(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + }) + .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ sizeof 2]")).collectList()) + .doOnNext(res2 -> { + System.out.println(res2); // >>> [[[4,5]]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[[4,5]]]"); + // REMOVE_END + }) + .flatMap(res2 -> reactiveCommands.jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue("{\"a\":[[],[1],\"\",[2,3],{},{\"k\":1}]}"))) + .doOnNext(res3 -> { + System.out.println(res3); // >>> OK + // REMOVE_START + assertThat(res3).isEqualTo("OK"); + // REMOVE_END + }) + .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ empty true]")).collectList()) + .doOnNext(res4 -> { + System.out.println(res4); // >>> [[[],"",{}]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[[],\"\",{}]]"); + // REMOVE_END + }) + .flatMap(res4 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ empty false]")).collectList()) + .doOnNext(res5 -> { + System.out.println(res5); // >>> [[[1],[2,3],{"k":1}]] + // REMOVE_START + assertThat(res5.toString()).isEqualTo("[[[1],[2,3],{\"k\":1}]]"); + // REMOVE_END + }) + .then(); + // STEP_END + + filterSizeEmptyExample.block(); + // REMOVE_START + reactiveCommands.del("doc").block(); + // REMOVE_END + + // STEP_START filter_getkeys + Mono filterGetkeysExample = reactiveCommands + .jsonSet("doc", JsonPath.ROOT_PATH, + parser.createJsonValue("{\"obj\":{\"x\":1,\"y\":2},\"books\":[{\"t\":\"a\"},{\"t\":\"b\"}]}")) + .doOnNext(res1 -> { + System.out.println(res1); // >>> OK + // REMOVE_START + assertThat(res1).isEqualTo("OK"); + // REMOVE_END + }) + .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.obj~")).collectList()) + .doOnNext(res2 -> { + System.out.println(res2); // >>> [["x","y"]] + // REMOVE_START + assertThat(res2.toString()).isEqualTo("[[\"x\",\"y\"]]"); + // REMOVE_END + }) + .flatMap(res2 -> reactiveCommands.jsonGet("doc", JsonPath.of("$~")).collectList()) + .doOnNext(res3 -> { + System.out.println(res3); // >>> [["obj","books"]] + // REMOVE_START + assertThat(res3.toString()).isEqualTo("[[\"obj\",\"books\"]]"); + // REMOVE_END + }) + .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.books~")).collectList()) + .doOnNext(res4 -> { + System.out.println(res4); // >>> [[]] + // REMOVE_START + assertThat(res4.toString()).isEqualTo("[[]]"); + // REMOVE_END + }) + .then(); + // STEP_END + + filterGetkeysExample.block(); + // REMOVE_START + reactiveCommands.del("doc").block(); + // REMOVE_END + + } finally { + redisClient.shutdown(); + } + } + +} diff --git a/local_examples/json_path_ops/node-redis/json-path-ops.js b/local_examples/json_path_ops/node-redis/json-path-ops.js new file mode 100644 index 0000000000..30702f9c39 --- /dev/null +++ b/local_examples/json_path_ops/node-redis/json-path-ops.js @@ -0,0 +1,179 @@ +// EXAMPLE: json_path_ops +// HIDE_START +import assert from 'node:assert'; +import { createClient } from 'redis'; + +const client = createClient(); +await client.connect().catch(console.error); +// HIDE_END + +// REMOVE_START +await client.del('doc'); +// REMOVE_END + +// STEP_START filter_negation +const res1 = await client.json.set('doc', '$', [{ a: 1, b: 1 }, { b: 2 }, { a: 1 }, { c: 3 }]); +console.log(res1); // >>> OK + +const res2 = await client.json.get('doc', { path: '$[?!@.a]' }); +console.log(res2); // >>> [ { b: 2 }, { c: 3 } ] + +const res3 = await client.json.get('doc', { path: '$[?!(@.a==1)]' }); +console.log(res3); // >>> [ { b: 2 }, { c: 3 } ] + +const res4 = await client.json.get('doc', { path: '$[?!@.a && @.b]' }); +console.log(res4); // >>> [ { b: 2 } ] +// STEP_END + +// REMOVE_START +assert.deepEqual(res2, [{ b: 2 }, { c: 3 }]); +assert.deepEqual(res3, [{ b: 2 }, { c: 3 }]); +assert.deepEqual(res4, [{ b: 2 }]); +await client.del('doc'); +// REMOVE_END + +// STEP_START filter_literal_eq +const res5 = await client.json.set('doc', '$', { + arrs: [[1], [2], [1, 2], [1, [2]]], + objs: [{ x: 1 }, { x: 2 }, { y: 1 }] +}); +console.log(res5); // >>> OK + +const res6 = await client.json.get('doc', { path: '$.arrs[?(@ == [1])]' }); +console.log(res6); // >>> [ [ 1 ] ] + +const res7 = await client.json.get('doc', { path: '$.arrs[?(@ == [1,[2]])]' }); +console.log(res7); // >>> [ [ 1, [ 2 ] ] ] + +const res8 = await client.json.get('doc', { path: '$.objs[?(@ == {"x":1})]' }); +console.log(res8); // >>> [ { x: 1 } ] +// STEP_END + +// REMOVE_START +assert.deepEqual(res6, [[1]]); +assert.deepEqual(res7, [[1, [2]]]); +assert.deepEqual(res8, [{ x: 1 }]); +await client.del('doc'); +// REMOVE_END + +// STEP_START filter_arithmetic +const res9 = await client.json.set('doc', '$', [{ a: 2, b: 3 }, { a: 5, b: 2 }]); +console.log(res9); // >>> OK + +const res10 = await client.json.get('doc', { path: '$[?@.a + 1 == 3]' }); +console.log(res10); // >>> [ { a: 2, b: 3 } ] + +const res11 = await client.json.get('doc', { path: '$[?@.a + @.b * 2 == 8]' }); +console.log(res11); // >>> [ { a: 2, b: 3 } ] + +const res12 = await client.json.get('doc', { path: '$[?(@.a + @.b) * 2 == 10]' }); +console.log(res12); // >>> [ { a: 2, b: 3 } ] +// STEP_END + +// REMOVE_START +assert.deepEqual(res10, [{ a: 2, b: 3 }]); +assert.deepEqual(res11, [{ a: 2, b: 3 }]); +assert.deepEqual(res12, [{ a: 2, b: 3 }]); +await client.del('doc'); +// REMOVE_END + +// STEP_START filter_membership +const res13 = await client.json.set('doc', '$', { a: [1, 2, 3, 4], allow: [2, 3] }); +console.log(res13); // >>> OK + +const res14 = await client.json.get('doc', { path: '$.a[?@ in [2,4]]' }); +console.log(res14); // >>> [ 2, 4 ] + +const res15 = await client.json.get('doc', { path: '$.a[?@ nin [2,4]]' }); +console.log(res15); // >>> [ 1, 3 ] + +const res16 = await client.json.get('doc', { path: '$.a[?@ in $.allow]' }); +console.log(res16); // >>> [ 2, 3 ] +// STEP_END + +// REMOVE_START +assert.deepEqual(res14, [2, 4]); +assert.deepEqual(res15, [1, 3]); +assert.deepEqual(res16, [2, 3]); +await client.del('doc'); +// REMOVE_END + +// STEP_START filter_set_relations +const res17 = await client.json.set('doc', '$', { a: [[1, 2], [1, 5], []] }); +console.log(res17); // >>> OK + +const res18 = await client.json.get('doc', { path: '$.a[?@ subsetof [1,2,3]]' }); +console.log(res18); // >>> [ [ 1, 2 ], [] ] + +const res19 = await client.json.set('doc', '$', { a: [[1, 9], [8, 9], []] }); +console.log(res19); // >>> OK + +const res20 = await client.json.get('doc', { path: '$.a[?@ anyof [1,2,3]]' }); +console.log(res20); // >>> [ [ 1, 9 ] ] + +const res21 = await client.json.set('doc', '$', { a: [[4, 5], [1, 9], []] }); +console.log(res21); // >>> OK + +const res22 = await client.json.get('doc', { path: '$.a[?@ noneof [1,2,3]]' }); +console.log(res22); // >>> [ [ 4, 5 ], [] ] +// STEP_END + +// REMOVE_START +assert.deepEqual(res18, [[1, 2], []]); +assert.deepEqual(res20, [[1, 9]]); +assert.deepEqual(res22, [[4, 5], []]); +await client.del('doc'); +// REMOVE_END + +// STEP_START filter_size_empty +const res23 = await client.json.set('doc', '$', { a: [[4, 5], [1], [7, 8, 9]] }); +console.log(res23); // >>> OK + +const res24 = await client.json.get('doc', { path: '$.a[?@ sizeof 2]' }); +console.log(res24); // >>> [ [ 4, 5 ] ] + +const res25 = await client.json.set('doc', '$', { a: [[], [1], '', [2, 3], {}, { k: 1 }] }); +console.log(res25); // >>> OK + +const res26 = await client.json.get('doc', { path: '$.a[?@ empty true]' }); +console.log(res26); // >>> [ [], '', {} ] + +const res27 = await client.json.get('doc', { path: '$.a[?@ empty false]' }); +console.log(res27); // >>> [ [ 1 ], [ 2, 3 ], { k: 1 } ] +// STEP_END + +// REMOVE_START +assert.deepEqual(res24, [[4, 5]]); +assert.deepEqual(res26, [[], '', {}]); +assert.deepEqual(res27, [[1], [2, 3], { k: 1 }]); +await client.del('doc'); +// REMOVE_END + +// STEP_START filter_getkeys +const res28 = await client.json.set('doc', '$', { + obj: { x: 1, y: 2 }, + books: [{ t: 'a' }, { t: 'b' }] +}); +console.log(res28); // >>> OK + +const res29 = await client.json.get('doc', { path: '$.obj~' }); +console.log(res29); // >>> [ 'x', 'y' ] + +const res30 = await client.json.get('doc', { path: '$~' }); +console.log(res30); // >>> [ 'obj', 'books' ] + +const res31 = await client.json.get('doc', { path: '$.books~' }); +console.log(res31); // >>> [] +// STEP_END + +// REMOVE_START +assert.deepEqual(res29, ['x', 'y']); +assert.deepEqual(res30, ['obj', 'books']); +assert.deepEqual(res31, []); +await client.del('doc'); +// REMOVE_END + + +// HIDE_START +await client.close(); +// HIDE_END diff --git a/local_examples/json_path_ops/nredisstack/JsonPathOpsExample.cs b/local_examples/json_path_ops/nredisstack/JsonPathOpsExample.cs new file mode 100644 index 0000000000..6e19daeb46 --- /dev/null +++ b/local_examples/json_path_ops/nredisstack/JsonPathOpsExample.cs @@ -0,0 +1,222 @@ +// EXAMPLE: json_path_ops +// HIDE_START + +using System.Text.Json; +using NRedisStack; +using NRedisStack.RedisStackCommands; +using NRedisStack.Tests; +using StackExchange.Redis; + +// HIDE_END + +// REMOVE_START +namespace Doc; + +[Collection("DocsTests")] +// REMOVE_END + +// HIDE_START +public class JsonPathOpsExample +// REMOVE_START +: AbstractNRedisStackTest, IDisposable +// REMOVE_END +{ + // REMOVE_START + public JsonPathOpsExample(EndpointsFixture fixture) : base(fixture) { } + + [Fact] + // REMOVE_END + public void Run() + { + //REMOVE_START + // This is needed because we're constructing ConfigurationOptions in the test before calling GetConnection + SkipIfTargetConnectionDoesNotExist(EndpointsFixture.Env.Standalone); + var _ = GetCleanDatabase(EndpointsFixture.Env.Standalone); + //REMOVE_END + var muxer = ConnectionMultiplexer.Connect("localhost:6379"); + var db = muxer.GetDatabase(); + // REMOVE_START + // Clear any keys here before using them in tests. + db.KeyDelete("doc"); + // REMOVE_END + // HIDE_END + + // STEP_START filter_negation + bool res1 = db.JSON().Set("doc", "$", "[{\"a\":1,\"b\":1},{\"b\":2},{\"a\":1},{\"c\":3}]"); + Console.WriteLine(res1); // >>> True + + RedisResult res2 = db.JSON().Get("doc", path: "$[?!@.a]"); + Console.WriteLine(res2); // >>> [{"b":2},{"c":3}] + + RedisResult res3 = db.JSON().Get("doc", path: "$[?!(@.a==1)]"); + Console.WriteLine(res3); // >>> [{"b":2},{"c":3}] + + RedisResult res4 = db.JSON().Get("doc", path: "$[?!@.a && @.b]"); + Console.WriteLine(res4); // >>> [{"b":2}] + // STEP_END + + // REMOVE_START + Assert.True(res1); + Assert.Equal("[{\"b\":2},{\"c\":3}]", (string?)res2); + Assert.Equal("[{\"b\":2},{\"c\":3}]", (string?)res3); + Assert.Equal("[{\"b\":2}]", (string?)res4); + db.KeyDelete("doc"); + // REMOVE_END + + // STEP_START filter_literal_eq + bool res5 = db.JSON().Set( + "doc", + "$", + "{\"arrs\":[[1],[2],[1,2],[1,[2]]],\"objs\":[{\"x\":1},{\"x\":2},{\"y\":1}]}" + ); + Console.WriteLine(res5); // >>> True + + RedisResult res6 = db.JSON().Get("doc", path: "$.arrs[?(@ == [1])]"); + Console.WriteLine(res6); // >>> [[1]] + + RedisResult res7 = db.JSON().Get("doc", path: "$.arrs[?(@ == [1,[2]])]"); + Console.WriteLine(res7); // >>> [[1,[2]]] + + RedisResult res8 = db.JSON().Get("doc", path: "$.objs[?(@ == {\"x\":1})]"); + Console.WriteLine(res8); // >>> [{"x":1}] + // STEP_END + + // REMOVE_START + Assert.True(res5); + Assert.Equal("[[1]]", (string?)res6); + Assert.Equal("[[1,[2]]]", (string?)res7); + Assert.Equal("[{\"x\":1}]", (string?)res8); + db.KeyDelete("doc"); + // REMOVE_END + + // STEP_START filter_arithmetic + bool res9 = db.JSON().Set("doc", "$", "[{\"a\":2,\"b\":3},{\"a\":5,\"b\":2}]"); + Console.WriteLine(res9); // >>> True + + RedisResult res10 = db.JSON().Get("doc", path: "$[?@.a + 1 == 3]"); + Console.WriteLine(res10); // >>> [{"a":2,"b":3}] + + RedisResult res11 = db.JSON().Get("doc", path: "$[?@.a + @.b * 2 == 8]"); + Console.WriteLine(res11); // >>> [{"a":2,"b":3}] + + RedisResult res12 = db.JSON().Get("doc", path: "$[?(@.a + @.b) * 2 == 10]"); + Console.WriteLine(res12); // >>> [{"a":2,"b":3}] + // STEP_END + + // REMOVE_START + Assert.True(res9); + Assert.Equal("[{\"a\":2,\"b\":3}]", (string?)res10); + Assert.Equal("[{\"a\":2,\"b\":3}]", (string?)res11); + Assert.Equal("[{\"a\":2,\"b\":3}]", (string?)res12); + db.KeyDelete("doc"); + // REMOVE_END + + // STEP_START filter_membership + bool res13 = db.JSON().Set("doc", "$", "{\"a\":[1,2,3,4],\"allow\":[2,3]}"); + Console.WriteLine(res13); // >>> True + + RedisResult res14 = db.JSON().Get("doc", path: "$.a[?@ in [2,4]]"); + Console.WriteLine(res14); // >>> [2,4] + + RedisResult res15 = db.JSON().Get("doc", path: "$.a[?@ nin [2,4]]"); + Console.WriteLine(res15); // >>> [1,3] + + RedisResult res16 = db.JSON().Get("doc", path: "$.a[?@ in $.allow]"); + Console.WriteLine(res16); // >>> [2,3] + // STEP_END + + // REMOVE_START + Assert.True(res13); + Assert.Equal("[2,4]", (string?)res14); + Assert.Equal("[1,3]", (string?)res15); + Assert.Equal("[2,3]", (string?)res16); + db.KeyDelete("doc"); + // REMOVE_END + + // STEP_START filter_set_relations + bool res17 = db.JSON().Set("doc", "$", "{\"a\":[[1,2],[1,5],[]]}"); + Console.WriteLine(res17); // >>> True + + RedisResult res18 = db.JSON().Get("doc", path: "$.a[?@ subsetof [1,2,3]]"); + Console.WriteLine(res18); // >>> [[1,2],[]] + + bool res19 = db.JSON().Set("doc", "$", "{\"a\":[[1,9],[8,9],[]]}"); + Console.WriteLine(res19); // >>> True + + RedisResult res20 = db.JSON().Get("doc", path: "$.a[?@ anyof [1,2,3]]"); + Console.WriteLine(res20); // >>> [[1,9]] + + bool res21 = db.JSON().Set("doc", "$", "{\"a\":[[4,5],[1,9],[]]}"); + Console.WriteLine(res21); // >>> True + + RedisResult res22 = db.JSON().Get("doc", path: "$.a[?@ noneof [1,2,3]]"); + Console.WriteLine(res22); // >>> [[4,5],[]] + // STEP_END + + // REMOVE_START + Assert.True(res17); + Assert.Equal("[[1,2],[]]", (string?)res18); + Assert.True(res19); + Assert.Equal("[[1,9]]", (string?)res20); + Assert.True(res21); + Assert.Equal("[[4,5],[]]", (string?)res22); + db.KeyDelete("doc"); + // REMOVE_END + + // STEP_START filter_size_empty + bool res23 = db.JSON().Set("doc", "$", "{\"a\":[[4,5],[1],[7,8,9]]}"); + Console.WriteLine(res23); // >>> True + + RedisResult res24 = db.JSON().Get("doc", path: "$.a[?@ sizeof 2]"); + Console.WriteLine(res24); // >>> [[4,5]] + + bool res25 = db.JSON().Set("doc", "$", "{\"a\":[[],[1],\"\",[2,3],{},{\"k\":1}]}"); + Console.WriteLine(res25); // >>> True + + RedisResult res26 = db.JSON().Get("doc", path: "$.a[?@ empty true]"); + Console.WriteLine(res26); // >>> [[],"",{}] + + RedisResult res27 = db.JSON().Get("doc", path: "$.a[?@ empty false]"); + Console.WriteLine(res27); // >>> [[1],[2,3],{"k":1}] + // STEP_END + + // REMOVE_START + Assert.True(res23); + Assert.Equal("[[4,5]]", (string?)res24); + Assert.True(res25); + Assert.Equal("[[],\"\",{}]", (string?)res26); + Assert.Equal("[[1],[2,3],{\"k\":1}]", (string?)res27); + db.KeyDelete("doc"); + // REMOVE_END + + // STEP_START filter_getkeys + bool res28 = db.JSON().Set( + "doc", + "$", + "{\"obj\":{\"x\":1,\"y\":2},\"books\":[{\"t\":\"a\"},{\"t\":\"b\"}]}" + ); + Console.WriteLine(res28); // >>> True + + RedisResult res29 = db.JSON().Get("doc", path: "$.obj~"); + Console.WriteLine(res29); // >>> ["x","y"] + + RedisResult res30 = db.JSON().Get("doc", path: "$~"); + Console.WriteLine(res30); // >>> ["obj","books"] + + RedisResult res31 = db.JSON().Get("doc", path: "$.books~"); + Console.WriteLine(res31); // >>> [] + // STEP_END + + // REMOVE_START + Assert.True(res28); + Assert.Equal("[\"x\",\"y\"]", (string?)res29); + Assert.Equal("[\"obj\",\"books\"]", (string?)res30); + Assert.Equal("[]", (string?)res31); + db.KeyDelete("doc"); + // REMOVE_END + + + // HIDE_START + } +} +// HIDE_END diff --git a/local_examples/json_path_ops/predis/JsonPathOpsTest.php b/local_examples/json_path_ops/predis/JsonPathOpsTest.php new file mode 100644 index 0000000000..1afecbc588 --- /dev/null +++ b/local_examples/json_path_ops/predis/JsonPathOpsTest.php @@ -0,0 +1,223 @@ +// EXAMPLE: json_path_ops +redis = new PredisClient([ + 'scheme' => 'tcp', + 'host' => '127.0.0.1', + 'port' => 6379, + 'password' => '', + 'database' => 0, + ]); + + // Clean up before each test + $this->redis->flushall(); + } + + public function testJsonPathOps(): void + { + // STEP_START filter_negation + $res1 = $this->redis->jsonset('doc', '$', json_encode( + [["a" => 1, "b" => 1], ["b" => 2], ["a" => 1], ["c" => 3]] + )); + echo $res1 . PHP_EOL; // >>> OK + + $res2 = $this->redis->jsonget('doc', '', '', '', '$[?!@.a]'); + echo $res2 . PHP_EOL; // >>> [{"b":2},{"c":3}] + + $res3 = $this->redis->jsonget('doc', '', '', '', '$[?!(@.a==1)]'); + echo $res3 . PHP_EOL; // >>> [{"b":2},{"c":3}] + + $res4 = $this->redis->jsonget('doc', '', '', '', '$[?!@.a && @.b]'); + echo $res4 . PHP_EOL; // >>> [{"b":2}] + // STEP_END + + // REMOVE_START + $this->assertEquals('OK', $res1); + $this->assertEquals('[{"b":2},{"c":3}]', $res2); + $this->assertEquals('[{"b":2},{"c":3}]', $res3); + $this->assertEquals('[{"b":2}]', $res4); + $this->redis->del('doc'); + // REMOVE_END + + // STEP_START filter_literal_eq + $res5 = $this->redis->jsonset('doc', '$', json_encode([ + "arrs" => [[1], [2], [1, 2], [1, [2]]], + "objs" => [["x" => 1], ["x" => 2], ["y" => 1]], + ])); + echo $res5 . PHP_EOL; // >>> OK + + $res6 = $this->redis->jsonget('doc', '', '', '', '$.arrs[?(@ == [1])]'); + echo $res6 . PHP_EOL; // >>> [[1]] + + $res7 = $this->redis->jsonget('doc', '', '', '', '$.arrs[?(@ == [1,[2]])]'); + echo $res7 . PHP_EOL; // >>> [[1,[2]]] + + $res8 = $this->redis->jsonget('doc', '', '', '', '$.objs[?(@ == {"x":1})]'); + echo $res8 . PHP_EOL; // >>> [{"x":1}] + // STEP_END + + // REMOVE_START + $this->assertEquals('OK', $res5); + $this->assertEquals('[[1]]', $res6); + $this->assertEquals('[[1,[2]]]', $res7); + $this->assertEquals('[{"x":1}]', $res8); + $this->redis->del('doc'); + // REMOVE_END + + // STEP_START filter_arithmetic + $res9 = $this->redis->jsonset('doc', '$', json_encode( + [["a" => 2, "b" => 3], ["a" => 5, "b" => 2]] + )); + echo $res9 . PHP_EOL; // >>> OK + + $res10 = $this->redis->jsonget('doc', '', '', '', '$[?@.a + 1 == 3]'); + echo $res10 . PHP_EOL; // >>> [{"a":2,"b":3}] + + $res11 = $this->redis->jsonget('doc', '', '', '', '$[?@.a + @.b * 2 == 8]'); + echo $res11 . PHP_EOL; // >>> [{"a":2,"b":3}] + + $res12 = $this->redis->jsonget('doc', '', '', '', '$[?(@.a + @.b) * 2 == 10]'); + echo $res12 . PHP_EOL; // >>> [{"a":2,"b":3}] + // STEP_END + + // REMOVE_START + $this->assertEquals('OK', $res9); + $this->assertEquals('[{"a":2,"b":3}]', $res10); + $this->assertEquals('[{"a":2,"b":3}]', $res11); + $this->assertEquals('[{"a":2,"b":3}]', $res12); + $this->redis->del('doc'); + // REMOVE_END + + // STEP_START filter_membership + $res13 = $this->redis->jsonset('doc', '$', json_encode( + ["a" => [1, 2, 3, 4], "allow" => [2, 3]] + )); + echo $res13 . PHP_EOL; // >>> OK + + $res14 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ in [2,4]]'); + echo $res14 . PHP_EOL; // >>> [2,4] + + $res15 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ nin [2,4]]'); + echo $res15 . PHP_EOL; // >>> [1,3] + + $res16 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ in $.allow]'); + echo $res16 . PHP_EOL; // >>> [2,3] + // STEP_END + + // REMOVE_START + $this->assertEquals('OK', $res13); + $this->assertEquals('[2,4]', $res14); + $this->assertEquals('[1,3]', $res15); + $this->assertEquals('[2,3]', $res16); + $this->redis->del('doc'); + // REMOVE_END + + // STEP_START filter_set_relations + $res17 = $this->redis->jsonset('doc', '$', json_encode( + ["a" => [[1, 2], [1, 5], []]] + )); + echo $res17 . PHP_EOL; // >>> OK + + $res18 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ subsetof [1,2,3]]'); + echo $res18 . PHP_EOL; // >>> [[1,2],[]] + + $res19 = $this->redis->jsonset('doc', '$', json_encode( + ["a" => [[1, 9], [8, 9], []]] + )); + echo $res19 . PHP_EOL; // >>> OK + + $res20 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ anyof [1,2,3]]'); + echo $res20 . PHP_EOL; // >>> [[1,9]] + + $res21 = $this->redis->jsonset('doc', '$', json_encode( + ["a" => [[4, 5], [1, 9], []]] + )); + echo $res21 . PHP_EOL; // >>> OK + + $res22 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ noneof [1,2,3]]'); + echo $res22 . PHP_EOL; // >>> [[4,5],[]] + // STEP_END + + // REMOVE_START + $this->assertEquals('OK', $res17); + $this->assertEquals('[[1,2],[]]', $res18); + $this->assertEquals('OK', $res19); + $this->assertEquals('[[1,9]]', $res20); + $this->assertEquals('OK', $res21); + $this->assertEquals('[[4,5],[]]', $res22); + $this->redis->del('doc'); + // REMOVE_END + + // STEP_START filter_size_empty + $res23 = $this->redis->jsonset('doc', '$', json_encode( + ["a" => [[4, 5], [1], [7, 8, 9]]] + )); + echo $res23 . PHP_EOL; // >>> OK + + $res24 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ sizeof 2]'); + echo $res24 . PHP_EOL; // >>> [[4,5]] + + $res25 = $this->redis->jsonset('doc', '$', json_encode( + ["a" => [[], [1], "", [2, 3], new \stdClass(), ["k" => 1]]] + )); + echo $res25 . PHP_EOL; // >>> OK + + $res26 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ empty true]'); + echo $res26 . PHP_EOL; // >>> [[],"",{}] + + $res27 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ empty false]'); + echo $res27 . PHP_EOL; // >>> [[1],[2,3],{"k":1}] + // STEP_END + + // REMOVE_START + $this->assertEquals('OK', $res23); + $this->assertEquals('[[4,5]]', $res24); + $this->assertEquals('OK', $res25); + $this->assertEquals('[[],"",{}]', $res26); + $this->assertEquals('[[1],[2,3],{"k":1}]', $res27); + $this->redis->del('doc'); + // REMOVE_END + + // STEP_START filter_getkeys + $res28 = $this->redis->jsonset('doc', '$', json_encode([ + "obj" => ["x" => 1, "y" => 2], + "books" => [["t" => "a"], ["t" => "b"]], + ])); + echo $res28 . PHP_EOL; // >>> OK + + $res29 = $this->redis->jsonget('doc', '', '', '', '$.obj~'); + echo $res29 . PHP_EOL; // >>> ["x","y"] + + $res30 = $this->redis->jsonget('doc', '', '', '', '$~'); + echo $res30 . PHP_EOL; // >>> ["obj","books"] + + $res31 = $this->redis->jsonget('doc', '', '', '', '$.books~'); + echo $res31 . PHP_EOL; // >>> [] + // STEP_END + + // REMOVE_START + $this->assertEquals('OK', $res28); + $this->assertEquals('["x","y"]', $res29); + $this->assertEquals('["obj","books"]', $res30); + $this->assertEquals('[]', $res31); + $this->redis->del('doc'); + // REMOVE_END + + } + + protected function tearDown(): void + { + // Clean up after each test + $this->redis->flushall(); + $this->redis->disconnect(); + } +} diff --git a/local_examples/json_path_ops/redis-py/json_path_ops.py b/local_examples/json_path_ops/redis-py/json_path_ops.py new file mode 100644 index 0000000000..430ed2b265 --- /dev/null +++ b/local_examples/json_path_ops/redis-py/json_path_ops.py @@ -0,0 +1,201 @@ +# EXAMPLE: json_path_ops +# HIDE_START +import redis + +r = redis.Redis(decode_responses=True) +# HIDE_END + +# REMOVE_START +r.delete("doc") +# REMOVE_END + +# STEP_START filter_negation +res1 = r.json().set("doc", "$", [{"a": 1, "b": 1}, {"b": 2}, {"a": 1}, {"c": 3}]) +print(res1) +# >>> True + +res2 = r.json().get("doc", "$[?!@.a]") +print(res2) +# >>> [{'b': 2}, {'c': 3}] + +res3 = r.json().get("doc", "$[?!(@.a==1)]") +print(res3) +# >>> [{'b': 2}, {'c': 3}] + +res4 = r.json().get("doc", "$[?!@.a && @.b]") +print(res4) +# >>> [{'b': 2}] +# STEP_END + +# REMOVE_START +assert res2 == [{"b": 2}, {"c": 3}] +assert res3 == [{"b": 2}, {"c": 3}] +assert res4 == [{"b": 2}] +r.delete("doc") +# REMOVE_END + +# STEP_START filter_literal_eq +res1 = r.json().set( + "doc", + "$", + {"arrs": [[1], [2], [1, 2], [1, [2]]], "objs": [{"x": 1}, {"x": 2}, {"y": 1}]}, +) +print(res1) +# >>> True + +res2 = r.json().get("doc", "$.arrs[?(@ == [1])]") +print(res2) +# >>> [[1]] + +res3 = r.json().get("doc", "$.arrs[?(@ == [1,[2]])]") +print(res3) +# >>> [[1, [2]]] + +res4 = r.json().get("doc", '$.objs[?(@ == {"x":1})]') +print(res4) +# >>> [{'x': 1}] +# STEP_END + +# REMOVE_START +assert res2 == [[1]] +assert res3 == [[1, [2]]] +assert res4 == [{"x": 1}] +r.delete("doc") +# REMOVE_END + +# STEP_START filter_arithmetic +res1 = r.json().set("doc", "$", [{"a": 2, "b": 3}, {"a": 5, "b": 2}]) +print(res1) +# >>> True + +res2 = r.json().get("doc", "$[?@.a + 1 == 3]") +print(res2) +# >>> [{'a': 2, 'b': 3}] + +res3 = r.json().get("doc", "$[?@.a + @.b * 2 == 8]") +print(res3) +# >>> [{'a': 2, 'b': 3}] + +res4 = r.json().get("doc", "$[?(@.a + @.b) * 2 == 10]") +print(res4) +# >>> [{'a': 2, 'b': 3}] +# STEP_END + +# REMOVE_START +assert res2 == [{"a": 2, "b": 3}] +assert res3 == [{"a": 2, "b": 3}] +assert res4 == [{"a": 2, "b": 3}] +r.delete("doc") +# REMOVE_END + +# STEP_START filter_membership +res1 = r.json().set("doc", "$", {"a": [1, 2, 3, 4], "allow": [2, 3]}) +print(res1) +# >>> True + +res2 = r.json().get("doc", "$.a[?@ in [2,4]]") +print(res2) +# >>> [2, 4] + +res3 = r.json().get("doc", "$.a[?@ nin [2,4]]") +print(res3) +# >>> [1, 3] + +res4 = r.json().get("doc", "$.a[?@ in $.allow]") +print(res4) +# >>> [2, 3] +# STEP_END + +# REMOVE_START +assert res2 == [2, 4] +assert res3 == [1, 3] +assert res4 == [2, 3] +r.delete("doc") +# REMOVE_END + +# STEP_START filter_set_relations +res1 = r.json().set("doc", "$", {"a": [[1, 2], [1, 5], []]}) +print(res1) +# >>> True + +res2 = r.json().get("doc", "$.a[?@ subsetof [1,2,3]]") +print(res2) +# >>> [[1, 2], []] + +res3 = r.json().set("doc", "$", {"a": [[1, 9], [8, 9], []]}) +print(res3) +# >>> True + +res4 = r.json().get("doc", "$.a[?@ anyof [1,2,3]]") +print(res4) +# >>> [[1, 9]] + +res5 = r.json().set("doc", "$", {"a": [[4, 5], [1, 9], []]}) +print(res5) +# >>> True + +res6 = r.json().get("doc", "$.a[?@ noneof [1,2,3]]") +print(res6) +# >>> [[4, 5], []] +# STEP_END + +# REMOVE_START +assert res2 == [[1, 2], []] +assert res4 == [[1, 9]] +assert res6 == [[4, 5], []] +r.delete("doc") +# REMOVE_END + +# STEP_START filter_size_empty +res1 = r.json().set("doc", "$", {"a": [[4, 5], [1], [7, 8, 9]]}) +print(res1) +# >>> True + +res2 = r.json().get("doc", "$.a[?@ sizeof 2]") +print(res2) +# >>> [[4, 5]] + +res3 = r.json().set("doc", "$", {"a": [[], [1], "", [2, 3], {}, {"k": 1}]}) +print(res3) +# >>> True + +res4 = r.json().get("doc", "$.a[?@ empty true]") +print(res4) +# >>> [[], '', {}] + +res5 = r.json().get("doc", "$.a[?@ empty false]") +print(res5) +# >>> [[1], [2, 3], {'k': 1}] +# STEP_END + +# REMOVE_START +assert res2 == [[4, 5]] +assert res4 == [[], "", {}] +assert res5 == [[1], [2, 3], {"k": 1}] +r.delete("doc") +# REMOVE_END + +# STEP_START filter_getkeys +res1 = r.json().set("doc", "$", {"obj": {"x": 1, "y": 2}, "books": [{"t": "a"}, {"t": "b"}]}) +print(res1) +# >>> True + +res2 = r.json().get("doc", "$.obj~") +print(res2) +# >>> ['x', 'y'] + +res3 = r.json().get("doc", "$~") +print(res3) +# >>> ['obj', 'books'] + +res4 = r.json().get("doc", "$.books~") +print(res4) +# >>> [] +# STEP_END + +# REMOVE_START +assert res2 == ["x", "y"] +assert res3 == ["obj", "books"] +assert res4 == [] +r.delete("doc") +# REMOVE_END diff --git a/local_examples/json_path_ops/ruby/json_path_ops.rb b/local_examples/json_path_ops/ruby/json_path_ops.rb new file mode 100644 index 0000000000..717377fe3d --- /dev/null +++ b/local_examples/json_path_ops/ruby/json_path_ops.rb @@ -0,0 +1,174 @@ +# EXAMPLE: json_path_ops +# HIDE_START +require 'redis' + +r = Redis.new +# HIDE_END + +# REMOVE_START +def assert_equal(expected, actual) + raise "Expected #{expected.inspect}, got #{actual.inspect}" unless actual == expected +end + +r.del('doc') +# REMOVE_END + +# STEP_START filter_negation +res1 = r.json_set('doc', '$', [{ 'a' => 1, 'b' => 1 }, { 'b' => 2 }, { 'a' => 1 }, { 'c' => 3 }]) +puts res1 # >>> OK + +res2 = r.json_get('doc', '$[?!@.a]') +p res2 # >>> [{"b"=>2}, {"c"=>3}] + +res3 = r.json_get('doc', '$[?!(@.a==1)]') +p res3 # >>> [{"b"=>2}, {"c"=>3}] + +res4 = r.json_get('doc', '$[?!@.a && @.b]') +p res4 # >>> [{"b"=>2}] +# STEP_END + +# REMOVE_START +assert_equal([{ 'b' => 2 }, { 'c' => 3 }], res2) +assert_equal([{ 'b' => 2 }, { 'c' => 3 }], res3) +assert_equal([{ 'b' => 2 }], res4) +r.del('doc') +# REMOVE_END + +# STEP_START filter_literal_eq +res1 = r.json_set( + 'doc', '$', + { 'arrs' => [[1], [2], [1, 2], [1, [2]]], 'objs' => [{ 'x' => 1 }, { 'x' => 2 }, { 'y' => 1 }] } +) +puts res1 # >>> OK + +res2 = r.json_get('doc', '$.arrs[?(@ == [1])]') +p res2 # >>> [[1]] + +res3 = r.json_get('doc', '$.arrs[?(@ == [1,[2]])]') +p res3 # >>> [[1, [2]]] + +res4 = r.json_get('doc', '$.objs[?(@ == {"x":1})]') +p res4 # >>> [{"x"=>1}] +# STEP_END + +# REMOVE_START +assert_equal([[1]], res2) +assert_equal([[1, [2]]], res3) +assert_equal([{ 'x' => 1 }], res4) +r.del('doc') +# REMOVE_END + +# STEP_START filter_arithmetic +res1 = r.json_set('doc', '$', [{ 'a' => 2, 'b' => 3 }, { 'a' => 5, 'b' => 2 }]) +puts res1 # >>> OK + +res2 = r.json_get('doc', '$[?@.a + 1 == 3]') +p res2 # >>> [{"a"=>2, "b"=>3}] + +res3 = r.json_get('doc', '$[?@.a + @.b * 2 == 8]') +p res3 # >>> [{"a"=>2, "b"=>3}] + +res4 = r.json_get('doc', '$[?(@.a + @.b) * 2 == 10]') +p res4 # >>> [{"a"=>2, "b"=>3}] +# STEP_END + +# REMOVE_START +assert_equal([{ 'a' => 2, 'b' => 3 }], res2) +assert_equal([{ 'a' => 2, 'b' => 3 }], res3) +assert_equal([{ 'a' => 2, 'b' => 3 }], res4) +r.del('doc') +# REMOVE_END + +# STEP_START filter_membership +res1 = r.json_set('doc', '$', { 'a' => [1, 2, 3, 4], 'allow' => [2, 3] }) +puts res1 # >>> OK + +res2 = r.json_get('doc', '$.a[?@ in [2,4]]') +p res2 # >>> [2, 4] + +res3 = r.json_get('doc', '$.a[?@ nin [2,4]]') +p res3 # >>> [1, 3] + +res4 = r.json_get('doc', '$.a[?@ in $.allow]') +p res4 # >>> [2, 3] +# STEP_END + +# REMOVE_START +assert_equal([2, 4], res2) +assert_equal([1, 3], res3) +assert_equal([2, 3], res4) +r.del('doc') +# REMOVE_END + +# STEP_START filter_set_relations +res1 = r.json_set('doc', '$', { 'a' => [[1, 2], [1, 5], []] }) +puts res1 # >>> OK + +res2 = r.json_get('doc', '$.a[?@ subsetof [1,2,3]]') +p res2 # >>> [[1, 2], []] + +res3 = r.json_set('doc', '$', { 'a' => [[1, 9], [8, 9], []] }) +puts res3 # >>> OK + +res4 = r.json_get('doc', '$.a[?@ anyof [1,2,3]]') +p res4 # >>> [[1, 9]] + +res5 = r.json_set('doc', '$', { 'a' => [[4, 5], [1, 9], []] }) +puts res5 # >>> OK + +res6 = r.json_get('doc', '$.a[?@ noneof [1,2,3]]') +p res6 # >>> [[4, 5], []] +# STEP_END + +# REMOVE_START +assert_equal([[1, 2], []], res2) +assert_equal([[1, 9]], res4) +assert_equal([[4, 5], []], res6) +r.del('doc') +# REMOVE_END + +# STEP_START filter_size_empty +res1 = r.json_set('doc', '$', { 'a' => [[4, 5], [1], [7, 8, 9]] }) +puts res1 # >>> OK + +res2 = r.json_get('doc', '$.a[?@ sizeof 2]') +p res2 # >>> [[4, 5]] + +res3 = r.json_set('doc', '$', { 'a' => [[], [1], '', [2, 3], {}, { 'k' => 1 }] }) +puts res3 # >>> OK + +res4 = r.json_get('doc', '$.a[?@ empty true]') +p res4 # >>> [[], "", {}] + +res5 = r.json_get('doc', '$.a[?@ empty false]') +p res5 # >>> [[1], [2, 3], {"k"=>1}] +# STEP_END + +# REMOVE_START +assert_equal([[4, 5]], res2) +assert_equal([[], '', {}], res4) +assert_equal([[1], [2, 3], { 'k' => 1 }], res5) +r.del('doc') +# REMOVE_END + +# STEP_START filter_getkeys +res1 = r.json_set('doc', '$', { 'obj' => { 'x' => 1, 'y' => 2 }, 'books' => [{ 't' => 'a' }, { 't' => 'b' }] }) +puts res1 # >>> OK + +res2 = r.json_get('doc', '$.obj~') +p res2 # >>> ["x", "y"] + +res3 = r.json_get('doc', '$~') +p res3 # >>> ["obj", "books"] + +res4 = r.json_get('doc', '$.books~') +p res4 # >>> [] +# STEP_END + +# REMOVE_START +assert_equal(%w[x y], res2) +assert_equal(%w[obj books], res3) +assert_equal([], res4) +r.del('doc') +r.close +# REMOVE_END diff --git a/local_examples/json_path_ops/rust-async/json_path_ops.rs b/local_examples/json_path_ops/rust-async/json_path_ops.rs new file mode 100644 index 0000000000..92a637ab23 --- /dev/null +++ b/local_examples/json_path_ops/rust-async/json_path_ops.rs @@ -0,0 +1,453 @@ +// EXAMPLE: json_path_ops +#[cfg(test)] +mod json_path_ops_tests { + use redis::{AsyncCommands, JsonAsyncCommands}; + use serde_json::json; + + #[tokio::test] + async fn run() { + let mut r = match redis::Client::open("redis://127.0.0.1") { + Ok(client) => { + match client.get_multiplexed_async_connection().await { + Ok(conn) => conn, + Err(e) => { + println!("Failed to connect to Redis: {e}"); + return; + } + } + }, + Err(e) => { + println!("Failed to create Redis client: {e}"); + return; + } + }; + + // Clean up any existing data + let _: Result = r.del("doc").await; + + // STEP_START filter_negation + let _: bool = match r.json_set("doc", "$", &json!([{"a":1,"b":1},{"b":2},{"a":1},{"c":3}])).await { + Ok(v) => v, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + }; + + match r.json_get("doc", "$[?!@.a]").await { + Ok(res1) => { + let res1: String = res1; + println!("{res1}"); // >>> [{"b":2},{"c":3}] + // REMOVE_START + assert_eq!(res1, r#"[{"b":2},{"c":3}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$[?!(@.a==1)]").await { + Ok(res2) => { + let res2: String = res2; + println!("{res2}"); // >>> [{"b":2},{"c":3}] + // REMOVE_START + assert_eq!(res2, r#"[{"b":2},{"c":3}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$[?!@.a && @.b]").await { + Ok(res3) => { + let res3: String = res3; + println!("{res3}"); // >>> [{"b":2}] + // REMOVE_START + assert_eq!(res3, r#"[{"b":2}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc").await; + // REMOVE_END + // STEP_END + + // STEP_START filter_literal_eq + let _: bool = match r.json_set( + "doc", + "$", + &json!({"arrs":[[1],[2],[1,2],[1,[2]]],"objs":[{"x":1},{"x":2},{"y":1}]}), + ).await { + Ok(v) => v, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + }; + + match r.json_get("doc", "$.arrs[?(@ == [1])]").await { + Ok(res4) => { + let res4: String = res4; + println!("{res4}"); // >>> [[1]] + // REMOVE_START + assert_eq!(res4, "[[1]]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.arrs[?(@ == [1,[2]])]").await { + Ok(res5) => { + let res5: String = res5; + println!("{res5}"); // >>> [[1,[2]]] + // REMOVE_START + assert_eq!(res5, "[[1,[2]]]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", r#"$.objs[?(@ == {"x":1})]"#).await { + Ok(res6) => { + let res6: String = res6; + println!("{res6}"); // >>> [{"x":1}] + // REMOVE_START + assert_eq!(res6, r#"[{"x":1}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc").await; + // REMOVE_END + // STEP_END + + // STEP_START filter_arithmetic + let _: bool = match r.json_set("doc", "$", &json!([{"a":2,"b":3},{"a":5,"b":2}])).await { + Ok(v) => v, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + }; + + match r.json_get("doc", "$[?@.a + 1 == 3]").await { + Ok(res7) => { + let res7: String = res7; + println!("{res7}"); // >>> [{"a":2,"b":3}] + // REMOVE_START + assert_eq!(res7, r#"[{"a":2,"b":3}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$[?@.a + @.b * 2 == 8]").await { + Ok(res8) => { + let res8: String = res8; + println!("{res8}"); // >>> [{"a":2,"b":3}] + // REMOVE_START + assert_eq!(res8, r#"[{"a":2,"b":3}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$[?(@.a + @.b) * 2 == 10]").await { + Ok(res9) => { + let res9: String = res9; + println!("{res9}"); // >>> [{"a":2,"b":3}] + // REMOVE_START + assert_eq!(res9, r#"[{"a":2,"b":3}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc").await; + // REMOVE_END + // STEP_END + + // STEP_START filter_membership + let _: bool = match r.json_set("doc", "$", &json!({"a":[1,2,3,4],"allow":[2,3]})).await { + Ok(v) => v, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + }; + + match r.json_get("doc", "$.a[?@ in [2,4]]").await { + Ok(res10) => { + let res10: String = res10; + println!("{res10}"); // >>> [2,4] + // REMOVE_START + assert_eq!(res10, "[2,4]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.a[?@ nin [2,4]]").await { + Ok(res11) => { + let res11: String = res11; + println!("{res11}"); // >>> [1,3] + // REMOVE_START + assert_eq!(res11, "[1,3]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.a[?@ in $.allow]").await { + Ok(res12) => { + let res12: String = res12; + println!("{res12}"); // >>> [2,3] + // REMOVE_START + assert_eq!(res12, "[2,3]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc").await; + // REMOVE_END + // STEP_END + + // STEP_START filter_set_relations + let _: bool = match r.json_set("doc", "$", &json!({"a":[[1,2],[1,5],[]]})).await { + Ok(v) => v, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + }; + + match r.json_get("doc", "$.a[?@ subsetof [1,2,3]]").await { + Ok(res13) => { + let res13: String = res13; + println!("{res13}"); // >>> [[1,2],[]] + // REMOVE_START + assert_eq!(res13, "[[1,2],[]]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + let _: bool = match r.json_set("doc", "$", &json!({"a":[[1,9],[8,9],[]]})).await { + Ok(v) => v, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + }; + + match r.json_get("doc", "$.a[?@ anyof [1,2,3]]").await { + Ok(res14) => { + let res14: String = res14; + println!("{res14}"); // >>> [[1,9]] + // REMOVE_START + assert_eq!(res14, "[[1,9]]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + let _: bool = match r.json_set("doc", "$", &json!({"a":[[4,5],[1,9],[]]})).await { + Ok(v) => v, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + }; + + match r.json_get("doc", "$.a[?@ noneof [1,2,3]]").await { + Ok(res15) => { + let res15: String = res15; + println!("{res15}"); // >>> [[4,5],[]] + // REMOVE_START + assert_eq!(res15, "[[4,5],[]]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc").await; + // REMOVE_END + // STEP_END + + // STEP_START filter_size_empty + let _: bool = match r.json_set("doc", "$", &json!({"a":[[4,5],[1],[7,8,9]]})).await { + Ok(v) => v, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + }; + + match r.json_get("doc", "$.a[?@ sizeof 2]").await { + Ok(res16) => { + let res16: String = res16; + println!("{res16}"); // >>> [[4,5]] + // REMOVE_START + assert_eq!(res16, "[[4,5]]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + let _: bool = match r.json_set("doc", "$", &json!({"a":[[],[1],"",[2,3],{},{"k":1}]})).await { + Ok(v) => v, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + }; + + match r.json_get("doc", "$.a[?@ empty true]").await { + Ok(res17) => { + let res17: String = res17; + println!("{res17}"); // >>> [[],"",{}] + // REMOVE_START + assert_eq!(res17, r#"[[],"",{}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.a[?@ empty false]").await { + Ok(res18) => { + let res18: String = res18; + println!("{res18}"); // >>> [[1],[2,3],{"k":1}] + // REMOVE_START + assert_eq!(res18, r#"[[1],[2,3],{"k":1}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc").await; + // REMOVE_END + // STEP_END + + // STEP_START filter_getkeys + let _: bool = match r.json_set( + "doc", + "$", + &json!({"obj":{"x":1,"y":2},"books":[{"t":"a"},{"t":"b"}]}), + ).await { + Ok(v) => v, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + }; + + match r.json_get("doc", "$.obj~").await { + Ok(res19) => { + let res19: String = res19; + println!("{res19}"); // >>> ["x","y"] + // REMOVE_START + assert_eq!(res19, r#"["x","y"]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$~").await { + Ok(res20) => { + let res20: String = res20; + println!("{res20}"); // >>> ["obj","books"] + // REMOVE_START + assert_eq!(res20, r#"["obj","books"]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.books~").await { + Ok(res21) => { + let res21: String = res21; + println!("{res21}"); // >>> [] + // REMOVE_START + assert_eq!(res21, "[]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc").await; + // REMOVE_END + // STEP_END + + } +} diff --git a/local_examples/json_path_ops/rust-sync/json_path_ops.rs b/local_examples/json_path_ops/rust-sync/json_path_ops.rs new file mode 100644 index 0000000000..b9e38ebdef --- /dev/null +++ b/local_examples/json_path_ops/rust-sync/json_path_ops.rs @@ -0,0 +1,513 @@ +// EXAMPLE: json_path_ops +#[cfg(test)] +mod json_path_ops_tests { + use redis::{Commands, JsonCommands}; + use serde_json::json; + + #[test] + fn run() { + let mut r = match redis::Client::open("redis://127.0.0.1") { + Ok(client) => { + match client.get_connection() { + Ok(conn) => conn, + Err(e) => { + println!("Failed to connect to Redis: {e}"); + return; + } + } + }, + Err(e) => { + println!("Failed to create Redis client: {e}"); + return; + } + }; + + // Clean up any existing data + let _: Result = r.del("doc"); + + // STEP_START filter_negation + match r.json_set("doc", "$", &json!([{"a":1,"b":1},{"b":2},{"a":1},{"c":3}])) { + Ok(res1) => { + let res1: bool = res1; + println!("{}", if res1 { "OK" } else { "(nil)" }); // >>> OK + // REMOVE_START + assert!(res1); + // REMOVE_END + }, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$[?!@.a]") { + Ok(res2) => { + let res2: String = res2; + println!("{res2}"); // >>> [{"b":2},{"c":3}] + // REMOVE_START + assert_eq!(res2, r#"[{"b":2},{"c":3}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$[?!(@.a==1)]") { + Ok(res3) => { + let res3: String = res3; + println!("{res3}"); // >>> [{"b":2},{"c":3}] + // REMOVE_START + assert_eq!(res3, r#"[{"b":2},{"c":3}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$[?!@.a && @.b]") { + Ok(res4) => { + let res4: String = res4; + println!("{res4}"); // >>> [{"b":2}] + // REMOVE_START + assert_eq!(res4, r#"[{"b":2}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc"); + // REMOVE_END + // STEP_END + + // STEP_START filter_literal_eq + match r.json_set( + "doc", + "$", + &json!({"arrs":[[1],[2],[1,2],[1,[2]]],"objs":[{"x":1},{"x":2},{"y":1}]}), + ) { + Ok(res5) => { + let res5: bool = res5; + println!("{}", if res5 { "OK" } else { "(nil)" }); // >>> OK + // REMOVE_START + assert!(res5); + // REMOVE_END + }, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.arrs[?(@ == [1])]") { + Ok(res6) => { + let res6: String = res6; + println!("{res6}"); // >>> [[1]] + // REMOVE_START + assert_eq!(res6, "[[1]]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.arrs[?(@ == [1,[2]])]") { + Ok(res7) => { + let res7: String = res7; + println!("{res7}"); // >>> [[1,[2]]] + // REMOVE_START + assert_eq!(res7, "[[1,[2]]]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", r#"$.objs[?(@ == {"x":1})]"#) { + Ok(res8) => { + let res8: String = res8; + println!("{res8}"); // >>> [{"x":1}] + // REMOVE_START + assert_eq!(res8, r#"[{"x":1}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc"); + // REMOVE_END + // STEP_END + + // STEP_START filter_arithmetic + match r.json_set("doc", "$", &json!([{"a":2,"b":3},{"a":5,"b":2}])) { + Ok(res9) => { + let res9: bool = res9; + println!("{}", if res9 { "OK" } else { "(nil)" }); // >>> OK + // REMOVE_START + assert!(res9); + // REMOVE_END + }, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$[?@.a + 1 == 3]") { + Ok(res10) => { + let res10: String = res10; + println!("{res10}"); // >>> [{"a":2,"b":3}] + // REMOVE_START + assert_eq!(res10, r#"[{"a":2,"b":3}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$[?@.a + @.b * 2 == 8]") { + Ok(res11) => { + let res11: String = res11; + println!("{res11}"); // >>> [{"a":2,"b":3}] + // REMOVE_START + assert_eq!(res11, r#"[{"a":2,"b":3}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$[?(@.a + @.b) * 2 == 10]") { + Ok(res12) => { + let res12: String = res12; + println!("{res12}"); // >>> [{"a":2,"b":3}] + // REMOVE_START + assert_eq!(res12, r#"[{"a":2,"b":3}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc"); + // REMOVE_END + // STEP_END + + // STEP_START filter_membership + match r.json_set("doc", "$", &json!({"a":[1,2,3,4],"allow":[2,3]})) { + Ok(res13) => { + let res13: bool = res13; + println!("{}", if res13 { "OK" } else { "(nil)" }); // >>> OK + // REMOVE_START + assert!(res13); + // REMOVE_END + }, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.a[?@ in [2,4]]") { + Ok(res14) => { + let res14: String = res14; + println!("{res14}"); // >>> [2,4] + // REMOVE_START + assert_eq!(res14, "[2,4]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.a[?@ nin [2,4]]") { + Ok(res15) => { + let res15: String = res15; + println!("{res15}"); // >>> [1,3] + // REMOVE_START + assert_eq!(res15, "[1,3]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.a[?@ in $.allow]") { + Ok(res16) => { + let res16: String = res16; + println!("{res16}"); // >>> [2,3] + // REMOVE_START + assert_eq!(res16, "[2,3]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc"); + // REMOVE_END + // STEP_END + + // STEP_START filter_set_relations + match r.json_set("doc", "$", &json!({"a":[[1,2],[1,5],[]]})) { + Ok(res17) => { + let res17: bool = res17; + println!("{}", if res17 { "OK" } else { "(nil)" }); // >>> OK + // REMOVE_START + assert!(res17); + // REMOVE_END + }, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.a[?@ subsetof [1,2,3]]") { + Ok(res18) => { + let res18: String = res18; + println!("{res18}"); // >>> [[1,2],[]] + // REMOVE_START + assert_eq!(res18, "[[1,2],[]]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_set("doc", "$", &json!({"a":[[1,9],[8,9],[]]})) { + Ok(res19) => { + let res19: bool = res19; + println!("{}", if res19 { "OK" } else { "(nil)" }); // >>> OK + // REMOVE_START + assert!(res19); + // REMOVE_END + }, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.a[?@ anyof [1,2,3]]") { + Ok(res20) => { + let res20: String = res20; + println!("{res20}"); // >>> [[1,9]] + // REMOVE_START + assert_eq!(res20, "[[1,9]]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_set("doc", "$", &json!({"a":[[4,5],[1,9],[]]})) { + Ok(res21) => { + let res21: bool = res21; + println!("{}", if res21 { "OK" } else { "(nil)" }); // >>> OK + // REMOVE_START + assert!(res21); + // REMOVE_END + }, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.a[?@ noneof [1,2,3]]") { + Ok(res22) => { + let res22: String = res22; + println!("{res22}"); // >>> [[4,5],[]] + // REMOVE_START + assert_eq!(res22, "[[4,5],[]]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc"); + // REMOVE_END + // STEP_END + + // STEP_START filter_size_empty + match r.json_set("doc", "$", &json!({"a":[[4,5],[1],[7,8,9]]})) { + Ok(res23) => { + let res23: bool = res23; + println!("{}", if res23 { "OK" } else { "(nil)" }); // >>> OK + // REMOVE_START + assert!(res23); + // REMOVE_END + }, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.a[?@ sizeof 2]") { + Ok(res24) => { + let res24: String = res24; + println!("{res24}"); // >>> [[4,5]] + // REMOVE_START + assert_eq!(res24, "[[4,5]]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_set("doc", "$", &json!({"a":[[],[1],"",[2,3],{},{"k":1}]})) { + Ok(res25) => { + let res25: bool = res25; + println!("{}", if res25 { "OK" } else { "(nil)" }); // >>> OK + // REMOVE_START + assert!(res25); + // REMOVE_END + }, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.a[?@ empty true]") { + Ok(res26) => { + let res26: String = res26; + println!("{res26}"); // >>> [[],"",{}] + // REMOVE_START + assert_eq!(res26, r#"[[],"",{}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.a[?@ empty false]") { + Ok(res27) => { + let res27: String = res27; + println!("{res27}"); // >>> [[1],[2,3],{"k":1}] + // REMOVE_START + assert_eq!(res27, r#"[[1],[2,3],{"k":1}]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc"); + // REMOVE_END + // STEP_END + + // STEP_START filter_getkeys + match r.json_set( + "doc", + "$", + &json!({"obj":{"x":1,"y":2},"books":[{"t":"a"},{"t":"b"}]}), + ) { + Ok(res28) => { + let res28: bool = res28; + println!("{}", if res28 { "OK" } else { "(nil)" }); // >>> OK + // REMOVE_START + assert!(res28); + // REMOVE_END + }, + Err(e) => { + println!("Error setting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.obj~") { + Ok(res29) => { + let res29: String = res29; + println!("{res29}"); // >>> ["x","y"] + // REMOVE_START + assert_eq!(res29, r#"["x","y"]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$~") { + Ok(res30) => { + let res30: String = res30; + println!("{res30}"); // >>> ["obj","books"] + // REMOVE_START + assert_eq!(res30, r#"["obj","books"]"#); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + match r.json_get("doc", "$.books~") { + Ok(res31) => { + let res31: String = res31; + println!("{res31}"); // >>> [] + // REMOVE_START + assert_eq!(res31, "[]"); + // REMOVE_END + }, + Err(e) => { + println!("Error getting doc: {e}"); + return; + } + } + + // REMOVE_START + let _: Result = r.del("doc"); + // REMOVE_END + // STEP_END + + } +} From c78a71f409a9befaf455e95952c91182845350c6 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Fri, 4 Sep 2026 14:37:31 +0100 Subject: [PATCH 2/3] DOC-7032: Fold operator descriptions into their Filter examples entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove "## Filter expression operators" as a separate section — move each operator's description to sit directly above its runnable example under Filter examples, so a reader sees what an operator does and how to run it in one place instead of jumping between two sections. Link each operator from the "Beginning with Redis 8.10..." list under JSONPath syntax instead. Co-Authored-By: Claude Sonnet 5 --- content/develop/data-types/json/path.md | 90 ++++++++++--------------- 1 file changed, 35 insertions(+), 55 deletions(-) diff --git a/content/develop/data-types/json/path.md b/content/develop/data-types/json/path.md index b79b168068..276c17c697 100644 --- a/content/develop/data-types/json/path.md +++ b/content/develop/data-types/json/path.md @@ -64,13 +64,13 @@ The following JSONPath syntax table was adapted from Goessner's [path syntax com Beginning with Redis 8.10, the JSON data type supports a richer JSONPath syntax, with additional operators and functions: -- Projection expressions at the top level of a JSONPath query -- `==` and `!=` can now compare any literal, including array and object literals -- Filter negation operator: `!` -- `size`/`sizeof` and `empty` operators on string, array, object, and nodelist -- `in` and `nin` operators: membership test on an array and nodelist -- Operators on numbers: binary `-`, `+`, `*`, `/`, `%`, and unary `-` and `+` -- Operator on object: `~` +- [Projection expressions](#projection-expressions) at the top level of a JSONPath query +- [`==` and `!=`](#comparing-array-and-object-literals) can now compare any literal, including array and object literals +- Filter negation operator: [`!`](#negation-) +- [`size`/`sizeof` and `empty`](#size-sizeof-and-empty) operators on string, array, object, and nodelist +- [`in` and `nin`](#membership-in-and-nin) operators: membership test on an array and nodelist +- [Operators on numbers](#arithmetic-operators): binary `-`, `+`, `*`, `/`, `%`, and unary `-` and `+` +- Operator on object: [`~`](#get-keys-operator-) - `length()` function on array, object, and string - Functions on number: `abs()`, `ceiling()`, `floor()` - Functions on string: `match()`, `search()` @@ -80,7 +80,9 @@ Beginning with Redis 8.10, the JSON data type supports a richer JSONPath syntax, - Function on object: `keys()` - Function on nodelist: `count()` - Function on nodelist with exactly one node: `value()` -- Relations functions on array and nodelist: `subsetof()`, `anyof()`, `noneof()` +- Relations functions on array and nodelist: [`subsetof()`, `anyof()`, `noneof()`](#set-relations-subsetof-anyof-and-noneof) + +These operators can be used within a filter expression (`?()`). {{< warning >}} Beginning with Redis 8.10, two changes to path parsing may affect existing queries: @@ -99,50 +101,6 @@ The following rules apply to the operators and functions described below: - **Arithmetic operators must be surrounded by spaces.** `@.a + 1` is addition, but `@.a+1` is a reference to a field literally named `a+1`, because the field-name character set includes characters such as `+`, `-`, `/`, `%`, `$`, `^`, `:`, and `_`. - **Functions are arity-checked.** Calling a function with the wrong number of arguments produces *Nothing* rather than silently using a subset of the arguments. -## Filter expression operators - -The following operators can be used within a filter expression (`?()`). - -### Negation `!` - -The `!` operator negates a filter condition. It binds more tightly than the logical `&&` and `||` operators, so use parentheses to negate a compound condition. A double negation (`!!`) is equivalent to an existence test. - -### Comparing array and object literals - -The `==` and `!=` operators can compare any JSON literal, including array and object literals, on either side of the operator. Comparison is by structural (deep) equality. A comparison between values of different types is always false. - -### Arithmetic operators - -The binary operators `+`, `-`, `*`, `/`, and `%` and the unary operators `-` and `+` operate on numbers. Precedence, from highest to lowest, is: unary `-`/`+`, then `*`/`/`/`%`, then `+`/`-`. Use parentheses to override precedence. Division always produces a floating-point result. Division or modulo by zero produces *Nothing*, so no element matches. - -Remember that arithmetic operators must be surrounded by spaces (see [Evaluation semantics](#evaluation-semantics)). - -### Membership: `in` and `nin` - -`value in array` matches when `value` is a member of `array`; `value nin array` is its strict negation. The right-hand side can be an array literal or a path to an array. Membership uses the same structural comparison and number coercion as `==`. If the right-hand side is not an array, `in` is false (so `nin` is true). - -### Set relations: `subsetof`, `anyof`, and `noneof` - -These operators compare two arrays (each side can be an array literal or a path to an array): - -- `subsetof` matches when every element of the left array is a member of the right array. An empty array is a subset of any array. -- `anyof` matches when the two arrays have a non-empty intersection. An empty array never matches. -- `noneof` matches when the two arrays have an empty intersection. An empty array always matches. - -### `size` (`sizeof`) and `empty` - -`left sizeof n` matches when the size of `left` equals the integer `n`, where size is the number of characters in a string, the number of elements in an array, or the number of members in an object. A non-integer `n` is truncated toward zero; a non-numeric `n` never matches. `size` is an alias for `sizeof`. - -`left empty true` matches an empty string, array, or object; `left empty false` matches a non-empty one. - -### Get-keys operator `~` - -The terminal `~` operator returns the member names of an object as a flat list of strings. Applied to the root (`$~`), it returns the top-level keys. Applied to a multiple-match receiver, it flattens the keys of every matched object. A non-object receiver contributes no keys. - -Because `~` is terminal, expressions such as `$.obj~.x`, `$.obj~~`, and `$.obj.keys()~` are parse errors. For a composable alternative that can be chained with other functions, use the [`keys()`](#keys) function. - -See [Filter examples](#filter-examples) for a runnable example of each operator. - ## Functions Functions can appear inside filter expressions and, when they return a value, as top-level [projection expressions](#projection-expressions). A function can be written in prefix form, `length($.arr)`, or in postfix (method) form, `$.arr.length()`. A path segment immediately followed by `(` is a method call, so `$.arr.length()` is a function call while `$.arr.length` is a reference to a field named `length`. @@ -465,11 +423,11 @@ OK "[\"Quaoar\",\"Weywot\"]" {{< /clients-example >}} -See [Filter expression operators](#filter-expression-operators) for more information. +These operators can be used within a filter expression (`?()`). Beginning with Redis 8.10, the JSONPath filter syntax supports the following additional operators. -Beginning with Redis 8.10, the JSONPath filter syntax supports additional operators, described in [Filter expression operators](#filter-expression-operators). The following examples demonstrate each one. +#### Negation `!` -#### Negation +The `!` operator negates a filter condition. It binds more tightly than the logical `&&` and `||` operators, so use parentheses to negate a compound condition. A double negation (`!!`) is equivalent to an existence test. {{< clients-example set="json_path_ops" step="filter_negation" description="Filter negation: Use the ! operator to invert a filter condition when you need to select elements that do not match" difficulty="advanced" >}} > JSON.SET doc $ '[{"a":1,"b":1},{"b":2},{"a":1},{"c":3}]' @@ -484,6 +442,8 @@ OK #### Comparing array and object literals +The `==` and `!=` operators can compare any JSON literal, including array and object literals, on either side of the operator. Comparison is by structural (deep) equality. A comparison between values of different types is always false. + {{< clients-example set="json_path_ops" step="filter_literal_eq" description="Literal comparison: Use == and != to compare array and object literals by structural equality when you need to match a whole nested value" difficulty="advanced" >}} > JSON.SET doc $ '{"arrs":[[1],[2],[1,2],[1,[2]]],"objs":[{"x":1},{"x":2},{"y":1}]}' OK @@ -497,6 +457,10 @@ OK #### Arithmetic operators +The binary operators `+`, `-`, `*`, `/`, and `%` and the unary operators `-` and `+` operate on numbers. Precedence, from highest to lowest, is: unary `-`/`+`, then `*`/`/`/`%`, then `+`/`-`. Use parentheses to override precedence. Division always produces a floating-point result. Division or modulo by zero produces *Nothing*, so no element matches. + +Remember that arithmetic operators must be surrounded by spaces (see [Evaluation semantics](#evaluation-semantics)). + {{< clients-example set="json_path_ops" step="filter_arithmetic" description="Arithmetic in filters: Use +, -, *, / and % inside a filter condition when you need to compute a value before comparing it" difficulty="advanced" >}} > JSON.SET doc $ '[{"a":2,"b":3},{"a":5,"b":2}]' OK @@ -510,6 +474,8 @@ OK #### Membership: `in` and `nin` +`value in array` matches when `value` is a member of `array`; `value nin array` is its strict negation. The right-hand side can be an array literal or a path to an array. Membership uses the same structural comparison and number coercion as `==`. If the right-hand side is not an array, `in` is false (so `nin` is true). + {{< clients-example set="json_path_ops" step="filter_membership" description="Membership filters: Use in and nin to test whether a value is a member of an array when you need set-style matching" difficulty="advanced" >}} > JSON.SET doc $ '{"a":[1,2,3,4],"allow":[2,3]}' OK @@ -523,6 +489,12 @@ OK #### Set relations: `subsetof`, `anyof`, and `noneof` +These operators compare two arrays (each side can be an array literal or a path to an array): + +- `subsetof` matches when every element of the left array is a member of the right array. An empty array is a subset of any array. +- `anyof` matches when the two arrays have a non-empty intersection. An empty array never matches. +- `noneof` matches when the two arrays have an empty intersection. An empty array always matches. + {{< clients-example set="json_path_ops" step="filter_set_relations" description="Set relation filters: Use subsetof, anyof, and noneof to compare two arrays when you need to match based on their overlap" difficulty="advanced" >}} > JSON.SET doc $ '{"a":[[1,2],[1,5],[]]}' OK @@ -540,6 +512,10 @@ OK #### `size` (`sizeof`) and `empty` +`left sizeof n` matches when the size of `left` equals the integer `n`, where size is the number of characters in a string, the number of elements in an array, or the number of members in an object. A non-integer `n` is truncated toward zero; a non-numeric `n` never matches. `size` is an alias for `sizeof`. + +`left empty true` matches an empty string, array, or object; `left empty false` matches a non-empty one. + {{< clients-example set="json_path_ops" step="filter_size_empty" description="Size and emptiness filters: Use sizeof and empty to filter by the size or emptiness of a string, array, or object" difficulty="advanced" >}} > JSON.SET doc $ '{"a":[[4,5],[1],[7,8,9]]}' OK @@ -555,6 +531,10 @@ OK #### Get-keys operator `~` +The terminal `~` operator returns the member names of an object as a flat list of strings. Applied to the root (`$~`), it returns the top-level keys. Applied to a multiple-match receiver, it flattens the keys of every matched object. A non-object receiver contributes no keys. + +Because `~` is terminal, expressions such as `$.obj~.x`, `$.obj~~`, and `$.obj.keys()~` are parse errors. For a composable alternative that can be chained with other functions, use the [`keys()`](#keys) function. + {{< clients-example set="json_path_ops" step="filter_getkeys" description="Get-keys filter: Use the ~ operator to retrieve an object's member names as a list of strings" difficulty="advanced" >}} > JSON.SET doc $ '{"obj":{"x":1,"y":2},"books":[{"t":"a"},{"t":"b"}]}' OK From a7f3599e4bdf4af722dc11f0d476e000d7b5a482 Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Fri, 4 Sep 2026 15:28:57 +0100 Subject: [PATCH 3/3] DOC-7032: Move Projection expressions after the worked examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move "## Projection expressions" to sit just above "## Legacy path syntax", after all the worked examples, so every section that carries a runnable example is grouped together. Rename "## Multi-language JSONPath examples" to "## JSONPath examples" — "multi-language" was redundant once every example on the page renders through the clients-example shortcode; confirmed the old heading's anchor isn't linked from any other page before renaming it. Also drops a duplicate transition sentence left over from the earlier operator-description move. This closes out the json_path_ops TCE work for DOC-7032: 19 examples across 11 clients, split into three stacked PRs. Two things surfaced during that work that are worth keeping in mind for whoever touches this set next: a multi-path JSON.GET reply's key order is genuinely nondeterministic at the protocol level (confirmed by repeated calls against an identical document returning both orderings) — one agent's output very nearly got reported as a wrong ground truth before checking whether the mismatch was in the server reply or in the client's own tooling; it was the latter, serde_json defaulting to BTreeMap ordering without `preserve_order`. And a batch of "flaky" lettuce-async failures during testing looked like a real timing race in the client's chained-future style, but turned out to be several of this session's own parallel fan-out agents hitting the same shared scratch Redis key concurrently, not a product defect. Learned: multi-path JSON.GET key order is nondeterministic — assert structurally, not by string Constraint: json_path_ops multi-path-reply assertions must compare parsed maps, not raw strings Rejected: treating a live output mismatch as a wrong ground truth before checking own tooling | serde_json's default map ordering had scrambled the key order, not the server Directive: jedis Path2 auto-prepends "$." to a path not starting with $ or . — breaks parenthesized expressions like "(a+b)/2"; use the legacy Path class for those Ticket: DOC-7032 Co-Authored-By: Claude Sonnet 5 --- content/develop/data-types/json/path.md | 66 ++++++++++++------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/content/develop/data-types/json/path.md b/content/develop/data-types/json/path.md index 276c17c697..b2a2ca64be 100644 --- a/content/develop/data-types/json/path.md +++ b/content/develop/data-types/json/path.md @@ -239,39 +239,9 @@ OK "[{\"t\":\"a\",\"price\":30},{\"t\":\"X\"}]" ``` -## Projection expressions - -The top level of a JSONPath query can be an expression that *computes* a value — arithmetic, a function call, a get-keys operator, and so on — rather than only selecting nodes from the document. A query that is a plain path (for example, `$.a.b`, `$..x`, or even a fully parenthesized path such as `($.a)`) still selects nodes; anything else (for example, `$.a + 1`, `-$.a`, `$.arr.length()`, `length($.arr)`, `$.obj~`, or `$.obj.keys()`) is a projection. - -A projection that evaluates to *Nothing* produces an empty reply (`[]`). - -``` -> JSON.SET doc $ '{"a":2,"b":4,"arr":[1,2,3]}' -OK -> JSON.GET doc '$.a + 1' -"[3]" -> JSON.GET doc '$.a * $.b' -"[8]" -> JSON.GET doc '($.a + $.b) / 2' -"[3.0]" -> JSON.GET doc '$.arr.length()' -"[3]" -> JSON.GET doc '$.a / 0' -"[]" -``` - -When [`JSON.GET`]({{< relref "commands/json.get/" >}}) is given more than one path, projections and plain paths can be mixed, and each path becomes a key in the returned object: - -``` -> JSON.GET doc '$.a + 1' '$.b' -"{\"$.a + 1\":[3],\"$.b\":[4]}" -``` - -[`JSON.MGET`]({{< relref "commands/json.mget/" >}}) evaluates the projection independently for each key. A missing key, or a per-key evaluation error, yields a null reply for that key rather than failing the whole request. - -## Multi-language JSONPath examples +## JSONPath examples -The following multi-language JSONPath examples use this JSON document, which stores details about items in a store's inventory: +The following examples use this JSON document, which stores details about items in a store's inventory: {{< trimmable head="12" tail="8" >}} ```json @@ -423,7 +393,7 @@ OK "[\"Quaoar\",\"Weywot\"]" {{< /clients-example >}} -These operators can be used within a filter expression (`?()`). Beginning with Redis 8.10, the JSONPath filter syntax supports the following additional operators. +Beginning with Redis 8.10, the JSONPath filter syntax supports the following operators. #### Negation `!` @@ -580,6 +550,36 @@ JSONPath queries also work with other JSON commands that accept a path as an arg "[[\"black\",\"silver\",\"pink\"],[\"black\",\"white\"],[\"black\",\"silver\",\"pink\"]]" {{< /clients-example >}} +## Projection expressions + +The top level of a JSONPath query can be an expression that *computes* a value — arithmetic, a function call, a get-keys operator, and so on — rather than only selecting nodes from the document. A query that is a plain path (for example, `$.a.b`, `$..x`, or even a fully parenthesized path such as `($.a)`) still selects nodes; anything else (for example, `$.a + 1`, `-$.a`, `$.arr.length()`, `length($.arr)`, `$.obj~`, or `$.obj.keys()`) is a projection. + +A projection that evaluates to *Nothing* produces an empty reply (`[]`). + +``` +> JSON.SET doc $ '{"a":2,"b":4,"arr":[1,2,3]}' +OK +> JSON.GET doc '$.a + 1' +"[3]" +> JSON.GET doc '$.a * $.b' +"[8]" +> JSON.GET doc '($.a + $.b) / 2' +"[3.0]" +> JSON.GET doc '$.arr.length()' +"[3]" +> JSON.GET doc '$.a / 0' +"[]" +``` + +When [`JSON.GET`]({{< relref "commands/json.get/" >}}) is given more than one path, projections and plain paths can be mixed, and each path becomes a key in the returned object: + +``` +> JSON.GET doc '$.a + 1' '$.b' +"{\"$.a + 1\":[3],\"$.b\":[4]}" +``` + +[`JSON.MGET`]({{< relref "commands/json.mget/" >}}) evaluates the projection independently for each key. A missing key, or a per-key evaluation error, yields a null reply for that key rather than failing the whole request. + ## Legacy path syntax RedisJSON v1 had the following path implementation. JSON v2 still supports this legacy path in addition to JSONPath.