From 95a771509556621aa8c3f8df40a3d95298b97e04 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Thu, 11 Feb 2016 20:11:42 +1100 Subject: [PATCH 1/2] Better generated documentation Process generated asciidoc files to add missing headers, titles, reorder sections Include usage examples for all search queries Include usage examples for all aggregation queries Confirm documentation structure with elastic docs generation process --- NuGet.config | 6 +- .../ChildrenAggregationMapping.doc.asciidoc | 16 - .../WritingAggregations.doc.asciidoc | 78 - .../ConnectionPooling.Doc.asciidoc | 161 -- .../BuildingBlocks/Transports.Doc.asciidoc | 39 - .../UnexpectedExceptions.doc.asciidoc | 114 - .../UnrecoverableExceptions.doc.asciidoc | 44 - .../Failover/FallingOver.doc.asciidoc | 80 - .../MaxRetries/RespectsMaxRetry.doc.asciidoc | 146 -- .../Pinging/FirstUsage.doc.asciidoc | 128 -- .../Pinging/Revival.doc.asciidoc | 48 - .../RespectsMaxRetryOverrides.doc.asciidoc | 68 - .../Sniffing/OnConnectionFailure.doc.asciidoc | 175 -- .../Sniffing/OnStaleClusterState.doc.asciidoc | 97 - .../Sniffing/OnStartup.doc.asciidoc | 119 - .../Sniffing/RoleDetection.doc.asciidoc | 121 - .../DocumentPaths/DocumentPaths.doc.asciidoc | 104 - .../Inferrence/FieldInference.doc.asciidoc | 429 ---- .../FieldNames/FieldInference.doc.asciidoc | 313 --- .../Inferrence/Id/IdsInference.doc.asciidoc | 79 - .../Inferrence/IdsInference.doc.asciidoc | 98 - .../Indices/IndicesPaths.doc.asciidoc | 34 - .../Inferrence/IndicesPaths.doc.asciidoc | 47 - .../Inferrence/PropertyInference.doc.asciidoc | 6 - .../HighLevel/Mapping/AutoMap.doc.asciidoc | 904 -------- .../LowLevel/Connecting.doc.asciidoc | 291 --- .../LowLevel/Lifetimes.doc.asciidoc | 38 - .../CodeStandards/Descriptors.doc.asciidoc | 52 - .../NamingConventions.doc.asciidoc | 128 -- .../CodeStandards/Queries.doc.asciidoc | 31 - .../DateMath/DateMathExpressions.doc.asciidoc | 97 - .../TimeUnit/TimeUnits.doc.asciidoc | 115 - .../QueryDsl/BoolDsl/BoolDsl.doc.asciidoc | 126 -- .../Geo/Distance/DistanceUnits.doc.asciidoc | 98 - docs/asciidoc/aggregations-usage.asciidoc | 92 + docs/asciidoc/aggregations.asciidoc | 104 + .../children-aggregation-mapping.asciidoc | 27 + .../children-aggregation-usage.asciidoc | 70 + .../date-histogram-aggregation-usage.asciidoc | 139 ++ .../date-range-aggregation-usage.asciidoc | 116 + .../filter/filter-aggregation-usage.asciidoc | 207 ++ .../filters-aggregation-usage.asciidoc | 349 +++ .../geo-distance-aggregation-usage.asciidoc | 89 + .../geo-hash-grid-aggregation-usage.asciidoc | 66 + .../global/global-aggregation-usage.asciidoc | 71 + .../histogram-aggregation-usage.asciidoc | 69 + .../ip-range-aggregation-usage.asciidoc | 77 + .../missing-aggregation-usage.asciidoc | 57 + .../nested/nested-aggregation-usage.asciidoc | 77 + .../range/range-aggregation-usage.asciidoc | 84 + .../reverse-nested-aggregation-usage.asciidoc | 111 + .../sampler-aggregation-usage.asciidoc | 78 + ...gnificant-terms-aggregation-usage.asciidoc | 74 + .../terms/terms-aggregation-usage.asciidoc | 111 + .../average-aggregation-usage.asciidoc | 77 + .../cardinality-aggregation-usage.asciidoc | 60 + .../extended-stats-aggregation-usage.asciidoc | 64 + .../geo-bounds-aggregation-usage.asciidoc | 72 + .../metric/max/max-aggregation-usage.asciidoc | 55 + .../metric/min/min-aggregation-usage.asciidoc | 55 + ...ercentile-ranks-aggregation-usage.asciidoc | 84 + .../percentiles-aggregation-usage.asciidoc | 85 + ...scripted-metric-aggregation-usage.asciidoc | 75 + .../stats/stats-aggregation-usage.asciidoc | 59 + .../metric/sum/sum-aggregation-usage.asciidoc | 55 + .../top-hits-aggregation-usage.asciidoc | 171 ++ .../value-count-aggregation-usage.asciidoc | 55 + .../average-bucket-aggregation-usage.asciidoc | 95 + .../bucket-script-aggregation-usage.asciidoc | 143 ++ ...bucket-selector-aggregation-usage.asciidoc | 105 + .../cumulative-sum-aggregation-usage.asciidoc | 91 + .../derivative-aggregation-usage.asciidoc | 91 + .../max-bucket-aggregation-usage.asciidoc | 93 + .../min-bucket-aggregation-usage.asciidoc | 93 + ...ng-average-ewma-aggregation-usage.asciidoc | 106 + ...age-holt-linear-aggregation-usage.asciidoc | 109 + ...ge-holt-winters-aggregation-usage.asciidoc | 117 + ...-average-linear-aggregation-usage.asciidoc | 102 + ...-average-simple-aggregation-usage.asciidoc | 105 + ...al-differencing-aggregation-usage.asciidoc | 96 + .../sum-bucket-aggregation-usage.asciidoc | 90 + .../writing-aggregations.asciidoc | 156 ++ .../analyzers/analyzer-usage.asciidoc | 77 + .../char-filters/char-filter-usage.asciidoc | 48 + .../token-filters/token-filter-usage.asciidoc | 240 ++ .../tokenizers/tokenizer-usage.asciidoc | 76 + .../{ClientConcepts/LowLevel => }/class.png | Bin docs/asciidoc/client-concepts.asciidoc | 4 + .../connection-pooling.asciidoc | 239 ++ .../date-time-providers.asciidoc} | 117 +- .../keeping-track-of-nodes.asciidoc} | 97 +- .../request-pipelines.asciidoc} | 202 +- .../building-blocks}/timeoutplot.png | Bin .../building-blocks/transports.asciidoc | 52 + .../exceptions/unexpected-exceptions.asciidoc | 125 ++ .../unrecoverable-exceptions.asciidoc | 188 ++ .../failover/falling-over.asciidoc | 97 + .../max-retries/respects-max-retry.asciidoc | 158 ++ .../pinging/first-usage.asciidoc | 119 + .../pinging/revival.asciidoc | 56 + .../disable-sniff-ping-per-request.asciidoc | 108 + .../request-timeouts-overrides.asciidoc | 96 + .../respects-allowed-status-code.asciidoc | 27 + .../respects-force-node.asciidoc | 28 + .../respects-max-retry-overrides.asciidoc | 76 + .../round-robin/round-robin.asciidoc} | 68 +- .../round-robin/skip-dead-nodes.asciidoc} | 256 ++- .../round-robin/volatile-updates.asciidoc} | 33 +- .../sniffing/on-connection-failure.asciidoc | 154 ++ .../sniffing/on-stale-cluster-state.asciidoc | 100 + .../sniffing/on-startup.asciidoc | 162 ++ .../sniffing/role-detection.asciidoc | 148 ++ .../sticky/skip-dead-nodes.asciidoc | 197 ++ .../connection-pooling/sticky/sticky.asciidoc | 36 + .../covariant-search-results.asciidoc} | 485 ++-- .../inference/document-paths.asciidoc} | 106 +- .../inference/features-inference.asciidoc | 35 + .../inference/field-inference.asciidoc | 510 +++++ .../inference/ids-inference.asciidoc | 139 ++ .../inference/index-name-inference.asciidoc | 107 + .../inference/indices-paths.asciidoc | 67 + .../inference/property-inference.asciidoc | 101 + .../high-level/mapping/auto-map.asciidoc | 1063 +++++++++ .../client-concepts/low-level/class.png | Bin 0 -> 66942 bytes .../low-level/connecting.asciidoc | 358 +++ .../low-level/lifetimes.asciidoc | 93 + .../low-level}/pipeline.png | Bin .../low-level/post-data.asciidoc} | 138 +- .../code-standards/descriptors.asciidoc | 61 + .../elastic-client.asciidoc} | 118 +- .../naming-conventions.asciidoc | 130 ++ docs/asciidoc/code-standards/queries.asciidoc | 63 + .../serialization/properties.asciidoc} | 12 +- docs/asciidoc/common-options.asciidoc | 24 + .../date-math/date-math-expressions.asciidoc | 133 ++ .../distance-unit/distance-units.asciidoc | 124 ++ .../time-unit/time-units.asciidoc | 249 +++ docs/asciidoc/connection-pooling.asciidoc | 64 + docs/asciidoc/hadouken-indentation.jpg | Bin 0 -> 43939 bytes docs/asciidoc/high-level.asciidoc | 66 + docs/asciidoc/index.asciidoc | 54 +- docs/asciidoc/intro.asciidoc | 62 + docs/asciidoc/low-level.asciidoc | 31 + docs/asciidoc/pipeline.png | Bin 0 -> 204561 bytes docs/asciidoc/query-dsl-usage.asciidoc | 134 ++ docs/asciidoc/query-dsl.asciidoc | 147 ++ .../query-dsl/bool-dsl/bool-dsl.asciidoc | 324 +++ .../bool-dsl/hadouken-indentation.jpg | Bin 0 -> 43939 bytes .../operators/and-operator-usage.asciidoc | 96 + .../operators/not-operator-usage.asciidoc | 103 + .../operators/or-operator-usage.asciidoc | 107 + .../unary-add-operator-usage.asciidoc | 114 + .../compound/and/and-query-usage.asciidoc | 62 + .../bool-dsl-complex-query-usage.asciidoc | 233 ++ .../compound/bool/bool-query-usage.asciidoc | 69 + .../boosting/boosting-query-usage.asciidoc | 59 + .../constant-score-query-usage.asciidoc | 49 + .../dismax/dismax-query-usage.asciidoc | 65 + .../filtered/filtered-query-usage.asciidoc | 56 + .../function-score-query-usage.asciidoc | 144 ++ .../indices-no-match-query-usage.asciidoc | 55 + .../indices/indices-query-usage.asciidoc | 60 + .../compound/limit/limit-query-usage.asciidoc | 45 + .../compound/not/not-query-usage.asciidoc | 62 + .../compound/or/or-query-usage.asciidoc | 62 + .../common-terms/common-terms-usage.asciidoc | 67 + .../match/match-phrase-prefix-usage.asciidoc | 83 + .../match/match-phrase-usage.asciidoc | 83 + .../full-text/match/match-usage.asciidoc | 82 + .../multi-match/multi-match-usage.asciidoc | 124 ++ .../query-string/query-string-usage.asciidoc | 120 + .../simple-query-string-usage.asciidoc | 75 + .../geo-bounding-box-query-usage.asciidoc | 75 + .../geo-distance-range-query-usage.asciidoc | 77 + .../geo-distance-query-usage.asciidoc | 68 + .../geo-hash-cell-query-usage.asciidoc | 56 + .../polygon/geo-polygon-query-usage.asciidoc | 67 + .../circle/geo-shape-circle-usage.asciidoc | 36 + .../envelope/geo-envelope-usage.asciidoc | 35 + .../geo-indexed-shape-usage.asciidoc | 63 + .../geo-line-string-usage.asciidoc | 35 + .../geo-multi-line-string-usage.asciidoc | 35 + .../geo-multi-point-usage.asciidoc | 35 + .../geo/shape/point/geo-point-usage.asciidoc | 35 + .../shape/polygon/geo-polygon-usage.asciidoc | 35 + .../has-child/has-child-query-usage.asciidoc | 63 + .../has-parent-query-usage.asciidoc | 58 + .../nested/nested-query-usage.asciidoc | 55 + .../raw/raw-combine-usage.asciidoc | 47 + .../raw/raw-query-usage.asciidoc | 34 + .../span-containing-query-usage.asciidoc | 64 + .../first/span-first-query-usage.asciidoc | 59 + .../span-multi-term-query-usage.asciidoc | 53 + .../span/near/span-near-query-usage.asciidoc | 85 + .../span/not/span-not-query-usage.asciidoc | 85 + .../span/or/span-or-query-usage.asciidoc | 76 + .../span/term/span-term-query-usage.asciidoc | 49 + .../within/span-within-query-usage.asciidoc | 64 + .../more-like-this-query-usage.asciidoc | 110 + .../script/script-query-usage.asciidoc | 55 + .../template/template-query-usage.asciidoc | 53 + .../exists/exists-query-usage.asciidoc | 45 + .../fuzzy/fuzzy-date-query-usage.asciidoc | 64 + .../fuzzy/fuzzy-numeric-query-usage.asciidoc | 64 + .../fuzzy/fuzzy-query-usage.asciidoc | 64 + .../term-level/ids/ids-query-usage.asciidoc | 56 + .../missing/missing-query-usage.asciidoc | 51 + .../prefix/prefix-query-usage.asciidoc | 52 + .../range/date-range-query-usage.asciidoc | 64 + .../range/numeric-range-query-usage.asciidoc | 58 + .../range/term-range-query-usage.asciidoc | 58 + .../regexp/regexp-query-usage.asciidoc | 55 + .../term-level/term/term-query-usage.asciidoc | 49 + .../terms/terms-list-query-usage.asciidoc | 148 ++ .../terms/terms-lookup-query-usage.asciidoc | 58 + .../terms/terms-query-usage.asciidoc | 79 + .../term-level/type/type-query-usage.asciidoc | 45 + .../wildcard/wildcard-query-usage.asciidoc | 52 + .../search/request/explain-usage.asciidoc | 34 + .../request/fielddata-fields-usage.asciidoc | 43 + .../search/request/fields-usage.asciidoc | 41 + .../request/from-and-size-usage.asciidoc | 38 + .../request/highlighting-usage.asciidoc | 193 ++ .../search/request/index-boost-usage.asciidoc | 45 + .../search/request/inner-hits-usage.asciidoc | 211 ++ .../search/request/min-score-usage.asciidoc | 50 + .../search/request/post-filter-usage.asciidoc | 37 + .../search/request/profile-usage.asciidoc | 50 + .../search/request/query-usage.asciidoc | 49 + .../request/script-fields-usage.asciidoc | 72 + .../search/request/sort-usage.asciidoc | 167 ++ .../request/source-filtering-usage.asciidoc | 119 + .../search/request/suggest-usage.asciidoc | 230 ++ .../search/suggesters/suggest-api.asciidoc | 164 ++ docs/asciidoc/timeoutplot.png | Bin 0 -> 10018 bytes .../AsciiDoc/GeneratedAsciidocVisitor.cs | 275 +++ .../AsciiDoc/RawAsciidocVisitor.cs | 99 + .../Documentation/Blocks/CodeBlock.cs | 39 +- .../Documentation/Blocks/CombinedBlock.cs | 2 +- .../Files/CSharpDocumentationFile.cs | 169 +- .../Documentation/Files/DocumentationFile.cs | 19 +- .../Files/ImageDocumentationFile.cs | 36 + .../Files/RawDocumentationFile.cs | 27 +- .../Nest.Litterateur/EnumerableExtensions.cs | 3 +- .../Nest.Litterateur/Language.cs | 8 + .../Nest.Litterateur/Linker/Linker.cs | 16 - src/CodeGeneration/Nest.Litterateur/LitUp.cs | 28 +- .../Nest.Litterateur/Program.cs | 17 +- .../Nest.Litterateur/StringExtensions.cs | 219 ++ .../Walkers/CodeWithDocumentationWalker.cs | 112 +- .../Walkers/DocumentationFileWalker.cs | 118 +- .../Nest.Litterateur/project.json | 99 +- src/Profiling/Profiling.csproj | 38 +- .../ChildrenAggregationMapping.doc.cs | 9 +- .../Children/ChildrenAggregationUsageTests.cs | 25 +- .../DateHistogramAggregationUsageTests.cs | 22 +- .../DateRangeAggregationUsageTests.cs | 30 +- .../Filter/FilterAggregationUsageTests.cs | 29 +- .../Filters/FiltersAggregationUsageTests.cs | 57 +- .../IpRange/IpRangeAggregationUsageTests.cs | 2 +- .../Range/RangeAggregationUsageTests.cs | 2 +- .../ReverseNestedAggregationUsageTests.cs | 2 +- .../SignificantTermsAggregationUsageTests.cs | 2 +- .../Terms/TermsAggregationUsageTests.cs | 2 +- .../Average/AverageAggregationUsageTests.cs | 5 +- .../CardinalityAggregationUsageTests.cs | 2 +- .../ExtendedStatsAggregationUsageTests.cs | 2 +- .../GeoBoundsAggregationUsageTests.cs | 2 +- .../Metric/Max/MaxAggregationUsageTests.cs | 2 +- .../Metric/Min/MinAggregationUsageTests.cs | 2 +- .../PercentileRanksAggregationUsageTests.cs | 2 +- .../PercentilesAggregationUsageTests.cs | 2 +- .../Stats/StatsAggregationUsageTests.cs | 2 +- .../Metric/Sum/SumAggregationUsageTests.cs | 2 +- .../TopHits/TopHitsAggregationUsageTests.cs | 6 +- .../ValueCountAggregationUsageTests.cs | 2 +- .../Aggregations/WritingAggregations.doc.cs | 240 +- src/Tests/Analysis/AnalysisCrudTests.cs | 6 +- .../BuildingBlocks/ConnectionPooling.Doc.cs | 90 +- .../BuildingBlocks/DateTimeProviders.Doc.cs | 11 +- .../BuildingBlocks/KeepingTrackOfNodes.Doc.cs | 36 +- .../BuildingBlocks/RequestPipelines.doc.cs | 97 +- .../BuildingBlocks/Transports.Doc.cs | 38 +- .../Exceptions/UnrecoverableExceptions.doc.cs | 21 +- .../Failover/FallingOver.doc.cs | 22 +- .../MaxRetries/RespectsMaxRetry.doc.cs | 41 +- .../Pinging/FirstUsage.doc.cs | 37 +- .../ConnectionPooling/Pinging/Revival.doc.cs | 10 +- ...t.cs => DisableSniffPingPerRequest.doc.cs} | 187 +- ...des.cs => RequestTimeoutsOverrides.doc.cs} | 135 +- ...de.cs => RespectsAllowedStatusCode.doc.cs} | 68 +- ...sForceNode.cs => RespectsForceNode.doc.cs} | 68 +- .../RespectsMaxRetryOverrides.doc.cs | 13 +- .../RoundRobin/RoundRobin.doc.cs | 24 +- .../RoundRobin/SkipDeadNodes.doc.cs | 39 +- .../RoundRobin/VolatileUpdates.doc.cs | 7 +- .../Sniffing/OnConnectionFailure.doc.cs | 15 +- .../Sniffing/OnStaleClusterState.doc.cs | 5 +- .../Sniffing/OnStartup.doc.cs | 14 +- .../Sniffing/RoleDetection.doc.cs | 8 +- .../Sticky/SkipDeadNodes.doc.cs | 1 - .../ConnectionPooling/Sticky/Sticky.doc.cs | 20 +- .../CovariantSearchResults.doc.cs | 95 +- .../DocumentPaths.doc.cs | 41 +- .../Inference/FeaturesInference.doc.cs | 38 + .../FieldInference.doc.cs | 232 +- .../HighLevel/Inference/IdsInference.doc.cs | 125 ++ .../Inference/IndexNameInference.doc.cs | 108 + .../IndicesPaths.doc.cs | 32 +- .../Inference/PropertyInference.doc.cs | 94 + .../HighLevel/Inferrence/FeaturesInference.cs | 36 - .../HighLevel/Inferrence/IdsInference.doc.cs | 88 - .../Inferrence/IndexNameInference.doc.cs | 52 - .../Inferrence/PropertyInference.doc.cs | 70 - .../HighLevel/Mapping/AutoMap.doc.cs | 1954 +++++++++-------- .../ClientConcepts/LowLevel/Connecting.doc.cs | 218 +- .../ClientConcepts/LowLevel/Lifetimes.doc.cs | 95 +- .../ClientConcepts/LowLevel/PostData.doc.cs | 66 +- .../CodeStandards/NamingConventions.doc.cs | 13 +- .../Serialization/Properties.doc.cs | 3 - .../DateMath/DateMathExpressions.doc.cs | 68 +- .../DistanceUnit/DistanceUnits.doc.cs | 104 + .../CommonOptions/TimeUnit/TimeUnits.doc.cs | 44 +- .../Document/Multiple/Bulk/BulkApiTests.cs | 54 +- .../Multiple/Bulk/BulkInvalidApiTests.cs | 4 +- .../DeleteByQuery/DeleteByQueryApiTests.cs | 15 +- .../Document/Single/Update/UpdateApiTests.cs | 2 +- src/Tests/QueryDsl/BoolDsl/BoolDsl.doc.cs | 348 +-- .../QueryDsl/BoolDsl/hadouken-indentation.jpg | Bin 0 -> 43939 bytes .../Geo/Distance/DistanceUnits.doc.cs | 103 - .../Span/Not/SpanNotQueryUsageTests.cs | 16 +- .../Template/TemplateQueryUsageTests.cs | 2 - .../TermLevel/Terms/TermsQueryUsageTests.cs | 8 + .../Search/Request/InnerHitsUsageTests.cs | 4 +- ...ggestApiTest.cs => SuggestApiTests.doc.cs} | 48 +- src/Tests/Tests.csproj | 36 +- src/Tests/aggregations-usage.asciidoc | 6 + src/Tests/aggregations.asciidoc | 14 + src/Tests/client-concepts.asciidoc | 8 + src/Tests/common-options.asciidoc | 24 + src/Tests/connection-pooling.asciidoc | 61 + src/Tests/high-level.asciidoc | 60 + src/Tests/index.asciidoc | 53 +- src/Tests/intro.asciidoc | 58 + src/Tests/low-level.asciidoc | 28 + src/Tests/query-dsl-usage.asciidoc | 6 + src/Tests/query-dsl.asciidoc | 17 + src/global.json | 14 +- 348 files changed, 23160 insertions(+), 7976 deletions(-) delete mode 100644 docs/asciidoc/Aggregations/Bucket/Children/ChildrenAggregationMapping.doc.asciidoc delete mode 100644 docs/asciidoc/Aggregations/WritingAggregations.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/ConnectionPooling.Doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/Transports.Doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/Exceptions/UnexpectedExceptions.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/Exceptions/UnrecoverableExceptions.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/Failover/FallingOver.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/MaxRetries/RespectsMaxRetry.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/Pinging/FirstUsage.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/Pinging/Revival.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsMaxRetryOverrides.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/OnConnectionFailure.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/OnStaleClusterState.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/OnStartup.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/RoleDetection.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/HighLevel/Inferrence/DocumentPaths/DocumentPaths.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/HighLevel/Inferrence/FieldInference.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/HighLevel/Inferrence/FieldNames/FieldInference.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/HighLevel/Inferrence/Id/IdsInference.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/HighLevel/Inferrence/IdsInference.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/HighLevel/Inferrence/Indices/IndicesPaths.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/HighLevel/Inferrence/IndicesPaths.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/HighLevel/Inferrence/PropertyInference.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/HighLevel/Mapping/AutoMap.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/LowLevel/Connecting.doc.asciidoc delete mode 100644 docs/asciidoc/ClientConcepts/LowLevel/Lifetimes.doc.asciidoc delete mode 100644 docs/asciidoc/CodeStandards/Descriptors.doc.asciidoc delete mode 100644 docs/asciidoc/CodeStandards/NamingConventions.doc.asciidoc delete mode 100644 docs/asciidoc/CodeStandards/Queries.doc.asciidoc delete mode 100644 docs/asciidoc/CommonOptions/DateMath/DateMathExpressions.doc.asciidoc delete mode 100644 docs/asciidoc/CommonOptions/TimeUnit/TimeUnits.doc.asciidoc delete mode 100644 docs/asciidoc/QueryDsl/BoolDsl/BoolDsl.doc.asciidoc delete mode 100644 docs/asciidoc/QueryDsl/Geo/Distance/DistanceUnits.doc.asciidoc create mode 100644 docs/asciidoc/aggregations-usage.asciidoc create mode 100644 docs/asciidoc/aggregations.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/children/children-aggregation-mapping.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/children/children-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/date-histogram/date-histogram-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/date-range/date-range-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/filter/filter-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/filters/filters-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/geo-distance/geo-distance-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/geo-hash-grid/geo-hash-grid-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/global/global-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/histogram/histogram-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/ip-range/ip-range-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/missing/missing-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/nested/nested-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/range/range-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/reverse-nested/reverse-nested-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/sampler/sampler-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/significant-terms/significant-terms-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/bucket/terms/terms-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/average/average-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/cardinality/cardinality-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/extended-stats/extended-stats-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/geo-bounds/geo-bounds-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/max/max-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/min/min-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/percentile-ranks/percentile-ranks-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/percentiles/percentiles-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/scripted-metric/scripted-metric-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/stats/stats-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/sum/sum-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/top-hits/top-hits-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/metric/value-count/value-count-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/average-bucket/average-bucket-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/bucket-script/bucket-script-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/bucket-selector/bucket-selector-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/cumulative-sum/cumulative-sum-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/derivative/derivative-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/max-bucket/max-bucket-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/min-bucket/min-bucket-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/moving-average/moving-average-ewma-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/moving-average/moving-average-holt-linear-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/moving-average/moving-average-holt-winters-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/moving-average/moving-average-linear-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/moving-average/moving-average-simple-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/serial-differencing/serial-differencing-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/pipeline/sum-bucket/sum-bucket-aggregation-usage.asciidoc create mode 100644 docs/asciidoc/aggregations/writing-aggregations.asciidoc create mode 100644 docs/asciidoc/analysis/analyzers/analyzer-usage.asciidoc create mode 100644 docs/asciidoc/analysis/char-filters/char-filter-usage.asciidoc create mode 100644 docs/asciidoc/analysis/token-filters/token-filter-usage.asciidoc create mode 100644 docs/asciidoc/analysis/tokenizers/tokenizer-usage.asciidoc rename docs/asciidoc/{ClientConcepts/LowLevel => }/class.png (100%) create mode 100644 docs/asciidoc/client-concepts.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/building-blocks/connection-pooling.asciidoc rename docs/asciidoc/{ClientConcepts/ConnectionPooling/BuildingBlocks/DateTimeProviders.Doc.asciidoc => client-concepts/connection-pooling/building-blocks/date-time-providers.asciidoc} (54%) rename docs/asciidoc/{ClientConcepts/ConnectionPooling/BuildingBlocks/KeepingTrackOfNodes.Doc.asciidoc => client-concepts/connection-pooling/building-blocks/keeping-track-of-nodes.asciidoc} (66%) rename docs/asciidoc/{ClientConcepts/ConnectionPooling/BuildingBlocks/RequestPipelines.doc.asciidoc => client-concepts/connection-pooling/building-blocks/request-pipelines.asciidoc} (56%) rename docs/asciidoc/{ClientConcepts/ConnectionPooling/BuildingBlocks => client-concepts/connection-pooling/building-blocks}/timeoutplot.png (100%) create mode 100644 docs/asciidoc/client-concepts/connection-pooling/building-blocks/transports.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/exceptions/unexpected-exceptions.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/exceptions/unrecoverable-exceptions.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/failover/falling-over.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/max-retries/respects-max-retry.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/pinging/first-usage.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/pinging/revival.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/request-overrides/disable-sniff-ping-per-request.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/request-overrides/request-timeouts-overrides.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/request-overrides/respects-allowed-status-code.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/request-overrides/respects-force-node.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/request-overrides/respects-max-retry-overrides.asciidoc rename docs/asciidoc/{ClientConcepts/ConnectionPooling/RoundRobin/RoundRobin.doc.asciidoc => client-concepts/connection-pooling/round-robin/round-robin.asciidoc} (65%) rename docs/asciidoc/{ClientConcepts/ConnectionPooling/RoundRobin/SkipDeadNodes.doc.asciidoc => client-concepts/connection-pooling/round-robin/skip-dead-nodes.asciidoc} (51%) rename docs/asciidoc/{ClientConcepts/ConnectionPooling/RoundRobin/VolatileUpdates.doc.asciidoc => client-concepts/connection-pooling/round-robin/volatile-updates.asciidoc} (64%) create mode 100644 docs/asciidoc/client-concepts/connection-pooling/sniffing/on-connection-failure.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/sniffing/on-stale-cluster-state.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/sniffing/on-startup.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/sniffing/role-detection.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/sticky/skip-dead-nodes.asciidoc create mode 100644 docs/asciidoc/client-concepts/connection-pooling/sticky/sticky.asciidoc rename docs/asciidoc/{ClientConcepts/HighLevel/CovariantHits/CovariantSearchResults.doc.asciidoc => client-concepts/high-level/covariant-hits/covariant-search-results.asciidoc} (64%) rename docs/asciidoc/{ClientConcepts/HighLevel/Inferrence/DocumentPaths.doc.asciidoc => client-concepts/high-level/inference/document-paths.asciidoc} (61%) create mode 100644 docs/asciidoc/client-concepts/high-level/inference/features-inference.asciidoc create mode 100644 docs/asciidoc/client-concepts/high-level/inference/field-inference.asciidoc create mode 100644 docs/asciidoc/client-concepts/high-level/inference/ids-inference.asciidoc create mode 100644 docs/asciidoc/client-concepts/high-level/inference/index-name-inference.asciidoc create mode 100644 docs/asciidoc/client-concepts/high-level/inference/indices-paths.asciidoc create mode 100644 docs/asciidoc/client-concepts/high-level/inference/property-inference.asciidoc create mode 100644 docs/asciidoc/client-concepts/high-level/mapping/auto-map.asciidoc create mode 100644 docs/asciidoc/client-concepts/low-level/class.png create mode 100644 docs/asciidoc/client-concepts/low-level/connecting.asciidoc create mode 100644 docs/asciidoc/client-concepts/low-level/lifetimes.asciidoc rename docs/asciidoc/{ClientConcepts/LowLevel => client-concepts/low-level}/pipeline.png (100%) rename docs/asciidoc/{ClientConcepts/LowLevel/PostData.doc.asciidoc => client-concepts/low-level/post-data.asciidoc} (50%) create mode 100644 docs/asciidoc/code-standards/descriptors.asciidoc rename docs/asciidoc/{CodeStandards/ElasticClient.doc.asciidoc => code-standards/elastic-client.asciidoc} (54%) create mode 100644 docs/asciidoc/code-standards/naming-conventions.asciidoc create mode 100644 docs/asciidoc/code-standards/queries.asciidoc rename docs/asciidoc/{CodeStandards/Serialization/Properties.doc.asciidoc => code-standards/serialization/properties.asciidoc} (76%) create mode 100644 docs/asciidoc/common-options.asciidoc create mode 100644 docs/asciidoc/common-options/date-math/date-math-expressions.asciidoc create mode 100644 docs/asciidoc/common-options/distance-unit/distance-units.asciidoc create mode 100644 docs/asciidoc/common-options/time-unit/time-units.asciidoc create mode 100644 docs/asciidoc/connection-pooling.asciidoc create mode 100644 docs/asciidoc/hadouken-indentation.jpg create mode 100644 docs/asciidoc/high-level.asciidoc create mode 100644 docs/asciidoc/intro.asciidoc create mode 100644 docs/asciidoc/low-level.asciidoc create mode 100644 docs/asciidoc/pipeline.png create mode 100644 docs/asciidoc/query-dsl-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl.asciidoc create mode 100644 docs/asciidoc/query-dsl/bool-dsl/bool-dsl.asciidoc create mode 100644 docs/asciidoc/query-dsl/bool-dsl/hadouken-indentation.jpg create mode 100644 docs/asciidoc/query-dsl/bool-dsl/operators/and-operator-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/bool-dsl/operators/not-operator-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/bool-dsl/operators/or-operator-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/bool-dsl/operators/unary-add-operator-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/and/and-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/bool/bool-dsl-complex-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/bool/bool-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/boosting/boosting-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/constant-score/constant-score-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/dismax/dismax-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/filtered/filtered-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/function-score/function-score-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/indices/indices-no-match-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/indices/indices-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/limit/limit-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/not/not-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/compound/or/or-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/full-text/common-terms/common-terms-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/full-text/match/match-phrase-prefix-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/full-text/match/match-phrase-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/full-text/match/match-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/full-text/multi-match/multi-match-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/full-text/query-string/query-string-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/full-text/simple-query-string/simple-query-string-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/bounding-box/geo-bounding-box-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/distance-range/geo-distance-range-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/distance/geo-distance-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/hash-cell/geo-hash-cell-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/polygon/geo-polygon-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/shape/circle/geo-shape-circle-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/shape/envelope/geo-envelope-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/shape/indexed-shape/geo-indexed-shape-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/shape/line-string/geo-line-string-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/shape/multi-line-string/geo-multi-line-string-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/shape/multi-point/geo-multi-point-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/shape/point/geo-point-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/geo/shape/polygon/geo-polygon-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/joining/has-child/has-child-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/joining/has-parent/has-parent-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/joining/nested/nested-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/nest-specific/raw/raw-combine-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/nest-specific/raw/raw-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/span/container/span-containing-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/span/first/span-first-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/span/multi-term/span-multi-term-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/span/near/span-near-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/span/not/span-not-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/span/or/span-or-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/span/term/span-term-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/span/within/span-within-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/specialized/more-like-this/more-like-this-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/specialized/script/script-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/specialized/template/template-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/exists/exists-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-date-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-numeric-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/ids/ids-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/missing/missing-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/prefix/prefix-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/range/date-range-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/range/numeric-range-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/range/term-range-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/regexp/regexp-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/term/term-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/terms/terms-list-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/terms/terms-lookup-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/terms/terms-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/type/type-query-usage.asciidoc create mode 100644 docs/asciidoc/query-dsl/term-level/wildcard/wildcard-query-usage.asciidoc create mode 100644 docs/asciidoc/search/request/explain-usage.asciidoc create mode 100644 docs/asciidoc/search/request/fielddata-fields-usage.asciidoc create mode 100644 docs/asciidoc/search/request/fields-usage.asciidoc create mode 100644 docs/asciidoc/search/request/from-and-size-usage.asciidoc create mode 100644 docs/asciidoc/search/request/highlighting-usage.asciidoc create mode 100644 docs/asciidoc/search/request/index-boost-usage.asciidoc create mode 100644 docs/asciidoc/search/request/inner-hits-usage.asciidoc create mode 100644 docs/asciidoc/search/request/min-score-usage.asciidoc create mode 100644 docs/asciidoc/search/request/post-filter-usage.asciidoc create mode 100644 docs/asciidoc/search/request/profile-usage.asciidoc create mode 100644 docs/asciidoc/search/request/query-usage.asciidoc create mode 100644 docs/asciidoc/search/request/script-fields-usage.asciidoc create mode 100644 docs/asciidoc/search/request/sort-usage.asciidoc create mode 100644 docs/asciidoc/search/request/source-filtering-usage.asciidoc create mode 100644 docs/asciidoc/search/request/suggest-usage.asciidoc create mode 100644 docs/asciidoc/search/suggesters/suggest-api.asciidoc create mode 100644 docs/asciidoc/timeoutplot.png create mode 100644 src/CodeGeneration/Nest.Litterateur/AsciiDoc/GeneratedAsciidocVisitor.cs create mode 100644 src/CodeGeneration/Nest.Litterateur/AsciiDoc/RawAsciidocVisitor.cs create mode 100644 src/CodeGeneration/Nest.Litterateur/Documentation/Files/ImageDocumentationFile.cs create mode 100644 src/CodeGeneration/Nest.Litterateur/Language.cs delete mode 100644 src/CodeGeneration/Nest.Litterateur/Linker/Linker.cs create mode 100644 src/CodeGeneration/Nest.Litterateur/StringExtensions.cs rename src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/{DisableSniffPingPerRequest.cs => DisableSniffPingPerRequest.doc.cs} (59%) rename src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/{RequestTimeoutsOverrides.cs => RequestTimeoutsOverrides.doc.cs} (95%) rename src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/{RespectsAllowedStatusCode.cs => RespectsAllowedStatusCode.doc.cs} (91%) rename src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/{RespectsForceNode.cs => RespectsForceNode.doc.cs} (93%) rename src/Tests/ClientConcepts/HighLevel/{Inferrence => Inference}/DocumentPaths.doc.cs (64%) create mode 100644 src/Tests/ClientConcepts/HighLevel/Inference/FeaturesInference.doc.cs rename src/Tests/ClientConcepts/HighLevel/{Inferrence => Inference}/FieldInference.doc.cs (54%) create mode 100644 src/Tests/ClientConcepts/HighLevel/Inference/IdsInference.doc.cs create mode 100644 src/Tests/ClientConcepts/HighLevel/Inference/IndexNameInference.doc.cs rename src/Tests/ClientConcepts/HighLevel/{Inferrence => Inference}/IndicesPaths.doc.cs (61%) create mode 100644 src/Tests/ClientConcepts/HighLevel/Inference/PropertyInference.doc.cs delete mode 100644 src/Tests/ClientConcepts/HighLevel/Inferrence/FeaturesInference.cs delete mode 100644 src/Tests/ClientConcepts/HighLevel/Inferrence/IdsInference.doc.cs delete mode 100644 src/Tests/ClientConcepts/HighLevel/Inferrence/IndexNameInference.doc.cs delete mode 100644 src/Tests/ClientConcepts/HighLevel/Inferrence/PropertyInference.doc.cs create mode 100644 src/Tests/CommonOptions/DistanceUnit/DistanceUnits.doc.cs create mode 100644 src/Tests/QueryDsl/BoolDsl/hadouken-indentation.jpg delete mode 100644 src/Tests/QueryDsl/Geo/Distance/DistanceUnits.doc.cs rename src/Tests/Search/Suggesters/{SuggestApiTest.cs => SuggestApiTests.doc.cs} (94%) create mode 100644 src/Tests/aggregations-usage.asciidoc create mode 100644 src/Tests/aggregations.asciidoc create mode 100644 src/Tests/client-concepts.asciidoc create mode 100644 src/Tests/common-options.asciidoc create mode 100644 src/Tests/connection-pooling.asciidoc create mode 100644 src/Tests/high-level.asciidoc create mode 100644 src/Tests/intro.asciidoc create mode 100644 src/Tests/low-level.asciidoc create mode 100644 src/Tests/query-dsl-usage.asciidoc create mode 100644 src/Tests/query-dsl.asciidoc diff --git a/NuGet.config b/NuGet.config index 3b6333eeb72..f951f89147f 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,7 +1,7 @@ - + - - + + \ No newline at end of file diff --git a/docs/asciidoc/Aggregations/Bucket/Children/ChildrenAggregationMapping.doc.asciidoc b/docs/asciidoc/Aggregations/Bucket/Children/ChildrenAggregationMapping.doc.asciidoc deleted file mode 100644 index e03f8eb564a..00000000000 --- a/docs/asciidoc/Aggregations/Bucket/Children/ChildrenAggregationMapping.doc.asciidoc +++ /dev/null @@ -1,16 +0,0 @@ -To use the child aggregation you have to make sure -a `_parent` mapping is in place, here we create the project -index with two mapped types, `project` and `commitactivity` and -we add a `_parent` mapping from `commitactivity` to `parent` - -[source, csharp] ----- -var createProjectIndex = TestClient.GetClient().CreateIndex(typeof(Project), c => c - .Mappings(map=>map - .Map(m=>m.AutoMap()) - .Map(m=>m - .Parent() - ) - ) -); ----- diff --git a/docs/asciidoc/Aggregations/WritingAggregations.doc.asciidoc b/docs/asciidoc/Aggregations/WritingAggregations.doc.asciidoc deleted file mode 100644 index 7f2a8739c7d..00000000000 --- a/docs/asciidoc/Aggregations/WritingAggregations.doc.asciidoc +++ /dev/null @@ -1,78 +0,0 @@ -Aggregations are arguably one of the most powerful features of Elasticsearch. -NEST allows you to write your aggregations using a strict fluent dsl, a verbatim object initializer -syntax that maps verbatim to the elasticsearch API -a more terse object initializer aggregation DSL. - -Three different ways, yikes thats a lot to take in! Lets go over them one by one and explain when you might -want to use which one. - -The fluent lambda syntax is the most terse way to write aggregations. -It benefits from types that are carried over to sub aggregations - -[source, csharp] ----- -s => s -.Aggregations(aggs => aggs - .Children("name_of_child_agg", child => child - .Aggregations(childAggs => childAggs - .Average("average_per_child", avg => avg.Field(p => p.ConfidenceFactor)) - .Max("max_per_child", avg => avg.Field(p => p.ConfidenceFactor)) - ) - ) -) ----- -The object initializer syntax (OIS) is a one-to-one mapping with how aggregations -have to be represented in the Elasticsearch API. While it has the benefit of being a one-to-one -mapping, being dictionary based in C# means it can grow exponentially in complexity rather quickly. - -[source, csharp] ----- -new SearchRequest -{ - Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(CommitActivity)) - { - Aggregations = - new AverageAggregation("average_per_child", "confidenceFactor") - && new MaxAggregation("max_per_child", "confidenceFactor") - } -} ----- -For this reason the OIS syntax can be shortened dramatically by using `*Agg` related family, -These allow you to forego introducing intermediary Dictionaries to represent the aggregation DSL. -It also allows you to combine multiple aggregations using bitwise AND (` -`) operator. - -Compare the following example with the previous vanilla OIS syntax - -[source, csharp] ----- -new SearchRequest -{ - Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(CommitActivity)) - { - Aggregations = - new AverageAggregation("average_per_child", Field(p => p.ConfidenceFactor)) - && new MaxAggregation("max_per_child", Field(p => p.ConfidenceFactor)) - } -} ----- -An advanced scenario may involve an existing collection of aggregation functions that should be set as aggregations -on the request. Using LINQ's `.Aggregate()` method, each function can be applied to the aggregation descriptor -(`childAggs` below) in turn, returning the descriptor after each function application. - -[source, csharp] ----- -var aggregations = new List, IAggregationContainer>> -{ - a => a.Average("average_per_child", avg => avg.Field(p => p.ConfidenceFactor)), - a => a.Max("max_per_child", avg => avg.Field(p => p.ConfidenceFactor)) -}; -return s => s - .Aggregations(aggs => aggs - .Children("name_of_child_agg", child => child - .Aggregations(childAggs => - aggregations.Aggregate(childAggs, (acc, agg) => { agg(acc); return acc; }) - ) - ) - ); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/ConnectionPooling.Doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/ConnectionPooling.Doc.asciidoc deleted file mode 100644 index 8d4ec9d1de7..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/ConnectionPooling.Doc.asciidoc +++ /dev/null @@ -1,161 +0,0 @@ -= Connection Pooling -Connection pooling is the internal mechanism that takes care of registering what nodes there are in the cluster and which -we can use to issue client calls on. - - -== SingleNodeConnectionPool -The simplest of all connection pools, this takes a single `Uri` and uses that to connect to elasticsearch for all the calls -It doesn't opt in to sniffing and pinging behavior, and will never mark nodes dead or alive. The one `Uri` it holds is always -ready to go. - - -[source, csharp] ----- -var uri = new Uri("http://localhost:9201"); -var pool = new SingleNodeConnectionPool(uri); -pool.Nodes.Should().HaveCount(1); -var node = pool.Nodes.First(); -node.Uri.Port.Should().Be(9201); ----- -This type of pool is hardwired to opt out of sniffing - -[source, csharp] ----- -pool.SupportsReseeding.Should().BeFalse(); ----- -and pinging - -[source, csharp] ----- -pool.SupportsPinging.Should().BeFalse(); ----- -When you use the low ceremony ElasticClient constructor that takes a single Uri, -We default to this SingleNodeConnectionPool - -[source, csharp] ----- -var client = new ElasticClient(uri); ----- -[source, csharp] ----- -client.ConnectionSettings.ConnectionPool.Should().BeOfType(); ----- -However we urge that you always pass your connection settings explicitly - -[source, csharp] ----- -client = new ElasticClient(new ConnectionSettings(uri)); ----- -[source, csharp] ----- -client.ConnectionSettings.ConnectionPool.Should().BeOfType(); ----- -or even better pass the connection pool explicitly - -[source, csharp] ----- -client = new ElasticClient(new ConnectionSettings(pool)); ----- -[source, csharp] ----- -client.ConnectionSettings.ConnectionPool.Should().BeOfType(); ----- -== StaticConnectionPool -The static connection pool is great if you have a known small sized cluster and do no want to enable -sniffing to find out the cluster topology. - - -[source, csharp] ----- -var uris = Enumerable.Range(9200, 5).Select(p => new Uri("http://localhost:" + p)); ----- -a connection pool can be seeded using an enumerable of `Uri`s - -[source, csharp] ----- -var pool = new StaticConnectionPool(uris); ----- -Or using an enumerable of `Node` - -[source, csharp] ----- -var nodes = uris.Select(u=>new Node(u)); ----- -[source, csharp] ----- -pool = new StaticConnectionPool(nodes); ----- -This type of pool is hardwired to opt out of sniffing - -[source, csharp] ----- -pool.SupportsReseeding.Should().BeFalse(); ----- -but supports pinging when enabled - -[source, csharp] ----- -pool.SupportsPinging.Should().BeTrue(); ----- -To create a client using this static connection pool pass -the connection pool to the connectionsettings you pass to ElasticClient - -[source, csharp] ----- -var client = new ElasticClient(new ConnectionSettings(pool)); ----- -[source, csharp] ----- -client.ConnectionSettings.ConnectionPool.Should().BeOfType(); ----- -== SniffingConnectionPool -A subclass of StaticConnectionPool that allows itself to be reseeded at run time. -It comes with a very minor overhead of a `ReaderWriterLockSlim` to ensure thread safety. - - -[source, csharp] ----- -var uris = Enumerable.Range(9200, 5).Select(p => new Uri("http://localhost:" + p)); ----- -a connection pool can be seeded using an enumerable of `Uri` - -[source, csharp] ----- -var pool = new SniffingConnectionPool(uris); ----- -Or using an enumerable of `Node` -A major benefit here is you can include known node roles when seeding -NEST can use this information to favour sniffing on master eligible nodes first -and take master only nodes out of rotation for issuing client calls on. - -[source, csharp] ----- -var nodes = uris.Select(u=>new Node(u)); ----- -[source, csharp] ----- -pool = new SniffingConnectionPool(nodes); ----- -This type of pool is hardwired to opt in to sniffing - -[source, csharp] ----- -pool.SupportsReseeding.Should().BeTrue(); ----- -and pinging - -[source, csharp] ----- -pool.SupportsPinging.Should().BeTrue(); ----- -To create a client using the sniffing connection pool pass -the connection pool to the connectionsettings you pass to ElasticClient - -[source, csharp] ----- -var client = new ElasticClient(new ConnectionSettings(pool)); ----- -[source, csharp] ----- -client.ConnectionSettings.ConnectionPool.Should().BeOfType(); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/Transports.Doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/Transports.Doc.asciidoc deleted file mode 100644 index 359ea30aefc..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/Transports.Doc.asciidoc +++ /dev/null @@ -1,39 +0,0 @@ -= Transports - -The `ITransport` interface can be seen as the motor block of the client. It's interface is deceitfully simple. -It's ultimately responsible from translating a client call to a response. If for some reason you do not agree with the way we wrote -the internals of the client, by implementing a custom `ITransport`, you can circumvent all of it and introduce your own. - - - -Transport is generically typed to a type that implements IConnectionConfigurationValues -This is the minimum ITransport needs to report back for the client to function. -e.g in the low level client, transport is instantiated like this: - -[source, csharp] ----- -var lowLevelTransport = new Transport(new ConnectionConfiguration()); ----- -In the high level client like this: - -[source, csharp] ----- -var highlevelTransport = new Transport(new ConnectionSettings()); ----- -[source, csharp] ----- -var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); -var inMemoryTransport = new Transport(new ConnectionSettings(connectionPool, new InMemoryConnection())); ----- -The only two methods on `ITransport` are `Request()` and `RequestAsync()`, the default `ITransport` implementation is responsible for introducing -many of the building blocks in the client, if these do not work for you can swap them out for your own custom `ITransport` implementation. -If you feel this need, please let us know as we'd love to learn why you've go down this route! - -[source, csharp] ----- -var response = inMemoryTransport.Request>(HttpMethod.GET, "/_search", new { query = new { match_all = new { } } }); ----- -[source, csharp] ----- -response = await inMemoryTransport.RequestAsync>(HttpMethod.GET, "/_search", new { query = new { match_all = new { } } }); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/Exceptions/UnexpectedExceptions.doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/Exceptions/UnexpectedExceptions.doc.asciidoc deleted file mode 100644 index 1dc333afec6..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/Exceptions/UnexpectedExceptions.doc.asciidoc +++ /dev/null @@ -1,114 +0,0 @@ -== Unexpected exceptions -When a client call throws an exception that the IConnction can not handle, this exception will bubble -out the client as an UnexpectedElasticsearchClientException, regardless whether the client is configured to throw or not. -An IConnection is in charge of knowning what exceptions it can recover from or not. The default IConnection that is based on WebRequest can and -will recover from WebExceptions but others will be grounds for immediately exiting the pipeline. - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.SucceedAlways()) - .ClientCalls(r => r.OnPort(9201).FailAlways(new Exception("boom!"))) - .StaticConnectionPool() - .Settings(s => s.DisablePing()) - ); -audit = await audit.TraceCall( - new ClientCall { - { AuditEvent.HealthyResponse, 9200 }, - } - ); -audit = await audit.TraceUnexpectedException( - new ClientCall { - { AuditEvent.BadResponse, 9201 }, - }, - (e) => - { - e.FailureReason.Should().Be(PipelineFailure.Unexpected); - e.InnerException.Should().NotBeNull(); - e.InnerException.Message.Should().Be("boom!"); - } - ); -e.FailureReason.Should().Be(PipelineFailure.Unexpected); -e.InnerException.Should().NotBeNull(); -e.InnerException.Message.Should().Be("boom!"); ----- - -Sometimes an unexpected exception happens further down in the pipeline, this is why we -wrap them inside an UnexpectedElasticsearchClientException so that information about where -in the pipeline the unexpected exception is not lost, here a call to 9200 fails using a webexception. -It then falls over to 9201 which throws an hard exception from within IConnection. We assert that we -can still see the audit trail for the whole coordinated request. - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) -#if DOTNETCORE - .ClientCalls(r => r.OnPort(9200).FailAlways(new System.Net.Http.HttpRequestException("recover"))) -#else - .ClientCalls(r => r.OnPort(9200).FailAlways(new WebException("recover"))) -#endif - .ClientCalls(r => r.OnPort(9201).FailAlways(new Exception("boom!"))) - .StaticConnectionPool() - .Settings(s => s.DisablePing()) - ); -audit = await audit.TraceUnexpectedException( - new ClientCall { - { AuditEvent.BadResponse, 9200 }, - { AuditEvent.BadResponse, 9201 }, - }, - (e) => - { - e.FailureReason.Should().Be(PipelineFailure.Unexpected); - e.InnerException.Should().NotBeNull(); - e.InnerException.Message.Should().Be("boom!"); - } - ); -e.FailureReason.Should().Be(PipelineFailure.Unexpected); -e.InnerException.Should().NotBeNull(); -e.InnerException.Message.Should().Be("boom!"); ----- - -An unexpected hard exception on ping and sniff is something we *do* try to revover from and failover. -Here pinging nodes on first use is enabled and 9200 throws on ping, we still fallover to 9201's ping succeeds. -However the client call on 9201 throws a hard exception we can not recover from - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .Ping(r => r.OnPort(9200).FailAlways(new Exception("ping exception"))) - .Ping(r => r.OnPort(9201).SucceedAlways()) - .ClientCalls(r => r.OnPort(9201).FailAlways(new Exception("boom!"))) - .StaticConnectionPool() - .AllDefaults() - ); ----- -[source, csharp] ----- -audit = await audit.TraceUnexpectedException( - new ClientCall { - { AuditEvent.PingFailure, 9200 }, - { AuditEvent.PingSuccess, 9201 }, - { AuditEvent.BadResponse, 9201 }, - }, - (e) => - { - e.FailureReason.Should().Be(PipelineFailure.Unexpected); -e.InnerException.Should().NotBeNull(); - e.InnerException.Message.Should().Be("boom!"); -e.SeenExceptions.Should().NotBeEmpty(); - var pipelineException = e.SeenExceptions.First(); - pipelineException.FailureReason.Should().Be(PipelineFailure.PingFailure); - pipelineException.InnerException.Message.Should().Be("ping exception"); -var pingException = e.AuditTrail.First(a => a.Event == AuditEvent.PingFailure).Exception; - pingException.Should().NotBeNull(); - pingException.Message.Should().Be("ping exception"); - - } -); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/Exceptions/UnrecoverableExceptions.doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/Exceptions/UnrecoverableExceptions.doc.asciidoc deleted file mode 100644 index 7a2c0e52b85..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/Exceptions/UnrecoverableExceptions.doc.asciidoc +++ /dev/null @@ -1,44 +0,0 @@ -== Unrecoverable exceptions -Unrecoverable exceptions are excepted exceptions that are grounds to exit the client pipeline immediately. -By default the client won't throw on any ElasticsearchClientException but return an invalid response. -You can configure the client to throw using ThrowExceptions() on ConnectionSettings. The following test -both a client that throws and one that returns an invalid response with an `.OriginalException` exposed - - -[source, csharp] ----- -var recoverablExceptions = new[] - { - new PipelineException(PipelineFailure.BadResponse), - new PipelineException(PipelineFailure.PingFailure), - }; -recoverablExceptions.Should().OnlyContain(e => e.Recoverable); -var unrecoverableExceptions = new[] - { - new PipelineException(PipelineFailure.CouldNotStartSniffOnStartup), - new PipelineException(PipelineFailure.SniffFailure), - new PipelineException(PipelineFailure.Unexpected), - new PipelineException(PipelineFailure.BadAuthentication), - new PipelineException(PipelineFailure.MaxRetriesReached), - new PipelineException(PipelineFailure.MaxTimeoutReached) - }; -unrecoverableExceptions.Should().OnlyContain(e => !e.Recoverable); -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .Ping(r => r.SucceedAlways()) - .ClientCalls(r => r.FailAlways(401)) - .StaticConnectionPool() - .AllDefaults() - ); -audit = await audit.TraceElasticsearchException( - new ClientCall { - { AuditEvent.PingSuccess, 9200 }, - { AuditEvent.BadResponse, 9200 }, - }, - (e) => - { - e.FailureReason.Should().Be(PipelineFailure.BadAuthentication); - } - ); -e.FailureReason.Should().Be(PipelineFailure.BadAuthentication); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/Failover/FallingOver.doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/Failover/FallingOver.doc.asciidoc deleted file mode 100644 index f83117ff4d0..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/Failover/FallingOver.doc.asciidoc +++ /dev/null @@ -1,80 +0,0 @@ -== Fail over -When using connection pooling and the pool has sufficient nodes a request will be retried if -the call to a node throws an exception or returns a 502 or 503 - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways()) - .ClientCalls(r => r.OnPort(9201).SucceedAlways()) - .StaticConnectionPool() - .Settings(s => s.DisablePing()) - ); -audit = await audit.TraceCall( - new ClientCall { - { BadResponse, 9200 }, - { HealthyResponse, 9201 }, - } - ); ----- -502 Bad Gateway -Will be treated as an error that requires retrying - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways(502)) - .ClientCalls(r => r.OnPort(9201).SucceedAlways()) - .StaticConnectionPool() - .Settings(s => s.DisablePing()) - ); -audit = await audit.TraceCall( - new ClientCall { - { BadResponse, 9200 }, - { HealthyResponse, 9201 }, - } - ); ----- -503 Service Unavailable -Will be treated as an error that requires retrying - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways(503)) - .ClientCalls(r => r.OnPort(9201).SucceedAlways()) - .StaticConnectionPool() - .Settings(s => s.DisablePing()) - ); -audit = await audit.TraceCall( - new ClientCall { - { BadResponse, 9200 }, - { HealthyResponse, 9201 }, - } - ); ----- - -If a call returns a valid http status code other then 502/503 the request won't be retried. - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways(418)) - .ClientCalls(r => r.OnPort(9201).SucceedAlways()) - .StaticConnectionPool() - .Settings(s => s.DisablePing()) - ); -audit = await audit.TraceCall( - new ClientCall { - { BadResponse, 9200 }, - } - ); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/MaxRetries/RespectsMaxRetry.doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/MaxRetries/RespectsMaxRetry.doc.asciidoc deleted file mode 100644 index 2aa8b032712..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/MaxRetries/RespectsMaxRetry.doc.asciidoc +++ /dev/null @@ -1,146 +0,0 @@ -== MaxRetries -By default retry as many times as we have nodes. However retries still respect the request timeout. -Meaning if you have a 100 node cluster and a request timeout of 20 seconds we will retry as many times as we can -but give up after 20 seconds - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways()) - .ClientCalls(r => r.OnPort(9209).SucceedAlways()) - .StaticConnectionPool() - .Settings(s => s.DisablePing()) - ); -audit = await audit.TraceCall( - new ClientCall { - { BadResponse, 9200 }, - { BadResponse, 9201 }, - { BadResponse, 9202 }, - { BadResponse, 9203 }, - { BadResponse, 9204 }, - { BadResponse, 9205 }, - { BadResponse, 9206 }, - { BadResponse, 9207 }, - { BadResponse, 9208 }, - { HealthyResponse, 9209 } - } - ); ----- - -When you have a 100 node cluster you might want to ensure a fixed number of retries. -Remember that the actual number of requests is initial attempt + set number of retries - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways()) - .ClientCalls(r => r.OnPort(9209).SucceedAlways()) - .StaticConnectionPool() - .Settings(s => s.DisablePing().MaximumRetries(3)) - ); -audit = await audit.TraceCall( - new ClientCall { - { BadResponse, 9200 }, - { BadResponse, 9201 }, - { BadResponse, 9202 }, - { BadResponse, 9203 }, - { MaxRetriesReached } - } - ); ----- - -In our previous test we simulated very fast failures, in the real world a call might take upwards of a second -Here we simulate a particular heavy search that takes 10 seconds to fail, our Request timeout is set to 20 seconds. -In this case it does not make sense to retry our 10 second query on 10 nodes. We should try it twice and give up before a third call is attempted - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways().Takes(TimeSpan.FromSeconds(10))) - .ClientCalls(r => r.OnPort(9209).SucceedAlways()) - .StaticConnectionPool() - .Settings(s => s.DisablePing().RequestTimeout(TimeSpan.FromSeconds(20))) - ); -audit = await audit.TraceCall( - new ClientCall { - { BadResponse, 9200 }, - { BadResponse, 9201 }, - { MaxTimeoutReached } - } - ); ----- - -If you set smaller request time outs you might not want it to also affect the retry timeout, therefor you can configure these separately too. -Here we simulate calls taking 3 seconds, a request time out of 2 and an overall retry timeout of 10 seconds. -We should see 5 attempts to perform this query, testing that our request timeout cuts the query off short and that our max retry timeout of 10 -wins over the configured request timeout - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways().Takes(TimeSpan.FromSeconds(3))) - .ClientCalls(r => r.OnPort(9209).FailAlways()) - .StaticConnectionPool() - .Settings(s => s.DisablePing().RequestTimeout(TimeSpan.FromSeconds(2)).MaxRetryTimeout(TimeSpan.FromSeconds(10))) - ); -audit = await audit.TraceCall( - new ClientCall { - { BadResponse, 9200 }, - { BadResponse, 9201 }, - { BadResponse, 9202 }, - { BadResponse, 9203 }, - { BadResponse, 9204 }, - { MaxTimeoutReached } - } - ); ----- - -If your retry policy expands beyond available nodes we won't retry the same node twice - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(2) - .ClientCalls(r => r.FailAlways().Takes(TimeSpan.FromSeconds(3))) - .ClientCalls(r => r.OnPort(9209).SucceedAlways()) - .StaticConnectionPool() - .Settings(s => s.DisablePing().RequestTimeout(TimeSpan.FromSeconds(2)).MaxRetryTimeout(TimeSpan.FromSeconds(10))) - ); -audit = await audit.TraceCall( - new ClientCall { - { BadResponse, 9200 }, - { BadResponse, 9201 }, - { MaxRetriesReached } - } - ); ----- - -This makes setting any retry setting on a single node connection pool a NOOP, this is by design! -Connection pooling and connection failover is about trying to fail sanely whilst still utilizing available resources and -not giving up on the fail fast principle. It's *NOT* a mechanism for forcing requests to succeed. - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways().Takes(TimeSpan.FromSeconds(3))) - .ClientCalls(r => r.OnPort(9209).SucceedAlways()) - .SingleNodeConnection() - .Settings(s => s.DisablePing().MaximumRetries(10)) - ); -audit = await audit.TraceCall( - new ClientCall { - { BadResponse, 9200 } - } - ); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/Pinging/FirstUsage.doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/Pinging/FirstUsage.doc.asciidoc deleted file mode 100644 index 6648607066a..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/Pinging/FirstUsage.doc.asciidoc +++ /dev/null @@ -1,128 +0,0 @@ -== Pinging - -Pinging is enabled by default for the Static & Sniffing connection pool. -This means that the first time a node is used or resurrected we issue a ping with a smaller (configurable) timeout. -This allows us to fail and fallover to a healthy node faster - - -A cluster with 2 nodes where the second node fails on ping - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(2) - .Ping(p => p.Succeeds(Always)) - .Ping(p => p.OnPort(9201).FailAlways()) - .StaticConnectionPool() - .AllDefaults() -); ----- -[source, csharp] ----- -await audit.TraceCalls( ----- -The first call goes to 9200 which succeeds - -[source, csharp] ----- -new ClientCall { - { PingSuccess, 9200}, - { HealthyResponse, 9200}, - { pool => - { - pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(0); - } } - }, ----- -The 2nd call does a ping on 9201 because its used for the first time. -It fails so we wrap over to node 9200 which we've already pinged - -[source, csharp] ----- -new ClientCall { - { PingFailure, 9201}, - { HealthyResponse, 9200}, ----- -Finally we assert that the connectionpool has one node that is marked as dead - -[source, csharp] ----- -{ pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(1) } - } -); ----- -A cluster with 4 nodes where the second and third pings fail - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(4) - .Ping(p => p.SucceedAlways()) - .Ping(p => p.OnPort(9201).FailAlways()) - .Ping(p => p.OnPort(9202).FailAlways()) - .StaticConnectionPool() - .AllDefaults() -); ----- -[source, csharp] ----- -await audit.TraceCalls( ----- -The first call goes to 9200 which succeeds - -[source, csharp] ----- -new ClientCall { - { PingSuccess, 9200}, - { HealthyResponse, 9200}, - { pool => - { - pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(0); - } } - }, ----- -The 2nd call does a ping on 9201 because its used for the first time. -It fails and so we ping 9202 which also fails. We then ping 9203 becuase -we haven't used it before and it succeeds - -[source, csharp] ----- -new ClientCall { - { PingFailure, 9201}, - { PingFailure, 9202}, - { PingSuccess, 9203}, - { HealthyResponse, 9203}, ----- -Finally we assert that the connectionpool has two nodes that are marked as dead - -[source, csharp] ----- -{ pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } - } -); ----- -A healthy cluster of 4 (min master nodes of 3 of course!) - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(4) - .Ping(p => p.SucceedAlways()) - .StaticConnectionPool() - .AllDefaults() -); ----- -[source, csharp] ----- -await audit.TraceCalls( - new ClientCall { { PingSuccess, 9200}, { HealthyResponse, 9200} }, - new ClientCall { { PingSuccess, 9201}, { HealthyResponse, 9201} }, - new ClientCall { { PingSuccess, 9202}, { HealthyResponse, 9202} }, - new ClientCall { { PingSuccess, 9203}, { HealthyResponse, 9203} }, - new ClientCall { { HealthyResponse, 9200} }, - new ClientCall { { HealthyResponse, 9201} }, - new ClientCall { { HealthyResponse, 9202} }, - new ClientCall { { HealthyResponse, 9203} }, - new ClientCall { { HealthyResponse, 9200} } - ); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/Pinging/Revival.doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/Pinging/Revival.doc.asciidoc deleted file mode 100644 index 89c4e99284a..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/Pinging/Revival.doc.asciidoc +++ /dev/null @@ -1,48 +0,0 @@ -== Pinging - -When a node is marked dead it will only be put in the dog house for a certain amount of time. Once it comes out of the dog house, or revived, we schedule a ping -before the actual call to make sure its up and running. If its still down we put it back in the dog house a little longer. For an explanation on these timeouts see: TODO LINK - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(3) - .ClientCalls(r => r.SucceedAlways()) - .ClientCalls(r => r.OnPort(9202).Fails(Once)) - .Ping(p => p.SucceedAlways()) - .StaticConnectionPool() - .AllDefaults() - ); -audit = await audit.TraceCalls( - new ClientCall { { PingSuccess, 9200 }, { HealthyResponse, 9200 } }, - new ClientCall { { PingSuccess, 9201 }, { HealthyResponse, 9201 } }, - new ClientCall { - { PingSuccess, 9202}, - { BadResponse, 9202}, - { HealthyResponse, 9200}, - { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(1) } - }, - new ClientCall { { HealthyResponse, 9201 } }, - new ClientCall { { HealthyResponse, 9200 } }, - new ClientCall { { HealthyResponse, 9201 } }, - new ClientCall { - { HealthyResponse, 9200 }, - { pool => pool.Nodes.First(n=>!n.IsAlive).DeadUntil.Should().BeAfter(DateTime.UtcNow) } - } - ); -audit = await audit.TraceCalls( - new ClientCall { { HealthyResponse, 9201 } }, - new ClientCall { { HealthyResponse, 9200 } }, - new ClientCall { { HealthyResponse, 9201 } } - ); -audit.ChangeTime(d => d.AddMinutes(20)); -audit = await audit.TraceCalls( - new ClientCall { { HealthyResponse, 9201 } }, - new ClientCall { - { Resurrection, 9202 }, - { PingSuccess, 9202 }, - { HealthyResponse, 9202 } - } - ); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsMaxRetryOverrides.doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsMaxRetryOverrides.doc.asciidoc deleted file mode 100644 index ce2f5afac55..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsMaxRetryOverrides.doc.asciidoc +++ /dev/null @@ -1,68 +0,0 @@ -== MaxRetries -By default retry as many times as we have nodes. However retries still respect the request timeout. -Meaning if you have a 100 node cluster and a request timeout of 20 seconds we will retry as many times as we can -but give up after 20 seconds - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways()) - .ClientCalls(r => r.OnPort(9209).SucceedAlways()) - .StaticConnectionPool() - .Settings(s => s.DisablePing()) - ); -audit = await audit.TraceCall( - new ClientCall(r => r.MaxRetries(2)) { - { BadResponse, 9200 }, - { BadResponse, 9201 }, - { BadResponse, 9202 }, - { MaxRetriesReached } - } - ); ----- - -When you have a 100 node cluster you might want to ensure a fixed number of retries. -Remember that the actual number of requests is initial attempt + set number of retries - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways()) - .ClientCalls(r => r.OnPort(9209).SucceedAlways()) - .StaticConnectionPool() - .Settings(s => s.DisablePing().MaximumRetries(5)) - ); -audit = await audit.TraceCall( - new ClientCall(r => r.MaxRetries(2)) { - { BadResponse, 9200 }, - { BadResponse, 9201 }, - { BadResponse, 9202 }, - { MaxRetriesReached } - } - ); ----- - -This makes setting any retry setting on a single node connection pool a NOOP, this is by design! -Connection pooling and connection failover is about trying to fail sanely whilst still utilizing available resources and -not giving up on the fail fast principle. It's *NOT* a mechanism for forcing requests to succeed. - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways().Takes(TimeSpan.FromSeconds(3))) - .ClientCalls(r => r.OnPort(9209).SucceedAlways()) - .SingleNodeConnection() - .Settings(s => s.DisablePing().MaximumRetries(10)) - ); -audit = await audit.TraceCall( - new ClientCall(r => r.MaxRetries(10)) { - { BadResponse, 9200 } - } - ); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/OnConnectionFailure.doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/OnConnectionFailure.doc.asciidoc deleted file mode 100644 index ef2b68cd4cc..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/OnConnectionFailure.doc.asciidoc +++ /dev/null @@ -1,175 +0,0 @@ -== Sniffing on connection failure -Sniffing on connection is enabled by default when using a connection pool that allows reseeding. -The only IConnectionPool we ship that allows this is the SniffingConnectionPool. - -This can be very handy to force a refresh of the pools known healthy node by inspecting elasticsearch itself. -A sniff tries to get the nodes by asking each currently known node until one response. - - -Here we seed our connection with 5 known nodes 9200-9204 of which we think -9202, 9203, 9204 are master eligible nodes. Our virtualized cluster will throw once when doing -a search on 9201. This should a sniff to be kicked off. - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(5) - .MasterEligible(9202, 9203, 9204) - .ClientCalls(r => r.SucceedAlways()) - .ClientCalls(r => r.OnPort(9201).Fails(Once)) ----- -When the call fails on 9201 the sniff succeeds and returns a new cluster of healty nodes -this cluster only has 3 nodes and the known masters are 9200 and 9202 but a search on 9201 -still fails once - -[source, csharp] ----- -.Sniff(p => p.SucceedAlways(Framework.Cluster - .Nodes(3) - .MasterEligible(9200, 9202) - .ClientCalls(r => r.OnPort(9201).Fails(Once)) ----- -After this second failure on 9201 another sniff will be returned a cluster that no -longer fails but looks completely different (9210-9212) we should be able to handle this - -[source, csharp] ----- -.Sniff(s => s.SucceedAlways(Framework.Cluster - .Nodes(3, 9210) - .MasterEligible(9210, 9212) - .ClientCalls(r => r.SucceedAlways()) - .Sniff(r => r.SucceedAlways()) - )) - )) - .SniffingConnectionPool() - .Settings(s => s.DisablePing().SniffOnStartup(false)) -); ----- -[source, csharp] ----- -audit = await audit.TraceCalls( ----- - - -[source, csharp] ----- -new ClientCall { - { HealthyResponse, 9200 }, - { pool => pool.Nodes.Count.Should().Be(5) } - }, - new ClientCall { - { BadResponse, 9201}, ----- -We assert we do a sniff on our first known master node 9202 - -[source, csharp] ----- -{ SniffOnFail }, - { SniffSuccess, 9202}, - { HealthyResponse, 9200}, ----- -Our pool should now have three nodes - -[source, csharp] ----- -{ pool => pool.Nodes.Count.Should().Be(3) } - }, - new ClientCall { - { BadResponse, 9201}, ----- -We assert we do a sniff on the first master node in our updated cluster - -[source, csharp] ----- -{ SniffOnFail }, - { SniffSuccess, 9200}, - { HealthyResponse, 9210}, - { pool => pool.Nodes.Count.Should().Be(3) } - }, - new ClientCall { { HealthyResponse, 9211 } }, - new ClientCall { { HealthyResponse, 9212 } }, - new ClientCall { { HealthyResponse, 9210 } }, - new ClientCall { { HealthyResponse, 9211 } }, - new ClientCall { { HealthyResponse, 9212 } }, - new ClientCall { { HealthyResponse, 9210 } }, - new ClientCall { { HealthyResponse, 9211 } }, - new ClientCall { { HealthyResponse, 9212 } }, - new ClientCall { { HealthyResponse, 9210 } } -); ----- -Here we set up our cluster exactly the same as the previous setup -Only we enable pinging (default is true) and make the ping fail - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(5) - .MasterEligible(9202, 9203, 9204) - .Ping(r => r.OnPort(9201).Fails(Once)) - .Sniff(p => p.SucceedAlways(Framework.Cluster - .Nodes(3) - .MasterEligible(9200, 9202) - .Ping(r => r.OnPort(9201).Fails(Once)) - .Sniff(s => s.SucceedAlways(Framework.Cluster - .Nodes(3, 9210) - .MasterEligible(9210, 9211) - .Ping(r => r.SucceedAlways()) - .Sniff(r => r.SucceedAlways()) - )) - )) - .SniffingConnectionPool() - .Settings(s => s.SniffOnStartup(false)) -); ----- -[source, csharp] ----- -audit = await audit.TraceCalls( - new ClientCall { - { PingSuccess, 9200 }, - { HealthyResponse, 9200 }, - { pool => pool.Nodes.Count.Should().Be(5) } - }, - new ClientCall { - { PingFailure, 9201}, ----- -We assert we do a sniff on our first known master node 9202 - -[source, csharp] ----- -{ SniffOnFail }, - { SniffSuccess, 9202}, - { PingSuccess, 9200}, - { HealthyResponse, 9200}, ----- -Our pool should now have three nodes - -[source, csharp] ----- -{ pool => pool.Nodes.Count.Should().Be(3) } - }, - new ClientCall { - { PingFailure, 9201}, ----- -We assert we do a sniff on the first master node in our updated cluster - -[source, csharp] ----- -{ SniffOnFail }, - { SniffSuccess, 9200}, - { PingSuccess, 9210}, - { HealthyResponse, 9210}, - { pool => pool.Nodes.Count.Should().Be(3) } - }, - new ClientCall { { PingSuccess, 9211 }, { HealthyResponse, 9211 } }, - new ClientCall { { PingSuccess, 9212 }, { HealthyResponse, 9212 } }, ----- -9210 was already pinged after the sniff returned the new nodes - -[source, csharp] ----- -new ClientCall { { HealthyResponse, 9210 } }, - new ClientCall { { HealthyResponse, 9211 } }, - new ClientCall { { HealthyResponse, 9212 } }, - new ClientCall { { HealthyResponse, 9210 } } -); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/OnStaleClusterState.doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/OnStaleClusterState.doc.asciidoc deleted file mode 100644 index d20b01abde4..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/OnStaleClusterState.doc.asciidoc +++ /dev/null @@ -1,97 +0,0 @@ -== Sniffing periodically - -Connection pools that return true for `SupportsReseeding` can be configured to sniff periodically. -In addition to sniffing on startup and sniffing on failures, sniffing periodically can benefit scenerio's where -clusters are often scaled horizontally during peak hours. An application might have a healthy view of a subset of the nodes -but without sniffing periodically it will never find the nodes that have been added to help out with load - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .MasterEligible(9202, 9203, 9204) - .ClientCalls(r => r.SucceedAlways()) - .Sniff(s => s.SucceedAlways(Framework.Cluster - .Nodes(100) - .MasterEligible(9202, 9203, 9204) - .ClientCalls(r => r.SucceedAlways()) - .Sniff(ss => ss.SucceedAlways(Framework.Cluster - .Nodes(10) - .MasterEligible(9202, 9203, 9204) - .ClientCalls(r => r.SucceedAlways()) - )) - )) - .SniffingConnectionPool() - .Settings(s => s - .DisablePing() - .SniffOnConnectionFault(false) - .SniffOnStartup(false) - .SniffLifeSpan(TimeSpan.FromMinutes(30)) - ) - ); ----- -healty cluster all nodes return healthy responses - -[source, csharp] ----- -audit = await audit.TraceCalls( - new ClientCall { { HealthyResponse, 9200 } }, - new ClientCall { { HealthyResponse, 9201 } }, - new ClientCall { { HealthyResponse, 9202 } }, - new ClientCall { { HealthyResponse, 9203 } }, - new ClientCall { { HealthyResponse, 9204 } }, - new ClientCall { { HealthyResponse, 9205 } }, - new ClientCall { { HealthyResponse, 9206 } }, - new ClientCall { { HealthyResponse, 9207 } }, - new ClientCall { { HealthyResponse, 9208 } }, - new ClientCall { { HealthyResponse, 9209 } }, - new ClientCall { - { HealthyResponse, 9200 }, - { pool => pool.Nodes.Count.Should().Be(10) } - } -); ----- -Now let's forward the clock 31 minutes, our sniff lifespan should now go state -and the first call should do a sniff which discovered we scaled up to a 100 nodes! - -[source, csharp] ----- -audit.ChangeTime(d => d.AddMinutes(31)); ----- -[source, csharp] ----- -audit = await audit.TraceCalls( - new ClientCall { ----- -a sniff is done first and it prefers the first node master node - -[source, csharp] ----- -{ SniffOnStaleCluster }, - { SniffSuccess, 9202 }, - { HealthyResponse, 9201 }, - { pool => pool.Nodes.Count.Should().Be(100) } - } -); ----- -[source, csharp] ----- -audit.ChangeTime(d => d.AddMinutes(31)); ----- -[source, csharp] ----- -audit = await audit.TraceCalls( - new ClientCall { ----- -a sniff is done first and it prefers the first node master node - -[source, csharp] ----- -{ SniffOnStaleCluster }, - { SniffSuccess, 9202 }, - { HealthyResponse, 9200 }, - { pool => pool.Nodes.Count.Should().Be(10) } - } -); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/OnStartup.doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/OnStartup.doc.asciidoc deleted file mode 100644 index 1f27d68c313..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/OnStartup.doc.asciidoc +++ /dev/null @@ -1,119 +0,0 @@ -== Sniffing on startup - -Connection pools that return true for `SupportsReseeding` by default sniff on startup. - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .Sniff(s => s.Fails(Always)) - .Sniff(s => s.OnPort(9202).Succeeds(Always)) - .SniffingConnectionPool() - .AllDefaults() - ); -await audit.TraceCall(new ClientCall - { - { SniffOnStartup}, - { SniffFailure, 9200}, - { SniffFailure, 9201}, - { SniffSuccess, 9202}, - { PingSuccess , 9200}, - { HealthyResponse, 9200} - }); -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .Sniff(s => s.Fails(Always)) - .Sniff(s => s.OnPort(9202).Succeeds(Always)) - .SniffingConnectionPool() - .AllDefaults() - ); -await audit.TraceCalls( - new ClientCall - { - { SniffOnStartup}, - { SniffFailure, 9200}, - { SniffFailure, 9201}, - { SniffSuccess, 9202}, - { PingSuccess , 9200}, - { HealthyResponse, 9200} - }, - new ClientCall - { - { PingSuccess, 9201}, - { HealthyResponse, 9201} - } - ); -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .Sniff(s => s.Fails(Always)) - .Sniff(s => s.OnPort(9202).Succeeds(Always, Framework.Cluster.Nodes(8, startFrom: 9204))) - .SniffingConnectionPool() - .AllDefaults() - ); -await audit.TraceCall(new ClientCall { - { SniffOnStartup}, - { SniffFailure, 9200}, - { SniffFailure, 9201}, - { SniffSuccess, 9202}, - { PingSuccess, 9204}, - { HealthyResponse, 9204} - }); -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .Sniff(s => s.Fails(Always)) - .Sniff(s => s.OnPort(9209).Succeeds(Always)) - .SniffingConnectionPool() - .AllDefaults() - ); -await audit.TraceCall(new ClientCall { - { SniffOnStartup}, - { SniffFailure, 9200}, - { SniffFailure, 9201}, - { SniffFailure, 9202}, - { SniffFailure, 9203}, - { SniffFailure, 9204}, - { SniffFailure, 9205}, - { SniffFailure, 9206}, - { SniffFailure, 9207}, - { SniffFailure, 9208}, - { SniffSuccess, 9209}, - { PingSuccess, 9200}, - { HealthyResponse, 9200} - }); -var audit = new Auditor(() => Framework.Cluster - .Nodes(new[] { - new Node(new Uri("http://localhost:9200")) { MasterEligible = false }, - new Node(new Uri("http://localhost:9201")) { MasterEligible = false }, - new Node(new Uri("http://localhost:9202")) { MasterEligible = true }, - }) - .Sniff(s => s.Succeeds(Always)) - .SniffingConnectionPool() - .AllDefaults() - ); -await audit.TraceCall(new ClientCall { - { SniffOnStartup}, - { SniffSuccess, 9202}, - { PingSuccess, 9200}, - { HealthyResponse, 9200} - }); -var audit = new Auditor(() => Framework.Cluster - .Nodes(new[] { - new Node(new Uri("http://localhost:9200")) { MasterEligible = true }, - new Node(new Uri("http://localhost:9201")) { MasterEligible = true }, - new Node(new Uri("http://localhost:9202")) { MasterEligible = false }, - }) - .Sniff(s => s.Fails(Always)) - .Sniff(s => s.OnPort(9202).Succeeds(Always)) - .SniffingConnectionPool() - .AllDefaults() - ); -await audit.TraceCall(new ClientCall { - { SniffOnStartup}, - { SniffFailure, 9200}, - { SniffFailure, 9201}, - { SniffSuccess, 9202}, - { PingSuccess, 9200}, - { HealthyResponse, 9200} - }); ----- diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/RoleDetection.doc.asciidoc b/docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/RoleDetection.doc.asciidoc deleted file mode 100644 index 576d9500d74..00000000000 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/Sniffing/RoleDetection.doc.asciidoc +++ /dev/null @@ -1,121 +0,0 @@ -== Sniffing role detection - -When we sniff the custer state we detect the role of the node whether its master eligible and holds data -We use this information when selecting a node to perform an API call on. - - -[source, csharp] ----- -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .Sniff(s => s.Fails(Always)) - .Sniff(s => s.OnPort(9202) - .Succeeds(Always, Framework.Cluster.Nodes(8).MasterEligible(9200, 9201, 9202)) - ) - .SniffingConnectionPool() - .AllDefaults() - ) - { - AssertPoolBeforeCall = (pool) => - { - pool.Should().NotBeNull(); - pool.Nodes.Should().HaveCount(10); - pool.Nodes.Where(n => n.MasterEligible).Should().HaveCount(10); - }, - AssertPoolAfterCall = (pool) => - { - pool.Should().NotBeNull(); - pool.Nodes.Should().HaveCount(8); - pool.Nodes.Where(n => n.MasterEligible).Should().HaveCount(3); - } - }; -pool.Should().NotBeNull(); -pool.Nodes.Should().HaveCount(10); -pool.Nodes.Where(n => n.MasterEligible).Should().HaveCount(10); -pool.Should().NotBeNull(); -pool.Nodes.Should().HaveCount(8); -pool.Nodes.Where(n => n.MasterEligible).Should().HaveCount(3); -await audit.TraceStartup(); -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .Sniff(s => s.Fails(Always)) - .Sniff(s => s.OnPort(9202) - .Succeeds(Always, Framework.Cluster.Nodes(8).StoresNoData(9200, 9201, 9202)) - ) - .SniffingConnectionPool() - .AllDefaults() - ) - { - AssertPoolBeforeCall = (pool) => - { - pool.Should().NotBeNull(); - pool.Nodes.Should().HaveCount(10); - pool.Nodes.Where(n => n.HoldsData).Should().HaveCount(10); - }, - - AssertPoolAfterCall = (pool) => - { - pool.Should().NotBeNull(); - pool.Nodes.Should().HaveCount(8); - pool.Nodes.Where(n => n.HoldsData).Should().HaveCount(5); - } - }; -pool.Should().NotBeNull(); -pool.Nodes.Should().HaveCount(10); -pool.Nodes.Where(n => n.HoldsData).Should().HaveCount(10); -pool.Should().NotBeNull(); -pool.Nodes.Should().HaveCount(8); -pool.Nodes.Where(n => n.HoldsData).Should().HaveCount(5); -await audit.TraceStartup(); -var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .Sniff(s => s.SucceedAlways() - .Succeeds(Always, Framework.Cluster.Nodes(8).StoresNoData(9200, 9201, 9202).SniffShouldReturnFqdn()) - ) - .SniffingConnectionPool() - .AllDefaults() - ) - { - AssertPoolBeforeCall = (pool) => - { - pool.Should().NotBeNull(); - pool.Nodes.Should().HaveCount(10); - pool.Nodes.Where(n => n.HoldsData).Should().HaveCount(10); - pool.Nodes.Should().OnlyContain(n => n.Uri.Host == "localhost"); - }, - - AssertPoolAfterCall = (pool) => - { - pool.Should().NotBeNull(); - pool.Nodes.Should().HaveCount(8); - pool.Nodes.Where(n => n.HoldsData).Should().HaveCount(5); - pool.Nodes.Should().OnlyContain(n => n.Uri.Host.StartsWith("fqdn") && !n.Uri.Host.Contains("/")); - } - }; -pool.Should().NotBeNull(); -pool.Nodes.Should().HaveCount(10); -pool.Nodes.Where(n => n.HoldsData).Should().HaveCount(10); -pool.Nodes.Should().OnlyContain(n => n.Uri.Host == "localhost"); -pool.Should().NotBeNull(); -pool.Nodes.Should().HaveCount(8); -pool.Nodes.Where(n => n.HoldsData).Should().HaveCount(5); -pool.Nodes.Should().OnlyContain(n => n.Uri.Host.StartsWith("fqdn") && !n.Uri.Host.Contains("/")); -await audit.TraceStartup(); -var node = SniffAndReturnNode(); -node.MasterEligible.Should().BeTrue(); -node.HoldsData.Should().BeFalse(); -node = await SniffAndReturnNodeAsync(); -node.MasterEligible.Should().BeTrue(); -node.HoldsData.Should().BeFalse(); -var pipeline = CreatePipeline(); -pipeline.Sniff(); -var pipeline = CreatePipeline(); -await pipeline.SniffAsync(); -this._settings = - this._cluster.Client(u => new SniffingConnectionPool(new[] {u}), c => c.PrettyJson()).ConnectionSettings; -var pipeline = new RequestPipeline(this._settings, DateTimeProvider.Default, new MemoryStreamFactory(), - new SearchRequestParameters()); -var nodes = this._settings.ConnectionPool.Nodes; -nodes.Should().NotBeEmpty().And.HaveCount(1); -var node = nodes.First(); ----- diff --git a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/DocumentPaths/DocumentPaths.doc.asciidoc b/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/DocumentPaths/DocumentPaths.doc.asciidoc deleted file mode 100644 index 22a2862b8ef..00000000000 --- a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/DocumentPaths/DocumentPaths.doc.asciidoc +++ /dev/null @@ -1,104 +0,0 @@ -# DocumentPaths -Many API's in elasticsearch describe a path to a document. In NEST besides generating a constructor that takes -and Index, Type and Id seperately we also generate a constructor taking a DocumentPath that allows you to describe the path -to your document more succintly - -Manually newing - -here we create a new document path based on Project with the id 1 - -[source, csharp] ----- -IDocumentPath path = new DocumentPath(1); ----- -[source, csharp] ----- -Expect("project").WhenSerializing(path.Index); -Expect("project").WhenSerializing(path.Type); -Expect(1).WhenSerializing(path.Id); ----- -You can still override the inferred index and type name - -[source, csharp] ----- -path = new DocumentPath(1).Type("project1"); ----- -[source, csharp] ----- -Expect("project1").WhenSerializing(path.Type); -path = new DocumentPath(1).Index("project1"); -Expect("project1").WhenSerializing(path.Index); ----- -there is also a static way to describe such paths - -[source, csharp] ----- -path = DocumentPath.Id(1); ----- -[source, csharp] ----- -Expect("project").WhenSerializing(path.Index); -Expect("project").WhenSerializing(path.Type); -Expect(1).WhenSerializing(path.Id); -var project = new Project { Name = "hello-world" }; ----- -here we create a new document path based on a Project - -[source, csharp] ----- -IDocumentPath path = new DocumentPath(project); ----- -[source, csharp] ----- -Expect("project").WhenSerializing(path.Index); -Expect("project").WhenSerializing(path.Type); -Expect("hello-world").WhenSerializing(path.Id); ----- -You can still override the inferred index and type name - -[source, csharp] ----- -path = new DocumentPath(project).Type("project1"); ----- -[source, csharp] ----- -Expect("project1").WhenSerializing(path.Type); -path = new DocumentPath(project).Index("project1"); -Expect("project1").WhenSerializing(path.Index); ----- -there is also a static way to describe such paths - -[source, csharp] ----- -path = DocumentPath.Id(project); ----- -[source, csharp] ----- -Expect("project").WhenSerializing(path.Index); -Expect("project").WhenSerializing(path.Type); -Expect("hello-world").WhenSerializing(path.Id); -DocumentPath p = project; -var project = new Project { Name = "hello-world" }; ----- -Here we can see and example how DocumentPath helps your describe your requests more tersely - -[source, csharp] ----- -var request = new IndexRequest(2) { Document = project }; ----- -[source, csharp] ----- -request = new IndexRequest(project) { }; ----- -when comparing with the full blown constructor and passing document manually -DocumentPath -T -'s benefits become apparent. - -[source, csharp] ----- -request = new IndexRequest(IndexName.From(), TypeName.From(), 2) -{ - Document = project -}; ----- diff --git a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/FieldInference.doc.asciidoc b/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/FieldInference.doc.asciidoc deleted file mode 100644 index 8701d88bf1d..00000000000 --- a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/FieldInference.doc.asciidoc +++ /dev/null @@ -1,429 +0,0 @@ -# Strongly typed field access - -Several places in the elasticsearch API expect the path to a field from your original source document as a string. -NEST allows you to use C# expressions to strongly type these field path strings. - -These expressions are assigned to a type called `Field` and there are several ways to create a instance of that type - - -Using the constructor directly is possible but rather involved - -[source, csharp] ----- -var fieldString = new Field { Name = "name" }; ----- -especially when using C# expressions since these can not be simply new'ed - -[source, csharp] ----- -Expression> expression = p => p.Name; ----- -[source, csharp] ----- -var fieldExpression = Field.Create(expression); -Expect("name") - .WhenSerializing(fieldExpression) - .WhenSerializing(fieldString); ----- -Therefore you can also implicitly convert strings and expressions to Field's - -[source, csharp] ----- -Field fieldString = "name"; ----- -but for expressions this is still rather involved - -[source, csharp] ----- -Expression> expression = p => p.Name; ----- -[source, csharp] ----- -Field fieldExpression = expression; -Expect("name") - .WhenSerializing(fieldExpression) - .WhenSerializing(fieldString); ----- -to ease creating Field's from expressions there is a static Property class you can use - -[source, csharp] ----- -Field fieldString = "name"; ----- -but for expressions this is still rather involved - -[source, csharp] ----- -var fieldExpression = Infer.Field(p => p.Name); ----- -Using static imports in c# 6 this can be even shortened: -using static Nest.Static; - -[source, csharp] ----- -fieldExpression = Field(p => p.Name); ----- -Now this is much much terser then our first example using the constructor! - -[source, csharp] ----- -Expect("name") - .WhenSerializing(fieldString) - .WhenSerializing(fieldExpression); ----- -By default NEST will camelCase all the field names to be more javascripty - -using DefaultFieldNameInferrer() on ConnectionSettings you can change this behavior - -[source, csharp] ----- -var setup = WithConnectionSettings(s => s.DefaultFieldNameInferrer(p => p.ToUpper())); ----- -[source, csharp] ----- -setup.Expect("NAME").WhenSerializing(Field(p => p.Name)); ----- -However string are *always* passed along verbatim - -[source, csharp] ----- -setup.Expect("NaMe").WhenSerializing("NaMe"); ----- -if you want the same behavior for expressions simply do nothing in the default inferrer - -[source, csharp] ----- -setup = WithConnectionSettings(s => s.DefaultFieldNameInferrer(p => p)); ----- -[source, csharp] ----- -setup.Expect("Name").WhenSerializing(Field(p => p.Name)); ----- -Complex field name expressions - -You can follow your property expression to any depth, here we are traversing to the LeadDeveloper's (Person) FirstName - -[source, csharp] ----- -Expect("leadDeveloper.firstName").WhenSerializing(Field(p => p.LeadDeveloper.FirstName)); ----- -When dealing with collection index access is ingnored allowing you to traverse into properties of collections - -[source, csharp] ----- -Expect("curatedTags").WhenSerializing(Field(p => p.CuratedTags[0])); ----- -Similarly .First() also works, remember these are expressions and not actual code that will be executed - -[source, csharp] ----- -Expect("curatedTags").WhenSerializing(Field(p => p.CuratedTags.First())); ----- -[source, csharp] ----- -Expect("curatedTags.added").WhenSerializing(Field(p => p.CuratedTags[0].Added)); -Expect("curatedTags.name").WhenSerializing(Field(p => p.CuratedTags.First().Name)); ----- -When we see an indexer on a dictionary we assume they describe property names - -[source, csharp] ----- -Expect("metadata.hardcoded").WhenSerializing(Field(p => p.Metadata["hardcoded"])); ----- -[source, csharp] ----- -Expect("metadata.hardcoded.created").WhenSerializing(Field(p => p.Metadata["hardcoded"].Created)); ----- -A cool feature here is that we'll evaluate variables passed to these indexers - -[source, csharp] ----- -var variable = "var"; ----- -[source, csharp] ----- -Expect("metadata.var").WhenSerializing(Field(p => p.Metadata[variable])); -Expect("metadata.var.created").WhenSerializing(Field(p => p.Metadata[variable].Created)); ----- -If you are using elasticearch's multifield mapping (you really should!) these "virtual" sub fields -do not always map back on to your POCO, by calling .Suffix() you describe the sub fields that do not live in your c# objects - -[source, csharp] ----- -Expect("leadDeveloper.firstName.raw").WhenSerializing(Field(p => p.LeadDeveloper.FirstName.Suffix("raw"))); ----- -[source, csharp] ----- -Expect("curatedTags.raw").WhenSerializing(Field(p => p.CuratedTags[0].Suffix("raw"))); -Expect("curatedTags.raw").WhenSerializing(Field(p => p.CuratedTags.First().Suffix("raw"))); -Expect("curatedTags.added.raw").WhenSerializing(Field(p => p.CuratedTags[0].Added.Suffix("raw"))); -Expect("metadata.hardcoded.raw").WhenSerializing(Field(p => p.Metadata["hardcoded"].Suffix("raw"))); -Expect("metadata.hardcoded.created.raw").WhenSerializing(Field(p => p.Metadata["hardcoded"].Created.Suffix("raw"))); ----- -You can even chain them to any depth! - -[source, csharp] ----- -Expect("curatedTags.name.raw.evendeeper").WhenSerializing(Field(p => p.CuratedTags.First().Name.Suffix("raw").Suffix("evendeeper"))); ----- -Variables passed to suffix will be evaluated as well - -[source, csharp] ----- -var suffix = "unanalyzed"; ----- -[source, csharp] ----- -Expect("metadata.var.unanalyzed").WhenSerializing(Field(p => p.Metadata[variable].Suffix(suffix))); -Expect("metadata.var.created.unanalyzed").WhenSerializing(Field(p => p.Metadata[variable].Created.Suffix(suffix))); ----- - -Suffixes can be appended to expressions. This is useful in cases where you want to apply the same suffix -to a list of fields - - - - -[source, csharp] ----- -var expressions = new List>> -{ - p => p.Name, - p => p.Description, - p => p.CuratedTags.First().Name, - p => p.LeadDeveloper.FirstName -}; ----- -append the suffix "raw" to each expression - -[source, csharp] ----- -var fieldExpressions = - expressions.Select>, Field>(e => e.AppendSuffix("raw")).ToList(); ----- -[source, csharp] ----- -Expect("name.raw").WhenSerializing(fieldExpressions[0]); -Expect("description.raw").WhenSerializing(fieldExpressions[1]); -Expect("curatedTags.name.raw").WhenSerializing(fieldExpressions[2]); -Expect("leadDeveloper.firstName.raw").WhenSerializing(fieldExpressions[3]); ----- -Annotations - -When using NEST's property attributes you can specify a new name for the properties - -[source, csharp] ----- -public class BuiltIn -{ - [String(Name = "naam")] - public string Name { get; set; } -} ----- -[source, csharp] ----- -Expect("naam").WhenSerializing(Field(p => p.Name)); ----- - -Starting with NEST 2.x we also ask the serializer if it can resolve the property to a name. -Here we ask the default JsonNetSerializer and it takes JsonProperty into account - -[source, csharp] ----- -public class SerializerSpecific -{ - [JsonProperty("nameInJson")] - public string Name { get; set; } -} ----- -[source, csharp] ----- -Expect("nameInJson").WhenSerializing(Field(p => p.Name)); ----- - -If both are specified NEST takes precedence though - -[source, csharp] ----- -public class Both -{ - [String(Name = "naam")] - [JsonProperty("nameInJson")] - public string Name { get; set; } -} ----- -[source, csharp] ----- -Expect("naam").WhenSerializing(Field(p => p.Name)); -Expect(new - { - naam = "Martijn Laarman" - }).WhenSerializing(new Both { Name = "Martijn Laarman" }); ----- -[source, csharp] ----- -class A { public C C { get; set; } } ----- -[source, csharp] ----- -class B { public C C { get; set; } } ----- -[source, csharp] ----- -class C -{ - public string Name { get; set; } -} ----- - -Resolving field names is cached but this is per connection settings - - -[source, csharp] ----- -var connectionSettings = TestClient.CreateSettings(forceInMemory: true); -var client = new ElasticClient(connectionSettings); -var fieldNameOnA = client.Infer.Field(Field(p => p.C.Name)); -var fieldNameOnB = client.Infer.Field(Field(p => p.C.Name)); ----- -Here we have to similary shaped expressions on coming from A and on from B -that will resolve to the same field name, as expected - -[source, csharp] ----- -fieldNameOnA.Should().Be("c.name"); ----- -[source, csharp] ----- -fieldNameOnB.Should().Be("c.name"); ----- -now we create a new connectionsettings with a remap for C on class A to `d` -now when we resolve the field path for A will be different - -[source, csharp] ----- -var newConnectionSettings = TestClient.CreateSettings(forceInMemory: true, modifySettings: s => s - .InferMappingFor(m => m - .Rename(p => p.C, "d") - ) -); ----- -[source, csharp] ----- -var newClient = new ElasticClient(newConnectionSettings); -fieldNameOnA = newClient.Infer.Field(Field(p => p.C.Name)); -fieldNameOnB = newClient.Infer.Field(Field(p => p.C.Name)); -fieldNameOnA.Should().Be("d.name"); -fieldNameOnB.Should().Be("c.name"); ----- -however we didn't break inferrence on the first client instance using its separate connectionsettings - -[source, csharp] ----- -fieldNameOnA = client.Infer.Field(Field(p => p.C.Name)); ----- -[source, csharp] ----- -fieldNameOnB = client.Infer.Field(Field(p => p.C.Name)); -fieldNameOnA.Should().Be("c.name"); -fieldNameOnB.Should().Be("c.name"); ----- -To wrap up lets showcase the precedence that field names are inferred -1. A hard rename of the property on connection settings using Rename() -2. A NEST property mapping -3. Ask the serializer if the property has a verbatim value e.g it has an explicit JsonPropery attribute. -4. Pass the MemberInfo's Name to the DefaultFieldNameInferrer which by default camelCases -In the following example we have a class where each case wins - -[source, csharp] ----- -class Precedence -{ ----- -Eventhough this property has a NEST property mapping and a JsonProperty attribute -We are going to provide a hard rename for it on ConnectionSettings later that should win. - -[source, csharp] ----- -[String(Name = "renamedIgnoresNest")] - [JsonProperty("renamedIgnoresJsonProperty")] - public string RenamedOnConnectionSettings { get; set; } ----- -This property has both a NEST attribute and a JsonProperty, NEST should win. - -[source, csharp] ----- -[String(Name = "nestAtt")] - [JsonProperty("jsonProp")] - public string NestAttribute { get; set; } ----- -We should take the json property into account by itself - -[source, csharp] ----- -[JsonProperty("jsonProp")] - public string JsonProperty { get; set; } ----- -This property we are going to special case in our custom serializer to resolve to `ask` - -[source, csharp] ----- -[JsonProperty("dontaskme")] - public string AskSerializer { get; set; } ----- -We are going to register a DefaultFieldNameInferrer on ConnectionSettings -that will uppercase all properties. - -[source, csharp] ----- -public string DefaultFieldNameInferrer { get; set; } - -} ----- -[source, csharp] ----- -var usingSettings = WithConnectionSettings(s => s ----- -here we provide an explicit rename of a property on connectionsettings - -[source, csharp] ----- -.InferMappingFor(m => m - .Rename(p => p.RenamedOnConnectionSettings, "renamed") - ) ----- -All properties that are not mapped verbatim should be uppercased - -[source, csharp] ----- -.DefaultFieldNameInferrer(p => p.ToUpperInvariant()) -).WithSerializer(s => new CustomSerializer(s)); ----- -[source, csharp] ----- -usingSettings.Expect("renamed").ForField(Field(p => p.RenamedOnConnectionSettings)); -usingSettings.Expect("nestAtt").ForField(Field(p => p.NestAttribute)); -usingSettings.Expect("jsonProp").ForField(Field(p => p.JsonProperty)); -usingSettings.Expect("ask").ForField(Field(p => p.AskSerializer)); -usingSettings.Expect("DEFAULTFIELDNAMEINFERRER").ForField(Field(p => p.DefaultFieldNameInferrer)); ----- -The same rules apply when indexing an object - -[source, csharp] ----- -usingSettings.Expect(new [] -{ - "ask", - "DEFAULTFIELDNAMEINFERRER", - "jsonProp", - "nestAtt", - "renamed" -}).AsPropertiesOf(new Precedence -{ - RenamedOnConnectionSettings = "renamed on connection settings", - NestAttribute = "using a nest attribute", - JsonProperty = "the default serializer resolves json property attributes", - AskSerializer = "serializer fiddled with this one", - DefaultFieldNameInferrer = "shouting much?" -}); ----- diff --git a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/FieldNames/FieldInference.doc.asciidoc b/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/FieldNames/FieldInference.doc.asciidoc deleted file mode 100644 index 82b58a192ec..00000000000 --- a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/FieldNames/FieldInference.doc.asciidoc +++ /dev/null @@ -1,313 +0,0 @@ -# Strongly typed field access - -Several places in the elasticsearch API expect the path to a field from your original source document as a string. -NEST allows you to use C# expressions to strongly type these field path strings. -These expressions are assigned to a type called `Field` and there are several ways to create a instance of that type - -Using the constructor directly is possible but rather involved - -[source, csharp] ----- -var fieldString = new Field { Name = "name" }; ----- -especially when using C# expressions since these can not be simply new'ed - -[source, csharp] ----- -Expression> expression = p => p.Name; ----- -[source, csharp] ----- -var fieldExpression = Field.Create(expression); -Expect("name") - .WhenSerializing(fieldExpression) - .WhenSerializing(fieldString); ----- -Therefor you can also implicitly convert strings and expressions to Field's - -[source, csharp] ----- -Field fieldString = "name"; ----- -but for expressions this is still rather involved - -[source, csharp] ----- -Expression> expression = p => p.Name; ----- -[source, csharp] ----- -Field fieldExpression = expression; -Expect("name") - .WhenSerializing(fieldExpression) - .WhenSerializing(fieldString); ----- -to ease creating Field's from expressions there is a static Property class you can use - -[source, csharp] ----- -Field fieldString = "name"; ----- -but for expressions this is still rather involved - -[source, csharp] ----- -var fieldExpression = Field(p => p.Name); ----- -Using static imports in c# 6 this can be even shortened: -using static Nest.Static; - -[source, csharp] ----- -fieldExpression = Field(p => p.Name); ----- -Now this is much much terser then our first example using the constructor! - -[source, csharp] ----- -Expect("name") - .WhenSerializing(fieldString) - .WhenSerializing(fieldExpression); ----- -By default NEST will camelCase all the field names to be more javascripty - -using DefaultFieldNameInferrer() on ConnectionSettings you can change this behavior - -[source, csharp] ----- -var setup = WithConnectionSettings(s => s.DefaultFieldNameInferrer(p => p.ToUpper())); ----- -[source, csharp] ----- -setup.Expect("NAME").WhenSerializing(Field(p => p.Name)); ----- -However string are *always* passed along verbatim - -[source, csharp] ----- -setup.Expect("NaMe").WhenSerializing("NaMe"); ----- -if you want the same behavior for expressions simply do nothing in the default inferrer - -[source, csharp] ----- -setup = WithConnectionSettings(s => s.DefaultFieldNameInferrer(p => p)); ----- -[source, csharp] ----- -setup.Expect("Name").WhenSerializing(Field(p => p.Name)); ----- -Complex field name expressions - -You can follow your property expression to any depth, here we are traversing to the LeadDeveloper's (Person) FirstName - -[source, csharp] ----- -Expect("leadDeveloper.firstName").WhenSerializing(Field(p => p.LeadDeveloper.FirstName)); ----- -When dealing with collection index access is ingnored allowing you to traverse into properties of collections - -[source, csharp] ----- -Expect("curatedTags").WhenSerializing(Field(p => p.CuratedTags[0])); ----- -Similarly .First() also works, remember these are expressions and not actual code that will be executed - -[source, csharp] ----- -Expect("curatedTags").WhenSerializing(Field(p => p.CuratedTags.First())); ----- -[source, csharp] ----- -Expect("curatedTags.added").WhenSerializing(Field(p => p.CuratedTags[0].Added)); -Expect("curatedTags.name").WhenSerializing(Field(p => p.CuratedTags.First().Name)); ----- -When we see an indexer on a dictionary we assume they describe property names - -[source, csharp] ----- -Expect("metadata.hardcoded").WhenSerializing(Field(p => p.Metadata["hardcoded"])); ----- -[source, csharp] ----- -Expect("metadata.hardcoded.created").WhenSerializing(Field(p => p.Metadata["hardcoded"].Created)); ----- -A cool feature here is that we'll evaluate variables passed to these indexers - -[source, csharp] ----- -var variable = "var"; ----- -[source, csharp] ----- -Expect("metadata.var").WhenSerializing(Field(p => p.Metadata[variable])); -Expect("metadata.var.created").WhenSerializing(Field(p => p.Metadata[variable].Created)); ----- -If you are using elasticearch's multifield mapping (you really should!) these "virtual" sub fields -do not always map back on to your POCO, by calling .Suffix() you describe the sub fields that do not live in your c# objects - -[source, csharp] ----- -Expect("leadDeveloper.firstName.raw").WhenSerializing(Field(p => p.LeadDeveloper.FirstName.Suffix("raw"))); ----- -[source, csharp] ----- -Expect("curatedTags.raw").WhenSerializing(Field(p => p.CuratedTags[0].Suffix("raw"))); -Expect("curatedTags.raw").WhenSerializing(Field(p => p.CuratedTags.First().Suffix("raw"))); -Expect("curatedTags.added.raw").WhenSerializing(Field(p => p.CuratedTags[0].Added.Suffix("raw"))); -Expect("metadata.hardcoded.raw").WhenSerializing(Field(p => p.Metadata["hardcoded"].Suffix("raw"))); -Expect("metadata.hardcoded.created.raw").WhenSerializing(Field(p => p.Metadata["hardcoded"].Created.Suffix("raw"))); ----- -You can even chain them to any depth! - -[source, csharp] ----- -Expect("curatedTags.name.raw.evendeeper").WhenSerializing(Field(p => p.CuratedTags.First().Name.Suffix("raw").Suffix("evendeeper"))); ----- -Variables passed to suffix will be evaluated as well - -[source, csharp] ----- -var suffix = "unanalyzed"; ----- -[source, csharp] ----- -Expect("metadata.var.unanalyzed").WhenSerializing(Field(p => p.Metadata[variable].Suffix(suffix))); -Expect("metadata.var.created.unanalyzed").WhenSerializing(Field(p => p.Metadata[variable].Created.Suffix(suffix))); ----- -Annotations - -When using NEST's property attributes you can specify a new name for the properties - -[source, csharp] ----- -Expect("naam").WhenSerializing(Field(p => p.Name)); ----- - -Starting with NEST 2.x we also ask the serializer if it can resolve the property to a name. -Here we ask the default JsonNetSerializer and it takes JsonProperty into account - -[source, csharp] ----- -Expect("nameInJson").WhenSerializing(Field(p => p.Name)); ----- - -If both are specified NEST takes precedence though - -[source, csharp] ----- -Expect("naam").WhenSerializing(Field(p => p.Name)); -Expect(new - { - naam = "Martijn Laarman" - }).WhenSerializing(new Both { Name = "Martijn Laarman" }); ----- -Resolving field names is cached but this is per connection settings - -[source, csharp] ----- -var connectionSettings = TestClient.CreateSettings(forceInMemory: true); -var client = new ElasticClient(connectionSettings); -var fieldNameOnA = client.Infer.Field(Field(p => p.C.Name)); -var fieldNameOnB = client.Infer.Field(Field(p => p.C.Name)); ----- -Here we have to similary shaped expressions on coming from A and on from B -that will resolve to the same field name, as expected - -[source, csharp] ----- -fieldNameOnA.Should().Be("c.name"); ----- -[source, csharp] ----- -fieldNameOnB.Should().Be("c.name"); ----- -now we create a new connectionsettings with a remap for C on class A to `d` -now when we resolve the field path for A will be different - -[source, csharp] ----- -var newConnectionSettings = TestClient.CreateSettings(forceInMemory: true, modifySettings: s => s - .InferMappingFor(m => m - .Rename(p => p.C, "d") - ) -); ----- -[source, csharp] ----- -var newClient = new ElasticClient(newConnectionSettings); -fieldNameOnA = newClient.Infer.Field(Field(p => p.C.Name)); -fieldNameOnB = newClient.Infer.Field(Field(p => p.C.Name)); -fieldNameOnA.Should().Be("d.name"); -fieldNameOnB.Should().Be("c.name"); ----- -however we didn't break inferrence on the first client instance using its separate connectionsettings - -[source, csharp] ----- -fieldNameOnA = client.Infer.Field(Field(p => p.C.Name)); ----- -[source, csharp] ----- -fieldNameOnB = client.Infer.Field(Field(p => p.C.Name)); -fieldNameOnA.Should().Be("c.name"); -fieldNameOnB.Should().Be("c.name"); ----- -To wrap up lets showcase the precedence that field names are inferred -1. A hard rename of the property on connection settings using Rename() -2. A NEST property mapping -3. Ask the serializer if the property has a verbatim value e.g it has an explicit JsonPropery attribute. -4. Pass the MemberInfo's Name to the DefaultFieldNameInferrer which by default camelCases -In the following example we have a class where each case wins - - -Here we create a custom converter that renames any property named `AskSerializer` to `ask` - -[source, csharp] ----- -var usingSettings = WithConnectionSettings(s => s ----- -here we provide an explicit rename of a property on connectionsettings - -[source, csharp] ----- -.InferMappingFor(m => m - .Rename(p => p.RenamedOnConnectionSettings, "renamed") - ) ----- -All properties that are not mapped verbatim should be uppercased - -[source, csharp] ----- -.DefaultFieldNameInferrer(p => p.ToUpperInvariant()) -).WithSerializer(s => new CustomSerializer(s)); ----- -[source, csharp] ----- -usingSettings.Expect("renamed").ForField(Field(p => p.RenamedOnConnectionSettings)); -usingSettings.Expect("nestAtt").ForField(Field(p => p.NestAttribute)); -usingSettings.Expect("jsonProp").ForField(Field(p => p.JsonProperty)); -usingSettings.Expect("ask").ForField(Field(p => p.AskSerializer)); -usingSettings.Expect("DEFAULTFIELDNAMEINFERRER").ForField(Field(p => p.DefaultFieldNameInferrer)); ----- -The same rules apply when indexing an object - -[source, csharp] ----- -usingSettings.Expect(new [] -{ - "ask", - "DEFAULTFIELDNAMEINFERRER", - "jsonProp", - "nestAtt", - "renamed" -}).AsPropertiesOf(new Precedence -{ - RenamedOnConnectionSettings = "renamed on connection settings", - NestAttribute = "using a nest attribute", - JsonProperty = "the default serializer resolves json property attributes", - AskSerializer = "serializer fiddled with this one", - DefaultFieldNameInferrer = "shouting much?" -}); ----- - diff --git a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/Id/IdsInference.doc.asciidoc b/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/Id/IdsInference.doc.asciidoc deleted file mode 100644 index 6a659778cb3..00000000000 --- a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/Id/IdsInference.doc.asciidoc +++ /dev/null @@ -1,79 +0,0 @@ -# Ids - -Several places in the elasticsearch API expect an Id object to be passed. This is a special box type that you can implicitly convert to and from many value types. - -Methods that take an Id can be passed longs, ints, strings -Guids and they will implicitly converted to Ids - -[source, csharp] ----- -Nest.Id idFromInt = 1; -Nest.Id idFromLong = 2L; -Nest.Id idFromString = "hello-world"; -Nest.Id idFromGuid = new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"); -Expect(1).WhenSerializing(idFromInt); -Expect(2).WhenSerializing(idFromLong); -Expect("hello-world").WhenSerializing(idFromString); -Expect("d70bd3cf-4e38-46f3-91ca-fcbef29b148e").WhenSerializing(idFromGuid); ----- -Sometimes a method takes an object and we need an Id from that object to build up a path. -There is no implicit conversion from any object to Id but we can call Id.From. -Imagine your codebase has the following type that we want to index into elasticsearch - -By default NEST will try to find a property called `Id` on the class using reflection -and create a cached fast func delegate based on the properties getter - -[source, csharp] ----- -var dto = new MyDTO { Id =new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"), Name = "x", OtherName = "y" }; ----- -[source, csharp] ----- -Expect("d70bd3cf-4e38-46f3-91ca-fcbef29b148e").WhenInferringIdOn(dto); ----- -Using the connection settings you can specify a different property NEST should look for ids. -Here we instruct NEST to infer the Id for MyDTO based on its Name property - -[source, csharp] ----- -WithConnectionSettings(x => x - .InferMappingFor(m => m - .IdProperty(p => p.Name) - ) -).Expect("x").WhenInferringIdOn(dto); ----- -Even though we have a cache at play the cache is per connection settings, so we can create a different config - -[source, csharp] ----- -WithConnectionSettings(x => x - .InferMappingFor(m => m - .IdProperty(p => p.OtherName) - ) -).Expect("y").WhenInferringIdOn(dto); ----- -Another way is to mark the type with an ElasticType attribute, using a string IdProperty - -Now when we infer the id we expect it to be the Name property without doing any configuration on the ConnectionSettings - -[source, csharp] ----- -var dto = new MyOtherDTO { Id =new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"), Name = "x", OtherName = "y" }; ----- -[source, csharp] ----- -Expect("x").WhenInferringIdOn(dto); ----- -This attribute IS cached statically/globally, however connectionsettings with a config for the type will -still win over this static configuration - -[source, csharp] ----- -WithConnectionSettings(x => x - .InferMappingFor(m => m - .IdProperty(p => p.OtherName) - ) -).Expect("y").WhenInferringIdOn(dto); ----- -Eventhough we have a cache at play the cache its per connection settings, so we can create a different config - diff --git a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/IdsInference.doc.asciidoc b/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/IdsInference.doc.asciidoc deleted file mode 100644 index b5818a65d59..00000000000 --- a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/IdsInference.doc.asciidoc +++ /dev/null @@ -1,98 +0,0 @@ -# Ids - -Several places in the elasticsearch API expect an Id object to be passed. This is a special box type that you can implicitly convert to and from many value types. - - -Methods that take an Id can be passed longs, ints, strings & Guids and they will implicitly converted to Ids - -[source, csharp] ----- -Id idFromInt = 1; -Id idFromLong = 2L; -Id idFromString = "hello-world"; -Id idFromGuid = new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"); -Expect(1).WhenSerializing(idFromInt); -Expect(2).WhenSerializing(idFromLong); -Expect("hello-world").WhenSerializing(idFromString); -Expect("d70bd3cf-4e38-46f3-91ca-fcbef29b148e").WhenSerializing(idFromGuid); ----- -Sometimes a method takes an object and we need an Id from that object to build up a path. -There is no implicit conversion from any object to Id but we can call Id.From. -Imagine your codebase has the following type that we want to index into elasticsearch - -[source, csharp] ----- -class MyDTO -{ - public Guid Id { get; set; } - public string Name { get; set; } - public string OtherName { get; set; } -} ----- -By default NEST will try to find a property called `Id` on the class using reflection -and create a cached fast func delegate based on the properties getter - -[source, csharp] ----- -var dto = new MyDTO { Id =new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"), Name = "x", OtherName = "y" }; ----- -[source, csharp] ----- -Expect("d70bd3cf-4e38-46f3-91ca-fcbef29b148e").WhenInferringIdOn(dto); ----- -Using the connection settings you can specify a different property NEST should look for ids. -Here we instruct NEST to infer the Id for MyDTO based on its Name property - -[source, csharp] ----- -WithConnectionSettings(x => x - .InferMappingFor(m => m - .IdProperty(p => p.Name) - ) -).Expect("x").WhenInferringIdOn(dto); ----- -Even though we have a cache at play the cache is per connection settings, so we can create a different config - -[source, csharp] ----- -WithConnectionSettings(x => x - .InferMappingFor(m => m - .IdProperty(p => p.OtherName) - ) -).Expect("y").WhenInferringIdOn(dto); ----- -Another way is to mark the type with an ElasticType attribute, using a string IdProperty - -[source, csharp] ----- -[ElasticsearchType(IdProperty = nameof(Name))] -class MyOtherDTO -{ - public Guid Id { get; set; } - public string Name { get; set; } - public string OtherName { get; set; } -} ----- -Now when we infer the id we expect it to be the Name property without doing any configuration on the ConnectionSettings - -[source, csharp] ----- -var dto = new MyOtherDTO { Id =new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"), Name = "x", OtherName = "y" }; ----- -[source, csharp] ----- -Expect("x").WhenInferringIdOn(dto); ----- -This attribute IS cached statically/globally, however connectionsettings with a config for the type will -still win over this static configuration - -[source, csharp] ----- -WithConnectionSettings(x => x - .InferMappingFor(m => m - .IdProperty(p => p.OtherName) - ) -).Expect("y").WhenInferringIdOn(dto); ----- -Eventhough we have a cache at play the cache its per connection settings, so we can create a different config - diff --git a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/Indices/IndicesPaths.doc.asciidoc b/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/Indices/IndicesPaths.doc.asciidoc deleted file mode 100644 index cc902f17fde..00000000000 --- a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/Indices/IndicesPaths.doc.asciidoc +++ /dev/null @@ -1,34 +0,0 @@ -# Indices paths - -Some API's in elasticsearch take one or many index name or a special "_all" marker to send the request to all the indices -In nest this is encoded using `Indices` - -Several types implicitly convert to `Indices` - -[source, csharp] ----- -Nest.Indices singleIndexFromString = "name"; -Nest.Indices multipleIndicesFromString = "name1, name2"; -Nest.Indices allFromString = "_all"; -Nest.Indices allWithOthersFromString = "_all, name2"; -singleIndexFromString.Match( - all => all.Should().BeNull(), - many => many.Indices.Should().HaveCount(1).And.Contain("name") - ); ----- -to ease creating Field's from expressions there is a static Property class you can use - - - -[source, csharp] ----- -var all = Nest.Indices.All; ----- -[source, csharp] ----- -var many = Nest.Indices.Index("name1", "name2"); -var manyTyped = Nest.Indices.Index().And(); -var singleTyped = Nest.Indices.Index(); -var singleString = Nest.Indices.Index("name1"); -var invalidSingleString = Nest.Indices.Index("name1, name2"); ----- diff --git a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/IndicesPaths.doc.asciidoc b/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/IndicesPaths.doc.asciidoc deleted file mode 100644 index c6c5a78f898..00000000000 --- a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/IndicesPaths.doc.asciidoc +++ /dev/null @@ -1,47 +0,0 @@ -# Indices paths - -Some API's in elasticsearch take one or many index name or a special "_all" marker to send the request to all the indices -In nest this is encoded using `Indices` - - -Several types implicitly convert to `Indices` - -[source, csharp] ----- -Nest.Indices singleIndexFromString = "name"; -Nest.Indices multipleIndicesFromString = "name1, name2"; -Nest.Indices allFromString = "_all"; -Nest.Indices allWithOthersFromString = "_all, name2"; -singleIndexFromString.Match( - all => all.Should().BeNull(), - many => many.Indices.Should().HaveCount(1).And.Contain("name") - ); -multipleIndicesFromString.Match( - all => all.Should().BeNull(), - many => many.Indices.Should().HaveCount(2).And.Contain("name2") - ); -allFromString.Match( - all => all.Should().NotBeNull(), - many => many.Indices.Should().BeNull() - ); -allWithOthersFromString.Match( - all => all.Should().NotBeNull(), - many => many.Indices.Should().BeNull() - ); ----- -to ease creating Field's from expressions there is a static Property class you can use - - - -[source, csharp] ----- -var all = Nest.Indices.All; ----- -[source, csharp] ----- -var many = Nest.Indices.Index("name1", "name2"); -var manyTyped = Nest.Indices.Index().And(); -var singleTyped = Nest.Indices.Index(); -var singleString = Nest.Indices.Index("name1"); -var invalidSingleString = Nest.Indices.Index("name1, name2"); ----- diff --git a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/PropertyInference.doc.asciidoc b/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/PropertyInference.doc.asciidoc deleted file mode 100644 index d21a723cd1b..00000000000 --- a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/PropertyInference.doc.asciidoc +++ /dev/null @@ -1,6 +0,0 @@ -[source, csharp] ----- -Expression> expression = p => p.Name.Suffix("raw"); -Expect("raw").WhenSerializing(expression); -Assert.Throws(() => Expect("exception!").WhenSerializing("name.raw")); ----- diff --git a/docs/asciidoc/ClientConcepts/HighLevel/Mapping/AutoMap.doc.asciidoc b/docs/asciidoc/ClientConcepts/HighLevel/Mapping/AutoMap.doc.asciidoc deleted file mode 100644 index 9db88699059..00000000000 --- a/docs/asciidoc/ClientConcepts/HighLevel/Mapping/AutoMap.doc.asciidoc +++ /dev/null @@ -1,904 +0,0 @@ -# Auto mapping properties - -When creating a mapping (either when creating an index or via the put mapping API), -NEST offers a feature called AutoMap(), which will automagically infer the correct -Elasticsearch datatypes of the POCO properties you are mapping. Alternatively, if -you're using attributes to map your properties, then calling AutoMap() is required -in order for your attributes to be applied. We'll look at examples of both. - - - -For these examples, we'll define two POCOS. A Company, which has a name -and a collection of Employees. And Employee, which has various properties of -different types, and itself has a collection of Employees. - -[source, csharp] ----- -public class Company -{ - public string Name { get; set; } - public List Employees { get; set; } -} ----- -[source, csharp] ----- -public class Employee -{ - public string FirstName { get; set; } - public string LastName { get; set; } - public int Salary { get; set; } - public DateTime Birthday { get; set; } - public bool IsManager { get; set; } - public List Employees { get; set; } - public TimeSpan Hours { get; set;} -} ----- -## Manual mapping -To create a mapping for our Company type, we can use the fluent API -and map each property explicitly - -[source, csharp] ----- -var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m - .Properties(ps => ps - .String(s => s - .Name(c => c.Name) - ) - .Object(o => o - .Name(c => c.Employees) - .Properties(eps => eps - .String(s => s - .Name(e => e.FirstName) - ) - .String(s => s - .Name(e => e.LastName) - ) - .Number(n => n - .Name(e => e.Salary) - .Type(NumberType.Integer) - ) - ) - ) - ) - ) - ); ----- -Which is all fine and dandy, and useful for some use cases. However in most cases -this is becomes too cumbersome of an approach, and you simply just want to map *all* -the properties of your POCO in a single go. - -[source, csharp] ----- -var expected = new -{ - mappings = new - { - company = new - { - properties = new - { - name = new - { - type = "string" - }, - employees = new - { - type = "object", - properties = new - { - firstName = new - { - type = "string" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "integer" - } - } - } - } - } - } -}; ----- -[source, csharp] ----- -Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor); ----- -## Simple Automapping -This is exactly where `AutoMap()` becomes useful. Instead of manually mapping each property, -explicitly, we can instead call `.AutoMap()` for each of our mappings and let NEST do all the work - -[source, csharp] ----- -var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m.AutoMap()) - .Map(m => m.AutoMap()) - ); ----- -Observe that NEST has inferred the Elasticsearch types based on the CLR type of our POCO properties. -In this example, -- Birthday was mapped as a date, -- Hours was mapped as a long (ticks) -- IsManager was mapped as a boolean, -- Salary as an integer -- Employees as an object -and the remaining string properties as strings. - -[source, csharp] ----- -var expected = new -{ - mappings = new - { - company = new - { - properties = new - { - employees = new - { - properties = new - { - birthday = new - { - type = "date" - }, - employees = new - { - properties = new { }, - type = "object" - }, - firstName = new - { - type = "string" - }, - hours = new - { - type = "long" - }, - isManager = new - { - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "integer" - } - }, - type = "object" - }, - name = new - { - type = "string" - } - } - }, - employee = new - { - properties = new - { - birthday = new - { - type = "date" - }, - employees = new - { - properties = new { }, - type = "object" - }, - firstName = new - { - type = "string" - }, - hours = new - { - type = "long" - }, - isManager = new - { - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "integer" - } - } - } - } -}; ----- -[source, csharp] ----- -Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor); ----- -## Automapping with overrides -In most cases, you'll want to map more than just the vanilla datatypes and also provide -various options on your properties (analyzer, doc_values, etc...). In that case, it's -possible to use AutoMap() in conjuction with explicitly mapped properties. - - -Here we are using AutoMap() to automatically map our company type, but then we're -overriding our employee property and making it a `nested` type, since by default, -AutoMap() will infer objects as `object`. - -[source, csharp] ----- -var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m - .AutoMap() - .Properties(ps => ps - .Nested(n => n - .Name(c => c.Employees) - .Properties(eps => eps - // snip - ) - ) - ) - ) - ); ----- -[source, csharp] ----- -var expected = new - { - mappings = new - { - company = new - { - properties = new - { - name = new - { - type = "string" - }, - employees = new - { - type = "nested", - properties = new {} - } - } - } - } - }; -Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor); ----- -## Automap with attributes -It is also possible to define your mappings using attributes on your POCOS. When you -use attributes, you MUST use AutoMap() in order for the attributes to be applied. -Here we define the same two types but this time using attributes. - -[source, csharp] ----- -[ElasticsearchType(Name = "company")] -public class CompanyWithAttributes -{ - [String(Analyzer = "keyword", NullValue = "null", Similarity = SimilarityOption.BM25)] - public string Name { get; set; } - - [String] - public TimeSpan? HeadOfficeHours { get; set; } - - [Object(Path = "employees", Store = false)] - public List Employees { get; set; } -} ----- -[source, csharp] ----- -[ElasticsearchType(Name = "employee")] -public class EmployeeWithAttributes -{ - [String] - public string FirstName { get; set; } - - [String] - public string LastName { get; set; } - - [Number(DocValues = false, IgnoreMalformed = true, Coerce = true)] - public int Salary { get; set; } - - [Date(Format = "MMddyyyy", NumericResolution = NumericResolutionUnit.Seconds)] - public DateTime Birthday { get; set; } - - [Boolean(NullValue = false, Store = true)] - public bool IsManager { get; set; } - - [Nested(Path = "employees")] - [JsonProperty("empl")] - public List Employees { get; set; } -} ----- -[source, csharp] ----- -var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m.AutoMap()) - .Map(m => m.AutoMap()) - ); -var expected = new - { - mappings = new - { - company = new - { - properties = new - { - employees = new - { - path = "employees", - properties = new - { - birthday = new - { - type = "date" - }, - employees = new - { - properties = new { }, - type = "object" - }, - firstName = new - { - type = "string" - }, - hours = new - { - type = "long" - }, - isManager = new - { - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "integer" - } - }, - store = false, - type = "object" - }, - name = new - { - analyzer = "keyword", - null_value = "null", - similarity = "BM25", - type = "string" - }, - headOfficeHours = new - { - type = "string" - } - } - }, - employee = new - { - properties = new - { - birthday = new - { - format = "MMddyyyy", - numeric_resolution = "seconds", - type = "date" - }, - empl = new - { - path = "employees", - properties = new - { - birthday = new - { - type = "date" - }, - employees = new - { - properties = new { }, - type = "object" - }, - firstName = new - { - type = "string" - }, - hours = new - { - type = "long" - }, - isManager = new - { - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "integer" - } - }, - type = "nested" - }, - firstName = new - { - type = "string" - }, - isManager = new - { - null_value = false, - store = true, - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - coerce = true, - doc_values = false, - ignore_malformed = true, - type = "double" - } - } - } - } - }; -Expect(expected).WhenSerializing(descriptor as ICreateIndexRequest); ----- - -Just as we were able to override the inferred properties in our earlier example, explicit (manual) -mappings also take precedence over attributes. Therefore we can also override any mappings applied -via any attributes defined on the POCO - - -[source, csharp] ----- -var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m - .AutoMap() - .Properties(ps => ps - .Nested(n => n - .Name(c => c.Employees) - ) - ) - ) - .Map(m => m - .AutoMap() - .TtlField(ttl => ttl - .Enable() - .Default("10m") - ) - .Properties(ps => ps - .String(s => s - .Name(e => e.FirstName) - .Fields(fs => fs - .String(ss => ss - .Name("firstNameRaw") - .Index(FieldIndexOption.NotAnalyzed) - ) - .TokenCount(t => t - .Name("length") - .Analyzer("standard") - ) - ) - ) - .Number(n => n - .Name(e => e.Salary) - .Type(NumberType.Double) - .IgnoreMalformed(false) - ) - .Date(d => d - .Name(e => e.Birthday) - .Format("MM-dd-yy") - ) - ) - ) - ); -var expected = new - { - mappings = new - { - company = new - { - properties = new - { - employees = new - { - type = "nested" - }, - name = new - { - analyzer = "keyword", - null_value = "null", - similarity = "BM25", - type = "string" - }, - headOfficeHours = new - { - type = "string" - } - } - }, - employee = new - { - _ttl = new - { - enabled = true, - @default = "10m" - }, - properties = new - { - birthday = new - { - format = "MM-dd-yy", - type = "date" - }, - empl = new - { - path = "employees", - properties = new - { - birthday = new - { - type = "date" - }, - employees = new - { - properties = new { }, - type = "object" - }, - firstName = new - { - type = "string" - }, - hours = new - { - type = "long" - }, - isManager = new - { - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "integer" - } - }, - type = "nested" - }, - firstName = new - { - fields = new - { - firstNameRaw = new - { - index = "not_analyzed", - type = "string" - }, - length = new - { - type = "token_count", - analyzer = "standard" - } - }, - type = "string" - }, - isManager = new - { - null_value = false, - store = true, - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - ignore_malformed = false, - type = "double" - } - } - } - } - }; -Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor); ----- -[source, csharp] ----- -[ElasticsearchType(Name = "company")] -public class CompanyWithAttributesAndPropertiesToIgnore -{ - public string Name { get; set; } - - [String(Ignore = true)] - public string PropertyToIgnore { get; set; } - - public string AnotherPropertyToIgnore { get; set; } - - [JsonIgnore] - public string JsonIgnoredProperty { get; set; } -} ----- -== Ignoring Properties -Properties on a POCO can be ignored in a few ways: - - - -- Using the `Ignore` property on a derived `ElasticsearchPropertyAttribute` type applied to the property that should be ignored on the POCO - - - -- Using the `.InferMappingFor(Func, IClrTypeMapping> selector)` on the connection settings - - - -- Using an ignore attribute applied to the POCO property that is understood by the `IElasticsearchSerializer` used and inspected inside of `CreatePropertyMapping()` on the serializer. In the case of the default `JsonNetSerializer`, this is the Json.NET `JsonIgnoreAttribute` - - - -This example demonstrates all ways, using the attribute way to ignore the property `PropertyToIgnore`, the infer mapping way to ignore the -property `AnotherPropertyToIgnore` and the json serializer specific attribute way to ignore the property `JsonIgnoredProperty` - - -[source, csharp] ----- -var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m - .AutoMap() - ) - ); -var expected = new - { - mappings = new - { - company = new - { - properties = new - { - name = new - { - type = "string" - } - } - } - } - }; -var settings = WithConnectionSettings(s => s - .InferMappingFor(i => i - .Ignore(p => p.AnotherPropertyToIgnore) - ) - ); -settings.Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor); ----- -If you notice in our previous Company/Employee examples, the Employee type is recursive -in that itself contains a collection of type `Employee`. By default, `.AutoMap()` will only -traverse a single depth when it encounters recursive instances like this. Hence, in the -previous examples, the second level of Employee did not get any of its properties mapped. -This is done as a safe-guard to prevent stack overflows and all the fun that comes with -infinite recursion. Additionally, in most cases, when it comes to Elasticsearch mappings, it is -often an edge case to have deeply nested mappings like this. However, you may still have -the need to do this, so you can control the recursion depth of AutoMap(). -Let's introduce a very simple class A, to reduce the noise, which itself has a property -Child of type A. - -[source, csharp] ----- -public class A -{ - public A Child { get; set; } -} ----- -By default, AutoMap() only goes as far as depth 1 - -[source, csharp] ----- -var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m.AutoMap()) - ); ----- -Thus we do not map properties on the second occurrence of our Child property - -[source, csharp] ----- -var expected = new -{ - mappings = new - { - a = new - { - properties = new - { - child = new - { - properties = new { }, - type = "object" - } - } - } - } -}; ----- -[source, csharp] ----- -Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor); ----- -Now lets specify a maxRecursion of 3 - -[source, csharp] ----- -var withMaxRecursionDescriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m.AutoMap(3)) - ); ----- -AutoMap() has now mapped three levels of our Child property - -[source, csharp] ----- -var expectedWithMaxRecursion = new -{ - mappings = new - { - a = new - { - properties = new - { - child = new - { - type = "object", - properties = new - { - child = new - { - type = "object", - properties = new - { - child = new - { - type = "object", - properties = new - { - child = new - { - type = "object", - properties = new { } - } - } - } - } - } - } - } - } - } - } -}; ----- -[source, csharp] ----- -Expect(expectedWithMaxRecursion).WhenSerializing((ICreateIndexRequest) withMaxRecursionDescriptor); ----- -Now we can pass an instance of our custom visitor to AutoMap() - -[source, csharp] ----- -var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m.AutoMap(new DisableDocValuesPropertyVisitor())) - ); ----- -and anytime it maps a property as a number (INumberProperty) or boolean (IBooleanProperty) -it will apply the transformation defined in each Visit() respectively, which in this example -disables doc values. - -[source, csharp] ----- -var expected = new -{ - mappings = new - { - employee = new - { - properties = new - { - birthday = new - { - type = "date" - }, - employees = new - { - properties = new { }, - type = "object" - }, - firstName = new - { - type = "string" - }, - isManager = new - { - doc_values = false, - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - doc_values = false, - type = "integer" - } - } - } - } -}; ----- -[source, csharp] ----- -var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m.AutoMap(new EverythingIsAStringPropertyVisitor())) - ); -var expected = new - { - mappings = new - { - employee = new - { - properties = new - { - birthday = new - { - type = "string" - }, - employees = new - { - type = "string" - }, - firstName = new - { - type = "string" - }, - isManager = new - { - type = "string" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "string" - } - } - } - } - }; ----- diff --git a/docs/asciidoc/ClientConcepts/LowLevel/Connecting.doc.asciidoc b/docs/asciidoc/ClientConcepts/LowLevel/Connecting.doc.asciidoc deleted file mode 100644 index 41ac4fd0d1b..00000000000 --- a/docs/asciidoc/ClientConcepts/LowLevel/Connecting.doc.asciidoc +++ /dev/null @@ -1,291 +0,0 @@ -# Connecting -Connecting to *Elasticsearch* with `Elasticsearch.Net` is quite easy but has a few toggles and options worth knowing. - -# Choosing the right connection strategy -If you simply new an `ElasticLowLevelClient`, it will be a non-failover connection to `http://localhost:9200` - - -[source, csharp] ----- -var client = new ElasticLowLevelClient(); -var tokenizers = new TokenizersDescriptor(); ----- - -If your Elasticsearch node does not live at `http://localhost:9200` but i.e `http://mynode.example.com:8082/apiKey`, then -you will need to pass in some instance of `IConnectionConfigurationValues`. - -The easiest way to do this is: - - -[source, csharp] ----- -var node = new Uri("http://mynode.example.com:8082/apiKey"); -var config = new ConnectionConfiguration(node); -var client = new ElasticLowLevelClient(config); ----- - -This however is still a non-failover connection. Meaning if that `node` goes down the operation will not be retried on any other nodes in the cluster. - -To get a failover connection we have to pass an `IConnectionPool` instance instead of a `Uri`. - - -[source, csharp] ----- -var node = new Uri("http://mynode.example.com:8082/apiKey"); -var connectionPool = new SniffingConnectionPool(new[] { node }); -var config = new ConnectionConfiguration(connectionPool); -var client = new ElasticLowLevelClient(config); ----- - -Here instead of directly passing `node`, we pass a `SniffingConnectionPool` which will use our `node` to find out the rest of the available cluster nodes. -Be sure to read more about [Connection Pooling and Cluster Failover here](/elasticsearch-net/cluster-failover.html) - -## Options - -Besides either passing a `Uri` or `IConnectionPool` to `ConnectionConfiguration`, you can also fluently control many more options. For instance: - - -[source, csharp] ----- -var node = new Uri("http://mynode.example.com:8082/apiKey"); -var connectionPool = new SniffingConnectionPool(new[] { node }); -var config = new ConnectionConfiguration(connectionPool) - .DisableDirectStreaming() - .BasicAuthentication("user", "pass") - .RequestTimeout(TimeSpan.FromSeconds(5)); ----- - -The following is a list of available connection configuration options: - - -[source, csharp] ----- -var client = new ElasticLowLevelClient(); ----- -[source, csharp] ----- -var config = new ConnectionConfiguration() - - .DisableAutomaticProxyDetection() ----- -Disable automatic proxy detection. Defaults to true. - -[source, csharp] ----- -.EnableHttpCompression() ----- -Enable compressed request and reesponses from Elasticsearch (Note that nodes need to be configured -to allow this. See the [http module settings](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-http.html) for more info). - -[source, csharp] ----- -.DisableDirectStreaming() ----- -By default responses are deserialized off stream to the object you tell it to. -For debugging purposes it can be very useful to keep a copy of the raw response on the result object. - -[source, csharp] ----- -var result = client.Search>(new { size = 12 }); -var raw = result.ResponseBodyInBytes; ----- -This will only have a value if the client configuration has ExposeRawResponse set - -[source, csharp] ----- -var stringResult = client.Search(new { }); ----- - -Please note that this only make sense if you need a mapped response and the raw response at the same time. -If you need a `string` or `byte[]` response simply call: - -[source, csharp] ----- -config = config - //endhide - .GlobalQueryStringParameters(new NameValueCollection()) ----- -Allows you to set querystring parameters that have to be added to every request. For instance, if you use a hosted elasticserch provider, and you need need to pass an `apiKey` parameter onto every request. - -[source, csharp] ----- -.Proxy(new Uri("http://myproxy"), "username", "pass") ----- -Sets proxy information on the connection. - -[source, csharp] ----- -.RequestTimeout(TimeSpan.FromSeconds(4)) ----- -Sets the global maximum time a connection may take. -Please note that this is the request timeout, the builtin .NET `WebRequest` has no way to set connection timeouts -(see http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.timeout(v=vs.110).aspx). - -[source, csharp] ----- -.ThrowExceptions() ----- -As an alternative to the C/go like error checking on `response.IsValid`, you can instead tell the client to throw -exceptions. -There are three category of exceptions thay may be thrown: - -1) ElasticsearchClientException: These are known exceptions, either an exception that occurred in the request pipeline -(such as max retries or timeout reached, bad authentication, etc...) or Elasticsearch itself returned an error (could -not parse the request, bad query, missing field, etc...). If it is an Elasticsearch error, the `ServerError` property -on the response will contain the the actual error that was returned. The inner exception will always contain the -root causing exception. - -2) UnexpectedElasticsearchClientException: These are unknown exceptions, for instance a response from Elasticsearch not -properly deserialized. These are usually bugs and should be reported. This excpetion also inherits from ElasticsearchClientException -so an additional catch block isn't necessary, but can be helpful in distinguishing between the two. -3) Development time exceptions: These are CLR exceptions like ArgumentException, NullArgumentException etc... that are thrown -when an API in the client is misused. These should not be handled as you want to know about them during development. - -[source, csharp] ----- -.PrettyJson() ----- -Forces all serialization to be indented and appends `pretty=true` to all the requests so that the responses are indented as well - -[source, csharp] ----- -.BasicAuthentication("username", "password") ----- -Sets the HTTP basic authentication credentials to specify with all requests. - -**Note:** This can alternatively be specified on the node URI directly: - -[source, csharp] ----- -var uri = new Uri("http://username:password@localhost:9200"); ----- -[source, csharp] ----- -var settings = new ConnectionConfiguration(uri); ----- - -...but may become tedious when using connection pooling with multiple nodes. - - - -You can pass a callback of type `Action` that can eaves drop every time a response (good or bad) is created. -If you have complex logging needs this is a good place to add that in. - - -[source, csharp] ----- -var counter = 0; -var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); -var settings = new ConnectionSettings(connectionPool, new InMemoryConnection()) - .OnRequestCompleted(r => counter++); -var client = new ElasticClient(settings); -client.RootNodeInfo(); -counter.Should().Be(1); -client.RootNodeInfoAsync(); -counter.Should().Be(2); ----- - -An example of using `OnRequestCompleted()` for complex logging. Remember, if you would also like -to capture the request and/or response bytes, you also need to set `.DisableDirectStreaming()` -to `true` - - -[source, csharp] ----- -var list = new List(); -var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); -var settings = new ConnectionSettings(connectionPool, new InMemoryConnection()) - .DisableDirectStreaming() - .OnRequestCompleted(response => - { - // log out the request - if (response.RequestBodyInBytes != null) - { - list.Add( - $"{response.HttpMethod} {response.Uri} \n" + - $"{Encoding.UTF8.GetString(response.RequestBodyInBytes)}"); - } - else - { - list.Add($"{response.HttpMethod} {response.Uri}"); - } - - // log out the response - if (response.ResponseBodyInBytes != null) - { - list.Add($"Status: {response.HttpStatusCode}\n" + - $"{Encoding.UTF8.GetString(response.ResponseBodyInBytes)}\n" + - $"{new string('-', 30)}\n"); - } - else - { - list.Add($"Status: {response.HttpStatusCode}\n" + - $"{new string('-', 30)}\n"); - } - }); -list.Add( - $"{response.HttpMethod} {response.Uri} \n" + - $"{Encoding.UTF8.GetString(response.RequestBodyInBytes)}"); -list.Add($"{response.HttpMethod} {response.Uri}"); -list.Add($"Status: {response.HttpStatusCode}\n" + - $"{Encoding.UTF8.GetString(response.ResponseBodyInBytes)}\n" + - $"{new string('-', 30)}\n"); -list.Add($"Status: {response.HttpStatusCode}\n" + - $"{new string('-', 30)}\n"); -var client = new ElasticClient(settings); -var syncResponse = client.Search(s => s - .Scroll("2m") - .Sort(ss => ss - .Ascending(SortSpecialField.DocumentIndexOrder) - ) - ); -list.Count.Should().Be(2); -var asyncResponse = await client.SearchAsync(s => s - .Scroll("2m") - .Sort(ss => ss - .Ascending(SortSpecialField.DocumentIndexOrder) - ) - ); -list.Count.Should().Be(4); -list.ShouldAllBeEquivalentTo(new [] - { - "POST http://localhost:9200/_search?scroll=2m \n{\"sort\":[{\"_doc\":{\"order\":\"asc\"}}]}", - "Status: 200\n------------------------------\n", - "POST http://localhost:9200/_search?scroll=2m \n{\"sort\":[{\"_doc\":{\"order\":\"asc\"}}]}", - "Status: 200\n------------------------------\n" - }); ----- -## Configuring SSL -SSL must be configured outside of the client using .NET's -[ServicePointManager](http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager%28v=vs.110%29.aspx) -class and setting the [ServerCertificateValidationCallback](http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback.aspx) -property. - -The bare minimum to make .NET accept self-signed SSL certs that are not in the Window's CA store would be to have the callback simply return `true`: - -[source, csharp] ----- -ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true; ----- - -However, this will accept all requests from the AppDomain to untrusted SSL sites, -therefore we recommend doing some minimal introspection on the passed in certificate. - - - -You can then register a factory on ConnectionSettings to create an instance of your subclass instead. -This is called once per instance of ConnectionSettings. - - -[source, csharp] ----- -var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); -var settings = new ConnectionSettings(connectionPool, new InMemoryConnection(), s => new MyJsonNetSerializer(s)); -var client = new ElasticClient(settings); -client.RootNodeInfo(); -client.RootNodeInfo(); -var serializer = ((IConnectionSettingsValues)settings).Serializer as MyJsonNetSerializer; -serializer.CallToModify.Should().BeGreaterThan(0); -serializer.SerializeToString(new Project { }); -serializer.CallToContractConverter.Should().BeGreaterThan(0); ----- diff --git a/docs/asciidoc/ClientConcepts/LowLevel/Lifetimes.doc.asciidoc b/docs/asciidoc/ClientConcepts/LowLevel/Lifetimes.doc.asciidoc deleted file mode 100644 index 2ea18a0d78a..00000000000 --- a/docs/asciidoc/ClientConcepts/LowLevel/Lifetimes.doc.asciidoc +++ /dev/null @@ -1,38 +0,0 @@ - -## Lifetimes - -If you are using an IOC container its always useful to know the best practices around the lifetime of your objects - -In general we advise folks to register their ElasticClient instances as singleton. The client is thread safe -so sharing this instance over threads is ok. - -Zooming in however the actual moving part that benefits the most of being static for most of the duration of your -application is ConnectionSettings. Caches are per ConnectionSettings. - -In some applications it could make perfect sense to have multiple singleton IElasticClient's registered with different -connectionsettings. e.g if you have 2 functionally isolated Elasticsearch clusters. - - - -[source, csharp] ----- -var connection = new AConnection(); -var connectionPool = new AConnectionPool(new Uri("http://localhost:9200")); -var settings = new AConnectionSettings(connectionPool, connection); -settings.IsDisposed.Should().BeFalse(); -connectionPool.IsDisposed.Should().BeFalse(); -connection.IsDisposed.Should().BeFalse(); ----- - -Disposing the ConnectionSettings will dispose the IConnectionPool and IConnection it has a hold of - - -[source, csharp] ----- -var connection = new AConnection(); -var connectionPool = new AConnectionPool(new Uri("http://localhost:9200")); -var settings = new AConnectionSettings(connectionPool, connection); -settings.IsDisposed.Should().BeTrue(); -connectionPool.IsDisposed.Should().BeTrue(); -connection.IsDisposed.Should().BeTrue(); ----- diff --git a/docs/asciidoc/CodeStandards/Descriptors.doc.asciidoc b/docs/asciidoc/CodeStandards/Descriptors.doc.asciidoc deleted file mode 100644 index 42149e5985e..00000000000 --- a/docs/asciidoc/CodeStandards/Descriptors.doc.asciidoc +++ /dev/null @@ -1,52 +0,0 @@ - -Every descriptor should inherit from `DescriptorBase`, this hides object members from the fluent interface - - -[source, csharp] ----- -var notDescriptors = new[] { typeof(ClusterProcessOpenFileDescriptors).Name, "DescriptorForAttribute" }; -var descriptors = from t in typeof(DescriptorBase<,>).Assembly().Types() - where t.IsClass() - && t.Name.Contains("Descriptor") - && !notDescriptors.Contains(t.Name) - && !t.GetInterfaces().Any(i => i == typeof(IDescriptor)) - select t.FullName; -descriptors.Should().BeEmpty(); ----- - -Methods taking a func should have that func return an interface - - -[source, csharp] ----- -var descriptors = - from t in typeof(DescriptorBase<,>).Assembly().Types() - where t.IsClass() && typeof(IDescriptor).IsAssignableFrom(t) - select t; -var selectorMethods = - from d in descriptors - from m in d.GetMethods() - let parameters = m.GetParameters() - from p in parameters - let type = p.ParameterType - let isGeneric = type.IsGeneric() - where isGeneric - let isFunc = type.GetGenericTypeDefinition() == typeof(Func<,>) - where isFunc - let firstFuncArg = type.GetGenericArguments().First() - let secondFuncArg = type.GetGenericArguments().Last() - let isQueryFunc = firstFuncArg.IsGeneric() && - firstFuncArg.GetGenericTypeDefinition() == typeof(QueryContainerDescriptor<>) && - typeof(QueryContainer).IsAssignableFrom(secondFuncArg) - where !isQueryFunc - let isFluentDictionaryFunc = - firstFuncArg.IsGeneric() && - firstFuncArg.GetGenericTypeDefinition() == typeof(FluentDictionary<,>) && - secondFuncArg.IsGeneric() && - secondFuncArg.GetGenericTypeDefinition() == typeof(FluentDictionary<,>) - where !isFluentDictionaryFunc - let lastArgIsNotInterface = !secondFuncArg.IsInterface() - where lastArgIsNotInterface - select $"{m.Name} on {m.DeclaringType.Name}"; -selectorMethods.Should().BeEmpty(); ----- diff --git a/docs/asciidoc/CodeStandards/NamingConventions.doc.asciidoc b/docs/asciidoc/CodeStandards/NamingConventions.doc.asciidoc deleted file mode 100644 index 79e6d70d120..00000000000 --- a/docs/asciidoc/CodeStandards/NamingConventions.doc.asciidoc +++ /dev/null @@ -1,128 +0,0 @@ -# Naming Conventions - -NEST uses the following naming conventions (with _some_ exceptions). - - -## Class Names - -Abstract class names should end with a `Base` suffix - - -[source, csharp] ----- -var exceptions = new[] - { - typeof(DateMath) - }; -var abstractClasses = typeof(IRequest).Assembly().GetTypes() - .Where(t => t.IsClass() && t.IsAbstract() && !t.IsSealed() && !exceptions.Contains(t)) - .Where(t => !t.Name.Split('`')[0].EndsWith("Base")) - .Select(t => t.Name.Split('`')[0]) - .ToList(); -abstractClasses.Should().BeEmpty(); ----- - -Class names that end with `Base` suffix are abstract - - -[source, csharp] ----- -var exceptions = new[] { typeof(DateMath) }; -var baseClassesNotAbstract = typeof(IRequest).Assembly().GetTypes() - .Where(t => t.IsClass() && !exceptions.Contains(t)) - .Where(t => t.Name.Split('`')[0].EndsWith("Base")) - .Where(t => !t.IsAbstractClass()) - .Select(t => t.Name.Split('`')[0]) - .ToList(); -baseClassesNotAbstract.Should().BeEmpty(); ----- -## Requests and Responses - -Request class names should end with `Request` - - -[source, csharp] ----- -var types = typeof(IRequest).Assembly().GetTypes(); -var requests = types - .Where(t => typeof(IRequest).IsAssignableFrom(t) && !t.IsAbstract()) - .Where(t => !typeof(IDescriptor).IsAssignableFrom(t)) - .Where(t => !t.Name.Split('`')[0].EndsWith("Request")) - .Select(t => t.Name.Split('`')[0]) - .ToList(); -requests.Should().BeEmpty(); ----- - -Response class names should end with `Response` - - -[source, csharp] ----- -var types = typeof(IRequest).Assembly().GetTypes(); -var responses = types - .Where(t => typeof(IResponse).IsAssignableFrom(t) && !t.IsAbstract()) - .Where(t => !t.Name.Split('`')[0].EndsWith("Response")) - .Select(t => t.Name.Split('`')[0]) - .ToList(); -responses.Should().BeEmpty(); ----- - -Request and Response class names should be one to one in *most* cases. -e.g. `ValidateRequest` => `ValidateResponse`, and not `ValidateQueryRequest` => `ValidateResponse` -There are a few exceptions to this rule, most notably the `Cat` prefixed requests and -`Exists` requests. - - -[source, csharp] ----- -var exceptions = new[] - { - typeof(CatAliasesRequest), - typeof(CatAllocationRequest), - typeof(CatCountRequest), - typeof(CatFielddataRequest), - typeof(CatHealthRequest), - typeof(CatHelpRequest), - typeof(CatIndicesRequest), - typeof(CatMasterRequest), - typeof(CatNodesRequest), - typeof(CatPendingTasksRequest), - typeof(CatPluginsRequest), - typeof(CatRecoveryRequest), - typeof(CatSegmentsRequest), - typeof(CatShardsRequest), - typeof(CatThreadPoolRequest), - typeof(DocumentExistsRequest), - typeof(DocumentExistsRequest<>), - typeof(AliasExistsRequest), - typeof(IndexExistsRequest), - typeof(TypeExistsRequest), - typeof(IndexTemplateExistsRequest), - typeof(SearchExistsRequest), - typeof(SearchExistsRequest<>), - typeof(SearchTemplateRequest), - typeof(SearchTemplateRequest<>), - typeof(ScrollRequest), - typeof(SourceRequest), - typeof(SourceRequest<>), - typeof(ValidateQueryRequest<>), - typeof(GetAliasRequest), - typeof(CatNodeattrsRequest), - typeof(IndicesShardStoresRequest), - typeof(RenderSearchTemplateRequest) - }; -var types = typeof(IRequest).Assembly().GetTypes(); -var requests = new HashSet(types - .Where(t => - t.IsClass() && - !t.IsAbstract() && - typeof(IRequest).IsAssignableFrom(t) && - !typeof(IDescriptor).IsAssignableFrom(t) - && !exceptions.Contains(t)) - .Select(t => t.Name.Split('`')[0].Replace("Request", "")) - ); -var responses = types - .Where(t => t.IsClass() && !t.IsAbstract() && typeof(IResponse).IsAssignableFrom(t)) - .Select(t => t.Name.Split('`')[0].Replace("Response", "")); -requests.Except(responses).Should().BeEmpty(); ----- diff --git a/docs/asciidoc/CodeStandards/Queries.doc.asciidoc b/docs/asciidoc/CodeStandards/Queries.doc.asciidoc deleted file mode 100644 index 821fa33f5be..00000000000 --- a/docs/asciidoc/CodeStandards/Queries.doc.asciidoc +++ /dev/null @@ -1,31 +0,0 @@ -[source, csharp] ----- -var properties = from p in QueryProperties - let a = p.GetCustomAttributes().Concat(p.GetCustomAttributes()) - where a.Count() != 1 - select p; -properties.Should().BeEmpty(); -var staticProperties = from p in typeof(Query<>).GetMethods() - let name = p.Name.StartsWith("GeoShape") ? "GeoShape" : p.Name - select name; -var placeHolders = QueryPlaceHolderProperties.Select(p => p.Name.StartsWith("GeoShape") ? "GeoShape" : p.Name); -staticProperties.Distinct().Should().Contain(placeHolders.Distinct()); -var fluentMethods = from p in typeof(QueryContainerDescriptor<>).GetMethods() - let name = p.Name.StartsWith("GeoShape") ? "GeoShape" : p.Name - select name; -var placeHolders = QueryPlaceHolderProperties.Select(p => p.Name.StartsWith("GeoShape") ? "GeoShape" : p.Name); -fluentMethods.Distinct().Should().Contain(placeHolders.Distinct()); -var skipQueryImplementations = new[] { typeof(IFieldNameQuery), typeof(IFuzzyQuery<,>), typeof(IConditionlessQuery) }; -var queries = typeof(IQuery).Assembly().ExportedTypes - .Where(t => t.IsInterface() && typeof(IQuery).IsAssignableFrom(t)) - .Where(t => !skipQueryImplementations.Contains(t)) - .ToList(); -queries.Should().NotBeEmpty(); -var visitMethods = typeof(IQueryVisitor).GetMethods().Where(m => m.Name == "Visit"); -visitMethods.Should().NotBeEmpty(); -var missingTypes = from q in queries - let visitMethod = visitMethods.FirstOrDefault(m => m.GetParameters().First().ParameterType == q) - where visitMethod == null - select q; -missingTypes.Should().BeEmpty(); ----- diff --git a/docs/asciidoc/CommonOptions/DateMath/DateMathExpressions.doc.asciidoc b/docs/asciidoc/CommonOptions/DateMath/DateMathExpressions.doc.asciidoc deleted file mode 100644 index dd78ae09ee8..00000000000 --- a/docs/asciidoc/CommonOptions/DateMath/DateMathExpressions.doc.asciidoc +++ /dev/null @@ -1,97 +0,0 @@ -# Date Expressions -The date type supports using date math expression when using it in a query/filter -Whenever durations need to be specified, eg for a timeout parameter, the duration can be specified - -The expression starts with an "anchor" date, which can be either now or a date string (in the applicable format) ending with ||. -It can then follow by a math expression, supporting +, - and / (rounding). -The units supported are y (year), M (month), w (week), d (day), h (hour), m (minute), and s (second). -as a whole number representing time in milliseconds, or as a time value like `2d` for 2 days. - -Be sure to read the elasticsearch documentation {ref}/mapping-date-format.html#date-math[on this subject here] - - - -You can create simple expressions using any of the static methods on `DateMath` - -[source, csharp] ----- -Expect("now").WhenSerializing(Nest.DateMath.Now); ----- -[source, csharp] ----- -Expect("2015-05-05T00:00:00").WhenSerializing(Nest.DateMath.Anchored(new DateTime(2015,05, 05))); ----- -strings implicitly convert to date maths - -[source, csharp] ----- -Expect("now").WhenSerializing("now"); ----- -but are lenient to bad math expressions - -[source, csharp] ----- -var nonsense = "now||*asdaqwe"; ----- -[source, csharp] ----- -Expect(nonsense).WhenSerializing(nonsense) ----- -the resulting date math will assume the whole string is the anchor - -[source, csharp] ----- -.Result(dateMath => ((IDateMath)dateMath) - .Anchor.Match( - d => d.Should().NotBe(default(DateTime)), - s => s.Should().Be(nonsense) - ) - ); ----- -date's also implicitly convert to simple date math expressions - -[source, csharp] ----- -var date = new DateTime(2015, 05, 05); ----- -[source, csharp] ----- -Expect("2015-05-05T00:00:00").WhenSerializing(date) ----- -the anchor will be an actual DateTime, even after a serialization - deserialization round trip - -[source, csharp] ----- -.Result(dateMath => ((IDateMath)dateMath) - . Anchor.Match( - d => d.Should().Be(date), - s => s.Should().BeNull() - ) - ); ----- -Ranges can be chained on to simple expressions - -[source, csharp] ----- -Expect("now+1d").WhenSerializing(Nest.DateMath.Now.Add("1d")); ----- -plural means that you can chain multiple - -[source, csharp] ----- -Expect("now+1d-1m").WhenSerializing(Nest.DateMath.Now.Add("1d").Subtract(TimeSpan.FromMinutes(1))); ----- -a rounding value can also be chained at the end afterwhich no more ranges can be appended - -[source, csharp] ----- -Expect("now+1d-1m/d").WhenSerializing(Nest.DateMath.Now.Add("1d").Subtract(TimeSpan.FromMinutes(1)).RoundTo(Nest.TimeUnit.Day)); ----- -When anchoring date's we need to append `||` as clear separator between anchor and ranges - -[source, csharp] ----- -Expect("2015-05-05T00:00:00||+1d-1m").WhenSerializing(Nest.DateMath.Anchored(new DateTime(2015,05,05)).Add("1d").Subtract(TimeSpan.FromMinutes(1))); ----- -plural means that you can chain multiple - diff --git a/docs/asciidoc/CommonOptions/TimeUnit/TimeUnits.doc.asciidoc b/docs/asciidoc/CommonOptions/TimeUnit/TimeUnits.doc.asciidoc deleted file mode 100644 index a68dbb3deae..00000000000 --- a/docs/asciidoc/CommonOptions/TimeUnit/TimeUnits.doc.asciidoc +++ /dev/null @@ -1,115 +0,0 @@ -# Time units -Whenever durations need to be specified, eg for a timeout parameter, the duration can be specified -as a whole number representing time in milliseconds, or as a time value like `2d` for 2 days. - -## Using Time units in NEST -NEST uses `Time` to strongly type this and there are several ways to construct one. - -### Constructor -The most straight forward way to construct a `Time` is through its constructor - - -[source, csharp] ----- -var unitString = new Time("2d"); -var unitComposed = new Time(2, Nest.TimeUnit.Day); -var unitTimeSpan = new Time(TimeSpan.FromDays(2)); -var unitMilliseconds = new Time(1000 * 60 * 60 * 24 * 2); ----- -When serializing Time constructed from a string, milliseconds, composition of factor and -interval, or a `TimeSpan` the expression will be serialized as time unit string - -[source, csharp] ----- -Expect("2d") - .WhenSerializing(unitString) - .WhenSerializing(unitComposed) - .WhenSerializing(unitTimeSpan) - .WhenSerializing(unitMilliseconds); ----- -Milliseconds are always calculated even when not using the constructor that takes a long - -[source, csharp] ----- -unitMilliseconds.Milliseconds.Should().Be(1000*60*60*24*2); ----- -[source, csharp] ----- -unitComposed.Milliseconds.Should().Be(1000*60*60*24*2); -unitTimeSpan.Milliseconds.Should().Be(1000*60*60*24*2); -unitString.Milliseconds.Should().Be(1000*60*60*24*2); ----- - -### Implicit conversion -Alternatively `string`, `TimeSpan` and `double` can be implicitly assigned to `Time` properties and variables - - -[source, csharp] ----- -Time oneAndHalfYear = "1.5y"; -Time twoWeeks = TimeSpan.FromDays(14); -Time twoDays = 1000*60*60*24*2; -Expect("1.5y").WhenSerializing(oneAndHalfYear); -Expect("2w").WhenSerializing(twoWeeks); -Expect("2d").WhenSerializing(twoDays); -Time oneAndHalfYear = "1.5y"; -Time twoWeeks = TimeSpan.FromDays(14); -Time twoDays = 1000*60*60*24*2; ----- -Milliseconds are calculated even when values are not passed as long - -[source, csharp] ----- -oneAndHalfYear.Milliseconds.Should().BeGreaterThan(1); ----- -[source, csharp] ----- -twoWeeks.Milliseconds.Should().BeGreaterThan(1); ----- -This allows you to do comparisons on the expressions - -[source, csharp] ----- -oneAndHalfYear.Should().BeGreaterThan(twoWeeks); ----- -[source, csharp] ----- -(oneAndHalfYear > twoWeeks).Should().BeTrue(); -(oneAndHalfYear >= twoWeeks).Should().BeTrue(); -(twoDays >= new Time("2d")).Should().BeTrue(); -twoDays.Should().BeLessThan(twoWeeks); -(twoDays < twoWeeks).Should().BeTrue(); -(twoDays <= twoWeeks).Should().BeTrue(); -(twoDays <= new Time("2d")).Should().BeTrue(); ----- -And assert equality - -[source, csharp] ----- -twoDays.Should().Be(new Time("2d")); ----- -[source, csharp] ----- -(twoDays == new Time("2d")).Should().BeTrue(); -(twoDays != new Time("2.1d")).Should().BeTrue(); -(new Time("2.1d") == new Time(TimeSpan.FromDays(2.1))).Should().BeTrue(); ----- -Time units are specified as a union of either a `DateInterval` or `Time` -both of which implicitly convert to the `Union` of these two. - -[source, csharp] ----- -Expect("month").WhenSerializing>(DateInterval.Month); ----- -[source, csharp] ----- -Expect("day").WhenSerializing>(DateInterval.Day); -Expect("hour").WhenSerializing>(DateInterval.Hour); -Expect("minute").WhenSerializing>(DateInterval.Minute); -Expect("quarter").WhenSerializing>(DateInterval.Quarter); -Expect("second").WhenSerializing>(DateInterval.Second); -Expect("week").WhenSerializing>(DateInterval.Week); -Expect("year").WhenSerializing>(DateInterval.Year); -Expect("2d").WhenSerializing>((Time)"2d"); -Expect("1.16w").WhenSerializing>((Time)TimeSpan.FromDays(8.1)); ----- diff --git a/docs/asciidoc/QueryDsl/BoolDsl/BoolDsl.doc.asciidoc b/docs/asciidoc/QueryDsl/BoolDsl/BoolDsl.doc.asciidoc deleted file mode 100644 index 9dde4bb05c8..00000000000 --- a/docs/asciidoc/QueryDsl/BoolDsl/BoolDsl.doc.asciidoc +++ /dev/null @@ -1,126 +0,0 @@ -Writing boolean queries can grow rather verbose rather quickly using the query DSL e.g - -[source, csharp] ----- -var searchResults = this.Client.Search(s => s - .Query(q => q - .Bool(b => b - .Should( - bs => bs.Term(p => p.Name, "x"), - bs => bs.Term(p => p.Name, "y") - ) - ) - ) - ); ----- -now this is just a single bool with only two clauses, imagine multiple nested bools this quickly becomes an exercise in -hadouken indenting - -[[indent]] -.hadouken indenting example -image::http://i.imgur.com/BtjZedW.jpg[dead indent] - - -For this reason, NEST introduces operator overloading so complex bool queries become easier to write, the previous example will become. - -[source, csharp] ----- -var searchResults = this.Client.Search(s => s - .Query(q => q.Term(p => p.Name, "x") || q.Term(p => p.Name, "y")) - ); ----- -Or using the object initializer syntax - -[source, csharp] ----- -searchResults = this.Client.Search(new SearchRequest -{ - Query = new TermQuery { Field = "name", Value= "x" } - || new TermQuery { Field = Field(p=>p.Name), Value = "y" } -}); ----- -A naive implementation of operator overloading would rewrite - -`term && term && term` to - -> bool -> |___must -> |___term -> |___bool -> |___must -> |___term -> |___term - -As you can image this becomes unwieldy quite fast the more complex a query becomes NEST can spot these and -join them together to become a single bool query - -> bool -> |___must -> |___term -> |___term -> |___term - - - -The bool DSL offers also a short hand notation to mark a query as a must_not using ! - -And to mark a query as a filter using + - -Both of these can be combined with ands to a single bool query - -When combining multiple queries some or all possibly marked as must_not or filter NEST still combines to a single bool query - -> bool -> |___must -> | |___term -> | |___term -> | |___term -> | -> |___must_not -> |___term - - -[source, csharp] ----- -Assert( - q => q.Query() && q.Query() && q.Query() && !q.Query(), - Query && Query && Query && !Query, - c=> - { - c.Bool.Must.Should().HaveCount(3); - c.Bool.MustNot.Should().HaveCount(1); - }); -c.Bool.Must.Should().HaveCount(3); -c.Bool.MustNot.Should().HaveCount(1); ----- -Even more involved `term && term && term && !term && +term && +term` still only results in a single bool query: - -> bool -> |___must -> | |___term -> | |___term -> | |___term -> | -> |___must_not -> | |___term -> | -> |___filter -> |___term -> |___term - - -You can still mix and match actual bool queries with the bool dsl e.g - -`bool(must=term, term, term) && !term` - -it would still merge into a single bool query. - -[source, csharp] ----- -c.Bool.Should.Should().HaveCount(2); -var nestedBool = c.Bool.Should.Cast().First(b=>!string.IsNullOrEmpty(b.Bool?.Name)); -nestedBool.Bool.Should.Should().HaveCount(1); -nestedBool.Bool.Name.Should().Be(firstName); -assert(fluent.InvokeQuery(new QueryContainerDescriptor())); -assert((QueryContainer)ois); ----- diff --git a/docs/asciidoc/QueryDsl/Geo/Distance/DistanceUnits.doc.asciidoc b/docs/asciidoc/QueryDsl/Geo/Distance/DistanceUnits.doc.asciidoc deleted file mode 100644 index c049e778115..00000000000 --- a/docs/asciidoc/QueryDsl/Geo/Distance/DistanceUnits.doc.asciidoc +++ /dev/null @@ -1,98 +0,0 @@ -# Distance Units -Whenever distances need to be specified, e.g. for a geo distance query, the distance unit can be specified -as a double number representing distance in meters, as a new instance of a `Distance`, or as a string -of the form number and distance unit e.g. `"2.72km"` - -## Using Distance units in NEST -NEST uses `Distance` to strongly type distance units and there are several ways to construct one. - -### Constructor -The most straight forward way to construct a `Distance` is through its constructor - - -[source, csharp] ----- -var unitComposed = new Nest.Distance(25); -var unitComposedWithUnits = new Nest.Distance(25, DistanceUnit.Meters); ----- -When serializing Distance constructed from a string, composition of distance value and unit - -[source, csharp] ----- -Expect("25.0m") - .WhenSerializing(unitComposed) - .WhenSerializing(unitComposedWithUnits); ----- - -### Implicit conversion -Alternatively a distance unit `string` can be assigned to a `Distance`, resulting in an implicit conversion to a new `Distance` instance. -If no `DistanceUnit` is specified, the default distance unit is meters - - -[source, csharp] ----- -Nest.Distance distanceString = "25"; -Nest.Distance distanceStringWithUnits = "25m"; -Expect(new Nest.Distance(25)) - .WhenSerializing(distanceString) - .WhenSerializing(distanceStringWithUnits); ----- - -### Supported units -A number of distance units are supported, from millimeters to nautical miles - - -Miles - -[source, csharp] ----- -Expect("0.62mi").WhenSerializing(new Nest.Distance(0.62, DistanceUnit.Miles)); ----- -Yards - -[source, csharp] ----- -Expect("9.0yd").WhenSerializing(new Nest.Distance(9, DistanceUnit.Yards)); ----- -Feet - -[source, csharp] ----- -Expect("3.33ft").WhenSerializing(new Nest.Distance(3.33, DistanceUnit.Feet)); ----- -Inches - -[source, csharp] ----- -Expect("43.23in").WhenSerializing(new Nest.Distance(43.23, DistanceUnit.Inch)); ----- -Kilometers - -[source, csharp] ----- -Expect("0.1km").WhenSerializing(new Nest.Distance(0.1, DistanceUnit.Kilometers)); ----- -Meters - -[source, csharp] ----- -Expect("400.0m").WhenSerializing(new Nest.Distance(400, DistanceUnit.Meters)); ----- -Centimeters - -[source, csharp] ----- -Expect("123.456cm").WhenSerializing(new Nest.Distance(123.456, DistanceUnit.Centimeters)); ----- -Millimeters - -[source, csharp] ----- -Expect("2.0mm").WhenSerializing(new Nest.Distance(2, DistanceUnit.Millimeters)); ----- -Nautical Miles - -[source, csharp] ----- -Expect("45.5nmi").WhenSerializing(new Nest.Distance(45.5, DistanceUnit.NauticalMiles)); ----- diff --git a/docs/asciidoc/aggregations-usage.asciidoc b/docs/asciidoc/aggregations-usage.asciidoc new file mode 100644 index 00000000000..a79afee8902 --- /dev/null +++ b/docs/asciidoc/aggregations-usage.asciidoc @@ -0,0 +1,92 @@ +:includes-from-dirs: aggregations/bucket,aggregations/metric,aggregations/pipeline + +include::../../docs/asciidoc/aggregations/bucket/children/children-aggregation-mapping.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/children/children-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/date-histogram/date-histogram-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/date-range/date-range-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/filter/filter-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/filters/filters-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/geo-distance/geo-distance-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/geo-hash-grid/geo-hash-grid-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/global/global-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/histogram/histogram-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/ip-range/ip-range-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/missing/missing-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/nested/nested-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/range/range-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/reverse-nested/reverse-nested-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/sampler/sampler-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/significant-terms/significant-terms-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/bucket/terms/terms-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/average/average-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/cardinality/cardinality-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/extended-stats/extended-stats-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/geo-bounds/geo-bounds-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/max/max-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/min/min-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/percentile-ranks/percentile-ranks-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/percentiles/percentiles-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/scripted-metric/scripted-metric-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/stats/stats-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/sum/sum-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/top-hits/top-hits-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/metric/value-count/value-count-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/average-bucket/average-bucket-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/bucket-script/bucket-script-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/bucket-selector/bucket-selector-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/cumulative-sum/cumulative-sum-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/derivative/derivative-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/max-bucket/max-bucket-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/min-bucket/min-bucket-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/moving-average/moving-average-ewma-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/moving-average/moving-average-holt-linear-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/moving-average/moving-average-holt-winters-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/moving-average/moving-average-linear-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/moving-average/moving-average-simple-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/serial-differencing/serial-differencing-aggregation-usage.asciidoc[] + +include::../../docs/asciidoc/aggregations/pipeline/sum-bucket/sum-bucket-aggregation-usage.asciidoc[] + diff --git a/docs/asciidoc/aggregations.asciidoc b/docs/asciidoc/aggregations.asciidoc new file mode 100644 index 00000000000..3fc54ece781 --- /dev/null +++ b/docs/asciidoc/aggregations.asciidoc @@ -0,0 +1,104 @@ +:output-dir: aggregations + +[[aggregations]] += Aggregations + +[partintro] +-- +Aggregations are arguably one of the most powerful features of Elasticsearch and NEST +exposes all of the available Aggregation types + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +-- + +include::{output-dir}/writing-aggregations.asciidoc[] + +include::aggregations-usage.asciidoc[] + diff --git a/docs/asciidoc/aggregations/bucket/children/children-aggregation-mapping.asciidoc b/docs/asciidoc/aggregations/bucket/children/children-aggregation-mapping.asciidoc new file mode 100644 index 00000000000..e4ef930dedb --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/children/children-aggregation-mapping.asciidoc @@ -0,0 +1,27 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[child-aggregation-mapping]] +== Child Aggregation Mapping + +To use the child aggregation you have to make sure + a `_parent` mapping is in place, here we create the project + index with two mapped types, `project` and `commitactivity` and + we add a `_parent` mapping from `commitactivity` to `parent` + +[source,csharp] +---- +var createProjectIndex = TestClient.GetClient().CreateIndex(typeof(Project), c => c + .Mappings(map => map + .Map(tm => tm.AutoMap()) + .Map(tm => tm + .Parent() <1> + ) + ) +); +---- +<1> Set the parent of `CommitActivity` to the `Project` type + diff --git a/docs/asciidoc/aggregations/bucket/children/children-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/children/children-aggregation-usage.asciidoc new file mode 100644 index 00000000000..aa45bb449c9 --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/children/children-aggregation-usage.asciidoc @@ -0,0 +1,70 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[children-aggregation-usage]] +== Children Aggregation Usage + +A special single bucket aggregation that enables aggregating from buckets on parent document types to +buckets on child documents. + +Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-children-aggregation.html[Children Aggregation] + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(aggs => aggs + .Children("name_of_child_agg", child => child + .Aggregations(childAggs => childAggs + .Average("average_per_child", avg => avg.Field(p => p.ConfidenceFactor)) + .Max("max_per_child", avg => avg.Field(p => p.ConfidenceFactor)) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(CommitActivity)) + { + Aggregations = + new AverageAggregation("average_per_child", "confidenceFactor") && + new MaxAggregation("max_per_child", "confidenceFactor") + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "name_of_child_agg": { + "children": { + "type": "commits" + }, + "aggs": { + "average_per_child": { + "avg": { + "field": "confidenceFactor" + } + }, + "max_per_child": { + "max": { + "field": "confidenceFactor" + } + } + } + } + } +} +---- + diff --git a/docs/asciidoc/aggregations/bucket/date-histogram/date-histogram-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/date-histogram/date-histogram-aggregation-usage.asciidoc new file mode 100644 index 00000000000..ebc96f38e4d --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/date-histogram/date-histogram-aggregation-usage.asciidoc @@ -0,0 +1,139 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[date-histogram-aggregation-usage]] +== Date Histogram Aggregation Usage + +A multi-bucket aggregation similar to the histogram except it can only be applied on date values. +From a functionality perspective, this histogram supports the same features as the normal histogram. +The main difference is that the interval can be specified by date/time expressions. + +NOTE: When specifying a `format` **and** `extended_bounds`, in order for Elasticsearch to be able to parse +the serialized `DateTime` of `extended_bounds` correctly, the `date_optional_time` format is included +as part of the `format` value. + +Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-datehistogram-aggregation.html[Date Histogram Aggregation]. + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(aggs => aggs + .DateHistogram("projects_started_per_month", date => date + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .MinimumDocumentCount(2) + .Format("yyyy-MM-dd'T'HH:mm:ss") + .ExtendedBounds(FixedDate.AddYears(-1), FixedDate.AddYears(1)) + .Order(HistogramOrder.CountAscending) + .Missing(FixedDate) + .Aggregations(childAggs => childAggs + .Nested("project_tags", n => n + .Path(p => p.Tags) + .Aggregations(nestedAggs => nestedAggs + .Terms("tags", avg => avg.Field(p => p.Tags.First().Name)) + ) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = Field(p => p.StartedOn), + Interval = DateInterval.Month, + MinimumDocumentCount = 2, + Format = "yyyy-MM-dd'T'HH:mm:ss", + ExtendedBounds = new ExtendedBounds + { + Minimum = FixedDate.AddYears(-1), + Maximum = FixedDate.AddYears(1), + }, + Order = HistogramOrder.CountAscending, + Missing = FixedDate, + Aggregations = new NestedAggregation("project_tags") + { + Path = Field(p => p.Tags), + Aggregations = new TermsAggregation("tags") + { + Field = Field(p => p.Tags.First().Name) + } + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month", + "min_doc_count": 2, + "format": "yyyy-MM-dd'T'HH:mm:ss||date_optional_time", + "order": { + "_count": "asc" + }, + "extended_bounds": { + "min": "2014-06-06T12:01:02.123", + "max": "2016-06-06T12:01:02.123" + }, + "missing": "2015-06-06T12:01:02.123" + }, + "aggs": { + "project_tags": { + "nested": { + "path": "tags" + }, + "aggs": { + "tags": { + "terms": { + "field": "tags.name" + } + } + } + } + } + } + } +} +---- + +=== Handling responses + +Using the `.Aggs` aggregation helper on `ISearchResponse`, we can fetch our aggregation results easily +in the correct type. <> + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); + +var dateHistogram = response.Aggs.DateHistogram("projects_started_per_month"); +dateHistogram.Should().NotBeNull(); +dateHistogram.Buckets.Should().NotBeNull(); +dateHistogram.Buckets.Count.Should().BeGreaterThan(10); +item.Date.Should().NotBe(default(DateTime)); +item.DocCount.Should().BeGreaterThan(0); +var nested = item.Nested("project_tags"); +nested.Should().NotBeNull(); +var nestedTerms = nested.Terms("tags"); +nestedTerms.Buckets.Count.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/bucket/date-range/date-range-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/date-range/date-range-aggregation-usage.asciidoc new file mode 100644 index 00000000000..fa59e26a4c6 --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/date-range/date-range-aggregation-usage.asciidoc @@ -0,0 +1,116 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[date-range-aggregation-usage]] +== Date Range Aggregation Usage + +A range aggregation that is dedicated for date values. The main difference between this aggregation and the normal range aggregation is that the `from` +and `to` values can be expressed in `DateMath` expressions, and it is also possible to specify a date format by which the from and +to response fields will be returned. + +IMPORTANT: this aggregation includes the `from` value and excludes the `to` value for each range. + +Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-daterange-aggregation.html[Date Range Aggregation] + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(aggs => aggs + .DateRange("projects_date_ranges", date => date + .Field(p => p.StartedOn) + .Ranges( + r => r.From(DateMath.Anchored(FixedDate).Add("2d")).To(DateMath.Now), + r => r.To(DateMath.Now.Add(TimeSpan.FromDays(1)).Subtract("30m").RoundTo(TimeUnit.Hour)), + r => r.From(DateMath.Anchored("2012-05-05").Add(TimeSpan.FromDays(1)).Subtract("1m")) + ) + .Aggregations(childAggs => childAggs + .Terms("project_tags", avg => avg.Field(p => p.Tags)) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new DateRangeAggregation("projects_date_ranges") + { + Field = Field(p => p.StartedOn), + Ranges = new List + { + new DateRangeExpression { From = DateMath.Anchored(FixedDate).Add("2d"), To = DateMath.Now}, + new DateRangeExpression { To = DateMath.Now.Add(TimeSpan.FromDays(1)).Subtract("30m").RoundTo(TimeUnit.Hour) }, + new DateRangeExpression { From = DateMath.Anchored("2012-05-05").Add(TimeSpan.FromDays(1)).Subtract("1m") } + }, + Aggregations = + new TermsAggregation("project_tags") { Field = Field(p => p.Tags) } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "projects_date_ranges": { + "date_range": { + "field": "startedOn", + "ranges": [ + { + "to": "now", + "from": "2015-06-06T12:01:02.123||+2d" + }, + { + "to": "now+1d-30m/h" + }, + { + "from": "2012-05-05||+1d-1m" + } + ] + }, + "aggs": { + "project_tags": { + "terms": { + "field": "tags" + } + } + } + } + } +} +---- + +=== Handling Responses + +Using the `.Agg` aggregation helper we can fetch our aggregation results easily +in the correct type. <> + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); + +var dateHistogram = response.Aggs.DateRange("projects_date_ranges"); +dateHistogram.Should().NotBeNull(); +dateHistogram.Buckets.Should().NotBeNull(); +---- + +We specified three ranges so we expect to have three of them in the response + +=== Handling Responses + +[source,csharp] +---- +dateHistogram.Buckets.Count.Should().Be(3); + +item.DocCount.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/bucket/filter/filter-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/filter/filter-aggregation-usage.asciidoc new file mode 100644 index 00000000000..478bc388bf5 --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/filter/filter-aggregation-usage.asciidoc @@ -0,0 +1,207 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[filter-aggregation-usage]] +== Filter Aggregation Usage + +Defines a single bucket of all the documents in the current document set context that match a specified filter. +Often this will be used to narrow down the current aggregation context to a specific set of documents. + +Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-filter-aggregation.html[Filter Aggregation] + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(aggs => aggs + .Filter("bethels_projects", date => date + .Filter(q => q.Term(p => p.LeadDeveloper.FirstName, FirstNameToFind)) + .Aggregations(childAggs => childAggs + .Terms("project_tags", avg => avg.Field(p => p.CuratedTags.First().Name)) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new FilterAggregation("bethels_projects") + { + Filter = new TermQuery {Field = Field(p => p.LeadDeveloper.FirstName), Value = FirstNameToFind}, + Aggregations = + new TermsAggregation("project_tags") {Field = Field(p => p.CuratedTags.First().Name)} + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "bethels_projects": { + "filter": { + "term": { + "leadDeveloper.firstName": { + "value": "pierce" + } + } + }, + "aggs": { + "project_tags": { + "terms": { + "field": "curatedTags.name" + } + } + } + } + } +} +---- + +=== Handling Responses + +Using the `.Aggs` aggregation helper we can fetch our aggregation results easily +in the correct type. <> + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); + +var filterAgg = response.Aggs.Filter("bethels_projects"); +filterAgg.Should().NotBeNull(); +filterAgg.DocCount.Should().BeGreaterThan(0); +var tags = filterAgg.Terms("project_tags"); +tags.Should().NotBeNull(); +tags.Buckets.Should().NotBeEmpty(); +---- + +[[empty-filter]] +[float] +== Empty Filter + +When the collection of filters is empty or all are conditionless, NEST will serialize them +to an empty object. + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(aggs => aggs + .Filter("empty_filter", date => date + .Filter(f => f + .Bool(b => b + .Filter(new QueryContainer[0]) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new FilterAggregation("empty_filter") + { + Filter = new BoolQuery + { + Filter = new List() + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "empty_filter": { + "filter": {} + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +response.Aggs.Filter("empty_filter").DocCount.Should().BeGreaterThan(0); +---- + +[[inline-script-filter]] +[float] +== Inline Script Filter + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(aggs => aggs + .Filter(_aggName, date => date + .Filter(f => f + .Script(b => b + .Inline(_ctxNumberofCommits) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new FilterAggregation(_aggName) + { + Filter = new ScriptQuery + { + Inline = _ctxNumberofCommits + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "script_filter": { + "filter": { + "script": { + "script": { + "inline": "_source.numberOfCommits > 0" + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +response.Aggs.Filter(_aggName).DocCount.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/bucket/filters/filters-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/filters/filters-aggregation-usage.asciidoc new file mode 100644 index 00000000000..a18acc603b3 --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/filters/filters-aggregation-usage.asciidoc @@ -0,0 +1,349 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[filters-aggregation-usage]] +== Filters Aggregation Usage + +Defines a multi bucket aggregations where each bucket is associated with a filter. +Each bucket will collect all documents that match its associated filter. For documents +that do not match any filter, these will be collected in the _other bucket_. + +Be sure to read the Elasticsearch documentation {ref_current}/search-aggregations-bucket-filters-aggregation.html[Filters Aggregation] + +[[named-filters]] +[float] +== Named filters + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(aggs => aggs + .Filters("projects_by_state", agg => agg + .OtherBucket() + .OtherBucketKey("other_states_of_being") + .NamedFilters(filters => filters + .Filter("belly_up", f => f.Term(p => p.State, StateOfBeing.BellyUp)) + .Filter("stable", f => f.Term(p => p.State, StateOfBeing.Stable)) + .Filter("very_active", f => f.Term(p => p.State, StateOfBeing.VeryActive)) + ) + .Aggregations(childAggs => childAggs + .Terms("project_tags", avg => avg.Field(p => p.CuratedTags.First().Name)) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new FiltersAggregation("projects_by_state") + { + OtherBucket = true, + OtherBucketKey = "other_states_of_being", + Filters = new NamedFiltersContainer + { + { "belly_up", Query.Term(p=>p.State, StateOfBeing.BellyUp) }, + { "stable", Query.Term(p=>p.State, StateOfBeing.Stable) }, + { "very_active", Query.Term(p=>p.State, StateOfBeing.VeryActive) } + }, + Aggregations = + new TermsAggregation("project_tags") { Field = Field(p => p.CuratedTags.First().Name) } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "projects_by_state": { + "filters": { + "other_bucket": true, + "other_bucket_key": "other_states_of_being", + "filters": { + "belly_up": { + "term": { + "state": { + "value": "BellyUp" + } + } + }, + "stable": { + "term": { + "state": { + "value": "Stable" + } + } + }, + "very_active": { + "term": { + "state": { + "value": "VeryActive" + } + } + } + } + }, + "aggs": { + "project_tags": { + "terms": { + "field": "curatedTags.name" + } + } + } + } + } +} +---- + +=== Handling Responses + +Using the `.Agg` aggregation helper we can fetch our aggregation results easily +in the correct type. <> + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); + +var filterAgg = response.Aggs.Filters("projects_by_state"); +filterAgg.Should().NotBeNull(); +var namedResult = filterAgg.NamedBucket("belly_up"); +namedResult.Should().NotBeNull(); +namedResult.DocCount.Should().BeGreaterThan(0); +namedResult = filterAgg.NamedBucket("stable"); +namedResult.Should().NotBeNull(); +namedResult.DocCount.Should().BeGreaterThan(0); +namedResult = filterAgg.NamedBucket("very_active"); +namedResult.Should().NotBeNull(); +namedResult.DocCount.Should().BeGreaterThan(0); +namedResult = filterAgg.NamedBucket("other_states_of_being"); +namedResult.Should().NotBeNull(); +namedResult.DocCount.Should().Be(0); +---- + +[[anonymous-filters]] +[float] +== Anonymous filters + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(aggs => aggs + .Filters("projects_by_state", agg => agg + .OtherBucket() + .AnonymousFilters( + f => f.Term(p => p.State, StateOfBeing.BellyUp), + f => f.Term(p => p.State, StateOfBeing.Stable), + f => f.Term(p => p.State, StateOfBeing.VeryActive) + ) + .Aggregations(childAggs => childAggs + .Terms("project_tags", avg => avg.Field(p => p.CuratedTags.First().Name)) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new FiltersAggregation("projects_by_state") + { + OtherBucket = true, + Filters = new List + { + Query.Term(p=>p.State, StateOfBeing.BellyUp) , + Query.Term(p=>p.State, StateOfBeing.Stable) , + Query.Term(p=>p.State, StateOfBeing.VeryActive) + }, + Aggregations = + new TermsAggregation("project_tags") + { + Field = Field(p => p.CuratedTags.First().Name) + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "projects_by_state": { + "filters": { + "other_bucket": true, + "filters": [ + { + "term": { + "state": { + "value": "BellyUp" + } + } + }, + { + "term": { + "state": { + "value": "Stable" + } + } + }, + { + "term": { + "state": { + "value": "VeryActive" + } + } + } + ] + }, + "aggs": { + "project_tags": { + "terms": { + "field": "curatedTags.name" + } + } + } + } + } +} +---- + +=== Handling Responses + +Using the `.Agg` aggregation helper we can fetch our aggregation results easily +in the correct type. <> + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); + +var filterAgg = response.Aggs.Filters("projects_by_state"); +filterAgg.Should().NotBeNull(); +var results = filterAgg.AnonymousBuckets(); +results.Count.Should().Be(4); +singleBucket.DocCount.Should().BeGreaterThan(0); +results.Last().DocCount.Should().Be(0); <1> +---- +<1> The last bucket is the _other bucket_ + +[[empty-filters]] +[float] +== Empty Filters + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(aggs => aggs + .Filters("empty_filters", agg => agg + .AnonymousFilters() + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new FiltersAggregation("empty_filters") + { + Filters = new List() + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "empty_filters": { + "filters": { + "filters": [] + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +response.Aggs.Filters("empty_filters").Buckets.Should().BeEmpty(); +---- + +[[conditionless-filters]] +[float] +== Conditionless Filters + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(aggs => aggs + .Filters("conditionless_filters", agg => agg + .AnonymousFilters( + q => new QueryContainer() + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new FiltersAggregation("conditionless_filters") + { + Filters = new List + { + new QueryContainer() + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "conditionless_filters": { + "filters": { + "filters": [] + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +response.Aggs.Filters("conditionless_filters").Buckets.Should().BeEmpty(); +---- + diff --git a/docs/asciidoc/aggregations/bucket/geo-distance/geo-distance-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/geo-distance/geo-distance-aggregation-usage.asciidoc new file mode 100644 index 00000000000..c02be78bf43 --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/geo-distance/geo-distance-aggregation-usage.asciidoc @@ -0,0 +1,89 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-distance-aggregation-usage]] +== Geo Distance Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .GeoDistance("rings_around_amsterdam", g => g + .Field(p => p.Location) + .Origin(52.376, 4.894) + .Ranges( + r => r.To(100), + r => r.From(100).To(300), + r => r.From(300) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new GeoDistanceAggregation("rings_around_amsterdam") + { + Field = Field((Project p) => p.Location), + Origin = "52.376, 4.894", + Ranges = new List + { + new Nest.Range { To = 100 }, + new Nest.Range { From = 100, To = 300 }, + new Nest.Range { From = 300 } + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "rings_around_amsterdam": { + "geo_distance": { + "field": "location", + "origin": { + "lat": 52.376, + "lon": 4.894 + }, + "ranges": [ + { + "to": 100.0 + }, + { + "from": 100.0, + "to": 300.0 + }, + { + "from": 300.0 + } + ] + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var ringsAroundAmsterdam = response.Aggs.GeoDistance("rings_around_amsterdam"); +ringsAroundAmsterdam.Should().NotBeNull(); +ringsAroundAmsterdam.Buckets.Where(r => r.Key == "*-100.0").FirstOrDefault().Should().NotBeNull(); +ringsAroundAmsterdam.Buckets.Where(r => r.Key == "100.0-300.0").FirstOrDefault().Should().NotBeNull(); +ringsAroundAmsterdam.Buckets.Where(r => r.Key == "300.0-*").FirstOrDefault().Should().NotBeNull(); +---- + diff --git a/docs/asciidoc/aggregations/bucket/geo-hash-grid/geo-hash-grid-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/geo-hash-grid/geo-hash-grid-aggregation-usage.asciidoc new file mode 100644 index 00000000000..9eb3bdafbf6 --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/geo-hash-grid/geo-hash-grid-aggregation-usage.asciidoc @@ -0,0 +1,66 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-hash-grid-aggregation-usage]] +== Geo Hash Grid Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .GeoHash("my_geohash_grid", g => g + .Field(p => p.Location) + .GeoHashPrecision(GeoHashPrecision.Precision3) + .Size(1000) + .ShardSize(100) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new GeoHashGridAggregation("my_geohash_grid") + { + Field = Field(p => p.Location), + Precision = GeoHashPrecision.Precision3, + Size = 1000, + ShardSize = 100 + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "my_geohash_grid": { + "geohash_grid": { + "field": "location", + "precision": 3, + "size": 1000, + "shard_size": 100 + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var myGeoHashGrid = response.Aggs.GeoHash("my_geohash_grid"); +myGeoHashGrid.Should().NotBeNull(); +---- + diff --git a/docs/asciidoc/aggregations/bucket/global/global-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/global/global-aggregation-usage.asciidoc new file mode 100644 index 00000000000..6bfd96b6823 --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/global/global-aggregation-usage.asciidoc @@ -0,0 +1,71 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[global-aggregation-usage]] +== Global Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Global("all_projects", g => g + .Aggregations(aa => aa + .Terms("names", t => t + .Field(p => p.Name) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new GlobalAggregation("all_projects") + { + Aggregations = new TermsAggregation("names") + { + Field = Field(p => p.Name) + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "all_projects": { + "global": {}, + "aggs": { + "names": { + "terms": { + "field": "name" + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var allProjects = response.Aggs.Global("all_projects"); +allProjects.Should().NotBeNull(); +var names = allProjects.Terms("names"); +names.Should().NotBeNull(); +---- + diff --git a/docs/asciidoc/aggregations/bucket/histogram/histogram-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/histogram/histogram-aggregation-usage.asciidoc new file mode 100644 index 00000000000..eecbd9d2b7d --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/histogram/histogram-aggregation-usage.asciidoc @@ -0,0 +1,69 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[histogram-aggregation-usage]] +== Histogram Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Histogram("commits", h => h + .Field(p => p.NumberOfCommits) + .Interval(100) + .Missing(0) + .Order(HistogramOrder.KeyDescending) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new HistogramAggregation("commits") + { + Field = Field(p => p.NumberOfCommits), + Interval = 100, + Missing = 0, + Order = HistogramOrder.KeyDescending + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "commits": { + "histogram": { + "field": "numberOfCommits", + "interval": 100.0, + "missing": 0.0, + "order": { + "_key": "desc" + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var commits = response.Aggs.Histogram("commits"); +commits.Should().NotBeNull(); +item.DocCount.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/bucket/ip-range/ip-range-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/ip-range/ip-range-aggregation-usage.asciidoc new file mode 100644 index 00000000000..56904224efd --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/ip-range/ip-range-aggregation-usage.asciidoc @@ -0,0 +1,77 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[ip-range-aggregation-usage]] +== Ip Range Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .IpRange("ip_ranges", ip => ip + .Field(p => p.LeadDeveloper.IPAddress) + .Ranges( + r => r.To("10.0.0.5"), + r => r.From("10.0.0.5") + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new IpRangeAggregation("ip_ranges") + { + Field = Field((Project p) => p.LeadDeveloper.IPAddress), + Ranges = new List + { + new Nest.IpRange { To = "10.0.0.5" }, + new Nest.IpRange { From = "10.0.0.5" } + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "ip_ranges": { + "ip_range": { + "field": "leadDeveloper.iPAddress", + "ranges": [ + { + "to": "10.0.0.5" + }, + { + "from": "10.0.0.5" + } + ] + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var ipRanges = response.Aggs.IpRange("ip_ranges"); +ipRanges.Should().NotBeNull(); +ipRanges.Buckets.Should().NotBeNull(); +ipRanges.Buckets.Count.Should().BeGreaterThan(0); +range.DocCount.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/bucket/missing/missing-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/missing/missing-aggregation-usage.asciidoc new file mode 100644 index 00000000000..e4f735ab277 --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/missing/missing-aggregation-usage.asciidoc @@ -0,0 +1,57 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[missing-aggregation-usage]] +== Missing Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Missing("projects_without_a_description", m => m + .Field(p => p.Description) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new MissingAggregation("projects_without_a_description") + { + Field = Field(p => p.Description) + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "projects_without_a_description": { + "missing": { + "field": "description" + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsWithoutDesc = response.Aggs.Missing("projects_without_a_description"); +projectsWithoutDesc.Should().NotBeNull(); +---- + diff --git a/docs/asciidoc/aggregations/bucket/nested/nested-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/nested/nested-aggregation-usage.asciidoc new file mode 100644 index 00000000000..d9d2836935d --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/nested/nested-aggregation-usage.asciidoc @@ -0,0 +1,77 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[nested-aggregation-usage]] +== Nested Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Nested("tags", n => n + .Path(p => p.Tags) + .Aggregations(aa => aa + .Terms("tag_names", t => t + .Field(p => p.Tags.Suffix("name")) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new NestedAggregation("tags") + { + Path = "tags", + Aggregations = new TermsAggregation("tag_names") + { + Field = "tags.name" + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "tags": { + "nested": { + "path": "tags" + }, + "aggs": { + "tag_names": { + "terms": { + "field": "tags.name" + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var tags = response.Aggs.Nested("tags"); +tags.Should().NotBeNull(); +var tagNames = tags.Terms("tag_names"); +tagNames.Should().NotBeNull(); +item.Key.Should().NotBeNullOrEmpty(); +item.DocCount.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/bucket/range/range-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/range/range-aggregation-usage.asciidoc new file mode 100644 index 00000000000..54bbe77c892 --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/range/range-aggregation-usage.asciidoc @@ -0,0 +1,84 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[range-aggregation-usage]] +== Range Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Range("commit_ranges", ra => ra + .Field(p => p.NumberOfCommits) + .Ranges( + r => r.To(100), + r => r.From(100).To(500), + r => r.From(500) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new RangeAggregation("commit_ranges") + { + Field = Field(p => p.NumberOfCommits), + Ranges = new List + { + { new Nest.Range { To = 100 } }, + { new Nest.Range { From = 100, To = 500 } }, + { new Nest.Range { From = 500 } } + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "commit_ranges": { + "range": { + "field": "numberOfCommits", + "ranges": [ + { + "to": 100.0 + }, + { + "from": 100.0, + "to": 500.0 + }, + { + "from": 500.0 + } + ] + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var commitRanges = response.Aggs.Range("commit_ranges"); +commitRanges.Should().NotBeNull(); +commitRanges.Buckets.Count.Should().Be(3); +commitRanges.Buckets.Where(r => r.Key == "*-100.0").FirstOrDefault().Should().NotBeNull(); +commitRanges.Buckets.Where(r => r.Key == "100.0-500.0").FirstOrDefault().Should().NotBeNull(); +commitRanges.Buckets.Where(r => r.Key == "500.0-*").FirstOrDefault().Should().NotBeNull(); +---- + diff --git a/docs/asciidoc/aggregations/bucket/reverse-nested/reverse-nested-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/reverse-nested/reverse-nested-aggregation-usage.asciidoc new file mode 100644 index 00000000000..f51fd140d5a --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/reverse-nested/reverse-nested-aggregation-usage.asciidoc @@ -0,0 +1,111 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[reverse-nested-aggregation-usage]] +== Reverse Nested Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Nested("tags", n => n + .Path(p => p.Tags) + .Aggregations(aa => aa + .Terms("tag_names", t => t + .Field(p => p.Tags.Suffix("name")) + .Aggregations(aaa => aaa + .ReverseNested("tags_to_project", r => r + .Aggregations(aaaa => aaaa + .Terms("top_projects_per_tag", tt => tt + .Field(p => p.Name) + ) + ) + ) + ) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new NestedAggregation("tags") + { + Path = "tags", + Aggregations = new TermsAggregation("tag_names") + { + Field = "tags.name", + Aggregations = new ReverseNestedAggregation("tags_to_project") + { + Aggregations = new TermsAggregation("top_projects_per_tag") + { + Field = Field(p => p.Name) + } + } + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "tags": { + "nested": { + "path": "tags" + }, + "aggs": { + "tag_names": { + "terms": { + "field": "tags.name" + }, + "aggs": { + "tags_to_project": { + "reverse_nested": {}, + "aggs": { + "top_projects_per_tag": { + "terms": { + "field": "name" + } + } + } + } + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var tags = response.Aggs.Nested("tags"); +tags.Should().NotBeNull(); +var tagNames = tags.Terms("tag_names"); +tagNames.Should().NotBeNull(); +tagName.Key.Should().NotBeNullOrEmpty(); +tagName.DocCount.Should().BeGreaterThan(0); +var tagsToProjects = tagName.ReverseNested("tags_to_project"); +tagsToProjects.Should().NotBeNull(); +var topProjectsPerTag = tagsToProjects.Terms("top_projects_per_tag"); +topProjectsPerTag.Should().NotBeNull(); +topProject.Key.Should().NotBeNullOrEmpty(); +topProject.DocCount.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/bucket/sampler/sampler-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/sampler/sampler-aggregation-usage.asciidoc new file mode 100644 index 00000000000..685d639ab54 --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/sampler/sampler-aggregation-usage.asciidoc @@ -0,0 +1,78 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[sampler-aggregation-usage]] +== Sampler Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(aggs => aggs + .Sampler("sample", sm => sm + .ShardSize(200) + .Field(p => p.Name) + .Aggregations(aa => aa + .SignificantTerms("significant_names", st => st + .Field(p => p.Name) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new SamplerAggregation("sample") + { + ShardSize = 200, + Field = "name", + Aggregations = new SignificantTermsAggregation("significant_names") + { + Field = "name" + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "sample": { + "sampler": { + "shard_size": 200, + "field": "name" + }, + "aggs": { + "significant_names": { + "significant_terms": { + "field": "name" + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var sample = response.Aggs.Sampler("sample"); +sample.Should().NotBeNull(); +var sigTags = sample.SignificantTerms("significant_names"); +sigTags.Should().NotBeNull(); +---- + diff --git a/docs/asciidoc/aggregations/bucket/significant-terms/significant-terms-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/significant-terms/significant-terms-aggregation-usage.asciidoc new file mode 100644 index 00000000000..33b6cc3225a --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/significant-terms/significant-terms-aggregation-usage.asciidoc @@ -0,0 +1,74 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[significant-terms-aggregation-usage]] +== Significant Terms Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .SignificantTerms("significant_names", st => st + .Field(p => p.Name) + .MinimumDocumentCount(10) + .MutualInformation(mi => mi + .BackgroundIsSuperSet() + .IncludeNegatives() + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new SignificantTermsAggregation("significant_names") + { + Field = Field(p => p.Name), + MinimumDocumentCount = 10, + MutualInformation = new MutualInformationHeuristic + { + BackgroundIsSuperSet = true, + IncludeNegatives = true + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "significant_names": { + "significant_terms": { + "field": "name", + "min_doc_count": 10, + "mutual_information": { + "background_is_superset": true, + "include_negatives": true + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var sigNames = response.Aggs.SignificantTerms("significant_names"); +sigNames.Should().NotBeNull(); +sigNames.DocCount.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/bucket/terms/terms-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/bucket/terms/terms-aggregation-usage.asciidoc new file mode 100644 index 00000000000..d0ad32dd1af --- /dev/null +++ b/docs/asciidoc/aggregations/bucket/terms/terms-aggregation-usage.asciidoc @@ -0,0 +1,111 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[terms-aggregation-usage]] +== Terms Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Terms("states", st => st + .Field(p => p.State) + .MinimumDocumentCount(2) + .Size(5) + .ShardSize(100) + .ShowTermDocumentCountError() + .ExecutionHint(TermsAggregationExecutionHint.Map) + .Missing("n/a") + .Script("'State of Being: '+_value") + .Order(TermsOrder.TermAscending) + .Order(TermsOrder.CountDescending) + .Meta(m => m + .Add("foo", "bar") + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new TermsAggregation("states") + { + Field = Field(p => p.State), + MinimumDocumentCount = 2, + Size = 5, + ShardSize = 100, + ShowTermDocumentCountError = true, + ExecutionHint = TermsAggregationExecutionHint.Map, + Missing = "n/a", + Script = new InlineScript("'State of Being: '+_value"), + Order = new List + { + TermsOrder.TermAscending, + TermsOrder.CountDescending + }, + Meta = new Dictionary + { + { "foo", "bar" } + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "states": { + "meta": { + "foo": "bar" + }, + "terms": { + "field": "state", + "min_doc_count": 2, + "size": 5, + "shard_size": 100, + "show_term_doc_error_count": true, + "execution_hint": "map", + "missing": "n/a", + "script": { + "inline": "'State of Being: '+_value" + }, + "order": [ + { + "_term": "asc" + }, + { + "_count": "desc" + } + ] + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var states = response.Aggs.Terms("states"); +states.Should().NotBeNull(); +states.DocCountErrorUpperBound.Should().HaveValue(); +states.SumOtherDocCount.Should().HaveValue(); +item.Key.Should().NotBeNullOrEmpty(); +item.DocCount.Should().BeGreaterOrEqualTo(1); +states.Meta.Should().NotBeNull().And.HaveCount(1); +states.Meta["foo"].Should().Be("bar"); +---- + diff --git a/docs/asciidoc/aggregations/metric/average/average-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/average/average-aggregation-usage.asciidoc new file mode 100644 index 00000000000..734b5e8cba7 --- /dev/null +++ b/docs/asciidoc/aggregations/metric/average/average-aggregation-usage.asciidoc @@ -0,0 +1,77 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[average-aggregation-usage]] +== Average Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Average("average_commits", avg => avg + .Meta(m => m + .Add("foo", "bar") + ) + .Field(p => p.NumberOfCommits) + .Missing(10) + .Script("_value * 1.2") + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new AverageAggregation("average_commits", Field(p => p.NumberOfCommits)) + { + Meta = new Dictionary + { + { "foo", "bar" } + }, + Missing = 10, + Script = new InlineScript("_value * 1.2") + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "average_commits": { + "meta": { + "foo": "bar" + }, + "avg": { + "field": "numberOfCommits", + "missing": 10.0, + "script": { + "inline": "_value * 1.2" + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var commitsAvg = response.Aggs.Average("average_commits"); +commitsAvg.Should().NotBeNull(); +commitsAvg.Value.Should().BeGreaterThan(0); +commitsAvg.Meta.Should().NotBeNull().And.HaveCount(1); +commitsAvg.Meta["foo"].Should().Be("bar"); +---- + diff --git a/docs/asciidoc/aggregations/metric/cardinality/cardinality-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/cardinality/cardinality-aggregation-usage.asciidoc new file mode 100644 index 00000000000..fb0810351cc --- /dev/null +++ b/docs/asciidoc/aggregations/metric/cardinality/cardinality-aggregation-usage.asciidoc @@ -0,0 +1,60 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[cardinality-aggregation-usage]] +== Cardinality Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Cardinality("state_count", c => c + .Field(p => p.State) + .PrecisionThreshold(100) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new CardinalityAggregation("state_count", Field(p => p.State)) + { + PrecisionThreshold = 100 + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "state_count": { + "cardinality": { + "field": "state", + "precision_threshold": 100 + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectCount = response.Aggs.Cardinality("state_count"); +projectCount.Should().NotBeNull(); +projectCount.Value.Should().Be(3); +---- + diff --git a/docs/asciidoc/aggregations/metric/extended-stats/extended-stats-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/extended-stats/extended-stats-aggregation-usage.asciidoc new file mode 100644 index 00000000000..fa6c851ad10 --- /dev/null +++ b/docs/asciidoc/aggregations/metric/extended-stats/extended-stats-aggregation-usage.asciidoc @@ -0,0 +1,64 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[extended-stats-aggregation-usage]] +== Extended Stats Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .ExtendedStats("commit_stats", es => es + .Field(p => p.NumberOfCommits) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new ExtendedStatsAggregation("commit_stats", Field(p => p.NumberOfCommits)) +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "commit_stats": { + "extended_stats": { + "field": "numberOfCommits" + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var commitStats = response.Aggs.ExtendedStats("commit_stats"); +commitStats.Should().NotBeNull(); +commitStats.Average.Should().BeGreaterThan(0); +commitStats.Max.Should().BeGreaterThan(0); +commitStats.Min.Should().BeGreaterThan(0); +commitStats.Count.Should().BeGreaterThan(0); +commitStats.Sum.Should().BeGreaterThan(0); +commitStats.SumOfSquares.Should().BeGreaterThan(0); +commitStats.StdDeviation.Should().BeGreaterThan(0); +commitStats.StdDeviationBounds.Should().NotBeNull(); +commitStats.StdDeviationBounds.Upper.Should().BeGreaterThan(0); +commitStats.StdDeviationBounds.Lower.Should().NotBe(0); +---- + diff --git a/docs/asciidoc/aggregations/metric/geo-bounds/geo-bounds-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/geo-bounds/geo-bounds-aggregation-usage.asciidoc new file mode 100644 index 00000000000..dcc46cf5830 --- /dev/null +++ b/docs/asciidoc/aggregations/metric/geo-bounds/geo-bounds-aggregation-usage.asciidoc @@ -0,0 +1,72 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-bounds-aggregation-usage]] +== Geo Bounds Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .GeoBounds("viewport", gb => gb + .Field(p => p.Location) + .WrapLongitude(true) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new GeoBoundsAggregation("viewport", Field(p => p.Location)) + { + WrapLongitude = true + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "viewport": { + "geo_bounds": { + "field": "location", + "wrap_longitude": true + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var viewport = response.Aggs.GeoBounds("viewport"); +viewport.Should().NotBeNull(); +viewport.Bounds.Should().NotBeNull(); +var bottomRight = viewport.Bounds.BottomRight; +bottomRight.Should().NotBeNull(); +bottomRight.Lat.Should().HaveValue(); +GeoLocation.IsValidLatitude(bottomRight.Lat.Value).Should().BeTrue(); +bottomRight.Lon.Should().HaveValue(); +GeoLocation.IsValidLongitude(bottomRight.Lon.Value).Should().BeTrue(); +var topLeft = viewport.Bounds.TopLeft; +topLeft.Should().NotBeNull(); +topLeft.Lat.Should().HaveValue(); +GeoLocation.IsValidLatitude(topLeft.Lat.Value).Should().BeTrue(); +topLeft.Lon.Should().HaveValue(); +GeoLocation.IsValidLongitude(topLeft.Lon.Value).Should().BeTrue(); +---- + diff --git a/docs/asciidoc/aggregations/metric/max/max-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/max/max-aggregation-usage.asciidoc new file mode 100644 index 00000000000..d015404711c --- /dev/null +++ b/docs/asciidoc/aggregations/metric/max/max-aggregation-usage.asciidoc @@ -0,0 +1,55 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[max-aggregation-usage]] +== Max Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Max("max_commits", m => m + .Field(p => p.NumberOfCommits) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new MaxAggregation("max_commits", Field(p => p.NumberOfCommits)) +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "max_commits": { + "max": { + "field": "numberOfCommits" + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var max = response.Aggs.Max("max_commits"); +max.Should().NotBeNull(); +max.Value.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/metric/min/min-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/min/min-aggregation-usage.asciidoc new file mode 100644 index 00000000000..4212daf58f2 --- /dev/null +++ b/docs/asciidoc/aggregations/metric/min/min-aggregation-usage.asciidoc @@ -0,0 +1,55 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[min-aggregation-usage]] +== Min Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Min("min_commits", m => m + .Field(p => p.NumberOfCommits) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new MinAggregation("min_commits", Field(p => p.NumberOfCommits)) +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "min_commits": { + "min": { + "field": "numberOfCommits" + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var min = response.Aggs.Max("min_commits"); +min.Should().NotBeNull(); +min.Value.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/metric/percentile-ranks/percentile-ranks-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/percentile-ranks/percentile-ranks-aggregation-usage.asciidoc new file mode 100644 index 00000000000..537a99e3ec5 --- /dev/null +++ b/docs/asciidoc/aggregations/metric/percentile-ranks/percentile-ranks-aggregation-usage.asciidoc @@ -0,0 +1,84 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[percentile-ranks-aggregation-usage]] +== Percentile Ranks Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .PercentileRanks("commits_outlier", pr => pr + .Field(p => p.NumberOfCommits) + .Values(15, 30) + .Method(m => m + .TDigest(td => td + .Compression(200) + ) + ) + .Script("doc['numberOfCommits'].value * 1.2") + .Missing(0) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new PercentileRanksAggregation("commits_outlier", Field(p => p.NumberOfCommits)) + { + Values = new List { 15, 30 }, + Method = new TDigestMethod + { + Compression = 200 + }, + Script = (InlineScript)"doc['numberOfCommits'].value * 1.2", + Missing = 0 + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "commits_outlier": { + "percentile_ranks": { + "field": "numberOfCommits", + "values": [ + 15.0, + 30.0 + ], + "tdigest": { + "compression": 200.0 + }, + "script": { + "inline": "doc['numberOfCommits'].value * 1.2" + }, + "missing": 0.0 + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var commitsOutlier = response.Aggs.PercentileRanks("commits_outlier"); +commitsOutlier.Should().NotBeNull(); +commitsOutlier.Items.Should().NotBeNullOrEmpty(); +item.Should().NotBeNull(); +---- + diff --git a/docs/asciidoc/aggregations/metric/percentiles/percentiles-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/percentiles/percentiles-aggregation-usage.asciidoc new file mode 100644 index 00000000000..34d361d36f5 --- /dev/null +++ b/docs/asciidoc/aggregations/metric/percentiles/percentiles-aggregation-usage.asciidoc @@ -0,0 +1,85 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[percentiles-aggregation-usage]] +== Percentiles Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Percentiles("commits_outlier", pr => pr + .Field(p => p.NumberOfCommits) + .Percents(95, 99, 99.9) + .Method(m => m + .HDRHistogram(hdr => hdr + .NumberOfSignificantValueDigits(3) + ) + ) + .Script("doc['numberOfCommits'].value * 1.2") + .Missing(0) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new PercentilesAggregation("commits_outlier", Field(p => p.NumberOfCommits)) + { + Percents = new[] { 95, 99, 99.9 }, + Method = new HDRHistogramMethod + { + NumberOfSignificantValueDigits = 3 + }, + Script = new InlineScript("doc['numberOfCommits'].value * 1.2"), + Missing = 0 + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "commits_outlier": { + "percentiles": { + "field": "numberOfCommits", + "percents": [ + 95.0, + 99.0, + 99.9 + ], + "hdr": { + "number_of_significant_value_digits": 3 + }, + "script": { + "inline": "doc['numberOfCommits'].value * 1.2" + }, + "missing": 0.0 + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var commitsOutlier = response.Aggs.Percentiles("commits_outlier"); +commitsOutlier.Should().NotBeNull(); +commitsOutlier.Items.Should().NotBeNullOrEmpty(); +item.Value.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/metric/scripted-metric/scripted-metric-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/scripted-metric/scripted-metric-aggregation-usage.asciidoc new file mode 100644 index 00000000000..b84ed678d92 --- /dev/null +++ b/docs/asciidoc/aggregations/metric/scripted-metric/scripted-metric-aggregation-usage.asciidoc @@ -0,0 +1,75 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[scripted-metric-aggregation-usage]] +== Scripted Metric Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .ScriptedMetric("sum_the_hard_way", sm => sm + .InitScript("_agg['commits'] = []") + .MapScript("if (doc['state'].value == \"Stable\") { _agg.commits.add(doc['numberOfCommits']) }") + .CombineScript("sum = 0; for (c in _agg.commits) { sum += c }; return sum") + .ReduceScript("sum = 0; for (a in _aggs) { sum += a }; return sum") + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new ScriptedMetricAggregation("sum_the_hard_way") + { + InitScript = new InlineScript("_agg['commits'] = []"), + MapScript = new InlineScript("if (doc['state'].value == \"Stable\") { _agg.commits.add(doc['numberOfCommits']) }"), + CombineScript = new InlineScript("sum = 0; for (c in _agg.commits) { sum += c }; return sum"), + ReduceScript = new InlineScript("sum = 0; for (a in _aggs) { sum += a }; return sum") + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "sum_the_hard_way": { + "scripted_metric": { + "init_script": { + "inline": "_agg['commits'] = []" + }, + "map_script": { + "inline": "if (doc['state'].value == \"Stable\") { _agg.commits.add(doc['numberOfCommits']) }" + }, + "combine_script": { + "inline": "sum = 0; for (c in _agg.commits) { sum += c }; return sum" + }, + "reduce_script": { + "inline": "sum = 0; for (a in _aggs) { sum += a }; return sum" + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var sumTheHardWay = response.Aggs.ScriptedMetric("sum_the_hard_way"); +sumTheHardWay.Should().NotBeNull(); +sumTheHardWay.Value().Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/metric/stats/stats-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/stats/stats-aggregation-usage.asciidoc new file mode 100644 index 00000000000..a74cb818864 --- /dev/null +++ b/docs/asciidoc/aggregations/metric/stats/stats-aggregation-usage.asciidoc @@ -0,0 +1,59 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[stats-aggregation-usage]] +== Stats Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Stats("commit_stats", st => st + .Field(p => p.NumberOfCommits) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new StatsAggregation("commit_stats", Field(p => p.NumberOfCommits)) +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "commit_stats": { + "stats": { + "field": "numberOfCommits" + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var commitStats = response.Aggs.Stats("commit_stats"); +commitStats.Should().NotBeNull(); +commitStats.Average.Should().BeGreaterThan(0); +commitStats.Max.Should().BeGreaterThan(0); +commitStats.Min.Should().BeGreaterThan(0); +commitStats.Count.Should().BeGreaterThan(0); +commitStats.Sum.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/metric/sum/sum-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/sum/sum-aggregation-usage.asciidoc new file mode 100644 index 00000000000..2fbce89b3bb --- /dev/null +++ b/docs/asciidoc/aggregations/metric/sum/sum-aggregation-usage.asciidoc @@ -0,0 +1,55 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[sum-aggregation-usage]] +== Sum Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Sum("commits_sum", sm => sm + .Field(p => p.NumberOfCommits) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new SumAggregation("commits_sum", Field(p => p.NumberOfCommits)) +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "commits_sum": { + "sum": { + "field": "numberOfCommits" + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var commitsSum = response.Aggs.Sum("commits_sum"); +commitsSum.Should().NotBeNull(); +commitsSum.Value.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/metric/top-hits/top-hits-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/top-hits/top-hits-aggregation-usage.asciidoc new file mode 100644 index 00000000000..df0b796d11c --- /dev/null +++ b/docs/asciidoc/aggregations/metric/top-hits/top-hits-aggregation-usage.asciidoc @@ -0,0 +1,171 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[top-hits-aggregation-usage]] +== Top Hits Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .Terms("states", t => t + .Field(p => p.State) + .Aggregations(aa => aa + .TopHits("top_state_hits", th => th + .Sort(srt => srt + .Field(p => p.StartedOn) + .Order(SortOrder.Descending) + ) + .Source(src => src + .Include(fs => fs + .Field(p => p.Name) + .Field(p => p.StartedOn) + ) + ) + .Size(1) + .Version() + .Explain() + .FielddataFields(fd => fd + .Field(p => p.State) + .Field(p => p.NumberOfCommits) + ) + .Highlight(h => h + .Fields( + hf => hf.Field(p => p.Tags), + hf => hf.Field(p => p.Description) + ) + ) + .ScriptFields(sfs => sfs + .ScriptField("commit_factor", sf => sf + .Inline("doc['numberOfCommits'].value * 2") + ) + ) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new TermsAggregation("states") + { + Field = Field(p => p.State), + Aggregations = new TopHitsAggregation("top_state_hits") + { + Sort = new List + { + { + new SortField { Field = Field(p => p.StartedOn), Order = SortOrder.Descending } + } + }, + Source = new SourceFilter + { + Include = new [] { "name", "startedOn" } + }, + Size = 1, + Version = true, + Explain = true, + FielddataFields = new [] { "state", "numberOfCommits" }, + Highlight = new Highlight + { + Fields = new Dictionary + { + { Field(p => p.Tags), new HighlightField() }, + { Field(p => p.Description), new HighlightField() } + } + }, + ScriptFields = new ScriptFields + { + { "commit_factor", new ScriptField { Script = new InlineScript("doc['numberOfCommits'].value * 2") } } + } + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "states": { + "terms": { + "field": "state" + }, + "aggs": { + "top_state_hits": { + "top_hits": { + "sort": [ + { + "startedOn": { + "order": "desc" + } + } + ], + "_source": { + "include": [ + "name", + "startedOn" + ] + }, + "size": 1, + "version": true, + "explain": true, + "fielddata_fields": [ + "state", + "numberOfCommits" + ], + "highlight": { + "fields": { + "tags": {}, + "description": {} + } + }, + "script_fields": { + "commit_factor": { + "script": { + "inline": "doc['numberOfCommits'].value * 2" + } + } + } + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var states = response.Aggs.Terms("states"); +states.Should().NotBeNull(); +states.Buckets.Should().NotBeNullOrEmpty(); +state.Key.Should().NotBeNullOrEmpty(); +state.DocCount.Should().BeGreaterThan(0); +var topStateHits = state.TopHits("top_state_hits"); +topStateHits.Should().NotBeNull(); +topStateHits.Total.Should().BeGreaterThan(0); +var hits = topStateHits.Hits(); +hits.Should().NotBeNullOrEmpty(); +hits.All(h => h.Explanation != null).Should().BeTrue(); +hits.All(h => h.Version.HasValue).Should().BeTrue(); +hits.All(h => h.Fields.ValuesOf("state").Any()).Should().BeTrue(); +hits.All(h => h.Fields.ValuesOf("numberOfCommits").Any()).Should().BeTrue(); +hits.All(h => h.Fields.ValuesOf("commit_factor").Any()).Should().BeTrue(); +topStateHits.Documents().Should().NotBeEmpty(); +---- + diff --git a/docs/asciidoc/aggregations/metric/value-count/value-count-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/metric/value-count/value-count-aggregation-usage.asciidoc new file mode 100644 index 00000000000..543960f3202 --- /dev/null +++ b/docs/asciidoc/aggregations/metric/value-count/value-count-aggregation-usage.asciidoc @@ -0,0 +1,55 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[value-count-aggregation-usage]] +== Value Count Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(a => a + .ValueCount("commit_count", c => c + .Field(p => p.NumberOfCommits) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new ValueCountAggregation("commit_count", Field(p => p.NumberOfCommits)) +} +---- + +[source,javascript] +.Example json output +---- +{ + "aggs": { + "commit_count": { + "value_count": { + "field": "numberOfCommits" + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var commitCount = response.Aggs.ValueCount("commit_count"); +commitCount.Should().NotBeNull(); +commitCount.Value.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/pipeline/average-bucket/average-bucket-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/average-bucket/average-bucket-aggregation-usage.asciidoc new file mode 100644 index 00000000000..5627246d18e --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/average-bucket/average-bucket-aggregation-usage.asciidoc @@ -0,0 +1,95 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[average-bucket-aggregation-usage]] +== Average Bucket Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + ) + ) + .AverageBucket("average_commits_per_month", aaa => aaa + .BucketsPath("projects_started_per_month>commits") + .GapPolicy(GapPolicy.InsertZeros) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = new SumAggregation("commits", "numberOfCommits") + } + && new AverageBucketAggregation("average_commits_per_month", "projects_started_per_month>commits") + { + GapPolicy = GapPolicy.InsertZeros + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + } + } + }, + "average_commits_per_month": { + "avg_bucket": { + "buckets_path": "projects_started_per_month>commits", + "gap_policy": "insert_zeros" + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var averageCommits = response.Aggs.AverageBucket("average_commits_per_month"); +averageCommits.Should().NotBeNull(); +averageCommits.Value.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/pipeline/bucket-script/bucket-script-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/bucket-script/bucket-script-aggregation-usage.asciidoc new file mode 100644 index 00000000000..5667d998671 --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/bucket-script/bucket-script-aggregation-usage.asciidoc @@ -0,0 +1,143 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[bucket-script-aggregation-usage]] +== Bucket Script Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + .Filter("stable_state", f => f + .Filter(ff => ff + .Term(p => p.State, "Stable") + ) + .Aggregations(aaa => aaa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + ) + ) + .BucketScript("stable_percentage", bs => bs + .BucketsPath(bp => bp + .Add("totalCommits", "commits") + .Add("stableCommits", "stable_state>commits") + ) + .Script("stableCommits / totalCommits * 100") + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = + new SumAggregation("commits", "numberOfCommits") && + new FilterAggregation("stable_state") + { + Filter = new TermQuery + { + Field = "state", + Value = "Stable" + }, + Aggregations = new SumAggregation("commits", "numberOfCommits") + } && + new BucketScriptAggregation("stable_percentage", new MultiBucketsPath + { + { "totalCommits", "commits" }, + { "stableCommits", "stable_state>commits" } + }) + { + Script = (InlineScript)"stableCommits / totalCommits * 100" + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + }, + "stable_state": { + "filter": { + "term": { + "state": { + "value": "Stable" + } + } + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + } + } + }, + "stable_percentage": { + "bucket_script": { + "buckets_path": { + "totalCommits": "commits", + "stableCommits": "stable_state>commits" + }, + "script": { + "inline": "stableCommits / totalCommits * 100" + } + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var stablePercentage = item.BucketScript("stable_percentage"); +stablePercentage.Should().NotBeNull(); +stablePercentage.Value.Should().HaveValue(); +---- + diff --git a/docs/asciidoc/aggregations/pipeline/bucket-selector/bucket-selector-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/bucket-selector/bucket-selector-aggregation-usage.asciidoc new file mode 100644 index 00000000000..bf611810360 --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/bucket-selector/bucket-selector-aggregation-usage.asciidoc @@ -0,0 +1,105 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[bucket-selector-aggregation-usage]] +== Bucket Selector Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + .BucketSelector("commits_bucket_filter", bs => bs + .BucketsPath(bp => bp + .Add("totalCommits", "commits") + ) + .Script("totalCommits >= 500") + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = + new SumAggregation("commits", "numberOfCommits") && + new BucketSelectorAggregation("commits_bucket_filter", new MultiBucketsPath + { + { "totalCommits", "commits" }, + }) + { + Script = (InlineScript)"totalCommits >= 500" + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + }, + "commits_bucket_filter": { + "bucket_selector": { + "buckets_path": { + "totalCommits": "commits" + }, + "script": { + "inline": "totalCommits >= 500" + } + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var commits = item.Sum("commits"); +commits.Should().NotBeNull(); +commits.Value.Should().BeGreaterOrEqualTo(500); +---- + diff --git a/docs/asciidoc/aggregations/pipeline/cumulative-sum/cumulative-sum-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/cumulative-sum/cumulative-sum-aggregation-usage.asciidoc new file mode 100644 index 00000000000..c3e67635a82 --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/cumulative-sum/cumulative-sum-aggregation-usage.asciidoc @@ -0,0 +1,91 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[cumulative-sum-aggregation-usage]] +== Cumulative Sum Aggregation Usage + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var commitsDerivative = item.Derivative("cumulative_commits"); +commitsDerivative.Should().NotBeNull(); +commitsDerivative.Value.Should().NotBe(null); +---- + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + .CumulativeSum("cumulative_commits", d => d + .BucketsPath("commits") + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = + new SumAggregation("commits", "numberOfCommits") && + new CumulativeSumAggregation("cumulative_commits", "commits") + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + }, + "cumulative_commits": { + "cumulative_sum": { + "buckets_path": "commits" + } + } + } + } + } +} +---- + diff --git a/docs/asciidoc/aggregations/pipeline/derivative/derivative-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/derivative/derivative-aggregation-usage.asciidoc new file mode 100644 index 00000000000..4731cc39da5 --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/derivative/derivative-aggregation-usage.asciidoc @@ -0,0 +1,91 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[derivative-aggregation-usage]] +== Derivative Aggregation Usage + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var commitsDerivative = item.Derivative("commits_derivative"); +commitsDerivative.Should().NotBeNull(); +commitsDerivative.Value.Should().NotBe(null); +---- + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + .Derivative("commits_derivative", d => d + .BucketsPath("commits") + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = + new SumAggregation("commits", "numberOfCommits") && + new DerivativeAggregation("commits_derivative", "commits") + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + }, + "commits_derivative": { + "derivative": { + "buckets_path": "commits" + } + } + } + } + } +} +---- + diff --git a/docs/asciidoc/aggregations/pipeline/max-bucket/max-bucket-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/max-bucket/max-bucket-aggregation-usage.asciidoc new file mode 100644 index 00000000000..fcfa9117d4d --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/max-bucket/max-bucket-aggregation-usage.asciidoc @@ -0,0 +1,93 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[max-bucket-aggregation-usage]] +== Max Bucket Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + ) + ) + .MaxBucket("max_commits_per_month", aaa => aaa + .BucketsPath("projects_started_per_month>commits") + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = new SumAggregation("commits", "numberOfCommits") + } + && new MaxBucketAggregation("max_commits_per_month", "projects_started_per_month>commits") +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + } + } + }, + "max_commits_per_month": { + "max_bucket": { + "buckets_path": "projects_started_per_month>commits" + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var maxCommits = response.Aggs.MaxBucket("max_commits_per_month"); +maxCommits.Should().NotBeNull(); +maxCommits.Value.Should().BeGreaterThan(0); +maxCommits.Keys.Should().NotBeNull(); +maxCommits.Keys.Count.Should().BeGreaterOrEqualTo(1); +key.Should().NotBeNull(); +---- + diff --git a/docs/asciidoc/aggregations/pipeline/min-bucket/min-bucket-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/min-bucket/min-bucket-aggregation-usage.asciidoc new file mode 100644 index 00000000000..ac8d91496e8 --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/min-bucket/min-bucket-aggregation-usage.asciidoc @@ -0,0 +1,93 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[min-bucket-aggregation-usage]] +== Min Bucket Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + ) + ) + .MinBucket("min_commits_per_month", aaa => aaa + .BucketsPath("projects_started_per_month>commits") + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = new SumAggregation("commits", "numberOfCommits") + } + && new MinBucketAggregation("min_commits_per_month", "projects_started_per_month>commits") +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + } + } + }, + "min_commits_per_month": { + "min_bucket": { + "buckets_path": "projects_started_per_month>commits" + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var minCommits = response.Aggs.MinBucket("min_commits_per_month"); +minCommits.Should().NotBeNull(); +minCommits.Value.Should().BeGreaterThan(0); +minCommits.Keys.Should().NotBeNull(); +minCommits.Keys.Count.Should().BeGreaterOrEqualTo(1); +key.Should().NotBeNullOrEmpty(); +---- + diff --git a/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-ewma-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-ewma-aggregation-usage.asciidoc new file mode 100644 index 00000000000..74a76fe555a --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-ewma-aggregation-usage.asciidoc @@ -0,0 +1,106 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[moving-average-ewma-aggregation-usage]] +== Moving Average Ewma Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + .MovingAverage("commits_moving_avg", mv => mv + .BucketsPath("commits") + .Model(m => m + .Ewma(e => e + .Alpha(0.3f) + ) + ) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = + new SumAggregation("commits", "numberOfCommits") && + new MovingAverageAggregation("commits_moving_avg", "commits") + { + Model = new EwmaModel + { + Alpha = 0.3f, + } + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + }, + "commits_moving_avg": { + "moving_avg": { + "buckets_path": "commits", + "model": "ewma", + "settings": { + "alpha": 0.3 + } + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var movingAvg = item.MovingAverage("commits_moving_avg"); +movingAvg.Should().NotBeNull(); +movingAvg.Value.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-holt-linear-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-holt-linear-aggregation-usage.asciidoc new file mode 100644 index 00000000000..0d93a7c0880 --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-holt-linear-aggregation-usage.asciidoc @@ -0,0 +1,109 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[moving-average-holt-linear-aggregation-usage]] +== Moving Average Holt Linear Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + .MovingAverage("commits_moving_avg", mv => mv + .BucketsPath("commits") + .Model(m => m + .HoltLinear(hl => hl + .Alpha(0.5f) + .Beta(0.5f) + ) + ) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = + new SumAggregation("commits", "numberOfCommits") && + new MovingAverageAggregation("commits_moving_avg", "commits") + { + Model = new HoltLinearModel + { + Alpha = 0.5f, + Beta = 0.5f, + } + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + }, + "commits_moving_avg": { + "moving_avg": { + "buckets_path": "commits", + "model": "holt", + "settings": { + "alpha": 0.5, + "beta": 0.5 + } + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var movingAvg = item.MovingAverage("commits_moving_avg"); +movingAvg.Should().NotBeNull(); +movingAvg.Value.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-holt-winters-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-holt-winters-aggregation-usage.asciidoc new file mode 100644 index 00000000000..21eeddf84d0 --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-holt-winters-aggregation-usage.asciidoc @@ -0,0 +1,117 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[moving-average-holt-winters-aggregation-usage]] +== Moving Average Holt Winters Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + .MovingAverage("commits_moving_avg", mv => mv + .BucketsPath("commits") + .Window(60) + .Model(m => m + .HoltWinters(hw => hw + .Type(HoltWintersType.Multiplicative) + .Alpha(0.5f) + .Beta(0.5f) + .Gamma(0.5f) + .Period(30) + .Pad(false) + ) + ) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = + new SumAggregation("commits", "numberOfCommits") && + new MovingAverageAggregation("commits_moving_avg", "commits") + { + Window = 60, + Model = new HoltWintersModel + { + Type = HoltWintersType.Multiplicative, + Alpha = 0.5f, + Beta = 0.5f, + Gamma = 0.5f, + Period = 30, + Pad = false + } + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + }, + "commits_moving_avg": { + "moving_avg": { + "buckets_path": "commits", + "window": 60, + "model": "holt_winters", + "settings": { + "type": "mult", + "alpha": 0.5, + "beta": 0.5, + "gamma": 0.5, + "period": 30, + "pad": false + } + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +---- + diff --git a/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-linear-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-linear-aggregation-usage.asciidoc new file mode 100644 index 00000000000..0e0a76591ff --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-linear-aggregation-usage.asciidoc @@ -0,0 +1,102 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[moving-average-linear-aggregation-usage]] +== Moving Average Linear Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + .MovingAverage("commits_moving_avg", mv => mv + .BucketsPath("commits") + .GapPolicy(GapPolicy.InsertZeros) + .Model(m => m + .Linear() + ) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = + new SumAggregation("commits", "numberOfCommits") && + new MovingAverageAggregation("commits_moving_avg", "commits") + { + GapPolicy = GapPolicy.InsertZeros, + Model = new LinearModel() + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + }, + "commits_moving_avg": { + "moving_avg": { + "buckets_path": "commits", + "gap_policy": "insert_zeros", + "model": "linear", + "settings": {} + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var movingAvg = item.MovingAverage("commits_moving_avg"); +movingAvg.Should().NotBeNull(); +movingAvg.Value.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-simple-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-simple-aggregation-usage.asciidoc new file mode 100644 index 00000000000..0081d2d43a7 --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/moving-average/moving-average-simple-aggregation-usage.asciidoc @@ -0,0 +1,105 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[moving-average-simple-aggregation-usage]] +== Moving Average Simple Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + .MovingAverage("commits_moving_avg", mv => mv + .BucketsPath("commits") + .Window(30) + .Predict(10) + .Model(m => m + .Simple() + ) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = + new SumAggregation("commits", "numberOfCommits") && + new MovingAverageAggregation("commits_moving_avg", "commits") + { + Window = 30, + Predict = 10, + Model = new SimpleModel() + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + }, + "commits_moving_avg": { + "moving_avg": { + "buckets_path": "commits", + "model": "simple", + "window": 30, + "predict": 10, + "settings": {} + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var movingAvg = item.Sum("commits_moving_avg"); +movingAvg.Should().NotBeNull(); +movingAvg.Value.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/pipeline/serial-differencing/serial-differencing-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/serial-differencing/serial-differencing-aggregation-usage.asciidoc new file mode 100644 index 00000000000..9970836ed9f --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/serial-differencing/serial-differencing-aggregation-usage.asciidoc @@ -0,0 +1,96 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[serial-differencing-aggregation-usage]] +== Serial Differencing Aggregation Usage + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var commits = item.Sum("commits"); +commits.Should().NotBeNull(); +commits.Value.Should().NotBe(null); +---- + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + .SerialDifferencing("thirtieth_difference", d => d + .BucketsPath("commits") + .Lag(30) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = + new SumAggregation("commits", "numberOfCommits") && + new SerialDifferencingAggregation("thirtieth_difference", "commits") + { + Lag = 30 + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + }, + "thirtieth_difference": { + "serial_diff": { + "buckets_path": "commits", + "lag": 30 + } + } + } + } + } +} +---- + diff --git a/docs/asciidoc/aggregations/pipeline/sum-bucket/sum-bucket-aggregation-usage.asciidoc b/docs/asciidoc/aggregations/pipeline/sum-bucket/sum-bucket-aggregation-usage.asciidoc new file mode 100644 index 00000000000..be50897a8cf --- /dev/null +++ b/docs/asciidoc/aggregations/pipeline/sum-bucket/sum-bucket-aggregation-usage.asciidoc @@ -0,0 +1,90 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[sum-bucket-aggregation-usage]] +== Sum Bucket Aggregation Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Size(0) +.Aggregations(a => a + .DateHistogram("projects_started_per_month", dh => dh + .Field(p => p.StartedOn) + .Interval(DateInterval.Month) + .Aggregations(aa => aa + .Sum("commits", sm => sm + .Field(p => p.NumberOfCommits) + ) + ) + ) + .SumBucket("sum_of_commits", aaa => aaa + .BucketsPath("projects_started_per_month>commits") + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + Size = 0, + Aggregations = new DateHistogramAggregation("projects_started_per_month") + { + Field = "startedOn", + Interval = DateInterval.Month, + Aggregations = new SumAggregation("commits", "numberOfCommits") + } + && new SumBucketAggregation("sum_of_commits", "projects_started_per_month>commits") +} +---- + +[source,javascript] +.Example json output +---- +{ + "size": 0, + "aggs": { + "projects_started_per_month": { + "date_histogram": { + "field": "startedOn", + "interval": "month" + }, + "aggs": { + "commits": { + "sum": { + "field": "numberOfCommits" + } + } + } + }, + "sum_of_commits": { + "sum_bucket": { + "buckets_path": "projects_started_per_month>commits" + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +var projectsPerMonth = response.Aggs.DateHistogram("projects_started_per_month"); +projectsPerMonth.Should().NotBeNull(); +projectsPerMonth.Buckets.Should().NotBeNull(); +projectsPerMonth.Buckets.Count.Should().BeGreaterThan(0); +var commitsSum = response.Aggs.SumBucket("sum_of_commits"); +commitsSum.Should().NotBeNull(); +commitsSum.Value.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/aggregations/writing-aggregations.asciidoc b/docs/asciidoc/aggregations/writing-aggregations.asciidoc new file mode 100644 index 00000000000..1473318d012 --- /dev/null +++ b/docs/asciidoc/aggregations/writing-aggregations.asciidoc @@ -0,0 +1,156 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[writing-aggregations]] +== Writing Aggregations + +NEST allows you to write your aggregations using + +* a strict fluent DSL + +* a verbatim object initializer syntax that maps verbatim to the Elasticsearch API + +* a more terse object initializer aggregation DSL + +Three different ways, yikes that's a lot to take in! Lets go over them one by one and explain when you might +want to use each. + +This is the json output for each example + +=== Fluent DSL + +The fluent lambda syntax is the most terse way to write aggregations. +It benefits from types that are carried over to sub aggregations + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Aggregations(aggs => aggs + .Children("name_of_child_agg", child => child + .Aggregations(childAggs => childAggs + .Average("average_per_child", avg => avg.Field(p => p.ConfidenceFactor)) + .Max("max_per_child", avg => avg.Field(p => p.ConfidenceFactor)) + ) + ) +) +---- + +=== Object Initializer syntax + +The object initializer syntax (OIS) is a one-to-one mapping with how aggregations +have to be represented in the Elasticsearch API. While it has the benefit of being a one-to-one +mapping, being dictionary based in C# means it can grow exponentially in complexity rather quickly. + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(CommitActivity)) + { + Aggregations = + new AverageAggregation("average_per_child", "confidenceFactor") + && new MaxAggregation("max_per_child", "confidenceFactor") + } +} +---- + +=== Terse Object Initializer DSL + +For this reason the OIS syntax can be shortened dramatically by using `*Agg` related family, +These allow you to forego introducing intermediary Dictionaries to represent the aggregation DSL. +It also allows you to combine multiple aggregations using bitwise AND `&&`) operator. + +Compare the following example with the previous vanilla OIS syntax + +[source,csharp] +---- +new SearchRequest +{ + Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(CommitActivity)) + { + Aggregations = + new AverageAggregation("average_per_child", Field(p => p.ConfidenceFactor)) + && new MaxAggregation("max_per_child", Field(p => p.ConfidenceFactor)) + } +} +---- + +=== Aggregating over a collection of aggregations + +An advanced scenario may involve an existing collection of aggregation functions that should be set as aggregations +on the request. Using LINQ's `.Aggregate()` method, each function can be applied to the aggregation descriptor +`childAggs` below) in turn, returning the descriptor after each function application. + +[source,csharp] +---- +var aggregations = new List, IAggregationContainer>> <1> +{ + a => a.Average("average_per_child", avg => avg.Field(p => p.ConfidenceFactor)), + a => a.Max("max_per_child", avg => avg.Field(p => p.ConfidenceFactor)) +}; +return s => s + .Aggregations(aggs => aggs + .Children("name_of_child_agg", child => child + .Aggregations(childAggs => + aggregations.Aggregate(childAggs, (acc, agg) => { agg(acc); return acc; }) <2> + ) + ) + ); +---- +<1> a list of aggregation functions to apply + +<2> Using LINQ's `Aggregate()` function to accumulate/apply all of the aggregation functions + +[[aggs-vs-aggregations]] +=== Aggs vs. Aggregations + +The response exposes both `.Aggregations` and `.Aggs` properties for handling aggregations. Why two properties you ask? +Well, the former is a dictionary of aggregation names to `IAggregate` types, a common interface for +aggregation responses (termed __Aggregates__ in NEST), and the latter is a convenience helper to get the right type +of aggregation response out of the dictionary based on a key name. + +This is better illustrated with an example + +Let's imagine we make the following request. + +[source,csharp] +---- +s => s +.Aggregations(aggs => aggs + .Children("name_of_child_agg", child => child + .Aggregations(childAggs => childAggs + .Average("average_per_child", avg => avg.Field(p => p.ConfidenceFactor)) + .Max("max_per_child", avg => avg.Field(p => p.ConfidenceFactor)) + ) + ) +) +---- + +=== Aggs usage + +Now, using `.Aggs`, we can easily get the `Children` aggregation response out and from that, +the `Average` and `Max` sub aggregations. + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); + +var childAggregation = response.Aggs.Children("name_of_child_agg"); + +var averagePerChild = childAggregation.Average("average_per_child"); + +averagePerChild.Should().NotBeNull(); <1> + +var maxPerChild = childAggregation.Max("max_per_child"); + +maxPerChild.Should().NotBeNull(); <2> +---- +<1> Do something with the average per child. Here we just assert it's not null + +<2> Do something with the max per child. Here we just assert it's not null + diff --git a/docs/asciidoc/analysis/analyzers/analyzer-usage.asciidoc b/docs/asciidoc/analysis/analyzers/analyzer-usage.asciidoc new file mode 100644 index 00000000000..223b62e0370 --- /dev/null +++ b/docs/asciidoc/analysis/analyzers/analyzer-usage.asciidoc @@ -0,0 +1,77 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[analyzer-usage]] +== Analyzer Usage + +=== Fluent DSL Example + +[source,csharp] +---- +FluentExample +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +InitializerExample +---- + +[source,javascript] +.Example json output +---- +{ + "analysis": { + "analyzer": { + "myCustom": { + "type": "custom", + "tokenizer": "ng", + "filter": [ + "myAscii", + "kstem" + ], + "char_filter": [ + "stripMe", + "patterned" + ] + }, + "myKeyword": { + "type": "keyword" + }, + "myPattern": { + "type": "pattern", + "pattern": "\\w" + }, + "mySimple": { + "type": "simple" + }, + "myLanguage": { + "type": "dutch" + }, + "mySnow": { + "type": "snowball", + "language": "Dutch" + }, + "myStandard": { + "type": "standard", + "max_token_length": 2 + }, + "myStop": { + "type": "stop", + "stopwords_path": "analysis/stopwords.txt" + }, + "myWhiteSpace": { + "type": "whitespace" + }, + "myWhiteSpace2": { + "type": "whitespace" + } + } + } +} +---- + diff --git a/docs/asciidoc/analysis/char-filters/char-filter-usage.asciidoc b/docs/asciidoc/analysis/char-filters/char-filter-usage.asciidoc new file mode 100644 index 00000000000..430437db32b --- /dev/null +++ b/docs/asciidoc/analysis/char-filters/char-filter-usage.asciidoc @@ -0,0 +1,48 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[char-filter-usage]] +== Char Filter Usage + +=== Fluent DSL Example + +[source,csharp] +---- +FluentExample +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +InitializerExample +---- + +[source,javascript] +.Example json output +---- +{ + "analysis": { + "char_filter": { + "stripMe": { + "type": "html_strip" + }, + "patterned": { + "pattern": "x", + "replacement": "y", + "type": "pattern_replace" + }, + "mapped": { + "mappings": [ + "a=>b" + ], + "type": "mapping" + } + } + } +} +---- + diff --git a/docs/asciidoc/analysis/token-filters/token-filter-usage.asciidoc b/docs/asciidoc/analysis/token-filters/token-filter-usage.asciidoc new file mode 100644 index 00000000000..9b8870f4a3b --- /dev/null +++ b/docs/asciidoc/analysis/token-filters/token-filter-usage.asciidoc @@ -0,0 +1,240 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[token-filter-usage]] +== Token Filter Usage + +=== Fluent DSL Example + +[source,csharp] +---- +FluentExample +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +InitializerExample +---- + +[source,javascript] +.Example json output +---- +{ + "analysis": { + "filter": { + "myAscii": { + "type": "asciifolding", + "preserveOriginal": true + }, + "myCommonGrams": { + "type": "common_grams", + "common_words": [ + "x", + "y", + "z" + ], + "ignore_case": true, + "query_mode": true + }, + "mydp": { + "type": "delimited_payload_filter", + "delimiter": "-", + "encoding": "identity" + }, + "dcc": { + "type": "dictionary_decompounder", + "word_list": [ + "x", + "y", + "z" + ], + "min_word_size": 2, + "min_subword_size": 2, + "max_subword_size": 2, + "only_longest_match": true + }, + "etf": { + "type": "edge_ngram", + "min_gram": 1, + "max_gram": 2 + }, + "elision": { + "type": "elision", + "articles": [ + "a", + "b", + "c" + ] + }, + "hunspell": { + "type": "hunspell", + "ignore_case": true, + "locale": "en_US", + "dictionary": "path_to_dict", + "dedup": true, + "longest_only": true + }, + "hypdecomp": { + "type": "hyphenation_decompounder", + "word_list": [ + "x", + "y", + "z" + ], + "min_word_size": 2, + "min_subword_size": 2, + "max_subword_size": 2, + "only_longest_match": true, + "hyphenation_patterns_path": "analysis/fop.xml" + }, + "keeptypes": { + "type": "keep_types", + "types": [ + "", + "" + ] + }, + "keepwords": { + "type": "keep", + "keep_words": [ + "a", + "b", + "c" + ], + "keep_words_case": true + }, + "marker": { + "type": "keyword_marker", + "keywords": [ + "a", + "b" + ], + "ignore_case": true + }, + "kstem": { + "type": "kstem" + }, + "length": { + "type": "length", + "min": 10, + "max": 200 + }, + "limit": { + "type": "limit", + "max_token_count": 12, + "consume_all_tokens": true + }, + "lc": { + "type": "lowercase" + }, + "ngram": { + "type": "ngram", + "min_gram": 3, + "max_gram": 30 + }, + "pc": { + "type": "pattern_capture", + "patterns": [ + "\\d", + "\\w" + ], + "preserve_original": true + }, + "pr": { + "type": "pattern_replace", + "pattern": "(\\d|\\w)", + "replacement": "replacement" + }, + "porter": { + "type": "porter_stem" + }, + "rev": { + "type": "reverse" + }, + "shing": { + "type": "shingle", + "min_shingle_size": 8, + "max_shingle_size": 12, + "output_unigrams": true, + "output_unigrams_if_no_shingles": true, + "token_separator": "|", + "filler_token": "x" + }, + "snow": { + "type": "snowball", + "language": "Dutch" + }, + "standard": { + "type": "standard" + }, + "stem": { + "type": "stemmer", + "language": "arabic" + }, + "stemo": { + "type": "stemmer_override", + "rules_path": "analysis/custom_stems.txt" + }, + "stop": { + "type": "stop", + "stopwords": [ + "x", + "y", + "z" + ], + "ignore_case": true, + "remove_trailing": true + }, + "syn": { + "type": "synonym", + "synonyms_path": "analysis/stopwords.txt", + "format": "wordnet", + "synonyms": [ + "x=>y", + "z=>s" + ], + "ignore_case": true, + "expand": true, + "tokenizer": "whitespace" + }, + "trimmer": { + "type": "trim" + }, + "truncer": { + "type": "truncate", + "length": 100 + }, + "uq": { + "type": "unique", + "only_on_same_position": true + }, + "upper": { + "type": "uppercase" + }, + "wd": { + "type": "word_delimiter", + "generate_word_parts": true, + "generate_number_parts": true, + "catenate_words": true, + "catenate_numbers": true, + "catenate_all": true, + "split_on_case_change": true, + "preserve_original": true, + "split_on_numerics": true, + "stem_english_possessive": true, + "protected_words": [ + "x", + "y", + "z" + ] + } + } + } +} +---- + diff --git a/docs/asciidoc/analysis/tokenizers/tokenizer-usage.asciidoc b/docs/asciidoc/analysis/tokenizers/tokenizer-usage.asciidoc new file mode 100644 index 00000000000..61048a3a42e --- /dev/null +++ b/docs/asciidoc/analysis/tokenizers/tokenizer-usage.asciidoc @@ -0,0 +1,76 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[tokenizer-usage]] +== Tokenizer Usage + +=== Fluent DSL Example + +[source,csharp] +---- +FluentExample +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +InitializerExample +---- + +[source,javascript] +.Example json output +---- +{ + "analysis": { + "tokenizer": { + "endgen": { + "min_gram": 1, + "max_gram": 2, + "token_chars": [ + "digit", + "letter" + ], + "type": "edge_ngram" + }, + "ng": { + "min_gram": 1, + "max_gram": 2, + "token_chars": [ + "digit", + "letter" + ], + "type": "ngram" + }, + "path": { + "delimiter": "|", + "replacement": "-", + "buffer_size": 2048, + "reverse": true, + "skip": 1, + "type": "path_hierarchy" + }, + "pattern": { + "pattern": "\\W+", + "flags": "CASE_INSENSITIVE", + "group": 1, + "type": "pattern" + }, + "standard": { + "type": "standard" + }, + "uax": { + "max_token_length": 12, + "type": "uax_url_email" + }, + "whitespace": { + "type": "whitespace" + } + } + } +} +---- + diff --git a/docs/asciidoc/ClientConcepts/LowLevel/class.png b/docs/asciidoc/class.png similarity index 100% rename from docs/asciidoc/ClientConcepts/LowLevel/class.png rename to docs/asciidoc/class.png diff --git a/docs/asciidoc/client-concepts.asciidoc b/docs/asciidoc/client-concepts.asciidoc new file mode 100644 index 00000000000..9c20575e2e1 --- /dev/null +++ b/docs/asciidoc/client-concepts.asciidoc @@ -0,0 +1,4 @@ +include::low-level.asciidoc[] + +include::high-level.asciidoc[] + diff --git a/docs/asciidoc/client-concepts/connection-pooling/building-blocks/connection-pooling.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/building-blocks/connection-pooling.asciidoc new file mode 100644 index 00000000000..f9d4f35e1a0 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/building-blocks/connection-pooling.asciidoc @@ -0,0 +1,239 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[connection-pooling]] +== Connection Pooling + +Connection pooling is the internal mechanism that takes care of registering what nodes there are in the cluster and which +NEST can use to issue client calls on. There are four types of connection pool + +* <> + +* <> + +* <> + +* <> + +[[single-node-connection-pool]] +=== SingleNodeConnectionPool + +The simplest of all connection pools, this takes a single `Uri` and uses that to connect to Elasticsearch for all the calls +It doesn't opt in to sniffing and pinging behavior, and will never mark nodes dead or alive. The one `Uri` it holds is always +ready to go. + +[source,csharp] +---- +var uri = new Uri("http://localhost:9201"); +var pool = new SingleNodeConnectionPool(uri); +pool.Nodes.Should().HaveCount(1); +var node = pool.Nodes.First(); +node.Uri.Port.Should().Be(9201); +---- + +This type of pool is hardwired to opt out of reseeding (and hence sniffing) + +[source,csharp] +---- +pool.SupportsReseeding.Should().BeFalse(); +---- + +and pinging + +[source,csharp] +---- +pool.SupportsPinging.Should().BeFalse(); +---- + +When you use the low ceremony `ElasticClient` constructor that takes a single `Uri`, +We default to using `SingleNodeConnectionPool` + +[source,csharp] +---- +var client = new ElasticClient(uri); + +client.ConnectionSettings.ConnectionPool.Should().BeOfType(); +---- + +However we urge that you always pass your connection settings explicitly + +[source,csharp] +---- +client = new ElasticClient(new ConnectionSettings(uri)); + +client.ConnectionSettings.ConnectionPool.Should().BeOfType(); +---- + +or even better pass the connection pool explicitly + +[source,csharp] +---- +client = new ElasticClient(new ConnectionSettings(pool)); + +client.ConnectionSettings.ConnectionPool.Should().BeOfType(); +---- + +[[static-connection-pool]] +=== StaticConnectionPool + +The static connection pool is great if you have a known small sized cluster and do no want to enable +sniffing to find out the cluster topology. + +[source,csharp] +---- +var uris = Enumerable.Range(9200, 5).Select(p => new Uri("http://localhost:" + p)); +---- + +a connection pool can be seeded using an enumerable of `Uri`s + +[source,csharp] +---- +var pool = new StaticConnectionPool(uris); +---- + +Or using an enumerable of `Node`s + +[source,csharp] +---- +var nodes = uris.Select(u => new Node(u)); + +pool = new StaticConnectionPool(nodes); +---- + +This type of pool is hardwired to opt out of reseeding (and hence sniffing) + +[source,csharp] +---- +pool.SupportsReseeding.Should().BeFalse(); +---- + +but supports pinging when enabled + +[source,csharp] +---- +pool.SupportsPinging.Should().BeTrue(); +---- + +To create a client using this static connection pool, pass +the connection pool to the `ConnectionSettings` you pass to `ElasticClient` + +[source,csharp] +---- +var client = new ElasticClient(new ConnectionSettings(pool)); + +client.ConnectionSettings.ConnectionPool.Should().BeOfType(); +---- + +[[sniffing-connection-pool]] +=== SniffingConnectionPool + +A subclass of `StaticConnectionPool` that allows itself to be reseeded at run time. +It comes with a very minor overhead of a `ReaderWriterLockSlim` to ensure thread safety. + +[source,csharp] +---- +var uris = Enumerable.Range(9200, 5).Select(p => new Uri("http://localhost:" + p)); +---- + +a connection pool can be seeded using an enumerable of `Uri` + +[source,csharp] +---- +var pool = new SniffingConnectionPool(uris); +---- + +Or using an enumerable of `Node`s. +A major benefit here is you can include known node roles when seeding and +NEST can use this information to favour sniffing on master eligible nodes first +and take master only nodes out of rotation for issuing client calls on. + +[source,csharp] +---- +var nodes = uris.Select(u=>new Node(u)); + +pool = new SniffingConnectionPool(nodes); +---- + +This type of pool is hardwired to opt in to reseeding (and hence sniffing) + +[source,csharp] +---- +pool.SupportsReseeding.Should().BeTrue(); +---- + +and pinging + +[source,csharp] +---- +pool.SupportsPinging.Should().BeTrue(); +---- + +To create a client using the sniffing connection pool pass +the connection pool to the `ConnectionSettings` you pass to `ElasticClient` + +[source,csharp] +---- +var client = new ElasticClient(new ConnectionSettings(pool)); + +client.ConnectionSettings.ConnectionPool.Should().BeOfType(); +---- + +[[sticky-connection-pool]] +=== StickyConnectionPool + +A type of `IConnectionPool` that returns the first live node such that it is sticky between +requests. +It uses https://msdn.microsoft.com/en-us/library/system.threading.interlocked(v=vs.110).aspx[`System.Threading.Interlocked`] +to keep an _indexer_ to the last live node in a thread safe manner. + +[source,csharp] +---- +var uris = Enumerable.Range(9200, 5).Select(p => new Uri("http://localhost:" + p)); +---- + +a connection pool can be seeded using an enumerable of `Uri` + +[source,csharp] +---- +var pool = new StickyConnectionPool(uris); +---- + +Or using an enumerable of `Node`. +A major benefit here is you can include known node roles when seeding and +NEST can use this information to favour sniffing on master eligible nodes first +and take master only nodes out of rotation for issuing client calls on. + +[source,csharp] +---- +var nodes = uris.Select(u=>new Node(u)); + +pool = new StickyConnectionPool(nodes); +---- + +This type of pool is hardwired to opt out of reseeding (and hence sniffing) + +[source,csharp] +---- +pool.SupportsReseeding.Should().BeFalse(); +---- + +but does support pinging + +[source,csharp] +---- +pool.SupportsPinging.Should().BeTrue(); +---- + +To create a client using the sticky connection pool pass +the connection pool to the `ConnectionSettings` you pass to `ElasticClient` + +[source,csharp] +---- +var client = new ElasticClient(new ConnectionSettings(pool)); + +client.ConnectionSettings.ConnectionPool.Should().BeOfType(); +---- + diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/DateTimeProviders.Doc.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/building-blocks/date-time-providers.asciidoc similarity index 54% rename from docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/DateTimeProviders.Doc.asciidoc rename to docs/asciidoc/client-concepts/connection-pooling/building-blocks/date-time-providers.asciidoc index 6865704b143..d2abaf2a86b 100644 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/DateTimeProviders.Doc.asciidoc +++ b/docs/asciidoc/client-concepts/connection-pooling/building-blocks/date-time-providers.asciidoc @@ -1,56 +1,61 @@ -= Date time providers - -Not typically something you'll have to pass to the client but all calls to `System.DateTime.UtcNow` -in the client have been abstracted by `IDateTimeProvider`. This allows us to unit test timeouts and clusterfailover -in run time not being bound to wall clock time. - - -[source, csharp] ----- -var dateTimeProvider = DateTimeProvider.Default; ----- -dates are always returned in UTC - -[source, csharp] ----- -dateTimeProvider.Now().Should().BeCloseTo(DateTime.UtcNow); ----- - -Another responsibility of this interface is to calculate the time a node has to be taken out of rotation -based on the number of attempts to revive it. For very advanced use cases, this might be something of interest -to provide a custom implementation for. - - -[source, csharp] ----- -var dateTimeProvider = DateTimeProvider.Default; ----- - -The default timeout calculation is: `min(timeout * 2 ^ (attempts * 0.5 -1), maxTimeout)` -The default values for `timeout` and `maxTimeout` are - -[source, csharp] ----- -var timeout = TimeSpan.FromMinutes(1); ----- -[source, csharp] ----- -var maxTimeout = TimeSpan.FromMinutes(30); ----- -Plotting these defaults looks as followed: -[[timeout]] -.Default formula, x-axis time in minutes, y-axis number of attempts to revive -image::timeoutplot.png[dead timeout] -The goal here is that whenever a node is resurrected and is found to still be offline, we send it -_back to the doghouse_ for an ever increasingly long period, until we hit a bounded maximum. - -[source, csharp] ----- -var timeouts = Enumerable.Range(0, 30) - .Select(attempt => dateTimeProvider.DeadTime(attempt, timeout, maxTimeout)) - .ToList(); ----- -[source, csharp] ----- -increasedTimeout.Should().BeWithin(maxTimeout); ----- +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[date-time-providers]] +== Date time providers + +Not typically something you'll have to pass to the client but all calls to `System.DateTime.UtcNow` +in the client have been abstracted by `IDateTimeProvider`. This allows us to unit test timeouts and cluster failover +without being bound to wall clock time as calculated by using `System.DateTime.UtcNow` directly. + +[source,csharp] +---- +var dateTimeProvider = DateTimeProvider.Default; +---- + +dates are always returned in UTC + +[source,csharp] +---- +dateTimeProvider.Now().Should().BeCloseTo(DateTime.UtcNow); +---- + +Another responsibility of this interface is to calculate the time a node has to be taken out of rotation +based on the number of attempts to revive it. For very advanced use cases, this might be something of interest +to provide a custom implementation for. + +[source,csharp] +---- +var dateTimeProvider = DateTimeProvider.Default; +---- + +The default timeout calculation is: `min(timeout * 2 ^ (attempts * 0.5 -1), maxTimeout)`, where the +default values for `timeout` and `maxTimeout` are + +[source,csharp] +---- +var timeout = TimeSpan.FromMinutes(1); + +var maxTimeout = TimeSpan.FromMinutes(30); +---- + +Plotting these defaults looks as followed: + +[[timeout]] +.Default formula, x-axis time in minutes, y-axis number of attempts to revive +image::timeoutplot.png[dead timeout] + +The goal here is that whenever a node is resurrected and is found to still be offline, we send it _back to the doghouse_ for an ever increasingly long period, until we hit a bounded maximum. + +[source,csharp] +---- +var timeouts = Enumerable.Range(0, 30) + .Select(attempt => dateTimeProvider.DeadTime(attempt, timeout, maxTimeout)) + .ToList(); + +increasedTimeout.Should().BeWithin(maxTimeout); +---- + diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/KeepingTrackOfNodes.Doc.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/building-blocks/keeping-track-of-nodes.asciidoc similarity index 66% rename from docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/KeepingTrackOfNodes.Doc.asciidoc rename to docs/asciidoc/client-concepts/connection-pooling/building-blocks/keeping-track-of-nodes.asciidoc index ab9533441a7..1078b219a3a 100644 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/KeepingTrackOfNodes.Doc.asciidoc +++ b/docs/asciidoc/client-concepts/connection-pooling/building-blocks/keeping-track-of-nodes.asciidoc @@ -1,106 +1,137 @@ -= Keeping track of nodes +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current +:github: https://github.com/elastic/elasticsearch-net +:nuget: https://www.nuget.org/packages -[source, csharp] +[[keeping-track-of-nodes]] +== Keeping track of nodes + +=== Creating a Node + +A `Node` can be instantiated by passing it a `Uri` + +[source,csharp] ---- var node = new Node(new Uri("http://localhost:9200")); + node.Uri.Should().NotBeNull(); node.Uri.Port.Should().Be(9200); ---- + By default master eligible and holds data is presumed to be true * -[source, csharp] +[source,csharp] ---- node.MasterEligible.Should().BeTrue(); ----- -[source, csharp] ----- + node.HoldsData.Should().BeTrue(); ---- + Is resurrected is true on first usage, hints to the transport that a ping might be useful -[source, csharp] +[source,csharp] ---- node.IsResurrected.Should().BeTrue(); ---- + When instantiating your connection pool you could switch these to false to initialize the client to a known cluster topology. +=== Building a Node path -passing a node with a path should be preserved. Sometimes an elasticsearch node lives behind a proxy +passing a node with a path should be preserved. +Sometimes an Elasticsearch node lives behind a proxy -[source, csharp] +[source,csharp] ---- var node = new Node(new Uri("http://test.example/elasticsearch")); ----- -[source, csharp] ----- + node.Uri.Port.Should().Be(80); + node.Uri.AbsolutePath.Should().Be("/elasticsearch/"); ---- -We force paths to end with a forward slash so that they can later be safely combined -[source, csharp] +*We force paths to end with a forward slash* so that they can later be safely combined + +[source,csharp] ---- var combinedPath = new Uri(node.Uri, "index/type/_search"); ----- -[source, csharp] ----- + combinedPath.AbsolutePath.Should().Be("/elasticsearch/index/type/_search"); ---- + which is exactly what the `CreatePath` method does on `Node` -[source, csharp] +[source,csharp] ---- combinedPath = node.CreatePath("index/type/_search"); + +combinedPath.AbsolutePath.Should().Be("/elasticsearch/index/type/_search"); ---- -[source, csharp] + +=== Marking Nodes + +[source,csharp] ---- -combinedPath.AbsolutePath.Should().Be("/elasticsearch/index/type/_search"); var node = new Node(new Uri("http://localhost:9200")); + node.FailedAttempts.Should().Be(0); + node.IsAlive.Should().BeTrue(); ---- -every time a node is marked dead the number of attempts should increase +every time a node is marked dead, the number of attempts should increase and the passed datetime should be exposed. - -[source, csharp] +[source,csharp] ---- var deadUntil = DateTime.Now.AddMinutes(1); + node.MarkDead(deadUntil); + node.FailedAttempts.Should().Be(i + 1); + node.IsAlive.Should().BeFalse(); + node.DeadUntil.Should().Be(deadUntil); ---- -however when marking a node alive deaduntil should be reset and attempts reset to 0 -[source, csharp] +however when marking a node alive, the `DeadUntil` property should be reset and `FailedAttempts` reset to 0 + +[source,csharp] ---- node.MarkAlive(); ----- -[source, csharp] ----- + node.FailedAttempts.Should().Be(0); + node.DeadUntil.Should().Be(default(DateTime)); + node.IsAlive.Should().BeTrue(); ---- -Nodes are considered equal if they have the same endpoint no matter what other metadata is associated -[source, csharp] +=== Node Equality + +Nodes are considered equal if they have the same endpoint, no matter what other metadata is associated + +[source,csharp] ---- var node = new Node(new Uri("http://localhost:9200")) { MasterEligible = false }; ----- -[source, csharp] ----- + var nodeAsMaster = new Node(new Uri("http://localhost:9200")) { MasterEligible = true }; + (node == nodeAsMaster).Should().BeTrue(); + (node != nodeAsMaster).Should().BeFalse(); + var uri = new Uri("http://localhost:9200"); + (node == uri).Should().BeTrue(); + var differentUri = new Uri("http://localhost:9201"); + (node != differentUri).Should().BeTrue(); + node.Should().Be(nodeAsMaster); ---- + diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/RequestPipelines.doc.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/building-blocks/request-pipelines.asciidoc similarity index 56% rename from docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/RequestPipelines.doc.asciidoc rename to docs/asciidoc/client-concepts/connection-pooling/building-blocks/request-pipelines.asciidoc index a8bcb9555d8..6477bb22d08 100644 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/RequestPipelines.doc.asciidoc +++ b/docs/asciidoc/client-concepts/connection-pooling/building-blocks/request-pipelines.asciidoc @@ -1,169 +1,251 @@ -= Request pipeline -Every request is executed in the context of `RequestPipeline` when using the default `ITransport` implementation. +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current +:github: https://github.com/elastic/elasticsearch-net +:nuget: https://www.nuget.org/packages -[source, csharp] +[[request-pipeline]] +== Request Pipeline + +Every request is executed in the context of a `RequestPipeline` when using the +default <> implementation. + +[source,csharp] ---- var settings = TestClient.CreateSettings(); ---- -When calling Request(Async) on Transport the whole coordination of the request is deferred to a new instance in a `using` block. -[source, csharp] ----- -var pipeline = new RequestPipeline(settings, DateTimeProvider.Default, new MemoryStreamFactory(), new SearchRequestParameters()); ----- -[source, csharp] +When calling `Request()` or `RequestAsync()` on an `ITransport`, +the whole coordination of the request is deferred to a new instance in a `using` block. + +[source,csharp] ---- +var pipeline = new RequestPipeline( + settings, + DateTimeProvider.Default, + new MemoryStreamFactory(), + new SearchRequestParameters()); + pipeline.GetType().Should().Implement(); ---- -However the transport does not instantiate RequestPipeline directly, it uses a pluggable `IRequestPipelineFactory` -[source, csharp] +An `ITransport` does not instantiate a `RequestPipeline` directly; it uses a pluggable `IRequestPipelineFactory` +to create it + +[source,csharp] ---- var requestPipelineFactory = new RequestPipelineFactory(); ----- -[source, csharp] ----- -var requestPipeline = requestPipelineFactory.Create(settings, DateTimeProvider.Default, new MemoryStreamFactory(), new SearchRequestParameters()); + +var requestPipeline = requestPipelineFactory.Create( + settings, + DateTimeProvider.Default, <1> + new MemoryStreamFactory(), + new SearchRequestParameters()); requestPipeline.Should().BeOfType(); requestPipeline.GetType().Should().Implement(); ---- -which can be passed to the transport when instantiating a client +<1> An <> + +You can pass your own `IRequestPipeline` implementation to the Transport when instantiating a client, +allowing you to have requests executed on your own custom request pipeline -[source, csharp] +[source,csharp] ---- -var transport = new Transport(settings, requestPipelineFactory, DateTimeProvider.Default, new MemoryStreamFactory()); +var transport = new Transport( + settings, + requestPipelineFactory, + DateTimeProvider.Default, + new MemoryStreamFactory()); ---- -this allows you to have requests executed on your own custom request pipeline -[source, csharp] +[source,csharp] ---- var pool = setupPool(new[] { TestClient.CreateNode(), TestClient.CreateNode(9201) }); + var settings = new ConnectionSettings(pool, TestClient.CreateConnection()); + settings = settingsSelector?.Invoke(settings) ?? settings; +---- + +=== Pipeline Behavior + +==== Sniffing on First usage + +[source,csharp] +---- var singleNodePipeline = CreatePipeline(uris => new SingleNodeConnectionPool(uris.First())); + var staticPipeline = CreatePipeline(uris => new StaticConnectionPool(uris)); + var sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris)); ---- -Here we have setup three pipelines using three different connection pools, lets see how they behave -[source, csharp] +Here we have setup three pipelines using three different connection pools. Let's see how they behave +on first usage + +[source,csharp] ---- singleNodePipeline.FirstPoolUsageNeedsSniffing.Should().BeFalse(); ----- -[source, csharp] ----- + staticPipeline.FirstPoolUsageNeedsSniffing.Should().BeFalse(); + sniffingPipeline.FirstPoolUsageNeedsSniffing.Should().BeTrue(); ---- -Only the cluster that supports reseeding will opt in to FirstPoolUsageNeedsSniffing() -You can however disable this on ConnectionSettings -[source, csharp] +We can see that only the cluster that supports reseeding will opt in to `FirstPoolUsageNeedsSniffing()`; +You can however disable reseeding/sniffing on ConnectionSettings + +[source,csharp] ---- sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris), s => s.SniffOnStartup(false)); + +sniffingPipeline.FirstPoolUsageNeedsSniffing.Should().BeFalse(); ---- -[source, csharp] + +==== Sniffing on Connection Failure + +[source,csharp] ---- -sniffingPipeline.FirstPoolUsageNeedsSniffing.Should().BeFalse(); var singleNodePipeline = CreatePipeline(uris => new SingleNodeConnectionPool(uris.First())); + var staticPipeline = CreatePipeline(uris => new StaticConnectionPool(uris)); + var sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris)); + singleNodePipeline.SniffsOnConnectionFailure.Should().BeFalse(); + staticPipeline.SniffsOnConnectionFailure.Should().BeFalse(); + sniffingPipeline.SniffsOnConnectionFailure.Should().BeTrue(); ---- -Only the cluster that supports reseeding will opt in to SniffsOnConnectionFailure() + +Only the cluster that supports reseeding will opt in to SniffsOnConnectionFailure() You can however disable this on ConnectionSettings -[source, csharp] +[source,csharp] ---- sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris), s => s.SniffOnConnectionFault(false)); + +sniffingPipeline.SniffsOnConnectionFailure.Should().BeFalse(); ---- -[source, csharp] + +==== Sniffing on Stale cluster + +[source,csharp] ---- -sniffingPipeline.SniffsOnConnectionFailure.Should().BeFalse(); var dateTime = new TestableDateTimeProvider(); -var singleNodePipeline = CreatePipeline(uris => new SingleNodeConnectionPool(uris.First(), dateTime), dateTimeProvider: dateTime); -var staticPipeline = CreatePipeline(uris => new StaticConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); -var sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); + +var singleNodePipeline = CreatePipeline(uris => + new SingleNodeConnectionPool(uris.First(), dateTime), dateTimeProvider: dateTime); + +var staticPipeline = CreatePipeline(uris => + new StaticConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); + +var sniffingPipeline = CreatePipeline(uris => + new SniffingConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); + singleNodePipeline.SniffsOnStaleCluster.Should().BeFalse(); + staticPipeline.SniffsOnStaleCluster.Should().BeFalse(); + sniffingPipeline.SniffsOnStaleCluster.Should().BeTrue(); + singleNodePipeline.StaleClusterState.Should().BeFalse(); + staticPipeline.StaleClusterState.Should().BeFalse(); + sniffingPipeline.StaleClusterState.Should().BeFalse(); ---- + go one hour into the future -[source, csharp] +[source,csharp] ---- dateTime.ChangeTime(d => d.Add(TimeSpan.FromHours(2))); ---- + connection pools that do not support reseeding never go stale -[source, csharp] +[source,csharp] ---- singleNodePipeline.StaleClusterState.Should().BeFalse(); ----- -[source, csharp] ----- + staticPipeline.StaleClusterState.Should().BeFalse(); ---- + the sniffing connection pool supports reseeding so the pipeline will signal the state is out of date -[source, csharp] +[source,csharp] ---- sniffingPipeline.StaleClusterState.Should().BeTrue(); ---- -A request pipeline also checks whether the overall time across multiple retries exceeds the request timeout -See the maxretry documentation for more details, here we assert that our request pipeline exposes this propertly +=== Retrying requests + +A request pipeline also checks whether the overall time across multiple retries exceeds the request timeout. +See the <> for more details, here we assert that our request pipeline exposes this propertly -[source, csharp] +[source,csharp] ---- var dateTime = new TestableDateTimeProvider(); -var singleNodePipeline = CreatePipeline(uris => new SingleNodeConnectionPool(uris.First(), dateTime), dateTimeProvider: dateTime); -var staticPipeline = CreatePipeline(uris => new StaticConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); -var sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); + +var singleNodePipeline = CreatePipeline(uris => + new SingleNodeConnectionPool(uris.First(), dateTime), dateTimeProvider: dateTime); + +var staticPipeline = CreatePipeline(uris => + new StaticConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); + +var sniffingPipeline = CreatePipeline(uris => + new SniffingConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); + singleNodePipeline.IsTakingTooLong.Should().BeFalse(); + staticPipeline.IsTakingTooLong.Should().BeFalse(); + sniffingPipeline.IsTakingTooLong.Should().BeFalse(); ---- + go one hour into the future -[source, csharp] +[source,csharp] ---- dateTime.ChangeTime(d => d.Add(TimeSpan.FromHours(2))); ---- + connection pools that do not support reseeding never go stale -[source, csharp] +[source,csharp] ---- singleNodePipeline.IsTakingTooLong.Should().BeTrue(); ----- -[source, csharp] ----- + staticPipeline.IsTakingTooLong.Should().BeTrue(); ---- + the sniffing connection pool supports reseeding so the pipeline will signal the state is out of date -[source, csharp] +[source,csharp] ---- sniffingPipeline.IsTakingTooLong.Should().BeTrue(); ---- + request pipeline exposes the DateTime it started, here we assert it started 2 hours in the past -[source, csharp] +[source,csharp] ---- (dateTime.Now() - singleNodePipeline.StartedOn).Should().BePositive().And.BeCloseTo(TimeSpan.FromHours(2)); ----- -[source, csharp] ----- + (dateTime.Now() - staticPipeline.StartedOn).Should().BePositive().And.BeCloseTo(TimeSpan.FromHours(2)); + (dateTime.Now() - sniffingPipeline.StartedOn).Should().BePositive().And.BeCloseTo(TimeSpan.FromHours(2)); +---- + +[source,csharp] +---- var dateTime = new TestableDateTimeProvider(); -var sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime) as RequestPipeline; + +var sniffingPipeline = CreatePipeline(uris => + new SniffingConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime) as RequestPipeline; + sniffingPipeline.SniffPath.Should().Be("_nodes/_all/settings?flat_settings&timeout=2s"); ---- + diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/timeoutplot.png b/docs/asciidoc/client-concepts/connection-pooling/building-blocks/timeoutplot.png similarity index 100% rename from docs/asciidoc/ClientConcepts/ConnectionPooling/BuildingBlocks/timeoutplot.png rename to docs/asciidoc/client-concepts/connection-pooling/building-blocks/timeoutplot.png diff --git a/docs/asciidoc/client-concepts/connection-pooling/building-blocks/transports.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/building-blocks/transports.asciidoc new file mode 100644 index 00000000000..46e4db85a57 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/building-blocks/transports.asciidoc @@ -0,0 +1,52 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[transports]] +== Transports + +The `ITransport` interface can be seen as the motor block of the client. It's interface is deceitfully simple and +it's ultimately responsible from translating a client call to a response. + +If for some reason you do not agree with the way we wrote the internals of the client, +by implementing a custom `ITransport`, you can circumvent all of it and introduce your own. + +Transport is generically typed to a type that implements `IConnectionConfigurationValues` +This is the minimum `ITransport` needs to report back for the client to function. + +In the low level client, `ElasticLowLevelClient`, a `Transport` is instantiated like this: + +[source,csharp] +---- +var lowLevelTransport = new Transport(new ConnectionConfiguration()); +---- + +and in the high level client, `ElasticClient`, like this: + +[source,csharp] +---- +var highlevelTransport = new Transport(new ConnectionSettings()); + +var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); +var inMemoryTransport = new Transport(new ConnectionSettings(connectionPool, new InMemoryConnection())); +---- + +The only two methods on `ITransport` are `Request()` and `RequestAsync()`; the default `ITransport` implementation is responsible for introducing +many of the building blocks in the client. If you feel that the defaults do not work for you then you can swap them out for your own +custom `ITransport` implementation and if you do, {github}/issues[please let us know] as we'd love to learn why you've go down this route! + +[source,csharp] +---- +var response = inMemoryTransport.Request>( + HttpMethod.GET, + "/_search", + new { query = new { match_all = new { } } }); + +response = await inMemoryTransport.RequestAsync>( + HttpMethod.GET, + "/_search", + new { query = new { match_all = new { } } }); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/exceptions/unexpected-exceptions.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/exceptions/unexpected-exceptions.asciidoc new file mode 100644 index 00000000000..a49b874df69 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/exceptions/unexpected-exceptions.asciidoc @@ -0,0 +1,125 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[unexpected-exceptions]] +== Unexpected exceptions + +When a client call throws an exception that the IConnction can not handle, this exception will bubble +out the client as an UnexpectedElasticsearchClientException, regardless whether the client is configured to throw or not. +An IConnection is in charge of knowning what exceptions it can recover from or not. The default IConnection that is based on WebRequest can and +will recover from WebExceptions but others will be grounds for immediately exiting the pipeline. + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.SucceedAlways()) + .ClientCalls(r => r.OnPort(9201).FailAlways(new Exception("boom!"))) + .StaticConnectionPool() + .Settings(s => s.DisablePing()) +); +audit = await audit.TraceCall( + new ClientCall { + { AuditEvent.HealthyResponse, 9200 }, + } +); +audit = await audit.TraceUnexpectedException( + new ClientCall { + { AuditEvent.BadResponse, 9201 }, + }, + (e) => + { + e.FailureReason.Should().Be(PipelineFailure.Unexpected); + e.InnerException.Should().NotBeNull(); + e.InnerException.Message.Should().Be("boom!"); + } +); +e.FailureReason.Should().Be(PipelineFailure.Unexpected); +e.InnerException.Should().NotBeNull(); +e.InnerException.Message.Should().Be("boom!"); +---- + +Sometimes an unexpected exception happens further down in the pipeline, this is why we +wrap them inside an UnexpectedElasticsearchClientException so that information about where +in the pipeline the unexpected exception is not lost, here a call to 9200 fails using a webexception. +It then falls over to 9201 which throws an hard exception from within IConnection. We assert that we +can still see the audit trail for the whole coordinated request. + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) +#if DOTNETCORE + .ClientCalls(r => r.OnPort(9200).FailAlways(new System.Net.Http.HttpRequestException("recover"))) +#else + .ClientCalls(r => r.OnPort(9200).FailAlways(new WebException("recover"))) +#endif + .ClientCalls(r => r.OnPort(9201).FailAlways(new Exception("boom!"))) + .StaticConnectionPool() + .Settings(s => s.DisablePing()) +); + +audit = await audit.TraceUnexpectedException( + new ClientCall { + { AuditEvent.BadResponse, 9200 }, + { AuditEvent.BadResponse, 9201 }, + }, + (e) => + { + e.FailureReason.Should().Be(PipelineFailure.Unexpected); + e.InnerException.Should().NotBeNull(); + e.InnerException.Message.Should().Be("boom!"); + } +); + +e.FailureReason.Should().Be(PipelineFailure.Unexpected); + +e.InnerException.Should().NotBeNull(); + +e.InnerException.Message.Should().Be("boom!"); +---- + +An unexpected hard exception on ping and sniff is something we *do* try to revover from and failover. +Here pinging nodes on first use is enabled and 9200 throws on ping, we still fallover to 9201's ping succeeds. +However the client call on 9201 throws a hard exception we can not recover from + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Ping(r => r.OnPort(9200).FailAlways(new Exception("ping exception"))) + .Ping(r => r.OnPort(9201).SucceedAlways()) + .ClientCalls(r => r.OnPort(9201).FailAlways(new Exception("boom!"))) + .StaticConnectionPool() + .AllDefaults() +); +---- + +[source,csharp] +---- +audit = await audit.TraceUnexpectedException( + new ClientCall { + { AuditEvent.PingFailure, 9200 }, + { AuditEvent.PingSuccess, 9201 }, + { AuditEvent.BadResponse, 9201 }, + }, + (e) => + { + e.FailureReason.Should().Be(PipelineFailure.Unexpected); +e.InnerException.Should().NotBeNull(); + e.InnerException.Message.Should().Be("boom!"); +e.SeenExceptions.Should().NotBeEmpty(); + var pipelineException = e.SeenExceptions.First(); + pipelineException.FailureReason.Should().Be(PipelineFailure.PingFailure); + pipelineException.InnerException.Message.Should().Be("ping exception"); +var pingException = e.AuditTrail.First(a => a.Event == AuditEvent.PingFailure).Exception; + pingException.Should().NotBeNull(); + pingException.Message.Should().Be("ping exception"); + + } +); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/exceptions/unrecoverable-exceptions.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/exceptions/unrecoverable-exceptions.asciidoc new file mode 100644 index 00000000000..3a1f5c3a497 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/exceptions/unrecoverable-exceptions.asciidoc @@ -0,0 +1,188 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[unrecoverable-exceptions]] +== Unrecoverable exceptions + +Unrecoverable exceptions are _excepted_ exceptions that are grounds to exit the client pipeline immediately. +By default, the client won't throw on any `ElasticsearchClientException` but instead return an invalid response which +can be detected by checking `.IsValid` on the response +You can configure the client to throw using `ThrowExceptions()` on `ConnectionSettings`. The following test +both a client that throws and one that returns an invalid response with an `.OriginalException` exposed + +The following are recoverable exceptions + +[source,csharp] +---- +var recoverablExceptions = new[] +{ + new PipelineException(PipelineFailure.BadResponse), + new PipelineException(PipelineFailure.PingFailure), +}; + +recoverablExceptions.Should().OnlyContain(e => e.Recoverable); +---- + +and the unrecoverable exceptions + +[source,csharp] +---- +var unrecoverableExceptions = new[] +{ + new PipelineException(PipelineFailure.CouldNotStartSniffOnStartup), + new PipelineException(PipelineFailure.SniffFailure), + new PipelineException(PipelineFailure.Unexpected), + new PipelineException(PipelineFailure.BadAuthentication), + new PipelineException(PipelineFailure.MaxRetriesReached), + new PipelineException(PipelineFailure.MaxTimeoutReached) +}; + +unrecoverableExceptions.Should().OnlyContain(e => !e.Recoverable); +---- + +As an example, let's set up a 10 node cluster that will always succeed when pinged but + will fail with a 401 response when making client calls + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Ping(r => r.SucceedAlways()) + .ClientCalls(r => r.FailAlways(401)) + .StaticConnectionPool() + .AllDefaults() +); +---- + +Here we make a client call and determine that the first audit event was a successful ping, +followed by a bad response as a result of a bad authentication response + +[source,csharp] +---- +audit = await audit.TraceElasticsearchException( + new ClientCall { + { AuditEvent.PingSuccess, 9200 }, + { AuditEvent.BadResponse, 9200 }, + }, + (e) => + { + e.FailureReason.Should().Be(PipelineFailure.BadAuthentication); + } +); +---- + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Ping(r => r.SucceedAlways()) + .ClientCalls(r => r.FailAlways(401).ReturnResponse(ResponseHtml)) + .StaticConnectionPool() + .AllDefaults() +); + +audit = await audit.TraceElasticsearchException( + new ClientCall { + { AuditEvent.PingSuccess, 9200 }, + { AuditEvent.BadResponse, 9200 }, + }, + (e) => + { + e.FailureReason.Should().Be(PipelineFailure.BadAuthentication); + e.Response.HttpStatusCode.Should().Be(401); + e.Response.ResponseBodyInBytes.Should().BeNull(); + } +); + +e.FailureReason.Should().Be(PipelineFailure.BadAuthentication); + +e.Response.HttpStatusCode.Should().Be(401); + +e.Response.ResponseBodyInBytes.Should().BeNull(); +---- + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Ping(r => r.SucceedAlways()) + .ClientCalls(r => r.FailAlways(401).ReturnResponse(ResponseHtml)) + .StaticConnectionPool() + .Settings(s=>s.DisableDirectStreaming()) +); + +audit = await audit.TraceElasticsearchException( + new ClientCall { + { AuditEvent.PingSuccess, 9200 }, + { AuditEvent.BadResponse, 9200 }, + }, + (e) => + { + e.FailureReason.Should().Be(PipelineFailure.BadAuthentication); + e.Response.HttpStatusCode.Should().Be(401); + e.Response.ResponseBodyInBytes.Should().NotBeNull(); + var responseString = Encoding.UTF8.GetString(e.Response.ResponseBodyInBytes); + responseString.Should().Contain("nginx/"); + e.DebugInformation.Should().Contain("nginx/"); + } +); + +e.FailureReason.Should().Be(PipelineFailure.BadAuthentication); + +e.Response.HttpStatusCode.Should().Be(401); + +e.Response.ResponseBodyInBytes.Should().NotBeNull(); + +var responseString = Encoding.UTF8.GetString(e.Response.ResponseBodyInBytes); + +responseString.Should().Contain("nginx/"); + +e.DebugInformation.Should().Contain("nginx/"); +---- + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Ping(r => r.SucceedAlways()) + .ClientCalls(r => r.FailAlways(401).ReturnResponse(ResponseHtml)) + .StaticConnectionPool() + .Settings(s=>s.DisableDirectStreaming().DefaultIndex("default-index")) + .ClientProxiesTo( + (c, r) => c.Get("1", s=>s.RequestConfiguration(r)), + async (c, r) => await c.GetAsync("1", s=>s.RequestConfiguration(r)) as IResponse + ) +); + +audit = await audit.TraceElasticsearchException( + new ClientCall { + { AuditEvent.PingSuccess, 9200 }, + { AuditEvent.BadResponse, 9200 }, + }, + (e) => + { + e.FailureReason.Should().Be(PipelineFailure.BadAuthentication); + e.Response.HttpStatusCode.Should().Be(401); + e.Response.ResponseBodyInBytes.Should().NotBeNull(); + var responseString = Encoding.UTF8.GetString(e.Response.ResponseBodyInBytes); + responseString.Should().Contain("nginx/"); + e.DebugInformation.Should().Contain("nginx/"); + } +); + +e.FailureReason.Should().Be(PipelineFailure.BadAuthentication); + +e.Response.HttpStatusCode.Should().Be(401); + +e.Response.ResponseBodyInBytes.Should().NotBeNull(); + +var responseString = Encoding.UTF8.GetString(e.Response.ResponseBodyInBytes); + +responseString.Should().Contain("nginx/"); + +e.DebugInformation.Should().Contain("nginx/"); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/failover/falling-over.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/failover/falling-over.asciidoc new file mode 100644 index 00000000000..1034edd1355 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/failover/falling-over.asciidoc @@ -0,0 +1,97 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[falling-over]] +== Fail over + +When using connection pooling and the pool has sufficient nodes a request will be retried if +the call to a node throws an exception or returns a 502 or 503 + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways()) + .ClientCalls(r => r.OnPort(9201).SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing()) +); +audit = await audit.TraceCall( + new ClientCall { + { BadResponse, 9200 }, + { HealthyResponse, 9201 }, + } +); +---- + +[[bad-gateway]] +=== 502 Bad Gateway + +Will be treated as an error that requires retrying + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways(502)) + .ClientCalls(r => r.OnPort(9201).SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing()) +); + +audit = await audit.TraceCall( + new ClientCall { + { BadResponse, 9200 }, + { HealthyResponse, 9201 }, + } +); +---- + +[[service-unavailable]] +=== 503 Service Unavailable + +Will be treated as an error that requires retrying + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways(503)) + .ClientCalls(r => r.OnPort(9201).SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing()) +); + +audit = await audit.TraceCall( + new ClientCall { + { BadResponse, 9200 }, + { HealthyResponse, 9201 }, + } +); +---- + +If a call returns a valid http status code other than 502 or 503, the request won't be retried. + +IMPORTANT: Different requests may have different status codes that are deemed valid. For example, +a *404 Not Found* response is a valid status code for an index exists request + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways(418)) + .ClientCalls(r => r.OnPort(9201).SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing()) +); + +audit = await audit.TraceCall( + new ClientCall { + { BadResponse, 9200 }, + } +); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/max-retries/respects-max-retry.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/max-retries/respects-max-retry.asciidoc new file mode 100644 index 00000000000..a932cc42591 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/max-retries/respects-max-retry.asciidoc @@ -0,0 +1,158 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[max-retries]] +== Max Retries + +By default, NEST will retry as many times as there are nodes in the cluster that the client knows about. +Retries still respects the request timeout however, +meaning if you have a 100 node cluster and a request timeout of 20 seconds, +the client will retry as many times as it before giving up at the request timeout of 20 seconds. + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways()) + .ClientCalls(r => r.OnPort(9209).SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing()) +); +audit = await audit.TraceCall( + new ClientCall { + { BadResponse, 9200 }, + { BadResponse, 9201 }, + { BadResponse, 9202 }, + { BadResponse, 9203 }, + { BadResponse, 9204 }, + { BadResponse, 9205 }, + { BadResponse, 9206 }, + { BadResponse, 9207 }, + { BadResponse, 9208 }, + { HealthyResponse, 9209 } + } +); +---- + +When you have a 100 node cluster, you might want to ensure a fixed number of retries. + +IMPORTANT: the actual number of requests is **initial attempt + set number of retries** + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways()) + .ClientCalls(r => r.OnPort(9209).SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing().MaximumRetries(3)) +); + +audit = await audit.TraceCall( + new ClientCall { + { BadResponse, 9200 }, + { BadResponse, 9201 }, + { BadResponse, 9202 }, + { BadResponse, 9203 }, + { MaxRetriesReached } + } +); +---- + +In our previous test we simulated very fast failures, but in the real world a call might take upwards of a second. +In this next example, we simulate a particular heavy search that takes 10 seconds to fail, and set a request timeout of 20 seconds. +We see that the request is tried twice and gives up before a third call is attempted, since the call takes 10 seconds and thus can be +tried twice (initial call and one retry) before the request timeout. + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways().Takes(TimeSpan.FromSeconds(10))) + .ClientCalls(r => r.OnPort(9209).SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing().RequestTimeout(TimeSpan.FromSeconds(20))) +); + +audit = await audit.TraceCall( + new ClientCall { + { BadResponse, 9200 }, + { BadResponse, 9201 }, + { MaxTimeoutReached } + } +); +---- + +If you set a smaller request timeout you might not want it to also affect the retry timeout. +In cases like this, you can configure the `MaxRetryTimeout` separately. +Here we simulate calls taking 3 seconds, a request timeout of 2 seconds and a max retry timeout of 10 seconds. +We should see 5 attempts to perform this query, testing that our request timeout cuts the query off short and that +our max retry timeout of 10 seconds wins over the configured request timeout + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways().Takes(TimeSpan.FromSeconds(3))) + .ClientCalls(r => r.OnPort(9209).FailAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing().RequestTimeout(TimeSpan.FromSeconds(2)).MaxRetryTimeout(TimeSpan.FromSeconds(10))) +); + +audit = await audit.TraceCall( + new ClientCall { + { BadResponse, 9200 }, + { BadResponse, 9201 }, + { BadResponse, 9202 }, + { BadResponse, 9203 }, + { BadResponse, 9204 }, + { MaxTimeoutReached } + } +); +---- + +If your retry policy expands beyond the number of available nodes, the client **won't** retry the same node twice + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(2) + .ClientCalls(r => r.FailAlways().Takes(TimeSpan.FromSeconds(3))) + .ClientCalls(r => r.OnPort(9209).SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing().RequestTimeout(TimeSpan.FromSeconds(2)).MaxRetryTimeout(TimeSpan.FromSeconds(10))) +); + +audit = await audit.TraceCall( + new ClientCall { + { BadResponse, 9200 }, + { BadResponse, 9201 }, + { MaxRetriesReached } + } +); +---- + +This makes setting any retry setting on a single node connection pool a no-op by design! +Connection pooling and failover is all about trying to fail sanely whilst still utilizing the available resources and +not giving up on the fail fast principle; **It is NOT a mechanism for forcing requests to succeed.** + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways().Takes(TimeSpan.FromSeconds(3))) + .ClientCalls(r => r.OnPort(9209).SucceedAlways()) + .SingleNodeConnection() + .Settings(s => s.DisablePing().MaximumRetries(10)) +); + +audit = await audit.TraceCall( + new ClientCall { + { BadResponse, 9200 } + } +); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/pinging/first-usage.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/pinging/first-usage.asciidoc new file mode 100644 index 00000000000..01ece2c32b3 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/pinging/first-usage.asciidoc @@ -0,0 +1,119 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[pinging-first-usage]] +== Pinging - First Usage + +Pinging is enabled by default for the <>, <> and <> connection pools. +This means that the first time a node is used or resurrected, a ping is issued a with a small (configurable) timeout, +allowing the client to fail and fallover to a healthy node much faster than attempting a request that may be heavier than a ping. + +Here's an example with a cluster with 2 nodes where the second node fails on ping + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(2) + .Ping(p => p.Succeeds(Always)) + .Ping(p => p.OnPort(9201).FailAlways()) + .StaticConnectionPool() + .AllDefaults() +); +---- + +When making the calls, the first call goes to 9200 which succeeds, +and the 2nd call does a ping on 9201 because it's used for the first time. +The ping fails so we wrap over to node 9200 which we've already pinged. + +Finally we assert that the connectionpool has one node that is marked as dead + +[source,csharp] +---- +await audit.TraceCalls( + + new ClientCall { + { PingSuccess, 9200}, + { HealthyResponse, 9200}, + { pool => + { + pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(0); + } } + }, + new ClientCall { + { PingFailure, 9201}, + { HealthyResponse, 9200}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(1) } + } +); +---- + +A cluster with 4 nodes where the second and third pings fail + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(4) + .Ping(p => p.SucceedAlways()) + .Ping(p => p.OnPort(9201).FailAlways()) + .Ping(p => p.OnPort(9202).FailAlways()) + .StaticConnectionPool() + .AllDefaults() +); +---- + +The first call goes to 9200 which succeeds + +The 2nd call does a ping on 9201 because its used for the first time. +It fails and so we ping 9202 which also fails. We then ping 9203 becuase +we haven't used it before and it succeeds + +Finally we assert that the connectionpool has two nodes that are marked as dead + +[source,csharp] +---- +await audit.TraceCalls( +new ClientCall { + { PingSuccess, 9200}, + { HealthyResponse, 9200}, + { pool => + { + pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(0); + } } + }, +new ClientCall { + { PingFailure, 9201}, + { PingFailure, 9202}, + { PingSuccess, 9203}, + { HealthyResponse, 9203}, +{ pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } + } +); +---- + +A healthy cluster of 4 (min master nodes of 3 of course!) + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(4) + .Ping(p => p.SucceedAlways()) + .StaticConnectionPool() + .AllDefaults() +); + +await audit.TraceCalls( + new ClientCall { { PingSuccess, 9200}, { HealthyResponse, 9200} }, + new ClientCall { { PingSuccess, 9201}, { HealthyResponse, 9201} }, + new ClientCall { { PingSuccess, 9202}, { HealthyResponse, 9202} }, + new ClientCall { { PingSuccess, 9203}, { HealthyResponse, 9203} }, + new ClientCall { { HealthyResponse, 9200} }, + new ClientCall { { HealthyResponse, 9201} }, + new ClientCall { { HealthyResponse, 9202} }, + new ClientCall { { HealthyResponse, 9203} }, + new ClientCall { { HealthyResponse, 9200} } +); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/pinging/revival.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/pinging/revival.asciidoc new file mode 100644 index 00000000000..3faf4c7dd70 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/pinging/revival.asciidoc @@ -0,0 +1,56 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[pinging-revival]] +== Pinging - Revival + +When a node is marked dead it will only be put in the dog house for a certain amount of time. Once it comes out of the dog house, or revived, we schedule a ping +before the actual call to make sure its up and running. If its still down we put it _back in the dog house_ a little longer. +Take a look at the <> for an explanation on these timeouts. + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(3) + .ClientCalls(r => r.SucceedAlways()) + .ClientCalls(r => r.OnPort(9202).Fails(Once)) + .Ping(p => p.SucceedAlways()) + .StaticConnectionPool() + .AllDefaults() +); +audit = await audit.TraceCalls( + new ClientCall { { PingSuccess, 9200 }, { HealthyResponse, 9200 } }, + new ClientCall { { PingSuccess, 9201 }, { HealthyResponse, 9201 } }, + new ClientCall { + { PingSuccess, 9202}, + { BadResponse, 9202}, + { HealthyResponse, 9200}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(1) } + }, + new ClientCall { { HealthyResponse, 9201 } }, + new ClientCall { { HealthyResponse, 9200 } }, + new ClientCall { { HealthyResponse, 9201 } }, + new ClientCall { + { HealthyResponse, 9200 }, + { pool => pool.Nodes.First(n=>!n.IsAlive).DeadUntil.Should().BeAfter(DateTime.UtcNow) } + } +); +audit = await audit.TraceCalls( + new ClientCall { { HealthyResponse, 9201 } }, + new ClientCall { { HealthyResponse, 9200 } }, + new ClientCall { { HealthyResponse, 9201 } } +); +audit.ChangeTime(d => d.AddMinutes(20)); +audit = await audit.TraceCalls( + new ClientCall { { HealthyResponse, 9201 } }, + new ClientCall { + { Resurrection, 9202 }, + { PingSuccess, 9202 }, + { HealthyResponse, 9202 } + } +); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/request-overrides/disable-sniff-ping-per-request.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/request-overrides/disable-sniff-ping-per-request.asciidoc new file mode 100644 index 00000000000..6be5fab01dc --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/request-overrides/disable-sniff-ping-per-request.asciidoc @@ -0,0 +1,108 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[disabling-sniffing-and-pinging-on-a-request-basis]] +== Disabling sniffing and pinging on a request basis + +Even if you are using a sniffing connection pool thats set up to sniff on start/failure +and pinging enabled, you can opt out of this behaviour on a _per request_ basis. + +In our first test we set up a cluster that pings and sniffs on startup +but we disable the sniffing on our first request so we only see the ping and the response + +Let's set up the cluster and configure clients to **always** sniff on startup + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.SucceedAlways()) + .SniffingConnectionPool() + .Settings(s => s.SniffOnStartup()) <1> +); +---- +<1> sniff on startup + +Now We disable sniffing on the request so even though it's our first call, we do not want to sniff on startup + +Instead, the sniff on startup is deferred to the second call into the cluster that +does not disable sniffing on a per request basis + +And after that no sniff on startup will happen again + +[source,csharp] +---- +audit = await audit.TraceCalls( +new ClientCall(r => r.DisableSniffing()) <1> + { + { PingSuccess, 9200 }, <2> + { HealthyResponse, 9200 } + }, +new ClientCall() + { + { SniffOnStartup }, <3> + { SniffSuccess, 9200 }, + { PingSuccess, 9200 }, + { HealthyResponse, 9200 } + }, +new ClientCall() + { + { PingSuccess, 9201 }, + { HealthyResponse, 9201 } + } +); +---- +<1> disable sniffing + +<2> first call is a successful ping + +<3> sniff on startup call happens here, on the second call + +Now, let's disable pinging on the request + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.SucceedAlways()) + .SniffingConnectionPool() + .Settings(s => s.SniffOnStartup()) +); +audit = await audit.TraceCall( + new ClientCall(r => r.DisablePing()) <1> + { + { SniffOnStartup }, + { SniffSuccess, 9200 }, <2> + { HealthyResponse, 9200 } + } +); +---- +<1> disable ping + +<2> No ping after sniffing + +Finally, let's demonstrate disabling both sniff and ping on the request + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.SucceedAlways()) + .SniffingConnectionPool() + .Settings(s => s.SniffOnStartup()) +); + +audit = await audit.TraceCall( + new ClientCall(r=>r.DisableSniffing().DisablePing()) <1> + { + { HealthyResponse, 9200 } <2> + } +); +---- +<1> diable ping and sniff + +<2> no ping or sniff before the call + diff --git a/docs/asciidoc/client-concepts/connection-pooling/request-overrides/request-timeouts-overrides.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/request-overrides/request-timeouts-overrides.asciidoc new file mode 100644 index 00000000000..c4a084c5c94 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/request-overrides/request-timeouts-overrides.asciidoc @@ -0,0 +1,96 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[request-timeouts]] +== Request Timeouts + +While you can specify Request time out globally you can override this per request too + +we set up a 10 node cluster with a global time out of 20 seconds. +Each call on a node takes 10 seconds. So we can only try this call on 2 nodes +before the max request time out kills the client call. + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways().Takes(TimeSpan.FromSeconds(10))) + .ClientCalls(r => r.OnPort(9209).SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing().RequestTimeout(TimeSpan.FromSeconds(20))) +); +---- + +On the second request we specify a request timeout override to 60 seconds +We should now see more nodes being tried. + +[source,csharp] +---- +audit = await audit.TraceCalls( + new ClientCall { + { BadResponse, 9200 }, + { BadResponse, 9201 }, + { MaxTimeoutReached } + }, +new ClientCall(r => r.RequestTimeout(TimeSpan.FromSeconds(80))) + { + { BadResponse, 9203 }, + { BadResponse, 9204 }, + { BadResponse, 9205 }, + { BadResponse, 9206 }, + { BadResponse, 9207 }, + { BadResponse, 9208 }, + { HealthyResponse, 9209 }, + } +); +---- + +[[connect-timeouts]] +== Connect Timeouts + +Connect timeouts can be overridden, webrequest/httpclient can not distinguish connect and retry timeouts however +we use this separate configuration value for ping requests. + +we set up a 10 node cluster with a global time out of 20 seconds. +Each call on a node takes 10 seconds. So we can only try this call on 2 nodes +before the max request time out kills the client call. + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Ping(p => p.SucceedAlways().Takes(TimeSpan.FromSeconds(20))) + .ClientCalls(r => r.SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.RequestTimeout(TimeSpan.FromSeconds(10)).PingTimeout(TimeSpan.FromSeconds(10))) +); +---- + +The first call uses the configured global settings, request times out after 10 seconds and ping +calls always take 20, so we should see a single ping failure + +On the second request we set a request ping timeout override of 2seconds +We should now see more nodes being tried before the request timeout is hit. + +[source,csharp] +---- +audit = await audit.TraceCalls( +new ClientCall { + { PingFailure, 9200 }, + { MaxTimeoutReached } + }, +new ClientCall(r => r.PingTimeout(TimeSpan.FromSeconds(2))) + { + { PingFailure, 9202 }, + { PingFailure, 9203 }, + { PingFailure, 9204 }, + { PingFailure, 9205 }, + { PingFailure, 9206 }, + { MaxTimeoutReached } + } +); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/request-overrides/respects-allowed-status-code.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/request-overrides/respects-allowed-status-code.asciidoc new file mode 100644 index 00000000000..c386afb595f --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/request-overrides/respects-allowed-status-code.asciidoc @@ -0,0 +1,27 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[allowed-status-codes]] +== Allowed status codes + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways(400)) + .StaticConnectionPool() + .Settings(s => s.DisablePing().MaximumRetries(0)) +); +audit = await audit.TraceCalls( + new ClientCall() { + { BadResponse, 9200 } + }, + new ClientCall(r => r.AllowedStatusCodes(400)) { + { HealthyResponse, 9201 } + } +); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/request-overrides/respects-force-node.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/request-overrides/respects-force-node.asciidoc new file mode 100644 index 00000000000..99d12bdf3bb --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/request-overrides/respects-force-node.asciidoc @@ -0,0 +1,28 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[forcing-nodes]] +== Forcing nodes + +Sometimes you might want to fire a single request to a specific node. You can do so using the `ForceNode` +request configuration. This will ignore the pool and not retry. + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.SucceedAlways()) + .ClientCalls(r => r.OnPort(9208).FailAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing()) +); +audit = await audit.TraceCall( + new ClientCall(r => r.ForceNode(new Uri("http://localhost:9208"))) { + { BadResponse, 9208 } + } +); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/request-overrides/respects-max-retry-overrides.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/request-overrides/respects-max-retry-overrides.asciidoc new file mode 100644 index 00000000000..fbee906c27a --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/request-overrides/respects-max-retry-overrides.asciidoc @@ -0,0 +1,76 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[maximum-retries]] +== Maximum Retries + +By default retry as many times as we have nodes. However retries still respect the request timeout. +Meaning if you have a 100 node cluster and a request timeout of 20 seconds we will retry as many times as we can +but give up after 20 seconds + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways()) + .ClientCalls(r => r.OnPort(9209).SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing()) +); +audit = await audit.TraceCall( + new ClientCall(r => r.MaxRetries(2)) { + { BadResponse, 9200 }, + { BadResponse, 9201 }, + { BadResponse, 9202 }, + { MaxRetriesReached } + } +); +---- + +When you have a 100 node cluster you might want to ensure a fixed number of retries. +Remember that the actual number of requests is initial attempt + set number of retries + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways()) + .ClientCalls(r => r.OnPort(9209).SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing().MaximumRetries(5)) +); + +audit = await audit.TraceCall( + new ClientCall(r => r.MaxRetries(2)) { + { BadResponse, 9200 }, + { BadResponse, 9201 }, + { BadResponse, 9202 }, + { MaxRetriesReached } + } +); +---- + +This makes setting any retry setting on a single node connection pool a NOOP, this is by design! +Connection pooling and connection failover is about trying to fail sanely whilst still utilizing available resources and +not giving up on the fail fast principle. It's *NOT* a mechanism for forcing requests to succeed. + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways().Takes(TimeSpan.FromSeconds(3))) + .ClientCalls(r => r.OnPort(9209).SucceedAlways()) + .SingleNodeConnection() + .Settings(s => s.DisablePing().MaximumRetries(10)) +); + +audit = await audit.TraceCall( + new ClientCall(r => r.MaxRetries(10)) { + { BadResponse, 9200 } + } +); +---- + diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/RoundRobin/RoundRobin.doc.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/round-robin/round-robin.asciidoc similarity index 65% rename from docs/asciidoc/ClientConcepts/ConnectionPooling/RoundRobin/RoundRobin.doc.asciidoc rename to docs/asciidoc/client-concepts/connection-pooling/round-robin/round-robin.asciidoc index 04cbc4ae7a0..965699f6806 100644 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/RoundRobin/RoundRobin.doc.asciidoc +++ b/docs/asciidoc/client-concepts/connection-pooling/round-robin/round-robin.asciidoc @@ -1,14 +1,22 @@ -Round Robin -Each connection pool round robins over the `live` nodes, to evenly distribute the load over all known nodes. +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current +:github: https://github.com/elastic/elasticsearch-net -== GetNext -GetNext is implemented in a lock free thread safe fashion, meaning each callee gets returned its own cursor to advance +:nuget: https://www.nuget.org/packages + +[[round-robin]] +== Round Robin + +<> and <> connection pools +round robin over the `live` nodes to evenly distribute request load over all known nodes. + +=== GetNext + +`GetNext` is implemented in a lock free thread safe fashion, meaning each callee gets returned its own cursor to advance over the internal list of nodes. This to guarantee each request that needs to fall over tries all the nodes without suffering from noisy neighboors advancing a global cursor. - -[source, csharp] +[source,csharp] ---- var uris = Enumerable.Range(9200, NumberOfNodes).Select(p => new Uri("http://localhost:" + p)); var staticPool = new StaticConnectionPool(uris, randomize: false); @@ -16,56 +24,60 @@ var sniffingPool = new SniffingConnectionPool(uris, randomize: false); this.AssertCreateView(staticPool); this.AssertCreateView(sniffingPool); ---- -Here we have setup a static connection pool seeded with 10 nodes. We force randomizationOnStartup to false -so that we can test the nodes being returned are int the order we expect them to. + +Here we have setup a static connection pool seeded with 10 nodes. We force randomization OnStartup to false +so that we can test the nodes being returned are int the order we expect them to. So what order we expect? Imagine the following: + Thread A calls GetNext first without a local cursor and takes the current from the internal global cursor which is 0. Thread B calls GetNext() second without a local cursor and therefor starts at 1. After this each thread should walk the nodes in successive order using their local cursor e.g Thread A might get 0,1,2,3,5 and thread B will get 1,2,3,4,0. -[source, csharp] +[source,csharp] ---- var startingPositions = Enumerable.Range(0, NumberOfNodes) - .Select(i => pool.CreateView().First()) - .Select(n => n.Uri.Port) - .ToList(); ----- -[source, csharp] ----- + .Select(i => pool.CreateView().First()) + .Select(n => n.Uri.Port) + .ToList(); + var expectedOrder = Enumerable.Range(9200, NumberOfNodes); + startingPositions.Should().ContainInOrder(expectedOrder); ---- What the above code just proved is that each call to GetNext(null) gets assigned the next available node. Lets up the ante: -- call get next over `NumberOfNodes * 2` threads -- on each thread call getnext `NumberOfNodes * 10` times using a local cursor. -We'll validate that each thread sees all the nodes and they they wrap over e.g after node 9209 + +* call get next over `NumberOfNodes * 2` threads + +* on each thread call getnext `NumberOfNodes * 10` times using a local cursor. +We'll validate that each thread sees all the nodes and they they wrap over e.g after node 9209 comes 9200 again -[source, csharp] +[source,csharp] ---- var threadedStartPositions = new ConcurrentBag(); ----- -[source, csharp] ----- + var threads = Enumerable.Range(0, 20) - .Select(i => CreateThreadCallingGetNext(pool, threadedStartPositions)) - .ToList(); + .Select(i => CreateThreadCallingGetNext(pool, threadedStartPositions)) + .ToList(); + t.Start(); + t.Join(); ---- + Each thread reported the first node it started off lets make sure we see each node twice as the first node because we started `NumberOfNodes * 2` threads -[source, csharp] +[source,csharp] ---- var grouped = threadedStartPositions.GroupBy(p => p).ToList(); ----- -[source, csharp] ----- + grouped.Count().Should().Be(NumberOfNodes); + grouped.Select(p => p.Count()).Should().OnlyContain(p => p == 2); ---- + diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/RoundRobin/SkipDeadNodes.doc.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/round-robin/skip-dead-nodes.asciidoc similarity index 51% rename from docs/asciidoc/ClientConcepts/ConnectionPooling/RoundRobin/SkipDeadNodes.doc.asciidoc rename to docs/asciidoc/client-concepts/connection-pooling/round-robin/skip-dead-nodes.asciidoc index 54688bd9f36..47b980a048a 100644 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/RoundRobin/SkipDeadNodes.doc.asciidoc +++ b/docs/asciidoc/client-concepts/connection-pooling/round-robin/skip-dead-nodes.asciidoc @@ -1,196 +1,220 @@ -Round Robin - Skipping Dead Nodes +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[round-robin-skipping-dead-nodes]] +== Round Robin - Skipping Dead Nodes + When selecting nodes the connection pool will try and skip all the nodes that are marked dead. +=== GetNext -== GetNext GetNext is implemented in a lock free thread safe fashion, meaning each callee gets returned its own cursor to advance over the internal list of nodes. This to guarantee each request that needs to fall over tries all the nodes without suffering from noisy neighboors advancing a global cursor. - -[source, csharp] +[source,csharp] ---- var seeds = Enumerable.Range(9200, NumberOfNodes).Select(p => new Node(new Uri("http://localhost:" + p))).ToList(); + +seeds.First().MarkDead(DateTime.Now.AddDays(1)); + var pool = new StaticConnectionPool(seeds, randomize: false); + var node = pool.CreateView().First(); -node.Uri.Port.Should().Be(9200); -node = pool.CreateView().First(); + node.Uri.Port.Should().Be(9201); + node = pool.CreateView().First(); + node.Uri.Port.Should().Be(9202); +---- + +[source,csharp] +---- var seeds = Enumerable.Range(9200, NumberOfNodes).Select(p => new Node(new Uri("http://localhost:" + p))).ToList(); -seeds.First().MarkDead(DateTime.Now.AddDays(1)); var pool = new StaticConnectionPool(seeds, randomize: false); var node = pool.CreateView().First(); +node.Uri.Port.Should().Be(9200); +node = pool.CreateView().First(); node.Uri.Port.Should().Be(9201); node = pool.CreateView().First(); node.Uri.Port.Should().Be(9202); ---- -After we marke the first node alive again we expect it to be hit again -[source, csharp] +After we marked the first node alive again, we expect it to be hit again + +[source,csharp] ---- seeds.First().MarkAlive(); ----- -[source, csharp] ----- + var node = pool.CreateView().First(); + node.Uri.Port.Should().Be(9201); + node = pool.CreateView().First(); + node.Uri.Port.Should().Be(9202); + node = pool.CreateView().First(); + node.Uri.Port.Should().Be(9200); +---- + +[source,csharp] +---- var dateTimeProvider = new TestableDateTimeProvider(); + var seeds = Enumerable.Range(9200, NumberOfNodes).Select(p => new Node(new Uri("http://localhost:" + p))).ToList(); + seeds.First().MarkDead(dateTimeProvider.Now().AddDays(1)); + var pool = new StaticConnectionPool(seeds, randomize: false, dateTimeProvider: dateTimeProvider); + var node = pool.CreateView().First(); + node.Uri.Port.Should().Be(9201); + node = pool.CreateView().First(); + node.Uri.Port.Should().Be(9202); ---- -If we forward our clock 2 days the node that was marked dead until tomorrow (or yesterday!) should be resurrected -[source, csharp] +If we roll the clock forward two days, the node that was marked dead until tomorrow (or yesterday!) should be resurrected + +[source,csharp] ---- dateTimeProvider.ChangeTime(d => d.AddDays(2)); ----- -[source, csharp] ----- + var n = pool.CreateView().First(); + n.Uri.Port.Should().Be(9201); + n = pool.CreateView().First(); + n.Uri.Port.Should().Be(9202); + n = pool.CreateView().First(); + n.Uri.Port.Should().Be(9200); + n.IsResurrected.Should().BeTrue(); ---- + A cluster with 2 nodes where the second node fails on ping -[source, csharp] +[source,csharp] ---- var audit = new Auditor(() => Framework.Cluster - .Nodes(4) - .ClientCalls(p => p.Succeeds(Always)) - .ClientCalls(p => p.OnPort(9201).FailAlways()) - .ClientCalls(p => p.OnPort(9203).FailAlways()) - .StaticConnectionPool() - .Settings(p=>p.DisablePing()) + .Nodes(4) + .ClientCalls(p => p.Succeeds(Always)) + .ClientCalls(p => p.OnPort(9201).FailAlways()) + .ClientCalls(p => p.OnPort(9203).FailAlways()) + .StaticConnectionPool() + .Settings(p=>p.DisablePing()) ); ---- -[source, csharp] ----- -await audit.TraceCalls( ----- + The first call goes to 9200 which succeeds -[source, csharp] ----- -new ClientCall { - { HealthyResponse, 9200}, - { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(0) } - }, ----- -The 2nd call does a ping on 9201 because its used for the first time. +The 2nd call does a ping on 9201 because its used for the first time. It fails so we wrap over to node 9202 -[source, csharp] ----- -new ClientCall { - { BadResponse, 9201}, - { HealthyResponse, 9202}, ----- Finally we assert that the connectionpool has one node that is marked as dead -[source, csharp] ----- -{ pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(1) } - }, ----- The next call goes to 9203 which fails so we should wrap over -[source, csharp] ----- -new ClientCall { - { BadResponse, 9203}, - { HealthyResponse, 9200}, - { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } - }, - new ClientCall { - { HealthyResponse, 9202}, - { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } - }, - new ClientCall { - { HealthyResponse, 9200}, - { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } - }, - new ClientCall { - { HealthyResponse, 9202}, - { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } - }, - new ClientCall { - { HealthyResponse, 9200}, - { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } - } +[source,csharp] +---- +await audit.TraceCalls( +new ClientCall { + { HealthyResponse, 9200}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(0) } + }, +new ClientCall { + { BadResponse, 9201}, + { HealthyResponse, 9202}, +{ pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(1) } + }, +new ClientCall { + { BadResponse, 9203}, + { HealthyResponse, 9200}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } + }, + new ClientCall { + { HealthyResponse, 9202}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } + }, + new ClientCall { + { HealthyResponse, 9200}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } + }, + new ClientCall { + { HealthyResponse, 9202}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } + }, + new ClientCall { + { HealthyResponse, 9200}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } + } ); ---- + A cluster with 2 nodes where the second node fails on ping -[source, csharp] +[source,csharp] ---- var audit = new Auditor(() => Framework.Cluster - .Nodes(4) - .ClientCalls(p => p.Fails(Always)) - .StaticConnectionPool() - .Settings(p=>p.DisablePing()) + .Nodes(4) + .ClientCalls(p => p.Fails(Always)) + .StaticConnectionPool() + .Settings(p=>p.DisablePing()) ); ---- -[source, csharp] ----- -await audit.TraceCalls( ----- + All the calls fail -[source, csharp] ----- -new ClientCall { - { BadResponse, 9200}, - { BadResponse, 9201}, - { BadResponse, 9202}, - { BadResponse, 9203}, - { MaxRetriesReached }, - { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } - }, ----- After all our registered nodes are marked dead we want to sample a single dead node each time to quickly see if the cluster is back up. We do not want to retry all 4 nodes -[source, csharp] ----- -new ClientCall { - { AllNodesDead }, - { Resurrection, 9201}, - { BadResponse, 9201}, - { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } - }, - new ClientCall { - { AllNodesDead }, - { Resurrection, 9202}, - { BadResponse, 9202}, - { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } - }, - new ClientCall { - { AllNodesDead }, - { Resurrection, 9203}, - { BadResponse, 9203}, - { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } - }, - new ClientCall { - { AllNodesDead }, - { Resurrection, 9200}, - { BadResponse, 9200}, - { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } - } +[source,csharp] +---- +await audit.TraceCalls( +new ClientCall { + { BadResponse, 9200}, + { BadResponse, 9201}, + { BadResponse, 9202}, + { BadResponse, 9203}, + { MaxRetriesReached }, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } + }, +new ClientCall { + { AllNodesDead }, + { Resurrection, 9201}, + { BadResponse, 9201}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } + }, + new ClientCall { + { AllNodesDead }, + { Resurrection, 9202}, + { BadResponse, 9202}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } + }, + new ClientCall { + { AllNodesDead }, + { Resurrection, 9203}, + { BadResponse, 9203}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } + }, + new ClientCall { + { AllNodesDead }, + { Resurrection, 9200}, + { BadResponse, 9200}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } + } ); ---- + diff --git a/docs/asciidoc/ClientConcepts/ConnectionPooling/RoundRobin/VolatileUpdates.doc.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/round-robin/volatile-updates.asciidoc similarity index 64% rename from docs/asciidoc/ClientConcepts/ConnectionPooling/RoundRobin/VolatileUpdates.doc.asciidoc rename to docs/asciidoc/client-concepts/connection-pooling/round-robin/volatile-updates.asciidoc index 33f1561476d..0a0978e29ff 100644 --- a/docs/asciidoc/ClientConcepts/ConnectionPooling/RoundRobin/VolatileUpdates.doc.asciidoc +++ b/docs/asciidoc/client-concepts/connection-pooling/round-robin/volatile-updates.asciidoc @@ -1,28 +1,39 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current +:github: https://github.com/elastic/elasticsearch-net +:nuget: https://www.nuget.org/packages +[[volatile-updates]] +== Volatile Updates -[source, csharp] +[source,csharp] ---- var uris = Enumerable.Range(9200, NumberOfNodes).Select(p => new Uri("http://localhost:" + p)); -var sniffingPool = new SniffingConnectionPool(uris, randomize: false); -Action callSniffing = () => this.AssertCreateView(sniffingPool); -callSniffing.ShouldNotThrow(); -var uris = Enumerable.Range(9200, NumberOfNodes).Select(p => new Uri("http://localhost:" + p)); + var staticPool = new StaticConnectionPool(uris, randomize: false); + Action callStatic = () => this.AssertCreateView(staticPool); + callStatic.ShouldNotThrow(); ---- - -[source, csharp] +[source,csharp] ---- -var threads = Enumerable.Range(0, 50) - .Select(i => CreateReadAndUpdateThread(pool)) - .ToList(); +var uris = Enumerable.Range(9200, NumberOfNodes).Select(p => new Uri("http://localhost:" + p)); +var sniffingPool = new SniffingConnectionPool(uris, randomize: false); +Action callSniffing = () => this.AssertCreateView(sniffingPool); +callSniffing.ShouldNotThrow(); ---- -[source, csharp] + +[source,csharp] ---- +var threads = Enumerable.Range(0, 50) + .Select(i => CreateReadAndUpdateThread(pool)) + .ToList(); + t.Start(); + t.Join(); ---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/sniffing/on-connection-failure.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/sniffing/on-connection-failure.asciidoc new file mode 100644 index 00000000000..b360772e70e --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/sniffing/on-connection-failure.asciidoc @@ -0,0 +1,154 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[sniffing-on-connection-failure]] +== Sniffing on connection failure + +Sniffing on connection is enabled by default when using a connection pool that allows reseeding. +The only IConnectionPool we ship that allows this is the <>. + +This can be very handy to force a refresh of the pools known healthy node by inspecting Elasticsearch itself. +A sniff tries to get the nodes by asking each currently known node until one response. + +Here we seed our connection with 5 known nodes 9200-9204 of which we think +9202, 9203, 9204 are master eligible nodes. Our virtualized cluster will throw once when doing +a search on 9201. This should a sniff to be kicked off. + +When the call fails on 9201 the sniff succeeds and returns a new cluster of healty nodes +this cluster only has 3 nodes and the known masters are 9200 and 9202 but a search on 9201 +still fails once + +After this second failure on 9201 another sniff will be returned a cluster that no +longer fails but looks completely different (9210-9212) we should be able to handle this + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(5) + .MasterEligible(9202, 9203, 9204) + .ClientCalls(r => r.SucceedAlways()) + .ClientCalls(r => r.OnPort(9201).Fails(Once)) +.Sniff(p => p.SucceedAlways(Framework.Cluster + .Nodes(3) + .MasterEligible(9200, 9202) + .ClientCalls(r => r.OnPort(9201).Fails(Once)) +.Sniff(s => s.SucceedAlways(Framework.Cluster + .Nodes(3, 9210) + .MasterEligible(9210, 9212) + .ClientCalls(r => r.SucceedAlways()) + .Sniff(r => r.SucceedAlways()) + )) + )) + .SniffingConnectionPool() + .Settings(s => s.DisablePing().SniffOnStartup(false)) +); +---- + +We assert we do a sniff on our first known master node 9202 + +Our pool should now have three nodes + +We assert we do a sniff on the first master node in our updated cluster + +[source,csharp] +---- +audit = await audit.TraceCalls( +new ClientCall { + { HealthyResponse, 9200 }, + { pool => pool.Nodes.Count.Should().Be(5) } + }, + new ClientCall { + { BadResponse, 9201}, +{ SniffOnFail }, + { SniffSuccess, 9202}, + { HealthyResponse, 9200}, +{ pool => pool.Nodes.Count.Should().Be(3) } + }, + new ClientCall { + { BadResponse, 9201}, +{ SniffOnFail }, + { SniffSuccess, 9200}, + { HealthyResponse, 9210}, + { pool => pool.Nodes.Count.Should().Be(3) } + }, + new ClientCall { { HealthyResponse, 9211 } }, + new ClientCall { { HealthyResponse, 9212 } }, + new ClientCall { { HealthyResponse, 9210 } }, + new ClientCall { { HealthyResponse, 9211 } }, + new ClientCall { { HealthyResponse, 9212 } }, + new ClientCall { { HealthyResponse, 9210 } }, + new ClientCall { { HealthyResponse, 9211 } }, + new ClientCall { { HealthyResponse, 9212 } }, + new ClientCall { { HealthyResponse, 9210 } } +); +---- + +Here we set up our cluster exactly the same as the previous setup +Only we enable pinging (default is true) and make the ping fail + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(5) + .MasterEligible(9202, 9203, 9204) + .Ping(r => r.OnPort(9201).Fails(Once)) + .Sniff(p => p.SucceedAlways(Framework.Cluster + .Nodes(3) + .MasterEligible(9200, 9202) + .Ping(r => r.OnPort(9201).Fails(Once)) + .Sniff(s => s.SucceedAlways(Framework.Cluster + .Nodes(3, 9210) + .MasterEligible(9210, 9211) + .Ping(r => r.SucceedAlways()) + .Sniff(r => r.SucceedAlways()) + )) + )) + .SniffingConnectionPool() + .Settings(s => s.SniffOnStartup(false)) +); +---- + +We assert we do a sniff on our first known master node 9202 + +Our pool should now have three nodes + +We assert we do a sniff on the first master node in our updated cluster + +9210 was already pinged after the sniff returned the new nodes + +[source,csharp] +---- +audit = await audit.TraceCalls( + new ClientCall { + { PingSuccess, 9200 }, + { HealthyResponse, 9200 }, + { pool => pool.Nodes.Count.Should().Be(5) } + }, + new ClientCall { + { PingFailure, 9201}, +{ SniffOnFail }, + { SniffSuccess, 9202}, + { PingSuccess, 9200}, + { HealthyResponse, 9200}, +{ pool => pool.Nodes.Count.Should().Be(3) } + }, + new ClientCall { + { PingFailure, 9201}, +{ SniffOnFail }, + { SniffSuccess, 9200}, + { PingSuccess, 9210}, + { HealthyResponse, 9210}, + { pool => pool.Nodes.Count.Should().Be(3) } + }, + new ClientCall { { PingSuccess, 9211 }, { HealthyResponse, 9211 } }, + new ClientCall { { PingSuccess, 9212 }, { HealthyResponse, 9212 } }, +new ClientCall { { HealthyResponse, 9210 } }, + new ClientCall { { HealthyResponse, 9211 } }, + new ClientCall { { HealthyResponse, 9212 } }, + new ClientCall { { HealthyResponse, 9210 } } +); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/sniffing/on-stale-cluster-state.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/sniffing/on-stale-cluster-state.asciidoc new file mode 100644 index 00000000000..b3003cdf3d5 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/sniffing/on-stale-cluster-state.asciidoc @@ -0,0 +1,100 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[sniffing-periodically]] +== Sniffing periodically + +Connection pools that return true for `SupportsReseeding` can be configured to sniff periodically. +In addition to sniffing on startup and sniffing on failures, sniffing periodically can benefit scenerio's where +clusters are often scaled horizontally during peak hours. An application might have a healthy view of a subset of the nodes +but without sniffing periodically it will never find the nodes that have been added to help out with load + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .MasterEligible(9202, 9203, 9204) + .ClientCalls(r => r.SucceedAlways()) + .Sniff(s => s.SucceedAlways(Framework.Cluster + .Nodes(100) + .MasterEligible(9202, 9203, 9204) + .ClientCalls(r => r.SucceedAlways()) + .Sniff(ss => ss.SucceedAlways(Framework.Cluster + .Nodes(10) + .MasterEligible(9202, 9203, 9204) + .ClientCalls(r => r.SucceedAlways()) + )) + )) + .SniffingConnectionPool() + .Settings(s => s + .DisablePing() + .SniffOnConnectionFault(false) + .SniffOnStartup(false) + .SniffLifeSpan(TimeSpan.FromMinutes(30)) + ) +); +---- + +healty cluster all nodes return healthy responses + +[source,csharp] +---- +audit = await audit.TraceCalls( + new ClientCall { { HealthyResponse, 9200 } }, + new ClientCall { { HealthyResponse, 9201 } }, + new ClientCall { { HealthyResponse, 9202 } }, + new ClientCall { { HealthyResponse, 9203 } }, + new ClientCall { { HealthyResponse, 9204 } }, + new ClientCall { { HealthyResponse, 9205 } }, + new ClientCall { { HealthyResponse, 9206 } }, + new ClientCall { { HealthyResponse, 9207 } }, + new ClientCall { { HealthyResponse, 9208 } }, + new ClientCall { { HealthyResponse, 9209 } }, + new ClientCall { + { HealthyResponse, 9200 }, + { pool => pool.Nodes.Count.Should().Be(10) } + } +); +---- + +Now let's forward the clock 31 minutes, our sniff lifespan should now go state +and the first call should do a sniff which discovered we scaled up to a 100 nodes! + +[source,csharp] +---- +audit.ChangeTime(d => d.AddMinutes(31)); +---- + +a sniff is done first and it prefers the first node master node + +[source,csharp] +---- +audit = await audit.TraceCalls( + new ClientCall { +{ SniffOnStaleCluster }, + { SniffSuccess, 9202 }, + { HealthyResponse, 9201 }, + { pool => pool.Nodes.Count.Should().Be(100) } + } +); + +audit.ChangeTime(d => d.AddMinutes(31)); +---- + +a sniff is done first and it prefers the first node master node + +[source,csharp] +---- +audit = await audit.TraceCalls( + new ClientCall { +{ SniffOnStaleCluster }, + { SniffSuccess, 9202 }, + { HealthyResponse, 9200 }, + { pool => pool.Nodes.Count.Should().Be(10) } + } +); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/sniffing/on-startup.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/sniffing/on-startup.asciidoc new file mode 100644 index 00000000000..42704963b28 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/sniffing/on-startup.asciidoc @@ -0,0 +1,162 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[sniffing-on-startup]] +== Sniffing on startup + +<> that return true for `SupportsReseeding` will sniff on startup by default. + +We can demonstrate this by creating a _virtual_ Elasticsearch cluster with NEST's Test Framework. +Here we create a 10 node cluster that uses a <>, setting +sniff to fail on all nodes *_except_* 9202 + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Sniff(s => s.Fails(Always)) + .Sniff(s => s.OnPort(9202).Succeeds(Always)) + .SniffingConnectionPool() + .AllDefaults() +); +---- + +When the client call is made, we can see from the audit trail that the pool first tried to sniff on startup, +with a sniff failure on 9200 and 9201, followed by a sniff success on 9202. A ping and healthy response are made on +9200 + +[source,csharp] +---- +await audit.TraceCall(new ClientCall + { + { SniffOnStartup}, + { SniffFailure, 9200}, + { SniffFailure, 9201}, + { SniffSuccess, 9202}, + { PingSuccess , 9200}, + { HealthyResponse, 9200} +}); +---- + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Sniff(s => s.Fails(Always)) + .Sniff(s => s.OnPort(9202).Succeeds(Always, Framework.Cluster.Nodes(8, startFrom: 9204))) + .SniffingConnectionPool() + .AllDefaults() +); + +await audit.TraceCall(new ClientCall { +{ SniffOnStartup}, +{ SniffFailure, 9200}, +{ SniffFailure, 9201}, +{ SniffSuccess, 9202}, +{ PingSuccess, 9204}, +{ HealthyResponse, 9204} + }); +---- + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Sniff(s => s.Fails(Always)) + .Sniff(s => s.OnPort(9209).Succeeds(Always)) + .SniffingConnectionPool() + .AllDefaults() +); + +await audit.TraceCall(new ClientCall { +{ SniffOnStartup}, +{ SniffFailure, 9200}, +{ SniffFailure, 9201}, +{ SniffFailure, 9202}, +{ SniffFailure, 9203}, +{ SniffFailure, 9204}, +{ SniffFailure, 9205}, +{ SniffFailure, 9206}, +{ SniffFailure, 9207}, +{ SniffFailure, 9208}, +{ SniffSuccess, 9209}, +{ PingSuccess, 9200}, +{ HealthyResponse, 9200} + }); +---- + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(new[] { + new Node(new Uri("http://localhost:9200")) { MasterEligible = false }, + new Node(new Uri("http://localhost:9201")) { MasterEligible = false }, + new Node(new Uri("http://localhost:9202")) { MasterEligible = true }, + }) + .Sniff(s => s.Succeeds(Always)) + .SniffingConnectionPool() + .AllDefaults() +); + +await audit.TraceCall(new ClientCall { +{ SniffOnStartup}, +{ SniffSuccess, 9202}, +{ PingSuccess, 9200}, +{ HealthyResponse, 9200} + }); +---- + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(new[] { + new Node(new Uri("http://localhost:9200")) { MasterEligible = true }, + new Node(new Uri("http://localhost:9201")) { MasterEligible = true }, + new Node(new Uri("http://localhost:9202")) { MasterEligible = false }, + }) + .Sniff(s => s.Fails(Always)) + .Sniff(s => s.OnPort(9202).Succeeds(Always)) + .SniffingConnectionPool() + .AllDefaults() +); + +await audit.TraceCall(new ClientCall { +{ SniffOnStartup}, +{ SniffFailure, 9200}, +{ SniffFailure, 9201}, +{ SniffSuccess, 9202}, +{ PingSuccess, 9200}, +{ HealthyResponse, 9200} + }); +---- + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Sniff(s => s.Fails(Always)) + .Sniff(s => s.OnPort(9202).Succeeds(Always)) + .SniffingConnectionPool() + .AllDefaults() +); +await audit.TraceCalls( + new ClientCall + { + { SniffOnStartup}, + { SniffFailure, 9200}, + { SniffFailure, 9201}, + { SniffSuccess, 9202}, + { PingSuccess , 9200}, + { HealthyResponse, 9200} + }, + new ClientCall + { + { PingSuccess, 9201}, + { HealthyResponse, 9201} + } +); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/sniffing/role-detection.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/sniffing/role-detection.asciidoc new file mode 100644 index 00000000000..69e045e1509 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/sniffing/role-detection.asciidoc @@ -0,0 +1,148 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[sniffing-role-detection]] +== Sniffing role detection + +When we sniff the cluster state, we detect the role of the node, whether it's master eligible and holds data. +We use this information when selecting a node to perform an API call on. + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Sniff(s => s.Fails(Always)) + .Sniff(s => s.OnPort(9202) + .Succeeds(Always, Framework.Cluster.Nodes(8).StoresNoData(9200, 9201, 9202)) + ) + .SniffingConnectionPool() + .AllDefaults() +) +{ + AssertPoolBeforeCall = (pool) => + { + pool.Should().NotBeNull(); + pool.Nodes.Should().HaveCount(10); + pool.Nodes.Where(n => n.HoldsData).Should().HaveCount(10); + }, + + AssertPoolAfterCall = (pool) => + { + pool.Should().NotBeNull(); + pool.Nodes.Should().HaveCount(8); + pool.Nodes.Where(n => n.HoldsData).Should().HaveCount(5); + } +}; + +await audit.TraceStartup(); +---- + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Sniff(s => s.SucceedAlways() + .Succeeds(Always, Framework.Cluster.Nodes(8).StoresNoData(9200, 9201, 9202).SniffShouldReturnFqdn()) + ) + .SniffingConnectionPool() + .AllDefaults() +) +{ + AssertPoolBeforeCall = (pool) => + { + pool.Should().NotBeNull(); + pool.Nodes.Should().HaveCount(10); + pool.Nodes.Where(n => n.HoldsData).Should().HaveCount(10); + pool.Nodes.Should().OnlyContain(n => n.Uri.Host == "localhost"); + }, + + AssertPoolAfterCall = (pool) => + { + pool.Should().NotBeNull(); + pool.Nodes.Should().HaveCount(8); + pool.Nodes.Where(n => n.HoldsData).Should().HaveCount(5); + pool.Nodes.Should().OnlyContain(n => n.Uri.Host.StartsWith("fqdn") && !n.Uri.Host.Contains("/")); + } +}; + +await audit.TraceStartup(); +---- + +[source,csharp] +---- +var node = SniffAndReturnNode(); + +node.MasterEligible.Should().BeTrue(); + +node.HoldsData.Should().BeFalse(); + +node = await SniffAndReturnNodeAsync(); + +node.MasterEligible.Should().BeTrue(); + +node.HoldsData.Should().BeFalse(); +---- + +[source,csharp] +---- +var pipeline = CreatePipeline(); + +pipeline.Sniff(); +---- + +[source,csharp] +---- +var pipeline = CreatePipeline(); + +await pipeline.SniffAsync(); +---- + +[source,csharp] +---- +this._settings = + this._cluster.Client(u => new SniffingConnectionPool(new[] {u}), c => c.PrettyJson()).ConnectionSettings; + +var pipeline = new RequestPipeline(this._settings, DateTimeProvider.Default, new MemoryStreamFactory(), + new SearchRequestParameters()); +---- + +[source,csharp] +---- +var nodes = this._settings.ConnectionPool.Nodes; + +nodes.Should().NotBeEmpty().And.HaveCount(1); + +var node = nodes.First(); +---- + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Sniff(s => s.Fails(Always)) + .Sniff(s => s.OnPort(9202) + .Succeeds(Always, Framework.Cluster.Nodes(8).MasterEligible(9200, 9201, 9202)) + ) + .SniffingConnectionPool() + .AllDefaults() +) +{ + AssertPoolBeforeCall = (pool) => + { + pool.Should().NotBeNull(); + pool.Nodes.Should().HaveCount(10); + pool.Nodes.Where(n => n.MasterEligible).Should().HaveCount(10); + }, + AssertPoolAfterCall = (pool) => + { + pool.Should().NotBeNull(); + pool.Nodes.Should().HaveCount(8); + pool.Nodes.Where(n => n.MasterEligible).Should().HaveCount(3); + } +}; +await audit.TraceStartup(); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/sticky/skip-dead-nodes.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/sticky/skip-dead-nodes.asciidoc new file mode 100644 index 00000000000..87de87660d1 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/sticky/skip-dead-nodes.asciidoc @@ -0,0 +1,197 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[skip-dead-nodes]] +== Skip Dead Nodes + +Sticky - Skipping Dead Nodes +When selecting nodes the connection pool will try and skip all the nodes that are marked dead. + +[source,csharp] +---- +var seeds = Enumerable.Range(9200, NumberOfNodes).Select(p => new Node(new Uri("http://localhost:" + p))).ToList(); + +seeds.First().MarkDead(DateTime.Now.AddDays(1)); + +var pool = new StickyConnectionPool(seeds); + +var node = pool.CreateView().First(); + +node.Uri.Port.Should().Be(9201); + +node = pool.CreateView().First(); + +node.Uri.Port.Should().Be(9201); +---- + +[source,csharp] +---- +var seeds = Enumerable.Range(9200, NumberOfNodes).Select(p => new Node(new Uri("http://localhost:" + p))).ToList(); +var pool = new StickyConnectionPool(seeds); +var node = pool.CreateView().First(); +node.Uri.Port.Should().Be(9200); +node = pool.CreateView().First(); +node.Uri.Port.Should().Be(9200); +node = pool.CreateView().First(); +node.Uri.Port.Should().Be(9200); +---- + +After we marke the first node alive again we expect it to be hit again + +[source,csharp] +---- +seeds.First().MarkAlive(); + +var node = pool.CreateView().First(); + +node.Uri.Port.Should().Be(9200); + +node = pool.CreateView().First(); + +node.Uri.Port.Should().Be(9200); + +node = pool.CreateView().First(); + +node.Uri.Port.Should().Be(9200); +---- + +[source,csharp] +---- +var dateTimeProvider = new TestableDateTimeProvider(); + +var seeds = Enumerable.Range(9200, NumberOfNodes).Select(p => new Node(new Uri("http://localhost:" + p))).ToList(); + +seeds.First().MarkDead(dateTimeProvider.Now().AddDays(1)); + +var pool = new StickyConnectionPool(seeds, dateTimeProvider: dateTimeProvider); + +var node = pool.CreateView().First(); + +node.Uri.Port.Should().Be(9201); + +node = pool.CreateView().First(); + +node.Uri.Port.Should().Be(9201); +---- + +If we forward our clock 2 days the node that was marked dead until tomorrow (or yesterday!) should be resurrected + +[source,csharp] +---- +dateTimeProvider.ChangeTime(d => d.AddDays(2)); + +var n = pool.CreateView().First(); + +n.Uri.Port.Should().Be(9200); + +n = pool.CreateView().First(); + +n.Uri.Port.Should().Be(9200); + +n = pool.CreateView().First(); + +n.Uri.Port.Should().Be(9200); + +n.IsResurrected.Should().BeTrue(); +---- + +A cluster with 2 nodes where the second node fails on ping + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(4) + .ClientCalls(p => p.Succeeds(Always)) + .ClientCalls(p => p.OnPort(9200).FailAlways()) + .ClientCalls(p => p.OnPort(9201).FailAlways()) + .StickyConnectionPool() + .Settings(p => p.DisablePing()) +); +---- + +The first call goes to 9200 which succeeds + +The 2nd call does a ping on 9201 because its used for the first time. +It fails so we wrap over to node 9202 + +Finally we assert that the connectionpool has one node that is marked as dead + +[source,csharp] +---- +await audit.TraceCalls( +new ClientCall { + { BadResponse, 9200}, + { BadResponse, 9201}, + { HealthyResponse, 9202}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } + }, +new ClientCall { + { HealthyResponse, 9202}, +{ pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } + }, + new ClientCall { + { HealthyResponse, 9202}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } + } +); +---- + +A cluster with 2 nodes where the second node fails on ping + +[source,csharp] +---- +var audit = new Auditor(() => Framework.Cluster + .Nodes(4) + .ClientCalls(p => p.Fails(Always)) + .StickyConnectionPool() + .Settings(p => p.DisablePing()) +); +---- + +All the calls fail + +After all our registered nodes are marked dead we want to sample a single dead node +each time to quickly see if the cluster is back up. We do not want to retry all 4 +nodes + +[source,csharp] +---- +await audit.TraceCalls( +new ClientCall { + { BadResponse, 9200}, + { BadResponse, 9201}, + { BadResponse, 9202}, + { BadResponse, 9203}, + { MaxRetriesReached }, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } + }, +new ClientCall { + { AllNodesDead }, + { Resurrection, 9200}, + { BadResponse, 9200}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } + }, + new ClientCall { + { AllNodesDead }, + { Resurrection, 9201}, + { BadResponse, 9201}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } + }, + new ClientCall { + { AllNodesDead }, + { Resurrection, 9202}, + { BadResponse, 9202}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } + }, + new ClientCall { + { AllNodesDead }, + { Resurrection, 9203}, + { BadResponse, 9203}, + { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } + } +); +---- + diff --git a/docs/asciidoc/client-concepts/connection-pooling/sticky/sticky.asciidoc b/docs/asciidoc/client-concepts/connection-pooling/sticky/sticky.asciidoc new file mode 100644 index 00000000000..30634533bb2 --- /dev/null +++ b/docs/asciidoc/client-concepts/connection-pooling/sticky/sticky.asciidoc @@ -0,0 +1,36 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[sticky]] +== Sticky + +Sticky +Each connection pool returns the first `live` node so that it is sticky between requests + +[source,csharp] +---- +var numberOfNodes = 10; +var uris = Enumerable.Range(9200, numberOfNodes).Select(p => new Uri("http://localhost:" + p)); +var pool = new StickyConnectionPool(uris); +---- + +Here we have setup a sticky connection pool seeded with 10 nodes. +So what order we expect? Imagine the following: + +Thread A calls GetNext and gets returned the first live node +Thread B calls GetNext() and gets returned the same node as it's still the first live. + +[source,csharp] +---- +var startingPositions = Enumerable.Range(0, numberOfNodes) + .Select(i => pool.CreateView().First()) + .Select(n => n.Uri.Port) + .ToList(); + +var expectedOrder = Enumerable.Repeat(9200, numberOfNodes); +startingPositions.Should().ContainInOrder(expectedOrder); +---- + diff --git a/docs/asciidoc/ClientConcepts/HighLevel/CovariantHits/CovariantSearchResults.doc.asciidoc b/docs/asciidoc/client-concepts/high-level/covariant-hits/covariant-search-results.asciidoc similarity index 64% rename from docs/asciidoc/ClientConcepts/HighLevel/CovariantHits/CovariantSearchResults.doc.asciidoc rename to docs/asciidoc/client-concepts/high-level/covariant-hits/covariant-search-results.asciidoc index 0ccfe32fdd1..0d14cb12ff0 100644 --- a/docs/asciidoc/ClientConcepts/HighLevel/CovariantHits/CovariantSearchResults.doc.asciidoc +++ b/docs/asciidoc/client-concepts/high-level/covariant-hits/covariant-search-results.asciidoc @@ -1,223 +1,262 @@ -# Covariant Search Results - -NEST directly supports returning covariant result sets. -Meaning a result can be typed to an interface or baseclass -but the actual instance type of the result can be that of the subclass directly - -Let look at an example, imagine we want to search over multiple types that all implement -`ISearchResult` - - - - -We have three implementations of `ISearchResult` namely `A`, `B` and `C` - - -The most straightforward way to search over multiple types is to -type the response to the parent interface or base class -and pass the actual types we want to search over using `.Types()` - -[source, csharp] ----- -var result = this._client.Search(s => s - .Type(Types.Type(typeof(A), typeof(B), typeof(C))) - .Size(100) -); ----- -Nest will translate this to a search over /index/a,b,c/_search. -hits that have `"_type" : "a"` will be serialized to `A` and so forth - -[source, csharp] ----- -result.IsValid.Should().BeTrue(); ----- -Here we assume our response is valid and that we received the 100 documents -we are expecting. Remember `result.Documents` is an `IEnumerable -ISearchResult -` - -[source, csharp] ----- -result.Documents.Count().Should().Be(100); ----- -To prove the returned result set is covariant we filter the documents based on their -actual type and assert the returned subsets are the expected sizes - -[source, csharp] ----- -var aDocuments = result.Documents.OfType(); ----- -[source, csharp] ----- -var bDocuments = result.Documents.OfType(); -var cDocuments = result.Documents.OfType(); -aDocuments.Count().Should().Be(25); -bDocuments.Count().Should().Be(25); -cDocuments.Count().Should().Be(50); ----- -and assume that properties that only exist on the subclass itself are properly filled - -[source, csharp] ----- -aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0); ----- -[source, csharp] ----- -bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0); -cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0); ----- -A more low level approach is to inspect the hit yourself and determine the CLR type to deserialize to - -[source, csharp] ----- -var result = this._client.Search(s => s - .ConcreteTypeSelector((d, h) => h.Type == "a" ? typeof(A) : h.Type == "b" ? typeof(B) : typeof(C)) - .Size(100) -); ----- -here for each hit we'll call the delegate with `d` which a dynamic representation of the `_source` -and a typed `h` which represents the encapsulating hit. - -[source, csharp] ----- -result.IsValid.Should().BeTrue(); ----- -Here we assume our response is valid and that we received the 100 documents -we are expecting. Remember `result.Documents` is an `IEnumerable -ISearchResult -` - -[source, csharp] ----- -result.Documents.Count().Should().Be(100); ----- -To prove the returned result set is covariant we filter the documents based on their -actual type and assert the returned subsets are the expected sizes - -[source, csharp] ----- -var aDocuments = result.Documents.OfType(); ----- -[source, csharp] ----- -var bDocuments = result.Documents.OfType(); -var cDocuments = result.Documents.OfType(); -aDocuments.Count().Should().Be(25); -bDocuments.Count().Should().Be(25); -cDocuments.Count().Should().Be(50); ----- -and assume that properties that only exist on the subclass itself are properly filled - -[source, csharp] ----- -aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0); ----- -[source, csharp] ----- -bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0); -cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0); ----- -Scroll also supports CovariantSearchResponses - - -Scroll() is a continuation of a previous Search() so Types() are lost. -You can hint the type types again using CovariantTypes() - -[source, csharp] ----- -var result = this._client.Scroll(TimeSpan.FromMinutes(60), "scrollId", s => s - .CovariantTypes(Types.Type(typeof(A), typeof(B), typeof(C))) -); ----- -Nest will translate this to a search over /index/a,b,c/_search. -hits that have `"_type" : "a"` will be serialized to `A` and so forth - -[source, csharp] ----- -result.IsValid.Should().BeTrue(); ----- -Here we assume our response is valid and that we received the 100 documents -we are expecting. Remember `result.Documents` is an `IEnumerable -ISearchResult -` - -[source, csharp] ----- -result.Documents.Count().Should().Be(100); ----- -To prove the returned result set is covariant we filter the documents based on their -actual type and assert the returned subsets are the expected sizes - -[source, csharp] ----- -var aDocuments = result.Documents.OfType(); ----- -[source, csharp] ----- -var bDocuments = result.Documents.OfType(); -var cDocuments = result.Documents.OfType(); -aDocuments.Count().Should().Be(25); -bDocuments.Count().Should().Be(25); -cDocuments.Count().Should().Be(50); ----- -and assume that properties that only exist on the subclass itself are properly filled - -[source, csharp] ----- -aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0); ----- -[source, csharp] ----- -bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0); -cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0); ----- -The more low level concrete type selector can also be specified on scroll - -[source, csharp] ----- -var result = this._client.Scroll(TimeSpan.FromMinutes(1), "scrollid", s => s - .ConcreteTypeSelector((d, h) => h.Type == "a" ? typeof(A) : h.Type == "b" ? typeof(B) : typeof(C)) -); ----- -here for each hit we'll call the delegate with `d` which a dynamic representation of the `_source` -and a typed `h` which represents the encapsulating hit. - -[source, csharp] ----- -result.IsValid.Should().BeTrue(); ----- -Here we assume our response is valid and that we received the 100 documents -we are expecting. Remember `result.Documents` is an `IEnumerable -ISearchResult -` - -[source, csharp] ----- -result.Documents.Count().Should().Be(100); ----- -To prove the returned result set is covariant we filter the documents based on their -actual type and assert the returned subsets are the expected sizes - -[source, csharp] ----- -var aDocuments = result.Documents.OfType(); ----- -[source, csharp] ----- -var bDocuments = result.Documents.OfType(); -var cDocuments = result.Documents.OfType(); -aDocuments.Count().Should().Be(25); -bDocuments.Count().Should().Be(25); -cDocuments.Count().Should().Be(50); ----- -and assume that properties that only exist on the subclass itself are properly filled - -[source, csharp] ----- -aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0); ----- -[source, csharp] ----- -bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0); -cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0); ----- +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[covariant-search-results]] +== Covariant Search Results + +NEST directly supports returning covariant result sets. +Meaning a result can be typed to an interface or base class +but the actual instance type of the result can be that of the subclass directly + +Let's look at an example; Imagine we want to search over multiple types that all implement `ISearchResult` + +[source,csharp] +---- +public interface ISearchResult +{ + string Name { get; set; } +} +---- + +We have three implementations of `ISearchResult` namely `A`, `B` and `C` + +[source,csharp] +---- +public class A : ISearchResult +{ + public string Name { get; set; } + public int PropertyOnA { get; set; } +} + +public class B : ISearchResult +{ + public string Name { get; set; } + public int PropertyOnB { get; set; } +} + +public class C : ISearchResult +{ + public string Name { get; set; } + public int PropertyOnC { get; set; } +} +---- + +=== Using Types + +The most straightforward way to search over multiple types is to +type the response to the parent interface or base class +and pass the actual types we want to search over using `.Type()` + +[source,csharp] +---- +var result = this._client.Search(s => s + .Type(Types.Type(typeof(A), typeof(B), typeof(C))) + .Size(100) +); +---- + +NEST will translate this to a search over `/index/a,b,c/_search`; +hits that have `"_type" : "a"` will be serialized to `A` and so forth + +Here we assume our response is valid and that we received the 100 documents +we are expecting. Remember `result.Documents` is an `IEnumerable` + +[source,csharp] +---- +result.IsValid.Should().BeTrue(); + +result.Documents.Count().Should().Be(100); +---- + +To prove the returned result set is covariant we filter the documents based on their +actual type and assert the returned subsets are the expected sizes + +[source,csharp] +---- +var aDocuments = result.Documents.OfType(); + +var bDocuments = result.Documents.OfType(); +var cDocuments = result.Documents.OfType(); +aDocuments.Count().Should().Be(25); +bDocuments.Count().Should().Be(25); +cDocuments.Count().Should().Be(50); +---- + +and assume that properties that only exist on the subclass itself are properly filled + +[source,csharp] +---- +aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0); + +bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0); +cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0); +---- + +=== Using ConcreteTypeSelector + +A more low level approach is to inspect the hit yourself and determine the CLR type to deserialize to + +[source,csharp] +---- +var result = this._client.Search(s => s + .ConcreteTypeSelector((d, h) => h.Type == "a" ? typeof(A) : h.Type == "b" ? typeof(B) : typeof(C)) + .Size(100) +); +---- + +here for each hit we'll call the delegate passed to `ConcreteTypeSelector where + +* `d` is a representation of the `_source` exposed as a `dynamic` type + +* a typed `h` which represents the encapsulating hit of the source i.e. `Hit` + +Here we assume our response is valid and that we received the 100 documents +we are expecting. Remember `result.Documents` is an `IEnumerable` + +[source,csharp] +---- +result.IsValid.Should().BeTrue(); + +result.Documents.Count().Should().Be(100); +---- + +To prove the returned result set is covariant we filter the documents based on their +actual type and assert the returned subsets are the expected sizes + +[source,csharp] +---- +var aDocuments = result.Documents.OfType(); + +var bDocuments = result.Documents.OfType(); + +var cDocuments = result.Documents.OfType(); + +aDocuments.Count().Should().Be(25); + +bDocuments.Count().Should().Be(25); + +cDocuments.Count().Should().Be(50); +---- + +and assume that properties that only exist on the subclass itself are properly filled + +[source,csharp] +---- +aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0); + +bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0); + +cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0); +---- + +=== Using CovariantTypes() + +The Scroll API is a continuation of the previous Search example so Types() are lost. +You can hint at the types using `.CovariantTypes()` + +[source,csharp] +---- +var result = this._client.Scroll(TimeSpan.FromMinutes(60), "scrollId", s => s + .CovariantTypes(Types.Type(typeof(A), typeof(B), typeof(C))) +); +---- + +NEST will translate this to a search over `/index/a,b,c/_search`; +hits that have `"_type" : "a"` will be serialized to `A` and so forth + +Here we assume our response is valid and that we received the 100 documents +we are expecting. Remember `result.Documents` is an `IEnumerable` + +[source,csharp] +---- +result.IsValid.Should().BeTrue(); + +result.Documents.Count().Should().Be(100); +---- + +To prove the returned result set is covariant we filter the documents based on their +actual type and assert the returned subsets are the expected sizes + +[source,csharp] +---- +var aDocuments = result.Documents.OfType(); + +var bDocuments = result.Documents.OfType(); + +var cDocuments = result.Documents.OfType(); + +aDocuments.Count().Should().Be(25); + +bDocuments.Count().Should().Be(25); + +cDocuments.Count().Should().Be(50); +---- + +and assume that properties that only exist on the subclass itself are properly filled + +[source,csharp] +---- +aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0); + +bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0); + +cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0); +---- + +The more low level concrete type selector can also be specified on scroll + +[source,csharp] +---- +var result = this._client.Scroll(TimeSpan.FromMinutes(1), "scrollid", s => s + .ConcreteTypeSelector((d, h) => h.Type == "a" ? typeof(A) : h.Type == "b" ? typeof(B) : typeof(C)) +); +---- + +As before, within the delegate passed to `.ConcreteTypeSelector` + +* `d` is the `_source` typed as `dynamic` + +* `h` is the encapsulating typed hit + +Here we assume our response is valid and that we received the 100 documents +we are expecting. Remember `result.Documents` is an `IEnumerable` + +[source,csharp] +---- +result.IsValid.Should().BeTrue(); + +result.Documents.Count().Should().Be(100); +---- + +To prove the returned result set is covariant we filter the documents based on their +actual type and assert the returned subsets are the expected sizes + +[source,csharp] +---- +var aDocuments = result.Documents.OfType(); + +var bDocuments = result.Documents.OfType(); + +var cDocuments = result.Documents.OfType(); + +aDocuments.Count().Should().Be(25); + +bDocuments.Count().Should().Be(25); + +cDocuments.Count().Should().Be(50); +---- + +and assume that properties that only exist on the subclass itself are properly filled + +[source,csharp] +---- +aDocuments.Should().OnlyContain(a => a.PropertyOnA > 0); + +bDocuments.Should().OnlyContain(a => a.PropertyOnB > 0); + +cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0); +---- + diff --git a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/DocumentPaths.doc.asciidoc b/docs/asciidoc/client-concepts/high-level/inference/document-paths.asciidoc similarity index 61% rename from docs/asciidoc/ClientConcepts/HighLevel/Inferrence/DocumentPaths.doc.asciidoc rename to docs/asciidoc/client-concepts/high-level/inference/document-paths.asciidoc index f493e031c5e..16fa15261b9 100644 --- a/docs/asciidoc/ClientConcepts/HighLevel/Inferrence/DocumentPaths.doc.asciidoc +++ b/docs/asciidoc/client-concepts/high-level/inference/document-paths.asciidoc @@ -1,105 +1,125 @@ -# DocumentPaths -Many API's in elasticsearch describe a path to a document. In NEST besides generating a constructor that takes -and Index, Type and Id seperately we also generate a constructor taking a DocumentPath that allows you to describe the path -to your document more succintly +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net +:nuget: https://www.nuget.org/packages -Manually newing +[[document-paths]] +== Document Paths + +Many API's in Elasticsearch describe a path to a document. In NEST, besides generating a constructor that takes +and Index, Type and Id seperately, we also generate a constructor taking a `DocumentPath` that allows you to describe the path +to your document more succintly + +=== Creating new instances here we create a new document path based on Project with the id 1 -[source, csharp] +[source,csharp] ---- IDocumentPath path = new DocumentPath(1); ----- -[source, csharp] ----- + Expect("project").WhenSerializing(path.Index); Expect("project").WhenSerializing(path.Type); Expect(1).WhenSerializing(path.Id); ---- + You can still override the inferred index and type name -[source, csharp] +[source,csharp] ---- path = new DocumentPath(1).Type("project1"); ----- -[source, csharp] ----- + Expect("project1").WhenSerializing(path.Type); path = new DocumentPath(1).Index("project1"); Expect("project1").WhenSerializing(path.Index); ---- -there is also a static way to describe such paths -[source, csharp] +and there is also a static way to describe such paths + +[source,csharp] ---- path = DocumentPath.Id(1); ----- -[source, csharp] ----- + Expect("project").WhenSerializing(path.Index); Expect("project").WhenSerializing(path.Type); Expect(1).WhenSerializing(path.Id); -var project = new Project { Name = "hello-world" }; ---- -here we create a new document path based on a Project -[source, csharp] +=== Creating from a document type instance + +if you have an instance of your document you can use it as well generate document paths + +[source,csharp] ---- -IDocumentPath path = new DocumentPath(project); +var project = new Project { Name = "hello-world" }; ---- -[source, csharp] + +here we create a new document path based on the instance of `Project`, project + +[source,csharp] ---- +IDocumentPath path = new DocumentPath(project); + Expect("project").WhenSerializing(path.Index); + Expect("project").WhenSerializing(path.Type); + Expect("hello-world").WhenSerializing(path.Id); ---- + You can still override the inferred index and type name -[source, csharp] +[source,csharp] ---- path = new DocumentPath(project).Type("project1"); ----- -[source, csharp] ----- + Expect("project1").WhenSerializing(path.Type); + path = new DocumentPath(project).Index("project1"); + Expect("project1").WhenSerializing(path.Index); ---- -there is also a static way to describe such paths -[source, csharp] +and again, there is also a static way to describe such paths + +[source,csharp] ---- path = DocumentPath.Id(project); ----- -[source, csharp] ----- + Expect("project").WhenSerializing(path.Index); + Expect("project").WhenSerializing(path.Type); + Expect("hello-world").WhenSerializing(path.Id); + DocumentPath p = project; -var project = new Project { Name = "hello-world" }; ---- -Here we can see and example how DocumentPath helps your describe your requests more tersely -[source, csharp] +=== An example with requests + +[source,csharp] ---- -var request = new IndexRequest(2) { Document = project }; +var project = new Project { Name = "hello-world" }; ---- -[source, csharp] + +we can see an example of how `DocumentPath` helps your describe your requests more tersely + +[source,csharp] ---- +var request = new IndexRequest(2) { Document = project }; + request = new IndexRequest(project) { }; ---- -when comparing with the full blown constructor and passing document manually -DocumentPath -T -'s benefits become apparent. -[source, csharp] +when comparing with the full blown constructor and passing document manually, +`DocumentPath`'s benefits become apparent. + +[source,csharp] ---- request = new IndexRequest(IndexName.From(), TypeName.From(), 2) { - Document = project + Document = project }; ---- + diff --git a/docs/asciidoc/client-concepts/high-level/inference/features-inference.asciidoc b/docs/asciidoc/client-concepts/high-level/inference/features-inference.asciidoc new file mode 100644 index 00000000000..131c375530e --- /dev/null +++ b/docs/asciidoc/client-concepts/high-level/inference/features-inference.asciidoc @@ -0,0 +1,35 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[features-inference]] +== Features Inference + +Some URIs in Elasticsearch take a `Feature` enum. +Within NEST, route values on the URI are represented as classes that implement an interface, `IUrlParameter`. +Since enums _cannot_ implement interfaces in C#, a route parameter that would be of type `Feature` is represented using the `Features` class that +the `Feature` enum implicitly converts to. + +=== Constructor + +Using the `Features` constructor directly is possible but rather involved + +[source,csharp] +---- +Features fieldString = Feature.Mappings | Feature.Aliases; +Expect("_mappings,_aliases") + .WhenSerializing(fieldString); +---- + +=== Implicit conversion + +Here we instantiate a GET index request whichs takes two features, settings and warmers. +Notice how we can use the `Feature` enum directly. + +[source,csharp] +---- +var request = new GetIndexRequest(All, Feature.Settings | Feature.Warmers); +---- + diff --git a/docs/asciidoc/client-concepts/high-level/inference/field-inference.asciidoc b/docs/asciidoc/client-concepts/high-level/inference/field-inference.asciidoc new file mode 100644 index 00000000000..2d74af7f502 --- /dev/null +++ b/docs/asciidoc/client-concepts/high-level/inference/field-inference.asciidoc @@ -0,0 +1,510 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[field-inference]] +== Field Inference + +Several places in the Elasticsearch API expect the path to a field from your original source document as a string. +NEST allows you to use C# expressions to strongly type these field path strings. + +These expressions are assigned to a type called `Field` and there are several ways to create an instance of one + +=== Constructor + +Using the constructor directly is possible but rather involved + +[source,csharp] +---- +var fieldString = new Field { Name = "name" }; +---- + +This is more cumbersome when using C# expressions since they cannot be instantiated easily + +[source,csharp] +---- +Expression> expression = p => p.Name; + +var fieldExpression = Field.Create(expression); +Expect("name") + .WhenSerializing(fieldExpression) + .WhenSerializing(fieldString); +---- + +=== Implicit Conversion + +Therefore you can also implicitly convert strings and expressions to a `Field` + +[source,csharp] +---- +Field fieldString = "name"; +---- + +but for expressions this is _still_ rather involved + +[source,csharp] +---- +Expression> expression = p => p.Name; + +Field fieldExpression = expression; + +Expect("name") + .WhenSerializing(fieldExpression) + .WhenSerializing(fieldString); +---- + +[[nest-infer]] +=== Using Nest.Infer + +To ease creating a `Field` instance from expressions, there is a static `Infer` class you can use + +[source,csharp] +---- +Field fieldString = "name"; +---- + +but for expressions this is still rather involved + +[source,csharp] +---- +var fieldExpression = Infer.Field(p => p.Name); +---- + +this can be even shortened even further using a https://msdn.microsoft.com/en-us/library/sf0df423.aspx#Anchor_0[static import in C# 6] i.e. + `using static Nest.Infer;` + +[source,csharp] +---- +fieldExpression = Field(p => p.Name); +---- + +Now that is much terser then our first example using the constructor! + +[source,csharp] +---- +Expect("name") + .WhenSerializing(fieldString) + .WhenSerializing(fieldExpression); +---- + +You can also specify boosts in the field using a string + +[source,csharp] +---- +fieldString = "name^2.1"; + +fieldString.Boost.Should().Be(2.1); +---- + +As well as using `Nest.Infer.Field` + +[source,csharp] +---- +fieldExpression = Field(p => p.Name, 2.1); + +Expect("name^2.1") + .WhenSerializing(fieldString) + .WhenSerializing(fieldExpression); +---- + +[[camel-casing]] +=== Field name casing + +By default, NEST will camel-case **all** field names to better align with typical +javascript/json conventions + +using `DefaultFieldNameInferrer()` on ConnectionSettings you can change this behavior + +[source,csharp] +---- +var setup = WithConnectionSettings(s => s.DefaultFieldNameInferrer(p => p.ToUpper())); + +setup.Expect("NAME").WhenSerializing(Field(p => p.Name)); +---- + +However `string` types are *always* passed along verbatim + +[source,csharp] +---- +setup.Expect("NaMe").WhenSerializing("NaMe"); +---- + +if you want the same behavior for expressions, simply pass a Func to `DefaultFieldNameInferrer` +to make no changes to the name + +[source,csharp] +---- +setup = WithConnectionSettings(s => s.DefaultFieldNameInferrer(p => p)); + +setup.Expect("Name").WhenSerializing(Field(p => p.Name)); +---- + +=== Complex field name expressions + +You can follow your property expression to any depth. Here we are traversing to the `LeadDeveloper` `FirstName` + +[source,csharp] +---- +Expect("leadDeveloper.firstName").WhenSerializing(Field(p => p.LeadDeveloper.FirstName)); +---- + +When dealing with collection indexers, the indexer access is ignored allowing you to traverse into properties of collections + +[source,csharp] +---- +Expect("curatedTags").WhenSerializing(Field(p => p.CuratedTags[0])); +---- + +Similarly, LINQ's `.First()` method also works + +[source,csharp] +---- +Expect("curatedTags").WhenSerializing(Field(p => p.CuratedTags.First())); + +Expect("curatedTags.added").WhenSerializing(Field(p => p.CuratedTags[0].Added)); + +Expect("curatedTags.name").WhenSerializing(Field(p => p.CuratedTags.First().Name)); +---- + +NOTE: Remember, these are _expressions_ and not actual code that will be executed + +An indexer on a dictionary is assumed to describe a property name + +[source,csharp] +---- +Expect("metadata.hardcoded").WhenSerializing(Field(p => p.Metadata["hardcoded"])); + +Expect("metadata.hardcoded.created").WhenSerializing(Field(p => p.Metadata["hardcoded"].Created)); +---- + +A cool feature here is that we'll evaluate variables passed to an indexer + +[source,csharp] +---- +var variable = "var"; + +Expect("metadata.var").WhenSerializing(Field(p => p.Metadata[variable])); + +Expect("metadata.var.created").WhenSerializing(Field(p => p.Metadata[variable].Created)); +---- + +If you are using Elasticearch's {ref_current}/_multi_fields.html[multi_fields], which you really should as they allow +you to analyze a string in a number of different ways, these __"virtual"__ sub fields +do not always map back on to your POCO. By calling `.Suffix()` on expressions, you describe the sub fields that +should be mapped and <> + +[source,csharp] +---- +Expect("leadDeveloper.firstName.raw").WhenSerializing( + Field(p => p.LeadDeveloper.FirstName.Suffix("raw"))); + +Expect("curatedTags.raw").WhenSerializing( + Field(p => p.CuratedTags[0].Suffix("raw"))); + +Expect("curatedTags.raw").WhenSerializing( + Field(p => p.CuratedTags.First().Suffix("raw"))); + +Expect("curatedTags.added.raw").WhenSerializing( + Field(p => p.CuratedTags[0].Added.Suffix("raw"))); + +Expect("metadata.hardcoded.raw").WhenSerializing( + Field(p => p.Metadata["hardcoded"].Suffix("raw"))); + +Expect("metadata.hardcoded.created.raw").WhenSerializing( + Field(p => p.Metadata["hardcoded"].Created.Suffix("raw"))); +---- + +You can even chain `.Suffix()` calls to any depth! + +[source,csharp] +---- +Expect("curatedTags.name.raw.evendeeper").WhenSerializing( + Field(p => p.CuratedTags.First().Name.Suffix("raw").Suffix("evendeeper"))); +---- + +Variables passed to suffix will be evaluated as well + +[source,csharp] +---- +var suffix = "unanalyzed"; + +Expect("metadata.var.unanalyzed").WhenSerializing( + Field(p => p.Metadata[variable].Suffix(suffix))); + +Expect("metadata.var.created.unanalyzed").WhenSerializing( + Field(p => p.Metadata[variable].Created.Suffix(suffix))); +---- + +Suffixes can also be appended to expressions using `.AppendSuffix()`. This is useful in cases where you want to apply the same suffix +to a list of fields. + +Here we have a list of expressions + +[source,csharp] +---- +var expressions = new List>> +{ + p => p.Name, + p => p.Description, + p => p.CuratedTags.First().Name, + p => p.LeadDeveloper.FirstName +}; +---- + +and we want to append the suffix "raw" to each + +[source,csharp] +---- +var fieldExpressions = + expressions.Select>, Field>(e => e.AppendSuffix("raw")).ToList(); + +Expect("name.raw").WhenSerializing(fieldExpressions[0]); + +Expect("description.raw").WhenSerializing(fieldExpressions[1]); + +Expect("curatedTags.name.raw").WhenSerializing(fieldExpressions[2]); + +Expect("leadDeveloper.firstName.raw").WhenSerializing(fieldExpressions[3]); +---- + +=== Attribute based naming + +Using NEST's property attributes you can specify a new name for the properties + +[source,csharp] +---- +public class BuiltIn +{ + [String(Name = "naam")] + public string Name { get; set; } +} +---- + +[source,csharp] +---- +Expect("naam").WhenSerializing(Field(p => p.Name)); +---- + +Starting with NEST 2.x, we also ask the serializer if it can resolve a property to a name. +Here we ask the default `JsonNetSerializer` to resolve a property name and it takes +the `JsonPropertyAttribute` into account + +[source,csharp] +---- +public class SerializerSpecific +{ + [JsonProperty("nameInJson")] + public string Name { get; set; } +} +---- + +[source,csharp] +---- +Expect("nameInJson").WhenSerializing(Field(p => p.Name)); +---- + +If both a NEST property attribute and a serializer specific attribute are present on a property, +**NEST attributes take precedence** + +[source,csharp] +---- +public class Both +{ + [String(Name = "naam")] + [JsonProperty("nameInJson")] + public string Name { get; set; } +} +---- + +[source,csharp] +---- +Expect("naam").WhenSerializing(Field(p => p.Name)); + +Expect(new +{ + naam = "Martijn Laarman" +}).WhenSerializing(new Both { Name = "Martijn Laarman" }); +---- + +[[field-inference-caching]] +=== Field Inference Caching + +Resolution of field names is cached _per_ `ConnectionSettings` instance. To demonstrate, +take the following simple POCOs + +[source,csharp] +---- +class A { public C C { get; set; } } + +class B { public C C { get; set; } } + +class C { public string Name { get; set; } } +---- + +[source,csharp] +---- +var connectionSettings = TestClient.CreateSettings(forceInMemory: true); + +var client = new ElasticClient(connectionSettings); + +var fieldNameOnA = client.Infer.Field(Field(p => p.C.Name)); + +var fieldNameOnB = client.Infer.Field(Field(p => p.C.Name)); +---- + +Here we have to similary shaped expressions on coming from A and on from B +that will resolve to the same field name, as expected + +[source,csharp] +---- +fieldNameOnA.Should().Be("c.name"); + +fieldNameOnB.Should().Be("c.name"); +---- + +now we create a new connection settings with a re-map for `C` on class `A` to `"d"` +now when we resolve the field path for property `C` on `A`, it will be different than +for property `C` on `B` + +[source,csharp] +---- +var newConnectionSettings = TestClient.CreateSettings(forceInMemory: true, modifySettings: s => s + .InferMappingFor(m => m + .Rename(p => p.C, "d") + ) +); + +var newClient = new ElasticClient(newConnectionSettings); + +fieldNameOnA = newClient.Infer.Field(Field(p => p.C.Name)); + +fieldNameOnB = newClient.Infer.Field(Field(p => p.C.Name)); + +fieldNameOnA.Should().Be("d.name"); + +fieldNameOnB.Should().Be("c.name"); +---- + +however we didn't break inferrence on the first client instance using its separate connection settings + +[source,csharp] +---- +fieldNameOnA = client.Infer.Field(Field(p => p.C.Name)); + +fieldNameOnB = client.Infer.Field(Field(p => p.C.Name)); + +fieldNameOnA.Should().Be("c.name"); + +fieldNameOnB.Should().Be("c.name"); +---- + +[[field-inference-precedence]] +=== Inference Precedence + +To wrap up, the precedence in which field names are inferred is: + +. A hard rename of the property on connection settings using `.Rename()` + +. A NEST property mapping + +. Ask the serializer if the property has a verbatim value e.g it has an explicit JsonPropery attribute. + +. Pass the MemberInfo's Name to the DefaultFieldNameInferrer which by default camelCases + +The following example class will demonstrate this precedence + +[source,csharp] +---- +class Precedence +{ + [String(Name = "renamedIgnoresNest")] + [JsonProperty("renamedIgnoresJsonProperty")] + public string RenamedOnConnectionSettings { get; set; } <1> + + [String(Name = "nestAtt")] + [JsonProperty("jsonProp")] + public string NestAttribute { get; set; } <2> + + [JsonProperty("jsonProp")] + public string JsonProperty { get; set; } <3> + + [JsonProperty("dontaskme")] + public string AskSerializer { get; set; } <4> + + public string DefaultFieldNameInferrer { get; set; } <5> +} +---- +<1> Even though this property has a NEST property mapping _and_ a `JsonProperty` attribute, We are going to provide a hard rename for it on ConnectionSettings later that should win. + +<2> This property has both a NEST attribute and a `JsonProperty`, NEST should win. + +<3> We should take the json property into account by itself + +<4> This property we are going to special case in our custom serializer to resolve to ask + +<5> We are going to register a DefaultFieldNameInferrer on ConnectionSettings that will uppercase all properties. + +Here we create a custom serializer that renames any property named `AskSerializer` to `ask` + +[source,csharp] +---- +class CustomSerializer : JsonNetSerializer +{ + public CustomSerializer(IConnectionSettingsValues settings) : base(settings) { } + + public override IPropertyMapping CreatePropertyMapping(MemberInfo memberInfo) + { + return memberInfo.Name == nameof(Precedence.AskSerializer) + ? new PropertyMapping { Name = "ask" } + : base.CreatePropertyMapping(memberInfo); + } +} +---- + +here we provide an explicit rename of a property on `ConnectionSettings` using `.Rename()` +and all properties that are not mapped verbatim should be uppercased + +[source,csharp] +---- +var usingSettings = WithConnectionSettings(s => s + + .InferMappingFor(m => m + .Rename(p => p.RenamedOnConnectionSettings, "renamed") + ) + .DefaultFieldNameInferrer(p => p.ToUpperInvariant()) +).WithSerializer(s => new CustomSerializer(s)); + +usingSettings.Expect("renamed").ForField(Field(p => p.RenamedOnConnectionSettings)); + +usingSettings.Expect("nestAtt").ForField(Field(p => p.NestAttribute)); + +usingSettings.Expect("jsonProp").ForField(Field(p => p.JsonProperty)); + +usingSettings.Expect("ask").ForField(Field(p => p.AskSerializer)); + +usingSettings.Expect("DEFAULTFIELDNAMEINFERRER").ForField(Field(p => p.DefaultFieldNameInferrer)); +---- + +The same naming rules also apply when indexing a document + +[source,csharp] +---- +usingSettings.Expect(new [] +{ + "ask", + "DEFAULTFIELDNAMEINFERRER", + "jsonProp", + "nestAtt", + "renamed" +}).AsPropertiesOf(new Precedence +{ + RenamedOnConnectionSettings = "renamed on connection settings", + NestAttribute = "using a nest attribute", + JsonProperty = "the default serializer resolves json property attributes", + AskSerializer = "serializer fiddled with this one", + DefaultFieldNameInferrer = "shouting much?" +}); +---- + diff --git a/docs/asciidoc/client-concepts/high-level/inference/ids-inference.asciidoc b/docs/asciidoc/client-concepts/high-level/inference/ids-inference.asciidoc new file mode 100644 index 00000000000..ed0629c6161 --- /dev/null +++ b/docs/asciidoc/client-concepts/high-level/inference/ids-inference.asciidoc @@ -0,0 +1,139 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[ids-inference]] +== Ids Inference + +=== Implicit Conversions + +Several places in the Elasticsearch API expect an `Id` object to be passed. +This is a special box type that you can implicitly convert to from the following types + +* `Int32` + +* `Int64` + +* `String` + +* `Guid` + +Methods that take an `Id` can be passed any of these types and it will be implicitly converted to an `Id` + +[source,csharp] +---- +Id idFromInt = 1; +Id idFromLong = 2L; +Id idFromString = "hello-world"; +Id idFromGuid = new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"); +Expect(1).WhenSerializing(idFromInt); +Expect(2).WhenSerializing(idFromLong); +Expect("hello-world").WhenSerializing(idFromString); +Expect("d70bd3cf-4e38-46f3-91ca-fcbef29b148e").WhenSerializing(idFromGuid); +---- + +=== Inferring from a Type + +Sometimes a method takes an object and we need an Id from that object to build up a path. +There is no implicit conversion from any object to Id but we can call `Id.From`. + +Imagine your codebase has the following type that we want to index into Elasticsearch + +[source,csharp] +---- +class MyDTO +{ + public Guid Id { get; set; } + public string Name { get; set; } + public string OtherName { get; set; } +} +---- + +By default NEST will try to find a property called `Id` on the class using reflection +and create a cached fast func delegate based on the properties getter + +[source,csharp] +---- +var dto = new MyDTO +{ + Id = new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"), + Name = "x", + OtherName = "y" +}; + +Expect("d70bd3cf-4e38-46f3-91ca-fcbef29b148e").WhenInferringIdOn(dto); +---- + +Using the connection settings you can specify a different property that NEST should use to infer the document Id. +Here we instruct NEST to infer the Id for `MyDTO` based on its `Name` property + +[source,csharp] +---- +WithConnectionSettings(x => x + .InferMappingFor(m => m + .IdProperty(p => p.Name) + ) +).Expect("x").WhenInferringIdOn(dto); +---- + +IMPORTANT: Inference rules are cached __per__ `ConnectionSettings` instance. + +Because the cache is per `ConnectionSettings` instance, we can create another `ConnectionSettings` instance +with different inference rules + +[source,csharp] +---- +WithConnectionSettings(x => x + .InferMappingFor(m => m + .IdProperty(p => p.OtherName) + ) +).Expect("y").WhenInferringIdOn(dto); +---- + +=== Using the ElasticsearchType attribute + +Another way is to mark the type with an `ElasticsearchType` attribute, setting `IdProperty` +to the name of the property that should be used for the document id + +[source,csharp] +---- +[ElasticsearchType(IdProperty = nameof(Name))] +class MyOtherDTO +{ + public Guid Id { get; set; } + public string Name { get; set; } + public string OtherName { get; set; } +} +---- + +Now when we infer the id we expect it to be the value of the `Name` property without doing any configuration on the `ConnectionSettings` + +[source,csharp] +---- +var dto = new MyOtherDTO +{ + Id = new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"), + Name = "x", + OtherName = "y" +}; + +Expect("x").WhenInferringIdOn(dto); +---- + +=== Using Mapping inference on ConnectionSettings + +This attribute *is* cached statically/globally, however an inference rule on the `ConnectionSettings` for the type will +still win over the attribute. Here we demonstrate this by creating a different `ConnectionSettings` instance +that will infer the document id from the property `OtherName`: + +[source,csharp] +---- +WithConnectionSettings(x => x + .InferMappingFor(m => m + .IdProperty(p => p.OtherName) + ) +).Expect("y").WhenInferringIdOn(dto); +---- + diff --git a/docs/asciidoc/client-concepts/high-level/inference/index-name-inference.asciidoc b/docs/asciidoc/client-concepts/high-level/inference/index-name-inference.asciidoc new file mode 100644 index 00000000000..c031e5dd8fe --- /dev/null +++ b/docs/asciidoc/client-concepts/high-level/inference/index-name-inference.asciidoc @@ -0,0 +1,107 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[index-name-inference]] +== Index Name Inference + +Many endpoints within the Elasticsearch API expect to receive one or more index names +as part of the request in order to know what index/indices a request should operate on. + +NEST has a number of ways in which an index name can be specified + +=== Default Index name on ConnectionSettings + +A default index name can be specified on `ConnectionSettings` usinf `.DefaultIndex()`. +This is the default index name to use when no other index name can be resolved for a request + +[source,csharp] +---- +var settings = new ConnectionSettings() + .DefaultIndex("defaultindex"); +var resolver = new IndexNameResolver(settings); +var index = resolver.Resolve(); +index.Should().Be("defaultindex"); +---- + +=== Mapping an Index name for POCOs + +A index name can be mapped for CLR types using `.MapDefaultTypeIndices()` on `ConnectionSettings`. + +[source,csharp] +---- +var settings = new ConnectionSettings() + .MapDefaultTypeIndices(m => m + .Add(typeof(Project), "projects") + ); + +var resolver = new IndexNameResolver(settings); + +var index = resolver.Resolve(); + +index.Should().Be("projects"); +---- + +=== Mapping an Index name for POCOs + +An index name for a POCO provided using `.MapDefaultTypeIndices()` **will take precedence** over +the default index name + +[source,csharp] +---- +var settings = new ConnectionSettings() + .DefaultIndex("defaultindex") + .MapDefaultTypeIndices(m => m + .Add(typeof(Project), "projects") + ); + +var resolver = new IndexNameResolver(settings); + +var index = resolver.Resolve(); + +index.Should().Be("projects"); +---- + +=== Explicitly specifying Index name on the request + +For API calls that expect an index name, the index name can be explicitly provided +on the request + +[source,csharp] +---- +Uri requestUri = null; + +var client = TestClient.GetInMemoryClient(s => s + .OnRequestCompleted(r => { requestUri = r.Uri; })); + +var response = client.Search(s => s.Index("some-other-index")); <1> + +requestUri.Should().NotBeNull(); + +requestUri.LocalPath.Should().StartWith("/some-other-index/"); +---- +<1> Provide the index name on the request + +When an index name is provided on a request, it **will take precedence** over the default +index name and any index name specified for the POCO type using `.MapDefaultTypeIndices()` + +[source,csharp] +---- +var client = TestClient.GetInMemoryClient(s => + new ConnectionSettings() + .DefaultIndex("defaultindex") + .MapDefaultTypeIndices(m => m + .Add(typeof(Project), "projects") + ) +); + +var response = client.Search(s => s.Index("some-other-index")); <1> + +response.ApiCall.Uri.Should().NotBeNull(); + +response.ApiCall.Uri.LocalPath.Should().StartWith("/some-other-index/"); +---- +<1> Provide the index name on the request + diff --git a/docs/asciidoc/client-concepts/high-level/inference/indices-paths.asciidoc b/docs/asciidoc/client-concepts/high-level/inference/indices-paths.asciidoc new file mode 100644 index 00000000000..7e9d632632b --- /dev/null +++ b/docs/asciidoc/client-concepts/high-level/inference/indices-paths.asciidoc @@ -0,0 +1,67 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[indices-paths]] +== Indices paths + +Some API's in Elasticsearch take one or many index name or a special `_all` marker to send the request to all the indices +In nest this is encoded using `Indices`. + +=== Implicit Conversion + +Several types implicitly convert to `Indices` + +[source,csharp] +---- +Nest.Indices singleIndexFromString = "name"; +Nest.Indices multipleIndicesFromString = "name1, name2"; +Nest.Indices allFromString = "_all"; +Nest.Indices allWithOthersFromString = "_all, name2"; +singleIndexFromString.Match( + all => all.Should().BeNull(), + many => many.Indices.Should().HaveCount(1).And.Contain("name") +); +multipleIndicesFromString.Match( + all => all.Should().BeNull(), + many => many.Indices.Should().HaveCount(2).And.Contain("name2") +); +allFromString.Match( + all => all.Should().NotBeNull(), + many => many.Indices.Should().BeNull() +); +allWithOthersFromString.Match( + all => all.Should().NotBeNull(), + many => many.Indices.Should().BeNull() +); +---- + +[[nest-indices]] +=== Using Nest.Indices + +To ease creating `IndexName` or `Indices` from expressions, there is a static `Nest.Indices` class you can use + +[source,csharp] +---- +var all = Nest.Indices.All; <1> + +var many = Nest.Indices.Index("name1", "name2"); <2> + +var manyTyped = Nest.Indices.Index().And(); <3> + +var singleTyped = Nest.Indices.Index(); + +var singleString = Nest.Indices.Index("name1"); + +var invalidSingleString = Nest.Indices.Index("name1, name2"); <4> +---- +<1> Using `_all` indices + +<2> specifying multiple indices using strings + +<3> speciying multiple using types + +<4> an **invalid** single index name + diff --git a/docs/asciidoc/client-concepts/high-level/inference/property-inference.asciidoc b/docs/asciidoc/client-concepts/high-level/inference/property-inference.asciidoc new file mode 100644 index 00000000000..2786e785bcc --- /dev/null +++ b/docs/asciidoc/client-concepts/high-level/inference/property-inference.asciidoc @@ -0,0 +1,101 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[property-inference]] +== Property Name Inference + +=== Appending suffixes to a Lambda expression body + +Suffixes can be appended to the body of a lambda expression, useful in cases where +you have a POCO property mapped as a {ref_current}/_multi_fields.html[multi_field] +and want to use strongly typed access based on the property, yet append a suffix to the +generated field name in order to access a particular `multi_field`. + +The `.Suffix()` extension method can be used for this purpose and when serializing expressions suffixed +in this way, the serialized field name resolves to the last token + +[source,csharp] +---- +Expression> expression = p => p.Name.Suffix("raw"); +Expect("raw").WhenSerializing(expression); +---- + +=== Appending suffixes to a Lambda expression + +Alternatively, suffixes can be applied to a lambda expression directly using +the `.ApplySuffix()` extension method. Again, the serialized field name +resolves to the last token + +[source,csharp] +---- +Expression> expression = p => p.Name; + +expression = expression.AppendSuffix("raw"); + +Expect("raw").WhenSerializing(expression); +---- + +=== Naming conventions + +Currently, the name of a field cannot contain a `.` in Elasticsearch due to the potential for ambiguity with +a field that is mapped as a {ref_current}/_multi_fields.html[multi_field]. + +In these cases, NEST allows the call to go to Elasticsearch, deferring the naming conventions to the server side and, +in the case of a `.` in a field name, a `400 Bad Response` is returned with a server error indicating the reason + +[source,csharp] +---- +var createIndexResponse = _client.CreateIndex("random-" + Guid.NewGuid().ToString().ToLowerInvariant(), c => c + .Mappings(m => m + .Map("type-with-dot", mm => mm + .Properties(p => p + .String(s => s + .Name("name-with.dot") + ) + ) + ) + ) +); +---- + +The response is not valid + +[source,csharp] +---- +createIndexResponse.IsValid.Should().BeFalse(); +---- + +`DebugInformation` provides an audit trail of information to help diagnose the issue + +[source,csharp] +---- +createIndexResponse.DebugInformation.Should().NotBeNullOrEmpty(); +---- + +`ServerError` contains information about the response from Elasticsearch + +[source,csharp] +---- +createIndexResponse.ServerError.Should().NotBeNull(); + +createIndexResponse.ServerError.Status.Should().Be(400); + +createIndexResponse.ServerError.Error.Should().NotBeNull(); + +createIndexResponse.ServerError.Error.RootCause.Should().NotBeNullOrEmpty(); + +var rootCause = createIndexResponse.ServerError.Error.RootCause[0]; +---- + +We can see that the underlying reason is a `.` in the field name "name-with.dot" + +[source,csharp] +---- +rootCause.Reason.Should().Be("Field name [name-with.dot] cannot contain '.'"); + +rootCause.Type.Should().Be("mapper_parsing_exception"); +---- + diff --git a/docs/asciidoc/client-concepts/high-level/mapping/auto-map.asciidoc b/docs/asciidoc/client-concepts/high-level/mapping/auto-map.asciidoc new file mode 100644 index 00000000000..5f85fc5fe5b --- /dev/null +++ b/docs/asciidoc/client-concepts/high-level/mapping/auto-map.asciidoc @@ -0,0 +1,1063 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[auto-map]] +== Auto mapping properties + +When creating a mapping (either when creating an index or via the put mapping API), +NEST offers a feature called `.AutoMap()`, which will automagically infer the correct +Elasticsearch datatypes of the POCO properties you are mapping. Alternatively, if +you're using attributes to map your properties, then calling `.AutoMap()` is required +in order for your attributes to be applied. We'll look at the features of auto mapping +with a number of examples. + +For these examples, we'll define two POCOS, `Company`, which has a name +and a collection of Employees, and `Employee` which has various properties of +different types, and itself has a collection of `Employee` types. + +[source,csharp] +---- +public class Company +{ + public string Name { get; set; } + public List Employees { get; set; } +} + +public class Employee +{ + public string FirstName { get; set; } + public string LastName { get; set; } + public int Salary { get; set; } + public DateTime Birthday { get; set; } + public bool IsManager { get; set; } + public List Employees { get; set; } + public TimeSpan Hours { get; set; } +} +---- + +=== Manual mapping + +To create a mapping for our Company type, we can use the fluent API +and map each property explicitly + +[source,csharp] +---- +var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m + .Properties(ps => ps + .String(s => s + .Name(c => c.Name) <1> + ) + .Object(o => o <2> + .Name(c => c.Employees) + .Properties(eps => eps + .String(s => s + .Name(e => e.FirstName) + ) + .String(s => s + .Name(e => e.LastName) + ) + .Number(n => n + .Name(e => e.Salary) + .Type(NumberType.Integer) + ) + ) + ) + ) + ) + ); +---- +<1> map `Name` as a `string` type +<2> map `Employees` as an `object` type, mapping each of the properties of `Employee` + +This is all fine and dandy and useful for some use cases however in most cases +this can become verbose and wieldy. The majority of the time you simply just want to map *all* +the properties of a POCO in a single go. + +[source,csharp] +---- +var expected = new +{ + mappings = new + { + company = new + { + properties = new + { + name = new + { + type = "string" + }, + employees = new + { + type = "object", + properties = new + { + firstName = new + { + type = "string" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "integer" + } + } + } + } + } + } +}; + +Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); +---- + +=== Simple Automapping + +This is exactly where `.AutoMap()` becomes useful. Instead of manually mapping each property, +explicitly, we can instead call `.AutoMap()` for each of our mappings and let NEST do all the work + +[source,csharp] +---- +var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m.AutoMap()) + .Map(m => m.AutoMap()) + ); +---- + +Observe that NEST has inferred the Elasticsearch types based on the CLR type of our POCO properties. +In this example, + +* Birthday was mapped as a `date`, + +* Hours was mapped as a `long` (ticks) + +* IsManager was mapped as a `bool`, + +* Salary as an `integer` + +* Employees as an `object` + +and the remaining string properties as `string` types + +[source,csharp] +---- +var expected = new +{ + mappings = new + { + company = new + { + properties = new + { + employees = new + { + properties = new + { + birthday = new + { + type = "date" + }, + employees = new + { + properties = new { }, + type = "object" + }, + firstName = new + { + type = "string" + }, + hours = new + { + type = "long" + }, + isManager = new + { + type = "boolean" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "integer" + } + }, + type = "object" + }, + name = new + { + type = "string" + } + } + }, + employee = new + { + properties = new + { + birthday = new + { + type = "date" + }, + employees = new + { + properties = new { }, + type = "object" + }, + firstName = new + { + type = "string" + }, + hours = new + { + type = "long" + }, + isManager = new + { + type = "boolean" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "integer" + } + } + } + } +}; + +Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); +---- + +[[auto-mapping-with-overrides]] +[float] +== Auto mapping with overrides + +In most cases, you'll want to map more than just the vanilla datatypes and also provide +various options for your properties (analyzer to use, whether to enable doc_values, etc...). +In that case, it's possible to use `.AutoMap()` in conjuction with explicitly mapped properties. + +Here we are using `.AutoMap()` to automatically map our company type, but then we're +overriding our employee property and making it a `nested` type, since by default, `.AutoMap()` will infer objects as `object`. + +[source,csharp] +---- +var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m + .AutoMap() + .Properties(ps => ps + .Nested(n => n + .Name(c => c.Employees) + ) + ) + ) + ); + +var expected = new +{ + mappings = new + { + company = new + { + properties = new + { + name = new + { + type = "string" + }, + employees = new + { + type = "nested", + } + } + } + } +}; + +Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); +---- + +`.AutoMap()` is idempotent; calling it before or after manually +mapped properties will still yield the same results. + +[source,csharp] +---- +descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m + .Properties(ps => ps + .Nested(n => n + .Name(c => c.Employees) + ) + ) + .AutoMap() + ) + ); + +Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); +---- + +[[attribute-mapping]] +[float] +== Attribute mapping + +It is also possible to define your mappings using attributes on your POCOs. When you +use attributes, you *must* use `.AutoMap()` in order for the attributes to be applied. +Here we define the same two types as before, but this time using attributes to define the mappings. + +[source,csharp] +---- +[ElasticsearchType(Name = "company")] +public class CompanyWithAttributes +{ + [String(Analyzer = "keyword", NullValue = "null", Similarity = SimilarityOption.BM25)] + public string Name { get; set; } + + [String(Name = "office_hours")] + public TimeSpan? HeadOfficeHours { get; set; } + + [Object(Path = "employees", Store = false)] + public List Employees { get; set; } +} + +[ElasticsearchType(Name = "employee")] +public class EmployeeWithAttributes +{ + [String(Name = "first_name")] + public string FirstName { get; set; } + + [String(Name = "last_name")] + public string LastName { get; set; } + + [Number(DocValues = false, IgnoreMalformed = true, Coerce = true)] + public int Salary { get; set; } + + [Date(Format = "MMddyyyy", NumericResolution = NumericResolutionUnit.Seconds)] + public DateTime Birthday { get; set; } + + [Boolean(NullValue = false, Store = true)] + public bool IsManager { get; set; } + + [Nested(Path = "employees")] + [JsonProperty("empl")] + public List Employees { get; set; } +} +---- + +Then we map the types by calling `.AutoMap()` + +[source,csharp] +---- +var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m.AutoMap()) + .Map(m => m.AutoMap()) + ); + +var expected = new +{ + mappings = new + { + company = new + { + properties = new + { + employees = new + { + path = "employees", + properties = new + { + birthday = new + { + type = "date" + }, + employees = new + { + properties = new { }, + type = "object" + }, + firstName = new + { + type = "string" + }, + hours = new + { + type = "long" + }, + isManager = new + { + type = "boolean" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "integer" + } + }, + store = false, + type = "object" + }, + name = new + { + analyzer = "keyword", + null_value = "null", + similarity = "BM25", + type = "string" + }, + office_hours = new + { + type = "string" + } + } + }, + employee = new + { + properties = new + { + birthday = new + { + format = "MMddyyyy", + numeric_resolution = "seconds", + type = "date" + }, + empl = new + { + path = "employees", + properties = new + { + birthday = new + { + type = "date" + }, + employees = new + { + properties = new { }, + type = "object" + }, + firstName = new + { + type = "string" + }, + hours = new + { + type = "long" + }, + isManager = new + { + type = "boolean" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "integer" + } + }, + type = "nested" + }, + first_name = new + { + type = "string" + }, + isManager = new + { + null_value = false, + store = true, + type = "boolean" + }, + last_name = new + { + type = "string" + }, + salary = new + { + coerce = true, + doc_values = false, + ignore_malformed = true, + type = "double" + } + } + } + } +}; + +Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor); +---- + +Just as we were able to override the inferred properties in our earlier example, explicit (manual) +mappings also take precedence over attributes. Therefore we can also override any mappings applied +via any attributes defined on the POCO + +[source,csharp] +---- +var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m + .AutoMap() + .Properties(ps => ps + .Nested(n => n + .Name(c => c.Employees) + ) + ) + ) + .Map(m => m + .AutoMap() + .TtlField(ttl => ttl + .Enable() + .Default("10m") + ) + .Properties(ps => ps + .String(s => s + .Name(e => e.FirstName) + .Fields(fs => fs + .String(ss => ss + .Name("firstNameRaw") + .Index(FieldIndexOption.NotAnalyzed) + ) + .TokenCount(t => t + .Name("length") + .Analyzer("standard") + ) + ) + ) + .Number(n => n + .Name(e => e.Salary) + .Type(NumberType.Double) + .IgnoreMalformed(false) + ) + .Date(d => d + .Name(e => e.Birthday) + .Format("MM-dd-yy") + ) + ) + ) + ); + +var expected = new +{ + mappings = new + { + company = new + { + properties = new + { + employees = new + { + type = "nested" + }, + name = new + { + analyzer = "keyword", + null_value = "null", + similarity = "BM25", + type = "string" + }, + office_hours = new + { + type = "string" + } + } + }, + employee = new + { + _ttl = new + { + enabled = true, + @default = "10m" + }, + properties = new + { + birthday = new + { + format = "MM-dd-yy", + type = "date" + }, + empl = new + { + path = "employees", + properties = new + { + birthday = new + { + type = "date" + }, + employees = new + { + properties = new { }, + type = "object" + }, + firstName = new + { + type = "string" + }, + hours = new + { + type = "long" + }, + isManager = new + { + type = "boolean" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "integer" + } + }, + type = "nested" + }, + first_name = new + { + fields = new + { + firstNameRaw = new + { + index = "not_analyzed", + type = "string" + }, + length = new + { + type = "token_count", + analyzer = "standard" + } + }, + type = "string" + }, + isManager = new + { + null_value = false, + store = true, + type = "boolean" + }, + last_name = new + { + type = "string" + }, + salary = new + { + ignore_malformed = false, + type = "double" + } + } + } + } +}; + +Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); +---- + +[[ignoring-properties]] +[float] +== Ignoring Properties + +Properties on a POCO can be ignored in a few ways: + +* Using the `Ignore` property on a derived `ElasticsearchPropertyAttribute` type applied to the property that should be ignored on the POCO + +* Using the `.InferMappingFor(Func, IClrTypeMapping> selector)` on the connection settings + +* Using an ignore attribute applied to the POCO property that is understood by the `IElasticsearchSerializer` used, and inspected inside of the `CreatePropertyMapping()` on the serializer. In the case of the default `JsonNetSerializer`, this is the Json.NET `JsonIgnoreAttribute` + +This example demonstrates all ways, using the `Ignore` property on the attribute to ignore the property `PropertyToIgnore`, the infer mapping to ignore the +property `AnotherPropertyToIgnore` and the json serializer specific attribute to ignore the property `JsonIgnoredProperty` + +[source,csharp] +---- +[ElasticsearchType(Name = "company")] +public class CompanyWithAttributesAndPropertiesToIgnore +{ + public string Name { get; set; } + + [String(Ignore = true)] + public string PropertyToIgnore { get; set; } + + public string AnotherPropertyToIgnore { get; set; } + + [JsonIgnore] + public string JsonIgnoredProperty { get; set; } +} +---- + +All of the properties except `Name` have been ignored in the mapping + +[source,csharp] +---- +var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m + .AutoMap() + ) + ); + +var expected = new +{ + mappings = new + { + company = new + { + properties = new + { + name = new + { + type = "string" + } + } + } + } +}; + +var settings = WithConnectionSettings(s => s + .InferMappingFor(i => i + .Ignore(p => p.AnotherPropertyToIgnore) + ) +); + +settings.Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); +---- + +[[mapping-recursion]] +[float] +== Mapping Recursion + +If you notice in our previous `Company` and `Employee` examples, the `Employee` type is recursive +in that the `Employee` class itself contains a collection of type `Employee`. By default, `.AutoMap()` will only +traverse a single depth when it encounters recursive instances like this. Hence, in the +previous examples, the collection of type `Employee` on the `Employee` class did not get any of its properties mapped. +This is done as a safe-guard to prevent stack overflows and all the fun that comes with +infinite recursion. Additionally, in most cases, when it comes to Elasticsearch mappings, it is +often an edge case to have deeply nested mappings like this. However, you may still have +the need to do this, so you can control the recursion depth of `.AutoMap()`. + +Let's introduce a very simple class, `A`, which itself has a property +Child of type `A`. + +[source,csharp] +---- +public class A +{ + public A Child { get; set; } +} +---- + +By default, `.AutoMap()` only goes as far as depth 1 + +[source,csharp] +---- +var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m.AutoMap()) + ); +---- + +Thus we do not map properties on the second occurrence of our Child property + +[source,csharp] +---- +var expected = new +{ + mappings = new + { + a = new + { + properties = new + { + child = new + { + properties = new { }, + type = "object" + } + } + } + } +}; + +Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); +---- + +Now lets specify a maxRecursion of 3 + +[source,csharp] +---- +var withMaxRecursionDescriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m.AutoMap(3)) + ); +---- + +`.AutoMap()` has now mapped three levels of our Child property + +[source,csharp] +---- +var expectedWithMaxRecursion = new +{ + mappings = new + { + a = new + { + properties = new + { + child = new + { + type = "object", + properties = new + { + child = new + { + type = "object", + properties = new + { + child = new + { + type = "object", + properties = new + { + child = new + { + type = "object", + properties = new { } + } + } + } + } + } + } + } + } + } + } +}; + +Expect(expectedWithMaxRecursion).WhenSerializing((ICreateIndexRequest)withMaxRecursionDescriptor); +---- + +[source,csharp] +---- +var descriptor = new PutMappingDescriptor().AutoMap(); + +var expected = new +{ + properties = new + { + child = new + { + properties = new { }, + type = "object" + } + } +}; + +Expect(expected).WhenSerializing((IPutMappingRequest)descriptor); + +var withMaxRecursionDescriptor = new PutMappingDescriptor().AutoMap(3); + +var expectedWithMaxRecursion = new +{ + properties = new + { + child = new + { + type = "object", + properties = new + { + child = new + { + type = "object", + properties = new + { + child = new + { + type = "object", + properties = new + { + child = new + { + type = "object", + properties = new { } + } + } + } + } + } + } + } + } +}; + +Expect(expectedWithMaxRecursion).WhenSerializing((IPutMappingRequest)withMaxRecursionDescriptor); +---- + +[[applying-conventions-through-the-visitor-pattern]] +[float] +== Applying conventions through the Visitor pattern + +It is also possible to apply a transformation on all or specific properties. + +`.AutoMap()` internally implements the https://en.wikipedia.org/wiki/Visitor_pattern[visitor pattern]. The default visitor, `NoopPropertyVisitor`, +does nothing and acts as a blank canvas for you to implement your own visiting methods. + +For instance, lets create a custom visitor that disables doc values for numeric and boolean types +(Not really a good idea in practice, but let's do it anyway for the sake of a clear example.) + +[source,csharp] +---- +public class DisableDocValuesPropertyVisitor : NoopPropertyVisitor +{ + public override void Visit( + INumberProperty type, + PropertyInfo propertyInfo, + ElasticsearchPropertyAttributeBase attribute) <1> + { + type.DocValues = false; + } + + public override void Visit( + IBooleanProperty type, + PropertyInfo propertyInfo, + ElasticsearchPropertyAttributeBase attribute) <2> + { + type.DocValues = false; + } +} +---- +<1> Override the `Visit` method on `INumberProperty` and set `DocValues = false` +<2> Similarily, override the `Visit` method on `IBooleanProperty` and set `DocValues = false` + +Now we can pass an instance of our custom visitor to `.AutoMap()` + +[source,csharp] +---- +var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m.AutoMap(new DisableDocValuesPropertyVisitor())) + ); +---- + +and any time the client maps a property of the POCO (Employee in this example) as a number (INumberProperty) or boolean (IBooleanProperty), +it will apply the transformation defined in each `Visit()` call respectively, which in this example +disables {ref_current}/doc-values.html[doc_values]. + +[source,csharp] +---- +var expected = new +{ + mappings = new + { + employee = new + { + properties = new + { + birthday = new + { + type = "date" + }, + employees = new + { + properties = new { }, + type = "object" + }, + firstName = new + { + type = "string" + }, + isManager = new + { + doc_values = false, + type = "boolean" + }, + lastName = new + { + type = "string" + }, + salary = new + { + doc_values = false, + type = "integer" + } + } + } + } +}; +---- + +=== Visiting on PropertyInfo + +You can even take the visitor approach a step further, and instead of visiting on `IProperty` types, visit +directly on your POCO properties (PropertyInfo). As an example, let's create a visitor that maps all CLR types +to an Elasticsearch string (IStringProperty). + +[source,csharp] +---- +public class EverythingIsAStringPropertyVisitor : NoopPropertyVisitor +{ + public override IProperty Visit( + PropertyInfo propertyInfo, + ElasticsearchPropertyAttributeBase attribute) => new StringProperty(); +} +---- + +[source,csharp] +---- +var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m.AutoMap(new EverythingIsAStringPropertyVisitor())) + ); + +var expected = new +{ + mappings = new + { + employee = new + { + properties = new + { + birthday = new + { + type = "string" + }, + employees = new + { + type = "string" + }, + firstName = new + { + type = "string" + }, + isManager = new + { + type = "string" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "string" + } + } + } + } +}; +---- + diff --git a/docs/asciidoc/client-concepts/low-level/class.png b/docs/asciidoc/client-concepts/low-level/class.png new file mode 100644 index 0000000000000000000000000000000000000000..bbc981cfe7a051af0152d2d83baaf219f33f60dd GIT binary patch literal 66942 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

B!o{oqhzUmEI=OF53x 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

im|0LnTy6JLiD3PxM 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 literal 0 HcmV?d00001 diff --git a/docs/asciidoc/client-concepts/low-level/connecting.asciidoc b/docs/asciidoc/client-concepts/low-level/connecting.asciidoc new file mode 100644 index 00000000000..b46e77947c7 --- /dev/null +++ b/docs/asciidoc/client-concepts/low-level/connecting.asciidoc @@ -0,0 +1,358 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[connecting]] +== Connecting + +Connecting to Elasticsearch with `Elasticsearch.Net` is quite easy and there a few options to suit a number of different use cases. + +[[connection-strategies]] +=== Choosing the right Connection Strategy + +If you simply new an `ElasticLowLevelClient`, it will be a non-failover connection to `http://localhost:9200` + +[source,csharp] +---- +var client = new ElasticLowLevelClient(); +---- + +If your Elasticsearch node does not live at `http://localhost:9200` but instead lives somewhere else, for example, `http://mynode.example.com:8082/apiKey`, then +you will need to pass in some instance of `IConnectionConfigurationValues`. + +The easiest way to do this is: + +[source,csharp] +---- +var node = new Uri("http://mynode.example.com:8082/apiKey"); + +var config = new ConnectionConfiguration(node); + +var client = new ElasticLowLevelClient(config); +---- + +This will still be a non-failover connection, meaning if that `node` goes down the operation will not be retried on any other nodes in the cluster. + +To get a failover connection we have to pass an <> instance instead of a `Uri`. + +[source,csharp] +---- +var node = new Uri("http://mynode.example.com:8082/apiKey"); + +var connectionPool = new SniffingConnectionPool(new[] { node }); + +var config = new ConnectionConfiguration(connectionPool); + +var client = new ElasticLowLevelClient(config); +---- + +Here instead of directly passing `node`, we pass a <> +which will use our `node` to find out the rest of the available cluster nodes. +Be sure to read more about <>. + +=== Configuration Options + +Besides either passing a `Uri` or `IConnectionPool` to `ConnectionConfiguration`, you can also fluently control many more options. For instance: + +[source,csharp] +---- +var node = new Uri("http://mynode.example.com:8082/apiKey"); + +var connectionPool = new SniffingConnectionPool(new[] { node }); + +var config = new ConnectionConfiguration(connectionPool) + .DisableDirectStreaming() <1> + .BasicAuthentication("user", "pass") + .RequestTimeout(TimeSpan.FromSeconds(5)); +---- +<1> Additional options are fluent method calls on `ConnectionConfiguration` + +The following is a list of available connection configuration options: + +[source,csharp] +---- +var config = new ConnectionConfiguration() + .DisableAutomaticProxyDetection() <1> + .EnableHttpCompression() <2> + .DisableDirectStreaming(); <3> + +var client = new ElasticLowLevelClient(config); + +var result = client.Search>(new { size = 12 }); +---- +<1> Disable automatic proxy detection. When called, defaults to `true`. + +<2> Enable compressed request and responses from Elasticsearch (Note that nodes need to be configured to allow this. See the {ref_current}/modules-http.html[http module settings] for more info). + +<3> By default responses are deserialized directly from the response stream to the object you tell it to. For debugging purposes, it can be very useful to keep a copy of the raw response on the result object, which is what calling this method will do. + +`.ResponseBodyInBytes` will only have a value if the client configuration has `DisableDirectStreaming` set + +[source,csharp] +---- +var raw = result.ResponseBodyInBytes; +---- + +Please note that using `.DisableDirectStreaming` only makes sense if you need the mapped response **and** the raw response __at the same time__. +If you need only a `string` response simply call + +[source,csharp] +---- +var stringResult = client.Search(new { }); +---- + +and similarly, if you need only a `byte[]` + +[source,csharp] +---- +var byteResult = client.Search(new { }); +---- + +other configuration options + +[source,csharp] +---- +config = config + .GlobalQueryStringParameters(new NameValueCollection()) <1> + .Proxy(new Uri("http://myproxy"), "username", "pass") <2> + .RequestTimeout(TimeSpan.FromSeconds(4)) <3> + .ThrowExceptions() <4> + .PrettyJson() <5> + .BasicAuthentication("username", "password"); +---- +<1> Allows you to set querystring parameters that have to be added to every request. For instance, if you use a hosted elasticserch provider, and you need need to pass an `apiKey` parameter onto every request. + +<2> Sets proxy information on the connection. + +<3> [[request-timeout]] Sets the global maximum time a connection may take. Please note that this is the request timeout, the builtin .NET `WebRequest` has no way to set connection timeouts (see http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.timeout(v=vs.110).aspx[the MSDN documentation on `HttpWebRequest.Timeout` Property]). + +<4> As an alternative to the C/go like error checking on `response.IsValid`, you can instead tell the client to <>. + +<5> forces all serialization to be indented and appends `pretty=true` to all the requests so that the responses are indented as well + +NOTE: Basic authentication credentials can alternatively be specified on the node URI directly: + +[source,csharp] +---- +var uri = new Uri("http://username:password@localhost:9200"); + +var settings = new ConnectionConfiguration(uri); +---- + +...but this may become tedious when using connection pooling with multiple nodes. + +[[thrown-exceptions]] +=== Exceptions + +There are three categories of exceptions that may be thrown: + +`ElasticsearchClientException`:: +These are known exceptions, either an exception that occurred in the request pipeline +(such as max retries or timeout reached, bad authentication, etc...) or Elasticsearch itself returned an error (could +not parse the request, bad query, missing field, etc...). If it is an Elasticsearch error, the `ServerError` property +on the response will contain the the actual error that was returned. The inner exception will always contain the +root causing exception. + +`UnexpectedElasticsearchClientException`:: +These are unknown exceptions, for instance a response from Elasticsearch not +properly deserialized. These are usually bugs and {github}/issues[should be reported]. This exception also inherits from `ElasticsearchClientException` +so an additional catch block isn't necessary, but can be helpful in distinguishing between the two. + +Development time exceptions:: +These are CLR exceptions like `ArgumentException`, `ArgumentOutOfRangeException`, etc. +that are thrown when an API in the client is misused. +These should not be handled as you want to know about them during development. + +=== OnRequestCompleted + +You can pass a callback of type `Action` that can eaves drop every time a response (good or bad) is created. +If you have complex logging needs this is a good place to add that in. + +[source,csharp] +---- +var counter = 0; + +var client = TestClient.GetInMemoryClient(s => s.OnRequestCompleted(r => counter++)); + +client.RootNodeInfo(); + +counter.Should().Be(1); + +client.RootNodeInfoAsync(); + +counter.Should().Be(2); +---- + +`OnRequestCompleted` is called even when an exception is thrown + +[source,csharp] +---- +var counter = 0; + +var client = TestClient.GetFixedReturnClient(new { }, 500, s => s + .ThrowExceptions() + .OnRequestCompleted(r => counter++) +); + +Assert.Throws(() => client.RootNodeInfo()); + +counter.Should().Be(1); + +Assert.ThrowsAsync(() => client.RootNodeInfoAsync()); + +counter.Should().Be(2); +---- + +[[complex-logging]] +=== Complex logging with OnRequestCompleted + +Here's an example of using `OnRequestCompleted()` for complex logging. Remember, if you would also like +to capture the request and/or response bytes, you also need to set `.DisableDirectStreaming()` to `true` + +[source,csharp] +---- +var list = new List(); + +var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); + +var settings = new ConnectionSettings(connectionPool, new InMemoryConnection()) <1> + .DefaultIndex("default-index") + .DisableDirectStreaming() + .OnRequestCompleted(response => + { + // log out the request and the request body, if one exists for the type of request + if (response.RequestBodyInBytes != null) + { + list.Add( + $"{response.HttpMethod} {response.Uri} \n" + + $"{Encoding.UTF8.GetString(response.RequestBodyInBytes)}"); + } + else + { + list.Add($"{response.HttpMethod} {response.Uri}"); + } + + // log out the response and the response body, if one exists for the type of response + if (response.ResponseBodyInBytes != null) + { + list.Add($"Status: {response.HttpStatusCode}\n" + + $"{Encoding.UTF8.GetString(response.ResponseBodyInBytes)}\n" + + $"{new string('-', 30)}\n"); + } + else + { + list.Add($"Status: {response.HttpStatusCode}\n" + + $"{new string('-', 30)}\n"); + } + }); + +var client = new ElasticClient(settings); + +var syncResponse = client.Search(s => s + .AllTypes() + .AllIndices() + .Scroll("2m") + .Sort(ss => ss + .Ascending(SortSpecialField.DocumentIndexOrder) + ) +); + +list.Count.Should().Be(2); + +var asyncResponse = await client.SearchAsync(s => s + .AllTypes() + .AllIndices() + .Scroll("2m") + .Sort(ss => ss + .Ascending(SortSpecialField.DocumentIndexOrder) + ) +); + +list.Count.Should().Be(4); + +list.ShouldAllBeEquivalentTo(new [] + { + "POST http://localhost:9200/_search?scroll=2m \n{\"sort\":[{\"_doc\":{\"order\":\"asc\"}}]}", + "Status: 200\n------------------------------\n", + "POST http://localhost:9200/_search?scroll=2m \n{\"sort\":[{\"_doc\":{\"order\":\"asc\"}}]}", + "Status: 200\n------------------------------\n" + }); +---- +<1> Here we use `InMemoryConnection`; in reality you would use another type of `IConnection` that actually makes a request. + +[[configuring-ssl]] +=== Configuring SSL + +SSL must be configured outside of the client using .NET's http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager%28v=vs.110%29.aspx[ServicePointManager] +class and setting the http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback.aspx[ServerCertificateValidationCallback] +property. + +The bare minimum to make .NET accept self-signed SSL certs that are not in the Window's CA store would be to have the callback simply return `true`: + +[source,csharp] +---- +ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true; +---- + +However, this will accept **all** requests from the AppDomain to untrusted SSL sites, +therefore **we recommend doing some minimal introspection on the passed in certificate.** + +IMPORTANT: Using `ServicePointManager` does not work on **Core CLR** as the request does not go through `ServicePointManager`; please file an {github}/issues[issue] if you need support for certificate validation on Core CLR. + +=== Overriding default Json.NET behavior + +Overriding the default Json.NET behaviour in NEST is an expert behavior but if you need to get to the nitty gritty, this can be really useful. +First, create a subclass of the `JsonNetSerializer` + +[source,csharp] +---- +public class MyJsonNetSerializer : JsonNetSerializer +{ + public MyJsonNetSerializer(IConnectionSettingsValues settings) : base(settings) { } + + public int CallToModify { get; set; } = 0; + + protected override void ModifyJsonSerializerSettings(JsonSerializerSettings settings) => ++CallToModify; <1> + + public int CallToContractConverter { get; set; } = 0; + + protected override IList> ContractConverters => new List> <2> + { + t => { + CallToContractConverter++; + return null; + } + }; + +} +---- +<1> Override ModifyJsonSerializerSettings if you need access to `JsonSerializerSettings` + +<2> You can inject contract resolved converters by implementing the ContractConverters property. This can be much faster then registering them on `JsonSerializerSettings.Converters` + +You can then register a factory on `ConnectionSettings` to create an instance of your subclass instead. +This is **_called once per instance_** of ConnectionSettings. + +[source,csharp] +---- +var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); + +var settings = new ConnectionSettings(connectionPool, new InMemoryConnection(), s => new MyJsonNetSerializer(s)); + +var client = new ElasticClient(settings); + +client.RootNodeInfo(); + +client.RootNodeInfo(); + +var serializer = ((IConnectionSettingsValues)settings).Serializer as MyJsonNetSerializer; + +serializer.CallToModify.Should().BeGreaterThan(0); + +serializer.SerializeToString(new Project { }); + +serializer.CallToContractConverter.Should().BeGreaterThan(0); +---- + diff --git a/docs/asciidoc/client-concepts/low-level/lifetimes.asciidoc b/docs/asciidoc/client-concepts/low-level/lifetimes.asciidoc new file mode 100644 index 00000000000..a7808a47ab8 --- /dev/null +++ b/docs/asciidoc/client-concepts/low-level/lifetimes.asciidoc @@ -0,0 +1,93 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[lifetimes]] +== Lifetimes + +If you are using an IOC container its always useful to know the best practices around the lifetime of your objects + +In general we advise folks to register their ElasticClient instances as singletons. The client is thread safe +so sharing an instance between threads is fine. + +Zooming in however the actual moving part that benefits the most from being static for most of the duration of your +application is `ConnectionSettings`; caches are __per__ `ConnectionSettings`. + +In some applications it could make perfect sense to have multiple singleton `ElasticClient`'s registered with different +connection settings. e.g if you have 2 functionally isolated Elasticsearch clusters. + +NOTE: Due to the semantic versioning of Elasticsearch.Net and NEST and their alignment to versions of Elasticsearch, all instances of `ElasticClient` and +Elasticsearch clusters that are connected to must be on the **same major version** i.e. it is not possible to have both an `ElasticClient` to connect to +Elasticsearch 1.x _and_ 2.x in the same application as the former would require NEST 1.x and the latter, NEST 2.x. + +Let's demonstrate which components are disposed by creating our own derived `ConnectionSettings`, `IConnectionPool` and `IConnection` types + +[source,csharp] +---- +class AConnectionSettings : ConnectionSettings +{ + public AConnectionSettings(IConnectionPool pool, IConnection connection) + : base(pool, connection) + { } + public bool IsDisposed { get; private set; } + protected override void DisposeManagedResources() + { + this.IsDisposed = true; + base.DisposeManagedResources(); + } +} + +class AConnectionPool : SingleNodeConnectionPool +{ + public AConnectionPool(Uri uri, IDateTimeProvider dateTimeProvider = null) : base(uri, dateTimeProvider) { } + + public bool IsDisposed { get; private set; } + protected override void DisposeManagedResources() + { + this.IsDisposed = true; + base.DisposeManagedResources(); + } +} + +class AConnection : InMemoryConnection +{ + public bool IsDisposed { get; private set; } + protected override void DisposeManagedResources() + { + this.IsDisposed = true; + base.DisposeManagedResources(); + } +} +---- + +`ConnectionSettings`, `IConnectionPool` and `IConnection` all explictily implement `IDisposable` + +[source,csharp] +---- +var connection = new AConnection(); +var connectionPool = new AConnectionPool(new Uri("http://localhost:9200")); +var settings = new AConnectionSettings(connectionPool, connection); +settings.IsDisposed.Should().BeFalse(); +connectionPool.IsDisposed.Should().BeFalse(); +connection.IsDisposed.Should().BeFalse(); +---- + +Disposing `ConnectionSettings` will dispose the `IConnectionPool` and `IConnection` it has a hold of + +[source,csharp] +---- +var connection = new AConnection(); + +var connectionPool = new AConnectionPool(new Uri("http://localhost:9200")); + +var settings = new AConnectionSettings(connectionPool, connection); + +settings.IsDisposed.Should().BeTrue(); + +connectionPool.IsDisposed.Should().BeTrue(); + +connection.IsDisposed.Should().BeTrue(); +---- + diff --git a/docs/asciidoc/ClientConcepts/LowLevel/pipeline.png b/docs/asciidoc/client-concepts/low-level/pipeline.png similarity index 100% rename from docs/asciidoc/ClientConcepts/LowLevel/pipeline.png rename to docs/asciidoc/client-concepts/low-level/pipeline.png diff --git a/docs/asciidoc/ClientConcepts/LowLevel/PostData.doc.asciidoc b/docs/asciidoc/client-concepts/low-level/post-data.asciidoc similarity index 50% rename from docs/asciidoc/ClientConcepts/LowLevel/PostData.doc.asciidoc rename to docs/asciidoc/client-concepts/low-level/post-data.asciidoc index b174112033f..fa15bb4606e 100644 --- a/docs/asciidoc/ClientConcepts/LowLevel/PostData.doc.asciidoc +++ b/docs/asciidoc/client-concepts/low-level/post-data.asciidoc @@ -1,37 +1,71 @@ -# Post data -The low level allows you to post a string, byte[] array directly. On top of this if you pass a list of strings or objects -they will be serialized in Elasticsearch's special bulk/multi format. +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current +:github: https://github.com/elastic/elasticsearch-net -Even though the argument for postData on the low level client takes a PostData -You can rely on C# implicit conversion to abstract the notion of PostData completely. -You can implicitly convert from the following types. +:nuget: https://www.nuget.org/packages -[source, csharp] +[[post-data]] +== Post data + +The low level client allows you to post a `string` or `byte[]` array directly. On top of this, +if you pass a collection of `string` or `object` they will be serialized +using Elasticsearch's special bulk/multi format. + +=== Implicit Conversion + +Even though the argument for PostData on the low level client takes a `PostData`, +You can rely on implicit conversion to abstract the notion of PostData completely. +You can implicitly convert from the following types + +* `string` + +* `byte[]` + +* collection of `string` + +* collection of `object` + +* `object` + +[source,csharp] ---- var fromString = ImplicitlyConvertsFrom(@string); ----- -[source, csharp] ----- + var fromByteArray = ImplicitlyConvertsFrom(bytes); var fromListOfString = ImplicitlyConvertsFrom(listOfStrings); var fromListOfObject = ImplicitlyConvertsFrom(listOfObjects); var fromObject = ImplicitlyConvertsFrom(@object); ---- -postData Bytes will always be set if it originated from a byte -[source, csharp] +PostData bytes will always be set if it originated from `byte[]` + +[source,csharp] ---- fromByteArray.WrittenBytes.Should().BeSameAs(bytes); ----- -[source, csharp] ----- + fromString.Type.Should().Be(PostType.LiteralString); fromByteArray.Type.Should().Be(PostType.ByteArray); fromListOfString.Type.Should().Be(PostType.EnumerableOfString); fromListOfObject.Type.Should().Be(PostType.EnumerableOfObject); fromObject.Type.Should().Be(PostType.Serializable); +---- + +and passing a `PostData` object to a method taking `PostData` should not wrap + +[source,csharp] +---- fromString = ImplicitlyConvertsFrom(fromString); +---- + +[source,csharp] +---- +await this.AssertOn(new ConnectionSettings()); + +await this.AssertOn(new ConnectionConfiguration()); +---- + +[source,csharp] +---- fromByteArray = ImplicitlyConvertsFrom(fromByteArray); fromListOfString = ImplicitlyConvertsFrom(fromListOfString); fromListOfObject = ImplicitlyConvertsFrom(fromListOfObject); @@ -41,70 +75,92 @@ fromByteArray.Type.Should().Be(PostType.ByteArray); fromListOfString.Type.Should().Be(PostType.EnumerableOfString); fromListOfObject.Type.Should().Be(PostType.EnumerableOfObject); fromObject.Type.Should().Be(PostType.Serializable); -await this.AssertOn(new ConnectionSettings()); -await this.AssertOn(new ConnectionConfiguration()); ---- + Although each implicitly types behaves slightly differently -[source, csharp] ----- -await Post(()=>@string, writes: Utf8Bytes(@string), storesBytes: true, settings: settings); +[source,csharp] ---- -[source, csharp] ----- -await Post(()=>bytes, writes: bytes, storesBytes: true, settings: settings); +await Post(() => @string, writes: Utf8Bytes(@string), storesBytes: true, settings: settings); + +await Post(() => bytes, writes: bytes, storesBytes: true, settings: settings); ---- -When passing a list of strings we assume its a list of valid serialized json that we -join with newlinefeeds making sure there is a trailing linefeed -[source, csharp] +When passing a list of strings we assume its a list of valid serialized json that we +join with newline feeds making sure there is a trailing linefeed + +[source,csharp] ---- -await Post(()=>listOfStrings, writes: multiStringJson, storesBytes: true, settings: settings); +await Post(() => listOfStrings, writes: multiStringJson, storesBytes: true, settings: settings); ---- + When passing a list of object we assume its a list of objects we need to serialize -individually to json and join with newlinefeeds aking sure there is a trailing linefeed +individually to json and join with newline feeds making sure there is a trailing linefeed -[source, csharp] +[source,csharp] ---- -await Post(()=>listOfObjects, writes: multiObjectJson, storesBytes: false, settings: settings); +await Post(() => listOfObjects, writes: multiObjectJson, storesBytes: false, settings: settings); ---- + In all other cases postdata is serialized as is. -[source, csharp] +[source,csharp] ---- -await Post(()=>@object, writes: objectJson, storesBytes: false, settings: settings); +await Post(() => @object, writes: objectJson, storesBytes: false, settings: settings); ---- -If you want to maintain a copy of the request that went out use the following settings -[source, csharp] +If you want to maintain a copy of the request that went out, use `DisableDirectStreaming` + +[source,csharp] ---- settings = new ConnectionSettings().DisableDirectStreaming(); ---- -by forcing `DisableDirectStreaming` serializing happens first in a private MemoryStream -so we can get a hold of the serialized bytes -[source, csharp] +by forcing `DisableDirectStreaming` on connection settings, serialization happens first in a private `MemoryStream` +so we can get hold of the serialized bytes + +[source,csharp] ---- -await Post(()=>listOfObjects, writes: multiObjectJson, storesBytes: true, settings: settings); +await Post(() => listOfObjects, writes: multiObjectJson, storesBytes: true, settings: settings); ---- + this behavior can also be observed when serializing a simple object using `DisableDirectStreaming` -[source, csharp] +[source,csharp] ---- -await Post(()=>@object, writes: objectJson, storesBytes: true, settings: settings); +await Post(() => @object, writes: objectJson, storesBytes: true, settings: settings); ---- -[source, csharp] + +[source,csharp] ---- PostAssert(postData(), writes, storesBytes, settings); + await PostAssertAsync(postData(), writes, storesBytes, settings); +---- + +[source,csharp] +---- postData.Write(ms, settings); + var sentBytes = ms.ToArray(); + sentBytes.Should().Equal(writes); + postData.WrittenBytes.Should().NotBeNull(); + postData.WrittenBytes.Should().BeNull(); +---- + +[source,csharp] +---- await postData.WriteAsync(ms, settings); + var sentBytes = ms.ToArray(); + sentBytes.Should().Equal(writes); + postData.WrittenBytes.Should().NotBeNull(); + postData.WrittenBytes.Should().BeNull(); ---- + diff --git a/docs/asciidoc/code-standards/descriptors.asciidoc b/docs/asciidoc/code-standards/descriptors.asciidoc new file mode 100644 index 00000000000..9ac9cf21d9a --- /dev/null +++ b/docs/asciidoc/code-standards/descriptors.asciidoc @@ -0,0 +1,61 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[descriptors]] +== Descriptors + +Every descriptor should inherit from `DescriptorBase`, this hides object members from the fluent interface + +[source,csharp] +---- +var notDescriptors = new[] { typeof(ClusterProcessOpenFileDescriptors).Name, "DescriptorForAttribute" }; +var descriptors = from t in typeof(DescriptorBase<,>).Assembly().Types() + where t.IsClass() + && t.Name.Contains("Descriptor") + && !notDescriptors.Contains(t.Name) + && !t.GetInterfaces().Any(i => i == typeof(IDescriptor)) + select t.FullName; +descriptors.Should().BeEmpty(); +---- + +Methods taking a func should have that func return an interface + +[source,csharp] +---- +var descriptors = + from t in typeof(DescriptorBase<,>).Assembly().Types() + where t.IsClass() && typeof(IDescriptor).IsAssignableFrom(t) + select t; + +var selectorMethods = + from d in descriptors + from m in d.GetMethods() + let parameters = m.GetParameters() + from p in parameters + let type = p.ParameterType + let isGeneric = type.IsGeneric() + where isGeneric + let isFunc = type.GetGenericTypeDefinition() == typeof(Func<,>) + where isFunc + let firstFuncArg = type.GetGenericArguments().First() + let secondFuncArg = type.GetGenericArguments().Last() + let isQueryFunc = firstFuncArg.IsGeneric() && + firstFuncArg.GetGenericTypeDefinition() == typeof(QueryContainerDescriptor<>) && + typeof(QueryContainer).IsAssignableFrom(secondFuncArg) + where !isQueryFunc + let isFluentDictionaryFunc = + firstFuncArg.IsGeneric() && + firstFuncArg.GetGenericTypeDefinition() == typeof(FluentDictionary<,>) && + secondFuncArg.IsGeneric() && + secondFuncArg.GetGenericTypeDefinition() == typeof(FluentDictionary<,>) + where !isFluentDictionaryFunc + let lastArgIsNotInterface = !secondFuncArg.IsInterface() + where lastArgIsNotInterface + select $"{m.Name} on {m.DeclaringType.Name}"; + +selectorMethods.Should().BeEmpty(); +---- + diff --git a/docs/asciidoc/CodeStandards/ElasticClient.doc.asciidoc b/docs/asciidoc/code-standards/elastic-client.asciidoc similarity index 54% rename from docs/asciidoc/CodeStandards/ElasticClient.doc.asciidoc rename to docs/asciidoc/code-standards/elastic-client.asciidoc index d8e7ee25432..7da07cba572 100644 --- a/docs/asciidoc/CodeStandards/ElasticClient.doc.asciidoc +++ b/docs/asciidoc/code-standards/elastic-client.asciidoc @@ -1,37 +1,63 @@ -[source, csharp] +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[elastic-client]] +== Elastic Client + +[source,csharp] ---- -var fluentParametersNotNamedSelector = - from m in typeof (IElasticClient).GetMethods() - from p in m.GetParameters() - where p.ParameterType.BaseType() == typeof (MulticastDelegate) - where !p.Name.Equals("selector") - select $"method '{nameof(IElasticClient)}.{m.Name}' should have parameter name of 'selector' but has a name of '{p.Name}'"; -fluentParametersNotNamedSelector.Should().BeEmpty(); var requestParametersNotNamedRequest = - from m in typeof(IElasticClient).GetMethods() - from p in m.GetParameters() - where typeof(IRequest).IsAssignableFrom(p.ParameterType) - where !p.Name.Equals("request") - select $"method '{nameof(IElasticClient)}.{m.Name}' should have parameter name of 'request' but has a name of '{p.Name}'"; + from m in typeof(IElasticClient).GetMethods() + from p in m.GetParameters() + where typeof(IRequest).IsAssignableFrom(p.ParameterType) + where !p.Name.Equals("request") + select $"method '{nameof(IElasticClient)}.{m.Name}' should have parameter name of 'request' but has a name of '{p.Name}'"; + requestParametersNotNamedRequest.Should().BeEmpty(); +---- + +[source,csharp] +---- var requestParameters = - (from m in typeof(IElasticClient).GetMethods() - from p in m.GetParameters() - where typeof(IRequest).IsAssignableFrom(p.ParameterType) - select p).ToList(); + (from m in typeof(IElasticClient).GetMethods() + from p in m.GetParameters() + where typeof(IRequest).IsAssignableFrom(p.ParameterType) + select p).ToList(); + requestParameter.HasDefaultValue.Should().BeFalse(); +---- + +[source,csharp] +---- var concreteMethodParametersDoNotMatchInterface = new List(); + var interfaceMap = typeof(ElasticClient).GetInterfaceMap(typeof(IElasticClient)); + var indexOfInterfaceMethod = Array.IndexOf(interfaceMap.InterfaceMethods, interfaceMethodInfo); + var concreteMethod = interfaceMap.TargetMethods[indexOfInterfaceMethod]; + var concreteParameters = concreteMethod.GetParameters(); + var interfaceParameters = interfaceMethodInfo.GetParameters(); + var parameterInfo = concreteParameters[i]; + var interfaceParameter = interfaceParameters[i]; + parameterInfo.Name.Should().Be(interfaceParameter.Name); + concreteMethodParametersDoNotMatchInterface.Add( $"'{interfaceParameter.Name}' parameter on concrete implementation of '{nameof(ElasticClient)}.{interfaceMethodInfo.Name}' to {(interfaceParameter.HasDefaultValue ? string.Empty : "NOT")} be optional"); + concreteMethodParametersDoNotMatchInterface.Should().BeEmpty(); +---- + +[source,csharp] +---- var methodGroups = from methodInfo in typeof(IElasticClient).GetMethods() where @@ -42,13 +68,71 @@ var methodGroups = let method = new MethodWithRequestParameter(methodInfo) group method by method.Name into methodGroup select methodGroup; + var parameters = asyncMethod.MethodInfo.GetParameters(); + var syncMethod = methodGroup.First(g => !g.IsAsync && g.MethodType == asyncMethod.MethodType && g.MethodInfo.GetParameters().Length == parameters.Length && (!asyncMethod.MethodInfo.IsGenericMethod || g.MethodInfo.GetGenericArguments().Length == asyncMethod.MethodInfo.GetGenericArguments().Length)); + asyncMethod.Parameter.HasDefaultValue.Should().Be(syncMethod.Parameter.HasDefaultValue, $"sync and async versions of {asyncMethod.MethodType} '{nameof(ElasticClient)}{methodGroup.Key}' should match"); ---- + +[source,csharp] +---- +var fluentParametersNotNamedSelector = + from m in typeof (IElasticClient).GetMethods() + from p in m.GetParameters() + where p.ParameterType.BaseType() == typeof (MulticastDelegate) + where !p.Name.Equals("selector") + select $"method '{nameof(IElasticClient)}.{m.Name}' should have parameter name of 'selector' but has a name of '{p.Name}'"; +fluentParametersNotNamedSelector.Should().BeEmpty(); +---- + +[source,csharp] +---- +private class MethodWithRequestParameter + { + public string Name { get; } + + public MethodInfo MethodInfo { get; } + + public bool IsAsync { get; } + + public ClientMethodType MethodType { get; } + + public ParameterInfo Parameter { get; } + + public MethodWithRequestParameter(MethodInfo methodInfo) + { + Name = methodInfo.Name.EndsWith("Async") + ? methodInfo.Name.Substring(0, methodInfo.Name.Length - "Async".Length) + : methodInfo.Name; + + IsAsync = methodInfo.ReturnType.IsGeneric() && + methodInfo.ReturnType.GetGenericTypeDefinition() == typeof(Task<>); + + MethodInfo = methodInfo; + + var parameterInfo = methodInfo.GetParameters() + .FirstOrDefault(p => typeof(IRequest).IsAssignableFrom(p.ParameterType)); + + if (parameterInfo != null) + { + Parameter = parameterInfo; + MethodType = ClientMethodType.Initializer; + } + else + { + Parameter = methodInfo.GetParameters() + .First(p => p.ParameterType.BaseType() == typeof(MulticastDelegate)); + MethodType = ClientMethodType.Fluent; + } + } + } +---- + diff --git a/docs/asciidoc/code-standards/naming-conventions.asciidoc b/docs/asciidoc/code-standards/naming-conventions.asciidoc new file mode 100644 index 00000000000..a4a0ea64bf0 --- /dev/null +++ b/docs/asciidoc/code-standards/naming-conventions.asciidoc @@ -0,0 +1,130 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[naming-conventions]] +== Naming Conventions + +NEST uses the following naming conventions (with _some_ exceptions). + +=== Class Names + +Abstract class names should end with a `Base` suffix + +[source,csharp] +---- +var exceptions = new[] +{ + typeof(DateMath) +}; +var abstractClasses = typeof(IRequest).Assembly().GetTypes() + .Where(t => t.IsClass() && t.IsAbstract() && !t.IsSealed() && !exceptions.Contains(t)) + .Where(t => !t.Name.Split('`')[0].EndsWith("Base")) + .Select(t => t.Name.Split('`')[0]) + .ToList(); +abstractClasses.Should().BeEmpty(); +---- + +Class names that end with `Base` suffix are abstract + +[source,csharp] +---- +var exceptions = new[] { typeof(DateMath) }; + +var baseClassesNotAbstract = typeof(IRequest).Assembly().GetTypes() + .Where(t => t.IsClass() && !exceptions.Contains(t)) + .Where(t => t.Name.Split('`')[0].EndsWith("Base")) + .Where(t => !t.IsAbstractClass()) + .Select(t => t.Name.Split('`')[0]) + .ToList(); + +baseClassesNotAbstract.Should().BeEmpty(); +---- + +=== Requests and Responses + +Request class names should end with `Request` + +[source,csharp] +---- +var types = typeof(IRequest).Assembly().GetTypes(); + +var requests = types + .Where(t => typeof(IRequest).IsAssignableFrom(t) && !t.IsAbstract()) + .Where(t => !typeof(IDescriptor).IsAssignableFrom(t)) + .Where(t => !t.Name.Split('`')[0].EndsWith("Request")) + .Select(t => t.Name.Split('`')[0]) + .ToList(); + +requests.Should().BeEmpty(); +---- + +Response class names should end with `Response` + +[source,csharp] +---- +var types = typeof(IRequest).Assembly().GetTypes(); + +var responses = types + .Where(t => typeof(IResponse).IsAssignableFrom(t) && !t.IsAbstract()) + .Where(t => !t.Name.Split('`')[0].EndsWith("Response")) + .Select(t => t.Name.Split('`')[0]) + .ToList(); + +responses.Should().BeEmpty(); +---- + +Request and Response class names should be one to one in *most* cases. +e.g. `ValidateRequest` => `ValidateResponse`, and not `ValidateQueryRequest` => `ValidateResponse` +There are a few exceptions to this rule, most notably the `Cat` prefixed requests and +the `Exists` requests. + +[source,csharp] +---- +var exceptions = new[] <1> +{ + typeof(DocumentExistsRequest), + typeof(DocumentExistsRequest<>), + typeof(AliasExistsRequest), + typeof(IndexExistsRequest), + typeof(TypeExistsRequest), + typeof(IndexTemplateExistsRequest), + typeof(SearchExistsRequest), + typeof(SearchExistsRequest<>), + typeof(SearchTemplateRequest), + typeof(SearchTemplateRequest<>), + typeof(ScrollRequest), + typeof(SourceRequest), + typeof(SourceRequest<>), + typeof(ValidateQueryRequest<>), + typeof(GetAliasRequest), +#pragma warning disable 612 + typeof(CatNodeattrsRequest), +#pragma warning restore 612 + typeof(IndicesShardStoresRequest), + typeof(RenderSearchTemplateRequest) +}; + +var types = typeof(IRequest).Assembly().GetTypes(); + +var requests = new HashSet(types + .Where(t => + t.IsClass() && + !t.IsAbstract() && + typeof(IRequest).IsAssignableFrom(t) && + !typeof(IDescriptor).IsAssignableFrom(t) + && !t.Name.StartsWith("Cat") + && !exceptions.Contains(t)) + .Select(t => t.Name.Split('`')[0].Replace("Request", "")) +); + +var responses = types + .Where(t => t.IsClass() && !t.IsAbstract() && typeof(IResponse).IsAssignableFrom(t)) + .Select(t => t.Name.Split('`')[0].Replace("Response", "")); + +requests.Except(responses).Should().BeEmpty(); +---- +<1> _Exceptions to the rule_ + diff --git a/docs/asciidoc/code-standards/queries.asciidoc b/docs/asciidoc/code-standards/queries.asciidoc new file mode 100644 index 00000000000..b78f193173f --- /dev/null +++ b/docs/asciidoc/code-standards/queries.asciidoc @@ -0,0 +1,63 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[queries]] +== Queries + +[source,csharp] +---- +var staticProperties = from p in typeof(Query<>).GetMethods() + let name = p.Name.StartsWith("GeoShape") ? "GeoShape" : p.Name + select name; + +var placeHolders = QueryPlaceHolderProperties.Select(p => p.Name.StartsWith("GeoShape") ? "GeoShape" : p.Name); + +staticProperties.Distinct().Should().Contain(placeHolders.Distinct()); +---- + +[source,csharp] +---- +var fluentMethods = from p in typeof(QueryContainerDescriptor<>).GetMethods() + let name = p.Name.StartsWith("GeoShape") ? "GeoShape" : p.Name + select name; + +var placeHolders = QueryPlaceHolderProperties.Select(p => p.Name.StartsWith("GeoShape") ? "GeoShape" : p.Name); + +fluentMethods.Distinct().Should().Contain(placeHolders.Distinct()); +---- + +[source,csharp] +---- +var skipQueryImplementations = new[] { typeof(IFieldNameQuery), typeof(IFuzzyQuery<,>), typeof(IConditionlessQuery) }; + +var queries = typeof(IQuery).Assembly().ExportedTypes + .Where(t => t.IsInterface() && typeof(IQuery).IsAssignableFrom(t)) + .Where(t => !skipQueryImplementations.Contains(t)) + .ToList(); + +queries.Should().NotBeEmpty(); + +var visitMethods = typeof(IQueryVisitor).GetMethods().Where(m => m.Name == "Visit"); + +visitMethods.Should().NotBeEmpty(); + +var missingTypes = from q in queries + let visitMethod = visitMethods.FirstOrDefault(m => m.GetParameters().First().ParameterType == q) + where visitMethod == null + select q; + +missingTypes.Should().BeEmpty(); +---- + +[source,csharp] +---- +var properties = from p in QueryProperties + let a = p.GetCustomAttributes().Concat(p.GetCustomAttributes()) + where a.Count() != 1 + select p; +properties.Should().BeEmpty(); +---- + diff --git a/docs/asciidoc/CodeStandards/Serialization/Properties.doc.asciidoc b/docs/asciidoc/code-standards/serialization/properties.asciidoc similarity index 76% rename from docs/asciidoc/CodeStandards/Serialization/Properties.doc.asciidoc rename to docs/asciidoc/code-standards/serialization/properties.asciidoc index 7814362db9b..1bfc7353ec6 100644 --- a/docs/asciidoc/CodeStandards/Serialization/Properties.doc.asciidoc +++ b/docs/asciidoc/code-standards/serialization/properties.asciidoc @@ -1,8 +1,15 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current -Our Json.NET contract resolver picks up attributes set on the interface +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages +[[properties]] +== Properties -[source, csharp] +Our Json.NET contract resolver picks up attributes set on the interface + +[source,csharp] ---- var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); var settings = new ConnectionSettings(pool, new InMemoryConnection()); @@ -14,3 +21,4 @@ serialized = c.Serializer.SerializeToString(new AnalysisDescriptor().CharFilters serialized.Should().NotContain("char_filters").And.NotContain("charFilters"); serialized.Should().Contain("char_filter"); ---- + diff --git a/docs/asciidoc/common-options.asciidoc b/docs/asciidoc/common-options.asciidoc new file mode 100644 index 00000000000..b0d00578726 --- /dev/null +++ b/docs/asciidoc/common-options.asciidoc @@ -0,0 +1,24 @@ +:output-dir: common-options + +[[common-options]] += Common Options + +[partintro] +-- +NEST has a number of types for working with Elasticsearch conventions for: + + +* <> + +* <> + +* <> + +-- + +include::{output-dir}/time-unit/time-units.asciidoc[] + +include::{output-dir}/distance-unit/distance-units.asciidoc[] + +include::{output-dir}/date-math/date-math-expressions.asciidoc[] + diff --git a/docs/asciidoc/common-options/date-math/date-math-expressions.asciidoc b/docs/asciidoc/common-options/date-math/date-math-expressions.asciidoc new file mode 100644 index 00000000000..93645822ee9 --- /dev/null +++ b/docs/asciidoc/common-options/date-math/date-math-expressions.asciidoc @@ -0,0 +1,133 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[date-math-expressions]] +== Date Math Expressions + +The date type supports using date math expression when using it in a query/filter +Whenever durations need to be specified, eg for a timeout parameter, the duration can be specified + +The expression starts with an "anchor" date, which can be either now or a date string (in the applicable format) ending with `||`. +It can then follow by a math expression, supporting `+`, `-` and `/` (rounding). +The units supported are + +* `y` (year) + +* `M` (month) + +* `w` (week) + +* `d` (day) + +* `h` (hour) + +* `m` (minute) + +* `s` (second) + +as a whole number representing time in milliseconds, or as a time value like `2d` for 2 days. + +:datemath: {ref_current}/common-options.html#date-math + +Be sure to read the Elasticsearch documentation on {datemath}[Date Math]. + +=== Simple Expressions + +You can create simple expressions using any of the static methods on `DateMath` + +[source,csharp] +---- +Expect("now").WhenSerializing(Nest.DateMath.Now); + +Expect("2015-05-05T00:00:00").WhenSerializing(Nest.DateMath.Anchored(new DateTime(2015,05, 05))); +---- + +strings implicitly convert to `DateMath` + +[source,csharp] +---- +Expect("now").WhenSerializing("now"); +---- + +but are lenient to bad math expressions + +[source,csharp] +---- +var nonsense = "now||*asdaqwe"; +---- + +the resulting date math will assume the whole string is the anchor + +[source,csharp] +---- +Expect(nonsense).WhenSerializing(nonsense) +.Result(dateMath => ((IDateMath)dateMath) + .Anchor.Match( + d => d.Should().NotBe(default(DateTime)), + s => s.Should().Be(nonsense) + ) + ); +---- + +`DateTime` also implicitly convert to simple date math expressions + +[source,csharp] +---- +var date = new DateTime(2015, 05, 05); +---- + +the anchor will be an actual `DateTime`, even after a serialization/deserialization round trip + +[source,csharp] +---- +Expect("2015-05-05T00:00:00").WhenSerializing(date) +.Result(dateMath => ((IDateMath)dateMath) + . Anchor.Match( + d => d.Should().Be(date), + s => s.Should().BeNull() + ) + ); +---- + +=== Complex Expressions + +Ranges can be chained on to simple expressions + +[source,csharp] +---- +Expect("now+1d").WhenSerializing( + Nest.DateMath.Now.Add("1d")); +---- + +Including multiple operations + +[source,csharp] +---- +Expect("now+1d-1m").WhenSerializing( + Nest.DateMath.Now.Add("1d").Subtract(TimeSpan.FromMinutes(1))); +---- + +A rounding value can be chained to the end of the expression, after which no more ranges can be appended + +[source,csharp] +---- +Expect("now+1d-1m/d").WhenSerializing( + Nest.DateMath.Now.Add("1d") + .Subtract(TimeSpan.FromMinutes(1)) + .RoundTo(Nest.TimeUnit.Day)); +---- + +When anchoring dates, a `||` needs to be appended as clear separator between the anchor and ranges. +Again, multiple ranges can be chained + +[source,csharp] +---- +Expect("2015-05-05T00:00:00||+1d-1m").WhenSerializing( + Nest.DateMath.Anchored(new DateTime(2015,05,05)) + .Add("1d") + .Subtract(TimeSpan.FromMinutes(1))); +---- + diff --git a/docs/asciidoc/common-options/distance-unit/distance-units.asciidoc b/docs/asciidoc/common-options/distance-unit/distance-units.asciidoc new file mode 100644 index 00000000000..c455d46b1ae --- /dev/null +++ b/docs/asciidoc/common-options/distance-unit/distance-units.asciidoc @@ -0,0 +1,124 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[distance-units]] +== Distance Units + +Whenever distances need to be specified, e.g. for a {ref_current}/query-dsl-geo-distance-query.html[geo distance query], +the distance unit can be specified as a double number representing distance in meters, as a new instance of +a `Distance`, or as a string of the form number and distance unit e.g. "`2.72km`" + +=== Using Distance units in NEST + +NEST uses `Distance` to strongly type distance units and there are several ways to construct one. + +==== Constructor + +The most straight forward way to construct a `Distance` is through its constructor + +[source,csharp] +---- +var unitComposed = new Distance(25); +var unitComposedWithUnits = new Distance(25, Nest.DistanceUnit.Meters); +---- + +`Distance` serializes to a string composed of a factor and distance unit. +The factor is a double so always has at least one decimal place when serialized + +[source,csharp] +---- +Expect("25.0m") + .WhenSerializing(unitComposed) + .WhenSerializing(unitComposedWithUnits); +---- + +==== Implicit conversion + +Alternatively a distance unit `string` can be assigned to a `Distance`, resulting in an implicit conversion to a new `Distance` instance. +If no `DistanceUnit` is specified, the default distance unit is meters + +[source,csharp] +---- +Distance distanceString = "25"; + +Distance distanceStringWithUnits = "25m"; + +Expect(new Distance(25)) + .WhenSerializing(distanceString) + .WhenSerializing(distanceStringWithUnits); +---- + +==== Supported units + +A number of distance units are supported, from millimeters to nautical miles + +===== Metric + +`mm` (Millimeters) + +[source,csharp] +---- +Expect("2.0mm").WhenSerializing(new Distance(2, Nest.DistanceUnit.Millimeters)); +---- + +`cm` (Centimeters) + +[source,csharp] +---- +Expect("123.456cm").WhenSerializing(new Distance(123.456, Nest.DistanceUnit.Centimeters)); +---- + +`m` (Meters) + +[source,csharp] +---- +Expect("400.0m").WhenSerializing(new Distance(400, Nest.DistanceUnit.Meters)); +---- + +`km` (Kilometers) + +[source,csharp] +---- +Expect("0.1km").WhenSerializing(new Distance(0.1, Nest.DistanceUnit.Kilometers)); +---- + +===== Imperial + +`in` (Inches) + +[source,csharp] +---- +Expect("43.23in").WhenSerializing(new Distance(43.23, Nest.DistanceUnit.Inch)); +---- + +`ft` (Feet) + +[source,csharp] +---- +Expect("3.33ft").WhenSerializing(new Distance(3.33, Nest.DistanceUnit.Feet)); +---- + +`yd` (Yards) + +[source,csharp] +---- +Expect("9.0yd").WhenSerializing(new Distance(9, Nest.DistanceUnit.Yards)); +---- + +`mi` (Miles) + +[source,csharp] +---- +Expect("0.62mi").WhenSerializing(new Distance(0.62, Nest.DistanceUnit.Miles)); +---- + +`nmi` or `NM` (Nautical Miles) + +[source,csharp] +---- +Expect("45.5nmi").WhenSerializing(new Distance(45.5, Nest.DistanceUnit.NauticalMiles)); +---- + diff --git a/docs/asciidoc/common-options/time-unit/time-units.asciidoc b/docs/asciidoc/common-options/time-unit/time-units.asciidoc new file mode 100644 index 00000000000..43e9ffee848 --- /dev/null +++ b/docs/asciidoc/common-options/time-unit/time-units.asciidoc @@ -0,0 +1,249 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[time-units]] +== Time units + +Whenever durations need to be specified, eg for a timeout parameter, the duration can be specified +as a whole number representing time in milliseconds, or as a time value like `2d` for 2 days. + +=== Using Time units in NEST + +NEST uses `Time` to strongly type this and there are several ways to construct one. + +==== Constructor + +The most straight forward way to construct a `Time` is through its constructor + +[source,csharp] +---- +var unitString = new Time("2d"); +var unitComposed = new Time(2, Nest.TimeUnit.Day); +var unitTimeSpan = new Time(TimeSpan.FromDays(2)); +var unitMilliseconds = new Time(1000 * 60 * 60 * 24 * 2); +---- + +When serializing Time constructed from + +* a string + +* milliseconds (as a double) + +* composition of factor and interval + +* a `TimeSpan` + +the expression will be serialized to a time unit string composed of the factor and interval e.g. `2d` + +[source,csharp] +---- +Expect("2d") + .WhenSerializing(unitString) + .WhenSerializing(unitComposed) + .WhenSerializing(unitTimeSpan) + .WhenSerializing(unitMilliseconds); +---- + +The `Milliseconds` property on `Time` is calculated even when not using the constructor that takes a double + +[source,csharp] +---- +unitMilliseconds.Milliseconds.Should().Be(1000*60*60*24*2); + +unitComposed.Milliseconds.Should().Be(1000*60*60*24*2); +unitTimeSpan.Milliseconds.Should().Be(1000*60*60*24*2); +unitString.Milliseconds.Should().Be(1000*60*60*24*2); +---- + +==== Implicit conversion + +Alternatively to using the constructor, `string`, `TimeSpan` and `double` can be implicitly converted to `Time` + +[source,csharp] +---- +Time oneAndHalfYear = "1.5y"; + +Time twoWeeks = TimeSpan.FromDays(14); + +Time twoDays = 1000*60*60*24*2; + +Expect("1.5y").WhenSerializing(oneAndHalfYear); + +Expect("2w").WhenSerializing(twoWeeks); + +Expect("2d").WhenSerializing(twoDays); +---- + +[source,csharp] +---- +Time oneAndHalfYear = "1.5y"; + +Time twoWeeks = TimeSpan.FromDays(14); + +Time twoDays = 1000*60*60*24*2; +---- + +Milliseconds are calculated even when values are not passed as long... + +[source,csharp] +---- +twoWeeks.Milliseconds.Should().BeGreaterThan(1); +---- + +...**except** when dealing with years or months, whose millsecond value cannot +be calculated *accurately*, since they are not fixed durations. For instance, +30 vs 31 vs 28 days in a month, or 366 vs 365 days in a year. +In this instance, Milliseconds will be -1. + +[source,csharp] +---- +oneAndHalfYear.Milliseconds.Should().Be(-1); +---- + +This allows you to do comparisons on the expressions + +[source,csharp] +---- +oneAndHalfYear.Should().BeGreaterThan(twoWeeks); + +(oneAndHalfYear > twoWeeks).Should().BeTrue(); + +(oneAndHalfYear >= twoWeeks).Should().BeTrue(); + +(twoDays >= new Time("2d")).Should().BeTrue(); + +twoDays.Should().BeLessThan(twoWeeks); + +(twoDays < twoWeeks).Should().BeTrue(); + +(twoDays <= twoWeeks).Should().BeTrue(); + +(twoDays <= new Time("2d")).Should().BeTrue(); +---- + +And assert equality + +[source,csharp] +---- +twoDays.Should().Be(new Time("2d")); + +(twoDays == new Time("2d")).Should().BeTrue(); + +(twoDays != new Time("2.1d")).Should().BeTrue(); + +(new Time("2.1d") == new Time(TimeSpan.FromDays(2.1))).Should().BeTrue(); + +(new Time("1") == new Time(1)).Should().BeTrue(); + +(new Time("-1") == new Time(-1)).Should().BeTrue(); +---- + +=== Units of Time + +Units of `Time` are specified as a union of either a `DateInterval` or `Time`, +both of which implicitly convert to the `Union` of these two. + +[source,csharp] +---- +Expect("month").WhenSerializing>(DateInterval.Month); + +Expect("day").WhenSerializing>(DateInterval.Day); + +Expect("hour").WhenSerializing>(DateInterval.Hour); + +Expect("minute").WhenSerializing>(DateInterval.Minute); + +Expect("quarter").WhenSerializing>(DateInterval.Quarter); + +Expect("second").WhenSerializing>(DateInterval.Second); + +Expect("week").WhenSerializing>(DateInterval.Week); + +Expect("year").WhenSerializing>(DateInterval.Year); + +Expect("2d").WhenSerializing>((Time)"2d"); + +Expect("1.16w").WhenSerializing>((Time)TimeSpan.FromDays(8.1)); +---- + +[source,csharp] +---- +double millisecondsInAMonth = 2592000000; + +Expect("4.29w").WhenSerializing(new Time(millisecondsInAMonth)); + +Expect("8.57w").WhenSerializing(new Time(millisecondsInAMonth * 2)); + +Expect("51.43w").WhenSerializing(new Time(millisecondsInAMonth * 12)); + +Expect("102.86w").WhenSerializing(new Time(millisecondsInAMonth * 24)); +---- + +[source,csharp] +---- +Expect("-1").WhenSerializing(new Time(-1)); + +Expect("-1").WhenSerializing(new Time("-1")); + +Assert( + 1, Nest.TimeUnit.Year, -1, "1y", + new Time(1, Nest.TimeUnit.Year), + new Time("1y") +); + +Assert( + 1, Nest.TimeUnit.Month, -1, "1M", + new Time(1, Nest.TimeUnit.Month), + new Time("1M") +); + +Assert( + 1, Nest.TimeUnit.Week, TimeSpan.FromDays(7).TotalMilliseconds, "1w", + new Time(1, Nest.TimeUnit.Week), + new Time("1w"), + new Time(TimeSpan.FromDays(7).TotalMilliseconds) +); + +Assert( + 1, Nest.TimeUnit.Day, TimeSpan.FromDays(1).TotalMilliseconds, "1d", + new Time(1, Nest.TimeUnit.Day), + new Time("1d"), + new Time(TimeSpan.FromDays(1).TotalMilliseconds) +); + +Assert( + 1, Nest.TimeUnit.Hour, TimeSpan.FromHours(1).TotalMilliseconds, "1h", + new Time(1, Nest.TimeUnit.Hour), + new Time("1h"), + new Time(TimeSpan.FromHours(1).TotalMilliseconds) +); + +Assert( + 1, Nest.TimeUnit.Minute, TimeSpan.FromMinutes(1).TotalMilliseconds, "1m", + new Time(1, Nest.TimeUnit.Minute), + new Time("1m"), + new Time(TimeSpan.FromMinutes(1).TotalMilliseconds) +); + +Assert( + 1, Nest.TimeUnit.Second, TimeSpan.FromSeconds(1).TotalMilliseconds, "1s", + new Time(1, Nest.TimeUnit.Second), + new Time("1s"), + new Time(TimeSpan.FromSeconds(1).TotalMilliseconds) +); +---- + +[source,csharp] +---- +time.Factor.Should().Be(expectedFactor); + +time.Interval.Should().Be(expectedInterval); + +time.Milliseconds.Should().Be(expectedMilliseconds); + +Expect(expectedSerialized).WhenSerializing(time); +---- + diff --git a/docs/asciidoc/connection-pooling.asciidoc b/docs/asciidoc/connection-pooling.asciidoc new file mode 100644 index 00000000000..58bc6ae0e48 --- /dev/null +++ b/docs/asciidoc/connection-pooling.asciidoc @@ -0,0 +1,64 @@ +:output-dir: client-concepts/connection-pooling + +:building-blocks: {output-dir}/building-blocks + +:sniffing: {output-dir}/sniffing + +:pinging: {output-dir}/pinging + +:round-robin: {output-dir}/round-robin + +:failover: {output-dir}/failover + +:max-retries: {output-dir}/max-retries + +:request-overrides: {output-dir}/request-overrides + +:exceptions: {output-dir}/exceptions + +include::{building-blocks}/connection-pooling.asciidoc[] + +include::{building-blocks}/request-pipelines.asciidoc[] + +include::{building-blocks}/transports.asciidoc[] + +include::{building-blocks}/keeping-track-of-nodes.asciidoc[] + +include::{building-blocks}/date-time-providers.asciidoc[] + +include::{sniffing}/on-startup.asciidoc[] + +include::{sniffing}/on-connection-failure.asciidoc[] + +include::{sniffing}/on-stale-cluster-state.asciidoc[] + +include::{sniffing}/role-detection.asciidoc[] + +include::{pinging}/first-usage.asciidoc[] + +include::{pinging}/revival.asciidoc[] + +include::{round-robin}/round-robin.asciidoc[] + +include::{round-robin}/skip-dead-nodes.asciidoc[] + +include::{round-robin}/volatile-updates.asciidoc[] + +include::{failover}/falling-over.asciidoc[] + +include::{max-retries}/respects-max-retry.asciidoc[] + +include::{request-overrides}/disable-sniff-ping-per-request.asciidoc[] + +include::{request-overrides}/request-timeouts-overrides.asciidoc[] + +include::{request-overrides}/respects-max-retry-overrides.asciidoc[] + +include::{request-overrides}/respects-allowed-status-code.asciidoc[] + +include::{request-overrides}/respects-force-node.asciidoc[] + +include::{exceptions}/unexpected-exceptions.asciidoc[] + +include::{exceptions}/unrecoverable-exceptions.asciidoc[] + diff --git a/docs/asciidoc/hadouken-indentation.jpg b/docs/asciidoc/hadouken-indentation.jpg new file mode 100644 index 0000000000000000000000000000000000000000..afe03b960d1203610f217e1045a22142c0acc720 GIT binary patch literal 43939 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 literal 0 HcmV?d00001 diff --git a/docs/asciidoc/high-level.asciidoc b/docs/asciidoc/high-level.asciidoc new file mode 100644 index 00000000000..ff18811690f --- /dev/null +++ b/docs/asciidoc/high-level.asciidoc @@ -0,0 +1,66 @@ +:output-dir: client-concepts/high-level + +[[nest]] += Client Concepts - NEST + +[partintro] +-- +The high level client, `ElasticClient`, provides a strongly typed query DSL that maps one-to-one with the Elasticsearch query DSL. + +It can be installed from the Package Manager Console inside Visual Studio using + + +[source,shell] +---- +Install-Package NEST +---- + + +Or by searching for https://www.nuget.org/packages/NEST[NEST] in the Package Manager GUI. + +NEST internally uses and still exposes the low level client, `ElasticLowLevelClient`, from <> via +the `.LowLevel` property on `ElasticClient`. + +There are a number of conventions that NEST uses for inference of + + +* <> + +* <> + +* <> and <> + +* <> + +* <> + +* <> + + +In addition to features such as + + +* <> + +* <> + +-- + +include::{output-dir}/inference/index-name-inference.asciidoc[] + +include::{output-dir}/inference/indices-paths.asciidoc[] + +include::{output-dir}/inference/field-inference.asciidoc[] + +include::{output-dir}/inference/property-inference.asciidoc[] + +include::{output-dir}/inference/ids-inference.asciidoc[] + +include::{output-dir}/inference/document-paths.asciidoc[] + +include::{output-dir}/inference/features-inference.asciidoc[] + +include::{output-dir}/mapping/auto-map.asciidoc[] + +include::{output-dir}/covariant-hits/covariant-search-results.asciidoc[] + diff --git a/docs/asciidoc/index.asciidoc b/docs/asciidoc/index.asciidoc index 873adf55c98..57662ec8c3d 100644 --- a/docs/asciidoc/index.asciidoc +++ b/docs/asciidoc/index.asciidoc @@ -1,53 +1,13 @@ -# Introduction +[[elasticsearch-net-reference]] += Elasticsearch.Net and NEST: the .NET clients -You've reached the documentation page for `Elasticsearch.Net` and `NEST`. The two official .NET clients for Elasticsearch. So why two clients I hear you say? +include::intro.asciidoc[] -`Elasticsearch.Net` is a very low level, dependency free, client that has no opinions about how you build and represent your requests and responses. It has abstracted -enough so that **all** the Elasticsearch API endpoints are represented as methods but not too much to get in the way of how you want to build your json/request/response objects. It also comes with builtin, configurable/overridable, cluster failover retry mechanisms. Elasticsearch is elastic so why not your client? +include::client-concepts.asciidoc[] -`NEST` is a high level client that has the advantage of having mapped all the request and response objects, comes with a strongly typed query DSL that maps 1 to 1 with the Elasticsearch query DSL, and takes advantage of specific .NET features such as covariant results. NEST internally uses, and still exposes, the low level `Elasticsearch.Net` client. +include::common-options.asciidoc[] -Please read the getting started guide for both. - - -## Who's using Nest -* [stackoverflow.com](http://www.stackoverflow.com) (and the rest of the stackexchange family). -* [7digital.com](http://www.7digital.com) (run NEST on mono). -* [rijksmuseum.nl](https://www.rijksmuseum.nl/en) (Elasticsearch is the only datastorage hit for each page). -* [Kiln](http://www.fogcreek.com/kiln/) FogCreek's version control & code review tooling. - They are so pleased with Elasticsearch that [they made a video about how pleased they are!](http://blog.fogcreek.com/kiln-powered-by-elasticsearch/) - -## Other resources - -[@joelabrahamsson](http://twitter.com/joelabrahamsson) wrote a great [intro into elasticsearch on .NET](http://joelabrahamsson.com/entry/extending-aspnet-mvc-music-store-with-elasticsearch) -using NEST. - -Also checkout the [searchbox.io guys](https://searchbox.io/) rocking NEST [on AppHarbor](http://blog.appharbor.com/2012/06/19/searchbox-elasticsearch-is-now-an-add-on) -with their [demo project](https://github.com/searchbox-io/.net-sample) - -## Questions, bugs, comments, requests - -All of these are more then welcome on the github issues pages! We try to to at least reply within the same day. - -We also monitor question tagged with ['nest' on stackoverflow](http://stackoverflow.com/questions/tagged/nest) or -['elasticsearch-net' on stackoverflow](http://stackoverflow.com/questions/tagged/elasticsearch-net) - -# License - -This software is licensed under the Apache 2 license, quoted below. - - Copyright (c) 2014 Elasticsearch - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +include::query-dsl.asciidoc[] +include::aggregations.asciidoc[] diff --git a/docs/asciidoc/intro.asciidoc b/docs/asciidoc/intro.asciidoc new file mode 100644 index 00000000000..ddc314e19bd --- /dev/null +++ b/docs/asciidoc/intro.asciidoc @@ -0,0 +1,62 @@ +:github: https://github.com/elastic/elasticsearch-net + +:stackoverflow: http://stackoverflow.com + +[[introduction]] +== Introduction + +You've reached the documentation page for `Elasticsearch.Net` and `NEST`. The two official .NET clients for Elasticsearch. So why two clients I hear you say? + +`Elasticsearch.Net` is a very low level, dependency free, client that has no opinions about how you build and represent your requests and responses. It has abstracted +enough so that **all** the Elasticsearch API endpoints are represented as methods but not too much to get in the way of how you want to build your json/request/response objects. It also comes with builtin, configurable/overridable, cluster failover retry mechanisms. Elasticsearch is elastic so why not your client? + +`NEST` is a high level client that has the advantage of having mapped all the request and response objects, comes with a strongly typed query DSL that maps 1 to 1 with the Elasticsearch query DSL, and takes advantage of specific .NET features such as covariant results. NEST internally uses, and still exposes, the low level `Elasticsearch.Net` client. + +Please read the getting started guide for both. + +=== Who's using Nest + +* {stackoverflow}[stackoverflow.com] (and the rest of the stackexchange family). + +* http://www.7digital.com[7digital.com] (run NEST on mono). + +* https://www.rijksmuseum.nl/en[rijksmuseum.nl] (Elasticsearch is the only datastorage hit for each page). + +* http://www.fogcreek.com/kiln/[Kiln] FogCreek's version control & code review tooling. + They are so pleased with Elasticsearch that http://blog.fogcreek.com/kiln-powered-by-elasticsearch/[they made a video about how pleased they are!] + +=== Other resources + +http://twitter.com/joelabrahamsson[@joelabrahamsson] wrote a great http://joelabrahamsson.com/entry/extending-aspnet-mvc-music-store-with-elasticsearch[intro into elasticsearch on .NET] +using NEST. + +Also checkout the https://searchbox.io/[searchbox.io guys] rocking NEST http://blog.appharbor.com/2012/06/19/searchbox-elasticsearch-is-now-an-add-on[on AppHarbor] +with their https://github.com/searchbox-io/.net-sample[demo project] + +=== Questions, bugs, comments, requests + +All of these are more then welcome on the {github}/issues[github issues pages]! We try to at least reply within the same day. + +We also monitor question tagged with {stackoverflow}/questions/tagged/nest['nest' on stackoverflow] or +{stackoverflow}/questions/tagged/elasticsearch-net['elasticsearch-net' on stackoverflow], as well as https://discuss.elastic.co[discussions on our discourse site] + +=== License + +.... +This software is licensed under the Apache 2 license, quoted below. + + Copyright (c) 2014 Elasticsearch + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +.... + diff --git a/docs/asciidoc/low-level.asciidoc b/docs/asciidoc/low-level.asciidoc new file mode 100644 index 00000000000..4abee9ec908 --- /dev/null +++ b/docs/asciidoc/low-level.asciidoc @@ -0,0 +1,31 @@ +:output-dir: client-concepts/low-level + +[[elasticsearch-net]] += Client Concepts - Elasticsearch.Net + +[partintro] +-- +The low level client, `ElasticLowLevelClient`, is a low level, dependency free client that has no +opinions about how you build and represent your requests and responses. + +It can be installed from the Package Manager Console inside Visual Studio using + + +[source,shell] +---- +Install-Package Elasticsearch.Net +---- + + +Or by searching for https://www.nuget.org/packages/Elasticsearch.Net[Elasticsearch.Net] in the Package Manager GUI. + +-- + +include::{output-dir}/connecting.asciidoc[] + +include::{output-dir}/lifetimes.asciidoc[] + +include::{output-dir}/post-data.asciidoc[] + +include::connection-pooling.asciidoc[] + diff --git a/docs/asciidoc/pipeline.png b/docs/asciidoc/pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..b15d2f0f8b6be04f79ff34091ceffcfad81c0525 GIT binary patch literal 204561 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 z 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%0AO|ykB#d zFfMM4J!#=c^AkoF<6=)DBfriAyxQ-J!4&su# z$nEYBrf_AEQcJgaG8<99e~$KYnYSce#KJS?syDuF(W}G7fq1edcu7pf@Q9d&Y(IWanlGhnc0BA|p1izts&& z8`n@a8IEUQu4GWfJ-0~@(eE$1<%8}#?#R3?5MDMHl`VydIayl#-5yEPTyV&1Gf@D! z^tND5f9Uu!SI$wy4L2M|XcuXV%fr0Xsbf<8D}Ei5Q*89;DAv>$;VylRF`8`ZdTwZQ zee;NP1a*dZ=C-iD!CGm6F8J&1;nrK)dksWqX-P^!2|`vqAjGfY1)`N%V3a&I_skhf zCU6_iA-40AH=p$5Iv!prrKE&F`OLTOz^YKQAaRo+)G4NLHIDi2i%8kXPUz{QBhVti zLaT;^Z-WYnOi-0g3P9hJTj+zb+qI_Iww(#4Hj^$>kMEMH%Le}uG2&7`&h1Z-PwlFq zRiQbczDi?(p*ZkXYS0iZ?VK837ZHRH9 z5#YqYKB-Q4)gMB$kO7e?ixe20C*e(n3i#pDM2@UVAtKqe8cqc-1Jd$Hu`=PrLP$%ZZbo{LS8ytL%WP@RJB!v_;1 zOS3j>5}WLaLsr?nZVt{3;~FByz?nY$<4k)zbO2(FNL@mjR(P4z@K3#QCCXSpFP&OI z8EnacW<5ws)yO!av*l`ba_)7icRs@=2wS$_z^c*WW&!NsJaquEwH;6|9s?5Xw`fDY zMTq*^ z$T|1v88N+~=W=gJ&hz_8f({W0(1+j`xrwK-3%Y6Gya&L_C%{7S=$JarA7bxzdboh9 zEk39>EAXyYxrlpntl>_39$ylV{e78l-2=c!JOSm z2XR7FhGs&K*#4{y^eIfOIVHlY(EaB2hzO`WI;v{uao7wxp z_d>THA*98KbfUcZsI*V&f=o?7cK*dY5NQ*XRXT1EIy#Z^M?{Oi&X`AKd-@+f^WIn| zi%$&9_&t~E=qLqjyoJL{t%T(#jtQu!TSOW20ok6cPVFoNI1UAdgShpo#h=Z0ch<*~ z4&q6SHs?s-M#=Mx#O|KbXU|e2UQanyFavI(5wP=-eSnQjZ>H+SBfG_G%lxkoZ~Q{p zqE=h+F}GwP1JZ$-x-LncRdF-ATdN+?sODGpph>J2Qr<|C`;~6v?(~f-e2cg!c>q-) zN{GX25b1gh&+K|1(?aD#89nDh6m-wiJ(v1O5)e+q?N2~3SpnZprZj$WCDx!oa=dml z|I0t5CS@1}Zm3H%lS#W3bi1hZhk~EJ>HIC%*hjHykb9BzueRkbXk&~G+s&UfRl9m6 zQfe1j#akoI5@m+T(v;qj#lMfpID`xgDZqwhUS-U^w?6d~9!y^?wH7x`l+g+I&|5f< z;{Y#NCStNiwUZnJ>RyF#<@@ehCB6HIGH0vfK+fQ*RTx>@*n`6ayC|uru)^lSnz~v9 z4$B3$uN{4~sRQ4mV^(QY(Q?_CFl6v;G$likRmJ$YId4t^AiW4;`VgX@AZwub^}Yy%PRsKDzn%Ka)7n>?#Hni5;^>e83V^|bUFVX+L$OE}Ea^@M1d zjDzXW8_yd3y>B9|Lzs1fBr!34I(IpnHF4}0%b9k?q4ybk!fgYZe+JcIabTP=x z)|3570orWjLM!O6Qo!3QoDZLCs-M(J%AtS;Y#Lx(dS5r*!YRw@XeAV&N$QT#mcB_G z1(J}W@MyOv5MK16BqBp`0Vpc|M^foAJ*Y^bVDAnSc7HUQe#Y5$QLX_Eyy*RvC)E-!~ojV?-T@b>XVi3lI2N%5QCLfPMb*cMvErPDeE zL6_KXIOmZ_dG@`My0T3sVdcXlbLCF>PX^Xhb1Ln!2{HlMZKB&7f`?PS6gRwf(Rq1f zFH;rgwqC>s^5Oj4LLAqkQ2Z4^`1ZV)0A;-ca7(xE!t^r{v_d}_LcXp+H`evd|t_~HM;7zAdeHMJr?|GSo!#ve(>q^Ol27XjQsM_Dx>o+ zIcue4P0zmm{$L3%K9()GL2G4pnE-QoKPYE`x2G5mO%ooT=<*$u39REigTXg=0!e>_ zGtgG3ME=!42mm>|pcKb8*`cINnHM$J&ZSO4x8y2g!9Mrd_+8oO8AzbGW!q(e4HbWV z-T7(G0MFmv`j)RLwZ|-;+&{e9SGKP?e|W{81y4_2fhXwS zFNgG%!Gmr3=E?A{q`r1yf#iwN&^(DDe_sbl*nX9nz_8)Pe?S&<8tt+@)EqDQ zhXMX;LC}eHX&q$CCY#UgJEbRS^=)b;80$POgt-kw*hnWyyX4@|gSn~*;)jS%(RoEa zj{N)vwy*f>9Pu92uax|XW|T%QxJD7**2j9Kj9iIcP>B##IH~RgymVPnL2U*9P^pAqU^@O`IuL4NEE_A|x&_%v>jQ1^dlz^l^}vu&}VaIk1S- zyRY>0%)5W%!9!&d0!B;pReH%57b;lYELpARKJyG_l?p=TRLIBUJozVrIy?2PZ*Iwv7%@$+r9r7VX!OD&Zb|QjZ#QD- zFc2>t`1UUo#$H#vU1fX-03FvvVWTN2In>>D90;8oLwy?Iri%;b%M)*uJk^rg)rGe+ z*1Cc2%^)vr3)Imj+;M4QCY^S8;Hgy#r#k|az55~RD`Tp5iU;}CGnN7(YE|P0B|Dwm zsKc_hgMS|?SFVNFFWWc`-VwVOO@?}iW;LGGv2=B!zvO}^>N!r)BC{pDFB zHQNbk7P_Qaoj3dU(y1@BpTm;9k|iPxf>N{8D^3x!q#jVb?G20oG1hKSouL4_op0mI zeHqxBC1damdpa>zhg);llXF+ZWLNJ_KZEhr;6dU&ojS_6_hKo4Y#EiX8Tp(|3 z(Gl;>jzD^DyH6kmfqjQMBaOEqy!4=%i{Ah%s9pMmrL2toJPyN{KRb}KbuZS?t60f_ z(oseB5!08V6?@li`^z|tq5Gq`Pdt_DmVpC093OZ}b%(=DFWrU)L0bsPz{je3lS1AC zT*?L|)?$(`(`wnAZb-T$<~Sw$8i-%%a^mvxOV$j>uw>(7EM1ym#Ryx1ZJ}(mJ%i^4 zKq>aHjJBHM$VLW%lLx_2Ekp~eLk&&AiyP@>xS&z@V#BDZsIXtB8wxOZm*#hGAJ`r< zMDKQ>_p1AQyDo+YmPjHT#sg-)kYW2~-O7(i2g)MZY(9#T@M@>{SE06$%asID<=w)Q z()NH7V+~*!CLNwjORs=Gq=U+2ylGE?2+gGu{GMJ*QySpYOJE{UZ$kNKLmgAUo&~uRWRh$+#VW@L`CQCBOy~2i>L;12=k0A#!QjPk2j2@t?H- zZm-828!qnv%EI33R>^W8Kg5pfHmurC*G5Y_*ONi-dw57mfo|@;jC=GFpa~xn9*L(T zzw0!wiBGdNYQ5`BkwhilY%Mu|`348qyCtbMCe-cP-~IVn-@d)GuUr!`rNY?AFhM9R zhMjKag<>T(W^(G^(nlB&p2b1V;(b4u@&0XOD(Wi zyILCfuB=1UhRFN{rRZaLl0(T@5Pbyr8~x+zDheEi{Cm5{*!<_wy)CH#CCXnab#^tC zcKHrm-ArWI;Io5@4#U#5lQ*Q~EWh4)k1{xqGF8d3&;#iWlTM$Ukjld39w@#gz(8Dd zLfabD4d!nOJ;c`Y@ozgP(Zi>yikuQlp# zHZZ+KjSWslh{Gt8OmRyG zjsCeBBvF~w)#YHXn4;acupoMB{~NqRg*L-cTV&#zhg zT2N0*3_AB}VA{{gh?A*W-p342xQSeR_O|X6s0tQd1E46dH?B)uQw5jg_Xow@Y0jMO z2WYAzNF*S!N78yAA(I(q{XEi zW^zx8a5ndK(>20>ed&v2a(%QWX(G&0Pa|Ky%PI^Vao1f??n5ZqN$1RLV5Bh+=)2?Z zYMuJCg)*s&jJJr&q}xNG7|0=9F0ak%BI+d>i&g)YzstXBxHJJ`ou{F}KVdsc(nqQ~ zCO2Y{<`MB@v^vwYvB;LNJTDtc?PDQl(;r|0G6dQKh7-K_lgwDvgBo_9CkEt-gMmoa z07M!WSBs0VfypCAumOpZf=f7JaTfdmlTn(mIf5hD31C@8p+MBn;oEo^t!s(N{-oiz z+0rYLwFO54GHF|U^0268!I;m9)v-I+vkAF}nfE@EHyO%W$(+)w-xc^JB&JMA*g!V* z`?i2AIMBvfp9TI8)(kU2va>~`}*b5Q_?rgcXC5P&CHK_pZ+A`y<%U))FYa(7xsy?EVo1 zc@)K_0f;P=N^x_d@QwU4WOc>c=huljsDnm-e^PY5J?8_qM_2Ic+gsT&d#ybs*IC(b zqf&HQNJ@aIFc^bPY4zi_)ko1VNXF z>Wd_Z^tyDKY2WWt-RDZh5WSM7N-@=$M zo(OS<^Bmt!sa`tG4hq&v%Geejmq{snAVmu@V1E0<&8DBE2d>?8CwBCRrxC&^7{XXm zv&_ibmccW}uSA1_!ihpD;>DEcok$2NBh~8qlvE#{M7K^j>t{68>DFpSWa^)3&e`S| z+aCsemI7ZNRsV0lSy1vh{1)FAb^@FtqrP4Mg6-CYO+xm|oR{7bkYXaetx-|H?`(MY zBDu)A@OB3}Yazh8>fSbV8MkmK2kWM@;8R{U3yZ2(0T=e^9R*ll<2f7szw-Di^B`!? zD+1=*J@@M=Nxfw?$>NK+fg^Y4p6|O}5l$}PyOoaJ_OHC@iMe$OZnF{tD(bhM*{4ma zx?PjU?UWQQxVn+|;Ns_o%5d_r~=w$S+1FD z5m!tUg7E!tctOe&(~txQ9>R_i^Z)KQorvUaJvc9H-`=qH^0!-3F=oSw7fwKNu7v{Q@JZFwL-GK$>Gs)SehPhe0B;kZ4W}WMg$~4`cdC-}D7D z^v`P$!M_H*(FfrE^iz2w(!kj(z@pxtJMfMQpbh|^Df=PHd%01ge~(Jtg;=*)>$`^w z_|c~>?z*E9D(`07fqh^I*UEi@TLE97$pzagekvg6d2yaQjvTw_Cut-&mWhdW#27qQk`bUpHn>R|MQ$4 zP)B~xy4iN6-uw#T(>t*i%?yILO~uD!=EK1kTs-hU8X^3@{K+2b&C0T+QW<$Oe!|+q z-O)({@RRKh$H#jS7v`Q@lHnAY-Tqhah=A0bW6C9}*G9&V9Y$Q$G-Ok|85tayX*9F= z-58d2UIyH!-=^M;svfy_9vqMY?apf%6j*_HZl?hr`C}DeQU4ed#b;Nh&MDtezeYqz zN$@t8c#Pk*!VcX}c=z^u?@_%S5{W!>LtOwLs$i-;_uy5tZYeR zm+i(IM!J)BXymQE;R#W%fX&;Gx3;*BJ?X_n1+aXL-t&Jm7=NG09Mw?u8`1C9y#avQ zhFerL&UJN24us}2Mn<0T2nb~6*PbR`fo;kn&(%`{t&Bs1cZBPrq{Y9ZI`X&9s1JG! zyYfQMJR<44Lo!E)Sx)VP6NZ_7QAafDD!Z z|9=v1IWb}%pNh%?qpP|dO-xwrMoTVNYR#qveBU6Q8sbN10|kaA=w`$#Fn8(#S~aLv zvMOlWO9K`3y@1%jIq-B5TORg7?82D=T?zaP`vu)75*gJylwJatqI`isxQ^ z*`GsW=sIlS4C*u5m`Q2n2qd+S7YL-E9}D@BP!bSQZfeN#MW60mO+3Bp2dU*t@AGo# zF#0`7cNUj%FGYEV|C&k!`b11v`))+f)jM&Qh$+t;GcN!`himu`s-zA2eA2_m5W00( zZ~BmE2#FP92U3KvRQTehNK9n$$l~t7=O5o@b2)XBjro4CX)5{?NN1)~t2u1T9#_MK zK7ycuhfdwU{rqP8C%heHt^2OcgqR}4_io%@QM_!bcY?QE|4on_lx5c4_wHJEb0mtH zvyI=KfUZC&PfAd2WM3)v)gdA*?#boc&gRFt|ij*{f+M^z32)q z*<8da8p{^^8=`>;;Z^xdQoY^FI@`LJjrGP=z`?j%o|As=PNeAe=l9j>MqEr2eyl^# zvg`wl=w2%FthObVQ~tNtLDgPx&GUc1-v~>*)tQ47_{IDK^O6#UxHlD$;{KZ&A{-=M zt<=ix9P+^4F#9AqO=Cqn8DcF2bY1`Afp_Y!>bMUx6YMXd`@8dRJNFkX(tI& zLKksH%p~V+-$Vtl{Y!th-_CLWQAwbs@m>Gl+G4R1p*MbES~Ulo=Z8Ok0|mf$m$S*< z8IRSsw<@*sj^+1}T>UE|FCr~}s?4?HH^k&{fPpbq-*Er;_Fo`Ma81#hFF3-S8zOc6 zvEWSXyKJf+R7b(QzZGGWP_{wR3A@oK= z$Wg4s&|h&^LpFK*5NiwL4|hTZ0XCCPNBv)=WyS8a_5p_f^iIr0-p-Cg6RH~XVxF4@gKuB|`|ab9 zNKW^bq8ZQgi&UJz{<wI(0D_CYh_-y0%HR765Cyf@Zy{%bluJk!OE zJ=X-4b-sYV@N~qvhS2vf1alg=yCwUp5qtB)VQd#&(PiEYg&F_pNpWnGi4Eg*?vF;Q zeO{MU+#gLT3H_kHc;901u>F@{JMbgLf|$;F4%3F|y8TWeEzh!wD=dC{PXo_DBlFk8 z^i=9v_a<_@6_Tx!BpgFk7o(iC-Kj#Ib{>*{!EsTi4&$E&?>{ty9LwlBWL1KUKfKI1 z+bqJ$5~$4K>xBJ8fiI6bErTDMaDWSF(wA#2S&R_GWd(<^saF_FYCnSo>%o5Hr!W#J z3;oe?as(7d(o;%Rysy}?gq?y?7?~;@{yMt;!=C!eX8aML?IpYbng0mzl`Bz&-(|xY zbM&2Oe!<{61a@PMVQwh{v96ETu1fJyaULdnXxyC^`MRawT>)PySi!KG-93kwZa(p5 z!A7jX&Jn9<8uN^%km7Lzy%!$H1^xwc2+~iZf8^Li3FLVESB_R(AM}6yl48O#mR)rLBOm{oq4p5$R468WOZ>`5@8Wyf#Nc8RkZ-}$fvZ%Ckfs?gxPhQ3Uo4EVk41a-3T|>xK9)W%&E0qMXkYpPMAgw9swiu^nZMZKXCit#i80g!uh4%EmOs*Q@X^r*%NOxgJAz!BD}<%b9tOu9S3=m~H;f`jcg%kUusY$xQgr|Kh_F z`So}?@Xi9o%t!Et%jBI#j(^+-9NvK*Xae>z#&W9ZDT(+S*bRc{kM{s7Aiwk%6%3Q< zdL7-W_P}WuiS`YpSyJe*Syr@?X-g|0FED(FDk}cySo^A}Rf88Mg!~vFZBH7{e?NX5 zO+WC0HxAs(2w3kLUP=+N-IxDJi4sVO0ztJAG8TmJF4m;LQ8*m40uaLZ&*gH_rvf}j zFYfG~PDd1pDw8h}i}1&5zxk5$qVB-v^XPjMa4oN2eK=b8$8Ks~1)zq{lPzK z+n+mzrQ)gpfdy}GZ6d_bKH2U1@hEz#D|x=ztfyC4u7l`Ea82D`bQdt5{L zK0JNh-j97VD$gTAuf^W`C~Qm%A%?L{bfHV$lg;T>F`*T<0;z_eEC?9+cjxt zWiTQHg%W-fjT8gc+y5M1sWo-K()A0>YG)4@=XUA@R;ULUD_6)-RILz zFaO%RkF4H-C%^pEE7MypueE?F3OG?7hP>pr2<*kU{3gB79e;`xPAW>k{VMRmXi`Qe zS}7i`@O%c_xYPJ#lzZU_8f@?H?|;{J=(Z@=w{MLn>Wr3u_AT0`17S!LK?BN<&_j6` zPEE%^QAWx?Lq-apjaccu@dv;YkRrhP9s+&X?hEBq5n`E1oLJ_G1H5gJ{A(L}Ac(UO zT;JN8$#kq%%G;X+MB5-DS~1=HP%E?ZT~d+;aJoKgrAUxWsm=cpJN2LCp=~-eH_{~X z#pW{5zH{%MV%s969>-fjkOYLSYItAMfwY7LogbL~M(Gv!;gaonu+;tkvQ&P4X85@a zggOMo@2iDlevW(-uDB1wKz4)QT5;`$ro-e71{7z&koJX~xN?xkk-atE=!0rGr}tuRk;$z2uSrM{s#oA`43C*zFAFjtQgXr@T95~#IHG1z?jwH1 z{(so}%D5`KZC|=Oq(d460qO4U5~V`{X^>b*OScG0cOwEK2uQagB1nVOLP|hbO6Q#q zzWbbg&c6FS_jf+uFYAN;nDd#V<{1Am<`{*c-l9Cci#It?vmktt4BUSJ54^y3FF1C) zF;v8y$bc=0Of!q{sjl+Qdxt|M!GbF^>IrK@QpyZZTj0_NKHMaG;y7*q z0zg`~=$pAxa0d9&LwBI#?M?EMqX5R<(OJ51swu4w!!YoOO6(*>3PtB_Q}t$6rnck& z(f#T7&Ld7hUSaufQ_?HaSLLgZOHV*fm(rz?B4X~4U|#mxzd#Bl0|ZX0|3@@rn^G>j z%>vKjOw1iy3lb_MG-v38yq4xM&C9$?a*QO<@iY{vqyoK|v+tJ;IYLgh>1t!4_sXR8 zlp5MsOhl3_QA#O*jx9C+;Np(z`TOl34eyz2(VZP(W+Gv62OpVpNfZZi;PjcofP0a~ zaV%Yu=rYc2`}*x8#V%$?s`je@#@q)nZx-V^-L5vu811kizxfgy_yICK9QSH{7({cf z{TcE^pXgvw`1f%DZfUEXxB;)|3eUPr3i*}pIseM-d_}oXf7FfTI9aWSte>-lg#;m~ zIL2u#8a|d@#WL8Yu}%>1#tG4==f#g^r-I+Qb@Ff~#)r%1kDHVO6c>1v`d_}!V`i*@ z7(y>LpvR2p%ApTrYTIFd@tv6wr|*k@-V-noq83foYOGG@(b({O>SG&GoIV9fy*w6h zx?GQGmCjkaHLRyP(*lxhS428rYm;;K>vohxSoyCR@2&&n?`MWmL)6$jCUC$%E zks3p;9)txt#n&fwo{?6UR$rXzy*k_pWko{41+R?Xuc$EJ z{$d{hqxfiw1;c zS?ZI1YieYtm!5^_iJqbuAh(ZAjQ(N>)G2v=wGQ~_<1AWKULWVONjwJ`zMGobyaRzi z_`u#)@so^x!#38N%2m;Q<+BsQjD!*giqqaF7bzHDTbu`7kj3e_gEGmrs*EGK`U|hy zWV$x2pCPqq7ZMwYyp=tlAGuS* z1CGXj9W8h;RCoi5#QgSy54wo?$DX=AGH8*rTVvH)(H{tx%x*6pkWhkFGUUX z2`|hJgN~(N{rjMoL0ArgyeV9Zp}c+x9)OSVBhPCf)P%DN90^0vIk8aN3_^E|!hI z!4G>_rS>C+M09JJ+duVQEPE;2tBRnH+TzpK0o)99UuHRYZ6kh$$?*~tCJkRkgH{1W z0{lb0#K^si#B9to(4}?*@YQ6@YbU(6Ku{_q?#HV0<&V%I_?4|GJIPo3Z9qRuduUro84dsZVVRV3{J53QXx>=)Ib#1hiZFw^C1av3dO z`SGW0kRZ$u6qvhC5qi)+`YB|rmv-B{+$c*}o$ou{LJm$OoyO;1zo>VnjE(dre(6;F zSV=NVxDPYKW_|BfG)O3u|Dz#KXftcEkzw^{N9S{<2s>P<@t0?%$(#7fmoM#Ti>yYh z2Dc`%xG09oGI@n{N3m@=j-DMFHrf;FJw#uvmrnoDe({oPAxEjfGI(dul0AXqONN4X zkv10Kf)j}4#bVwy3Il;lFrD__yccHH?8!P0I@|Aeq7sgP=T6&HGvAsYZ-P>x(h7sJ z(>hzR&M%q4m%sBi!6b+qS1dg$WhrA_^A)@fX*sf0;1wc^{k`*{Tnh(eNI9N>$kUP# z3@F{>)%YZBqS+RF%*_6IRQXP50`A(_LM0b9-=)dNmpK^<=Q$qw6I>gBWB?2PPmt?*<+3}KU^;HAhOkZXQkE0izS&_G;~SkUf;0P zy>^tz8QOIw@?JmMP7|%dC8ra~cI-G&ZXMvI7dToHHf(l4HhOLL(;>dMfUbP6X}NnU z^K%yUmDwAI(4tFMnw@X$Id@;1DaCj!0}t{u)zPiSQQrmn#j@~oE7 zWv{B=t4Fh)f91^GgS|giVYLj)R&fS2V0{Q)Y21n77lt$y+_~Abu&cC{3jdE-TtC0| z(U#eK|JdCjqZJqgChYu_VSIL<`7+eXjaCpk{(q4eI-tN(sU5bMbhEFs?x4v_ioFYW zl(y7ZeY_s+frCL&ZgVMFIa6!Vnz!-wMJ6kWp``(HcyjE~BB={*lGw{%PiW;n zdp@pkJ-Y2U*@|uyz}FZ9YUxydoiV5xeUdI2?88;+Xsw>7?oHwqj?Q5|2|SCP=ir_l zpPJM^_+ezozk8u9k6}N1WNjgzNAnVI2r-1NO-?iOAP2vs0~`S8;P6V&~a@=C{s%Zh|Yt?7hdCf zDx-A1-{hqH56M*Qz}yB6EDF%ys(_eT8S1a0HW0x6j@P0!y}IM_$?Q89njF8i3_-Vr z)(fB92X)EZ-}ZjK0M(9>u5AZ*-!DhfY(P04(A@qj=x(SD9#lOjN|JiApZ_t1eJoj} zQp%CUsYBgBClhfmn%!EfzxI@eN8(Q=nHCDDiBjQq@U)U7DNTjj=fSZmPfrZd28FB2 z9=dfXHLqKY9Q&xLVY9ZIrs`?oBaF2~-(KXe@cM(=p9iE`ZiLVc=|THZd>FB@n^X|K zqslYG{p-oTGZDY^Rs7(3tA-eliSB4QX)nUW-~K#}B#KTM9Tn5yV36GK;8BIB@T>cA zsO15NrZ#Cf3=>8zff7cyEfkeldn#s-F>$O*o_zkHZ2Wkxp{P)AvCJ+a!m#Bb?u7F< z#)X=i>KiUNiPY-oDg?!uydA}Z5e)jH(Rzx0*j&N>Y^>tG1?SR%74ROy@}OOME^4t% zN8zBGy;NIYwi~yNwY-IRU`}=$xP7K(jJY3%p9LO5B@s@AtDE()iJ35qm1^7<(sdyo zpanuc;b^S>Uyoq0aOw}+*T^_H17$OfVxumr<`*z2AB^zM$w!(u8}q7pva zM1|@>LG9|2-5sD)2lh>DUIA=H3rXx2Bx0Yd&zk-)bWo-s1faR0Xovp{_s8*~Y}Wef zf;;3E>!q6zX(fL}`;J8+$4&}4p85a5EC9SGaVXc30$1O@Lu=muNFOKCH-YSByo1T_ zaNjg;%Sl-8%T?Jz1(iVt(R9x^!opv^3#E$dJsi8y3-Z(D77b5vqUh%Kd>8!c?2Je| z9?#LqpIl%N%{kmPWbsMdLzt2Sdvwl>sTXvGYZH@w%-gycTClmcQc`0 zNty4YKA-`eLEX7?_XyI?b~`M`*X!cAK`+KZ`6waFhYlswi6An1!4f|xS!{PZ{u)e5 z00qiRV_l~YAAKY@)-SB(g8}Nw{lPZ6I)m=V9%E*?%MDoeZG$i|KXaDN2RLqY1IcR^ zk6Gz_955Xx-Zy;RTtAQmq+-w!%6nb#%hw*4xMV4Pg`>vbxAN2h+o;Bp-7)|BI~SGl zsth8j^2*pj`!=OkC)Uw6OrzGLYEipaRvSmrxIMUt2i^vVUg?{X7wfkDb-Bn{|$ zLO^Z=kGXw`_~d*dkXQvHXaisE#Oj&PJ;6TL>$zg*@Nh=jXd)`3pq@SCgflpQ{TI@5 zv>nk8p8e1euYY@Lk1B2VYU4QBu+1)^-1aLsZyruK)>mWNqCOSpRqtKS#I?frCLV$m z6H{Xgjdnz<-+?JR%%U@Fc9$k6U{Dj|AuRsoyGT3$yI%ut-ip2Hm2$gJFEJ#}Z6mA) z^a~BxE6wV4w|zeDPUQ`**KO3vkE<-3+*)<}oUb^bov=Hxd-5{2J(y|2d0KYjm9g+; z#>LAMlQtXH9m!UI7M#9M-%(5}u$RsZ0|XQfOp1(te+OMDS*vf|@%~|W3Z9^Zhafy|HS`!Yr>Sw!hW>(iN{S}J=vf`?!ldr(AZ-Yj6O`gz=y08 z&aWk;;fhSohjc2#~_1ODu?Qqn$uXE zN|RhCLh4% z5xDEa_}ij7_kV}b1)}W*sxvr|s^~XQet=`|UW$U=U=bhqK~Dv}XE{!B;Hj?)*+cs` z;a3;-GG|oaCBS3(Z%g9rW=G-Zgwowx#vP~RFJ8XHp*hT9&v32>0(h!I(FVFM7g}9_)}lPI({Ejjgmer&h52g=;ReN^y90~0*&5Z0 zIK1Bgw$p`Rj2~3?)N*7ve}G2xEmchw%fm@cE~hc!X?a9+u+ z!jWpB?)jX+#$FF$l1OxjUB|-}<=O14{BJ2i9Fik}+4izy{k0$qbe0P6`pwt|<%Uo+ z%Z30Wlcad?n4cwnF2KkFtrj0#szH#F-Hx2()kr#w_LbtiAqzXv{d^b=2;_NVnJtV}w&1qfR zr0tOa>uuSB;}8Yn%4I|#m*KMh5AI3oRXDm%vt(H9jVB0}pK6pxCzu|iR+=Ed`wg6R znWpsC;GKza?-47mpCkOD>^73Wo~Bt8zb9t?%f$QwCgufXy`SF(*4t}EOn^_oMXAWH75MVo#>7mLys`A z#V;%E0U9E-9C+yAoCZ8kO5k~Vd>AR1l}>_pPHfUTIXZ#~auK0)>zm^`^#Vyq?K&A0 zDfv>}CPf`sA$c-EYzG9dVNY3{GnJRDzdl5_1R=c_6wC3derV=Pe!@SCK|_4l3nzA^52hd)IC8 z^x@`T$YT(ujkL{Qz+%HmecD6u*BoyEunPd}t1Uu?MV+n^$V`wRueSK|R+B zpZUt#F(Q`S@aiH3MPhhNl+pbIKe)F%qeiK#p+D9{G^^ewF2gUe1!Pz_Zo5Y2{>2ppu%jkMZ zW%j=vM8M#Q6eeZtBf1=y7C(HCmpjRjLd{!z$1-c$3wzOJTxiYLJ#vvXsT9;%LUs*E ztd&Q|CaH7K$+NRPa7Kc4Cw?hz2iT9f8p#hKW^@Mt_z1AM5O-XxHXko@{C+;^{7tpx$$?w} zjJe3R-QZaIVNm?jLn5by3}z&0Is5YW2nFU`M(lZ3WLWb`#3TsTmmx^04M7^w%@7oV zkuzSqh0NQA=HoiA{wa;Sp!Wp%My$&D0mi(r319+b?%%6|B#?^uEc5*VF#`ILJd)<- z(cG%gx34N*m#~&-paS~KvbcX_h3JpXUHbXWWV-m?63GrO6B*$Ch$+d_ED_fY$d{J} zdOt-YaGT6hw>?e#@Mn>)RXiJ3Rr(R~3%XhU5lHCVKLWCQe+EYH_E*U0B_2XXFRR92 zN`WBeP!T_8KFu;%%hZHcwpr#ld9Sv%_8Dj+XII|n{z(x9oZiN8Xdl}tL2Ctt-fpcz zvnHNG=UMn=H-6CNae)Y+yC_6=%j_!4mjn`9&g$RSw`eGtu~CGKjP{o`crpIaT@j)? zq;?E0v~wj%-l-~?{s-jaZ_1yUymKI4eMBwMs5AMx=#*au3o?6Mo&0~y9+w~sQBL1;a9j{Z^ly8HKzTJB7Uw9As`@`FOqri)DC( z&q9Cp1-|D0`CEjK^+of8DxmGDG>`XEKaoyC`!YfMBGM(6xqi(4#&V3XsXfh~Q#KK_ z#h6H-rJLr}Aik3AUJ!Kv>=pw$cj^GxJs@7m{6f1ui^OSa+))Vd%$lS>)>4^*_o zUN-(~c;~RooI_uk2B*iR4)xWa1abHc{r@m{@2@Ej1P>6vgJnx}Ku+pOzHR^#@|rw? z;q`se=|5`5#RSwWwzI#}`P(ElNRd$%yH9dbvjbs&+dpKCx_1E5r9k8HAhJt^a^~F0 z_~pA>Od$2-2Nh9~;LTc31k>!yX6;`w&NFb=9f7-UgBha!7Dr<5fFB5AoyHpuDzEdfbm)D-IFy*8>jQyRpqTg2 z=MvPo*g{dMm371SApXyTd0GwlQ4?Yjh_iuHC+ZbgQ={lvI>n!!Kbzjfri4`dpp+A~ zwVE&rSUyey=x);PoD+Qiz3s5`5B??9)i9w?pO^em&;4QS8 z3#)T){-a2xp96CcQ^K4SsLcs!xMPW#7<9oZ+Ul;LeDBT(-rBVdYDfmCz5i~hGxQYQ)f3Z*4wS+qn0@Xevv37*AzkQEQ%DBozY( zlq-1GoR}eqRothr`}y)XcSlI&%OE%>;dT&OPKs-Ku@jBg`NN^E*B~$E8sp~%o}o#! zG##Bk_+_5j;)zc8wesONJ+wPl4U4IxKQC1pEeJ~H z*9|W(X-B1N=9Ud%`+Su=w~E@XE_{_~3a>_c${qVWE6*J~CJ|qSR9BOnbR4)zBftfhS*ivXB+%nQn{LUI*6Ja9QnPN zM)>fZ+`!_i#h^2g>ed+X1GQDd_X}2~Q6@bSQ6+o}6}1UQ9N2UXj8zzd-|*M2NDApt z^gLn&vP#W3;1FG2C8Ye2o?l&x)d1g6(jzOp6?X+HX2z!#eFn`s5C`p%9e#cP2(?Q= zwg>3Oe%9i7q}_0~8h8^-|Bs%_Rl7X@3K2@a`CSb95O^&H9*r_5Niro4G=ZL>o6j|F z7br4~QsNzMf_Cf0%RNzJ&?{fi$%PD>1v>uLUZ-xC`s8%0M&NYVHjzQbVp{J_mre~#b* z0`Yp&#wyg9h#$5Z;|Hi&K%|H@{MnDm*_H#+w@O1N=PofQb)sV9 zH9z2o!S@iaA|3d&oEWqUzg?#Sv=qpEuC!LD?1E9qbuxvVlu6mO$>1R=6mi60ni#TH zI$tPgwn+OP%*uH*kVpqr$B%(ITN_}|B{T;$4;nkB_yRO3z(sxtFiEP#7SPgpbKF3u z1nTO%@Bilwy3G)n7kq2m`0iaC>oxE^xR^QVo9}|enb$QsI$Z;0Mg;v2ojnYv`V(lf zuoyN2>}8;T#PzWI@a(47y!VuztE;OzqBkzV8IJvSol+B2NN$j^C(CAug4#WjC=M0- zMA)=3g~nD|iAr!V9_#Z&xVn&|MN+OI?3=sC_T7_gUnSBToDksKAn|T&A-F?3AfALb zaw@3#1(${lVCJ%7j3igy#Y-X~fKqB0eM*aIhoIpf_*)gfdI)ufg+I1Z&Ovsz-ToIhlRAm|9@#Io!)p75rwID}u@sjkuH zfV`R7))q3R{;~(FVM`#UQDA*KPLfo$2Yrlolu)8ko1GAOf@tn>Q}r)jJM87&Oxsm> z1XY-^fw>m$LM9dPEav+cwb)D$*Vk+7m_dCD6G!qAdp!CO! zTjf8-^tr^X@r0Z_WEIX7bC-s@DFy_~{2tGqf)AJ>)bu-%hR{_+hMHJ$Qj0@m zeIQZ#s5D?UG?v{pB{>4;1_v}8MK_6FDmrQU$y$be_=j3B5ctjS70{)n6pRNM1@osj zd^^tQq`>&3`iGxh*6$)*32wO4`)~8V(O3oTPYR$m(;YRYjfwP6Hg2ARhP^saB~&)q zT=L9y?&x^D#n;WGBdBd!uDhF{W4lH9>&dW$C4Jz&-ttx`CcWxk%~Ci^VDLU=VFa3l zPO7DS0WIh_$I)}cv)05~gGOY*Vei4Z zyGL)x?@M45d`U%)cR{~;w5jqU?uKrl5ji>i<80CD7|W9jOx+3J7J*-h8%hxhQ8BjJ zFkh@VK5pe&r03^#=jHc5F%3H-e7c?$^@);$_$d4C>&$6a>B9jq-}&ib>nRCHS32z% z+?SIxv3yJ*`3$VX(@S~7qgV3u&~1;Gx5lIDhtR8Xhu-qt-jqg~-jv<7508iUXX}Eb zTq>Cdr8q%wbsN6Rq1Nv7ps&brMt^X3B8p3fhxHP)(aLK#Yzv@TC8V5ocTP*t`){y3 z$lNI=WS;|6w7vQz7exSvPsz09wPm?Gm%p;xXMaBs?)~k)*nI`JzXc2JKPKKGAAX=|0zz?u-oeB!`5p}-A_@tJaHNXjaa@3CkqE5qs%xc}3ij z68w)^0Aux2zz}yYE+V30tJGftxH_mbhTT^c4)SO!s`V&^d zcFmXb+887rHhy`m?Ij%#;r5?R>K*DWo85zyD>c@FRlSP_&_K}Ci12;Jl%Pf3%XZ_f zbpF#&R2q1uDwecS3BHQA2fbnNJCpC4u5&($pEF}@TgofdYe7plUuNU)K5mvR4O|XYl zvezy`n-p{$UwG3UY~S@AAg->0Ju2=VZRA@HJX`pOF}|75EctOT_2td)@0m{31ueVn zS&mLlS=*9o>;X+Pen3sd5xty8j3lJC7z#*)nqBS#<(Jruz83GDe5)TNi`Ez2Q>?LQiR8Mm{mR8!By}6JV<2LK#~;*E&e*r~ahAm} z=lNXyzF&9NDPi{YWH9cm)wU312=uZPrgwvj%dMGwa`$Ou$#G?hD@#ho&oO04;;!Hy z-rV4!UQZtmKJ@+1EWp*2Xane~%O(SNr==vsZ|hzB%y$TLf1UBoXGiHMlXueKbDAj) zRm{AMlz$AJ)X=+=^c0T9K+LW>yD?=hYN<$1Ph4+>4o zFv0nb=(4EMF#&l=YkBSGf+;Y4FqP*=ve}c(5<#fv(yQ+i#+Kv*Zirj+{wF6V^5>NQ z0gv}*vb(jPut8my$Wqp4xdH;FYJv`KA>3`+sxsL@>n16Z>KDI2@k6ns=dGMQy}NfO zi`TFjMD7|Jvwv0wJu9)@iUcTzId6twva#N?Hbh1KUQPX>c=00;TanP#wmbMbJipZp-n%9oHQme#jTx9oxQjs>1(%h%@R)ukHl~&^h8!A1m z1deJ8>9oESa{PBs=;f4H2MhvTcwTf!DRAeOJt_HBwC6t{IQxm`wyRA`Uy@E)_6;6~ zqsIn)dS+%(5x$?sgWfW?)g0j4B#J(FUJl@*mDZ1DGdq?XEkGp)?d>P3Om8J7!;i|T zV!GGNYI-u6DWr6c{*itF;O+rQ@IXwr%}XA7F`i6=t`TcRZ*N2XBlF6dS8nm4qJ^*h zbvz`gCnjQ_@Ecm{_&2MbBV)#{OyA=pZV7*Xl;8Gtf#kreDh<+C{6>87OV^7R^8h4- zx(9#?odI!2X2j7n=*g7o2vcFS2qgmesBEo@r*1%A?1Os=S*aCH$)%?c&5&$=JmZIB z|Nio_Dgz8wz=BO-F}yzb=Z+=#)Pkzv!ejnsl(}(TzX^zLsHkgbY`&XwWey^02BUWr zrBXmI$gg>q`|Q^}O#Hr0#s$LZIL#Du<9hQv2RZja1NIDY-=CU3!6zHFpx@=$@PN;o z&N0wh>-3{eki5oAZV%S$0)yNKE3{)y$YX3wl6*JuyVa4JZ^?K9cs%NtHYR2SoQ9&( z|KNta8i-76>_ZOf5`R>hHH9>(it~m%2ELAwuH}8sUJ~g`E>M$G$SAstw`{^DhDoQm z8o)g{h=C}+Zy`;$C$p|MpX>JM%d4w2AGjaqIg#xl1LL;GXS}U1&Mke9>| zV%oWvxOvEN4>to=yP6j;C3z6;mrq@~~vG^3It(42=W5$tPSFwZ#A+3UVQ_ zHfxi#e(VuM=+7<8I0BZxHHT8f`qBi8WH=5bnF2UuC0dAWz9L`p&}!du+FJ_RzTo}%5cK`0;0 zPll8VjvMVqfG>7?v57D)fcwVoz4CLIT8cbeT9bOl`%`~Hy_kqxGy8?^aoJf~NooIL zuT{l|kNGQZDE0tBDg*_#0L@A0ZP9AF%Wly{GUAtQUn|hJQ3-vZV|T3^yVc-qF?jzX zF~nw-4I>doR*HoE>Z7oX&f$f|)3mlqzTd9<)Ju>)Nf7t@jsUx`y8+*sjbN9ABwq#* z`tec(lIIlEdFDS7Bh+4z!c+R3l!Euh)na zA<1NOEg!u8P^W)%z%VaN&+mP0W+z2$5V?UyeH=y%EC_s1oS1bvqDHB>pQh> z;_2Zms7UgmGoZqj62Sg6jb3ev1$$>@x>xOi->D;oRM32OzNx(uaVhOZzs7Uv`qY(* zKq|#X+rk;t;LCqD1vl(MQvn53x}+Eb0xTuY%;gLXU8fhIx-$1er-1~rZ67HX$1f6c z5p+N9B3EoeO}!*{ul1;JFKFhz0{Ex(b5?m^6oDr`urMfpr9!Z3btQp&ryENRk|#=I zo-Q}wF-{M1CBPe?cmg_r#>q0?0SO|mI|X?2ie5GMf6()KA&2&y5d*L^Gz2bV>tV3> zg-`p&UME?ZgNjr22Xj9}Jx(7Xm3S&iOt@rE?e}P;yqg^k%iWz&N;0vVwM}mXktThU z0Rz-4hpg9uCQoUdBiB!tm{ft=&qO&+q@^}BTT<;IA5LE_&S=#NLqYD7BO4R!(&_?G z&0#O9ff+Nyr605JYTq;52)8^0oA=hZTCUzbPfaV()2qIumIFejJ67D)+yFLRqVpjN z)N4pDHE-h}dRh4dq1Sz(Sb${}SQtVN4D{O~D`tZ}xc8V-pvm9Nm6i1%9^K7^moDW} zJt0~S#lBB;7YMdra+bs;&X4gr)&}Pp{p-z^R>*IaU!Jj}-{SxUxwPEq_+g11PdWXB zu@Suj;!|{>DVuU60T2&HA~Aq;lQ%z$^pvlhykj(j&-y@gCsWp;7w~J?HZJSwujQq!nt{b z#*-CLI}<1`jSoZwdql}Mn*Fv_0q3=dq(UMhSbH=3<+hO4%%OZV+3hnHA}$0Piu$3$ zO6jv4Vv}Naa8W)`xd+^!RmDWyKK99)qQjlpkNjA-s&kEgep>G!=cQ0coJ1ylit(lUV#<-$aQ zFDIFdB@o96;mWowP@N~=3n*DeopF4+>YFg;c6I!GU?>GUTpx&HT`<#1X-J$Y2C^jI z$9yarIU?Dgr)9Ts`Z`X;y`{brUG-aa35D{4x~O;cq9LBad(5Mae-PBQ*~BS==ni8~ z&r_fSVsD>0fsSNmxfxZ^-AK)RU360cB}vCW^v?0tIOSNJ^<@d$BtjADe}8 z+xc{a$1I*yakvY;(bN|B;`-d$Y9@ZfdaN=N{Hz}i>pnfqu z42K3JWp%Y&zyljlF^&yY3Tv_gysuOlBdGcQSsyj$FD3NQkvMkO+f3w0Vf8Ut^3V2* z#DwY73#q0=hj74NgC!}urM?lrPB-(u7DLQnlBQlZuIpA1Tejna} zlJS4Ij|s_OC)rLzDB0uqU@1+BagN&I-H{v#S}RF(IE+LE8C?XuhBGNB18XT1`-B+M zw%z2nz=-@9MU8;Bfl0F|xDmnic3Ml&fxg__pWxp(KJ&Ej0QW^K^4t)V{rInZ>g1IO z^>Qk>$E=iF&O$^5CTXDHxZ-f}#=z$KQ4LyG7}jQ(ecM!$z_Nd33>Fgd%j{7sgA9~S z;|Ou;A)_c3&@KVM^hHu#2k>N3zDce0&r6j5avI}1q2Gn{s*;&h3`wI?VI-K)VJ6ZP zTX$AMITsLwzqgZH7K%iUetfF1b-hh@&+z*^){`e}x_{*eu37Y@D`fPte<@k{`iTX;Ys@C0uo!nJD~6u=h4Z#( zOE80~_=-oMbYWshJ=X{{r@LqO77f8dbF-BYy4*(Dfsl+Qd8FDkHQMoB?8aSbjFoB^ zc~Ha`4SWg8&ScM2c@&!-Cx-@A2ILpBB&dy zah9)?H&V3 z{zM_3WF+ioA(d#yk|mB0HjH)9n(Uf|)kC%H_`mnEwk$KJ)5uPe%bM)D>taDi{;)0BrZcUUbGkKOOB@5 zyA|pE>yaq>NUM?GVxzUeY#t&Km z3mH;^q|?5Kw6oPsgRKzV)+imRI=7FV5cB2jWIFLo_`3ggi)neuH-#bc%e~tgJ-LT| zJrBM#VfIut`vk@na+9^g`QsCJ)=gF3O-9JN6w#<;nJ%4+kmB|UT)yx9nkP4u*dZgG zH5vtv@fhdKr5wn7sTzW&Dr1W(k==b7s!HrzTtAZgcq1+VZT`y3TF; zwe({^HYN_-h}|y=o_LMhzY?;gUnk|(DC`t-uHA^WKR>`OWhhFg3zjhxF2MyXsr|n& z{x2#`t4qFl`sWDU3(ygEk4^m!%~}>zz8G*UoY6Z|VWAK=UJ~6*PIed1`7k`JEH5w5 zIAkWsw&I4hCY_K@ekDz3P>u|Cc`AqK6`ibCL}8IdsU%tYom!b(U`eLagaI5`354Qb zyg=Rg9U0@)S&7!E{Q^g@|aHVcuo!Y}m5=PCzRf5*v$iCH^+e4uv2Jb#rr5 zRZD9^@}&$a-{qz5SWVIlV#}=X*@3c|O6*ob|&z%hNU?IE3778FnaU}nK>DPUv zI!_Sk876C0;Mx>Bq+52DPfuA>}tR#%cB)Tt<^2!Vo@Gc5r z-9lEv$a8V*b~r$Hhb%7Z7hgatu>F@6WD&jFLeLM^h%}^}={i6;*9Jo9kCj85CPgfq zcVxg8mURLqANXc-uQbA?b`(Rdj;A?iyM&CZ=a*vH?G+%l4oUL6{@?WBKdgu>qL}ni z5gLBcHf2M!13L_Bk0Ea$44y_r-ik7(=aCz+sxofyw#r z_UtdQ)|K33LkT4jMG>l=iHklAivl!x%Kp+P3wx@1o@w~xITALS1VmM(bnr1%k7Ce( z@)@#ru5%hOD|B$=y5py7g}~JaiAPK~M*Jn;QV{H5vLi3>q1&77@ZG95mr%@j%z{i( zOM1OV*59qcOFW{nH{wM5mI8MNQwCZiI6llf(yXP6`u=bnnrde?8b>ZAh2OvDZY+DC zqU@`^f`*P6-0;I4B|wkESYN)+FP%iv3qLjkem+Bj=wSqY{*3~H|B1soz7=vj_XRg? zIvfo}9xQ^~H7JMA*xBHO^dJ)m6(ce#Hn4SnW5kjANO_r;UJ=2a{CVfC1W2$rpyQD7 zViGbUSI;ALuZ}?CYJ~z3MXu~`7z|fHYb94~by*L4B+QDefVCI-k!VPe1PR5g1vzB! zrW!DWor)+xZNhZ^?nnbz`>gT%-U}*^owZRSNtW_Q0 z=&DW(K16>xUqG4&*azerSwPsHg`i^r+TKHDIBaXWr@2N!=y#%VSdfMETRU9aj&DA>1P+G0;q{+NK(*fot*k@Y}QgSYm ze3*@yArQ)cb0=}+I>aL}2n(V-@GSwXQ>$)HGZCUC)*bj#TX0Q0zo$_Ok)i3u%Du--WlL1T2NlqZXf{I*i&S3S16T0T_g+LbKY41AKzj)=?DCTc< zo)mRgkycZ+0kc1fvjf_PpMPrv19Jc6-cE}O5t(5h5?_9TjRx_$;(y6&ang-j zXW{qR&3Ho@q0ce;-+hj;JxbfdJie`PCstix%p6!gMd2qP&6slngQ?F&>(!4546$^l zjR9ncYoSZ#6yu}0QPe?WRqt_$#w#ZW#jrcxGx&5&>b_qD zi?Q7BqhXVIWk=Y!O`EMD% zPx$fB)pu5bzI&1lEao)u#dX1VSlW6rX1>tvJb*LT z;ej^z_P?_NYF1Cjh0cmaZVicJYvPG=T}Pv;z~SXmj*vc323iD3eTpn_Gz*jY{wH#W z;y!PL1QXi~PxQ|9Qp!+Zs}z2@uO#~*Pcz8&j*n#r4fZm2pwPM>$zB#vxsK5W-(pxH zF=NXu$XQd52pc|@1vf0_J!f_3Xg7dpvbf9rVuNTDy7DC%x;15$pvaG(%kgB~>rzts zVxfa`b$qk@37)6YtyP2koaX)24n{CpOg3xzI0rZrqfN9xF#lH7u$`Zs4g%V#S)Dxg zg5*v&0g4;qqMux*wjL20?kvRc8wZ+8tvfI2ZN7V5?%Tq@Tj=b90c{wCKdGy$nlLv;D-mzh96(c`bU>4)>d#-I4JU_; zx?m}!B`DU;s!lWz{`I{R^QyNQ8Fe{Z(r8$mlM{QTKjAGg^5ZgfAfdtklXk^p>rZWy z+T~F8~pcC0b%KUc<5yLO{BO4NW+w?J#+s)h-KASa7mSO^f z0Ztmfx`T-cha6vQd4s5~Wp_p?DY}Y^3SlOzOG8K~0F?0eg3yny_8_m7|HRd-%krqLr#0@rU?1NhuzZgxXpmtvj& zPP5L>I`i?yY3;FDcNfAZd}|I1*smRTfMlsMruU_sMzVc7ldsmF?)fs=DaEMGnTN77 zR3tMf=k?%T{^x1C^JmY9=7uJ!NnM0y$KC{ z$JC$Z@SFlDtT4PAT%LfwHy4+LOYxxKwy0?0+jNLl@F_EH9}RvxjXwxOFhIEUUwjI5 zZEIc~08pe|)-_0xOg@|3V%m}?6lBdCS z7{kwar|`pFo^tk2p>1GfeQ!Yu3pMS`<{%4powJL7{~#2-6f-5COR~|V+BMRETN{8`YU&_-JBiO~5e8AQ(&Pl@O==QpIEfNyu z2;g;kJah}|-@kWmdYQNjJ2nrcZ8`QSxVceUw#8J6&@ZBwQ;@!un)UjUqnam)j?Jm? z*{AR2VPo3~hRHfFAPRaR5D#0T|7QN$h&D7BH@760p?D66g%v@Z&M zke-gW=!mQ{R~~TJJ)dJwPfZQ?Sm{UKbWX^*VOdS{Y0kkZzGk#Hp>e<$xnIC|27rWD zXX|_qKQ?f+P}R2^<%ou!^JFbIEw|7;{yspz76EVtvgWr zt$807da>Y|KKaj{^13i)BxWLp1QzU8JZvt}0lHK|(8igG;^8r6y63^%84=O$cH)$6 zKd9$F!&c{c?%m->L+SYN$DW|CN*~T6)_SrP-6|>@!i(u6&njKuOlp zq7)AbaM%1`^H%hD#=e|dFg*fJ9zz4PHS~`FXUQ*h(Gl95B(Ca9-WT@19~J#|9zTUh z>*cN)0b=pt*vsXvM}(PhfrRHFj7zT=0f?Z_2Dw5cx?!?>vFxDkHuO35P~OW5j0ahM z$;L30GHu#{{pHu5Yu_Zy7STJ?woZQ;x(N0gH$-pj684c^WSDp>>J%K$h|J^JD~21D zS>Nx-qM>lO;=KoXuq~vw+O4d8KB0{HMo6QxGDMHz z>3>`~DN-z9gd!+Co12vnuw7P+oJT`pzg3Cot486b|16IP?f#Os70UFgzbU?k+5@W? z0FNkYQzo`^Pz~;l2Ms9)Z)B0*~pp0%i!@iCWCSYbzE} zN%yufNq3k|dj0;%Q)T3cPuJjBl>*~*FR31f^f*<72bDqXjhI}q^hx9I^&{*$PLw%CSw;t^pjK3#s0tyy9j4GTJ2m&f>D6|?Ii__rm4;xmPa6)2}t4d;PhSX zgza#XF<2dft*tuQ2)`U>(juV_0jL|P4qX#G@gc5$f=4GXiL!u^7k84?Av^J`0LaBX z2S_fk{>X(P%EANS<@b&g&CJ5`!xLo_oVKhyenHgE7YMZ?(E$%jT8k#qrNk8M)oF)m zP%sJedNamKxXE_$x$#|&J^#3hZ}ZlN010^pFggKf<(L76w((Ib1F+*G_|5Q$U@*f{aL3Q@KgDLcGeBF zw{I56w8?1Y?*{_r+@cf*<}^(H`oa^*k#&QdD1Ir)cV3+w);$~&@9>z|wIHvQ8XGMR z#so6T?@Eb`hiGb&v0Ba)mi9BKw?B|Zz)dCI|FNj20<>rQ@*zOh{r&jbyXY+WC1TmT z7*^Wt(p&ZkitQ8R13Wz~wfv-!(0l@ZVVh8J+Y}rl)lP`BfYU=t33(Aj8=R2us~$dZ zSDTW@fzsv6-LCHsuH9hNP%B~`i<2cr69s0zC%snqg3}Y0C|SPbH*fFL*0l!^rv9<3 zCla)0dXjH;sPkbY=LKcphySNbB)Is0f3f=dG~y7$`s?Be zt-Nesf(DFl+>Ix!FL-Y82W^rRb7#BDB`x%(kZ%31bz5z@*W}3AO={Cx+snpcMG*S4 z4iZak?rrA@?wdW_Ek21t>)H^x0*SG}2sHza%lCZ-#Pt1|QSCO>M}EEr0t+T@QzGC8 z7;`??%6k^#eFdY9S&V$DgY5Cl~qUjAT<7putqi-G6xaF+;f7p8KuqfYce^^2q z>Fyy0q(d0GK?F(Z4gsaRQ@Rl;DM=A&l@93!L8T=I1f+)!f%l&8-skLd&ilJA)PGc% z=ec98Pc769qOG^c`tI1uQhCG{m(q5j(Rx(W4tch2CCc!{AkWr(X5Fl}lCQgj!(?O; z9MAGNdJ zoPnqTE>E>35U=0-r#+;nC;Hv4*ttcy`u}SW5wH1kvQ^)CAW?$R#K>QjA0Z~?@aSLg z2_y`kBIAP|E-L|Ch{}m-ibwaz<3y~}YleQs$I37MpA)vBb|`bFi_W%Rq-jkR!E&*- zuz&~TSf6NSm_51`jKi_8anZWFzv(UCcFb(8^{)1?JG=~EJE~Fn4qp8h)`?RxTxB^y z%*eN1qiqj7E*F)$yocx#$o-(j3h6qCeiQNw10R0(Ki!L?i zW{ZC68kq5`HZ(;8iKs|;A{?ByN~yvF57rzW_%K%q9QQt+e>peYe*K`Nw3JDTU(08p zTgjHuYMSs= zOVq&|#~iR^X6TT#@B|4E==UT*CIT4NN5r zPrGEBjC)eEg=3h6!@ed3MNckk^~oVH>NiKc&@6K-;t-EeTDsT(^@e9cn&or{?y_0vXC^98#}jxa<>3_~lEOTpem!lb#Nu(|SwL#vj@W>2;hD!#FB;UCvY z#hdN{4TauWM@Avi{UTJM{n_cw*{M#dPb=fn>#790fWh)6_A5m;0#^$eqXUaH++VV#y-pyH%+XFGy0MHq3?3rziC{gX!5s` zj+Y}RxD?B~$WS|88*b~`8gJChj=vNUr#tV17;zIUp8vDWEAFQmvM)t^VFST(wU*!{ zrWrN+ow<{qp5Ao1k%IsMP+po;d=~fi+m6-$p1hYeuvt z4MTf+S_)lzIH0d$LGBp`kG=n@?A3@LXMxPc+$Tx_tB421rVRP_^;yFSnKrbQ!J z2;vx2?wRIBv1G>J9$bju{!9dJSHA8mJT*kGa?-!VamyO63ZJI8&59&fGCnNRSW%iRMatj zf94V+x28h*XErq+ZEh$Na~hwJHDQ!3zMSv6{)a4-Y*I{o98^6bwo162CKrynoFA$c z?|Q{AasG{p8qNa;J~4w9C(i9(&T36N`4+imjUM&&$cyWMtD?J?|3yP=<`WXhz+Iv7 z0?Ama7vQ|IF?kl;yicSNd^utcfkKt7M1A&YgU>z{Sgv+QY8&t$16-FaX#Na|?{KRo9o`s!?qsW3MmI^orazA1M4Dm4Do6z)>kgMrO8BCdhg}^_WLiM~i5l z+61cr_95uHsz)&bF-*%vJ zwK)LsVB$$S%A6^0#O`6MxNQ~C@Jrb764VT(Vl5u%S)STNL9-H-1WjShdj2xo8y^!Gh)-Kmf7zqUy zQGPm(MurlXr<@CX0#580XUUHu^L`~dFTRde;dqL|Ny^*M{QIndqDJ6jEYu1koMYC3T#i-_Dx+6eg(<4Zjd~05fn4j4zJhy>UJMg{SWjvQ8@B1P(C*zE5HksH>N@) zu$CH9%tm@y!^szTDjITA-%zw+L6(^zI5Y z&_&*~`XSn(qB;ZU9=>n#jDI&9(Q?7UN6<9c&D!n-KRn>rtyqj~tg&Cli5SpFuVl;5 z74v!$$2eo+kvJ+rl+A8MQ$yp@MifuM+YhX0i~@QQq4f?kJZ-?`>bst*{B3rY{eH%R zOD7$$uI|)n1O)}vYzChcGguD2X1$nh>|k|rnyqEp>P5b)D|{dtF+xoBEG)LV3>-3X z#4zy5B(Jv6F>^V*8O3@bWr99qw^JHJ%pbthEg-P6)Q^LdDmW) z!!M(lN-{eoHfpImyr+@(W3r@3R@dqm`qH{_`_i%;YF=4?Tfq&qulk^LgLxNmSm+^b zsevWft@KzeVNrNAXhjuK@*Tin_kn-qix;jD1-C5-P^pr-!6@^ELr3zF1t1)%>3y6X zO2~O6#hVh+>uo4M^_tJdroJ#-MME8TS)HC^VCXeY%w7-MC=Kf!CCA1E-Wxj9CttTw zBYxfx*t?4Qej^zSxKdFNrG1r&svbEb!-9O+?X9| zfbhEY%zI2e3)k>U5lF*^_9>yUe{2hqAjwO-@!-S zUZsIjVLg{&`GFlvqqUIO@EAV+V9>3o@qVPE;y=|weKQFW8&1X%-y0bn58P?z`H#kh z7uCF>eKzEHhM}j+1}#Ffq z2peP_iU|k^9PYOsJV8-({N#0abG`;_;sf9v`R_8Lzs~O`*%YIQk@nmoM2?A;0ON8q z$TfV&41T}dThfSw0_{xA(x&7kdiO(h>%m&f4)4;LsiY+<{^z6a>g{S2PMQQNBgfn-=eUfe-Lu}(4lHG zBKBFWger!8MaMfzJcc~TokM!@nF(DIofryYIomuZM(g`j6a;cN%3II%9}o1JU4OD2(;gYR=Vz^mgjJT!Tpw5q zL}SL_MDHcas2GZx{Cv-+V_kT$Z+0(WS74`P6U#tRg2?vWB zlXB$BYp+k66g`L~G*XbgOgVJ@{OpZtU9qf9KJ81;t#oF6>iNhMH+g;w zV5tgv=HzIYZEMRP9iiORk_~OaaYidtF?Uyy|iiHkQ(n z{_FMJC%fNgrqY)~8Y!<|Pg#Q(^d8aO)8jT{>SGBq1?xA4hSNE@4UZ#c681a$)#y^- zu^%q*AMxP!gGxfy4HUth0?0Ss+*qN{!(X9HuM5Ax=Bh=4P;X+6mD}) zkG=cL7c1ws4mUe(iqG*!$~z{nwiTARvo{v|B4rlvzmu|pyy_)+GPSsPT~gB3*2IM9 zHiZ`y1p_}qZiK_KWZqSLCdCwSj#$G8Ad5SH*YFlu5#ODzp{nKaJvsj=01-u<>w##k zy_5|$L@P^(iTX|yF4@QPMlbj@vUESni`Lb^3dUBdSoon7e}Q18xL(g3v2JqeM=Rz2 zqec!9)PC>G!eUzG02jgnL3c`Y604@Zi6V|%j+B8k>=9=}^W`0vStC*?%mcOJzC+Ly z9EVQ9Q(bM@sqR4|kO&1yxW(1g50WL%DD(X`sPO2-NRh+4UH87s4ErZZo)QWBi|ZR0 zu;We~EH>-l@ca4sU4VQ{hgjrhxvF}au2ke(q%DNHqq=X}Xl($O>1Aa>pC(j3$oRT1 z8Su^f&+mr6u;B{TZ~$ix#_pzxuq`L<_((?{qVG5smblIDI^PRZ%B0ybTZS|?4`-!B zZ8fo@RZ=??b@WH}__4ZQ*C{Roe7w9yTQu=BV)}%|g2J+k=lF+*lPi}4Q3}W^pXc+O zvT+clXIY{*XvNTtRcYWPJ-{yb*_)<&ClT{j2{WpcE7zH5$%&&U+-kPyU z3wx(zY=?-ZPJ=7?D-ldL5W_*wrTZ%r_Tz~Mw^iCn`{F#f!@M1SJQ(kx7!SCVAHYyr zv2na}T)t8e;afIUyB0oKl1fr&tH_TiAexVA^Va(7pt!{I02u=;tZ-AU%ELb_+R2$>?aD zteOJ*8J&l$MD?jB4@BZw5(j#A&wFZqKLt`Fqp+$-FUmJ7y;*PApXIw`SPl1nw&&P( z6nhqJ8EYw-ACHHddPb8oUn{I(j=T&eel<+ImqYY@=`*-Uj${`#w>tSv*LDWP$S#g3 z5iC)Rdw+z?1ZC-d*RUxgSbWj)&ezwl#I>TjI5@h*RU+;5eAlsF3MkiUwsrGwkADe` zg#s|JE`8-y>%#-PogSe#PDYTou9I6CZ>1Qg!`g@`Al-7Ml86tB+4Hud-g9$jYFWFM zT9WxBn>D$#(HK5CCpr_wyEgaVju)IYYqnjun+3}9-U8a?)N?4`T1x_ZyD2~b)F$X& zS>nj&23y+&-S+&+A0RI8@_+oM=D)N6qTmt!TwF|`TWDOkT4Rsw`Th~m1(npIBsX9= z!!E9c$*dNX{U`;4Sl}4_fM_gYo_RJ63ArX3ZE{ELpBU5rq1y734y6?r%ez3jCT@kf z_D{Zes+1oNv33;3@;Vn^v6D+j*z_f?V>?BCI)K~F=AvX@VK6bN-^n5Je{at{QME~r z5S9BjkI`(Z=U`0JkC!N?_>@F=ww?**^@_h;np_ ze!X2?Et+i2%GzY*_;F*Ww1%P^SBG2W2HS=urGJ$?c!Fz9`iwgvx2UvAh>nc0_8ayNb?8Y5KYz>3^Ee!1&Bed80!rc zlwt>+vq8Ib|Ml4Y%~UZyTB=tNEu%mE`*8nJh6Mut)Cao~+R*!%`Qx#p>3^vB?VVrr zP~6{iYuWx-bZ%7ru1EO^AFN^;oMb_BJA09QK=fB;_uftcas6ke^Y&54dHaD1BZ1Tf zV}8(?As%h{c*Y0_N1pmwvjiV;D63h1D4~J6L1NEKddE_)oFueNMdPojV;o-;hXZrD zO-`CyK&-KUjLL_{>N49EEqk}{3!{2WRuel024MD&4J$m8zP8Qve7+~P9D0+3>o(-| zJykUFk!FHHe8>Dd&2N(Y?g%f|y7njN5&=vT9e-?L%KuUzlI!VXa!rNWUlLe`~y;>EF+d+Z1no+hl;JjwP>ZOanGmD&bm6 znMsv#bb$%$FrY?S!tOA*u7{n0x48jDwb-KNoJF zgYt`EEK&`%E@!7=K(og$e}Ln(-TXC5C=kS{L$f-efjw5LkTi}C%E3A0Z8VSS`lBK% z@Np#z<9_(k+GDA%y74_2E`Ya=*=)wLtq1<(NNv`mU2Ex?#(x{xGiF~RLFfF3_MyP! z?6}Ra3?RnhLYWxQ(qT)8{!Y}CEQu~}Hea$8#S+90s%YE%mFOXd^thex{YNeh-DxX62MYe)!f;FiJ)~gl(@p#NH~%3y(7vaRrbX*H$=`(y#l-){ ztb$aQuGg%phhSIf zf<6jNH2=+jICcrzF*ay+FC9fCvQlz$<9F`3wB<5wAzi*bYecjz-#oCJ$WMp&gVhrt z0d+)91aCt0)71GQtT>u{`yHK~_otH?ZV#jDlL?`{t3(_MH)#wvTgt(g=XnQ#nDFGo zyMvHB@#z|K-uqOI3%}N~PESwU=LJers`Y*0>q#R#;I1pxO4=yjbA7lzoeu~0@`YUZvWrI--)gw!j`k;OnPo} zLej|;x#)89wd>Nnf@7G}&QKZIuZ`(4W*rDC2M=t}X!A?7tGSJ*=mrYP4sYD6Gk+=O z{a>y6ws~zQQ>p?By&j%7e!;;~w;;TT20*~H)YQ}^hlitr6O4j_B+bpu7bE7k;n0FW zVvw%3gIEMZIBXgQ{*89U@E^1uNQrJ_3);1u79Tg5c8erG2DvKUvH|9QNftt}mj@E4 zUcG)z%rTFJQeIbAH^Be#M{|?XGw&!vCc?Z^yU9j*IYsd+cf+WB0CzaQXJ&BOjya(t@+&dlD3!d-F1{3xtDr>3mW1+f(d(T=`%|nZ_tRKIZuYkt=suRaM-xeQDBK5az7G{umGW zc?Hxmc+GhPHN-kE4mgiXpkWF&`6r&v6Zw+9m%lTgM?LZMJh?x)HAE01sNsLCVe?i3 zL@3tZYaf(ozTR&|T&yL_JC6Mn#eMG~9Iq>&5pEOfLCs>Jg6hXfAAS6o2m_zeLglV% z-&4y^^yPppV7>d$R1m#z6rr9Vuqnn4mmPB$s^??M`fc&3x=^<|`r+%7I6_UP7c=4u z=W*+ncqkngGtq*g+dZkp=ux3uqy?=++270V_uo_AOaBU3P4h=q;pi(H)nZ+la*>KL(GeE-z#D z?Z@|Tp|>$mtJWGM%-*=<&q>Z@5ISQPfPc_-oJ8u}JDJB�M}w)fj3bA|kUHi9ch~ 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

0lMKZW$PG;}MOT!&uRTnk@bUydL^u*_`Ect`dN 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 literal 0 HcmV?d00001 diff --git a/docs/asciidoc/query-dsl-usage.asciidoc b/docs/asciidoc/query-dsl-usage.asciidoc new file mode 100644 index 00000000000..8d752caf371 --- /dev/null +++ b/docs/asciidoc/query-dsl-usage.asciidoc @@ -0,0 +1,134 @@ +:includes-from-dirs: query-dsl/compound,query-dsl/full-text,query-dsl/geo,query-dsl/joining,query-dsl/nest-specific,query-dsl/span,query-dsl/specialized,query-dsl/term-level + +include::../../docs/asciidoc/query-dsl/compound/and/and-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/compound/bool/bool-dsl-complex-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/compound/bool/bool-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/compound/boosting/boosting-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/compound/constant-score/constant-score-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/compound/dismax/dismax-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/compound/filtered/filtered-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/compound/function-score/function-score-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/compound/indices/indices-no-match-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/compound/indices/indices-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/compound/limit/limit-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/compound/not/not-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/compound/or/or-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/full-text/common-terms/common-terms-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/full-text/match/match-phrase-prefix-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/full-text/match/match-phrase-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/full-text/match/match-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/full-text/multi-match/multi-match-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/full-text/query-string/query-string-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/full-text/simple-query-string/simple-query-string-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/bounding-box/geo-bounding-box-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/distance/geo-distance-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/distance-range/geo-distance-range-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/hash-cell/geo-hash-cell-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/polygon/geo-polygon-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/shape/circle/geo-shape-circle-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/shape/envelope/geo-envelope-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/shape/indexed-shape/geo-indexed-shape-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/shape/line-string/geo-line-string-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/shape/multi-line-string/geo-multi-line-string-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/shape/multi-point/geo-multi-point-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/shape/point/geo-point-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/geo/shape/polygon/geo-polygon-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/joining/has-child/has-child-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/joining/has-parent/has-parent-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/joining/nested/nested-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/nest-specific/raw/raw-combine-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/nest-specific/raw/raw-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/span/container/span-containing-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/span/first/span-first-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/span/multi-term/span-multi-term-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/span/near/span-near-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/span/not/span-not-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/span/or/span-or-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/span/term/span-term-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/span/within/span-within-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/specialized/more-like-this/more-like-this-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/specialized/script/script-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/specialized/template/template-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/exists/exists-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-date-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-numeric-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/ids/ids-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/missing/missing-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/prefix/prefix-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/range/date-range-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/range/numeric-range-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/range/term-range-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/regexp/regexp-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/term/term-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/terms/terms-list-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/terms/terms-lookup-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/terms/terms-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/type/type-query-usage.asciidoc[] + +include::../../docs/asciidoc/query-dsl/term-level/wildcard/wildcard-query-usage.asciidoc[] + diff --git a/docs/asciidoc/query-dsl.asciidoc b/docs/asciidoc/query-dsl.asciidoc new file mode 100644 index 00000000000..32d3b0f824a --- /dev/null +++ b/docs/asciidoc/query-dsl.asciidoc @@ -0,0 +1,147 @@ +:output-dir: query-dsl + +[[query-dsl]] += Query DSL + +[partintro] +-- +NEST exposes all of the query DSL endpoints available in Elasticsearch + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +* <> + +-- + +include::{output-dir}/bool-dsl/bool-dsl.asciidoc[] + +include::query-dsl-usage.asciidoc[] + diff --git a/docs/asciidoc/query-dsl/bool-dsl/bool-dsl.asciidoc b/docs/asciidoc/query-dsl/bool-dsl/bool-dsl.asciidoc new file mode 100644 index 00000000000..5025932e39a --- /dev/null +++ b/docs/asciidoc/query-dsl/bool-dsl/bool-dsl.asciidoc @@ -0,0 +1,324 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[bool-queries]] +== Bool Queries + +Writing boolean queries can grow verbose rather quickly when using the query DSL. For example, +take a single {ref_current}/query-dsl-bool-query.html[bool query] with only two clauses + +[source,csharp] +---- +var searchResults = this.Client.Search(s => s + .Query(q => q + .Bool(b => b + .Should( + bs => bs.Term(p => p.Name, "x"), + bs => bs.Term(p => p.Name, "y") + ) + ) + ) +); +---- + +Now, imagine multiple nested bools; you'll realise that this quickly becomes an exercise in _hadouken indenting_ + +[[indent]] +.hadouken indenting +image::hadouken-indentation.jpg[hadouken indenting] + +=== Operator Overloading + +For this reason, NEST introduces **operator overloading** so complex bool queries become easier to write. +The previous example now becomes the following with the fluent API + +[source,csharp] +---- +var searchResults = this.Client.Search(s => s + .Query(q => q.Term(p => p.Name, "x") || q.Term(p => p.Name, "y")) +); +---- + +or, using the object initializer syntax + +[source,csharp] +---- +searchResults = this.Client.Search(new SearchRequest +{ + Query = new TermQuery { Field = "name", Value= "x" } + || new TermQuery { Field = Field(p=>p.Name), Value = "y" } +}); +---- + +A naive implementation of operator overloading would rewrite + +`term && term && term` to + +.... +bool +|___must + |___term + |___bool + |___must + |___term + |___term +.... + +As you can image this becomes unwieldy quite fast the more complex a query becomes NEST can spot these and +join them together to become a single bool query + +.... +bool +|___must + |___term + |___term + |___term +.... + +[source,csharp] +---- +Assert( + q => q.Query() && q.Query() && q.Query(), + Query && Query && Query, + c => c.Bool.Must.Should().HaveCount(3) + ); +---- + +The bool DSL offers also a short hand notation to mark a query as a `must_not` using the `!` operator + +[source,csharp] +---- +Assert(q => !q.Query(), !Query, c => c.Bool.MustNot.Should().HaveCount(1)); +---- + +And to mark a query as a `filter` using the `+` operator + +[source,csharp] +---- +Assert(q => +q.Query(), +Query, c => c.Bool.Filter.Should().HaveCount(1)); +---- + +Both of these can be combined with `&&` to form a single bool query + +[source,csharp] +---- +Assert(q => !q.Query() && !q.Query(), !Query && !Query, c => c.Bool.MustNot.Should().HaveCount(2)); +---- + +[source,csharp] +---- +Assert(q => +q.Query() && +q.Query(), +Query && +Query, c => c.Bool.Filter.Should().HaveCount(2)); +---- + +=== Combining/Merging bool queries + +When combining multiple queries some or all possibly marked as `must_not` or `filter`, NEST still combines to a single bool query + +.... +bool +|___must +| |___term +| |___term +| |___term +| +|___must_not + |___term +.... + +[source,csharp] +---- +Assert( + q => q.Query() && q.Query() && q.Query() && !q.Query(), + Query && Query && Query && !Query, + c=> + { + c.Bool.Must.Should().HaveCount(3); + c.Bool.MustNot.Should().HaveCount(1); + }); + +c.Bool.Must.Should().HaveCount(3); + +c.Bool.MustNot.Should().HaveCount(1); +---- + +Even more involved `term && term && term && !term && +term && +term` still only results in a single `bool` query: + +.... +bool +|___must +| |___term +| |___term +| |___term +| +|___must_not +| |___term +| +|___filter + |___term + |___term +.... + +[source,csharp] +---- +Assert( + q => q.Query() && q.Query() && q.Query() && !q.Query() && +q.Query() && +q.Query(), + Query && Query && Query && !Query && +Query && +Query, + c => + { + c.Bool.Must.Should().HaveCount(3); + c.Bool.MustNot.Should().HaveCount(1); + c.Bool.Filter.Should().HaveCount(2); + }); + +c.Bool.Must.Should().HaveCount(3); + +c.Bool.MustNot.Should().HaveCount(1); + +c.Bool.Filter.Should().HaveCount(2); +---- + +You can still mix and match actual bool queries with the bool DSL e.g `bool(must=term, term, term) && !term` would still merge into a single `bool` query. + +[source,csharp] +---- +Assert( + q => q.Bool(b => b.Must(mq => mq.Query(), mq => mq.Query(), mq => mq.Query())) && !q.Query(), + new BoolQuery { Must = new QueryContainer[] { Query, Query, Query } } && !Query, + c => + { + c.Bool.Must.Should().HaveCount(3); + c.Bool.MustNot.Should().HaveCount(1); + }); + +c.Bool.Must.Should().HaveCount(3); + +c.Bool.MustNot.Should().HaveCount(1); +---- + +[source,csharp] +---- +Assert( + q => q.Query() && (q.Query() || q.Query() || q.Query()), + Query && (Query || Query || Query), + c => + { + c.Bool.Must.Should().HaveCount(2); + var lastClause = c.Bool.Must.Last() as IQueryContainer; + lastClause.Should().NotBeNull(); + lastClause.Bool.Should().NotBeNull(); + lastClause.Bool.Should.Should().HaveCount(3); + }); + +c.Bool.Must.Should().HaveCount(2); + +var lastClause = c.Bool.Must.Last() as IQueryContainer; + +lastClause.Should().NotBeNull(); + +lastClause.Bool.Should().NotBeNull(); + +lastClause.Bool.Should.Should().HaveCount(3); +---- + +TIP: *add parentheses to force evaluation order* + +Also note that using shoulds as boosting factors can be really powerful so if you need this +always remember that you can mix and match an actual bool query with the bool dsl. + +There is another subtle situation where NEST will not blindly merge 2 bool queries with only should clauses. Imagine the following: + +`bool(should=term1, term2, term3, term4, minimum_should_match=2) || term5 || term6` + +if NEST identified both sides of the OR operation as only containing `should` clauses and it would +join them together it would give a different meaning to the `minimum_should_match` parameter of the first boolean query. +Rewriting this to a single bool with 5 `should` clauses would break because only matching on `term5` or `term6` should still be a hit. + +[source,csharp] +---- +Assert( + q => q.Bool(b => b + .Should(mq => mq.Query(), mq => mq.Query(), mq => mq.Query(), mq => mq.Query()) + .MinimumShouldMatch(2) + ) + || !q.Query() || q.Query(), + new BoolQuery + { + Should = new QueryContainer[] { Query, Query, Query, Query }, + MinimumShouldMatch = 2 + } || !Query || Query, + c => + { + c.Bool.Should.Should().HaveCount(3); + var nestedBool = c.Bool.Should.First() as IQueryContainer; + nestedBool.Bool.Should.Should().HaveCount(4); + }); + +c.Bool.Should.Should().HaveCount(3); + +var nestedBool = c.Bool.Should.First() as IQueryContainer; + +nestedBool.Bool.Should.Should().HaveCount(4); +---- + +=== Locked bool queries + +NEST will not combine `bool` queries if any of the query metadata is set e.g if metadata such as `boost` or `name` are set, +NEST will treat these as locked + +Here we demonstrate that two locked `bool` queries are not combined + +[source,csharp] +---- +Assert( + q => q.Bool(b => b.Name("leftBool").Should(mq => mq.Query())) + || q.Bool(b => b.Name("rightBool").Should(mq => mq.Query())), + new BoolQuery { Name = "leftBool", Should = new QueryContainer[] { Query } } + || new BoolQuery { Name = "rightBool", Should = new QueryContainer[] { Query } }, + c => AssertDoesNotJoinOntoLockedBool(c, "leftBool")); +---- + +neither are two `bool` queries where either right query is locked + +[source,csharp] +---- +Assert( + q => q.Bool(b => b.Should(mq => mq.Query())) + || q.Bool(b => b.Name("rightBool").Should(mq => mq.Query())), + new BoolQuery { Should = new QueryContainer[] { Query } } + || new BoolQuery { Name = "rightBool", Should = new QueryContainer[] { Query } }, + c => AssertDoesNotJoinOntoLockedBool(c, "rightBool")); +---- + +or the left query is locked + +[source,csharp] +---- +Assert( + q => q.Bool(b => b.Name("leftBool").Should(mq => mq.Query())) + || q.Bool(b => b.Should(mq => mq.Query())), + new BoolQuery { Name = "leftBool", Should = new QueryContainer[] { Query } } + || new BoolQuery { Should = new QueryContainer[] { Query } }, + c => AssertDoesNotJoinOntoLockedBool(c, "leftBool")); +---- + +[source,csharp] +---- +c.Bool.Should.Should().HaveCount(2); + +var nestedBool = c.Bool.Should.Cast().First(b=>!string.IsNullOrEmpty(b.Bool?.Name)); + +nestedBool.Bool.Should.Should().HaveCount(1); + +nestedBool.Bool.Name.Should().Be(firstName); +---- + +[source,csharp] +---- +assert(fluent.InvokeQuery(new QueryContainerDescriptor())); + +assert((QueryContainer)ois); +---- + diff --git a/docs/asciidoc/query-dsl/bool-dsl/hadouken-indentation.jpg b/docs/asciidoc/query-dsl/bool-dsl/hadouken-indentation.jpg new file mode 100644 index 0000000000000000000000000000000000000000..afe03b960d1203610f217e1045a22142c0acc720 GIT binary patch literal 43939 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 literal 0 HcmV?d00001 diff --git a/docs/asciidoc/query-dsl/bool-dsl/operators/and-operator-usage.asciidoc b/docs/asciidoc/query-dsl/bool-dsl/operators/and-operator-usage.asciidoc new file mode 100644 index 00000000000..26f7525fe1e --- /dev/null +++ b/docs/asciidoc/query-dsl/bool-dsl/operators/and-operator-usage.asciidoc @@ -0,0 +1,96 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[and-operator-usage]] +== And Operator Usage + +[source,csharp] +---- +var lotsOfAnds = Enumerable.Range(0, 100).Aggregate(new QueryContainer(), (q, c) => q && Query, q => q); + +LotsOfAnds(lotsOfAnds); +---- + +[source,csharp] +---- +QueryContainer container = null; + +container &= Query; + +LotsOfAnds(container); +---- + +[source,csharp] +---- +var container = new QueryContainer(); + +container &= Query; + +LotsOfAnds(container); +---- + +[source,csharp] +---- +lotsOfAnds.Should().NotBeNull(); + +lotsOfAnds.Bool.Should().NotBeNull(); + +lotsOfAnds.Bool.Must.Should().NotBeEmpty().And.HaveCount(100); +---- + +[source,csharp] +---- +ReturnsBool(Query && Query, q => q.Query() && q.Query(), b => +{ + b.Must.Should().NotBeEmpty().And.HaveCount(2); + b.Should.Should().BeNull(); + b.MustNot.Should().BeNull(); + b.Filter.Should().BeNull(); +}); +b.Must.Should().NotBeEmpty().And.HaveCount(2); +b.Should.Should().BeNull(); +b.MustNot.Should().BeNull(); +b.Filter.Should().BeNull(); +ReturnsBool(Query && Query && ConditionlessQuery, q => q.Query() && q.Query() && q.ConditionlessQuery(), b => +{ + b.Must.Should().NotBeEmpty().And.HaveCount(2); + b.Should.Should().BeNull(); + b.MustNot.Should().BeNull(); + b.Filter.Should().BeNull(); +}); +b.Must.Should().NotBeEmpty().And.HaveCount(2); +b.Should.Should().BeNull(); +b.MustNot.Should().BeNull(); +b.Filter.Should().BeNull(); +ReturnsSingleQuery(Query && ConditionlessQuery, q => q.Query() && q.ConditionlessQuery(), + c => c.Term.Value.Should().NotBeNull()); +ReturnsSingleQuery(ConditionlessQuery && Query, q => q.ConditionlessQuery() && q.Query(), + c => c.Term.Value.Should().NotBeNull()); +ReturnsSingleQuery(Query && NullQuery, q => q.Query() && q.NullQuery(), + c => c.Term.Value.Should().NotBeNull()); +ReturnsSingleQuery(NullQuery && Query, q=> q.NullQuery() && q.Query(), + c => c.Term.Value.Should().NotBeNull()); +ReturnsSingleQuery(ConditionlessQuery && ConditionlessQuery && ConditionlessQuery && Query, + q => q.ConditionlessQuery() && q.ConditionlessQuery() && q.ConditionlessQuery() && q.Query(), + c => c.Term.Value.Should().NotBeNull()); +ReturnsSingleQuery( + NullQuery && NullQuery && ConditionlessQuery && Query, + q=>q.NullQuery() && q.NullQuery() && q.ConditionlessQuery() && q.Query(), + c => c.Term.Value.Should().NotBeNull()); +ReturnsNull(NullQuery && ConditionlessQuery, q=> q.NullQuery() && q.ConditionlessQuery()); +ReturnsNull(ConditionlessQuery && NullQuery, q=>q.ConditionlessQuery() && q.NullQuery()); +ReturnsNull(ConditionlessQuery && ConditionlessQuery, q=>q.ConditionlessQuery() && q.ConditionlessQuery()); +ReturnsNull( + ConditionlessQuery && ConditionlessQuery && ConditionlessQuery && ConditionlessQuery, + q=>q.ConditionlessQuery() && q.ConditionlessQuery() && q.ConditionlessQuery() && q.ConditionlessQuery() + +); +ReturnsNull( + NullQuery && ConditionlessQuery && ConditionlessQuery && ConditionlessQuery, + q=>q.NullQuery() && q.ConditionlessQuery() && q.ConditionlessQuery() && q.ConditionlessQuery() +); +---- + diff --git a/docs/asciidoc/query-dsl/bool-dsl/operators/not-operator-usage.asciidoc b/docs/asciidoc/query-dsl/bool-dsl/operators/not-operator-usage.asciidoc new file mode 100644 index 00000000000..141d422d7f0 --- /dev/null +++ b/docs/asciidoc/query-dsl/bool-dsl/operators/not-operator-usage.asciidoc @@ -0,0 +1,103 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[not-operator-usage]] +== Not Operator Usage + +[source,csharp] +---- +var lotsOfNots = Enumerable.Range(0, 100).Aggregate(new QueryContainer(), (q, c) => q || Query, q => q); + +LotsOfNots(lotsOfNots); +---- + +[source,csharp] +---- +QueryContainer container = null; + +container |= Query; + +LotsOfNots(container); +---- + +[source,csharp] +---- +var container = new QueryContainer(); + +container |= Query; + +LotsOfNots(container); +---- + +[source,csharp] +---- +lotsOfNots.Should().NotBeNull(); + +lotsOfNots.Bool.Should().NotBeNull(); + +lotsOfNots.Bool.Should.Should().NotBeEmpty().And.HaveCount(100); +---- + +[source,csharp] +---- +ReturnsBool(!Query && !Query, q => !q.Query() && !q.Query(), b => +{ + b.MustNot.Should().NotBeEmpty().And.HaveCount(2); + b.Must.Should().BeNull(); + b.Should.Should().BeNull(); + b.Filter.Should().BeNull(); +}); +b.MustNot.Should().NotBeEmpty().And.HaveCount(2); +b.Must.Should().BeNull(); +b.Should.Should().BeNull(); +b.Filter.Should().BeNull(); +ReturnsBool(!Query || !Query || !ConditionlessQuery, q => !q.Query() || !q.Query() || !q.ConditionlessQuery(), b => +{ + b.Should.Should().NotBeEmpty().And.HaveCount(2); + b.Must.Should().BeNull(); + b.MustNot.Should().BeNull(); + b.Filter.Should().BeNull(); + foreach (IQueryContainer q in b.Should) + { + q.Bool.Should().NotBeNull(); + q.Bool.MustNot.Should().NotBeEmpty().And.HaveCount(1); + } +}); +b.Should.Should().NotBeEmpty().And.HaveCount(2); +b.Must.Should().BeNull(); +b.MustNot.Should().BeNull(); +b.Filter.Should().BeNull(); +q.Bool.Should().NotBeNull(); +q.Bool.MustNot.Should().NotBeEmpty().And.HaveCount(1); +ReturnsSingleQuery(!Query || !ConditionlessQuery, q => !q.Query() || !q.ConditionlessQuery(), + c => c.Bool.MustNot.Should().NotBeNull().And.HaveCount(1)); +ReturnsSingleQuery(!ConditionlessQuery || !Query, q => !q.ConditionlessQuery() || !q.Query(), + c => c.Bool.MustNot.Should().NotBeNull().And.HaveCount(1)); +ReturnsSingleQuery(!Query || !NullQuery, q => !q.Query() || !q.NullQuery(), + c => c.Bool.MustNot.Should().NotBeNull().And.HaveCount(1)); +ReturnsSingleQuery(!NullQuery && !Query, q => !q.NullQuery() && !q.Query(), + c => c.Bool.MustNot.Should().NotBeNull().And.HaveCount(1)); +ReturnsSingleQuery(!ConditionlessQuery || !ConditionlessQuery && !ConditionlessQuery || !Query, + q => !q.ConditionlessQuery() || !q.ConditionlessQuery() && !q.ConditionlessQuery() || !q.Query(), + c => c.Bool.MustNot.Should().NotBeNull().And.HaveCount(1)); +ReturnsSingleQuery( + !NullQuery || !NullQuery || !ConditionlessQuery || !Query, + q => !q.NullQuery() || !q.NullQuery() || !q.ConditionlessQuery() || !q.Query(), + c => c.Bool.MustNot.Should().NotBeNull()); +ReturnsNull(!NullQuery || !ConditionlessQuery, q => !q.NullQuery() || !q.ConditionlessQuery()); +ReturnsNull(!ConditionlessQuery && !NullQuery, q => !q.ConditionlessQuery() && !q.NullQuery()); +ReturnsNull(!ConditionlessQuery || !ConditionlessQuery, q => !q.ConditionlessQuery() || !q.ConditionlessQuery()); +ReturnsNull( + !ConditionlessQuery || !ConditionlessQuery || !ConditionlessQuery || !ConditionlessQuery, + q => !q.ConditionlessQuery() || !q.ConditionlessQuery() || !q.ConditionlessQuery() || !q.ConditionlessQuery() + +); +ReturnsNull( + !NullQuery || !ConditionlessQuery || !ConditionlessQuery || !ConditionlessQuery, + q => !q.NullQuery() || !q.ConditionlessQuery() || !q.ConditionlessQuery() || !q.ConditionlessQuery() +); +---- + diff --git a/docs/asciidoc/query-dsl/bool-dsl/operators/or-operator-usage.asciidoc b/docs/asciidoc/query-dsl/bool-dsl/operators/or-operator-usage.asciidoc new file mode 100644 index 00000000000..db7a5e887ff --- /dev/null +++ b/docs/asciidoc/query-dsl/bool-dsl/operators/or-operator-usage.asciidoc @@ -0,0 +1,107 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[or-operator-usage]] +== Or Operator Usage + +[source,csharp] +---- +var lotsOfOrs = Enumerable.Range(0, 100).Aggregate(new QueryContainer(), (q, c) => q || Query, q => q); + +LotsOfOrs(lotsOfOrs); +---- + +[source,csharp] +---- +QueryContainer container = null; + +container |= Query; + +LotsOfOrs(container); +---- + +[source,csharp] +---- +var container = new QueryContainer(); + +container |= Query; + +LotsOfOrs(container); +---- + +[source,csharp] +---- +lotsOfOrs.Should().NotBeNull(); + +lotsOfOrs.Bool.Should().NotBeNull(); + +lotsOfOrs.Bool.Should.Should().NotBeEmpty().And.HaveCount(100); +---- + +[source,csharp] +---- +ReturnsBool(Query || Query, q => q.Query() || q.Query(), b => +{ + b.Should.Should().NotBeEmpty().And.HaveCount(2); + b.Must.Should().BeNull(); + b.MustNot.Should().BeNull(); + b.Filter.Should().BeNull(); +}); +b.Should.Should().NotBeEmpty().And.HaveCount(2); +b.Must.Should().BeNull(); +b.MustNot.Should().BeNull(); +b.Filter.Should().BeNull(); +ReturnsBool(Query || Query || ConditionlessQuery, q => q.Query() || q.Query() || q.ConditionlessQuery(), b => +{ + b.Should.Should().NotBeEmpty().And.HaveCount(2); + b.Must.Should().BeNull(); + b.MustNot.Should().BeNull(); + b.Filter.Should().BeNull(); +}); +b.Should.Should().NotBeEmpty().And.HaveCount(2); +b.Must.Should().BeNull(); +b.MustNot.Should().BeNull(); +b.Filter.Should().BeNull(); +ReturnsBool(Query || Query || ConditionlessQuery, q => q.Query() || q.Query() || q.ConditionlessQuery(), b => +{ + b.Should.Should().NotBeEmpty().And.HaveCount(2); + b.Must.Should().BeNull(); + b.MustNot.Should().BeNull(); + b.Filter.Should().BeNull(); +}); +b.Should.Should().NotBeEmpty().And.HaveCount(2); +b.Must.Should().BeNull(); +b.MustNot.Should().BeNull(); +b.Filter.Should().BeNull(); +ReturnsSingleQuery(Query || ConditionlessQuery, q => q.Query() || q.ConditionlessQuery(), + c => c.Term.Value.Should().NotBeNull()); +ReturnsSingleQuery(ConditionlessQuery || Query, q => q.ConditionlessQuery() || q.Query(), + c => c.Term.Value.Should().NotBeNull()); +ReturnsSingleQuery(Query || NullQuery, q => q.Query() || q.NullQuery(), + c => c.Term.Value.Should().NotBeNull()); +ReturnsSingleQuery(NullQuery || Query, q=> q.NullQuery() || q.Query(), + c => c.Term.Value.Should().NotBeNull()); +ReturnsSingleQuery(ConditionlessQuery || ConditionlessQuery || ConditionlessQuery || Query, + q => q.ConditionlessQuery() || q.ConditionlessQuery() || q.ConditionlessQuery() || q.Query(), + c => c.Term.Value.Should().NotBeNull()); +ReturnsSingleQuery( + NullQuery || NullQuery || ConditionlessQuery || Query, + q=>q.NullQuery() || q.NullQuery() || q.ConditionlessQuery() || q.Query(), + c => c.Term.Value.Should().NotBeNull()); +ReturnsNull(NullQuery || ConditionlessQuery, q=> q.NullQuery() || q.ConditionlessQuery()); +ReturnsNull(ConditionlessQuery || NullQuery, q=>q.ConditionlessQuery() || q.NullQuery()); +ReturnsNull(ConditionlessQuery || ConditionlessQuery, q=>q.ConditionlessQuery() || q.ConditionlessQuery()); +ReturnsNull( + ConditionlessQuery || ConditionlessQuery || ConditionlessQuery || ConditionlessQuery, + q=>q.ConditionlessQuery() || q.ConditionlessQuery() || q.ConditionlessQuery() || q.ConditionlessQuery() + +); +ReturnsNull( + NullQuery || ConditionlessQuery || ConditionlessQuery || ConditionlessQuery, + q=>q.NullQuery() || q.ConditionlessQuery() || q.ConditionlessQuery() || q.ConditionlessQuery() +); +---- + diff --git a/docs/asciidoc/query-dsl/bool-dsl/operators/unary-add-operator-usage.asciidoc b/docs/asciidoc/query-dsl/bool-dsl/operators/unary-add-operator-usage.asciidoc new file mode 100644 index 00000000000..7f8ac00ce55 --- /dev/null +++ b/docs/asciidoc/query-dsl/bool-dsl/operators/unary-add-operator-usage.asciidoc @@ -0,0 +1,114 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[unary-add-operator-usage]] +== Unary Add Operator Usage + +[source,csharp] +---- +var lotsOfUnaryAdds = Enumerable.Range(0, 100).Aggregate(new QueryContainer(), (q, c) => q && +Query, q => q); + +LotsOfUnaryAdds(lotsOfUnaryAdds); +---- + +[source,csharp] +---- +QueryContainer container = null; + +container &= +Query; + +LotsOfUnaryAdds(container); +---- + +[source,csharp] +---- +var container = new QueryContainer(); + +container &= +Query; + +LotsOfUnaryAdds(container); +---- + +[source,csharp] +---- +lotsOfUnaryAdds.Should().NotBeNull(); + +lotsOfUnaryAdds.Bool.Should().NotBeNull(); + +lotsOfUnaryAdds.Bool.Filter.Should().NotBeEmpty().And.HaveCount(100); +---- + +[source,csharp] +---- +var container = new QueryContainer(); + +container |= +Query; + +var c = container as IQueryContainer; + +c.Bool.Should.Should().NotBeEmpty().And.HaveCount(100); +---- + +[source,csharp] +---- +ReturnsBool(+Query && +Query, q => +q.Query() && +q.Query(), b => +{ + b.Filter.Should().NotBeEmpty().And.HaveCount(2); + b.Must.Should().BeNull(); + b.Should.Should().BeNull(); + b.MustNot.Should().BeNull(); +}); +b.Filter.Should().NotBeEmpty().And.HaveCount(2); +b.Must.Should().BeNull(); +b.Should.Should().BeNull(); +b.MustNot.Should().BeNull(); +ReturnsBool(+Query || +Query || +ConditionlessQuery, q => +q.Query() || +q.Query() || +q.ConditionlessQuery(), b => +{ + b.Should.Should().NotBeEmpty().And.HaveCount(2); + b.Must.Should().BeNull(); + b.MustNot.Should().BeNull(); + b.MustNot.Should().BeNull(); + foreach (IQueryContainer q in b.Should) + { + q.Bool.Should().NotBeNull(); + q.Bool.Filter.Should().NotBeEmpty().And.HaveCount(1); + } +}); +b.Should.Should().NotBeEmpty().And.HaveCount(2); +b.Must.Should().BeNull(); +b.MustNot.Should().BeNull(); +b.MustNot.Should().BeNull(); +q.Bool.Should().NotBeNull(); +q.Bool.Filter.Should().NotBeEmpty().And.HaveCount(1); +ReturnsSingleQuery(+Query || +ConditionlessQuery, q => +q.Query() || +q.ConditionlessQuery(), + c => c.Bool.Filter.Should().NotBeNull().And.HaveCount(1)); +ReturnsSingleQuery(+ConditionlessQuery || +Query, q => +q.ConditionlessQuery() || +q.Query(), + c => c.Bool.Filter.Should().NotBeNull().And.HaveCount(1)); +ReturnsSingleQuery(+Query || +NullQuery, q => +q.Query() || +q.NullQuery(), + c => c.Bool.Filter.Should().NotBeNull().And.HaveCount(1)); +ReturnsSingleQuery(+NullQuery && +Query, q => +q.NullQuery() && +q.Query(), + c => c.Bool.Filter.Should().NotBeNull().And.HaveCount(1)); +ReturnsSingleQuery(+ConditionlessQuery || +ConditionlessQuery && +ConditionlessQuery || +Query, + q => +q.ConditionlessQuery() || +q.ConditionlessQuery() && +q.ConditionlessQuery() || +q.Query(), + c => c.Bool.Filter.Should().NotBeNull().And.HaveCount(1)); +ReturnsSingleQuery( + +NullQuery || +NullQuery || +ConditionlessQuery || +Query, + q => +q.NullQuery() || +q.NullQuery() || +q.ConditionlessQuery() || +q.Query(), + c => c.Bool.Filter.Should().NotBeNull()); +ReturnsNull(+NullQuery || +ConditionlessQuery, q => +q.NullQuery() || +q.ConditionlessQuery()); +ReturnsNull(+ConditionlessQuery && +NullQuery, q => +q.ConditionlessQuery() && +q.NullQuery()); +ReturnsNull(+ConditionlessQuery || +ConditionlessQuery, q => +q.ConditionlessQuery() || +q.ConditionlessQuery()); +ReturnsNull( + +ConditionlessQuery || +ConditionlessQuery || +ConditionlessQuery || +ConditionlessQuery, + q => +q.ConditionlessQuery() || +q.ConditionlessQuery() || +q.ConditionlessQuery() || +q.ConditionlessQuery() + +); +ReturnsNull( + +NullQuery || +ConditionlessQuery || +ConditionlessQuery || +ConditionlessQuery, + q => +q.NullQuery() || +q.ConditionlessQuery() || +q.ConditionlessQuery() || +q.ConditionlessQuery() +); +---- + diff --git a/docs/asciidoc/query-dsl/compound/and/and-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/and/and-query-usage.asciidoc new file mode 100644 index 00000000000..936a2d2d602 --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/and/and-query-usage.asciidoc @@ -0,0 +1,62 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[and-query-usage]] +== And Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.And(c => c + .Name("named_query") + .Boost(1.1) + .Filters( + qq => qq.MatchAll(m => m.Name("query1")), + qq => qq.MatchAll(m => m.Name("query2")) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new AndQuery() +{ + Name = "named_query", + Boost = 1.1, + Filters = new QueryContainer[] { + new MatchAllQuery() { Name = "query1" }, + new MatchAllQuery() { Name = "query2" }, + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "and": { + "_name": "named_query", + "boost": 1.1, + "filters": [ + { + "match_all": { + "_name": "query1" + } + }, + { + "match_all": { + "_name": "query2" + } + } + ] + } +} +---- + diff --git a/docs/asciidoc/query-dsl/compound/bool/bool-dsl-complex-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/bool/bool-dsl-complex-query-usage.asciidoc new file mode 100644 index 00000000000..3989690781a --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/bool/bool-dsl-complex-query-usage.asciidoc @@ -0,0 +1,233 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[bool-dsl-complex-query-usage]] +== Bool Dsl Complex Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q.Query() && q.Query() +//second bool +|| ( + //complex nested bool + (+q.Query() || +q.Query() || !q.Query() && (!q.Query() && !q.ConditionlessQuery())) + // simple nested or + && (q.Query() || q.Query() || q.Query()) + //all conditionless bool + && (q.NullQuery() || +q.ConditionlessQuery() || !q.ConditionlessQuery()) + // actual bool query + && (base.QueryFluent(q))) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +Query && Query +//second bool +|| ( + //complex nested bool + (+Query || +Query || !Query && (!Query && !ConditionlessQuery)) + // simple nested or + && (Query || Query || Query) + //all conditionless bool + && (NullQuery || +ConditionlessQuery || !ConditionlessQuery) + // actual bool query + && (base.QueryInitializer)) +---- + +[source,javascript] +.Example json output +---- +{ + "bool": { + "should": [ + { + "bool": { + "must": [ + { + "term": { + "x": { + "value": "y" + } + } + }, + { + "term": { + "x": { + "value": "y" + } + } + } + ] + } + }, + { + "bool": { + "must": [ + { + "bool": { + "should": [ + { + "bool": { + "filter": [ + { + "term": { + "x": { + "value": "y" + } + } + } + ] + } + }, + { + "bool": { + "filter": [ + { + "term": { + "x": { + "value": "y" + } + } + } + ] + } + }, + { + "bool": { + "must_not": [ + { + "term": { + "x": { + "value": "y" + } + } + }, + { + "term": { + "x": { + "value": "y" + } + } + } + ] + } + }, + { + "term": { + "x": { + "value": "y" + } + } + }, + { + "term": { + "x": { + "value": "y" + } + } + }, + { + "term": { + "x": { + "value": "y" + } + } + } + ] + } + }, + { + "bool": { + "must": [ + { + "match_all": {} + } + ], + "must_not": [ + { + "match_all": {} + } + ], + "should": [ + { + "match_all": {} + } + ], + "filter": [ + { + "match_all": {} + } + ], + "minimum_should_match": 1, + "boost": 2.0 + } + } + ] + } + } + ] + } +} +---- + +[source,csharp] +---- +container.Bool.Should().NotBeNull(); + +container.Bool.Should.Should().HaveCount(2); + +container.Bool.MustNot.Should().BeNull(); + +container.Bool.Filter.Should().BeNull(); + +container.Bool.Must.Should().BeNull(); + +var firstBool = (container.Bool.Should.First() as IQueryContainer)?.Bool; + +firstBool.Should().NotBeNull(); + +firstBool.Must.Should().HaveCount(2); + +firstBool.MustNot.Should().BeNull(); + +firstBool.Filter.Should().BeNull(); + +firstBool.Should.Should().BeNull(); + +var secondBool = (container.Bool.Should.Last() as IQueryContainer)?.Bool; + +secondBool.Should().NotBeNull(); + +secondBool.Must.Should().HaveCount(2); //the last bool query was all conditionless + +secondBool.MustNot.Should().BeNull(); + +secondBool.Filter.Should().BeNull(); + +secondBool.Should.Should().BeNull(); + +var complexBool = (secondBool.Must.First() as IQueryContainer)?.Bool; + +complexBool.Should().NotBeNull(); + +complexBool.Should.Should().HaveCount(6); + +var mustNotsBool = (complexBool.Should.Cast().FirstOrDefault(q => q.Bool != null && q.Bool.MustNot != null))?.Bool; + +mustNotsBool.Should().NotBeNull(); + +mustNotsBool.MustNot.Should().HaveCount(2); //one of the three must nots was conditionless +---- + +[source,csharp] +---- +this.AssertShape(this.QueryInitializer); +---- + diff --git a/docs/asciidoc/query-dsl/compound/bool/bool-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/bool/bool-query-usage.asciidoc new file mode 100644 index 00000000000..6fd39a3da8b --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/bool/bool-query-usage.asciidoc @@ -0,0 +1,69 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[bool-query-usage]] +== Bool Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Bool(b => b + .MustNot(m => m.MatchAll()) + .Should(m => m.MatchAll()) + .Must(m => m.MatchAll()) + .Filter(f => f.MatchAll()) + .MinimumShouldMatch(1) + .Boost(2)) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new BoolQuery() +{ + MustNot = new QueryContainer[] { new MatchAllQuery() }, + Should = new QueryContainer[] { new MatchAllQuery() }, + Must = new QueryContainer[] { new MatchAllQuery() }, + Filter = new QueryContainer[] { new MatchAllQuery() }, + MinimumShouldMatch = 1, + Boost = 2 +} +---- + +[source,javascript] +.Example json output +---- +{ + "bool": { + "must": [ + { + "match_all": {} + } + ], + "must_not": [ + { + "match_all": {} + } + ], + "should": [ + { + "match_all": {} + } + ], + "filter": [ + { + "match_all": {} + } + ], + "minimum_should_match": 1, + "boost": 2.0 + } +} +---- + diff --git a/docs/asciidoc/query-dsl/compound/boosting/boosting-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/boosting/boosting-query-usage.asciidoc new file mode 100644 index 00000000000..8f9932976ef --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/boosting/boosting-query-usage.asciidoc @@ -0,0 +1,59 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[boosting-query-usage]] +== Boosting Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Boosting(c => c + .Name("named_query") + .Boost(1.1) + .Positive(qq => qq.MatchAll(m => m.Name("filter"))) + .Negative(qq => qq.MatchAll(m => m.Name("query"))) + .NegativeBoost(1.12) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new BoostingQuery() +{ + Name = "named_query", + Boost = 1.1, + PositiveQuery = new MatchAllQuery { Name ="filter" }, + NegativeQuery= new MatchAllQuery() { Name = "query" }, + NegativeBoost = 1.12 +} +---- + +[source,javascript] +.Example json output +---- +{ + "boosting": { + "_name": "named_query", + "boost": 1.1, + "negative": { + "match_all": { + "_name": "query" + } + }, + "negative_boost": 1.12, + "positive": { + "match_all": { + "_name": "filter" + } + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/compound/constant-score/constant-score-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/constant-score/constant-score-query-usage.asciidoc new file mode 100644 index 00000000000..e06551baddb --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/constant-score/constant-score-query-usage.asciidoc @@ -0,0 +1,49 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[constant-score-query-usage]] +== Constant Score Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.ConstantScore(c => c + .Name("named_query") + .Boost(1.1) + .Filter(qq => qq.MatchAll(m => m.Name("filter"))) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new ConstantScoreQuery() +{ + Name = "named_query", + Boost = 1.1, + Filter = new MatchAllQuery { Name = "filter" }, +} +---- + +[source,javascript] +.Example json output +---- +{ + "constant_score": { + "_name": "named_query", + "boost": 1.1, + "filter": { + "match_all": { + "_name": "filter" + } + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/compound/dismax/dismax-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/dismax/dismax-query-usage.asciidoc new file mode 100644 index 00000000000..54466b1a0cb --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/dismax/dismax-query-usage.asciidoc @@ -0,0 +1,65 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[dismax-query-usage]] +== Dismax Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.DisMax(c => c + .Name("named_query") + .Boost(1.1) + .TieBreaker(1.11) + .Queries( + qq => qq.MatchAll(m => m.Name("query1")), + qq => qq.MatchAll(m => m.Name("query2")) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new DisMaxQuery() +{ + Name = "named_query", + Boost = 1.1, + TieBreaker = 1.11, + Queries = new QueryContainer[] { + new MatchAllQuery() { Name = "query1" }, + new MatchAllQuery() { Name = "query2" }, + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "dis_max": { + "_name": "named_query", + "boost": 1.1, + "queries": [ + { + "match_all": { + "_name": "query1" + } + }, + { + "match_all": { + "_name": "query2" + } + } + ], + "tie_breaker": 1.11 + } +} +---- + diff --git a/docs/asciidoc/query-dsl/compound/filtered/filtered-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/filtered/filtered-query-usage.asciidoc new file mode 100644 index 00000000000..73c5c33fc59 --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/filtered/filtered-query-usage.asciidoc @@ -0,0 +1,56 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[filtered-query-usage]] +== Filtered Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Filtered(c => c + .Name("named_query") + .Boost(1.1) + .Filter(qq => qq.MatchAll(m => m.Name("filter"))) + .Query(qq => qq.MatchAll(m => m.Name("query"))) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new FilteredQuery() +{ + Name = "named_query", + Boost = 1.1, + Filter = new MatchAllQuery { Name ="filter" }, + Query = new MatchAllQuery() { Name = "query" }, +} +---- + +[source,javascript] +.Example json output +---- +{ + "filtered": { + "_name": "named_query", + "boost": 1.1, + "filter": { + "match_all": { + "_name": "filter" + } + }, + "query": { + "match_all": { + "_name": "query" + } + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/compound/function-score/function-score-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/function-score/function-score-query-usage.asciidoc new file mode 100644 index 00000000000..af7aa2e4e3a --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/function-score/function-score-query-usage.asciidoc @@ -0,0 +1,144 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[function-score-query-usage]] +== Function Score Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.FunctionScore(c => c + .Name("named_query") + .Boost(1.1) + .Query(qq => qq.MatchAll()) + .BoostMode(FunctionBoostMode.Multiply) + .ScoreMode(FunctionScoreMode.Sum) + .MaxBoost(20.0) + .MinScore(1.0) + .Functions(f => f + .Exponential(b => b.Field(p => p.NumberOfCommits).Decay(0.5).Origin(1.0).Scale(0.1).Weight(2.1)) + .GaussDate(b => b.Field(p => p.LastActivity).Origin(DateMath.Now).Decay(0.5).Scale("1d")) + .LinearGeoLocation(b => b.Field(p => p.Location).Origin(new GeoLocation(70, -70)).Scale(Distance.Miles(1)).MultiValueMode(MultiValueMode.Average)) + .FieldValueFactor(b => b.Field("x").Factor(1.1).Missing(0.1).Modifier(FieldValueFactorModifier.Ln)) + .RandomScore(1337) + .RandomScore("randomstring") + .Weight(1.0) + .ScriptScore(ss => ss.Script(s => s.File("x"))) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new FunctionScoreQuery() +{ + Name = "named_query", + Boost = 1.1, + Query = new MatchAllQuery { }, + BoostMode = FunctionBoostMode.Multiply, + ScoreMode = FunctionScoreMode.Sum, + MaxBoost = 20.0, + MinScore = 1.0, + Functions = new List + { + new ExponentialDecayFunction { Origin = 1.0, Decay = 0.5, Field = Field(p=>p.NumberOfCommits), Scale = 0.1, Weight = 2.1 }, + new GaussDateDecayFunction { Origin = DateMath.Now, Field = Field(p=>p.LastActivity), Decay = 0.5, Scale = TimeSpan.FromDays(1) }, + new LinearGeoDecayFunction { Origin = new GeoLocation(70, -70), Field = Field(p=>p.Location), Scale = Distance.Miles(1), MultiValueMode = MultiValueMode.Average }, + new FieldValueFactorFunction + { + Field = "x", Factor = 1.1, Missing = 0.1, Modifier = FieldValueFactorModifier.Ln + }, + new RandomScoreFunction { Seed = 1337 }, + new RandomScoreFunction { Seed = "randomstring" }, + new WeightFunction { Weight = 1.0}, + new ScriptScoreFunction { Script = new ScriptQuery { File = "x" } } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "function_score": { + "_name": "named_query", + "boost": 1.1, + "boost_mode": "multiply", + "functions": [ + { + "exp": { + "numberOfCommits": { + "origin": 1.0, + "scale": 0.1, + "decay": 0.5 + } + }, + "weight": 2.1 + }, + { + "gauss": { + "lastActivity": { + "origin": "now", + "scale": "1d", + "decay": 0.5 + } + } + }, + { + "linear": { + "location": { + "origin": { + "lat": 70.0, + "lon": -70.0 + }, + "scale": "1.0mi" + }, + "multi_value_mode": "avg" + } + }, + { + "field_value_factor": { + "field": "x", + "factor": 1.1, + "missing": 0.1, + "modifier": "ln" + } + }, + { + "random_score": { + "seed": 1337 + } + }, + { + "random_score": { + "seed": "randomstring" + } + }, + { + "weight": 1.0 + }, + { + "script_score": { + "script": { + "file": "x" + } + } + } + ], + "max_boost": 20.0, + "min_score": 1.0, + "query": { + "match_all": {} + }, + "score_mode": "sum" + } +} +---- + diff --git a/docs/asciidoc/query-dsl/compound/indices/indices-no-match-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/indices/indices-no-match-query-usage.asciidoc new file mode 100644 index 00000000000..0625dd68a8f --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/indices/indices-no-match-query-usage.asciidoc @@ -0,0 +1,55 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[indices-no-match-query-usage]] +== Indices No Match Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Indices(c => c + .Name("named_query") + .Boost(1.1) + .Indices(Nest.Indices.All) + .Query(qq => qq.MatchAll()) + .NoMatchQuery(NoMatchShortcut.All) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new IndicesQuery() +{ + Name = "named_query", + Boost = 1.1, + Indices = Nest.Indices.All, + Query = new MatchAllQuery(), + NoMatchQuery = new NoMatchQueryContainer { Shortcut = NoMatchShortcut.All } +} +---- + +[source,javascript] +.Example json output +---- +{ + "indices": { + "_name": "named_query", + "boost": 1.1, + "indices": [ + "_all" + ], + "no_match_query": "all", + "query": { + "match_all": {} + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/compound/indices/indices-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/indices/indices-query-usage.asciidoc new file mode 100644 index 00000000000..18fb2e392f5 --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/indices/indices-query-usage.asciidoc @@ -0,0 +1,60 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[indices-query-usage]] +== Indices Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Indices(c => c + .Name("named_query") + .Boost(1.1) + .Indices(Index()) + .Query(qq => qq.MatchAll()) + .NoMatchQuery(qq => qq.MatchAll(m => m.Name("no_match"))) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new IndicesQuery() +{ + Name = "named_query", + Boost = 1.1, + Indices = Index(), + Query = new MatchAllQuery(), + NoMatchQuery = new MatchAllQuery { Name ="no_match" } + +} +---- + +[source,javascript] +.Example json output +---- +{ + "indices": { + "_name": "named_query", + "boost": 1.1, + "indices": [ + "project" + ], + "no_match_query": { + "match_all": { + "_name": "no_match" + } + }, + "query": { + "match_all": {} + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/compound/limit/limit-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/limit/limit-query-usage.asciidoc new file mode 100644 index 00000000000..ee23aba47d8 --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/limit/limit-query-usage.asciidoc @@ -0,0 +1,45 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[limit-query-usage]] +== Limit Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Limit(c => c + .Name("named_query") + .Boost(1.1) + .Limit(100) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new LimitQuery +{ + Name = "named_query", + Boost = 1.1, + Limit = 100 +} +---- + +[source,javascript] +.Example json output +---- +{ + "limit": { + "_name": "named_query", + "boost": 1.1, + "limit": 100 + } +} +---- + diff --git a/docs/asciidoc/query-dsl/compound/not/not-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/not/not-query-usage.asciidoc new file mode 100644 index 00000000000..efa03beffc0 --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/not/not-query-usage.asciidoc @@ -0,0 +1,62 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[not-query-usage]] +== Not Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Not(c => c + .Name("named_query") + .Boost(1.1) + .Filters( + qq => qq.MatchAll(m => m.Name("query1")), + qq => qq.MatchAll(m => m.Name("query2")) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new NotQuery() +{ + Name = "named_query", + Boost = 1.1, + Filters = new QueryContainer[] { + new MatchAllQuery() { Name = "query1" }, + new MatchAllQuery() { Name = "query2" }, + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "not": { + "_name": "named_query", + "boost": 1.1, + "filters": [ + { + "match_all": { + "_name": "query1" + } + }, + { + "match_all": { + "_name": "query2" + } + } + ] + } +} +---- + diff --git a/docs/asciidoc/query-dsl/compound/or/or-query-usage.asciidoc b/docs/asciidoc/query-dsl/compound/or/or-query-usage.asciidoc new file mode 100644 index 00000000000..202ede1811c --- /dev/null +++ b/docs/asciidoc/query-dsl/compound/or/or-query-usage.asciidoc @@ -0,0 +1,62 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[or-query-usage]] +== Or Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Or(c => c + .Name("named_query") + .Boost(1.1) + .Filters( + qq => qq.MatchAll(m => m.Name("query1")), + qq => qq.MatchAll(m => m.Name("query2")) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new OrQuery() +{ + Name = "named_query", + Boost = 1.1, + Filters = new QueryContainer[] { + new MatchAllQuery() { Name = "query1" }, + new MatchAllQuery() { Name = "query2" }, + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "or": { + "_name": "named_query", + "boost": 1.1, + "filters": [ + { + "match_all": { + "_name": "query1" + } + }, + { + "match_all": { + "_name": "query2" + } + } + ] + } +} +---- + diff --git a/docs/asciidoc/query-dsl/full-text/common-terms/common-terms-usage.asciidoc b/docs/asciidoc/query-dsl/full-text/common-terms/common-terms-usage.asciidoc new file mode 100644 index 00000000000..52f8c6fd1cb --- /dev/null +++ b/docs/asciidoc/query-dsl/full-text/common-terms/common-terms-usage.asciidoc @@ -0,0 +1,67 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[common-terms-usage]] +== Common Terms Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.CommonTerms(c => c + .Field(p => p.Description) + .Analyzer("standard") + .Boost(1.1) + .CutoffFrequency(0.001) + .DisableCoord() + .HighFrequencyOperator(Operator.And) + .LowFrequencyOperator(Operator.Or) + .MinimumShouldMatch(1) + .Name("named_query") + .Query("nelly the elephant not as a") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new CommonTermsQuery() +{ + Field = Field(p => p.Description), + Analyzer = "standard", + Boost = 1.1, + CutoffFrequency = 0.001, + DisableCoord = true, + HighFrequencyOperator = Operator.And, + LowFrequencyOperator = Operator.Or, + MinimumShouldMatch = 1, + Name = "named_query", + Query = "nelly the elephant not as a" +} +---- + +[source,javascript] +.Example json output +---- +{ + "common": { + "description": { + "_name": "named_query", + "boost": 1.1, + "query": "nelly the elephant not as a", + "cutoff_frequency": 0.001, + "low_freq_operator": "or", + "high_freq_operator": "and", + "minimum_should_match": 1, + "analyzer": "standard", + "disable_coord": true + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/full-text/match/match-phrase-prefix-usage.asciidoc b/docs/asciidoc/query-dsl/full-text/match/match-phrase-prefix-usage.asciidoc new file mode 100644 index 00000000000..cadfeba1b29 --- /dev/null +++ b/docs/asciidoc/query-dsl/full-text/match/match-phrase-prefix-usage.asciidoc @@ -0,0 +1,83 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[match-phrase-prefix-usage]] +== Match Phrase Prefix Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.MatchPhrasePrefix(c => c + .Field(p => p.Description) + .Analyzer("standard") + .Boost(1.1) + .CutoffFrequency(0.001) + .Query("hello worl") + .Fuzziness(Fuzziness.Auto) + .Lenient() + .FuzzyTranspositions() + .MaxExpansions(2) + .MinimumShouldMatch(2) + .PrefixLength(2) + .Operator(Operator.Or) + .FuzzyRewrite(RewriteMultiTerm.ConstantScoreBoolean) + .Slop(2) + .Name("named_query") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new MatchPhrasePrefixQuery +{ + Field = Field(p => p.Description), + Analyzer = "standard", + Boost = 1.1, + Name = "named_query", + CutoffFrequency = 0.001, + Query = "hello worl", + Fuzziness = Fuzziness.Auto, + FuzzyTranspositions = true, + MinimumShouldMatch = 2, + FuzzyRewrite = RewriteMultiTerm.ConstantScoreBoolean, + MaxExpansions = 2, + Slop = 2, + Lenient = true, + Operator = Operator.Or, + PrefixLength = 2 +} +---- + +[source,javascript] +.Example json output +---- +{ + "match": { + "description": { + "_name": "named_query", + "boost": 1.1, + "query": "hello worl", + "analyzer": "standard", + "fuzzy_rewrite": "constant_score_boolean", + "fuzziness": "AUTO", + "fuzzy_transpositions": true, + "cutoff_frequency": 0.001, + "prefix_length": 2, + "max_expansions": 2, + "slop": 2, + "lenient": true, + "minimum_should_match": 2, + "operator": "or", + "type": "phrase_prefix" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/full-text/match/match-phrase-usage.asciidoc b/docs/asciidoc/query-dsl/full-text/match/match-phrase-usage.asciidoc new file mode 100644 index 00000000000..850077abb54 --- /dev/null +++ b/docs/asciidoc/query-dsl/full-text/match/match-phrase-usage.asciidoc @@ -0,0 +1,83 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[match-phrase-usage]] +== Match Phrase Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.MatchPhrase(c => c + .Field(p => p.Description) + .Analyzer("standard") + .Boost(1.1) + .CutoffFrequency(0.001) + .Query("hello world") + .Fuzziness(Fuzziness.Auto) + .Lenient() + .FuzzyTranspositions() + .MaxExpansions(2) + .MinimumShouldMatch(2) + .PrefixLength(2) + .Operator(Operator.Or) + .FuzzyRewrite(RewriteMultiTerm.ConstantScoreBoolean) + .Slop(2) + .Name("named_query") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new MatchPhraseQuery +{ + Field = Field(p=>p.Description), + Analyzer = "standard", + Boost = 1.1, + Name = "named_query", + CutoffFrequency = 0.001, + Query = "hello world", + Fuzziness = Fuzziness.Auto, + FuzzyTranspositions = true, + MinimumShouldMatch = 2, + FuzzyRewrite = RewriteMultiTerm.ConstantScoreBoolean, + MaxExpansions = 2, + Slop = 2, + Lenient = true, + Operator = Operator.Or, + PrefixLength = 2 +} +---- + +[source,javascript] +.Example json output +---- +{ + "match": { + "description": { + "_name": "named_query", + "boost": 1.1, + "query": "hello world", + "analyzer": "standard", + "fuzzy_rewrite": "constant_score_boolean", + "fuzziness": "AUTO", + "fuzzy_transpositions": true, + "cutoff_frequency": 0.001, + "prefix_length": 2, + "max_expansions": 2, + "slop": 2, + "lenient": true, + "minimum_should_match": 2, + "operator": "or", + "type": "phrase" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/full-text/match/match-usage.asciidoc b/docs/asciidoc/query-dsl/full-text/match/match-usage.asciidoc new file mode 100644 index 00000000000..174b2c7f7dc --- /dev/null +++ b/docs/asciidoc/query-dsl/full-text/match/match-usage.asciidoc @@ -0,0 +1,82 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[match-usage]] +== Match Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Match(c => c + .Field(p => p.Description) + .Analyzer("standard") + .Boost(1.1) + .CutoffFrequency(0.001) + .Query("hello world") + .Fuzziness(Fuzziness.Auto) + .Lenient() + .FuzzyTranspositions() + .MaxExpansions(2) + .MinimumShouldMatch(2) + .PrefixLength(2) + .Operator(Operator.Or) + .FuzzyRewrite(RewriteMultiTerm.ConstantScoreBoolean) + .Slop(2) + .Name("named_query") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new MatchQuery +{ + Field = Field(p=>p.Description), + Analyzer = "standard", + Boost = 1.1, + Name = "named_query", + CutoffFrequency = 0.001, + Query = "hello world", + Fuzziness = Fuzziness.Auto, + FuzzyTranspositions = true, + MinimumShouldMatch = 2, + FuzzyRewrite = RewriteMultiTerm.ConstantScoreBoolean, + MaxExpansions = 2, + Slop = 2, + Lenient = true, + Operator = Operator.Or, + PrefixLength = 2 +} +---- + +[source,javascript] +.Example json output +---- +{ + "match": { + "description": { + "_name": "named_query", + "boost": 1.1, + "query": "hello world", + "analyzer": "standard", + "fuzzy_rewrite": "constant_score_boolean", + "fuzziness": "AUTO", + "fuzzy_transpositions": true, + "cutoff_frequency": 0.001, + "prefix_length": 2, + "max_expansions": 2, + "slop": 2, + "lenient": true, + "minimum_should_match": 2, + "operator": "or" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/full-text/multi-match/multi-match-usage.asciidoc b/docs/asciidoc/query-dsl/full-text/multi-match/multi-match-usage.asciidoc new file mode 100644 index 00000000000..af365d8a1a7 --- /dev/null +++ b/docs/asciidoc/query-dsl/full-text/multi-match/multi-match-usage.asciidoc @@ -0,0 +1,124 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[multi-match-usage]] +== Multi Match Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.MultiMatch(c => c + .Fields(f => f.Field(p=>p.Description).Field("myOtherField")) + .Query("hello world") + .Analyzer("standard") + .Boost(1.1) + .Slop(2) + .Fuzziness(Fuzziness.Auto) + .PrefixLength(2) + .MaxExpansions(2) + .Operator(Operator.Or) + .MinimumShouldMatch(2) + .FuzzyRewrite(RewriteMultiTerm.ConstantScoreBoolean) + .TieBreaker(1.1) + .CutoffFrequency(0.001) + .Lenient() + .ZeroTermsQuery(ZeroTermsQuery.All) + .Name("named_query") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new MultiMatchQuery +{ + Fields = Field(p=>p.Description).And("myOtherField"), + Query = "hello world", + Analyzer = "standard", + Boost = 1.1, + Slop = 2, + Fuzziness = Fuzziness.Auto, + PrefixLength = 2, + MaxExpansions = 2, + Operator = Operator.Or, + MinimumShouldMatch = 2, + FuzzyRewrite = RewriteMultiTerm.ConstantScoreBoolean, + TieBreaker = 1.1, + CutoffFrequency = 0.001, + Lenient = true, + ZeroTermsQuery = ZeroTermsQuery.All, + Name = "named_query", +} +---- + +[source,javascript] +.Example json output +---- +{ + "multi_match": { + "_name": "named_query", + "boost": 1.1, + "query": "hello world", + "analyzer": "standard", + "fuzzy_rewrite": "constant_score_boolean", + "fuzziness": "AUTO", + "cutoff_frequency": 0.001, + "prefix_length": 2, + "max_expansions": 2, + "slop": 2, + "lenient": true, + "tie_breaker": 1.1, + "minimum_should_match": 2, + "operator": "or", + "fields": [ + "description", + "myOtherField" + ], + "zero_terms_query": "all" + } +} +---- + +=== Fluent DSL Example + +[source,csharp] +---- +q +.MultiMatch(c => c + //.Fields(f => f.Field(p=>p.Description, 2.2).Field("myOtherField^0.3")) + .Fields(Field(p=>p.Description, 2.2).And("myOtherField^0.3")) + .Query("hello world") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new MultiMatchQuery +{ + Fields = Field(p=>p.Description, 2.2).And("myOtherField^0.3"), + Query = "hello world", +} +---- + +[source,javascript] +.Example json output +---- +{ + "multi_match": { + "query": "hello world", + "fields": [ + "description^2.2", + "myOtherField^0.3" + ] + } +} +---- + diff --git a/docs/asciidoc/query-dsl/full-text/query-string/query-string-usage.asciidoc b/docs/asciidoc/query-dsl/full-text/query-string/query-string-usage.asciidoc new file mode 100644 index 00000000000..165e0db6a4d --- /dev/null +++ b/docs/asciidoc/query-dsl/full-text/query-string/query-string-usage.asciidoc @@ -0,0 +1,120 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[query-string-usage]] +== Query String Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.QueryString(c => c + .Name("named_query") + .Boost(1.1) + .Fields(f => f.Field(p=>p.Description).Field("myOtherField")) + .Query("hello world") + .DefaultField(p=>p.Description) + .DefaultOperator(Operator.Or) + .Analyzer("standard") + .QuoteAnalyzer("quote-an") + .AllowLeadingWildcard() + .AutoGeneratePhraseQueries() + .MaximumDeterminizedStates(2) + .LowercaseExpendedTerms() + .EnablePositionIncrements() + .Escape() + .UseDisMax() + .FuzzyPrefixLength(2) + .FuzzyMaxExpansions(3) + .FuzzyRewrite(RewriteMultiTerm.ConstantScore) + .Rewrite(RewriteMultiTerm.ConstantScore) + .Fuziness(Fuzziness.Auto) + .TieBreaker(1.2) + .AnalyzeWildcard() + .MinimumShouldMatch(2) + .QuoteFieldSuffix("'") + .Lenient() + .Locale("en_US") + .Timezone("root") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new QueryStringQuery +{ + Fields = Field(p=>p.Description).And("myOtherField"), + Boost = 1.1, + Name = "named_query", + Query = "hello world", + DefaultField = Field(p=>p.Description), + DefaultOperator = Operator.Or, + Analyzer = "standard", + QuoteAnalyzer = "quote-an", + AllowLeadingWildcard = true, + AutoGeneratePhraseQueries = true, + MaximumDeterminizedStates = 2, + LowercaseExpendedTerms = true, + EnablePositionIncrements = true, + Escape = true, + UseDisMax = true, + FuzzyPrefixLength = 2, + FuzzyMaxExpansions = 3, + FuzzyRewrite = RewriteMultiTerm.ConstantScore, + Rewrite = RewriteMultiTerm.ConstantScore, + Fuzziness = Fuzziness.Auto, + TieBreaker = 1.2, + AnalyzeWildcard = true, + MinimumShouldMatch = 2, + QuoteFieldSuffix = "'", + Lenient = true, + Locale = "en_US", + Timezone = "root" +} +---- + +[source,javascript] +.Example json output +---- +{ + "query_string": { + "_name": "named_query", + "boost": 1.1, + "query": "hello world", + "default_field": "description", + "default_operator": "or", + "analyzer": "standard", + "quote_analyzer": "quote-an", + "allow_leading_wildcard": true, + "lowercase_expanded_terms": true, + "enable_position_increments": true, + "fuzzy_max_expansions": 3, + "fuziness": "AUTO", + "fuzzy_prefix_length": 2, + "analyze_wildcard": true, + "auto_generate_phrase_queries": true, + "max_determinized_states": 2, + "minimum_should_match": 2, + "lenient": true, + "locale": "en_US", + "time_zone": "root", + "fields": [ + "description", + "myOtherField" + ], + "use_dis_max": true, + "tie_breaker": 1.2, + "rewrite": "constant_score", + "fuzzy_rewrite": "constant_score", + "quote_field_suffix": "'", + "escape": true + } +} +---- + diff --git a/docs/asciidoc/query-dsl/full-text/simple-query-string/simple-query-string-usage.asciidoc b/docs/asciidoc/query-dsl/full-text/simple-query-string/simple-query-string-usage.asciidoc new file mode 100644 index 00000000000..b3236c9c12a --- /dev/null +++ b/docs/asciidoc/query-dsl/full-text/simple-query-string/simple-query-string-usage.asciidoc @@ -0,0 +1,75 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[simple-query-string-usage]] +== Simple Query String Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.SimpleQueryString(c => c + .Name("named_query") + .Boost(1.1) + .Fields(f => f.Field(p=>p.Description).Field("myOtherField")) + .Query("hello world") + .Analyzer("standard") + .DefaultOperator(Operator.Or) + .Flags(SimpleQueryStringFlags.And|SimpleQueryStringFlags.Near) + .Locale("en_US") + .LowercaseExpendedTerms() + .Lenient() + .AnalyzeWildcard() + .MinimumShouldMatch("30%") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SimpleQueryStringQuery +{ + Name = "named_query", + Boost = 1.1, + Fields = Field(p=>p.Description).And("myOtherField"), + Query = "hello world", + Analyzer = "standard", + DefaultOperator = Operator.Or, + Flags = SimpleQueryStringFlags.And|SimpleQueryStringFlags.Near, + Locale = "en_US", + LowercaseExpendedTerms = true, + Lenient = true, + AnalyzeWildcard = true, + MinimumShouldMatch = "30%" +} +---- + +[source,javascript] +.Example json output +---- +{ + "simple_query_string": { + "_name": "named_query", + "boost": 1.1, + "fields": [ + "description", + "myOtherField" + ], + "query": "hello world", + "analyzer": "standard", + "default_operator": "or", + "flags": "AND|NEAR", + "locale": "en_US", + "lowercase_expanded_terms": true, + "lenient": true, + "analyze_wildcard": true, + "minimum_should_match": "30%" + } +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/bounding-box/geo-bounding-box-query-usage.asciidoc b/docs/asciidoc/query-dsl/geo/bounding-box/geo-bounding-box-query-usage.asciidoc new file mode 100644 index 00000000000..75eb66470bd --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/bounding-box/geo-bounding-box-query-usage.asciidoc @@ -0,0 +1,75 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-bounding-box-query-usage]] +== Geo Bounding Box Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoBoundingBox(g=>g + .Boost(1.1) + .Name("named_query") + .Field(p=>p.Location) + .BoundingBox(b=>b + .TopLeft(34, -34) + .BottomRight(-34, 34) + ) + .Coerce() + .IgnoreMalformed() + .ValidationMethod(GeoValidationMethod.Strict) + .Type(GeoExecution.Indexed) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoBoundingBoxQuery +{ + Boost = 1.1, + Name = "named_query", + Field = Infer.Field(p => p.Location), + BoundingBox = new Nest.BoundingBox + { + TopLeft = new GeoLocation(34,-34), + BottomRight = new GeoLocation(-34,34), + }, + Type = GeoExecution.Indexed, + Coerce = true, + IgnoreMalformed = true, + ValidationMethod = GeoValidationMethod.Strict +} +---- + +[source,javascript] +.Example json output +---- +{ + "geo_bounding_box": { + "type": "indexed", + "coerce": true, + "ignore_malformed": true, + "validation_method": "strict", + "_name": "named_query", + "boost": 1.1, + "location": { + "top_left": { + "lat": 34.0, + "lon": -34.0 + }, + "bottom_right": { + "lat": -34.0, + "lon": 34.0 + } + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/distance-range/geo-distance-range-query-usage.asciidoc b/docs/asciidoc/query-dsl/geo/distance-range/geo-distance-range-query-usage.asciidoc new file mode 100644 index 00000000000..55f572f3208 --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/distance-range/geo-distance-range-query-usage.asciidoc @@ -0,0 +1,77 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-distance-range-query-usage]] +== Geo Distance Range Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoDistanceRange(g=>g + .Boost(1.1) + .Name("named_query") + .Field(p=>p.Location) + .DistanceType(GeoDistanceType.Arc) + .Coerce() + .GreaterThanOrEqualTo(200, DistanceUnit.Kilometers) + .GreaterThan(200, DistanceUnit.Kilometers) + .IgnoreMalformed() + .Location(new GeoLocation(40, -70)) + .Optimize(GeoOptimizeBBox.Indexed) + .LessThanOrEqualTo(Nest.Distance.Miles(400)) + .LessThan(Nest.Distance.Miles(400)) + .ValidationMethod(GeoValidationMethod.Strict) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoDistanceRangeQuery +{ + Boost = 1.1, + Name = "named_query", + Field = Infer.Field(p=>p.Location), + DistanceType = GeoDistanceType.Arc, + Coerce = true, + GreaterThanOrEqualTo = Nest.Distance.Kilometers(200), + IgnoreMalformed = true, + GreaterThan = Nest.Distance.Kilometers(200), + LessThan = Nest.Distance.Miles(400), + Location = new GeoLocation(40, -70), + OptimizeBoundingBox = GeoOptimizeBBox.Indexed, + LessThanOrEqualTo = Nest.Distance.Miles(400), + ValidationMethod = GeoValidationMethod.Strict +} +---- + +[source,javascript] +.Example json output +---- +{ + "geo_distance_range": { + "gt": "200.0km", + "gte": "200.0km", + "lt": "400.0mi", + "lte": "400.0mi", + "distance_type": "arc", + "optimize_bbox": "indexed", + "coerce": true, + "ignore_malformed": true, + "validation_method": "strict", + "_name": "named_query", + "boost": 1.1, + "location": { + "lat": 40.0, + "lon": -70.0 + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/distance/geo-distance-query-usage.asciidoc b/docs/asciidoc/query-dsl/geo/distance/geo-distance-query-usage.asciidoc new file mode 100644 index 00000000000..d4cc505c60d --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/distance/geo-distance-query-usage.asciidoc @@ -0,0 +1,68 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-distance-query-usage]] +== Geo Distance Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoDistance(g=>g + .Boost(1.1) + .Name("named_query") + .Field(p=>p.Location) + .DistanceType(GeoDistanceType.Arc) + .Coerce() + .Location(34, -34) + .Distance("200.0m") + .IgnoreMalformed() + .Optimize(GeoOptimizeBBox.Memory) + .ValidationMethod(GeoValidationMethod.Strict) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoDistanceQuery +{ + Boost = 1.1, + Name = "named_query", + Field = Infer.Field(p => p.Location), + DistanceType = GeoDistanceType.Arc, + Coerce = true, + Location = new GeoLocation(34,-34), + Distance = "200.0m", + IgnoreMalformed = true, + OptimizeBoundingBox = GeoOptimizeBBox.Memory, + ValidationMethod = GeoValidationMethod.Strict +} +---- + +[source,javascript] +.Example json output +---- +{ + "geo_distance": { + "_name": "named_query", + "boost": 1.1, + "distance": "200.0m", + "optimize_bbox": "memory", + "distance_type": "arc", + "coerce": true, + "ignore_malformed": true, + "validation_method": "strict", + "location": { + "lat": 34.0, + "lon": -34.0 + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/hash-cell/geo-hash-cell-query-usage.asciidoc b/docs/asciidoc/query-dsl/geo/hash-cell/geo-hash-cell-query-usage.asciidoc new file mode 100644 index 00000000000..9db82c4a816 --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/hash-cell/geo-hash-cell-query-usage.asciidoc @@ -0,0 +1,56 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-hash-cell-query-usage]] +== Geo Hash Cell Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoHashCell(c => c + .Name("named_query") + .Boost(1.1) + .Field(p=>p.Location) + .Location(new GeoLocation(13.4080, 52.5186)) + .Neighbors() + .Precision(Nest.Distance.Meters(3)) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoHashCellQuery +{ + Boost = 1.1, + Name = "named_query", + Field = Infer.Field(p=>p.Location), + Location = new GeoLocation(13.4080, 52.5186), + Neighbors = true, + Precision = Nest.Distance.Meters(3) +} +---- + +[source,javascript] +.Example json output +---- +{ + "geohash_cell": { + "_name": "named_query", + "boost": 1.1, + "precision": "3.0m", + "neighbors": true, + "location": { + "lat": 13.408, + "lon": 52.5186 + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/polygon/geo-polygon-query-usage.asciidoc b/docs/asciidoc/query-dsl/geo/polygon/geo-polygon-query-usage.asciidoc new file mode 100644 index 00000000000..15f49d76c2d --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/polygon/geo-polygon-query-usage.asciidoc @@ -0,0 +1,67 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-polygon-query-usage]] +== Geo Polygon Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoPolygon(c => c + .Name("named_query") + .Boost(1.1) + .Field(p=>p.Location) + .IgnoreMalformed() + .Coerce() + .ValidationMethod(GeoValidationMethod.Strict) + .Points( new GeoLocation(45,-45), new GeoLocation(-34,34)) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoPolygonQuery +{ + Boost = 1.1, + Name = "named_query", + ValidationMethod = GeoValidationMethod.Strict, + Coerce = true, + IgnoreMalformed = true, + Points = new [] { new GeoLocation(45,-45), new GeoLocation(-34,34), }, + Field = Field(p=>p.Location) +} +---- + +[source,javascript] +.Example json output +---- +{ + "geo_polygon": { + "_name": "named_query", + "boost": 1.1, + "coerce": true, + "ignore_malformed": true, + "validation_method": "strict", + "location": { + "points": [ + { + "lat": 45.0, + "lon": -45.0 + }, + { + "lat": -34.0, + "lon": 34.0 + } + ] + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/shape/circle/geo-shape-circle-usage.asciidoc b/docs/asciidoc/query-dsl/geo/shape/circle/geo-shape-circle-usage.asciidoc new file mode 100644 index 00000000000..a3185ee4299 --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/shape/circle/geo-shape-circle-usage.asciidoc @@ -0,0 +1,36 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-shape-circle-usage]] +== Geo Shape Circle Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoShapeCircle(c => c + .Name("named_query") + .Boost(1.1) + .Field(p=>p.Location) + .Coordinates(this._coordinates) + .Radius("100m") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoShapeCircleQuery +{ + Name = "named_query", + Boost = 1.1, + Field = Field(p=>p.Location), + Shape = new CircleGeoShape(this._coordinates) { Radius = "100m" } +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/shape/envelope/geo-envelope-usage.asciidoc b/docs/asciidoc/query-dsl/geo/shape/envelope/geo-envelope-usage.asciidoc new file mode 100644 index 00000000000..67a68dca84f --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/shape/envelope/geo-envelope-usage.asciidoc @@ -0,0 +1,35 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-envelope-usage]] +== Geo Envelope Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoShapeEnvelope(c => c + .Name("named_query") + .Boost(1.1) + .Field(p=>p.Location) + .Coordinates(this._coordinates) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoShapeEnvelopeQuery +{ + Name = "named_query", + Boost = 1.1, + Field = Field(p=>p.Location), + Shape = new EnvelopeGeoShape(this._coordinates) +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/shape/indexed-shape/geo-indexed-shape-usage.asciidoc b/docs/asciidoc/query-dsl/geo/shape/indexed-shape/geo-indexed-shape-usage.asciidoc new file mode 100644 index 00000000000..935d4062bc7 --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/shape/indexed-shape/geo-indexed-shape-usage.asciidoc @@ -0,0 +1,63 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-indexed-shape-usage]] +== Geo Indexed Shape Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoIndexedShape(c => c + .Name("named_query") + .Boost(1.1) + .Field(p=>p.Location) + .IndexedShape(p=>p + .Id(2) + .Path(pp=>pp.Location) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoIndexedShapeQuery +{ + Name = "named_query", + Boost = 1.1, + Field = Field(p=>p.Location), + IndexedShape = new FieldLookup + { + Id = 2, + Index = Index(), + Type = Type(), + Path = Field(p=>p.Location) + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "geo_shape": { + "location": { + "_name": "named_query", + "boost": 1.1, + "indexed_shape": { + "id": 2, + "type": "project", + "index": "project", + "path": "location" + } + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/shape/line-string/geo-line-string-usage.asciidoc b/docs/asciidoc/query-dsl/geo/shape/line-string/geo-line-string-usage.asciidoc new file mode 100644 index 00000000000..1d47da60b4a --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/shape/line-string/geo-line-string-usage.asciidoc @@ -0,0 +1,35 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-line-string-usage]] +== Geo Line String Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoShapeLineString(c => c + .Name("named_query") + .Boost(1.1) + .Field(p=>p.Location) + .Coordinates(this._coordinates) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoShapeLineStringQuery +{ + Name = "named_query", + Boost = 1.1, + Field = Field(p=>p.Location), + Shape = new LineStringGeoShape(this._coordinates) +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/shape/multi-line-string/geo-multi-line-string-usage.asciidoc b/docs/asciidoc/query-dsl/geo/shape/multi-line-string/geo-multi-line-string-usage.asciidoc new file mode 100644 index 00000000000..d1844b32a90 --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/shape/multi-line-string/geo-multi-line-string-usage.asciidoc @@ -0,0 +1,35 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-multi-line-string-usage]] +== Geo Multi Line String Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoShapeMultiLineString(c => c + .Name("named_query") + .Boost(1.1) + .Field(p=>p.Location) + .Coordinates(this._coordinates) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoShapeMultiLineStringQuery +{ + Name = "named_query", + Boost = 1.1, + Field = Field(p=>p.Location), + Shape = new MultiLineStringGeoShape(this._coordinates) +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/shape/multi-point/geo-multi-point-usage.asciidoc b/docs/asciidoc/query-dsl/geo/shape/multi-point/geo-multi-point-usage.asciidoc new file mode 100644 index 00000000000..2ec90304aed --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/shape/multi-point/geo-multi-point-usage.asciidoc @@ -0,0 +1,35 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-multi-point-usage]] +== Geo Multi Point Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoShapeMultiPoint(c => c + .Name("named_query") + .Boost(1.1) + .Field(p=>p.Location) + .Coordinates(this._coordinates) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoShapeMultiPointQuery +{ + Name = "named_query", + Boost = 1.1, + Field = Field(p=>p.Location), + Shape = new MultiPointGeoShape(this._coordinates) +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/shape/point/geo-point-usage.asciidoc b/docs/asciidoc/query-dsl/geo/shape/point/geo-point-usage.asciidoc new file mode 100644 index 00000000000..11c185b017f --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/shape/point/geo-point-usage.asciidoc @@ -0,0 +1,35 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-point-usage]] +== Geo Point Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoShapePoint(c => c + .Name("named_query") + .Boost(1.1) + .Field(p=>p.Location) + .Coordinates(this._coordinates) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoShapePointQuery +{ + Name = "named_query", + Boost = 1.1, + Field = Field(p=>p.Location), + Shape = new PointGeoShape(this._coordinates) +} +---- + diff --git a/docs/asciidoc/query-dsl/geo/shape/polygon/geo-polygon-usage.asciidoc b/docs/asciidoc/query-dsl/geo/shape/polygon/geo-polygon-usage.asciidoc new file mode 100644 index 00000000000..9b0a72c8f31 --- /dev/null +++ b/docs/asciidoc/query-dsl/geo/shape/polygon/geo-polygon-usage.asciidoc @@ -0,0 +1,35 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[geo-polygon-usage]] +== Geo Polygon Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.GeoShapePolygon(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Location) + .Coordinates(this._coordinates) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new GeoShapePolygonQuery +{ + Name = "named_query", + Boost = 1.1, + Field = Field(p => p.Location), + Shape = new PolygonGeoShape(this._coordinates) { } +} +---- + diff --git a/docs/asciidoc/query-dsl/joining/has-child/has-child-query-usage.asciidoc b/docs/asciidoc/query-dsl/joining/has-child/has-child-query-usage.asciidoc new file mode 100644 index 00000000000..3e5083af76e --- /dev/null +++ b/docs/asciidoc/query-dsl/joining/has-child/has-child-query-usage.asciidoc @@ -0,0 +1,63 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[has-child-query-usage]] +== Has Child Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.HasChild(c => c + .Name("named_query") + .Boost(1.1) + .InnerHits(i=>i.Explain()) + .MaxChildren(5) + .MinChildren(1) + .ScoreMode(ChildScoreMode.Average) + .Query(qq=>qq.MatchAll()) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new HasChildQuery +{ + Name = "named_query", + Boost = 1.1, + Type = Infer.Type(), + InnerHits = new InnerHits { Explain = true }, + MaxChildren = 5, + MinChildren = 1, + Query = new MatchAllQuery(), + ScoreMode = ChildScoreMode.Average +} +---- + +[source,javascript] +.Example json output +---- +{ + "has_child": { + "_name": "named_query", + "boost": 1.1, + "type": "developer", + "score_mode": "avg", + "min_children": 1, + "max_children": 5, + "query": { + "match_all": {} + }, + "inner_hits": { + "explain": true + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/joining/has-parent/has-parent-query-usage.asciidoc b/docs/asciidoc/query-dsl/joining/has-parent/has-parent-query-usage.asciidoc new file mode 100644 index 00000000000..30488fd07a7 --- /dev/null +++ b/docs/asciidoc/query-dsl/joining/has-parent/has-parent-query-usage.asciidoc @@ -0,0 +1,58 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[has-parent-query-usage]] +== Has Parent Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.HasParent(c => c + .Name("named_query") + .Boost(1.1) + .InnerHits(i=>i.Explain()) + .ScoreMode(ParentScoreMode.Score) + .Query(qq=>qq.MatchAll()) + +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new HasParentQuery +{ + Name = "named_query", + Boost = 1.1, + Type = Infer.Type(), + InnerHits = new InnerHits { Explain = true }, + Query = new MatchAllQuery(), + ScoreMode = ParentScoreMode.Score +} +---- + +[source,javascript] +.Example json output +---- +{ + "has_parent": { + "_name": "named_query", + "boost": 1.1, + "type": "developer", + "score_mode": "score", + "query": { + "match_all": {} + }, + "inner_hits": { + "explain": true + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/joining/nested/nested-query-usage.asciidoc b/docs/asciidoc/query-dsl/joining/nested/nested-query-usage.asciidoc new file mode 100644 index 00000000000..7cac48a1388 --- /dev/null +++ b/docs/asciidoc/query-dsl/joining/nested/nested-query-usage.asciidoc @@ -0,0 +1,55 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[nested-query-usage]] +== Nested Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Nested(c => c + .Name("named_query") + .Boost(1.1) + .InnerHits(i=>i.Explain()) + .Query(qq=>qq.MatchAll()) + .Path(p=>p.CuratedTags) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new NestedQuery +{ + Name = "named_query", + Boost = 1.1, + InnerHits = new InnerHits { Explain = true }, + Query = new MatchAllQuery(), + Path = Field(p=>p.CuratedTags) +} +---- + +[source,javascript] +.Example json output +---- +{ + "nested": { + "_name": "named_query", + "boost": 1.1, + "query": { + "match_all": {} + }, + "path": "curatedTags", + "inner_hits": { + "explain": true + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/nest-specific/raw/raw-combine-usage.asciidoc b/docs/asciidoc/query-dsl/nest-specific/raw/raw-combine-usage.asciidoc new file mode 100644 index 00000000000..5250786a5b3 --- /dev/null +++ b/docs/asciidoc/query-dsl/nest-specific/raw/raw-combine-usage.asciidoc @@ -0,0 +1,47 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[raw-combine-usage]] +== Raw Combine Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q.Raw(RawTermQuery) && q.Term("x", "y") +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new RawQuery(RawTermQuery) +&& new TermQuery { Field = "x", Value = "y" } +---- + +[source,javascript] +.Example json output +---- +{ + "bool": { + "must": [ + { + "term": { + "fieldname": "value" + } + }, + { + "term": { + "x": { + "value": "y" + } + } + } + ] + } +} +---- + diff --git a/docs/asciidoc/query-dsl/nest-specific/raw/raw-query-usage.asciidoc b/docs/asciidoc/query-dsl/nest-specific/raw/raw-query-usage.asciidoc new file mode 100644 index 00000000000..8a981e5c065 --- /dev/null +++ b/docs/asciidoc/query-dsl/nest-specific/raw/raw-query-usage.asciidoc @@ -0,0 +1,34 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[raw-query-usage]] +== Raw Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Raw(RawTermQuery) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new RawQuery(RawTermQuery) +---- + +[source,javascript] +.Example json output +---- +{ + "term": { + "fieldname": "value" + } +} +---- + diff --git a/docs/asciidoc/query-dsl/span/container/span-containing-query-usage.asciidoc b/docs/asciidoc/query-dsl/span/container/span-containing-query-usage.asciidoc new file mode 100644 index 00000000000..7b7118358e7 --- /dev/null +++ b/docs/asciidoc/query-dsl/span/container/span-containing-query-usage.asciidoc @@ -0,0 +1,64 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[span-containing-query-usage]] +== Span Containing Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.SpanContaining(sn => sn + .Name("named_query") + .Boost(1.1) + .Little(i=>i + .SpanTerm(st=>st.Field("field1").Value("hoya")) + ) + .Big(e=>e + .SpanTerm(st=>st.Field("field1").Value("hoya2")) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SpanContainingQuery +{ + Name = "named_query", + Boost = 1.1, + Little = new SpanQuery { SpanTerm = new SpanTermQuery { Field = "field1", Value = "hoya"} }, + Big = new SpanQuery { SpanTerm = new SpanTermQuery { Field = "field1", Value = "hoya2"} }, +} +---- + +[source,javascript] +.Example json output +---- +{ + "span_containing": { + "_name": "named_query", + "boost": 1.1, + "little": { + "span_term": { + "field1": { + "value": "hoya" + } + } + }, + "big": { + "span_term": { + "field1": { + "value": "hoya2" + } + } + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/span/first/span-first-query-usage.asciidoc b/docs/asciidoc/query-dsl/span/first/span-first-query-usage.asciidoc new file mode 100644 index 00000000000..73b2725f3b7 --- /dev/null +++ b/docs/asciidoc/query-dsl/span/first/span-first-query-usage.asciidoc @@ -0,0 +1,59 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[span-first-query-usage]] +== Span First Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.SpanFirst(c => c + .Name("named_query") + .Boost(1.1) + .Match(sq=>sq + .SpanTerm(st=>st.Field(p=>p.Name).Value("value")) + ) + .End(3) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SpanFirstQuery +{ + Name = "named_query", + Boost = 1.1, + End = 3, + Match = new SpanQuery + { + SpanTerm = new SpanTermQuery { Field = "name", Value = "value" } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "span_first": { + "_name": "named_query", + "boost": 1.1, + "match": { + "span_term": { + "name": { + "value": "value" + } + } + }, + "end": 3 + } +} +---- + diff --git a/docs/asciidoc/query-dsl/span/multi-term/span-multi-term-query-usage.asciidoc b/docs/asciidoc/query-dsl/span/multi-term/span-multi-term-query-usage.asciidoc new file mode 100644 index 00000000000..8e0db66aebe --- /dev/null +++ b/docs/asciidoc/query-dsl/span/multi-term/span-multi-term-query-usage.asciidoc @@ -0,0 +1,53 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[span-multi-term-query-usage]] +== Span Multi Term Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.SpanMultiTerm(c => c + .Name("named_query") + .Boost(1.1) + .Match(sq=>sq + .Prefix(pr=>pr.Field(p=>p.Name).Value("pre-*")) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SpanMultiTermQuery +{ + Name = "named_query", + Boost = 1.1, + Match = new PrefixQuery { Field = "name", Value = "pre-*" } +} +---- + +[source,javascript] +.Example json output +---- +{ + "span_multi": { + "_name": "named_query", + "boost": 1.1, + "match": { + "prefix": { + "name": { + "value": "pre-*" + } + } + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/span/near/span-near-query-usage.asciidoc b/docs/asciidoc/query-dsl/span/near/span-near-query-usage.asciidoc new file mode 100644 index 00000000000..aaac1e9a051 --- /dev/null +++ b/docs/asciidoc/query-dsl/span/near/span-near-query-usage.asciidoc @@ -0,0 +1,85 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[span-near-query-usage]] +== Span Near Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.SpanNear(sn => sn + .Name("named_query") + .Boost(1.1) + .Clauses( + c=>c.SpanTerm(st=>st.Field("field").Value("value1")), + c=>c.SpanTerm(st=>st.Field("field").Value("value2")), + c=>c.SpanTerm(st=>st.Field("field").Value("value3")) + ) + .Slop(12) + .InOrder(false) + .CollectPayloads(false) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SpanNearQuery +{ + Name = "named_query", + Boost = 1.1, + Clauses = new List + { + new SpanQuery { SpanTerm = new SpanTermQuery { Field = "field", Value = "value1" } }, + new SpanQuery { SpanTerm = new SpanTermQuery { Field = "field", Value = "value2" } }, + new SpanQuery { SpanTerm = new SpanTermQuery { Field = "field", Value = "value3" } } + }, + Slop = 12, + InOrder = false, + CollectPayloads = false +} +---- + +[source,javascript] +.Example json output +---- +{ + "span_near": { + "clauses": [ + { + "span_term": { + "field": { + "value": "value1" + } + } + }, + { + "span_term": { + "field": { + "value": "value2" + } + } + }, + { + "span_term": { + "field": { + "value": "value3" + } + } + } + ], + "slop": 12, + "in_order": false, + "collect_payloads": false, + "_name": "named_query", + "boost": 1.1 + } +} +---- + diff --git a/docs/asciidoc/query-dsl/span/not/span-not-query-usage.asciidoc b/docs/asciidoc/query-dsl/span/not/span-not-query-usage.asciidoc new file mode 100644 index 00000000000..6ef96d2e2a0 --- /dev/null +++ b/docs/asciidoc/query-dsl/span/not/span-not-query-usage.asciidoc @@ -0,0 +1,85 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[span-not-query-usage]] +== Span Not Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.SpanNot(sn => sn + .Name("named_query") + .Boost(1.1) + .Dist(12) + .Post(13) + .Pre(14) + .Include(i => i + .SpanTerm(st => st.Field("field1").Value("hoya")) + ) + .Exclude(e => e + .SpanTerm(st => st.Field("field1").Value("hoya2")) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SpanNotQuery +{ + Name = "named_query", + Boost = 1.1, + Dist = 12, + Post = 13, + Pre = 14, + Include = new SpanQuery + { + SpanTerm = new SpanTermQuery + { + Field = "field1", Value = "hoya" + } + }, + Exclude = new SpanQuery + { + SpanTerm = new SpanTermQuery + { + Field = "field1", Value = "hoya2" + } + }, +} +---- + +[source,javascript] +.Example json output +---- +{ + "span_not": { + "_name": "named_query", + "boost": 1.1, + "include": { + "span_term": { + "field1": { + "value": "hoya" + } + } + }, + "exclude": { + "span_term": { + "field1": { + "value": "hoya2" + } + } + }, + "pre": 14, + "post": 13, + "dist": 12 + } +} +---- + diff --git a/docs/asciidoc/query-dsl/span/or/span-or-query-usage.asciidoc b/docs/asciidoc/query-dsl/span/or/span-or-query-usage.asciidoc new file mode 100644 index 00000000000..83640752cd1 --- /dev/null +++ b/docs/asciidoc/query-dsl/span/or/span-or-query-usage.asciidoc @@ -0,0 +1,76 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[span-or-query-usage]] +== Span Or Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.SpanOr(sn => sn + .Name("named_query") + .Boost(1.1) + .Clauses( + c => c.SpanTerm(st => st.Field("field").Value("value1")), + c => c.SpanTerm(st => st.Field("field").Value("value2")), + c => c.SpanTerm(st => st.Field("field").Value("value3")) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SpanOrQuery +{ + Name = "named_query", + Boost = 1.1, + Clauses = new List + { + new SpanQuery { SpanTerm = new SpanTermQuery { Field = "field", Value = "value1" } }, + new SpanQuery { SpanTerm = new SpanTermQuery { Field = "field", Value = "value2" } }, + new SpanQuery { SpanTerm = new SpanTermQuery { Field = "field", Value = "value3" } } + }, +} +---- + +[source,javascript] +.Example json output +---- +{ + "span_or": { + "_name": "named_query", + "boost": 1.1, + "clauses": [ + { + "span_term": { + "field": { + "value": "value1" + } + } + }, + { + "span_term": { + "field": { + "value": "value2" + } + } + }, + { + "span_term": { + "field": { + "value": "value3" + } + } + } + ] + } +} +---- + diff --git a/docs/asciidoc/query-dsl/span/term/span-term-query-usage.asciidoc b/docs/asciidoc/query-dsl/span/term/span-term-query-usage.asciidoc new file mode 100644 index 00000000000..3465be21e88 --- /dev/null +++ b/docs/asciidoc/query-dsl/span/term/span-term-query-usage.asciidoc @@ -0,0 +1,49 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[span-term-query-usage]] +== Span Term Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.SpanTerm(c => c + .Name("named_query") + .Boost(1.1) + .Field("user") + .Value("kimchy") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SpanTermQuery +{ + Name = "named_query", + Boost = 1.1, + Value = "kimchy", + Field = "user" +} +---- + +[source,javascript] +.Example json output +---- +{ + "span_term": { + "user": { + "_name": "named_query", + "boost": 1.1, + "value": "kimchy" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/span/within/span-within-query-usage.asciidoc b/docs/asciidoc/query-dsl/span/within/span-within-query-usage.asciidoc new file mode 100644 index 00000000000..2aaddef7c6a --- /dev/null +++ b/docs/asciidoc/query-dsl/span/within/span-within-query-usage.asciidoc @@ -0,0 +1,64 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[span-within-query-usage]] +== Span Within Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.SpanWithin(sn => sn + .Name("named_query") + .Boost(1.1) + .Little(i=>i + .SpanTerm(st=>st.Field("field1").Value("hoya")) + ) + .Big(e=>e + .SpanTerm(st=>st.Field("field1").Value("hoya2")) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SpanWithinQuery +{ + Name = "named_query", + Boost = 1.1, + Little = new SpanQuery { SpanTerm = new SpanTermQuery { Field = "field1", Value = "hoya"} }, + Big = new SpanQuery { SpanTerm = new SpanTermQuery { Field = "field1", Value = "hoya2"} }, +} +---- + +[source,javascript] +.Example json output +---- +{ + "span_within": { + "_name": "named_query", + "boost": 1.1, + "little": { + "span_term": { + "field1": { + "value": "hoya" + } + } + }, + "big": { + "span_term": { + "field1": { + "value": "hoya2" + } + } + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/specialized/more-like-this/more-like-this-query-usage.asciidoc b/docs/asciidoc/query-dsl/specialized/more-like-this/more-like-this-query-usage.asciidoc new file mode 100644 index 00000000000..bbb1ae7e330 --- /dev/null +++ b/docs/asciidoc/query-dsl/specialized/more-like-this/more-like-this-query-usage.asciidoc @@ -0,0 +1,110 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[more-like-this-query-usage]] +== More Like This Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.MoreLikeThis(sn => sn + .Name("named_query") + .Boost(1.1) + .Like(l=>l + .Document(d=>d .Id(Project.Instance.Name)) + .Text("some long text") + ) + .Analyzer("some_analyzer") + .BoostTerms(1.1) + .Include() + .MaxDocumentFrequency(12) + .MaxQueryTerms(12) + .MaxWordLength(300) + .MinDocumentFrequency(1) + .MinTermFrequency(1) + .MinWordLength(10) + .StopWords("and", "the") + .MinimumShouldMatch(1) + .Fields(f=>f.Field(p=>p.Name)) + .Unlike(l=>l + .Text("not like this text") + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new MoreLikeThisQuery +{ + Name = "named_query", + Boost = 1.1, + Fields = Fields(p=>p.Name), + Like = new List + { + new LikeDocument(Project.Instance.Name), + "some long text" + }, + Analyzer = "some_analyzer", + BoostTerms = 1.1, + Include = true, + MaxDocumentFrequency = 12, + MaxQueryTerms = 12, + MaxWordLength = 300, + MinDocumentFrequency = 1, + MinTermFrequency = 1, + MinWordLength = 10, + MinimumShouldMatch = 1, + StopWords = new [] { "and", "the"}, + Unlike = new List + { + "not like this text" + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "mlt": { + "fields": [ + "name" + ], + "minimum_should_match": 1, + "stop_words": [ + "and", + "the" + ], + "min_term_freq": 1, + "max_query_terms": 12, + "min_doc_freq": 1, + "max_doc_freq": 12, + "min_word_len": 10, + "max_word_len": 300, + "boost_terms": 1.1, + "analyzer": "some_analyzer", + "include": true, + "like": [ + { + "_index": "project", + "_type": "project", + "_id": "Durgan LLC" + }, + "some long text" + ], + "unlike": [ + "not like this text" + ], + "_name": "named_query", + "boost": 1.1 + } +} +---- + diff --git a/docs/asciidoc/query-dsl/specialized/script/script-query-usage.asciidoc b/docs/asciidoc/query-dsl/specialized/script/script-query-usage.asciidoc new file mode 100644 index 00000000000..c669ac13d84 --- /dev/null +++ b/docs/asciidoc/query-dsl/specialized/script/script-query-usage.asciidoc @@ -0,0 +1,55 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[script-query-usage]] +== Script Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Script(sn => sn + .Name("named_query") + .Boost(1.1) + .Inline(_templateString) + .Params(p=>p.Add("param1", 1)) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new ScriptQuery +{ + Name = "named_query", + Boost = 1.1, + Inline = _templateString, + Params = new Dictionary + { + { "param1", 1 } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "script": { + "_name": "named_query", + "boost": 1.1, + "script": { + "inline": "doc['num1'].value > param1", + "params": { + "param1": 1 + } + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/specialized/template/template-query-usage.asciidoc b/docs/asciidoc/query-dsl/specialized/template/template-query-usage.asciidoc new file mode 100644 index 00000000000..13361285b06 --- /dev/null +++ b/docs/asciidoc/query-dsl/specialized/template/template-query-usage.asciidoc @@ -0,0 +1,53 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[template-query-usage]] +== Template Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Template(sn => sn + .Name("named_query") + .Boost(1.1) + .Inline(_templateString) + .Params(p=>p.Add("query_string", "all about search")) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new TemplateQuery +{ + Name = "named_query", + Boost = 1.1, + Inline = _templateString, + Params = new Dictionary + { + { "query_string", "all about search" } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "template": { + "_name": "named_query", + "boost": 1.1, + "inline": "{ \"match\": { \"text\": \"{{query_string}}\" } }", + "params": { + "query_string": "all about search" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/exists/exists-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/exists/exists-query-usage.asciidoc new file mode 100644 index 00000000000..974d4b6c7ac --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/exists/exists-query-usage.asciidoc @@ -0,0 +1,45 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[exists-query-usage]] +== Exists Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Exists(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new ExistsQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", +} +---- + +[source,javascript] +.Example json output +---- +{ + "exists": { + "_name": "named_query", + "boost": 1.1, + "field": "description" + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-date-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-date-query-usage.asciidoc new file mode 100644 index 00000000000..bee0aeb2619 --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-date-query-usage.asciidoc @@ -0,0 +1,64 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[fuzzy-date-query-usage]] +== Fuzzy Date Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.FuzzyDate(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .Fuzziness(TimeSpan.FromDays(2)) + .Value(Project.Instance.StartedOn) + .MaxExpansions(100) + .PrefixLength(3) + .Rewrite(RewriteMultiTerm.ConstantScore) + .Transpositions() +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new FuzzyDateQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + Fuzziness = TimeSpan.FromDays(2), + Value = Project.Instance.StartedOn, + MaxExpansions = 100, + PrefixLength = 3, + Rewrite = RewriteMultiTerm.ConstantScore, + Transpositions = true +} +---- + +[source,javascript] +.Example json output +---- +{ + "fuzzy": { + "description": { + "_name": "named_query", + "boost": 1.1, + "fuzziness": "2d", + "max_expansions": 100, + "prefix_length": 3, + "rewrite": "constant_score", + "transpositions": true, + "value": "2015-01-01T00:00:00" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-numeric-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-numeric-query-usage.asciidoc new file mode 100644 index 00000000000..bc8810eafe2 --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-numeric-query-usage.asciidoc @@ -0,0 +1,64 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[fuzzy-numeric-query-usage]] +== Fuzzy Numeric Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.FuzzyNumeric(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .Fuzziness(2) + .Value(12) + .MaxExpansions(100) + .PrefixLength(3) + .Rewrite(RewriteMultiTerm.ConstantScore) + .Transpositions() +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new FuzzyNumericQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + Fuzziness = 2, + Value = 12, + MaxExpansions = 100, + PrefixLength = 3, + Rewrite = RewriteMultiTerm.ConstantScore, + Transpositions = true +} +---- + +[source,javascript] +.Example json output +---- +{ + "fuzzy": { + "description": { + "_name": "named_query", + "boost": 1.1, + "fuzziness": 2.0, + "max_expansions": 100, + "prefix_length": 3, + "rewrite": "constant_score", + "transpositions": true, + "value": 12.0 + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-query-usage.asciidoc new file mode 100644 index 00000000000..1a0d703c63d --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/fuzzy/fuzzy-query-usage.asciidoc @@ -0,0 +1,64 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[fuzzy-query-usage]] +== Fuzzy Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Fuzzy(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .Fuzziness(Fuzziness.Auto) + .Value("ki") + .MaxExpansions(100) + .PrefixLength(3) + .Rewrite(RewriteMultiTerm.ConstantScore) + .Transpositions() +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new FuzzyQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + Fuzziness = Fuzziness.Auto, + Value = "ki", + MaxExpansions = 100, + PrefixLength = 3, + Rewrite = RewriteMultiTerm.ConstantScore, + Transpositions = true +} +---- + +[source,javascript] +.Example json output +---- +{ + "fuzzy": { + "description": { + "_name": "named_query", + "boost": 1.1, + "fuzziness": "AUTO", + "max_expansions": 100, + "prefix_length": 3, + "rewrite": "constant_score", + "transpositions": true, + "value": "ki" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/ids/ids-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/ids/ids-query-usage.asciidoc new file mode 100644 index 00000000000..19cd0489925 --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/ids/ids-query-usage.asciidoc @@ -0,0 +1,56 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[ids-query-usage]] +== Ids Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Ids(c => c + .Name("named_query") + .Boost(1.1) + .Values(1, 2, 3, 4) + .Types(typeof(Project), typeof(Developer)) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new IdsQuery +{ + Name = "named_query", + Boost = 1.1, + Values = new List { 1, 2,3,4 }, + Types = Type().And() +} +---- + +[source,javascript] +.Example json output +---- +{ + "ids": { + "_name": "named_query", + "boost": 1.1, + "types": [ + "project", + "developer" + ], + "values": [ + 1, + 2, + 3, + 4 + ] + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/missing/missing-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/missing/missing-query-usage.asciidoc new file mode 100644 index 00000000000..01a6f3ef835 --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/missing/missing-query-usage.asciidoc @@ -0,0 +1,51 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[missing-query-usage]] +== Missing Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Missing(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .NullValue() + .Existence() +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new MissingQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + NullValue = true, + Existence = true +} +---- + +[source,javascript] +.Example json output +---- +{ + "missing": { + "_name": "named_query", + "boost": 1.1, + "existence": true, + "field": "description", + "null_value": true + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/prefix/prefix-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/prefix/prefix-query-usage.asciidoc new file mode 100644 index 00000000000..719986ba859 --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/prefix/prefix-query-usage.asciidoc @@ -0,0 +1,52 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[prefix-query-usage]] +== Prefix Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Prefix(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .Value("proj") + .Rewrite(RewriteMultiTerm.TopTermsBoostN) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new PrefixQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + Value = "proj", + Rewrite = RewriteMultiTerm.TopTermsBoostN +} +---- + +[source,javascript] +.Example json output +---- +{ + "prefix": { + "description": { + "_name": "named_query", + "boost": 1.1, + "rewrite": "top_terms_boost_N", + "value": "proj" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/range/date-range-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/range/date-range-query-usage.asciidoc new file mode 100644 index 00000000000..eb60fdb35c6 --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/range/date-range-query-usage.asciidoc @@ -0,0 +1,64 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[date-range-query-usage]] +== Date Range Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.DateRange(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .GreaterThan(FixedDate) + .GreaterThanOrEquals(DateMath.Anchored(FixedDate).RoundTo(TimeUnit.Month)) + .LessThan("01/01/2012") + .LessThanOrEquals(DateMath.Now) + .Format("dd/MM/yyyy||yyyy") + .TimeZone("+01:00") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new DateRangeQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + GreaterThan = FixedDate, + GreaterThanOrEqualTo = DateMath.Anchored(FixedDate).RoundTo(TimeUnit.Month), + LessThan = "01/01/2012", + LessThanOrEqualTo = DateMath.Now, + TimeZone = "+01:00", + Format = "dd/MM/yyyy||yyyy" +} +---- + +[source,javascript] +.Example json output +---- +{ + "range": { + "description": { + "_name": "named_query", + "boost": 1.1, + "format": "dd/MM/yyyy||yyyy", + "gt": "2015-06-06T12:01:02.123", + "gte": "2015-06-06T12:01:02.123||/M", + "lt": "01/01/2012", + "lte": "now", + "time_zone": "+01:00" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/range/numeric-range-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/range/numeric-range-query-usage.asciidoc new file mode 100644 index 00000000000..f4c4e64f443 --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/range/numeric-range-query-usage.asciidoc @@ -0,0 +1,58 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[numeric-range-query-usage]] +== Numeric Range Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Range(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .GreaterThan(1.0) + .GreaterThanOrEquals(1.1) + .LessThan(2.1) + .LessThanOrEquals(2.0) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new NumericRangeQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + GreaterThan = 1.0, + GreaterThanOrEqualTo = 1.1, + LessThan = 2.1, + LessThanOrEqualTo = 2.0 +} +---- + +[source,javascript] +.Example json output +---- +{ + "range": { + "description": { + "_name": "named_query", + "boost": 1.1, + "gt": 1.0, + "gte": 1.1, + "lt": 2.1, + "lte": 2.0 + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/range/term-range-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/range/term-range-query-usage.asciidoc new file mode 100644 index 00000000000..ee93de92f42 --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/range/term-range-query-usage.asciidoc @@ -0,0 +1,58 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[term-range-query-usage]] +== Term Range Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.TermRange(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .GreaterThan("foo") + .GreaterThanOrEquals("foof") + .LessThan("bar") + .LessThanOrEquals("barb") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new TermRangeQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + GreaterThan = "foo", + GreaterThanOrEqualTo = "foof", + LessThan = "bar", + LessThanOrEqualTo = "barb" +} +---- + +[source,javascript] +.Example json output +---- +{ + "range": { + "description": { + "_name": "named_query", + "boost": 1.1, + "gt": "foo", + "gte": "foof", + "lt": "bar", + "lte": "barb" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/regexp/regexp-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/regexp/regexp-query-usage.asciidoc new file mode 100644 index 00000000000..5d99b771e17 --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/regexp/regexp-query-usage.asciidoc @@ -0,0 +1,55 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[regexp-query-usage]] +== Regexp Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Regexp(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .Value("s.*y") + .Flags("INTERSECTION|COMPLEMENT|EMPTY") + .MaximumDeterminizedStates(20000) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new RegexpQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + Value = "s.*y", + Flags = "INTERSECTION|COMPLEMENT|EMPTY", + MaximumDeterminizedStates = 20000 +} +---- + +[source,javascript] +.Example json output +---- +{ + "regexp": { + "description": { + "_name": "named_query", + "boost": 1.1, + "flags": "INTERSECTION|COMPLEMENT|EMPTY", + "max_determinized_states": 20000, + "value": "s.*y" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/term/term-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/term/term-query-usage.asciidoc new file mode 100644 index 00000000000..56bfff4117c --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/term/term-query-usage.asciidoc @@ -0,0 +1,49 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[term-query-usage]] +== Term Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Term(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .Value("project description") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new TermQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + Value = "project description" +} +---- + +[source,javascript] +.Example json output +---- +{ + "term": { + "description": { + "_name": "named_query", + "boost": 1.1, + "value": "project description" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/terms/terms-list-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/terms/terms-list-query-usage.asciidoc new file mode 100644 index 00000000000..41549d983dc --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/terms/terms-list-query-usage.asciidoc @@ -0,0 +1,148 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[terms-list-query-usage]] +== Terms List Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Terms(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .DisableCoord() + .MinimumShouldMatch(MinimumShouldMatch.Fixed(2)) + .Terms(new List { "term1", "term2" }) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new TermsQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + Terms = new List { "term1", "term2" }, + DisableCoord = true, + MinimumShouldMatch = 2 +} +---- + +[source,javascript] +.Example json output +---- +{ + "terms": { + "_name": "named_query", + "boost": 1.1, + "description": [ + "term1", + "term2" + ], + "disable_coord": true, + "minimum_should_match": 2 + } +} +---- + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Terms(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .DisableCoord() + .Terms(_terms) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new TermsQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + Terms = _terms, + DisableCoord = true, +} +---- + +[source,javascript] +.Example json output +---- +{ + "terms": { + "_name": "named_query", + "boost": 1.1, + "description": [ + [ + "term1", + "term2" + ] + ], + "disable_coord": true + } +} +---- + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Terms(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.NumberOfCommits) + .DisableCoord() + .Terms(_terms) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new TermsQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "numberOfCommits", + Terms = _terms, + DisableCoord = true, +} +---- + +[source,javascript] +.Example json output +---- +{ + "terms": { + "_name": "named_query", + "boost": 1.1, + "numberOfCommits": [ + [ + "term1", + "term2" + ] + ], + "disable_coord": true + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/terms/terms-lookup-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/terms/terms-lookup-query-usage.asciidoc new file mode 100644 index 00000000000..95f1f7ad444 --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/terms/terms-lookup-query-usage.asciidoc @@ -0,0 +1,58 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[terms-lookup-query-usage]] +== Terms Lookup Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Terms(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .TermsLookup(e=>e.Path(p=>p.LastName).Id(12)) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new TermsQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + TermsLookup = new FieldLookup + { + Id = 12, + Index = Index(), + Type = Type(), + Path = Field(p=>p.LastName) + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "terms": { + "_name": "named_query", + "boost": 1.1, + "description": { + "id": 12, + "index": "devs", + "path": "lastName", + "type": "developer" + } + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/terms/terms-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/terms/terms-query-usage.asciidoc new file mode 100644 index 00000000000..84e2dcd2185 --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/terms/terms-query-usage.asciidoc @@ -0,0 +1,79 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[terms-query-usage]] +== Terms Query Usage + +Filters documents that have fields that match any of the provided terms (not analyzed). + +Be sure to read the Elasticsearch documentation on {ref_current}/query-dsl-terms-query.html[Terms query] for more information. + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Terms(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .DisableCoord() + .MinimumShouldMatch(MinimumShouldMatch.Fixed(2)) + .Terms("term1", "term2") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new TermsQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + Terms = ExpectedTerms, + DisableCoord = true, + MinimumShouldMatch = 2 +} +---- + +[source,javascript] +.Example json output +---- +{ + "terms": { + "_name": "named_query", + "boost": 1.1, + "description": [ + "term1", + "term2" + ], + "disable_coord": true, + "minimum_should_match": 2 + } +} +---- + +[[single-term-terms-query]] +[float] +== Single term Terms Query + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Terms(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .DisableCoord() + .MinimumShouldMatch(MinimumShouldMatch.Fixed(2)) + .Terms("term1") +) +---- + diff --git a/docs/asciidoc/query-dsl/term-level/type/type-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/type/type-query-usage.asciidoc new file mode 100644 index 00000000000..2bff4a45c6f --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/type/type-query-usage.asciidoc @@ -0,0 +1,45 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[type-query-usage]] +== Type Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Type(c => c + .Name("named_query") + .Boost(1.1) + .Value() +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new TypeQuery +{ + Name = "named_query", + Boost = 1.1, + Value = Type() +} +---- + +[source,javascript] +.Example json output +---- +{ + "type": { + "_name": "named_query", + "boost": 1.1, + "value": "developer" + } +} +---- + diff --git a/docs/asciidoc/query-dsl/term-level/wildcard/wildcard-query-usage.asciidoc b/docs/asciidoc/query-dsl/term-level/wildcard/wildcard-query-usage.asciidoc new file mode 100644 index 00000000000..8e23c6dcab2 --- /dev/null +++ b/docs/asciidoc/query-dsl/term-level/wildcard/wildcard-query-usage.asciidoc @@ -0,0 +1,52 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[wildcard-query-usage]] +== Wildcard Query Usage + +=== Fluent DSL Example + +[source,csharp] +---- +q +.Wildcard(c => c + .Name("named_query") + .Boost(1.1) + .Field(p => p.Description) + .Value("p*oj") + .Rewrite(RewriteMultiTerm.TopTermsBoostN) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new WildcardQuery +{ + Name = "named_query", + Boost = 1.1, + Field = "description", + Value = "p*oj", + Rewrite = RewriteMultiTerm.TopTermsBoostN +} +---- + +[source,javascript] +.Example json output +---- +{ + "wildcard": { + "description": { + "_name": "named_query", + "boost": 1.1, + "rewrite": "top_terms_boost_N", + "value": "p*oj" + } + } +} +---- + diff --git a/docs/asciidoc/search/request/explain-usage.asciidoc b/docs/asciidoc/search/request/explain-usage.asciidoc new file mode 100644 index 00000000000..fb132480708 --- /dev/null +++ b/docs/asciidoc/search/request/explain-usage.asciidoc @@ -0,0 +1,34 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[explain-usage]] +== Explain Usage + +Enables explanation for each hit on how its score was computed. + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Explain() +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest { Explain = true } +---- + +[source,javascript] +.Example json output +---- +{ + "explain": true +} +---- + diff --git a/docs/asciidoc/search/request/fielddata-fields-usage.asciidoc b/docs/asciidoc/search/request/fielddata-fields-usage.asciidoc new file mode 100644 index 00000000000..8b266c42f30 --- /dev/null +++ b/docs/asciidoc/search/request/fielddata-fields-usage.asciidoc @@ -0,0 +1,43 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[fielddata-fields-usage]] +== Fielddata Fields Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.FielddataFields(fs => fs + .Field(p => p.Name) + .Field(p => p.LeadDeveloper) + .Field(p => p.StartedOn) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + FielddataFields = new string [] { "name", "leadDeveloper", "startedOn" } +} +---- + +[source,javascript] +.Example json output +---- +{ + "fielddata_fields": [ + "name", + "leadDeveloper", + "startedOn" + ] +} +---- + diff --git a/docs/asciidoc/search/request/fields-usage.asciidoc b/docs/asciidoc/search/request/fields-usage.asciidoc new file mode 100644 index 00000000000..bef2edea17a --- /dev/null +++ b/docs/asciidoc/search/request/fields-usage.asciidoc @@ -0,0 +1,41 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[fields-usage]] +== Fields Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Fields(fs => fs + .Field(p => p.Name) + .Field(p => p.StartedOn) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Fields = Fields(p => p.Name, p => p.StartedOn) +} +---- + +[source,javascript] +.Example json output +---- +{ + "fields": [ + "name", + "startedOn" + ] +} +---- + diff --git a/docs/asciidoc/search/request/from-and-size-usage.asciidoc b/docs/asciidoc/search/request/from-and-size-usage.asciidoc new file mode 100644 index 00000000000..c9569d7154f --- /dev/null +++ b/docs/asciidoc/search/request/from-and-size-usage.asciidoc @@ -0,0 +1,38 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[from-and-size-usage]] +== From And Size Usage + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + From = 10, + Size = 12 +} +---- + +[source,javascript] +.Example json output +---- +{ + "from": 10, + "size": 12 +} +---- + +=== Fluent DSL Example + +[source,csharp] +---- +s => s + .From(10) + .Size(12) +---- + diff --git a/docs/asciidoc/search/request/highlighting-usage.asciidoc b/docs/asciidoc/search/request/highlighting-usage.asciidoc new file mode 100644 index 00000000000..d6d248856ea --- /dev/null +++ b/docs/asciidoc/search/request/highlighting-usage.asciidoc @@ -0,0 +1,193 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[highlighting-usage]] +== Highlighting Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Query(q => q + .Match(m => m + .Field(f => f.Name.Suffix("standard")) + .Query("Upton Sons Shield Rice Rowe Roberts") + ) +) +.Highlight(h => h + .PreTags("") + .PostTags("") + .Fields( + fs => fs + .Field(p => p.Name.Suffix("standard")) + .Type(HighlighterType.Plain) + .ForceSource() + .FragmentSize(150) + .NumberOfFragments(3) + .NoMatchSize(150), + fs => fs + .Field(p => p.LeadDeveloper.FirstName) + .Type(HighlighterType.Fvh) + .PreTags("") + .PostTags("") + .HighlightQuery(q => q + .Match(m => m + .Field(p => p.LeadDeveloper.FirstName) + .Query("Kurt Edgardo Naomi Dariana Justice Felton") + ) + ), + fs => fs + .Field(p => p.State.Suffix("offsets")) + .Type(HighlighterType.Postings) + .PreTags("") + .PostTags("") + .HighlightQuery(q => q + .Terms(t => t + .Field(f => f.State.Suffix("offsets")) + .Terms( + StateOfBeing.Stable.ToString().ToLowerInvariant(), + StateOfBeing.BellyUp.ToString().ToLowerInvariant() + ) + ) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Query = new MatchQuery + { + Query = "Upton Sons Shield Rice Rowe Roberts", + Field = "name.standard" + }, + Highlight = new Highlight + { + PreTags = new[] { "" }, + PostTags = new[] { "" }, + Fields = new Dictionary + { + { "name.standard", new HighlightField + { + Type = HighlighterType.Plain, + ForceSource = true, + FragmentSize = 150, + NumberOfFragments = 3, + NoMatchSize = 150 + } + }, + { "leadDeveloper.firstName", new HighlightField + { + Type = HighlighterType.Fvh, + PreTags = new[] { ""}, + PostTags = new[] { ""}, + HighlightQuery = new MatchQuery + { + Field = "leadDeveloper.firstName", + Query = "Kurt Edgardo Naomi Dariana Justice Felton" + } + } + }, + { "state.offsets", new HighlightField + { + Type = HighlighterType.Postings, + PreTags = new[] { ""}, + PostTags = new[] { ""}, + HighlightQuery = new TermsQuery + { + Field = "state.offsets", + Terms = new [] { "stable", "bellyup" } + } + } + } + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "query": { + "match": { + "name.standard": { + "query": "Upton Sons Shield Rice Rowe Roberts" + } + } + }, + "highlight": { + "pre_tags": [ + "" + ], + "post_tags": [ + "" + ], + "fields": { + "name.standard": { + "type": "plain", + "force_source": true, + "fragment_size": 150, + "number_of_fragments": 3, + "no_match_size": 150 + }, + "leadDeveloper.firstName": { + "type": "fvh", + "pre_tags": [ + "" + ], + "post_tags": [ + "" + ], + "highlight_query": { + "match": { + "leadDeveloper.firstName": { + "query": "Kurt Edgardo Naomi Dariana Justice Felton" + } + } + } + }, + "state.offsets": { + "type": "postings", + "pre_tags": [ + "" + ], + "post_tags": [ + "" + ], + "highlight_query": { + "terms": { + "state.offsets": [ + "stable", + "bellyup" + ] + } + } + } + } + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +highlight.Should().Contain(""); +highlight.Should().Contain(""); +highlight.Should().Contain(""); +highlight.Should().Contain(""); +highlight.Should().Contain(""); +highlight.Should().Contain(""); +Assert.True(false, $"highlights contains unexpected key {highlightHit.Key}"); +---- + diff --git a/docs/asciidoc/search/request/index-boost-usage.asciidoc b/docs/asciidoc/search/request/index-boost-usage.asciidoc new file mode 100644 index 00000000000..f872e339457 --- /dev/null +++ b/docs/asciidoc/search/request/index-boost-usage.asciidoc @@ -0,0 +1,45 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[index-boost-usage]] +== Index Boost Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.IndicesBoost(b => b + .Add("index1", 1.4) + .Add("index2", 1.3) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + IndicesBoost = new Dictionary + { + { "index1", 1.4 }, + { "index2", 1.3 } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "indices_boost": { + "index1": 1.4, + "index2": 1.3 + } +} +---- + diff --git a/docs/asciidoc/search/request/inner-hits-usage.asciidoc b/docs/asciidoc/search/request/inner-hits-usage.asciidoc new file mode 100644 index 00000000000..e5fc2e80b7c --- /dev/null +++ b/docs/asciidoc/search/request/inner-hits-usage.asciidoc @@ -0,0 +1,211 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[inner-hits-usage]] +== Inner Hits Usage + +[source,csharp] +---- +public interface IRoyal +{ + string Name { get; set; } +} +---- + +[source,csharp] +---- +var current = create(); + +var royals = current.ToList(); + +var royal1 = royal; + +bulk.Index(i => i.Document(royal1).Index(this._index).Parent(parent)); + +indexChildren(royal); +---- + +[source,csharp] +---- +var create = this._client.CreateIndex(this._index, c => c + .Settings(s => s + .NumberOfReplicas(0) + .NumberOfShards(1) + ) + .Mappings(map => map + .Map(m => m.AutoMap() + .Properties(props => + RoyalProps(props) + .Nested(n => n.Name(p => p.Foes).AutoMap()) + ) + ) + .Map(m => m.AutoMap().Properties(RoyalProps).Parent()) + .Map(m => m.AutoMap().Properties(RoyalProps).Parent()) + .Map(m => m.AutoMap().Properties(RoyalProps).Parent()) + .Map(m => m.AutoMap().Properties(RoyalProps).Parent()) + ) +); +var kings = King.Generator.Generate(2) + .Select(k => + { + k.Foes = King.Generator.Generate(2).ToList(); + return k; + }); +var bulk = new BulkDescriptor(); +IndexAll(bulk, () => kings, indexChildren: king => + IndexAll(bulk, () => Prince.Generator.Generate(2), king.Name, prince => + IndexAll(bulk, () => Duke.Generator.Generate(3), prince.Name, duke => + IndexAll(bulk, () => Earl.Generator.Generate(5), duke.Name, earl => + IndexAll(bulk, () => Baron.Generator.Generate(1), earl.Name) + ) + ) + ) +); +this._client.Bulk(bulk); +this._client.Refresh(this._index); +---- + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Index(Index) +.InnerHits(ih => ih + .Type("earls", g => g + .Size(5) + .InnerHits(iih => iih + .Type("barons") + ) + .FielddataFields(p => p.Name) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest(Index, typeof(Duke)) +{ + InnerHits = new NamedInnerHits + { + { "earls", new InnerHitsContainer + { + Type = new TypeInnerHit + { + InnerHit = new GlobalInnerHit + { + Size = 5, + FielddataFields = new Field[]{ "name" }, + InnerHits = new NamedInnerHits + { + { "barons", new TypeInnerHit() } + } + } + } + } } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "inner_hits": { + "earls": { + "type": { + "earl": { + "fielddata_fields": [ + "name" + ], + "inner_hits": { + "barons": { + "type": { + "baron": {} + } + } + }, + "size": 5 + } + } + } + } +} +---- + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Index(Index) +.Query(q => + q.HasChild(hc => hc + .Query(hcq => hcq.MatchAll()) + .InnerHits(ih => ih.Name("princes")) + ) || q.Nested(n => n + .Path(p => p.Foes) + .Query(nq => nq.MatchAll()) + .InnerHits() + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest(Index, typeof(King)) +{ + Query = new HasChildQuery + { + Type = typeof(Prince), + Query = new MatchAllQuery(), + InnerHits = new InnerHits { Name = "princes" } + } || new NestedQuery + { + Path = Field(p => p.Foes), + Query = new MatchAllQuery(), + InnerHits = new InnerHits() + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "query": { + "bool": { + "should": [ + { + "has_child": { + "type": "prince", + "query": { + "match_all": {} + }, + "inner_hits": { + "name": "princes" + } + } + }, + { + "nested": { + "query": { + "match_all": {} + }, + "path": "foes", + "inner_hits": {} + } + } + ] + } + } +} +---- + diff --git a/docs/asciidoc/search/request/min-score-usage.asciidoc b/docs/asciidoc/search/request/min-score-usage.asciidoc new file mode 100644 index 00000000000..7be2b54260d --- /dev/null +++ b/docs/asciidoc/search/request/min-score-usage.asciidoc @@ -0,0 +1,50 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[min-score-usage]] +== Min Score Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.MinScore(0.5) +.Query(q => q + .Term(p => p.Name, "elasticsearch") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + MinScore = 0.5, + Query = new TermQuery + { + Field = "name", + Value = "elasticsearch" + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "min_score": 0.5, + "query": { + "term": { + "name": { + "value": "elasticsearch" + } + } + } +} +---- + diff --git a/docs/asciidoc/search/request/post-filter-usage.asciidoc b/docs/asciidoc/search/request/post-filter-usage.asciidoc new file mode 100644 index 00000000000..66a0dee434b --- /dev/null +++ b/docs/asciidoc/search/request/post-filter-usage.asciidoc @@ -0,0 +1,37 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[post-filter-usage]] +== Post Filter Usage + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest() +{ + PostFilter = new QueryContainer(new MatchAllQuery()) +} +---- + +[source,javascript] +.Example json output +---- +{ + "post_filter": { + "match_all": {} + } +} +---- + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.PostFilter(f => f.MatchAll()) +---- + diff --git a/docs/asciidoc/search/request/profile-usage.asciidoc b/docs/asciidoc/search/request/profile-usage.asciidoc new file mode 100644 index 00000000000..505d44c60c2 --- /dev/null +++ b/docs/asciidoc/search/request/profile-usage.asciidoc @@ -0,0 +1,50 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[profile-usage]] +== Profile Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Profile() +.Query(q => q + .Term(p => p.Name, "elasticsearch") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Profile = true, + Query = new TermQuery + { + Field = "name", + Value = "elasticsearch" + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "profile": true, + "query": { + "term": { + "name": { + "value": "elasticsearch" + } + } + } +} +---- + diff --git a/docs/asciidoc/search/request/query-usage.asciidoc b/docs/asciidoc/search/request/query-usage.asciidoc new file mode 100644 index 00000000000..4b507e99e59 --- /dev/null +++ b/docs/asciidoc/search/request/query-usage.asciidoc @@ -0,0 +1,49 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[query-usage]] +== Query Usage + +The query element within the search request body allows to define a query using the Query DSL. + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Query(q => q + .Term(p => p.Name, "elasticsearch") +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Query = new TermQuery + { + Field = "name", + Value = "elasticsearch" + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "query": { + "term": { + "name": { + "value": "elasticsearch" + } + } + } +} +---- + diff --git a/docs/asciidoc/search/request/script-fields-usage.asciidoc b/docs/asciidoc/search/request/script-fields-usage.asciidoc new file mode 100644 index 00000000000..9f38f7add9a --- /dev/null +++ b/docs/asciidoc/search/request/script-fields-usage.asciidoc @@ -0,0 +1,72 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[script-fields-usage]] +== Script Fields Usage + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.ScriptFields(sf=>sf + .ScriptField("test1", sc=>sc + .Inline("doc['my_field_name'].value * 2") + ) + .ScriptField("test2", sc=>sc + .Inline("doc['my_field_name'].value * factor") + .Params(p=>p + .Add("factor", 2.0) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + ScriptFields = new ScriptFields + { + { "test1", new ScriptField + { + Script = new InlineScript("doc['my_field_name'].value * 2") + } }, + { "test2", new InlineScript("doc['my_field_name'].value * factor") + { + Params = new FluentDictionary + { + { "factor", 2.0 } + } + } } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "script_fields": { + "test1": { + "script": { + "inline": "doc['my_field_name'].value * 2" + } + }, + "test2": { + "script": { + "inline": "doc['my_field_name'].value * factor", + "params": { + "factor": 2.0 + } + } + } + } +} +---- + diff --git a/docs/asciidoc/search/request/sort-usage.asciidoc b/docs/asciidoc/search/request/sort-usage.asciidoc new file mode 100644 index 00000000000..15592c3f987 --- /dev/null +++ b/docs/asciidoc/search/request/sort-usage.asciidoc @@ -0,0 +1,167 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[sort-usage]] +== Sort Usage + +Allows to add one or more sort on specific fields. Each sort can be reversed as well. +The sort is defined on a per field level, with special field name for _score to sort by score. + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Sort(ss => ss + .Ascending(p => p.StartedOn) + .Descending(p => p.Name) + .Descending(SortSpecialField.Score) + .Ascending(SortSpecialField.DocumentIndexOrder) + .Field(f => f + .Field(p => p.LastActivity) + .Order(SortOrder.Descending) + .MissingLast() + .UnmappedType(FieldType.Date) + .Mode(SortMode.Average) + .NestedPath(p => p.Tags) + .NestedFilter(q => q.MatchAll()) + ) + .GeoDistance(g => g + .Field(p => p.Location) + .DistanceType(GeoDistanceType.Arc) + .Order(SortOrder.Ascending) + .Unit(DistanceUnit.Centimeters) + .Mode(SortMode.Min) + .PinTo(new GeoLocation(70, -70), new GeoLocation(-12, 12)) + ) + .Script(sc => sc + .Type("number") + .Ascending() + .Script(script => script + .Inline("doc['numberOfCommits'].value * factor") + .Params(p => p.Add("factor", 1.1)) + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Sort = new List + { + new SortField { Field = "startedOn", Order = SortOrder.Ascending }, + new SortField { Field = "name", Order = SortOrder.Descending }, + new SortField { Field = "_score", Order = SortOrder.Descending }, + new SortField { Field = "_doc", Order = SortOrder.Ascending }, + new SortField { + Field = Field(p=>p.LastActivity), + Order = SortOrder.Descending, + Missing = "_last", + UnmappedType = FieldType.Date, + Mode = SortMode.Average, + NestedPath = Field(p=>p.Tags), + NestedFilter = new MatchAllQuery(), + }, + new GeoDistanceSort + { + Field = "location", + Order = SortOrder.Ascending, + DistanceType = GeoDistanceType.Arc, + GeoUnit = DistanceUnit.Centimeters, + Mode = SortMode.Min, + Points = new [] {new GeoLocation(70, -70), new GeoLocation(-12, 12) } + }, + new ScriptSort + { + Type = "number", + Order = SortOrder.Ascending, + Script = new InlineScript("doc['numberOfCommits'].value * factor") + { + Params = new Dictionary + { + { "factor", 1.1 } + } + } + } + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "sort": [ + { + "startedOn": { + "order": "asc" + } + }, + { + "name": { + "order": "desc" + } + }, + { + "_score": { + "order": "desc" + } + }, + { + "_doc": { + "order": "asc" + } + }, + { + "lastActivity": { + "missing": "_last", + "order": "desc", + "mode": "avg", + "nested_filter": { + "match_all": {} + }, + "nested_path": "tags", + "unmapped_type": "date" + } + }, + { + "_geo_distance": { + "location": [ + { + "lat": 70.0, + "lon": -70.0 + }, + { + "lat": -12.0, + "lon": 12.0 + } + ], + "order": "asc", + "mode": "min", + "distance_type": "arc", + "unit": "cm" + } + }, + { + "_script": { + "order": "asc", + "type": "number", + "script": { + "params": { + "factor": 1.1 + }, + "inline": "doc['numberOfCommits'].value * factor" + } + } + } + ] +} +---- + diff --git a/docs/asciidoc/search/request/source-filtering-usage.asciidoc b/docs/asciidoc/search/request/source-filtering-usage.asciidoc new file mode 100644 index 00000000000..dc8d099b34d --- /dev/null +++ b/docs/asciidoc/search/request/source-filtering-usage.asciidoc @@ -0,0 +1,119 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[source-filtering-usage]] +== Source Filtering Usage + +Allows to control how the _source field is returned with every hit. +By default operations return the contents of the _source field unless + you have used the fields parameter or if the _source field is disabled. + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Source(so => so + .Include(f => f + .Fields( + p => p.Name, + p => p.StartedOn + ) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Source = new SourceFilter + { + Include = Fields(p => p.Name, prop => prop.StartedOn) + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "_source": { + "include": [ + "name", + "startedOn" + ] + } +} +---- + +=== Handling Responses + +[source,csharp] +---- +response.IsValid.Should().BeTrue(); +document.Name.Should().NotBeNull(); +document.StartedOn.Should().NotBe(default(DateTime)); +document.Description.Should().BeNull(); +---- + +[source,csharp] +---- +internal class WithSourceFilterProperty +{ + [JsonProperty("_source")] + public ISourceFilter SourceFilter { get; set; } +} +---- + +[source,csharp] +---- +var o = base.Deserialize("{ \"_source\": false }"); + +o.Should().NotBeNull(); + +o.SourceFilter.Should().NotBeNull(); + +o.SourceFilter.Exclude.Should().Contain("*"); +---- + +[source,csharp] +---- +var o = base.Deserialize("{ \"_source\": [\"obj.*\"] }"); + +o.Should().NotBeNull(); + +o.SourceFilter.Should().NotBeNull(); + +o.SourceFilter.Include.Should().Contain("obj.*"); +---- + +[source,csharp] +---- +var o = base.Deserialize("{ \"_source\": \"obj.*\" }"); + +o.Should().NotBeNull(); + +o.SourceFilter.Should().NotBeNull(); + +o.SourceFilter.Include.Should().Contain("obj.*"); +---- + +[source,csharp] +---- +var o = base.Deserialize("{ \"_source\": { \"include\": [\"obj.*\"], \"exclude\": [\"foo.*\"] } }"); + +o.Should().NotBeNull(); + +o.SourceFilter.Should().NotBeNull(); + +o.SourceFilter.Include.Should().Contain("obj.*"); + +o.SourceFilter.Exclude.Should().Contain("foo.*"); +---- + diff --git a/docs/asciidoc/search/request/suggest-usage.asciidoc b/docs/asciidoc/search/request/suggest-usage.asciidoc new file mode 100644 index 00000000000..0519601c49c --- /dev/null +++ b/docs/asciidoc/search/request/suggest-usage.asciidoc @@ -0,0 +1,230 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[suggest-usage]] +== Suggest Usage + +Allows to add one or more sort on specific fields. Each sort can be reversed as well. +The sort is defined on a per field level, with special field name for _score to sort by score. + +=== Handling Responses + +[source,csharp] +---- +var myCompletionSuggest = response.Suggest["my-completion-suggest"]; +myCompletionSuggest.Should().NotBeNull(); +var suggest = myCompletionSuggest.First(); +suggest.Text.Should().Be(Project.Instance.Name); +suggest.Length.Should().BeGreaterThan(0); +var option = suggest.Options.First(); +option.Text.Should().NotBeNullOrEmpty(); +option.Score.Should().BeGreaterThan(0); +var payload = option.Payload(); +payload.Should().NotBeNull(); +payload.Name.Should().Be(Project.Instance.Name); +payload.State.Should().NotBeNull(); +---- + +=== Fluent DSL Example + +[source,csharp] +---- +s => s +.Suggest(ss => ss + .Term("my-term-suggest", t => t + .MaxEdits(1) + .MaxInspections(2) + .MaxTermFrequency(3) + .MinDocFrequency(4) + .MinWordLength(5) + .PrefixLength(6) + .SuggestMode(SuggestMode.Always) + .Analyzer("standard") + .Field(p => p.Name) + .ShardSize(7) + .Size(8) + .Text("hello world") + ) + .Completion("my-completion-suggest", c => c + .Context(ctx => ctx + .Add("color", Project.Projects.First().Suggest.Context.Values.SelectMany(v => v).First()) + ) + .Fuzzy(f => f + .Fuzziness(Fuzziness.Auto) + .MinLength(1) + .PrefixLength(2) + .Transpositions() + .UnicodeAware(false) + ) + .Analyzer("simple") + .Field(p => p.Suggest) + .ShardSize(7) + .Size(8) + .Text(Project.Instance.Name) + ) + .Phrase("my-phrase-suggest", ph => ph + .Collate(c => c + .Query(q => q + .Inline("{ \"match\": { \"{{field_name}}\": \"{{suggestion}}\" }}") + .Params(p => p.Add("field_name", "title")) + ) + .Prune() + ) + .Confidence(10.1) + .DirectGenerator(d => d + .Field(p => p.Description) + ) + .GramSize(1) + .Field(p => p.Name) + .Text("hello world") + .RealWordErrorLikelihood(0.5) + ) +) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SearchRequest +{ + Suggest = new SuggestContainer + { + { "my-term-suggest", new SuggestBucket + { + Text = "hello world", + Term = new TermSuggester + { + MaxEdits = 1, + MaxInspections = 2, + MaxTermFrequency = 3, + MinDocFrequency = 4, + MinWordLen = 5, + PrefixLen = 6, + SuggestMode = SuggestMode.Always, + Analyzer = "standard", + Field = Field(p=>p.Name), + ShardSize = 7, + Size = 8 + } + } }, + { "my-completion-suggest", new SuggestBucket + { + Text = Project.Instance.Name, + Completion = new CompletionSuggester + { + Context = new Dictionary { { "color", Project.Projects.First().Suggest.Context.Values.SelectMany(v => v).First() } }, + Fuzzy = new FuzzySuggester + { + Fuzziness = Fuzziness.Auto, + MinLength = 1, + PrefixLength = 2, + Transpositions = true, + UnicodeAware = false + }, + Analyzer = "simple", + Field = Field(p=>p.Suggest), + ShardSize = 7, + Size = 8 + } + } }, + { "my-phrase-suggest", new SuggestBucket + { + Text = "hello world", + Phrase = new PhraseSuggester + { + Collate = new PhraseSuggestCollate + { + Query = new InlineScript("{ \"match\": { \"{{field_name}}\": \"{{suggestion}}\" }}") + { + Params = new Dictionary + { + { "field_name", "title" } + } + }, + Prune = true + }, + Confidence = 10.1, + DirectGenerator = new List + { + new DirectGenerator { Field = "description" } + }, + GramSize = 1, + Field = "name", + RealWordErrorLikelihood = 0.5 + } + } }, + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "suggest": { + "my-completion-suggest": { + "completion": { + "analyzer": "simple", + "context": { + "color": "red" + }, + "field": "suggest", + "fuzzy": { + "fuzziness": "AUTO", + "min_length": 1, + "prefix_length": 2, + "transpositions": true, + "unicode_aware": false + }, + "shard_size": 7, + "size": 8 + }, + "text": "Durgan LLC" + }, + "my-phrase-suggest": { + "phrase": { + "collate": { + "query": { + "inline": "{ \"match\": { \"{{field_name}}\": \"{{suggestion}}\" }}", + "params": { + "field_name": "title" + } + }, + "prune": true + }, + "confidence": 10.1, + "direct_generator": [ + { + "field": "description" + } + ], + "field": "name", + "gram_size": 1, + "real_word_error_likelihood": 0.5 + }, + "text": "hello world" + }, + "my-term-suggest": { + "term": { + "analyzer": "standard", + "field": "name", + "max_edits": 1, + "max_inspections": 2, + "max_term_freq": 3.0, + "min_doc_freq": 4.0, + "min_word_len": 5, + "prefix_len": 6, + "shard_size": 7, + "size": 8, + "suggest_mode": "always" + }, + "text": "hello world" + } + } +} +---- + diff --git a/docs/asciidoc/search/suggesters/suggest-api.asciidoc b/docs/asciidoc/search/suggesters/suggest-api.asciidoc new file mode 100644 index 00000000000..d1832c9443c --- /dev/null +++ b/docs/asciidoc/search/suggesters/suggest-api.asciidoc @@ -0,0 +1,164 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/current + +:github: https://github.com/elastic/elasticsearch-net + +:nuget: https://www.nuget.org/packages + +[[suggest-api]] +== Suggest API + +=== Fluent DSL Example + +=== Fluent DSL Example + +[source,csharp] +---- +s => s + .Term("my-term-suggest", t => t + .MaxEdits(1) + .MaxInspections(2) + .MaxTermFrequency(3) + .MinDocFrequency(4) + .MinWordLength(5) + .PrefixLength(6) + .SuggestMode(SuggestMode.Always) + .Analyzer("standard") + .Field(p => p.Name) + .ShardSize(7) + .Size(8) + .Text("hello world") + ) + .Completion("my-completion-suggest", c => c + .Context(ctx => ctx + .Add("color", Project.Projects.First().Suggest.Context.Values.SelectMany(v => v).First()) + ) + .Fuzzy(f => f + .Fuzziness(Fuzziness.Auto) + .MinLength(1) + .PrefixLength(2) + .Transpositions() + .UnicodeAware(false) + ) + .Analyzer("simple") + .Field(p => p.Suggest) + .ShardSize(7) + .Size(8) + .Text(Project.Instance.Name) + ) + .Phrase("my-phrase-suggest", ph => ph + .Collate(c => c + .Query(q => q + .Inline("{ \"match\": { \"{{field_name}}\": \"{{suggestion}}\" }}") + .Params(p => p.Add("field_name", "title")) + ) + .Prune() + ) + .Confidence(10.1) + .DirectGenerator(d => d + .Field(p => p.Description) + ) + .GramSize(1) + .Field(p => p.Name) + .Text("hello world") + .RealWordErrorLikelihood(0.5) + ) +---- + +=== Object Initializer Syntax Example + +[source,csharp] +---- +new SuggestRequest +{ + Suggest = new SuggestContainer + { + { "my-term-suggest", new SuggestBucket + { + Text = "hello world", + Term = new TermSuggester + { + MaxEdits = 1, + MaxInspections = 2, + MaxTermFrequency = 3, + MinDocFrequency = 4, + MinWordLen = 5, + PrefixLen = 6, + SuggestMode = SuggestMode.Always, + Analyzer = "standard", + Field = Field(p=>p.Name), + ShardSize = 7, + Size = 8 + } + } }, + { "my-completion-suggest", new SuggestBucket + { + Text = Project.Instance.Name, + Completion = new CompletionSuggester + { + Context = new Dictionary { { "color", Project.Projects.First().Suggest.Context.Values.SelectMany(v => v).First() } }, + Fuzzy = new FuzzySuggester + { + Fuzziness = Fuzziness.Auto, + MinLength = 1, + PrefixLength = 2, + Transpositions = true, + UnicodeAware = false + }, + Analyzer = "simple", + Field = Field(p=>p.Suggest), + ShardSize = 7, + Size = 8 + } + } }, + { "my-phrase-suggest", new SuggestBucket + { + Text = "hello world", + Phrase = new PhraseSuggester + { + Collate = new PhraseSuggestCollate + { + Query = new InlineScript("{ \"match\": { \"{{field_name}}\": \"{{suggestion}}\" }}") + { + Params = new Dictionary + { + { "field_name", "title" } + } + }, + Prune = true + }, + Confidence = 10.1, + DirectGenerator = new List + { + new DirectGenerator { Field = "description" } + }, + GramSize = 1, + Field = "name", + RealWordErrorLikelihood = 0.5 + } + } }, + } +} +---- + +=== Handling Responses + +Get the suggestions for a suggester by indexing into +the `.Suggestions` on the response + +[source,csharp] +---- +var myCompletionSuggest = response.Suggestions["my-completion-suggest"]; + +myCompletionSuggest.Should().NotBeNull(); +var suggest = myCompletionSuggest.First(); +suggest.Text.Should().Be(Project.Instance.Name); +suggest.Length.Should().BeGreaterThan(0); +var option = suggest.Options.First(); +option.Text.Should().NotBeNullOrEmpty(); +option.Score.Should().BeGreaterThan(0); +var payload = option.Payload(); +payload.Should().NotBeNull(); +payload.Name.Should().Be(Project.Instance.Name); +payload.State.Should().NotBeNull(); +---- + diff --git a/docs/asciidoc/timeoutplot.png b/docs/asciidoc/timeoutplot.png new file mode 100644 index 0000000000000000000000000000000000000000..ceb819bff0b789818ffdafb129d5c63735ea138a GIT binary patch literal 10018 zcmeHt2Uk=}ujKdcj3TH=?$inxloA z=S>%LoTsNJx3z<n-p_P91`9 zOyqygPo!qvu})`jxj)3ZZA`Soe>)*L8n@+_`;&F1TBN4g*I%>xYqaYLsyzoY!$U&a z9Yx}mMBWH%GpI=l7h^ zZ`TRs;N*P5zMGo*xRMDipYfiXHdcH>*gP(XO}({3(kV!}o-hF@0s z@>X%c_L?R0;qzC%= zRQzg=*K^EhAAiz5-jS{t`y@I#qoYGTEj`_4cCcF1XEAeoA-a}u@gnKu$&4@dd&D60OBy)9(se$=84 zl9=rx)kDUwTU*`6w2fCpC8z>3+Ou?uECjq%${YsxO+SS8*4F-R7L!v}E*M}qb}SS5 zs$w!<&BTPlz@^I$cb~j=uUrfn+5d4zKalOxCDjP=g&Xnq2N*b26cj=)UAly4Wi8-P zOE2eSgS#57R7s(sp}9p(-<$}Nl7{p1^V|mzdKRtbTf(3JMuh+G0CDHTiN%qK%QnA% zHHrGIzF1$H%owcp?G`)B!=pUp_dD#(+qa!%jz-bcyK%TH4K^K&?-}_lthP7TbSpjb z&ML)aUdS)M3gA$(5e=y->Az0k;E;88c9v66aMLq}m?#dFjwktTOqWe=N{EW;I#$i! zV+mNj`Si&X!pW0TpFVwJ5^{)hadE*jGv`sWcwM=9^QOZ-8W^}DHtLJd$;r{s&`8tF z)0K@Bbm-4~d`{V>Ba;-F`tjop_t`-M4i1idMX2aO44q??)lcUVt=`<(H_??BD;>s^ zCN^887WpJGv0!e_sl;KRV)<)kCV?K0FPG$k`E?aiLQK9SNVretl})6_I<%4LFFbkr z^o;CzE-npYW8-*JDk>@sP0e4usc^RyY5TaKoyyg#SF4$oGSrkZik4@kq@w0(Xc3M&r+s|LWvXhdo1O#OGT54)WNk_2cy?OJ7!S}SHhPpbttl__% z2K^Gjo5f`v* zy&W7(bM9h&Jl{tEC;*h^iVq5=%)9_OHyZ0^W^4Dj8t-WvXXKw4aXzc?ENOAkEneJP z6`=6*$B!!N>gv3&5C!h_)ZtydXHe;prBz^T%R%Yz61?(Z8($9w=xpnIn_T*@I^3PJoq2`|JIGi7jc!;}x>md%eitcmR zSf0jiqDz9Cgj4q7f*j#fT6Rv()7aPsMxbNZh!V#kN%!$K;&fk`5Hn;f{}Gr5N{^s{ zd#484N!(F1Y}M)n44{VJU~~>7Mf*kAoKz_9JL}{Qibt)zsBfq3$9!CwZH) zHyW~)bp86B=F2;N?WF%0aY@W$O2y5sc>EY}K_cwP^W0ocY{VTH(Nk)F+-QIr7etp# zcw=5zSh#u+*}4}de;K$F01;0R7XIpn$Ki%g!3MnpSCwA|KFNBGbVptV_~V=eTn1Zk zxFK}O%-noDJ174Vik|X+h=-ayzQ5bIwK@_p(U}vCoKkm}K(;Cc6AZk6K3({2DzXbV zEWdhLEGBH@2+XF4C-NW1X1h+hUSvN|@f6r;5FLd;(UFsrD|Q`KS{iS!$LukITjhZX zLE?5)xZ7e2hB#^E)GKq)*xBB~g3jRzOk-wdHa3w{Acz^Y3ydmspVY**fJ}LCHU|oN zbG#S_@Z(hi`BaQueK<2hsK2PF6lH)QqbVMzWn}zjI{@1p3M1r|m9?PyVmqn`%PDMZ zR9Kqq{^E8Jw&^UGB@0kcv$d>;c{K&RDwIs1$QBkAbrxAFmsq!dz!re++_!S>FWm43 zOcL1?D`Z<2vV&|Nrqm+m=!G6B@*v~Wv>v-@g3>kmL64zt+~`XX15qbeJT3+@>!kvG!C11P0z8I=a-FU?W`rZGiy0_eX_Jpl_#re)wuY_(K z<(-k40_)wS~yFz-$=)csdg@^@~SUB=2 zCUu}dP`<6kT9UC`qJn&GS)T3#W^feX{yoZAW2hK3a*B#SF|SLLt_Q2CslA$lED9sx zZ)Rm@%Rv-npe!@R6SaG5{42R%{Wz=_=nl~8Aw#;8CScoT_4UO~3?G6CjG^ed$BDYjK~i@nNd+q)0K7-SlVogcve%cVb08qG>2JdH^Wzzb zHa|Z8i&;bmD9N0oRgjJ}2U$FEQ%*|iRyd19_Sl#eHi8;pp$n>jfZbPKkrTB4^BMy} zz#yX(!qrH$jcAq7WnuQxNs;cbTSLX$PeY?&WtEB5k83LUljDn94}}K3QZNSu!v)#( z?;$)`Vd{V};U`2WP!>UA`SX-y!qu#-Eab7xtt|}GS)lg$Z?BCa?uAYARt0aLpPQ>N z*_GE`(rsvqU|^0HPmI;84X8eK>Qu)oS4(WN`^kh}eL1=P(eb;GjzVejU;TEUNzm>Y zHVqXCZ_jz*f;=!K|MkU-D1^M&$doYu&H4I@pHX@WhzPNeDORRHjG6c>{?w3bFlu7> zbuj8tzkmOZZ92M1?SfW5km;0FK`8hwFppxM{wr0WfC4BKy?DtKnytll<@H~&j&5&h zgq|S|C4P+9dQUT7KkMe(ds)zo|Jg`sQgJe!UfSf2&aZGn9R;QsnwnH1Th8ab`6OViEY^@0RtvW%P z4`Q`xH-Rj;&@47P7A`h}@kp zg?e!b=3oC3xSirV^GaqkF|Y=+OMad=gf2TXv!yLf9@-mGEPjrTgi`3%$5J9Xad_i35=U4GmE5bx@%HT!fJ$eE0+tSq2qk@%jOrOy`%ta^*BU4&0yVF<%0UPJyX z<6mEz#z^>jRQp3?DtF_Cii=AjbdYI42*!R!8+9c}`0BGTbS+Qyq(B6Z&s5F|B2TP7 zlBR#((xOsg+uiIk^daoH`@#>|FeU-HNIWBB7Qps}GiTnrb2v-?9{tjNjZPvJ8ofX3 zwqC9u`7t_rL0nuvPp>S0fPtPq4NBy%8h)m48HoX^s;ZIDQvBJu^d4o2=%}dkyu2@( zOaIhgft9q@y#>|FF&{!1Q$l!Z{_tVbO(DClNq6qt@eu6iZ-v08=W)akG4Q*}h zN-hH71_a1?0^w2O(D>l}*X$Y*81Gxq6FtmW|e z#vo|kpwf8+?g%`i;(Mg@C8@^}pOTWI0a9MI?~0tLfuYCjVBYrDhL9LrvmNqqc(^8T zek?$!MJ$@*bR;}y`uSLjp{qb}VQp=VM827dNp7OFv<D$jmoHyJ zoaL4IzrMYj{O}=3TKeAikr4s&&-m!OcS_6~qEMgM#0||7j>cM@6bcr*xw!>zFw@?L z`{-F&XA6z3w9U=a68x8SxD2cQ?C)F9tayt3)?Du1xnngq{Q6G_bxnOCU2i}lT%3q& zvD3G!n1>@EtD<#E?Vh8WNy9AKfo0VQ$A?2HM|G;aUIMD) zj~=x|IrL&zGr{K}u;KCNmI)*|IWo4JXJPmm!+Oi-xbx*Rw7~1a4l7 z6|}#ELi6ihOMG=*u1=9#J+mi)ld};$-}p3SrW0z4LFh<{7xToLT)TUGWR@A2p!>11 zWL5}w#YgO3K0J|bxyLfm8dA;&s4o_bg4aolD(2?qK2tC2`5gzvp&fe`4$yD+fHIbB z==E(k0EpUYnQ(s020UGnR}uh-5@<#3LPLJ0*wQY-(vmo0{o?Uz?EC=-UJ?N|BYloU(grgIpxnC8~TpLPQ< zCc$KBq7x}i03g`JkHF%+^%1OEetuO9JchYgYY!H)J(T~#a&vug92Gt3&|^1|ZCALO z2~5jSD6ATyxOxlCL(sP4+*R;2qE5p>py;N9Qmk&@TYQn38rpeUZi(ZnBvNvpL!~0< z+z@Ghu0yFjS!B_qTIn&33`Z-$aXOFp1Ob;AYZ4k#x3EY*L3@837J`mJxTc`gR*WY2 zqF#dApaR>`7B+5)vZTn!!)Sqg7L6^BPF_=}hcYC5nrw2w2WmV$3rqgx?fD2S3gYOa z6ZAgH^Hiq-`?iHTgI(p{PkgyMSGPotr+P86t*s4z?3k4(C=XXsRPY0we0*vvLxHsb zHf}xU(a(_|Tdew1#-uk^X1l@6Tw(}=HEIAyz|OcMWJ>G_2_JRvZU+jKi>4niL!Ful zWQRE*abq8jJ9gaJcZA=4LcQG?hQ!>VjG1I8#kWCCO$9zI@w<@^n~R^3*m@U~DTF^c zzAH06KwlNn+N>DJl#q@m?;!uVbPBHr1cU<5@Dl0`J+N894mAHv{t8Qft`sMn z{O@U*iG>E9Zav59G*GR-xUs`Sqmu}N*MRBmp=03$y}H{dFrE+cye^}~F2)L&f{SQ; z48L_`Lg2PHW~6?2!JO@F!&=uprv{kD4V^L^vhkS8Cf6CD6#9gw6#*Z(}^*@!{De83PJZ{Q&Z=F=TKe) zcclVwPe6ru1sPygR?hl9+fTEz&sQ(D$uo(2yE)PL?Z^%Ky8jP>G3n>!v;Uz4Fl z%z?JCx5~SSyLQtBvoy4Di}C&U?m;17+snp)Zx-b8LPoJsk>y6Hg?h7vp7TOyB6 zbdZww?%j3Z_5j!fcJ}K)f#b3PQ)11Pyl?rhB>JrC*LQ2HenTdo14${cXK@9tE}`1K}p z72tZs7$t-sT3;lYHa@!uI=RzyfLJb74&wGD-2DAz^34IBT-Scn2#NIB+8x88z@2K) z^V}hM8Rhyf0e|54UNGa*E&k{d2?F4$+INtBJWOa% z#ofIGjow_HXHe+AgjPN^Wd~w60dN6WxE4t4$mrmrrYj3u?!3UNpp&>IgM7bMm27tWtw1kz;Fk|c$4 z!^G5d2?E->D_3U%m{;&+%Zvu4Lx&Enzh&J{g8s0hPYX~Lh_DsxG`5#kEo@Lk)g5cM zN^hmkxTZ61#_$w`%#YMh0xN!sPM~cU$666Dg+~VSH4S(Bc{2kOBJlF6L;~t%25hZ! z2nlIL`gh&xJP0Iyf_b!I@=7G_3E?CyLvP|25GdoPJ#?s5-}0>r7#UI1%&$|thU<21 z4ee|tB4Oq3fulCS96>}c0I7p%vnlDro4W@(OKi0ap!@ducK4_*;iV-y5kXQ9Bco0w z7w8YbE#qISTPVbfYUJsbsCs#oW2McxANYa-YvATf1XG_9CWp)TGH*fz{rLEtUkiuZ z^BBDg@bOUfa<6TSpnZ(w+K(e!YhMDR%nID+`WO`wC99i}Yp7ZMR022phB_ecMEjT)i@w1GUo@iZHO> z(qE0wQ&1DTyUjO!q&}kIo6}gNJa^Ur0|-|~&M6&J=v+X2h*{E48-*T={~~E1U}GB8 zdtT-S`*h{R`~y5yDj;|KYxV`1d1G0J8u&DlbU>feFfib{|Mui3!^xy%_(_}Ev3e;A zb~NRsX?<6pWwg6fI(FIey8!VehzE)O%g<-3R}AmoyJyo=XpTUemPI@>+R#TuJ#t%WuRqk zjw=Ud4~$MuCV>wlB$l7S@9BW%6GdMnAG7-}d^!hb%f;_Sg(1;~jHoR~T3O2t^!0O5 zjRCY%2Q~*x54Dx!?~fX}tzF$(8%?DD=bz!W?!~h}$~2?74W2xHoPy5DBGCspUOTR2 z5mk^MaxTH^Do7e#(MAC}qx&Sq@`CSK7{Ax*eqBH$62}Vyw{ieD?Mi3^xXz!KE3*9J zFlWebq(==0Vody2k2DJmxnxUN1B-yd8hCxXTk$K#)-Qk=gg0Q=O93CcyI%Kr>6660 z=8_~gruZOW<%|N>((-H8ZJ>Wug8A~R*ZS!V{g*UonD}o%hqwOYv2wA=`~BeMS5r$8 zBxmQB(C|S!#0_Ul=LG~%(6>S0ao>NNjE?FMDvNr~(+}?cv7N7s%!QMQbqV(pl{+i+YtO4kF9k#x+ z*B+1(r7OEHLb^Kc$zeG1^ZD_w%pyb=nvv4d1 zr@;|EfsHMxl(6l=T(vP%DOpAvFkQy?VnlS6tG@9$Uk)TppUKWJ^u3f_E1Bw1C~7y1q2YMIAR0`sF6p33?-u2 zkBd8jJc{UeiEZ?MoGriq@WBSaYN9uK)FR1eo3O(p2V8%VvsX zUU*Mphl-lEqemN@0w1iP1eOFY&kW=M#8|;W!~VT{RiNQT0SG=r8w8E9W(13b?&9E8 zt(zzRw5XU|jQVFF9C+eOT6T-!z_FH-igCguYT6Smz;@hL=WZJK&A+dJmtHzkWayyV z9S0>%Q(OBPoC~ + /// Visits the "raw" asciidoc generated using Roslyn and adds attribute entries, + /// section titles, rearranges sections, etc. + /// + public class GeneratedAsciidocVisitor : NoopVisitor + { + private static readonly Dictionary Ids = new Dictionary(); + + private readonly FileInfo _destination; + private Document _newDocument; + private bool _topLevel = true; + + public GeneratedAsciidocVisitor(FileInfo destination) + { + _destination = destination; + } + + public Document Convert(Document document) + { + document.Accept(this); + return _newDocument; + } + + public override void Visit(Document document) + { + _newDocument = new Document + { + Title = document.Title, + DocType = document.DocType + }; + + foreach (var authorInfo in document.Authors) + { + _newDocument.Authors.Add(authorInfo); + } + + RemoveDocDirectoryAttribute(_newDocument); + RemoveDocDirectoryAttribute(document); + + foreach (var attributeEntry in document.Attributes) + { + _newDocument.Attributes.Add(attributeEntry); + } + + if (!document.Attributes.Any(a => a.Name == "ref_current")) + { + _newDocument.Attributes.Add(new AttributeEntry("ref_current", "https://www.elastic.co/guide/en/elasticsearch/reference/current")); + } + + if (!document.Attributes.Any(a => a.Name == "github")) + { + _newDocument.Attributes.Add(new AttributeEntry("github", "https://github.com/elastic/elasticsearch-net")); + } + + if (!document.Attributes.Any(a => a.Name == "nuget")) + { + _newDocument.Attributes.Add(new AttributeEntry("nuget", "https://www.nuget.org/packages")); + } + + // see if the document has some kind of top level title and add one with an anchor if not. + if (document.Title == null && document.Count > 0) + { + var sectionTitle = document[0] as SectionTitle; + + if (sectionTitle == null || sectionTitle.Level != 2) + { + var id = Path.GetFileNameWithoutExtension(_destination.Name); + var title = id.LowercaseHyphenToPascal(); + sectionTitle = new SectionTitle(title, 2); + sectionTitle.Attributes.Add(new Anchor(id)); + + _newDocument.Add(sectionTitle); + } + } + + base.Visit(document); + } + + public override void Visit(Container elements) + { + if (_topLevel) + { + _topLevel = false; + Source exampleJson = null; + Source objectInitializerExample = null; + + for (int index = 0; index < elements.Count; index++) + { + var element = elements[index]; + var source = element as Source; + + if (source != null) + { + // remove empty source blocks + if (string.IsNullOrWhiteSpace(source.Text)) + { + continue; + } + + var method = source.Attributes.OfType().FirstOrDefault(a => a.Name == "method"); + if (method == null) + { + _newDocument.Add(element); + continue; + } + + if ((method.Value == "expectjson" || method.Value == "queryjson") && + source.Attributes.Count > 1 && + source.Attributes[1].Name == "javascript") + { + exampleJson = source; + continue; + } + + // if there is a section title since the last source block, don't add one + var lastSourceBlock = _newDocument.LastOrDefault(e => e is Source); + var lastSectionTitle = _newDocument.OfType().LastOrDefault(e => e.Level == 3); + if (lastSourceBlock != null && lastSectionTitle != null) + { + var lastSectionTitleIndex = _newDocument.IndexOf(lastSectionTitle); + var lastSourceBlockIndex = _newDocument.IndexOf(lastSourceBlock); + if (lastSectionTitleIndex > lastSourceBlockIndex) + { + _newDocument.Add(element); + continue; + } + } + + switch (method.Value) + { + case "fluent": + case "queryfluent": + _newDocument.Add(new SectionTitle("Fluent DSL Example", 3)); + _newDocument.Add(element); + + if (objectInitializerExample != null) + { + _newDocument.Add(new SectionTitle("Object Initializer Syntax Example", 3)); + _newDocument.Add(objectInitializerExample); + objectInitializerExample = null; + + if (exampleJson != null) + { + _newDocument.Add(exampleJson); + exampleJson = null; + } + } + break; + case "initializer": + _newDocument.Add(new SectionTitle("Object Initializer Syntax Example", 3)); + _newDocument.Add(element); + // Move the example json to after the initializer example + if (exampleJson != null) + { + _newDocument.Add(exampleJson); + exampleJson = null; + } + break; + case "queryinitializer": + if (objectInitializerExample != null) + { + _newDocument.Add(new SectionTitle("Object Initializer Syntax Example", 3)); + _newDocument.Add(objectInitializerExample); + + // Move the example json to after the initializer example + if (exampleJson != null) + { + _newDocument.Add(exampleJson); + exampleJson = null; + } + } + else + { + objectInitializerExample = source; + } + break; + case "expectresponse": + _newDocument.Add(new SectionTitle("Handling Responses", 3)); + _newDocument.Add(element); + break; + default: + _newDocument.Add(element); + break; + } + } + else + { + _newDocument.Add(element); + } + } + } + + base.Visit(elements); + } + + public override void Visit(Source source) + { + if (source.Attributes.Count > 1 && + source.Attributes[1].Name == "javascript" && + !source.Attributes.HasTitle) + { + source.Attributes.Add(new Title("Example json output")); + } + + // remove method attributes as the elastic doc generation doesn't like them; it + // expects a linenumbering in the index 2 position of a source block + var methodAttribute = source.Attributes.FirstOrDefault(a => a.Name == "method"); + if (methodAttribute != null) + { + source.Attributes.Remove(methodAttribute); + } + + // Replace tabs with spaces and remove comment escaping from output + // (elastic docs generation does not like this callout format) + source.Text = Regex.Replace(source.Text.Replace("\t", " "), @"//[ \t]*\<(\d+)\>.*", "<$1>"); + + base.Visit(source); + } + + public override void Visit(SectionTitle sectionTitle) + { + if (sectionTitle.Level != 2) + { + base.Visit(sectionTitle); + return; + } + + // Generate an anchor for all Level 2 section titles + if (!sectionTitle.Attributes.HasAnchor) + { + var builder = new StringBuilder(); + using (var writer = new AsciiDocVisitor(new StringWriter(builder))) + { + writer.Visit(sectionTitle.Elements); + } + + var title = builder.ToString().PascalToHyphen(); + sectionTitle.Attributes.Add(new Anchor(title)); + } + + // Check for duplicate ids across documents + var key = sectionTitle.Attributes.Anchor.Id; + string existingFile; + if (Ids.TryGetValue(key, out existingFile)) + { + throw new Exception($"duplicate id {key} in {_destination.FullName}. Id already exists in {existingFile}"); + } + + Ids.Add(key, _destination.FullName); + base.Visit(sectionTitle); + } + + private void RemoveDocDirectoryAttribute(Document document) + { + var directoryAttribute = document.Attributes.FirstOrDefault(a => a.Name == "docdir"); + if (directoryAttribute != null) + { + document.Attributes.Remove(directoryAttribute); + } + } + } +} +#endif diff --git a/src/CodeGeneration/Nest.Litterateur/AsciiDoc/RawAsciidocVisitor.cs b/src/CodeGeneration/Nest.Litterateur/AsciiDoc/RawAsciidocVisitor.cs new file mode 100644 index 00000000000..b78a16c889c --- /dev/null +++ b/src/CodeGeneration/Nest.Litterateur/AsciiDoc/RawAsciidocVisitor.cs @@ -0,0 +1,99 @@ +#if !DOTNETCORE +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using AsciiDocNet; + +namespace Nest.Litterateur.AsciiDoc +{ + ///

+ /// Visits raw asciidoc files (i.e. not generated) to make modifications + /// + public class RawAsciidocVisitor : NoopVisitor + { + private readonly FileInfo _destination; + + private static readonly Dictionary IncludeDirectories = new Dictionary + { + { "aggregations.asciidoc", "aggregations-usage.asciidoc" }, + { "query-dsl.asciidoc", "query-dsl-usage.asciidoc" } + }; + + public RawAsciidocVisitor(FileInfo destination) + { + _destination = destination; + } + + public override void Visit(Document document) + { + var directoryAttribute = document.Attributes.FirstOrDefault(a => a.Name == "docdir"); + if (directoryAttribute != null) + { + document.Attributes.Remove(directoryAttribute); + } + + // check if this document has generated includes to other files + var includeAttribute = document.Attributes.FirstOrDefault(a => a.Name == "includes-from-dirs"); + + if (includeAttribute != null) + { + var thisFileUri = new Uri(_destination.FullName); + var directories = includeAttribute.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var directory in directories) + { + foreach (var file in Directory.EnumerateFiles(Path.Combine(Program.OutputDirPath, directory), "*.asciidoc", SearchOption.AllDirectories)) + { + var fileInfo = new FileInfo(file); + var referencedFileUri = new Uri(fileInfo.FullName); + var relativePath = thisFileUri.MakeRelativeUri(referencedFileUri); + var include = new Include(relativePath.OriginalString); + + document.Add(include); + } + } + } + + base.Visit(document); + } + + public override void Visit(Open open) + { + // include links to all the query dsl usage and aggregation usage pages on the landing query dsl and aggregations pages, respectively. + string usageFilePath; + if (IncludeDirectories.TryGetValue(_destination.Name, out usageFilePath)) + { + var usageDoc = Document.Load(Path.Combine(Program.OutputDirPath, usageFilePath)); + + var includeAttribute = usageDoc.Attributes.FirstOrDefault(a => a.Name == "includes-from-dirs"); + + if (includeAttribute != null) + { + var directories = includeAttribute.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + var list = new UnorderedList(); + + foreach (var directory in directories) + { + foreach (var file in Directory.EnumerateFiles(Path.Combine(Program.OutputDirPath, directory), "*usage.asciidoc", SearchOption.AllDirectories)) + { + var fileInfo = new FileInfo(file); + var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileInfo.Name); + + list.Items.Add(new UnorderedListItem + { + new Paragraph(new InternalAnchor(fileNameWithoutExtension, fileNameWithoutExtension.LowercaseHyphenToPascal())) + }); + } + } + + open.Add(list); + } + } + + base.Visit(open); + } + } +} +#endif diff --git a/src/CodeGeneration/Nest.Litterateur/Documentation/Blocks/CodeBlock.cs b/src/CodeGeneration/Nest.Litterateur/Documentation/Blocks/CodeBlock.cs index f305486bf12..c94d317fb4d 100644 --- a/src/CodeGeneration/Nest.Litterateur/Documentation/Blocks/CodeBlock.cs +++ b/src/CodeGeneration/Nest.Litterateur/Documentation/Blocks/CodeBlock.cs @@ -1,13 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + namespace Nest.Litterateur.Documentation.Blocks { public class CodeBlock : IDocumentationBlock { - public string Value { get; } - public int LineNumber { get; } - public CodeBlock(string lineOfCode, int lineNumber) + public CodeBlock(string lineOfCode, int lineNumber, Language language, string propertyOrMethodName) { - Value = lineOfCode.Trim(); + Value = ExtractCallOutsFromText(lineOfCode); LineNumber = lineNumber; + Language = language; + PropertyName = propertyOrMethodName?.ToLowerInvariant(); + } + + public List CallOuts { get; } = new List(); + + public Language Language { get; set; } + + public int LineNumber { get; } + + public string PropertyName { get; set; } + + public string Value { get; } + + private string ExtractCallOutsFromText(string lineOfCode) + { + var matches = Regex.Matches(lineOfCode, @"//[ \t]*(?\<\d+\>)[ \t]*(?\S.*)"); + foreach (Match match in matches) + { + CallOuts.Add($"{match.Groups["callout"].Value} {match.Groups["text"].Value}"); + } + + if (CallOuts.Any()) + { + lineOfCode = Regex.Replace(lineOfCode, @"//[ \t]*\<(\d+)\>.*", "//<$1>"); + } + + return lineOfCode.Trim(); } } } \ No newline at end of file diff --git a/src/CodeGeneration/Nest.Litterateur/Documentation/Blocks/CombinedBlock.cs b/src/CodeGeneration/Nest.Litterateur/Documentation/Blocks/CombinedBlock.cs index a40927f0fc2..ac880574611 100644 --- a/src/CodeGeneration/Nest.Litterateur/Documentation/Blocks/CombinedBlock.cs +++ b/src/CodeGeneration/Nest.Litterateur/Documentation/Blocks/CombinedBlock.cs @@ -4,7 +4,7 @@ namespace Nest.Litterateur.Documentation.Blocks { /// /// Used to keep a line of code (could be multiple e.g fluent syntax) and its annotations in one logical unit. - /// So they do not suffer from reoordering based on line number when writing out the documentation + /// So they do not suffer from reordering based on line number when writing out the documentation /// public class CombinedBlock : IDocumentationBlock { diff --git a/src/CodeGeneration/Nest.Litterateur/Documentation/Files/CSharpDocumentationFile.cs b/src/CodeGeneration/Nest.Litterateur/Documentation/Files/CSharpDocumentationFile.cs index 2905f4f8a89..cb30fa58045 100644 --- a/src/CodeGeneration/Nest.Litterateur/Documentation/Files/CSharpDocumentationFile.cs +++ b/src/CodeGeneration/Nest.Litterateur/Documentation/Files/CSharpDocumentationFile.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; @@ -6,49 +7,134 @@ using Nest.Litterateur.Documentation.Blocks; using Nest.Litterateur.Walkers; +#if !DOTNETCORE +using AsciiDocNet; +using Nest.Litterateur.AsciiDoc; +#endif + namespace Nest.Litterateur.Documentation.Files { public class CSharpDocumentationFile : DocumentationFile { - internal CSharpDocumentationFile(FileInfo fileLocation) : base(fileLocation) { } + internal CSharpDocumentationFile(FileInfo fileLocation) : base(fileLocation) + { + } - private string RenderBlocksToDocumentation(IEnumerable blocks, StringBuilder builder = null) + private string RenderBlocksToDocumentation(IEnumerable blocks) + { + var builder = new StringBuilder(); + var lastBlockWasCodeBlock = false; + var callouts = new List(); + Language? language = null; + string propertyOrMethodName = null; + + RenderBlocksToDocumentation(blocks, builder, ref lastBlockWasCodeBlock, ref callouts, ref language, ref propertyOrMethodName); + if (lastBlockWasCodeBlock) + { + builder.AppendLine("----"); + foreach (var callout in callouts) + { + builder.AppendLine(callout); + } + } + return builder.ToString(); + } + + private void RenderBlocksToDocumentation( + IEnumerable blocks, + StringBuilder builder, + ref bool lastBlockWasCodeBlock, + ref List callouts, + ref Language? language, + ref string propertyOrMethodName) { - var sb = builder ?? new StringBuilder(); foreach (var block in blocks) { if (block is TextBlock) { - sb.AppendLine(block.Value); + if (lastBlockWasCodeBlock) + { + lastBlockWasCodeBlock = false; + builder.AppendLine("----"); + if (callouts.Any()) + { + foreach (var callout in callouts) + { + builder.AppendLine(callout); + } + builder.AppendLine(); + callouts = new List(); + } + } + + builder.AppendLine(block.Value); } else if (block is CodeBlock) { - sb.AppendLine("[source, csharp]"); - sb.AppendLine("----"); - sb.AppendLine(block.Value); - sb.AppendLine("----"); + var codeBlock = (CodeBlock)block; + + // don't write different language code blocks in the same delimited source block + if (lastBlockWasCodeBlock && (codeBlock.Language != language || codeBlock.PropertyName != propertyOrMethodName)) + { + lastBlockWasCodeBlock = false; + builder.AppendLine("----"); + if (callouts.Any()) + { + foreach (var callout in callouts) + { + builder.AppendLine(callout); + } + builder.AppendLine(); + callouts = new List(); + } + } + + if (!lastBlockWasCodeBlock) + { + builder.AppendLine($"[source,{codeBlock.Language.ToString().ToLowerInvariant()},method=\"{codeBlock.PropertyName ?? "unknown"}\"]"); + builder.AppendLine("----"); + } + else + { + builder.AppendLine(); + } + + builder.AppendLine(codeBlock.Value); + + // add call outs here to write out when closing the block + callouts.AddRange(codeBlock.CallOuts); + lastBlockWasCodeBlock = true; + language = codeBlock.Language; + propertyOrMethodName = codeBlock.PropertyName; } else if (block is CombinedBlock) { - RenderBlocksToDocumentation(MergeAdjacentCodeBlocks(((CombinedBlock)block).Blocks), sb); + var mergedBlocks = MergeAdjacentCodeBlocks(((CombinedBlock)block).Blocks); + RenderBlocksToDocumentation(mergedBlocks, builder, ref lastBlockWasCodeBlock, ref callouts, ref language, ref propertyOrMethodName); } } - return sb.ToString(); } private List MergeAdjacentCodeBlocks(IEnumerable unmergedBlocks) { var blocks = new List(); List collapseCodeBlocks = null; + List collapseCallouts = null; int lineNumber = 0; + Language? language = null; + string propertyOrMethodName = null; + foreach (var b in unmergedBlocks) { - //if current block is not a code block and we;ve been collapsing code blocks - //at this point close that buffre and add a new codeblock + //if current block is not a code block and we've been collapsing code blocks + //at this point close that buffer and add a new codeblock if (!(b is CodeBlock) && collapseCodeBlocks != null) { - blocks.Add(new CodeBlock(string.Join("\r\n", collapseCodeBlocks), lineNumber)); + var block = new CodeBlock(string.Join(Environment.NewLine, collapseCodeBlocks), lineNumber, language.Value, propertyOrMethodName); + block.CallOuts.AddRange(collapseCallouts); + blocks.Add(block); collapseCodeBlocks = null; + collapseCallouts = null; } //if not a codeblock simply add it to the final list @@ -57,23 +143,42 @@ private List MergeAdjacentCodeBlocks(IEnumerable(); - collapseCodeBlocks.Add(b.Value); - lineNumber = b.LineNumber; + if (collapseCallouts == null) collapseCallouts = new List(); + + var codeBlock = (CodeBlock)b; + + if ((language != null && codeBlock.Language != language) || + (propertyOrMethodName != null && codeBlock.PropertyName != propertyOrMethodName)) + { + blocks.Add(codeBlock); + continue; + } + + language = codeBlock.Language; + propertyOrMethodName = codeBlock.PropertyName; + collapseCodeBlocks.Add(codeBlock.Value); + collapseCallouts.AddRange(codeBlock.CallOuts); + + lineNumber = codeBlock.LineNumber; } //make sure we flush our code buffer if (collapseCodeBlocks != null) - blocks.Add(new CodeBlock(string.Join("\r\n", collapseCodeBlocks), lineNumber)); + { + var joinedCodeBlock = new CodeBlock(string.Join(Environment.NewLine, collapseCodeBlocks), lineNumber, language.Value, propertyOrMethodName); + joinedCodeBlock.CallOuts.AddRange(collapseCallouts); + blocks.Add(joinedCodeBlock); + } return blocks; } public override void SaveToDocumentationFolder() { var code = File.ReadAllText(this.FileLocation.FullName); - var ast = CSharpSyntaxTree.ParseText(code); + var walker = new DocumentationFileWalker(); walker.Visit(ast.GetRoot()); var blocks = walker.Blocks.OrderBy(b => b.LineNumber).ToList(); @@ -81,9 +186,31 @@ public override void SaveToDocumentationFolder() var mergedBlocks = MergeAdjacentCodeBlocks(blocks); var body = this.RenderBlocksToDocumentation(mergedBlocks); + var docFile = this.CreateDocumentationLocation(); + +#if !DOTNETCORE + CleanDocumentAndWriteToFile(body, docFile); +#else + File.WriteAllText(docFile.FullName, body); +#endif + } + +#if !DOTNETCORE + private void CleanDocumentAndWriteToFile(string body, FileInfo docFile) + { + // tidy up the asciidoc + var document = Document.Parse(body); - var docFileName = this.CreateDocumentationLocation(); - File.WriteAllText(docFileName.FullName, body); + var visitor = new GeneratedAsciidocVisitor(docFile); + document = visitor.Convert(document); + + // add attributes and write to destination + using (var file = new StreamWriter(docFile.FullName)) + { + + document.Accept(new AsciiDocVisitor(file)); + } } +#endif } } diff --git a/src/CodeGeneration/Nest.Litterateur/Documentation/Files/DocumentationFile.cs b/src/CodeGeneration/Nest.Litterateur/Documentation/Files/DocumentationFile.cs index 4d8faf0aa90..381cbf949db 100644 --- a/src/CodeGeneration/Nest.Litterateur/Documentation/Files/DocumentationFile.cs +++ b/src/CodeGeneration/Nest.Litterateur/Documentation/Files/DocumentationFile.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Reflection.Emit; using System.Text.RegularExpressions; +using Nest.Litterateur; namespace Nest.Litterateur.Documentation.Files { @@ -26,7 +29,9 @@ public static DocumentationFile Load(FileInfo fileLocation) return new CSharpDocumentationFile(fileLocation); case ".gif": case ".jpg": + case ".jpeg": case ".png": + return new ImageDocumentationFile(fileLocation); case ".asciidoc": return new RawDocumentationFile(fileLocation); } @@ -37,13 +42,19 @@ public static DocumentationFile Load(FileInfo fileLocation) protected virtual FileInfo CreateDocumentationLocation() { var testFullPath = this.FileLocation.FullName; - var testInDocumenationFolder = Regex.Replace(testFullPath, @"(^.+\\Tests\\|\" + this.Extension + "$)", "") + ".asciidoc"; - var documenationTargetPath = Path.GetFullPath(Path.Combine(Program.OutputFolder, testInDocumenationFolder)); - var fileInfo = new FileInfo(documenationTargetPath); + var testInDocumentationFolder = + Regex.Replace(testFullPath, @"(^.+\\Tests\\|\" + this.Extension + "$)", "") + .TrimEnd(".doc") + .TrimEnd("Tests") + .PascalToHyphen() + ".asciidoc"; + + var documentationTargetPath = Path.GetFullPath(Path.Combine(Program.OutputDirPath, testInDocumentationFolder)); + var fileInfo = new FileInfo(documentationTargetPath); if (fileInfo.Directory != null) Directory.CreateDirectory(fileInfo.Directory.FullName); + return fileInfo; - } + } } } \ No newline at end of file diff --git a/src/CodeGeneration/Nest.Litterateur/Documentation/Files/ImageDocumentationFile.cs b/src/CodeGeneration/Nest.Litterateur/Documentation/Files/ImageDocumentationFile.cs new file mode 100644 index 00000000000..ee958349255 --- /dev/null +++ b/src/CodeGeneration/Nest.Litterateur/Documentation/Files/ImageDocumentationFile.cs @@ -0,0 +1,36 @@ +using System.IO; +using System.Text.RegularExpressions; + +namespace Nest.Litterateur.Documentation.Files +{ + public class ImageDocumentationFile : DocumentationFile + { + public ImageDocumentationFile(FileInfo fileLocation) : base(fileLocation) { } + + public override void SaveToDocumentationFolder() + { + var docFileName = this.CreateDocumentationLocation(); + + // copy for asciidoc to work (path is relative to file) + this.FileLocation.CopyTo(docFileName.FullName, true); + + // copy to the root as well, for the doc generation process (path is relative to root) + this.FileLocation.CopyTo(Path.Combine(Program.OutputDirPath, docFileName.Name), true); + } + + protected override FileInfo CreateDocumentationLocation() + { + var testFullPath = this.FileLocation.FullName; + + var testInDocumenationFolder = Regex.Replace(testFullPath, @"(^.+\\Tests\\|\" + this.Extension + "$)", "") + .PascalToHyphen() + this.Extension; + + var documentationTargetPath = Path.GetFullPath(Path.Combine(Program.OutputDirPath, testInDocumenationFolder)); + + var fileInfo = new FileInfo(documentationTargetPath); + if (fileInfo.Directory != null) + Directory.CreateDirectory(fileInfo.Directory.FullName); + return fileInfo; + } + } +} diff --git a/src/CodeGeneration/Nest.Litterateur/Documentation/Files/RawDocumentationFile.cs b/src/CodeGeneration/Nest.Litterateur/Documentation/Files/RawDocumentationFile.cs index eb1a4d01550..26461e3436b 100644 --- a/src/CodeGeneration/Nest.Litterateur/Documentation/Files/RawDocumentationFile.cs +++ b/src/CodeGeneration/Nest.Litterateur/Documentation/Files/RawDocumentationFile.cs @@ -1,5 +1,11 @@ +using System; using System.IO; +using System.Linq; using System.Text.RegularExpressions; +#if !DOTNETCORE +using AsciiDocNet; +using Nest.Litterateur.AsciiDoc; +#endif namespace Nest.Litterateur.Documentation.Files { @@ -11,19 +17,34 @@ public override void SaveToDocumentationFolder() { //we simply do a copy of the markdown file var docFileName = this.CreateDocumentationLocation(); + +#if !DOTNETCORE + var document = Document.Load(FileLocation.FullName); + + // make any modifications + var rawVisitor = new RawAsciidocVisitor(FileLocation); + document.Accept(rawVisitor); + + // write out asciidoc to file + using (var visitor = new AsciiDocVisitor(docFileName.FullName)) + { + document.Accept(visitor); + } +#else this.FileLocation.CopyTo(docFileName.FullName, true); +#endif } protected override FileInfo CreateDocumentationLocation() { var testFullPath = this.FileLocation.FullName; - var testInDocumenationFolder = Regex.Replace(testFullPath, @"(^.+\\Tests\\|\" + this.Extension + "$)", "") + this.Extension; + var testInDocumenationFolder = Regex.Replace(testFullPath, @"(^.+\\Tests\\|\" + this.Extension + "$)", "").PascalToHyphen() + this.Extension; - var documenationTargetPath = Path.GetFullPath(Path.Combine(Program.OutputFolder, testInDocumenationFolder)); + var documenationTargetPath = Path.GetFullPath(Path.Combine(Program.OutputDirPath, testInDocumenationFolder)); var fileInfo = new FileInfo(documenationTargetPath); if (fileInfo.Directory != null) Directory.CreateDirectory(fileInfo.Directory.FullName); return fileInfo; } } -} \ No newline at end of file +} diff --git a/src/CodeGeneration/Nest.Litterateur/EnumerableExtensions.cs b/src/CodeGeneration/Nest.Litterateur/EnumerableExtensions.cs index 7f67ed50c2f..9c82fffdb04 100644 --- a/src/CodeGeneration/Nest.Litterateur/EnumerableExtensions.cs +++ b/src/CodeGeneration/Nest.Litterateur/EnumerableExtensions.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace Nest.Litterateur diff --git a/src/CodeGeneration/Nest.Litterateur/Language.cs b/src/CodeGeneration/Nest.Litterateur/Language.cs new file mode 100644 index 00000000000..07dea31177f --- /dev/null +++ b/src/CodeGeneration/Nest.Litterateur/Language.cs @@ -0,0 +1,8 @@ +namespace Nest.Litterateur +{ + public enum Language + { + CSharp, + JavaScript + } +} \ No newline at end of file diff --git a/src/CodeGeneration/Nest.Litterateur/Linker/Linker.cs b/src/CodeGeneration/Nest.Litterateur/Linker/Linker.cs deleted file mode 100644 index 80578ed3a1b..00000000000 --- a/src/CodeGeneration/Nest.Litterateur/Linker/Linker.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Nest.Litterateur.Linker -{ - /// - /// Goes over the generated docs, does heuristical touchups and writes outs ascii docs links at the bottom of files - /// - public class Linker - { - - } -} diff --git a/src/CodeGeneration/Nest.Litterateur/LitUp.cs b/src/CodeGeneration/Nest.Litterateur/LitUp.cs index d4256b43960..17d7588dd59 100644 --- a/src/CodeGeneration/Nest.Litterateur/LitUp.cs +++ b/src/CodeGeneration/Nest.Litterateur/LitUp.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using Nest.Litterateur.Documentation.Files; @@ -9,8 +10,9 @@ namespace Nest.Litterateur public static class LitUp { private static readonly string[] SkipFolders = { "Nest.Tests.Literate", "Debug", "Release" }; - public static IEnumerable InputFiles(string extension) => - from f in Directory.GetFiles(Program.InputFolder, $"*.{extension}", SearchOption.AllDirectories) + + public static IEnumerable InputFiles(string path) => + from f in Directory.GetFiles(Program.InputDirPath, $"{path}", SearchOption.AllDirectories) let dir = new DirectoryInfo(f) where dir?.Parent != null && !SkipFolders.Contains(dir.Parent.Name) select DocumentationFile.Load(new FileInfo(f)); @@ -19,17 +21,29 @@ public static IEnumerable> Input { get { - yield return InputFiles("doc.cs"); - yield return InputFiles("asciidoc"); - yield return InputFiles("ping"); - yield return InputFiles("gif"); + yield return InputFiles("*.doc.cs"); + yield return InputFiles("*UsageTests.cs"); + yield return InputFiles("*.png"); + yield return InputFiles("*.gif"); + yield return InputFiles("*.jpg"); + // process asciidocs last as they may have generated + // includes to other output asciidocs + yield return InputFiles("*.asciidoc"); } } public static void Go(string[] args) { - foreach (var file in Input.SelectMany(s=>s)) + foreach (var file in Input.SelectMany(s => s)) + { file.SaveToDocumentationFolder(); + } + +#if !DOTNETCORE + if (Debugger.IsAttached) + Console.WriteLine("Press any key to continue..."); + Console.ReadKey(); +#endif } } } \ No newline at end of file diff --git a/src/CodeGeneration/Nest.Litterateur/Program.cs b/src/CodeGeneration/Nest.Litterateur/Program.cs index 8ecdf2ab64d..5e42aa21701 100644 --- a/src/CodeGeneration/Nest.Litterateur/Program.cs +++ b/src/CodeGeneration/Nest.Litterateur/Program.cs @@ -1,32 +1,27 @@ using System.IO; -using Nest.Litterateur.Documentation; namespace Nest.Litterateur { public static class Program { - private static string DefaultTestFolder; - private static string DefaultDocFolder; - static Program() { var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory()); if (currentDirectory.Name == "Nest.Litterateur" && currentDirectory.Parent.Name == "CodeGeneration") { - DefaultTestFolder = @"..\..\Tests"; - DefaultDocFolder = @"..\..\..\docs\asciidoc"; + InputDirPath = @"..\..\Tests"; + OutputDirPath = @"..\..\..\docs\asciidoc"; } else { - DefaultTestFolder = @"..\..\..\..\..\src\Tests"; - DefaultDocFolder = @"..\..\..\..\..\docs\asciidoc"; + InputDirPath = @"..\..\..\..\..\src\Tests"; + OutputDirPath = @"..\..\..\..\..\docs\asciidoc"; } } - public static string InputFolder => DefaultTestFolder; + public static string InputDirPath { get; } - - public static string OutputFolder => DefaultDocFolder; + public static string OutputDirPath { get; } static void Main(string[] args) => LitUp.Go(args); } diff --git a/src/CodeGeneration/Nest.Litterateur/StringExtensions.cs b/src/CodeGeneration/Nest.Litterateur/StringExtensions.cs new file mode 100644 index 00000000000..4ee0037291f --- /dev/null +++ b/src/CodeGeneration/Nest.Litterateur/StringExtensions.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Newtonsoft.Json; + +namespace Nest.Litterateur +{ + public static class StringExtensions + { + public static string PascalToHyphen(this string input) + { + if (string.IsNullOrEmpty(input)) return string.Empty; + + return Regex.Replace( + Regex.Replace( + Regex.Replace(input, @"([A-Z]+)([A-Z][a-z])", "$1-$2"), @"([a-z\d])([A-Z])", "$1-$2") + , @"[-\s]+", "-").TrimEnd('-').ToLower(); + } + + public static string LowercaseHyphenToPascal(this string lowercaseHyphenatedInput) + { + return Regex.Replace(lowercaseHyphenatedInput.Replace("-", " "), @"\b([a-z])", m => m.Captures[0].Value.ToUpper()); + } + + public static string TrimEnd(this string input, string trim) + { + if (string.IsNullOrEmpty(input)) return string.Empty; + + return input.EndsWith(trim, StringComparison.OrdinalIgnoreCase) + ? input.Substring(0, input.Length - trim.Length) + : input; + } + + public static string RemoveLeadingAndTrailingMultiLineComments(this string input) + { + var match = Regex.Match(input, @"^(?[ \t]*\/\*)"); + + if (match.Success) + { + input = input.Substring(match.Groups["value"].Value.Length); + } + + match = Regex.Match(input, @"(?\*\/[ \t]*)$"); + + if (match.Success) + { + input = input.Substring(0, input.Length - match.Groups["value"].Value.Length); + } + + return input; + } + + public static string RemoveLeadingSpacesAndAsterisk(this string input) + { + var match = Regex.Match(input, @"^(?[ \t]*\*\s?).*"); + + if (match.Success) + { + input = input.Substring(match.Groups["value"].Value.Length); + } + + return input; + } + + public static string RemoveNumberOfLeadingTabsAfterNewline(this string input, int numberOfTabs) + { + var firstTab = input.IndexOf("\t", StringComparison.OrdinalIgnoreCase); + + if (firstTab == -1) + { + return input; + } + int count = 0; + char firstNonTabCharacter = Char.MinValue; + + for (int i = firstTab; i < input.Length; i++) + { + if (input[i] != '\t') + { + firstNonTabCharacter = input[i]; + count = i - firstTab; + break; + } + } + + if (firstNonTabCharacter == '{' && numberOfTabs != count) + { + numberOfTabs = count; + } + + return Regex.Replace( + Regex.Replace( + input, + $"(?[\n|\r\n]+\t{{{numberOfTabs}}})", + m => m.Value.Replace("\t", string.Empty) + ), + $"(?[\n|\r\n]+\\s{{{numberOfTabs * 4}}})", + m => m.Value.Replace(" ", string.Empty) + ); + } + + public static string[] SplitOnNewLines(this string input, StringSplitOptions options) + { + return input.Split(new[] { "\r\n", "\n" }, options); + } + +#if !DOTNETCORE + // TODO: Hack of replacements in anonymous types that represent json. This can be resolved by referencing tests assembly when building the dynamic assembly, + // but might want to put doc generation at same directory level as Tests to reference project directly. + private static Dictionary Substitutions = new Dictionary + { + { "FixedDate", "new DateTime(2015, 06, 06, 12, 01, 02, 123)" }, + { "FirstNameToFind", "\"pierce\"" }, + { "Project.Projects.First().Suggest.Context.Values.SelectMany(v => v).First()", "\"red\"" }, + { "Project.Instance.Name", "\"Durgan LLC\"" }, + { "Project.InstanceAnonymous", "new {name = \"Koch, Collier and Mohr\", state = \"BellyUp\",startedOn = " + + "\"2015-01-01T00:00:00\",lastActivity = \"0001-01-01T00:00:00\",leadDeveloper = " + + "new { gender = \"Male\", id = 0, firstName = \"Martijn\", lastName = \"Laarman\" }," + + "location = new { lat = 42.1523, lon = -80.321 }}" }, + { "_templateString", "\"{ \\\"match\\\": { \\\"text\\\": \\\"{{query_string}}\\\" } }\"" }, + { "base.QueryJson", "new{ @bool = new { must = new[] { new { match_all = new { } } }, must_not = new[] { new { match_all = new { } } }, should = new[] { new { match_all = new { } } }, filter = new[] { new { match_all = new { } } }, minimum_should_match = 1, boost = 2.0, } }" }, + { "ExpectedTerms", "new [] { \"term1\", \"term2\" }" }, + { "_ctxNumberofCommits", "\"_source.numberOfCommits > 0\"" } + }; + + public static bool TryGetJsonForAnonymousType(this string anonymousTypeString, out string json) + { + json = null; + + foreach (var substitution in Substitutions) + { + anonymousTypeString = anonymousTypeString.Replace(substitution.Key, substitution.Value); + } + + var text = + $@" + using System; + using System.Collections.Generic; + using System.ComponentModel; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + namespace Temporary + {{ + public class Json + {{ + public string Write() + {{ + var o = {anonymousTypeString}; + var json = JsonConvert.SerializeObject(o, Formatting.Indented); + return json; + }} + }} + }}"; + + var syntaxTree = CSharpSyntaxTree.ParseText(text); + var assemblyName = Path.GetRandomFileName(); + var references = new MetadataReference[] + { + MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location), + MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location), + MetadataReference.CreateFromFile(typeof(JsonConvert).GetTypeInfo().Assembly.Location), + MetadataReference.CreateFromFile(typeof(ITypedList).GetTypeInfo().Assembly.Location), + }; + + var compilation = + CSharpCompilation.Create( + assemblyName, + new[] { syntaxTree }, + references, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + using (var ms = new MemoryStream()) + { + var result = compilation.Emit(ms); + + if (!result.Success) + { + var failures = result.Diagnostics.Where(diagnostic => + diagnostic.IsWarningAsError || + diagnostic.Severity == DiagnosticSeverity.Error); + + var builder = new StringBuilder($"Unable to serialize: {anonymousTypeString}"); + foreach (var diagnostic in failures) + { + builder.AppendLine($"{diagnostic.Id}: {diagnostic.GetMessage()}"); + } + builder.AppendLine(new string('-', 30)); + + Console.Error.WriteLine(builder.ToString()); + return false; + } + + ms.Seek(0, SeekOrigin.Begin); + + var assembly = Assembly.Load(ms.ToArray()); + var type = assembly.GetType("Temporary.Json"); + var obj = Activator.CreateInstance(type); + + var output = type.InvokeMember("Write", + BindingFlags.Default | BindingFlags.InvokeMethod, + null, + obj, + new object[] { }); + + json = output.ToString(); + return true; + } + } +#endif + } +} diff --git a/src/CodeGeneration/Nest.Litterateur/Walkers/CodeWithDocumentationWalker.cs b/src/CodeGeneration/Nest.Litterateur/Walkers/CodeWithDocumentationWalker.cs index 2af708577a0..98ab9270d82 100644 --- a/src/CodeGeneration/Nest.Litterateur/Walkers/CodeWithDocumentationWalker.cs +++ b/src/CodeGeneration/Nest.Litterateur/Walkers/CodeWithDocumentationWalker.cs @@ -4,33 +4,47 @@ using Nest.Litterateur.Documentation; using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Formatting; +#if !DOTNETCORE +using Microsoft.CodeAnalysis.MSBuild; +#endif +using Microsoft.CodeAnalysis.Text; using Nest.Litterateur.Documentation.Blocks; namespace Nest.Litterateur.Walkers { class CodeWithDocumentationWalker : CSharpSyntaxWalker { - public List Blocks { get; } = new List(); - public List TextBlocks { get; } = new List(); - private bool _firstVisit = true; private string _code; + private readonly string _propertyOrMethodName; + public int ClassDepth { get; } + + public List Blocks { get; } = new List(); + + public List TextBlocks { get; } = new List(); + private readonly int? _lineNumberOverride; /// /// We want to support inlining /** */ documentations because its super handy /// to document fluent code, what ensues is total hackery /// - /// + /// the depth of the class /// line number used for sorting - public CodeWithDocumentationWalker(int classDepth = 1, int? lineNumber = null) : base(SyntaxWalkerDepth.StructuredTrivia) + /// the name of the property that we are walking + public CodeWithDocumentationWalker(int classDepth = 1, int? lineNumber = null, string propertyOrMethodName = null) : base(SyntaxWalkerDepth.StructuredTrivia) { ClassDepth = classDepth; _lineNumberOverride = lineNumber; + _propertyOrMethodName = propertyOrMethodName; } public override void Visit(SyntaxNode node) @@ -40,25 +54,38 @@ public override void Visit(SyntaxNode node) _firstVisit = false; var repeatedTabs = 2 + ClassDepth; + var language = Language.CSharp; _code = node.WithoutLeadingTrivia().WithTrailingTrivia().ToFullString(); + _code = _code.RemoveNumberOfLeadingTabsAfterNewline(repeatedTabs); - // find x or more repeated tabs and trim x number of tabs from the start - _code = Regex.Replace(_code, $"\t{{{repeatedTabs},}}", match => match.Value.Substring(repeatedTabs)); +#if !DOTNETCORE + if (_propertyOrMethodName == "ExpectJson" || _propertyOrMethodName == "QueryJson") + { + // try to get the json for the anonymous type. + // Only supports system types and Json.Net LINQ objects e.g. JObject + string json; + if (_code.TryGetJsonForAnonymousType(out json)) + { + language = Language.JavaScript; + _code = json; + } + } +#endif + // TODO: Can do this once we get the generic arguments from the Property declaration + //if (_propertyName == "Fluent") + //{ + // // need to know what type we're operating on + // _code += $"client.Search({_code});"; + //} var nodeLine = node.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line; - var line = _lineNumberOverride ?? nodeLine; - - var codeBlocks = Regex.Split(_code, @"\/\*\*.*?\*\/", RegexOptions.Singleline) - .Select(b => b.TrimStart('\r', '\n').TrimEnd('\r', '\n', '\t')) - .Where(b => !string.IsNullOrEmpty(b) && b != ";") - .Select(b=>new CodeBlock(b, line)) - .ToList(); + var codeBlocks = ParseCodeBlocks(_code, line, language, _propertyOrMethodName); base.Visit(node); - var nodeHasLeadingTriva = node.HasLeadingTrivia && node.GetLeadingTrivia() - .Any(c=>c.Kind() == SyntaxKind.MultiLineDocumentationCommentTrivia); + var nodeHasLeadingTriva = node.HasLeadingTrivia && + node.GetLeadingTrivia().Any(c => c.Kind() == SyntaxKind.MultiLineDocumentationCommentTrivia); var blocks = codeBlocks.Intertwine(this.TextBlocks, swap: nodeHasLeadingTriva); this.Blocks.Add(new CombinedBlock(blocks, line)); return; @@ -74,20 +101,15 @@ public override void VisitBlock(BlockSyntax node) _firstVisit = false; foreach (var statement in node.Statements) { - var leadingTabs = new string('\t', 3 + ClassDepth); + var repeatedTabs = 3 + ClassDepth; SyntaxNode formattedStatement = statement; - _code = formattedStatement.WithoutLeadingTrivia().WithTrailingTrivia().ToFullString().Replace(leadingTabs, string.Empty); + _code = formattedStatement.WithoutLeadingTrivia().WithTrailingTrivia().ToFullString(); + _code = _code.RemoveNumberOfLeadingTabsAfterNewline(repeatedTabs); var nodeLine = formattedStatement.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line; - var line = _lineNumberOverride ?? nodeLine; - - var codeBlocks = Regex.Split(_code, @"\/\*\*.*?\*\/", RegexOptions.Singleline) - .Select(b => b.TrimStart('\r', '\n').TrimEnd('\r', '\n', '\t')) - .Where(b => !string.IsNullOrEmpty(b) && b != ";") - .Select(b => new CodeBlock(b, line)) - .ToList(); + var codeBlocks = ParseCodeBlocks(_code, line, Language.CSharp, _propertyOrMethodName); this.Blocks.AddRange(codeBlocks); } @@ -96,19 +118,41 @@ public override void VisitBlock(BlockSyntax node) } } - public override void VisitXmlText(XmlTextSyntax node) + public override void VisitTrivia(SyntaxTrivia trivia) { - var nodeLine = node.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line; - var line = _lineNumberOverride ?? nodeLine; - var text = node.TextTokens - .Where(n => n.Kind() == SyntaxKind.XmlTextLiteralToken) - .Aggregate(new StringBuilder(), (a, t) => a.AppendLine(t.Text.TrimStart()), a => a.ToString()); + if (trivia.Kind() != SyntaxKind.MultiLineDocumentationCommentTrivia) + { + base.VisitTrivia(trivia); + return; + } - this.TextBlocks.Add(new TextBlock(text, line)); + var tokens = trivia.ToFullString() + .RemoveLeadingAndTrailingMultiLineComments() + .SplitOnNewLines(StringSplitOptions.None); + var builder = new StringBuilder(); - base.VisitXmlText(node); - } + foreach (var token in tokens) + { + var currentToken = token.RemoveLeadingSpacesAndAsterisk(); + var decodedToken = System.Net.WebUtility.HtmlDecode(currentToken); + builder.AppendLine(decodedToken); + } + + var text = builder.ToString(); + var line = _firstVisit + ? trivia.SyntaxTree.GetLineSpan(trivia.Span).StartLinePosition.Line + : _lineNumberOverride.GetValueOrDefault(0); + this.Blocks.Add(new TextBlock(text, line)); + } + private List ParseCodeBlocks(string code, int line, Language language, string propertyName) + { + return Regex.Split(code, @"\/\*\*.*?\*\/", RegexOptions.Singleline) + .Select(b => b.TrimStart('\r', '\n').TrimEnd('\r', '\n', '\t')) + .Where(b => !string.IsNullOrEmpty(b) && b != ";") + .Select(b => new CodeBlock(b, line, language, propertyName)) + .ToList(); + } } } diff --git a/src/CodeGeneration/Nest.Litterateur/Walkers/DocumentationFileWalker.cs b/src/CodeGeneration/Nest.Litterateur/Walkers/DocumentationFileWalker.cs index 6ed9d53a679..0002dfc7c86 100644 --- a/src/CodeGeneration/Nest.Litterateur/Walkers/DocumentationFileWalker.cs +++ b/src/CodeGeneration/Nest.Litterateur/Walkers/DocumentationFileWalker.cs @@ -1,7 +1,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Nest.Litterateur.Documentation; using System; using System.Collections.Generic; using System.Linq; @@ -12,43 +11,84 @@ namespace Nest.Litterateur.Walkers { class DocumentationFileWalker : CSharpSyntaxWalker { + private static readonly string[] PropertyOrMethodNamesOfInterest = + { + "ExpectJson", + "QueryJson", + "Fluent", + "Initializer", + "QueryFluent", + "QueryInitializer" + }; + + private string _propertyOrMethodName; + public DocumentationFileWalker() : base(SyntaxWalkerDepth.StructuredTrivia) { } private int ClassDepth { get; set; } + public int InterfaceDepth { get; set; } private bool InsideMultiLineDocumentation { get; set; } private bool InsideAutoIncludeMethodBlock { get; set; } private bool InsideFluentOrInitializerExample { get; set; } + private bool IncludeMethodBlockContainsLambda { get; set; } + private int EndLine { get; set; } public List Blocks { get; } = new List(); + public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) + { + if (node.ChildNodes().All(childNode => childNode is PropertyDeclarationSyntax || childNode is AttributeListSyntax)) + { + // simple nested interface + var line = node.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line; + var walker = new CodeWithDocumentationWalker(0, line); + walker.Visit(node); + this.Blocks.AddRange(walker.Blocks); + } + } + public override void VisitClassDeclaration(ClassDeclarationSyntax node) { ++ClassDepth; if (ClassDepth == 1) { base.VisitClassDeclaration(node); - } - // are we dealing with a simple nested POCO? + } else if (node.ChildNodes().All(childNode => childNode is PropertyDeclarationSyntax || childNode is AttributeListSyntax)) - { + { + // simple nested POCO var line = node.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line; var walker = new CodeWithDocumentationWalker(ClassDepth - 2, line); walker.Visit(node); this.Blocks.AddRange(walker.Blocks); } + else + { + var methods = node.ChildNodes().OfType(); + if (!methods.Any(m => m.AttributeLists.SelectMany(a => a.Attributes).Any())) + { + // nested class with methods that are not unit or integration tests e.g. example PropertyVisitor in Automap.doc.cs + var line = node.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line; + var walker = new CodeWithDocumentationWalker(ClassDepth - 2, line); + walker.Visit(node); + this.Blocks.AddRange(walker.Blocks); + } + } --ClassDepth; } public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) { - var propertyName = node.Identifier.Text; - if (propertyName == "Fluent") - { - this.InsideFluentOrInitializerExample = true; - base.VisitPropertyDeclaration(node); - this.InsideFluentOrInitializerExample = false; - } - else if (propertyName == "Initializer") + _propertyOrMethodName = node.Identifier.Text; + if (PropertyOrMethodNamesOfInterest.Contains(_propertyOrMethodName)) { + // TODO: Look to get the generic types for the call so that we can prettify the fluent and OIS calls in docs e.g. client.Search({Call}); + // var genericArguments = node.DescendantNodes().OfType().FirstOrDefault(); + // List arguments = new List(); + // if (genericArguments != null) + // { + // arguments.AddRange(genericArguments.TypeArgumentList.Arguments); + // } + this.InsideFluentOrInitializerExample = true; base.VisitPropertyDeclaration(node); this.InsideFluentOrInitializerExample = false; @@ -57,11 +97,11 @@ public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) public override void VisitArrowExpressionClause(ArrowExpressionClauseSyntax node) { - if (!this.InsideFluentOrInitializerExample) return; + if (!this.InsideFluentOrInitializerExample && !PropertyOrMethodNamesOfInterest.Contains(_propertyOrMethodName)) return; var syntaxNode = node?.ChildNodes()?.LastOrDefault()?.WithAdditionalAnnotations(); if (syntaxNode == null) return; var line = node.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line; - var walker = new CodeWithDocumentationWalker(ClassDepth, line); + var walker = new CodeWithDocumentationWalker(ClassDepth, line, _propertyOrMethodName); walker.Visit(syntaxNode); this.Blocks.AddRange(walker.Blocks); } @@ -72,7 +112,7 @@ public override void VisitAccessorDeclaration(AccessorDeclarationSyntax node) var syntaxNode = node?.ChildNodes()?.LastOrDefault()?.WithAdditionalAnnotations() as BlockSyntax; if (syntaxNode == null) return; var line = node.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line; - var walker = new CodeWithDocumentationWalker(ClassDepth, line); + var walker = new CodeWithDocumentationWalker(ClassDepth, line, _propertyOrMethodName); walker.VisitBlock(syntaxNode); this.Blocks.AddRange(walker.Blocks); } @@ -80,8 +120,11 @@ public override void VisitAccessorDeclaration(AccessorDeclarationSyntax node) public override void VisitMethodDeclaration(MethodDeclarationSyntax node) { if (this.ClassDepth == 1) this.InsideAutoIncludeMethodBlock = true; + _propertyOrMethodName = node.Identifier.Text; base.VisitMethodDeclaration(node); this.InsideAutoIncludeMethodBlock = false; + this.IncludeMethodBlockContainsLambda = false; + this.EndLine = 0; } public override void VisitExpressionStatement(ExpressionStatementSyntax node) @@ -89,19 +132,27 @@ public override void VisitExpressionStatement(ExpressionStatementSyntax node) if (this.InsideAutoIncludeMethodBlock) { var line = node.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line; + + // this lambda has already been included so skip it + if (IncludeMethodBlockContainsLambda && this.EndLine >= line) + { + return; + } + var allchildren = node.DescendantNodesAndTokens(descendIntoTrivia: true); if (allchildren.Any(a => a.Kind() == SyntaxKind.MultiLineDocumentationCommentTrivia)) { - var walker = new CodeWithDocumentationWalker(ClassDepth, line); + var walker = new CodeWithDocumentationWalker(ClassDepth, line, _propertyOrMethodName); walker.Visit(node.WithAdditionalAnnotations()); this.Blocks.AddRange(walker.Blocks); return; } base.VisitExpressionStatement(node); - this.Blocks.Add(new CodeBlock(node.WithoutLeadingTrivia().ToFullString(), line)); + var code = node.WithoutLeadingTrivia().ToFullString(); + code = code.RemoveNumberOfLeadingTabsAfterNewline(ClassDepth + 2); + this.Blocks.Add(new CodeBlock(code, line, Language.CSharp, _propertyOrMethodName)); } else base.VisitExpressionStatement(node); - } public override void VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node) @@ -109,15 +160,25 @@ public override void VisitLocalDeclarationStatement(LocalDeclarationStatementSyn if (this.InsideAutoIncludeMethodBlock) { var allchildren = node.DescendantNodesAndTokens(descendIntoTrivia: true); - var line = node.SyntaxTree.GetLineSpan(node.Span).StartLinePosition.Line; + var linePositionSpan = node.SyntaxTree.GetLineSpan(node.Span); + var line = linePositionSpan.StartLinePosition.Line; if (allchildren.Any(a => a.Kind() == SyntaxKind.MultiLineDocumentationCommentTrivia)) { - var walker = new CodeWithDocumentationWalker(ClassDepth, line); + var walker = new CodeWithDocumentationWalker(ClassDepth, line, _propertyOrMethodName); walker.Visit(node.WithAdditionalAnnotations()); this.Blocks.AddRange(walker.Blocks); return; } - this.Blocks.Add(new CodeBlock(node.WithoutLeadingTrivia().ToFullString(), line)); + var code = node.WithoutLeadingTrivia().ToFullString(); + code = code.RemoveNumberOfLeadingTabsAfterNewline(ClassDepth + 2); + this.Blocks.Add(new CodeBlock(code, line, Language.CSharp, _propertyOrMethodName)); + + if (allchildren.Any(a => a.Kind() == SyntaxKind.SimpleLambdaExpression)) + { + // nested lambda inside this local declaration + this.IncludeMethodBlockContainsLambda = true; + this.EndLine = linePositionSpan.EndLinePosition.Line; + } } base.VisitLocalDeclarationStatement(node); } @@ -131,23 +192,24 @@ public override void VisitTrivia(SyntaxTrivia trivia) } this.InsideMultiLineDocumentation = true; - this.CreateTextBlocksFromTrivia(trivia); - this.InsideMultiLineDocumentation = false; - } - private void CreateTextBlocksFromTrivia(SyntaxTrivia trivia) - { - var tokens = trivia.ToFullString().TrimStart('/', '*').TrimEnd('*', '/').Split('\n'); + var tokens = trivia.ToFullString() + .RemoveLeadingAndTrailingMultiLineComments() + .SplitOnNewLines(StringSplitOptions.None); var builder = new StringBuilder(); + foreach (var token in tokens) { - var decodedToken = System.Net.WebUtility.HtmlDecode(token.Trim().Trim('*').Trim()); + var currentToken = token.RemoveLeadingSpacesAndAsterisk(); + var decodedToken = System.Net.WebUtility.HtmlDecode(currentToken); builder.AppendLine(decodedToken); } var text = builder.ToString(); var line = trivia.SyntaxTree.GetLineSpan(trivia.Span).StartLinePosition.Line; this.Blocks.Add(new TextBlock(text, line)); + + this.InsideMultiLineDocumentation = false; } } } diff --git a/src/CodeGeneration/Nest.Litterateur/project.json b/src/CodeGeneration/Nest.Litterateur/project.json index 3365fd2e146..b1bccdffd1a 100644 --- a/src/CodeGeneration/Nest.Litterateur/project.json +++ b/src/CodeGeneration/Nest.Litterateur/project.json @@ -5,7 +5,7 @@ "emitEntryPoint": true }, "dependencies": { - + "Newtonsoft.Json": "8.0.2", }, "commands": { "Nest.Litterateur": "Nest.Litterateur" @@ -13,55 +13,68 @@ "configurations": { "Debug": { "compilationOptions": { - "define": [ "DEBUG", "TRACE" ] + "define": [ + "DEBUG", + "TRACE" + ] } }, "Release": { "compilationOptions": { - "define": [ "RELEASE", "TRACE" ], + "define": [ + "RELEASE", + "TRACE" + ], "optimize": true } } }, - "frameworks": { - "dnx451": { - "frameworkAssemblies": { - "System.Runtime": "", - "System.Runtime.Serialization": "", - "System.Threading.Tasks": "", - "System.Text.Encoding": "" - }, - "dependencies": { - "Microsoft.CSharp": "4.0.1-beta-23409", - "Microsoft.CodeAnalysis": "1.1.1" - } - }, - "dotnet5.1": { - "compilationOptions": { "define": [ "DOTNETCORE" ] }, - "dependencies": { - "System.Runtime": "4.0.21-beta-23225", - "System.Collections": "4.0.11-beta-23225", - "System.Reflection": "4.1.0-beta-23225", - "System.Collections.Specialized": "4.0.0-beta-23109", - "System.Linq": "4.0.0-beta-23109", - "System.IO.FileSystem": "4.0.0-beta-23109", - "System.IO.Compression": "4.0.0-beta-23109", - "System.Runtime.Serialization.Primitives": "4.0.10-beta-23109", - "System.Text.RegularExpressions": "4.0.10-beta-23109", - "System.Collections.Concurrent": "4.0.10-beta-23109", - "System.Reflection.Extensions": "4.0.0-beta-23109", - "System.Reflection.TypeExtensions": "4.0.0-beta-23109", - "System.Reflection.Metadata": "1.1.0-alpha-00009", - "System.Reflection.Primitives": "4.0.0-beta-23109", - "System.Linq.Expressions": "4.0.10-beta-23109", - "System.Dynamic.Runtime": "4.0.11-beta-23225", - "Microsoft.CSharp": "4.0.1-beta-23409", - "Microsoft.CodeAnalysis": "1.1.1", - "System.Security.Cryptography.Encoding": "4.0.0-beta-23225", - "System.Security.Cryptography.X509Certificates": "4.0.0-beta-23225", - "System.ComponentModel.TypeConverter": "4.0.0-beta-23109", - "System.Net.Http": "4.0.1-beta-23225" - } - } + "frameworks": { + "dnx451": { + "frameworkAssemblies": { + "System.Runtime": "", + "System.Runtime.Serialization": "", + "System.Threading.Tasks": "", + "System.Text.Encoding": "", + "System.IO": "" + }, + "dependencies": { + "Microsoft.CSharp": "4.0.1-beta-23409", + "Microsoft.CodeAnalysis": "1.1.1", + "AsciiDocNet": "1.0.0-alpha2" + } + }, + "dotnet5.1": { + "compilationOptions": { + "define": [ + "DOTNETCORE" + ] + }, + "dependencies": { + "System.Runtime": "4.0.21-beta-23225", + "System.Collections": "4.0.11-beta-23225", + "System.Reflection": "4.1.0-beta-23225", + "System.Collections.Specialized": "4.0.0-beta-23109", + "System.Linq": "4.0.0-beta-23109", + "System.IO": "4.0.0-beta-23109", + "System.IO.FileSystem": "4.0.0-beta-23109", + "System.IO.Compression": "4.0.0-beta-23109", + "System.Runtime.Serialization.Primitives": "4.0.10-beta-23109", + "System.Text.RegularExpressions": "4.0.10-beta-23109", + "System.Collections.Concurrent": "4.0.10-beta-23109", + "System.Reflection.Extensions": "4.0.0-beta-23109", + "System.Reflection.TypeExtensions": "4.0.0-beta-23109", + "System.Reflection.Metadata": "1.1.0-alpha-00009", + "System.Reflection.Primitives": "4.0.0-beta-23109", + "System.Linq.Expressions": "4.0.10-beta-23109", + "System.Dynamic.Runtime": "4.0.11-beta-23225", + "Microsoft.CSharp": "4.0.1-beta-23409", + "Microsoft.CodeAnalysis": "1.1.1", + "System.Security.Cryptography.Encoding": "4.0.0-beta-23225", + "System.Security.Cryptography.X509Certificates": "4.0.0-beta-23225", + "System.ComponentModel.TypeConverter": "4.0.0-beta-23109", + "System.Net.Http": "4.0.1-beta-23225" + } } + } } \ No newline at end of file diff --git a/src/Profiling/Profiling.csproj b/src/Profiling/Profiling.csproj index 3289de50989..eeec95cfa03 100644 --- a/src/Profiling/Profiling.csproj +++ b/src/Profiling/Profiling.csproj @@ -32,6 +32,24 @@ prompt 4 + + + + <__paket__xunit_core_props>win81\xunit.core + + + + + <__paket__xunit_core_props>wpa81\xunit.core + + + + + <__paket__xunit_core_props>portable-net45+win8+wp8+wpa81\xunit.core + + + + @@ -658,23 +676,6 @@ - - - - <__paket__xunit_core_props>win81\xunit.core - - - - - <__paket__xunit_core_props>wpa81\xunit.core - - - - - <__paket__xunit_core_props>portable-net45+win8+wp8+wpa81\xunit.core - - - @@ -751,6 +752,5 @@ - - + \ No newline at end of file diff --git a/src/Tests/Aggregations/Bucket/Children/ChildrenAggregationMapping.doc.cs b/src/Tests/Aggregations/Bucket/Children/ChildrenAggregationMapping.doc.cs index c17558c70db..10a5b6ac3b7 100644 --- a/src/Tests/Aggregations/Bucket/Children/ChildrenAggregationMapping.doc.cs +++ b/src/Tests/Aggregations/Bucket/Children/ChildrenAggregationMapping.doc.cs @@ -3,6 +3,7 @@ namespace Tests.Aggregations.Bucket.Children { + /** == Child Aggregation Mapping */ public class ChildrenAggregationMapping { private void MappingExample() @@ -12,10 +13,10 @@ private void MappingExample() * index with two mapped types, `project` and `commitactivity` and * we add a `_parent` mapping from `commitactivity` to `parent` */ var createProjectIndex = TestClient.GetClient().CreateIndex(typeof(Project), c => c - .Mappings(map=>map - .Map(m=>m.AutoMap()) - .Map(m=>m - .Parent() + .Mappings(map => map + .Map(tm => tm.AutoMap()) + .Map(tm => tm + .Parent() //<1> Set the parent of `CommitActivity` to the `Project` type ) ) ); diff --git a/src/Tests/Aggregations/Bucket/Children/ChildrenAggregationUsageTests.cs b/src/Tests/Aggregations/Bucket/Children/ChildrenAggregationUsageTests.cs index 29dc4b93ef0..50609f8fb52 100644 --- a/src/Tests/Aggregations/Bucket/Children/ChildrenAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Bucket/Children/ChildrenAggregationUsageTests.cs @@ -10,9 +10,8 @@ namespace Tests.Aggregations.Bucket.Children * A special single bucket aggregation that enables aggregating from buckets on parent document types to * buckets on child documents. * - * Be sure to read the elasticsearch documentation {ref}/search-aggregations-bucket-children-aggregation.html[on this subject here] + * Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-children-aggregation.html[Children Aggregation] */ - public class ChildrenAggregationUsageTests : AggregationUsageTestBase { public ChildrenAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } @@ -54,30 +53,10 @@ public ChildrenAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : b { Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(CommitActivity)) { - Aggregations = + Aggregations = new AverageAggregation("average_per_child", "confidenceFactor") && new MaxAggregation("max_per_child", "confidenceFactor") } }; } - - - // TODO : move this to a general documentation test explaining how to - // combine aggregations using boolean operators? - - public class ChildrenAggregationDslUsage : ChildrenAggregationUsageTests - { - public ChildrenAggregationDslUsage(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } - - protected override SearchRequest Initializer => - new SearchRequest - { - Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(CommitActivity)) - { - Aggregations = - new AverageAggregation("average_per_child", Field(p => p.ConfidenceFactor)) - && new MaxAggregation("max_per_child", Field(p => p.ConfidenceFactor)) - } - }; - } } diff --git a/src/Tests/Aggregations/Bucket/DateHistogram/DateHistogramAggregationUsageTests.cs b/src/Tests/Aggregations/Bucket/DateHistogram/DateHistogramAggregationUsageTests.cs index c160bbea132..1ccdd77536e 100644 --- a/src/Tests/Aggregations/Bucket/DateHistogram/DateHistogramAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Bucket/DateHistogram/DateHistogramAggregationUsageTests.cs @@ -9,15 +9,15 @@ namespace Tests.Aggregations.Bucket.DateHistogram { /** - * A multi-bucket aggregation similar to the histogram except it can only be applied on date values. - * From a functionality perspective, this histogram supports the same features as the normal histogram. + * A multi-bucket aggregation similar to the histogram except it can only be applied on date values. + * From a functionality perspective, this histogram supports the same features as the normal histogram. * The main difference is that the interval can be specified by date/time expressions. * - * When both format and extended_bounds are specified, the `date_optional_time` format is included - * as part of the format value so that Elasticsearch to be able to parse - * the serialized DateTimes of extended_bounds correctly. + * NOTE: When specifying a `format` **and** `extended_bounds`, in order for Elasticsearch to be able to parse + * the serialized `DateTime` of `extended_bounds` correctly, the `date_optional_time` format is included + * as part of the `format` value. * - * Be sure to read the elasticsearch documentation {ref}/search-aggregations-bucket-datehistogram-aggregation.html[on this subject here] + * Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-datehistogram-aggregation.html[Date Histogram Aggregation]. */ public class DateHistogramAggregationUsageTests : AggregationUsageTestBase { @@ -35,7 +35,7 @@ public DateHistogramAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage field = "startedOn", interval = "month", min_doc_count = 2, - format = "yyyy-MM-dd'T'HH:mm:ss||date_optional_time", + format = "yyyy-MM-dd'T'HH:mm:ss||date_optional_time", //<1> Note the inclusion of `date_optional_time` to `format` order = new { _count = "asc" }, extended_bounds = new { @@ -117,12 +117,12 @@ public DateHistogramAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage protected override void ExpectResponse(ISearchResponse response) { + /** === Handling responses + * Using the `.Aggs` aggregation helper on `ISearchResponse`, we can fetch our aggregation results easily + * in the correct type. <> + */ response.IsValid.Should().BeTrue(); - /** - * Using the `.Agg` aggregation helper we can fetch our aggregation results easily - * in the correct type. [Be sure to read more about `.Agg` vs `.Aggregations` on the response here]() - */ var dateHistogram = response.Aggs.DateHistogram("projects_started_per_month"); dateHistogram.Should().NotBeNull(); dateHistogram.Buckets.Should().NotBeNull(); diff --git a/src/Tests/Aggregations/Bucket/DateRange/DateRangeAggregationUsageTests.cs b/src/Tests/Aggregations/Bucket/DateRange/DateRangeAggregationUsageTests.cs index ea3ad958bae..765291b3292 100644 --- a/src/Tests/Aggregations/Bucket/DateRange/DateRangeAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Bucket/DateRange/DateRangeAggregationUsageTests.cs @@ -10,10 +10,12 @@ namespace Tests.Aggregations.Bucket.DateRange { /** * A range aggregation that is dedicated for date values. The main difference between this aggregation and the normal range aggregation is that the `from` - * and `to` values can be expressed in Date Math expressions, and it is also possible to specify a date format by which the from and to response fields will be returned. - * Note that this aggregation includes the from value and excludes the to value for each range. + * and `to` values can be expressed in `DateMath` expressions, and it is also possible to specify a date format by which the from and + * to response fields will be returned. * - * Be sure to read the elasticsearch documentation {ref}/search-aggregations-bucket-daterange-aggregation.html[on this subject here] + * IMPORTANT: this aggregation includes the `from` value and excludes the `to` value for each range. + * + * Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-daterange-aggregation.html[Date Range Aggregation] */ public class DateRangeAggregationUsageTests : AggregationUsageTestBase { @@ -30,9 +32,9 @@ public DateRangeAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : field = "startedOn", ranges = new object[] { - new { to = "now", from = "2015-06-06T12:01:02.123||+2d" }, - new { to = "now+1d-30m/h" }, - new { from = "2012-05-05||+1d-1m" }, + new { to = "now", from = "2015-06-06T12:01:02.123||+2d" }, + new { to = "now+1d-30m/h" }, + new { from = "2012-05-05||+1d-1m" }, } }, aggs = new @@ -66,9 +68,9 @@ public DateRangeAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : Field = Field(p => p.StartedOn), Ranges = new List { - {new DateRangeExpression { From = DateMath.Anchored(FixedDate).Add("2d"), To = DateMath.Now} }, - {new DateRangeExpression { To = DateMath.Now.Add(TimeSpan.FromDays(1)).Subtract("30m").RoundTo(TimeUnit.Hour) } }, - {new DateRangeExpression { From = DateMath.Anchored("2012-05-05").Add(TimeSpan.FromDays(1)).Subtract("1m") } } + new DateRangeExpression { From = DateMath.Anchored(FixedDate).Add("2d"), To = DateMath.Now}, + new DateRangeExpression { To = DateMath.Now.Add(TimeSpan.FromDays(1)).Subtract("30m").RoundTo(TimeUnit.Hour) }, + new DateRangeExpression { From = DateMath.Anchored("2012-05-05").Add(TimeSpan.FromDays(1)).Subtract("1m") } }, Aggregations = new TermsAggregation("project_tags") { Field = Field(p => p.Tags) } @@ -77,17 +79,17 @@ public DateRangeAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : protected override void ExpectResponse(ISearchResponse response) { + /** === Handling Responses + * Using the `.Agg` aggregation helper we can fetch our aggregation results easily + * in the correct type. <> + */ response.IsValid.Should().BeTrue(); - /** - * Using the `.Agg` aggregation helper we can fetch our aggregation results easily - * in the correct type. [Be sure to read more about `.Agg` vs `.Aggregations` on the response here]() - */ var dateHistogram = response.Aggs.DateRange("projects_date_ranges"); dateHistogram.Should().NotBeNull(); dateHistogram.Buckets.Should().NotBeNull(); - /** We specified three ranges so we expect to three of them in the response */ + /** We specified three ranges so we expect to have three of them in the response */ dateHistogram.Buckets.Count.Should().Be(3); foreach (var item in dateHistogram.Buckets) { diff --git a/src/Tests/Aggregations/Bucket/Filter/FilterAggregationUsageTests.cs b/src/Tests/Aggregations/Bucket/Filter/FilterAggregationUsageTests.cs index bd491dff198..d502a6578b1 100644 --- a/src/Tests/Aggregations/Bucket/Filter/FilterAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Bucket/Filter/FilterAggregationUsageTests.cs @@ -10,12 +10,11 @@ namespace Tests.Aggregations.Bucket.Filter { /** - * Defines a single bucket of all the documents in the current document set context that match a specified filter. + * Defines a single bucket of all the documents in the current document set context that match a specified filter. * Often this will be used to narrow down the current aggregation context to a specific set of documents. * - * Be sure to read the elasticsearch documentation {ref}/search-aggregations-bucket-filter-aggregation.html[on this subject here] + * Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-filter-aggregation.html[Filter Aggregation] */ - public class FilterAggregationUsageTests : AggregationUsageTestBase { public FilterAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) @@ -68,12 +67,12 @@ public FilterAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : bas protected override void ExpectResponse(ISearchResponse response) { + /** === Handling Responses + * Using the `.Aggs` aggregation helper we can fetch our aggregation results easily + * in the correct type. <> + */ response.IsValid.Should().BeTrue(); - /** - * Using the `.Agg` aggregation helper we can fetch our aggregation results easily - * in the correct type. [Be sure to read more about `.Agg` vs `.Aggregations` on the response here]() - */ var filterAgg = response.Aggs.Filter("bethels_projects"); filterAgg.Should().NotBeNull(); filterAgg.DocCount.Should().BeGreaterThan(0); @@ -83,6 +82,11 @@ protected override void ExpectResponse(ISearchResponse response) } } + /**[float] + * == Empty Filter + * When the collection of filters is empty or all are conditionless, NEST will serialize them + * to an empty object. + */ public class EmptyFilterAggregationUsageTests : AggregationUsageTestBase { public EmptyFilterAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } @@ -125,10 +129,13 @@ protected override void ExpectResponse(ISearchResponse response) } } + /**[float] + * == Inline Script Filter + */ //reproduce of https://github.com/elastic/elasticsearch-net/issues/1931 public class InlineScriptFilterAggregationUsageTests : AggregationUsageTestBase { - private string _ctxNumberofcommits = "_source.numberOfCommits > 0"; + private string _ctxNumberofCommits = "_source.numberOfCommits > 0"; private string _aggName = "script_filter"; public InlineScriptFilterAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } @@ -140,7 +147,7 @@ public InlineScriptFilterAggregationUsageTests(ReadOnlyCluster i, EndpointUsage filter = new { script = new { script = new { - inline = _ctxNumberofcommits + inline = _ctxNumberofCommits } } } @@ -153,7 +160,7 @@ public InlineScriptFilterAggregationUsageTests(ReadOnlyCluster i, EndpointUsage .Filter(_aggName, date => date .Filter(f => f .Script(b => b - .Inline(_ctxNumberofcommits) + .Inline(_ctxNumberofCommits) ) ) ) @@ -166,7 +173,7 @@ public InlineScriptFilterAggregationUsageTests(ReadOnlyCluster i, EndpointUsage { Filter = new ScriptQuery { - Inline = _ctxNumberofcommits + Inline = _ctxNumberofCommits } } }; diff --git a/src/Tests/Aggregations/Bucket/Filters/FiltersAggregationUsageTests.cs b/src/Tests/Aggregations/Bucket/Filters/FiltersAggregationUsageTests.cs index efa0fd0fa9d..0a7fe59644b 100644 --- a/src/Tests/Aggregations/Bucket/Filters/FiltersAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Bucket/Filters/FiltersAggregationUsageTests.cs @@ -9,13 +9,16 @@ namespace Tests.Aggregations.Bucket.Filters { - /** == Named filters - * + /** * Defines a multi bucket aggregations where each bucket is associated with a filter. * Each bucket will collect all documents that match its associated filter. For documents - * that do not match any filter, these will be collected in the other bucket. + * that do not match any filter, these will be collected in the _other bucket_. * - * Be sure to read the elasticsearch documentation {ref}/search-aggregations-bucket-filters-aggregation.html[on this subject here] + * Be sure to read the Elasticsearch documentation {ref_current}/search-aggregations-bucket-filters-aggregation.html[Filters Aggregation] + */ + + /**[float] + * == Named filters */ public class FiltersAggregationUsageTests : AggregationUsageTestBase { @@ -71,9 +74,9 @@ public FiltersAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : ba OtherBucketKey = "other_states_of_being", Filters = new NamedFiltersContainer { - { "belly_up", Query.Term(p=>p.State, StateOfBeing.BellyUp) }, - { "stable", Query.Term(p=>p.State, StateOfBeing.Stable) }, - { "very_active", Query.Term(p=>p.State, StateOfBeing.VeryActive) } + { "belly_up", Query.Term(p=>p.State, StateOfBeing.BellyUp) }, + { "stable", Query.Term(p=>p.State, StateOfBeing.Stable) }, + { "very_active", Query.Term(p=>p.State, StateOfBeing.VeryActive) } }, Aggregations = new TermsAggregation("project_tags") { Field = Field(p => p.CuratedTags.First().Name) } @@ -82,12 +85,12 @@ public FiltersAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : ba protected override void ExpectResponse(ISearchResponse response) { - response.IsValid.Should().BeTrue(); - - /** + /** === Handling Responses * Using the `.Agg` aggregation helper we can fetch our aggregation results easily - * in the correct type. [Be sure to read more about `.Agg` vs `.Aggregations` on the response here]() + * in the correct type. <> */ + response.IsValid.Should().BeTrue(); + var filterAgg = response.Aggs.Filters("projects_by_state"); filterAgg.Should().NotBeNull(); @@ -109,7 +112,9 @@ protected override void ExpectResponse(ISearchResponse response) } } - /** == Anonymous filters **/ + /**[float] + *== Anonymous filters + */ public class AnonymousUsage : AggregationUsageTestBase { public AnonymousUsage(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } @@ -160,23 +165,26 @@ public AnonymousUsage(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { OtherBucket = true, Filters = new List { - Query.Term(p=>p.State, StateOfBeing.BellyUp) , - Query.Term(p=>p.State, StateOfBeing.Stable) , - Query.Term(p=>p.State, StateOfBeing.VeryActive) + Query.Term(p=>p.State, StateOfBeing.BellyUp) , + Query.Term(p=>p.State, StateOfBeing.Stable) , + Query.Term(p=>p.State, StateOfBeing.VeryActive) }, Aggregations = - new TermsAggregation("project_tags") { Field = Field(p => p.CuratedTags.First().Name) } + new TermsAggregation("project_tags") + { + Field = Field(p => p.CuratedTags.First().Name) + } } }; protected override void ExpectResponse(ISearchResponse response) { - response.IsValid.Should().BeTrue(); - - /** + /** === Handling Responses * Using the `.Agg` aggregation helper we can fetch our aggregation results easily - * in the correct type. [Be sure to read more about `.Agg` vs `.Aggregations` on the response here]() + * in the correct type. <> */ + response.IsValid.Should().BeTrue(); + var filterAgg = response.Aggs.Filters("projects_by_state"); filterAgg.Should().NotBeNull(); var results = filterAgg.AnonymousBuckets(); @@ -187,10 +195,13 @@ protected override void ExpectResponse(ISearchResponse response) singleBucket.DocCount.Should().BeGreaterThan(0); } - results.Last().DocCount.Should().Be(0); + results.Last().DocCount.Should().Be(0); // <1> The last bucket is the _other bucket_ } } + /**[float] + * == Empty Filters + */ public class EmptyFiltersAggregationUsageTests : AggregationUsageTestBase { public EmptyFiltersAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } @@ -232,6 +243,8 @@ protected override void ExpectResponse(ISearchResponse response) } } + /**[float] + * == Conditionless Filters */ public class ConditionlessFiltersAggregationUsageTests : AggregationUsageTestBase { public ConditionlessFiltersAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } @@ -277,4 +290,4 @@ protected override void ExpectResponse(ISearchResponse response) response.Aggs.Filters("conditionless_filters").Buckets.Should().BeEmpty(); } } -} \ No newline at end of file +} diff --git a/src/Tests/Aggregations/Bucket/IpRange/IpRangeAggregationUsageTests.cs b/src/Tests/Aggregations/Bucket/IpRange/IpRangeAggregationUsageTests.cs index 5d0ade378c2..dce2f25f00b 100644 --- a/src/Tests/Aggregations/Bucket/IpRange/IpRangeAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Bucket/IpRange/IpRangeAggregationUsageTests.cs @@ -20,7 +20,7 @@ public IpRangeAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : ba { ip_range = new { - field = Field(p => p.LeadDeveloper.IPAddress), + field = "leadDeveloper.iPAddress", ranges = new object[] { new { to = "10.0.0.5" }, diff --git a/src/Tests/Aggregations/Bucket/Range/RangeAggregationUsageTests.cs b/src/Tests/Aggregations/Bucket/Range/RangeAggregationUsageTests.cs index 3d5a405572c..491b95ed639 100644 --- a/src/Tests/Aggregations/Bucket/Range/RangeAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Bucket/Range/RangeAggregationUsageTests.cs @@ -21,7 +21,7 @@ public RangeAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base { range = new { - field = Field(p => p.NumberOfCommits), + field = "numberOfCommits", ranges = new object[] { new { to = 100.0 }, diff --git a/src/Tests/Aggregations/Bucket/ReverseNested/ReverseNestedAggregationUsageTests.cs b/src/Tests/Aggregations/Bucket/ReverseNested/ReverseNestedAggregationUsageTests.cs index c848cd88e7b..b375feaf0d7 100644 --- a/src/Tests/Aggregations/Bucket/ReverseNested/ReverseNestedAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Bucket/ReverseNested/ReverseNestedAggregationUsageTests.cs @@ -40,7 +40,7 @@ public ReverseNestedAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage { terms = new { - field = Field(p => p.Name) + field = "name" } } } diff --git a/src/Tests/Aggregations/Bucket/SignificantTerms/SignificantTermsAggregationUsageTests.cs b/src/Tests/Aggregations/Bucket/SignificantTerms/SignificantTermsAggregationUsageTests.cs index 9c56525d42e..9ad94445552 100644 --- a/src/Tests/Aggregations/Bucket/SignificantTerms/SignificantTermsAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Bucket/SignificantTerms/SignificantTermsAggregationUsageTests.cs @@ -19,7 +19,7 @@ public SignificantTermsAggregationUsageTests(ReadOnlyCluster i, EndpointUsage us { significant_terms = new { - field = Field(p => p.Name), + field = "name", min_doc_count = 10, mutual_information = new { diff --git a/src/Tests/Aggregations/Bucket/Terms/TermsAggregationUsageTests.cs b/src/Tests/Aggregations/Bucket/Terms/TermsAggregationUsageTests.cs index cdb7bb5082a..227917ddf8d 100644 --- a/src/Tests/Aggregations/Bucket/Terms/TermsAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Bucket/Terms/TermsAggregationUsageTests.cs @@ -24,7 +24,7 @@ public TermsAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base }, terms = new { - field = Field(p => p.State), + field = "state", min_doc_count = 2, size = 5, shard_size = 100, diff --git a/src/Tests/Aggregations/Metric/Average/AverageAggregationUsageTests.cs b/src/Tests/Aggregations/Metric/Average/AverageAggregationUsageTests.cs index 4f7d4f29670..bf7f41a07a8 100644 --- a/src/Tests/Aggregations/Metric/Average/AverageAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Metric/Average/AverageAggregationUsageTests.cs @@ -12,6 +12,9 @@ public class AverageAggregationUsageTests : AggregationUsageTestBase { public AverageAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } + /// + /// + /// protected override object ExpectJson => new { aggs = new @@ -24,7 +27,7 @@ public AverageAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : ba }, avg = new { - field = Field(p => p.NumberOfCommits), + field = "numberOfCommits", missing = 10.0, script = new { diff --git a/src/Tests/Aggregations/Metric/Cardinality/CardinalityAggregationUsageTests.cs b/src/Tests/Aggregations/Metric/Cardinality/CardinalityAggregationUsageTests.cs index ccdd2b391ac..b77cf6816b3 100644 --- a/src/Tests/Aggregations/Metric/Cardinality/CardinalityAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Metric/Cardinality/CardinalityAggregationUsageTests.cs @@ -19,7 +19,7 @@ public CardinalityAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) { cardinality = new { - field = Field(p => p.State), + field = "state", precision_threshold = 100 } } diff --git a/src/Tests/Aggregations/Metric/ExtendedStats/ExtendedStatsAggregationUsageTests.cs b/src/Tests/Aggregations/Metric/ExtendedStats/ExtendedStatsAggregationUsageTests.cs index eddd5140909..f20c7de7d0b 100644 --- a/src/Tests/Aggregations/Metric/ExtendedStats/ExtendedStatsAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Metric/ExtendedStats/ExtendedStatsAggregationUsageTests.cs @@ -19,7 +19,7 @@ public ExtendedStatsAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage { extended_stats = new { - field = Field(p => p.NumberOfCommits) + field = "numberOfCommits" } } } diff --git a/src/Tests/Aggregations/Metric/GeoBounds/GeoBoundsAggregationUsageTests.cs b/src/Tests/Aggregations/Metric/GeoBounds/GeoBoundsAggregationUsageTests.cs index f5c90d1b911..03062bb15e9 100644 --- a/src/Tests/Aggregations/Metric/GeoBounds/GeoBoundsAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Metric/GeoBounds/GeoBoundsAggregationUsageTests.cs @@ -19,7 +19,7 @@ public GeoBoundsAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : { geo_bounds = new { - field = Field(p => p.Location), + field = "location", wrap_longitude = true } } diff --git a/src/Tests/Aggregations/Metric/Max/MaxAggregationUsageTests.cs b/src/Tests/Aggregations/Metric/Max/MaxAggregationUsageTests.cs index 750d2b0b375..eebc4719a86 100644 --- a/src/Tests/Aggregations/Metric/Max/MaxAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Metric/Max/MaxAggregationUsageTests.cs @@ -19,7 +19,7 @@ public MaxAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i { max = new { - field = Field(p => p.NumberOfCommits) + field = "numberOfCommits" } } } diff --git a/src/Tests/Aggregations/Metric/Min/MinAggregationUsageTests.cs b/src/Tests/Aggregations/Metric/Min/MinAggregationUsageTests.cs index effd8911f3d..f915e938ff6 100644 --- a/src/Tests/Aggregations/Metric/Min/MinAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Metric/Min/MinAggregationUsageTests.cs @@ -19,7 +19,7 @@ public MinAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i { min = new { - field = Field(p => p.NumberOfCommits) + field = "numberOfCommits" } } } diff --git a/src/Tests/Aggregations/Metric/PercentileRanks/PercentileRanksAggregationUsageTests.cs b/src/Tests/Aggregations/Metric/PercentileRanks/PercentileRanksAggregationUsageTests.cs index 0fc35aa96ef..1137a591914 100644 --- a/src/Tests/Aggregations/Metric/PercentileRanks/PercentileRanksAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Metric/PercentileRanks/PercentileRanksAggregationUsageTests.cs @@ -20,7 +20,7 @@ public PercentileRanksAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usa { percentile_ranks = new { - field = Field(p => p.NumberOfCommits), + field = "numberOfCommits", values = new [] { 15.0, 30.0 }, tdigest = new { diff --git a/src/Tests/Aggregations/Metric/Percentiles/PercentilesAggregationUsageTests.cs b/src/Tests/Aggregations/Metric/Percentiles/PercentilesAggregationUsageTests.cs index b5084517d02..36e886201fd 100644 --- a/src/Tests/Aggregations/Metric/Percentiles/PercentilesAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Metric/Percentiles/PercentilesAggregationUsageTests.cs @@ -19,7 +19,7 @@ public PercentilesAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) { percentiles = new { - field = Field(p => p.NumberOfCommits), + field = "numberOfCommits", percents = new[] { 95.0, 99.0, 99.9 }, hdr = new { diff --git a/src/Tests/Aggregations/Metric/Stats/StatsAggregationUsageTests.cs b/src/Tests/Aggregations/Metric/Stats/StatsAggregationUsageTests.cs index bab79f5e989..dfd29d1694c 100644 --- a/src/Tests/Aggregations/Metric/Stats/StatsAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Metric/Stats/StatsAggregationUsageTests.cs @@ -19,7 +19,7 @@ public StatsAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base { stats = new { - field = Field(p => p.NumberOfCommits) + field = "numberOfCommits" } } } diff --git a/src/Tests/Aggregations/Metric/Sum/SumAggregationUsageTests.cs b/src/Tests/Aggregations/Metric/Sum/SumAggregationUsageTests.cs index 706d3a7e54e..de2bfd84d57 100644 --- a/src/Tests/Aggregations/Metric/Sum/SumAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Metric/Sum/SumAggregationUsageTests.cs @@ -19,7 +19,7 @@ public SumAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i { sum = new { - field = Field(p => p.NumberOfCommits) + field = "numberOfCommits" } } } diff --git a/src/Tests/Aggregations/Metric/TopHits/TopHitsAggregationUsageTests.cs b/src/Tests/Aggregations/Metric/TopHits/TopHitsAggregationUsageTests.cs index d6113ea5837..9c3c59a60cf 100644 --- a/src/Tests/Aggregations/Metric/TopHits/TopHitsAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Metric/TopHits/TopHitsAggregationUsageTests.cs @@ -21,7 +21,7 @@ public TopHitsAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : ba { terms = new { - field = Field(p => p.State), + field = "state", }, aggs = new { @@ -41,12 +41,12 @@ public TopHitsAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : ba }, _source = new { - include = new string[] { "name", "startedOn" } + include = new [] { "name", "startedOn" } }, size = 1, version = true, explain = true, - fielddata_fields = new string[] { "state", "numberOfCommits" }, + fielddata_fields = new [] { "state", "numberOfCommits" }, highlight = new { fields = new diff --git a/src/Tests/Aggregations/Metric/ValueCount/ValueCountAggregationUsageTests.cs b/src/Tests/Aggregations/Metric/ValueCount/ValueCountAggregationUsageTests.cs index a646b488260..675b6a61b51 100644 --- a/src/Tests/Aggregations/Metric/ValueCount/ValueCountAggregationUsageTests.cs +++ b/src/Tests/Aggregations/Metric/ValueCount/ValueCountAggregationUsageTests.cs @@ -19,7 +19,7 @@ public ValueCountAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : { value_count = new { - field = Field(p => p.NumberOfCommits) + field = "numberOfCommits" } } } diff --git a/src/Tests/Aggregations/WritingAggregations.doc.cs b/src/Tests/Aggregations/WritingAggregations.doc.cs index 44b61ec73dc..7f07c03a071 100644 --- a/src/Tests/Aggregations/WritingAggregations.doc.cs +++ b/src/Tests/Aggregations/WritingAggregations.doc.cs @@ -5,121 +5,183 @@ using static Nest.Infer; using System.Collections.Generic; using System.Linq; +using Tests.Aggregations.Bucket.Children; +using Tests.Framework.Integration; +using FluentAssertions; namespace Tests.Aggregations { - public class WritingAggregations + /** + *== Writing Aggregations + * NEST allows you to write your aggregations using + * + * - a strict fluent DSL + * - a verbatim object initializer syntax that maps verbatim to the Elasticsearch API + * - a more terse object initializer aggregation DSL + * + * Three different ways, yikes that's a lot to take in! Lets go over them one by one and explain when you might + * want to use each. + */ + public class Usage : UsageTestBase, SearchRequest> { /** - * Aggregations are arguably one of the most powerful features of Elasticsearch. - * NEST allows you to write your aggregations using a strict fluent dsl, a verbatim object initializer - * syntax that maps verbatim to the elasticsearch API & a more terse object initializer aggregation DSL. - * - * Three different ways, yikes thats a lot to take in! Lets go over them one by one and explain when you might - * want to use which one. - */ - public class Usage : UsageTestBase, SearchRequest> + * This is the json output for each example + **/ + protected override object ExpectJson => new { - protected override object ExpectJson => new + aggs = new { - aggs = new + name_of_child_agg = new { - name_of_child_agg = new + children = new { - children = new { type = "commits" }, - aggs = new { - average_per_child = new + type = "commits" + }, + aggs = new + { + average_per_child = new + { + avg = new { - avg = new { field = "confidenceFactor" } - }, - max_per_child = new + field = "confidenceFactor" + } + }, + max_per_child = new + { + max = new { - max = new { field = "confidenceFactor" } + field = "confidenceFactor" } } } } - }; - /** - * The fluent lambda syntax is the most terse way to write aggregations. - * It benefits from types that are carried over to sub aggregations - */ - protected override Func, ISearchRequest> Fluent => s => s - .Aggregations(aggs => aggs - .Children("name_of_child_agg", child => child - .Aggregations(childAggs => childAggs - .Average("average_per_child", avg => avg.Field(p => p.ConfidenceFactor)) - .Max("max_per_child", avg => avg.Field(p => p.ConfidenceFactor)) - ) + } + }; + + /** === Fluent DSL + * The fluent lambda syntax is the most terse way to write aggregations. + * It benefits from types that are carried over to sub aggregations + */ + protected override Func, ISearchRequest> Fluent => s => s + .Aggregations(aggs => aggs + .Children("name_of_child_agg", child => child + .Aggregations(childAggs => childAggs + .Average("average_per_child", avg => avg.Field(p => p.ConfidenceFactor)) + .Max("max_per_child", avg => avg.Field(p => p.ConfidenceFactor)) ) - ); + ) + ); - /** - * The object initializer syntax (OIS) is a one-to-one mapping with how aggregations - * have to be represented in the Elasticsearch API. While it has the benefit of being a one-to-one - * mapping, being dictionary based in C# means it can grow exponentially in complexity rather quickly. - */ - protected override SearchRequest Initializer => - new SearchRequest + /** === Object Initializer syntax + * The object initializer syntax (OIS) is a one-to-one mapping with how aggregations + * have to be represented in the Elasticsearch API. While it has the benefit of being a one-to-one + * mapping, being dictionary based in C# means it can grow exponentially in complexity rather quickly. + */ + protected override SearchRequest Initializer => + new SearchRequest + { + Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(CommitActivity)) { - Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(CommitActivity)) - { - Aggregations = - new AverageAggregation("average_per_child", "confidenceFactor") - && new MaxAggregation("max_per_child", "confidenceFactor") - } - }; - } + Aggregations = + new AverageAggregation("average_per_child", "confidenceFactor") + && new MaxAggregation("max_per_child", "confidenceFactor") + } + }; + } - public class AggregationDslUsage : Usage - { - /** - * For this reason the OIS syntax can be shortened dramatically by using `*Agg` related family, - * These allow you to forego introducing intermediary Dictionaries to represent the aggregation DSL. - * It also allows you to combine multiple aggregations using bitwise AND (`&&`) operator. - * - * Compare the following example with the previous vanilla OIS syntax - */ - protected override SearchRequest Initializer => - new SearchRequest + public class AggregationDslUsage : Usage + { + /** === Terse Object Initializer DSL + * For this reason the OIS syntax can be shortened dramatically by using `*Agg` related family, + * These allow you to forego introducing intermediary Dictionaries to represent the aggregation DSL. + * It also allows you to combine multiple aggregations using bitwise AND (`&&`) operator. + * + * Compare the following example with the previous vanilla OIS syntax + */ + protected override SearchRequest Initializer => + new SearchRequest + { + Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(CommitActivity)) { - Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(CommitActivity)) - { - Aggregations = - new AverageAggregation("average_per_child", Field(p => p.ConfidenceFactor)) - && new MaxAggregation("max_per_child", Field(p => p.ConfidenceFactor)) - } - }; - } + Aggregations = + new AverageAggregation("average_per_child", Field(p => p.ConfidenceFactor)) + && new MaxAggregation("max_per_child", Field(p => p.ConfidenceFactor)) + } + }; + } - public class AdvancedAggregationDslUsage : Usage + public class AdvancedAggregationDslUsage : Usage + { + /** === Aggregating over a collection of aggregations + * An advanced scenario may involve an existing collection of aggregation functions that should be set as aggregations + * on the request. Using LINQ's `.Aggregate()` method, each function can be applied to the aggregation descriptor + * (`childAggs` below) in turn, returning the descriptor after each function application. + * + */ + protected override Func, ISearchRequest> Fluent { - /** - * An advanced scenario may involve an existing collection of aggregation functions that should be set as aggregations - * on the request. Using LINQ's `.Aggregate()` method, each function can be applied to the aggregation descriptor - * (`childAggs` below) in turn, returning the descriptor after each function application. - * - */ - protected override Func, ISearchRequest> Fluent - { - get + get + { + var aggregations = new List, IAggregationContainer>> //<1> a list of aggregation functions to apply { - var aggregations = new List, IAggregationContainer>> - { - a => a.Average("average_per_child", avg => avg.Field(p => p.ConfidenceFactor)), - a => a.Max("max_per_child", avg => avg.Field(p => p.ConfidenceFactor)) - }; + a => a.Average("average_per_child", avg => avg.Field(p => p.ConfidenceFactor)), + a => a.Max("max_per_child", avg => avg.Field(p => p.ConfidenceFactor)) + }; - return s => s - .Aggregations(aggs => aggs - .Children("name_of_child_agg", child => child - .Aggregations(childAggs => - aggregations.Aggregate(childAggs, (acc, agg) => { agg(acc); return acc; }) - ) + return s => s + .Aggregations(aggs => aggs + .Children("name_of_child_agg", child => child + .Aggregations(childAggs => + aggregations.Aggregate(childAggs, (acc, agg) => { agg(acc); return acc; }) // <2> Using LINQ's `Aggregate()` function to accumulate/apply all of the aggregation functions ) - ); - } + ) + ); } } } + + /**[[aggs-vs-aggregations]] + *=== Aggs vs. Aggregations + * + * The response exposes both `.Aggregations` and `.Aggs` properties for handling aggregations. Why two properties you ask? + * Well, the former is a dictionary of aggregation names to `IAggregate` types, a common interface for + * aggregation responses (termed __Aggregates__ in NEST), and the latter is a convenience helper to get the right type + * of aggregation response out of the dictionary based on a key name. + * + * This is better illustrated with an example + */ + public class AggsUsage : ChildrenAggregationUsageTests + { + public AggsUsage(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } + + /** Let's imagine we make the following request. */ + protected override Func, ISearchRequest> Fluent => s => s + .Aggregations(aggs => aggs + .Children("name_of_child_agg", child => child + .Aggregations(childAggs => childAggs + .Average("average_per_child", avg => avg.Field(p => p.ConfidenceFactor)) + .Max("max_per_child", avg => avg.Field(p => p.ConfidenceFactor)) + ) + ) + ); + + /**=== Aggs usage + * Now, using `.Aggs`, we can easily get the `Children` aggregation response out and from that, + * the `Average` and `Max` sub aggregations. + */ + protected override void ExpectResponse(ISearchResponse response) + { + response.IsValid.Should().BeTrue(); + + var childAggregation = response.Aggs.Children("name_of_child_agg"); + + var averagePerChild = childAggregation.Average("average_per_child"); + + averagePerChild.Should().NotBeNull(); //<1> Do something with the average per child. Here we just assert it's not null + + var maxPerChild = childAggregation.Max("max_per_child"); + + maxPerChild.Should().NotBeNull(); //<2> Do something with the max per child. Here we just assert it's not null + } + } } diff --git a/src/Tests/Analysis/AnalysisCrudTests.cs b/src/Tests/Analysis/AnalysisCrudTests.cs index 790a6d42510..7b1239c8a8a 100644 --- a/src/Tests/Analysis/AnalysisCrudTests.cs +++ b/src/Tests/Analysis/AnalysisCrudTests.cs @@ -14,8 +14,8 @@ public class AnalysisCrudTests : CrudTestBase { /** - * # Analysis crud - * + * == Analysis crud + * * In this example we will create an index with analysis settings, read those settings back, update the analysis settings * and do another read after the update to assert our new analysis setting is applied. * There is NO mechanism to delete an analysis setting in elasticsearch. @@ -77,7 +77,7 @@ protected override LazyResponses Read() => Calls u.Index(indexName); /** - * Here we assert over the response from `GetIndexSettings()` after the index creation to make sure our analysis chain did infact + * Here we assert over the response from `GetIndexSettings()` after the index creation to make sure our analysis chain did infact * store our html char filter called `stripMe` */ protected override void ExpectAfterCreate(IGetIndexSettingsResponse response) diff --git a/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/ConnectionPooling.Doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/ConnectionPooling.Doc.cs index 8b783966feb..712f57f7e97 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/ConnectionPooling.Doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/ConnectionPooling.Doc.cs @@ -9,15 +9,22 @@ namespace Tests.ClientConcepts.ConnectionPooling.BuildingBlocks { public class ConnectionPooling { - /** = Connection Pooling + /**== Connection Pooling * Connection pooling is the internal mechanism that takes care of registering what nodes there are in the cluster and which - * we can use to issue client calls on. + * NEST can use to issue client calls on. There are four types of connection pool + * + * - <> + * - <> + * - <> + * - <> */ - - /** == SingleNodeConnectionPool - * The simplest of all connection pools, this takes a single `Uri` and uses that to connect to elasticsearch for all the calls + + /** + * [[single-node-connection-pool]] + * === SingleNodeConnectionPool + * The simplest of all connection pools, this takes a single `Uri` and uses that to connect to Elasticsearch for all the calls * It doesn't opt in to sniffing and pinging behavior, and will never mark nodes dead or alive. The one `Uri` it holds is always - * ready to go. + * ready to go. */ [U] public void SingleNode() { @@ -27,13 +34,13 @@ [U] public void SingleNode() var node = pool.Nodes.First(); node.Uri.Port.Should().Be(9201); - /** This type of pool is hardwired to opt out of sniffing*/ + /** This type of pool is hardwired to opt out of reseeding (and hence sniffing)*/ pool.SupportsReseeding.Should().BeFalse(); /** and pinging */ pool.SupportsPinging.Should().BeFalse(); - /** When you use the low ceremony ElasticClient constructor that takes a single Uri, - * We default to this SingleNodeConnectionPool */ + /** When you use the low ceremony `ElasticClient` constructor that takes a single `Uri`, + * We default to using `SingleNodeConnectionPool` */ var client = new ElasticClient(uri); client.ConnectionSettings.ConnectionPool.Should().BeOfType(); @@ -46,8 +53,9 @@ [U] public void SingleNode() client.ConnectionSettings.ConnectionPool.Should().BeOfType(); } - /** == StaticConnectionPool - * The static connection pool is great if you have a known small sized cluster and do no want to enable + /**[[static-connection-pool]] + * === StaticConnectionPool + * The static connection pool is great if you have a known small sized cluster and do no want to enable * sniffing to find out the cluster topology. */ [U] public void Static() @@ -57,25 +65,26 @@ [U] public void Static() /** a connection pool can be seeded using an enumerable of `Uri`s */ var pool = new StaticConnectionPool(uris); - /** Or using an enumerable of `Node` */ - var nodes = uris.Select(u=>new Node(u)); + /** Or using an enumerable of `Node`s */ + var nodes = uris.Select(u => new Node(u)); pool = new StaticConnectionPool(nodes); - /** This type of pool is hardwired to opt out of sniffing*/ + /** This type of pool is hardwired to opt out of reseeding (and hence sniffing)*/ pool.SupportsReseeding.Should().BeFalse(); /** but supports pinging when enabled */ pool.SupportsPinging.Should().BeTrue(); - /** To create a client using this static connection pool pass - * the connection pool to the connectionsettings you pass to ElasticClient + /** To create a client using this static connection pool, pass + * the connection pool to the `ConnectionSettings` you pass to `ElasticClient` */ var client = new ElasticClient(new ConnectionSettings(pool)); client.ConnectionSettings.ConnectionPool.Should().BeOfType(); } - /** == SniffingConnectionPool - * A subclass of StaticConnectionPool that allows itself to be reseeded at run time. + /**[[sniffing-connection-pool]] + * === SniffingConnectionPool + * A subclass of `StaticConnectionPool` that allows itself to be reseeded at run time. * It comes with a very minor overhead of a `ReaderWriterLockSlim` to ensure thread safety. */ [U] public void Sniffing() @@ -85,25 +94,60 @@ [U] public void Sniffing() /** a connection pool can be seeded using an enumerable of `Uri` */ var pool = new SniffingConnectionPool(uris); - /** Or using an enumerable of `Node` - * A major benefit here is you can include known node roles when seeding + /** Or using an enumerable of `Node`s. + * A major benefit here is you can include known node roles when seeding and * NEST can use this information to favour sniffing on master eligible nodes first * and take master only nodes out of rotation for issuing client calls on. */ var nodes = uris.Select(u=>new Node(u)); pool = new SniffingConnectionPool(nodes); - /** This type of pool is hardwired to opt in to sniffing*/ + /** This type of pool is hardwired to opt in to reseeding (and hence sniffing)*/ pool.SupportsReseeding.Should().BeTrue(); /** and pinging */ pool.SupportsPinging.Should().BeTrue(); - /** To create a client using the sniffing connection pool pass - * the connection pool to the connectionsettings you pass to ElasticClient + /** To create a client using the sniffing connection pool pass + * the connection pool to the `ConnectionSettings` you pass to `ElasticClient` */ var client = new ElasticClient(new ConnectionSettings(pool)); client.ConnectionSettings.ConnectionPool.Should().BeOfType(); } + + /**[[sticky-connection-pool]] + * === StickyConnectionPool + * A type of `IConnectionPool` that returns the first live node such that it is sticky between + * requests. + * It uses https://msdn.microsoft.com/en-us/library/system.threading.interlocked(v=vs.110).aspx[`System.Threading.Interlocked`] + * to keep an _indexer_ to the last live node in a thread safe manner. + */ + [U] public void Sticky() + { + var uris = Enumerable.Range(9200, 5).Select(p => new Uri("http://localhost:" + p)); + + /** a connection pool can be seeded using an enumerable of `Uri` */ + var pool = new StickyConnectionPool(uris); + + /** Or using an enumerable of `Node`. + * A major benefit here is you can include known node roles when seeding and + * NEST can use this information to favour sniffing on master eligible nodes first + * and take master only nodes out of rotation for issuing client calls on. + */ + var nodes = uris.Select(u=>new Node(u)); + pool = new StickyConnectionPool(nodes); + + /** This type of pool is hardwired to opt out of reseeding (and hence sniffing)*/ + pool.SupportsReseeding.Should().BeFalse(); + + /** but does support pinging */ + pool.SupportsPinging.Should().BeTrue(); + + /** To create a client using the sticky connection pool pass + * the connection pool to the `ConnectionSettings` you pass to `ElasticClient` + */ + var client = new ElasticClient(new ConnectionSettings(pool)); + client.ConnectionSettings.ConnectionPool.Should().BeOfType(); + } } } diff --git a/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/DateTimeProviders.Doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/DateTimeProviders.Doc.cs index 472e0af5659..426aed21c95 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/DateTimeProviders.Doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/DateTimeProviders.Doc.cs @@ -8,12 +8,11 @@ namespace Tests.ClientConcepts.ConnectionPooling.BuildingBlocks { public class DateTimeProviders { - - /** = Date time providers + /**== Date time providers * * Not typically something you'll have to pass to the client but all calls to `System.DateTime.UtcNow` - * in the client have been abstracted by `IDateTimeProvider`. This allows us to unit test timeouts and clusterfailover - * in run time not being bound to wall clock time. + * in the client have been abstracted by `IDateTimeProvider`. This allows us to unit test timeouts and cluster failover + * without being bound to wall clock time as calculated by using `System.DateTime.UtcNow` directly. */ [U] public void DefaultNowBehaviour() { @@ -31,8 +30,8 @@ [U] public void DeadTimeoutCalculation() { var dateTimeProvider = DateTimeProvider.Default; /** - * The default timeout calculation is: `min(timeout * 2 ^ (attempts * 0.5 -1), maxTimeout)` - * The default values for `timeout` and `maxTimeout` are + * The default timeout calculation is: `min(timeout * 2 ^ (attempts * 0.5 -1), maxTimeout)`, where the + * default values for `timeout` and `maxTimeout` are */ var timeout = TimeSpan.FromMinutes(1); var maxTimeout = TimeSpan.FromMinutes(30); diff --git a/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/KeepingTrackOfNodes.Doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/KeepingTrackOfNodes.Doc.cs index 31419eb4e5a..b2f9e0ab316 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/KeepingTrackOfNodes.Doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/KeepingTrackOfNodes.Doc.cs @@ -8,12 +8,13 @@ namespace Tests.ClientConcepts.ConnectionPooling.BuildingBlocks public class KeepingTrackOfNodes { - /** = Keeping track of nodes - * + /**== Keeping track of nodes */ - [U] public void Creating() { + /** === Creating a Node + * A `Node` can be instantiated by passing it a `Uri` + */ var node = new Node(new Uri("http://localhost:9200")); node.Uri.Should().NotBeNull(); node.Uri.Port.Should().Be(9200); @@ -21,19 +22,26 @@ [U] public void Creating() /** By default master eligible and holds data is presumed to be true **/ node.MasterEligible.Should().BeTrue(); node.HoldsData.Should().BeTrue(); + /** Is resurrected is true on first usage, hints to the transport that a ping might be useful */ node.IsResurrected.Should().BeTrue(); - /** When instantiating your connection pool you could switch these to false to initialize the client to - * a known cluster topology. + /** + * When instantiating your connection pool you could switch these to false to initialize the client to + * a known cluster topology. */ } [U] public void BuildingPaths() { - /** passing a node with a path should be preserved. Sometimes an elasticsearch node lives behind a proxy */ + /** === Building a Node path + * passing a node with a path should be preserved. + * Sometimes an Elasticsearch node lives behind a proxy + */ var node = new Node(new Uri("http://test.example/elasticsearch")); + node.Uri.Port.Should().Be(80); node.Uri.AbsolutePath.Should().Be("/elasticsearch/"); - /** We force paths to end with a forward slash so that they can later be safely combined */ + + /** *We force paths to end with a forward slash* so that they can later be safely combined */ var combinedPath = new Uri(node.Uri, "index/type/_search"); combinedPath.AbsolutePath.Should().Be("/elasticsearch/index/type/_search"); @@ -42,13 +50,14 @@ [U] public void BuildingPaths() combinedPath.AbsolutePath.Should().Be("/elasticsearch/index/type/_search"); } + /** === Marking Nodes */ [U] public void MarkNodes() { var node = new Node(new Uri("http://localhost:9200")); node.FailedAttempts.Should().Be(0); node.IsAlive.Should().BeTrue(); - /** - * every time a node is marked dead the number of attempts should increase + /** + * every time a node is marked dead, the number of attempts should increase * and the passed datetime should be exposed. */ for(var i = 0; i<10;i++) @@ -59,7 +68,7 @@ [U] public void MarkNodes() node.IsAlive.Should().BeFalse(); node.DeadUntil.Should().Be(deadUntil); } - /** however when marking a node alive deaduntil should be reset and attempts reset to 0*/ + /** however when marking a node alive, the `DeadUntil` property should be reset and `FailedAttempts` reset to 0*/ node.MarkAlive(); node.FailedAttempts.Should().Be(0); node.DeadUntil.Should().Be(default(DateTime)); @@ -68,15 +77,20 @@ [U] public void MarkNodes() [U] public void Equality() { - /** Nodes are considered equal if they have the same endpoint no matter what other metadata is associated */ + /** === Node Equality + * Nodes are considered equal if they have the same endpoint, no matter what other metadata is associated */ var node = new Node(new Uri("http://localhost:9200")) { MasterEligible = false }; var nodeAsMaster = new Node(new Uri("http://localhost:9200")) { MasterEligible = true }; + (node == nodeAsMaster).Should().BeTrue(); (node != nodeAsMaster).Should().BeFalse(); + var uri = new Uri("http://localhost:9200"); (node == uri).Should().BeTrue(); + var differentUri = new Uri("http://localhost:9201"); (node != differentUri).Should().BeTrue(); + node.Should().Be(nodeAsMaster); } } diff --git a/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/RequestPipelines.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/RequestPipelines.doc.cs index 5ce62fa6731..815a9d1cb48 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/RequestPipelines.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/RequestPipelines.doc.cs @@ -7,33 +7,50 @@ using Tests.Framework; namespace Tests.ClientConcepts.ConnectionPooling.BuildingBlocks -{ - public class RequestPipelines +{ + /**== Request Pipeline + * Every request is executed in the context of a `RequestPipeline` when using the + * default <> implementation. + */ + public class RequestPipelines { - /** = Request pipeline - * Every request is executed in the context of `RequestPipeline` when using the default `ITransport` implementation. - * - */ - [U] public void RequestPipeline() { var settings = TestClient.CreateSettings(); - /** When calling Request(Async) on Transport the whole coordination of the request is deferred to a new instance in a `using` block. */ - var pipeline = new RequestPipeline(settings, DateTimeProvider.Default, new MemoryStreamFactory(), new SearchRequestParameters()); + /** When calling `Request()` or `RequestAsync()` on an `ITransport`, + * the whole coordination of the request is deferred to a new instance in a `using` block. + */ + var pipeline = new RequestPipeline( + settings, + DateTimeProvider.Default, + new MemoryStreamFactory(), + new SearchRequestParameters()); + pipeline.GetType().Should().Implement(); - /** However the transport does not instantiate RequestPipeline directly, it uses a pluggable `IRequestPipelineFactory`*/ + /** An `ITransport` does not instantiate a `RequestPipeline` directly; it uses a pluggable `IRequestPipelineFactory` + * to create it + */ var requestPipelineFactory = new RequestPipelineFactory(); - var requestPipeline = requestPipelineFactory.Create(settings, DateTimeProvider.Default, new MemoryStreamFactory(), new SearchRequestParameters()); + var requestPipeline = requestPipelineFactory.Create( + settings, + DateTimeProvider.Default, //<1> An <> + new MemoryStreamFactory(), + new SearchRequestParameters()); + requestPipeline.Should().BeOfType(); requestPipeline.GetType().Should().Implement(); - /** which can be passed to the transport when instantiating a client */ - var transport = new Transport(settings, requestPipelineFactory, DateTimeProvider.Default, new MemoryStreamFactory()); - - /** this allows you to have requests executed on your own custom request pipeline */ + /** You can pass your own `IRequestPipeline` implementation to the Transport when instantiating a client, + * allowing you to have requests executed on your own custom request pipeline + */ + var transport = new Transport( + settings, + requestPipelineFactory, + DateTimeProvider.Default, + new MemoryStreamFactory()); } private IRequestPipeline CreatePipeline( @@ -45,26 +62,31 @@ private IRequestPipeline CreatePipeline( return new FixedPipelineFactory(settings, dateTimeProvider ?? DateTimeProvider.Default).Pipeline; } + /**=== Pipeline Behavior + *==== Sniffing on First usage + */ [U] public void FirstUsageCheck() { var singleNodePipeline = CreatePipeline(uris => new SingleNodeConnectionPool(uris.First())); var staticPipeline = CreatePipeline(uris => new StaticConnectionPool(uris)); var sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris)); - /** Here we have setup three pipelines using three different connection pools, lets see how they behave*/ + /** Here we have setup three pipelines using three different connection pools. Let's see how they behave + * on first usage + */ singleNodePipeline.FirstPoolUsageNeedsSniffing.Should().BeFalse(); staticPipeline.FirstPoolUsageNeedsSniffing.Should().BeFalse(); sniffingPipeline.FirstPoolUsageNeedsSniffing.Should().BeTrue(); - - /** Only the cluster that supports reseeding will opt in to FirstPoolUsageNeedsSniffing() - * You can however disable this on ConnectionSettings + /** We can see that only the cluster that supports reseeding will opt in to `FirstPoolUsageNeedsSniffing()`; + * You can however disable reseeding/sniffing on ConnectionSettings */ - sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris), s => s.SniffOnStartup(false)); + sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris), s => s.SniffOnStartup(false)); //<1> Disable sniffing on startup sniffingPipeline.FirstPoolUsageNeedsSniffing.Should().BeFalse(); } + /**==== Sniffing on Connection Failure */ [U] public void SniffsOnConnectionFailure() { @@ -75,20 +97,27 @@ public void SniffsOnConnectionFailure() singleNodePipeline.SniffsOnConnectionFailure.Should().BeFalse(); staticPipeline.SniffsOnConnectionFailure.Should().BeFalse(); sniffingPipeline.SniffsOnConnectionFailure.Should().BeTrue(); - /** Only the cluster that supports reseeding will opt in to SniffsOnConnectionFailure() + + /** Only the cluster that supports reseeding will opt in to SniffsOnConnectionFailure() * You can however disable this on ConnectionSettings */ sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris), s => s.SniffOnConnectionFault(false)); sniffingPipeline.SniffsOnConnectionFailure.Should().BeFalse(); } + /**==== Sniffing on Stale cluster */ [U] public void SniffsOnStaleCluster() { var dateTime = new TestableDateTimeProvider(); - var singleNodePipeline = CreatePipeline(uris => new SingleNodeConnectionPool(uris.First(), dateTime), dateTimeProvider: dateTime); - var staticPipeline = CreatePipeline(uris => new StaticConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); - var sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); + var singleNodePipeline = CreatePipeline(uris => + new SingleNodeConnectionPool(uris.First(), dateTime), dateTimeProvider: dateTime); + + var staticPipeline = CreatePipeline(uris => + new StaticConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); + + var sniffingPipeline = CreatePipeline(uris => + new SniffingConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); singleNodePipeline.SniffsOnStaleCluster.Should().BeFalse(); staticPipeline.SniffsOnStaleCluster.Should().BeFalse(); @@ -110,16 +139,22 @@ public void SniffsOnStaleCluster() } - /** A request pipeline also checks whether the overall time across multiple retries exceeds the request timeout - * See the maxretry documentation for more details, here we assert that our request pipeline exposes this propertly + /**=== Retrying requests + * A request pipeline also checks whether the overall time across multiple retries exceeds the request timeout. + * See the <> for more details, here we assert that our request pipeline exposes this propertly */ [U] public void IsTakingTooLong() { var dateTime = new TestableDateTimeProvider(); - var singleNodePipeline = CreatePipeline(uris => new SingleNodeConnectionPool(uris.First(), dateTime), dateTimeProvider: dateTime); - var staticPipeline = CreatePipeline(uris => new StaticConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); - var sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); + var singleNodePipeline = CreatePipeline(uris => + new SingleNodeConnectionPool(uris.First(), dateTime), dateTimeProvider: dateTime); + + var staticPipeline = CreatePipeline(uris => + new StaticConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); + + var sniffingPipeline = CreatePipeline(uris => + new SniffingConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime); singleNodePipeline.IsTakingTooLong.Should().BeFalse(); staticPipeline.IsTakingTooLong.Should().BeFalse(); @@ -144,7 +179,9 @@ public void IsTakingTooLong() public void SetsSniffPathUsingToTimespan() { var dateTime = new TestableDateTimeProvider(); - var sniffingPipeline = CreatePipeline(uris => new SniffingConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime) as RequestPipeline; + var sniffingPipeline = CreatePipeline(uris => + new SniffingConnectionPool(uris, dateTimeProvider: dateTime), dateTimeProvider: dateTime) as RequestPipeline; + sniffingPipeline.SniffPath.Should().Be("_nodes/_all/settings?flat_settings&timeout=2s"); } } diff --git a/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/Transports.Doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/Transports.Doc.cs index a399c373213..7bdc38df663 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/Transports.Doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/BuildingBlocks/Transports.Doc.cs @@ -8,36 +8,44 @@ namespace Tests.ClientConcepts.ConnectionPooling.BuildingBlocks { public class Transports { - /** = Transports + /**== Transports * - * The `ITransport` interface can be seen as the motor block of the client. It's interface is deceitfully simple. - * It's ultimately responsible from translating a client call to a response. If for some reason you do not agree with the way we wrote - * the internals of the client, by implementing a custom `ITransport`, you can circumvent all of it and introduce your own. + * The `ITransport` interface can be seen as the motor block of the client. It's interface is deceitfully simple and + * it's ultimately responsible from translating a client call to a response. + * + * If for some reason you do not agree with the way we wrote the internals of the client, + * by implementing a custom `ITransport`, you can circumvent all of it and introduce your own. */ - public async Task InterfaceExplained() { - /** - * Transport is generically typed to a type that implements IConnectionConfigurationValues - * This is the minimum ITransport needs to report back for the client to function. + /** + * Transport is generically typed to a type that implements `IConnectionConfigurationValues` + * This is the minimum `ITransport` needs to report back for the client to function. * - * e.g in the low level client, transport is instantiated like this: + * In the low level client, `ElasticLowLevelClient`, a `Transport` is instantiated like this: */ var lowLevelTransport = new Transport(new ConnectionConfiguration()); - /** In the high level client like this: */ + /** and in the high level client, `ElasticClient`, like this: */ var highlevelTransport = new Transport(new ConnectionSettings()); var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); var inMemoryTransport = new Transport(new ConnectionSettings(connectionPool, new InMemoryConnection())); /** - * The only two methods on `ITransport` are `Request()` and `RequestAsync()`, the default `ITransport` implementation is responsible for introducing - * many of the building blocks in the client, if these do not work for you can swap them out for your own custom `ITransport` implementation. - * If you feel this need, please let us know as we'd love to learn why you've go down this route! + * The only two methods on `ITransport` are `Request()` and `RequestAsync()`; the default `ITransport` implementation is responsible for introducing + * many of the building blocks in the client. If you feel that the defaults do not work for you then you can swap them out for your own + * custom `ITransport` implementation and if you do, {github}/issues[please let us know] as we'd love to learn why you've go down this route! */ - var response = inMemoryTransport.Request>(HttpMethod.GET, "/_search", new { query = new { match_all = new { } } }); - response = await inMemoryTransport.RequestAsync>(HttpMethod.GET, "/_search", new { query = new { match_all = new { } } }); + var response = inMemoryTransport.Request>( + HttpMethod.GET, + "/_search", + new { query = new { match_all = new { } } }); + + response = await inMemoryTransport.RequestAsync>( + HttpMethod.GET, + "/_search", + new { query = new { match_all = new { } } }); } } } diff --git a/src/Tests/ClientConcepts/ConnectionPooling/Exceptions/UnrecoverableExceptions.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/Exceptions/UnrecoverableExceptions.doc.cs index 4cb492e30aa..fcb06d7eb2a 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/Exceptions/UnrecoverableExceptions.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/Exceptions/UnrecoverableExceptions.doc.cs @@ -13,22 +13,26 @@ namespace Tests.ClientConcepts.ConnectionPooling.Exceptions { public class UnrecoverableExceptions { - /** == Unrecoverable exceptions - * Unrecoverable exceptions are excepted exceptions that are grounds to exit the client pipeline immediately. - * By default the client won't throw on any ElasticsearchClientException but return an invalid response. - * You can configure the client to throw using ThrowExceptions() on ConnectionSettings. The following test - * both a client that throws and one that returns an invalid response with an `.OriginalException` exposed + /** == Unrecoverable exceptions + * Unrecoverable exceptions are _excepted_ exceptions that are grounds to exit the client pipeline immediately. + * By default, the client won't throw on any `ElasticsearchClientException` but instead return an invalid response which + * can be detected by checking `.IsValid` on the response + * You can configure the client to throw using `ThrowExceptions()` on `ConnectionSettings`. The following test + * both a client that throws and one that returns an invalid response with an `.OriginalException` exposed */ [U] public void SomePipelineFailuresAreRecoverable() { + /** The following are recoverable exceptions */ var recoverablExceptions = new[] { new PipelineException(PipelineFailure.BadResponse), new PipelineException(PipelineFailure.PingFailure), }; + recoverablExceptions.Should().OnlyContain(e => e.Recoverable); + /** and the unrecoverable exceptions */ var unrecoverableExceptions = new[] { new PipelineException(PipelineFailure.CouldNotStartSniffOnStartup), @@ -38,9 +42,13 @@ [U] public void SomePipelineFailuresAreRecoverable() new PipelineException(PipelineFailure.MaxRetriesReached), new PipelineException(PipelineFailure.MaxTimeoutReached) }; + unrecoverableExceptions.Should().OnlyContain(e => !e.Recoverable); } + /** As an example, let's set up a 10 node cluster that will always succeed when pinged but + will fail with a 401 response when making client calls + */ [U] public async Task BadAuthenticationIsUnrecoverable() { var audit = new Auditor(() => Framework.Cluster @@ -51,6 +59,9 @@ [U] public async Task BadAuthenticationIsUnrecoverable() .AllDefaults() ); + /** Here we make a client call and determine that the first audit event was a successful ping, + * followed by a bad response as a result of a bad authentication response + */ audit = await audit.TraceElasticsearchException( new ClientCall { { AuditEvent.PingSuccess, 9200 }, diff --git a/src/Tests/ClientConcepts/ConnectionPooling/Failover/FallingOver.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/Failover/FallingOver.doc.cs index a7d23a0873b..ead0e1ec70d 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/Failover/FallingOver.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/Failover/FallingOver.doc.cs @@ -9,8 +9,9 @@ namespace Tests.ClientConcepts.ConnectionPooling.FailOver { public class FallingOver { - /** == Fail over - * When using connection pooling and the pool has sufficient nodes a request will be retried if + /**[[falling-over]] + * == Fail over + * When using connection pooling and the pool has sufficient nodes a request will be retried if * the call to a node throws an exception or returns a 502 or 503 */ @@ -33,8 +34,10 @@ public async Task ExceptionFallsOverToNextNode() ); } - /** 502 Bad Gateway - * Will be treated as an error that requires retrying + /**[[bad-gateway]] + *=== 502 Bad Gateway + * + * Will be treated as an error that requires retrying */ [U] public async Task Http502FallsOver() @@ -55,8 +58,10 @@ public async Task Http502FallsOver() ); } - /** 503 Service Unavailable - * Will be treated as an error that requires retrying + /**[[service-unavailable]] + *=== 503 Service Unavailable + * + * Will be treated as an error that requires retrying */ [U] public async Task Http503FallsOver() @@ -78,7 +83,10 @@ public async Task Http503FallsOver() } /** - * If a call returns a valid http status code other then 502/503 the request won't be retried. + * If a call returns a valid http status code other than 502 or 503, the request won't be retried. + * + * IMPORTANT: Different requests may have different status codes that are deemed valid. For example, + * a *404 Not Found* response is a valid status code for an index exists request */ [U] public async Task HttpTeapotDoesNotFallOver() diff --git a/src/Tests/ClientConcepts/ConnectionPooling/MaxRetries/RespectsMaxRetry.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/MaxRetries/RespectsMaxRetry.doc.cs index 27b11b32ebe..e1da929061c 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/MaxRetries/RespectsMaxRetry.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/MaxRetries/RespectsMaxRetry.doc.cs @@ -8,12 +8,13 @@ namespace Tests.ClientConcepts.ConnectionPooling.MaxRetries { public class RespectsMaxRetry { - /** == MaxRetries - * By default retry as many times as we have nodes. However retries still respect the request timeout. - * Meaning if you have a 100 node cluster and a request timeout of 20 seconds we will retry as many times as we can - * but give up after 20 seconds + /**[[max-retries]] + *== Max Retries + * By default, NEST will retry as many times as there are nodes in the cluster that the client knows about. + * Retries still respects the request timeout however, + * meaning if you have a 100 node cluster and a request timeout of 20 seconds, + * the client will retry as many times as it before giving up at the request timeout of 20 seconds. */ - [U] public async Task DefaultMaxIsNumberOfNodes() { @@ -42,8 +43,9 @@ public async Task DefaultMaxIsNumberOfNodes() } /** - * When you have a 100 node cluster you might want to ensure a fixed number of retries. - * Remember that the actual number of requests is initial attempt + set number of retries + * When you have a 100 node cluster, you might want to ensure a fixed number of retries. + * + * IMPORTANT: the actual number of requests is **initial attempt + set number of retries** */ [U] @@ -68,9 +70,10 @@ public async Task FixedMaximumNumberOfRetries() ); } /** - * In our previous test we simulated very fast failures, in the real world a call might take upwards of a second - * Here we simulate a particular heavy search that takes 10 seconds to fail, our Request timeout is set to 20 seconds. - * In this case it does not make sense to retry our 10 second query on 10 nodes. We should try it twice and give up before a third call is attempted + * In our previous test we simulated very fast failures, but in the real world a call might take upwards of a second. + * In this next example, we simulate a particular heavy search that takes 10 seconds to fail, and set a request timeout of 20 seconds. + * We see that the request is tried twice and gives up before a third call is attempted, since the call takes 10 seconds and thus can be + * tried twice (initial call and one retry) before the request timeout. */ [U] public async Task RespectsOveralRequestTimeout() @@ -93,10 +96,11 @@ public async Task RespectsOveralRequestTimeout() } /** - * If you set smaller request time outs you might not want it to also affect the retry timeout, therefor you can configure these separately too. - * Here we simulate calls taking 3 seconds, a request time out of 2 and an overall retry timeout of 10 seconds. - * We should see 5 attempts to perform this query, testing that our request timeout cuts the query off short and that our max retry timeout of 10 - * wins over the configured request timeout + * If you set a smaller request timeout you might not want it to also affect the retry timeout. + * In cases like this, you can configure the `MaxRetryTimeout` separately. + * Here we simulate calls taking 3 seconds, a request timeout of 2 seconds and a max retry timeout of 10 seconds. + * We should see 5 attempts to perform this query, testing that our request timeout cuts the query off short and that + * our max retry timeout of 10 seconds wins over the configured request timeout */ [U] public async Task RespectsMaxRetryTimeoutOverRequestTimeout() @@ -122,7 +126,7 @@ public async Task RespectsMaxRetryTimeoutOverRequestTimeout() } /** - * If your retry policy expands beyond available nodes we won't retry the same node twice + * If your retry policy expands beyond the number of available nodes, the client **won't** retry the same node twice */ [U] public async Task RetriesAreLimitedByNodesInPool() @@ -145,9 +149,9 @@ public async Task RetriesAreLimitedByNodesInPool() } /** - * This makes setting any retry setting on a single node connection pool a NOOP, this is by design! - * Connection pooling and connection failover is about trying to fail sanely whilst still utilizing available resources and - * not giving up on the fail fast principle. It's *NOT* a mechanism for forcing requests to succeed. + * This makes setting any retry setting on a single node connection pool a no-op by design! + * Connection pooling and failover is all about trying to fail sanely whilst still utilizing the available resources and + * not giving up on the fail fast principle; **It is NOT a mechanism for forcing requests to succeed.** */ [U] public async Task DoesNotRetryOnSingleNodeConnectionPool() @@ -165,7 +169,6 @@ public async Task DoesNotRetryOnSingleNodeConnectionPool() { BadResponse, 9200 } } ); - } } } diff --git a/src/Tests/ClientConcepts/ConnectionPooling/Pinging/FirstUsage.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/Pinging/FirstUsage.doc.cs index 7339fd73400..d5c38557032 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/Pinging/FirstUsage.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/Pinging/FirstUsage.doc.cs @@ -11,17 +11,17 @@ namespace Tests.ClientConcepts.ConnectionPooling.Pinging { public class FirstUsage { - /** == Pinging - * - * Pinging is enabled by default for the Static & Sniffing connection pool. - * This means that the first time a node is used or resurrected we issue a ping with a smaller (configurable) timeout. - * This allows us to fail and fallover to a healthy node faster + /**== Pinging - First Usage + * + * Pinging is enabled by default for the <>, <> and <> connection pools. + * This means that the first time a node is used or resurrected, a ping is issued a with a small (configurable) timeout, + * allowing the client to fail and fallover to a healthy node much faster than attempting a request that may be heavier than a ping. */ [U, SuppressMessage("AsyncUsage", "AsyncFixer001:Unnecessary async/await usage", Justification = "Its a test")] public async Task PingFailsFallsOverToHealthyNodeWithoutPing() { - /** A cluster with 2 nodes where the second node fails on ping */ + /** Here's an example with a cluster with 2 nodes where the second node fails on ping */ var audit = new Auditor(() => Framework.Cluster .Nodes(2) .Ping(p => p.Succeeds(Always)) @@ -30,9 +30,15 @@ public async Task PingFailsFallsOverToHealthyNodeWithoutPing() .AllDefaults() ); + /** When making the calls, the first call goes to 9200 which succeeds, + * and the 2nd call does a ping on 9201 because it's used for the first time. + * The ping fails so we wrap over to node 9200 which we've already pinged. + * + * Finally we assert that the connectionpool has one node that is marked as dead + */ await audit.TraceCalls( - /** The first call goes to 9200 which succeeds */ - new ClientCall { + + new ClientCall { { PingSuccess, 9200}, { HealthyResponse, 9200}, { pool => @@ -40,12 +46,9 @@ await audit.TraceCalls( pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(0); } } }, - /** The 2nd call does a ping on 9201 because its used for the first time. - * It fails so we wrap over to node 9200 which we've already pinged */ - new ClientCall { + new ClientCall { { PingFailure, 9201}, { HealthyResponse, 9200}, - /** Finally we assert that the connectionpool has one node that is marked as dead */ { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(1) } } ); @@ -65,7 +68,7 @@ public async Task PingFailsFallsOverMultipleTimesToHealthyNode() await audit.TraceCalls( /** The first call goes to 9200 which succeeds */ - new ClientCall { + new ClientCall { { PingSuccess, 9200}, { HealthyResponse, 9200}, { pool => @@ -73,10 +76,10 @@ await audit.TraceCalls( pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(0); } } }, - /** The 2nd call does a ping on 9201 because its used for the first time. - * It fails and so we ping 9202 which also fails. We then ping 9203 becuase + /** The 2nd call does a ping on 9201 because its used for the first time. + * It fails and so we ping 9202 which also fails. We then ping 9203 becuase * we haven't used it before and it succeeds */ - new ClientCall { + new ClientCall { { PingFailure, 9201}, { PingFailure, 9202}, { PingSuccess, 9203}, @@ -89,7 +92,7 @@ await audit.TraceCalls( [U, SuppressMessage("AsyncUsage", "AsyncFixer001:Unnecessary async/await usage", Justification = "Its a test")] public async Task AllNodesArePingedOnlyOnFirstUseProvidedTheyAreHealthy() { - /** A healthy cluster of 4 (min master nodes of 3 of course!) */ + /**A healthy cluster of 4 (min master nodes of 3 of course!) */ var audit = new Auditor(() => Framework.Cluster .Nodes(4) .Ping(p => p.SucceedAlways()) diff --git a/src/Tests/ClientConcepts/ConnectionPooling/Pinging/Revival.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/Pinging/Revival.doc.cs index 3a2fce7d519..a4069be0f77 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/Pinging/Revival.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/Pinging/Revival.doc.cs @@ -11,12 +11,12 @@ namespace Tests.ClientConcepts.ConnectionPooling.Pinging { public class Revival { - /** == Pinging - * - * When a node is marked dead it will only be put in the dog house for a certain amount of time. Once it comes out of the dog house, or revived, we schedule a ping - * before the actual call to make sure its up and running. If its still down we put it back in the dog house a little longer. For an explanation on these timeouts see: TODO LINK + /**== Pinging - Revival + * + * When a node is marked dead it will only be put in the dog house for a certain amount of time. Once it comes out of the dog house, or revived, we schedule a ping + * before the actual call to make sure its up and running. If its still down we put it _back in the dog house_ a little longer. + * Take a look at the <> for an explanation on these timeouts. */ - [U] public async Task PingAfterRevival() { diff --git a/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/DisableSniffPingPerRequest.cs b/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/DisableSniffPingPerRequest.doc.cs similarity index 59% rename from src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/DisableSniffPingPerRequest.cs rename to src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/DisableSniffPingPerRequest.doc.cs index 427ff1156b3..88356bbb963 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/DisableSniffPingPerRequest.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/DisableSniffPingPerRequest.doc.cs @@ -1,92 +1,95 @@ -using System; -using System.Threading.Tasks; -using Elasticsearch.Net; -using Tests.Framework; -using static Elasticsearch.Net.AuditEvent; - -namespace Tests.ClientConcepts.ConnectionPooling.RequestOverrides -{ - public class DisableSniffPingPerRequest - { - /** == Disabling sniffing and pinging on a request basis - * Even if you are using a sniffing connection pool thats set up to sniff on start/failure - * and pinging enabled, you can opt out of this behaviour on a per request basis - * - * In our first test we set up a cluster that pings and sniffs on startup - * but we disable the sniffing on our first request so we only see the ping and the response - */ - - [U] public async Task DisableSniff() - { - var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.SucceedAlways()) - .SniffingConnectionPool() - .Settings(s => s.SniffOnStartup()) - ); - - audit = await audit.TraceCalls( - /** - * We disable sniffing so eventhoug its our first call we do not want to sniff on startup - */ - new ClientCall(r=>r.DisableSniffing()) { - { PingSuccess, 9200 }, - { HealthyResponse, 9200 } - }, - /** - * Instead the sniff on startup is deffered to the second call into the cluster that - * does not disable sniffing on a per request basis - */ - new ClientCall() - { - { SniffOnStartup }, - { SniffSuccess, 9200 }, - { PingSuccess, 9200 }, - { HealthyResponse, 9200 } - }, - /** - * And after that no sniff on startup will happen again - */ - new ClientCall() - { - { PingSuccess, 9201 }, - { HealthyResponse, 9201 } - } - ); - } - - [U] public async Task DisablePing() - { - var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.SucceedAlways()) - .SniffingConnectionPool() - .Settings(s => s.SniffOnStartup()) - ); - - audit = await audit.TraceCall( - new ClientCall(r=>r.DisablePing()) { - { SniffOnStartup }, - { SniffSuccess, 9200 }, - { HealthyResponse, 9200 } - } - ); - } - - [U] public async Task DisableSniffAndPing() - { - var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.SucceedAlways()) - .SniffingConnectionPool() - .Settings(s => s.SniffOnStartup()) - ); - - audit = await audit.TraceCall( - new ClientCall(r=>r.DisableSniffing().DisablePing()) { - { HealthyResponse, 9200 } - } - ); - } - } -} +using System; +using System.Threading.Tasks; +using Elasticsearch.Net; +using Tests.Framework; +using static Elasticsearch.Net.AuditEvent; + +namespace Tests.ClientConcepts.ConnectionPooling.RequestOverrides +{ + public class DisableSniffPingPerRequest + { + /**== Disabling sniffing and pinging on a request basis + * + * Even if you are using a sniffing connection pool thats set up to sniff on start/failure + * and pinging enabled, you can opt out of this behaviour on a _per request_ basis. + * + * In our first test we set up a cluster that pings and sniffs on startup + * but we disable the sniffing on our first request so we only see the ping and the response + */ + + [U] public async Task DisableSniff() + { + /** Let's set up the cluster and configure clients to **always** sniff on startup */ + var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.SucceedAlways()) + .SniffingConnectionPool() + .Settings(s => s.SniffOnStartup()) // <1> sniff on startup + ); + + + audit = await audit.TraceCalls( + /** Now We disable sniffing on the request so even though it's our first call, we do not want to sniff on startup */ + new ClientCall(r => r.DisableSniffing()) // <1> disable sniffing + { + { PingSuccess, 9200 }, // <2> first call is a successful ping + { HealthyResponse, 9200 } + }, + /** Instead, the sniff on startup is deferred to the second call into the cluster that + * does not disable sniffing on a per request basis + */ + new ClientCall() + { + { SniffOnStartup }, // <3> sniff on startup call happens here, on the second call + { SniffSuccess, 9200 }, + { PingSuccess, 9200 }, + { HealthyResponse, 9200 } + }, + /** And after that no sniff on startup will happen again */ + new ClientCall() + { + { PingSuccess, 9201 }, + { HealthyResponse, 9201 } + } + ); + } + + /** Now, let's disable pinging on the request */ + [U] public async Task DisablePing() + { + var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.SucceedAlways()) + .SniffingConnectionPool() + .Settings(s => s.SniffOnStartup()) + ); + + audit = await audit.TraceCall( + new ClientCall(r => r.DisablePing()) // <1> disable ping + { + { SniffOnStartup }, + { SniffSuccess, 9200 }, // <2> No ping after sniffing + { HealthyResponse, 9200 } + } + ); + } + + /** Finally, let's demonstrate disabling both sniff and ping on the request */ + [U] public async Task DisableSniffAndPing() + { + var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.SucceedAlways()) + .SniffingConnectionPool() + .Settings(s => s.SniffOnStartup()) + ); + + audit = await audit.TraceCall( + new ClientCall(r=>r.DisableSniffing().DisablePing()) // <1> diable ping and sniff + { + { HealthyResponse, 9200 } // <2> no ping or sniff before the call + } + ); + } + } +} diff --git a/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RequestTimeoutsOverrides.cs b/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RequestTimeoutsOverrides.doc.cs similarity index 95% rename from src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RequestTimeoutsOverrides.cs rename to src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RequestTimeoutsOverrides.doc.cs index 703726dd4dd..454e7d23c07 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RequestTimeoutsOverrides.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RequestTimeoutsOverrides.doc.cs @@ -6,19 +6,20 @@ namespace Tests.ClientConcepts.ConnectionPooling.RequestOverrides { public class RequestTimeoutsOverrides - { - /** == Request Timeouts + { + /**== Request Timeouts + * * While you can specify Request time out globally you can override this per request too - */ - - [U] + */ + + [U] public async Task RespectsRequestTimeoutOverride() - { - - /** we set up a 10 node cluster with a global time out of 20 seconds. + { + + /** we set up a 10 node cluster with a global time out of 20 seconds. * Each call on a node takes 10 seconds. So we can only try this call on 2 nodes * before the max request time out kills the client call. - */ + */ var audit = new Auditor(() => Framework.Cluster .Nodes(10) .ClientCalls(r => r.FailAlways().Takes(TimeSpan.FromSeconds(10))) @@ -30,70 +31,70 @@ public async Task RespectsRequestTimeoutOverride() audit = await audit.TraceCalls( new ClientCall { { BadResponse, 9200 }, - { BadResponse, 9201 }, + { BadResponse, 9201 }, { MaxTimeoutReached } - }, + }, /** * On the second request we specify a request timeout override to 60 seconds * We should now see more nodes being tried. - */ - new ClientCall(r => r.RequestTimeout(TimeSpan.FromSeconds(80))) - { - { BadResponse, 9203 }, - { BadResponse, 9204 }, - { BadResponse, 9205 }, - { BadResponse, 9206 }, - { BadResponse, 9207 }, - { BadResponse, 9208 }, - { HealthyResponse, 9209 }, - } + */ + new ClientCall(r => r.RequestTimeout(TimeSpan.FromSeconds(80))) + { + { BadResponse, 9203 }, + { BadResponse, 9204 }, + { BadResponse, 9205 }, + { BadResponse, 9206 }, + { BadResponse, 9207 }, + { BadResponse, 9208 }, + { HealthyResponse, 9209 }, + } + ); + + } + + /** == Connect Timeouts + * Connect timeouts can be overridden, webrequest/httpclient can not distinguish connect and retry timeouts however + * we use this separate configuration value for ping requests. + */ + [U] + public async Task RespectsConnectTimeoutOverride() + { + /** we set up a 10 node cluster with a global time out of 20 seconds. + * Each call on a node takes 10 seconds. So we can only try this call on 2 nodes + * before the max request time out kills the client call. + */ + var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .Ping(p => p.SucceedAlways().Takes(TimeSpan.FromSeconds(20))) + .ClientCalls(r => r.SucceedAlways()) + .StaticConnectionPool() + .Settings(s => s.RequestTimeout(TimeSpan.FromSeconds(10)).PingTimeout(TimeSpan.FromSeconds(10))) + ); + + audit = await audit.TraceCalls( + /** + * The first call uses the configured global settings, request times out after 10 seconds and ping + * calls always take 20, so we should see a single ping failure + */ + new ClientCall { + { PingFailure, 9200 }, + { MaxTimeoutReached } + }, + /** + * On the second request we set a request ping timeout override of 2seconds + * We should now see more nodes being tried before the request timeout is hit. + */ + new ClientCall(r => r.PingTimeout(TimeSpan.FromSeconds(2))) + { + { PingFailure, 9202 }, + { PingFailure, 9203 }, + { PingFailure, 9204 }, + { PingFailure, 9205 }, + { PingFailure, 9206 }, + { MaxTimeoutReached } + } ); - } - - /** == Connect Timeouts - * Connect timeouts can be overridden, webrequest/httpclient can not distinguish connect and retry timeouts however - * we use this separate configuration value for ping requests. - */ - [U] - public async Task RespectsConnectTimeoutOverride() - { - /** we set up a 10 node cluster with a global time out of 20 seconds. - * Each call on a node takes 10 seconds. So we can only try this call on 2 nodes - * before the max request time out kills the client call. - */ - var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .Ping(p => p.SucceedAlways().Takes(TimeSpan.FromSeconds(20))) - .ClientCalls(r => r.SucceedAlways()) - .StaticConnectionPool() - .Settings(s => s.RequestTimeout(TimeSpan.FromSeconds(10)).PingTimeout(TimeSpan.FromSeconds(10))) - ); - - audit = await audit.TraceCalls( - /** - * The first call uses the configured global settings, request times out after 10 seconds and ping - * calls always take 20, so we should see a single ping failure - */ - new ClientCall { - { PingFailure, 9200 }, - { MaxTimeoutReached } - }, - /** - * On the second request we set a request ping timeout override of 2seconds - * We should now see more nodes being tried before the request timeout is hit. - */ - new ClientCall(r => r.PingTimeout(TimeSpan.FromSeconds(2))) - { - { PingFailure, 9202 }, - { PingFailure, 9203 }, - { PingFailure, 9204 }, - { PingFailure, 9205 }, - { PingFailure, 9206 }, - { MaxTimeoutReached } - } - ); - } } } diff --git a/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsAllowedStatusCode.cs b/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsAllowedStatusCode.doc.cs similarity index 91% rename from src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsAllowedStatusCode.cs rename to src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsAllowedStatusCode.doc.cs index eca019f4184..bc971ce10b8 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsAllowedStatusCode.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsAllowedStatusCode.doc.cs @@ -1,34 +1,34 @@ -using System; -using System.Threading.Tasks; -using Elasticsearch.Net; -using Tests.Framework; -using static Elasticsearch.Net.AuditEvent; - -namespace Tests.ClientConcepts.ConnectionPooling.RequestOverrides -{ - public class RespectsAllowedStatusCode - { - /** == Allowed status codes - */ - - [U] - public async Task CanOverrideBadResponse() - { - var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.FailAlways(400)) - .StaticConnectionPool() - .Settings(s => s.DisablePing().MaximumRetries(0)) - ); - - audit = await audit.TraceCalls( - new ClientCall() { - { BadResponse, 9200 } - }, - new ClientCall(r => r.AllowedStatusCodes(400)) { - { HealthyResponse, 9201 } - } - ); - } - } -} +using System; +using System.Threading.Tasks; +using Elasticsearch.Net; +using Tests.Framework; +using static Elasticsearch.Net.AuditEvent; + +namespace Tests.ClientConcepts.ConnectionPooling.RequestOverrides +{ + public class RespectsAllowedStatusCode + { + /**== Allowed status codes + */ + + [U] + public async Task CanOverrideBadResponse() + { + var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.FailAlways(400)) + .StaticConnectionPool() + .Settings(s => s.DisablePing().MaximumRetries(0)) + ); + + audit = await audit.TraceCalls( + new ClientCall() { + { BadResponse, 9200 } + }, + new ClientCall(r => r.AllowedStatusCodes(400)) { + { HealthyResponse, 9201 } + } + ); + } + } +} diff --git a/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsForceNode.cs b/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsForceNode.doc.cs similarity index 93% rename from src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsForceNode.cs rename to src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsForceNode.doc.cs index 4f7d0bdd40e..924f6ead17d 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsForceNode.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsForceNode.doc.cs @@ -1,34 +1,34 @@ -using System; -using System.Threading.Tasks; -using Elasticsearch.Net; -using Tests.Framework; -using static Elasticsearch.Net.AuditEvent; - -namespace Tests.ClientConcepts.ConnectionPooling.RequestOverrides -{ - public class RespectsForceNode - { - /** == Forcing nodes - * Sometimes you might want to fire a single request to a specific node. You can do so using the `ForceNode` - * request configuration. This will ignore the pool and not retry. - */ - - [U] - public async Task OnlyCallsForcedNode() - { - var audit = new Auditor(() => Framework.Cluster - .Nodes(10) - .ClientCalls(r => r.SucceedAlways()) - .ClientCalls(r => r.OnPort(9208).FailAlways()) - .StaticConnectionPool() - .Settings(s => s.DisablePing()) - ); - - audit = await audit.TraceCall( - new ClientCall(r => r.ForceNode(new Uri("http://localhost:9208"))) { - { BadResponse, 9208 } - } - ); - } - } -} +using System; +using System.Threading.Tasks; +using Elasticsearch.Net; +using Tests.Framework; +using static Elasticsearch.Net.AuditEvent; + +namespace Tests.ClientConcepts.ConnectionPooling.RequestOverrides +{ + public class RespectsForceNode + { + /**== Forcing nodes + * Sometimes you might want to fire a single request to a specific node. You can do so using the `ForceNode` + * request configuration. This will ignore the pool and not retry. + */ + + [U] + public async Task OnlyCallsForcedNode() + { + var audit = new Auditor(() => Framework.Cluster + .Nodes(10) + .ClientCalls(r => r.SucceedAlways()) + .ClientCalls(r => r.OnPort(9208).FailAlways()) + .StaticConnectionPool() + .Settings(s => s.DisablePing()) + ); + + audit = await audit.TraceCall( + new ClientCall(r => r.ForceNode(new Uri("http://localhost:9208"))) { + { BadResponse, 9208 } + } + ); + } + } +} diff --git a/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsMaxRetryOverrides.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsMaxRetryOverrides.doc.cs index f419cfacc34..7d7502d5901 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsMaxRetryOverrides.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/RequestOverrides/RespectsMaxRetryOverrides.doc.cs @@ -8,7 +8,8 @@ namespace Tests.ClientConcepts.ConnectionPooling.RequestOverrides { public class RespectsMaxRetryOverrides { - /** == MaxRetries + /**== Maximum Retries + * * By default retry as many times as we have nodes. However retries still respect the request timeout. * Meaning if you have a 100 node cluster and a request timeout of 20 seconds we will retry as many times as we can * but give up after 20 seconds @@ -36,8 +37,8 @@ public async Task DefaultMaxIsNumberOfNodes() } /** - * When you have a 100 node cluster you might want to ensure a fixed number of retries. - * Remember that the actual number of requests is initial attempt + set number of retries + * When you have a 100 node cluster you might want to ensure a fixed number of retries. + * Remember that the actual number of requests is initial attempt + set number of retries */ [U] @@ -61,9 +62,9 @@ public async Task FixedMaximumNumberOfRetries() ); } - /** - * This makes setting any retry setting on a single node connection pool a NOOP, this is by design! - * Connection pooling and connection failover is about trying to fail sanely whilst still utilizing available resources and + /** + * This makes setting any retry setting on a single node connection pool a NOOP, this is by design! + * Connection pooling and connection failover is about trying to fail sanely whilst still utilizing available resources and * not giving up on the fail fast principle. It's *NOT* a mechanism for forcing requests to succeed. */ [U] diff --git a/src/Tests/ClientConcepts/ConnectionPooling/RoundRobin/RoundRobin.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/RoundRobin/RoundRobin.doc.cs index 7f20bcca692..ed27a15de30 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/RoundRobin/RoundRobin.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/RoundRobin/RoundRobin.doc.cs @@ -11,16 +11,16 @@ namespace Tests.ClientConcepts.ConnectionPooling.RoundRobin { public class RoundRobin { - /** Round Robin - * Each connection pool round robins over the `live` nodes, to evenly distribute the load over all known nodes. + /** == Round Robin + * <> and <> connection pools + * round robin over the `live` nodes to evenly distribute request load over all known nodes. */ - /** == GetNext - * GetNext is implemented in a lock free thread safe fashion, meaning each callee gets returned its own cursor to advance + /** === GetNext + * `GetNext` is implemented in a lock free thread safe fashion, meaning each callee gets returned its own cursor to advance * over the internal list of nodes. This to guarantee each request that needs to fall over tries all the nodes without * suffering from noisy neighboors advancing a global cursor. */ - protected int NumberOfNodes = 10; [U] public void EachViewStartsAtNexPositionAndWrapsOver() @@ -34,10 +34,10 @@ [U] public void EachViewStartsAtNexPositionAndWrapsOver() } public void AssertCreateView(IConnectionPool pool) - { + { /** - * Here we have setup a static connection pool seeded with 10 nodes. We force randomizationOnStartup to false - * so that we can test the nodes being returned are int the order we expect them to. + * Here we have setup a static connection pool seeded with 10 nodes. We force randomization OnStartup to false + * so that we can test the nodes being returned are int the order we expect them to. * So what order we expect? Imagine the following: * * Thread A calls GetNext first without a local cursor and takes the current from the internal global cursor which is 0. @@ -53,13 +53,13 @@ public void AssertCreateView(IConnectionPool pool) var expectedOrder = Enumerable.Range(9200, NumberOfNodes); startingPositions.Should().ContainInOrder(expectedOrder); - /** + /** * What the above code just proved is that each call to GetNext(null) gets assigned the next available node. - * + * * Lets up the ante: * - call get next over `NumberOfNodes * 2` threads - * - on each thread call getnext `NumberOfNodes * 10` times using a local cursor. - * We'll validate that each thread sees all the nodes and they they wrap over e.g after node 9209 + * - on each thread call getnext `NumberOfNodes * 10` times using a local cursor. + * We'll validate that each thread sees all the nodes and they they wrap over e.g after node 9209 * comes 9200 again */ var threadedStartPositions = new ConcurrentBag(); diff --git a/src/Tests/ClientConcepts/ConnectionPooling/RoundRobin/SkipDeadNodes.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/RoundRobin/SkipDeadNodes.doc.cs index 18624805b34..c179d5ff8ab 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/RoundRobin/SkipDeadNodes.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/RoundRobin/SkipDeadNodes.doc.cs @@ -12,16 +12,15 @@ namespace Tests.ClientConcepts.ConnectionPooling.RoundRobin { public class SkippingDeadNodes { - /** Round Robin - Skipping Dead Nodes - * When selecting nodes the connection pool will try and skip all the nodes that are marked dead. - */ - - /** == GetNext + /** == Round Robin - Skipping Dead Nodes + * + * When selecting nodes the connection pool will try and skip all the nodes that are marked dead. + * + * === GetNext * GetNext is implemented in a lock free thread safe fashion, meaning each callee gets returned its own cursor to advance * over the internal list of nodes. This to guarantee each request that needs to fall over tries all the nodes without * suffering from noisy neighboors advancing a global cursor. */ - protected int NumberOfNodes = 3; [U] public void EachViewSkipsAheadWithOne() @@ -51,7 +50,7 @@ [U] public void EachViewSeesNextButSkipsTheDeadNode() node = pool.CreateView().First(); node.Uri.Port.Should().Be(9202); } - /** After we marke the first node alive again we expect it to be hit again*/ + /** After we marked the first node alive again, we expect it to be hit again*/ seeds.First().MarkAlive(); for (var i = 0; i < 20; i++) { @@ -76,7 +75,7 @@ [U] public void ViewSeesResurrectedNodes() node = pool.CreateView().First(); node.Uri.Port.Should().Be(9202); } - /** If we forward our clock 2 days the node that was marked dead until tomorrow (or yesterday!) should be resurrected */ + /** If we roll the clock forward two days, the node that was marked dead until tomorrow (or yesterday!) should be resurrected */ dateTimeProvider.ChangeTime(d => d.AddDays(2)); var n = pool.CreateView().First(); n.Uri.Port.Should().Be(9201); @@ -103,33 +102,33 @@ public async Task FallsOverDeadNodes() await audit.TraceCalls( /** The first call goes to 9200 which succeeds */ - new ClientCall { + new ClientCall { { HealthyResponse, 9200}, { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(0) } }, - /** The 2nd call does a ping on 9201 because its used for the first time. + /** The 2nd call does a ping on 9201 because its used for the first time. * It fails so we wrap over to node 9202 */ - new ClientCall { + new ClientCall { { BadResponse, 9201}, { HealthyResponse, 9202}, /** Finally we assert that the connectionpool has one node that is marked as dead */ { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(1) } }, /** The next call goes to 9203 which fails so we should wrap over */ - new ClientCall { + new ClientCall { { BadResponse, 9203}, { HealthyResponse, 9200}, { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } }, - new ClientCall { + new ClientCall { { HealthyResponse, 9202}, { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } }, - new ClientCall { + new ClientCall { { HealthyResponse, 9200}, { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } }, - new ClientCall { + new ClientCall { { HealthyResponse, 9202}, { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(2) } }, @@ -153,7 +152,7 @@ public async Task PicksADifferentNodeEachTimeAnodeIsDown() await audit.TraceCalls( /** All the calls fail */ - new ClientCall { + new ClientCall { { BadResponse, 9200}, { BadResponse, 9201}, { BadResponse, 9202}, @@ -165,25 +164,25 @@ await audit.TraceCalls( * each time to quickly see if the cluster is back up. We do not want to retry all 4 * nodes */ - new ClientCall { + new ClientCall { { AllNodesDead }, { Resurrection, 9201}, { BadResponse, 9201}, { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } }, - new ClientCall { + new ClientCall { { AllNodesDead }, { Resurrection, 9202}, { BadResponse, 9202}, { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } }, - new ClientCall { + new ClientCall { { AllNodesDead }, { Resurrection, 9203}, { BadResponse, 9203}, { pool => pool.Nodes.Where(n=>!n.IsAlive).Should().HaveCount(4) } }, - new ClientCall { + new ClientCall { { AllNodesDead }, { Resurrection, 9200}, { BadResponse, 9200}, diff --git a/src/Tests/ClientConcepts/ConnectionPooling/RoundRobin/VolatileUpdates.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/RoundRobin/VolatileUpdates.doc.cs index f10a14ad12a..1bb51401479 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/RoundRobin/VolatileUpdates.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/RoundRobin/VolatileUpdates.doc.cs @@ -10,10 +10,7 @@ namespace Tests.ClientConcepts.ConnectionPooling.RoundRobin { public class VolatileUpdates { - /** - * - */ - + /**== Volatile Updates */ protected int NumberOfNodes = 10; private Random Random = new Random(); @@ -40,7 +37,7 @@ [U] public void StaticPoolWithstandsConcurrentReadAndWrites() public void AssertCreateView(IConnectionPool pool) { - /** + /** */ var threads = Enumerable.Range(0, 50) .Select(i => CreateReadAndUpdateThread(pool)) diff --git a/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/OnConnectionFailure.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/OnConnectionFailure.doc.cs index c2ed84ad5ba..f372ff5d179 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/OnConnectionFailure.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/OnConnectionFailure.doc.cs @@ -9,11 +9,12 @@ namespace Tests.ClientConcepts.ConnectionPooling.Sniffing { public class OnConnectionFailure { - /** == Sniffing on connection failure - * Sniffing on connection is enabled by default when using a connection pool that allows reseeding. - * The only IConnectionPool we ship that allows this is the SniffingConnectionPool. + /**== Sniffing on connection failure * - * This can be very handy to force a refresh of the pools known healthy node by inspecting elasticsearch itself. + * Sniffing on connection is enabled by default when using a connection pool that allows reseeding. + * The only IConnectionPool we ship that allows this is the <>. + * + * This can be very handy to force a refresh of the pools known healthy node by inspecting Elasticsearch itself. * A sniff tries to get the nodes by asking each currently known node until one response. */ @@ -21,7 +22,7 @@ [U] public async Task DoesASniffAfterConnectionFailure() { /** * Here we seed our connection with 5 known nodes 9200-9204 of which we think - * 9202, 9203, 9204 are master eligible nodes. Our virtualized cluster will throw once when doing + * 9202, 9203, 9204 are master eligible nodes. Our virtualized cluster will throw once when doing * a search on 9201. This should a sniff to be kicked off. */ var audit = new Auditor(() => Framework.Cluster @@ -39,7 +40,7 @@ [U] public async Task DoesASniffAfterConnectionFailure() .MasterEligible(9200, 9202) .ClientCalls(r => r.OnPort(9201).Fails(Once)) /** - * After this second failure on 9201 another sniff will be returned a cluster that no + * After this second failure on 9201 another sniff will be returned a cluster that no * longer fails but looks completely different (9210-9212) we should be able to handle this */ .Sniff(s => s.SucceedAlways(Framework.Cluster @@ -90,7 +91,7 @@ [U] public async Task DoesASniffAfterConnectionFailure() [U] public async Task DoesASniffAfterConnectionFailureOnPing() { - /** Here we set up our cluster exactly the same as the previous setup + /** Here we set up our cluster exactly the same as the previous setup * Only we enable pinging (default is true) and make the ping fail */ var audit = new Auditor(() => Framework.Cluster diff --git a/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/OnStaleClusterState.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/OnStaleClusterState.doc.cs index 3076e6f1aa1..abb6ecd405f 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/OnStaleClusterState.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/OnStaleClusterState.doc.cs @@ -10,14 +10,13 @@ namespace Tests.ClientConcepts.ConnectionPooling.Sniffing { public class OnStaleClusterState { - /** == Sniffing periodically - * + /**== Sniffing periodically + * * Connection pools that return true for `SupportsReseeding` can be configured to sniff periodically. * In addition to sniffing on startup and sniffing on failures, sniffing periodically can benefit scenerio's where * clusters are often scaled horizontally during peak hours. An application might have a healthy view of a subset of the nodes * but without sniffing periodically it will never find the nodes that have been added to help out with load */ - [U] [SuppressMessage("AsyncUsage", "AsyncFixer001:Unnecessary async/await usage", Justification = "Its a test")] public async Task ASniffOnStartupHappens() diff --git a/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/OnStartup.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/OnStartup.doc.cs index 6223fa14a91..794a137a397 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/OnStartup.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/OnStartup.doc.cs @@ -10,14 +10,16 @@ namespace Tests.ClientConcepts.ConnectionPooling.Sniffing { public class OnStartupSniffing { - /** == Sniffing on startup - * - * Connection pools that return true for `SupportsReseeding` by default sniff on startup. + /**== Sniffing on startup + * <> that return true for `SupportsReseeding` will sniff on startup by default. */ - [U] [SuppressMessage("AsyncUsage", "AsyncFixer001:Unnecessary async/await usage", Justification = "Its a test")] public async Task ASniffOnStartupHappens() { + /** We can demonstrate this by creating a _virtual_ Elasticsearch cluster with NEST's Test Framework. + * Here we create a 10 node cluster that uses a <>, setting + * sniff to fail on all nodes *_except_* 9202 + */ var audit = new Auditor(() => Framework.Cluster .Nodes(10) .Sniff(s => s.Fails(Always)) @@ -26,6 +28,10 @@ public async Task ASniffOnStartupHappens() .AllDefaults() ); + /** When the client call is made, we can see from the audit trail that the pool first tried to sniff on startup, + * with a sniff failure on 9200 and 9201, followed by a sniff success on 9202. A ping and healthy response are made on + * 9200 + */ await audit.TraceCall(new ClientCall { { SniffOnStartup}, diff --git a/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/RoleDetection.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/RoleDetection.doc.cs index c5e0a99df11..9ba739d9cac 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/RoleDetection.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/Sniffing/RoleDetection.doc.cs @@ -13,12 +13,12 @@ namespace Tests.ClientConcepts.ConnectionPooling.Sniffing { public class RoleDetection - { - /** == Sniffing role detection + { + /**== Sniffing role detection * - * When we sniff the custer state we detect the role of the node whether its master eligible and holds data + * When we sniff the cluster state, we detect the role of the node, whether it's master eligible and holds data. * We use this information when selecting a node to perform an API call on. - */ + */ [U, SuppressMessage("AsyncUsage", "AsyncFixer001:Unnecessary async/await usage", Justification = "Its a test")] public async Task DetectsMasterNodes() { diff --git a/src/Tests/ClientConcepts/ConnectionPooling/Sticky/SkipDeadNodes.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/Sticky/SkipDeadNodes.doc.cs index 3f9eb7f728b..2904498d6b3 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/Sticky/SkipDeadNodes.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/Sticky/SkipDeadNodes.doc.cs @@ -15,7 +15,6 @@ public class SkipDeadNodes /** Sticky - Skipping Dead Nodes * When selecting nodes the connection pool will try and skip all the nodes that are marked dead. */ - protected int NumberOfNodes = 3; [U] diff --git a/src/Tests/ClientConcepts/ConnectionPooling/Sticky/Sticky.doc.cs b/src/Tests/ClientConcepts/ConnectionPooling/Sticky/Sticky.doc.cs index c9384398c55..6c7434170db 100644 --- a/src/Tests/ClientConcepts/ConnectionPooling/Sticky/Sticky.doc.cs +++ b/src/Tests/ClientConcepts/ConnectionPooling/Sticky/Sticky.doc.cs @@ -14,34 +14,26 @@ public class Sticky /** Sticky * Each connection pool returns the first `live` node so that it is sticky between requests */ - - protected int NumberOfNodes = 10; - [U] public void EachViewStartsAtNextPositionAndWrapsOver() { - var uris = Enumerable.Range(9200, NumberOfNodes).Select(p => new Uri("http://localhost:" + p)); - var staticPool = new StickyConnectionPool(uris); - - this.AssertCreateView(staticPool); - } + var numberOfNodes = 10; + var uris = Enumerable.Range(9200, numberOfNodes).Select(p => new Uri("http://localhost:" + p)); + var pool = new StickyConnectionPool(uris); - public void AssertCreateView(StickyConnectionPool pool) - { /** - * Here we have setup a static connection pool seeded with 10 nodes. + * Here we have setup a sticky connection pool seeded with 10 nodes. * So what order we expect? Imagine the following: * * Thread A calls GetNext and gets returned the first live node * Thread B calls GetNext() and gets returned the same node as it's still the first live. */ - - var startingPositions = Enumerable.Range(0, NumberOfNodes) + var startingPositions = Enumerable.Range(0, numberOfNodes) .Select(i => pool.CreateView().First()) .Select(n => n.Uri.Port) .ToList(); - var expectedOrder = Enumerable.Repeat(9200, NumberOfNodes); + var expectedOrder = Enumerable.Repeat(9200, numberOfNodes); startingPositions.Should().ContainInOrder(expectedOrder); } } diff --git a/src/Tests/ClientConcepts/HighLevel/CovariantHits/CovariantSearchResults.doc.cs b/src/Tests/ClientConcepts/HighLevel/CovariantHits/CovariantSearchResults.doc.cs index 2a0ff0f766e..dec12fd7631 100644 --- a/src/Tests/ClientConcepts/HighLevel/CovariantHits/CovariantSearchResults.doc.cs +++ b/src/Tests/ClientConcepts/HighLevel/CovariantHits/CovariantSearchResults.doc.cs @@ -6,59 +6,59 @@ namespace Tests.ClientConcepts.HighLevel.CovariantHits { - /** # Covariant Search Results - * - * NEST directly supports returning covariant result sets. - * Meaning a result can be typed to an interface or baseclass - * but the actual instance type of the result can be that of the subclass directly - * - * Let look at an example, imagine we want to search over multiple types that all implement - * `ISearchResult` - * - */ - public interface ISearchResult + public class CovariantSearchResults { - string Name { get; set; } - } + /**== Covariant Search Results + * + * NEST directly supports returning covariant result sets. + * Meaning a result can be typed to an interface or base class + * but the actual instance type of the result can be that of the subclass directly + * + * Let's look at an example; Imagine we want to search over multiple types that all implement + * `ISearchResult` + */ + public interface ISearchResult + { + string Name { get; set; } + } - /** - * We have three implementations of `ISearchResult` namely `A`, `B` and `C` - */ + /** + * We have three implementations of `ISearchResult` namely `A`, `B` and `C` + */ + public class A : ISearchResult + { + public string Name { get; set; } + public int PropertyOnA { get; set; } + } - public class A : ISearchResult - { - public string Name { get; set; } - public int PropertyOnA { get; set; } - } + public class B : ISearchResult + { + public string Name { get; set; } + public int PropertyOnB { get; set; } + } - public class B : ISearchResult - { - public string Name { get; set; } - public int PropertyOnB { get; set; } - } + public class C : ISearchResult + { + public string Name { get; set; } + public int PropertyOnC { get; set; } + } - public class C : ISearchResult - { - public string Name { get; set; } - public int PropertyOnC { get; set; } - } - public class CovariantSearchResults - { - private IElasticClient _client = TestClient.GetFixedReturnClient(CovariantSearchResultMock.Json); + private readonly IElasticClient _client = TestClient.GetFixedReturnClient(CovariantSearchResultMock.Json); + [U] public void UsingTypes() { - /** + /** === Using Types * The most straightforward way to search over multiple types is to * type the response to the parent interface or base class - * and pass the actual types we want to search over using `.Types()` + * and pass the actual types we want to search over using `.Type()` */ var result = this._client.Search(s => s .Type(Types.Type(typeof(A), typeof(B), typeof(C))) .Size(100) ); /** - * Nest will translate this to a search over /index/a,b,c/_search. + * NEST will translate this to a search over `/index/a,b,c/_search`; * hits that have `"_type" : "a"` will be serialized to `A` and so forth */ @@ -91,7 +91,7 @@ [U] public void UsingTypes() [U] public void UsingConcreteTypeSelector() { - /** + /** === Using ConcreteTypeSelector * A more low level approach is to inspect the hit yourself and determine the CLR type to deserialize to */ var result = this._client.Search(s => s @@ -100,8 +100,9 @@ [U] public void UsingConcreteTypeSelector() ); /** - * here for each hit we'll call the delegate with `d` which a dynamic representation of the `_source` - * and a typed `h` which represents the encapsulating hit. + * here for each hit we'll call the delegate passed to `ConcreteTypeSelector where + * - `d` is a representation of the `_source` exposed as a `dynamic` type + * - a typed `h` which represents the encapsulating hit of the source i.e. `Hit` */ /** @@ -131,20 +132,19 @@ [U] public void UsingConcreteTypeSelector() cDocuments.Should().OnlyContain(a => a.PropertyOnC > 0); } - /** Scroll also supports CovariantSearchResponses + /** === Using CovariantTypes() */ - [U] public void UsingCovariantTypesOnScroll() { /** - * Scroll() is a continuation of a previous Search() so Types() are lost. - * You can hint the type types again using CovariantTypes() + * The Scroll API is a continuation of the previous Search example so Types() are lost. + * You can hint at the types using `.CovariantTypes()` */ var result = this._client.Scroll(TimeSpan.FromMinutes(60), "scrollId", s => s .CovariantTypes(Types.Type(typeof(A), typeof(B), typeof(C))) ); /** - * Nest will translate this to a search over /index/a,b,c/_search. + * NEST will translate this to a search over `/index/a,b,c/_search`; * hits that have `"_type" : "a"` will be serialized to `A` and so forth */ @@ -185,8 +185,9 @@ [U] public void UsingConcreteTypeSelectorOnScroll() ); /** - * here for each hit we'll call the delegate with `d` which a dynamic representation of the `_source` - * and a typed `h` which represents the encapsulating hit. + * As before, within the delegate passed to `.ConcreteTypeSelector` + * - `d` is the `_source` typed as `dynamic` + * - `h` is the encapsulating typed hit */ /** diff --git a/src/Tests/ClientConcepts/HighLevel/Inferrence/DocumentPaths.doc.cs b/src/Tests/ClientConcepts/HighLevel/Inference/DocumentPaths.doc.cs similarity index 64% rename from src/Tests/ClientConcepts/HighLevel/Inferrence/DocumentPaths.doc.cs rename to src/Tests/ClientConcepts/HighLevel/Inference/DocumentPaths.doc.cs index 8b770364865..7d7ee3eaae8 100644 --- a/src/Tests/ClientConcepts/HighLevel/Inferrence/DocumentPaths.doc.cs +++ b/src/Tests/ClientConcepts/HighLevel/Inference/DocumentPaths.doc.cs @@ -3,17 +3,18 @@ using Tests.Framework.MockData; using static Tests.Framework.RoundTripper; -namespace Tests.ClientConcepts.HighLevel.Inferrence +namespace Tests.ClientConcepts.HighLevel.Inference { public class DocumentPaths { - /** # DocumentPaths - * Many API's in elasticsearch describe a path to a document. In NEST besides generating a constructor that takes - * and Index, Type and Id seperately we also generate a constructor taking a DocumentPath that allows you to describe the path - * to your document more succintly + /**== Document Paths + * + * Many API's in Elasticsearch describe a path to a document. In NEST, besides generating a constructor that takes + * and Index, Type and Id seperately, we also generate a constructor taking a `DocumentPath` that allows you to describe the path + * to your document more succintly */ - /** Manually newing */ + /** === Creating new instances */ [U] public void FromId() { /** here we create a new document path based on Project with the id 1 */ @@ -28,20 +29,22 @@ [U] public void FromId() path = new DocumentPath(1).Index("project1"); Expect("project1").WhenSerializing(path.Index); - - /** there is also a static way to describe such paths */ - path = DocumentPath.Id(1); + + /** and there is also a static way to describe such paths */ + path = DocumentPath.Id(1); Expect("project").WhenSerializing(path.Index); Expect("project").WhenSerializing(path.Type); Expect(1).WhenSerializing(path.Id); } - //** if you have an instance of your document you can use it as well generate document paths */ + /** === Creating from a document type instance + * if you have an instance of your document you can use it as well generate document paths + */ [U] public void FromObject() { var project = new Project { Name = "hello-world" }; - /** here we create a new document path based on a Project */ + /** here we create a new document path based on the instance of `Project`, project */ IDocumentPath path = new DocumentPath(project); Expect("project").WhenSerializing(path.Index); Expect("project").WhenSerializing(path.Type); @@ -53,9 +56,9 @@ [U] public void FromObject() path = new DocumentPath(project).Index("project1"); Expect("project1").WhenSerializing(path.Index); - - /** there is also a static way to describe such paths */ - path = DocumentPath.Id(project); + + /** and again, there is also a static way to describe such paths */ + path = DocumentPath.Id(project); Expect("project").WhenSerializing(path.Index); Expect("project").WhenSerializing(path.Type); Expect("hello-world").WhenSerializing(path.Id); @@ -63,16 +66,18 @@ [U] public void FromObject() DocumentPath p = project; } + /** === An example with requests */ [U] public void UsingWithRequests() { + /* Given the following CLR type that describes a document */ var project = new Project { Name = "hello-world" }; - /** Here we can see and example how DocumentPath helps your describe your requests more tersely */ + /** we can see an example of how `DocumentPath` helps your describe your requests more tersely */ var request = new IndexRequest(2) { Document = project }; request = new IndexRequest(project) { }; - - /** when comparing with the full blown constructor and passing document manually - * DocumentPath<T>'s benefits become apparent. + + /** when comparing with the full blown constructor and passing document manually, + * `DocumentPath`'s benefits become apparent. */ request = new IndexRequest(IndexName.From(), TypeName.From(), 2) { diff --git a/src/Tests/ClientConcepts/HighLevel/Inference/FeaturesInference.doc.cs b/src/Tests/ClientConcepts/HighLevel/Inference/FeaturesInference.doc.cs new file mode 100644 index 00000000000..2f305f52de4 --- /dev/null +++ b/src/Tests/ClientConcepts/HighLevel/Inference/FeaturesInference.doc.cs @@ -0,0 +1,38 @@ +using Elasticsearch.Net; +using Nest; +using Tests.Framework; +using static Nest.Indices; +using static Tests.Framework.RoundTripper; + +namespace Tests.ClientConcepts.HighLevel.Inference +{ + public class FeaturesInference + { + /**[[features-inference]] + * == Features Inference + * Some URIs in Elasticsearch take a `Feature` enum. + * Within NEST, route values on the URI are represented as classes that implement an interface, `IUrlParameter`. + * Since enums _cannot_ implement interfaces in C#, a route parameter that would be of type `Feature` is represented using the `Features` class that + * the `Feature` enum implicitly converts to. + */ + + /**=== Constructor + * Using the `Features` constructor directly is possible but rather involved */ + [U] public void Serializes() + { + Features fieldString = Feature.Mappings | Feature.Aliases; + Expect("_mappings,_aliases") + .WhenSerializing(fieldString); + } + + [U] + public void ImplicitConversion() + { + /** === Implicit conversion + * Here we instantiate a GET index request whichs takes two features, settings and warmers. + * Notice how we can use the `Feature` enum directly. + */ + var request = new GetIndexRequest(All, Feature.Settings | Feature.Warmers); + } + } +} diff --git a/src/Tests/ClientConcepts/HighLevel/Inferrence/FieldInference.doc.cs b/src/Tests/ClientConcepts/HighLevel/Inference/FieldInference.doc.cs similarity index 54% rename from src/Tests/ClientConcepts/HighLevel/Inferrence/FieldInference.doc.cs rename to src/Tests/ClientConcepts/HighLevel/Inference/FieldInference.doc.cs index 5c2978ca3e9..a3340719fce 100644 --- a/src/Tests/ClientConcepts/HighLevel/Inferrence/FieldInference.doc.cs +++ b/src/Tests/ClientConcepts/HighLevel/Inference/FieldInference.doc.cs @@ -15,25 +15,26 @@ using Field = Nest.Field; using Xunit; -namespace Tests.ClientConcepts.HighLevel.Inferrence +namespace Tests.ClientConcepts.HighLevel.Inference { - public class FieldInferrence + public class FieldInference { - /** # Strongly typed field access - * - * Several places in the elasticsearch API expect the path to a field from your original source document as a string. - * NEST allows you to use C# expressions to strongly type these field path strings. + /**== Field Inference * - * These expressions are assigned to a type called `Field` and there are several ways to create a instance of that type + * Several places in the Elasticsearch API expect the path to a field from your original source document as a string. + * NEST allows you to use C# expressions to strongly type these field path strings. + * + * These expressions are assigned to a type called `Field` and there are several ways to create an instance of one */ - /** Using the constructor directly is possible but rather involved */ + /**=== Constructor + * Using the constructor directly is possible but rather involved */ [U] public void UsingConstructors() { var fieldString = new Field { Name = "name" }; - /** especially when using C# expressions since these can not be simply new'ed*/ + /** This is more cumbersome when using C# expressions since they cannot be instantiated easily*/ Expression> expression = p => p.Name; var fieldExpression = Field.Create(expression); @@ -42,13 +43,14 @@ public void UsingConstructors() .WhenSerializing(fieldString); } - /** Therefore you can also implicitly convert strings and expressions to Field's */ + /**=== Implicit Conversion + * Therefore you can also implicitly convert strings and expressions to a `Field` */ [U] public void ImplicitConversion() { Field fieldString = "name"; - /** but for expressions this is still rather involved */ + /** but for expressions this is _still_ rather involved */ Expression> expression = p => p.Name; Field fieldExpression = expression; @@ -57,7 +59,10 @@ public void ImplicitConversion() .WhenSerializing(fieldString); } - /** to ease creating Field's from expressions there is a static Property class you can use */ + /**[[nest-infer]] + * === Using Nest.Infer + * To ease creating a `Field` instance from expressions, there is a static `Infer` class you can use + */ [U] public void UsingStaticPropertyField() { @@ -66,97 +71,125 @@ public void UsingStaticPropertyField() /** but for expressions this is still rather involved */ var fieldExpression = Infer.Field(p => p.Name); - /** Using static imports in c# 6 this can be even shortened: - using static Nest.Static; + /** this can be even shortened even further using a https://msdn.microsoft.com/en-us/library/sf0df423.aspx#Anchor_0[static import in C# 6] i.e. + `using static Nest.Infer;` */ fieldExpression = Field(p => p.Name); - /** Now this is much much terser then our first example using the constructor! */ + /** Now that is much terser then our first example using the constructor! */ Expect("name") .WhenSerializing(fieldString) .WhenSerializing(fieldExpression); + /** You can also specify boosts in the field using a string */ fieldString = "name^2.1"; + fieldString.Boost.Should().Be(2.1); + + /** As well as using `Nest.Infer.Field` */ fieldExpression = Field(p => p.Name, 2.1); - /** Now this is much much terser then our first example using the constructor! */ Expect("name^2.1") .WhenSerializing(fieldString) .WhenSerializing(fieldExpression); } - /** By default NEST will camelCase all the field names to be more javascripty */ + /**[[camel-casing]] + * === Field name casing + * By default, NEST will camel-case **all** field names to better align with typical + * javascript/json conventions + */ [U] public void DefaultFieldNameInferrer() { - /** using DefaultFieldNameInferrer() on ConnectionSettings you can change this behavior */ + /** using `DefaultFieldNameInferrer()` on ConnectionSettings you can change this behavior */ var setup = WithConnectionSettings(s => s.DefaultFieldNameInferrer(p => p.ToUpper())); setup.Expect("NAME").WhenSerializing(Field(p => p.Name)); - /** However string are *always* passed along verbatim */ + /** However `string` types are *always* passed along verbatim */ setup.Expect("NaMe").WhenSerializing("NaMe"); - /** if you want the same behavior for expressions simply do nothing in the default inferrer */ + /** if you want the same behavior for expressions, simply pass a Func to `DefaultFieldNameInferrer` + * to make no changes to the name + */ setup = WithConnectionSettings(s => s.DefaultFieldNameInferrer(p => p)); setup.Expect("Name").WhenSerializing(Field(p => p.Name)); } - /** Complex field name expressions */ - + /**=== Complex field name expressions */ [U] public void ComplexFieldNameExpressions() { - /** You can follow your property expression to any depth, here we are traversing to the LeadDeveloper's (Person) FirstName */ + /** You can follow your property expression to any depth. Here we are traversing to the `LeadDeveloper` `FirstName` */ Expect("leadDeveloper.firstName").WhenSerializing(Field(p => p.LeadDeveloper.FirstName)); - /** When dealing with collection index access is ingnored allowing you to traverse into properties of collections */ + + /** When dealing with collection indexers, the indexer access is ignored allowing you to traverse into properties of collections */ Expect("curatedTags").WhenSerializing(Field(p => p.CuratedTags[0])); - /** Similarly .First() also works, remember these are expressions and not actual code that will be executed */ + + /** Similarly, LINQ's `.First()` method also works */ Expect("curatedTags").WhenSerializing(Field(p => p.CuratedTags.First())); Expect("curatedTags.added").WhenSerializing(Field(p => p.CuratedTags[0].Added)); Expect("curatedTags.name").WhenSerializing(Field(p => p.CuratedTags.First().Name)); - /** When we see an indexer on a dictionary we assume they describe property names */ + /** NOTE: Remember, these are _expressions_ and not actual code that will be executed + * + * An indexer on a dictionary is assumed to describe a property name */ Expect("metadata.hardcoded").WhenSerializing(Field(p => p.Metadata["hardcoded"])); Expect("metadata.hardcoded.created").WhenSerializing(Field(p => p.Metadata["hardcoded"].Created)); - /** A cool feature here is that we'll evaluate variables passed to these indexers */ + /** A cool feature here is that we'll evaluate variables passed to an indexer */ var variable = "var"; Expect("metadata.var").WhenSerializing(Field(p => p.Metadata[variable])); Expect("metadata.var.created").WhenSerializing(Field(p => p.Metadata[variable].Created)); - - /** If you are using elasticearch's multifield mapping (you really should!) these "virtual" sub fields - * do not always map back on to your POCO, by calling .Suffix() you describe the sub fields that do not live in your c# objects + /** + * If you are using Elasticearch's {ref_current}/_multi_fields.html[multi_fields], which you really should as they allow + * you to analyze a string in a number of different ways, these __"virtual"__ sub fields + * do not always map back on to your POCO. By calling `.Suffix()` on expressions, you describe the sub fields that + * should be mapped and <> */ - Expect("leadDeveloper.firstName.raw").WhenSerializing(Field(p => p.LeadDeveloper.FirstName.Suffix("raw"))); - Expect("curatedTags.raw").WhenSerializing(Field(p => p.CuratedTags[0].Suffix("raw"))); - Expect("curatedTags.raw").WhenSerializing(Field(p => p.CuratedTags.First().Suffix("raw"))); - Expect("curatedTags.added.raw").WhenSerializing(Field(p => p.CuratedTags[0].Added.Suffix("raw"))); - Expect("metadata.hardcoded.raw").WhenSerializing(Field(p => p.Metadata["hardcoded"].Suffix("raw"))); - Expect("metadata.hardcoded.created.raw").WhenSerializing(Field(p => p.Metadata["hardcoded"].Created.Suffix("raw"))); + Expect("leadDeveloper.firstName.raw").WhenSerializing( + Field(p => p.LeadDeveloper.FirstName.Suffix("raw"))); + + Expect("curatedTags.raw").WhenSerializing( + Field(p => p.CuratedTags[0].Suffix("raw"))); + + Expect("curatedTags.raw").WhenSerializing( + Field(p => p.CuratedTags.First().Suffix("raw"))); + + Expect("curatedTags.added.raw").WhenSerializing( + Field(p => p.CuratedTags[0].Added.Suffix("raw"))); + + Expect("metadata.hardcoded.raw").WhenSerializing( + Field(p => p.Metadata["hardcoded"].Suffix("raw"))); + + Expect("metadata.hardcoded.created.raw").WhenSerializing( + Field(p => p.Metadata["hardcoded"].Created.Suffix("raw"))); /** - * You can even chain them to any depth! + * You can even chain `.Suffix()` calls to any depth! */ - Expect("curatedTags.name.raw.evendeeper").WhenSerializing(Field(p => p.CuratedTags.First().Name.Suffix("raw").Suffix("evendeeper"))); - + Expect("curatedTags.name.raw.evendeeper").WhenSerializing( + Field(p => p.CuratedTags.First().Name.Suffix("raw").Suffix("evendeeper"))); /** Variables passed to suffix will be evaluated as well */ var suffix = "unanalyzed"; - Expect("metadata.var.unanalyzed").WhenSerializing(Field(p => p.Metadata[variable].Suffix(suffix))); - Expect("metadata.var.created.unanalyzed").WhenSerializing(Field(p => p.Metadata[variable].Created.Suffix(suffix))); + Expect("metadata.var.unanalyzed").WhenSerializing( + Field(p => p.Metadata[variable].Suffix(suffix))); + + Expect("metadata.var.created.unanalyzed").WhenSerializing( + Field(p => p.Metadata[variable].Created.Suffix(suffix))); } - /** - * Suffixes can be appended to expressions. This is useful in cases where you want to apply the same suffix - * to a list of fields + /** + * Suffixes can also be appended to expressions using `.AppendSuffix()`. This is useful in cases where you want to apply the same suffix + * to a list of fields. */ [U] public void AppendingSuffixToExpressions() { - /** */ + /** Here we have a list of expressions */ var expressions = new List>> { p => p.Name, @@ -165,8 +198,8 @@ public void AppendingSuffixToExpressions() p => p.LeadDeveloper.FirstName }; - /** append the suffix "raw" to each expression */ - var fieldExpressions = + /** and we want to append the suffix "raw" to each */ + var fieldExpressions = expressions.Select>, Field>(e => e.AppendSuffix("raw")).ToList(); Expect("name.raw").WhenSerializing(fieldExpressions[0]); @@ -175,9 +208,9 @@ public void AppendingSuffixToExpressions() Expect("leadDeveloper.firstName.raw").WhenSerializing(fieldExpressions[3]); } - /** Annotations - * - * When using NEST's property attributes you can specify a new name for the properties + /**=== Attribute based naming + * + * Using NEST's property attributes you can specify a new name for the properties */ public class BuiltIn { @@ -190,23 +223,26 @@ public void BuiltInAnnotiatons() Expect("naam").WhenSerializing(Field(p => p.Name)); } - /** - * Starting with NEST 2.x we also ask the serializer if it can resolve the property to a name. - * Here we ask the default JsonNetSerializer and it takes JsonProperty into account + /** + * Starting with NEST 2.x, we also ask the serializer if it can resolve a property to a name. + * Here we ask the default `JsonNetSerializer` to resolve a property name and it takes + * the `JsonPropertyAttribute` into account */ public class SerializerSpecific { [JsonProperty("nameInJson")] public string Name { get; set; } } + [U] public void SerializerSpecificAnnotations() { Expect("nameInJson").WhenSerializing(Field(p => p.Name)); } - /** - * If both are specified NEST takes precedence though + /** + * If both a NEST property attribute and a serializer specific attribute are present on a property, + * **NEST attributes take precedence** */ public class Both { @@ -224,16 +260,16 @@ public void NestAttributeTakesPrecedence() }).WhenSerializing(new Both { Name = "Martijn Laarman" }); } - class A { public C C { get; set; } } - class B { public C C { get; set; } } - class C - { - public string Name { get; set; } - } - /** - * Resolving field names is cached but this is per connection settings + /**[[field-inference-caching]] + *=== Field Inference Caching + * + * Resolution of field names is cached _per_ `ConnectionSettings` instance. To demonstrate, + * take the following simple POCOs */ + class A { public C C { get; set; } } + class B { public C C { get; set; } } + class C { public string Name { get; set; } } [U] public void ExpressionsAreCachedButSeeDifferentTypes() @@ -253,8 +289,9 @@ public void ExpressionsAreCachedButSeeDifferentTypes() fieldNameOnB.Should().Be("c.name"); /** - * now we create a new connectionsettings with a remap for C on class A to `d` - * now when we resolve the field path for A will be different + * now we create a new connection settings with a re-map for `C` on class `A` to `"d"` + * now when we resolve the field path for property `C` on `A`, it will be different than + * for property `C` on `B` */ var newConnectionSettings = TestClient.CreateSettings(forceInMemory: true, modifySettings: s => s .InferMappingFor(m => m @@ -269,7 +306,7 @@ public void ExpressionsAreCachedButSeeDifferentTypes() fieldNameOnA.Should().Be("d.name"); fieldNameOnB.Should().Be("c.name"); - /** however we didn't break inferrence on the first client instance using its separate connectionsettings */ + /** however we didn't break inferrence on the first client instance using its separate connection settings */ fieldNameOnA = client.Infer.Field(Field(p => p.C.Name)); fieldNameOnB = client.Infer.Field(Field(p => p.C.Name)); @@ -277,50 +314,38 @@ public void ExpressionsAreCachedButSeeDifferentTypes() fieldNameOnB.Should().Be("c.name"); } - /** - * To wrap up lets showcase the precedence that field names are inferred - * 1. A hard rename of the property on connection settings using Rename() - * 2. A NEST property mapping - * 3. Ask the serializer if the property has a verbatim value e.g it has an explicit JsonPropery attribute. - * 4. Pass the MemberInfo's Name to the DefaultFieldNameInferrer which by default camelCases + /**[[field-inference-precedence]] + *=== Inference Precedence + * To wrap up, the precedence in which field names are inferred is: + * + * . A hard rename of the property on connection settings using `.Rename()` + * . A NEST property mapping + * . Ask the serializer if the property has a verbatim value e.g it has an explicit JsonPropery attribute. + * . Pass the MemberInfo's Name to the DefaultFieldNameInferrer which by default camelCases * - * In the following example we have a class where each case wins + * The following example class will demonstrate this precedence */ - class Precedence { - /** - * Eventhough this property has a NEST property mapping and a JsonProperty attribute - * We are going to provide a hard rename for it on ConnectionSettings later that should win. - */ [String(Name = "renamedIgnoresNest")] [JsonProperty("renamedIgnoresJsonProperty")] - public string RenamedOnConnectionSettings { get; set; } + public string RenamedOnConnectionSettings { get; set; } //<1> Even though this property has a NEST property mapping _and_ a `JsonProperty` attribute, We are going to provide a hard rename for it on ConnectionSettings later that should win. - /** - * This property has both a NEST attribute and a JsonProperty, NEST should win. - */ [String(Name = "nestAtt")] [JsonProperty("jsonProp")] - public string NestAttribute { get; set; } + public string NestAttribute { get; set; } //<2> This property has both a NEST attribute and a `JsonProperty`, NEST should win. - /** We should take the json property into account by itself */ [JsonProperty("jsonProp")] - public string JsonProperty { get; set; } + public string JsonProperty { get; set; } //<3> We should take the json property into account by itself - /** This property we are going to special case in our custom serializer to resolve to `ask` */ [JsonProperty("dontaskme")] - public string AskSerializer { get; set; } - - /** We are going to register a DefaultFieldNameInferrer on ConnectionSettings - * that will uppercase all properties. - */ - public string DefaultFieldNameInferrer { get; set; } + public string AskSerializer { get; set; } //<4> This property we are going to special case in our custom serializer to resolve to ask + public string DefaultFieldNameInferrer { get; set; } //<5> We are going to register a DefaultFieldNameInferrer on ConnectionSettings that will uppercase all properties. } - /** - * Here we create a custom converter that renames any property named `AskSerializer` to `ask` + /** + * Here we create a custom serializer that renames any property named `AskSerializer` to `ask` */ class CustomSerializer : JsonNetSerializer { @@ -328,20 +353,23 @@ public CustomSerializer(IConnectionSettingsValues settings) : base(settings) { } public override IPropertyMapping CreatePropertyMapping(MemberInfo memberInfo) { - if (memberInfo.Name == "AskSerializer") return new PropertyMapping { Name = "ask" }; - return base.CreatePropertyMapping(memberInfo); + return memberInfo.Name == nameof(Precedence.AskSerializer) + ? new PropertyMapping { Name = "ask" } + : base.CreatePropertyMapping(memberInfo); } } [U] public void PrecedenceIsAsExpected() { + /** here we provide an explicit rename of a property on `ConnectionSettings` using `.Rename()` + * and all properties that are not mapped verbatim should be uppercased + */ var usingSettings = WithConnectionSettings(s => s - /** here we provide an explicit rename of a property on connectionsettings */ + .InferMappingFor(m => m .Rename(p => p.RenamedOnConnectionSettings, "renamed") ) - /** All properties that are not mapped verbatim should be uppercased*/ .DefaultFieldNameInferrer(p => p.ToUpperInvariant()) ).WithSerializer(s => new CustomSerializer(s)); @@ -350,9 +378,9 @@ public void PrecedenceIsAsExpected() usingSettings.Expect("jsonProp").ForField(Field(p => p.JsonProperty)); usingSettings.Expect("ask").ForField(Field(p => p.AskSerializer)); usingSettings.Expect("DEFAULTFIELDNAMEINFERRER").ForField(Field(p => p.DefaultFieldNameInferrer)); - - /** The same rules apply when indexing an object */ - usingSettings.Expect(new [] + + /** The same naming rules also apply when indexing a document */ + usingSettings.Expect(new [] { "ask", "DEFAULTFIELDNAMEINFERRER", @@ -368,6 +396,6 @@ public void PrecedenceIsAsExpected() DefaultFieldNameInferrer = "shouting much?" }); - } + } } } diff --git a/src/Tests/ClientConcepts/HighLevel/Inference/IdsInference.doc.cs b/src/Tests/ClientConcepts/HighLevel/Inference/IdsInference.doc.cs new file mode 100644 index 00000000000..11e3aed06a2 --- /dev/null +++ b/src/Tests/ClientConcepts/HighLevel/Inference/IdsInference.doc.cs @@ -0,0 +1,125 @@ +using System; +using Nest; +using Tests.Framework; +using static Tests.Framework.RoundTripper; + +namespace Tests.ClientConcepts.HighLevel.Inference +{ + public class IdsInference + { + /**[[ids-inference]] + *== Ids Inference + * + * === Implicit Conversions + * + * Several places in the Elasticsearch API expect an `Id` object to be passed. + * This is a special box type that you can implicitly convert to from the following types + * + * - `Int32` + * - `Int64` + * - `String` + * - `Guid` + * + * Methods that take an `Id` can be passed any of these types and it will be implicitly converted to an `Id` + */ + [U] public void CanImplicitlyConvertToId() + { + Id idFromInt = 1; + Id idFromLong = 2L; + Id idFromString = "hello-world"; + Id idFromGuid = new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"); + + Expect(1).WhenSerializing(idFromInt); + Expect(2).WhenSerializing(idFromLong); + Expect("hello-world").WhenSerializing(idFromString); + Expect("d70bd3cf-4e38-46f3-91ca-fcbef29b148e").WhenSerializing(idFromGuid); + } + + /** === Inferring from a Type + * + * Sometimes a method takes an object and we need an Id from that object to build up a path. + * There is no implicit conversion from any object to Id but we can call `Id.From`. + * + * Imagine your codebase has the following type that we want to index into Elasticsearch + */ + class MyDTO + { + public Guid Id { get; set; } + public string Name { get; set; } + public string OtherName { get; set; } + } + + [U] public void CanGetIdFromDocument() + { + /** By default NEST will try to find a property called `Id` on the class using reflection + * and create a cached fast func delegate based on the properties getter + */ + var dto = new MyDTO + { + Id = new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"), + Name = "x", + OtherName = "y" + }; + + Expect("d70bd3cf-4e38-46f3-91ca-fcbef29b148e").WhenInferringIdOn(dto); + + /** Using the connection settings you can specify a different property that NEST should use to infer the document Id. + * Here we instruct NEST to infer the Id for `MyDTO` based on its `Name` property + */ + WithConnectionSettings(x => x + .InferMappingFor(m => m + .IdProperty(p => p.Name) + ) + ).Expect("x").WhenInferringIdOn(dto); + + /** IMPORTANT: Inference rules are cached __per__ `ConnectionSettings` instance. + * + * Because the cache is per `ConnectionSettings` instance, we can create another `ConnectionSettings` instance + * with different inference rules + */ + WithConnectionSettings(x => x + .InferMappingFor(m => m + .IdProperty(p => p.OtherName) + ) + ).Expect("y").WhenInferringIdOn(dto); + } + + /** === Using the ElasticsearchType attribute + * + * Another way is to mark the type with an `ElasticsearchType` attribute, setting `IdProperty` + * to the name of the property that should be used for the document id + */ + [ElasticsearchType(IdProperty = nameof(Name))] + class MyOtherDTO + { + public Guid Id { get; set; } + public string Name { get; set; } + public string OtherName { get; set; } + } + + [U] public void CanGetIdFromAttribute() + { + /** Now when we infer the id we expect it to be the value of the `Name` property without doing any configuration on the `ConnectionSettings` */ + var dto = new MyOtherDTO + { + Id = new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"), + Name = "x", + OtherName = "y" + }; + + Expect("x").WhenInferringIdOn(dto); + + /** === Using Mapping inference on ConnectionSettings + * + * This attribute *is* cached statically/globally, however an inference rule on the `ConnectionSettings` for the type will + * still win over the attribute. Here we demonstrate this by creating a different `ConnectionSettings` instance + * that will infer the document id from the property `OtherName`: + */ + WithConnectionSettings(x => x + .InferMappingFor(m => m + .IdProperty(p => p.OtherName) + ) + ).Expect("y").WhenInferringIdOn(dto); + } + } +} diff --git a/src/Tests/ClientConcepts/HighLevel/Inference/IndexNameInference.doc.cs b/src/Tests/ClientConcepts/HighLevel/Inference/IndexNameInference.doc.cs new file mode 100644 index 00000000000..d68845cc3ef --- /dev/null +++ b/src/Tests/ClientConcepts/HighLevel/Inference/IndexNameInference.doc.cs @@ -0,0 +1,108 @@ +using Elasticsearch.Net; +using FluentAssertions; +using Nest; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tests.Framework; +using Tests.Framework.MockData; +using Xunit; + +namespace Tests.ClientConcepts.HighLevel.Inference +{ + /**[[index-name-inference]] + *== Index Name Inference + * + * Many endpoints within the Elasticsearch API expect to receive one or more index names + * as part of the request in order to know what index/indices a request should operate on. + * + * NEST has a number of ways in which an index name can be specified + */ + public class IndexNameInference + { + /**=== Default Index name on ConnectionSettings + * A default index name can be specified on `ConnectionSettings` usinf `.DefaultIndex()`. + * This is the default index name to use when no other index name can be resolved for a request + */ + [U] + public void DefaultIndexIsInferred() + { + var settings = new ConnectionSettings() + .DefaultIndex("defaultindex"); + var resolver = new IndexNameResolver(settings); + var index = resolver.Resolve(); + index.Should().Be("defaultindex"); + } + + /**=== Mapping an Index name for POCOs + * A index name can be mapped for CLR types using `.MapDefaultTypeIndices()` on `ConnectionSettings`. + */ + [U] + public void ExplicitMappingIsInferred() + { + var settings = new ConnectionSettings() + .MapDefaultTypeIndices(m => m + .Add(typeof(Project), "projects") + ); + var resolver = new IndexNameResolver(settings); + var index = resolver.Resolve(); + index.Should().Be("projects"); + } + + /**=== Mapping an Index name for POCOs + * An index name for a POCO provided using `.MapDefaultTypeIndices()` **will take precedence** over + * the default index name + */ + [U] + public void ExplicitMappingTakesPrecedence() + { + var settings = new ConnectionSettings() + .DefaultIndex("defaultindex") + .MapDefaultTypeIndices(m => m + .Add(typeof(Project), "projects") + ); + var resolver = new IndexNameResolver(settings); + var index = resolver.Resolve(); + index.Should().Be("projects"); + } + + /**=== Explicitly specifying Index name on the request + * For API calls that expect an index name, the index name can be explicitly provided + * on the request + */ + [U] + public void ExplicitIndexOnRequest() + { + Uri requestUri = null; + var client = TestClient.GetInMemoryClient(s => s + .OnRequestCompleted(r => { requestUri = r.Uri; })); + + var response = client.Search(s => s.Index("some-other-index")); //<1> Provide the index name on the request + + requestUri.Should().NotBeNull(); + requestUri.LocalPath.Should().StartWith("/some-other-index/"); + } + + /** When an index name is provided on a request, it **will take precedence** over the default + * index name and any index name specified for the POCO type using `.MapDefaultTypeIndices()` + */ + [U] + public void ExplicitIndexOnRequestTakesPrecedence() + { + var client = TestClient.GetInMemoryClient(s => + new ConnectionSettings() + .DefaultIndex("defaultindex") + .MapDefaultTypeIndices(m => m + .Add(typeof(Project), "projects") + ) + ); + + var response = client.Search(s => s.Index("some-other-index")); //<1> Provide the index name on the request + + response.ApiCall.Uri.Should().NotBeNull(); + response.ApiCall.Uri.LocalPath.Should().StartWith("/some-other-index/"); + } + } +} diff --git a/src/Tests/ClientConcepts/HighLevel/Inferrence/IndicesPaths.doc.cs b/src/Tests/ClientConcepts/HighLevel/Inference/IndicesPaths.doc.cs similarity index 61% rename from src/Tests/ClientConcepts/HighLevel/Inferrence/IndicesPaths.doc.cs rename to src/Tests/ClientConcepts/HighLevel/Inference/IndicesPaths.doc.cs index 3bd8fa97bcb..51995e316bb 100644 --- a/src/Tests/ClientConcepts/HighLevel/Inferrence/IndicesPaths.doc.cs +++ b/src/Tests/ClientConcepts/HighLevel/Inference/IndicesPaths.doc.cs @@ -2,17 +2,18 @@ using Tests.Framework; using Tests.Framework.MockData; -namespace Tests.ClientConcepts.HighLevel.Inferrence +namespace Tests.ClientConcepts.HighLevel.Inference { public class IndicesPaths { - /** # Indices paths - * - * Some API's in elasticsearch take one or many index name or a special "_all" marker to send the request to all the indices - * In nest this is encoded using `Indices` - */ - - /** Several types implicitly convert to `Indices` */ + /**== Indices paths + * + * Some API's in Elasticsearch take one or many index name or a special `_all` marker to send the request to all the indices + * In nest this is encoded using `Indices`. + * + *=== Implicit Conversion + * Several types implicitly convert to `Indices` + */ [U] public void ImplicitConversionFromString() { Nest.Indices singleIndexFromString = "name"; @@ -38,16 +39,19 @@ [U] public void ImplicitConversionFromString() ); } - /** to ease creating Field's from expressions there is a static Property class you can use */ + /**[[nest-indices]] + *=== Using Nest.Indices + * To ease creating `IndexName` or `Indices` from expressions, there is a static `Nest.Indices` class you can use + */ [U] public void UsingStaticPropertyField() { - /** */ - var all = Nest.Indices.All; - var many = Nest.Indices.Index("name1", "name2"); - var manyTyped = Nest.Indices.Index().And(); + var all = Nest.Indices.All; //<1> Using `_all` indices + var many = Nest.Indices.Index("name1", "name2"); //<2> specifying multiple indices using strings + var manyTyped = Nest.Indices.Index().And(); //<3> speciying multiple using types var singleTyped = Nest.Indices.Index(); var singleString = Nest.Indices.Index("name1"); - var invalidSingleString = Nest.Indices.Index("name1, name2"); + + var invalidSingleString = Nest.Indices.Index("name1, name2"); //<4> an **invalid** single index name } } } diff --git a/src/Tests/ClientConcepts/HighLevel/Inference/PropertyInference.doc.cs b/src/Tests/ClientConcepts/HighLevel/Inference/PropertyInference.doc.cs new file mode 100644 index 00000000000..a4cfb23673e --- /dev/null +++ b/src/Tests/ClientConcepts/HighLevel/Inference/PropertyInference.doc.cs @@ -0,0 +1,94 @@ +using System; +using System.Linq.Expressions; +using Nest; +using Tests.Framework; +using Tests.Framework.Integration; +using Tests.Framework.MockData; +using static Tests.Framework.RoundTripper; +using Xunit; +using FluentAssertions; + +namespace Tests.ClientConcepts.HighLevel.Inference +{ + /**[[property-inference]] + * == Property Name Inference + */ + [Collection(IntegrationContext.Indexing)] + public class PropertyNames : SimpleIntegration + { + private IElasticClient _client; + + public PropertyNames(IndexingCluster cluster) : base(cluster) + { + _client = cluster.Node.Client(); + } + + /**=== Appending suffixes to a Lambda expression body + * Suffixes can be appended to the body of a lambda expression, useful in cases where + * you have a POCO property mapped as a {ref_current}/_multi_fields.html[multi_field] + * and want to use strongly typed access based on the property, yet append a suffix to the + * generated field name in order to access a particular `multi_field`. + * + * The `.Suffix()` extension method can be used for this purpose and when serializing expressions suffixed + * in this way, the serialized field name resolves to the last token + */ + [U] public void PropertyNamesAreResolvedToLastTokenUsingSuffix() + { + Expression> expression = p => p.Name.Suffix("raw"); + Expect("raw").WhenSerializing(expression); + } + + /**=== Appending suffixes to a Lambda expression + * Alternatively, suffixes can be applied to a lambda expression directly using + * the `.ApplySuffix()` extension method. Again, the serialized field name + * resolves to the last token + */ + [U] + public void PropertyNamesAreResolvedToLastTokenUsingAppendSuffix() + { + Expression> expression = p => p.Name; + expression = expression.AppendSuffix("raw"); + Expect("raw").WhenSerializing(expression); + } + + /**=== Naming conventions + * Currently, the name of a field cannot contain a `.` in Elasticsearch due to the potential for ambiguity with + * a field that is mapped as a {ref_current}/_multi_fields.html[multi_field]. + * + * In these cases, NEST allows the call to go to Elasticsearch, deferring the naming conventions to the server side and, + * in the case of a `.` in a field name, a `400 Bad Response` is returned with a server error indicating the reason + */ + [I] public void PropertyNamesContainingDotsCausesElasticsearchServerError() + { + var createIndexResponse = _client.CreateIndex("random-" + Guid.NewGuid().ToString().ToLowerInvariant(), c => c + .Mappings(m => m + .Map("type-with-dot", mm => mm + .Properties(p => p + .String(s => s + .Name("name-with.dot") + ) + ) + ) + ) + ); + + /** The response is not valid */ + createIndexResponse.IsValid.Should().BeFalse(); + + /** `DebugInformation` provides an audit trail of information to help diagnose the issue */ + createIndexResponse.DebugInformation.Should().NotBeNullOrEmpty(); + + /** `ServerError` contains information about the response from Elasticsearch */ + createIndexResponse.ServerError.Should().NotBeNull(); + createIndexResponse.ServerError.Status.Should().Be(400); + createIndexResponse.ServerError.Error.Should().NotBeNull(); + createIndexResponse.ServerError.Error.RootCause.Should().NotBeNullOrEmpty(); + + var rootCause = createIndexResponse.ServerError.Error.RootCause[0]; + + /** We can see that the underlying reason is a `.` in the field name "name-with.dot" */ + rootCause.Reason.Should().Be("Field name [name-with.dot] cannot contain '.'"); + rootCause.Type.Should().Be("mapper_parsing_exception"); + } + } +} diff --git a/src/Tests/ClientConcepts/HighLevel/Inferrence/FeaturesInference.cs b/src/Tests/ClientConcepts/HighLevel/Inferrence/FeaturesInference.cs deleted file mode 100644 index 4dcaa3b9b20..00000000000 --- a/src/Tests/ClientConcepts/HighLevel/Inferrence/FeaturesInference.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Elasticsearch.Net; -using Nest; -using Tests.Framework; -using static Nest.Indices; -using static Tests.Framework.RoundTripper; - -namespace Tests.ClientConcepts.HighLevel.Inferrence -{ - public class FeaturesInference - { - /** # Features - * Some urls in Elasticsearch take a {feature} enum - * RouteValues in NEST are represented as classes implementing IUrlParameter - * Since enums can not implement interfaces in C# this route param is represented using the Features class that can - * be implicitly converted to from the Feature enum - */ - - /** Using the constructor directly is possible but rather involved */ - [U] public void Serializes() - { - Features fieldString = Feature.Mappings | Feature.Aliases; - Expect("_mappings,_aliases") - .WhenSerializing(fieldString); - } - - [U] - public void ImplicitConversion() - { - /** - * Here we new an GET index elasticsearch request whichs takes Indices and Features. - * Notice how we can use the Feature enum directly. - */ - var request = new GetIndexRequest(All, Feature.Settings | Feature.Warmers); - } - } -} diff --git a/src/Tests/ClientConcepts/HighLevel/Inferrence/IdsInference.doc.cs b/src/Tests/ClientConcepts/HighLevel/Inferrence/IdsInference.doc.cs deleted file mode 100644 index 95f1a6f6b59..00000000000 --- a/src/Tests/ClientConcepts/HighLevel/Inferrence/IdsInference.doc.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using Nest; -using Tests.Framework; -using static Tests.Framework.RoundTripper; - -namespace Tests.ClientConcepts.HighLevel.Inferrence -{ - public class IdsInference - { - /** # Ids - * - * Several places in the elasticsearch API expect an Id object to be passed. This is a special box type that you can implicitly convert to and from many value types. - */ - - /** Methods that take an Id can be passed longs, ints, strings & Guids and they will implicitly converted to Ids */ - [U] public void CanImplicitlyConvertToId() - { - Id idFromInt = 1; - Id idFromLong = 2L; - Id idFromString = "hello-world"; - Id idFromGuid = new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"); - - Expect(1).WhenSerializing(idFromInt); - Expect(2).WhenSerializing(idFromLong); - Expect("hello-world").WhenSerializing(idFromString); - Expect("d70bd3cf-4e38-46f3-91ca-fcbef29b148e").WhenSerializing(idFromGuid); - } - - /** Sometimes a method takes an object and we need an Id from that object to build up a path. - * There is no implicit conversion from any object to Id but we can call Id.From. - * - * Imagine your codebase has the following type that we want to index into elasticsearch - */ - class MyDTO - { - public Guid Id { get; set; } - public string Name { get; set; } - public string OtherName { get; set; } - } - - [U] public void CanGetIdFromDocument() - { - /** By default NEST will try to find a property called `Id` on the class using reflection - * and create a cached fast func delegate based on the properties getter*/ - var dto = new MyDTO { Id =new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"), Name = "x", OtherName = "y" }; - Expect("d70bd3cf-4e38-46f3-91ca-fcbef29b148e").WhenInferringIdOn(dto); - - /** Using the connection settings you can specify a different property NEST should look for ids. - * Here we instruct NEST to infer the Id for MyDTO based on its Name property */ - WithConnectionSettings(x => x - .InferMappingFor(m => m - .IdProperty(p => p.Name) - ) - ).Expect("x").WhenInferringIdOn(dto); - - /** Even though we have a cache at play the cache is per connection settings, so we can create a different config */ - WithConnectionSettings(x => x - .InferMappingFor(m => m - .IdProperty(p => p.OtherName) - ) - ).Expect("y").WhenInferringIdOn(dto); - } - - /** Another way is to mark the type with an ElasticType attribute, using a string IdProperty */ - [ElasticsearchType(IdProperty = nameof(Name))] - class MyOtherDTO - { - public Guid Id { get; set; } - public string Name { get; set; } - public string OtherName { get; set; } - } - - [U] public void CanGetIdFromAttribute() - { - /** Now when we infer the id we expect it to be the Name property without doing any configuration on the ConnectionSettings */ - var dto = new MyOtherDTO { Id =new Guid("D70BD3CF-4E38-46F3-91CA-FCBEF29B148E"), Name = "x", OtherName = "y" }; - Expect("x").WhenInferringIdOn(dto); - /** This attribute IS cached statically/globally, however connectionsettings with a config for the type will - * still win over this static configuration*/ - /** Eventhough we have a cache at play the cache its per connection settings, so we can create a different config */ - WithConnectionSettings(x => x - .InferMappingFor(m => m - .IdProperty(p => p.OtherName) - ) - ).Expect("y").WhenInferringIdOn(dto); - } - } -} diff --git a/src/Tests/ClientConcepts/HighLevel/Inferrence/IndexNameInference.doc.cs b/src/Tests/ClientConcepts/HighLevel/Inferrence/IndexNameInference.doc.cs deleted file mode 100644 index a9dd0dcfde7..00000000000 --- a/src/Tests/ClientConcepts/HighLevel/Inferrence/IndexNameInference.doc.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Elasticsearch.Net; -using FluentAssertions; -using Nest; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Tests.Framework; -using Tests.Framework.MockData; -using Xunit; - -namespace Tests.ClientConcepts.HighLevel.Inferrence -{ - public class IndexNameInference - { - [U] - public void DefaultIndexIsInferred() - { - var settings = new ConnectionSettings() - .DefaultIndex("defaultindex"); - var resolver = new IndexNameResolver(settings); - var index = resolver.Resolve(); - index.Should().Be("defaultindex"); - } - - [U] - public void ExplicitMappingIsInferred() - { - var settings = new ConnectionSettings() - .MapDefaultTypeIndices(m => m - .Add(typeof(Project), "projects") - ); - var resolver = new IndexNameResolver(settings); - var index = resolver.Resolve(); - index.Should().Be("projects"); - } - - [U] - public void ExplicitMappingTakesPrecedence() - { - var settings = new ConnectionSettings() - .DefaultIndex("defaultindex") - .MapDefaultTypeIndices(m => m - .Add(typeof(Project), "projects") - ); - var resolver = new IndexNameResolver(settings); - var index = resolver.Resolve(); - index.Should().Be("projects"); - } - } -} diff --git a/src/Tests/ClientConcepts/HighLevel/Inferrence/PropertyInference.doc.cs b/src/Tests/ClientConcepts/HighLevel/Inferrence/PropertyInference.doc.cs deleted file mode 100644 index bc9671c0ddf..00000000000 --- a/src/Tests/ClientConcepts/HighLevel/Inferrence/PropertyInference.doc.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Linq.Expressions; -using Nest; -using Tests.Framework; -using Tests.Framework.Integration; -using Tests.Framework.MockData; -using static Tests.Framework.RoundTripper; -using Xunit; -using FluentAssertions; - -namespace Tests.ClientConcepts.HighLevel.Inferrence.PropertyNames -{ - /** == Property Names */ - [Collection(IntegrationContext.Indexing)] - public class PropertyNames : SimpleIntegration - { - private IElasticClient _client; - - public PropertyNames(IndexingCluster cluster) : base(cluster) - { - _client = cluster.Node.Client(); - } - - /** Property names resolve to the last token */ - [U] public void PropertyNamesAreResolvedToLastToken() - { - Expression> expression = p => p.Name.Suffix("raw"); - Expect("raw").WhenSerializing(expression); - } - - /** :multi-field: {ref_current}/_multi_fields.html - *Property names cannot contain a `.` (dot), because of the potential for ambiguity with - *a field that is mapped as a {multi-field}[`multi_field`]. - * - *NEST allows the call to go to Elasticsearch, deferring the naming conventions to the server side and, - * in the case of dots in field names, returns a `400 Bad Response` with a server error indicating the reason. - */ - [I] public void PropertyNamesContainingDotsCausesElasticsearchServerError() - { - var createIndexResponse = _client.CreateIndex("random-" + Guid.NewGuid().ToString().ToLowerInvariant(), c => c - .Mappings(m => m - .Map("type-with-dot", mm => mm - .Properties(p => p - .String(s => s - .Name("name-with.dot") - ) - ) - ) - ) - ); - - /** The response is not valid */ - createIndexResponse.IsValid.Should().BeFalse(); - - /** `DebugInformation` provides an audit trail of information to help diagnose the issue */ - createIndexResponse.DebugInformation.Should().NotBeNullOrEmpty(); - - /** `ServerError` contains information from the response from Elasticsearch */ - createIndexResponse.ServerError.Should().NotBeNull(); - createIndexResponse.ServerError.Status.Should().Be(400); - createIndexResponse.ServerError.Error.Should().NotBeNull(); - createIndexResponse.ServerError.Error.RootCause.Should().NotBeNullOrEmpty(); - - var rootCause = createIndexResponse.ServerError.Error.RootCause[0]; - - rootCause.Reason.Should().Be("Field name [name-with.dot] cannot contain '.'"); - rootCause.Type.Should().Be("mapper_parsing_exception"); - } - } -} diff --git a/src/Tests/ClientConcepts/HighLevel/Mapping/AutoMap.doc.cs b/src/Tests/ClientConcepts/HighLevel/Mapping/AutoMap.doc.cs index df88d9e7431..9c7920f4be1 100644 --- a/src/Tests/ClientConcepts/HighLevel/Mapping/AutoMap.doc.cs +++ b/src/Tests/ClientConcepts/HighLevel/Mapping/AutoMap.doc.cs @@ -1,689 +1,694 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using Nest; -using Newtonsoft.Json; -using Tests.Framework; -using static Tests.Framework.RoundTripper; - -namespace Tests.ClientConcepts.HighLevel.Mapping -{ - /** # Auto mapping properties - * - * When creating a mapping (either when creating an index or via the put mapping API), - * NEST offers a feature called AutoMap(), which will automagically infer the correct - * Elasticsearch datatypes of the POCO properties you are mapping. Alternatively, if - * you're using attributes to map your properties, then calling AutoMap() is required - * in order for your attributes to be applied. We'll look at examples of both. - * - **/ - public class AutoMap - { - /** - * For these examples, we'll define two POCOS. A Company, which has a name - * and a collection of Employees. And Employee, which has various properties of - * different types, and itself has a collection of Employees. - */ - public class Company - { - public string Name { get; set; } - public List Employees { get; set; } - } - - public class Employee - { - public string FirstName { get; set; } - public string LastName { get; set; } - public int Salary { get; set; } - public DateTime Birthday { get; set; } - public bool IsManager { get; set; } - public List Employees { get; set; } - public TimeSpan Hours { get; set; } - } - - [U] - public void MappingManually() - { - /** ## Manual mapping - * To create a mapping for our Company type, we can use the fluent API - * and map each property explicitly - */ - var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m - .Properties(ps => ps - .String(s => s - .Name(c => c.Name) - ) - .Object(o => o - .Name(c => c.Employees) - .Properties(eps => eps - .String(s => s - .Name(e => e.FirstName) - ) - .String(s => s - .Name(e => e.LastName) - ) - .Number(n => n - .Name(e => e.Salary) - .Type(NumberType.Integer) - ) - ) - ) - ) - ) - ); - - /** - * Which is all fine and dandy, and useful for some use cases. However in most cases - * this is becomes too cumbersome of an approach, and you simply just want to map *all* - * the properties of your POCO in a single go. - */ - var expected = new - { - mappings = new - { - company = new - { - properties = new - { - name = new - { - type = "string" - }, - employees = new - { - type = "object", - properties = new - { - firstName = new - { - type = "string" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "integer" - } - } - } - } - } - } - }; - - Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); - } - - [U] - public void UsingAutoMap() - { - /** ## Simple Automapping - * This is exactly where `AutoMap()` becomes useful. Instead of manually mapping each property, - * explicitly, we can instead call `.AutoMap()` for each of our mappings and let NEST do all the work - */ - var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m.AutoMap()) - .Map(m => m.AutoMap()) - ); - - /** - * Observe that NEST has inferred the Elasticsearch types based on the CLR type of our POCO properties. - * In this example, - * - Birthday was mapped as a date, - * - Hours was mapped as a long (ticks) - * - IsManager was mapped as a boolean, - * - Salary as an integer - * - Employees as an object - * and the remaining string properties as strings. - */ - var expected = new - { - mappings = new - { - company = new - { - properties = new - { - employees = new - { - properties = new - { - birthday = new - { - type = "date" - }, - employees = new - { - properties = new { }, - type = "object" - }, - firstName = new - { - type = "string" - }, - hours = new - { - type = "long" - }, - isManager = new - { - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "integer" - } - }, - type = "object" - }, - name = new - { - type = "string" - } - } - }, - employee = new - { - properties = new - { - birthday = new - { - type = "date" - }, - employees = new - { - properties = new { }, - type = "object" - }, - firstName = new - { - type = "string" +using System; +using System.Collections.Generic; +using System.Reflection; +using Nest; +using Newtonsoft.Json; +using Tests.Framework; +using static Tests.Framework.RoundTripper; + +namespace Tests.ClientConcepts.HighLevel.Mapping +{ + /** + * [[auto-map]] + * == Auto mapping properties + * + * When creating a mapping (either when creating an index or via the put mapping API), + * NEST offers a feature called `.AutoMap()`, which will automagically infer the correct + * Elasticsearch datatypes of the POCO properties you are mapping. Alternatively, if + * you're using attributes to map your properties, then calling `.AutoMap()` is required + * in order for your attributes to be applied. We'll look at the features of auto mapping + * with a number of examples. + **/ + public class AutoMap + { + /** + * For these examples, we'll define two POCOS, `Company`, which has a name + * and a collection of Employees, and `Employee` which has various properties of + * different types, and itself has a collection of `Employee` types. + */ + public class Company + { + public string Name { get; set; } + public List Employees { get; set; } + } + + public class Employee + { + public string FirstName { get; set; } + public string LastName { get; set; } + public int Salary { get; set; } + public DateTime Birthday { get; set; } + public bool IsManager { get; set; } + public List Employees { get; set; } + public TimeSpan Hours { get; set; } + } + + [U] + public void MappingManually() + { + /** === Manual mapping + * To create a mapping for our Company type, we can use the fluent API + * and map each property explicitly + */ + var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m + .Properties(ps => ps + .String(s => s + .Name(c => c.Name) //<1> map `Name` as a `string` type + ) + .Object(o => o //<2> map `Employees` as an `object` type, mapping each of the properties of `Employee` + .Name(c => c.Employees) + .Properties(eps => eps + .String(s => s + .Name(e => e.FirstName) + ) + .String(s => s + .Name(e => e.LastName) + ) + .Number(n => n + .Name(e => e.Salary) + .Type(NumberType.Integer) + ) + ) + ) + ) + ) + ); + + /** + * This is all fine and dandy and useful for some use cases however in most cases + * this can become verbose and wieldy. The majority of the time you simply just want to map *all* + * the properties of a POCO in a single go. + */ + var expected = new + { + mappings = new + { + company = new + { + properties = new + { + name = new + { + type = "string" + }, + employees = new + { + type = "object", + properties = new + { + firstName = new + { + type = "string" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "integer" + } + } + } + } + } + } + }; + + Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); + } + + [U] + public void UsingAutoMap() + { + /** === Simple Automapping + * This is exactly where `.AutoMap()` becomes useful. Instead of manually mapping each property, + * explicitly, we can instead call `.AutoMap()` for each of our mappings and let NEST do all the work + */ + var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m.AutoMap()) + .Map(m => m.AutoMap()) + ); + + /** + * Observe that NEST has inferred the Elasticsearch types based on the CLR type of our POCO properties. + * In this example, + * - Birthday was mapped as a `date`, + * - Hours was mapped as a `long` (ticks) + * - IsManager was mapped as a `bool`, + * - Salary as an `integer` + * - Employees as an `object` + * + * and the remaining string properties as `string` types + */ + var expected = new + { + mappings = new + { + company = new + { + properties = new + { + employees = new + { + properties = new + { + birthday = new + { + type = "date" + }, + employees = new + { + properties = new { }, + type = "object" + }, + firstName = new + { + type = "string" + }, + hours = new + { + type = "long" + }, + isManager = new + { + type = "boolean" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "integer" + } + }, + type = "object" + }, + name = new + { + type = "string" + } + } + }, + employee = new + { + properties = new + { + birthday = new + { + type = "date" + }, + employees = new + { + properties = new { }, + type = "object" + }, + firstName = new + { + type = "string" }, hours = new { type = "long" - }, - isManager = new - { - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "integer" - } - } - } - } - }; - - Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); - } - - /** ## Automapping with overrides - * In most cases, you'll want to map more than just the vanilla datatypes and also provide - * various options on your properties (analyzer, doc_values, etc...). In that case, it's - * possible to use AutoMap() in conjuction with explicitly mapped properties. - */ - [U] - public void OverridingAutoMappedProperties() - { - /** - * Here we are using AutoMap() to automatically map our company type, but then we're - * overriding our employee property and making it a `nested` type, since by default, - * AutoMap() will infer objects as `object`. - */ - var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m - .AutoMap() - .Properties(ps => ps - .Nested(n => n - .Name(c => c.Employees) - ) - ) - ) - ); - - var expected = new - { - mappings = new - { - company = new - { - properties = new - { - name = new - { - type = "string" - }, - employees = new - { - type = "nested", - } - } - } - } - }; - - Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); - - /** - * AutoMap is idempotent. Calling it before or after manually - * mapped properties should still yield the same results. - */ - descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m - .Properties(ps => ps - .Nested(n => n - .Name(c => c.Employees) - ) - ) - .AutoMap() - ) - ); - - Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); - } - - /** ## Automap with attributes - * It is also possible to define your mappings using attributes on your POCOS. When you - * use attributes, you MUST use AutoMap() in order for the attributes to be applied. - * Here we define the same two types but this time using attributes. - */ - [ElasticsearchType(Name = "company")] - public class CompanyWithAttributes - { - [String(Analyzer = "keyword", NullValue = "null", Similarity = SimilarityOption.BM25)] - public string Name { get; set; } - - [String(Name = "office_hours")] - public TimeSpan? HeadOfficeHours { get; set; } - - [Object(Path = "employees", Store = false)] - public List Employees { get; set; } - } - - [ElasticsearchType(Name = "employee")] - public class EmployeeWithAttributes - { - [String(Name = "first_name")] - public string FirstName { get; set; } - - [String(Name = "last_name")] - public string LastName { get; set; } - - [Number(DocValues = false, IgnoreMalformed = true, Coerce = true)] - public int Salary { get; set; } - - [Date(Format = "MMddyyyy", NumericResolution = NumericResolutionUnit.Seconds)] - public DateTime Birthday { get; set; } - - [Boolean(NullValue = false, Store = true)] - public bool IsManager { get; set; } - + }, + isManager = new + { + type = "boolean" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "integer" + } + } + } + } + }; + + Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); + } + + /**[float] + * == Auto mapping with overrides + * In most cases, you'll want to map more than just the vanilla datatypes and also provide + * various options for your properties (analyzer to use, whether to enable doc_values, etc...). + * In that case, it's possible to use `.AutoMap()` in conjuction with explicitly mapped properties. + */ + [U] + public void OverridingAutoMappedProperties() + { + /** + * Here we are using `.AutoMap()` to automatically map our company type, but then we're + * overriding our employee property and making it a `nested` type, since by default, + * `.AutoMap()` will infer objects as `object`. + */ + var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m + .AutoMap() + .Properties(ps => ps + .Nested(n => n + .Name(c => c.Employees) + ) + ) + ) + ); + + var expected = new + { + mappings = new + { + company = new + { + properties = new + { + name = new + { + type = "string" + }, + employees = new + { + type = "nested", + } + } + } + } + }; + + Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); + + /** + * `.AutoMap()` is idempotent; calling it before or after manually + * mapped properties will still yield the same results. + */ + descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m + .Properties(ps => ps + .Nested(n => n + .Name(c => c.Employees) + ) + ) + .AutoMap() + ) + ); + + Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); + } + + /**[[attribute-mapping]] + * [float] + * == Attribute mapping + * It is also possible to define your mappings using attributes on your POCOs. When you + * use attributes, you *must* use `.AutoMap()` in order for the attributes to be applied. + * Here we define the same two types as before, but this time using attributes to define the mappings. + */ + [ElasticsearchType(Name = "company")] + public class CompanyWithAttributes + { + [String(Analyzer = "keyword", NullValue = "null", Similarity = SimilarityOption.BM25)] + public string Name { get; set; } + + [String(Name = "office_hours")] + public TimeSpan? HeadOfficeHours { get; set; } + + [Object(Path = "employees", Store = false)] + public List Employees { get; set; } + } + + [ElasticsearchType(Name = "employee")] + public class EmployeeWithAttributes + { + [String(Name = "first_name")] + public string FirstName { get; set; } + + [String(Name = "last_name")] + public string LastName { get; set; } + + [Number(DocValues = false, IgnoreMalformed = true, Coerce = true)] + public int Salary { get; set; } + + [Date(Format = "MMddyyyy", NumericResolution = NumericResolutionUnit.Seconds)] + public DateTime Birthday { get; set; } + + [Boolean(NullValue = false, Store = true)] + public bool IsManager { get; set; } + [Nested(Path = "employees")] - [JsonProperty("empl")] - public List Employees { get; set; } - } - - [U] - public void UsingAutoMapWithAttributes() - { - var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m.AutoMap()) - .Map(m => m.AutoMap()) - ); - - var expected = new - { - mappings = new - { - company = new - { - properties = new - { - employees = new - { - path = "employees", - properties = new - { - birthday = new - { - type = "date" - }, - employees = new - { - properties = new { }, - type = "object" - }, - firstName = new - { - type = "string" + [JsonProperty("empl")] + public List Employees { get; set; } + } + + /**Then we map the types by calling `.AutoMap()` */ + [U] + public void UsingAutoMapWithAttributes() + { + var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m.AutoMap()) + .Map(m => m.AutoMap()) + ); + + var expected = new + { + mappings = new + { + company = new + { + properties = new + { + employees = new + { + path = "employees", + properties = new + { + birthday = new + { + type = "date" + }, + employees = new + { + properties = new { }, + type = "object" + }, + firstName = new + { + type = "string" }, - hours = new - { + hours = new + { type = "long" + }, + isManager = new + { + type = "boolean" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "integer" + } + }, + store = false, + type = "object" + }, + name = new + { + analyzer = "keyword", + null_value = "null", + similarity = "BM25", + type = "string" + }, + office_hours = new + { + type = "string" + } + } + }, + employee = new + { + properties = new + { + birthday = new + { + format = "MMddyyyy", + numeric_resolution = "seconds", + type = "date" + }, + empl = new + { + path = "employees", + properties = new + { + birthday = new + { + type = "date" + }, + employees = new + { + properties = new { }, + type = "object" + }, + firstName = new + { + type = "string" }, - isManager = new - { - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "integer" - } - }, - store = false, - type = "object" - }, - name = new - { - analyzer = "keyword", - null_value = "null", - similarity = "BM25", - type = "string" - }, - office_hours = new - { - type = "string" - } - } - }, - employee = new - { - properties = new - { - birthday = new - { - format = "MMddyyyy", - numeric_resolution = "seconds", - type = "date" - }, - empl = new - { - path = "employees", - properties = new - { - birthday = new - { - type = "date" - }, - employees = new - { - properties = new { }, - type = "object" - }, - firstName = new - { - type = "string" - }, - hours = new - { + hours = new + { type = "long" + }, + isManager = new + { + type = "boolean" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "integer" + } + }, + type = "nested" + }, + first_name = new + { + type = "string" + }, + isManager = new + { + null_value = false, + store = true, + type = "boolean" + }, + last_name = new + { + type = "string" + }, + salary = new + { + coerce = true, + doc_values = false, + ignore_malformed = true, + type = "double" + } + } + } + } + }; + + Expect(expected).WhenSerializing((ICreateIndexRequest) descriptor); + } + + /** + * Just as we were able to override the inferred properties in our earlier example, explicit (manual) + * mappings also take precedence over attributes. Therefore we can also override any mappings applied + * via any attributes defined on the POCO + */ + [U] + public void OverridingAutoMappedAttributes() + { + var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m + .AutoMap() + .Properties(ps => ps + .Nested(n => n + .Name(c => c.Employees) + ) + ) + ) + .Map(m => m + .AutoMap() + .TtlField(ttl => ttl + .Enable() + .Default("10m") + ) + .Properties(ps => ps + .String(s => s + .Name(e => e.FirstName) + .Fields(fs => fs + .String(ss => ss + .Name("firstNameRaw") + .Index(FieldIndexOption.NotAnalyzed) + ) + .TokenCount(t => t + .Name("length") + .Analyzer("standard") + ) + ) + ) + .Number(n => n + .Name(e => e.Salary) + .Type(NumberType.Double) + .IgnoreMalformed(false) + ) + .Date(d => d + .Name(e => e.Birthday) + .Format("MM-dd-yy") + ) + ) + ) + ); + + var expected = new + { + mappings = new + { + company = new + { + properties = new + { + employees = new + { + type = "nested" + }, + name = new + { + analyzer = "keyword", + null_value = "null", + similarity = "BM25", + type = "string" + }, + office_hours = new + { + type = "string" + } + } + }, + employee = new + { + _ttl = new + { + enabled = true, + @default = "10m" + }, + properties = new + { + birthday = new + { + format = "MM-dd-yy", + type = "date" + }, + empl = new + { + path = "employees", + properties = new + { + birthday = new + { + type = "date" + }, + employees = new + { + properties = new { }, + type = "object" + }, + firstName = new + { + type = "string" }, - isManager = new - { - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "integer" - } - }, - type = "nested" - }, - first_name = new - { - type = "string" - }, - isManager = new - { - null_value = false, - store = true, - type = "boolean" - }, - last_name = new - { - type = "string" - }, - salary = new - { - coerce = true, - doc_values = false, - ignore_malformed = true, - type = "double" - } - } - } - } - }; - - Expect(expected).WhenSerializing(descriptor as ICreateIndexRequest); - } - - /** - * Just as we were able to override the inferred properties in our earlier example, explicit (manual) - * mappings also take precedence over attributes. Therefore we can also override any mappings applied - * via any attributes defined on the POCO - */ - [U] - public void OverridingAutoMappedAttributes() - { - var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m - .AutoMap() - .Properties(ps => ps - .Nested(n => n - .Name(c => c.Employees) - ) - ) - ) - .Map(m => m - .AutoMap() - .TtlField(ttl => ttl - .Enable() - .Default("10m") - ) - .Properties(ps => ps - .String(s => s - .Name(e => e.FirstName) - .Fields(fs => fs - .String(ss => ss - .Name("firstNameRaw") - .Index(FieldIndexOption.NotAnalyzed) - ) - .TokenCount(t => t - .Name("length") - .Analyzer("standard") - ) - ) - ) - .Number(n => n - .Name(e => e.Salary) - .Type(NumberType.Double) - .IgnoreMalformed(false) - ) - .Date(d => d - .Name(e => e.Birthday) - .Format("MM-dd-yy") - ) - ) - ) - ); - - var expected = new - { - mappings = new - { - company = new - { - properties = new - { - employees = new - { - type = "nested" - }, - name = new - { - analyzer = "keyword", - null_value = "null", - similarity = "BM25", - type = "string" - }, - office_hours = new - { - type = "string" - } - } - }, - employee = new - { - _ttl = new - { - enabled = true, - @default = "10m" - }, - properties = new - { - birthday = new - { - format = "MM-dd-yy", - type = "date" - }, - empl = new - { - path = "employees", - properties = new - { - birthday = new - { - type = "date" - }, - employees = new - { - properties = new { }, - type = "object" - }, - firstName = new - { - type = "string" - }, - hours = new - { + hours = new + { type = "long" - }, - isManager = new - { - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "integer" - } - }, - type = "nested" - }, - first_name = new - { - fields = new - { - firstNameRaw = new - { - index = "not_analyzed", - type = "string" + }, + isManager = new + { + type = "boolean" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "integer" + } + }, + type = "nested" + }, + first_name = new + { + fields = new + { + firstNameRaw = new + { + index = "not_analyzed", + type = "string" }, length = new { type = "token_count", analyzer = "standard" - } - }, - type = "string" - }, - isManager = new - { - null_value = false, - store = true, - type = "boolean" - }, - last_name = new - { - type = "string" - }, - salary = new - { - ignore_malformed = false, - type = "double" - } - } - } - } - }; - - Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); + } + }, + type = "string" + }, + isManager = new + { + null_value = false, + store = true, + type = "boolean" + }, + last_name = new + { + type = "string" + }, + salary = new + { + ignore_malformed = false, + type = "double" + } + } + } + } + }; + + Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); } - [ElasticsearchType(Name = "company")] - public class CompanyWithAttributesAndPropertiesToIgnore - { - public string Name { get; set; } - - [String(Ignore = true)] - public string PropertyToIgnore { get; set; } - - public string AnotherPropertyToIgnore { get; set; } + /**[float] + * == Ignoring Properties + * Properties on a POCO can be ignored in a few ways: + * + * - Using the `Ignore` property on a derived `ElasticsearchPropertyAttribute` type applied to the property that should be ignored on the POCO + * + * - Using the `.InferMappingFor(Func, IClrTypeMapping> selector)` on the connection settings + * + * - Using an ignore attribute applied to the POCO property that is understood by the `IElasticsearchSerializer` used, and inspected inside of the `CreatePropertyMapping()` on the serializer. In the case of the default `JsonNetSerializer`, this is the Json.NET `JsonIgnoreAttribute` + * + * This example demonstrates all ways, using the `Ignore` property on the attribute to ignore the property `PropertyToIgnore`, the infer mapping to ignore the + * property `AnotherPropertyToIgnore` and the json serializer specific attribute to ignore the property `JsonIgnoredProperty` + */ + [ElasticsearchType(Name = "company")] + public class CompanyWithAttributesAndPropertiesToIgnore + { + public string Name { get; set; } + + [String(Ignore = true)] + public string PropertyToIgnore { get; set; } + + public string AnotherPropertyToIgnore { get; set; } [JsonIgnore] - public string JsonIgnoredProperty { get; set; } + public string JsonIgnoredProperty { get; set; } } - /** == Ignoring Properties - * Properties on a POCO can be ignored in a few ways: - */ - /** - * - Using the `Ignore` property on a derived `ElasticsearchPropertyAttribute` type applied to the property that should be ignored on the POCO - */ - /** - * - Using the `.InferMappingFor(Func, IClrTypeMapping> selector)` on the connection settings - */ - /** - * - Using an ignore attribute applied to the POCO property that is understood by the `IElasticsearchSerializer` used and inspected inside of `CreatePropertyMapping()` on the serializer. In the case of the default `JsonNetSerializer`, this is the Json.NET `JsonIgnoreAttribute` - */ - /** - * This example demonstrates all ways, using the attribute way to ignore the property `PropertyToIgnore`, the infer mapping way to ignore the - * property `AnotherPropertyToIgnore` and the json serializer specific attribute way to ignore the property `JsonIgnoredProperty` - */ - [U] - public void IgnoringProperties() + [U] + public void IgnoringProperties() { + /** All of the properties except `Name` have been ignored in the mapping */ var descriptor = new CreateIndexDescriptor("myindex") .Mappings(ms => ms .Map(m => m @@ -691,319 +696,328 @@ public void IgnoringProperties() ) ); - var expected = new - { - mappings = new - { - company = new - { - properties = new - { - name = new - { - type = "string" - } - } - } - } - }; - - var settings = WithConnectionSettings(s => s - .InferMappingFor(i => i - .Ignore(p => p.AnotherPropertyToIgnore) - ) - ); - - settings.Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); + var expected = new + { + mappings = new + { + company = new + { + properties = new + { + name = new + { + type = "string" + } + } + } + } + }; + + var settings = WithConnectionSettings(s => s + .InferMappingFor(i => i + .Ignore(p => p.AnotherPropertyToIgnore) + ) + ); + + settings.Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); } - /** - * If you notice in our previous Company/Employee examples, the Employee type is recursive - * in that itself contains a collection of type `Employee`. By default, `.AutoMap()` will only - * traverse a single depth when it encounters recursive instances like this. Hence, in the - * previous examples, the second level of Employee did not get any of its properties mapped. - * This is done as a safe-guard to prevent stack overflows and all the fun that comes with - * infinite recursion. Additionally, in most cases, when it comes to Elasticsearch mappings, it is - * often an edge case to have deeply nested mappings like this. However, you may still have - * the need to do this, so you can control the recursion depth of AutoMap(). - * - * Let's introduce a very simple class A, to reduce the noise, which itself has a property - * Child of type A. + /**[float] + * == Mapping Recursion + * If you notice in our previous `Company` and `Employee` examples, the `Employee` type is recursive + * in that the `Employee` class itself contains a collection of type `Employee`. By default, `.AutoMap()` will only + * traverse a single depth when it encounters recursive instances like this. Hence, in the + * previous examples, the collection of type `Employee` on the `Employee` class did not get any of its properties mapped. + * This is done as a safe-guard to prevent stack overflows and all the fun that comes with + * infinite recursion. Additionally, in most cases, when it comes to Elasticsearch mappings, it is + * often an edge case to have deeply nested mappings like this. However, you may still have + * the need to do this, so you can control the recursion depth of `.AutoMap()`. + * + * Let's introduce a very simple class, `A`, which itself has a property + * Child of type `A`. */ - public class A - { - public A Child { get; set; } + public class A + { + public A Child { get; set; } + } + + [U] + public void ControllingRecursionDepth() + { + /** By default, `.AutoMap()` only goes as far as depth 1 */ + var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m.AutoMap()) + ); + + /** Thus we do not map properties on the second occurrence of our Child property */ + var expected = new + { + mappings = new + { + a = new + { + properties = new + { + child = new + { + properties = new { }, + type = "object" + } + } + } + } + }; + + Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); + + /** Now lets specify a maxRecursion of 3 */ + var withMaxRecursionDescriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m.AutoMap(3)) + ); + + /** `.AutoMap()` has now mapped three levels of our Child property */ + var expectedWithMaxRecursion = new + { + mappings = new + { + a = new + { + properties = new + { + child = new + { + type = "object", + properties = new + { + child = new + { + type = "object", + properties = new + { + child = new + { + type = "object", + properties = new + { + child = new + { + type = "object", + properties = new { } + } + } + } + } + } + } + } + } + } + } + }; + + Expect(expectedWithMaxRecursion).WhenSerializing((ICreateIndexRequest)withMaxRecursionDescriptor); } - [U] - public void ControllingRecursionDepth() - { - /** By default, AutoMap() only goes as far as depth 1 */ - var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m.AutoMap()) - ); - - /** Thus we do not map properties on the second occurrence of our Child property */ - var expected = new - { - mappings = new - { - a = new - { - properties = new - { - child = new - { - properties = new { }, - type = "object" - } - } - } - } - }; - - Expect(expected).WhenSerializing((ICreateIndexRequest)descriptor); - - /** Now lets specify a maxRecursion of 3 */ - var withMaxRecursionDescriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m.AutoMap(3)) - ); - - /** AutoMap() has now mapped three levels of our Child property */ - var expectedWithMaxRecursion = new - { - mappings = new - { - a = new - { - properties = new - { - child = new - { - type = "object", - properties = new - { - child = new - { - type = "object", - properties = new - { - child = new - { - type = "object", - properties = new - { - child = new - { - type = "object", - properties = new { } - } - } - } - } - } - } - } - } - } - } - }; - - Expect(expectedWithMaxRecursion).WhenSerializing((ICreateIndexRequest)withMaxRecursionDescriptor); - } - - [U] - //hide - public void PutMappingAlsoAdheresToMaxRecursion() - { - var descriptor = new PutMappingDescriptor().AutoMap(); - - var expected = new - { - properties = new - { - child = new - { - properties = new { }, - type = "object" - } - } - }; - - Expect(expected).WhenSerializing((IPutMappingRequest)descriptor); - - var withMaxRecursionDescriptor = new PutMappingDescriptor().AutoMap(3); - - var expectedWithMaxRecursion = new - { - properties = new - { - child = new - { - type = "object", - properties = new - { - child = new - { - type = "object", - properties = new - { - child = new - { - type = "object", - properties = new - { - child = new - { - type = "object", - properties = new { } - } - } - } - } - } - } - } - } - }; - - Expect(expectedWithMaxRecursion).WhenSerializing((IPutMappingRequest)withMaxRecursionDescriptor); - } - //endhide - - /** # Applying conventions through the Visitor pattern - * It is also possible to apply a transformation on all or specific properties. - * - * AutoMap internally implements the visitor pattern. The default visitor `NoopPropertyVisitor` does - * nothing, and acts as a blank canvas for you to implement your own visiting methods. - * - * For instance, lets create a custom visitor that disables doc values for numeric and boolean types. - * (Not really a good idea in practice, but let's do it anyway for the sake of a clear example.) + [U] + //hide + public void PutMappingAlsoAdheresToMaxRecursion() + { + var descriptor = new PutMappingDescriptor().AutoMap(); + + var expected = new + { + properties = new + { + child = new + { + properties = new { }, + type = "object" + } + } + }; + + Expect(expected).WhenSerializing((IPutMappingRequest)descriptor); + + var withMaxRecursionDescriptor = new PutMappingDescriptor().AutoMap(3); + + var expectedWithMaxRecursion = new + { + properties = new + { + child = new + { + type = "object", + properties = new + { + child = new + { + type = "object", + properties = new + { + child = new + { + type = "object", + properties = new + { + child = new + { + type = "object", + properties = new { } + } + } + } + } + } + } + } + } + }; + + Expect(expectedWithMaxRecursion).WhenSerializing((IPutMappingRequest)withMaxRecursionDescriptor); + } + //endhide + + /**[float] + * == Applying conventions through the Visitor pattern + * It is also possible to apply a transformation on all or specific properties. + * + * `.AutoMap()` internally implements the https://en.wikipedia.org/wiki/Visitor_pattern[visitor pattern]. The default visitor, `NoopPropertyVisitor`, + * does nothing and acts as a blank canvas for you to implement your own visiting methods. + * + * For instance, lets create a custom visitor that disables doc values for numeric and boolean types + * (Not really a good idea in practice, but let's do it anyway for the sake of a clear example.) */ - public class DisableDocValuesPropertyVisitor : NoopPropertyVisitor - { - /** Override the Visit method on INumberProperty and set DocValues = false */ - public override void Visit(INumberProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) - { - type.DocValues = false; - } - - /** Similarily, override the Visit method on IBooleanProperty and set DocValues = false */ - public override void Visit(IBooleanProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) - { - type.DocValues = false; - } - } - - [U] - public void UsingACustomPropertyVisitor() - { - /** Now we can pass an instance of our custom visitor to AutoMap() */ - var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m.AutoMap(new DisableDocValuesPropertyVisitor())) + public class DisableDocValuesPropertyVisitor : NoopPropertyVisitor + { + public override void Visit( + INumberProperty type, + PropertyInfo propertyInfo, + ElasticsearchPropertyAttributeBase attribute) //<1> Override the `Visit` method on `INumberProperty` and set `DocValues = false` + { + type.DocValues = false; + } + + public override void Visit( + IBooleanProperty type, + PropertyInfo propertyInfo, + ElasticsearchPropertyAttributeBase attribute) //<2> Similarily, override the `Visit` method on `IBooleanProperty` and set `DocValues = false` + { + type.DocValues = false; + } + } + + [U] + public void UsingACustomPropertyVisitor() + { + /** Now we can pass an instance of our custom visitor to `.AutoMap()` */ + var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m.AutoMap(new DisableDocValuesPropertyVisitor())) ); - /** and anytime it maps a property as a number (INumberProperty) or boolean (IBooleanProperty) - * it will apply the transformation defined in each Visit() respectively, which in this example - * disables doc values. + /** and any time the client maps a property of the POCO (Employee in this example) as a number (INumberProperty) or boolean (IBooleanProperty), + * it will apply the transformation defined in each `Visit()` call respectively, which in this example + * disables {ref_current}/doc-values.html[doc_values]. */ - var expected = new - { - mappings = new - { - employee = new - { - properties = new - { - birthday = new - { - type = "date" - }, - employees = new - { - properties = new { }, - type = "object" - }, - firstName = new - { - type = "string" - }, - isManager = new - { - doc_values = false, - type = "boolean" - }, - lastName = new - { - type = "string" - }, - salary = new - { - doc_values = false, - type = "integer" - } - } - } - } - }; - } - - /** You can even take the visitor approach a step further, and instead of visiting on IProperty types, visit - * directly on your POCO properties (PropertyInfo). For example, lets create a visitor that maps all CLR types - * to an Elasticsearch string (IStringProperty). - */ - public class EverythingIsAStringPropertyVisitor : NoopPropertyVisitor - { - public override IProperty Visit(PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute) => new StringProperty(); - } - - [U] - public void UsingACustomPropertyVisitorOnPropertyInfo() - { - var descriptor = new CreateIndexDescriptor("myindex") - .Mappings(ms => ms - .Map(m => m.AutoMap(new EverythingIsAStringPropertyVisitor())) - ); - - var expected = new - { - mappings = new - { - employee = new - { - properties = new - { - birthday = new - { - type = "string" - }, - employees = new - { - type = "string" - }, - firstName = new - { - type = "string" - }, - isManager = new - { - type = "string" - }, - lastName = new - { - type = "string" - }, - salary = new - { - type = "string" - } - } - } - } - }; - } - } -} + var expected = new + { + mappings = new + { + employee = new + { + properties = new + { + birthday = new + { + type = "date" + }, + employees = new + { + properties = new { }, + type = "object" + }, + firstName = new + { + type = "string" + }, + isManager = new + { + doc_values = false, + type = "boolean" + }, + lastName = new + { + type = "string" + }, + salary = new + { + doc_values = false, + type = "integer" + } + } + } + } + }; + } + + /**=== Visiting on PropertyInfo + * You can even take the visitor approach a step further, and instead of visiting on `IProperty` types, visit + * directly on your POCO properties (PropertyInfo). As an example, let's create a visitor that maps all CLR types + * to an Elasticsearch string (IStringProperty). + */ + public class EverythingIsAStringPropertyVisitor : NoopPropertyVisitor + { + public override IProperty Visit( + PropertyInfo propertyInfo, + ElasticsearchPropertyAttributeBase attribute) => new StringProperty(); + } + + [U] + public void UsingACustomPropertyVisitorOnPropertyInfo() + { + var descriptor = new CreateIndexDescriptor("myindex") + .Mappings(ms => ms + .Map(m => m.AutoMap(new EverythingIsAStringPropertyVisitor())) + ); + + var expected = new + { + mappings = new + { + employee = new + { + properties = new + { + birthday = new + { + type = "string" + }, + employees = new + { + type = "string" + }, + firstName = new + { + type = "string" + }, + isManager = new + { + type = "string" + }, + lastName = new + { + type = "string" + }, + salary = new + { + type = "string" + } + } + } + } + }; + } + } +} diff --git a/src/Tests/ClientConcepts/LowLevel/Connecting.doc.cs b/src/Tests/ClientConcepts/LowLevel/Connecting.doc.cs index 1739501e455..8511b909dce 100644 --- a/src/Tests/ClientConcepts/LowLevel/Connecting.doc.cs +++ b/src/Tests/ClientConcepts/LowLevel/Connecting.doc.cs @@ -16,26 +16,24 @@ namespace Tests.ClientConcepts.LowLevel { public class Connecting { - /** # Connecting - * Connecting to *Elasticsearch* with `Elasticsearch.Net` is quite easy but has a few toggles and options worth knowing. + /**== Connecting + * Connecting to Elasticsearch with `Elasticsearch.Net` is quite easy and there a few options to suit a number of different use cases. * - * # Choosing the right connection strategy + * [[connection-strategies]] + * === Choosing the right Connection Strategy * If you simply new an `ElasticLowLevelClient`, it will be a non-failover connection to `http://localhost:9200` */ - public void InstantiateUsingAllDefaults() { var client = new ElasticLowLevelClient(); - var tokenizers = new TokenizersDescriptor(); - } + /** - * If your Elasticsearch node does not live at `http://localhost:9200` but i.e `http://mynode.example.com:8082/apiKey`, then + * If your Elasticsearch node does not live at `http://localhost:9200` but instead lives somewhere else, for example, `http://mynode.example.com:8082/apiKey`, then * you will need to pass in some instance of `IConnectionConfigurationValues`. * * The easiest way to do this is: */ - public void InstantiatingASingleNodeClient() { var node = new Uri("http://mynode.example.com:8082/apiKey"); @@ -44,11 +42,10 @@ public void InstantiatingASingleNodeClient() } /** - * This however is still a non-failover connection. Meaning if that `node` goes down the operation will not be retried on any other nodes in the cluster. + * This will still be a non-failover connection, meaning if that `node` goes down the operation will not be retried on any other nodes in the cluster. * - * To get a failover connection we have to pass an `IConnectionPool` instance instead of a `Uri`. + * To get a failover connection we have to pass an <> instance instead of a `Uri`. */ - public void InstantiatingAConnectionPoolClient() { var node = new Uri("http://mynode.example.com:8082/apiKey"); @@ -58,139 +55,107 @@ public void InstantiatingAConnectionPoolClient() } /** - * Here instead of directly passing `node`, we pass a `SniffingConnectionPool` which will use our `node` to find out the rest of the available cluster nodes. - * Be sure to read more about [Connection Pooling and Cluster Failover here](/elasticsearch-net/cluster-failover.html) + * Here instead of directly passing `node`, we pass a <> + * which will use our `node` to find out the rest of the available cluster nodes. + * Be sure to read more about <>. * - * ## Options + * === Configuration Options * - * Besides either passing a `Uri` or `IConnectionPool` to `ConnectionConfiguration`, you can also fluently control many more options. For instance: + *Besides either passing a `Uri` or `IConnectionPool` to `ConnectionConfiguration`, you can also fluently control many more options. For instance: */ - public void SpecifyingClientOptions() { - //hide var node = new Uri("http://mynode.example.com:8082/apiKey"); var connectionPool = new SniffingConnectionPool(new[] { node }); - //endhide var config = new ConnectionConfiguration(connectionPool) - .DisableDirectStreaming() + .DisableDirectStreaming() //<1> Additional options are fluent method calls on `ConnectionConfiguration` .BasicAuthentication("user", "pass") .RequestTimeout(TimeSpan.FromSeconds(5)); - } /** * The following is a list of available connection configuration options: */ - public void AvailableOptions() { - //hide - var client = new ElasticLowLevelClient(); - //endhide - var config = new ConnectionConfiguration() + .DisableAutomaticProxyDetection() // <1> Disable automatic proxy detection. When called, defaults to `true`. + .EnableHttpCompression() // <2> Enable compressed request and responses from Elasticsearch (Note that nodes need to be configured to allow this. See the {ref_current}/modules-http.html[http module settings] for more info). + .DisableDirectStreaming(); // <3> By default responses are deserialized directly from the response stream to the object you tell it to. For debugging purposes, it can be very useful to keep a copy of the raw response on the result object, which is what calling this method will do. - .DisableAutomaticProxyDetection() - /** Disable automatic proxy detection. Defaults to true. */ - - .EnableHttpCompression() - /** - * Enable compressed request and reesponses from Elasticsearch (Note that nodes need to be configured - * to allow this. See the [http module settings](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-http.html) for more info). - */ - - .DisableDirectStreaming() - /** - * By default responses are deserialized off stream to the object you tell it to. - * For debugging purposes it can be very useful to keep a copy of the raw response on the result object. - */; - + var client = new ElasticLowLevelClient(config); var result = client.Search>(new { size = 12 }); + + /** `.ResponseBodyInBytes` will only have a value if the client configuration has `DisableDirectStreaming` set */ var raw = result.ResponseBodyInBytes; - /** This will only have a value if the client configuration has ExposeRawResponse set */ /** - * Please note that this only make sense if you need a mapped response and the raw response at the same time. - * If you need a `string` or `byte[]` response simply call: + * Please note that using `.DisableDirectStreaming` only makes sense if you need the mapped response **and** the raw response __at the same time__. + * If you need only a `string` response simply call */ var stringResult = client.Search(new { }); + /** + * and similarly, if you need only a `byte[]` + */ + var byteResult = client.Search(new { }); - //hide + /** other configuration options */ config = config - //endhide - .GlobalQueryStringParameters(new NameValueCollection()) - /** - * Allows you to set querystring parameters that have to be added to every request. For instance, if you use a hosted elasticserch provider, and you need need to pass an `apiKey` parameter onto every request. - */ - - .Proxy(new Uri("http://myproxy"), "username", "pass") - /** Sets proxy information on the connection. */ - - .RequestTimeout(TimeSpan.FromSeconds(4)) - /** - * Sets the global maximum time a connection may take. - * Please note that this is the request timeout, the builtin .NET `WebRequest` has no way to set connection timeouts - * (see http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.timeout(v=vs.110).aspx). - */ - - .ThrowExceptions() - /** - * As an alternative to the C/go like error checking on `response.IsValid`, you can instead tell the client to throw - * exceptions. - * - * There are three category of exceptions thay may be thrown: - * - * 1) ElasticsearchClientException: These are known exceptions, either an exception that occurred in the request pipeline - * (such as max retries or timeout reached, bad authentication, etc...) or Elasticsearch itself returned an error (could - * not parse the request, bad query, missing field, etc...). If it is an Elasticsearch error, the `ServerError` property - * on the response will contain the the actual error that was returned. The inner exception will always contain the - * root causing exception. - * - * 2) UnexpectedElasticsearchClientException: These are unknown exceptions, for instance a response from Elasticsearch not - * properly deserialized. These are usually bugs and should be reported. This excpetion also inherits from ElasticsearchClientException - * so an additional catch block isn't necessary, but can be helpful in distinguishing between the two. - * - * 3) Development time exceptions: These are CLR exceptions like ArgumentException, NullArgumentException etc... that are thrown - * when an API in the client is misused. These should not be handled as you want to know about them during development. - * - */ - - .PrettyJson() - /** - * Forces all serialization to be indented and appends `pretty=true` to all the requests so that the responses are indented as well - */ - - .BasicAuthentication("username", "password") - /** Sets the HTTP basic authentication credentials to specify with all requests. */; + .GlobalQueryStringParameters(new NameValueCollection()) // <1> Allows you to set querystring parameters that have to be added to every request. For instance, if you use a hosted elasticserch provider, and you need need to pass an `apiKey` parameter onto every request. + .Proxy(new Uri("http://myproxy"), "username", "pass") // <2> Sets proxy information on the connection. + .RequestTimeout(TimeSpan.FromSeconds(4)) // <3> [[request-timeout]] Sets the global maximum time a connection may take. Please note that this is the request timeout, the builtin .NET `WebRequest` has no way to set connection timeouts (see http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.timeout(v=vs.110).aspx[the MSDN documentation on `HttpWebRequest.Timeout` Property]). + .ThrowExceptions() // <4> As an alternative to the C/go like error checking on `response.IsValid`, you can instead tell the client to <>. + .PrettyJson() // <5> forces all serialization to be indented and appends `pretty=true` to all the requests so that the responses are indented as well + .BasicAuthentication("username", "password"); // <6> sets the HTTP basic authentication credentials to specify with all requests. /** - * **Note:** This can alternatively be specified on the node URI directly: - */ - + * NOTE: Basic authentication credentials can alternatively be specified on the node URI directly: + */ var uri = new Uri("http://username:password@localhost:9200"); var settings = new ConnectionConfiguration(uri); /** - * ...but may become tedious when using connection pooling with multiple nodes. + *...but this may become tedious when using connection pooling with multiple nodes. + * + * [[thrown-exceptions]] + * === Exceptions + * There are three categories of exceptions that may be thrown: + * + * `ElasticsearchClientException`:: These are known exceptions, either an exception that occurred in the request pipeline + * (such as max retries or timeout reached, bad authentication, etc...) or Elasticsearch itself returned an error (could + * not parse the request, bad query, missing field, etc...). If it is an Elasticsearch error, the `ServerError` property + * on the response will contain the the actual error that was returned. The inner exception will always contain the + * root causing exception. + * + * `UnexpectedElasticsearchClientException`:: These are unknown exceptions, for instance a response from Elasticsearch not + * properly deserialized. These are usually bugs and {github}/issues[should be reported]. This exception also inherits from `ElasticsearchClientException` + * so an additional catch block isn't necessary, but can be helpful in distinguishing between the two. + * + * Development time exceptions:: These are CLR exceptions like `ArgumentException`, `ArgumentOutOfRangeException`, etc. + * that are thrown when an API in the client is misused. + * These should not be handled as you want to know about them during development. + * */ } - /** - * You can pass a callback of type `Action<IApiCallDetails>` that can eaves drop every time a response (good or bad) is created. + /** === OnRequestCompleted + * You can pass a callback of type `Action` that can eaves drop every time a response (good or bad) is created. * If you have complex logging needs this is a good place to add that in. */ [U] public void OnRequestCompletedIsCalled() { var counter = 0; - var client = TestClient.GetInMemoryClient(s => s.OnRequestCompleted(r => counter++)); + var client = TestClient.GetInMemoryClient(s => s.OnRequestCompleted(r => counter++)); client.RootNodeInfo(); counter.Should().Be(1); client.RootNodeInfoAsync(); counter.Should().Be(2); } + /** + *`OnRequestCompleted` is called even when an exception is thrown + */ [U] public void OnRequestCompletedIsCalledWhenExceptionIsThrown() { @@ -205,21 +170,22 @@ public void OnRequestCompletedIsCalledWhenExceptionIsThrown() counter.Should().Be(2); } - /** - * An example of using `OnRequestCompleted()` for complex logging. Remember, if you would also like - * to capture the request and/or response bytes, you also need to set `.DisableDirectStreaming()` - * to `true` - */ + /** [[complex-logging]] + * === Complex logging with OnRequestCompleted + * Here's an example of using `OnRequestCompleted()` for complex logging. Remember, if you would also like + * to capture the request and/or response bytes, you also need to set `.DisableDirectStreaming()` to `true` + */ [U]public async Task UsingOnRequestCompletedForLogging() { var list = new List(); var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200")); - var settings = new ConnectionSettings(connectionPool, new InMemoryConnection()) + + var settings = new ConnectionSettings(connectionPool, new InMemoryConnection()) // <1> Here we use `InMemoryConnection`; in reality you would use another type of `IConnection` that actually makes a request. .DefaultIndex("default-index") .DisableDirectStreaming() .OnRequestCompleted(response => { - // log out the request + // log out the request and the request body, if one exists for the type of request if (response.RequestBodyInBytes != null) { list.Add( @@ -231,7 +197,7 @@ [U]public async Task UsingOnRequestCompletedForLogging() list.Add($"{response.HttpMethod} {response.Uri}"); } - // log out the response + // log out the response and the response body, if one exists for the type of response if (response.ResponseBodyInBytes != null) { list.Add($"Status: {response.HttpStatusCode}\n" + @@ -280,10 +246,11 @@ [U]public async Task UsingOnRequestCompletedForLogging() public void ConfiguringSSL() { /** - * ## Configuring SSL + * [[configuring-ssl]] + * === Configuring SSL * SSL must be configured outside of the client using .NET's - * [ServicePointManager](http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager%28v=vs.110%29.aspx) - * class and setting the [ServerCertificateValidationCallback](http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback.aspx) + * http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager%28v=vs.110%29.aspx[ServicePointManager] + * class and setting the http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback.aspx[ServerCertificateValidationCallback] * property. * * The bare minimum to make .NET accept self-signed SSL certs that are not in the Window's CA store would be to have the callback simply return `true`: @@ -293,48 +260,41 @@ public void ConfiguringSSL() ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true; #endif /** - * However, this will accept all requests from the AppDomain to untrusted SSL sites, - * therefore we recommend doing some minimal introspection on the passed in certificate. + * However, this will accept **all** requests from the AppDomain to untrusted SSL sites, + * therefore **we recommend doing some minimal introspection on the passed in certificate.** + * + * IMPORTANT: Using `ServicePointManager` does not work on **Core CLR** as the request does not go through `ServicePointManager`; please file an {github}/issues[issue] if you need support for certificate validation on Core CLR. */ } - /** - * ## Overriding default Json.NET behavior - * - * Please be advised that this is an expert behavior but if you need to get to the nitty gritty this can be really useful + /**=== Overriding default Json.NET behavior * - * Create a subclass of the `JsonNetSerializer` - + * Overriding the default Json.NET behaviour in NEST is an expert behavior but if you need to get to the nitty gritty, this can be really useful. + * First, create a subclass of the `JsonNetSerializer` */ public class MyJsonNetSerializer : JsonNetSerializer { public MyJsonNetSerializer(IConnectionSettingsValues settings) : base(settings) { } - - /** - * Override ModifyJsonSerializerSettings if you need access to `JsonSerializerSettings` - */ public int CallToModify { get; set; } = 0; - protected override void ModifyJsonSerializerSettings(JsonSerializerSettings settings) => ++CallToModify; - /** - * You can inject contract resolved converters by implementing the ContractConverters property - * This can be much faster then registering them on JsonSerializerSettings.Converters - */ + protected override void ModifyJsonSerializerSettings(JsonSerializerSettings settings) => ++CallToModify; //<1> Override ModifyJsonSerializerSettings if you need access to `JsonSerializerSettings` + public int CallToContractConverter { get; set; } = 0; - protected override IList> ContractConverters => new List>() + + protected override IList> ContractConverters => new List> //<2> You can inject contract resolved converters by implementing the ContractConverters property. This can be much faster then registering them on `JsonSerializerSettings.Converters` { - { t => { + t => { CallToContractConverter++; return null; - } } + } }; } /** - * You can then register a factory on ConnectionSettings to create an instance of your subclass instead. - * This is called once per instance of ConnectionSettings. + * You can then register a factory on `ConnectionSettings` to create an instance of your subclass instead. + * This is **_called once per instance_** of ConnectionSettings. */ [U] public void ModifyJsonSerializerSettingsIsCalled() diff --git a/src/Tests/ClientConcepts/LowLevel/Lifetimes.doc.cs b/src/Tests/ClientConcepts/LowLevel/Lifetimes.doc.cs index f6fcadfc92e..58852072c81 100644 --- a/src/Tests/ClientConcepts/LowLevel/Lifetimes.doc.cs +++ b/src/Tests/ClientConcepts/LowLevel/Lifetimes.doc.cs @@ -13,64 +13,43 @@ namespace Tests.ClientConcepts.LowLevel { public class Lifetimes { - /** - * ## Lifetimes + /**== Lifetimes * * If you are using an IOC container its always useful to know the best practices around the lifetime of your objects - - * In general we advise folks to register their ElasticClient instances as singleton. The client is thread safe - * so sharing this instance over threads is ok. - - * Zooming in however the actual moving part that benefits the most of being static for most of the duration of your - * application is ConnectionSettings. Caches are per ConnectionSettings. - - * In some applications it could make perfect sense to have multiple singleton IElasticClient's registered with different - * connectionsettings. e.g if you have 2 functionally isolated Elasticsearch clusters. - - */ - - - [U] public void InitialDisposeState() - { - var connection = new AConnection(); - var connectionPool = new AConnectionPool(new Uri("http://localhost:9200")); - var settings = new AConnectionSettings(connectionPool, connection); - settings.IsDisposed.Should().BeFalse(); - connectionPool.IsDisposed.Should().BeFalse(); - connection.IsDisposed.Should().BeFalse(); - } - - /** - * Disposing the ConnectionSettings will dispose the IConnectionPool and IConnection it has a hold of + * + * In general we advise folks to register their ElasticClient instances as singletons. The client is thread safe + * so sharing an instance between threads is fine. + * + * Zooming in however the actual moving part that benefits the most from being static for most of the duration of your + * application is `ConnectionSettings`; caches are __per__ `ConnectionSettings`. + * + * In some applications it could make perfect sense to have multiple singleton `ElasticClient`'s registered with different + * connection settings. e.g if you have 2 functionally isolated Elasticsearch clusters. + * + * NOTE: Due to the semantic versioning of Elasticsearch.Net and NEST and their alignment to versions of Elasticsearch, all instances of `ElasticClient` and + * Elasticsearch clusters that are connected to must be on the **same major version** i.e. it is not possible to have both an `ElasticClient` to connect to + * Elasticsearch 1.x _and_ 2.x in the same application as the former would require NEST 1.x and the latter, NEST 2.x. + * + * Let's demonstrate which components are disposed by creating our own derived `ConnectionSettings`, `IConnectionPool` and `IConnection` types */ - [U] public void DisposingSettingsDisposesMovingParts() - { - var connection = new AConnection(); - var connectionPool = new AConnectionPool(new Uri("http://localhost:9200")); - var settings = new AConnectionSettings(connectionPool, connection); - using (settings) { } - settings.IsDisposed.Should().BeTrue(); - connectionPool.IsDisposed.Should().BeTrue(); - connection.IsDisposed.Should().BeTrue(); - } - - class AConnectionPool : SingleNodeConnectionPool + class AConnectionSettings : ConnectionSettings { - public AConnectionPool(Uri uri, IDateTimeProvider dateTimeProvider = null) : base(uri, dateTimeProvider) { } - + public AConnectionSettings(IConnectionPool pool, IConnection connection) + : base(pool, connection) + { } public bool IsDisposed { get; private set; } - protected override void DisposeManagedResources() + protected override void DisposeManagedResources() { this.IsDisposed = true; base.DisposeManagedResources(); } } - class AConnectionSettings : ConnectionSettings + class AConnectionPool : SingleNodeConnectionPool { - public AConnectionSettings(IConnectionPool pool, IConnection connection) - : base(pool, connection) { } + public AConnectionPool(Uri uri, IDateTimeProvider dateTimeProvider = null) : base(uri, dateTimeProvider) { } + public bool IsDisposed { get; private set; } protected override void DisposeManagedResources() { @@ -89,5 +68,31 @@ protected override void DisposeManagedResources() } } + /** + * `ConnectionSettings`, `IConnectionPool` and `IConnection` all explictily implement `IDisposable` + */ + [U] public void InitialDisposeState() + { + var connection = new AConnection(); + var connectionPool = new AConnectionPool(new Uri("http://localhost:9200")); + var settings = new AConnectionSettings(connectionPool, connection); + settings.IsDisposed.Should().BeFalse(); + connectionPool.IsDisposed.Should().BeFalse(); + connection.IsDisposed.Should().BeFalse(); + } + + /** + * Disposing `ConnectionSettings` will dispose the `IConnectionPool` and `IConnection` it has a hold of + */ + [U] public void DisposingSettingsDisposesMovingParts() + { + var connection = new AConnection(); + var connectionPool = new AConnectionPool(new Uri("http://localhost:9200")); + var settings = new AConnectionSettings(connectionPool, connection); + using (settings) { } + settings.IsDisposed.Should().BeTrue(); + connectionPool.IsDisposed.Should().BeTrue(); + connection.IsDisposed.Should().BeTrue(); + } } } diff --git a/src/Tests/ClientConcepts/LowLevel/PostData.doc.cs b/src/Tests/ClientConcepts/LowLevel/PostData.doc.cs index 92fd97d6eff..4009a289316 100644 --- a/src/Tests/ClientConcepts/LowLevel/PostData.doc.cs +++ b/src/Tests/ClientConcepts/LowLevel/PostData.doc.cs @@ -13,9 +13,10 @@ namespace Tests.ClientConcepts.LowLevel { public class PostingData { - /** ## Post data - * The low level allows you to post a string, byte[] array directly. On top of this if you pass a list of strings or objects - * they will be serialized in Elasticsearch's special bulk/multi format. + /**== Post data + * The low level client allows you to post a `string` or `byte[]` array directly. On top of this, + * if you pass a collection of `string` or `object` they will be serialized + * using Elasticsearch's special bulk/multi format. */ private readonly string @string = "fromString"; private readonly byte[] bytes = Utf8Bytes("fromByteArray"); @@ -39,9 +40,16 @@ public PostingData() [U] public void ImplicitConversions() { - /** Even though the argument for postData on the low level client takes a PostData - * You can rely on C# implicit conversion to abstract the notion of PostData completely. - *You can implicitly convert from the following types.*/ + /**=== Implicit Conversion + * Even though the argument for PostData on the low level client takes a `PostData`, + * You can rely on implicit conversion to abstract the notion of PostData completely. + * You can implicitly convert from the following types + * - `string` + * - `byte[]` + * - collection of `string` + * - collection of `object` + * - `object` + */ var fromString = ImplicitlyConvertsFrom(@string); var fromByteArray = ImplicitlyConvertsFrom(bytes); @@ -49,7 +57,7 @@ [U] public void ImplicitConversions() var fromListOfObject = ImplicitlyConvertsFrom(listOfObjects); var fromObject = ImplicitlyConvertsFrom(@object); - /** postData Bytes will always be set if it originated from a byte*/ + /** PostData bytes will always be set if it originated from `byte[]` */ fromByteArray.WrittenBytes.Should().BeSameAs(bytes); fromString.Type.Should().Be(PostType.LiteralString); @@ -57,9 +65,8 @@ [U] public void ImplicitConversions() fromListOfString.Type.Should().Be(PostType.EnumerableOfString); fromListOfObject.Type.Should().Be(PostType.EnumerableOfObject); fromObject.Type.Should().Be(PostType.Serializable); - - //passing a PostData object to a method taking PostData should not wrap + /** and passing a `PostData` object to a method taking `PostData` should not wrap */ fromString = ImplicitlyConvertsFrom(fromString); fromByteArray = ImplicitlyConvertsFrom(fromByteArray); fromListOfString = ImplicitlyConvertsFrom(fromListOfString); @@ -71,8 +78,6 @@ [U] public void ImplicitConversions() fromListOfString.Type.Should().Be(PostType.EnumerableOfString); fromListOfObject.Type.Should().Be(PostType.EnumerableOfObject); fromObject.Type.Should().Be(PostType.Serializable); - - } [U] public async Task WritesCorrectlyUsingBothLowAndHighLevelSettings() @@ -82,35 +87,34 @@ [U] public async Task WritesCorrectlyUsingBothLowAndHighLevelSettings() } private async Task AssertOn(IConnectionConfigurationValues settings) - { - + { /** Although each implicitly types behaves slightly differently */ - await Post(()=>@string, writes: Utf8Bytes(@string), storesBytes: true, settings: settings); + await Post(() => @string, writes: Utf8Bytes(@string), storesBytes: true, settings: settings); + + await Post(() => bytes, writes: bytes, storesBytes: true, settings: settings); - await Post(()=>bytes, writes: bytes, storesBytes: true, settings: settings); - - /** When passing a list of strings we assume its a list of valid serialized json that we - * join with newlinefeeds making sure there is a trailing linefeed */ - await Post(()=>listOfStrings, writes: multiStringJson, storesBytes: true, settings: settings); + /** When passing a list of strings we assume its a list of valid serialized json that we + * join with newline feeds making sure there is a trailing linefeed */ + await Post(() => listOfStrings, writes: multiStringJson, storesBytes: true, settings: settings); /** * When passing a list of object we assume its a list of objects we need to serialize - * individually to json and join with newlinefeeds aking sure there is a trailing linefeed + * individually to json and join with newline feeds making sure there is a trailing linefeed */ - await Post(()=>listOfObjects, writes: multiObjectJson, storesBytes: false, settings: settings); - + await Post(() => listOfObjects, writes: multiObjectJson, storesBytes: false, settings: settings); + /** In all other cases postdata is serialized as is. */ - await Post(()=>@object, writes: objectJson, storesBytes: false, settings: settings); + await Post(() => @object, writes: objectJson, storesBytes: false, settings: settings); - /** If you want to maintain a copy of the request that went out use the following settings */ + /** If you want to maintain a copy of the request that went out, use `DisableDirectStreaming` */ settings = new ConnectionSettings().DisableDirectStreaming(); - /** by forcing `DisableDirectStreaming` serializing happens first in a private MemoryStream - * so we can get a hold of the serialized bytes */ - await Post(()=>listOfObjects, writes: multiObjectJson, storesBytes: true, settings: settings); - + /** by forcing `DisableDirectStreaming` on connection settings, serialization happens first in a private `MemoryStream` + * so we can get hold of the serialized bytes */ + await Post(() => listOfObjects, writes: multiObjectJson, storesBytes: true, settings: settings); + /** this behavior can also be observed when serializing a simple object using `DisableDirectStreaming` */ - await Post(()=>@object, writes: objectJson, storesBytes: true, settings: settings); + await Post(() => @object, writes: objectJson, storesBytes: true, settings: settings); } private static async Task Post(Func> postData, byte[] writes, bool storesBytes, IConnectionConfigurationValues settings) @@ -128,7 +132,7 @@ private static void PostAssert(PostData postData, byte[] writes, bool st sentBytes.Should().Equal(writes); if (storesBytes) postData.WrittenBytes.Should().NotBeNull(); - else + else postData.WrittenBytes.Should().BeNull(); } } @@ -142,7 +146,7 @@ private static async Task PostAssertAsync(PostData postData, byte[] writ sentBytes.Should().Equal(writes); if (storesBytes) postData.WrittenBytes.Should().NotBeNull(); - else + else postData.WrittenBytes.Should().BeNull(); } } diff --git a/src/Tests/CodeStandards/NamingConventions.doc.cs b/src/Tests/CodeStandards/NamingConventions.doc.cs index 88df362d339..e9f06626bde 100644 --- a/src/Tests/CodeStandards/NamingConventions.doc.cs +++ b/src/Tests/CodeStandards/NamingConventions.doc.cs @@ -7,13 +7,13 @@ namespace Tests.CodeStandards { - /** # Naming Conventions - * + /** == Naming Conventions + * * NEST uses the following naming conventions (with _some_ exceptions). */ public class NamingConventions { - /** ## Class Names + /** === Class Names * * Abstract class names should end with a `Base` suffix */ @@ -50,7 +50,7 @@ [U] public void ClassNameContainsBaseShouldBeAbstract() baseClassesNotAbstract.Should().BeEmpty(); } - /** ## Requests and Responses + /** === Requests and Responses * * Request class names should end with `Request` */ @@ -88,13 +88,12 @@ public void ResponseClassNamesEndWithResponse() * Request and Response class names should be one to one in *most* cases. * e.g. `ValidateRequest` => `ValidateResponse`, and not `ValidateQueryRequest` => `ValidateResponse` * There are a few exceptions to this rule, most notably the `Cat` prefixed requests and - * `Exists` requests. + * the `Exists` requests. */ [U] public void ParityBetweenRequestsAndResponses() { - // Add any exceptions to the rule here. - var exceptions = new[] + var exceptions = new[] // <1> _Exceptions to the rule_ { typeof(DocumentExistsRequest), typeof(DocumentExistsRequest<>), diff --git a/src/Tests/CodeStandards/Serialization/Properties.doc.cs b/src/Tests/CodeStandards/Serialization/Properties.doc.cs index 5e27213f989..23049da6abd 100644 --- a/src/Tests/CodeStandards/Serialization/Properties.doc.cs +++ b/src/Tests/CodeStandards/Serialization/Properties.doc.cs @@ -8,7 +8,6 @@ namespace Tests.CodeStandards.Serialization { public class JsonProperties { - /** * Our Json.NET contract resolver picks up attributes set on the interface */ @@ -27,8 +26,6 @@ public void SeesInterfaceProperties() serialized = c.Serializer.SerializeToString(new AnalysisDescriptor().CharFilters(cf=>cf)); serialized.Should().NotContain("char_filters").And.NotContain("charFilters"); serialized.Should().Contain("char_filter"); - - } } } diff --git a/src/Tests/CommonOptions/DateMath/DateMathExpressions.doc.cs b/src/Tests/CommonOptions/DateMath/DateMathExpressions.doc.cs index de3be865d88..9a030678c2a 100644 --- a/src/Tests/CommonOptions/DateMath/DateMathExpressions.doc.cs +++ b/src/Tests/CommonOptions/DateMath/DateMathExpressions.doc.cs @@ -8,30 +8,40 @@ namespace Tests.CommonOptions.DateMath { public class DateMathEpressions { - /** # Date Expressions - * The date type supports using date math expression when using it in a query/filter - *Whenever durations need to be specified, eg for a timeout parameter, the duration can be specified + /** == Date Math Expressions + * The date type supports using date math expression when using it in a query/filter + * Whenever durations need to be specified, eg for a timeout parameter, the duration can be specified * - * The expression starts with an "anchor" date, which can be either now or a date string (in the applicable format) ending with ||. - * It can then follow by a math expression, supporting +, - and / (rounding). - * The units supported are y (year), M (month), w (week), d (day), h (hour), m (minute), and s (second). - * as a whole number representing time in milliseconds, or as a time value like `2d` for 2 days. + * The expression starts with an "anchor" date, which can be either now or a date string (in the applicable format) ending with `||`. + * It can then follow by a math expression, supporting `+`, `-` and `/` (rounding). + * The units supported are + * + * - `y` (year) + * - `M` (month) + * - `w` (week) + * - `d` (day) + * - `h` (hour) + * - `m` (minute) + * - `s` (second) * - * Be sure to read the elasticsearch documentation {ref}/mapping-date-format.html#date-math[on this subject here] - + * as a whole number representing time in milliseconds, or as a time value like `2d` for 2 days. + * :datemath: {ref_current}/common-options.html#date-math + * Be sure to read the Elasticsearch documentation on {datemath}[Date Math]. */ - [U] public void SimpleExpressions() { - /** You can create simple expressions using any of the static methods on `DateMath` */ + /** === Simple Expressions + * You can create simple expressions using any of the static methods on `DateMath` + */ Expect("now").WhenSerializing(Nest.DateMath.Now); Expect("2015-05-05T00:00:00").WhenSerializing(Nest.DateMath.Anchored(new DateTime(2015,05, 05))); - /** strings implicitly convert to date maths */ + /** strings implicitly convert to `DateMath` */ Expect("now").WhenSerializing("now"); /** but are lenient to bad math expressions */ var nonsense = "now||*asdaqwe"; + Expect(nonsense).WhenSerializing(nonsense) /** the resulting date math will assume the whole string is the anchor */ .Result(dateMath => ((IDateMath)dateMath) @@ -41,10 +51,10 @@ [U] public void SimpleExpressions() ) ); - /** date's also implicitly convert to simple date math expressions */ + /** `DateTime` also implicitly convert to simple date math expressions */ var date = new DateTime(2015, 05, 05); Expect("2015-05-05T00:00:00").WhenSerializing(date) - /** the anchor will be an actual DateTime, even after a serialization - deserialization round trip */ + /** the anchor will be an actual `DateTime`, even after a serialization/deserialization round trip */ .Result(dateMath => ((IDateMath)dateMath) . Anchor.Match( d => d.Should().Be(date), @@ -55,19 +65,29 @@ . Anchor.Match( [U] public void ComplexExpressions() { - /** Ranges can be chained on to simple expressions */ - Expect("now+1d").WhenSerializing(Nest.DateMath.Now.Add("1d")); + /** === Complex Expressions + * Ranges can be chained on to simple expressions + */ + Expect("now+1d").WhenSerializing( + Nest.DateMath.Now.Add("1d")); - /** plural means that you can chain multiple */ - Expect("now+1d-1m").WhenSerializing(Nest.DateMath.Now.Add("1d").Subtract(TimeSpan.FromMinutes(1))); + /** Including multiple operations */ + Expect("now+1d-1m").WhenSerializing( + Nest.DateMath.Now.Add("1d").Subtract(TimeSpan.FromMinutes(1))); - /** a rounding value can also be chained at the end afterwhich no more ranges can be appended */ - Expect("now+1d-1m/d").WhenSerializing(Nest.DateMath.Now.Add("1d").Subtract(TimeSpan.FromMinutes(1)).RoundTo(Nest.TimeUnit.Day)); + /** A rounding value can be chained to the end of the expression, after which no more ranges can be appended */ + Expect("now+1d-1m/d").WhenSerializing( + Nest.DateMath.Now.Add("1d") + .Subtract(TimeSpan.FromMinutes(1)) + .RoundTo(Nest.TimeUnit.Day)); - /** When anchoring date's we need to append `||` as clear separator between anchor and ranges */ - /** plural means that you can chain multiple */ - Expect("2015-05-05T00:00:00||+1d-1m").WhenSerializing(Nest.DateMath.Anchored(new DateTime(2015,05,05)).Add("1d").Subtract(TimeSpan.FromMinutes(1))); - + /** When anchoring dates, a `||` needs to be appended as clear separator between the anchor and ranges. + * Again, multiple ranges can be chained + */ + Expect("2015-05-05T00:00:00||+1d-1m").WhenSerializing( + Nest.DateMath.Anchored(new DateTime(2015,05,05)) + .Add("1d") + .Subtract(TimeSpan.FromMinutes(1))); } } diff --git a/src/Tests/CommonOptions/DistanceUnit/DistanceUnits.doc.cs b/src/Tests/CommonOptions/DistanceUnit/DistanceUnits.doc.cs new file mode 100644 index 00000000000..1e021fc6a4c --- /dev/null +++ b/src/Tests/CommonOptions/DistanceUnit/DistanceUnits.doc.cs @@ -0,0 +1,104 @@ +using Nest; +using Tests.Framework; +using static Tests.Framework.RoundTripper; + +namespace Tests.CommonOptions.DistanceUnit +{ + public class DistanceUnits + { + /**== Distance Units + * Whenever distances need to be specified, e.g. for a {ref_current}/query-dsl-geo-distance-query.html[geo distance query], + * the distance unit can be specified as a double number representing distance in meters, as a new instance of + * a `Distance`, or as a string of the form number and distance unit e.g. "`2.72km`" + * + * === Using Distance units in NEST + * NEST uses `Distance` to strongly type distance units and there are several ways to construct one. + * + * ==== Constructor + * The most straight forward way to construct a `Distance` is through its constructor + */ + [U] + public void Constructor() + { + var unitComposed = new Distance(25); + var unitComposedWithUnits = new Distance(25, Nest.DistanceUnit.Meters); + + /** + * `Distance` serializes to a string composed of a factor and distance unit. + * The factor is a double so always has at least one decimal place when serialized + */ + Expect("25.0m") + .WhenSerializing(unitComposed) + .WhenSerializing(unitComposedWithUnits); + } + + /** + * ==== Implicit conversion + * Alternatively a distance unit `string` can be assigned to a `Distance`, resulting in an implicit conversion to a new `Distance` instance. + * If no `DistanceUnit` is specified, the default distance unit is meters + */ + [U] + public void ImplicitConversion() + { + Distance distanceString = "25"; + Distance distanceStringWithUnits = "25m"; + + Expect(new Distance(25)) + .WhenSerializing(distanceString) + .WhenSerializing(distanceStringWithUnits); + } + + /** + * ==== Supported units + * A number of distance units are supported, from millimeters to nautical miles + */ + [U] + public void UsingDifferentUnits() + { + /** ===== Metric + *`mm` (Millimeters) + */ + Expect("2.0mm").WhenSerializing(new Distance(2, Nest.DistanceUnit.Millimeters)); + + /** + *`cm` (Centimeters) + */ + Expect("123.456cm").WhenSerializing(new Distance(123.456, Nest.DistanceUnit.Centimeters)); + + /** + *`m` (Meters) + */ + Expect("400.0m").WhenSerializing(new Distance(400, Nest.DistanceUnit.Meters)); + + /** + *`km` (Kilometers) + */ + Expect("0.1km").WhenSerializing(new Distance(0.1, Nest.DistanceUnit.Kilometers)); + + /** ===== Imperial + *`in` (Inches) + */ + Expect("43.23in").WhenSerializing(new Distance(43.23, Nest.DistanceUnit.Inch)); + + /** + *`ft` (Feet) + */ + Expect("3.33ft").WhenSerializing(new Distance(3.33, Nest.DistanceUnit.Feet)); + + /** + *`yd` (Yards) + */ + Expect("9.0yd").WhenSerializing(new Distance(9, Nest.DistanceUnit.Yards)); + + /** + *`mi` (Miles) + */ + Expect("0.62mi").WhenSerializing(new Distance(0.62, Nest.DistanceUnit.Miles)); + + /** + *`nmi` or `NM` (Nautical Miles) + */ + Expect("45.5nmi").WhenSerializing(new Distance(45.5, Nest.DistanceUnit.NauticalMiles)); + } + } +} diff --git a/src/Tests/CommonOptions/TimeUnit/TimeUnits.doc.cs b/src/Tests/CommonOptions/TimeUnit/TimeUnits.doc.cs index d4c8de75b03..a550ba9e6ed 100644 --- a/src/Tests/CommonOptions/TimeUnit/TimeUnits.doc.cs +++ b/src/Tests/CommonOptions/TimeUnit/TimeUnits.doc.cs @@ -7,19 +7,18 @@ namespace Tests.CommonOptions.TimeUnit { - public class TimeUnits + public class TimeUnits { - /** # Time units - * Whenever durations need to be specified, eg for a timeout parameter, the duration can be specified - * as a whole number representing time in milliseconds, or as a time value like `2d` for 2 days. - * - * ## Using Time units in NEST + /** == Time units + * Whenever durations need to be specified, eg for a timeout parameter, the duration can be specified + * as a whole number representing time in milliseconds, or as a time value like `2d` for 2 days. + * + * === Using Time units in NEST * NEST uses `Time` to strongly type this and there are several ways to construct one. * - * ### Constructor + * ==== Constructor * The most straight forward way to construct a `Time` is through its constructor */ - [U] public void Constructor() { var unitString = new Time("2d"); @@ -28,8 +27,13 @@ [U] public void Constructor() var unitMilliseconds = new Time(1000 * 60 * 60 * 24 * 2); /** - * When serializing Time constructed from a string, milliseconds, composition of factor and - * interval, or a `TimeSpan` the expression will be serialized as time unit string + * When serializing Time constructed from + * - a string + * - milliseconds (as a double) + * - composition of factor and interval + * - a `TimeSpan` + * + * the expression will be serialized to a time unit string composed of the factor and interval e.g. `2d` */ Expect("2d") .WhenSerializing(unitString) @@ -38,7 +42,7 @@ [U] public void Constructor() .WhenSerializing(unitMilliseconds); /** - * Milliseconds are always calculated even when not using the constructor that takes a long + * The `Milliseconds` property on `Time` is calculated even when not using the constructor that takes a double */ unitMilliseconds.Milliseconds.Should().Be(1000*60*60*24*2); unitComposed.Milliseconds.Should().Be(1000*60*60*24*2); @@ -46,10 +50,9 @@ [U] public void Constructor() unitString.Milliseconds.Should().Be(1000*60*60*24*2); } /** - * ### Implicit conversion - * Alternatively `string`, `TimeSpan` and `double` can be implicitly assigned to `Time` properties and variables + * ==== Implicit conversion + * Alternatively to using the constructor, `string`, `TimeSpan` and `double` can be implicitly converted to `Time` */ - [U] [SuppressMessage("ReSharper", "SuggestVarOrType_SimpleTypes")] public void ImplicitConversion() { @@ -62,7 +65,6 @@ public void ImplicitConversion() Expect("2d").WhenSerializing(twoDays); } - [U] [SuppressMessage("ReSharper", "SuggestVarOrType_SimpleTypes")] public void EqualityAndComparable() { @@ -71,12 +73,12 @@ public void EqualityAndComparable() Time twoDays = 1000*60*60*24*2; /** - * Milliseconds are calculated even when values are not passed as long + * Milliseconds are calculated even when values are not passed as long... */ twoWeeks.Milliseconds.Should().BeGreaterThan(1); /** - * Except when dealing with years or months, whose millsecond value cannot + * ...**except** when dealing with years or months, whose millsecond value cannot * be calculated *accurately*, since they are not fixed durations. For instance, * 30 vs 31 vs 28 days in a month, or 366 vs 365 days in a year. * In this instance, Milliseconds will be -1. @@ -90,12 +92,12 @@ public void EqualityAndComparable() (oneAndHalfYear > twoWeeks).Should().BeTrue(); (oneAndHalfYear >= twoWeeks).Should().BeTrue(); (twoDays >= new Time("2d")).Should().BeTrue(); - + twoDays.Should().BeLessThan(twoWeeks); (twoDays < twoWeeks).Should().BeTrue(); (twoDays <= twoWeeks).Should().BeTrue(); (twoDays <= new Time("2d")).Should().BeTrue(); - + /** * And assert equality */ @@ -110,8 +112,8 @@ public void EqualityAndComparable() [U] public void UsingInterval() { - /** - * Time units are specified as a union of either a `DateInterval` or `Time` + /** === Units of Time + * Units of `Time` are specified as a union of either a `DateInterval` or `Time`, * both of which implicitly convert to the `Union` of these two. */ Expect("month").WhenSerializing>(DateInterval.Month); diff --git a/src/Tests/Document/Multiple/Bulk/BulkApiTests.cs b/src/Tests/Document/Multiple/Bulk/BulkApiTests.cs index fba1b200e34..8c3ec8e7ecb 100644 --- a/src/Tests/Document/Multiple/Bulk/BulkApiTests.cs +++ b/src/Tests/Document/Multiple/Bulk/BulkApiTests.cs @@ -28,7 +28,7 @@ protected override LazyResponses ClientUsage() => Calls( protected override bool SupportsDeserialization => false; - protected override object ExpectJson { get; } = new object[] + protected override object ExpectJson => new object[] { new Dictionary{ { "index", new { _type = "project", _id = Project.Instance.Name } } }, Project.InstanceAnonymous, @@ -39,6 +39,32 @@ protected override LazyResponses ClientUsage() => Calls( new Dictionary{ { "delete", new { _type="project", _id = Project.Instance.Name + "1" } } }, }; + protected override Func Fluent => d => d + .Index(CallIsolatedValue) + .Index(b => b.Document(Project.Instance)) + .Update(b => b.Doc(new { leadDeveloper = new { firstName = "martijn" } }).Id(Project.Instance.Name)) + .Create(b => b.Document(Project.Instance).Id(Project.Instance.Name + "1")) + .Delete(b=>b.Id(Project.Instance.Name + "1")); + + + protected override BulkRequest Initializer => + new BulkRequest(CallIsolatedValue) + { + Operations = new List + { + new BulkIndexOperation(Project.Instance), + new BulkUpdateOperation(Project.Instance) + { + Doc = new { leadDeveloper = new { firstName = "martijn" } } + }, + new BulkCreateOperation(Project.Instance) + { + Id = Project.Instance.Name + "1" + }, + new BulkDeleteOperation(Project.Instance.Name + "1"), + } + }; + protected override void ExpectResponse(IBulkResponse response) { response.Took.Should().BeGreaterThan(0); @@ -58,33 +84,9 @@ protected override void ExpectResponse(IBulkResponse response) item.Shards.Successful.Should().BeGreaterThan(0); } - var p1 = this.Client.Source(Project.Instance.Name, p=>p.Index(CallIsolatedValue)); + var p1 = this.Client.Source(Project.Instance.Name, p => p.Index(CallIsolatedValue)); p1.LeadDeveloper.FirstName.Should().Be("martijn"); } - protected override Func Fluent => d => d - .Index(CallIsolatedValue) - .Index(b => b.Document(Project.Instance)) - .Update(b => b.Doc(new { leadDeveloper = new { firstName = "martijn" } }).Id(Project.Instance.Name)) - .Create(b => b.Document(Project.Instance).Id(Project.Instance.Name + "1")) - .Delete(b=>b.Id(Project.Instance.Name + "1")); - - - protected override BulkRequest Initializer => new BulkRequest(CallIsolatedValue) - { - Operations = new List - { - new BulkIndexOperation(Project.Instance), - new BulkUpdateOperation(Project.Instance) - { - Doc = new { leadDeveloper = new { firstName = "martijn" } } - }, - new BulkCreateOperation(Project.Instance) - { - Id = Project.Instance.Name + "1" - }, - new BulkDeleteOperation(Project.Instance.Name + "1"), - } - }; } } diff --git a/src/Tests/Document/Multiple/Bulk/BulkInvalidApiTests.cs b/src/Tests/Document/Multiple/Bulk/BulkInvalidApiTests.cs index 3db7a6fc09a..2f811f31370 100644 --- a/src/Tests/Document/Multiple/Bulk/BulkInvalidApiTests.cs +++ b/src/Tests/Document/Multiple/Bulk/BulkInvalidApiTests.cs @@ -41,7 +41,7 @@ protected override void ExpectResponse(IBulkResponse response) response.Took.Should().BeGreaterThan(0); response.Errors.Should().BeTrue(); - //a delete not found is not an error (also in elasticsearch) + //a delete not found is not an error (also in Elasticsearch) //if you do a single bulk delete on an unknown id .Errors will be false response.ItemsWithErrors.Should().NotBeNull().And.HaveCount(1); response.Items.Should().NotBeEmpty(); @@ -63,7 +63,7 @@ protected override void ExpectResponse(IBulkResponse response) .Index(CallIsolatedValue) .Update(b => b.Doc(new { leadDeveloper = new { firstName = "martijn" } }).Id(Project.Instance.Name)) .Delete(b=>b.Id(Project.Instance.Name + "1")); - + protected override BulkRequest Initializer => new BulkRequest(CallIsolatedValue) { diff --git a/src/Tests/Document/Multiple/DeleteByQuery/DeleteByQueryApiTests.cs b/src/Tests/Document/Multiple/DeleteByQuery/DeleteByQueryApiTests.cs index 441c7a60872..3e3785561ae 100644 --- a/src/Tests/Document/Multiple/DeleteByQuery/DeleteByQueryApiTests.cs +++ b/src/Tests/Document/Multiple/DeleteByQuery/DeleteByQueryApiTests.cs @@ -39,6 +39,7 @@ protected override LazyResponses ClientUsage() => Calls( protected override bool ExpectIsValid => true; protected override int ExpectStatusCode => 200; protected override HttpMethod HttpMethod => HttpMethod.DELETE; + protected override string UrlPath => $"/{CallIsolatedValue}%2C{SecondIndex}/_query?ignore_unavailable=true"; protected override bool SupportsDeserialization => false; @@ -55,13 +56,6 @@ protected override LazyResponses ClientUsage() => Calls( } }; - protected override void ExpectResponse(IDeleteByQueryResponse response) - { - response.Indices.Should().NotBeEmpty().And.HaveCount(2).And.ContainKey(CallIsolatedValue); - response.Indices[CallIsolatedValue].Deleted.Should().Be(1); - response.Indices[CallIsolatedValue].Found.Should().Be(1); - } - protected override DeleteByQueryDescriptor NewDescriptor() => new DeleteByQueryDescriptor(this.Indices); protected override Func, IDeleteByQueryRequest> Fluent => d => d @@ -82,5 +76,12 @@ protected override void ExpectResponse(IDeleteByQueryResponse response) Values = new Id[] { Project.Projects.First().Name, "x" } } }; + + protected override void ExpectResponse(IDeleteByQueryResponse response) + { + response.Indices.Should().NotBeEmpty().And.HaveCount(2).And.ContainKey(CallIsolatedValue); + response.Indices[CallIsolatedValue].Deleted.Should().Be(1); + response.Indices[CallIsolatedValue].Found.Should().Be(1); + } } } diff --git a/src/Tests/Document/Single/Update/UpdateApiTests.cs b/src/Tests/Document/Single/Update/UpdateApiTests.cs index cbcf8909490..fc71b21b602 100644 --- a/src/Tests/Document/Single/Update/UpdateApiTests.cs +++ b/src/Tests/Document/Single/Update/UpdateApiTests.cs @@ -43,7 +43,7 @@ protected override LazyResponses ClientUsage() => Calls( protected override UpdateDescriptor NewDescriptor() => new UpdateDescriptor(DocumentPath.Id(CallIsolatedValue)); - protected override Func, IUpdateRequest> Fluent => d => d + protected override Func, IUpdateRequest> Fluent => u => u .Doc(Project.Instance) .DocAsUpsert() .DetectNoop(); diff --git a/src/Tests/QueryDsl/BoolDsl/BoolDsl.doc.cs b/src/Tests/QueryDsl/BoolDsl/BoolDsl.doc.cs index 7082f9fe20c..76c2919efd9 100644 --- a/src/Tests/QueryDsl/BoolDsl/BoolDsl.doc.cs +++ b/src/Tests/QueryDsl/BoolDsl/BoolDsl.doc.cs @@ -9,11 +9,15 @@ namespace Tests.QueryDsl.BoolDsl { + /**== Bool Queries + */ public class BoolDslTests : OperatorUsageBase { protected readonly IElasticClient Client = TestClient.GetFixedReturnClient(new { }); - - /** Writing boolean queries can grow rather verbose rather quickly using the query DSL e.g */ + + /** Writing boolean queries can grow verbose rather quickly when using the query DSL. For example, + * take a single {ref_current}/query-dsl-bool-query.html[bool query] with only two clauses + */ public void VerboseWay() { var searchResults = this.Client.Search(s => s @@ -27,22 +31,23 @@ public void VerboseWay() ) ); } - /** now this is just a single bool with only two clauses, imagine multiple nested bools this quickly becomes an exercise in - hadouken indenting + /**Now, imagine multiple nested bools; you'll realise that this quickly becomes an exercise in _hadouken indenting_ * *[[indent]] - *.hadouken indenting example - *image::http://i.imgur.com/BtjZedW.jpg[dead indent] + *.hadouken indenting + *image::hadouken-indentation.jpg[hadouken indenting] * - - * For this reason, NEST introduces operator overloading so complex bool queries become easier to write, the previous example will become. */ - + *=== Operator Overloading + * + *For this reason, NEST introduces **operator overloading** so complex bool queries become easier to write. + *The previous example now becomes the following with the fluent API + */ public void UsingOperator() { var searchResults = this.Client.Search(s => s .Query(q => q.Term(p => p.Name, "x") || q.Term(p => p.Name, "y")) ); - /** Or using the object initializer syntax */ + /** or, using the object initializer syntax */ searchResults = this.Client.Search(new SearchRequest { Query = new TermQuery { Field = "name", Value= "x" } @@ -51,58 +56,78 @@ public void UsingOperator() } /** A naive implementation of operator overloading would rewrite - + * * `term && term && term` to - - *> bool - *> |___must - *> |___term - *> |___bool - *> |___must - *> |___term - *> |___term - + * + *.... + *bool + *|___must + * |___term + * |___bool + * |___must + * |___term + * |___term + *.... + * * As you can image this becomes unwieldy quite fast the more complex a query becomes NEST can spot these and * join them together to become a single bool query - - *> bool - *> |___must - *> |___term - *> |___term - *> |___term - + * + *.... + *bool + *|___must + * |___term + * |___term + * |___term + *.... */ - [U] public void JoinsMustQueries() => + [U] public void JoinsMustQueries() + { Assert( q => q.Query() && q.Query() && q.Query(), Query && Query && Query, c => c.Bool.Must.Should().HaveCount(3) - ); - - /** The bool DSL offers also a short hand notation to mark a query as a must_not using ! */ - - [U] public void MustNotOperator() => Assert(q => !q.Query(), !Query, c => c.Bool.MustNot.Should().HaveCount(1)); + ); + } - /** And to mark a query as a filter using + */ + /** The bool DSL offers also a short hand notation to mark a query as a `must_not` using the `!` operator */ + [U] public void MustNotOperator() + { + Assert(q => !q.Query(), !Query, c => c.Bool.MustNot.Should().HaveCount(1)); + } - [U] public void UnaryAddOperator() => Assert(q => +q.Query(), +Query, c => c.Bool.Filter.Should().HaveCount(1)); + /** And to mark a query as a `filter` using the `+` operator*/ + [U] public void UnaryAddOperator() + { + Assert(q => +q.Query(), +Query, c => c.Bool.Filter.Should().HaveCount(1)); + } - /** Both of these can be combined with ands to a single bool query */ + /** Both of these can be combined with `&&` to form a single bool query */ - [U] public void MustNotOperatorAnd() => Assert(q => !q.Query() && !q.Query(), !Query && !Query, c => c.Bool.MustNot.Should().HaveCount(2)); - [U] public void UnaryAddOperatorAnd() => Assert(q => +q.Query() && +q.Query(), +Query && +Query, c => c.Bool.Filter.Should().HaveCount(2)); + [U] public void MustNotOperatorAnd() + { + Assert(q => !q.Query() && !q.Query(), !Query && !Query, c => c.Bool.MustNot.Should().HaveCount(2)); + } - /** When combining multiple queries some or all possibly marked as must_not or filter NEST still combines to a single bool query + [U] public void UnaryAddOperatorAnd() + { + Assert(q => +q.Query() && +q.Query(), +Query && +Query, c => c.Bool.Filter.Should().HaveCount(2)); + } - *> bool - *> |___must - *> | |___term - *> | |___term - *> | |___term - > | - *> |___must_not - *> |___term + /** === Combining/Merging bool queries + * + * When combining multiple queries some or all possibly marked as `must_not` or `filter`, NEST still combines to a single bool query + * + *.... + *bool + *|___must + *| |___term + *| |___term + *| |___term + *| + *|___must_not + * |___term + *.... */ [U] public void JoinsMustWithMustNot() @@ -118,100 +143,101 @@ [U] public void JoinsMustWithMustNot() } - /** Even more involved `term && term && term && !term && +term && +term` still only results in a single bool query: - - *> bool - *> |___must - *> | |___term - *> | |___term - *> | |___term - *> | - *> |___must_not - *> | |___term - *> | - *> |___filter - *> |___term - *> |___term + /** Even more involved `term && term && term && !term && +term && +term` still only results in a single `bool` query: + *.... + *bool + *|___must + *| |___term + *| |___term + *| |___term + *| + *|___must_not + *| |___term + *| + *|___filter + * |___term + * |___term + *.... */ - - [U] public void JoinsMustWithMustNotAndFilter() => + [U] public void JoinsMustWithMustNotAndFilter() + { Assert( q => q.Query() && q.Query() && q.Query() && !q.Query() && +q.Query() && +q.Query(), Query && Query && Query && !Query && +Query && +Query, - c=> + c => { c.Bool.Must.Should().HaveCount(3); c.Bool.MustNot.Should().HaveCount(1); c.Bool.Filter.Should().HaveCount(2); }); + } - /** You can still mix and match actual bool queries with the bool dsl e.g - - * `bool(must=term, term, term) && !term` - - * it would still merge into a single bool query. */ - - [U] public void MixAndMatch() => + /** You can still mix and match actual bool queries with the bool DSL e.g + * `bool(must=term, term, term) && !term` would still merge into a single `bool` query. + */ + [U] public void MixAndMatch() + { Assert( - q => q.Bool(b=>b.Must(mq=>mq.Query(),mq=>mq.Query(), mq=>mq.Query())) && !q.Query(), + q => q.Bool(b => b.Must(mq => mq.Query(), mq => mq.Query(), mq => mq.Query())) && !q.Query(), new BoolQuery { Must = new QueryContainer[] { Query, Query, Query } } && !Query, - c=> + c => { c.Bool.Must.Should().HaveCount(3); c.Bool.MustNot.Should().HaveCount(1); }); + } - /* NEST will also do the same with `should`'s or OR's when it sees that the boolean queries in play **ONLY** consist of `should clauses`. - * This is because the boolquery does not quite follow the same boolean logic you expect from a programming language. - - * To summarize the latter: - + /* NEST will also do the same with `should`s or `||` when it sees that the boolean queries in play **ONLY** consist of `should` clauses. + * This is because the `bool` query does not quite follow the same boolean logic you expect from a programming language. + * + * To summarize, the latter: + * * `term || term || term` - + * * becomes - - *> bool - *> |___should - *> |___term - *> |___term - *> |___term - - * but - - * `term1 && (term2 || term3 || term4)` will NOT become - - *> bool - *> |___must - *> | |___term1 - *> | - *> |___should - *> |___term2 - *> |___term3 - *> |___term4 - - * This is because when a bool query has **only** should clauses atleast 1 of them has to match. When that bool query also has a must clause the should clauses start acting as a boost factor + *.... + *bool + *|___should + * |___term + * |___term + * |___term + *.... + * but `term1 && (term2 || term3 || term4)` does **NOT** become + *.... + *bool + *|___must + *| |___term1 + *| + *|___should + * |___term2 + * |___term3 + * |___term4 + *.... + * + * This is because when a `bool` query has **only** `should` clauses, at least one of them must match. + * When that `bool` query also has a `must` clause then the `should` clauses start acting as a _boost_ factor * and none of them have to match, drastically altering its meaning. - - * So in the previous you could get back results that ONLY contain `term1` this is clearly not what you want in the strict boolean sense of the input. - - * NEST therefor rewrites this query to - - *> bool - *> |___must - *> |___term1 - *> |___bool - *> |___should - *> |___term2 - *> |___term3 - *> |___term4 - + * + * So in the previous you could get back results that **ONLY** contain `term1`. This is clearly not what you want in the strict boolean sense of the input. + * + * To aid with this, NEST rewrites the previous query to + *.... + *bool + *|___must + * |___term1 + * |___bool + * |___should + * |___term2 + * |___term3 + * |___term4 + *.... */ - - [U] public void JoinsWithShouldClauses() => + [U] public void JoinsWithShouldClauses() + { Assert( q => q.Query() && (q.Query() || q.Query() || q.Query()), Query && (Query || Query || Query), - c=> + c => { c.Bool.Must.Should().HaveCount(2); var lastClause = c.Bool.Must.Last() as IQueryContainer; @@ -219,63 +245,81 @@ [U] public void JoinsWithShouldClauses() => lastClause.Bool.Should().NotBeNull(); lastClause.Bool.Should.Should().HaveCount(3); }); + } - /* Note also that you can parenthesis to force evaluation order */ - - /* Also note that using shoulds as boosting factors can be really powerful so if you need this always remember that you can mix and match an actual bool query with the bool dsl */ - - /* There is another subtle situation where NEST will not blindly merge 2 bool queries with only should clauses. Image the following: - + /** TIP: *add parentheses to force evaluation order* + * + * Also note that using shoulds as boosting factors can be really powerful so if you need this + *always remember that you can mix and match an actual bool query with the bool dsl. + * + * There is another subtle situation where NEST will not blindly merge 2 bool queries with only should clauses. Imagine the following: + * * `bool(should=term1, term2, term3, term4, minimum_should_match=2) || term5 || term6` - - * if NEST identified both sides of the OR operation as only containing should clauses and it would join them together it would give a different meaning to the `minimum_should_match` parameter of the first boolean query. - * Rewriting this to a single bool with 5 should clauses would break because only matching on term5 or term6 should still be a hit. - */ - - [U] public void MixAndMatchMinimumShouldMatch() => + * + * if NEST identified both sides of the OR operation as only containing `should` clauses and it would + * join them together it would give a different meaning to the `minimum_should_match` parameter of the first boolean query. + * Rewriting this to a single bool with 5 `should` clauses would break because only matching on `term5` or `term6` should still be a hit. + **/ + [U] + public void MixAndMatchMinimumShouldMatch() + { Assert( - q => q.Bool(b=>b - .Should(mq=>mq.Query(),mq=>mq.Query(), mq=>mq.Query(), mq=>mq.Query()) - .MinimumShouldMatch(2) - ) - || !q.Query() || q.Query(), + q => q.Bool(b => b + .Should(mq => mq.Query(), mq => mq.Query(), mq => mq.Query(), mq => mq.Query()) + .MinimumShouldMatch(2) + ) + || !q.Query() || q.Query(), new BoolQuery { Should = new QueryContainer[] { Query, Query, Query, Query }, MinimumShouldMatch = 2 } || !Query || Query, - c=> + c => { c.Bool.Should.Should().HaveCount(3); var nestedBool = c.Bool.Should.First() as IQueryContainer; nestedBool.Bool.Should.Should().HaveCount(4); }); + } - /* Nest will also not combine if any metadata is set on the bool e.g boost/name nest will treat these as locked */ - - [U] public void DoNotCombineLockedBools() => + /** === Locked bool queries + * + * NEST will not combine `bool` queries if any of the query metadata is set e.g if metadata such as `boost` or `name` are set, + * NEST will treat these as locked + * + * Here we demonstrate that two locked `bool` queries are not combined + */ + [U] public void DoNotCombineLockedBools() + { Assert( - q => q.Bool(b=>b.Name("leftBool").Should(mq=>mq.Query())) - || q.Bool(b=>b.Name("rightBool").Should(mq=>mq.Query())), + q => q.Bool(b => b.Name("leftBool").Should(mq => mq.Query())) + || q.Bool(b => b.Name("rightBool").Should(mq => mq.Query())), new BoolQuery { Name = "leftBool", Should = new QueryContainer[] { Query } } - || new BoolQuery { Name = "rightBool", Should = new QueryContainer[] { Query } }, - c=>AssertDoesNotJoinOntoLockedBool(c, "leftBool")); + || new BoolQuery { Name = "rightBool", Should = new QueryContainer[] { Query } }, + c => AssertDoesNotJoinOntoLockedBool(c, "leftBool")); + } - [U] public void DoNotCombineRightLockedBool() => + /** neither are two `bool` queries where either right query is locked */ + [U] public void DoNotCombineRightLockedBool() + { Assert( - q => q.Bool(b=>b.Should(mq=>mq.Query())) - || q.Bool(b=>b.Name("rightBool").Should(mq=>mq.Query())), + q => q.Bool(b => b.Should(mq => mq.Query())) + || q.Bool(b => b.Name("rightBool").Should(mq => mq.Query())), new BoolQuery { Should = new QueryContainer[] { Query } } - || new BoolQuery { Name = "rightBool", Should = new QueryContainer[] { Query } }, - c=>AssertDoesNotJoinOntoLockedBool(c, "rightBool")); + || new BoolQuery { Name = "rightBool", Should = new QueryContainer[] { Query } }, + c => AssertDoesNotJoinOntoLockedBool(c, "rightBool")); + } - [U] public void DoNotCombineLeftLockedBool() => + /** or the left query is locked */ + [U] public void DoNotCombineLeftLockedBool() + { Assert( - q => q.Bool(b=>b.Name("leftBool").Should(mq=>mq.Query())) - || q.Bool(b=>b.Should(mq=>mq.Query())), + q => q.Bool(b => b.Name("leftBool").Should(mq => mq.Query())) + || q.Bool(b => b.Should(mq => mq.Query())), new BoolQuery { Name = "leftBool", Should = new QueryContainer[] { Query } } - || new BoolQuery { Should = new QueryContainer[] { Query } }, - c=>AssertDoesNotJoinOntoLockedBool(c, "leftBool")); + || new BoolQuery { Should = new QueryContainer[] { Query } }, + c => AssertDoesNotJoinOntoLockedBool(c, "leftBool")); + } private static void AssertDoesNotJoinOntoLockedBool(IQueryContainer c, string firstName) { diff --git a/src/Tests/QueryDsl/BoolDsl/hadouken-indentation.jpg b/src/Tests/QueryDsl/BoolDsl/hadouken-indentation.jpg new file mode 100644 index 0000000000000000000000000000000000000000..afe03b960d1203610f217e1045a22142c0acc720 GIT binary patch literal 43939 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 literal 0 HcmV?d00001 diff --git a/src/Tests/QueryDsl/Geo/Distance/DistanceUnits.doc.cs b/src/Tests/QueryDsl/Geo/Distance/DistanceUnits.doc.cs deleted file mode 100644 index a4546a9c3a7..00000000000 --- a/src/Tests/QueryDsl/Geo/Distance/DistanceUnits.doc.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Nest; -using Tests.Framework; -using static Tests.Framework.RoundTripper; - -namespace Tests.QueryDsl.Geo.Distance -{ - public class DistanceUnits - { - /** # Distance Units - * Whenever distances need to be specified, e.g. for a geo distance query, the distance unit can be specified - * as a double number representing distance in meters, as a new instance of a `Distance`, or as a string - * of the form number and distance unit e.g. `"2.72km"` - * - * ## Using Distance units in NEST - * NEST uses `Distance` to strongly type distance units and there are several ways to construct one. - * - * ### Constructor - * The most straight forward way to construct a `Distance` is through its constructor - */ - [U] - public void Constructor() - { - var unitComposed = new Nest.Distance(25); - var unitComposedWithUnits = new Nest.Distance(25, DistanceUnit.Meters); - - /** - * When serializing Distance constructed from a string, composition of distance value and unit - */ - Expect("25.0m") - .WhenSerializing(unitComposed) - .WhenSerializing(unitComposedWithUnits); - } - - /** - * ### Implicit conversion - * Alternatively a distance unit `string` can be assigned to a `Distance`, resulting in an implicit conversion to a new `Distance` instance. - * If no `DistanceUnit` is specified, the default distance unit is meters - */ - [U] - public void ImplicitConversion() - { - Nest.Distance distanceString = "25"; - Nest.Distance distanceStringWithUnits = "25m"; - - Expect(new Nest.Distance(25)) - .WhenSerializing(distanceString) - .WhenSerializing(distanceStringWithUnits); - } - - /** - * ### Supported units - * A number of distance units are supported, from millimeters to nautical miles - */ - [U] - public void UsingDifferentUnits() - { - /** - * Miles - */ - Expect("0.62mi").WhenSerializing(new Nest.Distance(0.62, DistanceUnit.Miles)); - - /** - * Yards - */ - Expect("9.0yd").WhenSerializing(new Nest.Distance(9, DistanceUnit.Yards)); - - /** - * Feet - */ - Expect("3.33ft").WhenSerializing(new Nest.Distance(3.33, DistanceUnit.Feet)); - - /** - * Inches - */ - Expect("43.23in").WhenSerializing(new Nest.Distance(43.23, DistanceUnit.Inch)); - - /** - * Kilometers - */ - Expect("0.1km").WhenSerializing(new Nest.Distance(0.1, DistanceUnit.Kilometers)); - - /** - * Meters - */ - Expect("400.0m").WhenSerializing(new Nest.Distance(400, DistanceUnit.Meters)); - - /** - * Centimeters - */ - Expect("123.456cm").WhenSerializing(new Nest.Distance(123.456, DistanceUnit.Centimeters)); - - /** - * Millimeters - */ - Expect("2.0mm").WhenSerializing(new Nest.Distance(2, DistanceUnit.Millimeters)); - - /** - * Nautical Miles - */ - Expect("45.5nmi").WhenSerializing(new Nest.Distance(45.5, DistanceUnit.NauticalMiles)); - } - } -} \ No newline at end of file diff --git a/src/Tests/QueryDsl/Span/Not/SpanNotQueryUsageTests.cs b/src/Tests/QueryDsl/Span/Not/SpanNotQueryUsageTests.cs index 17b41a1fc2e..ddb45def3df 100644 --- a/src/Tests/QueryDsl/Span/Not/SpanNotQueryUsageTests.cs +++ b/src/Tests/QueryDsl/Span/Not/SpanNotQueryUsageTests.cs @@ -36,8 +36,20 @@ public SpanNotUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage Dist = 12, Post = 13, Pre = 14, - Include = new SpanQuery { SpanTerm = new SpanTermQuery { Field = "field1", Value = "hoya" } }, - Exclude = new SpanQuery { SpanTerm = new SpanTermQuery { Field = "field1", Value = "hoya2" } }, + Include = new SpanQuery + { + SpanTerm = new SpanTermQuery + { + Field = "field1", Value = "hoya" + } + }, + Exclude = new SpanQuery + { + SpanTerm = new SpanTermQuery + { + Field = "field1", Value = "hoya2" + } + }, }; protected override QueryContainer QueryFluent(QueryContainerDescriptor q) => q diff --git a/src/Tests/QueryDsl/Specialized/Template/TemplateQueryUsageTests.cs b/src/Tests/QueryDsl/Specialized/Template/TemplateQueryUsageTests.cs index 014dc1a0ced..c6288903a2c 100644 --- a/src/Tests/QueryDsl/Specialized/Template/TemplateQueryUsageTests.cs +++ b/src/Tests/QueryDsl/Specialized/Template/TemplateQueryUsageTests.cs @@ -23,8 +23,6 @@ public TemplateUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usag query_string = "all about search" } } - - }; protected override QueryContainer QueryInitializer => new TemplateQuery diff --git a/src/Tests/QueryDsl/TermLevel/Terms/TermsQueryUsageTests.cs b/src/Tests/QueryDsl/TermLevel/Terms/TermsQueryUsageTests.cs index 1984fba674b..32ed0aad48f 100644 --- a/src/Tests/QueryDsl/TermLevel/Terms/TermsQueryUsageTests.cs +++ b/src/Tests/QueryDsl/TermLevel/Terms/TermsQueryUsageTests.cs @@ -6,6 +6,11 @@ namespace Tests.QueryDsl.TermLevel.Terms { + /** + * Filters documents that have fields that match any of the provided terms (not analyzed). + * + * Be sure to read the Elasticsearch documentation on {ref_current}/query-dsl-terms-query.html[Terms query] for more information. + */ public class TermsQueryUsageTests : QueryDslUsageTestsBase { protected virtual string[] ExpectedTerms => new [] { "term1", "term2" }; @@ -53,6 +58,9 @@ protected override QueryContainer QueryFluent(QueryContainerDescriptor }; } + /**[float] + *== Single term Terms Query + */ public class SingleTermTermsQueryUsageTests : TermsQueryUsageTests { protected override string[] ExpectedTerms => new [] { "term1" }; diff --git a/src/Tests/Search/Request/InnerHitsUsageTests.cs b/src/Tests/Search/Request/InnerHitsUsageTests.cs index b160ee41022..6f65982ad4a 100644 --- a/src/Tests/Search/Request/InnerHitsUsageTests.cs +++ b/src/Tests/Search/Request/InnerHitsUsageTests.cs @@ -146,7 +146,7 @@ public GlobalInnerHitsApiTests(OwnIndexCluster cluster, EndpointUsage usage) : b private static IndexName IndexName { get; } = RandomString(); protected override IndexName Index => GlobalInnerHitsApiTests.IndexName; - protected override object ExpectJson { get; } = new + protected override object ExpectJson => new { inner_hits = new { @@ -238,7 +238,7 @@ public QueryInnerHitsApiTests(OwnIndexCluster cluster, EndpointUsage usage) : ba private static IndexName IndexName { get; } = RandomString(); protected override IndexName Index => QueryInnerHitsApiTests.IndexName; - protected override object ExpectJson { get; } = new + protected override object ExpectJson => new { query = new { diff --git a/src/Tests/Search/Suggesters/SuggestApiTest.cs b/src/Tests/Search/Suggesters/SuggestApiTests.doc.cs similarity index 94% rename from src/Tests/Search/Suggesters/SuggestApiTest.cs rename to src/Tests/Search/Suggesters/SuggestApiTests.doc.cs index ddf6724fbf9..2f04dd38193 100644 --- a/src/Tests/Search/Suggesters/SuggestApiTest.cs +++ b/src/Tests/Search/Suggesters/SuggestApiTests.doc.cs @@ -12,11 +12,14 @@ namespace Tests.Search.Suggesters { + /** == Suggest API + + */ [Collection(IntegrationContext.ReadOnly)] - public class SuggestApiTest + public class SuggestApiTests : ApiIntegrationTestBase, SuggestRequest> { - public SuggestApiTest(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + public SuggestApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } protected override LazyResponses ClientUsage() => Calls( fluent: (c, f) => c.Suggest(f), @@ -31,22 +34,6 @@ protected override LazyResponses ClientUsage() => Calls( protected override string UrlPath => "/_suggest"; protected override bool SupportsDeserialization => false; - protected override void ExpectResponse(ISuggestResponse response) - { - var myCompletionSuggest = response.Suggestions["my-completion-suggest"]; - myCompletionSuggest.Should().NotBeNull(); - var suggest = myCompletionSuggest.First(); - suggest.Text.Should().Be(Project.Instance.Name); - suggest.Length.Should().BeGreaterThan(0); - var option = suggest.Options.First(); - option.Text.Should().NotBeNullOrEmpty(); - option.Score.Should().BeGreaterThan(0); - var payload = option.Payload(); - payload.Should().NotBeNull(); - payload.Name.Should().Be(Project.Instance.Name); - payload.State.Should().NotBeNull(); - } - protected override object ExpectJson => new Dictionary { { "my-completion-suggest", new { @@ -108,6 +95,7 @@ protected override void ExpectResponse(ISuggestResponse response) } }; + /** === Fluent DSL Example */ protected override Func, ISuggestRequest> Fluent => s => s .Term("my-term-suggest", t => t .MaxEdits(1) @@ -158,6 +146,7 @@ protected override void ExpectResponse(ISuggestResponse response) .RealWordErrorLikelihood(0.5) ); + /** === Object Initializer Syntax Example */ protected override SuggestRequest Initializer => new SuggestRequest { @@ -229,5 +218,28 @@ protected override void ExpectResponse(ISuggestResponse response) } }, } }; + + protected override void ExpectResponse(ISuggestResponse response) + { + /** === Handling Responses + * Get the suggestions for a suggester by indexing into + * the `.Suggestions` on the response + */ + var myCompletionSuggest = response.Suggestions["my-completion-suggest"]; + myCompletionSuggest.Should().NotBeNull(); + + var suggest = myCompletionSuggest.First(); + suggest.Text.Should().Be(Project.Instance.Name); + suggest.Length.Should().BeGreaterThan(0); + + var option = suggest.Options.First(); + option.Text.Should().NotBeNullOrEmpty(); + option.Score.Should().BeGreaterThan(0); + + var payload = option.Payload(); + payload.Should().NotBeNull(); + payload.Name.Should().Be(Project.Instance.Name); + payload.State.Should().NotBeNull(); + } } } diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj index df3ca1f274b..08f1f1f7339 100644 --- a/src/Tests/Tests.csproj +++ b/src/Tests/Tests.csproj @@ -31,6 +31,24 @@ prompt 4 + + + + <__paket__xunit_core_props>win81\xunit.core + + + + + <__paket__xunit_core_props>wpa81\xunit.core + + + + + <__paket__xunit_core_props>portable-net45+win8+wp8+wpa81\xunit.core + + + + @@ -1203,23 +1221,6 @@ - - - - <__paket__xunit_core_props>win81\xunit.core - - - - - <__paket__xunit_core_props>wpa81\xunit.core - - - - - <__paket__xunit_core_props>portable-net45+win8+wp8+wpa81\xunit.core - - - @@ -1296,5 +1297,4 @@ - \ No newline at end of file diff --git a/src/Tests/aggregations-usage.asciidoc b/src/Tests/aggregations-usage.asciidoc new file mode 100644 index 00000000000..3a605010ed7 --- /dev/null +++ b/src/Tests/aggregations-usage.asciidoc @@ -0,0 +1,6 @@ +:includes-from-dirs: aggregations/bucket,aggregations/metric,aggregations/pipeline + + + + + diff --git a/src/Tests/aggregations.asciidoc b/src/Tests/aggregations.asciidoc new file mode 100644 index 00000000000..fd5ea121571 --- /dev/null +++ b/src/Tests/aggregations.asciidoc @@ -0,0 +1,14 @@ +:output-dir: aggregations + +[[aggregations]] += Aggregations + +[partintro] +-- +Aggregations are arguably one of the most powerful features of Elasticsearch and NEST +exposes all of the available Aggregation types +-- + +include::{output-dir}/writing-aggregations.asciidoc[] + +include::aggregations-usage.asciidoc[] \ No newline at end of file diff --git a/src/Tests/client-concepts.asciidoc b/src/Tests/client-concepts.asciidoc new file mode 100644 index 00000000000..1d9bbe92f75 --- /dev/null +++ b/src/Tests/client-concepts.asciidoc @@ -0,0 +1,8 @@ +include::low-level.asciidoc[] + +include::high-level.asciidoc[] + + + + + diff --git a/src/Tests/common-options.asciidoc b/src/Tests/common-options.asciidoc new file mode 100644 index 00000000000..0da6edff8b3 --- /dev/null +++ b/src/Tests/common-options.asciidoc @@ -0,0 +1,24 @@ +:output-dir: common-options + +[[common-options]] += Common Options + +[partintro] +-- +NEST has a number of types for working with Elasticsearch conventions for: + +- <> +- <> +- <> + +-- + +include::{output-dir}/time-unit/time-units.asciidoc[] + +include::{output-dir}/distance-unit/distance-units.asciidoc[] + +include::{output-dir}/date-math/date-math-expressions.asciidoc[] + + + + diff --git a/src/Tests/connection-pooling.asciidoc b/src/Tests/connection-pooling.asciidoc new file mode 100644 index 00000000000..eadbe1d9f85 --- /dev/null +++ b/src/Tests/connection-pooling.asciidoc @@ -0,0 +1,61 @@ +:output-dir: client-concepts/connection-pooling +:building-blocks: {output-dir}/building-blocks +:sniffing: {output-dir}/sniffing +:pinging: {output-dir}/pinging +:round-robin: {output-dir}/round-robin +:failover: {output-dir}/failover +:max-retries: {output-dir}/max-retries +:request-overrides: {output-dir}/request-overrides +:exceptions: {output-dir}/exceptions + +include::{building-blocks}/connection-pooling.asciidoc[] + +include::{building-blocks}/request-pipelines.asciidoc[] + +include::{building-blocks}/transports.asciidoc[] + +include::{building-blocks}/keeping-track-of-nodes.asciidoc[] + +include::{building-blocks}/date-time-providers.asciidoc[] + +include::{sniffing}/on-startup.asciidoc[] + +include::{sniffing}/on-connection-failure.asciidoc[] + +include::{sniffing}/on-stale-cluster-state.asciidoc[] + +include::{sniffing}/role-detection.asciidoc[] + +include::{pinging}/first-usage.asciidoc[] + +include::{pinging}/revival.asciidoc[] + +include::{round-robin}/round-robin.asciidoc[] + +include::{round-robin}/skip-dead-nodes.asciidoc[] + +include::{round-robin}/volatile-updates.asciidoc[] + +include::{failover}/falling-over.asciidoc[] + +include::{max-retries}/respects-max-retry.asciidoc[] + +include::{request-overrides}/disable-sniff-ping-per-request.asciidoc[] + +include::{request-overrides}/request-timeouts-overrides.asciidoc[] + +include::{request-overrides}/respects-max-retry-overrides.asciidoc[] + +include::{request-overrides}/respects-allowed-status-code.asciidoc[] + +include::{request-overrides}/respects-force-node.asciidoc[] + +include::{exceptions}/unexpected-exceptions.asciidoc[] + +include::{exceptions}/unrecoverable-exceptions.asciidoc[] + + + + + + diff --git a/src/Tests/high-level.asciidoc b/src/Tests/high-level.asciidoc new file mode 100644 index 00000000000..ce41681823f --- /dev/null +++ b/src/Tests/high-level.asciidoc @@ -0,0 +1,60 @@ +:output-dir: client-concepts/high-level + +[[nest]] += Client Concepts - NEST + +[partintro] +-- +The high level client, `ElasticClient`, provides a strongly typed query DSL that maps one-to-one with the Elasticsearch query DSL. + +It can be installed from the Package Manager Console inside Visual Studio using + +[source, shell] +---- +Install-Package NEST +---- + +Or by searching for https://www.nuget.org/packages/NEST[NEST] in the Package Manager GUI. + +NEST internally uses and still exposes the low level client, `ElasticLowLevelClient`, from <> via +the `.LowLevel` property on `ElasticClient`. + +There are a number of conventions that NEST uses for inference of + +- <> +- <> +- <> and <> +- <> +- <> +- <> + +In addition to features such as + +- <> +- <> + +-- + +include::{output-dir}/inference/index-name-inference.asciidoc[] + +include::{output-dir}/inference/indices-paths.asciidoc[] + +include::{output-dir}/inference/field-inference.asciidoc[] + +include::{output-dir}/inference/property-inference.asciidoc[] + +include::{output-dir}/inference/ids-inference.asciidoc[] + +include::{output-dir}/inference/document-paths.asciidoc[] + +include::{output-dir}/inference/features-inference.asciidoc[] + +include::{output-dir}/mapping/auto-map.asciidoc[] + +include::{output-dir}/covariant-hits/covariant-search-results.asciidoc[] + + + + + + diff --git a/src/Tests/index.asciidoc b/src/Tests/index.asciidoc index 873adf55c98..b90d7bcdd69 100644 --- a/src/Tests/index.asciidoc +++ b/src/Tests/index.asciidoc @@ -1,53 +1,14 @@ -# Introduction +[[elasticsearch-net-reference]] += Elasticsearch.Net and NEST: the .NET clients -You've reached the documentation page for `Elasticsearch.Net` and `NEST`. The two official .NET clients for Elasticsearch. So why two clients I hear you say? +include::intro.asciidoc[] -`Elasticsearch.Net` is a very low level, dependency free, client that has no opinions about how you build and represent your requests and responses. It has abstracted -enough so that **all** the Elasticsearch API endpoints are represented as methods but not too much to get in the way of how you want to build your json/request/response objects. It also comes with builtin, configurable/overridable, cluster failover retry mechanisms. Elasticsearch is elastic so why not your client? +include::client-concepts.asciidoc[] -`NEST` is a high level client that has the advantage of having mapped all the request and response objects, comes with a strongly typed query DSL that maps 1 to 1 with the Elasticsearch query DSL, and takes advantage of specific .NET features such as covariant results. NEST internally uses, and still exposes, the low level `Elasticsearch.Net` client. +include::common-options.asciidoc[] -Please read the getting started guide for both. +include::query-dsl.asciidoc[] - -## Who's using Nest -* [stackoverflow.com](http://www.stackoverflow.com) (and the rest of the stackexchange family). -* [7digital.com](http://www.7digital.com) (run NEST on mono). -* [rijksmuseum.nl](https://www.rijksmuseum.nl/en) (Elasticsearch is the only datastorage hit for each page). -* [Kiln](http://www.fogcreek.com/kiln/) FogCreek's version control & code review tooling. - They are so pleased with Elasticsearch that [they made a video about how pleased they are!](http://blog.fogcreek.com/kiln-powered-by-elasticsearch/) - -## Other resources - -[@joelabrahamsson](http://twitter.com/joelabrahamsson) wrote a great [intro into elasticsearch on .NET](http://joelabrahamsson.com/entry/extending-aspnet-mvc-music-store-with-elasticsearch) -using NEST. - -Also checkout the [searchbox.io guys](https://searchbox.io/) rocking NEST [on AppHarbor](http://blog.appharbor.com/2012/06/19/searchbox-elasticsearch-is-now-an-add-on) -with their [demo project](https://github.com/searchbox-io/.net-sample) - -## Questions, bugs, comments, requests - -All of these are more then welcome on the github issues pages! We try to to at least reply within the same day. - -We also monitor question tagged with ['nest' on stackoverflow](http://stackoverflow.com/questions/tagged/nest) or -['elasticsearch-net' on stackoverflow](http://stackoverflow.com/questions/tagged/elasticsearch-net) - -# License - -This software is licensed under the Apache 2 license, quoted below. - - Copyright (c) 2014 Elasticsearch - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +include::aggregations.asciidoc[] diff --git a/src/Tests/intro.asciidoc b/src/Tests/intro.asciidoc new file mode 100644 index 00000000000..0b41b0b3bd4 --- /dev/null +++ b/src/Tests/intro.asciidoc @@ -0,0 +1,58 @@ +:github: https://github.com/elastic/elasticsearch-net +:stackoverflow: http://stackoverflow.com + +[[introduction]] +== Introduction + +You've reached the documentation page for `Elasticsearch.Net` and `NEST`. The two official .NET clients for Elasticsearch. So why two clients I hear you say? + +`Elasticsearch.Net` is a very low level, dependency free, client that has no opinions about how you build and represent your requests and responses. It has abstracted +enough so that **all** the Elasticsearch API endpoints are represented as methods but not too much to get in the way of how you want to build your json/request/response objects. It also comes with builtin, configurable/overridable, cluster failover retry mechanisms. Elasticsearch is elastic so why not your client? + +`NEST` is a high level client that has the advantage of having mapped all the request and response objects, comes with a strongly typed query DSL that maps 1 to 1 with the Elasticsearch query DSL, and takes advantage of specific .NET features such as covariant results. NEST internally uses, and still exposes, the low level `Elasticsearch.Net` client. + +Please read the getting started guide for both. + +=== Who's using Nest +- {stackoverflow}[stackoverflow.com] (and the rest of the stackexchange family). +- http://www.7digital.com[7digital.com] (run NEST on mono). +- https://www.rijksmuseum.nl/en[rijksmuseum.nl] (Elasticsearch is the only datastorage hit for each page). +- http://www.fogcreek.com/kiln/[Kiln] FogCreek's version control & code review tooling. + They are so pleased with Elasticsearch that http://blog.fogcreek.com/kiln-powered-by-elasticsearch/[they made a video about how pleased they are!] + +=== Other resources + +http://twitter.com/joelabrahamsson[@joelabrahamsson] wrote a great http://joelabrahamsson.com/entry/extending-aspnet-mvc-music-store-with-elasticsearch[intro into elasticsearch on .NET] +using NEST. + +Also checkout the https://searchbox.io/[searchbox.io guys] rocking NEST http://blog.appharbor.com/2012/06/19/searchbox-elasticsearch-is-now-an-add-on[on AppHarbor] +with their https://github.com/searchbox-io/.net-sample[demo project] + +=== Questions, bugs, comments, requests + +All of these are more then welcome on the {github}/issues[github issues pages]! We try to at least reply within the same day. + +We also monitor question tagged with {stackoverflow}/questions/tagged/nest['nest' on stackoverflow] or +{stackoverflow}/questions/tagged/elasticsearch-net['elasticsearch-net' on stackoverflow], as well as https://discuss.elastic.co[discussions on our discourse site] + +=== License + +.... +This software is licensed under the Apache 2 license, quoted below. + + Copyright (c) 2014 Elasticsearch + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +.... + + diff --git a/src/Tests/low-level.asciidoc b/src/Tests/low-level.asciidoc new file mode 100644 index 00000000000..e4a56b277e5 --- /dev/null +++ b/src/Tests/low-level.asciidoc @@ -0,0 +1,28 @@ +:output-dir: client-concepts/low-level + +[[elasticsearch-net]] += Client Concepts - Elasticsearch.Net + +[partintro] +-- +The low level client, `ElasticLowLevelClient`, is a low level, dependency free client that has no +opinions about how you build and represent your requests and responses. + +It can be installed from the Package Manager Console inside Visual Studio using + +[source, shell] +---- +Install-Package Elasticsearch.Net +---- + +Or by searching for https://www.nuget.org/packages/Elasticsearch.Net[Elasticsearch.Net] in the Package Manager GUI. +-- + +include::{output-dir}/connecting.asciidoc[] + +include::{output-dir}/lifetimes.asciidoc[] + +include::{output-dir}/post-data.asciidoc[] + +include::connection-pooling.asciidoc[] + diff --git a/src/Tests/query-dsl-usage.asciidoc b/src/Tests/query-dsl-usage.asciidoc new file mode 100644 index 00000000000..ae871f36c9f --- /dev/null +++ b/src/Tests/query-dsl-usage.asciidoc @@ -0,0 +1,6 @@ +:includes-from-dirs: query-dsl/compound,query-dsl/full-text,query-dsl/geo,query-dsl/joining,query-dsl/nest-specific,query-dsl/span,query-dsl/specialized,query-dsl/term-level + + + + + diff --git a/src/Tests/query-dsl.asciidoc b/src/Tests/query-dsl.asciidoc new file mode 100644 index 00000000000..f79459d44c0 --- /dev/null +++ b/src/Tests/query-dsl.asciidoc @@ -0,0 +1,17 @@ +:output-dir: query-dsl + +[[query-dsl]] += Query DSL + +[partintro] +-- +NEST exposes all of the query DSL endpoints available in Elasticsearch +-- + +include::{output-dir}/bool-dsl/bool-dsl.asciidoc[] + +include::query-dsl-usage.asciidoc[] + + + + diff --git a/src/global.json b/src/global.json index 7a69a1ac22c..7017597592b 100644 --- a/src/global.json +++ b/src/global.json @@ -1,8 +1,10 @@ { - "projects": [ "src" ], - "sdk": { - "version": "1.0.0-rc1-update1", - "runtime": "clr", - "architecture": "x86" - } + "projects": [ + "src" + ], + "sdk": { + "version": "1.0.0-rc1-update1", + "runtime": "clr", + "architecture": "x86" + } } \ No newline at end of file From 395ee7540be30c9560c7b01be07e78ed2065e9ab Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Tue, 29 Mar 2016 14:02:03 +1100 Subject: [PATCH 2/2] Ensure csproj based sln compiles --- paket.dependencies | 4 + paket.lock | 13 ++-- .../Nest.Litterateur/Nest.Litterateur.csproj | 75 ++++++++++++++++++- .../Walkers/CodeWithDocumentationWalker.cs | 12 +-- .../Nest.Litterateur/paket.references | 4 +- src/Profiling/Profiling.csproj | 13 +++- ...gestApiTests.doc.cs => SuggestApiTests.cs} | 2 +- src/Tests/Tests.csproj | 49 ++++++++---- 8 files changed, 136 insertions(+), 36 deletions(-) rename src/Tests/Search/Suggesters/{SuggestApiTests.doc.cs => SuggestApiTests.cs} (99%) diff --git a/paket.dependencies b/paket.dependencies index 750cb99bbb3..9deb90f83dc 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -26,6 +26,10 @@ nuget Rx-Linq nuget Rx-Main nuget Rx-PlatformServices +source https://api.nuget.org/v3/index.json + +nuget AsciiDocNet + group build source https://www.nuget.org/api/v2 diff --git a/paket.lock b/paket.lock index 5133eaa023e..c7c3ed424e0 100644 --- a/paket.lock +++ b/paket.lock @@ -1,6 +1,7 @@ NUGET remote: https://www.nuget.org/api/v2 specs: + AsciiDocNet (1.0.0-alpha2) Bogus (3.0.5-beta-2) Newtonsoft.Json (>= 8.0.2) - framework: >= net40, dnx451, dnxcore50 System.ComponentModel (>= 4.0.1-beta-23516) - framework: dnxcore50 @@ -304,7 +305,7 @@ NUGET xunit (2.1.0) xunit.assert (2.1.0) xunit.core (2.1.0) - xunit.abstractions (2.0.0) - framework: >= net45, dnx451, dnxcore50, monoandroid, monotouch, portable-net45+win80+wp80+wpa81, xamarinios, winv4.5, wpv8.0 + xunit.abstractions (2.0.0) - framework: >= net45, dnx451, dnxcore50, monoandroid, monotouch, portable-net45+win80+wp80+wpa81, xamarinios, winv4.5, wpv8.1, wpv8.0 xunit.assert (2.1.0) System.Collections (>= 4.0.0) - framework: dnxcore50 System.Diagnostics.Debug (>= 4.0.0) - framework: dnxcore50 @@ -328,11 +329,11 @@ NUGET System.Runtime.Extensions (>= 4.0.0) - framework: dnxcore50 System.Threading.Tasks (>= 4.0.0) - framework: dnxcore50 xunit.abstractions (>= 2.0.0) - framework: dnxcore50 - xunit.extensibility.core (2.1.0) - framework: >= net45, dnx451, dnxcore50, monoandroid, monotouch, portable-net45+win80+wp80+wpa81, xamarinios, winv4.5, wpv8.0 - xunit.extensibility.execution (2.1.0) - framework: >= net45, dnx451, dnxcore50, monoandroid, monotouch, portable-net45+win80+wp80+wpa81, xamarinios, winv4.5, wpv8.0 - xunit.extensibility.core (2.1.0) - framework: >= net45, dnx451, dnxcore50, monoandroid, monotouch, portable-net45+win80+wp80+wpa81, xamarinios, winv4.5, wpv8.0 + xunit.extensibility.core (2.1.0) - framework: >= net45, dnx451, dnxcore50, monoandroid, monotouch, portable-net45+win80+wp80+wpa81, xamarinios, winv4.5, wpv8.1, wpv8.0 + xunit.extensibility.execution (2.1.0) - framework: >= net45, dnx451, dnxcore50, monoandroid, monotouch, portable-net45+win80+wp80+wpa81, xamarinios, winv4.5, wpv8.1, wpv8.0 + xunit.extensibility.core (2.1.0) - framework: >= net45, dnx451, dnxcore50, monoandroid, monotouch, portable-net45+win80+wp80+wpa81, xamarinios, winv4.5, wpv8.1, wpv8.0 xunit.abstractions (2.0.0) - xunit.extensibility.execution (2.1.0) - framework: >= net45, dnx451, dnxcore50, monoandroid, monotouch, portable-net45+win80+wp80+wpa81, xamarinios, winv4.5, wpv8.0 + xunit.extensibility.execution (2.1.0) - framework: >= net45, dnx451, dnxcore50, monoandroid, monotouch, portable-net45+win80+wp80+wpa81, xamarinios, winv4.5, wpv8.1, wpv8.0 System.Collections (>= 4.0.0) - framework: dnxcore50 System.Diagnostics.Debug (>= 4.0.0) - framework: dnxcore50 System.Globalization (>= 4.0.0) - framework: dnxcore50 @@ -347,7 +348,7 @@ NUGET System.Threading (>= 4.0.0) - framework: dnxcore50 System.Threading.Tasks (>= 4.0.0) - framework: dnxcore50 xunit.abstractions (>= 2.0.0) - framework: dnxcore50 - xunit.extensibility.core (2.1.0) - framework: >= net45, dnx451, dnxcore50, monoandroid, monotouch, xamarinios, winv4.5, wpv8.0 + xunit.extensibility.core (2.1.0) - framework: >= net45, dnx451, dnxcore50, monoandroid, monotouch, xamarinios, winv4.5, wpv8.1, wpv8.0 GROUP build NUGET diff --git a/src/CodeGeneration/Nest.Litterateur/Nest.Litterateur.csproj b/src/CodeGeneration/Nest.Litterateur/Nest.Litterateur.csproj index 07c7e16e3fb..49b5171627e 100644 --- a/src/CodeGeneration/Nest.Litterateur/Nest.Litterateur.csproj +++ b/src/CodeGeneration/Nest.Litterateur/Nest.Litterateur.csproj @@ -39,18 +39,22 @@ + + + - + + @@ -65,6 +69,17 @@ --> + + + + + ..\..\..\packages\AsciiDocNet\lib\net45\AsciiDocNet.dll + True + True + + + + True @@ -113,6 +128,62 @@ + + + + + ..\..\..\packages\Newtonsoft.Json\lib\net35\Newtonsoft.Json.dll + True + True + + + + + + + ..\..\..\packages\Newtonsoft.Json\lib\net20\Newtonsoft.Json.dll + True + True + + + + + + + ..\..\..\packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll + True + True + + + + + + + ..\..\..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll + True + True + + + + + + + ..\..\..\packages\Newtonsoft.Json\lib\portable-net45+wp80+win8+wpa81+dnxcore50\Newtonsoft.Json.dll + True + True + + + + + + + ..\..\..\packages\Newtonsoft.Json\lib\portable-net40+sl5+wp80+win8+wpa81\Newtonsoft.Json.dll + True + True + + + + @@ -135,4 +206,4 @@ - + \ No newline at end of file diff --git a/src/CodeGeneration/Nest.Litterateur/Walkers/CodeWithDocumentationWalker.cs b/src/CodeGeneration/Nest.Litterateur/Walkers/CodeWithDocumentationWalker.cs index 98ab9270d82..63fe9a39320 100644 --- a/src/CodeGeneration/Nest.Litterateur/Walkers/CodeWithDocumentationWalker.cs +++ b/src/CodeGeneration/Nest.Litterateur/Walkers/CodeWithDocumentationWalker.cs @@ -9,12 +9,6 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using Microsoft.CodeAnalysis.Emit; -using Microsoft.CodeAnalysis.Formatting; -#if !DOTNETCORE -using Microsoft.CodeAnalysis.MSBuild; -#endif -using Microsoft.CodeAnalysis.Text; using Nest.Litterateur.Documentation.Blocks; namespace Nest.Litterateur.Walkers @@ -34,7 +28,7 @@ class CodeWithDocumentationWalker : CSharpSyntaxWalker private readonly int? _lineNumberOverride; /// - /// We want to support inlining /** */ documentations because its super handy + /// We want to support inlining /** */ documentations because its super handy /// to document fluent code, what ensues is total hackery /// /// the depth of the class @@ -61,7 +55,7 @@ public override void Visit(SyntaxNode node) #if !DOTNETCORE if (_propertyOrMethodName == "ExpectJson" || _propertyOrMethodName == "QueryJson") { - // try to get the json for the anonymous type. + // try to get the json for the anonymous type. // Only supports system types and Json.Net LINQ objects e.g. JObject string json; if (_code.TryGetJsonForAnonymousType(out json)) @@ -84,7 +78,7 @@ public override void Visit(SyntaxNode node) base.Visit(node); - var nodeHasLeadingTriva = node.HasLeadingTrivia && + var nodeHasLeadingTriva = node.HasLeadingTrivia && node.GetLeadingTrivia().Any(c => c.Kind() == SyntaxKind.MultiLineDocumentationCommentTrivia); var blocks = codeBlocks.Intertwine(this.TextBlocks, swap: nodeHasLeadingTriva); this.Blocks.Add(new CombinedBlock(blocks, line)); diff --git a/src/CodeGeneration/Nest.Litterateur/paket.references b/src/CodeGeneration/Nest.Litterateur/paket.references index b3b05a0fa7b..366c1bd2d08 100644 --- a/src/CodeGeneration/Nest.Litterateur/paket.references +++ b/src/CodeGeneration/Nest.Litterateur/paket.references @@ -1 +1,3 @@ -Microsoft.CodeAnalysis.CSharp \ No newline at end of file +Microsoft.CodeAnalysis.CSharp +AsciiDocNet +Newtonsoft.Json \ No newline at end of file diff --git a/src/Profiling/Profiling.csproj b/src/Profiling/Profiling.csproj index eeec95cfa03..bd279b1d981 100644 --- a/src/Profiling/Profiling.csproj +++ b/src/Profiling/Profiling.csproj @@ -655,7 +655,7 @@ - + ..\..\packages\xunit.abstractions\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.abstractions.dll @@ -677,7 +677,7 @@ - + ..\..\packages\xunit.extensibility.core\lib\portable-net45+win8+wp8+wpa81\xunit.core.dll @@ -733,6 +733,15 @@ + + + + ..\..\packages\xunit.extensibility.execution\lib\wpa81\xunit.execution.dotnet.dll + True + True + + + diff --git a/src/Tests/Search/Suggesters/SuggestApiTests.doc.cs b/src/Tests/Search/Suggesters/SuggestApiTests.cs similarity index 99% rename from src/Tests/Search/Suggesters/SuggestApiTests.doc.cs rename to src/Tests/Search/Suggesters/SuggestApiTests.cs index 2f04dd38193..8df619047b3 100644 --- a/src/Tests/Search/Suggesters/SuggestApiTests.doc.cs +++ b/src/Tests/Search/Suggesters/SuggestApiTests.cs @@ -221,7 +221,7 @@ protected override LazyResponses ClientUsage() => Calls( protected override void ExpectResponse(ISuggestResponse response) { - /** === Handling Responses + /** === Handling Responses * Get the suggestions for a suggester by indexing into * the `.Suggestions` on the response */ diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj index 08f1f1f7339..86d4fedb50e 100644 --- a/src/Tests/Tests.csproj +++ b/src/Tests/Tests.csproj @@ -152,10 +152,6 @@ - - - - @@ -168,12 +164,6 @@ - - - - - - @@ -447,7 +437,6 @@ - @@ -549,7 +538,6 @@ - @@ -560,7 +548,18 @@ - + + + + + + + + + + + + @@ -570,8 +569,19 @@ + + + + + + + + + + + @@ -1200,7 +1210,7 @@ - + ..\..\packages\xunit.abstractions\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.abstractions.dll @@ -1222,7 +1232,7 @@ - + ..\..\packages\xunit.extensibility.core\lib\portable-net45+win8+wp8+wpa81\xunit.core.dll @@ -1278,6 +1288,15 @@ + + + + ..\..\packages\xunit.extensibility.execution\lib\wpa81\xunit.execution.dotnet.dll + True + True + + +