├── .github ├── dependabot.yml └── workflows │ ├── CI.yml │ └── update-contributors.yml ├── .gitignore ├── CODEOWNERS ├── CONTRIBUTORS.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── README.md ├── aggregations │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── boolean-query │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── completion-suggester │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── handle-search-response │ ├── Cargo.toml │ └── src │ │ └── main.rs └── simple-query │ ├── Cargo.toml │ └── src │ └── main.rs └── src ├── analyze ├── mod.rs ├── request.rs └── response.rs ├── lib.rs ├── macros.rs ├── search ├── aggregations │ ├── bucket │ │ ├── diversified_sampler_aggregation.rs │ │ ├── filter_aggregation.rs │ │ ├── mod.rs │ │ ├── sampler_aggregation.rs │ │ └── terms_aggregation.rs │ ├── metrics │ │ ├── avg_aggregation.rs │ │ ├── boxplot_aggregation.rs │ │ ├── cardinality_aggregation.rs │ │ ├── max_aggregation.rs │ │ ├── min_aggregation.rs │ │ ├── mod.rs │ │ ├── rate_aggregation.rs │ │ ├── sum_aggregation.rs │ │ └── top_hits_aggregation.rs │ ├── mod.rs │ ├── params │ │ ├── aggregation_name.rs │ │ ├── gap_policy.rs │ │ ├── mod.rs │ │ ├── rate_mode.rs │ │ └── terms_order.rs │ └── pipeline │ │ └── mod.rs ├── highlight │ ├── boundary_scanner.rs │ ├── encoder.rs │ ├── fragmenter.rs │ ├── highlighter.rs │ ├── matched_fields.rs │ ├── mod.rs │ ├── order.rs │ └── tags.rs ├── mod.rs ├── params │ ├── coordinate.rs │ ├── date.rs │ ├── geo_distance_type.rs │ ├── geo_location.rs │ ├── geo_shape.rs │ ├── mod.rs │ ├── number.rs │ ├── score_mode.rs │ ├── script_sort_type.rs │ ├── search_filter.rs │ ├── shape.rs │ ├── term.rs │ ├── terms.rs │ ├── text.rs │ ├── track_total_hits.rs │ └── units.rs ├── queries │ ├── compound │ │ ├── bool_query.rs │ │ ├── boosting_query.rs │ │ ├── constant_score_query.rs │ │ ├── dis_max_query.rs │ │ ├── function_score_query.rs │ │ └── mod.rs │ ├── custom │ │ ├── json_query.rs │ │ └── mod.rs │ ├── full_text │ │ ├── combined_fields_query.rs │ │ ├── match_bool_prefix_query.rs │ │ ├── match_phrase_prefix_query.rs │ │ ├── match_phrase_query.rs │ │ ├── match_query.rs │ │ ├── mod.rs │ │ ├── multi_match_query.rs │ │ ├── query_string_query.rs │ │ └── simple_query_string_query.rs │ ├── geo │ │ ├── geo_bounding_box_query.rs │ │ ├── geo_distance_query.rs │ │ ├── geo_shape_lookup_query.rs │ │ ├── geo_shape_query.rs │ │ └── mod.rs │ ├── joining │ │ ├── has_child_query.rs │ │ ├── has_parent_query.rs │ │ ├── mod.rs │ │ ├── nested_query.rs │ │ └── parent_id_query.rs │ ├── match_all_query.rs │ ├── match_none_query.rs │ ├── mod.rs │ ├── params │ │ ├── function_score_query.rs │ │ ├── fuzziness.rs │ │ ├── geo_query.rs │ │ ├── has_child_query.rs │ │ ├── inner_hits.rs │ │ ├── mod.rs │ │ ├── negative_boost.rs │ │ ├── nested_query.rs │ │ ├── operator.rs │ │ ├── percolate_query.rs │ │ ├── pinned_query.rs │ │ ├── range_query.rs │ │ ├── regexp_query.rs │ │ ├── rewrite.rs │ │ ├── script_object.rs │ │ ├── shape_query.rs │ │ ├── simple_query_string_query.rs │ │ ├── terms_set_query.rs │ │ ├── text_query_type.rs │ │ └── zero_terms_query.rs │ ├── query_collection.rs │ ├── shape │ │ ├── mod.rs │ │ ├── shape_lookup_query.rs │ │ └── shape_query.rs │ ├── span │ │ ├── mod.rs │ │ ├── span_containing_query.rs │ │ ├── span_field_masking_query.rs │ │ ├── span_first_query.rs │ │ ├── span_multi_query.rs │ │ ├── span_near_query.rs │ │ ├── span_not_query.rs │ │ ├── span_or_query.rs │ │ ├── span_term_query.rs │ │ └── span_within_query.rs │ ├── specialized │ │ ├── distance_feature_query.rs │ │ ├── mod.rs │ │ ├── more_like_this_query.rs │ │ ├── percolate_lookup_query.rs │ │ ├── percolate_query.rs │ │ ├── pinned_query.rs │ │ ├── rank_feature_query.rs │ │ ├── script_query.rs │ │ ├── script_score_query.rs │ │ └── wrapper_query.rs │ └── term_level │ │ ├── exists_query.rs │ │ ├── fuzzy_query.rs │ │ ├── ids_query.rs │ │ ├── mod.rs │ │ ├── prefix_query.rs │ │ ├── range_query.rs │ │ ├── regexp_query.rs │ │ ├── term_query.rs │ │ ├── terms_lookup_query.rs │ │ ├── terms_query.rs │ │ ├── terms_set_query.rs │ │ └── wildcard_query.rs ├── request.rs ├── rescoring │ ├── mod.rs │ ├── rescore_.rs │ └── rescore_collection.rs ├── response │ ├── cluster_statistics.rs │ ├── error_cause.rs │ ├── explanation.rs │ ├── hit.rs │ ├── hits_metadata.rs │ ├── inner_hits_result.rs │ ├── mod.rs │ ├── nested_identity.rs │ ├── search_response.rs │ ├── shard_failure.rs │ ├── shard_statistics.rs │ ├── source.rs │ ├── suggest.rs │ ├── suggest_option.rs │ ├── total_hits.rs │ └── total_hits_relation.rs ├── runtime_mappings │ └── mod.rs ├── sort │ ├── field_sort.rs │ ├── geo_distance_sort.rs │ ├── mod.rs │ ├── script_sort.rs │ ├── sort_.rs │ ├── sort_collection.rs │ ├── sort_missing.rs │ ├── sort_mode.rs │ ├── sort_order.rs │ └── sort_special_field.rs └── suggesters │ ├── completion_suggester.rs │ ├── mod.rs │ ├── suggest_context_query.rs │ ├── suggest_fuzziness.rs │ └── suggester.rs ├── types.rs └── util ├── assert_serialize.rs ├── join_with_pipe.rs ├── key_value_pair.rs ├── mod.rs └── should_skip.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: / 5 | schedule: 6 | interval: daily 7 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | pull_request: 4 | types: [opened] 5 | 6 | name: Rust 7 | 8 | jobs: 9 | ci: 10 | name: CI Build 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | features: [--all-features] 15 | fail-fast: false 16 | env: 17 | RUSTFLAGS: -D warnings 18 | CARGO_TERM_COLOR: always 19 | steps: 20 | - name: Checkout sources 21 | uses: actions/checkout@v3 22 | 23 | - name: Install stable toolchain 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | profile: minimal 27 | toolchain: stable 28 | override: true 29 | components: rustfmt, clippy, llvm-tools-preview 30 | 31 | - name: Install cargo-llvm-cov 32 | uses: taiki-e/install-action@cargo-llvm-cov 33 | 34 | - name: Install nextest 35 | uses: taiki-e/install-action@v1 36 | with: 37 | tool: nextest 38 | 39 | - name: Rust Cache 40 | uses: Swatinem/rust-cache@v2.0.0 41 | 42 | - name: Run cargo check 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: check 46 | 47 | - name: Run cargo fmt 48 | uses: actions-rs/cargo@v1 49 | with: 50 | command: fmt 51 | args: --all -- --check 52 | 53 | - name: Run cargo clippy 54 | uses: actions-rs/clippy-check@v1 55 | with: 56 | token: ${{ secrets.GITHUB_TOKEN }} 57 | args: ${{ matrix.features }} -- -D warnings 58 | 59 | - name: Run library tests 60 | uses: actions-rs/cargo@v1 61 | with: 62 | command: nextest 63 | args: run ${{ matrix.features }} 64 | 65 | - name: Run doc tests 66 | uses: actions-rs/cargo@v1 67 | with: 68 | command: test 69 | args: --doc 70 | 71 | - name: Collect coverage data 72 | run: cargo llvm-cov nextest --lcov --output-path lcov.info 73 | 74 | - name: Upload coverage to Codecov 75 | uses: codecov/codecov-action@v3 76 | with: 77 | files: lcov.info 78 | fail_ci_if_error: true 79 | 80 | - name: Run cargo doc 81 | uses: actions-rs/cargo@v1 82 | with: 83 | command: doc 84 | args: --no-deps 85 | -------------------------------------------------------------------------------- /.github/workflows/update-contributors.yml: -------------------------------------------------------------------------------- 1 | name: Update CONTRIBUTORS file 2 | on: 3 | schedule: 4 | - cron: '0 0 1 * *' 5 | workflow_dispatch: 6 | jobs: 7 | main: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: minicli/action-contributors@v3 12 | name: 'Update a projects CONTRIBUTORS file' 13 | env: 14 | CONTRIB_REPOSITORY: 'vinted/elasticsearch-dsl-rs' 15 | CONTRIB_OUTPUT_FILE: 'CONTRIBUTORS.md' 16 | - name: Create a PR 17 | uses: peter-evans/create-pull-request@v3 18 | with: 19 | commit-message: Update Contributors 20 | title: '[automated] Update Contributors File' 21 | token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @vinted/boost 2 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | Shout out to our top contributors! 4 | 5 | - [buinauskas](https://api.github.com/users/buinauskas) 6 | - [iamazy](https://api.github.com/users/iamazy) 7 | - [JonasBakys0](https://api.github.com/users/JonasBakys0) 8 | - [github-actions[bot]](https://api.github.com/users/github-actions%5Bbot%5D) 9 | - [oleg-vinted](https://api.github.com/users/oleg-vinted) 10 | - [dainiusjocas](https://api.github.com/users/dainiusjocas) 11 | - [Jakimcikas](https://api.github.com/users/Jakimcikas) 12 | - [jbcrail](https://api.github.com/users/jbcrail) 13 | - [ypenglyn](https://api.github.com/users/ypenglyn) 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elasticsearch-dsl" 3 | version = "0.4.11" 4 | authors = ["Evaldas Buinauskas ", "Boost "] 5 | edition = "2018" 6 | description = "Strongly typed Elasticsearch DSL" 7 | repository = "https://github.com/vinted/elasticsearch-dsl-rs" 8 | documentation = "https://docs.rs/elasticsearch-dsl/" 9 | license = "MIT OR Apache-2.0" 10 | 11 | [workspace] 12 | members = ["examples/*"] 13 | 14 | [dependencies] 15 | chrono = { version = "0.4", default-features = false, features = ["std", "serde"] } 16 | num-traits = { version = "0.2" } 17 | serde = { version = "1", default-features = false, features = ["derive"] } 18 | serde_json = { version = "1", features = ["raw_value"] } 19 | 20 | [dev-dependencies] 21 | pretty_assertions = { version = "1" } 22 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Vinted 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Strongly typed Elasticsearch DSL written in Rust 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/elasticsearch-dsl)](https://crates.io/crates/elasticsearch-dsl) 4 | [![Crates.io](https://img.shields.io/crates/l/elasticsearch-dsl)](https://crates.io/crates/elasticsearch-dsl) 5 | [![Crates.io](https://img.shields.io/crates/d/elasticsearch-dsl)](https://crates.io/crates/elasticsearch-dsl) 6 | [![Docs.io](https://docs.rs/elasticsearch-dsl/badge.svg)](https://docs.rs/elasticsearch-dsl) 7 | [![Rust](https://github.com/vinted/elasticsearch-dsl-rs/actions/workflows/CI.yml/badge.svg)](https://github.com/vinted/elasticsearch-dsl-rs/actions/workflows/CI.yml) 8 | 9 | A high level library, giving a strongly typed DSL that maps one to one with the official 10 | Elasticsearch query DSL. 11 | 12 | ## Features 13 | 14 | - Strongly typed queries 15 | - Strongly typed aggregations 16 | - Strongly typed completions 17 | - Response structures 18 | - Automatically skips empty queries making DSL pleasant to use 19 | - Crate doesn't depend on [elasticsearch-rs](https://github.com/elastic/elasticsearch-rs) and can 20 | be used as a standalone library with any HTTP client to call Elasticsearch 21 | 22 | ## Installation 23 | 24 | Add `elasticsearch-dsl` crate and version to Cargo.toml 25 | 26 | ```toml 27 | [dependencies] 28 | elasticsearch-dsl = "0.4" 29 | ``` 30 | 31 | ## Documentation 32 | 33 | Documentation for the library is available on [docs.rs](https://docs.rs/elasticsearch-dsl) 34 | 35 | ## Quick start 36 | 37 | ```rust 38 | use elasticsearch_dsl::*; 39 | 40 | fn main() { 41 | let query = Search::new() 42 | .source(false) 43 | .stats("statistics") 44 | .from(0) 45 | .size(30) 46 | .query( 47 | Query::bool() 48 | .must(Query::multi_match( 49 | ["title", "description"], 50 | "you know, for search", 51 | )) 52 | .filter(Query::terms("tags", ["elasticsearch"])) 53 | .should(Query::term("verified", true).boost(10)), 54 | ) 55 | .aggregate( 56 | "country_ids", 57 | Aggregation::terms("country_id") 58 | .aggregate("catalog_ids", Aggregation::terms("catalog_id")) 59 | .aggregate("company_ids", Aggregation::terms("company_id")) 60 | .aggregate( 61 | "top1", 62 | Aggregation::top_hits() 63 | .size(1) 64 | .sort(FieldSort::ascending("user_id")), 65 | ), 66 | ) 67 | .rescore(Rescore::new(Query::term("field", 1)).query_weight(1.2)); 68 | } 69 | ``` 70 | 71 | ```json 72 | { 73 | "_source": false, 74 | "stats": ["statistics"], 75 | "from": 0, 76 | "size": 30, 77 | "query": { 78 | "bool": { 79 | "must": [ 80 | { 81 | "multi_match": { 82 | "fields": ["title", "description"], 83 | "query": "you know, for search" 84 | } 85 | } 86 | ], 87 | "filter": [{ "terms": { "tags": ["elasticsearch"] } }], 88 | "should": [{ "term": { "verified": { "value": true, "boost": 10.0 } } }] 89 | } 90 | }, 91 | "aggs": { 92 | "country_ids": { 93 | "terms": { "field": "country_id" }, 94 | "aggs": { 95 | "catalog_ids": { "terms": { "field": "catalog_id" } }, 96 | "company_ids": { "terms": { "field": "company_id" } }, 97 | "top1": { 98 | "top_hits": { 99 | "size": 1, 100 | "sort": [{ "user_id": { "order": "asc" } }] 101 | } 102 | } 103 | } 104 | } 105 | }, 106 | "rescore": [ 107 | { 108 | "query": { 109 | "rescore_query": { "term": { "field": { "value": 1 } } }, 110 | "query_weight": 1.2 111 | } 112 | } 113 | ] 114 | } 115 | ``` 116 | 117 | See [examples](examples) for more. 118 | 119 | #### License 120 | 121 | 122 | Licensed under either of Apache License, Version 123 | 2.0 or MIT license at your option. 124 | 125 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This folder contains numerous example showing how to use this library. 4 | Each example is setup as its own crate so its dependencies are clear. 5 | -------------------------------------------------------------------------------- /examples/aggregations/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aggregations" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | elasticsearch-dsl = { path = "../.." } 10 | serde_json = { version = "1" } 11 | -------------------------------------------------------------------------------- /examples/aggregations/src/main.rs: -------------------------------------------------------------------------------- 1 | use elasticsearch_dsl::*; 2 | 3 | fn main() { 4 | let search = Search::new() 5 | .size(0) 6 | .query(Query::bool().must_not(Query::exists("country_id"))) 7 | .aggregate( 8 | "country_ids", 9 | Aggregation::terms("country_id") 10 | .aggregate("catalog_ids", Aggregation::terms("catalog_id")) 11 | .aggregate("company_ids", Aggregation::terms("company_id")) 12 | .aggregate( 13 | "top1", 14 | Aggregation::top_hits() 15 | .size(1) 16 | .sort(FieldSort::descending("field")), 17 | ), 18 | ); 19 | 20 | println!("{}", serde_json::to_string_pretty(&search).unwrap()); 21 | } 22 | -------------------------------------------------------------------------------- /examples/boolean-query/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "boolean-query" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | elasticsearch-dsl = { path = "../.." } 10 | serde_json = { version = "1" } 11 | -------------------------------------------------------------------------------- /examples/boolean-query/src/main.rs: -------------------------------------------------------------------------------- 1 | use elasticsearch_dsl::*; 2 | 3 | fn main() { 4 | let search = Search::new() 5 | .source(false) 6 | .from(0) 7 | .size(10) 8 | .stats("boolean-query") 9 | .query( 10 | Query::bool() 11 | .must(Query::term("user.id", "kimchy")) 12 | .filter(Query::term("tags", "production")) 13 | .must_not(Query::range("age").gte(10).lte(10)) 14 | .should([Query::term("tags", "env1"), Query::term("tags", "deployed")]) 15 | .minimum_should_match("1") 16 | .boost(1), 17 | ) 18 | .rescore(Rescore::new(Query::term("field", 1))); 19 | 20 | println!("{}", serde_json::to_string_pretty(&search).unwrap()); 21 | } 22 | -------------------------------------------------------------------------------- /examples/completion-suggester/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "completion-suggester" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | elasticsearch-dsl = { path = "../.." } 10 | serde_json = { version = "1" } 11 | -------------------------------------------------------------------------------- /examples/completion-suggester/src/main.rs: -------------------------------------------------------------------------------- 1 | use elasticsearch_dsl::*; 2 | 3 | fn main() { 4 | let search = Search::new().source("suggest").suggest( 5 | "song-suggest", 6 | Suggester::completion("suggest", "nir") 7 | .size(5) 8 | .skip_duplicates(true) 9 | .fuzzy( 10 | SuggestFuzziness::new() 11 | .transpositions(true) 12 | .fuzziness(2..4) 13 | .min_length(4) 14 | .prefix_length(2) 15 | .unicode_aware(false), 16 | ), 17 | ); 18 | 19 | println!("{}", serde_json::to_string_pretty(&search).unwrap()); 20 | } 21 | -------------------------------------------------------------------------------- /examples/handle-search-response/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "handle-search-response" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | elasticsearch-dsl = { path = "../.." } 10 | serde = { version = "1", features = ["derive"] } 11 | serde_json = { version = "1" } 12 | -------------------------------------------------------------------------------- /examples/handle-search-response/src/main.rs: -------------------------------------------------------------------------------- 1 | use elasticsearch_dsl::*; 2 | 3 | fn main() { 4 | #[derive(Debug, PartialEq, serde::Deserialize)] 5 | struct Message { 6 | #[serde(rename = "@timestamp")] 7 | timestamp: String, 8 | 9 | message: String, 10 | 11 | user_id: String, 12 | } 13 | 14 | let json = serde_json::json!({ 15 | "took": 5, 16 | "timed_out": false, 17 | "_shards": { 18 | "total": 1, 19 | "successful": 1, 20 | "skipped": 0, 21 | "failed": 0 22 | }, 23 | "hits": { 24 | "total": { 25 | "value": 20, 26 | "relation": "eq" 27 | }, 28 | "max_score": 1.3862942, 29 | "hits": [ 30 | { 31 | "_index": "my-index-000001", 32 | "_id": "0", 33 | "_score": 1.3862942, 34 | "_source": { 35 | "@timestamp": "2099-11-15T14:12:12", 36 | "message": "GET /search HTTP/1.1 200 1070000", 37 | "user_id": "kimchy" 38 | } 39 | } 40 | ] 41 | } 42 | }); 43 | 44 | let response: SearchResponse = serde_json::from_value(json).unwrap(); 45 | 46 | let documents = response.documents::().unwrap(); 47 | 48 | assert_eq!( 49 | documents, 50 | vec![Message { 51 | timestamp: "2099-11-15T14:12:12".to_string(), 52 | message: "GET /search HTTP/1.1 200 1070000".to_string(), 53 | user_id: "kimchy".to_string(), 54 | }] 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /examples/simple-query/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple-query" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | elasticsearch-dsl = { path = "../.." } 10 | serde_json = { version = "1" } 11 | -------------------------------------------------------------------------------- /examples/simple-query/src/main.rs: -------------------------------------------------------------------------------- 1 | use elasticsearch_dsl::*; 2 | 3 | fn main() { 4 | let search = Search::new() 5 | .size(10) 6 | .query(Query::term("user.id", "kimchy").boost(1)); 7 | 8 | println!("{}", serde_json::to_string_pretty(&search).unwrap()); 9 | } 10 | -------------------------------------------------------------------------------- /src/analyze/mod.rs: -------------------------------------------------------------------------------- 1 | //! Performs 2 | //! [analysis](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis.html) 3 | //! on a text string and returns the resulting tokens. 4 | //! 5 | //! 6 | 7 | mod request; 8 | mod response; 9 | 10 | pub use self::request::*; 11 | pub use self::response::*; 12 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Strongly typed Elasticsearch DSL written in Rust 2 | //! 3 | //! This is an unofficial library and doesn't yet support all the DSL, it's still work in progress. 4 | //! 5 | //! ## Features 6 | //! 7 | //! - Strongly typed queries 8 | //! - Strongly typed aggregations 9 | //! - Automatically skips empty queries making DSL pleasant to use 10 | //! - Crate doesn't depend on [elasticsearch-rs](https://github.com/elastic/elasticsearch-rs) and can be used as a standalone library with any HTTP client to call Elasticsearch 11 | //! 12 | //! ## Installation 13 | //! 14 | //! Add `elasticsearch-dsl` crate and version to Cargo.toml 15 | //! 16 | //! ```toml 17 | //! [dependencies] 18 | //! elasticsearch-dsl = "0.2" 19 | //! ``` 20 | //! 21 | //! ## Documentation 22 | //! 23 | //! Documentation for the library is available on [docs.rs](https://docs.rs/elasticsearch-dsl) 24 | //! 25 | //! ## Quick start 26 | //! 27 | //! ```rust 28 | //! use elasticsearch_dsl::*; 29 | //! 30 | //! fn main() { 31 | //! let query = Search::new() 32 | //! .source(false) 33 | //! .stats("statistics") 34 | //! .from(0) 35 | //! .size(30) 36 | //! .query( 37 | //! Query::bool() 38 | //! .must(Query::multi_match( 39 | //! ["title", "description"], 40 | //! "you know, for search", 41 | //! )) 42 | //! .filter(Query::terms("tags", ["elasticsearch"])) 43 | //! .should(Query::term("verified", true).boost(10)), 44 | //! ) 45 | //! .aggregate( 46 | //! "country_ids", 47 | //! Aggregation::terms("country_id") 48 | //! .aggregate("catalog_ids", Aggregation::terms("catalog_id")) 49 | //! .aggregate("company_ids", Aggregation::terms("company_id")) 50 | //! .aggregate( 51 | //! "top1", 52 | //! Aggregation::top_hits() 53 | //! .size(1) 54 | //! .sort(FieldSort::ascending("user_id")), 55 | //! ), 56 | //! ).rescore(Rescore::new(Query::term("field", 1)).query_weight(1.2)); 57 | //! } 58 | //! ``` 59 | //! 60 | //! See examples for more. 61 | //! 62 | //! #### License 63 | //! 64 | //! 65 | //! Licensed under either of Apache License, Version 66 | //! 2.0 or MIT license at your option. 67 | //! 68 | #![doc( 69 | html_logo_url = "https://play-lh.googleusercontent.com/VvtT2Dvf_oOC3DL4c2i5hfNvwIqzdU2apScRMlmeRW10Yf-vJXnXqAjdNWE9KW5YvK0" 70 | )] 71 | #![deny( 72 | bad_style, 73 | dead_code, 74 | deprecated, 75 | improper_ctypes, 76 | missing_debug_implementations, 77 | missing_docs, 78 | non_shorthand_field_patterns, 79 | no_mangle_generic_items, 80 | overflowing_literals, 81 | path_statements, 82 | patterns_in_fns_without_body, 83 | private_in_public, 84 | trivial_casts, 85 | trivial_numeric_casts, 86 | unconditional_recursion, 87 | unknown_lints, 88 | unreachable_code, 89 | unreachable_pub, 90 | unused, 91 | unused_allocation, 92 | unused_comparisons, 93 | unused_extern_crates, 94 | unused_import_braces, 95 | unused_mut, 96 | unused_parens, 97 | unused_qualifications, 98 | unused_results, 99 | warnings, 100 | while_true 101 | )] 102 | 103 | #[cfg(test)] 104 | #[macro_use] 105 | extern crate pretty_assertions; 106 | 107 | #[macro_use] 108 | extern crate serde; 109 | 110 | #[cfg(test)] 111 | #[macro_use] 112 | extern crate serde_json; 113 | 114 | // Macro modules 115 | #[macro_use] 116 | mod macros; 117 | mod types; 118 | 119 | // Crate modules 120 | #[macro_use] 121 | pub(crate) mod util; 122 | pub(crate) use self::types::*; 123 | 124 | // Public modules 125 | pub mod analyze; 126 | pub mod search; 127 | 128 | // Public re-exports 129 | pub use self::analyze::*; 130 | pub use self::search::*; 131 | -------------------------------------------------------------------------------- /src/search/aggregations/bucket/filter_aggregation.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | #[derive(Debug, Clone, Serialize, PartialEq)] 5 | /// A single bucket aggregation that narrows the set of documents to those that match a query. 6 | /// 7 | /// 8 | pub struct FilterAggregation { 9 | filter: Query, 10 | 11 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 12 | aggs: Aggregations, 13 | } 14 | 15 | impl Aggregation { 16 | /// Creates an instance of [`FilterAggregation`] 17 | /// 18 | /// - `query` - query to filter by 19 | pub fn filter(query: Q) -> FilterAggregation 20 | where 21 | Q: Into, 22 | { 23 | FilterAggregation { 24 | filter: query.into(), 25 | aggs: Aggregations::new(), 26 | } 27 | } 28 | } 29 | 30 | impl FilterAggregation { 31 | add_aggregate!(); 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | 38 | #[test] 39 | fn serialization() { 40 | assert_serialize_aggregation( 41 | Aggregation::filter(Query::term("type", "t-shirt")) 42 | .aggregate("sizes", Aggregation::terms("size")), 43 | json!({ 44 | "filter": { "term": { "type": { "value": "t-shirt"} } }, 45 | "aggs": { 46 | "sizes": { "terms": { "field": "size" } } 47 | } 48 | }), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/search/aggregations/bucket/mod.rs: -------------------------------------------------------------------------------- 1 | //! Bucket aggregations don’t calculate metrics over fields like the metrics aggregations do, 2 | //! but instead, they create buckets of documents. Each bucket is associated with a criterion 3 | //! (depending on the aggregation type) which determines whether or not a document in the current 4 | //! context "falls" into it. In other words, the buckets effectively define document sets. 5 | //! In addition to the buckets themselves, the `bucket` aggregations also compute 6 | //! and return the number of documents that "fell into" each bucket. 7 | //! 8 | //! Bucket aggregations, as opposed to `metrics` aggregations, can hold sub-aggregations. 9 | //! These sub-aggregations will be aggregated for the buckets created by their "parent" bucket aggregation. 10 | //! 11 | //! There are different bucket aggregators, each with a different "bucketing" strategy. 12 | //! Some define a single bucket, some define fixed number of multiple buckets, 13 | //! and others dynamically create the buckets during the aggregation process. 14 | //! 15 | //! > The maximum number of buckets allowed in a single response is limited by 16 | //! a dynamic cluster setting named 17 | //! [`search.max_buckets`](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-settings.html#search-settings-max-buckets). 18 | //! It defaults to `65,535`. Requests that try to return more than the limit will fail with an exception. 19 | //! 20 | //! 21 | 22 | mod diversified_sampler_aggregation; 23 | mod filter_aggregation; 24 | mod sampler_aggregation; 25 | mod terms_aggregation; 26 | 27 | pub use self::diversified_sampler_aggregation::*; 28 | pub use self::filter_aggregation::*; 29 | pub use self::sampler_aggregation::*; 30 | pub use self::terms_aggregation::*; 31 | -------------------------------------------------------------------------------- /src/search/aggregations/bucket/sampler_aggregation.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// A filtering aggregation used to limit any sub aggregations' processing to a sample of the top-scoring documents. 5 | /// 6 | /// 7 | #[derive(Debug, Clone, Serialize, PartialEq)] 8 | pub struct SamplerAggregation { 9 | sampler: SamplerAggregationInner, 10 | 11 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 12 | aggs: Aggregations, 13 | } 14 | 15 | #[derive(Debug, Clone, Serialize, PartialEq)] 16 | struct SamplerAggregationInner { 17 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 18 | shard_size: Option, 19 | } 20 | 21 | impl Aggregation { 22 | /// Creates an instance of [`SamplerAggregation`] 23 | pub fn sampler() -> SamplerAggregation { 24 | SamplerAggregation { 25 | sampler: SamplerAggregationInner { shard_size: None }, 26 | aggs: Aggregations::new(), 27 | } 28 | } 29 | } 30 | 31 | impl SamplerAggregation { 32 | /// The shard_size parameter limits how many top-scoring documents are 33 | /// collected in the sample processed on each shard. The default value is 100. 34 | pub fn shard_size(mut self, shard_size: u64) -> Self { 35 | self.sampler.shard_size = Some(shard_size); 36 | self 37 | } 38 | 39 | add_aggregate!(); 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn serialization() { 48 | assert_serialize_aggregation(Aggregation::sampler(), json!({ "sampler": {} })); 49 | 50 | assert_serialize_aggregation( 51 | Aggregation::sampler().shard_size(100), 52 | json!({ "sampler": { "shard_size": 100 } }), 53 | ); 54 | 55 | assert_serialize_aggregation( 56 | Aggregation::sampler() 57 | .shard_size(50) 58 | .aggregate("catalog", Aggregation::terms("catalog_id")) 59 | .aggregate("brand", Aggregation::terms("brand_id")), 60 | json!({ 61 | "sampler": { "shard_size": 50 }, 62 | "aggs": { 63 | "catalog": { 64 | "terms": { 65 | "field": "catalog_id" 66 | } 67 | }, 68 | "brand": { 69 | "terms": { 70 | "field": "brand_id" 71 | } 72 | } 73 | } 74 | }), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/search/aggregations/metrics/avg_aggregation.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// A `single-value` metrics aggregation that computes the average of numeric values that are extracted 5 | /// from the aggregated documents. These values can be extracted either from specific numeric fields 6 | /// in the documents. 7 | /// 8 | /// 9 | #[derive(Debug, Clone, Serialize, PartialEq)] 10 | pub struct AvgAggregation { 11 | avg: AvgAggregationInner, 12 | } 13 | 14 | #[derive(Debug, Clone, Serialize, PartialEq)] 15 | struct AvgAggregationInner { 16 | field: String, 17 | 18 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 19 | missing: Option, 20 | } 21 | 22 | impl Aggregation { 23 | /// Creates an instance of [`AvgAggregation`] 24 | /// 25 | /// - `field` - field to aggregate 26 | pub fn avg(field: T) -> AvgAggregation 27 | where 28 | T: ToString, 29 | { 30 | AvgAggregation { 31 | avg: AvgAggregationInner { 32 | field: field.to_string(), 33 | missing: None, 34 | }, 35 | } 36 | } 37 | } 38 | 39 | impl AvgAggregation { 40 | /// The missing parameter defines how documents that are missing a value should be treated. By 41 | /// default they will be ignored but it is also possible to treat them as if they had a value. 42 | pub fn missing(mut self, missing: T) -> Self 43 | where 44 | T: Into, 45 | { 46 | self.avg.missing = Some(missing.into()); 47 | self 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | 55 | #[test] 56 | fn serialization() { 57 | assert_serialize_aggregation( 58 | Aggregation::avg("test_field"), 59 | json!({ "avg": { "field": "test_field" } }), 60 | ); 61 | 62 | assert_serialize_aggregation( 63 | Aggregation::avg("test_field").missing(100.1), 64 | json!({ 65 | "avg": { 66 | "field": "test_field", 67 | "missing": 100.1 68 | } 69 | }), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/search/aggregations/metrics/cardinality_aggregation.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// A `single-value` metrics aggregation that calculates an approximate count of distinct values. 5 | /// 6 | /// 7 | #[derive(Debug, Clone, Serialize, PartialEq)] 8 | pub struct CardinalityAggregation { 9 | cardinality: CardinalityAggregationInner, 10 | } 11 | 12 | #[derive(Debug, Clone, Serialize, PartialEq)] 13 | struct CardinalityAggregationInner { 14 | field: String, 15 | 16 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 17 | precision_threshold: Option, 18 | 19 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 20 | missing: Option, 21 | } 22 | 23 | impl Aggregation { 24 | /// Creates an instance of [`CardinalityAggregation`] 25 | /// 26 | /// - `field` - field to aggregate 27 | pub fn cardinality(field: T) -> CardinalityAggregation 28 | where 29 | T: ToString, 30 | { 31 | CardinalityAggregation { 32 | cardinality: CardinalityAggregationInner { 33 | field: field.to_string(), 34 | precision_threshold: None, 35 | missing: None, 36 | }, 37 | } 38 | } 39 | } 40 | 41 | impl CardinalityAggregation { 42 | /// The `precision_threshold` options allows to trade memory for accuracy, and defines a unique count below 43 | /// which counts are expected to be close to accurate. Above this value, counts might become a bit more fuzzy. 44 | /// The maximum supported value is 40000, thresholds above this number will have the same effect as a threshold 45 | /// of 40000. The default value is 3000 46 | pub fn precision_threshold(mut self, precision_threshold: u16) -> Self { 47 | self.cardinality.precision_threshold = Some(precision_threshold); 48 | self 49 | } 50 | 51 | /// The `missing` parameter defines how documents that are missing a value should be treated. By default they will 52 | /// be ignored but it is also possible to treat them as if they had a value. 53 | pub fn missing(mut self, missing: T) -> Self 54 | where 55 | T: ToString, 56 | { 57 | self.cardinality.missing = Some(missing.to_string()); 58 | self 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::*; 65 | 66 | #[test] 67 | fn serialization() { 68 | assert_serialize_aggregation( 69 | Aggregation::cardinality("test_field"), 70 | json!({ "cardinality": { "field": "test_field" } }), 71 | ); 72 | 73 | assert_serialize_aggregation( 74 | Aggregation::cardinality("test_field") 75 | .precision_threshold(100u16) 76 | .missing("N/A"), 77 | json!({ 78 | "cardinality": { 79 | "field": "test_field", 80 | "precision_threshold": 100, 81 | "missing": "N/A" 82 | } 83 | }), 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/search/aggregations/metrics/max_aggregation.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// A `single-value` metrics aggregation that keeps track and returns the maximum value among the 5 | /// numeric values extracted from the aggregated documents. 6 | /// 7 | /// > The `min` and `max` aggregation operate on the `double` representation of the data. As a 8 | /// consequence, the result may be approximate when running on longs whose absolute value is greater 9 | /// than `2^53`. 10 | /// 11 | /// 12 | #[derive(Debug, Clone, Serialize, PartialEq)] 13 | pub struct MaxAggregation { 14 | max: MaxAggregationInner, 15 | } 16 | 17 | #[derive(Debug, Clone, Serialize, PartialEq)] 18 | struct MaxAggregationInner { 19 | field: String, 20 | 21 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 22 | missing: Option, 23 | } 24 | 25 | impl Aggregation { 26 | /// Creates an instance of [`MaxAggregation`] 27 | /// 28 | /// - `field` - field to aggregate 29 | pub fn max(field: T) -> MaxAggregation 30 | where 31 | T: ToString, 32 | { 33 | MaxAggregation { 34 | max: MaxAggregationInner { 35 | field: field.to_string(), 36 | missing: None, 37 | }, 38 | } 39 | } 40 | } 41 | 42 | impl MaxAggregation { 43 | /// The `missing` parameter defines how documents that are missing a value should be treated. By 44 | /// default they will be ignored but it is also possible to treat them as if they had a value. 45 | pub fn missing(mut self, missing: T) -> Self 46 | where 47 | T: Into, 48 | { 49 | self.max.missing = Some(missing.into()); 50 | self 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | 58 | #[test] 59 | fn serialization() { 60 | assert_serialize_aggregation( 61 | Aggregation::max("test_field"), 62 | json!({ "max": { "field": "test_field" } }), 63 | ); 64 | 65 | assert_serialize_aggregation( 66 | Aggregation::max("test_field").missing(100.1), 67 | json!({ 68 | "max": { 69 | "field": "test_field", 70 | "missing": 100.1 71 | } 72 | }), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/search/aggregations/metrics/min_aggregation.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// A `single-value` metrics aggregation that keeps track and returns the minimum value among numeric 5 | /// values extracted from the aggregated documents. 6 | /// 7 | /// > The `min` and `max` aggregation operate on the `double` representation of the data. As a 8 | /// consequence, the result may be approximate when running on longs whose absolute value is greater 9 | /// than `2^53`. 10 | /// 11 | /// 12 | #[derive(Debug, Clone, Serialize, PartialEq)] 13 | pub struct MinAggregation { 14 | min: MinAggregationInner, 15 | } 16 | 17 | #[derive(Debug, Clone, Serialize, PartialEq)] 18 | struct MinAggregationInner { 19 | field: String, 20 | 21 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 22 | missing: Option, 23 | } 24 | 25 | impl Aggregation { 26 | /// Creates an instance of [`MinAggregation`] 27 | /// 28 | /// - `field` - field to aggregate 29 | pub fn min(field: T) -> MinAggregation 30 | where 31 | T: ToString, 32 | { 33 | MinAggregation { 34 | min: MinAggregationInner { 35 | field: field.to_string(), 36 | missing: None, 37 | }, 38 | } 39 | } 40 | } 41 | 42 | impl MinAggregation { 43 | /// The `missing` parameter defines how documents that are missing a value should be treated. By 44 | /// default they will be ignored but it is also possible to treat them as if they had a value. 45 | pub fn missing(mut self, missing: T) -> Self 46 | where 47 | T: Into, 48 | { 49 | self.min.missing = Some(missing.into()); 50 | self 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | 58 | #[test] 59 | fn serialization() { 60 | assert_serialize_aggregation( 61 | Aggregation::min("test_field"), 62 | json!({ "min": { "field": "test_field" } }), 63 | ); 64 | 65 | assert_serialize_aggregation( 66 | Aggregation::min("test_field").missing(100.1), 67 | json!({ 68 | "min": { 69 | "field": "test_field", 70 | "missing": 100.1 71 | } 72 | }), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/search/aggregations/metrics/mod.rs: -------------------------------------------------------------------------------- 1 | //! The aggregations in this family compute metrics based on values extracted in one way or another from the documents that 2 | //! are being aggregated. The values are typically extracted from the fields of the document (using the field data), but 3 | //! can also be generated using scripts. 4 | //! 5 | //! Numeric metrics aggregations are a special type of metrics aggregation which output numeric values. Some aggregations output 6 | //! a single numeric metric (e.g. `avg`) and are called `single-value numeric metrics aggregation`, others generate multiple 7 | //! metrics (e.g. `stats`) and are called `multi-value numeric metrics aggregation`. The distinction between single-value and 8 | //! multi-value numeric metrics aggregations plays a role when these aggregations serve as direct sub-aggregations of some 9 | //! bucket aggregations (some bucket aggregations enable you to sort the returned buckets based on the numeric metrics in each bucket). 10 | //! 11 | //! 12 | 13 | mod avg_aggregation; 14 | mod boxplot_aggregation; 15 | mod cardinality_aggregation; 16 | mod max_aggregation; 17 | mod min_aggregation; 18 | mod rate_aggregation; 19 | mod sum_aggregation; 20 | mod top_hits_aggregation; 21 | 22 | pub use self::avg_aggregation::*; 23 | pub use self::boxplot_aggregation::*; 24 | pub use self::cardinality_aggregation::*; 25 | pub use self::max_aggregation::*; 26 | pub use self::min_aggregation::*; 27 | pub use self::rate_aggregation::*; 28 | pub use self::sum_aggregation::*; 29 | pub use self::top_hits_aggregation::*; 30 | -------------------------------------------------------------------------------- /src/search/aggregations/metrics/rate_aggregation.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// A `rate` metrics aggregation can be used only inside a `date_histogram` and calculates a rate of 5 | /// documents or a field in each `date_histogram` bucket. The field values can be generated extracted 6 | /// from specific numeric or [histogram fields](https://www.elastic.co/guide/en/elasticsearch/reference/current/histogram.html) 7 | /// in the documents. 8 | /// 9 | /// 10 | #[derive(Debug, Clone, Serialize, PartialEq)] 11 | pub struct RateAggregation { 12 | rate: RateAggregationInner, 13 | } 14 | 15 | #[derive(Debug, Clone, Serialize, PartialEq)] 16 | struct RateAggregationInner { 17 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 18 | field: Option, 19 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 20 | unit: Option, 21 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 22 | mode: Option, 23 | } 24 | 25 | impl Aggregation { 26 | /// Creates an instance of [`RateAggregation`] 27 | pub fn rate() -> RateAggregation { 28 | RateAggregation { 29 | rate: RateAggregationInner { 30 | field: None, 31 | unit: None, 32 | mode: None, 33 | }, 34 | } 35 | } 36 | } 37 | 38 | impl RateAggregation { 39 | /// Calculate sum or number of values of the `field` 40 | pub fn field(mut self, field: T) -> Self 41 | where 42 | T: ToString, 43 | { 44 | self.rate.field = Some(field.to_string()); 45 | self 46 | } 47 | 48 | /// The `rate` aggregation supports all rate that can be used [calendar_intervals parameter](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html#calendar_intervals) 49 | /// of `date_histogram` aggregation. The specified rate should compatible with the date_histogram 50 | /// aggregation interval, i.e. it should be possible to convert the bucket size into the rate. 51 | /// By default the interval of the `date_histogram` is used. 52 | /// 53 | /// There is also an additional limitations if the date histogram is not a direct parent of the 54 | /// rate histogram. In this case both rate interval and histogram interval have to be in the 55 | /// same group: [second, `minute`, hour, day, week] or [month, quarter, year]. For example, 56 | /// if the date histogram is month based, only rate intervals of month, quarter or year are 57 | /// supported. If the date histogram is `day` based, only `second`, ` minute`, `hour`, `day, 58 | /// and `week` rate intervals are supported. 59 | pub fn unit(mut self, unit: CalendarInterval) -> Self { 60 | self.rate.unit = Some(unit); 61 | self 62 | } 63 | 64 | /// By default sum mode is used. 65 | /// 66 | /// By adding the `mode` parameter with the value `value_count`, we can change the calculation from 67 | /// `sum` to the number of values of the field. 68 | pub fn mode(mut self, mode: RateMode) -> Self { 69 | self.rate.mode = Some(mode); 70 | self 71 | } 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use super::*; 77 | 78 | #[test] 79 | fn serialization() { 80 | assert_serialize_aggregation(Aggregation::rate(), json!({ "rate": { } })); 81 | 82 | assert_serialize_aggregation( 83 | Aggregation::rate() 84 | .field("price") 85 | .unit(CalendarInterval::Day) 86 | .mode(RateMode::ValueCount), 87 | json!({ 88 | "rate": { 89 | "field": "price", 90 | "unit": "day", 91 | "mode": "value_count" 92 | } 93 | }), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/search/aggregations/metrics/sum_aggregation.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// A `single-value` metrics aggregation that sums up numeric values that are extracted from the 5 | /// aggregated documents. These values can be extracted either from specific numeric or histogram fields. 6 | /// 7 | /// 8 | #[derive(Debug, Clone, Serialize, PartialEq)] 9 | pub struct SumAggregation { 10 | sum: SumAggregationInner, 11 | } 12 | 13 | #[derive(Debug, Clone, Serialize, PartialEq)] 14 | struct SumAggregationInner { 15 | field: String, 16 | 17 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 18 | missing: Option, 19 | } 20 | 21 | impl Aggregation { 22 | /// Creates an instance of [`SumAggregation`] 23 | /// 24 | /// - `field` - field to aggregate 25 | pub fn sum(field: T) -> SumAggregation 26 | where 27 | T: ToString, 28 | { 29 | SumAggregation { 30 | sum: SumAggregationInner { 31 | field: field.to_string(), 32 | missing: None, 33 | }, 34 | } 35 | } 36 | } 37 | 38 | impl SumAggregation { 39 | /// The `missing` parameter defines how documents that are missing a value should be treated. By 40 | /// default documents missing the value will be ignored but it is also possible to treat them 41 | /// as if they had a value. 42 | pub fn missing(mut self, missing: T) -> Self 43 | where 44 | T: Into, 45 | { 46 | self.sum.missing = Some(missing.into()); 47 | self 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | 55 | #[test] 56 | fn serialization() { 57 | assert_serialize_aggregation( 58 | Aggregation::sum("test_field"), 59 | json!({ "sum": { "field": "test_field" } }), 60 | ); 61 | 62 | assert_serialize_aggregation( 63 | Aggregation::sum("test_field").missing(100.1), 64 | json!({ 65 | "sum": { 66 | "field": "test_field", 67 | "missing": 100.1 68 | } 69 | }), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/search/aggregations/metrics/top_hits_aggregation.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// A `top_hits` metric aggregation keeps track of the most relevant document being aggregated. 5 | /// This aggregation is intended to be used as a sub aggregation, 6 | /// so that the top matching documents can be aggregated per bucket. 7 | /// 8 | /// > We do not recommend using `top_hits` as a top-level aggregation. 9 | /// If you want to group search hits, use the 10 | /// [`collapse`](https://www.elastic.co/guide/en/elasticsearch/reference/current/collapse-search-results.html) 11 | /// parameter instead. 12 | /// 13 | /// The `top_hits` aggregation can effectively be used to group result sets 14 | /// by certain fields via a bucket aggregation. One or more bucket aggregations 15 | /// determines by which properties a result set get sliced into. 16 | /// 17 | /// 18 | #[derive(Debug, Clone, Serialize, PartialEq)] 19 | pub struct TopHitsAggregation { 20 | top_hits: TopHitsAggregationInner, 21 | } 22 | 23 | #[derive(Debug, Clone, Serialize, PartialEq)] 24 | struct TopHitsAggregationInner { 25 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 26 | _source: Option, 27 | 28 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 29 | from: Option, 30 | 31 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 32 | size: Option, 33 | 34 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 35 | sort: SortCollection, 36 | } 37 | 38 | impl Aggregation { 39 | /// Creates an instance of [`TopHitsAggregation`] 40 | pub fn top_hits() -> TopHitsAggregation { 41 | TopHitsAggregation { 42 | top_hits: TopHitsAggregationInner { 43 | _source: None, 44 | from: None, 45 | size: None, 46 | sort: Default::default(), 47 | }, 48 | } 49 | } 50 | } 51 | 52 | impl TopHitsAggregation { 53 | /// Indicates which source fields are returned for matching documents 54 | pub fn source(mut self, source: T) -> Self 55 | where 56 | T: Into, 57 | { 58 | self.top_hits._source = Some(source.into()); 59 | self 60 | } 61 | 62 | /// The offset from the first result you want to fetch. 63 | pub fn from(mut self, from: u64) -> Self { 64 | self.top_hits.from = Some(from); 65 | self 66 | } 67 | 68 | /// The maximum number of top matching hits to return per bucket. 69 | /// 70 | /// By default the top three matching hits are returned. 71 | pub fn size(mut self, size: u64) -> Self { 72 | self.top_hits.size = Some(size); 73 | self 74 | } 75 | 76 | /// A collection of sorting fields 77 | pub fn sort(mut self, sort: T) -> Self 78 | where 79 | T: IntoIterator, 80 | T::Item: Into, 81 | { 82 | self.top_hits.sort.extend(sort); 83 | self 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | use super::*; 90 | 91 | #[test] 92 | fn serialization() { 93 | assert_serialize_aggregation(Aggregation::top_hits(), json!({ "top_hits": { } })); 94 | 95 | assert_serialize_aggregation( 96 | Aggregation::top_hits() 97 | .source(false) 98 | .from(2) 99 | .size(10) 100 | .sort(FieldSort::new("sort_field").order(SortOrder::Desc)), 101 | json!({ 102 | "top_hits": { 103 | "_source": false, 104 | "from": 2, 105 | "size": 10, 106 | "sort": [ 107 | { "sort_field": { "order": "desc" } } 108 | ] 109 | } 110 | }), 111 | ); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/search/aggregations/mod.rs: -------------------------------------------------------------------------------- 1 | //! An aggregation summarizes your data as metrics, statistics, or other analytics. 2 | //! 3 | //! Aggregations help you answer questions like: 4 | //! 5 | //! 1. What’s the average load time for my website? 6 | //! 2. Who are my most valuable customers based on transaction volume? 7 | //! 3. What would be considered a large file on my network? 8 | //! 4. How many products are in each product category? 9 | //! 10 | //! Elasticsearch organizes aggregations into three categories: 11 | //! 12 | //! - [Metrics](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics.html) aggregations that calculate metrics, such as a sum or average, from field values. 13 | //! - [Bucket](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket.html) aggregations that group documents into buckets, also called bins, based on field values, ranges, or other criteria. 14 | //! - [Pipeline](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline.html) aggregations that take input from other aggregations instead of documents or fields. 15 | 16 | pub mod bucket; 17 | pub mod metrics; 18 | pub mod params; 19 | pub mod pipeline; 20 | 21 | use crate::Map; 22 | 23 | pub use self::bucket::*; 24 | pub use self::metrics::*; 25 | pub use self::params::*; 26 | pub use self::pipeline::*; 27 | 28 | macro_rules! aggregation { 29 | ($($variant:ident($query:ty)),+ $(,)?) => { 30 | /// A container enum for supported Elasticsearch query types 31 | #[derive(Clone, PartialEq, Serialize)] 32 | #[serde(untagged)] 33 | #[allow(missing_docs, clippy::large_enum_variant)] 34 | pub enum Aggregation { 35 | $( 36 | $variant($query), 37 | )* 38 | } 39 | 40 | impl std::fmt::Debug for Aggregation { 41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 42 | match self { 43 | $( 44 | Self::$variant(q) => q.fmt(f), 45 | )+ 46 | } 47 | } 48 | } 49 | 50 | $( 51 | impl From<$query> for Aggregation { 52 | fn from(q: $query) -> Self { 53 | Aggregation::$variant(q) 54 | } 55 | } 56 | )+ 57 | }; 58 | } 59 | 60 | aggregation!( 61 | Terms(TermsAggregation), 62 | TopHits(TopHitsAggregation), 63 | Cardinality(CardinalityAggregation), 64 | Avg(AvgAggregation), 65 | Max(MaxAggregation), 66 | Min(MinAggregation), 67 | Sum(SumAggregation), 68 | Rate(RateAggregation), 69 | Sampler(SamplerAggregation), 70 | Filter(FilterAggregation), 71 | DiversifiedSampler(DiversifiedSamplerAggregation), 72 | Boxplot(BoxplotAggregation) 73 | ); 74 | 75 | /// Type alias for a collection of aggregations 76 | pub type Aggregations = Map; 77 | -------------------------------------------------------------------------------- /src/search/aggregations/params/aggregation_name.rs: -------------------------------------------------------------------------------- 1 | /// Aggregation name 2 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 3 | pub struct AggregationName(String); 4 | 5 | impl std::fmt::Debug for AggregationName { 6 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 7 | self.0.fmt(f) 8 | } 9 | } 10 | 11 | impl From for AggregationName 12 | where 13 | T: ToString, 14 | { 15 | fn from(value: T) -> Self { 16 | Self(value.to_string()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/search/aggregations/params/gap_policy.rs: -------------------------------------------------------------------------------- 1 | /// Gap policies are a mechanism to inform the pipeline aggregation about the desired behavior when 2 | /// "gappy" or missing data is encountered. All pipeline aggregations accept the `gap_policy` 3 | /// parameter. 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] 5 | #[serde(rename_all = "snake_case")] 6 | pub enum GapPolicy { 7 | /// This option treats missing data as if the bucket does not exist. It will skip the bucket 8 | /// and continue calculating using the next available value. 9 | Skip, 10 | 11 | /// This option will replace missing values with a zero (0) and pipeline aggregation 12 | /// computation will proceed as normal. 13 | InsertZeros, 14 | 15 | /// This option is similar to skip, except if the metric provides a non-null, non-NaN value 16 | /// this value is used, otherwise the empty bucket is skipped. 17 | KeepValues, 18 | } 19 | -------------------------------------------------------------------------------- /src/search/aggregations/params/mod.rs: -------------------------------------------------------------------------------- 1 | //! Value types accepted by aggregation clauses 2 | 3 | mod aggregation_name; 4 | mod gap_policy; 5 | mod rate_mode; 6 | mod terms_order; 7 | 8 | pub use self::aggregation_name::*; 9 | pub use self::gap_policy::*; 10 | pub use self::rate_mode::*; 11 | pub use self::terms_order::*; 12 | -------------------------------------------------------------------------------- /src/search/aggregations/params/rate_mode.rs: -------------------------------------------------------------------------------- 1 | /// Calculate sum or number of values of the field for [RateAggregation](crate::search::RateAggregation) 2 | #[derive(Debug, PartialEq, Eq, Clone, Serialize)] 3 | #[serde(rename_all = "snake_case")] 4 | pub enum RateMode { 5 | /// calculate the sum of all values field 6 | Sum, 7 | /// use the number of values in the field 8 | ValueCount, 9 | } 10 | -------------------------------------------------------------------------------- /src/search/aggregations/params/terms_order.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | util::{KeyValuePair, ShouldSkip}, 3 | SortOrder, 4 | }; 5 | 6 | /// Terms Aggregation sorting criterion 7 | #[derive(Clone, PartialEq, Eq, Serialize)] 8 | pub struct TermsOrder(KeyValuePair); 9 | 10 | impl std::fmt::Debug for TermsOrder { 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | f.debug_struct("TermsOrder") 13 | .field(&self.0.key, &self.0.value) 14 | .finish() 15 | } 16 | } 17 | 18 | impl TermsOrder { 19 | /// Creates an instance of [TermsOrder] 20 | /// 21 | /// - `key` - Key to sort by 22 | /// - `order` - Sorting order 23 | pub fn new(key: T, order: SortOrder) -> Self 24 | where 25 | T: ToString, 26 | { 27 | Self(KeyValuePair::new(key.to_string(), order)) 28 | } 29 | 30 | /// Sorts terms by a given key in ascending order 31 | pub fn ascending(key: T) -> Self 32 | where 33 | T: ToString, 34 | { 35 | Self::new(key, SortOrder::Asc) 36 | } 37 | 38 | /// Sorts terms by a given key in descending order 39 | pub fn descending(key: T) -> Self 40 | where 41 | T: ToString, 42 | { 43 | Self::new(key, SortOrder::Desc) 44 | } 45 | 46 | /// Sorts terms by count ascending 47 | pub fn count_ascending() -> Self { 48 | Self::ascending("_count") 49 | } 50 | 51 | /// Sorts terms by count descending 52 | pub fn count_descending() -> Self { 53 | Self::descending("_count") 54 | } 55 | 56 | /// Sorts terms by count ascending 57 | pub fn key_ascending() -> Self { 58 | Self::ascending("_key") 59 | } 60 | 61 | /// Sorts terms by count descending 62 | pub fn key_descending() -> Self { 63 | Self::descending("_key") 64 | } 65 | } 66 | 67 | /// Terms Aggregation sorting criteria 68 | #[derive(Default, Clone, PartialEq, Eq, Serialize)] 69 | pub struct TermsOrderCollection(Vec); 70 | 71 | impl ShouldSkip for TermsOrderCollection { 72 | fn should_skip(&self) -> bool { 73 | self.0.should_skip() 74 | } 75 | } 76 | 77 | impl std::fmt::Debug for TermsOrderCollection { 78 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 79 | self.0.fmt(f) 80 | } 81 | } 82 | 83 | impl From for TermsOrderCollection { 84 | fn from(value: TermsOrder) -> Self { 85 | Self(vec![value]) 86 | } 87 | } 88 | 89 | impl From for TermsOrderCollection 90 | where 91 | T: IntoIterator, 92 | T::Item: Into, 93 | { 94 | fn from(value: T) -> Self { 95 | Self(value.into_iter().map(Into::into).collect()) 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use super::*; 102 | use crate::util::assert_serialize; 103 | 104 | #[test] 105 | fn serializes() { 106 | assert_serialize(TermsOrder::key_ascending(), json!({ "_key": "asc" })); 107 | assert_serialize(TermsOrder::count_descending(), json!({ "_count": "desc" })); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/search/aggregations/pipeline/mod.rs: -------------------------------------------------------------------------------- 1 | //! Pipeline aggregations work on the outputs produced from other aggregations rather than from document sets, adding 2 | //! information to the output tree. There are many different types of pipeline aggregation, each computing different information from 3 | //! other aggregations, but these types can be broken down into two families: 4 | //! 5 | //! **Parent** 6 | //! > A family of pipeline aggregations that is provided with the output of its parent aggregation and is able 7 | //! to compute new buckets or new aggregations to add to existing buckets. 8 | //! 9 | //! **Sibling** 10 | //! > Pipeline aggregations that are provided with the output of a sibling aggregation and are able to compute a 11 | //! new aggregation which will be at the same level as the sibling aggregation. 12 | //! 13 | //! Pipeline aggregations can reference the aggregations they need to perform their computation by using the `buckets_path` 14 | //! parameter to indicate the paths to the required metrics. The syntax for defining these paths can be found in the 15 | //! <> section below. 16 | //! 17 | //! Pipeline aggregations cannot have sub-aggregations but depending on the type it can reference another pipeline in the `buckets_path` 18 | //! allowing pipeline aggregations to be chained. For example, you can chain together two derivatives to calculate the second derivative 19 | //! (i.e. a derivative of a derivative). 20 | //! 21 | //! > **NOTE**: Because pipeline aggregations only add to the output, when chaining pipeline aggregations the output of each pipeline aggregation 22 | //! will be included in the final output. 23 | //! 24 | //! 25 | 26 | #[doc(hidden)] 27 | pub mod todo {} 28 | -------------------------------------------------------------------------------- /src/search/highlight/encoder.rs: -------------------------------------------------------------------------------- 1 | /// Indicates if the snippet should be HTML encoded. 2 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)] 3 | #[serde(rename_all = "snake_case")] 4 | pub enum Encoder { 5 | /// No encoding 6 | Default, 7 | 8 | /// HTML-escape the snippet text and then insert the highlighting tags 9 | Html, 10 | } 11 | -------------------------------------------------------------------------------- /src/search/highlight/fragmenter.rs: -------------------------------------------------------------------------------- 1 | /// Specifies how text should be broken up in highlight snippets. 2 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)] 3 | #[serde(rename_all = "snake_case")] 4 | pub enum Fragmenter { 5 | /// Breaks up text into same-sized fragments. 6 | Simple, 7 | 8 | /// Breaks up text into same-sized fragments, but tries to avoid breaking up text between 9 | /// highlighted terms. This is helpful when you’re querying for phrases. Default. 10 | Span, 11 | } 12 | -------------------------------------------------------------------------------- /src/search/highlight/matched_fields.rs: -------------------------------------------------------------------------------- 1 | /// Matched fields logic with type conversions 2 | #[derive(Clone, Default, PartialEq, Eq, Serialize)] 3 | pub struct MatchedFields(Vec); 4 | 5 | impl std::fmt::Debug for MatchedFields { 6 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 7 | self.0.fmt(f) 8 | } 9 | } 10 | 11 | impl From for MatchedFields 12 | where 13 | T: IntoIterator, 14 | T::Item: ToString, 15 | { 16 | fn from(value: T) -> Self { 17 | Self(value.into_iter().map(|x| x.to_string()).collect()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/search/highlight/order.rs: -------------------------------------------------------------------------------- 1 | /// Sorts highlighted fragments by score when set to [`score`](Order::Score). By default, 2 | /// fragments will be output in the order they appear in the field 3 | /// (order: [`none`](Order::None)). Setting this option to [`score`](Order::Score) will output 4 | /// the most relevant fragments first. Each highlighter applies its own logic to compute 5 | /// relevancy scores. See the document 6 | /// [How highlighters work internally](https://www.elastic.co/guide/en/elasticsearch/reference/current/highlighting.html#how-es-highlighters-work-internally) 7 | /// for more details how different highlighters find the best fragments. 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)] 9 | #[serde(rename_all = "snake_case")] 10 | pub enum Order { 11 | /// Sorts highlighted fragments by score. 12 | Score, 13 | 14 | /// Highlighted fragments will be output in the order they appear in the field. 15 | None, 16 | } 17 | -------------------------------------------------------------------------------- /src/search/mod.rs: -------------------------------------------------------------------------------- 1 | //! Search APIs are used to search and aggregate data stored in Elasticsearch 2 | //! indices and data streams. For an overview and related tutorials, see 3 | //! [Search your data](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-your-data.html). 4 | //! 5 | //! Most search APIs support 6 | //! [multi-target syntax](https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-index.html), 7 | //! with the exception of the 8 | //! [explain API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html). 9 | //! 10 | //! 11 | 12 | // Private modules 13 | mod response; 14 | 15 | // Public modules 16 | pub mod aggregations; 17 | pub mod highlight; 18 | pub mod params; 19 | pub mod queries; 20 | pub mod request; 21 | pub mod rescoring; 22 | pub mod runtime_mappings; 23 | pub mod sort; 24 | pub mod suggesters; 25 | 26 | // Public re-exports 27 | pub use self::aggregations::*; 28 | pub use self::highlight::*; 29 | pub use self::params::*; 30 | pub use self::queries::params::*; 31 | pub use self::queries::*; 32 | pub use self::request::*; 33 | pub use self::rescoring::*; 34 | pub use self::response::*; 35 | pub use self::runtime_mappings::*; 36 | pub use self::sort::*; 37 | pub use self::suggesters::*; 38 | -------------------------------------------------------------------------------- /src/search/params/coordinate.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::{fmt::Display, str::FromStr}; 3 | 4 | /// Represents a point in two dimensional space 5 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] 6 | pub struct Coordinate { 7 | x: f32, 8 | y: f32, 9 | } 10 | 11 | impl Coordinate { 12 | /// Creates an instance of [`Coordinate`] 13 | pub fn new(x: f32, y: f32) -> Self { 14 | Self { x, y } 15 | } 16 | } 17 | 18 | impl Display for Coordinate { 19 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 20 | format!("[{}, {}]", &self.x, &self.y).fmt(f) 21 | } 22 | } 23 | 24 | impl Serialize for Coordinate { 25 | fn serialize(&self, serializer: S) -> Result 26 | where 27 | S: serde::Serializer, 28 | { 29 | [self.x, self.y].serialize(serializer) 30 | } 31 | } 32 | 33 | impl From<[f32; 2]> for Coordinate { 34 | fn from(value: [f32; 2]) -> Self { 35 | Self { 36 | x: value[0], 37 | y: value[1], 38 | } 39 | } 40 | } 41 | 42 | impl From<(f32, f32)> for Coordinate { 43 | fn from(value: (f32, f32)) -> Self { 44 | Self { 45 | x: value.0, 46 | y: value.1, 47 | } 48 | } 49 | } 50 | 51 | impl FromStr for Coordinate { 52 | type Err = String; 53 | 54 | fn from_str(s: &str) -> Result { 55 | let mut values = s.split(','); 56 | 57 | let x = values.next().and_then(|x| x.trim().parse().ok()); 58 | let y = values.next().and_then(|x| x.trim().parse().ok()); 59 | 60 | match (x, y, values.next()) { 61 | (Some(x), Some(y), None) => Ok(Self { x, y }), 62 | _ => Err(format!("Couldn't parse '{s}' as coordinate")), 63 | } 64 | } 65 | } 66 | 67 | #[cfg(test)] 68 | mod tests { 69 | use super::*; 70 | use crate::util::*; 71 | 72 | #[test] 73 | fn serialization() { 74 | assert_serialize(Coordinate::new(1.1, 2.2), json!([1.1, 2.2])); 75 | assert_serialize(Coordinate::from([1.1, 2.2]), json!([1.1, 2.2])); 76 | assert_serialize(Coordinate::from((1.1, 2.2)), json!([1.1, 2.2])); 77 | } 78 | 79 | #[test] 80 | fn from_str() { 81 | assert_eq!( 82 | Coordinate::from_str("1.1, 2.2").unwrap(), 83 | Coordinate::new(1.1, 2.2) 84 | ); 85 | assert_eq!( 86 | Coordinate::from_str("1,2").unwrap(), 87 | Coordinate::new(1., 2.) 88 | ); 89 | 90 | assert!(Coordinate::from_str("1.1").is_err()); 91 | assert!(Coordinate::from_str("1.1,2.2,3").is_err()); 92 | assert!(Coordinate::from_str("abc").is_err()); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/search/params/date.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use std::time::SystemTime; 3 | 4 | /// [`DateTime`] type alias 5 | pub type ChronoTime = DateTime; 6 | 7 | /// Time variants to serialize 8 | #[derive(Clone, Copy, Serialize)] 9 | #[serde(untagged)] 10 | pub enum Date { 11 | /// System time 12 | System(SystemTime), 13 | 14 | /// Chrono time 15 | Chrono(ChronoTime), 16 | } 17 | 18 | impl std::fmt::Debug for Date { 19 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 20 | match self { 21 | Self::System(value) => value.fmt(f), 22 | Self::Chrono(value) => value.fmt(f), 23 | } 24 | } 25 | } 26 | 27 | impl From for Date { 28 | fn from(value: SystemTime) -> Self { 29 | Self::System(value) 30 | } 31 | } 32 | 33 | impl From for Date { 34 | fn from(value: ChronoTime) -> Self { 35 | Self::Chrono(value) 36 | } 37 | } 38 | 39 | impl From<&SystemTime> for Date { 40 | fn from(value: &SystemTime) -> Self { 41 | Self::System(*value) 42 | } 43 | } 44 | 45 | impl From<&ChronoTime> for Date { 46 | fn from(value: &ChronoTime) -> Self { 47 | Self::Chrono(*value) 48 | } 49 | } 50 | 51 | impl PartialEq for Date { 52 | fn eq(&self, other: &Self) -> bool { 53 | match (self, other) { 54 | (Date::System(s), Date::System(o)) => s.eq(o), 55 | (Date::System(s), Date::Chrono(o)) => ChronoTime::from(*s).eq(o), 56 | (Date::Chrono(s), Date::System(o)) => s.eq(&ChronoTime::from(*o)), 57 | (Date::Chrono(s), Date::Chrono(o)) => s.eq(o), 58 | } 59 | } 60 | } 61 | 62 | impl PartialEq for Date { 63 | fn eq(&self, other: &SystemTime) -> bool { 64 | match self { 65 | Self::System(s) => s.eq(other), 66 | Self::Chrono(s) => s.eq(&ChronoTime::from(*other)), 67 | } 68 | } 69 | } 70 | 71 | impl PartialEq for Date { 72 | fn eq(&self, other: &ChronoTime) -> bool { 73 | match self { 74 | Self::Chrono(s) => s.eq(other), 75 | Self::System(s) => ChronoTime::from(*s).eq(other), 76 | } 77 | } 78 | } 79 | 80 | impl Eq for Date {} 81 | 82 | impl PartialOrd for Date { 83 | fn partial_cmp(&self, other: &Self) -> Option { 84 | Some(self.cmp(other)) 85 | } 86 | } 87 | 88 | impl PartialOrd for Date { 89 | fn partial_cmp(&self, other: &SystemTime) -> Option { 90 | match self { 91 | Date::System(s) => s.partial_cmp(other), 92 | Date::Chrono(s) => s.partial_cmp(&ChronoTime::from(*other)), 93 | } 94 | } 95 | } 96 | 97 | impl PartialOrd for Date { 98 | fn partial_cmp(&self, other: &ChronoTime) -> Option { 99 | match self { 100 | Self::Chrono(s) => s.partial_cmp(other), 101 | Self::System(s) => ChronoTime::from(*s).partial_cmp(other), 102 | } 103 | } 104 | } 105 | 106 | impl Ord for Date { 107 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 108 | match (self, other) { 109 | (Date::System(s), Date::System(o)) => s.cmp(o), 110 | (Date::System(s), Date::Chrono(o)) => ChronoTime::from(*s).cmp(o), 111 | (Date::Chrono(s), Date::System(o)) => s.cmp(&ChronoTime::from(*o)), 112 | (Date::Chrono(s), Date::Chrono(o)) => s.cmp(o), 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/search/params/geo_distance_type.rs: -------------------------------------------------------------------------------- 1 | /// How to compute the distance 2 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 3 | #[serde(rename_all = "snake_case")] 4 | pub enum GeoDistanceType { 5 | /// Faster, but inaccurate on long distances and close to the poles 6 | Plane, 7 | 8 | /// Default 9 | Arc, 10 | } 11 | -------------------------------------------------------------------------------- /src/search/params/geo_location.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | /// Represents a point in two dimensional space 4 | #[derive(Debug, Clone, Copy, PartialEq)] 5 | pub struct GeoLocation { 6 | latitude: f32, 7 | longitude: f32, 8 | } 9 | 10 | impl GeoLocation { 11 | /// Creates an instance of [GeoLocation] 12 | pub fn new(latitude: f32, longitude: f32) -> Self { 13 | Self { 14 | latitude, 15 | longitude, 16 | } 17 | } 18 | } 19 | 20 | impl Serialize for GeoLocation { 21 | fn serialize(&self, serializer: S) -> Result 22 | where 23 | S: serde::Serializer, 24 | { 25 | [self.longitude, self.latitude].serialize(serializer) 26 | } 27 | } 28 | 29 | impl From<[f32; 2]> for GeoLocation { 30 | fn from(value: [f32; 2]) -> Self { 31 | Self { 32 | latitude: value[1], 33 | longitude: value[0], 34 | } 35 | } 36 | } 37 | 38 | impl From<(f32, f32)> for GeoLocation { 39 | fn from(value: (f32, f32)) -> Self { 40 | Self { 41 | latitude: value.1, 42 | longitude: value.0, 43 | } 44 | } 45 | } 46 | 47 | impl IntoIterator for GeoLocation { 48 | type Item = Self; 49 | 50 | type IntoIter = std::option::IntoIter; 51 | 52 | fn into_iter(self) -> Self::IntoIter { 53 | Some(self).into_iter() 54 | } 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | use crate::util::*; 61 | 62 | #[test] 63 | fn serialization() { 64 | assert_serialize(GeoLocation::new(1.1, 2.2), json!([2.2, 1.1])); 65 | assert_serialize(GeoLocation::from([2.2, 1.1]), json!([2.2, 1.1])); 66 | assert_serialize(GeoLocation::from((2.2, 1.1)), json!([2.2, 1.1])); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/search/params/mod.rs: -------------------------------------------------------------------------------- 1 | //! Value types accepted by leaf query clauses 2 | 3 | mod coordinate; 4 | mod date; 5 | mod geo_distance_type; 6 | mod geo_location; 7 | mod geo_shape; 8 | mod number; 9 | mod score_mode; 10 | mod script_sort_type; 11 | mod search_filter; 12 | mod shape; 13 | mod term; 14 | mod terms; 15 | mod text; 16 | mod track_total_hits; 17 | mod units; 18 | 19 | pub use self::coordinate::*; 20 | pub use self::date::*; 21 | pub use self::geo_distance_type::*; 22 | pub use self::geo_location::*; 23 | pub use self::geo_shape::*; 24 | pub use self::number::*; 25 | pub use self::score_mode::*; 26 | pub use self::script_sort_type::*; 27 | pub use self::search_filter::*; 28 | pub use self::shape::*; 29 | pub use self::term::*; 30 | pub use self::terms::*; 31 | pub use self::text::*; 32 | pub use self::track_total_hits::*; 33 | pub use self::units::*; 34 | -------------------------------------------------------------------------------- /src/search/params/score_mode.rs: -------------------------------------------------------------------------------- 1 | /// The way the scores are combined can be controlled 2 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] 3 | #[serde(rename_all = "snake_case")] 4 | pub enum ScoreMode { 5 | /// Add the original score and the rescore query score. 6 | Total, 7 | 8 | /// Multiply the original score by the rescore query score. 9 | Multiply, 10 | 11 | /// Take the min of the original score and the rescore query score. 12 | Min, 13 | 14 | /// Take the max of original score and the rescore query score. 15 | Max, 16 | 17 | /// Average the original score and the rescore query score. 18 | Avg, 19 | } 20 | -------------------------------------------------------------------------------- /src/search/params/script_sort_type.rs: -------------------------------------------------------------------------------- 1 | /// How to treat sorting script value 2 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] 3 | #[serde(rename_all = "snake_case")] 4 | pub enum ScriptSortType { 5 | /// Sort script result as a string 6 | String, 7 | 8 | /// Sort script result as a number 9 | Number, 10 | } 11 | -------------------------------------------------------------------------------- /src/search/params/terms.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::util::*; 3 | 4 | /// A collection of terms 5 | #[derive(Clone, PartialEq, Serialize)] 6 | pub struct Terms(Vec); 7 | 8 | impl std::fmt::Debug for Terms { 9 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 10 | self.0.fmt(f) 11 | } 12 | } 13 | 14 | impl From for Terms 15 | where 16 | T: IntoIterator, 17 | T::Item: serde::Serialize, 18 | { 19 | fn from(value: T) -> Self { 20 | Self(value.into_iter().filter_map(Term::new).collect()) 21 | } 22 | } 23 | 24 | impl ShouldSkip for Terms { 25 | fn should_skip(&self) -> bool { 26 | self.0.is_empty() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/search/params/text.rs: -------------------------------------------------------------------------------- 1 | use crate::util::*; 2 | use std::borrow::Cow; 3 | 4 | /// Search text 5 | #[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)] 6 | pub struct Text(Option); 7 | 8 | impl std::fmt::Debug for Text { 9 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 10 | self.0.fmt(f) 11 | } 12 | } 13 | 14 | impl ShouldSkip for Text { 15 | fn should_skip(&self) -> bool { 16 | self.0.as_ref().map_or(true, ShouldSkip::should_skip) 17 | } 18 | } 19 | 20 | impl From for Text { 21 | fn from(value: String) -> Self { 22 | Self(Some(value)) 23 | } 24 | } 25 | 26 | impl From> for Text { 27 | fn from(value: Option) -> Self { 28 | Self(value) 29 | } 30 | } 31 | 32 | impl From<&str> for Text { 33 | fn from(value: &str) -> Self { 34 | Self(Some(value.into())) 35 | } 36 | } 37 | 38 | impl From> for Text { 39 | fn from(value: Option<&str>) -> Self { 40 | Self(value.map(Into::into)) 41 | } 42 | } 43 | 44 | impl<'a> From> for Text { 45 | fn from(value: Cow<'a, str>) -> Self { 46 | Self(Some(value.into())) 47 | } 48 | } 49 | 50 | impl<'a> From>> for Text { 51 | fn from(value: Option>) -> Self { 52 | Self(value.map(Into::into)) 53 | } 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use super::*; 59 | 60 | #[test] 61 | fn skips_correctly() { 62 | assert!(Text::from(None::).should_skip()); 63 | assert!(Text::from("").should_skip()); 64 | assert!(Text::from(" ").should_skip()); 65 | } 66 | 67 | #[test] 68 | fn compares_correctly() { 69 | assert_eq!(Text::from("abc"), Text::from(Some("abc"))); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/search/params/track_total_hits.rs: -------------------------------------------------------------------------------- 1 | /// Control how the total number of hits should be tracked. 2 | /// 3 | /// When set to `Track` with a value `true`, the response will always track the number of hits that 4 | /// match the query accurately. 5 | /// 6 | /// When set to `Count` with an integer value `n`, the response accurately tracks the total 7 | /// hit count that match the query up to `n` documents. 8 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 9 | #[serde(untagged)] 10 | pub enum TrackTotalHits { 11 | /// Whether to accurately track the number of hits that match the query accurately 12 | Track(bool), 13 | 14 | /// Accurately track the number of hits up to the specified value 15 | Count(i64), 16 | } 17 | 18 | impl From for TrackTotalHits { 19 | fn from(value: bool) -> Self { 20 | TrackTotalHits::Track(value) 21 | } 22 | } 23 | 24 | impl From for TrackTotalHits { 25 | fn from(value: i64) -> Self { 26 | TrackTotalHits::Count(value) 27 | } 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | use crate::util::*; 34 | 35 | #[test] 36 | fn serialization() { 37 | assert_serialize( 38 | [ 39 | TrackTotalHits::Track(false), 40 | TrackTotalHits::Track(true), 41 | TrackTotalHits::Count(10), 42 | ], 43 | json!([false, true, 10,]), 44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/search/queries/compound/constant_score_query.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// Wraps a [filter query](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html) 5 | /// and returns every matching document with a 6 | /// [relevance score](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html#relevance-scores) 7 | /// equal to the `boost` parameter value. 8 | /// 9 | /// To create constant score query: 10 | /// ``` 11 | /// # use elasticsearch_dsl::queries::*; 12 | /// # use elasticsearch_dsl::queries::params::*; 13 | /// # let query = 14 | /// Query::constant_score(Query::term("test1", 123)) 15 | /// .boost(3) 16 | /// .name("test"); 17 | /// ``` 18 | /// 19 | #[derive(Debug, Clone, PartialEq, Serialize)] 20 | #[serde(remote = "Self")] 21 | pub struct ConstantScoreQuery { 22 | filter: Box, 23 | 24 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 25 | boost: Option, 26 | 27 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 28 | _name: Option, 29 | } 30 | 31 | impl Query { 32 | /// Creates an instance of [`ConstantScoreQuery`] 33 | /// 34 | /// - `filter` - [Filter query](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html) 35 | /// you wish to run. Any returned documents must match this query.
36 | /// Filter queries do not calculate 37 | /// [relevance scores](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html#relevance-scores). 38 | /// To speed up performance, Elasticsearch automatically caches frequently used filter queries. 39 | pub fn constant_score(filter: T) -> ConstantScoreQuery 40 | where 41 | T: Into, 42 | { 43 | ConstantScoreQuery { 44 | filter: Box::new(filter.into()), 45 | boost: None, 46 | _name: None, 47 | } 48 | } 49 | } 50 | 51 | impl ConstantScoreQuery { 52 | add_boost_and_name!(); 53 | } 54 | 55 | impl ShouldSkip for ConstantScoreQuery { 56 | fn should_skip(&self) -> bool { 57 | self.filter.should_skip() 58 | } 59 | } 60 | 61 | serialize_with_root!("constant_score": ConstantScoreQuery); 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::*; 66 | 67 | #[test] 68 | fn serialization() { 69 | assert_serialize_query( 70 | Query::constant_score(Query::term("test1", 123)), 71 | json!({ 72 | "constant_score": { 73 | "filter": { 74 | "term": { 75 | "test1": { 76 | "value": 123 77 | } 78 | } 79 | } 80 | } 81 | }), 82 | ); 83 | 84 | assert_serialize_query( 85 | Query::constant_score(Query::term("test1", 123)) 86 | .boost(3) 87 | .name("test"), 88 | json!({ 89 | "constant_score": { 90 | "filter": { 91 | "term": { 92 | "test1": { 93 | "value": 123 94 | } 95 | } 96 | }, 97 | "boost": 3.0, 98 | "_name": "test" 99 | } 100 | }), 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/search/queries/compound/mod.rs: -------------------------------------------------------------------------------- 1 | //! Compound queries wrap other compound or leaf queries, either to combine their results and 2 | //! scores, to change their behavior, or to switch from query to filter context. 3 | 4 | mod bool_query; 5 | mod boosting_query; 6 | mod constant_score_query; 7 | mod dis_max_query; 8 | mod function_score_query; 9 | 10 | pub use self::bool_query::*; 11 | pub use self::boosting_query::*; 12 | pub use self::constant_score_query::*; 13 | pub use self::dis_max_query::*; 14 | pub use self::function_score_query::*; 15 | -------------------------------------------------------------------------------- /src/search/queries/custom/json_query.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// Raw JSON query for something not yet supported. 5 | /// 6 | /// To create JSON query: 7 | /// ``` 8 | /// # use elasticsearch_dsl::queries::*; 9 | /// # use elasticsearch_dsl::queries::params::*; 10 | /// # let query = 11 | /// Query::json(serde_json::json!({ "term": { "user": "username" } })); 12 | /// ``` 13 | /// **NOTE**: This is fallible and can lead to incorrect queries and 14 | /// rejected search requests, use ar your own risk. 15 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 16 | pub struct JsonQuery(serde_json::Value); 17 | 18 | impl Query { 19 | /// Creates an instance of [`JsonQuery`] 20 | /// 21 | /// - `query` - raw JSON query 22 | pub fn json(query: serde_json::Value) -> JsonQuery { 23 | JsonQuery(query) 24 | } 25 | } 26 | 27 | impl From for Query { 28 | fn from(value: serde_json::Value) -> Self { 29 | Self::Json(JsonQuery(value)) 30 | } 31 | } 32 | 33 | impl From for JsonQuery { 34 | fn from(value: serde_json::Value) -> Self { 35 | Self(value) 36 | } 37 | } 38 | 39 | impl ShouldSkip for JsonQuery { 40 | fn should_skip(&self) -> bool { 41 | !self.0.is_object() 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | 49 | #[test] 50 | fn serialization() { 51 | assert_serialize_query( 52 | Query::json(json!({ "term": { "user": "username" } })), 53 | json!({ "term": { "user": "username" } }), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/search/queries/custom/mod.rs: -------------------------------------------------------------------------------- 1 | //! Non official queries, such as plugins or raw JSON queries 2 | 3 | mod json_query; 4 | 5 | pub use self::json_query::*; 6 | -------------------------------------------------------------------------------- /src/search/queries/full_text/mod.rs: -------------------------------------------------------------------------------- 1 | //! The full text queries enable you to search analyzed text fields such as the body of an email. 2 | //! The query string is processed using the same analyzer that was applied to the field during 3 | //! indexing. 4 | //! 5 | //! 6 | 7 | mod combined_fields_query; 8 | mod match_bool_prefix_query; 9 | mod match_phrase_prefix_query; 10 | mod match_phrase_query; 11 | mod match_query; 12 | mod multi_match_query; 13 | mod query_string_query; 14 | mod simple_query_string_query; 15 | 16 | pub use self::combined_fields_query::*; 17 | pub use self::match_bool_prefix_query::*; 18 | pub use self::match_phrase_prefix_query::*; 19 | pub use self::match_phrase_query::*; 20 | pub use self::match_query::*; 21 | pub use self::multi_match_query::*; 22 | pub use self::query_string_query::*; 23 | pub use self::simple_query_string_query::*; 24 | -------------------------------------------------------------------------------- /src/search/queries/geo/mod.rs: -------------------------------------------------------------------------------- 1 | //! Elasticsearch supports two types of geo data: geo_point fields which support lat/lon pairs, and 2 | //! geo_shape fields, which support points, lines, circles, polygons, multi-polygons, etc. 3 | 4 | mod geo_bounding_box_query; 5 | mod geo_distance_query; 6 | mod geo_shape_lookup_query; 7 | mod geo_shape_query; 8 | 9 | pub use self::geo_bounding_box_query::*; 10 | pub use self::geo_distance_query::*; 11 | pub use self::geo_shape_lookup_query::*; 12 | pub use self::geo_shape_query::*; 13 | -------------------------------------------------------------------------------- /src/search/queries/joining/mod.rs: -------------------------------------------------------------------------------- 1 | //! Performing full SQL-style joins in a distributed system like Elasticsearch is prohibitively 2 | //! expensive. Instead, Elasticsearch offers two forms of join which are designed to scale 3 | //! horizontally. 4 | //! 5 | //! 6 | 7 | mod has_child_query; 8 | mod has_parent_query; 9 | mod nested_query; 10 | mod parent_id_query; 11 | 12 | pub use self::has_child_query::*; 13 | pub use self::has_parent_query::*; 14 | pub use self::nested_query::*; 15 | pub use self::parent_id_query::*; 16 | -------------------------------------------------------------------------------- /src/search/queries/joining/parent_id_query.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// Returns child documents joined to a specific parent document. You can use a join field mapping 5 | /// to create parent-child relationships between documents in the same index. 6 | /// 7 | /// To create parent_id query: 8 | /// ``` 9 | /// # use elasticsearch_dsl::queries::*; 10 | /// # use elasticsearch_dsl::queries::params::*; 11 | /// # let query = 12 | /// Query::parent_id("test", 1); 13 | /// ``` 14 | /// 15 | #[derive(Debug, Clone, PartialEq, Serialize)] 16 | #[serde(remote = "Self")] 17 | pub struct ParentIdQuery { 18 | r#type: String, 19 | 20 | id: String, 21 | 22 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 23 | ignore_unmapped: Option, 24 | 25 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 26 | boost: Option, 27 | 28 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 29 | _name: Option, 30 | } 31 | 32 | impl Query { 33 | /// Creates an instance of [`ParentIdQuery`] 34 | /// 35 | /// - `type` - Name of the child relationship mapped for the join field 36 | /// - `id` - ID of the parent document. The query will return child documents of this 37 | /// parent document. 38 | pub fn parent_id(r#type: T, id: U) -> ParentIdQuery 39 | where 40 | T: ToString, 41 | U: ToString, 42 | { 43 | ParentIdQuery { 44 | r#type: r#type.to_string(), 45 | id: id.to_string(), 46 | ignore_unmapped: None, 47 | boost: None, 48 | _name: None, 49 | } 50 | } 51 | } 52 | 53 | impl ParentIdQuery { 54 | /// Indicates whether to ignore an unmapped `type` and not return any documents instead of an 55 | /// error. Defaults to `false`. 56 | /// 57 | /// If `false`, Elasticsearch returns an error if the `type` is unmapped. 58 | /// 59 | /// You can use this parameter to query multiple indices that may not contain the `type`. 60 | pub fn ignore_unmapped(mut self, ignore_unmapped: bool) -> Self { 61 | self.ignore_unmapped = Some(ignore_unmapped); 62 | self 63 | } 64 | 65 | add_boost_and_name!(); 66 | } 67 | 68 | impl ShouldSkip for ParentIdQuery { 69 | fn should_skip(&self) -> bool { 70 | self.r#type.should_skip() || self.id.should_skip() 71 | } 72 | } 73 | 74 | serialize_with_root!("parent_id": ParentIdQuery); 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | use super::*; 79 | 80 | #[test] 81 | fn serialization() { 82 | assert_serialize_query( 83 | Query::parent_id("my-child", 1), 84 | json!({ 85 | "parent_id": { 86 | "type": "my-child", 87 | "id": "1" 88 | } 89 | }), 90 | ); 91 | 92 | assert_serialize_query( 93 | Query::parent_id("my-child", 1) 94 | .boost(2) 95 | .name("test") 96 | .ignore_unmapped(true), 97 | json!({ 98 | "parent_id": { 99 | "type": "my-child", 100 | "id": "1", 101 | "ignore_unmapped": true, 102 | "boost": 2.0, 103 | "_name": "test" 104 | } 105 | }), 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/search/queries/match_all_query.rs: -------------------------------------------------------------------------------- 1 | use super::Query; 2 | use crate::util::*; 3 | 4 | /// The most simple query, which matches all documents, giving them all a 5 | /// `_score` of `1.0`. 6 | /// 7 | /// To create match_all query: 8 | /// ``` 9 | /// # use elasticsearch_dsl::queries::*; 10 | /// # use elasticsearch_dsl::queries::params::*; 11 | /// # let query = 12 | /// Query::match_all() 13 | /// .boost(2) 14 | /// .name("matches_everything"); 15 | /// ``` 16 | /// 17 | #[derive(Debug, Clone, PartialEq, Serialize, Default)] 18 | #[serde(remote = "Self")] 19 | pub struct MatchAllQuery { 20 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 21 | boost: Option, 22 | 23 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 24 | _name: Option, 25 | } 26 | 27 | serialize_with_root!("match_all": MatchAllQuery); 28 | 29 | impl Query { 30 | /// Creates an instance of [`MatchAllQuery`] 31 | pub fn match_all() -> MatchAllQuery { 32 | MatchAllQuery::default() 33 | } 34 | } 35 | 36 | impl MatchAllQuery { 37 | add_boost_and_name!(); 38 | } 39 | 40 | impl ShouldSkip for MatchAllQuery {} 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn serialization() { 48 | assert_serialize_query(Query::match_all(), json!({ "match_all": {} })); 49 | 50 | assert_serialize_query( 51 | Query::match_all().boost(2).name("test"), 52 | json!({ "match_all": { "boost": 2.0, "_name": "test" } }), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/search/queries/match_none_query.rs: -------------------------------------------------------------------------------- 1 | use super::Query; 2 | use crate::util::*; 3 | 4 | /// This is the inverse of the [`match_all`](crate::queries::MatchAllQuery) 5 | /// query, which matches no documents. 6 | /// 7 | /// To create match_none query: 8 | /// ``` 9 | /// # use elasticsearch_dsl::queries::*; 10 | /// # use elasticsearch_dsl::queries::params::*; 11 | /// # let query = 12 | /// Query::match_none() 13 | /// .boost(2) 14 | /// .name("matches_nothing"); 15 | /// ``` 16 | /// 17 | #[derive(Debug, Clone, PartialEq, Serialize, Default)] 18 | #[serde(remote = "Self")] 19 | pub struct MatchNoneQuery { 20 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 21 | boost: Option, 22 | 23 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 24 | _name: Option, 25 | } 26 | 27 | impl Query { 28 | /// Creates an instance of [`MatchNoneQuery`] 29 | pub fn match_none() -> MatchNoneQuery { 30 | MatchNoneQuery::default() 31 | } 32 | } 33 | 34 | impl MatchNoneQuery { 35 | add_boost_and_name!(); 36 | } 37 | 38 | serialize_with_root!("match_none": MatchNoneQuery); 39 | 40 | impl ShouldSkip for MatchNoneQuery {} 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn serialization() { 48 | assert_serialize_query(Query::match_none(), json!({"match_none": {} })); 49 | 50 | assert_serialize_query( 51 | Query::match_none().boost(2).name("test"), 52 | json!({ "match_none": { "boost": 2.0, "_name": "test" } }), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/search/queries/params/fuzziness.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Serializer}; 2 | use std::ops::Range; 3 | 4 | /// Some queries and APIs support parameters to allow inexact _fuzzy_ matching, 5 | /// using the `fuzziness` parameter. 6 | /// 7 | /// When querying `text` or `keyword` fields, `fuzziness` is interpreted as a 8 | /// [Levenshtein Edit Distance](https://en.wikipedia.org/wiki/Levenshtein_distance) 9 | /// — the number of one character changes that need to be made to one string to make it the same as another string. 10 | /// 11 | /// 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 13 | pub enum Fuzziness { 14 | /// Generates an edit distance based on the length of the term. 15 | /// 16 | /// `AUTO` should generally be the preferred value for `fuzziness`. 17 | Auto, 18 | 19 | /// Low and high distance arguments may be optionally provided 20 | /// `AUTO:[low],[high]`. If not specified, the default values are 3 and 6, 21 | /// equivalent to `AUTO:3,6` that make for lengths: 22 | /// 23 | /// **`0..2`** 24 | /// 25 | ///     Must match exactly 26 | /// 27 | /// **`3..5`** 28 | /// 29 | ///     One edit allowed 30 | /// 31 | /// **`>5`** 32 | /// 33 | ///     Two edits allowed 34 | Range(u8, u8), 35 | 36 | /// The maximum allowed Levenshtein Edit Distance (or number of edits) 37 | Distance(u8), 38 | } 39 | 40 | impl Serialize for Fuzziness { 41 | fn serialize(&self, serializer: S) -> Result 42 | where 43 | S: Serializer, 44 | { 45 | match self { 46 | Self::Auto => "AUTO".serialize(serializer), 47 | Self::Range(start, end) => format!("AUTO:{start},{end}").serialize(serializer), 48 | Self::Distance(d) => d.serialize(serializer), 49 | } 50 | } 51 | } 52 | 53 | impl From> for Fuzziness { 54 | fn from(v: Range) -> Self { 55 | Self::Range(v.start, v.end) 56 | } 57 | } 58 | 59 | impl From<[u8; 2]> for Fuzziness { 60 | fn from(v: [u8; 2]) -> Self { 61 | Self::Range(v[0], v[1]) 62 | } 63 | } 64 | 65 | impl From for Fuzziness { 66 | fn from(v: u8) -> Self { 67 | Self::Distance(v) 68 | } 69 | } 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use super::*; 74 | use crate::util::assert_serialize; 75 | 76 | #[test] 77 | fn implements_from_u8() { 78 | let result = Fuzziness::from(8); 79 | 80 | let expectation = Fuzziness::Distance(8); 81 | 82 | assert_eq!(result, expectation); 83 | } 84 | 85 | #[test] 86 | fn implements_from_range_u8() { 87 | let result = Fuzziness::from(0..2); 88 | 89 | let expectation = Fuzziness::Range(0, 2); 90 | 91 | assert_eq!(result, expectation); 92 | } 93 | 94 | #[test] 95 | fn serializes() { 96 | assert_serialize( 97 | [ 98 | Fuzziness::Auto, 99 | Fuzziness::Range(0, 2), 100 | Fuzziness::Distance(5), 101 | ], 102 | json!(["AUTO", "AUTO:0,2", 5,]), 103 | ) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/search/queries/params/geo_query.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use serde::Serialize; 3 | 4 | /// Strategies to verify the correctness of coordinates 5 | #[derive(Debug, PartialEq, Eq, Clone, Serialize)] 6 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 7 | pub enum ValidationMethod { 8 | /// accept geo points with invalid latitude or longitude 9 | IgnoreMalformed, 10 | 11 | /// try to infer correct latitude or longitude 12 | Coerce, 13 | 14 | /// strict mode 15 | Strict, 16 | } 17 | 18 | /// Different representations of geo bounding box 19 | #[derive(Debug, PartialEq, Clone, Serialize)] 20 | #[serde(untagged)] 21 | pub enum GeoBoundingBox { 22 | /// MainDiagonal vertices of geo bounding box 23 | MainDiagonal { 24 | /// The coordinates of the upper left vertex 25 | top_left: GeoLocation, 26 | /// The coordinates of the lower right vertex 27 | bottom_right: GeoLocation, 28 | }, 29 | 30 | /// SubDiagonal vertices of geo bounding box 31 | SubDiagonal { 32 | /// The coordinates of the upper right vertex 33 | top_right: GeoLocation, 34 | /// The coordinates of the lower left vertex 35 | bottom_left: GeoLocation, 36 | }, 37 | 38 | /// Well-Known Text (WKT). 39 | WellKnownText { 40 | /// e.g. `BBOX (-74.1, -71.12, 40.73, 40.01)` 41 | wkt: String, 42 | }, 43 | 44 | /// The vertices of the bounding box can either be set by `top_left` and `bottom_right` or by 45 | /// `top_right` and `bottom_left` parameters. More over the names `topLeft`, `bottomRight`, `topRight` 46 | /// and `bottomLeft` are supported. Instead of setting the values pairwise, one can use the simple 47 | /// names `top`, `left`, `bottom` and `right` to set the values separately. 48 | Vertices { 49 | /// Set top separately 50 | top: f32, 51 | /// Set left separately 52 | left: f32, 53 | /// Set bottom separately 54 | bottom: f32, 55 | /// Set right separately 56 | right: f32, 57 | }, 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::*; 63 | use crate::util::*; 64 | 65 | #[test] 66 | fn serialization() { 67 | assert_serialize( 68 | GeoBoundingBox::MainDiagonal { 69 | top_left: GeoLocation::new(40.73, -74.1), 70 | bottom_right: GeoLocation::new(40.01, -71.12), 71 | }, 72 | json!({ 73 | "top_left": [-74.1, 40.73], 74 | "bottom_right": [-71.12, 40.01] 75 | }), 76 | ); 77 | 78 | assert_serialize( 79 | GeoBoundingBox::WellKnownText { 80 | wkt: "BBOX (-74.1, -71.12, 40.73, 40.01)".into(), 81 | }, 82 | json!({ 83 | "wkt": "BBOX (-74.1, -71.12, 40.73, 40.01)" 84 | }), 85 | ); 86 | 87 | assert_serialize( 88 | GeoBoundingBox::Vertices { 89 | top: 40.73, 90 | left: -74.1, 91 | bottom: 40.01, 92 | right: -71.12, 93 | }, 94 | json!({ 95 | "top": 40.73, 96 | "left": -74.1, 97 | "bottom": 40.01, 98 | "right": -71.12 99 | }), 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/search/queries/params/has_child_query.rs: -------------------------------------------------------------------------------- 1 | /// Indicates how scores for matching child documents affect the root parent document’s relevance 2 | /// score. 3 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 4 | #[serde(rename_all = "snake_case")] 5 | pub enum HasChildScoreMode { 6 | /// Do not use the relevance scores of matching child documents. The query assigns parent 7 | /// documents a score of 0. 8 | None, 9 | 10 | /// Use the mean relevance score of all matching child documents. 11 | Avg, 12 | 13 | /// Uses the highest relevance score of all matching child documents. 14 | Max, 15 | 16 | /// Uses the lowest relevance score of all matching child documents. 17 | Min, 18 | 19 | ///Add together the relevance scores of all matching child documents. 20 | Sum, 21 | } 22 | -------------------------------------------------------------------------------- /src/search/queries/params/inner_hits.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | use crate::Set; 4 | 5 | /// The [parent-join](https://www.elastic.co/guide/en/elasticsearch/reference/current/parent-join.html) 6 | /// and [nested](https://www.elastic.co/guide/en/elasticsearch/reference/current/nested.html) 7 | /// features allow the return of documents that have matches in a different scope. In the 8 | /// parent/child case, parent documents are returned based on matches in child documents or 9 | /// child documents are returned based on matches in parent documents. In the nested case, 10 | /// documents are returned based on matches in nested inner objects. 11 | /// 12 | /// In both cases, the actual matches in the different scopes that caused a document to be 13 | /// returned are hidden. In many cases, it’s very useful to know which inner nested objects 14 | /// (in the case of nested) or children/parent documents (in the case of parent/child) caused 15 | /// certain information to be returned. The inner hits feature can be used for this. This 16 | /// feature returns per search hit in the search response additional nested hits that caused a 17 | /// search hit to match in a different scope. 18 | /// 19 | /// Inner hits can be used by defining an `inner_hits` definition on a `nested`, `has_child` 20 | /// or `has_parent` query and filter. 21 | /// 22 | /// 23 | #[derive(Debug, Default, Clone, PartialEq, Serialize)] 24 | pub struct InnerHits { 25 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 26 | _source: Option, 27 | 28 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 29 | from: Option, 30 | 31 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 32 | size: Option, 33 | 34 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 35 | sort: SortCollection, 36 | 37 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 38 | highlight: Option, 39 | 40 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 41 | docvalue_fields: Set, 42 | } 43 | 44 | impl InnerHits { 45 | /// Creates a new instance of [InnerHits](InnerHits) 46 | pub fn new() -> Self { 47 | Default::default() 48 | } 49 | 50 | /// Indicates which source fields are returned for matching documents 51 | pub fn source(mut self, source: T) -> Self 52 | where 53 | T: Into, 54 | { 55 | self._source = Some(source.into()); 56 | self 57 | } 58 | 59 | /// Starting document offset. 60 | /// 61 | /// Defaults to `0`. 62 | pub fn from(mut self, from: u64) -> Self { 63 | self.from = Some(from); 64 | self 65 | } 66 | 67 | /// The number of hits to return. 68 | /// 69 | /// Defaults to `10`. 70 | pub fn size(mut self, size: u64) -> Self { 71 | self.size = Some(size); 72 | self 73 | } 74 | 75 | /// A collection of sorting fields 76 | pub fn sort(mut self, sort: T) -> Self 77 | where 78 | T: IntoIterator, 79 | T::Item: Into, 80 | { 81 | self.sort.extend(sort); 82 | self 83 | } 84 | 85 | /// Highlight 86 | pub fn highlight(mut self, highlight: T) -> Self 87 | where 88 | T: Into, 89 | { 90 | self.highlight = Some(highlight.into()); 91 | self 92 | } 93 | 94 | /// A collection of docvalue fields 95 | pub fn docvalue_fields(mut self, docvalue_fields: T) -> Self 96 | where 97 | T: IntoIterator, 98 | T::Item: ToString, 99 | { 100 | self.docvalue_fields 101 | .extend(docvalue_fields.into_iter().map(|x| x.to_string())); 102 | self 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/search/queries/params/mod.rs: -------------------------------------------------------------------------------- 1 | //! Strongly typed Elasticsearch query params 2 | 3 | // Common parameters 4 | mod fuzziness; 5 | mod has_child_query; 6 | mod inner_hits; 7 | mod negative_boost; 8 | mod operator; 9 | mod rewrite; 10 | mod script_object; 11 | mod zero_terms_query; 12 | 13 | // Query specific parameters 14 | mod function_score_query; 15 | mod geo_query; 16 | mod nested_query; 17 | mod percolate_query; 18 | mod pinned_query; 19 | mod range_query; 20 | mod regexp_query; 21 | mod shape_query; 22 | mod simple_query_string_query; 23 | mod terms_set_query; 24 | mod text_query_type; 25 | 26 | // Public re-exports 27 | pub use self::function_score_query::*; 28 | pub use self::fuzziness::*; 29 | pub use self::geo_query::*; 30 | pub use self::has_child_query::*; 31 | pub use self::inner_hits::*; 32 | pub use self::negative_boost::*; 33 | pub use self::nested_query::*; 34 | pub use self::operator::*; 35 | pub use self::percolate_query::*; 36 | pub use self::pinned_query::*; 37 | pub use self::range_query::*; 38 | pub use self::regexp_query::*; 39 | pub use self::rewrite::*; 40 | pub use self::script_object::*; 41 | pub use self::shape_query::*; 42 | pub use self::simple_query_string_query::*; 43 | pub use self::terms_set_query::*; 44 | pub use self::text_query_type::*; 45 | pub use self::zero_terms_query::*; 46 | -------------------------------------------------------------------------------- /src/search/queries/params/negative_boost.rs: -------------------------------------------------------------------------------- 1 | //! A container type for boost values 2 | use std::{f32, fmt}; 3 | 4 | /// A container type for boost values 5 | #[derive(Clone, Copy, PartialEq, PartialOrd, Serialize)] 6 | pub struct NegativeBoost(f32); 7 | 8 | impl fmt::Debug for NegativeBoost { 9 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 10 | self.0.fmt(f) 11 | } 12 | } 13 | 14 | impl fmt::Display for NegativeBoost { 15 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 16 | self.0.fmt(f) 17 | } 18 | } 19 | 20 | impl NegativeBoost { 21 | /// Minimum boost value 22 | const MINIMUM: f32 = 0f32; 23 | 24 | /// Maximum boost value 25 | const MAXIMUM: f32 = 1f32; 26 | 27 | /// Creates a new instance of a negative boost value 28 | /// 29 | /// Floating point number between `0` and `1.0` used to decrease the 30 | /// [relevance scores](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html#relevance-scores) 31 | /// of documents matching the `negative` query. 32 | pub fn new(boost: f32) -> Self { 33 | Self(boost.clamp(Self::MINIMUM, Self::MAXIMUM)) 34 | } 35 | } 36 | 37 | impl From for NegativeBoost { 38 | fn from(boost: f32) -> Self { 39 | Self::new(boost) 40 | } 41 | } 42 | 43 | impl From for NegativeBoost { 44 | fn from(boost: i32) -> Self { 45 | Self::new(boost as f32) 46 | } 47 | } 48 | 49 | impl PartialEq for NegativeBoost { 50 | fn eq(&self, other: &f32) -> bool { 51 | self.0.eq(other) 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | 59 | #[test] 60 | fn out_of_bounds_integers() { 61 | let min: NegativeBoost = (-1).into(); 62 | let max: NegativeBoost = 101.into(); 63 | 64 | assert_eq!(min, NegativeBoost::new(0f32)); 65 | assert_eq!(max, NegativeBoost::new(1f32)); 66 | } 67 | 68 | #[test] 69 | fn out_of_bounds_floats() { 70 | let min: NegativeBoost = (-1.0).into(); 71 | let max: NegativeBoost = 101.0.into(); 72 | 73 | assert_eq!(min, NegativeBoost::new(0f32)); 74 | assert_eq!(max, NegativeBoost::new(1f32)); 75 | } 76 | 77 | #[test] 78 | fn within_bounds_floats() { 79 | let min: NegativeBoost = 0.01.into(); 80 | let max: NegativeBoost = 0.99.into(); 81 | 82 | assert_eq!(min, NegativeBoost::new(0.01)); 83 | assert_eq!(max, NegativeBoost::new(0.99)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/search/queries/params/nested_query.rs: -------------------------------------------------------------------------------- 1 | /// Indicates how scores for matching child objects affect the root parent 2 | /// document’s 3 | /// [relevance score](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html#relevance-scores). 4 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 5 | pub enum NestedQueryScoreMode { 6 | /// Use the mean relevance score of all matching child objects. 7 | #[serde(rename = "avg")] 8 | Average, 9 | 10 | /// Uses the highest relevance score of all matching child objects. 11 | #[serde(rename = "max")] 12 | Maximum, 13 | 14 | /// Uses the lowest relevance score of all matching child objects. 15 | #[serde(rename = "min")] 16 | Minimum, 17 | 18 | /// Do not use the relevance scores of matching child objects. The query 19 | /// assigns parent documents a score of `0`. 20 | #[serde(rename = "none")] 21 | None, 22 | 23 | /// Add together the relevance scores of all matching child objects. 24 | #[serde(rename = "sum")] 25 | Sum, 26 | } 27 | -------------------------------------------------------------------------------- /src/search/queries/params/operator.rs: -------------------------------------------------------------------------------- 1 | /// Boolean logic used to interpret text in the `query` value 2 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 3 | #[serde(rename_all = "UPPERCASE")] 4 | pub enum Operator { 5 | /// For example, a `query` value of `capital of Hungary` is interpreted as 6 | /// `capital OR of OR Hungary`. 7 | Or, 8 | 9 | /// For example, a `query` value of `capital of Hungary` is interpreted as 10 | /// `capital AND of AND Hungary`. 11 | /// 12 | And, 13 | } 14 | -------------------------------------------------------------------------------- /src/search/queries/params/percolate_query.rs: -------------------------------------------------------------------------------- 1 | use crate::util::*; 2 | 3 | /// Values that can be percolated 4 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 5 | #[serde(rename_all = "snake_case")] 6 | pub enum PercolateSource { 7 | /// A document 8 | Document(serde_json::Value), 9 | 10 | /// A collection of documents 11 | Documents(Vec), 12 | } 13 | 14 | impl ShouldSkip for PercolateSource { 15 | fn should_skip(&self) -> bool { 16 | match self { 17 | PercolateSource::Document(document) => !document.is_object(), 18 | PercolateSource::Documents(documents) => { 19 | documents.is_empty() || documents.iter().any(|document| !document.is_object()) 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/search/queries/params/pinned_query.rs: -------------------------------------------------------------------------------- 1 | use crate::Set; 2 | 3 | /// Ids or documents to filter by 4 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 5 | #[serde(rename_all = "snake_case")] 6 | pub enum PinnedQueryValues { 7 | /// [Document IDs](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-id-field.html) 8 | /// listed in the order they are to appear in results. 9 | Ids(Set), 10 | 11 | /// Documents listed in the order they are to appear in results. 12 | Docs(Set), 13 | } 14 | 15 | /// Pinned document 16 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 17 | pub struct PinnedDocument { 18 | _index: String, 19 | _id: String, 20 | } 21 | 22 | impl PinnedDocument { 23 | /// Creates an instance of [`PinnedDocument`] 24 | pub fn new(index: IX, id: ID) -> Self 25 | where 26 | IX: ToString, 27 | ID: ToString, 28 | { 29 | Self { 30 | _index: index.to_string(), 31 | _id: id.to_string(), 32 | } 33 | } 34 | } 35 | 36 | impl PinnedQueryValues { 37 | /// Creates an instance of [`PinnedQueryValues`] with [`PinnedQueryValues::Ids`] 38 | pub fn ids(ids: I) -> Self 39 | where 40 | I: IntoIterator, 41 | I::Item: ToString, 42 | { 43 | Self::Ids(ids.into_iter().map(|x| x.to_string()).collect()) 44 | } 45 | 46 | /// Creates an instance of [`PinnedQueryValues`] with [`PinnedQueryValues::Docs`] 47 | pub fn docs(docs: I) -> Self 48 | where 49 | I: IntoIterator, 50 | { 51 | Self::Docs(docs.into_iter().collect()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/search/queries/params/range_query.rs: -------------------------------------------------------------------------------- 1 | /// Indicates how the range query matches values for range fields. 2 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 3 | #[serde(rename_all = "UPPERCASE")] 4 | pub enum RangeRelation { 5 | /// Matches documents with a range field value that intersects the query’s range. 6 | Intersects, 7 | 8 | /// Matches documents with a range field value that entirely contains the query’s range. 9 | Contains, 10 | 11 | /// Matches documents with a range field value entirely within the query’s range. 12 | Within, 13 | } 14 | -------------------------------------------------------------------------------- /src/search/queries/params/regexp_query.rs: -------------------------------------------------------------------------------- 1 | use serde::ser::{Serialize, Serializer}; 2 | 3 | /// You can use the flags parameter to enable more optional operators for Lucene’s regular 4 | /// expression engine. 5 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 6 | pub enum RegexpFlag { 7 | /// Enables all optional operators. 8 | All, 9 | 10 | /// Enables the `~` operator. You can use `~` to negate the shortest following pattern. 11 | /// For example: 12 | /// 13 | /// `a~bc # matches 'adc' and 'aec' but not 'abc'` 14 | Complement, 15 | 16 | /// Enables the `<>` operators. You can use `<>` to match a numeric range. For example: 17 | /// 18 | /// `foo<1-100> # matches 'foo1', 'foo2' ... 'foo99', 'foo100'` 19 | /// `foo<01-100> # matches 'foo01', 'foo02' ... 'foo99', 'foo100'` 20 | Interval, 21 | 22 | /// Enables the `&` operator, which acts as an AND operator. The match will succeed if patterns 23 | /// on both the left side AND the right side matches. For example: 24 | /// 25 | /// `aaa.+&.+bbb # matches 'aaabbb'` 26 | Intersection, 27 | 28 | /// Enables the `@` operator. You can use @ to match any entire string. 29 | /// 30 | /// You can combine the `@` operator with `&` and `~` operators to create an 31 | /// "everything except" logic. For example: 32 | /// 33 | /// `@&~(abc.+) # matches everything except terms beginning with 'abc'` 34 | Anystring, 35 | } 36 | 37 | impl From for &'static str { 38 | fn from(value: RegexpFlag) -> Self { 39 | match value { 40 | RegexpFlag::All => "ALL", 41 | RegexpFlag::Complement => "COMPLEMENT", 42 | RegexpFlag::Interval => "INTERVAL", 43 | RegexpFlag::Intersection => "INTERSECTION", 44 | RegexpFlag::Anystring => "ANYSTRING", 45 | } 46 | } 47 | } 48 | 49 | impl From for String { 50 | fn from(value: RegexpFlag) -> Self { 51 | <&'static str>::from(value).to_string() 52 | } 53 | } 54 | 55 | impl std::fmt::Display for RegexpFlag { 56 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 57 | <&'static str>::from(*self).fmt(f) 58 | } 59 | } 60 | 61 | impl Serialize for RegexpFlag { 62 | fn serialize(&self, serializer: S) -> Result 63 | where 64 | S: Serializer, 65 | { 66 | <&'static str>::from(*self).serialize(serializer) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/search/queries/params/shape_query.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | /// Relation between coordinates 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)] 5 | #[serde(rename_all = "SCREAMING_SNAKE_CASE")] 6 | pub enum SpatialRelation { 7 | /// Return all documents whose `shape` field intersects the query geometry 8 | Intersects, 9 | 10 | /// Return all documents whose `shape` field has nothing in common with the 11 | /// query geometry. 12 | Disjoint, 13 | 14 | /// Return all documents whose `shape` field is within the query geometry. 15 | Within, 16 | 17 | /// Return all documents whose `shape` field contains the query geometry. 18 | Contains, 19 | } 20 | 21 | impl Default for SpatialRelation { 22 | fn default() -> Self { 23 | Self::Intersects 24 | } 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use super::*; 30 | use crate::util::*; 31 | 32 | #[test] 33 | fn serialization() { 34 | assert_serialize( 35 | [ 36 | SpatialRelation::Intersects, 37 | SpatialRelation::Disjoint, 38 | SpatialRelation::Within, 39 | SpatialRelation::Contains, 40 | ], 41 | json!(["INTERSECTS", "DISJOINT", "WITHIN", "CONTAINS"]), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/search/queries/params/simple_query_string_query.rs: -------------------------------------------------------------------------------- 1 | use serde::ser::{Serialize, Serializer}; 2 | 3 | /// You can use the flags parameter to enable more optional operators for Lucene’s regular 4 | /// expression engine. 5 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 6 | pub enum SimpleQueryStringQueryFlags { 7 | /// Enables all optional operators. 8 | All, 9 | 10 | /// Enables the `+` AND operator. 11 | And, 12 | 13 | /// Enables `\` as an escape character. 14 | Escape, 15 | 16 | /// Enables the `~N` operator after a word, where `N` is an integer 17 | /// denoting the allowed edit distance for matching. See 18 | /// [Fuzziness](https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#fuzziness). 19 | Fuzzy, 20 | 21 | /// Enables the `~N` operator, after a phrase where `N` is the maximum 22 | /// number of positions allowed between matching tokens. Synonymous to 23 | /// [SLOP](SimpleQueryStringQueryFlags::Slop). 24 | Near, 25 | 26 | /// Disables all operators. 27 | None, 28 | 29 | /// Enables the `-` NOT operator. 30 | Not, 31 | 32 | /// Enables the `\|` OR operator. 33 | Or, 34 | 35 | /// Enables the `"` quotes operator used to search for phrases. 36 | Phrase, 37 | 38 | /// Enables the `(` and `)` operators to control operator precedence. 39 | Precedence, 40 | 41 | /// Enables the `*` prefix operator. 42 | Prefix, 43 | 44 | /// Enables the `~N` operator, after a phrase where `N` is maximum 45 | /// number of positions allowed between matching tokens. Synonymous to 46 | /// [NEAR](SimpleQueryStringQueryFlags::Near). 47 | Slop, 48 | 49 | /// Enables whitespace as split characters. 50 | Whitespace, 51 | } 52 | 53 | impl From for &'static str { 54 | fn from(value: SimpleQueryStringQueryFlags) -> Self { 55 | match value { 56 | SimpleQueryStringQueryFlags::All => "ALL", 57 | SimpleQueryStringQueryFlags::And => "AND", 58 | SimpleQueryStringQueryFlags::Escape => "ESCAPE", 59 | SimpleQueryStringQueryFlags::Fuzzy => "FUZZY", 60 | SimpleQueryStringQueryFlags::Near => "NEAR", 61 | SimpleQueryStringQueryFlags::None => "NONE", 62 | SimpleQueryStringQueryFlags::Not => "NOT", 63 | SimpleQueryStringQueryFlags::Or => "OR", 64 | SimpleQueryStringQueryFlags::Phrase => "PHRASE", 65 | SimpleQueryStringQueryFlags::Precedence => "PRECEDENCE", 66 | SimpleQueryStringQueryFlags::Prefix => "PREFIX", 67 | SimpleQueryStringQueryFlags::Slop => "SLOP", 68 | SimpleQueryStringQueryFlags::Whitespace => "WHITESPACE", 69 | } 70 | } 71 | } 72 | 73 | impl From for String { 74 | fn from(value: SimpleQueryStringQueryFlags) -> Self { 75 | <&'static str>::from(value).to_string() 76 | } 77 | } 78 | 79 | impl std::fmt::Display for SimpleQueryStringQueryFlags { 80 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 81 | <&'static str>::from(*self).fmt(f) 82 | } 83 | } 84 | 85 | impl Serialize for SimpleQueryStringQueryFlags { 86 | fn serialize(&self, serializer: S) -> Result 87 | where 88 | S: Serializer, 89 | { 90 | <&'static str>::from(*self).serialize(serializer) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/search/queries/params/terms_set_query.rs: -------------------------------------------------------------------------------- 1 | use serde_json::Value; 2 | 3 | /// Number of matching terms to be required 4 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 5 | pub enum TermsSetMinimumShouldMatch { 6 | /// [Numeric](https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html) 7 | /// field containing the number of matching terms required to return a document. 8 | #[serde(rename = "minimum_should_match_field")] 9 | Field(String), 10 | 11 | /// Custom script containing the number of matching terms required to return a document. 12 | /// 13 | /// For parameters and valid values, see 14 | /// [Scripting](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html). 15 | /// 16 | /// For an example query using the `minimum_should_match_script` parameter, see 17 | /// [How to use the `minimum_should_match_script` parameter](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-set-query.html#terms-set-query-script). 18 | #[serde(rename = "minimum_should_match_script")] 19 | Script(TermsSetScript), 20 | } 21 | 22 | impl From for TermsSetMinimumShouldMatch { 23 | fn from(field: String) -> Self { 24 | Self::Field(field) 25 | } 26 | } 27 | 28 | impl<'a> From<&'a str> for TermsSetMinimumShouldMatch { 29 | fn from(field: &'a str) -> Self { 30 | Self::Field(field.to_string()) 31 | } 32 | } 33 | 34 | impl From for TermsSetMinimumShouldMatch { 35 | fn from(script: TermsSetScript) -> Self { 36 | Self::Script(script) 37 | } 38 | } 39 | 40 | /// Custom script containing the number of matching terms required to return a document. 41 | /// 42 | /// For parameters and valid values, see 43 | /// [Scripting](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html). 44 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 45 | pub struct TermsSetScript { 46 | source: String, 47 | params: Option, 48 | } 49 | 50 | impl From for TermsSetScript { 51 | fn from(source: String) -> Self { 52 | Self { 53 | source, 54 | params: None, 55 | } 56 | } 57 | } 58 | 59 | impl<'a> From<&'a str> for TermsSetScript { 60 | fn from(source: &'a str) -> Self { 61 | Self { 62 | source: source.to_string(), 63 | params: None, 64 | } 65 | } 66 | } 67 | 68 | impl TermsSetScript { 69 | /// Creates an instance of [TermsSetScript] 70 | pub fn new(source: T) -> Self 71 | where 72 | T: ToString, 73 | { 74 | Self { 75 | source: source.to_string(), 76 | params: None, 77 | } 78 | } 79 | 80 | /// Assign params 81 | pub fn params(mut self, params: Value) -> Self { 82 | self.params = Some(params); 83 | self 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/search/queries/params/text_query_type.rs: -------------------------------------------------------------------------------- 1 | /// The way the `multi_match` query is executed internally. 2 | /// 3 | /// 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] 5 | #[serde(rename_all = "snake_case")] 6 | pub enum TextQueryType { 7 | /// Finds documents which match any field, but uses the `_score` from the 8 | /// best field. See 9 | /// [`best_fields`](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#type-best-fields). 10 | BestFields, 11 | 12 | /// Finds documents which match any field and combines the `_score` from 13 | /// each field. See 14 | /// [`most_fields`](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#type-most-fields). 15 | MostFields, 16 | 17 | /// Treats fields with the same `analyzer` as though they were one big 18 | /// field. Looks for each word in **any** field. See 19 | /// [`cross_fields`](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#type-cross-fields). 20 | CrossFields, 21 | 22 | /// Runs a `match_phrase` query on each field and uses the `_score` from 23 | /// the best field. See 24 | /// [`phrase` and `phrase_prefix`](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#type-phrase). 25 | Phrase, 26 | 27 | /// Runs a `match_phrase_prefix` query on each field and uses the `_score` 28 | /// from the best field. See 29 | /// [`phrase` and `phrase_prefix`](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#type-phrase). 30 | PhrasePrefix, 31 | 32 | /// Creates a `match_bool_prefix` query on each field and combines the 33 | /// `_score` from each field. See 34 | /// [`bool_prefix`](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#type-bool-prefix). 35 | BoolPrefix, 36 | } 37 | -------------------------------------------------------------------------------- /src/search/queries/params/zero_terms_query.rs: -------------------------------------------------------------------------------- 1 | /// Indicates whether no documents are returned if the `analyzer` removes all 2 | /// tokens, such as when using a `stop` filter. 3 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 4 | #[serde(rename_all = "snake_case")] 5 | pub enum ZeroTermsQuery { 6 | /// No documents are returned if the `analyzer` removes all tokens. 7 | None, 8 | 9 | /// Returns all documents, similar to a 10 | /// [`match_all`](crate::queries::MatchAllQuery) 11 | /// query. 12 | All, 13 | } 14 | -------------------------------------------------------------------------------- /src/search/queries/query_collection.rs: -------------------------------------------------------------------------------- 1 | use super::Query; 2 | use crate::util::ShouldSkip; 3 | 4 | /// A collection of queries 5 | #[derive(Default, Clone, PartialEq, Serialize)] 6 | pub struct QueryCollection(Vec); 7 | 8 | impl std::fmt::Debug for QueryCollection { 9 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 10 | self.0.fmt(f) 11 | } 12 | } 13 | 14 | impl IntoIterator for QueryCollection { 15 | type Item = Query; 16 | 17 | type IntoIter = std::vec::IntoIter; 18 | 19 | fn into_iter(self) -> Self::IntoIter { 20 | self.0.into_iter() 21 | } 22 | } 23 | 24 | impl ShouldSkip for QueryCollection { 25 | fn should_skip(&self) -> bool { 26 | self.0.should_skip() 27 | } 28 | } 29 | 30 | impl QueryCollection { 31 | /// Extends query collection 32 | pub fn extend(&mut self, query: T) 33 | where 34 | T: IntoIterator, 35 | T::Item: Into, 36 | { 37 | self.0.extend( 38 | query 39 | .into_iter() 40 | .map(Into::into) 41 | .filter(ShouldSkip::should_keep), 42 | ) 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use super::*; 49 | 50 | #[test] 51 | fn adds_query() { 52 | let mut queries = QueryCollection::default(); 53 | 54 | let query = Query::terms("test", [1]); 55 | 56 | queries.extend(query); 57 | 58 | assert_eq!(queries.0.len(), 1); 59 | } 60 | 61 | #[test] 62 | fn adds_queries() { 63 | let mut queries = QueryCollection::default(); 64 | 65 | let query_1 = Query::terms("test", [1]); 66 | let query_2 = Query::terms("test", [2]); 67 | 68 | queries.extend([query_1, query_2]); 69 | 70 | assert_eq!(queries.0.len(), 2); 71 | } 72 | 73 | #[test] 74 | fn skips_queries() { 75 | let mut queries = QueryCollection::default(); 76 | 77 | let empty_values: [i32; 0] = []; 78 | 79 | let query_1 = Query::terms("test", empty_values).into(); 80 | let query_2 = Query::from(Query::terms("test", empty_values)); 81 | let query_3 = Query::Terms(Query::terms("test", empty_values)); 82 | 83 | queries.extend([query_1, query_2, query_3]); 84 | 85 | assert!(queries.0.is_empty()); 86 | } 87 | 88 | #[test] 89 | fn skips_query() { 90 | let mut queries = QueryCollection::default(); 91 | 92 | let empty_values: [i32; 0] = []; 93 | 94 | let query = Query::terms("test", empty_values); 95 | 96 | queries.extend(query); 97 | 98 | assert!(queries.0.is_empty()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/search/queries/shape/mod.rs: -------------------------------------------------------------------------------- 1 | //! Like geo_shape Elasticsearch supports the ability to index arbitrary two dimension 2 | //! (non Geospatial) geometries making it possible to map out virtual worlds, sporting venues, 3 | //! theme parks, and CAD diagrams. 4 | //! 5 | //! Elasticsearch supports two types of cartesian data: point fields which support x/y pairs, and 6 | //! shape fields, which support points, lines, circles, polygons, multi-polygons, etc. 7 | 8 | mod shape_lookup_query; 9 | mod shape_query; 10 | 11 | pub use self::shape_lookup_query::*; 12 | pub use self::shape_query::*; 13 | -------------------------------------------------------------------------------- /src/search/queries/span/span_containing_query.rs: -------------------------------------------------------------------------------- 1 | use super::SpanQuery; 2 | use crate::util::*; 3 | use crate::Query; 4 | 5 | /// Returns matches which enclose another span query. The span containing query maps to Lucene 6 | /// `SpanContainingQuery`.
7 | /// The `big` and `little` clauses can be any span type query. Matching spans from `big` that 8 | /// contain matches from `little` are returned. 9 | /// 10 | /// 11 | #[derive(Debug, Clone, PartialEq, Serialize)] 12 | #[serde(remote = "Self")] 13 | pub struct SpanContainingQuery { 14 | little: Box, 15 | big: Box, 16 | } 17 | 18 | impl Query { 19 | /// Creates an instance of [`SpanContainingQuery`] 20 | pub fn span_containing(little: T, big: U) -> SpanContainingQuery 21 | where 22 | T: Into, 23 | U: Into, 24 | { 25 | SpanContainingQuery { 26 | little: Box::new(little.into()), 27 | big: Box::new(big.into()), 28 | } 29 | } 30 | } 31 | 32 | impl ShouldSkip for SpanContainingQuery {} 33 | 34 | serialize_with_root!("span_containing": SpanContainingQuery); 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use super::*; 39 | 40 | #[test] 41 | fn serialization() { 42 | assert_serialize_query( 43 | Query::span_containing( 44 | Query::span_term("little", "1324"), 45 | Query::span_term("big", "4321"), 46 | ), 47 | json!({ 48 | "span_containing": { 49 | "little": { 50 | "span_term": { 51 | "little": { 52 | "value": "1324" 53 | } 54 | } 55 | }, 56 | "big": { 57 | "span_term": { 58 | "big": { 59 | "value": "4321" 60 | } 61 | } 62 | } 63 | } 64 | }), 65 | ); 66 | 67 | assert_serialize_query( 68 | Query::span_containing( 69 | Query::span_term("little", "1324"), 70 | Query::span_term("big", "4321"), 71 | ), 72 | json!({ 73 | "span_containing": { 74 | "little": { 75 | "span_term": { 76 | "little": { 77 | "value": "1324" 78 | } 79 | } 80 | }, 81 | "big": { 82 | "span_term": { 83 | "big": { 84 | "value": "4321" 85 | } 86 | } 87 | } 88 | } 89 | }), 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/search/queries/span/span_field_masking_query.rs: -------------------------------------------------------------------------------- 1 | use crate::util::*; 2 | use crate::{Query, SpanQuery}; 3 | 4 | /// Wrapper to allow span queries to participate in composite single-field span queries by 5 | /// _lying_ about their search field. The span field masking query maps to Lucene’s 6 | /// `SpanFieldMaskingQuery` 7 | /// 8 | /// This can be used to support queries like `span-near` or `span-or` across different fields, 9 | /// which is not ordinarily permitted. 10 | /// 11 | /// Span field masking query is invaluable in conjunction with **multi-fields** when same content 12 | /// is indexed with multiple analyzers. For instance we could index a field with the standard 13 | /// analyzer which breaks text up into words, and again with the english analyzer which stems words 14 | /// into their root form. 15 | /// 16 | /// Note: as span field masking query returns the masked field, scoring will be done using the 17 | /// norms of the field name supplied. This may lead to unexpected scoring behavior. 18 | /// 19 | /// 20 | #[derive(Debug, Clone, PartialEq, Serialize)] 21 | #[serde(remote = "Self")] 22 | pub struct SpanFieldMaskingQuery { 23 | query: Box, 24 | field: String, 25 | } 26 | 27 | impl Query { 28 | /// Creates an instance of [`SpanFieldMaskingQuery`] 29 | #[allow(unused)] 30 | pub fn span_field_masking(query: Q, field: F) -> SpanFieldMaskingQuery 31 | where 32 | Q: Into, 33 | F: ToString, 34 | { 35 | SpanFieldMaskingQuery { 36 | query: Box::new(query.into()), 37 | field: field.to_string(), 38 | } 39 | } 40 | } 41 | 42 | impl ShouldSkip for SpanFieldMaskingQuery {} 43 | 44 | serialize_with_root!("span_field_masking": SpanFieldMaskingQuery); 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use super::*; 49 | 50 | #[test] 51 | fn serialization() { 52 | assert_serialize_query( 53 | Query::span_field_masking(Query::span_term("test", 1234), "test"), 54 | json!({ 55 | "span_field_masking": { 56 | "query": { 57 | "span_term": { 58 | "test": { 59 | "value": 1234 60 | } 61 | } 62 | }, 63 | "field": "test" 64 | } 65 | }), 66 | ); 67 | 68 | assert_serialize_query( 69 | Query::span_field_masking(Query::span_term("test", 1234), "test"), 70 | json!({ 71 | "span_field_masking": { 72 | "query": { 73 | "span_term": { 74 | "test": { 75 | "value": 1234 76 | } 77 | } 78 | }, 79 | "field": "test" 80 | } 81 | }), 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/search/queries/span/span_first_query.rs: -------------------------------------------------------------------------------- 1 | use crate::util::*; 2 | use crate::{Query, SpanQuery}; 3 | use serde::Serialize; 4 | 5 | /// Matches spans near the beginning of a field. The span first query maps to Lucene 6 | /// `SpanFirstQuery`.
7 | /// The `match` clause can be any other span type query. The `end` controls the maximum end 8 | /// position permitted in a match. 9 | /// 10 | /// 11 | #[derive(Debug, Clone, PartialEq, Serialize)] 12 | #[serde(remote = "Self")] 13 | pub struct SpanFirstQuery { 14 | r#match: Box, 15 | end: u32, 16 | } 17 | 18 | impl Query { 19 | /// Creates an instance of [`SpanFirstQuery`] 20 | pub fn span_first(r#match: T, end: u32) -> SpanFirstQuery 21 | where 22 | T: Into, 23 | { 24 | SpanFirstQuery { 25 | r#match: Box::new(r#match.into()), 26 | end, 27 | } 28 | } 29 | } 30 | 31 | impl ShouldSkip for SpanFirstQuery {} 32 | 33 | serialize_with_root!("span_first": SpanFirstQuery); 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::*; 38 | 39 | #[test] 40 | fn serialization() { 41 | assert_serialize_query( 42 | Query::span_first(Query::span_term("test", 1234), 10), 43 | json!({ 44 | "span_first": { 45 | "match": { 46 | "span_term": { 47 | "test": { 48 | "value": 1234 49 | } 50 | } 51 | }, 52 | "end": 10 53 | } 54 | }), 55 | ); 56 | 57 | assert_serialize_query( 58 | Query::span_first(Query::span_term("test", 1234), 10), 59 | json!({ 60 | "span_first": { 61 | "match": { 62 | "span_term": { 63 | "test": { 64 | "value": 1234 65 | } 66 | } 67 | }, 68 | "end": 10 69 | } 70 | }), 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/search/queries/span/span_multi_query.rs: -------------------------------------------------------------------------------- 1 | use crate::util::*; 2 | use crate::{MultiTermQuery, Query}; 3 | use serde::Serialize; 4 | 5 | /// The span_multi query allows you to wrap a `multi term query` (one of 6 | /// [`wildcard`](crate::WildcardQuery), [`fuzzy`](crate::FuzzyQuery), 7 | /// [`prefix`](crate::PrefixQuery), [`range`](crate::RangeQuery) or 8 | /// [`regexp`](crate::RegexpQuery) query) as a [`span query`](crate::SpanQuery), so it can be 9 | /// nested. 10 | /// 11 | /// 12 | #[derive(Debug, Clone, PartialEq, Serialize)] 13 | #[serde(remote = "Self")] 14 | pub struct SpanMultiQuery { 15 | r#match: Box, 16 | } 17 | 18 | impl ShouldSkip for SpanMultiQuery {} 19 | 20 | serialize_with_root!("span_multi": SpanMultiQuery); 21 | 22 | impl Query { 23 | /// Creates an instance of [`SpanMultiQuery`] 24 | #[allow(unused)] 25 | pub fn span_multi(r#match: Q) -> SpanMultiQuery 26 | where 27 | Q: Into, 28 | { 29 | SpanMultiQuery { 30 | r#match: Box::new(r#match.into()), 31 | } 32 | } 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::*; 38 | 39 | #[test] 40 | fn serialization() { 41 | assert_serialize_query( 42 | Query::span_multi(Query::prefix("test", "1234")), 43 | json!({ 44 | "span_multi": { 45 | "match" : { 46 | "prefix": { 47 | "test": { 48 | "value": "1234" 49 | } 50 | } 51 | } 52 | } 53 | }), 54 | ); 55 | 56 | assert_serialize_query( 57 | Query::span_multi(Query::prefix("test", "1234")), 58 | json!({ 59 | "span_multi": { 60 | "match" : { 61 | "prefix": { 62 | "test": { 63 | "value": "1234" 64 | } 65 | } 66 | } 67 | } 68 | }), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/search/queries/span/span_near_query.rs: -------------------------------------------------------------------------------- 1 | use crate::util::*; 2 | use crate::Query; 3 | use crate::SpanQuery; 4 | use serde::Serialize; 5 | 6 | /// Matches spans which are near one another. One can specify _slop_, the maximum number of 7 | /// intervening unmatched positions, as well as whether matches are required to be in-order. The 8 | /// span near query maps to Lucene `SpanNearQuery`.
9 | /// The `clauses` element is a list of one or more other span type queries and the `slop` controls 10 | /// the maximum number of intervening unmatched positions permitted. 11 | /// 12 | /// 13 | #[derive(Debug, Clone, PartialEq, Serialize)] 14 | #[serde(remote = "Self")] 15 | pub struct SpanNearQuery { 16 | clauses: Vec, 17 | 18 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 19 | in_order: Option, 20 | 21 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 22 | slop: Option, 23 | } 24 | 25 | impl ShouldSkip for SpanNearQuery { 26 | fn should_skip(&self) -> bool { 27 | self.clauses.should_skip() 28 | } 29 | } 30 | 31 | impl Query { 32 | /// Creates an instance of [`SpanNearQuery`] 33 | pub fn span_near(clauses: T) -> SpanNearQuery 34 | where 35 | T: IntoIterator, 36 | T::Item: Into, 37 | { 38 | SpanNearQuery { 39 | clauses: clauses.into_iter().map(Into::into).collect(), 40 | in_order: None, 41 | slop: None, 42 | } 43 | } 44 | } 45 | 46 | impl SpanNearQuery { 47 | /// TODO 48 | pub fn in_order(mut self, in_order: bool) -> Self { 49 | self.in_order = Some(in_order); 50 | self 51 | } 52 | 53 | /// TODO 54 | pub fn slop(mut self, slop: i32) -> Self { 55 | self.slop = Some(slop); 56 | self 57 | } 58 | } 59 | 60 | serialize_with_root!("span_near": SpanNearQuery); 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::*; 65 | 66 | #[test] 67 | fn serialization() { 68 | assert_serialize_query( 69 | Query::span_near([Query::span_term("test", 1234)]), 70 | json!({ 71 | "span_near": { 72 | "clauses": [ 73 | { 74 | "span_term": { 75 | "test": { 76 | "value": 1234 77 | } 78 | } 79 | } 80 | ] 81 | } 82 | }), 83 | ); 84 | 85 | assert_serialize_query( 86 | Query::span_near([Query::span_term("test", 1234)]) 87 | .in_order(true) 88 | .slop(4321), 89 | json!({ 90 | "span_near": { 91 | "clauses": [ 92 | { 93 | "span_term": { 94 | "test": { 95 | "value": 1234 96 | } 97 | } 98 | } 99 | ], 100 | "in_order": true, 101 | "slop": 4321 102 | } 103 | }), 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/search/queries/span/span_or_query.rs: -------------------------------------------------------------------------------- 1 | use super::SpanQuery; 2 | use crate::util::*; 3 | use crate::Query; 4 | use serde::Serialize; 5 | 6 | /// Matches the union of its span clauses. The span or query maps to Lucene `SpanOrQuery`. 7 | /// 8 | /// The `clauses` element is a list of one or more other span type queries. 9 | /// 10 | /// 11 | #[derive(Debug, Clone, PartialEq, Serialize)] 12 | #[serde(remote = "Self")] 13 | pub struct SpanOrQuery { 14 | clauses: Vec, 15 | } 16 | 17 | impl ShouldSkip for SpanOrQuery {} 18 | 19 | impl Query { 20 | /// Creates an instance of [`SpanOrQuery`] 21 | pub fn span_or(clauses: T) -> SpanOrQuery 22 | where 23 | T: IntoIterator, 24 | T::Item: Into, 25 | { 26 | SpanOrQuery { 27 | clauses: clauses.into_iter().map(Into::into).collect(), 28 | } 29 | } 30 | } 31 | 32 | serialize_with_root!("span_or": SpanOrQuery); 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | 38 | #[test] 39 | fn serialization() { 40 | assert_serialize_query( 41 | Query::span_or([Query::span_term("test", 1234)]), 42 | json!({ 43 | "span_or": { 44 | "clauses": [ 45 | { 46 | "span_term": { 47 | "test": { 48 | "value": 1234 49 | } 50 | } 51 | } 52 | ] 53 | } 54 | }), 55 | ); 56 | 57 | assert_serialize_query( 58 | Query::span_or([Query::span_term("test", 1234)]), 59 | json!({ 60 | "span_or": { 61 | "clauses": [ 62 | { 63 | "span_term": { 64 | "test": { 65 | "value": 1234 66 | } 67 | } 68 | } 69 | ] 70 | } 71 | }), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/search/queries/span/span_term_query.rs: -------------------------------------------------------------------------------- 1 | use crate::util::*; 2 | use crate::{Query, Term}; 3 | use serde::Serialize; 4 | 5 | /// Matches spans containing a term. The span term query maps to Lucene `SpanTermQuery`. 6 | /// 7 | /// 8 | #[derive(Debug, Clone, PartialEq, Serialize)] 9 | #[serde(remote = "Self")] 10 | pub struct SpanTermQuery { 11 | #[serde(skip)] 12 | field: String, 13 | 14 | value: Option, 15 | 16 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 17 | boost: Option, 18 | 19 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 20 | _name: Option, 21 | } 22 | 23 | impl Query { 24 | /// Creates an instance of [`SpanTermQuery`] 25 | /// 26 | /// - `field` - Field you wish to search. 27 | /// - `value` - Term you wish to find in the provided field. 28 | /// To return a document, the term must exactly match the field value, including whitespace and capitalization. 29 | pub fn span_term(field: T, value: U) -> SpanTermQuery 30 | where 31 | T: ToString, 32 | U: Serialize, 33 | { 34 | SpanTermQuery { 35 | field: field.to_string(), 36 | value: Term::new(value), 37 | boost: None, 38 | _name: None, 39 | } 40 | } 41 | } 42 | 43 | impl SpanTermQuery { 44 | add_boost_and_name!(); 45 | } 46 | 47 | impl ShouldSkip for SpanTermQuery { 48 | fn should_skip(&self) -> bool { 49 | self.value.should_skip() 50 | } 51 | } 52 | 53 | serialize_with_root_keyed!("span_term": SpanTermQuery); 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | 59 | #[test] 60 | fn serialization() { 61 | assert_serialize_query( 62 | Query::span_term("test", 123u32), 63 | json!({ 64 | "span_term": { 65 | "test": { 66 | "value": 123 67 | } 68 | } 69 | }), 70 | ); 71 | 72 | assert_serialize_query( 73 | Query::span_term("test", 123).boost(2).name("test"), 74 | json!({ 75 | "span_term": { 76 | "test": { 77 | "value": 123, 78 | "boost": 2.0, 79 | "_name": "test" 80 | } 81 | } 82 | }), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/search/queries/span/span_within_query.rs: -------------------------------------------------------------------------------- 1 | use super::SpanQuery; 2 | use crate::util::*; 3 | use crate::Query; 4 | use serde::Serialize; 5 | 6 | /// Returns matches which are enclosed inside another span query. The span within query maps to 7 | /// Lucene `SpanWithinQuery`. 8 | /// 9 | /// The `big` and `little` clauses can be any span type query. Matching spans from `little` that 10 | /// are enclosed within `big` are returned. 11 | /// 12 | /// 13 | #[derive(Debug, Clone, PartialEq, Serialize)] 14 | #[serde(remote = "Self")] 15 | pub struct SpanWithinQuery { 16 | big: Box, 17 | little: Box, 18 | } 19 | 20 | impl Query { 21 | /// Creates an instance of [`SpanWithinQuery`] 22 | pub fn span_within(little: T, big: U) -> SpanWithinQuery 23 | where 24 | T: Into, 25 | U: Into, 26 | { 27 | SpanWithinQuery { 28 | little: Box::new(little.into()), 29 | big: Box::new(big.into()), 30 | } 31 | } 32 | } 33 | 34 | impl ShouldSkip for SpanWithinQuery {} 35 | 36 | serialize_with_root!("span_within": SpanWithinQuery); 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use super::*; 41 | 42 | #[test] 43 | fn serialization() { 44 | assert_serialize_query( 45 | Query::span_within( 46 | Query::span_term("little", 1234), 47 | Query::span_term("big", 4321), 48 | ), 49 | json!({ 50 | "span_within": { 51 | "little": { 52 | "span_term": { 53 | "little": { 54 | "value": 1234 55 | } 56 | } 57 | }, 58 | "big": { 59 | "span_term": { 60 | "big": { 61 | "value": 4321 62 | } 63 | } 64 | } 65 | } 66 | }), 67 | ); 68 | 69 | assert_serialize_query( 70 | Query::span_within( 71 | Query::span_term("little", 1234), 72 | Query::span_term("big", 4321), 73 | ), 74 | json!({ 75 | "span_within": { 76 | "little": { 77 | "span_term": { 78 | "little": { 79 | "value": 1234 80 | } 81 | } 82 | }, 83 | "big": { 84 | "span_term": { 85 | "big": { 86 | "value": 4321 87 | } 88 | } 89 | } 90 | } 91 | }), 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/search/queries/specialized/mod.rs: -------------------------------------------------------------------------------- 1 | //! This group contains queries which do not fit into the other groups 2 | 3 | mod distance_feature_query; 4 | mod more_like_this_query; 5 | mod percolate_lookup_query; 6 | mod percolate_query; 7 | mod pinned_query; 8 | mod rank_feature_query; 9 | mod script_query; 10 | mod script_score_query; 11 | mod wrapper_query; 12 | 13 | pub use self::distance_feature_query::*; 14 | pub use self::more_like_this_query::*; 15 | pub use self::percolate_lookup_query::*; 16 | pub use self::percolate_query::*; 17 | pub use self::pinned_query::*; 18 | pub use self::rank_feature_query::*; 19 | pub use self::script_query::*; 20 | pub use self::script_score_query::*; 21 | pub use self::wrapper_query::*; 22 | -------------------------------------------------------------------------------- /src/search/queries/specialized/script_query.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// Filters documents based on a provided 5 | /// [script](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-using.html). 6 | /// The script query is typically used in a 7 | /// [filter context](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html). 8 | /// 9 | /// To create script query: 10 | /// ``` 11 | /// # use elasticsearch_dsl::queries::*; 12 | /// # use elasticsearch_dsl::queries::params::*; 13 | /// # let query = 14 | /// Query::script(Script::source("return doc['amount'].value < 10;")); 15 | /// ``` 16 | /// 17 | #[derive(Debug, Clone, PartialEq, Serialize)] 18 | #[serde(remote = "Self")] 19 | pub struct ScriptQuery { 20 | script: Script, 21 | 22 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 23 | boost: Option, 24 | 25 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 26 | _name: Option, 27 | } 28 | 29 | impl Query { 30 | /// Creates an instance of [`ScriptQuery`] 31 | /// 32 | /// - `script` - Contains a script to run as a query. This script must 33 | /// return a boolean value, `true` or `false` 34 | pub fn script(script: Script) -> ScriptQuery { 35 | ScriptQuery { 36 | script, 37 | boost: None, 38 | _name: None, 39 | } 40 | } 41 | } 42 | 43 | impl ScriptQuery { 44 | add_boost_and_name!(); 45 | } 46 | 47 | impl ShouldSkip for ScriptQuery {} 48 | 49 | serialize_with_root!("script": ScriptQuery); 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | 55 | #[test] 56 | fn serialization() { 57 | assert_serialize_query( 58 | Query::script( 59 | Script::source("doc['numberOfCommits'].value > params.param1").param("param1", 50), 60 | ) 61 | .name("_named_query") 62 | .boost(1.1), 63 | json!({ 64 | "script": { 65 | "_name": "_named_query", 66 | "boost": 1.1, 67 | "script": { 68 | "source": "doc['numberOfCommits'].value > params.param1", 69 | "params": { 70 | "param1": 50 71 | } 72 | } 73 | } 74 | }), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/search/queries/specialized/script_score_query.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// A query allowing you to modify the score of documents that are retrieved by 5 | /// a query. This can be useful if, for example, a score function is 6 | /// computationally expensive and it is sufficient to compute the score on a 7 | /// filtered set of documents. 8 | /// 9 | /// To create script score query: 10 | /// ``` 11 | /// # use elasticsearch_dsl::queries::*; 12 | /// # use elasticsearch_dsl::queries::params::*; 13 | /// # let query = 14 | /// Query::script_score( 15 | /// Query::r#match("message", "elasticsearch"), 16 | /// Script::source("doc['my-int'].value / 10"), 17 | /// ); 18 | /// ``` 19 | /// 20 | #[derive(Debug, Clone, PartialEq, Serialize)] 21 | #[serde(remote = "Self")] 22 | pub struct ScriptScoreQuery { 23 | query: Box, 24 | 25 | script: Script, 26 | 27 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 28 | min_score: Option, 29 | 30 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 31 | boost: Option, 32 | 33 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 34 | _name: Option, 35 | } 36 | 37 | impl Query { 38 | /// Creates an instance of [`ScriptScoreQuery`] 39 | /// 40 | /// - `query` - Query used to return documents 41 | /// - `script` - Script used to compute the score of documents returned by 42 | /// the `query` 43 | pub fn script_score(query: Q, script: Script) -> ScriptScoreQuery 44 | where 45 | Q: Into, 46 | { 47 | ScriptScoreQuery { 48 | query: Box::new(query.into()), 49 | script, 50 | min_score: None, 51 | boost: None, 52 | _name: None, 53 | } 54 | } 55 | } 56 | 57 | impl ScriptScoreQuery { 58 | add_boost_and_name!(); 59 | } 60 | 61 | impl ShouldSkip for ScriptScoreQuery {} 62 | 63 | serialize_with_root!("script_score": ScriptScoreQuery); 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::*; 68 | 69 | #[test] 70 | fn serialization() { 71 | assert_serialize_query( 72 | Query::script_score( 73 | Query::r#match("message", "elasticsearch"), 74 | Script::source("doc['my-int'].value / 10"), 75 | ) 76 | .name("_named_query") 77 | .boost(1.1), 78 | json!({ 79 | "script_score": { 80 | "_name": "_named_query", 81 | "boost": 1.1, 82 | "query": { "match": { "message": { "query": "elasticsearch" } } }, 83 | "script": { 84 | "source": "doc['my-int'].value / 10" 85 | } 86 | } 87 | }), 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/search/queries/specialized/wrapper_query.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// A query that accepts any other query as base64 encoded string. 5 | /// 6 | /// This query is more useful in the context of the Java high-level REST client 7 | /// or transport client to also accept queries as json formatted string. In 8 | /// these cases queries can be specified as a json or yaml formatted string or 9 | /// as a query builder (which is a available in the Java high-level REST client). 10 | /// 11 | /// To create wrapper query: 12 | /// ``` 13 | /// # use elasticsearch_dsl::queries::*; 14 | /// # use elasticsearch_dsl::queries::params::*; 15 | /// # let query = 16 | /// Query::wrapper("eyJ0ZXJtIiA6IHsgInVzZXIuaWQiIDogImtpbWNoeSIgfX0="); 17 | /// ``` 18 | /// 19 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 20 | #[serde(remote = "Self")] 21 | pub struct WrapperQuery { 22 | query: String, 23 | } 24 | 25 | impl Query { 26 | /// Creates an instance of [`WrapperQuery`] 27 | pub fn wrapper(query: S) -> WrapperQuery 28 | where 29 | S: ToString, 30 | { 31 | WrapperQuery { 32 | query: query.to_string(), 33 | } 34 | } 35 | } 36 | 37 | impl ShouldSkip for WrapperQuery {} 38 | 39 | serialize_with_root!("wrapper": WrapperQuery); 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use super::*; 44 | 45 | #[test] 46 | fn serialization() { 47 | assert_serialize_query( 48 | Query::wrapper("eyJ0ZXJtIiA6IHsgInVzZXIuaWQiIDogImtpbWNoeSIgfX0="), 49 | json!({ "wrapper": { "query": "eyJ0ZXJtIiA6IHsgInVzZXIuaWQiIDogImtpbWNoeSIgfX0=" } }), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/search/queries/term_level/exists_query.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// Returns documents that contain an indexed value for a field. 5 | /// 6 | /// An indexed value may not exist for a document’s field due to a variety of reasons: 7 | /// 8 | /// - The field in the source JSON is `null` or `[]` 9 | /// - The field has `"index" : false` set in the mapping 10 | /// - The length of the field value exceeded an `ignore_above` setting in the mapping 11 | /// - The field value was malformed and `ignore_malformed` was defined in the mapping 12 | /// 13 | /// To create exists query: 14 | /// ``` 15 | /// # use elasticsearch_dsl::queries::*; 16 | /// # use elasticsearch_dsl::queries::params::*; 17 | /// # let query = 18 | /// Query::exists("test"); 19 | /// ``` 20 | /// 21 | #[derive(Debug, Clone, PartialEq, Serialize)] 22 | #[serde(remote = "Self")] 23 | pub struct ExistsQuery { 24 | field: String, 25 | 26 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 27 | boost: Option, 28 | 29 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 30 | _name: Option, 31 | } 32 | 33 | impl Query { 34 | /// Creates an instance of [`ExistsQuery`] 35 | /// 36 | /// - `field` - Name of the field you wish to search. 37 | /// While a field is deemed non-existent if the JSON value is `null` or `[]`, 38 | /// these values will indicate the field does exist: 39 | /// - Empty strings, such as `""` or `"-"` 40 | /// - Arrays containing `null` and another value, such as `[null, "foo"]` 41 | /// - A custom [`null-value`](https://www.elastic.co/guide/en/elasticsearch/reference/current/null-value.html), defined in field mapping 42 | pub fn exists(field: T) -> ExistsQuery 43 | where 44 | T: ToString, 45 | { 46 | ExistsQuery { 47 | field: field.to_string(), 48 | boost: None, 49 | _name: None, 50 | } 51 | } 52 | } 53 | 54 | impl ExistsQuery { 55 | add_boost_and_name!(); 56 | } 57 | 58 | impl ShouldSkip for ExistsQuery {} 59 | 60 | serialize_with_root!("exists": ExistsQuery); 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::*; 65 | 66 | #[test] 67 | fn serialization() { 68 | assert_serialize_query( 69 | Query::exists("test"), 70 | json!({ 71 | "exists": { 72 | "field": "test" 73 | } 74 | }), 75 | ); 76 | 77 | assert_serialize_query( 78 | Query::exists("test").boost(2).name("test"), 79 | json!({ 80 | "exists": { 81 | "field": "test", 82 | "boost": 2.0, 83 | "_name": "test" 84 | } 85 | }), 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/search/queries/term_level/ids_query.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | use crate::Set; 4 | 5 | /// Returns documents based on their IDs. This query uses document IDs stored in the 6 | /// [`_id`](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-id-field.html) 7 | /// field. 8 | /// 9 | /// To create IDs query: 10 | /// ``` 11 | /// # use elasticsearch_dsl::queries::*; 12 | /// # use elasticsearch_dsl::queries::params::*; 13 | /// # let query = 14 | /// Query::ids(vec!["2"]); 15 | /// ``` 16 | /// 17 | #[derive(Debug, Clone, PartialEq, Serialize)] 18 | #[serde(remote = "Self")] 19 | pub struct IdsQuery { 20 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 21 | values: Set, 22 | 23 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 24 | boost: Option, 25 | 26 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 27 | _name: Option, 28 | } 29 | 30 | impl Query { 31 | /// Creates an instance of [`IdsQuery`] 32 | /// 33 | /// - `values` - An array of 34 | /// [document IDs](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-id-field.html). 35 | pub fn ids(values: I) -> IdsQuery 36 | where 37 | I: IntoIterator, 38 | I::Item: ToString, 39 | { 40 | IdsQuery { 41 | values: values.into_iter().map(|value| value.to_string()).collect(), 42 | boost: None, 43 | _name: None, 44 | } 45 | } 46 | } 47 | 48 | impl IdsQuery { 49 | add_boost_and_name!(); 50 | } 51 | 52 | impl ShouldSkip for IdsQuery { 53 | fn should_skip(&self) -> bool { 54 | self.values.should_skip() 55 | } 56 | } 57 | 58 | serialize_with_root!("ids": IdsQuery); 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::*; 63 | 64 | #[test] 65 | fn serialization() { 66 | assert_serialize_query( 67 | Query::ids(vec![1, 3, 2, 5, 4, 6]), 68 | json!({ 69 | "ids": { 70 | "values": ["1", "2", "3", "4", "5", "6"], 71 | } 72 | }), 73 | ); 74 | 75 | assert_serialize_query( 76 | Query::ids(vec![1, 3, 2, 5, 4, 6]).boost(1.3).name("test"), 77 | json!({ 78 | "ids": { 79 | "values": ["1", "2", "3", "4", "5", "6"], 80 | "boost": 1.3, 81 | "_name": "test" 82 | } 83 | }), 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/search/queries/term_level/mod.rs: -------------------------------------------------------------------------------- 1 | //! You can use **term-level queries** to find documents based on precise values in structured data. 2 | //! Examples of structured data include date ranges, IP addresses, prices, or product IDs. 3 | //! 4 | //! Unlike full-text queries, term-level queries do not analyze search terms. Instead, term-level 5 | //! queries match the exact terms stored in a field. 6 | 7 | mod exists_query; 8 | mod fuzzy_query; 9 | mod ids_query; 10 | mod prefix_query; 11 | mod range_query; 12 | mod regexp_query; 13 | mod term_query; 14 | mod terms_lookup_query; 15 | mod terms_query; 16 | mod terms_set_query; 17 | mod wildcard_query; 18 | 19 | pub use self::exists_query::*; 20 | pub use self::fuzzy_query::*; 21 | pub use self::ids_query::*; 22 | pub use self::prefix_query::*; 23 | pub use self::range_query::*; 24 | pub use self::regexp_query::*; 25 | pub use self::term_query::*; 26 | pub use self::terms_lookup_query::*; 27 | pub use self::terms_query::*; 28 | pub use self::terms_set_query::*; 29 | pub use self::wildcard_query::*; 30 | -------------------------------------------------------------------------------- /src/search/queries/term_level/term_query.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | use serde::Serialize; 4 | 5 | /// Returns documents that contain an **exact** term in a provided field. 6 | /// 7 | /// You can use the term query to find documents based on a precise value such as a price, a product ID, or a username. 8 | /// 9 | /// To create a term query with numeric values: 10 | /// ``` 11 | /// # use elasticsearch_dsl::queries::*; 12 | /// # use elasticsearch_dsl::queries::params::*; 13 | /// # let query = 14 | /// Query::term("test", 123); 15 | /// ``` 16 | /// To create a term query with string values and optional fields: 17 | /// ``` 18 | /// # use elasticsearch_dsl::queries::*; 19 | /// # use elasticsearch_dsl::queries::params::*; 20 | /// # let query = 21 | /// Query::term("test", "username") 22 | /// .boost(2) 23 | /// .name("test"); 24 | /// ``` 25 | /// 26 | #[derive(Debug, Clone, PartialEq, Serialize)] 27 | #[serde(remote = "Self")] 28 | pub struct TermQuery { 29 | #[serde(skip)] 30 | field: String, 31 | 32 | value: Option, 33 | 34 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 35 | boost: Option, 36 | 37 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 38 | _name: Option, 39 | } 40 | 41 | impl Query { 42 | /// Creates an instance of [`TermQuery`] 43 | /// 44 | /// - `field` - Field you wish to search. 45 | /// - `value` - Term you wish to find in the provided field. 46 | /// To return a document, the term must exactly match the field value, including whitespace and capitalization. 47 | pub fn term(field: T, value: U) -> TermQuery 48 | where 49 | T: ToString, 50 | U: Serialize, 51 | { 52 | TermQuery { 53 | field: field.to_string(), 54 | value: Term::new(value), 55 | boost: None, 56 | _name: None, 57 | } 58 | } 59 | } 60 | 61 | impl TermQuery { 62 | add_boost_and_name!(); 63 | } 64 | 65 | impl ShouldSkip for TermQuery { 66 | fn should_skip(&self) -> bool { 67 | self.value.should_skip() 68 | } 69 | } 70 | 71 | serialize_with_root_keyed!("term": TermQuery); 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use super::*; 76 | 77 | #[test] 78 | fn serialization() { 79 | assert_serialize_query( 80 | Query::term("test", 123), 81 | json!({ 82 | "term": { 83 | "test": { 84 | "value": 123 85 | } 86 | } 87 | }), 88 | ); 89 | 90 | assert_serialize_query( 91 | Query::term("test", 123).boost(2).name("test"), 92 | json!({ 93 | "term": { 94 | "test": { 95 | "value": 123, 96 | "boost": 2.0, 97 | "_name": "test" 98 | } 99 | } 100 | }), 101 | ); 102 | 103 | assert_serialize_query( 104 | Query::bool().filter(Query::term("test", None::)), 105 | json!({ "bool": {} }), 106 | ) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/search/queries/term_level/terms_query.rs: -------------------------------------------------------------------------------- 1 | use crate::search::*; 2 | use crate::util::*; 3 | 4 | /// Returns documents that contain one or more **exact** terms in a provided field. 5 | /// The terms query is the same as the term query, except you can search for multiple values. 6 | /// 7 | /// To create a terms query with numeric values: 8 | /// ``` 9 | /// # use elasticsearch_dsl::queries::*; 10 | /// # use elasticsearch_dsl::queries::params::*; 11 | /// # let query = 12 | /// Query::terms("test", vec![123]); 13 | /// ``` 14 | /// To create a terms query with string values and optional fields: 15 | /// ``` 16 | /// # use elasticsearch_dsl::queries::*; 17 | /// # use elasticsearch_dsl::queries::params::*; 18 | /// # let query = 19 | /// Query::terms("test", vec!["username"]) 20 | /// .boost(2) 21 | /// .name("test"); 22 | /// ``` 23 | /// 24 | #[derive(Debug, Clone, PartialEq, Serialize)] 25 | #[serde(remote = "Self")] 26 | pub struct TermsQuery { 27 | #[serde(skip)] 28 | field: String, 29 | 30 | #[serde(skip)] 31 | terms: Terms, 32 | 33 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 34 | boost: Option, 35 | 36 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 37 | _name: Option, 38 | } 39 | 40 | impl Query { 41 | /// Creates an instance of [`TermsQuery`] 42 | /// 43 | /// - `field` - Field you wish to search. 44 | /// - `values` - An array of terms you wish to find in the provided field. To return a 45 | /// document, one or more terms must exactly match a field value, 46 | /// including whitespace and capitalization.
47 | /// By default, Elasticsearch limits the `terms` query to a maximum of 48 | /// 65,536 terms. You can change this limit using the 49 | /// [`index.max_terms_count setting`](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#index-max-terms-count).
50 | /// > To use the field values of an existing document as search terms, 51 | /// use the terms lookup parameters. 52 | pub fn terms(field: S, terms: I) -> TermsQuery 53 | where 54 | S: ToString, 55 | I: Into, 56 | { 57 | TermsQuery { 58 | field: field.to_string(), 59 | terms: terms.into(), 60 | boost: None, 61 | _name: None, 62 | } 63 | } 64 | } 65 | 66 | impl TermsQuery { 67 | add_boost_and_name!(); 68 | } 69 | 70 | impl ShouldSkip for TermsQuery { 71 | fn should_skip(&self) -> bool { 72 | self.terms.should_skip() 73 | } 74 | } 75 | 76 | serialize_with_root_key_value_pair!("terms": TermsQuery, field, terms); 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use super::*; 81 | 82 | #[test] 83 | fn serialization() { 84 | assert_serialize_query( 85 | Query::terms("test", [12, 13, 123]), 86 | json!({"terms": { "test": [12, 13, 123] } }), 87 | ); 88 | 89 | assert_serialize_query( 90 | Query::terms("test", [123]).boost(2).name("test"), 91 | json!({ 92 | "terms": { 93 | "test": [123], 94 | "boost": 2.0, 95 | "_name": "test", 96 | } 97 | }), 98 | ); 99 | } 100 | 101 | #[test] 102 | fn should_skip_when_there_are_no_values() { 103 | let values: Vec = Vec::new(); 104 | let query = Query::terms("test", values); 105 | 106 | assert!(query.should_skip()) 107 | } 108 | 109 | #[test] 110 | fn should_not_skip_when_there_are_no_values() { 111 | let query = Query::terms("test", [123]); 112 | 113 | assert!(!query.should_skip()) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/search/rescoring/mod.rs: -------------------------------------------------------------------------------- 1 | //! Rescore clause to run second query over original one results and that way give more accuracy for final results 2 | //! 3 | 4 | mod rescore_; 5 | mod rescore_collection; 6 | 7 | pub use self::rescore_::*; 8 | pub use self::rescore_collection::*; 9 | -------------------------------------------------------------------------------- /src/search/rescoring/rescore_collection.rs: -------------------------------------------------------------------------------- 1 | use super::Rescore; 2 | use crate::util::ShouldSkip; 3 | 4 | /// Rescoring criteria 5 | #[derive(Default, Clone, PartialEq, Serialize)] 6 | pub struct RescoreCollection(Vec); 7 | 8 | impl std::fmt::Debug for RescoreCollection { 9 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 10 | self.0.fmt(f) 11 | } 12 | } 13 | 14 | impl IntoIterator for RescoreCollection { 15 | type Item = Rescore; 16 | 17 | type IntoIter = std::vec::IntoIter; 18 | 19 | fn into_iter(self) -> Self::IntoIter { 20 | self.0.into_iter() 21 | } 22 | } 23 | 24 | impl ShouldSkip for RescoreCollection { 25 | fn should_skip(&self) -> bool { 26 | self.0.should_skip() 27 | } 28 | } 29 | 30 | impl RescoreCollection { 31 | /// Creates a new instance of [RescoreCollection] 32 | pub fn new() -> Self { 33 | Default::default() 34 | } 35 | 36 | /// Extends rescoring collection 37 | pub fn extend(&mut self, rescore: T) 38 | where 39 | T: IntoIterator, 40 | T::Item: Into, 41 | { 42 | self.0.extend( 43 | rescore 44 | .into_iter() 45 | .map(Into::into) 46 | .filter(ShouldSkip::should_keep), 47 | ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/search/response/cluster_statistics.rs: -------------------------------------------------------------------------------- 1 | /// Cluster statistics 2 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] 3 | pub struct ClusterStatistics { 4 | /// Total number of touched clusters 5 | pub total: u32, 6 | 7 | /// Total number of successful clusters 8 | pub successful: u32, 9 | 10 | /// Total number of skipped clusters 11 | pub skipped: u32, 12 | } 13 | -------------------------------------------------------------------------------- /src/search/response/error_cause.rs: -------------------------------------------------------------------------------- 1 | use crate::{util::ShouldSkip, Map}; 2 | use serde_json::Value; 3 | 4 | /// Error cause 5 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 6 | pub struct ErrorCause { 7 | /// Deeper error cause 8 | pub caused_by: Option>, 9 | 10 | /// Error cause reason 11 | pub reason: Option, 12 | 13 | /// Root error cause 14 | #[serde(skip_serializing_if = "ShouldSkip::should_skip", default)] 15 | pub root_cause: Vec, 16 | 17 | /// Exception stack trace 18 | pub stack_trace: Option, 19 | 20 | /// Suppressed error causes 21 | #[serde(skip_serializing_if = "ShouldSkip::should_skip", default)] 22 | pub suppressed: Vec, 23 | 24 | /// Type of error cause 25 | #[serde(rename = "type")] 26 | pub ty: Option, 27 | 28 | /// Additional fields that are not part of the strongly typed error cause 29 | #[serde(skip_serializing_if = "ShouldSkip::should_skip", default, flatten)] 30 | pub additional_details: Map, 31 | } 32 | -------------------------------------------------------------------------------- /src/search/response/explanation.rs: -------------------------------------------------------------------------------- 1 | /// Score explanation 2 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 3 | pub struct Explanation { 4 | /// Cumulative score description 5 | pub description: String, 6 | 7 | /// Cumulative score 8 | pub value: f64, 9 | 10 | /// Score details 11 | #[serde(default)] 12 | pub details: Vec, 13 | } 14 | -------------------------------------------------------------------------------- /src/search/response/hit.rs: -------------------------------------------------------------------------------- 1 | use super::{Explanation, NestedIdentity, Source}; 2 | use crate::{util::ShouldSkip, InnerHitsResult, Map}; 3 | use serde::de::DeserializeOwned; 4 | use serde_json::Value; 5 | 6 | /// Represents a single matched document 7 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 8 | pub struct Hit { 9 | /// Search explanation 10 | #[serde( 11 | skip_serializing_if = "ShouldSkip::should_skip", 12 | rename = "_explanation" 13 | )] 14 | pub explanation: Option, 15 | 16 | /// Document index 17 | #[serde( 18 | default, 19 | skip_serializing_if = "ShouldSkip::should_skip", 20 | rename = "_index" 21 | )] 22 | pub index: String, 23 | 24 | /// Document ID 25 | #[serde( 26 | default, 27 | skip_serializing_if = "ShouldSkip::should_skip", 28 | rename = "_id" 29 | )] 30 | pub id: String, 31 | 32 | /// Document score. [`None`] when documents are implicitly sorted by a 33 | /// field other than `_score` 34 | #[serde(skip_serializing_if = "ShouldSkip::should_skip", rename = "_score")] 35 | pub score: Option, 36 | 37 | /// Nested document identity 38 | #[serde(skip_serializing_if = "ShouldSkip::should_skip", rename = "_nested")] 39 | pub nested: Option, 40 | 41 | /// Document source 42 | #[serde( 43 | skip_serializing_if = "ShouldSkip::should_skip", 44 | rename = "_source", 45 | default 46 | )] 47 | pub source: Source, 48 | 49 | /// Highlighted matches 50 | #[serde(skip_serializing_if = "ShouldSkip::should_skip", default)] 51 | pub highlight: Map>, 52 | 53 | /// Inner hits 54 | #[serde(skip_serializing_if = "ShouldSkip::should_skip", default)] 55 | pub inner_hits: Map, 56 | 57 | /// Matched queries 58 | #[serde(skip_serializing_if = "ShouldSkip::should_skip", default)] 59 | pub matched_queries: Vec, 60 | 61 | /// Values document was sorted by 62 | #[serde(skip_serializing_if = "ShouldSkip::should_skip", default)] 63 | pub sort: Vec, 64 | 65 | /// Field values for the documents. Need to be specified in the request 66 | #[serde(skip_serializing_if = "ShouldSkip::should_skip", default)] 67 | pub fields: Map, 68 | } 69 | 70 | impl Hit { 71 | /// Parses document source into a concrete type 72 | pub fn source(&self) -> Result 73 | where 74 | T: DeserializeOwned, 75 | { 76 | self.source.parse() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/search/response/hits_metadata.rs: -------------------------------------------------------------------------------- 1 | use super::{Hit, TotalHits}; 2 | use crate::util::ShouldSkip; 3 | 4 | /// Matched hits 5 | #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] 6 | pub struct HitsMetadata { 7 | /// Total number of matched documents 8 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 9 | pub total: Option, 10 | 11 | /// Maximum document score. [`None`] when documents are implicitly sorted 12 | /// by a field other than `_score` 13 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 14 | pub max_score: Option, 15 | 16 | /// Matched hits 17 | #[serde(default = "Vec::new")] 18 | pub hits: Vec, 19 | } 20 | -------------------------------------------------------------------------------- /src/search/response/inner_hits_result.rs: -------------------------------------------------------------------------------- 1 | use super::HitsMetadata; 2 | 3 | /// Represents inner hits 4 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 5 | pub struct InnerHitsResult { 6 | /// The actual inner hits 7 | pub hits: HitsMetadata, 8 | } 9 | -------------------------------------------------------------------------------- /src/search/response/mod.rs: -------------------------------------------------------------------------------- 1 | mod cluster_statistics; 2 | mod error_cause; 3 | mod explanation; 4 | mod hit; 5 | mod hits_metadata; 6 | mod inner_hits_result; 7 | mod nested_identity; 8 | mod search_response; 9 | mod shard_failure; 10 | mod shard_statistics; 11 | mod source; 12 | mod suggest; 13 | mod suggest_option; 14 | mod total_hits; 15 | mod total_hits_relation; 16 | 17 | pub use self::cluster_statistics::*; 18 | pub use self::error_cause::*; 19 | pub use self::explanation::*; 20 | pub use self::hit::*; 21 | pub use self::hits_metadata::*; 22 | pub use self::inner_hits_result::*; 23 | pub use self::nested_identity::*; 24 | pub use self::search_response::*; 25 | pub use self::shard_failure::*; 26 | pub use self::shard_statistics::*; 27 | pub use self::source::*; 28 | pub use self::suggest::*; 29 | pub use self::suggest_option::*; 30 | pub use self::total_hits::*; 31 | pub use self::total_hits_relation::*; 32 | -------------------------------------------------------------------------------- /src/search/response/nested_identity.rs: -------------------------------------------------------------------------------- 1 | use crate::util::ShouldSkip; 2 | 3 | /// Nested document metadata 4 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 5 | pub struct NestedIdentity { 6 | /// Field 7 | pub field: String, 8 | 9 | /// Offset 10 | pub offset: u64, 11 | 12 | /// Nested document metadata 13 | #[serde(skip_serializing_if = "ShouldSkip::should_skip", rename = "_nested")] 14 | pub nested: Option>, 15 | } 16 | -------------------------------------------------------------------------------- /src/search/response/shard_failure.rs: -------------------------------------------------------------------------------- 1 | use super::ErrorCause; 2 | use crate::util::ShouldSkip; 3 | 4 | /// Shard failure details 5 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 6 | pub struct ShardFailure { 7 | /// Index name 8 | pub index: Option, 9 | 10 | /// Node name 11 | pub node: Option, 12 | 13 | /// Status 14 | pub status: Option, 15 | 16 | /// Shard 17 | pub shard: Option, 18 | 19 | /// Reason 20 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 21 | pub reason: Option, 22 | } 23 | -------------------------------------------------------------------------------- /src/search/response/shard_statistics.rs: -------------------------------------------------------------------------------- 1 | use super::ShardFailure; 2 | use crate::util::ShouldSkip; 3 | 4 | /// Number of shards touched with their states 5 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 6 | pub struct ShardStatistics { 7 | /// Total number of touched shards 8 | pub total: u32, 9 | 10 | /// Total number of successful shards 11 | pub successful: u32, 12 | 13 | /// Total number of skipped shards 14 | pub skipped: u32, 15 | 16 | /// Total number of failed shards 17 | pub failed: u32, 18 | 19 | /// Partial response failures 20 | #[serde(skip_serializing_if = "ShouldSkip::should_skip", default)] 21 | pub failures: Vec, 22 | } 23 | 24 | impl Default for ShardStatistics { 25 | fn default() -> Self { 26 | Self { 27 | total: 1, 28 | successful: 1, 29 | skipped: 0, 30 | failed: 0, 31 | failures: Vec::new(), 32 | } 33 | } 34 | } 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use super::*; 39 | 40 | #[test] 41 | fn deserializes_successfully() { 42 | let value = json!({ 43 | "total": 280, 44 | "successful": 277, 45 | "skipped": 0, 46 | "failed": 3, 47 | "failures": [ 48 | { 49 | "shard": 1, 50 | "index": "nbs_comprehend-2021-w41", 51 | "node": "oGEHA-aRSnmwuEmqSZc6Kw", 52 | "reason": { 53 | "type": "script_exception", 54 | "reason": "runtime error", 55 | "script_stack": [ 56 | "org.elasticsearch.index.fielddata.ScriptDocValues$Longs.get(ScriptDocValues.java:121)", 57 | "org.elasticsearch.index.fielddata.ScriptDocValues$Longs.getValue(ScriptDocValues.java:115)", 58 | "doc['user.followers_count'].value > 9999 ? 1 : 0", 59 | " ^---- HERE" 60 | ], 61 | "script": "doc['user.followers_count'].value > 9999 ? 1 : 0", 62 | "lang": "painless", 63 | "position": { 64 | "offset": 27, 65 | "start": 0, 66 | "end": 48 67 | }, 68 | "caused_by": { 69 | "type": "illegal_state_exception", 70 | "reason": "A document doesn't have a value for a field! Use doc[].size()==0 to check if a document is missing a field!" 71 | } 72 | } 73 | } 74 | ] 75 | }); 76 | 77 | let _ = serde_json::from_value::(value).unwrap(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/search/response/source.rs: -------------------------------------------------------------------------------- 1 | use crate::util::ShouldSkip; 2 | use serde::de::DeserializeOwned; 3 | use serde_json::{value::RawValue, Value}; 4 | 5 | /// Document source with delayed deserialization 6 | #[derive(Clone, Default, Serialize, Deserialize)] 7 | pub struct Source(Box); 8 | 9 | impl std::fmt::Debug for Source { 10 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 11 | self.0.fmt(f) 12 | } 13 | } 14 | 15 | impl std::fmt::Display for Source { 16 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 17 | self.0.fmt(f) 18 | } 19 | } 20 | 21 | impl PartialEq for Source { 22 | fn eq(&self, other: &Self) -> bool { 23 | self.0.get() == other.0.get() 24 | } 25 | } 26 | 27 | impl ShouldSkip for Source { 28 | fn should_skip(&self) -> bool { 29 | self.eq(&Source::default()) 30 | } 31 | } 32 | 33 | impl Source { 34 | /// Parses document source into a concrete type 35 | pub fn parse(&self) -> Result 36 | where 37 | T: DeserializeOwned, 38 | { 39 | serde_json::from_str(self.0.get()) 40 | } 41 | 42 | /// Creates source from a string 43 | pub fn from_string(value: String) -> Result { 44 | RawValue::from_string(value).map(Self) 45 | } 46 | } 47 | 48 | impl From for Source { 49 | /// Creates source from a [Value] 50 | /// 51 | /// Calling expect here because [Value] always represents a valid JSON and it 52 | /// _should be safe_ to do so. 53 | fn from(value: Value) -> Self { 54 | Self(RawValue::from_string(format!("{value}")).expect("valid json")) 55 | } 56 | } 57 | 58 | impl From> for Source { 59 | fn from(value: Box) -> Self { 60 | Self(value) 61 | } 62 | } 63 | 64 | impl<'a> From<&'a RawValue> for Source { 65 | fn from(value: &'a RawValue) -> Self { 66 | Self(value.to_owned()) 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::*; 73 | 74 | #[test] 75 | fn creates_from_json_value() { 76 | let _ = Source::from(json!({"key": "value"})); 77 | let _ = Source::from(json!({"key": ["value", 1, 1.2, true, null, {"key2": "value2"}]})); 78 | let _ = Source::from(json!(["one", 2, 3.0, false, null, {}])); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/search/response/suggest.rs: -------------------------------------------------------------------------------- 1 | use super::SuggestOption; 2 | 3 | /// Suggester response item 4 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 5 | pub struct Suggest { 6 | /// Suggestion text 7 | pub text: String, 8 | 9 | /// Suggestion length 10 | pub length: u64, 11 | 12 | /// Suggestion offset 13 | pub offset: u64, 14 | 15 | /// Suggestion options 16 | pub options: Vec, 17 | } 18 | -------------------------------------------------------------------------------- /src/search/response/suggest_option.rs: -------------------------------------------------------------------------------- 1 | use crate::{util::ShouldSkip, Map}; 2 | use serde::de::DeserializeOwned; 3 | 4 | /// Suggester response option variants 5 | #[derive(Clone, PartialEq, Serialize, Deserialize)] 6 | #[serde(untagged)] 7 | pub enum SuggestOption { 8 | /// Completion suggester response option 9 | Completion(CompletionSuggestOption), 10 | 11 | /// Term suggester response option 12 | Term(TermSuggestOption), 13 | 14 | /// Phrase suggester response option 15 | Phrase(PhraseSuggestOption), 16 | } 17 | 18 | impl std::fmt::Debug for SuggestOption { 19 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 20 | match self { 21 | Self::Completion(suggest_option) => suggest_option.fmt(f), 22 | Self::Term(suggest_option) => suggest_option.fmt(f), 23 | Self::Phrase(suggest_option) => suggest_option.fmt(f), 24 | } 25 | } 26 | } 27 | 28 | /// Suggester response item option 29 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 30 | pub struct CompletionSuggestOption { 31 | /// Suggested text 32 | pub text: String, 33 | 34 | /// Document index 35 | #[serde(rename = "_index")] 36 | pub index: String, 37 | 38 | /// Document id 39 | #[serde(rename = "_id")] 40 | pub id: String, 41 | 42 | /// Document score for completion suggester, suggest score for term, phrase 43 | #[serde(alias = "_score")] 44 | pub score: f32, 45 | 46 | /// Document source 47 | /// 48 | /// Not using [crate::Source] due to a bug in enums and RawValue 49 | /// 50 | #[serde( 51 | skip_serializing_if = "ShouldSkip::should_skip", 52 | rename = "_source", 53 | default 54 | )] 55 | pub source: Option, 56 | 57 | /// The contexts associated with the completed document 58 | /// 59 | /// Contexts always return either as a category or as geohash 60 | #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")] 61 | pub contexts: Map>, 62 | } 63 | 64 | impl CompletionSuggestOption { 65 | /// Parses document source into a concrete type 66 | pub fn parse(&self) -> Result 67 | where 68 | T: DeserializeOwned, 69 | { 70 | serde_json::from_value(self.source.clone().unwrap_or_default()) 71 | } 72 | } 73 | 74 | /// Term suggester response item option 75 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 76 | pub struct TermSuggestOption { 77 | /// Suggested text 78 | pub text: String, 79 | 80 | /// Suggest score 81 | pub score: f32, 82 | 83 | /// Term frequency 84 | #[serde(rename = "freq")] 85 | pub frequency: u64, 86 | } 87 | 88 | /// Phrase suggester response item option 89 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 90 | pub struct PhraseSuggestOption { 91 | /// Suggested text 92 | pub text: String, 93 | 94 | /// Suggest score 95 | pub score: f32, 96 | 97 | /// Phrase suggestions only, true if matching documents for the collate query were found 98 | #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")] 99 | pub collate_match: Option, 100 | 101 | /// Highlighted version of text 102 | #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")] 103 | pub highlighted: Option, 104 | } 105 | -------------------------------------------------------------------------------- /src/search/response/total_hits.rs: -------------------------------------------------------------------------------- 1 | use super::TotalHitsRelation; 2 | 3 | /// Total number of matched documents 4 | #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] 5 | pub struct TotalHits { 6 | /// Number of total documents 7 | pub value: u64, 8 | 9 | /// Relation to total number of matched documents 10 | pub relation: TotalHitsRelation, 11 | } 12 | 13 | impl TotalHits { 14 | /// Create default Total instance 15 | pub fn new(value: Option) -> Self { 16 | Self { 17 | value: value.unwrap_or(0), 18 | relation: TotalHitsRelation::Equal, 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/search/response/total_hits_relation.rs: -------------------------------------------------------------------------------- 1 | /// Relation to total number of matched documents 2 | #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] 3 | pub enum TotalHitsRelation { 4 | /// When `track_total_hits` is `false` (default), Elasticsearch returns that 5 | /// there have been more than 10,000 documents 6 | #[serde(rename = "gte")] 7 | GreaterThanOrEqualTo, 8 | 9 | /// When there are less than 10,000 documents or `track_total_hits` is set 10 | /// to `true`, exact number of matched documents will be brought back 11 | #[serde(rename = "eq")] 12 | Equal, 13 | } 14 | -------------------------------------------------------------------------------- /src/search/sort/mod.rs: -------------------------------------------------------------------------------- 1 | //! Allows you to add one or more sorts on specific fields. 2 | //! Each sort can be reversed as well. 3 | //! The sort is defined on a per field level, with special field name for `_score` to sort by score, and `_doc` to sort by index order. 4 | //! 5 | //! 6 | 7 | mod field_sort; 8 | mod geo_distance_sort; 9 | mod script_sort; 10 | mod sort_; 11 | mod sort_collection; 12 | mod sort_missing; 13 | mod sort_mode; 14 | mod sort_order; 15 | mod sort_special_field; 16 | 17 | pub use self::field_sort::*; 18 | pub use self::geo_distance_sort::*; 19 | pub use self::script_sort::*; 20 | pub use self::sort_::*; 21 | pub use self::sort_collection::*; 22 | pub use self::sort_missing::*; 23 | pub use self::sort_mode::*; 24 | pub use self::sort_order::*; 25 | pub use self::sort_special_field::*; 26 | -------------------------------------------------------------------------------- /src/search/sort/script_sort.rs: -------------------------------------------------------------------------------- 1 | use super::SortOrder; 2 | use crate::util::ShouldSkip; 3 | use crate::{Script, ScriptSortType}; 4 | use serde::Serialize; 5 | 6 | /// Sorts search hits by script result 7 | /// 8 | /// 9 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 10 | #[serde(remote = "Self")] 11 | pub struct ScriptSort { 12 | script: Script, 13 | 14 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 15 | order: Option, 16 | 17 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 18 | r#type: Option, 19 | } 20 | 21 | impl ScriptSort { 22 | /// Creates an instance of [ScriptSort] 23 | pub fn new(script: Script) -> Self { 24 | Self { 25 | script, 26 | order: None, 27 | r#type: None, 28 | } 29 | } 30 | 31 | /// Creates an instance of [ScriptSort] by ascending order 32 | pub fn ascending(script: Script) -> Self { 33 | Self::new(script).order(SortOrder::Asc) 34 | } 35 | 36 | /// Creates an instance of [ScriptSort] by descending order 37 | pub fn descending(script: Script) -> Self { 38 | Self::new(script).order(SortOrder::Desc) 39 | } 40 | 41 | /// Explicit order 42 | /// 43 | /// 44 | pub fn order(mut self, order: SortOrder) -> Self { 45 | self.order = Some(order); 46 | self 47 | } 48 | 49 | /// Sort type for script result 50 | pub fn r#type(mut self, r#type: ScriptSortType) -> Self { 51 | self.r#type = Some(r#type); 52 | self 53 | } 54 | } 55 | 56 | impl IntoIterator for ScriptSort { 57 | type Item = Self; 58 | 59 | type IntoIter = std::option::IntoIter; 60 | 61 | fn into_iter(self) -> Self::IntoIter { 62 | Some(self).into_iter() 63 | } 64 | } 65 | 66 | serialize_with_root!("_script": ScriptSort); 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use super::*; 71 | use crate::util::assert_serialize; 72 | 73 | #[test] 74 | fn serialization() { 75 | assert_serialize( 76 | ScriptSort::ascending( 77 | Script::source("doc['numberOfCommits'].value * params.factor").param("factor", 1.1), 78 | ) 79 | .r#type(ScriptSortType::Number), 80 | json!({ 81 | "_script": { 82 | "order": "asc", 83 | "type": "number", 84 | "script": { 85 | "source": "doc['numberOfCommits'].value * params.factor", 86 | "params": { 87 | "factor": 1.1 88 | } 89 | } 90 | } 91 | }), 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/search/sort/sort_.rs: -------------------------------------------------------------------------------- 1 | use super::{FieldSort, GeoDistanceSort, ScriptSort, SortSpecialField}; 2 | use std::borrow::Cow; 3 | 4 | /// Sorting criterion 5 | #[derive(Clone, PartialEq, Serialize)] 6 | #[serde(untagged)] 7 | pub enum Sort { 8 | /// Special sort field, 9 | SpecialField(SortSpecialField), 10 | 11 | /// Sorts by field name 12 | Field(String), 13 | 14 | /// Sorts by field name with finer control 15 | FieldSort(FieldSort), 16 | 17 | /// Sorts by a geo distance 18 | GeoDistanceSort(GeoDistanceSort), 19 | 20 | /// Sort by a script 21 | ScriptSort(ScriptSort), 22 | } 23 | 24 | impl std::fmt::Debug for Sort { 25 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 26 | match self { 27 | Self::SpecialField(sort) => sort.fmt(f), 28 | Self::Field(sort) => sort.fmt(f), 29 | Self::FieldSort(sort) => sort.fmt(f), 30 | Self::GeoDistanceSort(sort) => sort.fmt(f), 31 | Self::ScriptSort(sort) => sort.fmt(f), 32 | } 33 | } 34 | } 35 | 36 | impl From for Sort { 37 | fn from(value: SortSpecialField) -> Self { 38 | Self::SpecialField(value) 39 | } 40 | } 41 | 42 | impl From<&str> for Sort { 43 | fn from(value: &str) -> Self { 44 | Self::Field(value.to_string()) 45 | } 46 | } 47 | 48 | impl From> for Sort { 49 | fn from(value: Cow<'_, str>) -> Self { 50 | Self::Field(value.to_string()) 51 | } 52 | } 53 | 54 | impl From for Sort { 55 | fn from(value: String) -> Self { 56 | Self::Field(value) 57 | } 58 | } 59 | 60 | impl From for Sort { 61 | fn from(value: FieldSort) -> Self { 62 | Self::FieldSort(value) 63 | } 64 | } 65 | 66 | impl From for Sort { 67 | fn from(value: GeoDistanceSort) -> Self { 68 | Self::GeoDistanceSort(value) 69 | } 70 | } 71 | 72 | impl From for Sort { 73 | fn from(value: ScriptSort) -> Self { 74 | Self::ScriptSort(value) 75 | } 76 | } 77 | 78 | impl IntoIterator for Sort { 79 | type Item = Self; 80 | 81 | type IntoIter = std::option::IntoIter; 82 | 83 | fn into_iter(self) -> Self::IntoIter { 84 | Some(self).into_iter() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/search/sort/sort_collection.rs: -------------------------------------------------------------------------------- 1 | use super::{FieldSort, Sort}; 2 | use crate::util::ShouldSkip; 3 | 4 | /// A sorting criteria 5 | #[derive(Default, Clone, PartialEq, Serialize)] 6 | pub struct SortCollection(Vec); 7 | 8 | impl std::fmt::Debug for SortCollection { 9 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 10 | self.0.fmt(f) 11 | } 12 | } 13 | 14 | impl IntoIterator for SortCollection { 15 | type Item = Sort; 16 | 17 | type IntoIter = std::vec::IntoIter; 18 | 19 | fn into_iter(self) -> Self::IntoIter { 20 | self.0.into_iter() 21 | } 22 | } 23 | 24 | impl ShouldSkip for SortCollection { 25 | fn should_skip(&self) -> bool { 26 | self.0.should_skip() 27 | } 28 | } 29 | 30 | impl SortCollection { 31 | /// Creates a new instance of [SortCollection] 32 | pub fn new() -> Self { 33 | Default::default() 34 | } 35 | 36 | /// Extends sorting collection 37 | pub fn extend(&mut self, sort: T) 38 | where 39 | T: IntoIterator, 40 | T::Item: Into, 41 | { 42 | self.0.extend(sort.into_iter().map(Into::into)) 43 | } 44 | 45 | /// Add a field to sort by ascending order 46 | pub fn ascending(mut self, field: T) -> Self 47 | where 48 | T: ToString, 49 | { 50 | self.0.push(Sort::FieldSort(FieldSort::ascending(field))); 51 | self 52 | } 53 | 54 | /// Add a field to sort by descending order 55 | pub fn descending(mut self, field: T) -> Self 56 | where 57 | T: ToString, 58 | { 59 | self.0.push(Sort::FieldSort(FieldSort::descending(field))); 60 | self 61 | } 62 | 63 | /// Add a field sort 64 | pub fn field(mut self, field_sort: FieldSort) -> Self { 65 | self.0.push(Sort::FieldSort(field_sort)); 66 | self 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | use super::*; 73 | use crate::util::assert_serialize_sort; 74 | use crate::SortSpecialField; 75 | 76 | #[test] 77 | fn serializes_correctly() { 78 | assert_serialize_sort(["abc", "def"], json!(["abc", "def"])); 79 | 80 | assert_serialize_sort( 81 | [ 82 | FieldSort::ascending("field1"), 83 | FieldSort::descending("field2"), 84 | ], 85 | json!([ 86 | { "field1": { "order": "asc" } }, 87 | { "field2": { "order": "desc" } }, 88 | ]), 89 | ); 90 | 91 | assert_serialize_sort( 92 | [ 93 | Sort::FieldSort( 94 | FieldSort::ascending("post_date").format("strict_date_optional_time_nanos"), 95 | ), 96 | Sort::Field("user".to_string()), 97 | Sort::FieldSort(FieldSort::descending("name")), 98 | Sort::FieldSort(FieldSort::descending("age")), 99 | Sort::SpecialField(SortSpecialField::Score), 100 | ], 101 | json!([ 102 | { "post_date" : {"order" : "asc", "format": "strict_date_optional_time_nanos" } }, 103 | "user", 104 | { "name" : { "order": "desc" } }, 105 | { "age" : { "order": "desc" } }, 106 | "_score" 107 | ]), 108 | ); 109 | 110 | assert_serialize_sort( 111 | SortCollection::new() 112 | .ascending("name") 113 | .descending("age") 114 | .field(FieldSort::ascending("post_date").format("strict_date_optional_time_nanos")), 115 | json!([ 116 | { "name" : { "order": "asc" } }, 117 | { "age" : { "order": "desc" } }, 118 | { "post_date" : {"order" : "asc", "format": "strict_date_optional_time_nanos" } }, 119 | ]), 120 | ); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/search/sort/sort_missing.rs: -------------------------------------------------------------------------------- 1 | /// The `missing` parameter specifies how docs which are missing the sort field should be treated: 2 | /// 3 | /// The `missing` value can be set to `_last`, `_first`, or a custom value (that will be used for missing docs as the sort value). The default is `_last`. 4 | /// 5 | /// 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] 7 | pub enum SortMissing { 8 | /// Sorts missing fields first 9 | #[serde(rename = "_first")] 10 | First, 11 | 12 | /// Sorts missing field last 13 | #[serde(rename = "_last")] 14 | Last, 15 | } 16 | -------------------------------------------------------------------------------- /src/search/sort/sort_mode.rs: -------------------------------------------------------------------------------- 1 | /// Elasticsearch supports sorting by array or multi-valued fields. The `mode` option controls what array value is picked for sorting the document it belongs to. 2 | /// 3 | /// The default sort mode in the ascending sort order is `min` — the lowest value is picked. The default sort mode in the descending order is `max` — the highest value is picked. 4 | /// 5 | /// 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] 7 | #[serde(rename_all = "snake_case")] 8 | pub enum SortMode { 9 | /// Pick the lowest value. 10 | Min, 11 | 12 | /// Pick the highest value. 13 | Max, 14 | 15 | /// Use the sum of all values as sort value.\ 16 | /// Only applicable for number based array fields. 17 | Sum, 18 | 19 | /// Use the average of all values as sort value.\ 20 | /// Only applicable for number based array fields. 21 | Avg, 22 | 23 | /// Use the median of all values as sort value.\ 24 | /// Only applicable for number based array fields. 25 | Median, 26 | } 27 | -------------------------------------------------------------------------------- /src/search/sort/sort_order.rs: -------------------------------------------------------------------------------- 1 | /// The order defaults to `desc` when sorting on the `_score`, and defaults to `asc` when sorting on anything else. 2 | /// 3 | /// 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] 5 | #[serde(rename_all = "snake_case")] 6 | pub enum SortOrder { 7 | /// Sort in ascending order 8 | Asc, 9 | 10 | /// Sort in descending order 11 | Desc, 12 | } 13 | -------------------------------------------------------------------------------- /src/search/sort/sort_special_field.rs: -------------------------------------------------------------------------------- 1 | /// Special sorting field variants 2 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] 3 | pub enum SortSpecialField { 4 | /// Document score 5 | #[serde(rename = "_score")] 6 | Score, 7 | 8 | /// The most efficient way to sort, does not guarantee any order, useful for scrolling 9 | #[serde(rename = "_doc")] 10 | DocumentIndexOrder, 11 | 12 | /// Sorts by shard doc value, useful for PIT queries 13 | #[serde(rename = "_shard_doc")] 14 | ShardDocumentOrder, 15 | } 16 | 17 | impl ToString for SortSpecialField { 18 | fn to_string(&self) -> String { 19 | String::from(match self { 20 | Self::Score => "_score", 21 | Self::DocumentIndexOrder => "_doc", 22 | Self::ShardDocumentOrder => "_shard_doc", 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/search/suggesters/mod.rs: -------------------------------------------------------------------------------- 1 | //! Suggests similar looking terms based on a provided text by using a suggester. 2 | //! 3 | //! 4 | 5 | mod completion_suggester; 6 | mod suggest_context_query; 7 | mod suggest_fuzziness; 8 | mod suggester; 9 | 10 | pub use self::completion_suggester::*; 11 | pub use self::suggest_context_query::*; 12 | pub use self::suggest_fuzziness::*; 13 | pub use self::suggester::*; 14 | -------------------------------------------------------------------------------- /src/search/suggesters/suggest_context_query.rs: -------------------------------------------------------------------------------- 1 | use crate::util::ShouldSkip; 2 | 3 | /// The completion suggester considers all documents in the index, but it is often desirable to 4 | /// serve suggestions filtered and/or boosted by some criteria. For example, you want to suggest 5 | /// song titles filtered by certain artists or you want to boost song titles based on their genre. 6 | /// 7 | /// To achieve suggestion filtering and/or boosting, you can add context mappings while configuring 8 | /// a completion field. You can define multiple context mappings for a completion field. Every 9 | /// context mapping has a unique name and a type. There are two types: `category` and `geo`. 10 | /// Context mappings are configured under the contexts parameter in the field mapping. 11 | #[derive(Debug, Clone, PartialEq, Serialize)] 12 | pub struct SuggestContextQuery { 13 | context: String, 14 | 15 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 16 | boost: Option, 17 | 18 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 19 | prefix: Option, 20 | } 21 | 22 | impl SuggestContextQuery { 23 | /// Creates an instance of [SuggestContextQuery] 24 | /// 25 | /// - `context` - The value of the category to filter/boost on 26 | pub fn new(context: T) -> Self 27 | where 28 | T: ToString, 29 | { 30 | Self { 31 | context: context.to_string(), 32 | boost: None, 33 | prefix: None, 34 | } 35 | } 36 | 37 | /// The factor by which the score of the suggestion should be boosted, the score is computed by 38 | /// multiplying the boost with the suggestion weight, defaults to `1` 39 | pub fn boost(mut self, boost: T) -> Self 40 | where 41 | T: num_traits::AsPrimitive, 42 | { 43 | self.boost = Some(boost.as_()); 44 | self 45 | } 46 | 47 | /// Whether the category value should be treated as a prefix or not. For example, if set to 48 | /// `true`, you can filter category of _type1_, _type2_ and so on, by specifying a category 49 | /// prefix of type. Defaults to `false` 50 | pub fn prefix(mut self, prefix: bool) -> Self { 51 | self.prefix = Some(prefix); 52 | self 53 | } 54 | } 55 | 56 | impl IntoIterator for SuggestContextQuery { 57 | type Item = Self; 58 | 59 | type IntoIter = std::option::IntoIter; 60 | 61 | fn into_iter(self) -> Self::IntoIter { 62 | Some(self).into_iter() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/search/suggesters/suggest_fuzziness.rs: -------------------------------------------------------------------------------- 1 | use crate::util::ShouldSkip; 2 | use crate::Fuzziness; 3 | 4 | /// Suggester fuzziness parameters 5 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize)] 6 | pub struct SuggestFuzziness { 7 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 8 | fuzziness: Option, 9 | 10 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 11 | min_length: Option, 12 | 13 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 14 | prefix_length: Option, 15 | 16 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 17 | transpositions: Option, 18 | 19 | #[serde(skip_serializing_if = "ShouldSkip::should_skip")] 20 | unicode_aware: Option, 21 | } 22 | 23 | impl SuggestFuzziness { 24 | /// Creates a new instance of [SuggestFuzziness] 25 | pub fn new() -> Self { 26 | Default::default() 27 | } 28 | 29 | /// The fuzziness factor, defaults to [`AUTO`](Fuzziness::Auto). See [Fuzziness] for allowed 30 | /// settings. 31 | pub fn fuzziness(mut self, fuzziness: T) -> Self 32 | where 33 | T: Into, 34 | { 35 | self.fuzziness = Some(fuzziness.into()); 36 | self 37 | } 38 | 39 | /// If set to `true`, transpositions are counted as one change instead of two, defaults to 40 | /// `true` 41 | pub fn transpositions(mut self, transpositions: bool) -> Self { 42 | self.transpositions = Some(transpositions); 43 | self 44 | } 45 | 46 | /// Minimum length of the input before fuzzy suggestions are returned, defaults `3` 47 | pub fn min_length(mut self, min_length: u64) -> Self { 48 | self.min_length = Some(min_length); 49 | self 50 | } 51 | 52 | /// Minimum length of the input, which is not checked for fuzzy alternatives, defaults to `1` 53 | pub fn prefix_length(mut self, prefix_length: u64) -> Self { 54 | self.prefix_length = Some(prefix_length); 55 | self 56 | } 57 | 58 | /// If `true`, all measurements (like fuzzy edit distance, transpositions, and lengths) are 59 | /// measured in Unicode code points instead of in bytes. This is slightly slower than raw 60 | /// bytes, so it is set to `false` by default. 61 | pub fn unicode_aware(mut self, unicode_aware: bool) -> Self { 62 | self.unicode_aware = Some(unicode_aware); 63 | self 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/search/suggesters/suggester.rs: -------------------------------------------------------------------------------- 1 | use super::CompletionSuggester; 2 | 3 | /// Suggester variants 4 | #[derive(Clone, PartialEq, Serialize)] 5 | #[serde(untagged)] 6 | #[allow(missing_docs)] 7 | pub enum Suggester { 8 | Completion(CompletionSuggester), 9 | } 10 | 11 | impl std::fmt::Debug for Suggester { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | match self { 14 | Self::Completion(suggester) => suggester.fmt(f), 15 | } 16 | } 17 | } 18 | 19 | impl From for Suggester { 20 | fn from(value: CompletionSuggester) -> Self { 21 | Self::Completion(value) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | /// Map type alias for the whole library 2 | pub(crate) type Map = std::collections::BTreeMap; 3 | 4 | /// Set type alias for the whole library 5 | pub(crate) type Set = std::collections::BTreeSet; 6 | -------------------------------------------------------------------------------- /src/util/assert_serialize.rs: -------------------------------------------------------------------------------- 1 | /// Tests if a type is serialized to correct JSON [`Value`] 2 | #[cfg(test)] 3 | pub(crate) fn assert_serialize(subject: T, expectation: serde_json::Value) 4 | where 5 | T: serde::Serialize, 6 | { 7 | let string = serde_json::to_string(&subject).unwrap(); 8 | let result: serde_json::Value = serde_json::from_str(&string).unwrap(); 9 | 10 | pretty_assertions::assert_str_eq!( 11 | serde_json::to_string_pretty(&result).unwrap(), 12 | serde_json::to_string_pretty(&expectation).unwrap(), 13 | ); 14 | } 15 | 16 | /// Tests if a query is serialized to correct JSON [`Value`] 17 | #[cfg(test)] 18 | pub(crate) fn assert_serialize_query(subject: T, expectation: serde_json::Value) 19 | where 20 | T: Into, 21 | { 22 | let subject = crate::Search::new().query(subject); 23 | let expectation = json!({ "query": expectation }); 24 | 25 | assert_serialize(subject, expectation) 26 | } 27 | 28 | /// Tests if an aggregation is serialized to correct JSON [`Value`] 29 | #[cfg(test)] 30 | pub(crate) fn assert_serialize_aggregation(subject: T, expectation: serde_json::Value) 31 | where 32 | T: Into, 33 | { 34 | let subject = crate::Search::new().aggregate("aggregation_name", subject); 35 | let expectation = json!({ "aggs": { "aggregation_name": expectation } }); 36 | 37 | assert_serialize(subject, expectation) 38 | } 39 | 40 | /// Tests if sorting criteria is serialized to correct JSON [`Value`] 41 | #[cfg(test)] 42 | pub(crate) fn assert_serialize_sort(subject: T, expectation: serde_json::Value) 43 | where 44 | T: IntoIterator, 45 | T::Item: Into, 46 | { 47 | let subject = crate::Search::new().sort(subject); 48 | let expectation = json!({ "sort": expectation }); 49 | 50 | assert_serialize(subject, expectation) 51 | } 52 | 53 | /// Tests if rescoring criteria is serialized to correct JSON [`Value`] 54 | #[cfg(test)] 55 | pub(crate) fn assert_serialize_rescore(subject: T, expectation: serde_json::Value) 56 | where 57 | T: IntoIterator, 58 | T::Item: Into, 59 | { 60 | let subject = crate::Search::new().rescore(subject); 61 | let expectation = json!({ "rescore": [expectation] }); 62 | 63 | assert_serialize(subject, expectation) 64 | } 65 | -------------------------------------------------------------------------------- /src/util/join_with_pipe.rs: -------------------------------------------------------------------------------- 1 | use serde::ser::{Serialize, Serializer}; 2 | 3 | pub(crate) fn join_with_pipe(value: &[T], serializer: S) -> Result 4 | where 5 | S: Serializer, 6 | T: ToString, 7 | { 8 | value 9 | .iter() 10 | .map(ToString::to_string) 11 | .collect::>() 12 | .join("|") 13 | .serialize(serializer) 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::*; 19 | use crate::util::*; 20 | 21 | #[test] 22 | fn tests_serialization() { 23 | #[derive(Serialize)] 24 | struct JoinWithPipe { 25 | #[serde(serialize_with = "join_with_pipe")] 26 | value: &'static [i32], 27 | } 28 | 29 | assert_serialize( 30 | JoinWithPipe { value: &[1, 2, 3] }, 31 | json!({ "value": "1|2|3" }), 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/util/key_value_pair.rs: -------------------------------------------------------------------------------- 1 | use serde::ser::{Serialize, SerializeMap, Serializer}; 2 | 3 | #[derive(Clone, PartialEq, Eq)] 4 | pub(crate) struct KeyValuePair { 5 | pub(crate) key: K, 6 | pub(crate) value: V, 7 | } 8 | 9 | impl KeyValuePair { 10 | /// Creates an instance of [`KeyValuePair`] 11 | pub(crate) fn new(key: K, value: V) -> Self { 12 | Self { key, value } 13 | } 14 | } 15 | 16 | impl std::fmt::Debug for KeyValuePair 17 | where 18 | K: std::fmt::Debug + AsRef, 19 | V: std::fmt::Debug, 20 | { 21 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 22 | f.debug_struct("KeyValuePair") 23 | .field(self.key.as_ref(), &self.value) 24 | .finish() 25 | } 26 | } 27 | 28 | impl Serialize for KeyValuePair 29 | where 30 | K: Serialize, 31 | V: Serialize, 32 | { 33 | fn serialize(&self, serializer: S) -> Result 34 | where 35 | S: Serializer, 36 | { 37 | let mut map = serializer.serialize_map(Some(1))?; 38 | map.serialize_entry(&self.key, &self.value)?; 39 | map.end() 40 | } 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::*; 46 | use crate::util::*; 47 | 48 | #[test] 49 | fn serializes_as_key_value_pair() { 50 | assert_serialize(KeyValuePair::new("key", "value"), json!({ "key": "value" })); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | //! Module containing helpers and util functions that are not specific to any DSL 2 | 3 | mod assert_serialize; 4 | mod join_with_pipe; 5 | mod key_value_pair; 6 | mod should_skip; 7 | 8 | #[cfg(test)] 9 | pub(crate) use self::assert_serialize::*; 10 | pub(crate) use self::join_with_pipe::*; 11 | pub(crate) use self::key_value_pair::*; 12 | pub(crate) use self::should_skip::*; 13 | -------------------------------------------------------------------------------- /src/util/should_skip.rs: -------------------------------------------------------------------------------- 1 | use crate::{Map, Set}; 2 | 3 | /// Trait to handle skippable queries or their values 4 | pub(crate) trait ShouldSkip { 5 | /// Whether a query or a query value can be skipped 6 | fn should_skip(&self) -> bool { 7 | false 8 | } 9 | 10 | /// Inverse of the condition 11 | fn should_keep(&self) -> bool { 12 | !self.should_skip() 13 | } 14 | } 15 | 16 | impl ShouldSkip for String { 17 | fn should_skip(&self) -> bool { 18 | self.trim().is_empty() 19 | } 20 | } 21 | 22 | impl ShouldSkip for str { 23 | fn should_skip(&self) -> bool { 24 | self.trim().is_empty() 25 | } 26 | } 27 | 28 | impl ShouldSkip for &str { 29 | fn should_skip(&self) -> bool { 30 | self.trim().is_empty() 31 | } 32 | } 33 | 34 | impl ShouldSkip for Option { 35 | fn should_skip(&self) -> bool { 36 | self.is_none() 37 | } 38 | } 39 | 40 | impl ShouldSkip for &Option { 41 | fn should_skip(&self) -> bool { 42 | self.is_none() 43 | } 44 | } 45 | 46 | impl ShouldSkip for Vec { 47 | fn should_skip(&self) -> bool { 48 | self.is_empty() 49 | } 50 | } 51 | 52 | impl ShouldSkip for Set { 53 | fn should_skip(&self) -> bool { 54 | self.is_empty() 55 | } 56 | } 57 | 58 | impl ShouldSkip for &Set { 59 | fn should_skip(&self) -> bool { 60 | self.is_empty() 61 | } 62 | } 63 | 64 | impl ShouldSkip for &[T] { 65 | fn should_skip(&self) -> bool { 66 | self.is_empty() 67 | } 68 | } 69 | 70 | impl ShouldSkip for Map { 71 | fn should_skip(&self) -> bool { 72 | self.is_empty() 73 | } 74 | } 75 | 76 | impl ShouldSkip for &Map { 77 | fn should_skip(&self) -> bool { 78 | self.is_empty() 79 | } 80 | } 81 | --------------------------------------------------------------------------------